summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/hid
Initial import
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig910
-rw-r--r--drivers/hid/Makefile110
-rw-r--r--drivers/hid/hid-a4tech.c138
-rw-r--r--drivers/hid/hid-apple.c576
-rw-r--r--drivers/hid/hid-appleir.c355
-rw-r--r--drivers/hid/hid-aureal.c42
-rw-r--r--drivers/hid/hid-axff.c198
-rw-r--r--drivers/hid/hid-belkin.c91
-rw-r--r--drivers/hid/hid-betopff.c160
-rw-r--r--drivers/hid/hid-cherry.c74
-rw-r--r--drivers/hid/hid-chicony.c75
-rw-r--r--drivers/hid/hid-core.c2765
-rw-r--r--drivers/hid/hid-cp2112.c1176
-rw-r--r--drivers/hid/hid-cypress.c149
-rw-r--r--drivers/hid/hid-debug.c1251
-rw-r--r--drivers/hid/hid-dr.c300
-rw-r--r--drivers/hid/hid-elecom.c46
-rw-r--r--drivers/hid/hid-elo.c306
-rw-r--r--drivers/hid/hid-emsff.c154
-rw-r--r--drivers/hid/hid-ezkey.c81
-rw-r--r--drivers/hid/hid-gaff.c179
-rw-r--r--drivers/hid/hid-generic.c41
-rw-r--r--drivers/hid/hid-gt683r.c321
-rw-r--r--drivers/hid/hid-gyration.c93
-rw-r--r--drivers/hid/hid-holtek-kbd.c172
-rw-r--r--drivers/hid/hid-holtek-mouse.c92
-rw-r--r--drivers/hid/hid-holtekff.c225
-rw-r--r--drivers/hid/hid-hyperv.c615
-rw-r--r--drivers/hid/hid-icade.c242
-rw-r--r--drivers/hid/hid-ids.h1047
-rw-r--r--drivers/hid/hid-input.c1567
-rw-r--r--drivers/hid/hid-kensington.c52
-rw-r--r--drivers/hid/hid-keytouch.c55
-rw-r--r--drivers/hid/hid-kye.c612
-rw-r--r--drivers/hid/hid-lcpower.c59
-rw-r--r--drivers/hid/hid-lenovo.c784
-rw-r--r--drivers/hid/hid-lg.c828
-rw-r--r--drivers/hid/hid-lg.h27
-rw-r--r--drivers/hid/hid-lg2ff.c101
-rw-r--r--drivers/hid/hid-lg3ff.c156
-rw-r--r--drivers/hid/hid-lg4ff.c1211
-rw-r--r--drivers/hid/hid-lg4ff.h18
-rw-r--r--drivers/hid/hid-lgff.c161
-rw-r--r--drivers/hid/hid-logitech-dj.c1174
-rw-r--r--drivers/hid/hid-logitech-hidpp.c1302
-rw-r--r--drivers/hid/hid-magicmouse.c583
-rw-r--r--drivers/hid/hid-microsoft.c301
-rw-r--r--drivers/hid/hid-monterey.c68
-rw-r--r--drivers/hid/hid-multitouch.c1407
-rw-r--r--drivers/hid/hid-ntrig.c1032
-rw-r--r--drivers/hid/hid-ortek.c55
-rw-r--r--drivers/hid/hid-penmount.c49
-rw-r--r--drivers/hid/hid-petalynx.c108
-rw-r--r--drivers/hid/hid-picolcd.h309
-rw-r--r--drivers/hid/hid-picolcd_backlight.c120
-rw-r--r--drivers/hid/hid-picolcd_cir.c151
-rw-r--r--drivers/hid/hid-picolcd_core.c682
-rw-r--r--drivers/hid/hid-picolcd_debugfs.c895
-rw-r--r--drivers/hid/hid-picolcd_fb.c617
-rw-r--r--drivers/hid/hid-picolcd_lcd.c105
-rw-r--r--drivers/hid/hid-picolcd_leds.c173
-rw-r--r--drivers/hid/hid-pl.c234
-rw-r--r--drivers/hid/hid-plantronics.c55
-rw-r--r--drivers/hid/hid-primax.c81
-rw-r--r--drivers/hid/hid-prodikeys.c892
-rw-r--r--drivers/hid/hid-rmi.c1282
-rw-r--r--drivers/hid/hid-roccat-arvo.c460
-rw-r--r--drivers/hid/hid-roccat-arvo.h85
-rw-r--r--drivers/hid/hid-roccat-common.c180
-rw-r--r--drivers/hid/hid-roccat-common.h97
-rw-r--r--drivers/hid/hid-roccat-isku.c462
-rw-r--r--drivers/hid/hid-roccat-isku.h100
-rw-r--r--drivers/hid/hid-roccat-kone.c909
-rw-r--r--drivers/hid/hid-roccat-kone.h227
-rw-r--r--drivers/hid/hid-roccat-koneplus.c578
-rw-r--r--drivers/hid/hid-roccat-koneplus.h125
-rw-r--r--drivers/hid/hid-roccat-konepure.c232
-rw-r--r--drivers/hid/hid-roccat-kovaplus.c665
-rw-r--r--drivers/hid/hid-roccat-kovaplus.h133
-rw-r--r--drivers/hid/hid-roccat-lua.c215
-rw-r--r--drivers/hid/hid-roccat-lua.h29
-rw-r--r--drivers/hid/hid-roccat-pyra.c615
-rw-r--r--drivers/hid/hid-roccat-pyra.h152
-rw-r--r--drivers/hid/hid-roccat-ryos.c241
-rw-r--r--drivers/hid/hid-roccat-savu.c229
-rw-r--r--drivers/hid/hid-roccat-savu.h55
-rw-r--r--drivers/hid/hid-roccat.c461
-rw-r--r--drivers/hid/hid-saitek.c203
-rw-r--r--drivers/hid/hid-samsung.c201
-rw-r--r--drivers/hid/hid-sensor-custom.c849
-rw-r--r--drivers/hid/hid-sensor-hub.c800
-rw-r--r--drivers/hid/hid-sjoy.c182
-rw-r--r--drivers/hid/hid-sony.c2184
-rw-r--r--drivers/hid/hid-speedlink.c81
-rw-r--r--drivers/hid/hid-steelseries.c388
-rw-r--r--drivers/hid/hid-sunplus.c68
-rw-r--r--drivers/hid/hid-thingm.c313
-rw-r--r--drivers/hid/hid-tivo.c80
-rw-r--r--drivers/hid/hid-tmff.c264
-rw-r--r--drivers/hid/hid-topseed.c81
-rw-r--r--drivers/hid/hid-twinhan.c136
-rw-r--r--drivers/hid/hid-uclogic.c978
-rw-r--r--drivers/hid/hid-waltop.c784
-rw-r--r--drivers/hid/hid-wiimote-core.c1875
-rw-r--r--drivers/hid/hid-wiimote-debug.c225
-rw-r--r--drivers/hid/hid-wiimote-modules.c2206
-rw-r--r--drivers/hid/hid-wiimote.h376
-rw-r--r--drivers/hid/hid-xinmo.c61
-rw-r--r--drivers/hid/hid-zpff.c147
-rw-r--r--drivers/hid/hid-zydacron.c211
-rw-r--r--drivers/hid/hidraw.c628
-rw-r--r--drivers/hid/huiontablet.c285
-rw-r--r--drivers/hid/i2c-hid/Kconfig18
-rw-r--r--drivers/hid/i2c-hid/Makefile5
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.c1187
-rw-r--r--drivers/hid/uhid.c784
-rw-r--r--drivers/hid/usbhid/Kconfig84
-rw-r--r--drivers/hid/usbhid/Makefile12
-rw-r--r--drivers/hid/usbhid/hid-core.c1661
-rw-r--r--drivers/hid/usbhid/hid-pidff.c1335
-rw-r--r--drivers/hid/usbhid/hid-quirks.c379
-rw-r--r--drivers/hid/usbhid/hiddev.c939
-rw-r--r--drivers/hid/usbhid/usbhid.h102
-rw-r--r--drivers/hid/usbhid/usbkbd.c411
-rw-r--r--drivers/hid/usbhid/usbmouse.c245
-rw-r--r--drivers/hid/wacom.h148
-rw-r--r--drivers/hid/wacom_sys.c1661
-rw-r--r--drivers/hid/wacom_wac.c3288
-rw-r--r--drivers/hid/wacom_wac.h221
129 files changed, 61626 insertions, 0 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
new file mode 100644
index 000000000..e92f840c4
--- /dev/null
+++ b/drivers/hid/Kconfig
@@ -0,0 +1,910 @@
+#
+# 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_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_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 Tactical pad"
+ depends on HID
+ default !EXPERT
+ ---help---
+ Support for Chicony Tactical pad.
+
+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_CP2112
+ tristate "Silicon Labs CP2112 HID USB-to-SMBus Bridge support"
+ depends on USB_HID && I2C && GPIOLIB
+ ---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_ELECOM
+ tristate "ELECOM BM084 bluetooth mouse"
+ depends on HID
+ ---help---
+ Support for the ELECOM BM084 (bluetooth mouse).
+
+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_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_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_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_LENOVO
+ tristate "Lenovo / Thinkpad devices"
+ depends on HID
+ select NEW_LEDS
+ select LEDS_CLASS
+ ---help---
+ Support for Lenovo devices that are not fully compliant with HID standard.
+
+ Say Y if you want support for 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
+ ---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
+ - Logitech Formula Force EX
+ - Logitech WingMan Formula Force GP
+
+ 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 Driving Force
+ - Logitech Driving Force Pro
+ - Logitech Driving Force GT
+ - Logitech G25
+ - Logitech G27
+ - Logitech MOMO/MOMO 2
+ - Logitech Formula Force EX
+
+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_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
+ - 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_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 telephony devices.
+
+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_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
+ - R.A.T.9 Gaming Mouse
+ - R.A.T.7 Gaming Mouse
+ - M.M.O.7 Gaming Mouse
+
+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_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
+ ---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
+ ---help---
+ Support for the ThingM blink(1) USB RGB LED. This driver registers a
+ Linux LED class instance, plus additional sysfs attributes to control
+ RGB colors, fade time and playing. The device is exposed through hidraw
+ to access other functions.
+
+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_WACOM
+ tristate "Wacom Intuos/Graphire tablet support (USB)"
+ depends on HID
+ select POWER_SUPPLY
+ select NEW_LEDS
+ select LEDS_CLASS
+ 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_HUIONTABLET
+ tristate "Huion tablet"
+ depends on USB_HID
+ ---help---
+ Support for Huion tablet.
+
+endmenu
+
+endif # HID
+
+source "drivers/hid/usbhid/Kconfig"
+
+source "drivers/hid/i2c-hid/Kconfig"
+
+endmenu
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
new file mode 100644
index 000000000..0dfb119d3
--- /dev/null
+++ b/drivers/hid/Makefile
@@ -0,0 +1,110 @@
+#
+# Makefile for the HID driver
+#
+hid-y := hid-core.o hid-input.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_ACRUX) += hid-axff.o
+obj-$(CONFIG_HID_APPLE) += hid-apple.o
+obj-$(CONFIG_HID_APPLEIR) += hid-appleir.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_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_ELECOM) += hid-elecom.o
+obj-$(CONFIG_HID_ELO) += hid-elo.o
+obj-$(CONFIG_HID_EZKEY) += hid-ezkey.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_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_MICROSOFT) += hid-microsoft.o
+obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
+obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.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_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_STEELSERIES) += hid-steelseries.o
+obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
+obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
+obj-$(CONFIG_HID_THINGM) += hid-thingm.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_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_HID_HUIONTABLET) += huiontablet.o
+
+obj-$(CONFIG_USB_HID) += usbhid/
+obj-$(CONFIG_USB_MOUSE) += usbhid/
+obj-$(CONFIG_USB_KBD) += usbhid/
+
+obj-$(CONFIG_I2C_HID) += i2c-hid/
diff --git a/drivers/hid/hid-a4tech.c b/drivers/hid/hid-a4tech.c
new file mode 100644
index 000000000..9428ea7cd
--- /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-apple.c b/drivers/hid/hid-apple.c
new file mode 100644
index 000000000..f822fd2a1
--- /dev/null
+++ b/drivers/hid/hid-apple.c
@@ -0,0 +1,576 @@
+/*
+ * 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
+#define APPLE_ISO_KEYBOARD 0x0010
+#define APPLE_MIGHTYMOUSE 0x0020
+#define APPLE_INVERT_HWHEEL 0x0040
+#define APPLE_IGNORE_HIDINPUT 0x0080
+#define APPLE_NUMLOCK_EMULATION 0x0100
+
+#define APPLE_FLAG_FKEY 0x01
+
+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 (asc->quirks & APPLE_ISO_KEYBOARD) {
+ 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)) {
+ /* 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 |
+ APPLE_ISO_KEYBOARD },
+ { 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 |
+ APPLE_ISO_KEYBOARD },
+ { 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 |
+ APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 |
+ APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 |
+ APPLE_ISO_KEYBOARD },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
+ .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+ APPLE_ISO_KEYBOARD },
+ { 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_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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { 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 | APPLE_ISO_KEYBOARD },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_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 |
+ APPLE_ISO_KEYBOARD },
+ { 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 000000000..0e6a42d37
--- /dev/null
+++ b/drivers/hid/hid-appleir.c
@@ -0,0 +1,355 @@
+/*
+ * 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(unsigned long data)
+{
+ struct appleir *appleir = (struct appleir *)data;
+ 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 void 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);
+}
+
+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);
+ setup_timer(&appleir->key_up_timer,
+ key_up_tick, (unsigned long) appleir);
+
+ 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-aureal.c b/drivers/hid/hid-aureal.c
new file mode 100644
index 000000000..340ba9d39
--- /dev/null
+++ b/drivers/hid/hid-aureal.c
@@ -0,0 +1,42 @@
+/*
+ * 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 000000000..a594e478a
--- /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 000000000..cc4cf138b
--- /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 000000000..69cfc8dc6
--- /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 000000000..f745d2c13
--- /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 000000000..b613d5a79
--- /dev/null
+++ b/drivers/hid/hid-chicony.c
@@ -0,0 +1,75 @@
+/*
+ * 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 "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 const struct hid_device_id ch_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, ch_devices);
+
+static struct hid_driver ch_driver = {
+ .name = "chicony",
+ .id_table = ch_devices,
+ .input_mapping = ch_input_mapping,
+};
+module_hid_driver(ch_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
new file mode 100644
index 000000000..722a92579
--- /dev/null
+++ b/drivers/hid/hid-core.c
@@ -0,0 +1,2765 @@
+/*
+ * 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"
+#define DRIVER_LICENSE "GPL"
+
+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 type, unsigned id)
+{
+ 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_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 == HID_COLLECTION_STACK_SIZE) {
+ hid_err(parser->device, "collection stack overflow\n");
+ return -EINVAL;
+ }
+
+ if (parser->device->maxcollection == parser->device->collection_size) {
+ collection = kmalloc(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 usages;
+ unsigned offset;
+ unsigned i;
+
+ report = hid_register_report(parser->device, report_type, parser->global.report_id);
+ 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 = hid_lookup_collection(parser, HID_COLLECTION_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;
+
+ 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;
+
+ 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_err(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 = container_of(dev, struct 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_TYPE_COVER_3 ||
+ hid->product == USB_DEVICE_ID_MS_TYPE_COVER_3_JP) &&
+ 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;
+ }
+
+ 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 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;
+ }
+ vfree(parser);
+ device->status |= HID_STAT_PARSED;
+ return 0;
+ }
+ }
+
+ hid_err(device, "item fetching failed at offset %d\n", (int)(end - start));
+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 | (-1 << 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/devclass_docs/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(const struct hid_device *hid, __u8 *report,
+ unsigned offset, unsigned n)
+{
+ u64 x;
+
+ if (n > 32)
+ hid_warn(hid, "extract() called with n (%d) > 32! (%s)\n",
+ n, current->comm);
+
+ report += offset >> 3; /* adjust byte index */
+ offset &= 7; /* now only need bit offset into one byte */
+ x = get_unaligned_le64(report);
+ x = (x >> offset) & ((1ULL << n) - 1); /* extract bit field */
+ return (u32) x;
+}
+
+/*
+ * "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 endiad bit stream.
+ */
+static void implement(const struct hid_device *hid, __u8 *report,
+ unsigned offset, unsigned n, __u32 value)
+{
+ u64 x;
+ u64 m = (1ULL << n) - 1;
+
+ if (n > 32)
+ hid_warn(hid, "%s() called with n (%d) > 32! (%s)\n",
+ __func__, n, current->comm);
+
+ if (value > m)
+ hid_warn(hid, "%s() called with too large value %d! (%s)\n",
+ __func__, value, current->comm);
+ WARN_ON(value > m);
+ value &= m;
+
+ report += offset >> 3;
+ offset &= 7;
+
+ x = get_unaligned_le64(report);
+ x &= ~(m << offset);
+ x |= ((u64)value) << offset;
+ put_unaligned_le64(x, report);
+}
+
+/*
+ * 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(sizeof(__s32) * count, GFP_ATOMIC);
+ if (!value)
+ return;
+
+ for (n = 0; n < count; n++) {
+
+ value[n] = min < 0 ?
+ snto32(extract(hid, data, offset + n * size, size),
+ size) :
+ extract(hid, data, offset + n * size, size);
+
+ /* Ignore report if ErrorRollOver */
+ if (!(field->flags & HID_MAIN_ITEM_VARIABLE) &&
+ value[n] >= min && value[n] <= max &&
+ 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->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
+ && 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
+ */
+
+ int 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;
+ int 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, int size,
+ int interrupt)
+{
+ struct hid_report_enum *report_enum = hid->report_enum + type;
+ struct hid_report *report;
+ struct hid_driver *hdrv;
+ unsigned int a;
+ int 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, int 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);
+
+static bool hid_match_one_id(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(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 = container_of(kobj, struct device, kobj);
+ struct hid_device *hdev = container_of(dev, struct 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 = container_of(dev, struct 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 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 ? "," : "",
+ hdev->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;
+ 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);
+
+ ret = device_create_bin_file(&hdev->dev, &dev_bin_attr_report_desc);
+ if (ret)
+ hid_warn(hdev,
+ "can't create sysfs report descriptor 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);
+ device_remove_bin_file(&hdev->dev, &dev_bin_attr_report_desc);
+ 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);
+
+/*
+ * 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[] = {
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0x0802) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0xf705) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD) },
+ { 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_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) },
+ { 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_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_FOUNTAIN_TP_ONLY) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) },
+ { 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) },
+ { 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_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) },
+ { 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_GREENASIA, 0x0012) },
+ { 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) },
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_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_2) },
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
+#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) },
+#endif
+ { 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_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651) },
+ { 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_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_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) },
+#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
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) },
+ { 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_TYPE_COVER_3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3_JP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
+ { 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) },
+ { 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_PENMOUNT, USB_DEVICE_ID_PENMOUNT_6000) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
+#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_MMO7) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9) },
+#endif
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) },
+ { 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_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_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_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_VAIO_VGX_MOUSE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
+ { 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, 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) },
+ { 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) },
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
+ { 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_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) },
+ { 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) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) },
+
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, USB_DEVICE_ID_RAZER_BLADE_14) },
+ { }
+};
+
+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 store_new_id(struct device_driver *drv, const char *buf,
+ size_t count)
+{
+ struct hid_driver *hdrv = container_of(drv, struct hid_driver, driver);
+ 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(new_id, S_IWUSR, NULL, store_new_id);
+
+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);
+}
+
+static 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);
+}
+
+static int hid_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct hid_driver *hdrv = container_of(drv, struct hid_driver, driver);
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+
+ return hid_match_device(hdev, hdrv) != NULL;
+}
+
+static int hid_device_probe(struct device *dev)
+{
+ struct hid_driver *hdrv = container_of(dev->driver,
+ struct hid_driver, driver);
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ const struct hid_device_id *id;
+ int ret = 0;
+
+ if (down_interruptible(&hdev->driver_lock))
+ return -EINTR;
+ if (down_interruptible(&hdev->driver_input_lock)) {
+ ret = -EINTR;
+ goto unlock_driver_lock;
+ }
+ hdev->io_started = false;
+
+ if (!hdev->driver) {
+ id = hid_match_device(hdev, hdrv);
+ if (id == NULL) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
+ 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);
+unlock_driver_lock:
+ up(&hdev->driver_lock);
+ return ret;
+}
+
+static int hid_device_remove(struct device *dev)
+{
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ struct hid_driver *hdrv;
+ int ret = 0;
+
+ if (down_interruptible(&hdev->driver_lock))
+ return -EINTR;
+ if (down_interruptible(&hdev->driver_input_lock)) {
+ ret = -EINTR;
+ goto unlock_driver_lock;
+ }
+ 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);
+unlock_driver_lock:
+ up(&hdev->driver_lock);
+ 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);
+ int len;
+
+ len = snprintf(buf, PAGE_SIZE, "hid:b%04Xg%04Xv%08Xp%08X\n",
+ hdev->bus, hdev->group, hdev->vendor, hdev->product);
+
+ return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *hid_dev_attrs[] = {
+ &dev_attr_modalias.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(hid_dev);
+
+static int hid_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct hid_device *hdev = container_of(dev, struct 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;
+}
+
+static struct bus_type hid_bus_type = {
+ .name = "hid",
+ .dev_groups = hid_dev_groups,
+ .match = hid_bus_match,
+ .probe = hid_device_probe,
+ .remove = hid_device_remove,
+ .uevent = hid_uevent,
+};
+
+/* 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_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, 0x0004) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, 0x000a) },
+ { 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_GLAB, USB_DEVICE_ID_4_PHIDGETSERVO_30) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_1_PHIDGETSERVO_30) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_0_4_IF_KIT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_16_16_IF_KIT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_8_8_8_IF_KIT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_8_7_IF_KIT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_8_8_IF_KIT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_GLAB, USB_DEVICE_ID_PHIDGET_MOTORCONTROL) },
+ { 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_SPEAK_410) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_510) },
+ { 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_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_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_PHILIPS, USB_DEVICE_ID_PHILIPS_IEEE802154_DONGLE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_POWERCOM, USB_DEVICE_ID_POWERCOM_UPS) },
+#if defined(CONFIG_MOUSE_SYNAPTICS_USB) || defined(CONFIG_MOUSE_SYNAPTICS_USB_MODULE)
+ { 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_VERNIER, USB_DEVICE_ID_VERNIER_LABPRO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_GOTEMP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_SKIP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_CYCLOPS) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LCSPEC) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_4_PHIDGETSERVO_20) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_1_PHIDGETSERVO_20) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_8_8_4_IF_KIT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) },
+ { }
+};
+
+/**
+ * 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_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;
+ }
+
+ 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);
+
+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;
+
+ /* 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_match_id(hdev, hid_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;
+
+ 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_lock, 1);
+ sema_init(&hdev->driver_input_lock, 1);
+
+ 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);
+
+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)
+ return ret;
+
+ ret = driver_create_file(&hdrv->driver, &driver_attr_new_id);
+ if (ret)
+ driver_unregister(&hdrv->driver);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__hid_register_driver);
+
+void hid_unregister_driver(struct hid_driver *hdrv)
+{
+ driver_remove_file(&hdrv->driver, &driver_attr_new_id);
+ driver_unregister(&hdrv->driver);
+ hid_free_dynids(hdrv);
+}
+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);
+}
+
+module_init(hid_init);
+module_exit(hid_exit);
+
+MODULE_AUTHOR("Andreas Gal");
+MODULE_AUTHOR("Vojtech Pavlik");
+MODULE_AUTHOR("Jiri Kosina");
+MODULE_LICENSE(DRIVER_LICENSE);
+
diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c
new file mode 100644
index 000000000..3318de690
--- /dev/null
+++ b/drivers/hid/hid-cp2112.c
@@ -0,0 +1,1176 @@
+/*
+ * 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:
+ * http://www.silabs.com/Support%20Documents/TechnicalDocs/AN495.pdf
+ */
+
+#include <linux/gpio.h>
+#include <linux/hid.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/nls.h>
+#include <linux/usb/ch9.h>
+#include "hid-ids.h"
+
+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;
+ int xfer_status;
+ atomic_t read_avail;
+ atomic_t xfer_avail;
+ struct gpio_chip gc;
+};
+
+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 = container_of(chip, struct cp2112_device,
+ gc);
+ struct hid_device *hdev = dev->hdev;
+ u8 buf[5];
+ int ret;
+
+ ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
+ sizeof(buf), HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+ if (ret != sizeof(buf)) {
+ hid_err(hdev, "error requesting GPIO config: %d\n", ret);
+ return ret;
+ }
+
+ buf[1] &= ~(1 << offset);
+ buf[2] = gpio_push_pull;
+
+ ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, sizeof(buf),
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret < 0) {
+ hid_err(hdev, "error setting GPIO config: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct cp2112_device *dev = container_of(chip, struct cp2112_device,
+ gc);
+ struct hid_device *hdev = dev->hdev;
+ u8 buf[3];
+ int ret;
+
+ buf[0] = CP2112_GPIO_SET;
+ buf[1] = value ? 0xff : 0;
+ buf[2] = 1 << offset;
+
+ ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf, sizeof(buf),
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret < 0)
+ hid_err(hdev, "error setting GPIO values: %d\n", ret);
+}
+
+static int cp2112_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct cp2112_device *dev = container_of(chip, struct cp2112_device,
+ gc);
+ struct hid_device *hdev = dev->hdev;
+ u8 buf[2];
+ int ret;
+
+ ret = hid_hw_raw_request(hdev, CP2112_GPIO_GET, buf, sizeof(buf),
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret != sizeof(buf)) {
+ hid_err(hdev, "error requesting GPIO values: %d\n", ret);
+ return ret;
+ }
+
+ return (buf[1] >> offset) & 1;
+}
+
+static int cp2112_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct cp2112_device *dev = container_of(chip, struct cp2112_device,
+ gc);
+ struct hid_device *hdev = dev->hdev;
+ u8 buf[5];
+ int ret;
+
+ ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
+ sizeof(buf), HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+ if (ret != sizeof(buf)) {
+ hid_err(hdev, "error requesting GPIO config: %d\n", ret);
+ return ret;
+ }
+
+ buf[1] |= 1 << offset;
+ buf[2] = gpio_push_pull;
+
+ ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, sizeof(buf),
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret < 0) {
+ hid_err(hdev, "error setting GPIO config: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * 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;
+}
+
+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;
+
+ 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_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;
+ unsigned int retries;
+ int ret;
+
+ hid_dbg(hdev, "I2C %d messages\n", num);
+
+ if (num != 1) {
+ hid_err(hdev,
+ "Multi-message I2C transactions not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (msgs->flags & I2C_M_RD)
+ count = cp2112_read_req(buf, msgs->addr, msgs->len);
+ else
+ count = cp2112_i2c_write_req(buf, msgs->addr, msgs->buf,
+ msgs->len);
+
+ 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 (!(msgs->flags & I2C_M_RD))
+ goto finish;
+
+ ret = cp2112_read(dev, msgs->buf, msgs->len);
+ if (ret < 0)
+ goto power_normal;
+ if (ret != msgs->len) {
+ hid_warn(hdev, "short read: %d < %d\n", ret, msgs->len);
+ ret = -EIO;
+ goto power_normal;
+ }
+
+finish:
+ /* return the number of transferred messages */
+ ret = 1;
+
+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];
+ __be16 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, data->byte, 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_be16(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_be16(data->word);
+
+ count = cp2112_write_read_req(buf, addr, read_length, command,
+ (u8 *)&word, 2);
+ break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ size = I2C_SMBUS_BLOCK_DATA;
+ /* fallthrough */
+ 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 = be16_to_cpup((__be16 *)buf);
+ 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 = container_of(kdev, struct hid_device, dev); \
+ 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 = container_of(kdev, struct hid_device, dev); \
+ 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 = container_of(kdev, struct hid_device, dev);
+ 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 = container_of(kdev, struct hid_device, dev);
+ 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 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;
+
+ 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;
+ }
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ 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 hiddev%d", hdev->minor);
+ 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_free_dev;
+ }
+
+ 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.dev = &hdev->dev;
+
+ ret = gpiochip_add(&dev->gc);
+ 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);
+
+ return ret;
+
+err_gpiochip_remove:
+ gpiochip_remove(&dev->gc);
+err_free_i2c:
+ i2c_del_adapter(&dev->adap);
+err_free_dev:
+ kfree(dev);
+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);
+
+ sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group);
+ gpiochip_remove(&dev->gc);
+ i2c_del_adapter(&dev->adap);
+ /* 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);
+ kfree(dev);
+}
+
+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 000000000..c4ef3bc72
--- /dev/null
+++ b/drivers/hid/hid-cypress.c
@@ -0,0 +1,149 @@
+/*
+ * 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;
+
+ for (i = 0; i < *rsize - 4; i++)
+ if (rdesc[i] == 0x29 && rdesc[i + 2] == 0x19) {
+ __u8 tmp;
+
+ rdesc[i] = 0x19;
+ rdesc[i + 2] = 0x29;
+ tmp = rdesc[i + 3];
+ rdesc[i + 3] = rdesc[i + 1];
+ rdesc[i + 1] = tmp;
+ }
+ 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 000000000..2886b645c
--- /dev/null
+++ b/drivers/hid/hid-debug.c
@@ -0,0 +1,1251 @@
+/*
+ * (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.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, 0x20, "Stylus"},
+ {0, 0x21, "Puck"},
+ {0, 0x22, "Finger"},
+ {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(sizeof(char) * 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)
+{
+ int 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; i < strlen(buf); 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(sizeof(char) * 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(sizeof(char) * 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 (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 (copy_to_user(buffer, &list->hid_debug_buf[list->head], len)) {
+ ret = -EFAULT;
+ goto out;
+ }
+ list->head = 0;
+ ret += len;
+ goto copy_rest;
+ }
+
+ }
+out:
+ mutex_unlock(&list->read_mutex);
+ return ret;
+}
+
+static unsigned int 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 POLLIN | POLLRDNORM;
+ if (!list->hdev->debug)
+ return POLLERR | POLLHUP;
+ 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 000000000..ce0644424
--- /dev/null
+++ b/drivers/hid/hid-dr.c
@@ -0,0 +1,300 @@
+/*
+ * 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 (Joystik), ; Joystik (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 (Joystik), */
+ 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;
+}
+
+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,
+};
+module_hid_driver(dr_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c
new file mode 100644
index 000000000..d0bd13b62
--- /dev/null
+++ b/drivers/hid/hid-elecom.c
@@ -0,0 +1,46 @@
+/*
+ * HID driver for Elecom BM084 (bluetooth mouse).
+ * Removes a non-existing horizontal wheel from
+ * the HID descriptor.
+ * (This module is based on "hid-ortek".)
+ *
+ * Copyright (c) 2010 Richard Nauber <Richard.Nauber@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"
+
+static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ if (*rsize >= 48 && rdesc[46] == 0x05 && rdesc[47] == 0x0c) {
+ hid_info(hdev, "Fixing up Elecom BM084 report descriptor\n");
+ rdesc[47] = 0x00;
+ }
+ return rdesc;
+}
+
+static const struct hid_device_id elecom_devices[] = {
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084)},
+ { }
+};
+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 000000000..4e4946287
--- /dev/null
+++ b/drivers/hid/hid-elo.c
@@ -0,0 +1,306 @@
+/*
+ * 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 void elo_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ struct input_dev *input = hidinput->input;
+
+ set_bit(BTN_TOUCH, input->keybit);
+ set_bit(ABS_PRESSURE, input->absbit);
+ input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 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);
+ flush_workqueue(wq);
+ 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 000000000..d82d75bb1
--- /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 000000000..212ac6be2
--- /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 000000000..2d8cead3a
--- /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-generic.c b/drivers/hid/hid-generic.c
new file mode 100644
index 000000000..e288a4a06
--- /dev/null
+++ b/drivers/hid/hid-generic.c
@@ -0,0 +1,41 @@
+/*
+ * 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 const struct hid_device_id hid_table[] = {
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_GENERIC, 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,
+};
+module_hid_driver(hid_generic);
+
+MODULE_AUTHOR("Henrik Rydberg");
+MODULE_DESCRIPTION("HID generic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gt683r.c b/drivers/hid/hid-gt683r.c
new file mode 100644
index 000000000..0d6f135e2
--- /dev/null
+++ b/drivers/hid/hid-gt683r.c
@@ -0,0 +1,321 @@
+/*
+ * 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 = container_of(dev, struct 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 = container_of(dev->parent,
+ struct hid_device, dev);
+ 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 = container_of(dev->parent,
+ struct hid_device, dev);
+ 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 000000000..288d61c97
--- /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 000000000..6e1a4a4fc
--- /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 000000000..78b3a0c76
--- /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 000000000..9325545fc
--- /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
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anssi Hannula <anssi.hannula@iki.fi>");
+MODULE_DESCRIPTION("Force feedback support for Holtek On Line Grip based devices");
+
+/*
+ * 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);
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c
new file mode 100644
index 000000000..6039f071f
--- /dev/null
+++ b/drivers/hid/hid-hyperv.c
@@ -0,0 +1,615 @@
+/*
+ * 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_event(&input_dev->device->device, 0);
+
+ break;
+ default:
+ pr_err("unsupported hid msg type - type %d len %d",
+ 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,
+};
+
+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 000000000..76b5a7570
--- /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 000000000..aa2379ebd
--- /dev/null
+++ b/drivers/hid/hid-ids.h
@@ -0,0 +1,1047 @@
+/*
+ * 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_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_ANTON 0x1130
+#define USB_DEVICE_ID_ANTON_TOUCH_PAD 0x3101
+
+#define USB_VENDOR_ID_APPLE 0x05ac
+#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_WELLSPRING8_ANSI 0x0290
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292
+#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_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_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_WIRELESS2 0x1123
+#define USB_DEVICE_ID_CHICONY_AK1D 0x1125
+
+#define USB_VENDOR_ID_CHUNGHWAT 0x2247
+#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001
+
+#define USB_VENDOR_ID_CIDC 0x1677
+
+#define USB_VENDOR_ID_CMEDIA 0x0d8c
+#define USB_DEVICE_ID_CM109 0x000e
+
+#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_CREATIVELABS 0x041e
+#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_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_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_ELAN_TOUCHSCREEN 0x0089
+#define USB_DEVICE_ID_ELAN_TOUCHSCREEN_009B 0x009b
+#define USB_DEVICE_ID_ELAN_TOUCHSCREEN_0103 0x0103
+#define USB_DEVICE_ID_ELAN_TOUCHSCREEN_010c 0x010c
+#define USB_DEVICE_ID_ELAN_TOUCHSCREEN_016F 0x016f
+
+#define USB_VENDOR_ID_ELECOM 0x056e
+#define USB_DEVICE_ID_ELECOM_BM084 0x0061
+
+#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34
+
+#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_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_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_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_GLAB 0x06c2
+#define USB_DEVICE_ID_4_PHIDGETSERVO_30 0x0038
+#define USB_DEVICE_ID_1_PHIDGETSERVO_30 0x0039
+#define USB_DEVICE_ID_0_0_4_IF_KIT 0x0040
+#define USB_DEVICE_ID_0_16_16_IF_KIT 0x0044
+#define USB_DEVICE_ID_8_8_8_IF_KIT 0x0045
+#define USB_DEVICE_ID_0_8_7_IF_KIT 0x0051
+#define USB_DEVICE_ID_0_8_8_IF_KIT 0x0053
+#define USB_DEVICE_ID_PHIDGET_MOTORCONTROL 0x0058
+
+#define USB_VENDOR_ID_GOODTOUCH 0x1aad
+#define USB_DEVICE_ID_GOODTOUCH_000f 0x000f
+
+#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 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_VENDOR_ID_HUION 0x256c
+#define USB_DEVICE_ID_HUION_TABLET 0x006e
+
+#define USB_VENDOR_ID_IDEACOM 0x1cb6
+#define USB_DEVICE_ID_IDEACOM_IDC6650 0x6650
+#define USB_DEVICE_ID_IDEACOM_IDC6651 0x6651
+
+#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_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_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_2 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_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_VENDOR_ID_LG 0x1fd2
+#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
+
+#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_C077 0xc007
+#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_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_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_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_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_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_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_COMFORT_MOUSE_4500 0x076c
+#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_TYPE_COVER_3 0x07dc
+#define USB_DEVICE_ID_MS_TYPE_COVER_3_JP 0x07dd
+
+#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_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_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_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_3008 0x3008
+
+#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_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 0x0cd7
+#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_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_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_NAVIGATION_CONTROLLER 0x042f
+#define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER 0x0002
+#define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER 0x1000
+
+#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_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_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_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 0x8883
+
+#define USB_VENDOR_ID_TURBOX 0x062a
+#define USB_DEVICE_ID_TURBOX_KEYBOARD 0x0201
+#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_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_VERNIER 0x08f7
+#define USB_DEVICE_ID_VERNIER_LABPRO 0x0001
+#define USB_DEVICE_ID_VERNIER_GOTEMP 0x0002
+#define USB_DEVICE_ID_VERNIER_SKIP 0x0003
+#define USB_DEVICE_ID_VERNIER_CYCLOPS 0x0004
+#define USB_DEVICE_ID_VERNIER_LCSPEC 0x0006
+
+#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_WISEGROUP 0x0925
+#define USB_DEVICE_ID_SMARTJOY_PLUS 0x0005
+#define USB_DEVICE_ID_1_PHIDGETSERVO_20 0x8101
+#define USB_DEVICE_ID_4_PHIDGETSERVO_20 0x8104
+#define USB_DEVICE_ID_8_8_4_IF_KIT 0x8201
+#define USB_DEVICE_ID_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_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_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */
+#define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */
+
+#define USB_VENDOR_ID_HUIONTABLET 0x256C
+#define USB_VENDOR_ID_HUIONTABLET2 0x5543
+#define USB_DEVICE_ID_HUIONTABLET 0x0005
+#define USB_DEVICE_ID_HUIONTABLET2 0x006E
+
+#endif
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
new file mode 100644
index 000000000..008e89bf6
--- /dev/null
+++ b/drivers/hid/hid-input.c
@@ -0,0 +1,1567 @@
+/*
+ * 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_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 */
+
+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 },
+ {}
+};
+
+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_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 ret = 0;
+ __u8 *buf;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 1;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+
+ buf = kmalloc(2 * sizeof(__u8), GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ break;
+ }
+ ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 2,
+ dev->battery_report_type,
+ HID_REQ_GET_REPORT);
+
+ if (ret != 2) {
+ ret = -ENODATA;
+ kfree(buf);
+ break;
+ }
+ ret = 0;
+
+ if (dev->battery_min < dev->battery_max &&
+ buf[1] >= dev->battery_min &&
+ buf[1] <= dev->battery_max)
+ val->intval = (100 * (buf[1] - dev->battery_min)) /
+ (dev->battery_max - dev->battery_min);
+ kfree(buf);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = dev->name;
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ 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 bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field)
+{
+ struct power_supply_desc *psy_desc = NULL;
+ struct power_supply_config psy_cfg = { .drv_data = dev, };
+ unsigned quirks;
+ s32 min, max;
+
+ if (field->usage->hid != HID_DC_BATTERYSTRENGTH)
+ return false; /* no match */
+
+ if (dev->battery != NULL)
+ goto out; /* already initialized? */
+
+ psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL);
+ if (psy_desc == NULL)
+ goto out;
+
+ psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery", dev->uniq);
+ if (psy_desc->name == NULL) {
+ kfree(psy_desc);
+ goto out;
+ }
+
+ 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;
+
+ 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);
+
+ 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;
+
+ dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
+ if (IS_ERR(dev->battery)) {
+ hid_warn(dev, "can't register power supply: %ld\n",
+ PTR_ERR(dev->battery));
+ kfree(psy_desc->name);
+ kfree(psy_desc);
+ dev->battery = NULL;
+ } else {
+ power_supply_powers(dev->battery, &dev->dev);
+ }
+
+out:
+ return true;
+}
+
+static void hidinput_cleanup_battery(struct hid_device *dev)
+{
+ if (!dev->battery)
+ return;
+
+ power_supply_unregister(dev->battery);
+ kfree(dev->battery->desc->name);
+ kfree(dev->battery->desc);
+ dev->battery = NULL;
+}
+#else /* !CONFIG_HID_BATTERY_STRENGTH */
+static bool hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
+ struct hid_field *field)
+{
+ return false;
+}
+
+static void hidinput_cleanup_battery(struct hid_device *dev)
+{
+}
+#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;
+ }
+
+ 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;
+
+ 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 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 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:
+ if (hidinput_setup_battery(device, HID_INPUT_REPORT, field))
+ goto ignore;
+ else
+ goto unknown;
+ break;
+
+ 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 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:
+ 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);
+
+ while (usage->code <= max && test_and_set_bit(usage->code, bit))
+ usage->code = find_next_zero_bit(bit, max + 1, usage->code);
+
+ 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 (!field->hidinput)
+ return;
+
+ input = field->hidinput->input;
+
+ if (!usage->type)
+ return;
+
+ 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.
+ *
+ * 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) &&
+ (value < field->logical_minimum ||
+ value > field->logical_maximum)) {
+ dbg_hid("Ignoring out-of-range value %x\n", value);
+ return;
+ }
+
+ /*
+ * 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))
+ 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 len, ret;
+ __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;
+ 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++) {
+ /* Verify if Battery Strength feature is available */
+ hidinput_setup_battery(hid, HID_FEATURE_REPORT, rep->field[i]);
+
+ if (drv->feature_mapping)
+ drv->feature_mapping(hid, rep->field[i],
+ rep->field[i]->usage + j);
+ }
+ }
+}
+
+static struct hid_input *hidinput_allocate(struct hid_device *hid)
+{
+ struct hid_input *hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL);
+ struct input_dev *input_dev = input_allocate_device();
+ if (!hidinput || !input_dev) {
+ kfree(hidinput);
+ input_free_device(input_dev);
+ hid_err(hid, "Out of memory during hid input probe\n");
+ return NULL;
+ }
+
+ 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 = 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;
+ list_add_tail(&hidinput->list, &hid->inputs);
+
+ return hidinput;
+}
+
+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);
+
+ 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);
+}
+
+/*
+ * 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 *hidinput = NULL;
+ int i, j, k;
+
+ INIT_LIST_HEAD(&hid->inputs);
+ INIT_WORK(&hid->led_work, hidinput_led_worker);
+
+ 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;
+
+ if (!hidinput) {
+ hidinput = hidinput_allocate(hid);
+ if (!hidinput)
+ goto out_unwind;
+ }
+
+ for (i = 0; i < report->maxfield; i++)
+ for (j = 0; j < report->field[i]->maxusage; j++)
+ hidinput_configure_usage(hidinput, report->field[i],
+ report->field[i]->usage + j);
+
+ if ((hid->quirks & HID_QUIRK_NO_EMPTY_INPUT) &&
+ !hidinput_has_been_populated(hidinput))
+ continue;
+
+ if (hid->quirks & HID_QUIRK_MULTI_INPUT) {
+ /* This will leave hidinput NULL, so that it
+ * allocates another one if we have more inputs on
+ * the same interface. Some devices (e.g. Happ's
+ * UGCI) cram a lot of unrelated inputs into the
+ * same interface. */
+ hidinput->report = report;
+ if (drv->input_configured)
+ drv->input_configured(hid, hidinput);
+ if (input_register_device(hidinput->input))
+ goto out_cleanup;
+ hidinput = NULL;
+ }
+ }
+ }
+
+ if (hidinput && (hid->quirks & HID_QUIRK_NO_EMPTY_INPUT) &&
+ !hidinput_has_been_populated(hidinput)) {
+ /* no need to register an input device not populated */
+ hidinput_cleanup_hidinput(hid, hidinput);
+ hidinput = NULL;
+ }
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "No inputs registered, leaving\n");
+ goto out_unwind;
+ }
+
+ if (hidinput) {
+ if (drv->input_configured)
+ drv->input_configured(hid, hidinput);
+ if (input_register_device(hidinput->input))
+ goto out_cleanup;
+ }
+
+ return 0;
+
+out_cleanup:
+ list_del(&hidinput->list);
+ input_free_device(hidinput->input);
+ kfree(hidinput);
+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);
+ input_unregister_device(hidinput->input);
+ 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-kensington.c b/drivers/hid/hid-kensington.c
new file mode 100644
index 000000000..fe9a99dd8
--- /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 000000000..3074671b7
--- /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 000000000..32e6d8d9d
--- /dev/null
+++ b/drivers/hid/hid-kye.c
@@ -0,0 +1,612 @@
+/*
+ * 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"
+
+/*
+ * See EasyPen i405X description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_i405X
+ */
+
+/* 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 */
+};
+
+/*
+ * See MousePen i608X description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=KYE_MousePen_i608X
+ */
+
+/* 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 */
+};
+
+/*
+ * See EasyPen M610X description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_M610X
+ */
+
+/* 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 Comsumer 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:
+ case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2:
+ if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) {
+ rdesc = mousepen_i608x_rdesc_fixed;
+ *rsize = sizeof(mousepen_i608x_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_2:
+ 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_2) },
+ { 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 000000000..6424cfdb7
--- /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-lenovo.c b/drivers/hid/hid-lenovo.c
new file mode 100644
index 000000000..c4c3f0952
--- /dev/null
+++ b/drivers/hid/hid-lenovo.c
@@ -0,0 +1,784 @@
+/*
+ * 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>
+ */
+
+/*
+ * 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 {
+ bool fn_lock;
+ int sensitivity;
+};
+
+#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+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, 0x06);
+ return 1;
+ case 0x0001:
+ hid_map_usage(hi, usage, bit, max, EV_REL, 0x08);
+ return 1;
+ default:
+ 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);
+ 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[] = {0x18, byte2, byte3};
+
+ switch (hdev->product) {
+ case USB_DEVICE_ID_LENOVO_CUSBKBD:
+ ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf),
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ break;
+ case USB_DEVICE_ID_LENOVO_CBTKBD:
+ ret = hid_hw_output_report(hdev, buf, sizeof(buf));
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ 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);
+ ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity);
+ if (ret)
+ hid_err(hdev, "Fn-lock 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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_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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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");
+ return -ENOMEM;
+ }
+
+ // 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");
+ return -ENOMEM;
+ }
+ 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;
+}
+
+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->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 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) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, lenovo_devices);
+
+static struct hid_driver lenovo_driver = {
+ .name = "lenovo",
+ .id_table = lenovo_devices,
+ .input_mapping = lenovo_input_mapping,
+ .probe = lenovo_probe,
+ .remove = lenovo_remove,
+ .raw_event = lenovo_raw_event,
+};
+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 000000000..b86c18e65
--- /dev/null
+++ b/drivers/hid/hid-lg.c
@@ -0,0 +1,828 @@
+/*
+ * 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
+
+/* 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 (Joystik), */
+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 (Joystik), */
+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 (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, 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 (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, 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 (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, 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 */
+};
+
+/*
+ * 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);
+ struct usb_device_descriptor *udesc;
+ __u16 bcdDevice, rev_maj, rev_min;
+
+ 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) {
+
+ /* Several wheels report as this id when operating in emulation mode. */
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ udesc = &(hid_to_usb_dev(hdev)->descriptor);
+ if (!udesc) {
+ hid_err(hdev, "NULL USB device descriptor\n");
+ break;
+ }
+ bcdDevice = le16_to_cpu(udesc->bcdDevice);
+ rev_maj = bcdDevice >> 8;
+ rev_min = bcdDevice & 0xff;
+
+ /* Update the report descriptor for only the Driving Force wheel */
+ if (rev_maj == 1 && rev_min == 2 &&
+ *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_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_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ unsigned int connect_mask = HID_CONNECT_DEFAULT;
+ struct lg_drv_data *drv_data;
+ int ret;
+
+ 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) {
+ unsigned char buf[] = { 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf),
+ 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, 10);
+
+ /* Select random Address */
+ buf[1] = 0xB2;
+ get_random_bytes(&buf[2], 2);
+
+ ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf),
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ }
+ }
+
+ if (drv_data->quirks & LG_FF)
+ lgff_init(hdev);
+ if (drv_data->quirks & LG_FF2)
+ lg2ff_init(hdev);
+ if (drv_data->quirks & LG_FF3)
+ lg3ff_init(hdev);
+ if (drv_data->quirks & LG_FF4)
+ lg4ff_init(hdev);
+
+ 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);
+
+ 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_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_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
+ .driver_data = 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,
+ .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 000000000..10dd8f024
--- /dev/null
+++ b/drivers/hid/hid-lg.h
@@ -0,0 +1,27 @@
+#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 000000000..0e3fb1a7e
--- /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 000000000..8c2da183d
--- /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 000000000..1232210b1
--- /dev/null
+++ b/drivers/hid/hid-lg4ff.c
@@ -0,0 +1,1211 @@
+/*
+ * 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 to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
+
+#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_MAX_IDX 6
+
+#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_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_DFGT_TAG "DFGT"
+#define LG4FF_DFGT_NAME "Driving Force GT"
+
+#define LG4FF_FFEX_REV_MAJ 0x21
+#define LG4FF_FFEX_REV_MIN 0x00
+
+static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
+static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
+
+struct lg4ff_device_entry {
+ __u32 product_id;
+ __u16 range;
+ __u16 min_range;
+ __u16 max_range;
+#ifdef CONFIG_LEDS_CLASS
+ __u8 led_state;
+ struct led_classdev *led[5];
+#endif
+ u32 alternate_modes;
+ const char *real_tag;
+ const char *real_name;
+ u16 real_product_id;
+ struct list_head list;
+ void (*set_range)(struct hid_device *hid, u16 range);
+};
+
+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 u16 mask;
+ const u16 result;
+ const u16 real_product_id;
+};
+
+struct lg4ff_wheel_ident_checklist {
+ const u32 count;
+ const struct lg4ff_wheel_ident_info *models[];
+};
+
+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_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, hid_lg4ff_set_range_dfp},
+ {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
+ {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
+ {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, hid_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},
+};
+
+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}
+};
+
+/* Multimode wheel identificators */
+static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
+ 0xf000,
+ 0x1000,
+ USB_DEVICE_ID_LOGITECH_DFP_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
+ 0xff00,
+ 0x1200,
+ USB_DEVICE_ID_LOGITECH_G25_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
+ 0xfff0,
+ 0x1230,
+ USB_DEVICE_ID_LOGITECH_G27_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
+ 0xff00,
+ 0x1300,
+ USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
+};
+
+/* Multimode wheel identification checklists */
+static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
+ 4,
+ {&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 */
+};
+
+/* 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->product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ switch (usage->code) {
+ case ABS_X:
+ new_value = lg4ff_adjust_dfp_x_axis(value, entry->range);
+ input_event(field->hidinput->input, usage->type, usage->code, new_value);
+ return 1;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static int hid_lg4ff_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);
+ __s32 *value = report->field[0]->value;
+ int x;
+
+#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);
+
+ 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, report, HID_REQ_SET_REPORT);
+ 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, report, HID_REQ_SET_REPORT);
+ break;
+ }
+ return 0;
+}
+
+/* Sends default autocentering command compatible with
+ * all wheels except Formula Force EX */
+static void hid_lg4ff_set_autocenter_default(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;
+ __u32 expand_a, expand_b;
+ struct lg4ff_device_entry *entry;
+ struct lg_drv_data *drv_data;
+
+ 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;
+ }
+
+ /* De-activate Auto-Center */
+ 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, report, HID_REQ_SET_REPORT);
+ 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->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, 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, report, HID_REQ_SET_REPORT);
+}
+
+/* Sends autocentering command compatible with Formula Force EX */
+static void hid_lg4ff_set_autocenter_ffex(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 * 90 / 65535;
+
+ 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, report, HID_REQ_SET_REPORT);
+}
+
+/* Sends command to set range compatible with G25/G27/Driving Force GT */
+static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range)
+{
+ 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;
+
+ dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
+
+ 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, report, HID_REQ_SET_REPORT);
+}
+
+/* Sends commands to set range compatible with Driving Force Pro wheel */
+static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
+{
+ 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 start_left, start_right, full_range;
+ __s32 *value = report->field[0]->value;
+
+ dbg_hid("Driving Force Pro: setting range to %u\n", range);
+
+ /* Prepare "coarse" limit command */
+ 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) {
+ report->field[0]->value[1] = 0x03;
+ full_range = 900;
+ } else {
+ report->field[0]->value[1] = 0x02;
+ full_range = 200;
+ }
+ hid_hw_request(hid, 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, report, HID_REQ_SET_REPORT);
+ 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, report, HID_REQ_SET_REPORT);
+}
+
+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_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 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;
+ u8 i;
+
+ 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, report, HID_REQ_SET_REPORT);
+ }
+ 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->real_name) {
+ hid_err(hid, "NULL pointer to string\n");
+ return 0;
+ }
+
+ for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+ if (entry->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->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->product_id ||
+ (lg4ff_alternate_modes[i].product_id == 0 && entry->product_id == entry->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->alternate_modes & BIT(i)) {
+ if (!strcmp(tag, lbuf)) {
+ if (!mode_product_id)
+ target_product_id = entry->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->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->real_name);
+ return -EINVAL;
+ }
+
+ /* Take care of hardware limitations */
+ if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
+ entry->product_id > target_product_id) {
+ hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->real_name, lg4ff_alternate_modes[i].name);
+ return -EINVAL;
+ }
+
+ s = lg4ff_get_mode_switch_command(entry->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);
+
+/* Read current range and display it in terminal */
+static ssize_t 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->range);
+ return count;
+}
+
+/* Set range to user specified value, call appropriate function
+ * according to the type of the wheel */
+static ssize_t 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->max_range;
+
+ /* Check if the wheel supports range setting
+ * and that the range is within limits for the wheel */
+ if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) {
+ entry->set_range(hid, range);
+ entry->range = range;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_RW(range);
+
+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->real_tag || !entry->real_name) {
+ hid_err(hid, "NULL pointer to string\n");
+ return 0;
+ }
+
+ count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->real_tag, entry->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 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;
+
+ 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, report, HID_REQ_SET_REPORT);
+}
+
+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 = container_of(dev, struct 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 = (struct lg4ff_device_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->led[i])
+ continue;
+ state = (entry->led_state >> i) & 1;
+ if (value == LED_OFF && state) {
+ entry->led_state &= ~(1 << i);
+ lg4ff_set_leds(hid, entry->led_state);
+ } else if (value != LED_OFF && !state) {
+ entry->led_state |= 1 << i;
+ lg4ff_set_leds(hid, entry->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 = container_of(dev, struct 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 = (struct lg4ff_device_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->led[i]) {
+ value = (entry->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)
+{
+ const struct lg4ff_wheel_ident_checklist *checklist;
+ int i, from_idx, to_idx;
+
+ switch (reported_product_id) {
+ case USB_DEVICE_ID_LOGITECH_WHEEL:
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 1;
+ break;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 2; /* End identity check at G25 */
+ break;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 1; /* Start identity check at G27 */
+ to_idx = checklist->count - 3; /* End identity check at G27 */
+ break;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ checklist = &lg4ff_main_checklist;
+ from_idx = 0;
+ to_idx = checklist->count - 4; /* End identity check at DFGT */
+ break;
+ default:
+ return 0;
+ }
+
+ for (i = from_idx; i <= to_idx; i++) {
+ const u16 mask = checklist->models[i]->mask;
+ const u16 result = checklist->models[i]->result;
+ const u16 real_product_id = checklist->models[i]->real_product_id;
+
+ if ((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;
+ const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+ const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
+ 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;
+
+ /* 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;
+
+ /* 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, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to"
+ "LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>\n");
+ return -1;
+ }
+
+ 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);
+ return -1;
+ }
+ }
+
+ /* 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, hid_lg4ff_play);
+
+ if (error)
+ return error;
+
+ /* Get private driver data */
+ drv_data = hid_get_drvdata(hid);
+ if (!drv_data) {
+ hid_err(hid, "Cannot add device, private driver data not allocated\n");
+ return -1;
+ }
+
+ /* Initialize device properties */
+ entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL);
+ if (!entry) {
+ hid_err(hid, "Cannot add device, insufficient memory to allocate device properties.\n");
+ return -ENOMEM;
+ }
+ drv_data->device_props = entry;
+
+ entry->product_id = lg4ff_devices[i].product_id;
+ entry->real_product_id = real_product_id;
+ entry->min_range = lg4ff_devices[i].min_range;
+ entry->max_range = lg4ff_devices[i].max_range;
+ entry->set_range = lg4ff_devices[i].set_range;
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ BUG_ON(mmode_idx == -1);
+ entry->alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes;
+ entry->real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag;
+ entry->real_name = lg4ff_multimode_wheels[mmode_idx].real_name;
+ }
+
+ /* 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 = hid_lg4ff_set_autocenter_ffex;
+ else
+ dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
+
+ dev->ff->set_autocenter(dev, 0);
+ }
+
+ /* Create sysfs interface */
+ error = device_create_file(&hid->dev, &dev_attr_range);
+ if (error)
+ return error;
+ if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+ error = device_create_file(&hid->dev, &dev_attr_real_id);
+ if (error)
+ return error;
+ error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
+ if (error)
+ return error;
+ }
+ dbg_hid("sysfs interface created\n");
+
+ /* Set the maximum range to start with */
+ entry->range = entry->max_range;
+ if (entry->set_range != NULL)
+ entry->set_range(hid, entry->range);
+
+#ifdef CONFIG_LEDS_CLASS
+ /* register led subsystem - G27 only */
+ entry->led_state = 0;
+ for (j = 0; j < 5; j++)
+ entry->led[j] = NULL;
+
+ if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_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;
+ }
+
+ 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->led[j] = led;
+ error = led_classdev_register(&hid->dev, led);
+
+ if (error) {
+ hid_err(hid, "failed to register LED %d. Aborting.\n", j);
+err:
+ /* Deregister LEDs (if any) */
+ for (j = 0; j < 5; j++) {
+ led = entry->led[j];
+ entry->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;
+}
+
+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 */
+
+ device_remove_file(&hid->dev, &dev_attr_range);
+
+ /* Multimode devices will have at least the "MODE_NATIVE" bit set */
+ if (entry->alternate_modes) {
+ device_remove_file(&hid->dev, &dev_attr_real_id);
+ device_remove_file(&hid->dev, &dev_attr_alternate_modes);
+ }
+
+#ifdef CONFIG_LEDS_CLASS
+ {
+ int j;
+ struct led_classdev *led;
+
+ /* Deregister LEDs (if any) */
+ for (j = 0; j < 5; j++) {
+
+ led = entry->led[j];
+ entry->led[j] = NULL;
+ if (!led)
+ continue;
+ led_classdev_unregister(led);
+ kfree(led);
+ }
+ }
+#endif
+
+ /* Deallocate memory */
+ 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 000000000..5b6a5086c
--- /dev/null
+++ b/drivers/hid/hid-lg4ff.h
@@ -0,0 +1,18 @@
+#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_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_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 000000000..e1394af0a
--- /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 000000000..5bc6d80d5
--- /dev/null
+++ b/drivers/hid/hid-logitech-dj.c
@@ -0,0 +1,1174 @@
+/*
+ * 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);
+}
+
+static u8 unifying_name_query[] = {0x10, 0xff, 0x83, 0xb5, 0x40, 0x00, 0x00};
+static u8 unifying_name_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_name_query,
+ sizeof(unifying_name_query)))
+ buf[4] |= 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_name_answer,
+ sizeof(unifying_name_answer)) &&
+ ((data[4] & 0xF0) == 0x40))
+ 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 000000000..5fd530acf
--- /dev/null
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -0,0 +1,1302 @@
+/*
+ * 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/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/kfifo.h>
+#include <linux/input/mt.h>
+#include <asm/unaligned.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.");
+
+#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_QUIRK_CLASS_WTP BIT(0)
+
+/* bits 1..20 are reserved for classes */
+#define HIDPP_QUIRK_DELAYED_INIT BIT(21)
+#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
+
+/*
+ * 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_LONG_LENGTH - 4U];
+};
+
+struct rap {
+ u8 sub_id;
+ u8 reg_address;
+ u8 params[HIDPP_REPORT_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_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;
+};
+
+
+/* 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)
+{
+ int fields_count, ret;
+
+ 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;
+ 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;
+
+ 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->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;
+ 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;
+
+ if ((report_id != REPORT_ID_HIDPP_SHORT) &&
+ (report_id != REPORT_ID_HIDPP_LONG))
+ return -EINVAL;
+
+ if (param_count > sizeof(message->rap.params))
+ 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_PAIRING_INFORMATION 0xB5
+#define DEVICE_NAME 0x40
+
+static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev)
+{
+ struct hidpp_report response;
+ int ret;
+ /* hid-logitech-dj is in charge of setting the right device index */
+ u8 params[1] = { 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;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 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;
+
+ *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;
+
+ if (response.report_id == REPORT_ID_HIDPP_LONG)
+ count = HIDPP_REPORT_LONG_LENGTH - 4;
+ else
+ count = HIDPP_REPORT_SHORT_LENGTH - 4;
+
+ 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;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 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]);
+ }
+}
+
+/* ************************************************************************** */
+/* */
+/* 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 (!connected)
+ return 0;
+
+ 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);
+}
+
+/* -------------------------------------------------------------------------- */
+/* 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);
+
+ 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);
+}
+
+static void 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);
+}
+
+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;
+
+ /*
+ * 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 ((hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) &&
+ (schedule_work(&hidpp->work) == 0))
+ dbg_hid("%s: connect event already queued\n", __func__);
+ return 1;
+ }
+
+ 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_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);
+
+ return 0;
+}
+
+static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying)
+{
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char *name;
+
+ if (use_unifying)
+ /*
+ * the device is connected through an Unifying receiver, and
+ * might not be already connected.
+ * Ask the receiver for its name.
+ */
+ name = hidpp_get_unifying_name(hidpp);
+ else
+ name = hidpp_get_device_name(hidpp);
+
+ if (!name)
+ hid_err(hdev, "unable to retrieve the name of the device");
+ else
+ 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 (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
+ ret = wtp_connect(hdev, connected);
+ if (ret)
+ return;
+ }
+
+ if (!connected || hidpp->delayed_input)
+ return;
+
+ if (!hidpp->protocol_major) {
+ ret = !hidpp_is_connected(hidpp);
+ if (ret) {
+ hid_err(hdev, "Can not get the protocol version.\n");
+ return;
+ }
+ }
+
+ /* the device is already connected, we can ask for its name and
+ * protocol */
+ hid_info(hdev, "HID++ %u.%u device connected.\n",
+ hidpp->protocol_major, hidpp->protocol_minor);
+
+ if (!hidpp->name || hidpp->name == hdev->name) {
+ 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;
+ }
+
+ 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 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 (disable_raw_mode) {
+ hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
+ hidpp->quirks &= ~HIDPP_QUIRK_DELAYED_INIT;
+ }
+
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
+ ret = wtp_allocate(hdev, id);
+ if (ret)
+ goto wtp_allocate_fail;
+ }
+
+ INIT_WORK(&hidpp->work, delayed_work_cb);
+ mutex_init(&hidpp->send_mutex);
+ init_waitqueue_head(&hidpp->wait);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "%s:parse failed\n", __func__);
+ goto hid_parse_fail;
+ }
+
+ /* Allow incoming packets */
+ hid_device_io_start(hdev);
+
+ connected = hidpp_is_connected(hidpp);
+ if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
+ if (!connected) {
+ ret = -ENODEV;
+ hid_err(hdev, "Device not connected");
+ hid_device_io_stop(hdev);
+ goto hid_parse_fail;
+ }
+
+ hid_info(hdev, "HID++ %u.%u device connected.\n",
+ hidpp->protocol_major, hidpp->protocol_minor);
+ }
+
+ hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE);
+ atomic_set(&hidpp->connected, connected);
+
+ if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
+ ret = wtp_get_config(hidpp);
+ if (ret)
+ goto hid_parse_fail;
+ }
+
+ /* Block incoming packets */
+ hid_device_io_stop(hdev);
+
+ if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
+ connect_mask &= ~HID_CONNECT_HIDINPUT;
+
+ 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;
+ }
+
+ if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) {
+ /* Allow incoming packets */
+ hid_device_io_start(hdev);
+
+ hidpp_connect_event(hidpp);
+ }
+
+ return ret;
+
+hid_hw_start_fail:
+hid_parse_fail:
+ cancel_work_sync(&hidpp->work);
+ mutex_destroy(&hidpp->send_mutex);
+wtp_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);
+
+ cancel_work_sync(&hidpp->work);
+ mutex_destroy(&hidpp->send_mutex);
+ hid_hw_stop(hdev);
+}
+
+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 },
+
+ { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+ USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
+ {}
+};
+
+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,
+};
+
+module_hid_driver(hidpp_driver);
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
new file mode 100644
index 000000000..29a74c1ef
--- /dev/null
+++ b/drivers/hid/hid-magicmouse.c
@@ -0,0 +1,583 @@
+/*
+ * 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, 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 void magicmouse_input_configured(struct hid_device *hdev,
+ struct hid_input *hi)
+
+{
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+ int 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;
+ }
+}
+
+
+static int magicmouse_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ __u8 feature[] = { 0xd7, 0x01 };
+ 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);
+ else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+ report = hid_register_report(hdev, HID_INPUT_REPORT,
+ TRACKPAD_REPORT_ID);
+ report = hid_register_report(hdev, HID_INPUT_REPORT,
+ DOUBLE_REPORT_ID);
+ }
+
+ if (!report) {
+ hid_err(hdev, "unable to register touch report\n");
+ ret = -ENOMEM;
+ goto err_stop_hw;
+ }
+ report->size = 6;
+
+ /*
+ * 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, feature[0], feature, sizeof(feature),
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ 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-microsoft.c b/drivers/hid/hid-microsoft.c
new file mode 100644
index 000000000..af935eb19
--- /dev/null
+++ b/drivers/hid/hid-microsoft.c
@@ -0,0 +1,301 @@
+/*
+ * 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 0x01
+#define MS_ERGONOMY 0x02
+#define MS_PRESENTER 0x04
+#define MS_RDESC 0x08
+#define MS_NOGET 0x10
+#define MS_DUPLICATE_USAGES 0x20
+#define MS_RDESC_3K 0x40
+
+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;
+ }
+ /* the same as above (s/usage/physical/) */
+ if ((quirks & MS_RDESC_3K) && *rsize == 106 && rdesc[94] == 0x19 &&
+ rdesc[95] == 0x00 && rdesc[96] == 0x29 &&
+ rdesc[97] == 0xff) {
+ rdesc[94] = 0x35;
+ rdesc[96] = 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_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;
+
+ 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;
+
+ 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 | MS_RDESC_3K },
+ { 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_TYPE_COVER_3),
+ .driver_data = MS_HIDINPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3_JP),
+ .driver_data = MS_HIDINPUT },
+
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT),
+ .driver_data = MS_PRESENTER },
+ { }
+};
+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 000000000..25daf28b2
--- /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 000000000..6a9b05b32
--- /dev/null
+++ b/drivers/hid/hid-multitouch.c
@@ -0,0 +1,1407 @@
+/*
+ * 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 tool hid-test[1].
+ * This tool relies on hid-replay[2] and a database of hid devices[3].
+ * Please run these regression tests before patching this module so that
+ * your patch won't break existing known devices.
+ *
+ * [1] https://github.com/bentiss/hid-test
+ * [2] https://github.com/bentiss/hid-replay
+ * [3] https://github.com/bentiss/hid-devices
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input/mt.h>
+#include <linux/string.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 (1 << 0)
+#define MT_QUIRK_SLOT_IS_CONTACTID (1 << 1)
+#define MT_QUIRK_CYPRESS (1 << 2)
+#define MT_QUIRK_SLOT_IS_CONTACTNUMBER (1 << 3)
+#define MT_QUIRK_ALWAYS_VALID (1 << 4)
+#define MT_QUIRK_VALID_IS_INRANGE (1 << 5)
+#define MT_QUIRK_VALID_IS_CONFIDENCE (1 << 6)
+#define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE (1 << 8)
+#define MT_QUIRK_NO_AREA (1 << 9)
+#define MT_QUIRK_IGNORE_DUPLICATES (1 << 10)
+#define MT_QUIRK_HOVERING (1 << 11)
+#define MT_QUIRK_CONTACT_CNT_ACCURATE (1 << 12)
+#define MT_QUIRK_FORCE_GET_FEATURE (1 << 13)
+
+#define MT_INPUTMODE_TOUCHSCREEN 0x02
+#define MT_INPUTMODE_TOUCHPAD 0x03
+
+#define MT_BUTTONTYPE_CLICKPAD 0
+
+struct mt_slot {
+ __s32 x, y, cx, cy, p, w, h;
+ __s32 contactid; /* the device ContactID assigned to this slot */
+ bool touch_state; /* is the touch valid? */
+ bool inrange_state; /* is the finger in proximity of the sensor? */
+};
+
+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_fields {
+ unsigned usages[HID_MAX_FIELDS];
+ unsigned int length;
+};
+
+struct mt_device {
+ struct mt_slot curdata; /* placeholder of incoming data */
+ struct mt_class mtclass; /* our mt device class */
+ struct mt_fields *fields; /* temporary placeholder for storing the
+ multitouch fields */
+ int cc_index; /* contact count field index in the report */
+ int cc_value_index; /* contact count value index in the field */
+ unsigned last_slot_field; /* the last field of a slot */
+ unsigned mt_report_id; /* the report ID of the multitouch device */
+ __s16 inputmode; /* InputMode HID feature, -1 if non-existent */
+ __s16 inputmode_index; /* InputMode HID feature index in the report */
+ __s16 maxcontact_report_id; /* Maximum Contact Number HID feature,
+ -1 if non-existent */
+ __u8 inputmode_value; /* InputMode HID feature value */
+ __u8 num_received; /* how many contacts we received */
+ __u8 num_expected; /* expected last contact index */
+ __u8 maxcontacts;
+ __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 */
+ __u8 buttons_count; /* number of physical buttons per touchpad */
+ bool is_buttonpad; /* is this device a button pad? */
+ bool serial_maybe; /* need to check for serial protocol */
+ bool curvalid; /* is the current contact valid? */
+ unsigned mt_flags; /* flags to pass to input-mt */
+};
+
+static void mt_post_parse_default_settings(struct mt_device *td);
+static void mt_post_parse(struct mt_device *td);
+
+/* 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
+
+/* 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_VTL 0x0110
+
+#define MT_DEFAULT_MAXCONTACT 10
+#define MT_MAX_MAXCONTACT 250
+
+#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_device *td)
+{
+ if (td->curdata.contactid != 0 || td->num_received == 0)
+ return td->curdata.contactid;
+ else
+ return -1;
+}
+
+static 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 },
+ { .name = MT_CLS_EXPORT_ALL_INPUTS,
+ .quirks = MT_QUIRK_ALWAYS_VALID |
+ MT_QUIRK_CONTACT_CNT_ACCURATE,
+ .export_all_inputs = true },
+
+ /*
+ * vendor specific classes
+ */
+ { .name = MT_CLS_3M,
+ .quirks = MT_QUIRK_VALID_IS_CONFIDENCE |
+ MT_QUIRK_SLOT_IS_CONTACTID,
+ .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_VTL,
+ .quirks = MT_QUIRK_ALWAYS_VALID |
+ MT_QUIRK_CONTACT_CNT_ACCURATE |
+ MT_QUIRK_FORCE_GET_FEATURE,
+ },
+ { }
+};
+
+static ssize_t mt_show_quirks(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = container_of(dev, struct 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 = container_of(dev, struct hid_device, dev);
+ struct mt_device *td = hid_get_drvdata(hdev);
+
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ td->mtclass.quirks = val;
+
+ if (td->cc_index < 0)
+ td->mtclass.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 struct attribute_group mt_attribute_group = {
+ .attrs = sysfs_attrs
+};
+
+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_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;
+ }
+
+ td->inputmode = field->report->id;
+ td->inputmode_index = usage->usage_index;
+
+ break;
+ case HID_DG_CONTACTMAX:
+ td->maxcontact_report_id = field->report->id;
+ 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;
+ }
+
+ if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD)
+ td->is_buttonpad = true;
+
+ 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 void mt_store_field(struct hid_usage *usage, struct mt_device *td,
+ struct hid_input *hi)
+{
+ struct mt_fields *f = td->fields;
+
+ if (f->length >= HID_MAX_FIELDS)
+ return;
+
+ f->usages[f->length++] = usage->hid;
+}
+
+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_device *td = hid_get_drvdata(hdev);
+ struct mt_class *cls = &td->mtclass;
+ int code;
+ struct hid_usage *prev_usage = NULL;
+
+ if (field->application == HID_DG_TOUCHSCREEN)
+ td->mt_flags |= INPUT_MT_DIRECT;
+
+ /*
+ * Model touchscreens providing buttons as touchpads.
+ */
+ if (field->application == HID_DG_TOUCHPAD ||
+ (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+ td->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)
+ td->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)) {
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_TOOL_X);
+ set_abs(hi->input, ABS_MT_TOOL_X, field,
+ cls->sn_move);
+ } else {
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_POSITION_X);
+ set_abs(hi->input, ABS_MT_POSITION_X, field,
+ cls->sn_move);
+ }
+
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_GD_Y:
+ if (prev_usage && (prev_usage->hid == usage->hid)) {
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_TOOL_Y);
+ set_abs(hi->input, ABS_MT_TOOL_Y, field,
+ cls->sn_move);
+ } else {
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_POSITION_Y);
+ set_abs(hi->input, ABS_MT_POSITION_Y, field,
+ cls->sn_move);
+ }
+
+ mt_store_field(usage, td, hi);
+ return 1;
+ }
+ return 0;
+
+ case HID_UP_DIGITIZER:
+ switch (usage->hid) {
+ case HID_DG_INRANGE:
+ if (cls->quirks & MT_QUIRK_HOVERING) {
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_DISTANCE);
+ input_set_abs_params(hi->input,
+ ABS_MT_DISTANCE, 0, 1, 0, 0);
+ }
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_DG_CONFIDENCE:
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_DG_TIPSWITCH:
+ hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
+ input_set_capability(hi->input, EV_KEY, BTN_TOUCH);
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_DG_CONTACTID:
+ mt_store_field(usage, td, hi);
+ td->touches_by_report++;
+ td->mt_report_id = field->report->id;
+ return 1;
+ case HID_DG_WIDTH:
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_TOUCH_MAJOR);
+ if (!(cls->quirks & MT_QUIRK_NO_AREA))
+ set_abs(hi->input, ABS_MT_TOUCH_MAJOR, field,
+ cls->sn_width);
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_DG_HEIGHT:
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_TOUCH_MINOR);
+ if (!(cls->quirks & MT_QUIRK_NO_AREA)) {
+ set_abs(hi->input, ABS_MT_TOUCH_MINOR, field,
+ cls->sn_height);
+ input_set_abs_params(hi->input,
+ ABS_MT_ORIENTATION, 0, 1, 0, 0);
+ }
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_DG_TIPPRESSURE:
+ hid_map_usage(hi, usage, bit, max,
+ EV_ABS, ABS_MT_PRESSURE);
+ set_abs(hi->input, ABS_MT_PRESSURE, field,
+ cls->sn_pressure);
+ mt_store_field(usage, td, hi);
+ return 1;
+ case HID_DG_CONTACTCOUNT:
+ /* Ignore if indexes are out of bounds. */
+ if (field->index >= field->report->maxfield ||
+ usage->usage_index >= field->report_count)
+ return 1;
+ td->cc_index = field->index;
+ td->cc_value_index = usage->usage_index;
+ return 1;
+ case HID_DG_CONTACTMAX:
+ /* we don't set td->last_slot_field as contactcount and
+ * 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);
+ 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_touch_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 || usage->type == EV_ABS)
+ set_bit(usage->type, hi->input->evbit);
+
+ return -1;
+}
+
+static int mt_compute_slot(struct mt_device *td, struct input_dev *input)
+{
+ __s32 quirks = td->mtclass.quirks;
+
+ if (quirks & MT_QUIRK_SLOT_IS_CONTACTID)
+ return td->curdata.contactid;
+
+ if (quirks & MT_QUIRK_CYPRESS)
+ return cypress_compute_slot(td);
+
+ if (quirks & MT_QUIRK_SLOT_IS_CONTACTNUMBER)
+ return td->num_received;
+
+ if (quirks & MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE)
+ return td->curdata.contactid - 1;
+
+ return input_mt_get_slot_by_key(input, td->curdata.contactid);
+}
+
+/*
+ * this function is called when a whole contact has been processed,
+ * so that it can assign it to a slot and store the data there
+ */
+static void mt_complete_slot(struct mt_device *td, struct input_dev *input)
+{
+ if ((td->mtclass.quirks & MT_QUIRK_CONTACT_CNT_ACCURATE) &&
+ td->num_received >= td->num_expected)
+ return;
+
+ if (td->curvalid || (td->mtclass.quirks & MT_QUIRK_ALWAYS_VALID)) {
+ int slotnum = mt_compute_slot(td, input);
+ struct mt_slot *s = &td->curdata;
+ struct input_mt *mt = input->mt;
+
+ if (slotnum < 0 || slotnum >= td->maxcontacts)
+ return;
+
+ if ((td->mtclass.quirks & MT_QUIRK_IGNORE_DUPLICATES) && mt) {
+ struct input_mt_slot *slot = &mt->slots[slotnum];
+ if (input_mt_is_active(slot) &&
+ input_mt_is_used(mt, slot))
+ return;
+ }
+
+ input_mt_slot(input, slotnum);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER,
+ s->touch_state || s->inrange_state);
+ if (s->touch_state || s->inrange_state) {
+ /* this finger is in proximity of the sensor */
+ int wide = (s->w > s->h);
+ /* divided by two to match visual scale of touch */
+ int major = max(s->w, s->h) >> 1;
+ int minor = min(s->w, s->h) >> 1;
+
+ input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x);
+ input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y);
+ input_event(input, EV_ABS, ABS_MT_TOOL_X, s->cx);
+ input_event(input, EV_ABS, ABS_MT_TOOL_Y, s->cy);
+ input_event(input, EV_ABS, ABS_MT_DISTANCE,
+ !s->touch_state);
+ input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide);
+ input_event(input, EV_ABS, ABS_MT_PRESSURE, s->p);
+ input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+ input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+ }
+ }
+
+ td->num_received++;
+}
+
+/*
+ * 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 input_dev *input)
+{
+ input_mt_sync_frame(input);
+ input_sync(input);
+ td->num_received = 0;
+}
+
+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 void mt_process_mt_event(struct hid_device *hid, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct mt_device *td = hid_get_drvdata(hid);
+ __s32 quirks = td->mtclass.quirks;
+ struct input_dev *input = field->hidinput->input;
+
+ if (hid->claimed & HID_CLAIMED_INPUT) {
+ switch (usage->hid) {
+ case HID_DG_INRANGE:
+ if (quirks & MT_QUIRK_VALID_IS_INRANGE)
+ td->curvalid = value;
+ if (quirks & MT_QUIRK_HOVERING)
+ td->curdata.inrange_state = value;
+ break;
+ case HID_DG_TIPSWITCH:
+ if (quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
+ td->curvalid = value;
+ td->curdata.touch_state = value;
+ break;
+ case HID_DG_CONFIDENCE:
+ if (quirks & MT_QUIRK_VALID_IS_CONFIDENCE)
+ td->curvalid = value;
+ break;
+ case HID_DG_CONTACTID:
+ td->curdata.contactid = value;
+ break;
+ case HID_DG_TIPPRESSURE:
+ td->curdata.p = value;
+ break;
+ case HID_GD_X:
+ if (usage->code == ABS_MT_TOOL_X)
+ td->curdata.cx = value;
+ else
+ td->curdata.x = value;
+ break;
+ case HID_GD_Y:
+ if (usage->code == ABS_MT_TOOL_Y)
+ td->curdata.cy = value;
+ else
+ td->curdata.y = value;
+ break;
+ case HID_DG_WIDTH:
+ td->curdata.w = value;
+ break;
+ case HID_DG_HEIGHT:
+ td->curdata.h = value;
+ break;
+ case HID_DG_CONTACTCOUNT:
+ break;
+ case HID_DG_TOUCH:
+ /* do nothing */
+ break;
+
+ default:
+ if (usage->type)
+ input_event(input, usage->type, usage->code,
+ value);
+ return;
+ }
+
+ if (usage->usage_index + 1 == field->report_count) {
+ /* we only take into account the last report. */
+ if (usage->hid == td->last_slot_field)
+ mt_complete_slot(td, field->hidinput->input);
+ }
+
+ }
+}
+
+static void mt_touch_report(struct hid_device *hid, struct hid_report *report)
+{
+ struct mt_device *td = hid_get_drvdata(hid);
+ struct hid_field *field;
+ unsigned count;
+ int r, n;
+
+ /*
+ * Includes multi-packet support where subsequent
+ * packets are sent with zero contactcount.
+ */
+ if (td->cc_index >= 0) {
+ struct hid_field *field = report->field[td->cc_index];
+ int value = field->value[td->cc_value_index];
+ if (value)
+ td->num_expected = value;
+ }
+
+ 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, field, &field->usage[n],
+ field->value[n]);
+ }
+
+ if (td->num_received >= td->num_expected)
+ mt_sync_frame(td, report->field[0]->hidinput->input);
+}
+
+static void mt_touch_input_configured(struct hid_device *hdev,
+ struct hid_input *hi)
+{
+ struct mt_device *td = hid_get_drvdata(hdev);
+ struct mt_class *cls = &td->mtclass;
+ struct input_dev *input = hi->input;
+
+ if (!td->maxcontacts)
+ td->maxcontacts = MT_DEFAULT_MAXCONTACT;
+
+ mt_post_parse(td);
+ if (td->serial_maybe)
+ mt_post_parse_default_settings(td);
+
+ if (cls->is_indirect)
+ td->mt_flags |= INPUT_MT_POINTER;
+
+ if (cls->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
+ td->mt_flags |= INPUT_MT_DROP_UNUSED;
+
+ /* check for clickpads */
+ if ((td->mt_flags & INPUT_MT_POINTER) && (td->buttons_count == 1))
+ td->is_buttonpad = true;
+
+ if (td->is_buttonpad)
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
+ input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
+
+ td->mt_flags = 0;
+}
+
+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);
+
+ /*
+ * 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)
+ return -1;
+
+ /*
+ * some egalax touchscreens have "application == HID_DG_TOUCHSCREEN"
+ * for the stylus.
+ */
+ if (field->physical == HID_DG_STYLUS)
+ return 0;
+
+ if (field->application == HID_DG_TOUCHSCREEN ||
+ field->application == HID_DG_TOUCHPAD)
+ return mt_touch_input_mapping(hdev, hi, field, usage, bit, max);
+
+ /* 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)
+{
+ /*
+ * some egalax touchscreens have "application == HID_DG_TOUCHSCREEN"
+ * for the stylus.
+ */
+ if (field->physical == HID_DG_STYLUS)
+ return 0;
+
+ if (field->application == HID_DG_TOUCHSCREEN ||
+ field->application == HID_DG_TOUCHPAD)
+ return mt_touch_input_mapped(hdev, hi, field, usage, bit, max);
+
+ /* 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);
+
+ if (field->report->id == td->mt_report_id)
+ 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];
+
+ if (!(hid->claimed & HID_CLAIMED_INPUT))
+ return;
+
+ if (report->id == td->mt_report_id)
+ return mt_touch_report(hid, report);
+
+ if (field && field->hidinput && field->hidinput->input)
+ input_sync(field->hidinput->input);
+}
+
+static void mt_set_input_mode(struct hid_device *hdev)
+{
+ struct mt_device *td = hid_get_drvdata(hdev);
+ struct hid_report *r;
+ struct hid_report_enum *re;
+ struct mt_class *cls = &td->mtclass;
+ char *buf;
+ int report_len;
+
+ if (td->inputmode < 0)
+ return;
+
+ re = &(hdev->report_enum[HID_FEATURE_REPORT]);
+ r = re->report_id_hash[td->inputmode];
+ if (r) {
+ if (cls->quirks & MT_QUIRK_FORCE_GET_FEATURE) {
+ report_len = hid_report_len(r);
+ buf = hid_alloc_report_buf(r, GFP_KERNEL);
+ if (!buf) {
+ hid_err(hdev, "failed to allocate buffer for report\n");
+ return;
+ }
+ hid_hw_raw_request(hdev, r->id, buf, report_len,
+ HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+ kfree(buf);
+ }
+ r->field[0]->value[td->inputmode_index] = td->inputmode_value;
+ hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
+ }
+}
+
+static void mt_set_maxcontacts(struct hid_device *hdev)
+{
+ struct mt_device *td = hid_get_drvdata(hdev);
+ struct hid_report *r;
+ struct hid_report_enum *re;
+ int fieldmax, max;
+
+ if (td->maxcontact_report_id < 0)
+ return;
+
+ if (!td->mtclass.maxcontacts)
+ return;
+
+ re = &hdev->report_enum[HID_FEATURE_REPORT];
+ r = re->report_id_hash[td->maxcontact_report_id];
+ if (r) {
+ max = td->mtclass.maxcontacts;
+ fieldmax = r->field[0]->logical_maximum;
+ max = min(fieldmax, max);
+ if (r->field[0]->value[0] != max) {
+ r->field[0]->value[0] = max;
+ hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
+ }
+ }
+}
+
+static void mt_post_parse_default_settings(struct mt_device *td)
+{
+ __s32 quirks = td->mtclass.quirks;
+
+ /* unknown serial device needs special quirks */
+ if (td->touches_by_report == 1) {
+ 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;
+ }
+
+ td->mtclass.quirks = quirks;
+}
+
+static void mt_post_parse(struct mt_device *td)
+{
+ struct mt_fields *f = td->fields;
+ struct mt_class *cls = &td->mtclass;
+
+ if (td->touches_by_report > 0) {
+ int field_count_per_touch = f->length / td->touches_by_report;
+ td->last_slot_field = f->usages[field_count_per_touch - 1];
+ }
+
+ if (td->cc_index < 0)
+ cls->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE;
+}
+
+static void 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;
+ struct hid_field *field = hi->report->field[0];
+
+ if (hi->report->id == td->mt_report_id)
+ mt_touch_input_configured(hdev, hi);
+
+ /*
+ * some egalax touchscreens have "application == HID_DG_TOUCHSCREEN"
+ * for the stylus. Check this first, and then rely on the application
+ * field.
+ */
+ if (hi->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);
+ } else {
+ switch (field->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";
+ /* force BTN_STYLUS to allow tablet matching in udev */
+ __set_bit(BTN_STYLUS, hi->input->keybit);
+ break;
+ case HID_DG_TOUCHSCREEN:
+ /* we do not set suffix = "Touchscreen" */
+ break;
+ case HID_GD_SYSTEM_CONTROL:
+ suffix = "System Control";
+ break;
+ case HID_CP_CONSUMER_CONTROL:
+ suffix = "Consumer Control";
+ 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;
+ }
+ }
+}
+
+static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret, i;
+ struct mt_device *td;
+ 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;
+ }
+ }
+
+ /* 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 reports on the same HID
+ * device.
+ */
+ hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+ hdev->quirks |= HID_QUIRK_NO_EMPTY_INPUT;
+
+ /*
+ * Handle special quirks for Windows 8 certified devices.
+ */
+ if (id->group == HID_GROUP_MULTITOUCH_WIN_8)
+ /*
+ * Some multitouch screens do not like to be polled for input
+ * reports. Fortunately, the Win8 spec says that all touches
+ * should be sent during each report, making the initialization
+ * of input reports unnecessary.
+ */
+ hdev->quirks |= HID_QUIRK_NO_INIT_INPUT_REPORTS;
+
+ 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->mtclass = *mtclass;
+ td->inputmode = -1;
+ td->maxcontact_report_id = -1;
+ td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
+ td->cc_index = -1;
+ td->mt_report_id = -1;
+ hid_set_drvdata(hdev, td);
+
+ td->fields = devm_kzalloc(&hdev->dev, sizeof(struct mt_fields),
+ GFP_KERNEL);
+ if (!td->fields) {
+ dev_err(&hdev->dev, "cannot allocate multitouch fields data\n");
+ return -ENOMEM;
+ }
+
+ if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
+ td->serial_maybe = true;
+
+ ret = hid_parse(hdev);
+ if (ret != 0)
+ return ret;
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group);
+
+ mt_set_maxcontacts(hdev);
+ mt_set_input_mode(hdev);
+
+ /* release .fields memory as it is not used anymore */
+ devm_kfree(&hdev->dev, td->fields);
+ td->fields = NULL;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mt_reset_resume(struct hid_device *hdev)
+{
+ mt_set_maxcontacts(hdev);
+ mt_set_input_mode(hdev);
+ 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)
+{
+ 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) },
+
+ /* Anton devices */
+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS,
+ MT_USB_DEVICE(USB_VENDOR_ID_ANTON,
+ USB_DEVICE_ID_ANTON_TOUCH_PAD) },
+
+ /* 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) },
+
+ /* 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) },
+
+ /* 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) },
+
+ /* 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) },
+
+ /* Novatek Panel */
+ { .driver_data = MT_CLS_NSMU,
+ MT_USB_DEVICE(USB_VENDOR_ID_NOVATEK,
+ USB_DEVICE_ID_NOVATEK_PCT) },
+
+ /* 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) },
+
+ /* 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) },
+
+ /* 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-ntrig.c b/drivers/hid/hid-ntrig.c
new file mode 100644
index 000000000..600f20755
--- /dev/null
+++ b/drivers/hid/hid-ntrig.c
@@ -0,0 +1,1032 @@
+/*
+ * 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 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 = 1;
+ nd->first_contact_touch = 0;
+ 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 = 0;
+ 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 = 0;
+ } 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 = 0;
+
+
+ /*
+ * 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 void ntrig_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
+
+{
+ struct input_dev *input = hidinput->input;
+
+ if (hidinput->report->maxfield < 1)
+ return;
+
+ 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;
+ }
+}
+
+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 = 0;
+ 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);
+
+ 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 000000000..6620f15fe
--- /dev/null
+++ b/drivers/hid/hid-ortek.c
@@ -0,0 +1,55 @@
+/*
+ * 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
+ * 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 minimum in report descriptor (Ortek)\n");
+ rdesc[55] = 0x92;
+ } else if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
+ hid_info(hdev, "Fixing up logical minimum 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_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 000000000..c11dce85c
--- /dev/null
+++ b/drivers/hid/hid-penmount.c
@@ -0,0 +1,49 @@
+/*
+ * 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) {
+ hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
+ 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 000000000..6aca4f255
--- /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 000000000..e56d847b2
--- /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 000000000..a32c5f86b
--- /dev/null
+++ b/drivers/hid/hid-picolcd_backlight.c
@@ -0,0 +1,120 @@
+/***************************************************************************
+ * 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;
+ if (bdev)
+ 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 000000000..045f8ebf1
--- /dev/null
+++ b/drivers/hid/hid-picolcd_cir.c
@@ -0,0 +1,151 @@
+/***************************************************************************
+ * 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();
+ if (!rdev)
+ return -ENOMEM;
+
+ rdev->priv = data;
+ rdev->driver_type = RC_DRIVER_IR_RAW;
+ rdev->allowed_protocols = RC_BIT_ALL;
+ rdev->open = picolcd_cir_open;
+ rdev->close = picolcd_cir_close;
+ rdev->input_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;
+ if (rdev)
+ 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 000000000..c1b29a9eb
--- /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 000000000..3c13af684
--- /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_WRTIE_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 000000000..7f965e231
--- /dev/null
+++ b/drivers/hid/hid-picolcd_fb.c
@@ -0,0 +1,617 @@
+/***************************************************************************
+ * 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(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 000000000..89821c2da
--- /dev/null
+++ b/drivers/hid/hid-picolcd_lcd.c
@@ -0,0 +1,105 @@
+/***************************************************************************
+ * 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;
+ if (ldev)
+ 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 000000000..e994f9c29
--- /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 = container_of(dev, struct 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 = container_of(dev, struct 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 000000000..2dcd7d98d
--- /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 000000000..2180e0789
--- /dev/null
+++ b/drivers/hid/hid-plantronics.c
@@ -0,0 +1,55 @@
+/*
+ * Plantronics USB HID Driver
+ *
+ * Copyright (c) 2014 JD Cole <jd.cole@plantronics.com>
+ * Copyright (c) 2014 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>
+
+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)
+{
+ if (field->application == HID_CP_CONSUMERCONTROL
+ && (usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+ hid_dbg(hdev, "usage: %08x (appl: %08x) - defaulted\n",
+ usage->hid, field->application);
+ return 0;
+ }
+
+ hid_dbg(hdev, "usage: %08x (appl: %08x) - ignored\n",
+ usage->hid, field->application);
+
+ return -1;
+}
+
+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,
+};
+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 000000000..3a1c3c4c5
--- /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 000000000..91fab9750
--- /dev/null
+++ b/drivers/hid/hid-prodikeys.c
@@ -0,0 +1,892 @@
+/*
+ * 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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(unsigned long data)
+{
+ struct pcmidi_sustain *pms = (struct pcmidi_sustain *)data;
+
+ 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;
+ setup_timer(&pms->timer, pcmidi_sustained_note_release,
+ (unsigned long)pms);
+ }
+}
+
+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++) {
+ key = pm->last_key[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 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-rmi.c b/drivers/hid/hid-rmi.c
new file mode 100644
index 000000000..368ffdf2c
--- /dev/null
+++ b/drivers/hid/hid-rmi.c
@@ -0,0 +1,1282 @@
+/*
+ * 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/module.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/sched.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 BIT(0)
+#define RMI_READ_DATA_PENDING BIT(1)
+#define RMI_STARTED BIT(2)
+
+/* device flags */
+#define RMI_DEVICE BIT(0)
+#define RMI_DEVICE_HAS_PHYS_BUTTONS BIT(1)
+
+enum rmi_mode_type {
+ RMI_MODE_OFF = 0,
+ RMI_MODE_ATTN_REPORTS = 1,
+ RMI_MODE_NO_PACKED_ATTN_REPORTS = 2,
+};
+
+struct rmi_function {
+ unsigned page; /* page of the function */
+ u16 query_base_addr; /* base address for queries */
+ u16 command_base_addr; /* base address for commands */
+ u16 control_base_addr; /* base address for controls */
+ u16 data_base_addr; /* base address for datas */
+ unsigned int interrupt_base; /* cross-function interrupt number
+ * (uniq in the device)*/
+ unsigned int interrupt_count; /* number of interrupts */
+ unsigned int report_size; /* size of a report */
+ unsigned long irq_mask; /* mask of the interrupts
+ * (to be applied against ATTN IRQ) */
+};
+
+/**
+ * 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
+ *
+ * @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...)
+ *
+ * @f11: placeholder of internal RMI function F11 description
+ * @f30: placeholder of internal RMI function F30 description
+ *
+ * @max_fingers: maximum finger count reported by the device
+ * @max_x: maximum x value reported by the device
+ * @max_y: maximum y value reported by the device
+ *
+ * @gpio_led_count: count of GPIOs + LEDs reported by F30
+ * @button_count: actual physical buttons count
+ * @button_mask: button mask used to decode GPIO ATTN reports
+ * @button_state_mask: pull state of the buttons
+ *
+ * @input: pointer to the kernel input device
+ *
+ * @reset_work: worker which will be called in case of a mouse report
+ * @hdev: pointer to the struct hid_device
+ */
+struct rmi_data {
+ struct mutex page_mutex;
+ int page;
+
+ wait_queue_head_t wait;
+
+ u8 *writeReport;
+ u8 *readReport;
+
+ int input_report_size;
+ int output_report_size;
+
+ unsigned long flags;
+
+ struct rmi_function f01;
+ struct rmi_function f11;
+ struct rmi_function f30;
+
+ unsigned int max_fingers;
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int x_size_mm;
+ unsigned int y_size_mm;
+
+ unsigned int gpio_led_count;
+ unsigned int button_count;
+ unsigned long button_mask;
+ unsigned long button_state_mask;
+
+ struct input_dev *input;
+
+ struct work_struct reset_work;
+ struct hid_device *hdev;
+
+ unsigned long device_flags;
+ unsigned long firmware_id;
+};
+
+#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;
+ u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
+
+ ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, txbuf,
+ sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ 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_read_block(struct hid_device *hdev, u16 addr, void *buf,
+ const int len)
+{
+ struct rmi_data *data = hid_get_drvdata(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 inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
+{
+ return rmi_read_block(hdev, addr, buf, 1);
+}
+
+static int rmi_write_block(struct hid_device *hdev, u16 addr, void *buf,
+ const int len)
+{
+ struct rmi_data *data = hid_get_drvdata(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 inline int rmi_write(struct hid_device *hdev, u16 addr, void *buf)
+{
+ return rmi_write_block(hdev, addr, buf, 1);
+}
+
+static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
+ u8 finger_state, u8 *touch_data)
+{
+ int x, y, wx, wy;
+ int wide, major, minor;
+ int z;
+
+ input_mt_slot(hdata->input, slot);
+ input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER,
+ finger_state == 0x01);
+ if (finger_state == 0x01) {
+ x = (touch_data[0] << 4) | (touch_data[2] & 0x0F);
+ y = (touch_data[1] << 4) | (touch_data[2] >> 4);
+ wx = touch_data[3] & 0x0F;
+ wy = touch_data[3] >> 4;
+ wide = (wx > wy);
+ major = max(wx, wy);
+ minor = min(wx, wy);
+ z = touch_data[4];
+
+ /* y is inverted */
+ y = hdata->max_y - y;
+
+ input_event(hdata->input, EV_ABS, ABS_MT_POSITION_X, x);
+ input_event(hdata->input, EV_ABS, ABS_MT_POSITION_Y, y);
+ input_event(hdata->input, EV_ABS, ABS_MT_ORIENTATION, wide);
+ input_event(hdata->input, EV_ABS, ABS_MT_PRESSURE, z);
+ input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+ input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+ }
+}
+
+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_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+static inline int rmi_schedule_reset(struct hid_device *hdev)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ return schedule_work(&hdata->reset_work);
+}
+
+static int rmi_f11_input_event(struct hid_device *hdev, u8 irq, u8 *data,
+ int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ int offset;
+ int i;
+
+ if (!(irq & hdata->f11.irq_mask) || size <= 0)
+ return 0;
+
+ offset = (hdata->max_fingers >> 2) + 1;
+ for (i = 0; i < hdata->max_fingers; i++) {
+ int fs_byte_position = i >> 2;
+ int fs_bit_position = (i & 0x3) << 1;
+ int finger_state = (data[fs_byte_position] >> fs_bit_position) &
+ 0x03;
+ int position = offset + 5 * i;
+
+ if (position + 5 > size) {
+ /* partial report, go on with what we received */
+ printk_once(KERN_WARNING
+ "%s %s: Detected incomplete finger report. Finger reports may occasionally get dropped on this platform.\n",
+ dev_driver_string(&hdev->dev),
+ dev_name(&hdev->dev));
+ hid_dbg(hdev, "Incomplete finger report\n");
+ break;
+ }
+
+ rmi_f11_process_touch(hdata, i, finger_state, &data[position]);
+ }
+ input_mt_sync_frame(hdata->input);
+ input_sync(hdata->input);
+ return hdata->f11.report_size;
+}
+
+static int rmi_f30_input_event(struct hid_device *hdev, u8 irq, u8 *data,
+ int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ int i;
+ int button = 0;
+ bool value;
+
+ if (!(irq & hdata->f30.irq_mask))
+ return 0;
+
+ if (size < (int)hdata->f30.report_size) {
+ hid_warn(hdev, "Click Button pressed, but the click data is missing\n");
+ return 0;
+ }
+
+ for (i = 0; i < hdata->gpio_led_count; i++) {
+ if (test_bit(i, &hdata->button_mask)) {
+ value = (data[i / 8] >> (i & 0x07)) & BIT(0);
+ if (test_bit(i, &hdata->button_state_mask))
+ value = !value;
+ input_event(hdata->input, EV_KEY, BTN_LEFT + button++,
+ value);
+ }
+ }
+ return hdata->f30.report_size;
+}
+
+static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ unsigned long irq_mask = 0;
+ unsigned index = 2;
+
+ if (!(test_bit(RMI_STARTED, &hdata->flags)))
+ return 0;
+
+ irq_mask |= hdata->f11.irq_mask;
+ irq_mask |= hdata->f30.irq_mask;
+
+ if (data[1] & ~irq_mask)
+ hid_dbg(hdev, "unknown intr source:%02lx %s:%d\n",
+ data[1] & ~irq_mask, __FILE__, __LINE__);
+
+ if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
+ index += rmi_f11_input_event(hdev, data[1], &data[index],
+ size - index);
+ index += rmi_f30_input_event(hdev, data[1], &data[index],
+ size - index);
+ } else {
+ index += rmi_f30_input_event(hdev, data[1], &data[index],
+ size - index);
+ index += rmi_f11_input_event(hdev, data[1], &data[index],
+ size - index);
+ }
+
+ 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)
+{
+ 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;
+ }
+
+ rmi_schedule_reset(hdev);
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int rmi_post_reset(struct hid_device *hdev)
+{
+ return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+static int rmi_post_resume(struct hid_device *hdev)
+{
+ return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+}
+#endif /* CONFIG_PM */
+
+#define RMI4_MAX_PAGE 0xff
+#define RMI4_PAGE_SIZE 0x0100
+
+#define PDT_START_SCAN_LOCATION 0x00e9
+#define PDT_END_SCAN_LOCATION 0x0005
+#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
+
+struct pdt_entry {
+ u8 query_base_addr:8;
+ u8 command_base_addr:8;
+ u8 control_base_addr:8;
+ u8 data_base_addr:8;
+ u8 interrupt_source_count:3;
+ u8 bits3and4:2;
+ u8 function_version:2;
+ u8 bit7:1;
+ u8 function_number:8;
+} __attribute__((__packed__));
+
+static inline unsigned long rmi_gen_mask(unsigned irq_base, unsigned irq_count)
+{
+ return GENMASK(irq_count + irq_base - 1, irq_base);
+}
+
+static void rmi_register_function(struct rmi_data *data,
+ struct pdt_entry *pdt_entry, int page, unsigned interrupt_count)
+{
+ struct rmi_function *f = NULL;
+ u16 page_base = page << 8;
+
+ switch (pdt_entry->function_number) {
+ case 0x01:
+ f = &data->f01;
+ break;
+ case 0x11:
+ f = &data->f11;
+ break;
+ case 0x30:
+ f = &data->f30;
+ break;
+ }
+
+ if (f) {
+ f->page = page;
+ f->query_base_addr = page_base | pdt_entry->query_base_addr;
+ f->command_base_addr = page_base | pdt_entry->command_base_addr;
+ f->control_base_addr = page_base | pdt_entry->control_base_addr;
+ f->data_base_addr = page_base | pdt_entry->data_base_addr;
+ f->interrupt_base = interrupt_count;
+ f->interrupt_count = pdt_entry->interrupt_source_count;
+ f->irq_mask = rmi_gen_mask(f->interrupt_base,
+ f->interrupt_count);
+ }
+}
+
+static int rmi_scan_pdt(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ struct pdt_entry entry;
+ int page;
+ bool page_has_function;
+ int i;
+ int retval;
+ int interrupt = 0;
+ u16 page_start, pdt_start , pdt_end;
+
+ hid_info(hdev, "Scanning PDT...\n");
+
+ for (page = 0; (page <= RMI4_MAX_PAGE); page++) {
+ page_start = RMI4_PAGE_SIZE * page;
+ pdt_start = page_start + PDT_START_SCAN_LOCATION;
+ pdt_end = page_start + PDT_END_SCAN_LOCATION;
+
+ page_has_function = false;
+ for (i = pdt_start; i >= pdt_end; i -= sizeof(entry)) {
+ retval = rmi_read_block(hdev, i, &entry, sizeof(entry));
+ if (retval) {
+ hid_err(hdev,
+ "Read of PDT entry at %#06x failed.\n",
+ i);
+ goto error_exit;
+ }
+
+ if (RMI4_END_OF_PDT(entry.function_number))
+ break;
+
+ page_has_function = true;
+
+ hid_info(hdev, "Found F%02X on page %#04x\n",
+ entry.function_number, page);
+
+ rmi_register_function(data, &entry, page, interrupt);
+ interrupt += entry.interrupt_source_count;
+ }
+
+ if (!page_has_function)
+ break;
+ }
+
+ hid_info(hdev, "%s: Done with PDT scan.\n", __func__);
+ retval = 0;
+
+error_exit:
+ return retval;
+}
+
+#define RMI_DEVICE_F01_BASIC_QUERY_LEN 11
+
+static int rmi_populate_f01(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 basic_queries[RMI_DEVICE_F01_BASIC_QUERY_LEN];
+ u8 info[3];
+ int ret;
+ bool has_query42;
+ bool has_lts;
+ bool has_sensor_id;
+ bool has_ds4_queries = false;
+ bool has_build_id_query = false;
+ bool has_package_id_query = false;
+ u16 query_offset = data->f01.query_base_addr;
+ u16 prod_info_addr;
+ u8 ds4_query_len;
+
+ ret = rmi_read_block(hdev, query_offset, basic_queries,
+ RMI_DEVICE_F01_BASIC_QUERY_LEN);
+ if (ret) {
+ hid_err(hdev, "Can not read basic queries from Function 0x1.\n");
+ return ret;
+ }
+
+ has_lts = !!(basic_queries[0] & BIT(2));
+ has_sensor_id = !!(basic_queries[1] & BIT(3));
+ has_query42 = !!(basic_queries[1] & BIT(7));
+
+ query_offset += 11;
+ prod_info_addr = query_offset + 6;
+ query_offset += 10;
+
+ if (has_lts)
+ query_offset += 20;
+
+ if (has_sensor_id)
+ query_offset++;
+
+ if (has_query42) {
+ ret = rmi_read(hdev, query_offset, info);
+ if (ret) {
+ hid_err(hdev, "Can not read query42.\n");
+ return ret;
+ }
+ has_ds4_queries = !!(info[0] & BIT(0));
+ query_offset++;
+ }
+
+ if (has_ds4_queries) {
+ ret = rmi_read(hdev, query_offset, &ds4_query_len);
+ if (ret) {
+ hid_err(hdev, "Can not read DS4 Query length.\n");
+ return ret;
+ }
+ query_offset++;
+
+ if (ds4_query_len > 0) {
+ ret = rmi_read(hdev, query_offset, info);
+ if (ret) {
+ hid_err(hdev, "Can not read DS4 query.\n");
+ return ret;
+ }
+
+ has_package_id_query = !!(info[0] & BIT(0));
+ has_build_id_query = !!(info[0] & BIT(1));
+ }
+ }
+
+ if (has_package_id_query)
+ prod_info_addr++;
+
+ if (has_build_id_query) {
+ ret = rmi_read_block(hdev, prod_info_addr, info, 3);
+ if (ret) {
+ hid_err(hdev, "Can not read product info.\n");
+ return ret;
+ }
+
+ data->firmware_id = info[1] << 8 | info[0];
+ data->firmware_id += info[2] * 65536;
+ }
+
+ return 0;
+}
+
+static int rmi_populate_f11(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 buf[20];
+ int ret;
+ bool has_query9;
+ bool has_query10 = false;
+ bool has_query11;
+ bool has_query12;
+ bool has_query27;
+ bool has_query28;
+ bool has_query36 = false;
+ bool has_physical_props;
+ bool has_gestures;
+ bool has_rel;
+ bool has_data40 = false;
+ bool has_dribble = false;
+ bool has_palm_detect = false;
+ unsigned x_size, y_size;
+ u16 query_offset;
+
+ if (!data->f11.query_base_addr) {
+ hid_err(hdev, "No 2D sensor found, giving up.\n");
+ return -ENODEV;
+ }
+
+ /* query 0 contains some useful information */
+ ret = rmi_read(hdev, data->f11.query_base_addr, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 0: %d.\n", ret);
+ return ret;
+ }
+ has_query9 = !!(buf[0] & BIT(3));
+ has_query11 = !!(buf[0] & BIT(4));
+ has_query12 = !!(buf[0] & BIT(5));
+ has_query27 = !!(buf[0] & BIT(6));
+ has_query28 = !!(buf[0] & BIT(7));
+
+ /* query 1 to get the max number of fingers */
+ ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
+ if (ret) {
+ hid_err(hdev, "can not get NumberOfFingers: %d.\n", ret);
+ return ret;
+ }
+ data->max_fingers = (buf[0] & 0x07) + 1;
+ if (data->max_fingers > 5)
+ data->max_fingers = 10;
+
+ data->f11.report_size = data->max_fingers * 5 +
+ DIV_ROUND_UP(data->max_fingers, 4);
+
+ if (!(buf[0] & BIT(4))) {
+ hid_err(hdev, "No absolute events, giving up.\n");
+ return -ENODEV;
+ }
+
+ has_rel = !!(buf[0] & BIT(3));
+ has_gestures = !!(buf[0] & BIT(5));
+
+ ret = rmi_read(hdev, data->f11.query_base_addr + 5, buf);
+ if (ret) {
+ hid_err(hdev, "can not get absolute data sources: %d.\n", ret);
+ return ret;
+ }
+
+ has_dribble = !!(buf[0] & BIT(4));
+
+ /*
+ * At least 4 queries are guaranteed to be present in F11
+ * +1 for query 5 which is present since absolute events are
+ * reported and +1 for query 12.
+ */
+ query_offset = 6;
+
+ if (has_rel)
+ ++query_offset; /* query 6 is present */
+
+ if (has_gestures) {
+ /* query 8 to find out if query 10 exists */
+ ret = rmi_read(hdev,
+ data->f11.query_base_addr + query_offset + 1, buf);
+ if (ret) {
+ hid_err(hdev, "can not read gesture information: %d.\n",
+ ret);
+ return ret;
+ }
+ has_palm_detect = !!(buf[0] & BIT(0));
+ has_query10 = !!(buf[0] & BIT(2));
+
+ query_offset += 2; /* query 7 and 8 are present */
+ }
+
+ if (has_query9)
+ ++query_offset;
+
+ if (has_query10)
+ ++query_offset;
+
+ if (has_query11)
+ ++query_offset;
+
+ /* query 12 to know if the physical properties are reported */
+ if (has_query12) {
+ ret = rmi_read(hdev, data->f11.query_base_addr
+ + query_offset, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 12: %d.\n", ret);
+ return ret;
+ }
+ has_physical_props = !!(buf[0] & BIT(5));
+
+ if (has_physical_props) {
+ query_offset += 1;
+ ret = rmi_read_block(hdev,
+ data->f11.query_base_addr
+ + query_offset, buf, 4);
+ if (ret) {
+ hid_err(hdev, "can not read query 15-18: %d.\n",
+ ret);
+ return ret;
+ }
+
+ x_size = buf[0] | (buf[1] << 8);
+ y_size = buf[2] | (buf[3] << 8);
+
+ data->x_size_mm = DIV_ROUND_CLOSEST(x_size, 10);
+ data->y_size_mm = DIV_ROUND_CLOSEST(y_size, 10);
+
+ hid_info(hdev, "%s: size in mm: %d x %d\n",
+ __func__, data->x_size_mm, data->y_size_mm);
+
+ /*
+ * query 15 - 18 contain the size of the sensor
+ * and query 19 - 26 contain bezel dimensions
+ */
+ query_offset += 12;
+ }
+ }
+
+ if (has_query27)
+ ++query_offset;
+
+ if (has_query28) {
+ ret = rmi_read(hdev, data->f11.query_base_addr
+ + query_offset, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 28: %d.\n", ret);
+ return ret;
+ }
+
+ has_query36 = !!(buf[0] & BIT(6));
+ }
+
+ if (has_query36) {
+ query_offset += 2;
+ ret = rmi_read(hdev, data->f11.query_base_addr
+ + query_offset, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 36: %d.\n", ret);
+ return ret;
+ }
+
+ has_data40 = !!(buf[0] & BIT(5));
+ }
+
+
+ if (has_data40)
+ data->f11.report_size += data->max_fingers * 2;
+
+ /*
+ * 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.
+ */
+ ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 12);
+ if (ret) {
+ hid_err(hdev, "can not read ctrl block of size 11: %d.\n", ret);
+ return ret;
+ }
+
+ data->max_x = buf[6] | (buf[7] << 8);
+ data->max_y = buf[8] | (buf[9] << 8);
+
+ if (has_dribble) {
+ buf[0] = buf[0] & ~BIT(6);
+ ret = rmi_write(hdev, data->f11.control_base_addr, buf);
+ if (ret) {
+ hid_err(hdev, "can not write to control reg 0: %d.\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (has_palm_detect) {
+ buf[11] = buf[11] & ~BIT(0);
+ ret = rmi_write(hdev, data->f11.control_base_addr + 11,
+ &buf[11]);
+ if (ret) {
+ hid_err(hdev, "can not write to control reg 11: %d.\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int rmi_populate_f30(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 buf[20];
+ int ret;
+ bool has_gpio, has_led;
+ unsigned bytes_per_ctrl;
+ u8 ctrl2_addr;
+ int ctrl2_3_length;
+ int i;
+
+ /* function F30 is for physical buttons */
+ if (!data->f30.query_base_addr) {
+ hid_err(hdev, "No GPIO/LEDs found, giving up.\n");
+ return -ENODEV;
+ }
+
+ ret = rmi_read_block(hdev, data->f30.query_base_addr, buf, 2);
+ if (ret) {
+ hid_err(hdev, "can not get F30 query registers: %d.\n", ret);
+ return ret;
+ }
+
+ has_gpio = !!(buf[0] & BIT(3));
+ has_led = !!(buf[0] & BIT(2));
+ data->gpio_led_count = buf[1] & 0x1f;
+
+ /* retrieve ctrl 2 & 3 registers */
+ bytes_per_ctrl = (data->gpio_led_count + 7) / 8;
+ /* Ctrl0 is present only if both has_gpio and has_led are set*/
+ ctrl2_addr = (has_gpio && has_led) ? bytes_per_ctrl : 0;
+ /* Ctrl1 is always be present */
+ ctrl2_addr += bytes_per_ctrl;
+ ctrl2_3_length = 2 * bytes_per_ctrl;
+
+ data->f30.report_size = bytes_per_ctrl;
+
+ ret = rmi_read_block(hdev, data->f30.control_base_addr + ctrl2_addr,
+ buf, ctrl2_3_length);
+ if (ret) {
+ hid_err(hdev, "can not read ctrl 2&3 block of size %d: %d.\n",
+ ctrl2_3_length, ret);
+ return ret;
+ }
+
+ for (i = 0; i < data->gpio_led_count; i++) {
+ int byte_position = i >> 3;
+ int bit_position = i & 0x07;
+ u8 dir_byte = buf[byte_position];
+ u8 data_byte = buf[byte_position + bytes_per_ctrl];
+ bool dir = (dir_byte >> bit_position) & BIT(0);
+ bool dat = (data_byte >> bit_position) & BIT(0);
+
+ if (dir == 0) {
+ /* input mode */
+ if (dat) {
+ /* actual buttons have pull up resistor */
+ data->button_count++;
+ set_bit(i, &data->button_mask);
+ set_bit(i, &data->button_state_mask);
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+static int rmi_populate(struct hid_device *hdev)
+{
+ int ret;
+
+ ret = rmi_scan_pdt(hdev);
+ if (ret) {
+ hid_err(hdev, "PDT scan failed with code %d.\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f01(hdev);
+ if (ret) {
+ hid_err(hdev, "Error while initializing F01 (%d).\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f11(hdev);
+ if (ret) {
+ hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f30(hdev);
+ if (ret)
+ hid_warn(hdev, "Error while initializing F30 (%d).\n", ret);
+
+ return 0;
+}
+
+static void 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;
+ 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;
+
+ if (!(data->device_flags & RMI_DEVICE))
+ return;
+
+ /* 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_populate(hdev);
+ if (ret)
+ goto exit;
+
+ hid_info(hdev, "firmware id: %ld\n", data->firmware_id);
+
+ __set_bit(EV_ABS, input->evbit);
+ input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
+
+ if (data->x_size_mm && data->y_size_mm) {
+ res_x = (data->max_x - 1) / data->x_size_mm;
+ res_y = (data->max_y - 1) / data->y_size_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_ORIENTATION, 0, 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xff, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
+
+ input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
+
+ if (data->button_count) {
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < data->button_count; i++)
+ __set_bit(BTN_LEFT + i, input->keybit);
+
+ if (data->button_count == 1)
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+ }
+
+ set_bit(RMI_STARTED, &data->flags);
+
+exit:
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+}
+
+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 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;
+
+ 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) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ data->readReport = data->writeReport + data->output_report_size;
+
+ init_waitqueue_head(&data->wait);
+
+ mutex_init(&data->page_mutex);
+
+start:
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ if ((data->device_flags & RMI_DEVICE) &&
+ !test_bit(RMI_STARTED, &data->flags))
+ /*
+ * The device maybe in the bootloader if rmi_input_configured
+ * failed to find F11 in the PDT. Print an error, but don't
+ * return an error from rmi_probe so that hidraw will be
+ * accessible from userspace. That way a userspace tool
+ * can be used to reload working firmware on the touchpad.
+ */
+ hid_err(hdev, "Device failed to be properly configured\n");
+
+ return 0;
+}
+
+static void rmi_remove(struct hid_device *hdev)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+ clear_bit(RMI_STARTED, &hdata->flags);
+
+ 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_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,
+ .input_mapping = rmi_input_mapping,
+ .input_configured = rmi_input_configured,
+#ifdef CONFIG_PM
+ .resume = rmi_post_resume,
+ .reset_resume = rmi_post_reset,
+#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 000000000..1948208fe
--- /dev/null
+++ b/drivers/hid/hid-roccat-arvo.c
@@ -0,0 +1,460 @@
+/*
+ * 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..ce8415e4f
--- /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 000000000..02e28e9f4
--- /dev/null
+++ b/drivers/hid/hid-roccat-common.c
@@ -0,0 +1,180 @@
+/*
+ * 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..eaa56eb7d
--- /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 000000000..bc62ed91e
--- /dev/null
+++ b/drivers/hid/hid-roccat-isku.c
@@ -0,0 +1,462 @@
+/*
+ * 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..53056860d
--- /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 000000000..c29265055
--- /dev/null
+++ b/drivers/hid/hid-roccat-kone.c
@@ -0,0 +1,909 @@
+/*
+ * 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..52c6167d0
--- /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 000000000..5e99fcdc7
--- /dev/null
+++ b/drivers/hid/hid-roccat-koneplus.c
@@ -0,0 +1,578 @@
+/*
+ * 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..af7f57e8c
--- /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 000000000..07de2f901
--- /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 000000000..966047711
--- /dev/null
+++ b/drivers/hid/hid-roccat-kovaplus.c
@@ -0,0 +1,665 @@
+/*
+ * 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)
+{
+ 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..fbb7a16a7
--- /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 000000000..65e2e76bf
--- /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 = container_of(kobj, struct device, 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 = container_of(kobj, struct device, 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 000000000..547d77a37
--- /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 000000000..47d7e7423
--- /dev/null
+++ b/drivers/hid/hid-roccat-pyra.c
@@ -0,0 +1,615 @@
+/*
+ * 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 =
+ container_of(kobj, struct device, 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 000000000..beedcf001
--- /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 000000000..47cc8f30f
--- /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 000000000..6dbf6e04d
--- /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 000000000..d23217bd2
--- /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 000000000..65c4ccfcb
--- /dev/null
+++ b/drivers/hid/hid-roccat.c
@@ -0,0 +1,461 @@
+/*
+ * 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.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 unsigned int 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 POLLIN | POLLRDNORM;
+ if (!reader->device->exist)
+ return POLLERR | POLLHUP;
+ 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");
+
+ roccat_major = MAJOR(dev_id);
+
+ if (retval < 0) {
+ pr_warn("can't get major number\n");
+ goto error;
+ }
+
+ 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 000000000..a014f2127
--- /dev/null
+++ b/drivers/hid/hid-saitek.c
@@ -0,0 +1,203 @@
+/*
+ * 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_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_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 000000000..7cbb067d4
--- /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 000000000..5614fee82
--- /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 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-%d-%x-%s", &index, &usage,
+ name) == 3) {
+ feature = true;
+ field_index = index + sensor_inst->input_field_count;
+ } else if (sscanf(attr->attr.name, "input-%d-%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);
+ } 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-%d-%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 unsigned int hid_sensor_custom_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct hid_sensor_custom *sensor_inst;
+ unsigned int 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 = POLLIN | POLLRDNORM;
+
+ 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 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 000000000..090a1ba0a
--- /dev/null
+++ b/drivers/hid/hid-sensor-hub.c
@@ -0,0 +1,800 @@
+/*
+ * 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 = do_div(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;
+
+ 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);
+ memcpy(buffer, report->field[field_index]->value, ret);
+
+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)
+{
+ 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:
+ ret_val = *(u8 *)hsdev->pending.raw_data;
+ break;
+ case 2:
+ 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)
+{
+ int index;
+ struct sensor_hub_data *sd = hid_get_drvdata(hdev);
+ unsigned char report_block[] = {
+ 0x0a, 0x16, 0x03, 0x15, 0x00, 0x25, 0x05};
+ unsigned char power_block[] = {
+ 0x0a, 0x19, 0x03, 0x15, 0x00, 0x25, 0x05};
+
+ if (!(sd->quirks & HID_SENSOR_HUB_ENUM_QUIRK)) {
+ hid_dbg(hdev, "No Enum quirks\n");
+ return rdesc;
+ }
+
+ /* Looks for power and report state usage id and force to 1 */
+ for (index = 0; index < *rsize; ++index) {
+ if (((*rsize - index) > sizeof(report_block)) &&
+ !memcmp(&rdesc[index], report_block,
+ sizeof(report_block))) {
+ rdesc[index + 4] = 0x01;
+ index += sizeof(report_block);
+ }
+ if (((*rsize - index) > sizeof(power_block)) &&
+ !memcmp(&rdesc[index], power_block,
+ sizeof(power_block))) {
+ rdesc[index + 4] = 0x01;
+ index += sizeof(power_block);
+ }
+ }
+
+ 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_kzalloc(&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, USB_VENDOR_ID_INTEL_0,
+ USB_DEVICE_ID_INTEL_HID_SENSOR_0),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_INTEL_1,
+ USB_DEVICE_ID_INTEL_HID_SENSOR_0),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_INTEL_1,
+ USB_DEVICE_ID_INTEL_HID_SENSOR_1),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_MICROSOFT,
+ USB_DEVICE_ID_MS_SURFACE_PRO_2),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_MICROSOFT,
+ USB_DEVICE_ID_MS_TOUCH_COVER_2),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_MICROSOFT,
+ USB_DEVICE_ID_MS_TYPE_COVER_2),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_STM_0,
+ USB_DEVICE_ID_STM_HID_SENSOR),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_STM_0,
+ USB_DEVICE_ID_STM_HID_SENSOR_1),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_TEXAS_INSTRUMENTS,
+ USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { 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 000000000..37845eccd
--- /dev/null
+++ b/drivers/hid/hid-sjoy.c
@@ -0,0 +1,182 @@
+/*
+ * 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 },
+ { }
+};
+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 000000000..6ca96cebb
--- /dev/null
+++ b/drivers/hid/hid-sony.c
@@ -0,0 +1,2184 @@
+/*
+ * 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 Frank Praznik <frank.praznik@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.
+ */
+
+/*
+ * 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 "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 SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
+#define DUALSHOCK4_CONTROLLER (DUALSHOCK4_CONTROLLER_USB |\
+ DUALSHOCK4_CONTROLLER_BT)
+#define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\
+ DUALSHOCK4_CONTROLLER)
+#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)
+#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)
+
+#define MAX_LEDS 4
+
+static __u8 sixaxis_rdesc[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x04, /* Usage (Joystik), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0xA1, 0x02, /* Collection (Logical), */
+ 0x85, 0x01, /* Report ID (1), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x13, /* Report Count (19), */
+ 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, 0x13, /* Usage Maximum (13h), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x0D, /* Report Count (13), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x01, /* Usage (Pointer), */
+ 0xA1, 0x00, /* Collection (Physical), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x35, 0x00, /* Physical Minimum (0), */
+ 0x46, 0xFF, 0x00, /* Physical Maximum (255), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x09, 0x32, /* Usage (Z), */
+ 0x09, 0x35, /* Usage (Rz), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x95, 0x13, /* Report Count (19), */
+ 0x09, 0x01, /* Usage (Pointer), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x0C, /* Report Count (12), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
+ 0x46, 0xFF, 0x03, /* Physical Maximum (1023), */
+ 0x09, 0x01, /* Usage (Pointer), */
+ 0x81, 0x02, /* Input (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 */
+};
+
+/*
+ * The default descriptor doesn't provide mapping for the accelerometers
+ * or orientation sensors. This fixed descriptor maps the accelerometers
+ * to usage values 0x40, 0x41 and 0x42 and maps the orientation sensors
+ * to usage values 0x43, 0x44 and 0x45.
+ */
+static u8 dualshock4_usb_rdesc[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x05, /* Usage (Gamepad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x01, /* Report ID (1), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x09, 0x32, /* Usage (Z), */
+ 0x09, 0x35, /* Usage (Rz), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x39, /* Usage (Hat Switch), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 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, 0x42, /* Input (Variable, Null State), */
+ 0x65, 0x00, /* Unit, */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x0E, /* Usage Maximum (0Eh), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x0E, /* Report Count (14), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x20, /* Usage (20h), */
+ 0x75, 0x06, /* Report Size (6), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x25, 0x3F, /* Logical Maximum (63), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x33, /* Usage (Rx), */
+ 0x09, 0x34, /* Usage (Ry), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x19, 0x40, /* Usage Minimum (40h), */
+ 0x29, 0x42, /* Usage Maximum (42h), */
+ 0x16, 0x00, 0x80, /* Logical Minimum (-32768), */
+ 0x26, 0x00, 0x7F, /* Logical Maximum (32767), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x19, 0x43, /* Usage Minimum (43h), */
+ 0x29, 0x45, /* Usage Maximum (45h), */
+ 0x16, 0x00, 0xE0, /* Logical Minimum (-8192), */
+ 0x26, 0xFF, 0x1F, /* Logical Maximum (8191), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x27, /* Report Count (39), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x85, 0x05, /* Report ID (5), */
+ 0x09, 0x22, /* Usage (22h), */
+ 0x95, 0x1F, /* Report Count (31), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x04, /* Report ID (4), */
+ 0x09, 0x23, /* Usage (23h), */
+ 0x95, 0x24, /* Report Count (36), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x02, /* Report ID (2), */
+ 0x09, 0x24, /* Usage (24h), */
+ 0x95, 0x24, /* Report Count (36), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x08, /* Report ID (8), */
+ 0x09, 0x25, /* Usage (25h), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x10, /* Report ID (16), */
+ 0x09, 0x26, /* Usage (26h), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x11, /* Report ID (17), */
+ 0x09, 0x27, /* Usage (27h), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x12, /* Report ID (18), */
+ 0x06, 0x02, 0xFF, /* Usage Page (FF02h), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x95, 0x0F, /* Report Count (15), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x13, /* Report ID (19), */
+ 0x09, 0x22, /* Usage (22h), */
+ 0x95, 0x16, /* Report Count (22), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x14, /* Report ID (20), */
+ 0x06, 0x05, 0xFF, /* Usage Page (FF05h), */
+ 0x09, 0x20, /* Usage (20h), */
+ 0x95, 0x10, /* Report Count (16), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x15, /* Report ID (21), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x95, 0x2C, /* Report Count (44), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x06, 0x80, 0xFF, /* Usage Page (FF80h), */
+ 0x85, 0x80, /* Report ID (128), */
+ 0x09, 0x20, /* Usage (20h), */
+ 0x95, 0x06, /* Report Count (6), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x81, /* Report ID (129), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x95, 0x06, /* Report Count (6), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x82, /* Report ID (130), */
+ 0x09, 0x22, /* Usage (22h), */
+ 0x95, 0x05, /* Report Count (5), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x83, /* Report ID (131), */
+ 0x09, 0x23, /* Usage (23h), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x84, /* Report ID (132), */
+ 0x09, 0x24, /* Usage (24h), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x85, /* Report ID (133), */
+ 0x09, 0x25, /* Usage (25h), */
+ 0x95, 0x06, /* Report Count (6), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x86, /* Report ID (134), */
+ 0x09, 0x26, /* Usage (26h), */
+ 0x95, 0x06, /* Report Count (6), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x87, /* Report ID (135), */
+ 0x09, 0x27, /* Usage (27h), */
+ 0x95, 0x23, /* Report Count (35), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x88, /* Report ID (136), */
+ 0x09, 0x28, /* Usage (28h), */
+ 0x95, 0x22, /* Report Count (34), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x89, /* Report ID (137), */
+ 0x09, 0x29, /* Usage (29h), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x90, /* Report ID (144), */
+ 0x09, 0x30, /* Usage (30h), */
+ 0x95, 0x05, /* Report Count (5), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x91, /* Report ID (145), */
+ 0x09, 0x31, /* Usage (31h), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x92, /* Report ID (146), */
+ 0x09, 0x32, /* Usage (32h), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x93, /* Report ID (147), */
+ 0x09, 0x33, /* Usage (33h), */
+ 0x95, 0x0C, /* Report Count (12), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA0, /* Report ID (160), */
+ 0x09, 0x40, /* Usage (40h), */
+ 0x95, 0x06, /* Report Count (6), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA1, /* Report ID (161), */
+ 0x09, 0x41, /* Usage (41h), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA2, /* Report ID (162), */
+ 0x09, 0x42, /* Usage (42h), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA3, /* Report ID (163), */
+ 0x09, 0x43, /* Usage (43h), */
+ 0x95, 0x30, /* Report Count (48), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA4, /* Report ID (164), */
+ 0x09, 0x44, /* Usage (44h), */
+ 0x95, 0x0D, /* Report Count (13), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA5, /* Report ID (165), */
+ 0x09, 0x45, /* Usage (45h), */
+ 0x95, 0x15, /* Report Count (21), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA6, /* Report ID (166), */
+ 0x09, 0x46, /* Usage (46h), */
+ 0x95, 0x15, /* Report Count (21), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xF0, /* Report ID (240), */
+ 0x09, 0x47, /* Usage (47h), */
+ 0x95, 0x3F, /* Report Count (63), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xF1, /* Report ID (241), */
+ 0x09, 0x48, /* Usage (48h), */
+ 0x95, 0x3F, /* Report Count (63), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xF2, /* Report ID (242), */
+ 0x09, 0x49, /* Usage (49h), */
+ 0x95, 0x0F, /* Report Count (15), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA7, /* Report ID (167), */
+ 0x09, 0x4A, /* Usage (4Ah), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA8, /* Report ID (168), */
+ 0x09, 0x4B, /* Usage (4Bh), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA9, /* Report ID (169), */
+ 0x09, 0x4C, /* Usage (4Ch), */
+ 0x95, 0x08, /* Report Count (8), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xAA, /* Report ID (170), */
+ 0x09, 0x4E, /* Usage (4Eh), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xAB, /* Report ID (171), */
+ 0x09, 0x4F, /* Usage (4Fh), */
+ 0x95, 0x39, /* Report Count (57), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xAC, /* Report ID (172), */
+ 0x09, 0x50, /* Usage (50h), */
+ 0x95, 0x39, /* Report Count (57), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xAD, /* Report ID (173), */
+ 0x09, 0x51, /* Usage (51h), */
+ 0x95, 0x0B, /* Report Count (11), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xAE, /* Report ID (174), */
+ 0x09, 0x52, /* Usage (52h), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xAF, /* Report ID (175), */
+ 0x09, 0x53, /* Usage (53h), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xB0, /* Report ID (176), */
+ 0x09, 0x54, /* Usage (54h), */
+ 0x95, 0x3F, /* Report Count (63), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0xC0 /* End Collection */
+};
+
+/*
+ * 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 button and axis definitions must be moved to report 17 or
+ * the HID layer won't process the received input.
+ */
+static u8 dualshock4_bt_rdesc[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x05, /* Usage (Gamepad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x01, /* Report ID (1), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x0A, /* Report Count (9), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x06, 0x04, 0xFF, /* Usage Page (FF04h), */
+ 0x85, 0x02, /* Report ID (2), */
+ 0x09, 0x24, /* Usage (24h), */
+ 0x95, 0x24, /* Report Count (36), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA3, /* Report ID (163), */
+ 0x09, 0x25, /* Usage (25h), */
+ 0x95, 0x30, /* Report Count (48), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x05, /* Report ID (5), */
+ 0x09, 0x26, /* Usage (26h), */
+ 0x95, 0x28, /* Report Count (40), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x06, /* Report ID (6), */
+ 0x09, 0x27, /* Usage (27h), */
+ 0x95, 0x34, /* Report Count (52), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x07, /* Report ID (7), */
+ 0x09, 0x28, /* Usage (28h), */
+ 0x95, 0x30, /* Report Count (48), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x08, /* Report ID (8), */
+ 0x09, 0x29, /* Usage (29h), */
+ 0x95, 0x2F, /* Report Count (47), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x06, 0x03, 0xFF, /* Usage Page (FF03h), */
+ 0x85, 0x03, /* Report ID (3), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x95, 0x26, /* Report Count (38), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x04, /* Report ID (4), */
+ 0x09, 0x22, /* Usage (22h), */
+ 0x95, 0x2E, /* Report Count (46), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xF0, /* Report ID (240), */
+ 0x09, 0x47, /* Usage (47h), */
+ 0x95, 0x3F, /* Report Count (63), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xF1, /* Report ID (241), */
+ 0x09, 0x48, /* Usage (48h), */
+ 0x95, 0x3F, /* Report Count (63), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xF2, /* Report ID (242), */
+ 0x09, 0x49, /* Usage (49h), */
+ 0x95, 0x0F, /* Report Count (15), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x11, /* Report ID (17), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x20, /* Usage (20h), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x09, 0x32, /* Usage (Z), */
+ 0x09, 0x35, /* Usage (Rz), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x04, /* Report Count (4), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x39, /* Usage (Hat Switch), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x25, 0x07, /* Logical Maximum (7), */
+ 0x75, 0x04, /* Report Size (4), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x42, /* Input (Variable, Null State), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x0E, /* Usage Maximum (0Eh), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x0E, /* Report Count (14), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x06, /* Report Size (6), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x33, /* Usage (Rx), */
+ 0x09, 0x34, /* Usage (Ry), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x20, /* Usage (20h), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x19, 0x40, /* Usage Minimum (40h), */
+ 0x29, 0x42, /* Usage Maximum (42h), */
+ 0x16, 0x00, 0x80, /* Logical Minimum (-32768), */
+ 0x26, 0x00, 0x7F, /* Logical Maximum (32767), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x19, 0x43, /* Usage Minimum (43h), */
+ 0x29, 0x45, /* Usage Maximum (45h), */
+ 0x16, 0x00, 0xE0, /* Logical Minimum (-8192), */
+ 0x26, 0xFF, 0x1F, /* Logical Maximum (8191), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x20, /* Usage (20h), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x31, /* Report Count (51), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x21, /* Usage (21h), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x4D, /* Report Count (77), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x12, /* Report ID (18), */
+ 0x09, 0x22, /* Usage (22h), */
+ 0x95, 0x8D, /* Report Count (141), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x23, /* Usage (23h), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x13, /* Report ID (19), */
+ 0x09, 0x24, /* Usage (24h), */
+ 0x95, 0xCD, /* Report Count (205), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x25, /* Usage (25h), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x14, /* Report ID (20), */
+ 0x09, 0x26, /* Usage (26h), */
+ 0x96, 0x0D, 0x01, /* Report Count (269), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x27, /* Usage (27h), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x15, /* Report ID (21), */
+ 0x09, 0x28, /* Usage (28h), */
+ 0x96, 0x4D, 0x01, /* Report Count (333), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x29, /* Usage (29h), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x16, /* Report ID (22), */
+ 0x09, 0x2A, /* Usage (2Ah), */
+ 0x96, 0x8D, 0x01, /* Report Count (397), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x2B, /* Usage (2Bh), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x17, /* Report ID (23), */
+ 0x09, 0x2C, /* Usage (2Ch), */
+ 0x96, 0xCD, 0x01, /* Report Count (461), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x2D, /* Usage (2Dh), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x18, /* Report ID (24), */
+ 0x09, 0x2E, /* Usage (2Eh), */
+ 0x96, 0x0D, 0x02, /* Report Count (525), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x2F, /* Usage (2Fh), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x85, 0x19, /* Report ID (25), */
+ 0x09, 0x30, /* Usage (30h), */
+ 0x96, 0x22, 0x02, /* Report Count (546), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (31h), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0x06, 0x80, 0xFF, /* Usage Page (FF80h), */
+ 0x85, 0x82, /* Report ID (130), */
+ 0x09, 0x22, /* Usage (22h), */
+ 0x95, 0x3F, /* Report Count (63), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x83, /* Report ID (131), */
+ 0x09, 0x23, /* Usage (23h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x84, /* Report ID (132), */
+ 0x09, 0x24, /* Usage (24h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x90, /* Report ID (144), */
+ 0x09, 0x30, /* Usage (30h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x91, /* Report ID (145), */
+ 0x09, 0x31, /* Usage (31h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x92, /* Report ID (146), */
+ 0x09, 0x32, /* Usage (32h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0x93, /* Report ID (147), */
+ 0x09, 0x33, /* Usage (33h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA0, /* Report ID (160), */
+ 0x09, 0x40, /* Usage (40h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 0x85, 0xA4, /* Report ID (164), */
+ 0x09, 0x44, /* Usage (44h), */
+ 0xB1, 0x02, /* Feature (Variable), */
+ 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,
+};
+
+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];
+};
+
+#define DS4_REPORT_0x02_SIZE 37
+#define DS4_REPORT_0x05_SIZE 32
+#define DS4_REPORT_0x11_SIZE 78
+#define DS4_REPORT_0x81_SIZE 7
+#define SIXAXIS_REPORT_0xF2_SIZE 17
+#define SIXAXIS_REPORT_0xF5_SIZE 8
+
+static DEFINE_SPINLOCK(sony_dev_list_lock);
+static LIST_HEAD(sony_device_list);
+static DEFINE_IDA(sony_device_id_allocator);
+
+struct sony_sc {
+ spinlock_t lock;
+ struct list_head list_node;
+ struct hid_device *hdev;
+ struct led_classdev *leds[MAX_LEDS];
+ unsigned long quirks;
+ struct work_struct state_worker;
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+ int device_id;
+ __u8 *output_report_dmabuf;
+
+#ifdef CONFIG_SONY_FF
+ __u8 left;
+ __u8 right;
+#endif
+
+ __u8 mac_address[6];
+ __u8 worker_initialized;
+ __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;
+};
+
+static __u8 *sixaxis_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ *rsize = sizeof(sixaxis_rdesc);
+ return sixaxis_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 __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ struct sony_sc *sc = hid_get_drvdata(hdev);
+
+ /*
+ * 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;
+ }
+
+ /*
+ * The default Dualshock 4 USB descriptor doesn't assign
+ * the gyroscope values to corresponding axes so we need a
+ * modified one.
+ */
+ if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && *rsize == 467) {
+ hid_info(hdev, "Using modified Dualshock 4 report descriptor with gyroscope axes\n");
+ rdesc = dualshock4_usb_rdesc;
+ *rsize = sizeof(dualshock4_usb_rdesc);
+ } else if ((sc->quirks & DUALSHOCK4_CONTROLLER_BT) && *rsize == 357) {
+ hid_info(hdev, "Using modified Dualshock 4 Bluetooth report descriptor\n");
+ rdesc = dualshock4_bt_rdesc;
+ *rsize = sizeof(dualshock4_bt_rdesc);
+ }
+
+ if (sc->quirks & SIXAXIS_CONTROLLER)
+ return sixaxis_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;
+ __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.
+ */
+ if (rd[30] >= 0xee) {
+ battery_capacity = 100;
+ battery_charging = !(rd[30] & 0x01);
+ cable_state = 1;
+ } else {
+ __u8 index = rd[30] <= 5 ? rd[30] : 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);
+}
+
+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, offset;
+ __u8 cable_state, battery_capacity, battery_charging;
+
+ /*
+ * Battery and touchpad data starts at byte 30 in the USB report and
+ * 32 in Bluetooth report.
+ */
+ offset = (sc->quirks & DUALSHOCK4_CONTROLLER_USB) ? 30 : 32;
+
+ /*
+ * The lower 4 bits of byte 30 contain the battery level
+ * and the 5th bit contains the USB cable state.
+ */
+ 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);
+
+ offset += 5;
+
+ /*
+ * The Dualshock 4 multi-touch trackpad data starts at offset 35 on USB
+ * and 37 on Bluetooth.
+ * 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 immediatly
+ * follows the data for the first.
+ */
+ for (n = 0; n < 2; n++) {
+ __u16 x, y;
+
+ x = rd[offset+1] | ((rd[offset+2] & 0xF) << 8);
+ y = ((rd[offset+2] & 0xF0) >> 4) | (rd[offset+3] << 4);
+
+ input_mt_slot(input_dev, n);
+ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER,
+ !(rd[offset] >> 7));
+ input_report_abs(input_dev, ABS_MT_POSITION_X, x);
+ input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
+
+ offset += 4;
+ }
+}
+
+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) {
+ 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 & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 &&
+ size == 64) || ((sc->quirks & DUALSHOCK4_CONTROLLER_BT)
+ && rd[0] == 0x11 && size == 78)) {
+ dualshock4_parse_report(sc, rd, size);
+ }
+
+ 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);
+
+ /* Let hid-core decide for the others */
+ return 0;
+}
+
+static int sony_register_touchpad(struct hid_input *hi, int touch_count,
+ int w, int h)
+{
+ struct input_dev *input_dev = hi->input;
+ int ret;
+
+ ret = input_mt_init_slots(input_dev, touch_count, 0);
+ if (ret < 0)
+ return ret;
+
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, w, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, h, 0, 0);
+
+ return 0;
+}
+
+static void sony_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ struct sony_sc *sc = hid_get_drvdata(hdev);
+
+ /*
+ * The Dualshock 4 touchpad supports 2 touches and has a
+ * resolution of 1920x942 (44.86 dots/mm).
+ */
+ if (sc->quirks & DUALSHOCK4_CONTROLLER) {
+ if (sony_register_touchpad(hidinput, 2, 1920, 942) != 0)
+ hid_err(sc->hdev,
+ "Unable to initialize multi-touch slots\n");
+ }
+}
+
+/*
+ * 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;
+ }
+
+ ret = hid_hw_output_report(hdev, buf, 1);
+ if (ret < 0)
+ hid_err(hdev, "can't set operational mode: step 3\n");
+
+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;
+}
+
+/*
+ * Requesting feature report 0x02 in Bluetooth mode changes the state of the
+ * controller so that it sends full input reports of type 0x11.
+ */
+static int dualshock4_set_operational_bt(struct hid_device *hdev)
+{
+ __u8 *buf;
+ int ret;
+
+ buf = kmalloc(DS4_REPORT_0x02_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, 0x02, buf, DS4_REPORT_0x02_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static void sixaxis_set_leds_from_id(int id, __u8 values[MAX_LEDS])
+{
+ 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 }
+ };
+
+ BUG_ON(MAX_LEDS < ARRAY_SIZE(sixaxis_leds[0]));
+
+ if (id < 0)
+ return;
+
+ id %= 10;
+ memcpy(values, sixaxis_leds[id], sizeof(sixaxis_leds[id]));
+}
+
+static void dualshock4_set_leds_from_id(int id, __u8 values[MAX_LEDS])
+{
+ /* The first 4 color/index entries match what the PS4 assigns */
+ static const __u8 color_code[7][3] = {
+ /* Blue */ { 0x00, 0x00, 0x01 },
+ /* Red */ { 0x01, 0x00, 0x00 },
+ /* Green */ { 0x00, 0x01, 0x00 },
+ /* Pink */ { 0x02, 0x00, 0x01 },
+ /* Orange */ { 0x02, 0x01, 0x00 },
+ /* Teal */ { 0x00, 0x01, 0x01 },
+ /* White */ { 0x01, 0x01, 0x01 }
+ };
+
+ BUG_ON(MAX_LEDS < ARRAY_SIZE(color_code[0]));
+
+ if (id < 0)
+ return;
+
+ id %= 7;
+ memcpy(values, color_code[id], sizeof(color_code[id]));
+}
+
+static void buzz_set_leds(struct hid_device *hdev, const __u8 *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] = 0x00;
+ value[1] = leds[0] ? 0xff : 0x00;
+ value[2] = leds[1] ? 0xff : 0x00;
+ value[3] = leds[2] ? 0xff : 0x00;
+ value[4] = leds[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, const __u8 *leds, int count)
+{
+ int n;
+
+ BUG_ON(count > MAX_LEDS);
+
+ if (sc->quirks & BUZZ_CONTROLLER && count == 4) {
+ buzz_set_leds(sc->hdev, leds);
+ } else {
+ for (n = 0; n < count; n++)
+ sc->led_state[n] = leds[n];
+ schedule_work(&sc->state_worker);
+ }
+}
+
+static void sony_led_set_brightness(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct device *dev = led->dev->parent;
+ struct hid_device *hdev = container_of(dev, struct 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, drv_data->led_state,
+ drv_data->led_count);
+ break;
+ }
+ }
+}
+
+static enum led_brightness sony_led_get_brightness(struct led_classdev *led)
+{
+ struct device *dev = led->dev->parent;
+ struct hid_device *hdev = container_of(dev, struct 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 = container_of(dev, struct 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;
+ schedule_work(&drv_data->state_worker);
+ }
+
+ return 0;
+}
+
+static void sony_leds_remove(struct sony_sc *sc)
+{
+ struct led_classdev *led;
+ int n;
+
+ BUG_ON(!(sc->quirks & SONY_LED_SUPPORT));
+
+ for (n = 0; n < sc->led_count; n++) {
+ led = sc->leds[n];
+ sc->leds[n] = NULL;
+ if (!led)
+ continue;
+ led_classdev_unregister(led);
+ kfree(led);
+ }
+
+ sc->led_count = 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 initial_values[MAX_LEDS] = { 0 };
+ __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->device_id, initial_values);
+ initial_values[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 {
+ sixaxis_set_leds_from_id(sc->device_id, initial_values);
+ 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, initial_values, sc->led_count);
+
+ 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 = kzalloc(sizeof(struct led_classdev) + name_sz, GFP_KERNEL);
+ if (!led) {
+ hid_err(hdev, "Couldn't allocate memory for LED %d\n", n);
+ ret = -ENOMEM;
+ goto error_leds;
+ }
+
+ 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 = initial_values[n];
+ led->max_brightness = max_brightness[n];
+ 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 = led_classdev_register(&hdev->dev, led);
+ if (ret) {
+ hid_err(hdev, "Failed to register LED %d\n", n);
+ sc->leds[n] = NULL;
+ kfree(led);
+ goto error_leds;
+ }
+ }
+
+ return ret;
+
+error_leds:
+ sony_leds_remove(sc);
+
+ return ret;
+}
+
+static void sixaxis_state_worker(struct work_struct *work)
+{
+ static const union sixaxis_output_report_01 default_report = {
+ .buf = {
+ 0x01,
+ 0x00, 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 sony_sc *sc = container_of(work, struct sony_sc, state_worker);
+ 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_state_worker(struct work_struct *work)
+{
+ struct sony_sc *sc = container_of(work, struct sony_sc, state_worker);
+ struct hid_device *hdev = sc->hdev;
+ __u8 *buf = sc->output_report_dmabuf;
+ int offset;
+
+ if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) {
+ memset(buf, 0, DS4_REPORT_0x05_SIZE);
+ buf[0] = 0x05;
+ buf[1] = 0xFF;
+ offset = 4;
+ } else {
+ memset(buf, 0, DS4_REPORT_0x11_SIZE);
+ buf[0] = 0x11;
+ buf[1] = 0xB0;
+ buf[3] = 0x0F;
+ 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)
+ hid_hw_output_report(hdev, buf, DS4_REPORT_0x05_SIZE);
+ else
+ hid_hw_raw_request(hdev, 0x11, buf, DS4_REPORT_0x11_SIZE,
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+}
+
+static int sony_allocate_output_report(struct sony_sc *sc)
+{
+ if (sc->quirks & SIXAXIS_CONTROLLER)
+ sc->output_report_dmabuf =
+ kmalloc(sizeof(union sixaxis_output_report_01),
+ GFP_KERNEL);
+ else if (sc->quirks & DUALSHOCK4_CONTROLLER_BT)
+ sc->output_report_dmabuf = kmalloc(DS4_REPORT_0x11_SIZE,
+ GFP_KERNEL);
+ else if (sc->quirks & DUALSHOCK4_CONTROLLER_USB)
+ sc->output_report_dmabuf = kmalloc(DS4_REPORT_0x05_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;
+
+ schedule_work(&sc->state_worker);
+ 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)
+{
+ 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 = kasprintf(GFP_KERNEL,
+ "sony_controller_battery_%pMR",
+ sc->mac_address);
+ if (!sc->battery_desc.name)
+ return -ENOMEM;
+
+ sc->battery = 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");
+ goto err_free;
+ }
+
+ power_supply_powers(sc->battery, &hdev->dev);
+ return 0;
+
+err_free:
+ kfree(sc->battery_desc.name);
+ sc->battery_desc.name = NULL;
+ return ret;
+}
+
+static void sony_battery_remove(struct sony_sc *sc)
+{
+ if (!sc->battery_desc.name)
+ return;
+
+ power_supply_unregister(sc->battery);
+ kfree(sc->battery_desc.name);
+ sc->battery_desc.name = NULL;
+}
+
+/*
+ * 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.
+ */
+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) {
+ 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 & 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) {
+ buf = kmalloc(DS4_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_REPORT_0x81_SIZE, HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+
+ if (ret != DS4_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));
+ } else if (sc->quirks & SIXAXIS_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];
+ } 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_work(struct sony_sc *sc,
+ void (*worker)(struct work_struct *))
+{
+ if (!sc->worker_initialized)
+ INIT_WORK(&sc->state_worker, worker);
+
+ sc->worker_initialized = 1;
+}
+
+static inline void sony_cancel_work_sync(struct sony_sc *sc)
+{
+ if (sc->worker_initialized)
+ cancel_work_sync(&sc->state_worker);
+}
+
+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;
+
+ 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;
+
+ ret = hid_hw_start(hdev, connect_mask);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ ret = sony_allocate_output_report(sc);
+ if (ret < 0) {
+ hid_err(hdev, "failed to allocate the output report buffer\n");
+ goto err_stop;
+ }
+
+ ret = sony_set_device_id(sc);
+ if (ret < 0) {
+ hid_err(hdev, "failed to allocate the device id\n");
+ goto err_stop;
+ }
+
+ if (sc->quirks & SIXAXIS_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!
+ */
+ hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
+ hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
+ ret = sixaxis_set_operational_usb(hdev);
+ sony_init_work(sc, sixaxis_state_worker);
+ } 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);
+ sony_init_work(sc, sixaxis_state_worker);
+ } else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
+ if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) {
+ /*
+ * The DualShock 4 wants output reports sent on the ctrl
+ * endpoint when connected via Bluetooth.
+ */
+ hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
+ ret = dualshock4_set_operational_bt(hdev);
+ if (ret < 0) {
+ hid_err(hdev, "failed to set the Dualshock 4 operational mode\n");
+ goto err_stop;
+ }
+ }
+
+ sony_init_work(sc, dualshock4_state_worker);
+ } else {
+ ret = 0;
+ }
+
+ if (ret < 0)
+ goto err_stop;
+
+ ret = sony_check_add(sc);
+ if (ret < 0)
+ goto err_stop;
+
+ 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);
+ 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:
+ if (sc->quirks & SONY_LED_SUPPORT)
+ sony_leds_remove(sc);
+ if (sc->quirks & SONY_BATTERY_SUPPORT)
+ sony_battery_remove(sc);
+ sony_cancel_work_sync(sc);
+ kfree(sc->output_report_dmabuf);
+ sony_remove_dev_list(sc);
+ sony_release_device_id(sc);
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void sony_remove(struct hid_device *hdev)
+{
+ struct sony_sc *sc = hid_get_drvdata(hdev);
+
+ if (sc->quirks & SONY_LED_SUPPORT)
+ sony_leds_remove(sc);
+
+ if (sc->quirks & SONY_BATTERY_SUPPORT) {
+ hid_hw_close(hdev);
+ sony_battery_remove(sc);
+ }
+
+ sony_cancel_work_sync(sc);
+
+ kfree(sc->output_report_dmabuf);
+
+ sony_remove_dev_list(sc);
+
+ sony_release_device_id(sc);
+
+ hid_hw_stop(hdev);
+}
+
+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 = SIXAXIS_CONTROLLER_USB },
+ { 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 },
+ { }
+};
+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
+};
+
+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 000000000..7112f3e83
--- /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-steelseries.c b/drivers/hid/hid-steelseries.c
new file mode 100644
index 000000000..3edd4ac36
--- /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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 = container_of(dev, struct 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 000000000..91072fa54
--- /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-thingm.c b/drivers/hid/hid-thingm.c
new file mode 100644
index 000000000..b95d3978c
--- /dev/null
+++ b/drivers/hid/hid-thingm.c
@@ -0,0 +1,313 @@
+/*
+ * ThingM blink(1) USB RGB LED driver
+ *
+ * Copyright 2013-2014 Savoir-faire Linux Inc.
+ * Vivien Didelot <vivien.didelot@savoirfairelinux.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, version 2.
+ */
+
+#include <linux/hid.h>
+#include <linux/hidraw.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define REPORT_ID 1
+#define REPORT_SIZE 9
+
+/* Firmware major number of supported devices */
+#define THINGM_MAJOR_MK1 '1'
+#define THINGM_MAJOR_MK2 '2'
+
+struct thingm_fwinfo {
+ char major;
+ unsigned numrgb;
+ unsigned first;
+};
+
+static const struct thingm_fwinfo thingm_fwinfo[] = {
+ {
+ .major = THINGM_MAJOR_MK1,
+ .numrgb = 1,
+ .first = 0,
+ }, {
+ .major = THINGM_MAJOR_MK2,
+ .numrgb = 2,
+ .first = 1,
+ }
+};
+
+/* A red, green or blue channel, part of an RGB chip */
+struct thingm_led {
+ struct thingm_rgb *rgb;
+ struct led_classdev ldev;
+ char name[32];
+};
+
+/* Basically a WS2812 5050 RGB LED chip */
+struct thingm_rgb {
+ struct thingm_device *tdev;
+ struct thingm_led red;
+ struct thingm_led green;
+ struct thingm_led blue;
+ struct work_struct work;
+ u8 num;
+};
+
+struct thingm_device {
+ struct hid_device *hdev;
+ struct {
+ char major;
+ char minor;
+ } version;
+ const struct thingm_fwinfo *fwinfo;
+ struct mutex lock;
+ struct thingm_rgb *rgb;
+};
+
+static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
+{
+ int ret;
+
+ hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4],
+ buf[5], buf[6], buf[7], buf[8]);
+
+ ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
+{
+ int ret;
+
+ ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret < 0)
+ return ret;
+
+ hid_dbg(tdev->hdev, "<- %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4],
+ buf[5], buf[6], buf[7], buf[8]);
+
+ return 0;
+}
+
+static int thingm_version(struct thingm_device *tdev)
+{
+ u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 };
+ int err;
+
+ err = thingm_send(tdev, buf);
+ if (err)
+ return err;
+
+ err = thingm_recv(tdev, buf);
+ if (err)
+ return err;
+
+ tdev->version.major = buf[3];
+ tdev->version.minor = buf[4];
+
+ return 0;
+}
+
+static int thingm_write_color(struct thingm_rgb *rgb)
+{
+ u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 };
+
+ buf[2] = rgb->red.ldev.brightness;
+ buf[3] = rgb->green.ldev.brightness;
+ buf[4] = rgb->blue.ldev.brightness;
+
+ return thingm_send(rgb->tdev, buf);
+}
+
+static void thingm_work(struct work_struct *work)
+{
+ struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work);
+
+ mutex_lock(&rgb->tdev->lock);
+
+ if (thingm_write_color(rgb))
+ hid_err(rgb->tdev->hdev, "failed to write color\n");
+
+ mutex_unlock(&rgb->tdev->lock);
+}
+
+static void thingm_led_set(struct led_classdev *ldev,
+ enum led_brightness brightness)
+{
+ struct thingm_led *led = container_of(ldev, struct thingm_led, ldev);
+
+ /* the ledclass has already stored the brightness value */
+ schedule_work(&led->rgb->work);
+}
+
+static int thingm_init_rgb(struct thingm_rgb *rgb)
+{
+ const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor;
+ int err;
+
+ /* Register the red diode */
+ snprintf(rgb->red.name, sizeof(rgb->red.name),
+ "thingm%d:red:led%d", minor, rgb->num);
+ rgb->red.ldev.name = rgb->red.name;
+ rgb->red.ldev.max_brightness = 255;
+ rgb->red.ldev.brightness_set = thingm_led_set;
+ rgb->red.rgb = rgb;
+
+ err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->red.ldev);
+ if (err)
+ return err;
+
+ /* Register the green diode */
+ snprintf(rgb->green.name, sizeof(rgb->green.name),
+ "thingm%d:green:led%d", minor, rgb->num);
+ rgb->green.ldev.name = rgb->green.name;
+ rgb->green.ldev.max_brightness = 255;
+ rgb->green.ldev.brightness_set = thingm_led_set;
+ rgb->green.rgb = rgb;
+
+ err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->green.ldev);
+ if (err)
+ goto unregister_red;
+
+ /* Register the blue diode */
+ snprintf(rgb->blue.name, sizeof(rgb->blue.name),
+ "thingm%d:blue:led%d", minor, rgb->num);
+ rgb->blue.ldev.name = rgb->blue.name;
+ rgb->blue.ldev.max_brightness = 255;
+ rgb->blue.ldev.brightness_set = thingm_led_set;
+ rgb->blue.rgb = rgb;
+
+ err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->blue.ldev);
+ if (err)
+ goto unregister_green;
+
+ INIT_WORK(&rgb->work, thingm_work);
+
+ return 0;
+
+unregister_green:
+ led_classdev_unregister(&rgb->green.ldev);
+
+unregister_red:
+ led_classdev_unregister(&rgb->red.ldev);
+
+ return err;
+}
+
+static void thingm_remove_rgb(struct thingm_rgb *rgb)
+{
+ led_classdev_unregister(&rgb->red.ldev);
+ led_classdev_unregister(&rgb->green.ldev);
+ led_classdev_unregister(&rgb->blue.ldev);
+ flush_work(&rgb->work);
+}
+
+static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct thingm_device *tdev;
+ int i, err;
+
+ tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device),
+ GFP_KERNEL);
+ if (!tdev)
+ return -ENOMEM;
+
+ tdev->hdev = hdev;
+ hid_set_drvdata(hdev, tdev);
+
+ err = hid_parse(hdev);
+ if (err)
+ goto error;
+
+ err = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (err)
+ goto error;
+
+ mutex_init(&tdev->lock);
+
+ err = thingm_version(tdev);
+ if (err)
+ goto stop;
+
+ hid_dbg(hdev, "firmware version: %c.%c\n",
+ tdev->version.major, tdev->version.minor);
+
+ for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i)
+ if (thingm_fwinfo[i].major == tdev->version.major)
+ tdev->fwinfo = &thingm_fwinfo[i];
+
+ if (!tdev->fwinfo) {
+ hid_err(hdev, "unsupported firmware %c\n", tdev->version.major);
+ err = -ENODEV;
+ goto stop;
+ }
+
+ tdev->rgb = devm_kzalloc(&hdev->dev,
+ sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb,
+ GFP_KERNEL);
+ if (!tdev->rgb) {
+ err = -ENOMEM;
+ goto stop;
+ }
+
+ for (i = 0; i < tdev->fwinfo->numrgb; ++i) {
+ struct thingm_rgb *rgb = tdev->rgb + i;
+
+ rgb->tdev = tdev;
+ rgb->num = tdev->fwinfo->first + i;
+ err = thingm_init_rgb(rgb);
+ if (err) {
+ while (--i >= 0)
+ thingm_remove_rgb(tdev->rgb + i);
+ goto stop;
+ }
+ }
+
+ return 0;
+stop:
+ hid_hw_stop(hdev);
+error:
+ return err;
+}
+
+static void thingm_remove(struct hid_device *hdev)
+{
+ struct thingm_device *tdev = hid_get_drvdata(hdev);
+ int i;
+
+ hid_hw_stop(hdev);
+
+ for (i = 0; i < tdev->fwinfo->numrgb; ++i)
+ thingm_remove_rgb(tdev->rgb + i);
+}
+
+static const struct hid_device_id thingm_table[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, thingm_table);
+
+static struct hid_driver thingm_driver = {
+ .name = "thingm",
+ .probe = thingm_probe,
+ .remove = thingm_remove,
+ .id_table = thingm_table,
+};
+
+module_hid_driver(thingm_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Vivien Didelot <vivien.didelot@savoirfairelinux.com>");
+MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver");
diff --git a/drivers/hid/hid-tivo.c b/drivers/hid/hid-tivo.c
new file mode 100644
index 000000000..d98696927
--- /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 000000000..b83376077
--- /dev/null
+++ b/drivers/hid/hid-tmff.c
@@ -0,0 +1,264 @@
+/*
+ * 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, 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 000000000..8a5b843e9
--- /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 000000000..c08c36443
--- /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 000000000..94167310e
--- /dev/null
+++ b/drivers/hid/hid-uclogic.c
@@ -0,0 +1,978 @@
+/*
+ * 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"
+
+/*
+ * See WPXXXXU model descriptions, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP4030U
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP5540U
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP8060U
+ */
+
+/* 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 */
+};
+
+/*
+ * See WP1062 description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP1062
+ */
+
+/* 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 */
+};
+
+/*
+ * See PF1209 description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_PF1209
+ */
+
+/* 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 */
+};
+
+/*
+ * See TWHL850 description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Wireless_Tablet_TWHL850
+ */
+
+/* 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 */
+};
+
+/*
+ * See TWHA60 description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_TWHA60
+ */
+
+/* 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 */
+};
+
+/* 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;
+};
+
+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);
+
+ 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;
+ default:
+ if (drvdata->rdesc != NULL) {
+ rdesc = drvdata->rdesc;
+ *rsize = drvdata->rsize;
+ }
+ }
+
+ 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 void 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;
+
+ 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;
+ }
+ }
+}
+
+/**
+ * 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) {
+ hid_err(hdev, "failed to allocate parameter buffer\n");
+ 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) {
+ hid_err(hdev, "failed to allocate fixed rdesc\n");
+ 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] < sizeof(params)) {
+ v = params[p[3]];
+ put_unaligned(cpu_to_le32(v), (s32 *)p);
+ p += 4;
+ } else {
+ p++;
+ }
+ }
+
+ rc = 0;
+
+cleanup:
+ kfree(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 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;
+ hdev->quirks |= HID_QUIRK_NO_EMPTY_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:
+ /* 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;
+ } 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 ((drvdata->invert_pen_inrange) &&
+ (report->type == HID_INPUT_REPORT) &&
+ (report->id == UCLOGIC_PEN_REPORT_ID) &&
+ (size >= 2))
+ /* 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) },
+ { }
+};
+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-waltop.c b/drivers/hid/hid-waltop.c
new file mode 100644
index 000000000..059931d7b
--- /dev/null
+++ b/drivers/hid/hid-waltop.c
@@ -0,0 +1,784 @@
+/*
+ * 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
+ */
+
+/*
+ * See Slim Tablet 5.8 inch description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Slim_Tablet_5.8%22
+ */
+
+/* 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 */
+};
+
+/*
+ * See Slim Tablet 12.1 inch description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Slim_Tablet_12.1%22
+ */
+
+/* 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 */
+};
+
+/*
+ * See Q Pad description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Q_Pad
+ */
+
+/* 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 */
+};
+
+/*
+ * See description, device and HID report descriptors of tablet with PID 0038 at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_PID_0038
+ */
+
+/* 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 */
+};
+
+/*
+ * See Media Tablet 10.6 inch description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Media_Tablet_10.6%22
+ */
+
+/* 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 */
+};
+
+/*
+ * See Media Tablet 14.1 inch description, device and HID report descriptors at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Media_Tablet_14.1%22
+ */
+
+/* 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 */
+};
+
+/*
+ * See Sirius Battery Free Tablet description, device and HID report descriptors
+ * at
+ * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Sirius_Battery_Free_Tablet
+ */
+
+/* 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 000000000..d00391418
--- /dev/null
+++ b/drivers/hid/hid-wiimote-core.c
@@ -0,0 +1,1875 @@
+/*
+ * 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;
+
+ 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:
+ 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",
+};
+
+/*
+ * 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(unsigned long arg)
+{
+ struct wiimote_data *wdata = (void*)arg;
+
+ 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_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);
+ setup_timer(&wdata->timer, wiimote_init_timeout, (long)wdata);
+
+ 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 000000000..c13fb5bd7
--- /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 000000000..05e23c417
--- /dev/null
+++ b/drivers/hid/hid-wiimote-modules.c
@@ -0,0 +1,2206 @@
+/*
+ * 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 wiimote_data *wdata;
+ struct device *dev = led_dev->dev->parent;
+ int i;
+ unsigned long flags;
+ bool value = false;
+
+ wdata = hid_get_drvdata(container_of(dev, struct hid_device, dev));
+
+ 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 wiimote_data *wdata;
+ struct device *dev = led_dev->dev->parent;
+ int i;
+ unsigned long flags;
+ __u8 state, flag;
+
+ wdata = hid_get_drvdata(container_of(dev, struct hid_device, dev));
+
+ 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,
+};
+
+/*
+ * 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 440 deg/s and for fast rotation 2000 deg/s. To get a
+ * linear scale we multiply by 2000/440 = ~4.5454 which is 18 for fast
+ * and 9 for slow.
+ * 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 *= 18;
+ else
+ x *= 9;
+ if (!(ext[4] & 0x02))
+ y *= 18;
+ else
+ y *= 9;
+ if (!(ext[3] & 0x01))
+ z *= 18;
+ 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,
+};
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
new file mode 100644
index 000000000..875694d43
--- /dev/null
+++ b/drivers/hid/hid-wiimote.h
@@ -0,0 +1,376 @@
+#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_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 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(container_of(pdev, struct hid_device, \
+ dev))
+
+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 000000000..7df5227a7
--- /dev/null
+++ b/drivers/hid/hid-xinmo.c
@@ -0,0 +1,61 @@
+/*
+ * 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) },
+ { }
+};
+
+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 000000000..a29756c6c
--- /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 000000000..1a660bd97
--- /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 000000000..9c2d7c23f
--- /dev/null
+++ b/drivers/hid/hidraw.c
@@ -0,0 +1,628 @@
+/*
+ * 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.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 = kmalloc(count * sizeof(__u8), GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (copy_from_user(buf, buffer, count)) {
+ ret = -EFAULT;
+ goto out_free;
+ }
+
+ 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;
+
+ 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 * sizeof(__u8), 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 unsigned int 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 POLLIN | POLLRDNORM;
+ if (!list->hidraw->exist)
+ return POLLERR | POLLHUP;
+ 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_power(hidraw->hid, PM_HINT_NORMAL);
+ hid_hw_close(hidraw->hid);
+ }
+ }
+}
+
+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");
+
+ hidraw_major = MAJOR(dev_id);
+
+ if (result < 0) {
+ pr_warn("can't get major number\n");
+ goto out;
+ }
+
+ 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/huiontablet.c b/drivers/hid/huiontablet.c
new file mode 100644
index 000000000..9996032e1
--- /dev/null
+++ b/drivers/hid/huiontablet.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2013 Tan Huang,Shenzhen Huion
+ *
+ * USB HID Tablet support
+ */
+
+
+
+#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>
+#include <asm/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/cdev.h>
+#include <linux/miscdevice.h>
+#define DEVICE_NAME "huiontablet"
+
+/* for apple IDs */
+#ifdef CONFIG_USB_HID_MODULE
+#include "./hid-ids.h"
+#endif
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v1.0"
+#define DRIVER_AUTHOR "Tan Huang <tanhuang@huion.cn>"
+#define DRIVER_DESC "USB HID Boot Protocol tablet driver"
+#define DRIVER_LICENSE "GPL"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+struct usb_mouse {
+ char name[128];
+ char phys[64];
+ struct usb_device *usbdev;
+ struct input_dev *dev;
+ struct urb *irq;
+
+ signed char *data;
+ dma_addr_t data_dma;
+};
+
+
+
+unsigned short int pos_x=0,pos_y=0,pos_z=0;
+
+//static struct miscdevice misc = {
+// .minor = MISC_DYNAMIC_MINOR,
+// .name = DEVICE_NAME,
+// .fops = &dev_fops,
+//};
+
+
+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;
+ //unsigned short int pos_x=0,pos_y=0,pos_z=0;
+ // printk(KERN_NOTICE "enter usb_mouse_irq function\n");
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+ case -ECONNRESET: /* unlink */
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ /* -EPIPE: should clear the halt */
+ default: /* error */
+ goto resubmit;
+ }
+
+ pos_x =(data[3] & 0x0FF)<<8;
+ pos_x |=(data[2] & 0x0FF);
+ pos_y =(data[5] & 0x0FF)<<8;
+ pos_y |=(data[4] & 0x0FF);
+ pos_z =(data[7] & 0x0FF)<<8;
+ pos_z |=(data[6] & 0x0FF);
+ printk(KERN_NOTICE "pos_x=%x,pos_y=%x,pos_z=%x,button=%x\n",pos_x,pos_y,pos_z,(data[1] & 0x0F));
+ input_report_abs(dev, ABS_X, pos_x);
+ input_report_abs(dev, ABS_Y, pos_y);
+ input_report_key(dev, BTN_TOOL_PEN,1);
+ input_report_abs(dev, ABS_PRESSURE,pos_z);
+ input_report_key(dev, BTN_LEFT,data[1] & 0x01);
+ input_report_key(dev, BTN_TOUCH,data[1] & 0x01);
+ input_report_key(dev, BTN_MIDDLE,data[1] & 0x02);
+ input_report_key(dev, BTN_RIGHT,data[1] & 0x04);
+
+ input_sync(dev);
+resubmit:
+ status = usb_submit_urb (urb, GFP_ATOMIC);
+ /* Not sure what the function 'err' would have been, but holy shit this code is UGLY.
+ Do people really still use the goto statement?
+ if (status)
+ err ("can't resubmit intr, %s-%s/input0, status %d",
+ mouse->usbdev->bus->bus_name,
+ mouse->usbdev->devpath, status);
+ // printk(KERN_NOTICE "quit usb_mouse_irq function\n");
+ */
+}
+
+static int usb_mouse_open(struct input_dev *dev)
+{
+ struct usb_mouse *mouse = input_get_drvdata(dev);
+ // printk(KERN_NOTICE "enter usinput_report_key(dev, BTN_TOUCH,(pos_z>0)?1:0); b_mouse_open function\n");
+ mouse->irq->dev = mouse->usbdev;
+ if (usb_submit_urb(mouse->irq, GFP_KERNEL))
+ return -EIO;
+ // printk(KERN_NOTICE "quit usb_mouse_open function\n");
+ return 0;
+}
+
+static void usb_mouse_close(struct input_dev *dev)
+{
+ struct usb_mouse *mouse = input_get_drvdata(dev);
+ // printk(KERN_NOTICE "enter usb_mouse_close function\n");
+ usb_kill_urb(mouse->irq);
+ // printk(KERN_NOTICE "quit usb_mouse_close function\n");
+}
+
+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;
+ // printk(KERN_NOTICE "enter usb_mouse_probe function\n");
+ 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;
+
+ set_bit(EV_MSC, input_dev->evbit);
+ set_bit(EV_KEY, input_dev->evbit);
+ set_bit(EV_ABS, input_dev->evbit);
+ set_bit(ABS_X, input_dev->absbit);
+ set_bit(ABS_Y, input_dev->absbit);
+ set_bit(ABS_PRESSURE, input_dev->absbit);
+ set_bit(BTN_TOUCH, input_dev->keybit);
+ set_bit(BTN_TOOL_PEN, input_dev->keybit);
+ set_bit(BTN_LEFT, input_dev->keybit);
+ set_bit(BTN_MIDDLE, input_dev->keybit);
+ set_bit(BTN_RIGHT, input_dev->keybit);
+ set_bit(MSC_SERIAL, input_dev->mscbit);
+
+ input_set_drvdata(input_dev, mouse);
+
+ input_set_abs_params(input_dev, ABS_X, 0, 0x7ff, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 0x7ff, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1024, 0, 0);
+
+ 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);
+ // printk(KERN_NOTICE "quit usb_mouse_probe function\n");
+ return error;
+}
+
+static void usb_mouse_disconnect(struct usb_interface *intf)
+{
+ struct usb_mouse *mouse = usb_get_intfdata (intf);
+ //printk(KERN_NOTICE "enter usb_mouse_disconnect function\n");
+ 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);
+ }
+ // misc_deregister(&misc);
+ // printk(KERN_NOTICE "quit usb_mouse_disconnect function\n");
+}
+
+static struct usb_device_id usb_mouse_id_table [] = {
+ //{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_MOUSE) },
+ { USB_DEVICE(0x256C, 0x0005) },
+ { USB_DEVICE(0x256C, 0x006E) },
+ { USB_DEVICE(0x5543, 0x0005) },
+ { USB_DEVICE(0x5543, 0x006E) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
+
+
+static struct usb_driver usb_mouse_driver = {
+ .name = "usbmouse",
+ .probe = usb_mouse_probe,
+ .disconnect = usb_mouse_disconnect,
+ .id_table = usb_mouse_id_table,
+};
+
+static int __init usb_mouse_init(void)
+{
+ int retval = usb_register(&usb_mouse_driver);
+ if (retval == 0)
+ printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
+ DRIVER_DESC "\n");
+ return retval;
+}
+
+static void __exit usb_mouse_exit(void)
+{
+ usb_deregister(&usb_mouse_driver);
+}
+
+module_init(usb_mouse_init);
+module_exit(usb_mouse_exit);
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
new file mode 100644
index 000000000..b66617a02
--- /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 000000000..832d8f9aa
--- /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 000000000..92d6cdf02
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -0,0 +1,1187 @@
+/*
+ * 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/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/gpio/consumer.h>
+
+#include <linux/i2c/i2c-hid.h>
+
+/* flags */
+#define I2C_HID_STARTED (1 << 0)
+#define I2C_HID_RESET_PENDING (1 << 1)
+#define I2C_HID_READ_PENDING (1 << 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) };
+ */
+
+static DEFINE_MUTEX(i2c_hid_open_mut);
+
+/* 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 */
+ char *inbuf; /* Input buffer */
+ char *rawbuf; /* Raw Input buffer */
+ char *cmdbuf; /* Command buffer */
+ char *argsbuf; /* Command arguments buffer */
+
+ unsigned long flags; /* device flags */
+
+ wait_queue_head_t wait; /* For waiting the interrupt */
+ struct gpio_desc *desc;
+ int irq;
+
+ struct i2c_hid_platform_data pdata;
+};
+
+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) {
+ 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);
+
+ /* hid_hw_* already checked that data_len < HID_MAX_BUFFER_SIZE */
+ u16 size = 2 /* size */ +
+ (reportID ? 1 : 0) /* reportID */ +
+ data_len /* buf */;
+ int args_len = (reportID >= 0x0F ? 1 : 0) /* optional third byte */ +
+ 2 /* dataRegister */ +
+ size /* args */;
+ int index = 0;
+
+ i2c_hid_dbg(ihid, "%s\n", __func__);
+
+ 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;
+
+ i2c_hid_dbg(ihid, "%s\n", __func__);
+
+ ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
+ 0, NULL, 0, NULL, 0);
+ if (ret)
+ dev_err(&client->dev, "failed to change power setting.\n");
+
+ 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__);
+
+ ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
+ if (ret)
+ return ret;
+
+ 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);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void i2c_hid_get_input(struct i2c_hid *ihid)
+{
+ int ret, 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) {
+ 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;
+}
+
+static void i2c_hid_init_report(struct hid_report *report, u8 *buffer,
+ size_t bufsize)
+{
+ struct hid_device *hid = report->device;
+ struct i2c_client *client = hid->driver_data;
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+ unsigned int size, ret_size;
+
+ size = i2c_hid_get_report_length(report);
+ if (i2c_hid_get_report(client,
+ report->type == HID_FEATURE_REPORT ? 0x03 : 0x01,
+ report->id, buffer, size))
+ return;
+
+ i2c_hid_dbg(ihid, "report (len=%d): %*ph\n", size, size, buffer);
+
+ ret_size = buffer[0] | (buffer[1] << 8);
+
+ if (ret_size != size) {
+ dev_err(&client->dev, "error in %s size:%d / ret_size:%d\n",
+ __func__, size, ret_size);
+ return;
+ }
+
+ /* hid->driver_lock is held as we are in probe function,
+ * we just need to setup the input fields, so using
+ * hid_report_raw_event is safe. */
+ hid_report_raw_event(hid, report->type, buffer + 2, size - 2, 1);
+}
+
+/*
+ * Initialize all reports
+ */
+static void i2c_hid_init_reports(struct hid_device *hid)
+{
+ struct hid_report *report;
+ struct i2c_client *client = hid->driver_data;
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+ u8 *inbuf = kzalloc(ihid->bufsize, GFP_KERNEL);
+
+ if (!inbuf) {
+ dev_err(&client->dev, "can not retrieve initial reports\n");
+ return;
+ }
+
+ /*
+ * The device must be powered on while we fetch initial reports
+ * from it.
+ */
+ pm_runtime_get_sync(&client->dev);
+
+ list_for_each_entry(report,
+ &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
+ i2c_hid_init_report(report, inbuf, ihid->bufsize);
+
+ pm_runtime_put(&client->dev);
+
+ kfree(inbuf);
+}
+
+/*
+ * 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) + /* 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;
+ int report_id = buf[0];
+ int ret;
+
+ if (report_type == HID_INPUT_REPORT)
+ return -EINVAL;
+
+ 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 */
+
+ 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) {
+ i2c_hid_free_buffers(ihid);
+
+ ret = i2c_hid_alloc_buffers(ihid, bufsize);
+
+ if (ret)
+ return ret;
+ }
+
+ if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
+ i2c_hid_init_reports(hid);
+
+ 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;
+
+ mutex_lock(&i2c_hid_open_mut);
+ if (!hid->open++) {
+ ret = pm_runtime_get_sync(&client->dev);
+ if (ret < 0) {
+ hid->open--;
+ goto done;
+ }
+ set_bit(I2C_HID_STARTED, &ihid->flags);
+ }
+done:
+ mutex_unlock(&i2c_hid_open_mut);
+ return ret < 0 ? ret : 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);
+
+ /* protecting hid->open to make sure we don't restart
+ * data acquistion due to a resumption we no longer
+ * care about
+ */
+ mutex_lock(&i2c_hid_open_mut);
+ if (!--hid->open) {
+ clear_bit(I2C_HID_STARTED, &ihid->flags);
+
+ /* Save some power */
+ pm_runtime_put(&client->dev);
+ }
+ mutex_unlock(&i2c_hid_open_mut);
+}
+
+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;
+}
+
+static 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,
+};
+
+static int i2c_hid_init_irq(struct i2c_client *client)
+{
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+ int ret;
+
+ dev_dbg(&client->dev, "Requesting IRQ: %d\n", ihid->irq);
+
+ ret = request_threaded_irq(ihid->irq, NULL, i2c_hid_irq,
+ IRQF_TRIGGER_LOW | 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, ihid->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
+
+/* Default GPIO mapping */
+static const struct acpi_gpio_params i2c_hid_irq_gpio = { 0, 0, true };
+static const struct acpi_gpio_mapping i2c_hid_acpi_gpios[] = {
+ { "gpios", &i2c_hid_irq_gpio, 1 },
+ { },
+};
+
+static int i2c_hid_acpi_pdata(struct i2c_client *client,
+ struct i2c_hid_platform_data *pdata)
+{
+ static u8 i2c_hid_guid[] = {
+ 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
+ 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
+ };
+ union acpi_object *obj;
+ struct acpi_device *adev;
+ acpi_handle handle;
+ int ret;
+
+ handle = ACPI_HANDLE(&client->dev);
+ if (!handle || acpi_bus_get_device(handle, &adev))
+ return -ENODEV;
+
+ obj = acpi_evaluate_dsm_typed(handle, i2c_hid_guid, 1, 1, NULL,
+ ACPI_TYPE_INTEGER);
+ if (!obj) {
+ dev_err(&client->dev, "device _DSM execution failed\n");
+ return -ENODEV;
+ }
+
+ pdata->hid_descriptor_address = obj->integer.value;
+ ACPI_FREE(obj);
+
+ /* GPIOs are optional */
+ ret = acpi_dev_add_driver_gpios(adev, i2c_hid_acpi_gpios);
+ return ret < 0 && ret != -ENXIO ? ret : 0;
+}
+
+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;
+}
+#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 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);
+
+ ihid = kzalloc(sizeof(struct i2c_hid), GFP_KERNEL);
+ if (!ihid)
+ return -ENOMEM;
+
+ if (client->dev.of_node) {
+ ret = i2c_hid_of_probe(client, &ihid->pdata);
+ if (ret)
+ goto err;
+ } else if (!platform_data) {
+ ret = i2c_hid_acpi_pdata(client, &ihid->pdata);
+ if (ret) {
+ dev_err(&client->dev,
+ "HID register address not provided\n");
+ goto err;
+ }
+ } else {
+ ihid->pdata = *platform_data;
+ }
+
+ if (client->irq > 0) {
+ ihid->irq = client->irq;
+ } else if (ACPI_COMPANION(&client->dev)) {
+ ihid->desc = gpiod_get(&client->dev, NULL, GPIOD_IN);
+ if (IS_ERR(ihid->desc)) {
+ dev_err(&client->dev, "Failed to get GPIO interrupt\n");
+ return PTR_ERR(ihid->desc);
+ }
+
+ ihid->irq = gpiod_to_irq(ihid->desc);
+ if (ihid->irq < 0) {
+ gpiod_put(ihid->desc);
+ dev_err(&client->dev, "Failed to convert GPIO to IRQ\n");
+ return ihid->irq;
+ }
+ }
+
+ 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);
+
+ /* 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;
+
+ pm_runtime_get_noresume(&client->dev);
+ pm_runtime_set_active(&client->dev);
+ pm_runtime_enable(&client->dev);
+
+ 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;
+ ACPI_COMPANION_SET(&hid->dev, ACPI_COMPANION(&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);
+
+ 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;
+ }
+
+ pm_runtime_put(&client->dev);
+ return 0;
+
+err_mem_free:
+ hid_destroy_device(hid);
+
+err_irq:
+ free_irq(ihid->irq, ihid);
+
+err_pm:
+ pm_runtime_put_noidle(&client->dev);
+ pm_runtime_disable(&client->dev);
+
+err:
+ if (ihid->desc)
+ gpiod_put(ihid->desc);
+
+ i2c_hid_free_buffers(ihid);
+ kfree(ihid);
+ return ret;
+}
+
+static int i2c_hid_remove(struct i2c_client *client)
+{
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+ struct hid_device *hid;
+
+ 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(ihid->irq, ihid);
+
+ if (ihid->bufsize)
+ i2c_hid_free_buffers(ihid);
+
+ if (ihid->desc)
+ gpiod_put(ihid->desc);
+
+ kfree(ihid);
+
+ acpi_dev_remove_driver_gpios(ACPI_COMPANION(&client->dev));
+
+ return 0;
+}
+
+#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 = 0;
+
+ disable_irq(ihid->irq);
+ if (device_may_wakeup(&client->dev))
+ enable_irq_wake(ihid->irq);
+
+ if (hid->driver && hid->driver->suspend)
+ ret = hid->driver->suspend(hid, PMSG_SUSPEND);
+
+ /* Save some power */
+ i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+
+ return ret;
+}
+
+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;
+
+ enable_irq(ihid->irq);
+ ret = i2c_hid_hwreset(client);
+ if (ret)
+ return ret;
+
+ if (device_may_wakeup(&client->dev))
+ disable_irq_wake(ihid->irq);
+
+ 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);
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+ i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+ disable_irq(ihid->irq);
+ return 0;
+}
+
+static int i2c_hid_runtime_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+ enable_irq(ihid->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 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, i2c_hid_id_table);
+
+
+static struct i2c_driver i2c_hid_driver = {
+ .driver = {
+ .name = "i2c_hid",
+ .owner = THIS_MODULE,
+ .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,
+
+ .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/uhid.c b/drivers/hid/uhid.c
new file mode 100644
index 000000000..e094c572b
--- /dev/null
+++ b/drivers/hid/uhid.c
@@ -0,0 +1,784 @@
+/*
+ * 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/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;
+};
+
+static struct miscdevice uhid_misc;
+
+static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
+{
+ __u8 newhead;
+
+ newhead = (uhid->head + 1) % UHID_BUFSIZE;
+
+ if (newhead != uhid->tail) {
+ uhid->outq[uhid->head] = ev;
+ uhid->head = newhead;
+ wake_up_interruptible(&uhid->waitq);
+ } else {
+ hid_warn(uhid->hid, "Output queue is full\n");
+ kfree(ev);
+ }
+}
+
+static int uhid_queue_event(struct uhid_device *uhid, __u32 event)
+{
+ unsigned long flags;
+ struct uhid_event *ev;
+
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ return -ENOMEM;
+
+ ev->type = event;
+
+ spin_lock_irqsave(&uhid->qlock, flags);
+ uhid_queue(uhid, ev);
+ spin_unlock_irqrestore(&uhid->qlock, flags);
+
+ return 0;
+}
+
+static int uhid_hid_start(struct hid_device *hid)
+{
+ struct uhid_device *uhid = hid->driver_data;
+ 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);
+}
+
+static 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,
+};
+
+#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 (is_compat_task()) {
+ 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;
+ }
+
+ 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;
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ hid_err(hid, "Cannot register HID device\n");
+ goto err_hid;
+ }
+
+ return 0;
+
+err_hid:
+ hid_destroy_device(hid);
+ uhid->hid = NULL;
+ uhid->running = false;
+err_free:
+ kfree(uhid->rd_data);
+ 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);
+
+ 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;
+
+ 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:
+ 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 unsigned int uhid_char_poll(struct file *file, poll_table *wait)
+{
+ struct uhid_device *uhid = file->private_data;
+
+ poll_wait(file, &uhid->waitq, wait);
+
+ if (uhid->head != uhid->tail)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static const struct file_operations uhid_fops = {
+ .owner = THIS_MODULE,
+ .open = uhid_char_open,
+ .release = uhid_char_release,
+ .read = uhid_char_read,
+ .write = uhid_char_write,
+ .poll = uhid_char_poll,
+ .llseek = no_llseek,
+};
+
+static struct miscdevice uhid_misc = {
+ .fops = &uhid_fops,
+ .minor = UHID_MINOR,
+ .name = UHID_NAME,
+};
+
+static int __init uhid_init(void)
+{
+ return misc_register(&uhid_misc);
+}
+
+static void __exit uhid_exit(void)
+{
+ misc_deregister(&uhid_misc);
+}
+
+module_init(uhid_init);
+module_exit(uhid_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem");
+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 000000000..0108c5991
--- /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.txt>.
+
+ If unsure, say Y.
+
+ To compile this driver as a module, choose M here: the
+ module will be called usbhid.
+
+comment "Input core support is needed for USB HID input layer or HIDBP support"
+ depends on USB_HID && INPUT=n
+
+config 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 000000000..890f2914a
--- /dev/null
+++ b/drivers/hid/usbhid/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the USB input drivers
+#
+
+usbhid-y := hid-core.o hid-quirks.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 000000000..bfbe1bedd
--- /dev/null
+++ b/drivers/hid/usbhid/hid-core.c
@@ -0,0 +1,1661 @@
+/*
+ * 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"
+#define DRIVER_LICENSE "GPL"
+
+/*
+ * 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 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 DEFINE_MUTEX(hid_open_mut);
+
+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 ((hid->open > 0 || hid->quirks & HID_QUIRK_ALWAYS_POLL) &&
+ !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(unsigned long _hid)
+{
+ struct hid_device *hid = (struct hid_device *) _hid;
+ struct usbhid_device *usbhid = hid->driver_data;
+
+ dev_dbg(&usbhid->intf->dev, "retrying intr urb\n");
+ if (hid_start_in(hid))
+ hid_io_error(hid);
+}
+
+/* Workqueue routine to reset the device or clear a halt */
+static void hid_reset(struct work_struct *work)
+{
+ struct usbhid_device *usbhid =
+ container_of(work, struct usbhid_device, reset_work);
+ struct hid_device *hid = usbhid->hid;
+ int rc;
+
+ 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_mark_busy(usbhid);
+ usbhid->retry_delay = 0;
+ if ((hid->quirks & HID_QUIRK_ALWAYS_POLL) && !hid->open)
+ break;
+ 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;
+ int unplug = 0, status = urb->status;
+
+ spin_lock(&usbhid->lock);
+
+ 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);
+ }
+
+ 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(&usbhid->lock);
+ return;
+ }
+ }
+
+ clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+ spin_unlock(&usbhid->lock);
+ 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;
+}
+
+int usbhid_open(struct hid_device *hid)
+{
+ struct usbhid_device *usbhid = hid->driver_data;
+ int res = 0;
+
+ mutex_lock(&hid_open_mut);
+ if (!hid->open++) {
+ res = usb_autopm_get_interface(usbhid->intf);
+ /* the device must be awake to reliably request remote wakeup */
+ if (res < 0) {
+ hid->open--;
+ res = -EIO;
+ goto done;
+ }
+ usbhid->intf->needs_remote_wakeup = 1;
+ set_bit(HID_RESUME_RUNNING, &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 */
+ hid->open--;
+ res = -EBUSY;
+ 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.
+ */
+ msleep(50);
+ clear_bit(HID_RESUME_RUNNING, &usbhid->iofl);
+ }
+done:
+ mutex_unlock(&hid_open_mut);
+ return res;
+}
+
+void usbhid_close(struct hid_device *hid)
+{
+ struct usbhid_device *usbhid = hid->driver_data;
+
+ mutex_lock(&hid_open_mut);
+
+ /* protecting hid->open to make sure we don't restart
+ * data acquistion due to a resumption we no longer
+ * care about
+ */
+ spin_lock_irq(&usbhid->lock);
+ if (!--hid->open) {
+ spin_unlock_irq(&usbhid->lock);
+ hid_cancel_delayed_stuff(usbhid);
+ if (!(hid->quirks & HID_QUIRK_ALWAYS_POLL)) {
+ usb_kill_urb(usbhid->urbin);
+ usbhid->intf->needs_remote_wakeup = 0;
+ }
+ } else {
+ spin_unlock_irq(&usbhid->lock);
+ }
+ mutex_unlock(&hid_open_mut);
+}
+
+/*
+ * 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;
+
+ if (!(hid->quirks & HID_QUIRK_NO_INIT_INPUT_REPORTS)) {
+ 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 usbhid_restart_queues(struct usbhid_device *usbhid)
+{
+ 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);
+}
+
+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;
+
+ quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),
+ le16_to_cpu(dev->descriptor.idProduct));
+
+ 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;
+ }
+
+ hid->version = le16_to_cpu(hdesc->bcdHID);
+ hid->country = hdesc->bCountryCode;
+
+ for (n = 0; n < hdesc->bNumDescriptors; n++)
+ if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)
+ rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
+
+ if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+ dbg_hid("weird size of report descriptor (%u)\n", rsize);
+ return -EINVAL;
+ }
+
+ if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
+ dbg_hid("couldn't allocate rdesc memory\n");
+ 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);
+ printk(KERN_INFO "%s: Fixing fullspeed to highspeed interval: %d -> %d\n",
+ hid->name, endpoint->bInterval, interval);
+ }
+
+ /* Change the polling interval of mice. */
+ if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)
+ interval = hid_mousepoll_interval;
+
+ 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;
+
+ if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
+ usbhid_init_reports(hid);
+
+ set_bit(HID_STARTED, &usbhid->iofl);
+
+ if (hid->quirks & HID_QUIRK_ALWAYS_POLL) {
+ ret = usb_autopm_get_interface(usbhid->intf);
+ if (ret)
+ goto fail;
+ 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)
+ 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)
+{
+ int r = 0;
+
+ switch (lvl) {
+ case PM_HINT_FULLON:
+ r = usbhid_get_power(hid);
+ break;
+ case PM_HINT_NORMAL:
+ usbhid_put_power(hid);
+ 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);
+}
+
+static 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,
+};
+
+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->name[0] = 0;
+ hid->quirks = usbhid_lookup_quirk(hid->vendor, hid->product);
+ 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);
+ setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
+ 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);
+}
+
+/* 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) {
+ dbg_hid("couldn't allocate rdesc memory (post_reset)\n");
+ return 1;
+ }
+ 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 1;
+ }
+ status = memcmp(rdesc, hid->dev_rdesc, hid->dev_rsize);
+ kfree(rdesc);
+ if (status != 0) {
+ dbg_hid("report descriptor changed\n");
+ return 1;
+ }
+
+ spin_lock_irq(&usbhid->lock);
+ clear_bit(HID_RESET_PENDING, &usbhid->iofl);
+ spin_unlock_irq(&usbhid->lock);
+ hid_set_idle(dev, intf->cur_altsetting->desc.bInterfaceNumber, 0, 0);
+ status = hid_start_in(hid);
+ if (status < 0)
+ hid_io_error(hid);
+ usbhid_restart_queues(usbhid);
+
+ return 0;
+}
+
+int usbhid_get_power(struct hid_device *hid)
+{
+ struct usbhid_device *usbhid = hid->driver_data;
+
+ return usb_autopm_get_interface(usbhid->intf);
+}
+
+void usbhid_put_power(struct hid_device *hid)
+{
+ struct usbhid_device *usbhid = hid->driver_data;
+
+ usb_autopm_put_interface(usbhid->intf);
+}
+
+
+#ifdef CONFIG_PM
+static int hid_resume_common(struct hid_device *hid, bool driver_suspended)
+{
+ struct usbhid_device *usbhid = hid->driver_data;
+ int status;
+
+ spin_lock_irq(&usbhid->lock);
+ clear_bit(HID_SUSPENDED, &usbhid->iofl);
+ usbhid_mark_busy(usbhid);
+
+ if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
+ test_bit(HID_RESET_PENDING, &usbhid->iofl))
+ schedule_work(&usbhid->reset_work);
+ usbhid->retry_delay = 0;
+
+ usbhid_restart_queues(usbhid);
+ spin_unlock_irq(&usbhid->lock);
+
+ status = hid_start_in(hid);
+ if (status < 0)
+ hid_io_error(hid);
+
+ if (driver_suspended && hid->driver && hid->driver->resume)
+ status = hid->driver->resume(hid);
+ return status;
+}
+
+static int hid_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct hid_device *hid = usb_get_intfdata(intf);
+ struct usbhid_device *usbhid = hid->driver_data;
+ int status = 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);
+ struct usbhid_device *usbhid = hid->driver_data;
+ int status;
+
+ if (!test_bit(HID_STARTED, &usbhid->iofl))
+ return 0;
+
+ 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);
+ struct usbhid_device *usbhid = hid->driver_data;
+ int status;
+
+ clear_bit(HID_SUSPENDED, &usbhid->iofl);
+ status = hid_post_reset(intf);
+ if (status >= 0 && hid->driver && hid->driver->reset_resume) {
+ int ret = hid->driver->reset_resume(hid);
+ 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 = usbhid_quirks_init(quirks_param);
+ if (retval)
+ goto usbhid_quirks_init_fail;
+ retval = usb_register(&hid_driver);
+ if (retval)
+ goto usb_register_fail;
+ printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");
+
+ return 0;
+usb_register_fail:
+ usbhid_quirks_exit();
+usbhid_quirks_init_fail:
+ return retval;
+}
+
+static void __exit hid_exit(void)
+{
+ usb_deregister(&hid_driver);
+ usbhid_quirks_exit();
+}
+
+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(DRIVER_LICENSE);
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
new file mode 100644
index 000000000..08174d341
--- /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/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
new file mode 100644
index 000000000..f1395f8ad
--- /dev/null
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -0,0 +1,379 @@
+/*
+ * USB HID quirks support for Linux
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2006-2007 Jiri Kosina
+ * Copyright (c) 2007 Paul Walmsley
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+
+#include "../hid-ids.h"
+
+/*
+ * Alphabetically sorted blacklist by quirk type.
+ */
+
+static const struct hid_blacklist {
+ __u16 idVendor;
+ __u16 idProduct;
+ __u32 quirks;
+} hid_blacklist[] = {
+ { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER, HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_NATSU, USB_DEVICE_ID_NATSU_GAMEPAD, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_NEC, USB_DEVICE_ID_NEC_USB_GAME_PAD, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_NEXTWINDOW, USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN, HID_QUIRK_MULTI_INPUT},
+ { USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD, HID_QUIRK_BADPAD },
+ { USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD },
+
+ { USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016, HID_QUIRK_FULLSPEED_INTERVAL },
+
+ { USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH_2968, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK, HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS },
+ { USB_VENDOR_ID_PLAYDOTCOM, USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_TOUCHPACK, USB_DEVICE_ID_TOUCHPACK_RTS, HID_QUIRK_MULTI_INPUT },
+
+ { USB_VENDOR_ID_AIREN, USB_DEVICE_ID_AIREN_SLIMPLUS, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FIGHTERSTICK, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_COMBATSTICK, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_THROTTLE, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_PEDALS, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_AXIS_295, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_009B, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_0103, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_010c, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_016F, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3_JP, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1610, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1640, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22, HID_QUIRK_ALWAYS_POLL },
+ { USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_REALTEK, USB_DEVICE_ID_REALTEK_READER, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS9200_TOUCH, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS_TS, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_1, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_2, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+
+ { USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+
+ { USB_VENDOR_ID_PI_ENGINEERING, USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL, HID_QUIRK_HIDINPUT_FORCE },
+
+ { USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912, HID_QUIRK_MULTI_INPUT },
+ { USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS2, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096, HID_QUIRK_NO_INIT_INPUT_REPORTS },
+
+ { USB_VENDOR_ID_HUIONTABLET,USB_DEVICE_ID_HUIONTABLET, HID_QUIRK_IGNORE},
+ { USB_VENDOR_ID_HUIONTABLET,USB_DEVICE_ID_HUIONTABLET2, HID_QUIRK_IGNORE},
+ { USB_VENDOR_ID_HUIONTABLET2,USB_DEVICE_ID_HUIONTABLET, HID_QUIRK_IGNORE},
+ { USB_VENDOR_ID_HUIONTABLET2,USB_DEVICE_ID_HUIONTABLET2, HID_QUIRK_IGNORE},
+
+ { 0, 0 }
+};
+
+/* Dynamic HID quirks list - specified at runtime */
+struct quirks_list_struct {
+ struct hid_blacklist hid_bl_item;
+ struct list_head node;
+};
+
+static LIST_HEAD(dquirks_list);
+static DECLARE_RWSEM(dquirks_rwsem);
+
+/* Runtime ("dynamic") quirks manipulation functions */
+
+/**
+ * usbhid_exists_dquirk: find any dynamic quirks for a USB HID device
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ *
+ * Description:
+ * Scans dquirks_list for a matching dynamic quirk and returns
+ * the pointer to the relevant struct hid_blacklist if found.
+ * Must be called with a read lock held on dquirks_rwsem.
+ *
+ * Returns: NULL if no quirk found, struct hid_blacklist * if found.
+ */
+static struct hid_blacklist *usbhid_exists_dquirk(const u16 idVendor,
+ const u16 idProduct)
+{
+ struct quirks_list_struct *q;
+ struct hid_blacklist *bl_entry = NULL;
+
+ list_for_each_entry(q, &dquirks_list, node) {
+ if (q->hid_bl_item.idVendor == idVendor &&
+ q->hid_bl_item.idProduct == idProduct) {
+ bl_entry = &q->hid_bl_item;
+ break;
+ }
+ }
+
+ if (bl_entry != NULL)
+ dbg_hid("Found dynamic quirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n",
+ bl_entry->quirks, bl_entry->idVendor,
+ bl_entry->idProduct);
+
+ return bl_entry;
+}
+
+
+/**
+ * usbhid_modify_dquirk: add/replace a HID quirk
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ * @quirks: the u32 quirks value to add/replace
+ *
+ * Description:
+ * If an dynamic quirk exists in memory for this (idVendor,
+ * idProduct) pair, replace its quirks value with what was
+ * provided. Otherwise, add the quirk to the dynamic quirks list.
+ *
+ * Returns: 0 OK, -error on failure.
+ */
+static int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,
+ const u32 quirks)
+{
+ struct quirks_list_struct *q_new, *q;
+ int list_edited = 0;
+
+ if (!idVendor) {
+ dbg_hid("Cannot add a quirk with idVendor = 0\n");
+ return -EINVAL;
+ }
+
+ q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);
+ if (!q_new) {
+ dbg_hid("Could not allocate quirks_list_struct\n");
+ return -ENOMEM;
+ }
+
+ q_new->hid_bl_item.idVendor = idVendor;
+ q_new->hid_bl_item.idProduct = idProduct;
+ q_new->hid_bl_item.quirks = quirks;
+
+ down_write(&dquirks_rwsem);
+
+ list_for_each_entry(q, &dquirks_list, node) {
+
+ if (q->hid_bl_item.idVendor == idVendor &&
+ q->hid_bl_item.idProduct == idProduct) {
+
+ list_replace(&q->node, &q_new->node);
+ kfree(q);
+ list_edited = 1;
+ break;
+
+ }
+
+ }
+
+ if (!list_edited)
+ list_add_tail(&q_new->node, &dquirks_list);
+
+ up_write(&dquirks_rwsem);
+
+ return 0;
+}
+
+/**
+ * usbhid_remove_all_dquirks: remove all runtime HID quirks from memory
+ *
+ * Description:
+ * Free all memory associated with dynamic quirks - called before
+ * module unload.
+ *
+ */
+static void usbhid_remove_all_dquirks(void)
+{
+ struct quirks_list_struct *q, *temp;
+
+ down_write(&dquirks_rwsem);
+ list_for_each_entry_safe(q, temp, &dquirks_list, node) {
+ list_del(&q->node);
+ kfree(q);
+ }
+ up_write(&dquirks_rwsem);
+
+}
+
+/**
+ * usbhid_quirks_init: apply USB HID quirks specified at module load time
+ */
+int usbhid_quirks_init(char **quirks_param)
+{
+ u16 idVendor, idProduct;
+ u32 quirks;
+ int n = 0, m;
+
+ for (; n < MAX_USBHID_BOOT_QUIRKS && quirks_param[n]; n++) {
+
+ m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
+ &idVendor, &idProduct, &quirks);
+
+ if (m != 3 ||
+ usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {
+ printk(KERN_WARNING
+ "Could not parse HID quirk module param %s\n",
+ quirks_param[n]);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * usbhid_quirks_exit: release memory associated with dynamic_quirks
+ *
+ * Description:
+ * Release all memory associated with dynamic quirks. Called upon
+ * module unload.
+ *
+ * Returns: nothing
+ */
+void usbhid_quirks_exit(void)
+{
+ usbhid_remove_all_dquirks();
+}
+
+/**
+ * usbhid_exists_squirk: return any static quirks for a USB HID device
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ *
+ * Description:
+ * Given a USB vendor ID and product ID, return a pointer to
+ * the hid_blacklist entry associated with that device.
+ *
+ * Returns: pointer if quirk found, or NULL if no quirks found.
+ */
+static const struct hid_blacklist *usbhid_exists_squirk(const u16 idVendor,
+ const u16 idProduct)
+{
+ const struct hid_blacklist *bl_entry = NULL;
+ int n = 0;
+
+ for (; hid_blacklist[n].idVendor; n++)
+ if (hid_blacklist[n].idVendor == idVendor &&
+ hid_blacklist[n].idProduct == idProduct)
+ bl_entry = &hid_blacklist[n];
+
+ if (bl_entry != NULL)
+ dbg_hid("Found squirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n",
+ bl_entry->quirks, bl_entry->idVendor,
+ bl_entry->idProduct);
+ return bl_entry;
+}
+
+/**
+ * usbhid_lookup_quirk: return any quirks associated with a USB HID device
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ *
+ * Description:
+ * Given a USB vendor ID and product ID, return any quirks associated
+ * with that device.
+ *
+ * Returns: a u32 quirks value.
+ */
+u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct)
+{
+ u32 quirks = 0;
+ const struct hid_blacklist *bl_entry = NULL;
+
+ /* NCR devices must not be queried for reports */
+ if (idVendor == USB_VENDOR_ID_NCR &&
+ idProduct >= USB_DEVICE_ID_NCR_FIRST &&
+ idProduct <= USB_DEVICE_ID_NCR_LAST)
+ return HID_QUIRK_NO_INIT_REPORTS;
+
+ down_read(&dquirks_rwsem);
+ bl_entry = usbhid_exists_dquirk(idVendor, idProduct);
+ if (!bl_entry)
+ bl_entry = usbhid_exists_squirk(idVendor, idProduct);
+ if (bl_entry)
+ quirks = bl_entry->quirks;
+ up_read(&dquirks_rwsem);
+
+ return quirks;
+}
+
+EXPORT_SYMBOL_GPL(usbhid_lookup_quirk);
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
new file mode 100644
index 000000000..2f1ddca6f
--- /dev/null
+++ b/drivers/hid/usbhid/hiddev.c
@@ -0,0 +1,939 @@
+/*
+ * Copyright (c) 2001 Paul Stewart
+ * Copyright (c) 2001 Vojtech Pavlik
+ *
+ * HID char devices, giving access to raw HID device events.
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to Paul Stewart <stewart@wetlogic.net>
+ */
+
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/compat.h>
+#include <linux/vmalloc.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 {
+ int exist;
+ int open;
+ struct mutex existancelock;
+ wait_queue_head_t wait;
+ struct hid_device *hid;
+ struct list_head list;
+ spinlock_t list_lock;
+};
+
+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) {
+ usbhid_close(list->hiddev->hid);
+ usbhid_put_power(list->hiddev->hid);
+ } 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 = usbhid_open(hiddev->hid);
+ if (res < 0) {
+ res = -EIO;
+ 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 = usbhid_get_power(hid);
+ if (res < 0) {
+ res = -EIO;
+ goto bail_unlock;
+ }
+ usbhid_open(hid);
+ }
+ mutex_unlock(&hiddev->existancelock);
+ return 0;
+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 unsigned int hiddev_poll(struct file *file, poll_table *wait)
+{
+ struct hiddev_list *list = file->private_data;
+
+ poll_wait(file, &list->hiddev->wait, wait);
+ if (list->head != list->tail)
+ return POLLIN | POLLRDNORM;
+ if (!list->hiddev->exist)
+ return POLLERR | POLLHUP;
+ return 0;
+}
+
+/*
+ * "ioctl" file op
+ */
+static 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;
+
+ field = report->field[uref->field_index];
+ if (uref->usage_index >= field->maxusage)
+ goto inval;
+
+ uref->usage_code = field->usage[uref->usage_index].hid;
+
+ if (copy_to_user(user_arg, uref, sizeof(*uref)))
+ goto fault;
+
+ 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;
+
+ field = report->field[uref->field_index];
+
+ if (cmd == HIDIOCGCOLLECTIONINDEX) {
+ if (uref->usage_index >= field->maxusage)
+ goto inval;
+ } else if (uref->usage_index >= field->report_count)
+ goto inval;
+
+ else if ((cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) &&
+ (uref_multi->num_values > HID_MAX_MULTI_USAGES ||
+ uref->usage_index + uref_multi->num_values > field->report_count))
+ goto inval;
+ }
+
+ switch (cmd) {
+ case HIDIOCGUSAGE:
+ uref->value = field->value[uref->usage_index];
+ if (copy_to_user(user_arg, uref, sizeof(*uref)))
+ goto fault;
+ goto goodreturn;
+
+ case HIDIOCSUSAGE:
+ field->value[uref->usage_index] = uref->value;
+ goto goodreturn;
+
+ case HIDIOCGCOLLECTIONINDEX:
+ 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);
+ 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;
+
+ 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:
+ 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.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;
+ }
+ 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);
+ usbhid_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 000000000..807922b49
--- /dev/null
+++ b/drivers/hid/usbhid/usbhid.h
@@ -0,0 +1,102 @@
+#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_close(struct hid_device *hid);
+int usbhid_open(struct hid_device *hid);
+void usbhid_init_reports(struct hid_device *hid);
+int usbhid_get_power(struct hid_device *hid);
+void usbhid_put_power(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
+
+/*
+ * 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) \
+ container_of(hid_dev->dev.parent->parent, struct usb_device, dev)
+
+#endif
+
diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c
new file mode 100644
index 000000000..9a332e683
--- /dev/null
+++ b/drivers/hid/usbhid/usbkbd.c
@@ -0,0 +1,411 @@
+/*
+ * 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"
+#define DRIVER_LICENSE "GPL"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+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 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 000000000..bf16d72dc
--- /dev/null
+++ b/drivers/hid/usbhid/usbmouse.c
@@ -0,0 +1,245 @@
+/*
+ * 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"
+#define DRIVER_LICENSE "GPL"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+struct usb_mouse {
+ char name[128];
+ char phys[64];
+ struct usb_device *usbdev;
+ struct input_dev *dev;
+ struct urb *irq;
+
+ signed char *data;
+ dma_addr_t data_dma;
+};
+
+static void usb_mouse_irq(struct urb *urb)
+{
+ struct usb_mouse *mouse = urb->context;
+ signed char *data = mouse->data;
+ struct input_dev *dev = mouse->dev;
+ int status;
+
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+ case -ECONNRESET: /* unlink */
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ /* -EPIPE: should clear the halt */
+ default: /* error */
+ goto resubmit;
+ }
+
+ input_report_key(dev, BTN_LEFT, data[0] & 0x01);
+ input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
+ input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
+ input_report_key(dev, BTN_SIDE, data[0] & 0x08);
+ input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
+
+ input_report_rel(dev, REL_X, data[1]);
+ input_report_rel(dev, REL_Y, data[2]);
+ input_report_rel(dev, REL_WHEEL, data[3]);
+
+ input_sync(dev);
+resubmit:
+ status = usb_submit_urb (urb, GFP_ATOMIC);
+ if (status)
+ 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 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 000000000..024f4d89d
--- /dev/null
+++ b/drivers/hid/wacom.h
@@ -0,0 +1,148 @@
+/*
+ * 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/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 DRIVER_LICENSE "GPL"
+
+#define USB_VENDOR_ID_WACOM 0x056a
+#define USB_VENDOR_ID_LENOVO 0x17ef
+
+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 work;
+ struct wacom_led {
+ u8 select[2]; /* status led selector (0..3) */
+ 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 */
+ } led;
+ bool led_initialized;
+ struct power_supply *battery;
+ struct power_supply *ac;
+ struct power_supply_desc battery_desc;
+ struct power_supply_desc ac_desc;
+};
+
+static inline void wacom_schedule_work(struct wacom_wac *wacom_wac)
+{
+ struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+ schedule_work(&wacom->work);
+}
+
+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_features *features);
+int wacom_setup_pentouch_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);
+int 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);
+#endif
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
new file mode 100644
index 000000000..e8607d096
--- /dev/null
+++ b/drivers/hid/wacom_sys.c
@@ -0,0 +1,1661 @@
+/*
+ * 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_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_RETRIES 10
+
+#define DEV_ATTR_RW_PERM (S_IRUGO | S_IWUSR | S_IWGRP)
+#define DEV_ATTR_WO_PERM (S_IWUSR | S_IWGRP)
+
+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 == -EPIPE) && --retries);
+
+ 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 == -EPIPE) && --retries);
+
+ return retval;
+}
+
+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;
+
+ 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);
+
+ 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_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;
+ u8 *data;
+ int ret;
+
+ switch (usage->hid) {
+ 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, 0);
+ if (ret == 2)
+ features->touch_max = data[1];
+ 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;
+ }
+}
+
+/*
+ * 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);
+
+ /*
+ * Requiring Stylus Usage will ignore boot mouse
+ * X/Y values and some cases of invalid Digitizer X/Y
+ * values commonly reported.
+ */
+ if (!pen && !finger)
+ return;
+
+ /*
+ * Bamboo models do not support HID_DG_CONTACTMAX.
+ * And, Bamboo Pen only descriptor contains touch.
+ */
+ if (features->type != BAMBOO_PT) {
+ /* ISDv4 touch devices at least supports one touch point */
+ if (finger && !features->touch_max)
+ features->touch_max = 1;
+ }
+
+ switch (usage->hid) {
+ case HID_GD_X:
+ features->x_max = field->logical_maximum;
+ if (finger) {
+ features->device_type = BTN_TOOL_FINGER;
+ features->x_phy = field->physical_maximum;
+ if (features->type != BAMBOO_PT) {
+ features->unit = field->unit;
+ features->unitExpo = field->unit_exponent;
+ }
+ } else {
+ features->device_type = BTN_TOOL_PEN;
+ }
+ 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->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 (features->touch_max > 1) {
+ input_mt_init_slots(wacom_wac->input, wacom_wac->features.touch_max,
+ INPUT_MT_DIRECT);
+ }
+ }
+}
+
+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, int report_id,
+ int length, int mode)
+{
+ unsigned char *rep_data;
+ int error = -ENOMEM, limit = 0;
+
+ rep_data = kzalloc(length, GFP_KERNEL);
+ if (!rep_data)
+ return error;
+
+ do {
+ rep_data[0] = report_id;
+ rep_data[1] = mode;
+
+ 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] != mode) && 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 hid_device *hdev,
+ struct wacom_features *features)
+{
+ if (hdev->bus == BUS_BLUETOOTH)
+ return wacom_bt_query_tablet_data(hdev, 1, features);
+
+ if (features->type == HID_GENERIC)
+ return wacom_hid_set_device_mode(hdev);
+
+ if (features->device_type == BTN_TOOL_FINGER) {
+ if (features->type > TABLETPC) {
+ /* MT Tablet PC touch */
+ return wacom_set_device_mode(hdev, 3, 4, 4);
+ }
+ else if (features->type == WACOM_24HDT || features->type == CINTIQ_HYBRID) {
+ return wacom_set_device_mode(hdev, 18, 3, 2);
+ }
+ else if (features->type == WACOM_27QHDT) {
+ return wacom_set_device_mode(hdev, 131, 3, 2);
+ }
+ else if (features->type == BAMBOO_PAD) {
+ return wacom_set_device_mode(hdev, 2, 2, 2);
+ }
+ } else if (features->device_type == BTN_TOOL_PEN) {
+ if (features->type <= BAMBOO_PT && features->type != WIRELESS) {
+ return wacom_set_device_mode(hdev, 2, 2, 2);
+ }
+ }
+
+ 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->device_type = BTN_TOOL_PEN;
+ features->x_fuzz = 4;
+ features->y_fuzz = 4;
+ features->pressure_fuzz = 0;
+ features->distance_fuzz = 0;
+
+ /*
+ * 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 = 0;
+ } else if (intf->cur_altsetting->desc.bInterfaceNumber == 2) {
+ features->device_type = BTN_TOOL_FINGER;
+ features->pktlen = WACOM_PKGLEN_BBTOUCH3;
+ }
+ }
+
+ /* only devices that support touch need to retrieve the info */
+ if (features->type < BAMBOO_PT)
+ 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;
+ int vid = features->oVid;
+ int pid = features->oPid;
+ int n1,n2;
+
+ if (vid == 0 && pid == 0) {
+ vid = hdev->vendor;
+ pid = hdev->product;
+ }
+
+ if (vid != sibling->vendor || pid != sibling->product)
+ return false;
+
+ /* Compare the physical path. */
+ n1 = strrchr(hdev->phys, '.') - hdev->phys;
+ n2 = strrchr(sibling->phys, '.') - sibling->phys;
+ if (n1 != n2 || n1 <= 0 || n2 <= 0)
+ return false;
+
+ return !strncmp(hdev->phys, sibling->phys, n1);
+}
+
+static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
+{
+ struct wacom_hdev_data *data;
+
+ 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 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;
+
+ if (wacom_wac->features.device_type == BTN_TOOL_FINGER)
+ wacom_wac->shared->touch = hdev;
+ else if (wacom_wac->features.device_type == BTN_TOOL_PEN)
+ wacom_wac->shared->pen = hdev;
+
+out:
+ mutex_unlock(&wacom_udev_list_lock);
+ return retval;
+}
+
+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(struct wacom *wacom)
+{
+ 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_led_control(struct wacom *wacom)
+{
+ unsigned char *buf;
+ int retval;
+ unsigned char report_id = WAC_CMD_LED_CONTROL;
+ int buf_size = 9;
+
+ if (wacom->wacom_wac.pid) { /* wireless connected */
+ report_id = WAC_CMD_WL_LED_CONTROL;
+ buf_size = 13;
+ }
+ buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ 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.select[0] & 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 {
+ int led = wacom->led.select[0] | 0x4;
+
+ if (wacom->wacom_wac.features.type == WACOM_21UX2 ||
+ wacom->wacom_wac.features.type == WACOM_24HD)
+ led |= (wacom->led.select[1] << 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 = container_of(dev, struct 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.select[set_id] = 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 = container_of(dev, struct hid_device, dev);\
+ struct wacom *wacom = hid_get_drvdata(hdev); \
+ return scnprintf(buf, PAGE_SIZE, "%d\n", \
+ wacom->led.select[SET_ID]); \
+} \
+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 = container_of(dev, struct 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 = container_of(dev, struct 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 int wacom_initialize_leds(struct wacom *wacom)
+{
+ int error;
+
+ /* Initialize default values */
+ switch (wacom->wacom_wac.features.type) {
+ case INTUOS4S:
+ case INTUOS4:
+ case INTUOS4WL:
+ case INTUOS4L:
+ wacom->led.select[0] = 0;
+ wacom->led.select[1] = 0;
+ wacom->led.llv = 10;
+ wacom->led.hlv = 20;
+ wacom->led.img_lum = 10;
+ error = sysfs_create_group(&wacom->hdev->dev.kobj,
+ &intuos4_led_attr_group);
+ break;
+
+ case WACOM_24HD:
+ case WACOM_21UX2:
+ wacom->led.select[0] = 0;
+ wacom->led.select[1] = 0;
+ wacom->led.llv = 0;
+ wacom->led.hlv = 0;
+ wacom->led.img_lum = 0;
+
+ error = sysfs_create_group(&wacom->hdev->dev.kobj,
+ &cintiq_led_attr_group);
+ break;
+
+ case INTUOS5S:
+ case INTUOS5:
+ case INTUOS5L:
+ case INTUOSPS:
+ case INTUOSPM:
+ case INTUOSPL:
+ if (wacom->wacom_wac.features.device_type == BTN_TOOL_PEN) {
+ wacom->led.select[0] = 0;
+ wacom->led.select[1] = 0;
+ wacom->led.llv = 32;
+ wacom->led.hlv = 0;
+ wacom->led.img_lum = 0;
+
+ error = sysfs_create_group(&wacom->hdev->dev.kobj,
+ &intuos5_led_attr_group);
+ } else
+ return 0;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (error) {
+ hid_err(wacom->hdev,
+ "cannot create sysfs group err: %d\n", error);
+ return error;
+ }
+ wacom_led_control(wacom);
+ wacom->led_initialized = true;
+
+ return 0;
+}
+
+static void wacom_destroy_leds(struct wacom *wacom)
+{
+ if (!wacom->led_initialized)
+ return;
+
+ wacom->led_initialized = false;
+
+ switch (wacom->wacom_wac.features.type) {
+ case INTUOS4S:
+ case INTUOS4:
+ case INTUOS4WL:
+ case INTUOS4L:
+ sysfs_remove_group(&wacom->hdev->dev.kobj,
+ &intuos4_led_attr_group);
+ break;
+
+ case WACOM_24HD:
+ case WACOM_21UX2:
+ sysfs_remove_group(&wacom->hdev->dev.kobj,
+ &cintiq_led_attr_group);
+ break;
+
+ case INTUOS5S:
+ case INTUOS5:
+ case INTUOS5L:
+ case INTUOSPS:
+ case INTUOSPM:
+ case INTUOSPL:
+ if (wacom->wacom_wac.features.device_type == BTN_TOOL_PEN)
+ sysfs_remove_group(&wacom->hdev->dev.kobj,
+ &intuos5_led_attr_group);
+ break;
+ }
+}
+
+static enum power_supply_property wacom_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_CAPACITY
+};
+
+static enum power_supply_property wacom_ac_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+
+static int wacom_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wacom *wacom = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = wacom->wacom_wac.bat_connected;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval =
+ wacom->wacom_wac.battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (wacom->wacom_wac.bat_charging)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (wacom->wacom_wac.battery_capacity == 100 &&
+ wacom->wacom_wac.ps_connected)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (wacom->wacom_wac.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_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wacom *wacom = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ /* fall through */
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = wacom->wacom_wac.ps_connected;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int wacom_initialize_battery(struct wacom *wacom)
+{
+ static atomic_t battery_no = ATOMIC_INIT(0);
+ struct power_supply_config psy_cfg = { .drv_data = wacom, };
+ unsigned long n;
+
+ if (wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) {
+ struct power_supply_desc *bat_desc = &wacom->battery_desc;
+ struct power_supply_desc *ac_desc = &wacom->ac_desc;
+ 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(wacom->wacom_wac.bat_name, "wacom_battery_%ld", n);
+ bat_desc->name = wacom->wacom_wac.bat_name;
+ bat_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+ bat_desc->use_for_apm = 0;
+
+ ac_desc->properties = wacom_ac_props;
+ ac_desc->num_properties = ARRAY_SIZE(wacom_ac_props);
+ ac_desc->get_property = wacom_ac_get_property;
+ sprintf(wacom->wacom_wac.ac_name, "wacom_ac_%ld", n);
+ ac_desc->name = wacom->wacom_wac.ac_name;
+ ac_desc->type = POWER_SUPPLY_TYPE_MAINS;
+ ac_desc->use_for_apm = 0;
+
+ wacom->battery = power_supply_register(&wacom->hdev->dev,
+ &wacom->battery_desc, &psy_cfg);
+ if (IS_ERR(wacom->battery))
+ return PTR_ERR(wacom->battery);
+
+ power_supply_powers(wacom->battery, &wacom->hdev->dev);
+
+ wacom->ac = power_supply_register(&wacom->hdev->dev,
+ &wacom->ac_desc,
+ &psy_cfg);
+ if (IS_ERR(wacom->ac)) {
+ power_supply_unregister(wacom->battery);
+ return PTR_ERR(wacom->ac);
+ }
+
+ power_supply_powers(wacom->ac, &wacom->hdev->dev);
+ }
+
+ return 0;
+}
+
+static void wacom_destroy_battery(struct wacom *wacom)
+{
+ if (wacom->battery) {
+ power_supply_unregister(wacom->battery);
+ wacom->battery = NULL;
+ power_supply_unregister(wacom->ac);
+ wacom->ac = NULL;
+ }
+}
+
+static ssize_t wacom_show_speed(struct device *dev,
+ struct device_attribute
+ *attr, char *buf)
+{
+ struct hid_device *hdev = container_of(dev, struct 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 = container_of(dev, struct 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 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 = input_allocate_device();
+ if (!input_dev)
+ return NULL;
+
+ input_dev->name = wacom_wac->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 void wacom_free_inputs(struct wacom *wacom)
+{
+ struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+
+ if (wacom_wac->input)
+ input_free_device(wacom_wac->input);
+ if (wacom_wac->pad_input)
+ input_free_device(wacom_wac->pad_input);
+ wacom_wac->input = NULL;
+ wacom_wac->pad_input = NULL;
+}
+
+static int wacom_allocate_inputs(struct wacom *wacom)
+{
+ struct input_dev *input_dev, *pad_input_dev;
+ struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+
+ input_dev = wacom_allocate_input(wacom);
+ pad_input_dev = wacom_allocate_input(wacom);
+ if (!input_dev || !pad_input_dev) {
+ wacom_free_inputs(wacom);
+ return -ENOMEM;
+ }
+
+ wacom_wac->input = input_dev;
+ wacom_wac->pad_input = pad_input_dev;
+ wacom_wac->pad_input->name = wacom_wac->pad_name;
+
+ return 0;
+}
+
+static void wacom_clean_inputs(struct wacom *wacom)
+{
+ if (wacom->wacom_wac.input) {
+ if (wacom->wacom_wac.input_registered)
+ input_unregister_device(wacom->wacom_wac.input);
+ else
+ input_free_device(wacom->wacom_wac.input);
+ }
+ if (wacom->wacom_wac.pad_input) {
+ if (wacom->wacom_wac.pad_registered)
+ input_unregister_device(wacom->wacom_wac.pad_input);
+ else
+ input_free_device(wacom->wacom_wac.pad_input);
+ }
+ wacom->wacom_wac.input = NULL;
+ wacom->wacom_wac.pad_input = NULL;
+ wacom_destroy_leds(wacom);
+}
+
+static int wacom_register_inputs(struct wacom *wacom)
+{
+ struct input_dev *input_dev, *pad_input_dev;
+ struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+ int error;
+
+ input_dev = wacom_wac->input;
+ pad_input_dev = wacom_wac->pad_input;
+
+ if (!input_dev || !pad_input_dev)
+ return -EINVAL;
+
+ error = wacom_setup_pentouch_input_capabilities(input_dev, wacom_wac);
+ if (!error) {
+ error = input_register_device(input_dev);
+ if (error)
+ return error;
+ wacom_wac->input_registered = true;
+ }
+
+ 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_register_pad_input;
+ wacom_wac->pad_registered = true;
+
+ error = wacom_initialize_leds(wacom);
+ if (error)
+ goto fail_leds;
+ }
+
+ return 0;
+
+fail_leds:
+ input_unregister_device(pad_input_dev);
+ pad_input_dev = NULL;
+ wacom_wac->pad_registered = false;
+fail_register_pad_input:
+ input_unregister_device(input_dev);
+ wacom_wac->input = NULL;
+ wacom_wac->input_registered = false;
+ return error;
+}
+
+static void wacom_wireless_work(struct work_struct *work)
+{
+ struct wacom *wacom = container_of(work, struct wacom, 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_clean_inputs(wacom1);
+
+ /* Touch interface */
+ hdev2 = usb_get_intfdata(usbdev->config->interface[2]);
+ wacom2 = hid_get_drvdata(hdev2);
+ wacom_wac2 = &(wacom2->wacom_wac);
+ wacom_clean_inputs(wacom2);
+
+ if (wacom_wac->pid == 0) {
+ hid_info(wacom->hdev, "wireless tablet disconnected\n");
+ wacom_wac1->shared->type = 0;
+ } 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->features.device_type = BTN_TOOL_PEN;
+ snprintf(wacom_wac1->name, WACOM_NAME_MAX, "%s (WL) Pen",
+ wacom_wac1->features.name);
+ snprintf(wacom_wac1->pad_name, WACOM_NAME_MAX, "%s (WL) Pad",
+ wacom_wac1->features.name);
+ wacom_wac1->shared->touch_max = wacom_wac1->features.touch_max;
+ wacom_wac1->shared->type = wacom_wac1->features.type;
+ wacom_wac1->pid = wacom_wac->pid;
+ error = wacom_allocate_inputs(wacom1) ||
+ wacom_register_inputs(wacom1);
+ if (error)
+ goto fail;
+
+ /* Touch interface */
+ if (wacom_wac1->features.touch_max ||
+ wacom_wac1->features.type == INTUOSHT) {
+ wacom_wac2->features =
+ *((struct wacom_features *)id->driver_data);
+ wacom_wac2->features.pktlen = WACOM_PKGLEN_BBTOUCH3;
+ wacom_wac2->features.device_type = BTN_TOOL_FINGER;
+ wacom_wac2->features.x_max = wacom_wac2->features.y_max = 4096;
+ if (wacom_wac2->features.touch_max)
+ snprintf(wacom_wac2->name, WACOM_NAME_MAX,
+ "%s (WL) Finger",wacom_wac2->features.name);
+ else
+ snprintf(wacom_wac2->name, WACOM_NAME_MAX,
+ "%s (WL) Pad",wacom_wac2->features.name);
+ snprintf(wacom_wac2->pad_name, WACOM_NAME_MAX,
+ "%s (WL) Pad", wacom_wac2->features.name);
+ wacom_wac2->pid = wacom_wac->pid;
+ error = wacom_allocate_inputs(wacom2) ||
+ wacom_register_inputs(wacom2);
+ if (error)
+ goto fail;
+
+ if (wacom_wac1->features.type == INTUOSHT &&
+ wacom_wac1->features.touch_max)
+ wacom_wac->shared->touch_input = wacom_wac2->input;
+ }
+
+ error = wacom_initialize_battery(wacom);
+ if (error)
+ goto fail;
+ }
+
+ return;
+
+fail:
+ wacom_clean_inputs(wacom1);
+ wacom_clean_inputs(wacom2);
+ return;
+}
+
+void wacom_battery_work(struct work_struct *work)
+{
+ struct wacom *wacom = container_of(work, struct wacom, work);
+
+ if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+ !wacom->battery) {
+ wacom_initialize_battery(wacom);
+ }
+ else if (!(wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+ wacom->battery) {
+ wacom_destroy_battery(wacom);
+ }
+}
+
+/*
+ * 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)
+{
+ 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);
+}
+
+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 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;
+ unsigned int connect_mask = HID_CONNECT_HIDRAW;
+
+ 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 = kzalloc(sizeof(struct wacom), GFP_KERNEL);
+ if (!wacom)
+ return -ENOMEM;
+
+ hid_set_drvdata(hdev, wacom);
+ wacom->hdev = hdev;
+
+ /* ask for the report descriptor to be loaded by HID */
+ error = hid_parse(hdev);
+ if (error) {
+ hid_err(hdev, "parse failed\n");
+ goto fail_parse;
+ }
+
+ wacom_wac = &wacom->wacom_wac;
+ wacom_wac->features = *((struct wacom_features *)id->driver_data);
+ features = &wacom_wac->features;
+ features->pktlen = wacom_compute_pktlen(hdev);
+ if (features->pktlen > WACOM_PKGLEN_MAX) {
+ error = -EINVAL;
+ goto fail_pktlen;
+ }
+
+ if (features->check_for_hid_type && features->hid_type != hdev->type) {
+ error = -ENODEV;
+ goto fail_type;
+ }
+
+ wacom->usbdev = dev;
+ wacom->intf = intf;
+ mutex_init(&wacom->lock);
+ INIT_WORK(&wacom->work, wacom_wireless_work);
+
+ if (!(features->quirks & WACOM_QUIRK_NO_INPUT)) {
+ error = wacom_allocate_inputs(wacom);
+ if (error)
+ goto fail_allocate_inputs;
+ }
+
+ /*
+ * 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_shared_data;
+ }
+ }
+
+ /* 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);
+
+ /*
+ * Intuos5 has no useful data about its touch interface in its
+ * HID descriptor. If this is the touch interface (PacketSize
+ * of WACOM_PKGLEN_BBTOUCH3), override the table values.
+ */
+ if (features->type >= INTUOS5S && features->type <= INTUOSHT) {
+ if (features->pktlen == WACOM_PKGLEN_BBTOUCH3) {
+ features->device_type = BTN_TOOL_FINGER;
+
+ features->x_max = 4096;
+ features->y_max = 4096;
+ } else {
+ features->device_type = BTN_TOOL_PEN;
+ }
+ }
+
+ /*
+ * Same thing for Bamboo 3rd gen.
+ */
+ if ((features->type == BAMBOO_PT) &&
+ (features->pktlen == WACOM_PKGLEN_BBTOUCH3) &&
+ (features->device_type == BTN_TOOL_PEN)) {
+ features->device_type = BTN_TOOL_FINGER;
+
+ features->x_max = 4096;
+ features->y_max = 4096;
+ }
+
+ /*
+ * Same thing for Bamboo PAD
+ */
+ if (features->type == BAMBOO_PAD)
+ features->device_type = BTN_TOOL_FINGER;
+
+ if (hdev->bus == BUS_BLUETOOTH)
+ features->quirks |= WACOM_QUIRK_BATTERY;
+
+ wacom_setup_device_quirks(features);
+
+ /* set unit to "100th of a mm" for devices not reported by HID */
+ if (!features->unit) {
+ features->unit = 0x11;
+ features->unitExpo = -3;
+ }
+ wacom_calculate_res(features);
+
+ strlcpy(wacom_wac->name, features->name, sizeof(wacom_wac->name));
+ snprintf(wacom_wac->pad_name, sizeof(wacom_wac->pad_name),
+ "%s Pad", features->name);
+
+ /* Append the device type to the name */
+ if (features->device_type != BTN_TOOL_FINGER)
+ strlcat(wacom_wac->name, " Pen", WACOM_NAME_MAX);
+ else if (features->touch_max)
+ strlcat(wacom_wac->name, " Finger", WACOM_NAME_MAX);
+ else
+ strlcat(wacom_wac->name, " Pad", WACOM_NAME_MAX);
+
+ error = wacom_add_shared_data(hdev);
+ if (error)
+ goto fail_shared_data;
+
+ if (!(features->quirks & WACOM_QUIRK_MONITOR) &&
+ (features->quirks & WACOM_QUIRK_BATTERY)) {
+ error = wacom_initialize_battery(wacom);
+ if (error)
+ goto fail_battery;
+ }
+
+ if (!(features->quirks & WACOM_QUIRK_NO_INPUT)) {
+ error = wacom_register_inputs(wacom);
+ if (error)
+ goto fail_register_inputs;
+ }
+
+ 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);
+ }
+
+ 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_hw_start;
+ }
+
+ /* Note that if query fails it is not a hard failure */
+ wacom_query_tablet_data(hdev, features);
+
+ if (features->quirks & WACOM_QUIRK_MONITOR)
+ error = hid_hw_open(hdev);
+
+ if (wacom_wac->features.type == INTUOSHT && wacom_wac->features.touch_max) {
+ if (wacom_wac->features.device_type == BTN_TOOL_FINGER)
+ wacom_wac->shared->touch_input = wacom_wac->input;
+ }
+
+ return 0;
+
+fail_hw_start:
+ if (hdev->bus == BUS_BLUETOOTH)
+ device_remove_file(&hdev->dev, &dev_attr_speed);
+fail_register_inputs:
+ wacom_clean_inputs(wacom);
+ wacom_destroy_battery(wacom);
+fail_battery:
+ wacom_remove_shared_data(wacom);
+fail_shared_data:
+ wacom_clean_inputs(wacom);
+fail_allocate_inputs:
+fail_type:
+fail_pktlen:
+fail_parse:
+ kfree(wacom);
+ hid_set_drvdata(hdev, NULL);
+ return error;
+}
+
+static void wacom_remove(struct hid_device *hdev)
+{
+ struct wacom *wacom = hid_get_drvdata(hdev);
+
+ hid_hw_stop(hdev);
+
+ cancel_work_sync(&wacom->work);
+ wacom_clean_inputs(wacom);
+ if (hdev->bus == BUS_BLUETOOTH)
+ device_remove_file(&hdev->dev, &dev_attr_speed);
+ wacom_destroy_battery(wacom);
+ wacom_remove_shared_data(wacom);
+
+ hid_set_drvdata(hdev, NULL);
+ kfree(wacom);
+}
+
+#ifdef CONFIG_PM
+static int wacom_resume(struct hid_device *hdev)
+{
+ struct wacom *wacom = hid_get_drvdata(hdev);
+ struct wacom_features *features = &wacom->wacom_wac.features;
+
+ mutex_lock(&wacom->lock);
+
+ /* switch to wacom mode first */
+ wacom_query_tablet_data(hdev, features);
+ 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,
+ .event = wacom_wac_event,
+ .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(DRIVER_LICENSE);
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
new file mode 100644
index 000000000..adf959dcf
--- /dev/null
+++ b/drivers/hid/wacom_wac.c
@@ -0,0 +1,3288 @@
+/*
+ * 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
+
+/*
+ * 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_wac *wacom_wac,
+ int bat_capacity, bool bat_charging, bool bat_connected,
+ bool ps_connected)
+{
+ struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+ bool changed = wacom_wac->battery_capacity != bat_capacity ||
+ wacom_wac->bat_charging != bat_charging ||
+ wacom_wac->bat_connected != bat_connected ||
+ wacom_wac->ps_connected != ps_connected;
+
+ if (changed) {
+ wacom_wac->battery_capacity = bat_capacity;
+ wacom_wac->bat_charging = bat_charging;
+ wacom_wac->bat_connected = bat_connected;
+ wacom_wac->ps_connected = ps_connected;
+
+ if (wacom->battery)
+ power_supply_changed(wacom->battery);
+ }
+}
+
+static int wacom_penpartner_irq(struct wacom_wac *wacom)
+{
+ unsigned char *data = wacom->data;
+ struct input_dev *input = wacom->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->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 (prox) {
+ wacom->id[0] = ERASER_DEVICE_ID;
+ 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;
+
+ /*
+ * if going from out of proximity into proximity select between the eraser
+ * and the pen based on the state of the stylus2 button, choose eraser if
+ * pressed else choose pen. if not a proximity change from out to in, send
+ * an out of proximity for previous tool then a in for new tool.
+ */
+ if (!wacom->tool[0]) {
+ /* Eraser bit set for DTF */
+ if (data[1] & 0x10)
+ wacom->tool[1] = BTN_TOOL_RUBBER;
+ else
+ /* Going into proximity select tool */
+ wacom->tool[1] = (data[4] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+ } else {
+ /* was entered with stylus2 pressed */
+ if (wacom->tool[1] == BTN_TOOL_RUBBER && !(data[4] & 0x20)) {
+ /* report out proximity for previous tool */
+ input_report_key(input, wacom->tool[1], 0);
+ input_sync(input);
+ wacom->tool[1] = BTN_TOOL_PEN;
+ return 0;
+ }
+ }
+ if (wacom->tool[1] != BTN_TOOL_RUBBER) {
+ /* Unknown tool selected default to pen tool */
+ wacom->tool[1] = BTN_TOOL_PEN;
+ wacom->id[0] = STYLUS_DEVICE_ID;
+ }
+ input_report_key(input, wacom->tool[1], prox); /* report in proximity for tool */
+ input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
+ 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[1] == BTN_TOOL_PEN) && (data[4] & 0x20));
+ } else {
+ /* report proximity-out of a (valid) tool */
+ if (wacom->tool[1] != BTN_TOOL_RUBBER) {
+ /* Unknown tool selected default to pen tool */
+ wacom->tool[1] = BTN_TOOL_PEN;
+ }
+ input_report_key(input, wacom->tool[1], prox);
+ }
+
+ wacom->tool[0] = prox; /* Save proximity state */
+ return 1;
+}
+
+static int wacom_ptu_irq(struct wacom_wac *wacom)
+{
+ unsigned char *data = wacom->data;
+ struct input_dev *input = wacom->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->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->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->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, 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 hid_report *r;
+ struct hid_report_enum *re;
+
+ re = &(wacom->hdev->report_enum[HID_FEATURE_REPORT]);
+ r = re->report_id_hash[WACOM_REPORT_INTUOSREAD];
+ if (r) {
+ hid_hw_request(wacom->hdev, r, HID_REQ_GET_REPORT);
+ }
+}
+
+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->input;
+ int idx = 0;
+
+ /* tool number */
+ if (features->type == INTUOS)
+ idx = data[1] & 0x01;
+
+ /* 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) << 20) | ((data[8] & 0xf0) << 12);
+
+ switch (wacom->id[idx]) {
+ case 0x812: /* Inking pen */
+ case 0x801: /* Intuos3 Inking pen */
+ case 0x120802: /* Intuos4/5 Inking Pen */
+ case 0x012:
+ wacom->tool[idx] = 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 0x022:
+ case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */
+ case 0x140802: /* Intuos4/5 13HD/24HD Classic Pen */
+ case 0x160802: /* Cintiq 13HD Pro Pen */
+ case 0x180802: /* DTH2242 Pen */
+ case 0x100802: /* Intuos4/5 13HD/24HD General Pen */
+ wacom->tool[idx] = BTN_TOOL_PEN;
+ break;
+
+ case 0x832: /* Stroke pen */
+ case 0x032:
+ wacom->tool[idx] = BTN_TOOL_BRUSH;
+ break;
+
+ case 0x007: /* Mouse 4D and 2D */
+ case 0x09c:
+ case 0x094:
+ case 0x017: /* Intuos3 2D Mouse */
+ case 0x806: /* Intuos4 Mouse */
+ wacom->tool[idx] = BTN_TOOL_MOUSE;
+ break;
+
+ case 0x096: /* Lens cursor */
+ case 0x097: /* Intuos3 Lens cursor */
+ case 0x006: /* Intuos4 Lens cursor */
+ wacom->tool[idx] = BTN_TOOL_LENS;
+ break;
+
+ case 0x82a: /* Eraser */
+ 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 0x14080a: /* Intuos4/5 13HD/24HD Classic Pen Eraser */
+ case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
+ case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
+ case 0x16080a: /* Cintiq 13HD Pro Pen Eraser */
+ case 0x18080a: /* DTH2242 Eraser */
+ case 0x10080a: /* Intuos4/5 13HD/24HD General Pen Eraser */
+ wacom->tool[idx] = BTN_TOOL_RUBBER;
+ break;
+
+ case 0xd12:
+ case 0x912:
+ case 0x112:
+ case 0x913: /* Intuos3 Airbrush */
+ case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
+ case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */
+ wacom->tool[idx] = BTN_TOOL_AIRBRUSH;
+ break;
+
+ default: /* Unknown tool */
+ wacom->tool[idx] = BTN_TOOL_PEN;
+ break;
+ }
+ return 1;
+ }
+
+ /*
+ * don't report events for invalid data
+ */
+ /* older I4 styli don't work with new Cintiqs */
+ if ((!((wacom->id[idx] >> 20) & 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;
+
+ wacom->shared->stylus_in_proximity = true;
+ if (wacom->shared->touch_down)
+ return 1;
+
+ /* in Range while exiting */
+ if (((data[1] & 0xfe) == 0x20) && 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;
+ }
+
+ /* 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;
+
+ /*
+ * 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;
+ return 2;
+ }
+
+ /* don't report other events if we don't know the ID */
+ if (!wacom->id[idx]) {
+ /* but reschedule a read of the current tool */
+ wacom_intuos_schedule_prox_event(wacom);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void wacom_intuos_general(struct wacom_wac *wacom)
+{
+ struct wacom_features *features = &wacom->features;
+ unsigned char *data = wacom->data;
+ struct input_dev *input = wacom->input;
+ unsigned int t;
+
+ /* general pen packet */
+ if ((data[1] & 0xb8) == 0xa0) {
+ t = (data[6] << 2) | ((data[7] >> 6) & 3);
+ if (features->type >= INTUOS4S && features->type <= CINTIQ_HYBRID) {
+ t = (t << 1) | (data[1] & 1);
+ }
+ input_report_abs(input, ABS_PRESSURE, t);
+ 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);
+ }
+
+ /* airbrush second packet */
+ if ((data[1] & 0xbc) == 0xb4) {
+ 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);
+ }
+}
+
+static int wacom_intuos_irq(struct wacom_wac *wacom)
+{
+ struct wacom_features *features = &wacom->features;
+ unsigned char *data = wacom->data;
+ struct input_dev *input = wacom->input;
+ unsigned int t;
+ int idx = 0, result;
+
+ if (data[0] != WACOM_REPORT_PENABLED &&
+ data[0] != WACOM_REPORT_INTUOSREAD &&
+ data[0] != WACOM_REPORT_INTUOSWRITE &&
+ data[0] != WACOM_REPORT_INTUOSPAD &&
+ 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;
+ }
+
+ /* tool number */
+ if (features->type == INTUOS)
+ idx = data[1] & 0x01;
+
+ /* 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) {
+ input = wacom->pad_input;
+ if (features->type >= INTUOS4S && features->type <= INTUOS4L) {
+ input_report_key(input, BTN_0, (data[2] & 0x01));
+ input_report_key(input, BTN_1, (data[3] & 0x01));
+ input_report_key(input, BTN_2, (data[3] & 0x02));
+ input_report_key(input, BTN_3, (data[3] & 0x04));
+ input_report_key(input, BTN_4, (data[3] & 0x08));
+ input_report_key(input, BTN_5, (data[3] & 0x10));
+ input_report_key(input, BTN_6, (data[3] & 0x20));
+ if (data[1] & 0x80) {
+ input_report_abs(input, ABS_WHEEL, (data[1] & 0x7f));
+ } else {
+ /* Out of proximity, clear wheel value. */
+ input_report_abs(input, ABS_WHEEL, 0);
+ }
+ if (features->type != INTUOS4S) {
+ input_report_key(input, BTN_7, (data[3] & 0x40));
+ input_report_key(input, BTN_8, (data[3] & 0x80));
+ }
+ if (data[1] | (data[2] & 0x01) | data[3]) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } else if (features->type == DTK) {
+ input_report_key(input, BTN_0, (data[6] & 0x01));
+ input_report_key(input, BTN_1, (data[6] & 0x02));
+ input_report_key(input, BTN_2, (data[6] & 0x04));
+ input_report_key(input, BTN_3, (data[6] & 0x08));
+ input_report_key(input, BTN_4, (data[6] & 0x10));
+ input_report_key(input, BTN_5, (data[6] & 0x20));
+ if (data[6] & 0x3f) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } else if (features->type == WACOM_13HD) {
+ input_report_key(input, BTN_0, (data[3] & 0x01));
+ input_report_key(input, BTN_1, (data[4] & 0x01));
+ input_report_key(input, BTN_2, (data[4] & 0x02));
+ input_report_key(input, BTN_3, (data[4] & 0x04));
+ input_report_key(input, BTN_4, (data[4] & 0x08));
+ input_report_key(input, BTN_5, (data[4] & 0x10));
+ input_report_key(input, BTN_6, (data[4] & 0x20));
+ input_report_key(input, BTN_7, (data[4] & 0x40));
+ input_report_key(input, BTN_8, (data[4] & 0x80));
+ if ((data[3] & 0x01) | data[4]) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } else if (features->type == WACOM_24HD) {
+ input_report_key(input, BTN_0, (data[6] & 0x01));
+ input_report_key(input, BTN_1, (data[6] & 0x02));
+ input_report_key(input, BTN_2, (data[6] & 0x04));
+ input_report_key(input, BTN_3, (data[6] & 0x08));
+ input_report_key(input, BTN_4, (data[6] & 0x10));
+ input_report_key(input, BTN_5, (data[6] & 0x20));
+ input_report_key(input, BTN_6, (data[6] & 0x40));
+ input_report_key(input, BTN_7, (data[6] & 0x80));
+ input_report_key(input, BTN_8, (data[8] & 0x01));
+ input_report_key(input, BTN_9, (data[8] & 0x02));
+ input_report_key(input, BTN_A, (data[8] & 0x04));
+ input_report_key(input, BTN_B, (data[8] & 0x08));
+ input_report_key(input, BTN_C, (data[8] & 0x10));
+ input_report_key(input, BTN_X, (data[8] & 0x20));
+ input_report_key(input, BTN_Y, (data[8] & 0x40));
+ input_report_key(input, BTN_Z, (data[8] & 0x80));
+
+ /*
+ * 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])
+ */
+ input_report_key(input, KEY_PROG1, data[4] & 0x07);
+ input_report_key(input, KEY_PROG2, data[4] & 0xE0);
+ input_report_key(input, KEY_PROG3, data[3] & 0x1C);
+
+ if (data[1] & 0x80) {
+ input_report_abs(input, ABS_WHEEL, (data[1] & 0x7f));
+ } else {
+ /* Out of proximity, clear wheel value. */
+ input_report_abs(input, ABS_WHEEL, 0);
+ }
+
+ if (data[2] & 0x80) {
+ input_report_abs(input, ABS_THROTTLE, (data[2] & 0x7f));
+ } else {
+ /* Out of proximity, clear second wheel value. */
+ input_report_abs(input, ABS_THROTTLE, 0);
+ }
+
+ if (data[1] | data[2] | (data[3] & 0x1f) | data[4] | data[6] | data[8]) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } else if (features->type == WACOM_27QHD) {
+ input_report_key(input, KEY_PROG1, data[2] & 0x01);
+ input_report_key(input, KEY_PROG2, data[2] & 0x02);
+ input_report_key(input, KEY_PROG3, data[2] & 0x04);
+
+ 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]));
+ if ((data[2] & 0x07) | data[4] | data[5] | data[6] | data[7] | data[8] | data[9]) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } 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).
+ */
+ input_report_key(input, BTN_1, (data[4] & 0x01));
+ input_report_key(input, BTN_2, (data[4] & 0x02));
+ input_report_key(input, BTN_3, (data[4] & 0x04));
+ input_report_key(input, BTN_4, (data[4] & 0x08));
+
+ input_report_key(input, BTN_5, (data[4] & 0x10)); /* Right */
+ input_report_key(input, BTN_6, (data[4] & 0x20)); /* Up */
+ input_report_key(input, BTN_7, (data[4] & 0x40)); /* Left */
+ input_report_key(input, BTN_8, (data[4] & 0x80)); /* Down */
+ input_report_key(input, BTN_0, (data[3] & 0x01)); /* Center */
+
+ if (data[4] | (data[3] & 0x01)) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } else if (features->type >= INTUOS5S && features->type <= INTUOSPL) {
+ int i;
+
+ /* Touch ring mode switch has no capacitive sensor */
+ input_report_key(input, BTN_0, (data[3] & 0x01));
+
+ /*
+ * 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].
+ */
+ for (i = 0; i < 8; i++)
+ input_report_key(input, BTN_1 + i, data[4] & (1 << i));
+
+ if (data[2] & 0x80) {
+ input_report_abs(input, ABS_WHEEL, (data[2] & 0x7f));
+ } else {
+ /* Out of proximity, clear wheel value. */
+ input_report_abs(input, ABS_WHEEL, 0);
+ }
+
+ if (data[2] | (data[3] & 0x01) | data[4] | data[5]) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ } else {
+ if (features->type == WACOM_21UX2 || features->type == WACOM_22HD) {
+ input_report_key(input, BTN_0, (data[5] & 0x01));
+ input_report_key(input, BTN_1, (data[6] & 0x01));
+ input_report_key(input, BTN_2, (data[6] & 0x02));
+ input_report_key(input, BTN_3, (data[6] & 0x04));
+ input_report_key(input, BTN_4, (data[6] & 0x08));
+ input_report_key(input, BTN_5, (data[6] & 0x10));
+ input_report_key(input, BTN_6, (data[6] & 0x20));
+ input_report_key(input, BTN_7, (data[6] & 0x40));
+ input_report_key(input, BTN_8, (data[6] & 0x80));
+ input_report_key(input, BTN_9, (data[7] & 0x01));
+ input_report_key(input, BTN_A, (data[8] & 0x01));
+ input_report_key(input, BTN_B, (data[8] & 0x02));
+ input_report_key(input, BTN_C, (data[8] & 0x04));
+ input_report_key(input, BTN_X, (data[8] & 0x08));
+ input_report_key(input, BTN_Y, (data[8] & 0x10));
+ input_report_key(input, BTN_Z, (data[8] & 0x20));
+ input_report_key(input, BTN_BASE, (data[8] & 0x40));
+ input_report_key(input, BTN_BASE2, (data[8] & 0x80));
+
+ if (features->type == WACOM_22HD) {
+ input_report_key(input, KEY_PROG1, data[9] & 0x01);
+ input_report_key(input, KEY_PROG2, data[9] & 0x02);
+ input_report_key(input, KEY_PROG3, data[9] & 0x04);
+ }
+ } else {
+ input_report_key(input, BTN_0, (data[5] & 0x01));
+ input_report_key(input, BTN_1, (data[5] & 0x02));
+ input_report_key(input, BTN_2, (data[5] & 0x04));
+ input_report_key(input, BTN_3, (data[5] & 0x08));
+ input_report_key(input, BTN_4, (data[6] & 0x01));
+ input_report_key(input, BTN_5, (data[6] & 0x02));
+ input_report_key(input, BTN_6, (data[6] & 0x04));
+ input_report_key(input, BTN_7, (data[6] & 0x08));
+ input_report_key(input, BTN_8, (data[5] & 0x10));
+ input_report_key(input, BTN_9, (data[6] & 0x10));
+ }
+ input_report_abs(input, ABS_RX, ((data[1] & 0x1f) << 8) | data[2]);
+ input_report_abs(input, ABS_RY, ((data[3] & 0x1f) << 8) | data[4]);
+
+ if ((data[5] & 0x1f) | data[6] | (data[1] & 0x1f) |
+ data[2] | (data[3] & 0x1f) | data[4] | data[8] |
+ (data[7] & 0x01)) {
+ input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+ } else {
+ input_report_abs(input, ABS_MISC, 0);
+ }
+ }
+ return 1;
+ }
+
+ /* process in/out prox events */
+ result = wacom_intuos_inout(wacom);
+ if (result)
+ return result - 1;
+
+ if (features->type >= INTUOS3S) {
+ input_report_abs(input, ABS_X, (data[2] << 9) | (data[3] << 1) | ((data[9] >> 1) & 1));
+ input_report_abs(input, ABS_Y, (data[4] << 9) | (data[5] << 1) | (data[9] & 1));
+ input_report_abs(input, ABS_DISTANCE, ((data[9] >> 2) & 0x3f));
+ } else {
+ input_report_abs(input, ABS_X, be16_to_cpup((__be16 *)&data[2]));
+ input_report_abs(input, ABS_Y, be16_to_cpup((__be16 *)&data[4]));
+ input_report_abs(input, ABS_DISTANCE, ((data[9] >> 3) & 0x1f));
+ }
+
+ /* process general packets */
+ wacom_intuos_general(wacom);
+
+ /* 4D mouse, 2D mouse, marker pen rotation, tilt mouse, or Lens cursor packets */
+ if ((data[1] & 0xbc) == 0xa8 || (data[1] & 0xbe) == 0xb0 || (data[1] & 0xbc) == 0xac) {
+
+ if (data[1] & 0x02) {
+ /* 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 rotation packet */
+ t = (data[6] << 3) | ((data[7] >> 5) & 7);
+ input_report_abs(input, ABS_RZ, (data[7] & 0x20) ?
+ ((t - 1) / 2) : -t / 2);
+ }
+
+ } else if (!(data[1] & 0x10) && features->type < INTUOS3S) {
+ /* 4D mouse 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);
+
+ } else if (wacom->tool[idx] == BTN_TOOL_MOUSE) {
+ /* I4 mouse */
+ if (features->type >= INTUOS4S && features->type <= INTUOSPL) {
+ 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);
+ } else {
+ /* 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 ((features->type < INTUOS3S || features->type == INTUOS3L ||
+ features->type == INTUOS4L || features->type == INTUOS5L ||
+ features->type == INTUOSPL) &&
+ 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);
+ }
+ }
+
+ input_report_abs(input, ABS_MISC, 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 1;
+}
+
+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->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, battery_capacity, bat_charging,
+ battery_capacity || bat_charging,
+ ps_connected);
+ break;
+ default:
+ dev_dbg(wacom->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->input;
+ unsigned touch_max = wacom->features.touch_max;
+ int count = 0;
+ int i;
+
+ if (!touch_max)
+ return 0;
+
+ /* non-HID_GENERIC single touch input doesn't call this routine */
+ if ((touch_max == 1) && (wacom->features.type == HID_GENERIC))
+ return wacom->hid_data.tipswitch &&
+ !wacom->shared->stylus_in_proximity;
+
+ 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 int wacom_24hdt_irq(struct wacom_wac *wacom)
+{
+ struct input_dev *input = wacom->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) && !wacom->shared->stylus_in_proximity;
+ 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->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) && !wacom->shared->stylus_in_proximity;
+ 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->input;
+ unsigned char *data = wacom->data;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ int p = data[1] & (1 << i);
+ bool touch = p && !wacom->shared->stylus_in_proximity;
+
+ 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->input;
+ bool prox = !wacom->shared->stylus_in_proximity;
+ 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->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 */
+ if (!wacom->shared->touch_down) {
+ 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;
+
+ dev_dbg(wacom->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);
+
+ case WACOM_PKGLEN_PENABLED:
+ return wacom_tpc_pen(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);
+
+ case WACOM_REPORT_PENABLED:
+ return wacom_tpc_pen(wacom);
+ }
+ }
+
+ return 0;
+}
+
+static void wacom_map_usage(struct wacom *wacom, struct hid_usage *usage,
+ struct hid_field *field, __u8 type, __u16 code, int fuzz)
+{
+ struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+ struct input_dev *input = wacom_wac->input;
+ int fmin = field->logical_minimum;
+ int fmax = field->logical_maximum;
+
+ 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, code));
+ break;
+ case EV_KEY:
+ input_set_capability(input, EV_KEY, code);
+ break;
+ case EV_MSC:
+ input_set_capability(input, EV_MSC, code);
+ break;
+ }
+}
+
+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);
+
+ switch (usage->hid) {
+ case HID_GD_X:
+ wacom_map_usage(wacom, usage, field, EV_ABS, ABS_X, 4);
+ break;
+ case HID_GD_Y:
+ wacom_map_usage(wacom, usage, field, EV_ABS, ABS_Y, 4);
+ break;
+ case HID_DG_TIPPRESSURE:
+ wacom_map_usage(wacom, usage, field, EV_ABS, ABS_PRESSURE, 0);
+ break;
+ case HID_DG_INRANGE:
+ wacom_map_usage(wacom, usage, field, EV_KEY, BTN_TOOL_PEN, 0);
+ break;
+ case HID_DG_INVERT:
+ wacom_map_usage(wacom, usage, field, EV_KEY,
+ BTN_TOOL_RUBBER, 0);
+ break;
+ case HID_DG_ERASER:
+ case HID_DG_TIPSWITCH:
+ wacom_map_usage(wacom, usage, field, EV_KEY, BTN_TOUCH, 0);
+ break;
+ case HID_DG_BARRELSWITCH:
+ wacom_map_usage(wacom, usage, field, EV_KEY, BTN_STYLUS, 0);
+ break;
+ case HID_DG_BARRELSWITCH2:
+ wacom_map_usage(wacom, usage, field, EV_KEY, BTN_STYLUS2, 0);
+ break;
+ case HID_DG_TOOLSERIALNUMBER:
+ wacom_map_usage(wacom, usage, field, EV_MSC, MSC_SERIAL, 0);
+ break;
+ }
+}
+
+static int 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 input_dev *input = wacom_wac->input;
+
+ /* checking which Tool / tip switch to send */
+ switch (usage->hid) {
+ case HID_DG_INRANGE:
+ wacom_wac->hid_data.inrange_state = value;
+ return 0;
+ case HID_DG_INVERT:
+ wacom_wac->hid_data.invert_state = value;
+ return 0;
+ case HID_DG_ERASER:
+ case HID_DG_TIPSWITCH:
+ wacom_wac->hid_data.tipswitch |= value;
+ return 0;
+ }
+
+ /* send pen events only when touch is up or forced out */
+ if (!usage->type || wacom_wac->shared->touch_down)
+ return 0;
+
+ input_event(input, usage->type, usage->code, value);
+
+ return 0;
+}
+
+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->input;
+ bool prox = wacom_wac->hid_data.inrange_state;
+
+ if (!wacom_wac->shared->stylus_in_proximity) /* first in prox */
+ /* Going into proximity select tool */
+ wacom_wac->tool[0] = wacom_wac->hid_data.invert_state ?
+ BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+
+ /* keep pen state for touch events */
+ wacom_wac->shared->stylus_in_proximity = prox;
+
+ /* send pen events only when touch is up or forced out */
+ if (!wacom_wac->shared->touch_down) {
+ input_report_key(input, BTN_TOUCH,
+ wacom_wac->hid_data.tipswitch);
+ input_report_key(input, wacom_wac->tool[0], prox);
+
+ wacom_wac->hid_data.tipswitch = false;
+
+ input_sync(input);
+ }
+}
+
+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 wacom_features *features = &wacom_wac->features;
+ unsigned touch_max = wacom_wac->features.touch_max;
+
+ switch (usage->hid) {
+ case HID_GD_X:
+ features->last_slot_field = usage->hid;
+ if (touch_max == 1)
+ wacom_map_usage(wacom, usage, field, EV_ABS, ABS_X, 4);
+ else
+ wacom_map_usage(wacom, usage, field, EV_ABS,
+ ABS_MT_POSITION_X, 4);
+ break;
+ case HID_GD_Y:
+ features->last_slot_field = usage->hid;
+ if (touch_max == 1)
+ wacom_map_usage(wacom, usage, field, EV_ABS, ABS_Y, 4);
+ else
+ wacom_map_usage(wacom, usage, field, EV_ABS,
+ ABS_MT_POSITION_Y, 4);
+ break;
+ case HID_DG_CONTACTID:
+ features->last_slot_field = usage->hid;
+ break;
+ case HID_DG_INRANGE:
+ features->last_slot_field = usage->hid;
+ break;
+ case HID_DG_INVERT:
+ features->last_slot_field = usage->hid;
+ break;
+ case HID_DG_TIPSWITCH:
+ features->last_slot_field = usage->hid;
+ wacom_map_usage(wacom, usage, field, EV_KEY, BTN_TOUCH, 0);
+ 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 &&
+ !wacom_wac->shared->stylus_in_proximity;
+
+ 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);
+ }
+}
+
+static int 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;
+
+ switch (usage->hid) {
+ 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_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 (usage->hid == wacom_wac->features.last_slot_field)
+ wacom_wac_finger_slot(wacom_wac, wacom_wac->input);
+ }
+
+ return 0;
+}
+
+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->input;
+ unsigned touch_max = wacom_wac->features.touch_max;
+
+ if (touch_max > 1)
+ input_mt_sync_frame(input);
+
+ input_sync(input);
+
+ /* 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 input_dev *input = wacom_wac->input;
+
+ /* currently, only direct devices have proper hid report descriptors */
+ __set_bit(INPUT_PROP_DIRECT, input->propbit);
+
+ if (WACOM_PEN_FIELD(field))
+ return wacom_wac_pen_usage_mapping(hdev, field, usage);
+
+ if (WACOM_FINGER_FIELD(field))
+ return wacom_wac_finger_usage_mapping(hdev, field, usage);
+}
+
+int 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 0;
+
+ if (WACOM_PEN_FIELD(field))
+ return wacom_wac_pen_event(hdev, field, usage, value);
+
+ if (WACOM_FINGER_FIELD(field))
+ return wacom_wac_finger_event(hdev, field, usage, value);
+
+ 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 = report->field[0];
+
+ if (wacom_wac->features.type != HID_GENERIC)
+ return;
+
+ if (WACOM_PEN_FIELD(field))
+ return wacom_wac_pen_report(hdev, report);
+
+ if (WACOM_FINGER_FIELD(field))
+ return wacom_wac_finger_report(hdev, report);
+}
+
+static int wacom_bpt_touch(struct wacom_wac *wacom)
+{
+ struct wacom_features *features = &wacom->features;
+ struct input_dev *input = wacom->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 = data[offset + 3] & 0x80;
+
+ /*
+ * Touch events need to be disabled while stylus is
+ * in proximity because user's hand is resting on touchpad
+ * and sending unwanted events. User expects tablet buttons
+ * to continue working though.
+ */
+ touch = touch && !wacom->shared->stylus_in_proximity;
+
+ 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->input;
+ bool touch = data[1] & 0x80;
+ int slot = input_mt_get_slot_by_key(input, data[0]);
+
+ if (slot < 0)
+ return;
+
+ touch = touch && !wacom->shared->stylus_in_proximity;
+
+ 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 <= INTUOSPL) {
+ 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) {
+ 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)
+{
+ struct input_dev *input = wacom->input;
+ unsigned char *data = wacom->data;
+ int count = data[1] & 0x07;
+ int 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);
+ else if (msg_id == 128)
+ wacom_bpt3_button_msg(wacom, data + offset);
+
+ }
+ input_mt_sync_frame(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->input;
+ unsigned char *data = wacom->data;
+ int prox = 0, x = 0, y = 0, p = 0, d = 0, pen = 0, btn1 = 0, btn2 = 0;
+
+ if (data[0] != WACOM_REPORT_PENABLED)
+ return 0;
+
+ prox = (data[1] & 0x20) == 0x20;
+
+ /*
+ * All reports shared between PEN and RUBBER tool must be
+ * forced to a known starting value (zero) when transitioning to
+ * out-of-prox.
+ *
+ * If not reset then, to userspace, it will look like lost events
+ * if new tool comes in-prox with same values as previous tool sent.
+ *
+ * Hardware does report zero in most out-of-prox cases but not all.
+ */
+ if (!wacom->shared->stylus_in_proximity) {
+ 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->shared->stylus_in_proximity = prox;
+ if (wacom->shared->touch_down)
+ return 0;
+
+ if (prox) {
+ x = le16_to_cpup((__le16 *)&data[2]);
+ y = le16_to_cpup((__le16 *)&data[4]);
+ p = le16_to_cpup((__le16 *)&data[6]);
+ /*
+ * 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];
+
+ pen = data[1] & 0x01;
+ btn1 = data[1] & 0x02;
+ btn2 = data[1] & 0x04;
+ } else {
+ wacom->id[0] = 0;
+ }
+
+ input_report_key(input, BTN_TOUCH, pen);
+ input_report_key(input, BTN_STYLUS, btn1);
+ input_report_key(input, BTN_STYLUS2, btn2);
+
+ 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], prox); /* PEN or RUBBER */
+ input_report_abs(input, ABS_MISC, wacom->id[0]); /* TOOL ID */
+
+ return 1;
+}
+
+static int wacom_bpt_irq(struct wacom_wac *wacom, size_t len)
+{
+ 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->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)) &&
+ !wacom->shared->stylus_in_proximity;
+
+ 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 &&
+ !wacom->shared->stylus_in_proximity;
+
+ 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->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);
+ }
+
+ if (wacom->shared->type)
+ wacom_notify_battery(wacom, battery, charging, 1, 0);
+
+ } else if (wacom->pid != 0) {
+ /* disconnected while previously connected */
+ wacom->pid = 0;
+ wacom_schedule_work(wacom);
+ wacom_notify_battery(wacom, 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 &&
+ 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, battery, charging,
+ battery || charging, 1);
+
+ if (!wacom->battery &&
+ !(features->quirks & WACOM_QUIRK_BATTERY)) {
+ features->quirks |= WACOM_QUIRK_BATTERY;
+ INIT_WORK(&wacom->work, wacom_battery_work);
+ wacom_schedule_work(wacom_wac);
+ }
+ }
+ else if ((features->quirks & WACOM_QUIRK_BATTERY) &&
+ wacom->battery) {
+ features->quirks &= ~WACOM_QUIRK_BATTERY;
+ INIT_WORK(&wacom->work, wacom_battery_work);
+ wacom_schedule_work(wacom_wac);
+ wacom_notify_battery(wacom_wac, 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:
+ 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 TABLETPC:
+ case TABLETPCE:
+ case TABLETPC2FG:
+ case MTSCREEN:
+ case MTTPC:
+ case MTTPC_B:
+ sync = wacom_tpc_irq(wacom_wac, len);
+ break;
+
+ case BAMBOO_PT:
+ case INTUOSHT:
+ 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;
+
+ default:
+ sync = false;
+ break;
+ }
+
+ if (sync) {
+ input_sync(wacom_wac->input);
+ if (wacom_wac->pad_input)
+ input_sync(wacom_wac->pad_input);
+ }
+}
+
+static void wacom_setup_cintiq(struct wacom_wac *wacom_wac)
+{
+ struct input_dev *input_dev = wacom_wac->input;
+
+ input_set_capability(input_dev, EV_MSC, MSC_SERIAL);
+
+ __set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+ __set_bit(BTN_TOOL_PEN, 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);
+ __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, 0, 0);
+ input_set_abs_params(input_dev, ABS_WHEEL, 0, 1023, 0, 0);
+ input_set_abs_params(input_dev, ABS_TILT_X, -64, 63, 0, 0);
+ input_abs_set_res(input_dev, ABS_TILT_X, 57);
+ input_set_abs_params(input_dev, ABS_TILT_Y, -64, 63, 0, 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->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_features *features)
+{
+
+ /* touch device found but size is not defined. use default */
+ if (features->device_type == BTN_TOOL_FINGER && !features->x_max) {
+ features->x_max = 1023;
+ features->y_max = 1023;
+ }
+
+ /* quirk for bamboo touch with 2 low res touches */
+ if (features->type == BAMBOO_PT &&
+ 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) {
+
+ /* monitor never has input and pen/touch have delayed create */
+ features->quirks |= WACOM_QUIRK_NO_INPUT;
+
+ /* must be monitor interface if no device_type set */
+ if (!features->device_type) {
+ features->quirks |= WACOM_QUIRK_MONITOR;
+ features->quirks |= WACOM_QUIRK_BATTERY;
+ }
+ }
+}
+
+static void wacom_abs_set_axis(struct input_dev *input_dev,
+ struct wacom_wac *wacom_wac)
+{
+ struct wacom_features *features = &wacom_wac->features;
+
+ if (features->device_type == BTN_TOOL_PEN) {
+ input_set_abs_params(input_dev, ABS_X, features->x_min,
+ features->x_max, features->x_fuzz, 0);
+ input_set_abs_params(input_dev, ABS_Y, features->y_min,
+ features->y_max, 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);
+ } else {
+ 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);
+ }
+
+ 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);
+ }
+ }
+}
+
+int wacom_setup_pentouch_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->type == HID_GENERIC)
+ /* setup has already been done */
+ return 0;
+
+ __set_bit(BTN_TOUCH, input_dev->keybit);
+ __set_bit(ABS_MISC, input_dev->absbit);
+
+ wacom_abs_set_axis(input_dev, wacom_wac);
+
+ 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,
+ 0, 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);
+
+ __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+ 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:
+ input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
+ input_abs_set_res(input_dev, ABS_Z, 287);
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+ 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:
+ __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+
+ wacom_setup_intuos(wacom_wac);
+ break;
+
+ case INTUOS5:
+ case INTUOS5L:
+ case INTUOSPM:
+ case INTUOSPL:
+ case INTUOS5S:
+ case INTUOSPS:
+ __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+
+ if (features->device_type == BTN_TOOL_PEN) {
+ input_set_abs_params(input_dev, ABS_DISTANCE, 0,
+ features->distance_max,
+ 0, 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);
+ } else if (features->device_type == BTN_TOOL_FINGER) {
+ __clear_bit(ABS_MISC, input_dev->absbit);
+
+ 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:
+ if (features->device_type == BTN_TOOL_FINGER) {
+ 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:
+ if (features->device_type == BTN_TOOL_FINGER && features->touch_max > 1)
+ input_mt_init_slots(input_dev, features->touch_max, INPUT_MT_DIRECT);
+ /* fall through */
+
+ case TABLETPC:
+ case TABLETPCE:
+ __clear_bit(ABS_MISC, input_dev->absbit);
+
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+
+ if (features->device_type != BTN_TOOL_PEN)
+ break; /* no need to process stylus stuff */
+
+ /* 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);
+
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+ 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);
+
+ __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+ break;
+
+ case INTUOSHT:
+ if (features->touch_max &&
+ features->device_type == BTN_TOOL_FINGER) {
+ input_dev->evbit[0] |= BIT_MASK(EV_SW);
+ __set_bit(SW_MUTE_DEVICE, input_dev->swbit);
+ }
+ /* fall through */
+
+ case BAMBOO_PT:
+ __clear_bit(ABS_MISC, input_dev->absbit);
+
+ if (features->device_type == BTN_TOOL_FINGER) {
+
+ if (features->touch_max) {
+ 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);
+ } else {
+ /* buttons/keys only interface */
+ __clear_bit(ABS_X, input_dev->absbit);
+ __clear_bit(ABS_Y, input_dev->absbit);
+ __clear_bit(BTN_TOUCH, input_dev->keybit);
+
+ /* PAD is setup by wacom_setup_pad_input_capabilities later */
+ return 1;
+ }
+ } else if (features->device_type == BTN_TOOL_PEN) {
+ __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+ __set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+ __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,
+ features->distance_max,
+ 0, 0);
+ }
+ break;
+ case BAMBOO_PAD:
+ __clear_bit(ABS_MISC, input_dev->absbit);
+ 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;
+}
+
+int wacom_setup_pad_input_capabilities(struct input_dev *input_dev,
+ struct wacom_wac *wacom_wac)
+{
+ struct wacom_features *features = &wacom_wac->features;
+ int i;
+
+ 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 */
+ input_set_abs_params(input_dev, ABS_X, 0, 1, 0, 0);
+ 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);
+
+ switch (features->type) {
+ case GRAPHIRE_BT:
+ __set_bit(BTN_0, input_dev->keybit);
+ __set_bit(BTN_1, input_dev->keybit);
+ 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(BTN_A, input_dev->keybit);
+ __set_bit(BTN_B, input_dev->keybit);
+ __set_bit(BTN_C, input_dev->keybit);
+ __set_bit(BTN_X, input_dev->keybit);
+ __set_bit(BTN_Y, input_dev->keybit);
+ __set_bit(BTN_Z, input_dev->keybit);
+
+ for (i = 0; i < 10; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ __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 DTK:
+ for (i = 0; i < 6; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ 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:
+ __set_bit(BTN_A, input_dev->keybit);
+ __set_bit(BTN_B, input_dev->keybit);
+ __set_bit(BTN_C, input_dev->keybit);
+ __set_bit(BTN_X, input_dev->keybit);
+ __set_bit(BTN_Y, input_dev->keybit);
+ __set_bit(BTN_Z, input_dev->keybit);
+ __set_bit(BTN_BASE, input_dev->keybit);
+ __set_bit(BTN_BASE2, input_dev->keybit);
+ /* fall through */
+
+ case WACOM_BEE:
+ __set_bit(BTN_8, input_dev->keybit);
+ __set_bit(BTN_9, input_dev->keybit);
+ /* fall through */
+
+ case CINTIQ:
+ for (i = 0; i < 8; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ 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:
+ for (i = 0; i < 9; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+ break;
+
+ case INTUOS3:
+ case INTUOS3L:
+ __set_bit(BTN_4, input_dev->keybit);
+ __set_bit(BTN_5, input_dev->keybit);
+ __set_bit(BTN_6, input_dev->keybit);
+ __set_bit(BTN_7, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_RY, 0, 4096, 0, 0);
+ /* fall through */
+
+ case INTUOS3S:
+ __set_bit(BTN_0, input_dev->keybit);
+ __set_bit(BTN_1, input_dev->keybit);
+ __set_bit(BTN_2, input_dev->keybit);
+ __set_bit(BTN_3, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_RX, 0, 4096, 0, 0);
+ break;
+
+ case INTUOS5:
+ case INTUOS5L:
+ case INTUOSPM:
+ case INTUOSPL:
+ __set_bit(BTN_7, input_dev->keybit);
+ __set_bit(BTN_8, input_dev->keybit);
+ /* fall through */
+
+ case INTUOS5S:
+ case INTUOSPS:
+ /* touch interface does not have the pad device */
+ if (features->device_type != BTN_TOOL_PEN)
+ return -ENODEV;
+
+ for (i = 0; i < 7; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ 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:
+ __set_bit(BTN_7, input_dev->keybit);
+ __set_bit(BTN_8, input_dev->keybit);
+ /* fall through */
+
+ case INTUOS4S:
+ for (i = 0; i < 7; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+ break;
+
+ case CINTIQ_HYBRID:
+ for (i = 0; i < 9; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+
+ break;
+
+ case DTUS:
+ for (i = 0; i < 4; i++)
+ __set_bit(BTN_0 + i, input_dev->keybit);
+ break;
+
+ case INTUOSHT:
+ case BAMBOO_PT:
+ /* pad device is on the touch interface */
+ if ((features->device_type != BTN_TOOL_FINGER) ||
+ /* Bamboo Pen only tablet does not have pad */
+ ((features->type == BAMBOO_PT) && !features->touch_max))
+ return -ENODEV;
+
+ __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;
+
+ 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 };
+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 };
+static const struct wacom_features wacom_features_0xB1 =
+ { "Wacom Intuos3 6x8", 40640, 30480, 1023, 63,
+ INTUOS3, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB2 =
+ { "Wacom Intuos3 9x12", 60960, 45720, 1023, 63,
+ INTUOS3, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB3 =
+ { "Wacom Intuos3 12x12", 60960, 60960, 1023, 63,
+ INTUOS3L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB4 =
+ { "Wacom Intuos3 12x19", 97536, 60960, 1023, 63,
+ INTUOS3L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB5 =
+ { "Wacom Intuos3 6x11", 54204, 31750, 1023, 63,
+ INTUOS3, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB7 =
+ { "Wacom Intuos3 4x6", 31496, 19685, 1023, 63,
+ INTUOS3S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB8 =
+ { "Wacom Intuos4 4x6", 31496, 19685, 2047, 63,
+ INTUOS4S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xB9 =
+ { "Wacom Intuos4 6x9", 44704, 27940, 2047, 63,
+ INTUOS4, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xBA =
+ { "Wacom Intuos4 8x13", 65024, 40640, 2047, 63,
+ INTUOS4L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xBB =
+ { "Wacom Intuos4 12x19", 97536, 60960, 2047, 63,
+ INTUOS4L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xBC =
+ { "Wacom Intuos4 WL", 40640, 25400, 2047, 63,
+ INTUOS4, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xBD =
+ { "Wacom Intuos4 WL", 40640, 25400, 2047, 63,
+ INTUOS4WL, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0x26 =
+ { "Wacom Intuos5 touch S", 31496, 19685, 2047, 63,
+ INTUOS5S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, .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, .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, .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 };
+static const struct wacom_features wacom_features_0x2A =
+ { "Wacom Intuos5 M", 44704, 27940, 2047, 63,
+ INTUOS5, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0x314 =
+ { "Wacom Intuos Pro S", 31496, 19685, 2047, 63,
+ INTUOSPS, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, .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, .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, .touch_max = 16,
+ .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0xF4 =
+ { "Wacom Cintiq 24HD", 104080, 65200, 2047, 63,
+ WACOM_24HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0xF8 =
+ { "Wacom Cintiq 24HD touch", 104080, 65200, 2047, 63, /* Pen */
+ WACOM_24HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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", 119740, 67520, 2047, 63,
+ WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x32B =
+ { "Wacom Cintiq 27QHD touch", 119740, 67520, 2047, 63,
+ WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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 };
+static const struct wacom_features wacom_features_0xC5 =
+ { "Wacom Cintiq 20WSX", 86680, 54180, 1023, 63,
+ WACOM_BEE, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0xC6 =
+ { "Wacom Cintiq 12WX", 53020, 33440, 1023, 63,
+ WACOM_BEE, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES };
+static const struct wacom_features wacom_features_0x304 =
+ { "Wacom Cintiq 13HD", 59152, 33448, 1023, 63,
+ WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x333 =
+ { "Wacom Cintiq 13HD touch", 59152, 33448, 2047, 63,
+ WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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", 21896, 13760, 511, 0,
+ DTUS, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+ WACOM_DTU_OFFSET, WACOM_DTU_OFFSET };
+static const struct wacom_features wacom_features_0x32F =
+ { "Wacom DTU1031X", 22472, 12728, 511, 0,
+ DTUSX, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+ WACOM_DTU_OFFSET, WACOM_DTU_OFFSET };
+static const struct wacom_features wacom_features_0x57 =
+ { "Wacom DTK2241", 95640, 54060, 2047, 63,
+ DTK, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x59 = /* Pen */
+ { "Wacom DTH2242", 95640, 54060, 2047, 63,
+ DTK, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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", 86800, 65200, 2047, 63,
+ WACOM_21UX2, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0xFA =
+ { "Wacom Cintiq 22HD", 95440, 53860, 2047, 63,
+ WACOM_22HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x5B =
+ { "Wacom Cintiq 22HDT", 95440, 53860, 2047, 63,
+ WACOM_22HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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 };
+static const struct wacom_features wacom_features_0x93 =
+ { "Wacom ISDv4 93", 26202, 16325, 255, 0,
+ TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x97 =
+ { "Wacom ISDv4 97", 26202, 16325, 511, 0,
+ TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x9A =
+ { "Wacom ISDv4 9A", 26202, 16325, 255, 0,
+ TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x9F =
+ { "Wacom ISDv4 9F", 26202, 16325, 255, 0,
+ TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+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 };
+static const struct wacom_features wacom_features_0xED =
+ { "Wacom ISDv4 ED", 26202, 16325, 255, 0,
+ TABLETPCE, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xEF =
+ { "Wacom ISDv4 EF", 26202, 16325, 255, 0,
+ TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+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 };
+static const struct wacom_features wacom_features_0x12C =
+ { "Wacom ISDv4 12C", 27848, 15752, 2047, 0,
+ TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+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", 0, 0, 0, 0,
+ WIRELESS, 0, 0, .touch_max = 16 };
+static const struct wacom_features wacom_features_0xD0 =
+ { "Wacom Bamboo 2FG", 14720, 9200, 1023, 31,
+ BAMBOO_PT, 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_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xD5 =
+ { "Wacom Bamboo Pen 6x8", 21648, 13700, 1023, 31,
+ BAMBOO_PT, 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_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x301 =
+ { "Wacom Bamboo One M", 21648, 13530, 1023, 31,
+ BAMBOO_PT, 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", 59152, 33448, 2047, 63,
+ CINTIQ_HYBRID, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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", 59152, 33448, 2047, 63,
+ CINTIQ_HYBRID, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+ 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_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_HID_ANY_ID =
+ { "Wacom HID", .type = HID_GENERIC };
+
+#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(0x32A) },
+ { USB_DEVICE_WACOM(0x32B) },
+ { USB_DEVICE_WACOM(0x32C) },
+ { USB_DEVICE_WACOM(0x32F) },
+ { USB_DEVICE_WACOM(0x333) },
+ { USB_DEVICE_WACOM(0x335) },
+ { 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) },
+ { }
+};
+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 000000000..4700ac994
--- /dev/null
+++ b/drivers/hid/wacom_wac.h
@@ -0,0 +1,221 @@
+/*
+ * 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>
+
+/* maximum packet length for USB devices */
+#define WACOM_PKGLEN_MAX 192
+
+#define WACOM_NAME_MAX 64
+
+/* packet length for individual models */
+#define WACOM_PKGLEN_PENPRTN 7
+#define WACOM_PKGLEN_GRAPHIRE 8
+#define WACOM_PKGLEN_BBFUN 9
+#define WACOM_PKGLEN_INTUOS 10
+#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_MTOUCH 62
+#define WACOM_PKGLEN_MTTPC 40
+#define WACOM_PKGLEN_DTUS 68
+#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_INTUOSREAD 5
+#define WACOM_REPORT_INTUOSWRITE 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
+
+/* device quirks */
+#define WACOM_QUIRK_BBTOUCH_LOWRES 0x0001
+#define WACOM_QUIRK_NO_INPUT 0x0002
+#define WACOM_QUIRK_MONITOR 0x0004
+#define WACOM_QUIRK_BATTERY 0x0008
+
+#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))
+#define WACOM_FINGER_FIELD(f) (((f)->logical == HID_DG_FINGER) || \
+ ((f)->physical == HID_DG_FINGER) || \
+ ((f)->application == HID_DG_TOUCHSCREEN))
+
+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,
+ INTUOSHT,
+ WACOM_21UX2,
+ WACOM_22HD,
+ DTK,
+ WACOM_24HD,
+ WACOM_27QHD,
+ CINTIQ_HYBRID,
+ CINTIQ,
+ WACOM_BEE,
+ WACOM_13HD,
+ WACOM_MO,
+ WIRELESS,
+ BAMBOO_PT,
+ WACOM_24HDT,
+ WACOM_27QHDT,
+ BAMBOO_PAD,
+ 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 x_min;
+ int y_min;
+ 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;
+ unsigned quirks;
+ unsigned touch_max;
+ int oVid;
+ int oPid;
+ int pktlen;
+ bool check_for_hid_type;
+ int hid_type;
+ int last_slot_field;
+};
+
+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;
+};
+
+struct hid_data {
+ __s16 inputmode; /* InputMode HID feature, -1 if non-existent */
+ __s16 inputmode_index; /* InputMode HID feature index in the report */
+ bool inrange_state;
+ bool invert_state;
+ bool tipswitch;
+ int x;
+ int y;
+ int pressure;
+ int width;
+ int height;
+ int id;
+};
+
+struct wacom_wac {
+ char name[WACOM_NAME_MAX];
+ char pad_name[WACOM_NAME_MAX];
+ char bat_name[WACOM_NAME_MAX];
+ char ac_name[WACOM_NAME_MAX];
+ unsigned char data[WACOM_PKGLEN_MAX];
+ int tool[2];
+ int id[2];
+ __u32 serial[2];
+ bool reporting_data;
+ struct wacom_features features;
+ struct wacom_shared *shared;
+ struct input_dev *input;
+ struct input_dev *pad_input;
+ bool input_registered;
+ bool pad_registered;
+ int pid;
+ int battery_capacity;
+ int num_contacts_left;
+ int bat_charging;
+ int bat_connected;
+ int ps_connected;
+ u8 bt_features;
+ u8 bt_high_speed;
+ struct hid_data hid_data;
+};
+
+#endif