diff options
Diffstat (limited to 'drivers/hid')
87 files changed, 36707 insertions, 0 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig new file mode 100644 index 00000000..1283fa3b --- /dev/null +++ b/drivers/hid/Kconfig @@ -0,0 +1,670 @@ +# +# HID driver configuration +# +menuconfig HID_SUPPORT + bool "HID Devices" + depends on INPUT + default y + ---help--- + Say Y here to get to see options for various computer-human interface + device drivers. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if HID_SUPPORT + +config HID + tristate "Generic HID 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 compiles into kernel the generic + HID layer code (parser, usages, etc.), which can then be used by + transport-specific HID implementation (like USB or Bluetooth). + + For docs and specs, see http://www.usb.org/developers/hidpage/ + + If unsure, say Y. + +config HID_BATTERY_STRENGTH + bool + depends on HID && POWER_SUPPLY && HID = POWER_SUPPLY + default n + +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. + +source "drivers/hid/usbhid/Kconfig" + +menu "Special HID drivers" + depends on HID + +config HID_A4TECH + tristate "A4 tech mice" if EXPERT + depends on USB_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 USB_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" if EXPERT + depends on (USB_HID || BT_HIDP) + 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_BELKIN + tristate "Belkin Flip KVM and Wireless keyboard" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Belkin Flip KVM and Wireless keyboard. + +config HID_CHERRY + tristate "Cherry Cymotion keyboard" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Cherry Cymotion keyboard. + +config HID_CHICONY + tristate "Chicony Tactical pad" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Chicony Tactical pad. + +config HID_PRODIKEYS + tristate "Prodikeys PC-MIDI Keyboard support" + depends on USB_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_CYPRESS + tristate "Cypress mouse and barcode readers" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for cypress mouse and barcode readers. + +config HID_DRAGONRISE + tristate "DragonRise Inc. game controller" + depends on USB_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 USB_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 BT_HIDP + ---help--- + Support for the ELECOM BM084 (bluetooth mouse). + +config HID_EZKEY + tristate "Ezkey BTC 8193 keyboard" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Ezkey BTC 8193 keyboard. + +config HID_HOLTEK + tristate "Holtek On Line Grip based game controller support" + depends on USB_HID + ---help--- + Say Y here if you have a Holtek On Line Grip based game controller. + +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_KEYTOUCH + tristate "Keytouch HID devices" + depends on USB_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 USB_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 tablets. + +config HID_WALTOP + tristate "Waltop" + depends on USB_HID + ---help--- + Support for Waltop tablets. + +config HID_GYRATION + tristate "Gyration remote control" + depends on USB_HID + ---help--- + Support for Gyration remote control. + +config HID_TWINHAN + tristate "Twinhan IR remote control" + depends on USB_HID + ---help--- + Support for Twinhan IR remote control. + +config HID_KENSINGTON + tristate "Kensington Slimblade Trackball" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Kensington Slimblade Trackball. + +config HID_LCPOWER + tristate "LC-Power" + depends on USB_HID + ---help--- + Support for LC-Power RC1000MCE RF remote control. + +config HID_LOGITECH + tristate "Logitech devices" if EXPERT + depends on USB_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 HID_LOGITECH + default m + ---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 incomming events will be multiplexed + into a single mouse and a single keyboard device. + +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 + - Logitech MOMO Force wheel + + and if you want to enable force feedback for them. + Note: if you say N here, this device will still be supported, but without + force feedback. + +config LOGIRUMBLEPAD2_FF + bool "Logitech RumblePad/Rumblepad 2 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 + RumblePad and Rumblepad 2 devices. + +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 MagicMouse multi-touch support" + depends on BT_HIDP + ---help--- + Support for the Apple Magic Mouse multi-touch. + + Say Y here if you want support for the multi-touch features of the + Apple Wireless "Magic" Mouse. + +config HID_MICROSOFT + tristate "Microsoft non-fully HID-compliant devices" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Microsoft devices that are not fully compliant with HID standard. + +config HID_MONTEREY + tristate "Monterey Genius KB29E keyboard" if EXPERT + depends on USB_HID + default !EXPERT + ---help--- + Support for Monterey Genius KB29E. + +config HID_MULTITOUCH + tristate "HID Multitouch panels" + depends on USB_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 + - 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 + - Stantum multitouch panels + - Touch International Panels + - Unitec Panels + - XAT optical touch panels + - Xiroku optical 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 USB_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 USB_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_PETALYNX + tristate "Petalynx Maxter remote control" + depends on USB_HID + ---help--- + Support for Petalynx Maxter remote control. + +config HID_PICOLCD + tristate "PicoLCD (graphic version)" + depends on USB_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 + frambuffer 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_PRIMAX + tristate "Primax non-fully HID-compliant devices" + depends on USB_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 non-fully HID-compliant devices" + depends on USB_HID + ---help--- + Support for Saitek devices that are not fully compliant with the + HID standard. + + Currently only supports the PS1000 controller. + +config HID_SAMSUNG + tristate "Samsung InfraRed remote control or keyboards" + depends on USB_HID + ---help--- + Support for Samsung InfraRed remote control or keyboards. + +config HID_SONY + tristate "Sony PS3 controller" + depends on USB_HID + ---help--- + Support for Sony PS3 controller. + +config HID_SPEEDLINK + tristate "Speedlink VAD Cezanne mouse support" + depends on USB_HID + ---help--- + Support for Speedlink Vicious and Divine Cezanne mouse. + +config HID_SUNPLUS + tristate "Sunplus wireless desktop" + depends on USB_HID + ---help--- + Support for Sunplus wireless desktop. + +config HID_GREENASIA + tristate "GreenAsia (Product ID 0x12) game controller support" + depends on USB_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 USB_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 (USB_HID || BT_HIDP) + ---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 USB_HID + ---help--- + Say Y if you have a TopSeed Cyberlink or BTC Emprex or Conceptronic + CLLRCMCE remote control. + +config HID_THRUSTMASTER + tristate "ThrustMaster devices support" + depends on USB_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 Bluetooth devices support" + depends on BT_HIDP + ---help--- + Support for Wacom Graphire Bluetooth tablet. + +config HID_WACOM_POWER_SUPPLY + bool "Wacom Bluetooth devices power supply status support" + depends on HID_WACOM + select POWER_SUPPLY + ---help--- + Say Y here if you want to enable power supply status monitoring for + Wacom Bluetooth devices. + +config HID_WIIMOTE + tristate "Nintendo Wii Remote support" + depends on BT_HIDP + depends on LEDS_CLASS + select POWER_SUPPLY + select INPUT_FF_MEMLESS + ---help--- + Support for the Nintendo Wii Remote bluetooth device. + +config HID_WIIMOTE_EXT + bool "Nintendo Wii Remote Extension support" + depends on HID_WIIMOTE + default HID_WIIMOTE + ---help--- + Support for extension controllers of the Nintendo Wii Remote. Say yes + here if you want to use the Nintendo Motion+, Nunchuck or Classic + extension controllers with your Wii Remote. + +config HID_ZEROPLUS + tristate "Zeroplus based game controller support" + depends on USB_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 USB_HID + ---help--- + Support for Zydacron remote control. + +endmenu + +endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile new file mode 100644 index 00000000..9dca8459 --- /dev/null +++ b/drivers/hid/Makefile @@ -0,0 +1,92 @@ +# +# Makefile for the HID driver +# +hid-y := hid-core.o hid-input.o + +ifdef CONFIG_DEBUG_FS + hid-objs += hid-debug.o +endif + +obj-$(CONFIG_HID) += hid.o +obj-$(CONFIG_UHID) += uhid.o + +hid-$(CONFIG_HIDRAW) += hidraw.o + +hid-logitech-y := hid-lg.o +ifdef CONFIG_LOGITECH_FF + hid-logitech-y += hid-lgff.o +endif +ifdef CONFIG_LOGIRUMBLEPAD2_FF + hid-logitech-y += hid-lg2ff.o +endif +ifdef CONFIG_LOGIG940_FF + hid-logitech-y += hid-lg3ff.o +endif +ifdef CONFIG_LOGIWHEELS_FF + hid-logitech-y += hid-lg4ff.o +endif + +hid-wiimote-y := hid-wiimote-core.o +ifdef CONFIG_HID_WIIMOTE_EXT + hid-wiimote-y += hid-wiimote-ext.o +endif +ifdef CONFIG_DEBUG_FS + hid-wiimote-y += hid-wiimote-debug.o +endif + +obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o +obj-$(CONFIG_HID_ACRUX) += hid-axff.o +obj-$(CONFIG_HID_APPLE) += hid-apple.o +obj-$(CONFIG_HID_BELKIN) += hid-belkin.o +obj-$(CONFIG_HID_CHERRY) += hid-cherry.o +obj-$(CONFIG_HID_CHICONY) += hid-chicony.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_EZKEY) += hid-ezkey.o +obj-$(CONFIG_HID_GYRATION) += hid-gyration.o +obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o +obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o +obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o +obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o +obj-$(CONFIG_HID_KYE) += hid-kye.o +obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o +obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o +obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.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_PETALYNX) += hid-petalynx.o +obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.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-kovaplus.o hid-roccat-pyra.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_SUNPLUS) += hid-sunplus.o +obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o +obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o +obj-$(CONFIG_HID_TIVO) += hid-tivo.o +obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o +obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o +obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o +obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o +obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o +obj-$(CONFIG_HID_WACOM) += hid-wacom.o +obj-$(CONFIG_HID_WALTOP) += hid-waltop.o +obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o + +obj-$(CONFIG_USB_HID) += usbhid/ +obj-$(CONFIG_USB_MOUSE) += usbhid/ +obj-$(CONFIG_USB_KBD) += usbhid/ + diff --git a/drivers/hid/hid-a4tech.c b/drivers/hid/hid-a4tech.c new file mode 100644 index 00000000..902d1dfe --- /dev/null +++ b/drivers/hid/hid-a4tech.c @@ -0,0 +1,163 @@ +/* + * 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) 2007 Paul Walmsley + * Copyright (c) 2008 Jiri Slaby + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/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 = kzalloc(sizeof(*a4), GFP_KERNEL); + if (a4 == NULL) { + hid_err(hdev, "can't alloc device descriptor\n"); + ret = -ENOMEM; + goto err_free; + } + + a4->quirks = id->driver_data; + + hid_set_drvdata(hdev, a4); + + 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: + kfree(a4); + return ret; +} + +static void a4_remove(struct hid_device *hdev) +{ + struct a4tech_sc *a4 = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + kfree(a4); +} + +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, + .remove = a4_remove, +}; + +static int __init a4_init(void) +{ + return hid_register_driver(&a4_driver); +} + +static void __exit a4_exit(void) +{ + hid_unregister_driver(&a4_driver); +} + +module_init(a4_init); +module_exit(a4_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c new file mode 100644 index 00000000..299d2387 --- /dev/null +++ b/drivers/hid/hid-apple.c @@ -0,0 +1,562 @@ +/* + * 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 + * 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 <linux/usb.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)"); + +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 *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; + } + } + } + + 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 = kzalloc(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"); + goto err_free; + } + + 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"); + goto err_free; + } + + return 0; +err_free: + kfree(asc); + return ret; +} + +static void apple_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); + kfree(hid_get_drvdata(hdev)); +} + +static const struct hid_device_id apple_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL), + .driver_data = APPLE_HIDDEV | APPLE_IGNORE_HIDINPUT }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4), + .driver_data = APPLE_HIDDEV | APPLE_IGNORE_HIDINPUT }, + { 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 }, + { 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_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_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, + .remove = apple_remove, + .event = apple_event, + .input_mapping = apple_input_mapping, + .input_mapped = apple_input_mapped, +}; + +static int __init apple_init(void) +{ + int ret; + + ret = hid_register_driver(&apple_driver); + if (ret) + pr_err("can't register apple driver\n"); + + return ret; +} + +static void __exit apple_exit(void) +{ + hid_unregister_driver(&apple_driver); +} + +module_init(apple_init); +module_exit(apple_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c new file mode 100644 index 00000000..5be858dd --- /dev/null +++ b/drivers/hid/hid-axff.c @@ -0,0 +1,211 @@ +/* + * 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/usb.h> +#include <linux/hid.h> +#include <linux/module.h> + +#include "hid-ids.h" + +#ifdef CONFIG_HID_ACRUX_FF +#include "usbhid/usbhid.h" + +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); + usbhid_submit_report(hid, axff->report, USB_DIR_OUT); + + 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_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; + usbhid_submit_report(hid, axff->report, USB_DIR_OUT); + + 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), }, + { } +}; +MODULE_DEVICE_TABLE(hid, ax_devices); + +static struct hid_driver ax_driver = { + .name = "acrux", + .id_table = ax_devices, + .probe = ax_probe, + .remove = ax_remove, +}; + +static int __init ax_init(void) +{ + return hid_register_driver(&ax_driver); +} + +static void __exit ax_exit(void) +{ + hid_unregister_driver(&ax_driver); +} + +module_init(ax_init); +module_exit(ax_exit); + +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 00000000..a1a765a5 --- /dev/null +++ b/drivers/hid/hid-belkin.c @@ -0,0 +1,103 @@ +/* + * 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) 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/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, +}; + +static int __init belkin_init(void) +{ + return hid_register_driver(&belkin_driver); +} + +static void __exit belkin_exit(void) +{ + hid_unregister_driver(&belkin_driver); +} + +module_init(belkin_init); +module_exit(belkin_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-cherry.c b/drivers/hid/hid-cherry.c new file mode 100644 index 00000000..888ece68 --- /dev/null +++ b/drivers/hid/hid-cherry.c @@ -0,0 +1,86 @@ +/* + * 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) 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/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 >= 17 && 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, +}; + +static int __init ch_init(void) +{ + return hid_register_driver(&ch_driver); +} + +static void __exit ch_exit(void) +{ + hid_unregister_driver(&ch_driver); +} + +module_init(ch_init); +module_exit(ch_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-chicony.c b/drivers/hid/hid-chicony.c new file mode 100644 index 00000000..b99af346 --- /dev/null +++ b/drivers/hid/hid-chicony.c @@ -0,0 +1,85 @@ +/* + * 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) }, + { } +}; +MODULE_DEVICE_TABLE(hid, ch_devices); + +static struct hid_driver ch_driver = { + .name = "chicony", + .id_table = ch_devices, + .input_mapping = ch_input_mapping, +}; + +static int __init ch_init(void) +{ + return hid_register_driver(&ch_driver); +} + +static void __exit ch_exit(void) +{ + hid_unregister_driver(&ch_driver); +} + +module_init(ch_init); +module_exit(ch_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c new file mode 100644 index 00000000..4da66b4b --- /dev/null +++ b/drivers/hid/hid-core.c @@ -0,0 +1,2254 @@ +/* + * 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(debug, "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 (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 -1; + } + + 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 -1; + } + 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 -1; + } + 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; + int usages; + unsigned offset; + int 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; + } + + if (parser->global.logical_maximum < parser->global.logical_minimum) { + hid_err(parser->device, "logical range invalid %d %d\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(int, 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++) { + int 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->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) +{ + 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: + parser->global.unit_exponent = item_sdata(item); + 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 > 96) { + 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) { + hid_err(parser->device, "report_id 0 is invalid\n"); + 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 1; + + 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); +} + +/* + * Free a device structure, all reports, and all fields. + */ + +static void hid_device_release(struct device *dev) +{ + struct hid_device *device = container_of(dev, struct hid_device, dev); + 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 < 256; j++) { + struct hid_report *report = report_enum->report_id_hash[j]; + if (report) + hid_free_report(report); + } + } + + kfree(device->rdesc); + kfree(device->collection); + kfree(device); +} + +/* + * 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; +} + +/** + * hid_parse_report - parse device report + * + * @device: hid device + * @start: report start + * @size: report size + * + * 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. + */ +int hid_parse_report(struct hid_device *device, __u8 *start, + unsigned size) +{ + struct hid_parser *parser; + struct hid_item item; + __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 (device->driver->report_fixup) + start = device->driver->report_fixup(device, start, &size); + + device->rdesc = kmemdup(start, size, GFP_KERNEL); + if (device->rdesc == NULL) + return -ENOMEM; + device->rsize = size; + + parser = vzalloc(sizeof(struct hid_parser)); + if (!parser) { + ret = -ENOMEM; + goto err; + } + + parser->device = device; + + end = start + size; + 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); + return 0; + } + } + + hid_err(device, "item fetching failed at offset %d\n", (int)(end - start)); +err: + vfree(parser); + return ret; +} +EXPORT_SYMBOL_GPL(hid_parse_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; +} + +/* + * 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; + + 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. + */ + +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); + +/* + * 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 = 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; +} + +void 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; + unsigned int a; + int rsize, csize = size; + u8 *cdata = data; + + report = hid_get_report(report_enum, data); + if (!report) + return; + + 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) + hidraw_report_event(hid, data, size); + + for (a = 0; a < report->maxfield; a++) + hid_input_field(hid, report->field[a], cdata, interrupt); + + if (hid->claimed & HID_CLAIMED_INPUT) + hidinput_report_event(hid, report); +} +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; + char *buf; + unsigned int i; + int ret = 0; + + if (!hid) + return -ENODEV; + + if (down_trylock(&hid->driver_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; + } + + buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); + + if (!buf) + goto nomem; + + /* 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); + +nomem: + 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) { + ret = ret < 0 ? ret : 0; + goto unlock; + } + } + + hid_report_raw_event(hid, type, data, size, interrupt); + +unlock: + up(&hid->driver_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 == hdev->bus && + (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 struct bin_attribute dev_bin_attr_report_desc = { + .attr = { .name = "report_descriptor", .mode = 0444 }, + .read = read_report_descriptor, + .size = HID_MAX_DESCRIPTOR_SIZE, +}; + +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 (hdev->quirks & HID_QUIRK_MULTITOUCH) { + /* this device should be handled by hid-multitouch, skip it */ + return -ENODEV; + } + + 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 (!hdev->claimed) { + hid_err(hdev, "claimed by neither input, hiddev nor hidraw\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_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_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); +} +EXPORT_SYMBOL_GPL(hid_disconnect); + +/* a list of devices for which there is a specialized driver on HID bus */ +static const struct hid_device_id hid_have_special_driver[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, + { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) }, + { 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_ACTIONSTAR, USB_DEVICE_ID_ACTIONSTAR_1011) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) }, + { 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_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_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_ISO) }, + { 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_ASUS, USB_DEVICE_ID_ASUS_T91MT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) }, + { 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_CANDO, USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH_10_1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH_15_6) }, + { 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_CHUNGHWAT, USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CVTOUCH, USB_DEVICE_ID_CVTOUCH_SCREEN) }, + { 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_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_TRUETOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480D) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_720C) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_725E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_726B) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2515) }, + { 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_BLUETOOTH_DEVICE(USB_VENDOR_ID_FRUCTEL, USB_DEVICE_ID_GAMETEL_MT_MODE) }, + { 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_GENERAL_TOUCH, USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_GOODTOUCH, USB_DEVICE_ID_GOODTOUCH_000f) }, + { 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_HANVON, USB_DEVICE_ID_HANVON_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HANVON_ALT, USB_DEVICE_ID_HANVON_ALT_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6650) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ILITEK, USB_DEVICE_ID_ILITEK_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, USB_DEVICE_ID_IRTOUCH_INFRARED_USB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) }, + { 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_EASYPEN_M610X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LG, USB_DEVICE_ID_LG_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_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_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_LUMIO, USB_DEVICE_ID_CRYSTALTOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LUMIO, USB_DEVICE_ID_CRYSTALTOUCH_DUAL) }, + { 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_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_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) }, + { 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_PANASONIC, USB_DEVICE_ID_PANABOARD_UBT780) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PANASONIC, USB_DEVICE_ID_PANABOARD_UBT880) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_PCI) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) }, + { 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_KONEPLUS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) }, + { 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_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STANTUM, USB_DEVICE_ID_MTP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STANTUM_STM, USB_DEVICE_ID_MTP_STM) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STANTUM_SITRONIX, USB_DEVICE_ID_MTP_SITRONIX) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, + { 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_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_TOUCH_INTL, USB_DEVICE_ID_TOUCH_INTL_MULTI_TOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART) }, + { 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_UNITEC, USB_DEVICE_ID_UNITEC_USB_TOUCH_0709) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UNITEC, USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19) }, + { 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_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH) }, + { 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_XAT, USB_DEVICE_ID_XAT_CSR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_SPX) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_MPX) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_CSR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_SPX1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_MPX1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_CSR1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_SPX2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_MPX2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_CSR2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE) }, + { 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) }, + { } +}; + +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.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); + + if ((hdev->quirks & HID_QUIRK_MULTITOUCH) && + !strncmp(hdrv->name, "hid-multitouch", 14)) + return 1; + + if (!hid_match_device(hdev, hdrv)) + return 0; + + /* generic wants all that don't have specialized driver */ + if (!strncmp(hdrv->name, "generic-", 8) && !hid_ignore_special_drivers) + return !hid_match_id(hdev, hid_have_special_driver); + + return 1; +} + +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 (!hdev->driver) { + id = hid_match_device(hdev, hdrv); + if (id == NULL) { + if (!((hdev->quirks & HID_QUIRK_MULTITOUCH) && + !strncmp(hdrv->name, "hid-multitouch", 14))) { + ret = -ENODEV; + goto unlock; + } + } + + hdev->driver = hdrv; + if (hdrv->probe) { + ret = hdrv->probe(hdev, id); + } else { /* default probe */ + ret = hid_parse(hdev); + if (!ret) + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + } + if (ret) + hdev->driver = NULL; + } +unlock: + 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; + + if (down_interruptible(&hdev->driver_lock)) + return -EINTR; + + hdrv = hdev->driver; + if (hdrv) { + if (hdrv->remove) + hdrv->remove(hdev); + else /* default remove */ + hid_hw_stop(hdev); + hdev->driver = NULL; + } + + up(&hdev->driver_lock); + return 0; +} + +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%04Xv%08Xp%08X", + hdev->bus, hdev->vendor, hdev->product)) + return -ENOMEM; + + return 0; +} + +static struct bus_type hid_bus_type = { + .name = "hid", + .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_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_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_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_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_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_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_WACOM, HID_ANY_ID) }, + { 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_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_FOUNTAIN_TP_ONLY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, + { } +}; + +static bool hid_ignore(struct hid_device *hdev) +{ + 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; + } + + if (hdev->type == HID_TYPE_USBMOUSE && + hid_match_id(hdev, hid_mouse_ignore_list)) + return true; + + return !!hid_match_id(hdev, hid_ignore_list); +} + +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 (!(hdev->quirks & HID_QUIRK_NO_IGNORE) + && (hid_ignore(hdev) || (hdev->quirks & HID_QUIRK_IGNORE))) + return -ENODEV; + + /* 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; + unsigned int i; + 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; + + hdev->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS, + sizeof(struct hid_collection), GFP_KERNEL); + if (hdev->collection == NULL) + goto err; + hdev->collection_size = HID_DEFAULT_NUM_COLLECTIONS; + + for (i = 0; i < HID_REPORT_TYPES; i++) + INIT_LIST_HEAD(&hdev->report_enum[i].report_list); + + init_waitqueue_head(&hdev->debug_wait); + INIT_LIST_HEAD(&hdev->debug_list); + sema_init(&hdev->driver_lock, 1); + + return hdev; +err: + put_device(&hdev->dev); + return ERR_PTR(ret); +} +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; + } +} + +/** + * 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-cypress.c b/drivers/hid/hid-cypress.c new file mode 100644 index 00000000..2f0be4c6 --- /dev/null +++ b/drivers/hid/hid-cypress.c @@ -0,0 +1,159 @@ +/* + * 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) 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/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_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, +}; + +static int __init cp_init(void) +{ + return hid_register_driver(&cp_driver); +} + +static void __exit cp_exit(void) +{ + hid_unregister_driver(&cp_driver); +} + +module_init(cp_init); +module_exit(cp_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c new file mode 100644 index 00000000..01dd9a7d --- /dev/null +++ b/drivers/hid/hid-debug.c @@ -0,0 +1,1104 @@ +/* + * (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"}, + { 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"}, + { 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; + + 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; + } + + wake_up_interruptible(&hdev->debug_wait); +} +EXPORT_SYMBOL_GPL(hid_debug_event); + +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_DIRECTION] = "Direction", + [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_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_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", +}; + +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; + int i; + + /* dump HID report descriptor */ + for (i = 0; i < hdev->rsize; i++) + seq_printf(f, "%02x ", hdev->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; + + 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); + + list_add_tail(&list->node, &list->hdev->debug_list); + +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; + break; + } + + /* 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; + + list_del(&list->node); + 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 00000000..e832f44a --- /dev/null +++ b/drivers/hid/hid-dr.c @@ -0,0 +1,313 @@ +/* + * 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/usb.h> +#include <linux/hid.h> +#include <linux/module.h> + +#include "hid-ids.h" + +#ifdef CONFIG_DRAGONRISE_FF +#include "usbhid/usbhid.h" + +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; + usbhid_submit_report(hid, drff->report, USB_DIR_OUT); + + 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); + usbhid_submit_report(hid, drff->report, USB_DIR_OUT); + + 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; + usbhid_submit_report(hid, drff->report, USB_DIR_OUT); + + 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, +}; + +static int __init dr_init(void) +{ + return hid_register_driver(&dr_driver); +} + +static void __exit dr_exit(void) +{ + hid_unregister_driver(&dr_driver); +} + +module_init(dr_init); +module_exit(dr_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c new file mode 100644 index 00000000..79d0c61e --- /dev/null +++ b/drivers/hid/hid-elecom.c @@ -0,0 +1,57 @@ +/* + * 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 +}; + +static int __init elecom_init(void) +{ + return hid_register_driver(&elecom_driver); +} + +static void __exit elecom_exit(void) +{ + hid_unregister_driver(&elecom_driver); +} + +module_init(elecom_init); +module_exit(elecom_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c new file mode 100644 index 00000000..2630d483 --- /dev/null +++ b/drivers/hid/hid-emsff.c @@ -0,0 +1,167 @@ +/* + * 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/usb.h> +#include <linux/module.h> + +#include "hid-ids.h" +#include "usbhid/usbhid.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); + usbhid_submit_report(hid, emsff->report, USB_DIR_OUT); + + 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; + usbhid_submit_report(hid, emsff->report, USB_DIR_OUT); + + 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, +}; + +static int ems_init(void) +{ + return hid_register_driver(&ems_driver); +} + +static void ems_exit(void) +{ + hid_unregister_driver(&ems_driver); +} + +module_init(ems_init); +module_exit(ems_exit); +MODULE_LICENSE("GPL"); + diff --git a/drivers/hid/hid-ezkey.c b/drivers/hid/hid-ezkey.c new file mode 100644 index 00000000..ca1163e9 --- /dev/null +++ b/drivers/hid/hid-ezkey.c @@ -0,0 +1,93 @@ +/* + * 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) 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 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, +}; + +static int __init ez_init(void) +{ + return hid_register_driver(&ez_driver); +} + +static void __exit ez_exit(void) +{ + hid_unregister_driver(&ez_driver); +} + +module_init(ez_init); +module_exit(ez_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c new file mode 100644 index 00000000..f1e1bcf6 --- /dev/null +++ b/drivers/hid/hid-gaff.c @@ -0,0 +1,192 @@ +/* + * 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/usb.h> +#include <linux/hid.h> +#include <linux/module.h> +#include "hid-ids.h" + +#ifdef CONFIG_GREENASIA_FF +#include "usbhid/usbhid.h" + +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); + usbhid_submit_report(hid, gaff->report, USB_DIR_OUT); + + 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; + + usbhid_submit_report(hid, gaff->report, USB_DIR_OUT); + + 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; + usbhid_submit_report(hid, gaff->report, USB_DIR_OUT); + + gaff->report->field[0]->value[0] = 0xfa; + gaff->report->field[0]->value[1] = 0xfe; + + usbhid_submit_report(hid, gaff->report, USB_DIR_OUT); + + 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, +}; + +static int __init ga_init(void) +{ + return hid_register_driver(&ga_driver); +} + +static void __exit ga_exit(void) +{ + hid_unregister_driver(&ga_driver); +} + +module_init(ga_init); +module_exit(ga_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-gyration.c b/drivers/hid/hid-gyration.c new file mode 100644 index 00000000..e88b951c --- /dev/null +++ b/drivers/hid/hid-gyration.c @@ -0,0 +1,105 @@ +/* + * 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) 2007 Paul Walmsley + * 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, +}; + +static int __init gyration_init(void) +{ + return hid_register_driver(&gyration_driver); +} + +static void __exit gyration_exit(void) +{ + hid_unregister_driver(&gyration_driver); +} + +module_init(gyration_init); +module_exit(gyration_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c new file mode 100644 index 00000000..4e754215 --- /dev/null +++ b/drivers/hid/hid-holtekff.c @@ -0,0 +1,241 @@ +/* + * 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 <linux/usb.h> + +#include "hid-ids.h" + +#ifdef CONFIG_HOLTEK_FF +#include "usbhid/usbhid.h" + +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 %02x %02x %02x %02x %02x %02x %02x\n", data[0], + data[1], data[2], data[3], data[4], data[5], data[6]); + + usbhid_submit_report(hid, holtekff->field->report, USB_DIR_OUT); +} + +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, +}; + +static int __init holtek_init(void) +{ + return hid_register_driver(&holtek_driver); +} + +static void __exit holtek_exit(void) +{ + hid_unregister_driver(&holtek_driver); +} + +module_init(holtek_init); +module_exit(holtek_exit); + diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c new file mode 100644 index 00000000..40663247 --- /dev/null +++ b/drivers/hid/hid-hyperv.c @@ -0,0 +1,587 @@ +/* + * 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; +}; + + +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 = kzalloc(desc->bLength, GFP_ATOMIC); + + if (!input_device->hid_desc) + goto cleanup; + + memcpy(input_device->hid_desc, desc, desc->bLength); + + 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; + + 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; + hid_input_report(input_dev->hid_device, + HID_INPUT_REPORT, input_report->buffer, + input_report->header.size, 1); + 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; + int 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_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 struct hid_ll_driver mousevsc_ll_driver = { + .open = mousevsc_hid_open, + .close = mousevsc_hid_close, + .start = mousevsc_hid_start, + .stop = mousevsc_hid_stop, +}; + +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"); + + ret = hid_add_device(hid_dev); + if (ret) + goto probe_err1; + + ret = hid_parse_report(hid_dev, input_dev->report_desc, + input_dev->report_desc_size); + + 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; + } + + 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); + + 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 */ + { VMBUS_DEVICE(0x9E, 0xB6, 0xA8, 0xCF, 0x4A, 0x5B, 0xc0, 0x4c, + 0xB9, 0x8B, 0x8B, 0xA1, 0xA1, 0xF3, 0xF9, 0x5A) }, + { }, +}; + +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_VERSION(HV_DRV_VERSION); +module_init(mousevsc_init); +module_exit(mousevsc_exit); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h new file mode 100644 index 00000000..e39aecb1 --- /dev/null +++ b/drivers/hid/hid-ids.h @@ -0,0 +1,797 @@ +/* + * 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. + */ + +#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_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_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_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_FOUNTAIN_TP_ONLY 0x030a +#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b +#define USB_DEVICE_ID_APPLE_ATV_IRCONTROL 0x8241 +#define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242 + +#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_VENDOR_ID_ATMEL 0x03eb +#define USB_DEVICE_ID_ATMEL_MULTITOUCH 0x211c +#define USB_DEVICE_ID_ATMEL_MXT_DIGITIZER 0x2118 + +#define USB_VENDOR_ID_AVERMEDIA 0x07ca +#define USB_DEVICE_ID_AVER_FM_MR800 0xb800 + +#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_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_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_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_TRUETOUCH 0xc001 + +#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_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_72AA 0x72aa +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1 0x72a1 +#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_A001 0xa001 + +#define USB_VENDOR_ID_ELECOM 0x056e +#define USB_DEVICE_ID_ELECOM_BM084 0x0061 + +#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34 + +#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_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_VENDOR_ID_EZKEY 0x0518 +#define USB_DEVICE_ID_BTC_8193 0x0002 + +#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_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_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_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_HOLTEK 0x1241 +#define USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP 0x5015 + +#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_JESS 0x0c45 +#define USB_DEVICE_ID_JESS_YUREX 0x1010 + +#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_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_EASYPEN_M610X 0x5013 + +#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_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_RECEIVER 0xc101 +#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110 +#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f +#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_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_WHEEL 0xc294 +#define USB_DEVICE_ID_LOGITECH_WINGMAN_FFG 0xc293 +#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_VENDOR_ID_LUMIO 0x202e +#define USB_DEVICE_ID_CRYSTALTOUCH 0x0006 +#define USB_DEVICE_ID_CRYSTALTOUCH_DUAL 0x0007 + +#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_VENDOR_ID_MICROSOFT 0x045e +#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b +#define USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0 0x009d +#define USB_DEVICE_ID_MS_NE4K 0x00db +#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_DIGITAL_MEDIA_3K 0x0730 +#define USB_DEVICE_ID_MS_COMFORT_MOUSE_4500 0x076c + +#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_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_NEXTWINDOW 0x1926 +#define USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN 0x0003 + +#define USB_VENDOR_ID_NINTENDO 0x057e +#define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306 + +#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_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_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_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_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_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN 0x3001 + +#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_KONE 0x2ced +#define USB_DEVICE_ID_ROCCAT_KONEPLUS 0x2d51 +#define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50 +#define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24 +#define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6 + +#define USB_VENDOR_ID_SAITEK 0x06a3 +#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 +#define USB_DEVICE_ID_SAITEK_PS1000 0x0621 + +#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_SIGMA_MICRO 0x1c4f +#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002 + +#define USB_VENDOR_ID_SKYCABLE 0x1223 +#define USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER 0x3F07 + +#define USB_VENDOR_ID_SONY 0x054c +#define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b +#define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268 +#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f + +#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_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_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_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_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_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_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_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_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_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_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_PRIMAX 0x0461 +#define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05 + +#endif diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c new file mode 100644 index 00000000..c19bff70 --- /dev/null +++ b/drivers/hid/hid-input.c @@ -0,0 +1,1255 @@ +/* + * 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. + */ +static __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 + */ + if (code == ABS_X || code == ABS_Y || code == ABS_Z) { + 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; + } + } else if (code == ABS_RX || code == ABS_RY || code == ABS_RZ) { + 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; + } + } else { + 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 logical_extents / physical_extents; +} + +#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_2011_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 = container_of(psy, struct hid_device, battery); + int ret = 0; + __u8 buf[2] = {}; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 1; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = dev->hid_get_raw_report(dev, dev->battery_report_id, + buf, sizeof(buf), + dev->battery_report_type); + + if (ret != 2) { + if (ret >= 0) + ret = -EINVAL; + break; + } + + 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); + 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 *battery = &dev->battery; + int ret; + unsigned quirks; + s32 min, max; + + if (field->usage->hid != HID_DC_BATTERYSTRENGTH) + return false; /* no match */ + + if (battery->name != NULL) + goto out; /* already initialized? */ + + battery->name = kasprintf(GFP_KERNEL, "hid-%s-battery", dev->uniq); + if (battery->name == NULL) + goto out; + + battery->type = POWER_SUPPLY_TYPE_BATTERY; + battery->properties = hidinput_battery_props; + battery->num_properties = ARRAY_SIZE(hidinput_battery_props); + battery->use_for_apm = 0; + battery->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; + + ret = power_supply_register(&dev->dev, battery); + if (ret != 0) { + hid_warn(dev, "can't register power supply: %d\n", ret); + kfree(battery->name); + battery->name = NULL; + } + + power_supply_powers(battery, &dev->dev); + +out: + return true; +} + +static void hidinput_cleanup_battery(struct hid_device *dev) +{ + if (!dev->battery.name) + return; + + power_supply_unregister(&dev->battery); + kfree(dev->battery.name); + dev->battery.name = 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; + + /* 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; + break; + case HID_GD_GAMEPAD: code += BTN_GAMEPAD; 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: + 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 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 */ + map_key_clear(BTN_STYLUS2); + break; + + case 0x51: /* ContactID */ + device->quirks |= HID_QUIRK_MULTITOUCH; + goto unknown; + + default: goto unknown; + } + 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 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 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 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 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 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 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; + + default: goto ignore; + } + 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_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 + */ + if ((field->flags & HID_MAIN_ITEM_VARIABLE) && + (value < field->logical_minimum || + value > field->logical_maximum)) { + dbg_hid("Ignoring out-of-range value %x\n", value); + 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 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++) + 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); + } +} + +/* + * 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_report *report; + struct hid_input *hidinput = NULL; + struct input_dev *input_dev; + int i, j, k; + + INIT_LIST_HEAD(&hid->inputs); + + 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 = kzalloc(sizeof(*hidinput), GFP_KERNEL); + 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"); + goto out_unwind; + } + + input_set_drvdata(input_dev, hid); + input_dev->event = + hid->ll_driver->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.parent; + hidinput->input = input_dev; + list_add_tail(&hidinput->list, &hid->inputs); + } + + for (i = 0; i < report->maxfield; i++) + for (j = 0; j < report->field[i]->maxusage; j++) + hidinput_configure_usage(hidinput, report->field[i], + report->field[i]->usage + j); + + if (hid->quirks & HID_QUIRK_MULTI_INPUT) { + /* This will leave hidinput NULL, so that it + * allocates another one if we have more inputs on + * the same interface. Some devices (e.g. Happ's + * UGCI) cram a lot of unrelated inputs into the + * same interface. */ + hidinput->report = report; + if (hid->driver->input_register && + hid->driver->input_register(hid, hidinput)) + goto out_cleanup; + if (input_register_device(hidinput->input)) + goto out_cleanup; + hidinput = NULL; + } + } + } + + if (hid->quirks & HID_QUIRK_MULTITOUCH) { + /* generic hid does not know how to handle multitouch devices */ + if (hidinput) + goto out_cleanup; + goto out_unwind; + } + + if (hidinput && hid->driver->input_register && + hid->driver->input_register(hid, hidinput)) + goto out_cleanup; + + if (hidinput && 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); + } +} +EXPORT_SYMBOL_GPL(hidinput_disconnect); + diff --git a/drivers/hid/hid-kensington.c b/drivers/hid/hid-kensington.c new file mode 100644 index 00000000..a5b4016e --- /dev/null +++ b/drivers/hid/hid-kensington.c @@ -0,0 +1,63 @@ +/* + * 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, +}; + +static int __init ks_init(void) +{ + return hid_register_driver(&ks_driver); +} + +static void __exit ks_exit(void) +{ + hid_unregister_driver(&ks_driver); +} + +module_init(ks_init); +module_exit(ks_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-keytouch.c b/drivers/hid/hid-keytouch.c new file mode 100644 index 00000000..07cd825f --- /dev/null +++ b/drivers/hid/hid-keytouch.c @@ -0,0 +1,66 @@ +/* + * 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, +}; + +static int __init keytouch_init(void) +{ + return hid_register_driver(&keytouch_driver); +} + +static void __exit keytouch_exit(void) +{ + hid_unregister_driver(&keytouch_driver); +} + +module_init(keytouch_init); +module_exit(keytouch_exit); +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 00000000..b4f0d821 --- /dev/null +++ b/drivers/hid/hid-kye.c @@ -0,0 +1,435 @@ +/* + * 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 <linux/usb.h> +#include "usbhid/usbhid.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 */ +}; + +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 >= 74 && + rdesc[61] == 0x05 && rdesc[62] == 0x08 && + rdesc[63] == 0x19 && rdesc[64] == 0x08 && + rdesc[65] == 0x29 && rdesc[66] == 0x0f && + rdesc[71] == 0x75 && rdesc[72] == 0x08 && + rdesc[73] == 0x95 && rdesc[74] == 0x01) { + hid_info(hdev, + "fixing up Kye/Genius Ergo Mouse " + "report descriptor\n"); + rdesc[62] = 0x09; + rdesc[64] = 0x04; + rdesc[66] = 0x07; + rdesc[72] = 0x01; + rdesc[74] = 0x08; + } + break; + case USB_DEVICE_ID_KYE_EASYPEN_I405X: + if (*rsize == EASYPEN_I405X_RDESC_ORIG_SIZE) { + rdesc = easypen_i405x_rdesc_fixed; + *rsize = sizeof(easypen_i405x_rdesc_fixed); + } + break; + case USB_DEVICE_ID_KYE_MOUSEPEN_I608X: + if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) { + rdesc = mousepen_i608x_rdesc_fixed; + *rsize = sizeof(mousepen_i608x_rdesc_fixed); + } + break; + case USB_DEVICE_ID_KYE_EASYPEN_M610X: + if (*rsize == EASYPEN_M610X_RDESC_ORIG_SIZE) { + rdesc = easypen_m610x_rdesc_fixed; + *rsize = sizeof(easypen_m610x_rdesc_fixed); + } + 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; + usbhid_submit_report(hdev, report, USB_DIR_OUT); + + 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_EASYPEN_M610X: + ret = kye_tablet_enable(hdev); + if (ret) { + hid_err(hdev, "tablet enabling failed\n"); + goto enabling_err; + } + 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_EASYPEN_M610X) }, + { } +}; +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, +}; + +static int __init kye_init(void) +{ + return hid_register_driver(&kye_driver); +} + +static void __exit kye_exit(void) +{ + hid_unregister_driver(&kye_driver); +} + +module_init(kye_init); +module_exit(kye_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-lcpower.c b/drivers/hid/hid-lcpower.c new file mode 100644 index 00000000..c4fe9bd0 --- /dev/null +++ b/drivers/hid/hid-lcpower.c @@ -0,0 +1,70 @@ +/* + * 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) != 0x0ffbc0000) + 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, +}; + +static int __init ts_init(void) +{ + return hid_register_driver(&ts_driver); +} + +static void __exit ts_exit(void) +{ + hid_unregister_driver(&ts_driver); +} + +module_init(ts_init); +module_exit(ts_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c new file mode 100644 index 00000000..e7a7bd1e --- /dev/null +++ b/drivers/hid/hid-lg.c @@ -0,0 +1,508 @@ +/* + * 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) 2007 Paul Walmsley + * 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/wait.h> + +#include "hid-ids.h" +#include "hid-lg.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 descriptor of the Driving Force Pro wheel */ +#define DFP_RDESC_ORIG_SIZE 97 + +/* Fixed report descriptor for Logitech Driving Force Pro wheel controller + * + * The original descriptor hides the separate throttle and brake axes in + * a custom vendor usage page, providing only a combined value as + * GenericDesktop.Y. + * This descriptor removes the combined Y axis and instead reports + * separate throttle (Y) and brake (RZ). + */ +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 */ +}; + + +/* + * 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) +{ + unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + + if ((quirks & LG_RDESC) && *rsize >= 90 && 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 ((quirks & LG_RDESC_REL_ABS) && *rsize >= 50 && + 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; + } + if ((quirks & LG_FF4) && *rsize >= 101 && + rdesc[41] == 0x95 && rdesc[42] == 0x0B && + rdesc[47] == 0x05 && rdesc[48] == 0x09) { + hid_info(hdev, "fixing up Logitech Speed Force Wireless button descriptor\n"); + rdesc[41] = 0x05; + rdesc[42] = 0x09; + rdesc[47] = 0x95; + rdesc[48] = 0x0B; + } + + switch (hdev->product) { + 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; + } + + 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 + }; + unsigned long quirks = (unsigned long)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 ((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 ((quirks & LG_IGNORE_DOUBLED_WHEEL) && + (hid == 7 || hid == 8)) + return -1; + } else { + if ((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) +{ + unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + + if ((quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY && + (field->flags & HID_MAIN_ITEM_RELATIVE)) + field->flags &= ~HID_MAIN_ITEM_RELATIVE; + + if ((quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY || + usage->type == EV_REL || usage->type == EV_ABS)) + clear_bit(usage->code, *bit); + + return 0; +} + +static int lg_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 ((quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) { + input_event(field->hidinput->input, usage->type, usage->code, + -value); + return 1; + } + + return 0; +} + +static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + unsigned long quirks = id->driver_data; + unsigned int connect_mask = HID_CONNECT_DEFAULT; + int ret; + + hid_set_drvdata(hdev, (void *)quirks); + + if (quirks & LG_NOGET) + hdev->quirks |= HID_QUIRK_NOGET; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + goto err_free; + } + + if (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 = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_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 = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); + } + } + + if (quirks & LG_FF) + lgff_init(hdev); + if (quirks & LG_FF2) + lg2ff_init(hdev); + if (quirks & LG_FF3) + lg3ff_init(hdev); + if (quirks & LG_FF4) + lg4ff_init(hdev); + + return 0; +err_free: + return ret; +} + +static void lg_remove(struct hid_device *hdev) +{ + unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + if(quirks & LG_FF4) + lg4ff_deinit(hdev); + + hid_hw_stop(hdev); +} + +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_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_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_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, +}; + +static int __init lg_init(void) +{ + return hid_register_driver(&lg_driver); +} + +static void __exit lg_exit(void) +{ + hid_unregister_driver(&lg_driver); +} + +module_init(lg_init); +module_exit(lg_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h new file mode 100644 index 00000000..4b097286 --- /dev/null +++ b/drivers/hid/hid-lg.h @@ -0,0 +1,30 @@ +#ifndef __HID_LG_H +#define __HID_LG_H + +#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 + +#ifdef CONFIG_LOGIWHEELS_FF +int lg4ff_init(struct hid_device *hdev); +int lg4ff_deinit(struct hid_device *hdev); +#else +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-lg2ff.c b/drivers/hid/hid-lg2ff.c new file mode 100644 index 00000000..3c31bc65 --- /dev/null +++ b/drivers/hid/hid-lg2ff.c @@ -0,0 +1,116 @@ +/* + * 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/usb.h> +#include <linux/hid.h> + +#include "usbhid/usbhid.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; + } + + usbhid_submit_report(hid, lg2ff->report, USB_DIR_OUT); + 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 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) { + hid_err(hid, "output report is empty\n"); + return -ENODEV; + } + if (report->field[0]->report_count < 7) { + hid_err(hid, "not enough values in the field\n"); + 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; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + + hid_info(hid, "Force feedback for Logitech RumblePad/Rumblepad 2 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 00000000..f98644c2 --- /dev/null +++ b/drivers/hid/hid-lg3ff.c @@ -0,0 +1,175 @@ +/* + * 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/usb.h> +#include <linux/hid.h> + +#include "usbhid/usbhid.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; + +/* + * Maxusage should always be 63 (maximum fields) + * likely a better way to ensure this data is clean + */ + memset(report->field[0]->value, 0, sizeof(__s32)*report->field[0]->maxusage); + + 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); + + usbhid_submit_report(hid, report, USB_DIR_OUT); + 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; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + + +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 list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + struct hid_report *report; + struct hid_field *field; + const signed short *ff_bits = ff3_joystick_ac; + int error; + int i; + + /* Find the report to use */ + if (list_empty(report_list)) { + hid_err(hid, "No output report found\n"); + return -1; + } + + /* Check that the report looks ok */ + report = list_entry(report_list->next, struct hid_report, list); + if (!report) { + hid_err(hid, "NULL output report\n"); + return -1; + } + + field = report->field[0]; + if (!field) { + hid_err(hid, "NULL field\n"); + return -1; + } + + /* 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 00000000..6ecc9e22 --- /dev/null +++ b/drivers/hid/hid-lg4ff.c @@ -0,0 +1,488 @@ +/* + * Force feedback support for Logitech Speed Force Wireless + * + * http://wiibrew.org/wiki/Logitech_USB_steering_wheel + * + * 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-ids.h" + +#define DFGT_REV_MAJ 0x13 +#define DFGT_REV_MIN 0x22 +#define DFP_REV_MAJ 0x11 +#define DFP_REV_MIN 0x06 +#define FFEX_REV_MAJ 0x21 +#define FFEX_REV_MIN 0x00 +#define G25_REV_MAJ 0x12 +#define G25_REV_MIN 0x22 +#define G27_REV_MAJ 0x12 +#define G27_REV_MIN 0x38 + +#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) + +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); +static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); + +static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store); + +static bool list_inited; + +struct lg4ff_device_entry { + char *device_id; /* Use name in respective kobject structure's address as the ID */ + __u16 range; + __u16 min_range; + __u16 max_range; + __u8 leds; + struct list_head list; + void (*set_range)(struct hid_device *hid, u16 range); +}; + +static struct lg4ff_device_entry device_list; + +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); +}; + +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} +}; + +struct lg4ff_native_cmd { + const __u8 cmd_num; /* Number of commands to send */ + const __u8 cmd[]; +}; + +struct lg4ff_usb_revision { + const __u16 rev_maj; + const __u16 rev_min; + const struct lg4ff_native_cmd *command; +}; + +static const struct lg4ff_native_cmd native_dfp = { + 1, + {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct lg4ff_native_cmd native_dfgt = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ + 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ +}; + +static const struct lg4ff_native_cmd native_g25 = { + 1, + {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct lg4ff_native_cmd native_g27 = { + 2, + {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ + 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ +}; + +static const struct lg4ff_usb_revision lg4ff_revs[] = { + {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */ + {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */ + {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */ + {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ +}; + +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); + int x; + +#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 + 0x80; /* 0x80 is no force */ + CLAMP(x); + report->field[0]->value[0] = 0x11; /* Slot 1 */ + report->field[0]->value[1] = 0x08; + report->field[0]->value[2] = x; + report->field[0]->value[3] = 0x80; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + 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); + + report->field[0]->value[0] = 0xfe; + report->field[0]->value[1] = 0x0d; + report->field[0]->value[2] = magnitude >> 13; + report->field[0]->value[3] = magnitude >> 13; + report->field[0]->value[4] = magnitude >> 8; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +/* 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); + magnitude = magnitude * 90 / 65535; + + + report->field[0]->value[0] = 0xfe; + report->field[0]->value[1] = 0x03; + report->field[0]->value[2] = magnitude >> 14; + report->field[0]->value[3] = magnitude >> 14; + report->field[0]->value[4] = magnitude; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +/* 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); + dbg_hid("G25/G27/DFGT: setting range to %u\n", range); + + report->field[0]->value[0] = 0xf8; + report->field[0]->value[1] = 0x81; + report->field[0]->value[2] = range & 0x00ff; + report->field[0]->value[3] = (range & 0xff00) >> 8; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +/* 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; + dbg_hid("Driving Force Pro: setting range to %u\n", range); + + /* Prepare "coarse" limit command */ + report->field[0]->value[0] = 0xf8; + report->field[0]->value[1] = 0x00; /* Set later */ + 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; + + if (range > 200) { + report->field[0]->value[1] = 0x03; + full_range = 900; + } else { + report->field[0]->value[1] = 0x02; + full_range = 200; + } + usbhid_submit_report(hid, report, USB_DIR_OUT); + + /* Prepare "fine" limit command */ + report->field[0]->value[0] = 0x81; + report->field[0]->value[1] = 0x0b; + 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; + + if (range == 200 || range == 900) { /* Do not apply any fine limit */ + usbhid_submit_report(hid, report, USB_DIR_OUT); + return; + } + + /* Construct fine limit command */ + start_left = (((full_range - range + 1) * 2047) / full_range); + start_right = 0xfff - start_left; + + report->field[0]->value[2] = start_left >> 4; + report->field[0]->value[3] = start_right >> 4; + report->field[0]->value[4] = 0xff; + report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + report->field[0]->value[6] = 0xff; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) +{ + 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); + __u8 i, j; + + j = 0; + while (j < 7*cmd->cmd_num) { + for (i = 0; i < 7; i++) + report->field[0]->value[i] = cmd->cmd[j++]; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + } +} + +/* Read current range and display it in terminal */ +static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct lg4ff_device_entry *uninitialized_var(entry); + struct list_head *h; + struct hid_device *hid = to_hid_device(dev); + size_t count; + + list_for_each(h, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) + break; + } + if (h == &device_list.list) { + dbg_hid("Device not found!"); + 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 lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct lg4ff_device_entry *uninitialized_var(entry); + struct list_head *h; + struct hid_device *hid = to_hid_device(dev); + __u16 range = simple_strtoul(buf, NULL, 10); + + list_for_each(h, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) + break; + } + if (h == &device_list.list) { + dbg_hid("Device not found!"); + return count; + } + + 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; +} + +int lg4ff_init(struct hid_device *hid) +{ + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + struct hid_report *report; + struct hid_field *field; + struct lg4ff_device_entry *entry; + struct usb_device_descriptor *udesc; + int error, i, j; + __u16 bcdDevice, rev_maj, rev_min; + + /* Find the report to use */ + if (list_empty(report_list)) { + hid_err(hid, "No output report found\n"); + return -1; + } + + /* Check that the report looks ok */ + report = list_entry(report_list->next, struct hid_report, list); + if (!report) { + hid_err(hid, "NULL output report\n"); + return -1; + } + + field = report->field[0]; + if (!field) { + hid_err(hid, "NULL field\n"); + return -1; + } + + /* 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; + } + + /* Attempt to switch wheel to native mode when applicable */ + udesc = &(hid_to_usb_dev(hid)->descriptor); + if (!udesc) { + hid_err(hid, "NULL USB device descriptor\n"); + return -1; + } + bcdDevice = le16_to_cpu(udesc->bcdDevice); + rev_maj = bcdDevice >> 8; + rev_min = bcdDevice & 0xff; + + if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) { + dbg_hid("Generic wheel detected, can it do native?\n"); + dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min); + + for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) { + if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) { + hid_lg4ff_switch_native(hid, lg4ff_revs[j].command); + hid_info(hid, "Switched to native mode\n"); + } + } + } + + /* 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; + + /* Check if autocentering is available and + * set the centering force to zero by default */ + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { + if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */ + 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); + } + + /* Initialize device_list if this is the first device to handle by lg4ff */ + if (!list_inited) { + INIT_LIST_HEAD(&device_list.list); + list_inited = 1; + } + + /* Add the device to device_list */ + entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL); + if (!entry) { + hid_err(hid, "Cannot add device, insufficient memory.\n"); + return -ENOMEM; + } + entry->device_id = kstrdup((&hid->dev)->kobj.name, GFP_KERNEL); + if (!entry->device_id) { + hid_err(hid, "Cannot set device_id, insufficient memory.\n"); + kfree(entry); + return -ENOMEM; + } + entry->min_range = lg4ff_devices[i].min_range; + entry->max_range = lg4ff_devices[i].max_range; + entry->set_range = lg4ff_devices[i].set_range; + list_add(&entry->list, &device_list.list); + + /* Create sysfs interface */ + error = device_create_file(&hid->dev, &dev_attr_range); + 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); + + hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n"); + return 0; +} + +int lg4ff_deinit(struct hid_device *hid) +{ + bool found = 0; + struct lg4ff_device_entry *entry; + struct list_head *h, *g; + list_for_each_safe(h, g, &device_list.list) { + entry = list_entry(h, struct lg4ff_device_entry, list); + if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) { + list_del(h); + kfree(entry->device_id); + kfree(entry); + found = 1; + break; + } + } + + if (!found) { + dbg_hid("Device entry not found!\n"); + return -1; + } + + device_remove_file(&hid->dev, &dev_attr_range); + dbg_hid("Device successfully unregistered\n"); + return 0; +} diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c new file mode 100644 index 00000000..27bc54f9 --- /dev/null +++ b/drivers/hid/hid-lgff.c @@ -0,0 +1,176 @@ +/* + * 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/usb.h> +#include <linux/hid.h> + +#include "usbhid/usbhid.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); + usbhid_submit_report(hid, report, USB_DIR_OUT); + break; + + case FF_RUMBLE: + right = effect->u.rumble.strong_magnitude; + left = effect->u.rumble.weak_magnitude; + right = right * 0xff / 0xffff; + left = left * 0xff / 0xffff; + CLAMP(left); + CLAMP(right); + report->field[0]->value[0] = 0x42; + report->field[0]->value[1] = 0x00; + report->field[0]->value[2] = left; + report->field[0]->value[3] = right; + dbg_hid("(left, right)=(%04x, %04x)\n", left, right); + usbhid_submit_report(hid, report, USB_DIR_OUT); + 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; + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + +int lgff_init(struct hid_device* hid) +{ + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + struct hid_report *report; + struct hid_field *field; + const signed short *ff_bits = ff_joystick; + int error; + int i; + + /* Find the report to use */ + if (list_empty(report_list)) { + hid_err(hid, "No output report found\n"); + return -1; + } + + /* Check that the report looks ok */ + report = list_entry(report_list->next, struct hid_report, list); + field = report->field[0]; + if (!field) { + hid_err(hid, "NULL field\n"); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(devices); i++) { + if (dev->id.vendor == devices[i].idVendor && + dev->id.product == devices[i].idProduct) { + ff_bits = devices[i].ff; + break; + } + } + + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], dev->ffbit); + + error = input_ff_create_memless(dev, NULL, hid_lgff_play); + if (error) + return error; + + 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-magicmouse.c b/drivers/hid/hid-magicmouse.c new file mode 100644 index 00000000..8427463b --- /dev/null +++ b/drivers/hid/hid-magicmouse.c @@ -0,0 +1,615 @@ +/* + * 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/module.h> +#include <linux/slab.h> +#include <linux/usb.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 || strict_strtoul(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_touches = true; +module_param(report_touches, bool, 0644); +MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)"); + +static bool report_undeciphered; +module_param(report_undeciphered, bool, 0644); +MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); + +#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 + +/* Single touch emulation should only begin when no touches are currently down. + * This is true when single_touch_id is equal to NO_TOUCHES. If multiple touches + * are down and the touch providing for single touch emulation is lifted, + * single_touch_id is equal to SINGLE_TOUCH_UP. While single touch emulation is + * occurring, single_touch_id corresponds with the tracking id of the touch used. + */ +#define NO_TOUCHES -1 +#define SINGLE_TOUCH_UP -2 + +/* Touch surface information. Dimension is in hundredths of a mm, min and max + * are in units. */ +#define MOUSE_DIMENSION_X (float)9056 +#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]; + int single_touch_id; +}; + +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++; + if (msc->single_touch_id == NO_TOUCHES) + msc->single_touch_id = id; + } else if (msc->single_touch_id == id) + msc->single_touch_id = SINGLE_TOUCH_UP; + + /* Generate the input events for this touch. */ + if (report_touches && down) { + input_report_abs(input, ABS_MT_TRACKING_ID, id); + 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]); + } + + input_mt_sync(input); + } +} + +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; + msc->ntouches = 0; + for (ii = 0; ii < npoints; ii++) + magicmouse_emit_touch(msc, ii, data + ii * 9 + 4); + + /* We don't need an MT sync here because trackpad emits a + * BTN_TOUCH event in a new frame when all touches are released. + */ + if (msc->ntouches == 0) + msc->single_touch_id = NO_TOUCHES; + + clicks = data[1]; + + /* The following bits provide a device specific timestamp. They + * 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; + msc->ntouches = 0; + for (ii = 0; ii < npoints; ii++) + magicmouse_emit_touch(msc, ii, data + ii * 8 + 6); + + if (report_touches && msc->ntouches == 0) + input_mt_sync(input); + + /* When emulating three-button mode, it is important + * to have the current touch information before + * generating a click event. + */ + 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_report_key(input, BTN_TOUCH, msc->ntouches > 0); + input_report_key(input, BTN_TOOL_FINGER, msc->ntouches == 1); + input_report_key(input, BTN_TOOL_DOUBLETAP, msc->ntouches == 2); + input_report_key(input, BTN_TOOL_TRIPLETAP, msc->ntouches == 3); + input_report_key(input, BTN_TOOL_QUADTAP, msc->ntouches == 4); + if (msc->single_touch_id >= 0) { + input_report_abs(input, ABS_X, + msc->touches[msc->single_touch_id].x); + input_report_abs(input, ABS_Y, + msc->touches[msc->single_touch_id].y); + } + } + + input_sync(input); + return 1; +} + +static int magicmouse_setup_input(struct hid_device *hdev, struct hid_input *hi) +{ + struct input_dev *input = hi->input; + + __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_TOUCH, input->keybit); + __set_bit(INPUT_PROP_POINTER, input->propbit); + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + } + + if (report_touches) { + __set_bit(EV_ABS, input->evbit); + + input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 15, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 4, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 4, 0); + input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0); + + /* Note: Touch Y position from the device is inverted relative + * to how pointer motion is reported (and relative to how USB + * HID recommends the coordinates work). This driver keeps + * the origin at the same position, and just uses the additive + * inverse of the reported Y. + */ + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + input_set_abs_params(input, ABS_MT_POSITION_X, + MOUSE_MIN_X, MOUSE_MAX_X, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, + MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0); + + input_abs_set_res(input, ABS_MT_POSITION_X, + MOUSE_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, + MOUSE_RES_Y); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X, + TRACKPAD_MAX_X, 4, 0); + input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y, + TRACKPAD_MAX_Y, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, + TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, + TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0); + + input_abs_set_res(input, ABS_X, TRACKPAD_RES_X); + input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y); + input_abs_set_res(input, ABS_MT_POSITION_X, + TRACKPAD_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, + TRACKPAD_RES_Y); + } + + input_set_events_per_packet(input, 60); + } + + if (report_undeciphered) { + __set_bit(EV_MSC, input->evbit); + __set_bit(MSC_RAW, input->mscbit); + } + + return 0; +} + +static int magicmouse_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + if (!msc->input) + msc->input = hi->input; + + /* Magic Trackpad does not give relative data after switching to MT */ + if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD && + field->flags & HID_MAIN_ITEM_RELATIVE) + return -1; + + return 0; +} + +static int magicmouse_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 = kzalloc(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); + + msc->single_touch_id = NO_TOUCHES; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "magicmouse hid parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "magicmouse hw start failed\n"); + goto err_free; + } + + 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 = hdev->hid_output_raw_report(hdev, feature, sizeof(feature), + HID_FEATURE_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); +err_free: + kfree(msc); + return ret; +} + +static void magicmouse_remove(struct hid_device *hdev) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + kfree(msc); +} + +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, + .remove = magicmouse_remove, + .raw_event = magicmouse_raw_event, + .input_mapping = magicmouse_input_mapping, + .input_register = magicmouse_setup_input, +}; + +static int __init magicmouse_init(void) +{ + int ret; + + ret = hid_register_driver(&magicmouse_driver); + if (ret) + pr_err("can't register magicmouse driver\n"); + + return ret; +} + +static void __exit magicmouse_exit(void) +{ + hid_unregister_driver(&magicmouse_driver); +} + +module_init(magicmouse_init); +module_exit(magicmouse_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c new file mode 100644 index 00000000..e5c699b6 --- /dev/null +++ b/drivers/hid/hid-microsoft.c @@ -0,0 +1,230 @@ +/* + * 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) 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 MS_HIDINPUT 0x01 +#define MS_ERGONOMY 0x02 +#define MS_PRESENTER 0x04 +#define MS_RDESC 0x08 +#define MS_NOGET 0x10 +#define MS_DUPLICATE_USAGES 0x20 + +/* + * Microsoft Wireless Desktop Receiver (Model 1028) has + * 'Usage Min/Max' where it ought to have 'Physical Min/Max' + */ +static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + + if ((quirks & MS_RDESC) && *rsize == 571 && rdesc[557] == 0x19 && + rdesc[559] == 0x29) { + hid_info(hdev, "fixing up Microsoft Wireless Receiver Model 1028 report descriptor\n"); + rdesc[557] = 0x35; + rdesc[559] = 0x45; + } + return rdesc; +} + +#define ms_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \ + EV_KEY, (c)) +static int ms_ergonomy_kb_quirk(struct hid_input *hi, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct input_dev *input = hi->input; + + switch (usage->hid & HID_USAGE) { + case 0xfd06: ms_map_key_clear(KEY_CHAT); break; + case 0xfd07: ms_map_key_clear(KEY_PHONE); break; + 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); + default: + return 0; + } + return 1; +} + +static int ms_presenter_8k_quirk(struct hid_input *hi, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + 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 ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR) + return 0; + + 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); + + if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput || + !usage->type) + return 0; + + /* Handling MS keyboards special buttons */ + if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff05)) { + struct input_dev *input = field->hidinput->input; + 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_NE4K), + .driver_data = MS_ERGONOMY }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K), + .driver_data = MS_ERGONOMY | MS_RDESC }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB), + .driver_data = MS_PRESENTER }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K), + .driver_data = MS_ERGONOMY }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_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_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, +}; + +static int __init ms_init(void) +{ + return hid_register_driver(&ms_driver); +} + +static void __exit ms_exit(void) +{ + hid_unregister_driver(&ms_driver); +} + +module_init(ms_init); +module_exit(ms_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-monterey.c b/drivers/hid/hid-monterey.c new file mode 100644 index 00000000..dedf7577 --- /dev/null +++ b/drivers/hid/hid-monterey.c @@ -0,0 +1,80 @@ +/* + * 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) 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/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 >= 30 && 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, +}; + +static int __init mr_init(void) +{ + return hid_register_driver(&mr_driver); +} + +static void __exit mr_exit(void) +{ + hid_unregister_driver(&mr_driver); +} + +module_init(mr_init); +module_exit(mr_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c new file mode 100644 index 00000000..8cf4310a --- /dev/null +++ b/drivers/hid/hid-multitouch.c @@ -0,0 +1,1081 @@ +/* + * HID driver for multitouch panels + * + * Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr> + * Copyright (c) 2010-2012 Benjamin Tissoires <benjamin.tissoires@gmail.com> + * Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France + * + * 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. + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/input/mt.h> +#include "usbhid/usbhid.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) + +struct mt_slot { + __s32 x, y, p, w, h; + __s32 contactid; /* the device ContactID assigned to this slot */ + bool touch_state; /* is the touch valid? */ + bool seen_in_this_frame;/* has this slot been updated */ +}; + +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 */ +}; + +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 */ + unsigned last_field_index; /* last field index of the report */ + unsigned last_slot_field; /* the last field of a slot */ + __s8 inputmode; /* InputMode HID feature, -1 if non-existent */ + __s8 maxcontact_report_id; /* Maximum Contact Number HID feature, + -1 if non-existent */ + __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 */ + bool curvalid; /* is the current contact valid? */ + struct mt_slot *slots; +}; + +/* 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 +#define MT_CLS_DUAL_NSMU_CONTACTID 0x0008 +#define MT_CLS_INRANGE_CONTACTNUMBER 0x0009 + +/* vendor specific classes */ +#define MT_CLS_3M 0x0101 +#define MT_CLS_CYPRESS 0x0102 +#define MT_CLS_EGALAX 0x0103 +#define MT_CLS_EGALAX_SERIAL 0x0104 +#define MT_CLS_TOPSEED 0x0105 +#define MT_CLS_PANASONIC 0x0106 + +#define MT_DEFAULT_MAXCONTACT 10 + +/* + * 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 int find_slot_from_contactid(struct mt_device *td) +{ + int i; + for (i = 0; i < td->maxcontacts; ++i) { + if (td->slots[i].contactid == td->curdata.contactid && + td->slots[i].touch_state) + return i; + } + for (i = 0; i < td->maxcontacts; ++i) { + if (!td->slots[i].seen_in_this_frame && + !td->slots[i].touch_state) + return i; + } + /* should not occurs. If this happens that means + * that the device sent more touches that it says + * in the report descriptor. It is ignored then. */ + return -1; +} + +static struct mt_class mt_classes[] = { + { .name = MT_CLS_DEFAULT, + .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_DUAL_NSMU_CONTACTID, + .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP | + MT_QUIRK_SLOT_IS_CONTACTID, + .maxcontacts = 2 }, + { .name = MT_CLS_INRANGE_CONTACTNUMBER, + .quirks = MT_QUIRK_VALID_IS_INRANGE | + MT_QUIRK_SLOT_IS_CONTACTNUMBER }, + + /* + * 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 }, + { .name = MT_CLS_CYPRESS, + .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP | + MT_QUIRK_CYPRESS, + .maxcontacts = 10 }, + { .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 }, + + { } +}; + +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; + + 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: + td->inputmode = field->report->id; + break; + case HID_DG_CONTACTMAX: + td->maxcontact_report_id = field->report->id; + td->maxcontacts = field->value[0]; + if (td->mtclass.maxcontacts) + /* check if the maxcontacts is given by the class */ + td->maxcontacts = td->mtclass.maxcontacts; + + 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); +} + +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_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; + + /* 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 (field->application == HID_DG_TOUCHSCREEN) + set_bit(INPUT_PROP_DIRECT, hi->input->propbit); + else if (field->application != HID_DG_TOUCHPAD) + return 0; + + /* In case of an indirect device (touchpad), we need to add + * specific BTN_TOOL_* to be handled by the synaptics xorg + * driver. + * We also consider that touchscreens providing buttons are touchpads. + */ + if (field->application == HID_DG_TOUCHPAD || + (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON || + cls->is_indirect) { + set_bit(INPUT_PROP_POINTER, hi->input->propbit); + set_bit(BTN_TOOL_FINGER, hi->input->keybit); + set_bit(BTN_TOOL_DOUBLETAP, hi->input->keybit); + set_bit(BTN_TOOL_TRIPLETAP, hi->input->keybit); + set_bit(BTN_TOOL_QUADTAP, hi->input->keybit); + } + + /* eGalax devices provide a Digitizer.Stylus input which overrides + * the correct Digitizers.Finger X/Y ranges. + * Let's just ignore this input. */ + if (field->physical == HID_DG_STYLUS) + return -1; + + /* 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 (field->application == HID_DG_TOUCHSCREEN) + set_bit(INPUT_PROP_DIRECT, hi->input->propbit); + else if (field->application == HID_DG_TOUCHPAD) + set_bit(INPUT_PROP_POINTER, hi->input->propbit); + else + 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); + set_abs(hi->input, ABS_MT_POSITION_X, field, + cls->sn_move); + /* touchscreen emulation */ + set_abs(hi->input, ABS_X, field, cls->sn_move); + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + return 1; + case HID_GD_Y: + 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); + /* touchscreen emulation */ + set_abs(hi->input, ABS_Y, field, cls->sn_move); + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + return 1; + } + return 0; + + case HID_UP_DIGITIZER: + switch (usage->hid) { + case HID_DG_INRANGE: + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + return 1; + case HID_DG_CONFIDENCE: + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + 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); + td->last_field_index = field->index; + return 1; + case HID_DG_CONTACTID: + if (!td->maxcontacts) + td->maxcontacts = MT_DEFAULT_MAXCONTACT; + input_mt_init_slots(hi->input, td->maxcontacts); + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + td->touches_by_report++; + return 1; + case HID_DG_WIDTH: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_TOUCH_MAJOR); + set_abs(hi->input, ABS_MT_TOUCH_MAJOR, field, + cls->sn_width); + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + return 1; + case HID_DG_HEIGHT: + hid_map_usage(hi, usage, bit, max, + EV_ABS, ABS_MT_TOUCH_MINOR); + 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); + td->last_field_index = field->index; + 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); + /* touchscreen emulation */ + set_abs(hi->input, ABS_PRESSURE, field, + cls->sn_pressure); + mt_store_field(usage, td, hi); + td->last_field_index = field->index; + return 1; + case HID_DG_CONTACTCOUNT: + td->last_field_index = field->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 */ + td->last_field_index = field->index; + 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_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) +{ + __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 find_slot_from_contactid(td); +} + +/* + * 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) +{ + td->curdata.seen_in_this_frame = true; + if (td->curvalid) { + int slotnum = mt_compute_slot(td); + + if (slotnum >= 0 && slotnum < td->maxcontacts) + td->slots[slotnum] = td->curdata; + } + 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_emit_event(struct mt_device *td, struct input_dev *input) +{ + int i; + + for (i = 0; i < td->maxcontacts; ++i) { + struct mt_slot *s = &(td->slots[i]); + if ((td->mtclass.quirks & MT_QUIRK_NOT_SEEN_MEANS_UP) && + !s->seen_in_this_frame) { + s->touch_state = false; + } + + input_mt_slot(input, i); + input_mt_report_slot_state(input, MT_TOOL_FINGER, + s->touch_state); + if (s->touch_state) { + /* this finger is on the screen */ + 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_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); + } + s->seen_in_this_frame = false; + + } + + input_mt_report_pointer_emulation(input, true); + input_sync(input); + td->num_received = 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); + __s32 quirks = td->mtclass.quirks; + + if (hid->claimed & HID_CLAIMED_INPUT && td->slots) { + switch (usage->hid) { + case HID_DG_INRANGE: + if (quirks & MT_QUIRK_ALWAYS_VALID) + td->curvalid = true; + else if (quirks & MT_QUIRK_VALID_IS_INRANGE) + td->curvalid = 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: + td->curdata.x = value; + break; + case HID_GD_Y: + 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: + /* + * Includes multi-packet support where subsequent + * packets are sent with zero contactcount. + */ + if (value) + td->num_expected = value; + break; + case HID_DG_TOUCH: + /* do nothing */ + break; + + default: + /* fallback to the generic hidinput handling */ + return 0; + } + + if (usage->hid == td->last_slot_field) + mt_complete_slot(td); + + if (field->index == td->last_field_index + && td->num_received >= td->num_expected) + mt_emit_event(td, field->hidinput->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 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; + + if (td->inputmode < 0) + return; + + re = &(hdev->report_enum[HID_FEATURE_REPORT]); + r = re->report_id_hash[td->inputmode]; + if (r) { + r->field[0]->value[0] = 0x02; + usbhid_submit_report(hdev, r, USB_DIR_OUT); + } +} + +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; + usbhid_submit_report(hdev, r, USB_DIR_OUT); + } + } +} + +static void mt_post_parse(struct mt_device *td) +{ + struct mt_fields *f = td->fields; + + 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]; + } +} + +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 */ + + if (id) { + 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; + hdev->quirks &= ~HID_QUIRK_MULTITOUCH; + + td = kzalloc(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; + hid_set_drvdata(hdev, td); + + td->fields = kzalloc(sizeof(struct mt_fields), GFP_KERNEL); + if (!td->fields) { + dev_err(&hdev->dev, "cannot allocate multitouch fields data\n"); + ret = -ENOMEM; + goto fail; + } + + ret = hid_parse(hdev); + if (ret != 0) + goto fail; + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + goto fail; + + mt_post_parse(td); + + if (!id && td->touches_by_report == 1) { + /* the device has been sent by hid-generic */ + mtclass = &td->mtclass; + mtclass->quirks |= MT_QUIRK_ALWAYS_VALID; + mtclass->quirks &= ~MT_QUIRK_NOT_SEEN_MEANS_UP; + mtclass->quirks &= ~MT_QUIRK_VALID_IS_INRANGE; + mtclass->quirks &= ~MT_QUIRK_VALID_IS_CONFIDENCE; + } + + td->slots = kzalloc(td->maxcontacts * sizeof(struct mt_slot), + GFP_KERNEL); + if (!td->slots) { + dev_err(&hdev->dev, "cannot allocate multitouch slots\n"); + hid_hw_stop(hdev); + ret = -ENOMEM; + goto fail; + } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + + mt_set_maxcontacts(hdev); + mt_set_input_mode(hdev); + + kfree(td->fields); + td->fields = NULL; + + return 0; + +fail: + kfree(td->fields); + kfree(td); + return ret; +} + +#ifdef CONFIG_PM +static int mt_reset_resume(struct hid_device *hdev) +{ + mt_set_maxcontacts(hdev); + mt_set_input_mode(hdev); + return 0; +} +#endif + +static void mt_remove(struct hid_device *hdev) +{ + struct mt_device *td = hid_get_drvdata(hdev); + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); + hid_hw_stop(hdev); + kfree(td->slots); + kfree(td); + hid_set_drvdata(hdev, NULL); +} + +static const struct hid_device_id mt_devices[] = { + + /* 3M panels */ + { .driver_data = MT_CLS_3M, + HID_USB_DEVICE(USB_VENDOR_ID_3M, + USB_DEVICE_ID_3M1968) }, + { .driver_data = MT_CLS_3M, + HID_USB_DEVICE(USB_VENDOR_ID_3M, + USB_DEVICE_ID_3M2256) }, + { .driver_data = MT_CLS_3M, + HID_USB_DEVICE(USB_VENDOR_ID_3M, + USB_DEVICE_ID_3M3266) }, + + /* ActionStar panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_ACTIONSTAR, + USB_DEVICE_ID_ACTIONSTAR_1011) }, + + /* Atmel panels */ + { .driver_data = MT_CLS_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_ATMEL, + USB_DEVICE_ID_ATMEL_MULTITOUCH) }, + { .driver_data = MT_CLS_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_ATMEL, + USB_DEVICE_ID_ATMEL_MXT_DIGITIZER) }, + + /* Cando panels */ + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_MULTI_TOUCH) }, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_MULTI_TOUCH_10_1) }, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6) }, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_MULTI_TOUCH_15_6) }, + + /* Chunghwa Telecom touch panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_CHUNGHWAT, + USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH) }, + + /* CVTouch panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_CVTOUCH, + USB_DEVICE_ID_CVTOUCH_SCREEN) }, + + /* Cypress panel */ + { .driver_data = MT_CLS_CYPRESS, + HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, + USB_DEVICE_ID_CYPRESS_TRUETOUCH) }, + + /* eGalax devices (resistive) */ + { .driver_data = MT_CLS_EGALAX, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480D) }, + { .driver_data = MT_CLS_EGALAX, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480E) }, + + /* eGalax devices (capacitive) */ + { .driver_data = MT_CLS_EGALAX, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_720C) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7207) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_725E) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_722A) }, + { .driver_data = MT_CLS_EGALAX, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_726B) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7262) }, + { .driver_data = MT_CLS_EGALAX, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_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_72FA) }, + { .driver_data = MT_CLS_EGALAX, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) }, + + /* Elo TouchSystems IntelliTouch Plus panel */ + { .driver_data = MT_CLS_DUAL_NSMU_CONTACTID, + HID_USB_DEVICE(USB_VENDOR_ID_ELO, + USB_DEVICE_ID_ELO_TS2515) }, + + /* GeneralTouch panel */ + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH, + USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS) }, + + /* Gametel game controller */ + { .driver_data = MT_CLS_DEFAULT, + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_FRUCTEL, + USB_DEVICE_ID_GAMETEL_MT_MODE) }, + + /* GoodTouch panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_GOODTOUCH, + USB_DEVICE_ID_GOODTOUCH_000f) }, + + /* Hanvon panels */ + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, + HID_USB_DEVICE(USB_VENDOR_ID_HANVON_ALT, + USB_DEVICE_ID_HANVON_ALT_MULTITOUCH) }, + + /* Ideacom panel */ + { .driver_data = MT_CLS_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, + USB_DEVICE_ID_IDEACOM_IDC6650) }, + { .driver_data = MT_CLS_SERIAL, + HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, + USB_DEVICE_ID_IDEACOM_IDC6651) }, + + /* Ilitek dual touch panel */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH) }, + + /* IRTOUCH panels */ + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, + HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, + USB_DEVICE_ID_IRTOUCH_INFRARED_USB) }, + + /* LG Display panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_LG, + USB_DEVICE_ID_LG_MULTITOUCH) }, + + /* Lumio panels */ + { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, + HID_USB_DEVICE(USB_VENDOR_ID_LUMIO, + USB_DEVICE_ID_CRYSTALTOUCH) }, + { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, + HID_USB_DEVICE(USB_VENDOR_ID_LUMIO, + USB_DEVICE_ID_CRYSTALTOUCH_DUAL) }, + + /* MosArt panels */ + { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, + HID_USB_DEVICE(USB_VENDOR_ID_ASUS, + USB_DEVICE_ID_ASUS_T91MT)}, + { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, + HID_USB_DEVICE(USB_VENDOR_ID_ASUS, + USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO) }, + { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, + HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, + USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART) }, + + /* Panasonic panels */ + { .driver_data = MT_CLS_PANASONIC, + HID_USB_DEVICE(USB_VENDOR_ID_PANASONIC, + USB_DEVICE_ID_PANABOARD_UBT780) }, + { .driver_data = MT_CLS_PANASONIC, + HID_USB_DEVICE(USB_VENDOR_ID_PANASONIC, + USB_DEVICE_ID_PANABOARD_UBT880) }, + + /* PenMount panels */ + { .driver_data = MT_CLS_CONFIDENCE, + HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, + USB_DEVICE_ID_PENMOUNT_PCI) }, + + /* PixArt optical touch screen */ + { .driver_data = MT_CLS_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_PIXART, + USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN) }, + { .driver_data = MT_CLS_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_PIXART, + USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1) }, + { .driver_data = MT_CLS_INRANGE_CONTACTNUMBER, + HID_USB_DEVICE(USB_VENDOR_ID_PIXART, + USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2) }, + + /* PixCir-based panels */ + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, + HID_USB_DEVICE(USB_VENDOR_ID_HANVON, + USB_DEVICE_ID_HANVON_MULTITOUCH) }, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, + HID_USB_DEVICE(USB_VENDOR_ID_CANDO, + USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH) }, + + /* Quanta-based panels */ + { .driver_data = MT_CLS_CONFIDENCE_CONTACT_ID, + HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, + USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, + { .driver_data = MT_CLS_CONFIDENCE_CONTACT_ID, + HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, + USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001) }, + { .driver_data = MT_CLS_CONFIDENCE_CONTACT_ID, + HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, + USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008) }, + + /* Stantum panels */ + { .driver_data = MT_CLS_CONFIDENCE, + HID_USB_DEVICE(USB_VENDOR_ID_STANTUM, + USB_DEVICE_ID_MTP)}, + { .driver_data = MT_CLS_CONFIDENCE, + HID_USB_DEVICE(USB_VENDOR_ID_STANTUM_STM, + USB_DEVICE_ID_MTP_STM)}, + { .driver_data = MT_CLS_CONFIDENCE, + HID_USB_DEVICE(USB_VENDOR_ID_STANTUM_SITRONIX, + USB_DEVICE_ID_MTP_SITRONIX)}, + + /* TopSeed panels */ + { .driver_data = MT_CLS_TOPSEED, + HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, + USB_DEVICE_ID_TOPSEED2_PERIPAD_701) }, + + /* Touch International panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_TOUCH_INTL, + USB_DEVICE_ID_TOUCH_INTL_MULTI_TOUCH) }, + + /* Unitec panels */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_UNITEC, + USB_DEVICE_ID_UNITEC_USB_TOUCH_0709) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_UNITEC, + USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19) }, + /* XAT */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XAT, + USB_DEVICE_ID_XAT_CSR) }, + + /* Xiroku */ + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_SPX) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_MPX) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_SPX1) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_MPX1) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR1) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_SPX2) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_MPX2) }, + { .driver_data = MT_CLS_DEFAULT, + HID_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + + { } +}; +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, + .feature_mapping = mt_feature_mapping, + .usage_table = mt_grabbed_usages, + .event = mt_event, +#ifdef CONFIG_PM + .reset_resume = mt_reset_resume, +#endif +}; + +static int __init mt_init(void) +{ + return hid_register_driver(&mt_driver); +} + +static void __exit mt_exit(void) +{ + hid_unregister_driver(&mt_driver); +} + +module_init(mt_init); +module_exit(mt_exit); diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c new file mode 100644 index 00000000..9fae2ebd --- /dev/null +++ b/drivers/hid/hid-ntrig.c @@ -0,0 +1,1042 @@ +/* + * 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) + return -EINVAL; + + usbhid_submit_report(hdev, report, USB_DIR_IN); + usbhid_wait_io(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; + + usbhid_submit_report(hdev, report, USB_DIR_IN); +} + +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 (strict_strtoul(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 (strict_strtoul(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 (strict_strtoul(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 (strict_strtoul(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 (strict_strtoul(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 (strict_strtoul(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 int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct ntrig_data *nd; + struct hid_input *hidinput; + struct input_dev *input; + 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 = 0; + nd->sensor_logical_height = 0; + nd->sensor_physical_width = 0; + nd->sensor_physical_height = 0; + + 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; + } + + + list_for_each_entry(hidinput, &hdev->inputs, list) { + if (hidinput->report->maxfield < 1) + continue; + + input = hidinput->input; + 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; + } + } + + /* 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 */ + usbhid_wait_io(hdev); + usbhid_submit_report(hdev, report, USB_DIR_IN); + + /* + * 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, + .usage_table = ntrig_grabbed_usages, + .event = ntrig_event, +}; + +static int __init ntrig_init(void) +{ + return hid_register_driver(&ntrig_driver); +} + +static void __exit ntrig_exit(void) +{ + hid_unregister_driver(&ntrig_driver); +} + +module_init(ntrig_init); +module_exit(ntrig_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ortek.c b/drivers/hid/hid-ortek.c new file mode 100644 index 00000000..0ffa1d2d --- /dev/null +++ b/drivers/hid/hid-ortek.c @@ -0,0 +1,66 @@ +/* + * 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 +}; + +static int __init ortek_init(void) +{ + return hid_register_driver(&ortek_driver); +} + +static void __exit ortek_exit(void) +{ + hid_unregister_driver(&ortek_driver); +} + +module_init(ortek_init); +module_exit(ortek_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-petalynx.c b/drivers/hid/hid-petalynx.c new file mode 100644 index 00000000..f1ea3ff8 --- /dev/null +++ b/drivers/hid/hid-petalynx.c @@ -0,0 +1,120 @@ +/* + * 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) 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/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 >= 60 && 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, +}; + +static int __init pl_init(void) +{ + return hid_register_driver(&pl_driver); +} + +static void __exit pl_exit(void) +{ + hid_unregister_driver(&pl_driver); +} + +module_init(pl_init); +module_exit(pl_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c new file mode 100644 index 00000000..45c3433f --- /dev/null +++ b/drivers/hid/hid-picolcd.c @@ -0,0 +1,2752 @@ +/*************************************************************************** + * Copyright (C) 2010 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 "usbhid/usbhid.h" +#include <linux/usb.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> + +#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] */ + +#ifdef CONFIG_HID_PICOLCD_FB +/* 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, + }, +}; +#endif /* CONFIG_HID_PICOLCD_FB */ + +/* 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[] = { + 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 */ +}; +#define PICOLCD_KEYS ARRAY_SIZE(def_keymap) + +/* 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]; +}; + +/* 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; + struct input_dev *input_cir; + unsigned short keycode[PICOLCD_KEYS]; + +#ifdef CONFIG_HID_PICOLCD_FB + /* Framebuffer stuff */ + u8 fb_update_rate; + u8 fb_bpp; + u8 fb_force; + u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ + u8 *fb_bitmap; /* framebuffer */ + struct fb_info *fb_info; + struct fb_deferred_io fb_defio; +#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_READY_FB 4 +}; + + +/* 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) + +static 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; +} + +#ifdef CONFIG_DEBUG_FS +static void picolcd_debug_out_report(struct picolcd_data *data, + struct hid_device *hdev, struct hid_report *report); +#define usbhid_submit_report(a, b, c) \ + do { \ + picolcd_debug_out_report(hid_get_drvdata(a), a, b); \ + usbhid_submit_report(a, b, c); \ + } while (0) +#endif + +/* Submit a report and wait for a reply from device - if device fades away + * or does not respond in time, return NULL */ +static 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++; + } + data->pending = work; + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + 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; +} + +#ifdef CONFIG_HID_PICOLCD_FB +/* Send a given tile to PicoLCD */ +static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int tile) +{ + struct picolcd_data *data = hid_get_drvdata(hdev); + struct hid_report *report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, hdev); + struct hid_report *report2 = picolcd_out_report(REPORT_LCD_DATA, hdev); + unsigned long flags; + u8 *tdata; + int i; + + if (!report1 || report1->maxfield != 1 || !report2 || report2->maxfield != 1) + return -ENODEV; + + spin_lock_irqsave(&data->lock, flags); + 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 = data->fb_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]); + + usbhid_submit_report(data->hdev, report1, USB_DIR_OUT); + usbhid_submit_report(data->hdev, report2, USB_DIR_OUT); + 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; +} + +/* Reconfigure LCD display */ +static int picolcd_fb_reset(struct picolcd_data *data, int clear) +{ + struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev); + 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); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + } + + data->status |= PICOLCD_READY_FB; + spin_unlock_irqrestore(&data->lock, flags); + + if (data->fb_bitmap) { + if (clear) { + memset(data->fb_vbitmap, 0, PICOLCDFB_SIZE); + memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); + } + data->fb_force = 1; + } + + /* schedule first output of framebuffer */ + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); + + return 0; +} + +/* Update fb_vbitmap from the screen_base and send changed tiles to device */ +static void picolcd_fb_update(struct picolcd_data *data) +{ + int chip, tile, n; + unsigned long flags; + + if (!data) + return; + + spin_lock_irqsave(&data->lock, flags); + if (!(data->status & PICOLCD_READY_FB)) { + spin_unlock_irqrestore(&data->lock, flags); + picolcd_fb_reset(data, 0); + } else { + spin_unlock_irqrestore(&data->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 (picolcd_fb_update_tile(data->fb_vbitmap, + data->fb_bitmap, data->fb_bpp, chip, tile) || + data->fb_force) { + n += 2; + if (!data->fb_info->par) + return; /* device lost! */ + if (n >= HID_OUTPUT_FIFO_SIZE / 2) { + usbhid_wait_io(data->hdev); + n = 0; + } + picolcd_fb_send_tile(data->hdev, chip, tile); + } + data->fb_force = false; + if (n) + usbhid_wait_io(data->hdev); +} + +/* 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) +{ + if (!info->par) + return -ENODEV; + /* 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_data *data = info->par; + u32 *ref_cnt = info->pseudo_palette; + int may_release; + + info->par = NULL; + if (data) + data->fb_info = NULL; + fb_deferred_io_cleanup(info); + + ref_cnt--; + mutex_lock(&info->lock); + (*ref_cnt)--; + may_release = !*ref_cnt; + mutex_unlock(&info->lock); + if (may_release) { + 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_data *data = info->par; + u8 *tmp_fb, *o_fb; + if (!data) + return -ENODEV; + if (info->var.bits_per_pixel == data->fb_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 = data->fb_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); + data->fb_bpp = info->var.bits_per_pixel; + return 0; +} + +/* Do refcounting on our FB and cleanup per worker if FB is + * closed after unplug of our device + * (fb_release holds info->lock and still touches info after + * we return so we can't release it immediately. + */ +struct picolcd_fb_cleanup_item { + struct fb_info *info; + struct picolcd_fb_cleanup_item *next; +}; +static struct picolcd_fb_cleanup_item *fb_pending; +static DEFINE_SPINLOCK(fb_pending_lock); + +static void picolcd_fb_do_cleanup(struct work_struct *data) +{ + struct picolcd_fb_cleanup_item *item; + unsigned long flags; + + do { + spin_lock_irqsave(&fb_pending_lock, flags); + item = fb_pending; + fb_pending = item ? item->next : NULL; + spin_unlock_irqrestore(&fb_pending_lock, flags); + + if (item) { + u8 *fb = (u8 *)item->info->fix.smem_start; + /* make sure we do not race against fb core when + * releasing */ + mutex_lock(&item->info->lock); + mutex_unlock(&item->info->lock); + framebuffer_release(item->info); + vfree(fb); + } + } while (item); +} + +static DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup); + +static int picolcd_fb_open(struct fb_info *info, int u) +{ + u32 *ref_cnt = info->pseudo_palette; + ref_cnt--; + + (*ref_cnt)++; + return 0; +} + +static int picolcd_fb_release(struct fb_info *info, int u) +{ + u32 *ref_cnt = info->pseudo_palette; + ref_cnt--; + + (*ref_cnt)++; + if (!*ref_cnt) { + unsigned long flags; + struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt; + item--; + spin_lock_irqsave(&fb_pending_lock, flags); + item->next = fb_pending; + fb_pending = item; + spin_unlock_irqrestore(&fb_pending_lock, flags); + schedule_work(&picolcd_fb_cleanup); + } + 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_open = picolcd_fb_open, + .fb_release = picolcd_fb_release, + .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->par); +} + +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); + unsigned i, fb_update_rate = data->fb_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); + 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; + + data->fb_update_rate = u; + data->fb_defio.delay = HZ / data->fb_update_rate; + return count; +} + +static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show, + picolcd_fb_update_rate_store); + +/* initialize Framebuffer device */ +static int picolcd_init_framebuffer(struct picolcd_data *data) +{ + struct device *dev = &data->hdev->dev; + struct fb_info *info = NULL; + int i, error = -ENOMEM; + u8 *fb_vbitmap = NULL; + u8 *fb_bitmap = NULL; + u32 *palette; + + fb_bitmap = vmalloc(PICOLCDFB_SIZE*8); + if (fb_bitmap == NULL) { + dev_err(dev, "can't get a free page for framebuffer\n"); + goto err_nomem; + } + + fb_vbitmap = kmalloc(PICOLCDFB_SIZE, GFP_KERNEL); + if (fb_vbitmap == NULL) { + dev_err(dev, "can't alloc vbitmap image buffer\n"); + goto err_nomem; + } + + data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; + data->fb_defio = picolcd_fb_defio; + /* The extra memory is: + * - struct picolcd_fb_cleanup_item + * - u32 for ref_count + * - 256*u32 for pseudo_palette + */ + info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev); + if (info == NULL) { + dev_err(dev, "failed to allocate a framebuffer\n"); + goto err_nomem; + } + + palette = info->par + sizeof(struct picolcd_fb_cleanup_item); + *palette = 1; + palette++; + for (i = 0; i < 256; i++) + palette[i] = i > 0 && i < 16 ? 0xff : 0; + info->pseudo_palette = palette; + info->fbdefio = &data->fb_defio; + info->screen_base = (char __force __iomem *)fb_bitmap; + info->fbops = &picolcdfb_ops; + info->var = picolcdfb_var; + info->fix = picolcdfb_fix; + info->fix.smem_len = PICOLCDFB_SIZE*8; + info->fix.smem_start = (unsigned long)fb_bitmap; + info->par = data; + info->flags = FBINFO_FLAG_DEFAULT; + + data->fb_vbitmap = fb_vbitmap; + data->fb_bitmap = fb_bitmap; + data->fb_bpp = picolcdfb_var.bits_per_pixel; + 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); + data->fb_info = info; + error = register_framebuffer(info); + if (error) { + dev_err(dev, "failed to register framebuffer\n"); + goto err_sysfs; + } + /* schedule first output of framebuffer */ + data->fb_force = 1; + schedule_delayed_work(&info->deferred_work, 0); + return 0; + +err_sysfs: + fb_deferred_io_cleanup(info); + device_remove_file(dev, &dev_attr_fb_update_rate); +err_cleanup: + data->fb_vbitmap = NULL; + data->fb_bitmap = NULL; + data->fb_bpp = 0; + data->fb_info = NULL; + +err_nomem: + framebuffer_release(info); + vfree(fb_bitmap); + kfree(fb_vbitmap); + return error; +} + +static void picolcd_exit_framebuffer(struct picolcd_data *data) +{ + struct fb_info *info = data->fb_info; + u8 *fb_vbitmap = data->fb_vbitmap; + + if (!info) + return; + + info->par = NULL; + device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); + unregister_framebuffer(info); + data->fb_vbitmap = NULL; + data->fb_bitmap = NULL; + data->fb_bpp = 0; + data->fb_info = NULL; + kfree(fb_vbitmap); +} + +#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) +{ +} +#define picolcd_fbinfo(d) NULL +#endif /* CONFIG_HID_PICOLCD_FB */ + +#ifdef CONFIG_HID_PICOLCD_BACKLIGHT +/* + * backlight class device + */ +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); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + 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, +}; + +static 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; +} + +static void picolcd_exit_backlight(struct picolcd_data *data) +{ + struct backlight_device *bdev = data->backlight; + + data->backlight = NULL; + if (bdev) + backlight_device_unregister(bdev); +} + +static inline int picolcd_resume_backlight(struct picolcd_data *data) +{ + if (!data->backlight) + return 0; + return picolcd_set_brightness(data->backlight); +} + +#ifdef CONFIG_PM +static 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 */ +#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 +/* + * 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); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + 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, +}; + +static 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; +} + +static void picolcd_exit_lcd(struct picolcd_data *data) +{ + struct lcd_device *ldev = data->lcd; + + data->lcd = NULL; + if (ldev) + lcd_device_unregister(ldev); +} + +static inline int picolcd_resume_lcd(struct picolcd_data *data) +{ + if (!data->lcd) + return 0; + return picolcd_set_contrast(data->lcd, data->lcd_contrast); +} +#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 +/** + * LED class device + */ +static 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); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + 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); + 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; +} + +static 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; +} + +static 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); + } +} + +#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 int picolcd_leds_set(struct picolcd_data *data) +{ + return 0; +} +#endif /* CONFIG_HID_PICOLCD_LEDS */ + +/* + * 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_raw_cir(struct picolcd_data *data, + struct hid_report *report, u8 *raw_data, int size) +{ + /* Need understanding of CIR data format to implement ... */ + 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 + */ +static 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); + usbhid_submit_report(hdev, report, USB_DIR_OUT); + spin_unlock_irqrestore(&data->lock, flags); + + error = picolcd_check_version(hdev); + if (error) + return error; + + picolcd_resume_lcd(data); + picolcd_resume_backlight(data); +#ifdef CONFIG_HID_PICOLCD_FB + if (data->fb_info) + schedule_delayed_work(&data->fb_info->deferred_work, 0); +#endif /* CONFIG_HID_PICOLCD_FB */ + + 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) + 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); + usbhid_submit_report(data->hdev, report, USB_DIR_OUT); + 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); + + +#ifdef CONFIG_DEBUG_FS +/* + * The "reset" file + */ +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((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 *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 + 3 < dst_sz; i++) { + dst[j++] = hex_asc[(data[i] >> 4) & 0x0f]; + dst[j++] = hex_asc[data[i] & 0x0f]; + dst[j++] = ' '; + } + if (j < dst_sz) { + dst[j--] = '\0'; + dst[j] = '\n'; + } else + dst[j] = '\0'; +} + +static void picolcd_debug_out_report(struct picolcd_data *data, + struct hid_device *hdev, struct hid_report *report) +{ + u8 raw_data[70]; + int raw_size = (report->size >> 3) + 1; + char *buff; +#define BUFF_SZ 256 + + /* Avoid unnecessary overhead if debugfs is disabled */ + if (!hdev->debug_events) + return; + + buff = kmalloc(BUFF_SZ, GFP_ATOMIC); + if (!buff) + return; + + snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ", + report->id, raw_size); + hid_debug_event(hdev, buff); + if (raw_size + 5 > sizeof(raw_data)) { + kfree(buff); + hid_debug_event(hdev, " TOO BIG\n"); + return; + } else { + 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(buff); +} + +static 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 (!hdev->debug_events) + 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]-1); + hid_debug_event(hdev, buff); + dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]-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); +} + +static 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"); +} + +static void picolcd_exit_devfs(struct picolcd_data *data) +{ + struct dentry *dent; + + dent = data->debug_reset; + data->debug_reset = NULL; + if (dent) + debugfs_remove(dent); + dent = data->debug_eeprom; + data->debug_eeprom = NULL; + if (dent) + debugfs_remove(dent); + dent = data->debug_flash; + data->debug_flash = NULL; + if (dent) + debugfs_remove(dent); + mutex_destroy(&data->mutex_flash); +} +#else +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 */ + +/* + * 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 (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) { + if (data->input_cir) + 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.parent; + 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); +} + +/* initialize CIR input device */ +static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) +{ + /* support not implemented yet */ + return 0; +} + +static inline void picolcd_exit_cir(struct picolcd_data *data) +{ +} + +static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) +{ + int error; + + error = picolcd_check_version(hdev); + if (error) + return error; + + if (data->version[0] != 0 && data->version[1] != 3) + hid_info(hdev, "Device with untested firmware revision, please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", + dev_name(&hdev->dev)); + + /* 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) +{ + int error; + + error = picolcd_check_version(hdev); + if (error) + return error; + + if (data->version[0] != 1 && data->version[1] != 0) + hid_info(hdev, "Device with untested bootloader revision, please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", + dev_name(&hdev->dev)); + + 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; + } + + /* We don't use hidinput but hid_hw_start() fails if nothing is + * claimed. So spoof claimed input. */ + hdev->claimed = HID_CLAIMED_INPUT; + error = hid_hw_start(hdev, 0); + hdev->claimed = 0; + if (error) { + hid_err(hdev, "hardware start failed\n"); + goto err_cleanup_data; + } + + 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); +#ifdef CONFIG_HID_PICOLCD_FB + /* short-circuit FB as early as possible in order to + * avoid long delays if we host console. + */ + if (data->fb_info) + data->fb_info->par = NULL; +#endif + + 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); + hid_set_drvdata(hdev, NULL); + + /* 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); + + 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 +}; + +static int __init picolcd_init(void) +{ + return hid_register_driver(&picolcd_driver); +} + +static void __exit picolcd_exit(void) +{ + hid_unregister_driver(&picolcd_driver); +#ifdef CONFIG_HID_PICOLCD_FB + flush_work_sync(&picolcd_fb_cleanup); + WARN_ON(fb_pending); +#endif +} + +module_init(picolcd_init); +module_exit(picolcd_exit); +MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c new file mode 100644 index 00000000..47ed74c4 --- /dev/null +++ b/drivers/hid/hid-pl.c @@ -0,0 +1,232 @@ +/* + * 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 + * + * 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/usb.h> +#include <linux/hid.h> + +#include "hid-ids.h" + +#ifdef CONFIG_PANTHERLORD_FF +#include "usbhid/usbhid.h" + +struct plff_device { + struct hid_report *report; + 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 * 0x7f / 0xffff; + right = right * 0x7f / 0xffff; + + *plff->strong = left; + *plff->weak = right; + debug("running with 0x%02x 0x%02x", left, right); + usbhid_submit_report(hid, plff->report, USB_DIR_OUT); + + 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 *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; + } + + 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->maxfield >= 4 && report->field[0]->maxusage == 1 && + report->field[0]->usage[0].hid == (HID_UP_LED | 0x43)) { + report->field[0]->value[0] = 0x00; + report->field[1]->value[0] = 0x00; + strong = &report->field[2]->value[0]; + weak = &report->field[3]->value[0]; + 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; + + *strong = 0x00; + *weak = 0x00; + usbhid_submit_report(hid, plff->report, USB_DIR_OUT); + } + + 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), }, + { } +}; +MODULE_DEVICE_TABLE(hid, pl_devices); + +static struct hid_driver pl_driver = { + .name = "pantherlord", + .id_table = pl_devices, + .probe = pl_probe, +}; + +static int __init pl_init(void) +{ + return hid_register_driver(&pl_driver); +} + +static void __exit pl_exit(void) +{ + hid_unregister_driver(&pl_driver); +} + +module_init(pl_init); +module_exit(pl_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c new file mode 100644 index 00000000..4d3c60d8 --- /dev/null +++ b/drivers/hid/hid-primax.c @@ -0,0 +1,117 @@ +/* + * 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 int px_probe(struct hid_device *hid, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hid); + if (ret) { + hid_err(hid, "parse failed\n"); + goto fail; + } + + ret = hid_hw_start(hid, HID_CONNECT_DEFAULT); + if (ret) + hid_err(hid, "hw start failed\n"); + +fail: + return ret; +} + +static void px_remove(struct hid_device *hid) +{ + hid_hw_stop(hid); +} + +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, + .probe = px_probe, + .remove = px_remove, +}; + +static int __init px_init(void) +{ + return hid_register_driver(&px_driver); +} + +static void __exit px_exit(void) +{ + hid_unregister_driver(&px_driver); +} + +module_init(px_init); +module_exit(px_exit); +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 00000000..b71b77ab --- /dev/null +++ b/drivers/hid/hid-prodikeys.c @@ -0,0 +1,911 @@ +/* + * 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 "usbhid/usbhid.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 = (struct pk_device *)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 = (struct pk_device *)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 = (struct pk_device *)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 = (struct pk_device *)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 = (struct pk_device *)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 = (struct pk_device *)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; + + usbhid_submit_report(hdev, report, USB_DIR_OUT); +} + +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_create(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); + + snd_card_set_dev(card, &pm->pk->hdev->dev); + + /* 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 = (struct pk_device *)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 = (struct pk_device *)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 = (struct pk_device *)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, +}; + +static int pk_init(void) +{ + int ret; + + ret = hid_register_driver(&pk_driver); + if (ret) + pr_err("can't register prodikeys driver\n"); + + return ret; +} + +static void pk_exit(void) +{ + hid_unregister_driver(&pk_driver); +} + +module_init(pk_init); +module_exit(pk_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c new file mode 100644 index 00000000..093bfad0 --- /dev/null +++ b/drivers/hid/hid-roccat-arvo.c @@ -0,0 +1,453 @@ +/* + * 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_common_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 = strict_strtoul(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_common_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 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_common_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 = strict_strtoul(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_common_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; +} + +/* 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_common_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 = strict_strtoul(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_common_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 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_common_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_common_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 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 struct device_attribute arvo_attributes[] = { + __ATTR(mode_key, 0660, + arvo_sysfs_show_mode_key, arvo_sysfs_set_mode_key), + __ATTR(key_mask, 0660, + arvo_sysfs_show_key_mask, arvo_sysfs_set_key_mask), + __ATTR(actual_profile, 0660, + arvo_sysfs_show_actual_profile, + arvo_sysfs_set_actual_profile), + __ATTR_NULL +}; + +static struct bin_attribute arvo_bin_attributes[] = { + { + .attr = { .name = "button", .mode = 0220 }, + .size = sizeof(struct arvo_button), + .write = arvo_sysfs_write_button + }, + { + .attr = { .name = "info", .mode = 0440 }, + .size = sizeof(struct arvo_info), + .read = arvo_sysfs_read_info + }, + __ATTR_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_attrs = arvo_attributes; + arvo_class->dev_bin_attrs = arvo_bin_attributes; + + 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 00000000..ce8415e4 --- /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 00000000..a6d93992 --- /dev/null +++ b/drivers/hid/hid-roccat-common.c @@ -0,0 +1,69 @@ +/* + * 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_common_feature_report(uint8_t report_id) +{ + return 0x300 | report_id; +} + +int roccat_common_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_common_feature_report(report_id), + 0, buf, size, USB_CTRL_SET_TIMEOUT); + + memcpy(data, buf, size); + kfree(buf); + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} +EXPORT_SYMBOL_GPL(roccat_common_receive); + +int roccat_common_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_common_feature_report(report_id), + 0, buf, size, USB_CTRL_SET_TIMEOUT); + + kfree(buf); + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} +EXPORT_SYMBOL_GPL(roccat_common_send); + +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 00000000..9a5bc61f --- /dev/null +++ b/drivers/hid/hid-roccat-common.h @@ -0,0 +1,23 @@ +#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> + +int roccat_common_receive(struct usb_device *usb_dev, uint report_id, + void *data, uint size); +int roccat_common_send(struct usb_device *usb_dev, uint report_id, + void const *data, uint size); + +#endif diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c new file mode 100644 index 00000000..0e4a0ab4 --- /dev/null +++ b/drivers/hid/hid-roccat-isku.c @@ -0,0 +1,487 @@ +/* + * 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_common_receive(usb_dev, command, buf, size); +} + +static int isku_receive_control_status(struct usb_device *usb_dev) +{ + int retval; + struct isku_control control; + + do { + msleep(50); + retval = isku_receive(usb_dev, ISKU_COMMAND_CONTROL, + &control, sizeof(struct isku_control)); + + if (retval) + return retval; + + switch (control.value) { + case ISKU_CONTROL_VALUE_STATUS_OK: + return 0; + case ISKU_CONTROL_VALUE_STATUS_WAIT: + continue; + case ISKU_CONTROL_VALUE_STATUS_INVALID: + /* seems to be critical - replug necessary */ + case ISKU_CONTROL_VALUE_STATUS_OVERLOAD: + return -EINVAL; + default: + hid_err(usb_dev, "isku_receive_control_status: " + "unknown response value 0x%x\n", + control.value); + return -EINVAL; + } + + } while (1); +} + +static int isku_send(struct usb_device *usb_dev, uint command, + void const *buf, uint size) +{ + int retval; + + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; + + return isku_receive_control_status(usb_dev); +} + +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 isku_send(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 = strict_strtoul(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 struct device_attribute isku_attributes[] = { + __ATTR(actual_profile, 0660, + isku_sysfs_show_actual_profile, + isku_sysfs_set_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, real_size); + mutex_unlock(&isku->isku_lock); + + return retval ? retval : real_size; +} + +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 = isku_send(usb_dev, command, (void *)buf, real_size); + mutex_unlock(&isku->isku_lock); + + return retval ? retval : real_size; +} + +#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, \ + sizeof(struct isku_ ## 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, \ + sizeof(struct isku_ ## 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) \ +{ \ + .attr = { .name = #thingy, .mode = 0660 }, \ + .size = sizeof(struct isku_ ## thingy), \ + .read = isku_sysfs_read_ ## thingy, \ + .write = isku_sysfs_write_ ## thingy \ +} + +#define ISKU_BIN_ATTR_R(thingy) \ +{ \ + .attr = { .name = #thingy, .mode = 0440 }, \ + .size = sizeof(struct isku_ ## thingy), \ + .read = isku_sysfs_read_ ## thingy, \ +} + +#define ISKU_BIN_ATTR_W(thingy) \ +{ \ + .attr = { .name = #thingy, .mode = 0220 }, \ + .size = sizeof(struct isku_ ## thingy), \ + .write = isku_sysfs_write_ ## thingy \ +} + +ISKU_SYSFS_RW(macro, MACRO) +ISKU_SYSFS_RW(keys_function, KEYS_FUNCTION) +ISKU_SYSFS_RW(keys_easyzone, KEYS_EASYZONE) +ISKU_SYSFS_RW(keys_media, KEYS_MEDIA) +ISKU_SYSFS_RW(keys_thumbster, KEYS_THUMBSTER) +ISKU_SYSFS_RW(keys_macro, KEYS_MACRO) +ISKU_SYSFS_RW(keys_capslock, KEYS_CAPSLOCK) +ISKU_SYSFS_RW(light, LIGHT) +ISKU_SYSFS_RW(key_mask, KEY_MASK) +ISKU_SYSFS_RW(last_set, LAST_SET) +ISKU_SYSFS_W(talk, TALK) +ISKU_SYSFS_R(info, INFO) +ISKU_SYSFS_W(control, CONTROL) + +static struct bin_attribute isku_bin_attributes[] = { + ISKU_BIN_ATTR_RW(macro), + ISKU_BIN_ATTR_RW(keys_function), + ISKU_BIN_ATTR_RW(keys_easyzone), + ISKU_BIN_ATTR_RW(keys_media), + ISKU_BIN_ATTR_RW(keys_thumbster), + ISKU_BIN_ATTR_RW(keys_macro), + ISKU_BIN_ATTR_RW(keys_capslock), + ISKU_BIN_ATTR_RW(light), + ISKU_BIN_ATTR_RW(key_mask), + ISKU_BIN_ATTR_RW(last_set), + ISKU_BIN_ATTR_W(talk), + ISKU_BIN_ATTR_R(info), + ISKU_BIN_ATTR_W(control), + __ATTR_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) }, + { } +}; + +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_attrs = isku_attributes; + isku_class->dev_bin_attrs = isku_bin_attributes; + + 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 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 00000000..075f6efa --- /dev/null +++ b/drivers/hid/hid-roccat-isku.h @@ -0,0 +1,147 @@ +#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_PROFILE_NUM = 5, + ISKU_USB_INTERFACE_PROTOCOL = 0, +}; + +struct isku_control { + uint8_t command; /* ISKU_COMMAND_CONTROL */ + uint8_t value; + uint8_t request; +} __packed; + +enum isku_control_values { + ISKU_CONTROL_VALUE_STATUS_OVERLOAD = 0, + ISKU_CONTROL_VALUE_STATUS_OK = 1, + ISKU_CONTROL_VALUE_STATUS_INVALID = 2, + ISKU_CONTROL_VALUE_STATUS_WAIT = 3, +}; + +struct isku_actual_profile { + uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */ + uint8_t size; /* always 3 */ + uint8_t actual_profile; +} __packed; + +struct isku_key_mask { + uint8_t command; /* ISKU_COMMAND_KEY_MASK */ + uint8_t size; /* 6 */ + uint8_t profile_number; /* 0-4 */ + uint8_t mask; + uint16_t checksum; +} __packed; + +struct isku_keys_function { + uint8_t data[0x29]; +} __packed; + +struct isku_keys_easyzone { + uint8_t data[0x41]; +} __packed; + +struct isku_keys_media { + uint8_t data[0x1d]; +} __packed; + +struct isku_keys_thumbster { + uint8_t data[0x17]; +} __packed; + +struct isku_keys_macro { + uint8_t data[0x23]; +} __packed; + +struct isku_keys_capslock { + uint8_t data[0x6]; +} __packed; + +struct isku_macro { + uint8_t data[0x823]; +} __packed; + +struct isku_light { + uint8_t data[0xa]; +} __packed; + +struct isku_info { + uint8_t data[2]; + uint8_t firmware_version; + uint8_t unknown[3]; +} __packed; + +struct isku_talk { + uint8_t data[0x10]; +} __packed; + +struct isku_last_set { + uint8_t data[0x14]; +} __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_KEYS_CAPSLOCK = 0x13, + ISKU_COMMAND_LAST_SET = 0x14, + ISKU_COMMAND_15 = 0x15, + ISKU_COMMAND_TALK = 0x16, + 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 00000000..40090d60 --- /dev/null +++ b/drivers/hid/hid-roccat-kone.c @@ -0,0 +1,913 @@ +/* + * 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 */ + hid_err(usb_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 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); +} + +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 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); +} + +/* 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 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 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 = strict_strtoul(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) { + hid_err(usb_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: + hid_err(usb_dev, "couldn't read settings\n"); +exit_unlock: + mutex_unlock(&kone->kone_lock); + return retval; +} + +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 = strict_strtoul(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 struct device_attribute kone_attributes[] = { + /* + * Read actual dpi settings. + * Returns raw value for further processing. Refer to enum + * kone_polling_rates to get real value. + */ + __ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL), + __ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL), + + /* + * 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. + */ + __ATTR(weight, 0440, kone_sysfs_show_weight, NULL), + + /* + * 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. + */ + __ATTR(firmware_version, 0440, + kone_sysfs_show_firmware_version, NULL), + + /* + * 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 + */ + __ATTR(tcu, 0660, kone_sysfs_show_tcu, kone_sysfs_set_tcu), + + /* Prints and takes the number of the profile the mouse starts with */ + __ATTR(startup_profile, 0660, + kone_sysfs_show_startup_profile, + kone_sysfs_set_startup_profile), + __ATTR_NULL +}; + +static struct bin_attribute kone_bin_attributes[] = { + { + .attr = { .name = "settings", .mode = 0660 }, + .size = sizeof(struct kone_settings), + .read = kone_sysfs_read_settings, + .write = kone_sysfs_write_settings + }, + { + .attr = { .name = "profile1", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profilex, + .write = kone_sysfs_write_profilex, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profilex, + .write = kone_sysfs_write_profilex, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profilex, + .write = kone_sysfs_write_profilex, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profilex, + .write = kone_sysfs_write_profilex, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5", .mode = 0660 }, + .size = sizeof(struct kone_profile), + .read = kone_sysfs_read_profilex, + .write = kone_sysfs_write_profilex, + .private = &profile_numbers[4] + }, + __ATTR_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) { + hid_err(hdev, "can't alloc device descriptor\n"); + 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: + if (event->value == kone_keystroke_action_press) { + roccat_report.event = kone_mouse_event_call_overlong_macro; + 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_attrs = kone_attributes; + kone_class->dev_bin_attrs = kone_bin_attributes; + + 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 00000000..64abb5b8 --- /dev/null +++ b/drivers/hid/hid-roccat-kone.h @@ -0,0 +1,226 @@ +#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, + /* 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 00000000..59e47770 --- /dev/null +++ b/drivers/hid/hid-roccat-koneplus.c @@ -0,0 +1,811 @@ +/* + * 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. + */ + +#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 koneplus_control control; + + if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || + request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && + value > 4) + return -EINVAL; + + control.command = KONEPLUS_COMMAND_CONTROL; + control.value = value; + control.request = request; + + return roccat_common_send(usb_dev, KONEPLUS_COMMAND_CONTROL, + &control, sizeof(struct koneplus_control)); +} + +static int koneplus_receive_control_status(struct usb_device *usb_dev) +{ + int retval; + struct koneplus_control control; + + do { + retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_CONTROL, + &control, sizeof(struct koneplus_control)); + + /* check if we get a completely wrong answer */ + if (retval) + return retval; + + if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OK) + return 0; + + /* indicates that hardware needs some more time to complete action */ + if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_WAIT) { + msleep(500); /* windows driver uses 1000 */ + continue; + } + + /* seems to be critical - replug necessary */ + if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) + return -EINVAL; + + hid_err(usb_dev, "koneplus_receive_control_status: " + "unknown response value 0x%x\n", control.value); + return -EINVAL; + } while (1); +} + +static int koneplus_send(struct usb_device *usb_dev, uint command, + void const *buf, uint size) +{ + int retval; + + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; + + return koneplus_receive_control_status(usb_dev); +} + +static int koneplus_select_profile(struct usb_device *usb_dev, uint number, + enum koneplus_control_requests request) +{ + int retval; + + retval = koneplus_send_control(usb_dev, number, request); + if (retval) + return retval; + + /* allow time to settle things - windows driver uses 500 */ + msleep(100); + + retval = koneplus_receive_control_status(usb_dev); + if (retval) + return retval; + + return 0; +} + +static int koneplus_get_info(struct usb_device *usb_dev, + struct koneplus_info *buf) +{ + return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_INFO, + buf, sizeof(struct koneplus_info)); +} + +static int koneplus_get_profile_settings(struct usb_device *usb_dev, + struct koneplus_profile_settings *buf, uint number) +{ + int retval; + + retval = koneplus_select_profile(usb_dev, number, + KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); + if (retval) + return retval; + + return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS, + buf, sizeof(struct koneplus_profile_settings)); +} + +static int koneplus_set_profile_settings(struct usb_device *usb_dev, + struct koneplus_profile_settings const *settings) +{ + return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS, + settings, sizeof(struct koneplus_profile_settings)); +} + +static int koneplus_get_profile_buttons(struct usb_device *usb_dev, + struct koneplus_profile_buttons *buf, int number) +{ + int retval; + + retval = koneplus_select_profile(usb_dev, number, + KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); + if (retval) + return retval; + + return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS, + buf, sizeof(struct koneplus_profile_buttons)); +} + +static int koneplus_set_profile_buttons(struct usb_device *usb_dev, + struct koneplus_profile_buttons const *buttons) +{ + return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS, + buttons, sizeof(struct koneplus_profile_buttons)); +} + +/* 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_common_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE, + &buf, sizeof(struct koneplus_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 = sizeof(struct koneplus_actual_profile); + buf.actual_profile = new_profile; + + return koneplus_send(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE, + &buf, sizeof(struct koneplus_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_common_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 = koneplus_send(usb_dev, command, buf, real_size); + mutex_unlock(&koneplus->koneplus_lock); + + if (retval) + return retval; + + return real_size; +} + +static ssize_t koneplus_sysfs_write_talk(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, + sizeof(struct koneplus_talk), KONEPLUS_COMMAND_TALK); +} + +static ssize_t koneplus_sysfs_write_macro(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, + sizeof(struct koneplus_macro), KONEPLUS_COMMAND_MACRO); +} + +static ssize_t koneplus_sysfs_read_sensor(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, + sizeof(struct koneplus_sensor), KONEPLUS_COMMAND_SENSOR); +} + +static ssize_t koneplus_sysfs_write_sensor(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, + sizeof(struct koneplus_sensor), KONEPLUS_COMMAND_SENSOR); +} + +static ssize_t koneplus_sysfs_write_tcu(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, + sizeof(struct koneplus_tcu), KONEPLUS_COMMAND_TCU); +} + +static ssize_t koneplus_sysfs_read_tcu_image(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, + sizeof(struct koneplus_tcu_image), KONEPLUS_COMMAND_TCU); +} + +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 koneplus_device *koneplus = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct koneplus_profile_settings)) + return 0; + + if (off + count > sizeof(struct koneplus_profile_settings)) + count = sizeof(struct koneplus_profile_settings) - off; + + mutex_lock(&koneplus->koneplus_lock); + memcpy(buf, ((char const *)&koneplus->profile_settings[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&koneplus->koneplus_lock); + + return count; +} + +static ssize_t koneplus_sysfs_write_profile_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 koneplus_device *koneplus = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + int profile_number; + struct koneplus_profile_settings *profile_settings; + + if (off != 0 || count != sizeof(struct koneplus_profile_settings)) + return -EINVAL; + + profile_number = ((struct koneplus_profile_settings const *)buf)->number; + profile_settings = &koneplus->profile_settings[profile_number]; + + mutex_lock(&koneplus->koneplus_lock); + difference = memcmp(buf, profile_settings, + sizeof(struct koneplus_profile_settings)); + if (difference) { + retval = koneplus_set_profile_settings(usb_dev, + (struct koneplus_profile_settings const *)buf); + if (!retval) + memcpy(profile_settings, buf, + sizeof(struct koneplus_profile_settings)); + } + mutex_unlock(&koneplus->koneplus_lock); + + if (retval) + return retval; + + return sizeof(struct koneplus_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 koneplus_device *koneplus = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct koneplus_profile_buttons)) + return 0; + + if (off + count > sizeof(struct koneplus_profile_buttons)) + count = sizeof(struct koneplus_profile_buttons) - off; + + mutex_lock(&koneplus->koneplus_lock); + memcpy(buf, ((char const *)&koneplus->profile_buttons[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&koneplus->koneplus_lock); + + return count; +} + +static ssize_t koneplus_sysfs_write_profile_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 koneplus_device *koneplus = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + uint profile_number; + struct koneplus_profile_buttons *profile_buttons; + + if (off != 0 || count != sizeof(struct koneplus_profile_buttons)) + return -EINVAL; + + profile_number = ((struct koneplus_profile_buttons const *)buf)->number; + profile_buttons = &koneplus->profile_buttons[profile_number]; + + mutex_lock(&koneplus->koneplus_lock); + difference = memcmp(buf, profile_buttons, + sizeof(struct koneplus_profile_buttons)); + if (difference) { + retval = koneplus_set_profile_buttons(usb_dev, + (struct koneplus_profile_buttons const *)buf); + if (!retval) + memcpy(profile_buttons, buf, + sizeof(struct koneplus_profile_buttons)); + } + mutex_unlock(&koneplus->koneplus_lock); + + if (retval) + return retval; + + return sizeof(struct koneplus_profile_buttons); +} + +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 = strict_strtoul(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 ssize_t koneplus_sysfs_show_firmware_version(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->info.firmware_version); +} + +static struct device_attribute koneplus_attributes[] = { + __ATTR(actual_profile, 0660, + koneplus_sysfs_show_actual_profile, + koneplus_sysfs_set_actual_profile), + __ATTR(startup_profile, 0660, + koneplus_sysfs_show_actual_profile, + koneplus_sysfs_set_actual_profile), + __ATTR(firmware_version, 0440, + koneplus_sysfs_show_firmware_version, NULL), + __ATTR_NULL +}; + +static struct bin_attribute koneplus_bin_attributes[] = { + { + .attr = { .name = "sensor", .mode = 0660 }, + .size = sizeof(struct koneplus_sensor), + .read = koneplus_sysfs_read_sensor, + .write = koneplus_sysfs_write_sensor + }, + { + .attr = { .name = "tcu", .mode = 0220 }, + .size = sizeof(struct koneplus_tcu), + .write = koneplus_sysfs_write_tcu + }, + { + .attr = { .name = "tcu_image", .mode = 0440 }, + .size = sizeof(struct koneplus_tcu_image), + .read = koneplus_sysfs_read_tcu_image + }, + { + .attr = { .name = "profile_settings", .mode = 0220 }, + .size = sizeof(struct koneplus_profile_settings), + .write = koneplus_sysfs_write_profile_settings + }, + { + .attr = { .name = "profile1_settings", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_settings), + .read = koneplus_sysfs_read_profilex_settings, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_settings", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_settings), + .read = koneplus_sysfs_read_profilex_settings, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_settings", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_settings), + .read = koneplus_sysfs_read_profilex_settings, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_settings", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_settings), + .read = koneplus_sysfs_read_profilex_settings, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_settings", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_settings), + .read = koneplus_sysfs_read_profilex_settings, + .private = &profile_numbers[4] + }, + { + .attr = { .name = "profile_buttons", .mode = 0220 }, + .size = sizeof(struct koneplus_profile_buttons), + .write = koneplus_sysfs_write_profile_buttons + }, + { + .attr = { .name = "profile1_buttons", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_buttons), + .read = koneplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_buttons", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_buttons), + .read = koneplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_buttons", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_buttons), + .read = koneplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_buttons", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_buttons), + .read = koneplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_buttons", .mode = 0440 }, + .size = sizeof(struct koneplus_profile_buttons), + .read = koneplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[4] + }, + { + .attr = { .name = "macro", .mode = 0220 }, + .size = sizeof(struct koneplus_macro), + .write = koneplus_sysfs_write_macro + }, + { + .attr = { .name = "talk", .mode = 0220 }, + .size = sizeof(struct koneplus_talk), + .write = koneplus_sysfs_write_talk + }, + __ATTR_NULL +}; + +static int koneplus_init_koneplus_device_struct(struct usb_device *usb_dev, + struct koneplus_device *koneplus) +{ + int retval, i; + static uint wait = 200; + + mutex_init(&koneplus->koneplus_lock); + + retval = koneplus_get_info(usb_dev, &koneplus->info); + if (retval) + return retval; + + for (i = 0; i < 5; ++i) { + msleep(wait); + retval = koneplus_get_profile_settings(usb_dev, + &koneplus->profile_settings[i], i); + if (retval) + return retval; + + msleep(wait); + retval = koneplus_get_profile_buttons(usb_dev, + &koneplus->profile_buttons[i], i); + if (retval) + return retval; + } + + msleep(wait); + 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) }, + { } +}; + +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_attrs = koneplus_attributes; + koneplus_class->dev_bin_attrs = koneplus_bin_attributes; + + 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[+] 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 00000000..c03332a4 --- /dev/null +++ b/drivers/hid/hid-roccat-koneplus.h @@ -0,0 +1,218 @@ +#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> + +struct koneplus_talk { + uint8_t command; /* KONEPLUS_COMMAND_TALK */ + uint8_t size; /* always 0x10 */ + uint8_t data[14]; +} __packed; + +/* + * case 1: writes request 80 and reads value 1 + * + */ +struct koneplus_control { + uint8_t command; /* KONEPLUS_COMMAND_CONTROL */ + /* + * value is profile number in range 0-4 for requesting settings and buttons + * 1 if status ok for requesting status + */ + uint8_t value; + uint8_t request; +} __attribute__ ((__packed__)); + +enum koneplus_control_requests { + KONEPLUS_CONTROL_REQUEST_STATUS = 0x00, + KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x80, + KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x90, +}; + +enum koneplus_control_values { + KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, + KONEPLUS_CONTROL_REQUEST_STATUS_OK = 1, + KONEPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, +}; + +struct koneplus_actual_profile { + uint8_t command; /* KONEPLUS_COMMAND_ACTUAL_PROFILE */ + uint8_t size; /* always 3 */ + uint8_t actual_profile; /* Range 0-4! */ +} __attribute__ ((__packed__)); + +struct koneplus_profile_settings { + uint8_t command; /* KONEPLUS_COMMAND_PROFILE_SETTINGS */ + uint8_t size; /* always 43 */ + uint8_t number; /* range 0-4 */ + uint8_t advanced_sensitivity; + uint8_t sensitivity_x; + uint8_t sensitivity_y; + uint8_t cpi_levels_enabled; + uint8_t cpi_levels_x[5]; + uint8_t cpi_startup_level; /* range 0-4 */ + uint8_t cpi_levels_y[5]; /* range 1-60 means 100-6000 cpi */ + uint8_t unknown1; + uint8_t polling_rate; + uint8_t lights_enabled; + uint8_t light_effect_mode; + uint8_t color_flow_effect; + uint8_t light_effect_type; + uint8_t light_effect_speed; + uint8_t lights[16]; + uint16_t checksum; +} __attribute__ ((__packed__)); + +struct koneplus_profile_buttons { + uint8_t command; /* KONEPLUS_COMMAND_PROFILE_BUTTONS */ + uint8_t size; /* always 77 */ + uint8_t number; /* range 0-4 */ + uint8_t data[72]; + uint16_t checksum; +} __attribute__ ((__packed__)); + +struct koneplus_macro { + uint8_t command; /* KONEPLUS_COMMAND_MACRO */ + uint16_t size; /* always 0x822 little endian */ + uint8_t profile; /* range 0-4 */ + uint8_t button; /* range 0-23 */ + uint8_t data[2075]; + uint16_t checksum; +} __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__)); + +struct koneplus_e { + uint8_t command; /* KONEPLUS_COMMAND_E */ + uint8_t size; /* always 3 */ + uint8_t unknown; /* TODO 1; 0 before firmware update */ +} __attribute__ ((__packed__)); + +struct koneplus_sensor { + uint8_t command; /* KONEPLUS_COMMAND_SENSOR */ + uint8_t size; /* always 6 */ + uint8_t data[4]; +} __attribute__ ((__packed__)); + +struct koneplus_firmware_write { + uint8_t command; /* KONEPLUS_COMMAND_FIRMWARE_WRITE */ + uint8_t unknown[1025]; +} __attribute__ ((__packed__)); + +struct koneplus_firmware_write_control { + uint8_t command; /* KONEPLUS_COMMAND_FIRMWARE_WRITE_CONTROL */ + /* + * value is 1 on success + * 3 means "not finished yet" + */ + uint8_t value; + uint8_t unknown; /* always 0x75 */ +} __attribute__ ((__packed__)); + +struct koneplus_tcu { + uint16_t usb_command; /* KONEPLUS_USB_COMMAND_TCU */ + uint8_t data[2]; +} __attribute__ ((__packed__)); + +struct koneplus_tcu_image { + uint16_t usb_command; /* KONEPLUS_USB_COMMAND_TCU */ + uint8_t data[1024]; + uint16_t checksum; +} __attribute__ ((__packed__)); + +enum koneplus_commands { + KONEPLUS_COMMAND_CONTROL = 0x4, + KONEPLUS_COMMAND_ACTUAL_PROFILE = 0x5, + KONEPLUS_COMMAND_PROFILE_SETTINGS = 0x6, + KONEPLUS_COMMAND_PROFILE_BUTTONS = 0x7, + KONEPLUS_COMMAND_MACRO = 0x8, + KONEPLUS_COMMAND_INFO = 0x9, + KONEPLUS_COMMAND_TCU = 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; + + struct koneplus_info info; + struct koneplus_profile_settings profile_settings[5]; + struct koneplus_profile_buttons profile_buttons[5]; +}; + +#endif diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c new file mode 100644 index 00000000..112d9341 --- /dev/null +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -0,0 +1,731 @@ +/* + * 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 kovaplus_control control; + + if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || + request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && + value > 4) + return -EINVAL; + + control.command = KOVAPLUS_COMMAND_CONTROL; + control.value = value; + control.request = request; + + retval = roccat_common_send(usb_dev, KOVAPLUS_COMMAND_CONTROL, + &control, sizeof(struct kovaplus_control)); + + return retval; +} + +static int kovaplus_receive_control_status(struct usb_device *usb_dev) +{ + int retval; + struct kovaplus_control control; + + do { + retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_CONTROL, + &control, sizeof(struct kovaplus_control)); + + /* check if we get a completely wrong answer */ + if (retval) + return retval; + + if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OK) + return 0; + + /* indicates that hardware needs some more time to complete action */ + if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT) { + msleep(500); /* windows driver uses 1000 */ + continue; + } + + /* seems to be critical - replug necessary */ + if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) + return -EINVAL; + + hid_err(usb_dev, "roccat_common_receive_control_status: " + "unknown response value 0x%x\n", control.value); + return -EINVAL; + } while (1); +} + +static int kovaplus_send(struct usb_device *usb_dev, uint command, + void const *buf, uint size) +{ + int retval; + + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; + + msleep(100); + + return kovaplus_receive_control_status(usb_dev); +} + +static int kovaplus_select_profile(struct usb_device *usb_dev, uint number, + enum kovaplus_control_requests request) +{ + return kovaplus_send_control(usb_dev, number, request); +} + +static int kovaplus_get_info(struct usb_device *usb_dev, + struct kovaplus_info *buf) +{ + return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_INFO, + buf, sizeof(struct kovaplus_info)); +} + +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_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS, + buf, sizeof(struct kovaplus_profile_settings)); +} + +static int kovaplus_set_profile_settings(struct usb_device *usb_dev, + struct kovaplus_profile_settings const *settings) +{ + return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS, + settings, sizeof(struct kovaplus_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_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS, + buf, sizeof(struct kovaplus_profile_buttons)); +} + +static int kovaplus_set_profile_buttons(struct usb_device *usb_dev, + struct kovaplus_profile_buttons const *buttons) +{ + return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS, + buttons, sizeof(struct kovaplus_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_common_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 kovaplus_send(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE, + &buf, sizeof(struct kovaplus_actual_profile)); +} + +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 kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct kovaplus_profile_settings)) + return 0; + + if (off + count > sizeof(struct kovaplus_profile_settings)) + count = sizeof(struct kovaplus_profile_settings) - off; + + mutex_lock(&kovaplus->kovaplus_lock); + memcpy(buf, ((char const *)&kovaplus->profile_settings[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&kovaplus->kovaplus_lock); + + return count; +} + +static ssize_t kovaplus_sysfs_write_profile_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 kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + int profile_index; + struct kovaplus_profile_settings *profile_settings; + + if (off != 0 || count != sizeof(struct kovaplus_profile_settings)) + return -EINVAL; + + profile_index = ((struct kovaplus_profile_settings const *)buf)->profile_index; + profile_settings = &kovaplus->profile_settings[profile_index]; + + mutex_lock(&kovaplus->kovaplus_lock); + difference = memcmp(buf, profile_settings, + sizeof(struct kovaplus_profile_settings)); + if (difference) { + retval = kovaplus_set_profile_settings(usb_dev, + (struct kovaplus_profile_settings const *)buf); + if (!retval) + memcpy(profile_settings, buf, + sizeof(struct kovaplus_profile_settings)); + } + mutex_unlock(&kovaplus->kovaplus_lock); + + if (retval) + return retval; + + return sizeof(struct kovaplus_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 kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct kovaplus_profile_buttons)) + return 0; + + if (off + count > sizeof(struct kovaplus_profile_buttons)) + count = sizeof(struct kovaplus_profile_buttons) - off; + + mutex_lock(&kovaplus->kovaplus_lock); + memcpy(buf, ((char const *)&kovaplus->profile_buttons[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&kovaplus->kovaplus_lock); + + return count; +} + +static ssize_t kovaplus_sysfs_write_profile_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 kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + uint profile_index; + struct kovaplus_profile_buttons *profile_buttons; + + if (off != 0 || count != sizeof(struct kovaplus_profile_buttons)) + return -EINVAL; + + profile_index = ((struct kovaplus_profile_buttons const *)buf)->profile_index; + profile_buttons = &kovaplus->profile_buttons[profile_index]; + + mutex_lock(&kovaplus->kovaplus_lock); + difference = memcmp(buf, profile_buttons, + sizeof(struct kovaplus_profile_buttons)); + if (difference) { + retval = kovaplus_set_profile_buttons(usb_dev, + (struct kovaplus_profile_buttons const *)buf); + if (!retval) + memcpy(profile_buttons, buf, + sizeof(struct kovaplus_profile_buttons)); + } + mutex_unlock(&kovaplus->kovaplus_lock); + + if (retval) + return retval; + + return sizeof(struct kovaplus_profile_buttons); +} + +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 = strict_strtoul(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 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 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 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 ssize_t kovaplus_sysfs_show_firmware_version(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->info.firmware_version); +} + +static struct device_attribute kovaplus_attributes[] = { + __ATTR(actual_cpi, 0440, + kovaplus_sysfs_show_actual_cpi, NULL), + __ATTR(firmware_version, 0440, + kovaplus_sysfs_show_firmware_version, NULL), + __ATTR(actual_profile, 0660, + kovaplus_sysfs_show_actual_profile, + kovaplus_sysfs_set_actual_profile), + __ATTR(actual_sensitivity_x, 0440, + kovaplus_sysfs_show_actual_sensitivity_x, NULL), + __ATTR(actual_sensitivity_y, 0440, + kovaplus_sysfs_show_actual_sensitivity_y, NULL), + __ATTR_NULL +}; + +static struct bin_attribute kovaplus_bin_attributes[] = { + { + .attr = { .name = "profile_settings", .mode = 0220 }, + .size = sizeof(struct kovaplus_profile_settings), + .write = kovaplus_sysfs_write_profile_settings + }, + { + .attr = { .name = "profile1_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[4] + }, + { + .attr = { .name = "profile_buttons", .mode = 0220 }, + .size = sizeof(struct kovaplus_profile_buttons), + .write = kovaplus_sysfs_write_profile_buttons + }, + { + .attr = { .name = "profile1_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[4] + }, + __ATTR_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); + + retval = kovaplus_get_info(usb_dev, &kovaplus->info); + if (retval) + return retval; + + 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); + case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY: + kovaplus->actual_x_sensitivity = button_report->data1; + kovaplus->actual_y_sensitivity = button_report->data2; + } +} + +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_attrs = kovaplus_attributes; + kovaplus_class->dev_bin_attrs = kovaplus_bin_attributes; + + 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 00000000..fb2aed44 --- /dev/null +++ b/drivers/hid/hid-roccat-kovaplus.h @@ -0,0 +1,148 @@ +#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> + +struct kovaplus_control { + uint8_t command; /* KOVAPLUS_COMMAND_CONTROL */ + uint8_t value; + uint8_t request; +} __packed; + +enum kovaplus_control_requests { + /* read after write; value = 1 */ + KOVAPLUS_CONTROL_REQUEST_STATUS = 0x0, + /* write; value = profile number range 0-4 */ + KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10, + /* write; value = profile number range 0-4 */ + KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20, +}; + +enum kovaplus_control_values { + KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, /* supposed */ + KOVAPLUS_CONTROL_REQUEST_STATUS_OK = 1, + KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, /* supposed */ +}; + +struct kovaplus_actual_profile { + uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */ + uint8_t size; /* always 3 */ + 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; + +/* writes 1 on plugin */ +struct kovaplus_a { + uint8_t command; /* KOVAPLUS_COMMAND_A */ + uint8_t size; /* 3 */ + uint8_t unknown; +} __packed; + +enum kovaplus_commands { + KOVAPLUS_COMMAND_CONTROL = 0x4, + KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5, + 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_info info; + struct kovaplus_profile_settings profile_settings[5]; + struct kovaplus_profile_buttons profile_buttons[5]; +}; + +#endif diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c new file mode 100644 index 00000000..df05c1b1 --- /dev/null +++ b/drivers/hid/hid-roccat-pyra.c @@ -0,0 +1,704 @@ +/* + * 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) +{ + 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 pyra_control control; + + if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS || + request == PYRA_CONTROL_REQUEST_PROFILE_BUTTONS) && + (value < 0 || value > 4)) + return -EINVAL; + + control.command = PYRA_COMMAND_CONTROL; + control.value = value; + control.request = request; + + return roccat_common_send(usb_dev, PYRA_COMMAND_CONTROL, + &control, sizeof(struct pyra_control)); +} + +static int pyra_receive_control_status(struct usb_device *usb_dev) +{ + int retval; + struct pyra_control control; + + do { + msleep(10); + retval = roccat_common_receive(usb_dev, PYRA_COMMAND_CONTROL, + &control, sizeof(struct pyra_control)); + + /* requested too early, try again */ + } while (retval == -EPROTO); + + if (!retval && control.command == PYRA_COMMAND_CONTROL && + control.request == PYRA_CONTROL_REQUEST_STATUS && + control.value == 1) + return 0; + else { + hid_err(usb_dev, "receive control status: unknown response 0x%x 0x%x\n", + control.request, control.value); + return retval ? retval : -EINVAL; + } +} + +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_common_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, + buf, sizeof(struct pyra_profile_settings)); +} + +static int pyra_get_profile_buttons(struct usb_device *usb_dev, + struct pyra_profile_buttons *buf, int number) +{ + int retval; + retval = pyra_send_control(usb_dev, number, + PYRA_CONTROL_REQUEST_PROFILE_BUTTONS); + if (retval) + return retval; + return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, + buf, sizeof(struct pyra_profile_buttons)); +} + +static int pyra_get_settings(struct usb_device *usb_dev, + struct pyra_settings *buf) +{ + return roccat_common_receive(usb_dev, PYRA_COMMAND_SETTINGS, + buf, sizeof(struct pyra_settings)); +} + +static int pyra_get_info(struct usb_device *usb_dev, struct pyra_info *buf) +{ + return roccat_common_receive(usb_dev, PYRA_COMMAND_INFO, + buf, sizeof(struct pyra_info)); +} + +static int pyra_send(struct usb_device *usb_dev, uint command, + void const *buf, uint size) +{ + int retval; + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; + return pyra_receive_control_status(usb_dev); +} + +static int pyra_set_profile_settings(struct usb_device *usb_dev, + struct pyra_profile_settings const *settings) +{ + return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, settings, + sizeof(struct pyra_profile_settings)); +} + +static int pyra_set_profile_buttons(struct usb_device *usb_dev, + struct pyra_profile_buttons const *buttons) +{ + return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, buttons, + sizeof(struct pyra_profile_buttons)); +} + +static int pyra_set_settings(struct usb_device *usb_dev, + struct pyra_settings const *settings) +{ + return pyra_send(usb_dev, PYRA_COMMAND_SETTINGS, settings, + sizeof(struct pyra_settings)); +} + +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 pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct pyra_profile_settings)) + return 0; + + if (off + count > sizeof(struct pyra_profile_settings)) + count = sizeof(struct pyra_profile_settings) - off; + + mutex_lock(&pyra->pyra_lock); + memcpy(buf, ((char const *)&pyra->profile_settings[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&pyra->pyra_lock); + + return count; +} + +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 pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct pyra_profile_buttons)) + return 0; + + if (off + count > sizeof(struct pyra_profile_buttons)) + count = sizeof(struct pyra_profile_buttons) - off; + + mutex_lock(&pyra->pyra_lock); + memcpy(buf, ((char const *)&pyra->profile_buttons[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&pyra->pyra_lock); + + return count; +} + +static ssize_t pyra_sysfs_write_profile_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; + int difference; + int profile_number; + struct pyra_profile_settings *profile_settings; + + if (off != 0 || count != sizeof(struct pyra_profile_settings)) + return -EINVAL; + + profile_number = ((struct pyra_profile_settings const *)buf)->number; + profile_settings = &pyra->profile_settings[profile_number]; + + mutex_lock(&pyra->pyra_lock); + difference = memcmp(buf, profile_settings, + sizeof(struct pyra_profile_settings)); + if (difference) { + retval = pyra_set_profile_settings(usb_dev, + (struct pyra_profile_settings const *)buf); + if (!retval) + memcpy(profile_settings, buf, + sizeof(struct pyra_profile_settings)); + } + mutex_unlock(&pyra->pyra_lock); + + if (retval) + return retval; + + return sizeof(struct pyra_profile_settings); +} + +static ssize_t pyra_sysfs_write_profile_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 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; + int difference; + int profile_number; + struct pyra_profile_buttons *profile_buttons; + + if (off != 0 || count != sizeof(struct pyra_profile_buttons)) + return -EINVAL; + + profile_number = ((struct pyra_profile_buttons const *)buf)->number; + profile_buttons = &pyra->profile_buttons[profile_number]; + + mutex_lock(&pyra->pyra_lock); + difference = memcmp(buf, profile_buttons, + sizeof(struct pyra_profile_buttons)); + if (difference) { + retval = pyra_set_profile_buttons(usb_dev, + (struct pyra_profile_buttons const *)buf); + if (!retval) + memcpy(profile_buttons, buf, + sizeof(struct pyra_profile_buttons)); + } + mutex_unlock(&pyra->pyra_lock); + + if (retval) + return retval; + + return sizeof(struct pyra_profile_buttons); +} + +static ssize_t pyra_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 pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct pyra_settings)) + return 0; + + if (off + count > sizeof(struct pyra_settings)) + count = sizeof(struct pyra_settings) - off; + + mutex_lock(&pyra->pyra_lock); + memcpy(buf, ((char const *)&pyra->settings) + off, count); + mutex_unlock(&pyra->pyra_lock); + + return count; +} + +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; + int difference; + struct pyra_roccat_report roccat_report; + + if (off != 0 || count != sizeof(struct pyra_settings)) + return -EINVAL; + + mutex_lock(&pyra->pyra_lock); + difference = memcmp(buf, &pyra->settings, sizeof(struct pyra_settings)); + if (difference) { + retval = pyra_set_settings(usb_dev, + (struct pyra_settings const *)buf); + if (retval) { + mutex_unlock(&pyra->pyra_lock); + return retval; + } + + memcpy(&pyra->settings, buf, + sizeof(struct pyra_settings)); + + profile_activated(pyra, pyra->settings.startup_profile); + + roccat_report.type = PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2; + roccat_report.value = pyra->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 sizeof(struct pyra_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 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)); + return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_profile); +} + +static ssize_t pyra_sysfs_show_firmware_version(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->firmware_version); +} + +static ssize_t pyra_sysfs_show_startup_profile(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->settings.startup_profile); +} + +static struct device_attribute pyra_attributes[] = { + __ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL), + __ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL), + __ATTR(firmware_version, 0440, + pyra_sysfs_show_firmware_version, NULL), + __ATTR(startup_profile, 0440, + pyra_sysfs_show_startup_profile, NULL), + __ATTR_NULL +}; + +static struct bin_attribute pyra_bin_attributes[] = { + { + .attr = { .name = "profile_settings", .mode = 0220 }, + .size = sizeof(struct pyra_profile_settings), + .write = pyra_sysfs_write_profile_settings + }, + { + .attr = { .name = "profile1_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profilex_settings, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profilex_settings, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profilex_settings, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profilex_settings, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profilex_settings, + .private = &profile_numbers[4] + }, + { + .attr = { .name = "profile_buttons", .mode = 0220 }, + .size = sizeof(struct pyra_profile_buttons), + .write = pyra_sysfs_write_profile_buttons + }, + { + .attr = { .name = "profile1_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profilex_buttons, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profilex_buttons, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profilex_buttons, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profilex_buttons, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profilex_buttons, + .private = &profile_numbers[4] + }, + { + .attr = { .name = "settings", .mode = 0660 }, + .size = sizeof(struct pyra_settings), + .read = pyra_sysfs_read_settings, + .write = pyra_sysfs_write_settings + }, + __ATTR_NULL +}; + +static int pyra_init_pyra_device_struct(struct usb_device *usb_dev, + struct pyra_device *pyra) +{ + struct pyra_info info; + int retval, i; + + mutex_init(&pyra->pyra_lock); + + retval = pyra_get_info(usb_dev, &info); + if (retval) + return retval; + + pyra->firmware_version = info.firmware_version; + + retval = pyra_get_settings(usb_dev, &pyra->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; + + retval = pyra_get_profile_buttons(usb_dev, + &pyra->profile_buttons[i], i); + if (retval) + return retval; + } + + profile_activated(pyra, 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_attrs = pyra_attributes; + pyra_class->dev_bin_attrs = pyra_bin_attributes; + + 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 00000000..0442d7fa --- /dev/null +++ b/drivers/hid/hid-roccat-pyra.h @@ -0,0 +1,172 @@ +#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> + +struct pyra_b { + uint8_t command; /* PYRA_COMMAND_B */ + uint8_t size; /* always 3 */ + uint8_t unknown; /* 1 */ +} __attribute__ ((__packed__)); + +struct pyra_control { + uint8_t command; /* PYRA_COMMAND_CONTROL */ + /* + * value is profile number for request_settings and request_buttons + * 1 if status ok for request_status + */ + uint8_t value; /* Range 0-4 */ + uint8_t request; +} __attribute__ ((__packed__)); + +enum pyra_control_requests { + PYRA_CONTROL_REQUEST_STATUS = 0x00, + PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10, + PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20 +}; + +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_profile_buttons { + uint8_t command; /* PYRA_COMMAND_PROFILE_BUTTONS */ + uint8_t size; /* always 0x13 */ + uint8_t number; /* Range 0-4 */ + uint8_t buttons[14]; + 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 firmware_version; + int roccat_claimed; + int chrdev_minor; + struct mutex pyra_lock; + struct pyra_settings settings; + struct pyra_profile_settings profile_settings[5]; + struct pyra_profile_buttons profile_buttons[5]; +}; + +#endif diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c new file mode 100644 index 00000000..b685b04d --- /dev/null +++ b/drivers/hid/hid-roccat.c @@ -0,0 +1,451 @@ +/* + * 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 + * @len: size of 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. + * + * 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->f_path.dentry->d_inode; + 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"); + return retval; + } + + cdev_init(&roccat_cdev, &roccat_ops); + cdev_add(&roccat_cdev, dev_id, ROCCAT_MAX_DEVICES); + + return 0; +} + +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 00000000..45aea77b --- /dev/null +++ b/drivers/hid/hid-saitek.c @@ -0,0 +1,70 @@ +/* + * HID driver for Saitek devices, currently only the 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 + */ + +/* + * 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" + +static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (*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 const struct hid_device_id saitek_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000)}, + { } +}; + +MODULE_DEVICE_TABLE(hid, saitek_devices); + +static struct hid_driver saitek_driver = { + .name = "saitek", + .id_table = saitek_devices, + .report_fixup = saitek_report_fixup +}; + +static int __init saitek_init(void) +{ + return hid_register_driver(&saitek_driver); +} + +static void __exit saitek_exit(void) +{ + hid_unregister_driver(&saitek_driver); +} + +module_init(saitek_init); +module_exit(saitek_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-samsung.c b/drivers/hid/hid-samsung.c new file mode 100644 index 00000000..3c1fd8af --- /dev/null +++ b/drivers/hid/hid-samsung.c @@ -0,0 +1,213 @@ +/* + * 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) 2007 Paul Walmsley + * 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, +}; + +static int __init samsung_init(void) +{ + return hid_register_driver(&samsung_driver); +} + +static void __exit samsung_exit(void) +{ + hid_unregister_driver(&samsung_driver); +} + +module_init(samsung_init); +module_exit(samsung_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c new file mode 100644 index 00000000..42257acf --- /dev/null +++ b/drivers/hid/hid-sjoy.c @@ -0,0 +1,195 @@ +/* + * 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/usb.h> +#include <linux/hid.h> +#include <linux/module.h> +#include "hid-ids.h" + +#ifdef CONFIG_SMARTJOYPLUS_FF +#include "usbhid/usbhid.h" + +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); + usbhid_submit_report(hid, sjoyff->report, USB_DIR_OUT); + + 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; + usbhid_submit_report(hid, sjoyff->report, USB_DIR_OUT); + } + + 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, +}; + +static int __init sjoy_init(void) +{ + return hid_register_driver(&sjoy_driver); +} + +static void __exit sjoy_exit(void) +{ + hid_unregister_driver(&sjoy_driver); +} + +module_init(sjoy_init); +module_exit(sjoy_exit); +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 00000000..5cd25bd9 --- /dev/null +++ b/drivers/hid/hid-sony.c @@ -0,0 +1,246 @@ +/* + * HID driver for some sony "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) 2007 Paul Walmsley + * 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/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include "hid-ids.h" + +#define VAIO_RDESC_CONSTANT (1 << 0) +#define SIXAXIS_CONTROLLER_USB (1 << 1) +#define SIXAXIS_CONTROLLER_BT (1 << 2) + +static const u8 sixaxis_rdesc_fixup[] = { + 0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C, + 0x81, 0x01, 0x75, 0x10, 0x95, 0x04, 0x26, 0xFF, + 0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02 +}; + +struct sony_sc { + unsigned long quirks; +}; + +/* Sony Vaio VGX has wrongly mouse pointer declared as constant */ +static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct sony_sc *sc = hid_get_drvdata(hdev); + + if ((sc->quirks & VAIO_RDESC_CONSTANT) && + *rsize >= 56 && rdesc[54] == 0x81 && rdesc[55] == 0x07) { + hid_info(hdev, "Fixing up Sony Vaio VGX report descriptor\n"); + rdesc[55] = 0x06; + } + + /* The HID descriptor exposed over BT has a trailing zero byte */ + if ((((sc->quirks & SIXAXIS_CONTROLLER_USB) && *rsize == 148) || + ((sc->quirks & SIXAXIS_CONTROLLER_BT) && *rsize == 149)) && + rdesc[83] == 0x75) { + hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n"); + memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup, + sizeof(sixaxis_rdesc_fixup)); + } + return rdesc; +} + +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_USB | SIXAXIS_CONTROLLER_BT)) && + 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]); + } + + return 0; +} + +/* + * The Sony Sixaxis does not handle HID Output Reports on the Interrupt EP + * like it should according to usbhid/hid-core.c::usbhid_output_raw_report() + * so we need to override that forcing HID Output Reports 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! + */ +static int sixaxis_usb_output_raw_report(struct hid_device *hid, __u8 *buf, + size_t count, unsigned char report_type) +{ + struct usb_interface *intf = to_usb_interface(hid->dev.parent); + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface = intf->cur_altsetting; + int report_id = buf[0]; + int ret; + + if (report_type == HID_OUTPUT_REPORT) { + /* Don't send the Report ID */ + buf++; + count--; + } + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ((report_type + 1) << 8) | report_id, + interface->desc.bInterfaceNumber, buf, count, + USB_CTRL_SET_TIMEOUT); + + /* Count also the Report ID, in case of an Output report. */ + if (ret > 0 && report_type == HID_OUTPUT_REPORT) + ret++; + + return ret; +} + +/* + * 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) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *dev = interface_to_usbdev(intf); + __u16 ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + int ret; + char *buf = kmalloc(18, GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (3 << 8) | 0xf2, ifnum, buf, 17, + USB_CTRL_GET_TIMEOUT); + if (ret < 0) + hid_err(hdev, "can't set operational mode\n"); + + kfree(buf); + + return ret; +} + +static int sixaxis_set_operational_bt(struct hid_device *hdev) +{ + unsigned char buf[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 }; + return hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); +} + +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; + + sc = kzalloc(sizeof(*sc), GFP_KERNEL); + if (sc == NULL) { + hid_err(hdev, "can't alloc sony descriptor\n"); + return -ENOMEM; + } + + sc->quirks = quirks; + hid_set_drvdata(hdev, sc); + + 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_HIDDEV_FORCE); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto err_free; + } + + if (sc->quirks & SIXAXIS_CONTROLLER_USB) { + hdev->hid_output_raw_report = sixaxis_usb_output_raw_report; + ret = sixaxis_set_operational_usb(hdev); + } + else if (sc->quirks & SIXAXIS_CONTROLLER_BT) + ret = sixaxis_set_operational_bt(hdev); + else + ret = 0; + + if (ret < 0) + goto err_stop; + + return 0; +err_stop: + hid_hw_stop(hdev); +err_free: + kfree(sc); + return ret; +} + +static void sony_remove(struct hid_device *hdev) +{ + hid_hw_stop(hdev); + kfree(hid_get_drvdata(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 }, + { } +}; +MODULE_DEVICE_TABLE(hid, sony_devices); + +static struct hid_driver sony_driver = { + .name = "sony", + .id_table = sony_devices, + .probe = sony_probe, + .remove = sony_remove, + .report_fixup = sony_report_fixup, + .raw_event = sony_raw_event +}; + +static int __init sony_init(void) +{ + return hid_register_driver(&sony_driver); +} + +static void __exit sony_exit(void) +{ + hid_unregister_driver(&sony_driver); +} + +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 00000000..60201374 --- /dev/null +++ b/drivers/hid/hid-speedlink.c @@ -0,0 +1,89 @@ +/* + * 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 Stefan Kriwanek <mail@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 <linux/usb.h> + +#include "hid-ids.h" +#include "usbhid/usbhid.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. */ + /* Fix "jumpy" cursor (invalid events sent by device). */ + if (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, +}; + +static int __init speedlink_init(void) +{ + return hid_register_driver(&speedlink_driver); +} + +static void __exit speedlink_exit(void) +{ + hid_unregister_driver(&speedlink_driver); +} + +module_init(speedlink_init); +module_exit(speedlink_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-sunplus.c b/drivers/hid/hid-sunplus.c new file mode 100644 index 00000000..d484a004 --- /dev/null +++ b/drivers/hid/hid-sunplus.c @@ -0,0 +1,80 @@ +/* + * 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) 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/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 >= 107 && 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, +}; + +static int __init sp_init(void) +{ + return hid_register_driver(&sp_driver); +} + +static void __exit sp_exit(void) +{ + hid_unregister_driver(&sp_driver); +} + +module_init(sp_init); +module_exit(sp_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-tivo.c b/drivers/hid/hid-tivo.c new file mode 100644 index 00000000..9f85f827 --- /dev/null +++ b/drivers/hid/hid-tivo.c @@ -0,0 +1,90 @@ +/* + * 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) }, + { } +}; +MODULE_DEVICE_TABLE(hid, tivo_devices); + +static struct hid_driver tivo_driver = { + .name = "tivo_slide", + .id_table = tivo_devices, + .input_mapping = tivo_input_mapping, +}; + +static int __init tivo_init(void) +{ + return hid_register_driver(&tivo_driver); +} + +static void __exit tivo_exit(void) +{ + hid_unregister_driver(&tivo_driver); +} + +module_init(tivo_init); +module_exit(tivo_exit); +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 00000000..83a933b9 --- /dev/null +++ b/drivers/hid/hid-tmff.c @@ -0,0 +1,277 @@ +/* + * 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/usb.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 +#include "usbhid/usbhid.h" + +/* 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; + usbhid_submit_report(hid, tmff->report, USB_DIR_OUT); + 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; + usbhid_submit_report(hid, tmff->report, USB_DIR_OUT); + 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, +}; + +static int __init tm_init(void) +{ + return hid_register_driver(&tm_driver); +} + +static void __exit tm_exit(void) +{ + hid_unregister_driver(&tm_driver); +} + +module_init(tm_init); +module_exit(tm_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-topseed.c b/drivers/hid/hid-topseed.c new file mode 100644 index 00000000..613ff7b1 --- /dev/null +++ b/drivers/hid/hid-topseed.c @@ -0,0 +1,92 @@ +/* + * 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, +}; + +static int __init ts_init(void) +{ + return hid_register_driver(&ts_driver); +} + +static void __exit ts_exit(void) +{ + hid_unregister_driver(&ts_driver); +} + +module_init(ts_init); +module_exit(ts_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-twinhan.c b/drivers/hid/hid-twinhan.c new file mode 100644 index 00000000..f23456b1 --- /dev/null +++ b/drivers/hid/hid-twinhan.c @@ -0,0 +1,147 @@ +/* + * 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, +}; + +static int __init twinhan_init(void) +{ + return hid_register_driver(&twinhan_driver); +} + +static void __exit twinhan_exit(void) +{ + hid_unregister_driver(&twinhan_driver); +} + +module_init(twinhan_init); +module_exit(twinhan_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c new file mode 100644 index 00000000..1f112891 --- /dev/null +++ b/drivers/hid/hid-uclogic.c @@ -0,0 +1,427 @@ +/* + * HID driver for UC-Logic 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" + +/* + * 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 */ +}; + +static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + 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; + } + + return rdesc; +} + +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) }, + { } +}; +MODULE_DEVICE_TABLE(hid, uclogic_devices); + +static struct hid_driver uclogic_driver = { + .name = "uclogic", + .id_table = uclogic_devices, + .report_fixup = uclogic_report_fixup, +}; + +static int __init uclogic_init(void) +{ + return hid_register_driver(&uclogic_driver); +} + +static void __exit uclogic_exit(void) +{ + hid_unregister_driver(&uclogic_driver); +} + +module_init(uclogic_init); +module_exit(uclogic_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-wacom.c b/drivers/hid/hid-wacom.c new file mode 100644 index 00000000..067e2963 --- /dev/null +++ b/drivers/hid/hid-wacom.c @@ -0,0 +1,697 @@ +/* + * Bluetooth Wacom Tablet support + * + * 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 <jirislaby@gmail.com> + * Copyright (c) 2006 Andrew Zabolotny <zap@homelink.ru> + * Copyright (c) 2009 Bastien Nocera <hadess@hadess.net> + * Copyright (c) 2011 Przemysław Firszt <przemo@firszt.eu> + */ + +/* + * 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> +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY +#include <linux/power_supply.h> +#endif + +#include "hid-ids.h" + +#define PAD_DEVICE_ID 0x0F + +struct wacom_data { + __u16 tool; + __u16 butstate; + __u8 whlstate; + __u8 features; + __u32 id; + __u32 serial; + unsigned char high_speed; +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + int battery_capacity; + struct power_supply battery; + struct power_supply ac; +#endif +}; + +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY +/*percent of battery capacity, 0 means AC online*/ +static unsigned short batcap[8] = { 1, 15, 25, 35, 50, 70, 100, 0 }; + +static enum power_supply_property wacom_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_SCOPE, +}; + +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_data *wdata = container_of(psy, + struct wacom_data, battery); + int power_state = batcap[wdata->battery_capacity]; + int ret = 0; + + 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: + /* show 100% battery capacity when charging */ + if (power_state == 0) + val->intval = 100; + else + val->intval = power_state; + 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_data *wdata = container_of(psy, struct wacom_data, ac); + int power_state = batcap[wdata->battery_capacity]; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + /* fall through */ + case POWER_SUPPLY_PROP_ONLINE: + if (power_state == 0) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +#endif + +static void wacom_set_features(struct hid_device *hdev) +{ + int ret; + __u8 rep_data[2]; + + /*set high speed, tablet mode*/ + rep_data[0] = 0x03; + rep_data[1] = 0x20; + ret = hdev->hid_output_raw_report(hdev, rep_data, 2, + HID_FEATURE_REPORT); + return; +} + +static void wacom_poke(struct hid_device *hdev, u8 speed) +{ + struct wacom_data *wdata = hid_get_drvdata(hdev); + int limit, ret; + char rep_data[2]; + + rep_data[0] = 0x03 ; rep_data[1] = 0x00; + limit = 3; + do { + ret = hdev->hid_output_raw_report(hdev, rep_data, 2, + HID_FEATURE_REPORT); + } while (ret < 0 && limit-- > 0); + + if (ret >= 0) { + if (speed == 0) + rep_data[0] = 0x05; + else + rep_data[0] = 0x06; + + rep_data[1] = 0x00; + limit = 3; + do { + ret = hdev->hid_output_raw_report(hdev, rep_data, 2, + HID_FEATURE_REPORT); + } while (ret < 0 && limit-- > 0); + + if (ret >= 0) { + wdata->high_speed = speed; + return; + } + } + + /* + * 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); + return; +} + +static ssize_t wacom_show_speed(struct device *dev, + struct device_attribute + *attr, char *buf) +{ + struct wacom_data *wdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%i\n", wdata->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); + int new_speed; + + if (sscanf(buf, "%1d", &new_speed ) != 1) + return -EINVAL; + + if (new_speed == 0 || new_speed == 1) { + wacom_poke(hdev, new_speed); + return strnlen(buf, PAGE_SIZE); + } else + return -EINVAL; +} + +static DEVICE_ATTR(speed, S_IRUGO | S_IWUSR | S_IWGRP, + wacom_show_speed, wacom_store_speed); + +static int wacom_gr_parse_report(struct hid_device *hdev, + struct wacom_data *wdata, + struct input_dev *input, unsigned char *data) +{ + int tool, x, y, rw; + + tool = 0; + /* Get X & Y positions */ + x = le16_to_cpu(*(__le16 *) &data[2]); + y = le16_to_cpu(*(__le16 *) &data[4]); + + /* Get current tool identifier */ + if (data[1] & 0x90) { /* If pen is in the in/active area */ + switch ((data[1] >> 5) & 3) { + case 0: /* Pen */ + tool = BTN_TOOL_PEN; + break; + + case 1: /* Rubber */ + tool = BTN_TOOL_RUBBER; + break; + + case 2: /* Mouse with wheel */ + case 3: /* Mouse without wheel */ + tool = BTN_TOOL_MOUSE; + break; + } + + /* Reset tool if out of active tablet area */ + if (!(data[1] & 0x10)) + tool = 0; + } + + /* If tool changed, notify input subsystem */ + if (wdata->tool != tool) { + if (wdata->tool) { + /* Completely reset old tool state */ + if (wdata->tool == BTN_TOOL_MOUSE) { + input_report_key(input, BTN_LEFT, 0); + input_report_key(input, BTN_RIGHT, 0); + input_report_key(input, BTN_MIDDLE, 0); + input_report_abs(input, ABS_DISTANCE, + input_abs_get_max(input, ABS_DISTANCE)); + } else { + input_report_key(input, BTN_TOUCH, 0); + input_report_key(input, BTN_STYLUS, 0); + input_report_key(input, BTN_STYLUS2, 0); + input_report_abs(input, ABS_PRESSURE, 0); + } + input_report_key(input, wdata->tool, 0); + input_sync(input); + } + wdata->tool = tool; + if (tool) + input_report_key(input, tool, 1); + } + + if (tool) { + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + + switch ((data[1] >> 5) & 3) { + case 2: /* Mouse with wheel */ + input_report_key(input, BTN_MIDDLE, data[1] & 0x04); + rw = (data[6] & 0x01) ? -1 : + (data[6] & 0x02) ? 1 : 0; + input_report_rel(input, REL_WHEEL, rw); + /* fall through */ + + case 3: /* Mouse without wheel */ + input_report_key(input, BTN_LEFT, data[1] & 0x01); + input_report_key(input, BTN_RIGHT, data[1] & 0x02); + /* Compute distance between mouse and tablet */ + rw = 44 - (data[6] >> 2); + if (rw < 0) + rw = 0; + else if (rw > 31) + rw = 31; + input_report_abs(input, ABS_DISTANCE, rw); + break; + + default: + input_report_abs(input, ABS_PRESSURE, + data[6] | (((__u16) (data[1] & 0x08)) << 5)); + input_report_key(input, BTN_TOUCH, data[1] & 0x01); + input_report_key(input, BTN_STYLUS, data[1] & 0x02); + input_report_key(input, BTN_STYLUS2, (tool == BTN_TOOL_PEN) && data[1] & 0x04); + break; + } + + input_sync(input); + } + + /* Report the state of the two buttons at the top of the tablet + * as two extra fingerpad keys (buttons 4 & 5). */ + rw = data[7] & 0x03; + if (rw != wdata->butstate) { + wdata->butstate = rw; + input_report_key(input, BTN_0, rw & 0x02); + input_report_key(input, BTN_1, rw & 0x01); + input_report_key(input, BTN_TOOL_FINGER, 0xf0); + input_event(input, EV_MSC, MSC_SERIAL, 0xf0); + input_sync(input); + } + +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + /* Store current battery capacity */ + rw = (data[7] >> 2 & 0x07); + if (rw != wdata->battery_capacity) + wdata->battery_capacity = rw; +#endif + return 1; +} + +static void wacom_i4_parse_button_report(struct wacom_data *wdata, + struct input_dev *input, unsigned char *data) +{ + __u16 new_butstate; + __u8 new_whlstate; + __u8 sync = 0; + + new_whlstate = data[1]; + if (new_whlstate != wdata->whlstate) { + wdata->whlstate = new_whlstate; + if (new_whlstate & 0x80) { + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_WHEEL, (new_whlstate & 0x7f)); + input_report_key(input, BTN_TOOL_FINGER, 1); + } else { + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_WHEEL, 0); + input_report_key(input, BTN_TOOL_FINGER, 0); + } + sync = 1; + } + + new_butstate = (data[3] << 1) | (data[2] & 0x01); + if (new_butstate != wdata->butstate) { + wdata->butstate = new_butstate; + input_report_key(input, BTN_0, new_butstate & 0x001); + input_report_key(input, BTN_1, new_butstate & 0x002); + input_report_key(input, BTN_2, new_butstate & 0x004); + input_report_key(input, BTN_3, new_butstate & 0x008); + input_report_key(input, BTN_4, new_butstate & 0x010); + input_report_key(input, BTN_5, new_butstate & 0x020); + input_report_key(input, BTN_6, new_butstate & 0x040); + input_report_key(input, BTN_7, new_butstate & 0x080); + input_report_key(input, BTN_8, new_butstate & 0x100); + input_report_key(input, BTN_TOOL_FINGER, 1); + sync = 1; + } + + if (sync) { + input_report_abs(input, ABS_MISC, PAD_DEVICE_ID); + input_event(input, EV_MSC, MSC_SERIAL, 0xffffffff); + input_sync(input); + } +} + +static void wacom_i4_parse_pen_report(struct wacom_data *wdata, + struct input_dev *input, unsigned char *data) +{ + __u16 x, y, pressure; + __u8 distance; + + switch (data[1]) { + case 0x80: /* Out of proximity report */ + input_report_key(input, BTN_TOUCH, 0); + 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, wdata->tool, 0); + input_report_abs(input, ABS_MISC, 0); + input_event(input, EV_MSC, MSC_SERIAL, wdata->serial); + wdata->tool = 0; + input_sync(input); + break; + case 0xC2: /* Tool report */ + wdata->id = ((data[2] << 4) | (data[3] >> 4) | + ((data[7] & 0x0f) << 20) | + ((data[8] & 0xf0) << 12)); + wdata->serial = ((data[3] & 0x0f) << 28) + + (data[4] << 20) + (data[5] << 12) + + (data[6] << 4) + (data[7] >> 4); + + switch (wdata->id) { + case 0x100802: + wdata->tool = BTN_TOOL_PEN; + break; + case 0x10080A: + wdata->tool = BTN_TOOL_RUBBER; + break; + } + break; + default: /* Position/pressure report */ + x = data[2] << 9 | data[3] << 1 | ((data[9] & 0x02) >> 1); + y = data[4] << 9 | data[5] << 1 | (data[9] & 0x01); + pressure = (data[6] << 3) | ((data[7] & 0xC0) >> 5) + | (data[1] & 0x01); + distance = (data[9] >> 2) & 0x3f; + + input_report_key(input, BTN_TOUCH, pressure > 1); + + input_report_key(input, BTN_STYLUS, data[1] & 0x02); + input_report_key(input, BTN_STYLUS2, data[1] & 0x04); + input_report_key(input, wdata->tool, 1); + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + input_report_abs(input, ABS_PRESSURE, pressure); + input_report_abs(input, ABS_DISTANCE, distance); + input_report_abs(input, ABS_MISC, wdata->id); + input_event(input, EV_MSC, MSC_SERIAL, wdata->serial); + input_report_key(input, wdata->tool, 1); + input_sync(input); + break; + } + + return; +} + +static void wacom_i4_parse_report(struct hid_device *hdev, + struct wacom_data *wdata, + struct input_dev *input, unsigned char *data) +{ + switch (data[0]) { + case 0x00: /* Empty report */ + break; + case 0x02: /* Pen report */ + wacom_i4_parse_pen_report(wdata, input, data); + break; + case 0x03: /* Features Report */ + wdata->features = data[2]; + break; + case 0x0C: /* Button report */ + wacom_i4_parse_button_report(wdata, input, data); + break; + default: + hid_err(hdev, "Unknown report: %d,%d\n", data[0], data[1]); + break; + } +} + +static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *raw_data, int size) +{ + struct wacom_data *wdata = hid_get_drvdata(hdev); + struct hid_input *hidinput; + struct input_dev *input; + unsigned char *data = (unsigned char *) raw_data; + int i; + + if (!(hdev->claimed & HID_CLAIMED_INPUT)) + return 0; + + hidinput = list_entry(hdev->inputs.next, struct hid_input, list); + input = hidinput->input; + + /* Check if this is a tablet report */ + if (data[0] != 0x03) + return 0; + + switch (hdev->product) { + case USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH: + return wacom_gr_parse_report(hdev, wdata, input, data); + break; + case USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH: + i = 1; + + switch (data[0]) { + case 0x04: + wacom_i4_parse_report(hdev, wdata, input, data + i); + i += 10; + /* fall through */ + case 0x03: + wacom_i4_parse_report(hdev, wdata, input, data + i); + i += 10; + wacom_i4_parse_report(hdev, wdata, input, data + i); + break; + default: + hid_err(hdev, "Unknown report: %d,%d size:%d\n", + data[0], data[1], size); + return 0; + } + } + return 1; +} + +static int wacom_input_mapped(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, unsigned long **bit, + int *max) +{ + struct input_dev *input = hi->input; + + __set_bit(INPUT_PROP_POINTER, input->propbit); + + /* Basics */ + input->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_REL); + + __set_bit(REL_WHEEL, input->relbit); + + __set_bit(BTN_TOOL_PEN, input->keybit); + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_STYLUS, input->keybit); + __set_bit(BTN_STYLUS2, input->keybit); + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_RIGHT, input->keybit); + __set_bit(BTN_MIDDLE, input->keybit); + + /* Pad */ + input_set_capability(input, EV_MSC, MSC_SERIAL); + + __set_bit(BTN_0, input->keybit); + __set_bit(BTN_1, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + + /* Distance, rubber and mouse */ + __set_bit(BTN_TOOL_RUBBER, input->keybit); + __set_bit(BTN_TOOL_MOUSE, input->keybit); + + switch (hdev->product) { + case USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH: + input_set_abs_params(input, ABS_X, 0, 16704, 4, 0); + input_set_abs_params(input, ABS_Y, 0, 12064, 4, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, 511, 0, 0); + input_set_abs_params(input, ABS_DISTANCE, 0, 32, 0, 0); + break; + case USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH: + __set_bit(ABS_WHEEL, input->absbit); + __set_bit(ABS_MISC, input->absbit); + __set_bit(BTN_2, input->keybit); + __set_bit(BTN_3, input->keybit); + __set_bit(BTN_4, input->keybit); + __set_bit(BTN_5, input->keybit); + __set_bit(BTN_6, input->keybit); + __set_bit(BTN_7, input->keybit); + __set_bit(BTN_8, input->keybit); + input_set_abs_params(input, ABS_WHEEL, 0, 71, 0, 0); + input_set_abs_params(input, ABS_X, 0, 40640, 4, 0); + input_set_abs_params(input, ABS_Y, 0, 25400, 4, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, 2047, 0, 0); + input_set_abs_params(input, ABS_DISTANCE, 0, 63, 0, 0); + break; + } + + return 0; +} + +static int wacom_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct wacom_data *wdata; + int ret; + + wdata = kzalloc(sizeof(*wdata), GFP_KERNEL); + if (wdata == NULL) { + hid_err(hdev, "can't alloc wacom descriptor\n"); + return -ENOMEM; + } + + hid_set_drvdata(hdev, wdata); + + /* Parse the HID report now */ + 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; + } + + ret = device_create_file(&hdev->dev, &dev_attr_speed); + if (ret) + hid_warn(hdev, + "can't create sysfs speed attribute err: %d\n", ret); + + switch (hdev->product) { + case USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH: + /* Set Wacom mode 2 with high reporting speed */ + wacom_poke(hdev, 1); + break; + case USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH: + sprintf(hdev->name, "%s", "Wacom Intuos4 WL"); + wdata->features = 0; + wacom_set_features(hdev); + break; + } + +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + wdata->battery.properties = wacom_battery_props; + wdata->battery.num_properties = ARRAY_SIZE(wacom_battery_props); + wdata->battery.get_property = wacom_battery_get_property; + wdata->battery.name = "wacom_battery"; + wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY; + wdata->battery.use_for_apm = 0; + + + ret = power_supply_register(&hdev->dev, &wdata->battery); + if (ret) { + hid_warn(hdev, "can't create sysfs battery attribute, err: %d\n", + ret); + goto err_battery; + } + + power_supply_powers(&wdata->battery, &hdev->dev); + + wdata->ac.properties = wacom_ac_props; + wdata->ac.num_properties = ARRAY_SIZE(wacom_ac_props); + wdata->ac.get_property = wacom_ac_get_property; + wdata->ac.name = "wacom_ac"; + wdata->ac.type = POWER_SUPPLY_TYPE_MAINS; + wdata->ac.use_for_apm = 0; + + ret = power_supply_register(&hdev->dev, &wdata->ac); + if (ret) { + hid_warn(hdev, + "can't create ac battery attribute, err: %d\n", ret); + goto err_ac; + } + + power_supply_powers(&wdata->ac, &hdev->dev); +#endif + return 0; + +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY +err_ac: + power_supply_unregister(&wdata->battery); +err_battery: + device_remove_file(&hdev->dev, &dev_attr_speed); + hid_hw_stop(hdev); +#endif +err_free: + kfree(wdata); + return ret; +} + +static void wacom_remove(struct hid_device *hdev) +{ +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + struct wacom_data *wdata = hid_get_drvdata(hdev); +#endif + device_remove_file(&hdev->dev, &dev_attr_speed); + hid_hw_stop(hdev); + +#ifdef CONFIG_HID_WACOM_POWER_SUPPLY + power_supply_unregister(&wdata->battery); + power_supply_unregister(&wdata->ac); +#endif + kfree(hid_get_drvdata(hdev)); +} + +static const struct hid_device_id wacom_devices[] = { + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH) }, + + { } +}; +MODULE_DEVICE_TABLE(hid, wacom_devices); + +static struct hid_driver wacom_driver = { + .name = "wacom", + .id_table = wacom_devices, + .probe = wacom_probe, + .remove = wacom_remove, + .raw_event = wacom_raw_event, + .input_mapped = wacom_input_mapped, +}; + +static int __init wacom_init(void) +{ + int ret; + + ret = hid_register_driver(&wacom_driver); + if (ret) + pr_err("can't register wacom driver\n"); + return ret; +} + +static void __exit wacom_exit(void) +{ + hid_unregister_driver(&wacom_driver); +} + +module_init(wacom_init); +module_exit(wacom_exit); +MODULE_LICENSE("GPL"); + diff --git a/drivers/hid/hid-waltop.c b/drivers/hid/hid-waltop.c new file mode 100644 index 00000000..2cfd95c4 --- /dev/null +++ b/drivers/hid/hid-waltop.c @@ -0,0 +1,666 @@ +/* + * 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 */ +}; + +struct waltop_state { + u8 pressure0; + u8 pressure1; +}; + +static int waltop_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct waltop_state *s; + + s = kzalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) { + hid_err(hdev, "can't allocate device state\n"); + ret = -ENOMEM; + goto err; + } + + s->pressure0 = 0; + s->pressure1 = 0; + + hid_set_drvdata(hdev, s); + + 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; + } + + return 0; +err: + kfree(s); + return ret; +} + +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; + } + 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 of a tablet with PID 0038 */ + if (hdev->product == USB_DEVICE_ID_WALTOP_PID_0038 && + report->type == HID_INPUT_REPORT && + report->id == 16 && + size == 8) { + struct waltop_state *s = hid_get_drvdata(hdev); + + /* + * Ignore maximum pressure reported when a barrel button is + * pressed. + */ + + /* If a barrel button is pressed */ + if ((data[1] & 0xF) > 1) { + /* Use the last known pressure */ + data[6] = s->pressure0; + data[7] = s->pressure1; + } else { + /* Remember reported pressure */ + s->pressure0 = data[6]; + s->pressure1 = data[7]; + } + } + + return 0; +} + +static void waltop_remove(struct hid_device *hdev) +{ + struct waltop_state *s = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + kfree(s); +} + +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) }, + { } +}; +MODULE_DEVICE_TABLE(hid, waltop_devices); + +static struct hid_driver waltop_driver = { + .name = "waltop", + .id_table = waltop_devices, + .probe = waltop_probe, + .report_fixup = waltop_report_fixup, + .raw_event = waltop_raw_event, + .remove = waltop_remove, +}; + +static int __init waltop_init(void) +{ + return hid_register_driver(&waltop_driver); +} + +static void __exit waltop_exit(void) +{ + hid_unregister_driver(&waltop_driver); +} + +module_init(waltop_init); +module_exit(waltop_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c new file mode 100644 index 00000000..84e2fbec --- /dev/null +++ b/drivers/hid/hid-wiimote-core.c @@ -0,0 +1,1318 @@ +/* + * HID driver for Nintendo Wiimote devices + * Copyright (c) 2011 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/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 "hid-ids.h" +#include "hid-wiimote.h" + +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 +}; + +static __u16 wiiproto_keymap[] = { + 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 enum power_supply_property wiimote_battery_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_SCOPE, +}; + +static ssize_t wiimote_hid_send(struct hid_device *hdev, __u8 *buffer, + size_t count) +{ + __u8 *buf; + ssize_t ret; + + if (!hdev->hid_output_raw_report) + return -ENODEV; + + buf = kmemdup(buffer, count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hdev->hid_output_raw_report(hdev, buf, count, HID_OUTPUT_REPORT); + + kfree(buf); + return ret; +} + +static void wiimote_worker(struct work_struct *work) +{ + struct wiimote_data *wdata = container_of(work, struct wiimote_data, + worker); + unsigned long flags; + + spin_lock_irqsave(&wdata->qlock, flags); + + while (wdata->head != wdata->tail) { + spin_unlock_irqrestore(&wdata->qlock, flags); + wiimote_hid_send(wdata->hdev, wdata->outq[wdata->tail].data, + wdata->outq[wdata->tail].size); + spin_lock_irqsave(&wdata->qlock, flags); + + wdata->tail = (wdata->tail + 1) % WIIMOTE_BUFSIZE; + } + + spin_unlock_irqrestore(&wdata->qlock, 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"); + return; + } + + /* + * 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->qlock, flags); + + memcpy(wdata->outq[wdata->head].data, buffer, count); + wdata->outq[wdata->head].size = count; + newhead = (wdata->head + 1) % WIIMOTE_BUFSIZE; + + if (wdata->head == wdata->tail) { + wdata->head = newhead; + schedule_work(&wdata->worker); + } else if (newhead != wdata->tail) { + wdata->head = newhead; + } else { + hid_warn(wdata->hdev, "Output queue is full"); + } + + spin_unlock_irqrestore(&wdata->qlock, 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; +} + +static 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)); +} + +static 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. + */ +static __u8 select_drm(struct wiimote_data *wdata) +{ + __u8 ir = wdata->state.flags & WIIPROTO_FLAGS_IR; + bool ext = wiiext_active(wdata); + + if (ir == WIIPROTO_FLAG_IR_BASIC) { + if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) + 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_KE; + else + return WIIPROTO_REQ_DRM_K; + } + } +} + +void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) +{ + __u8 cmd[3]; + + 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)); +} + +static 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)); +} + +static 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); +} + +static 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)); +} + +static 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; +} + +static int wiimote_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wiimote_data *wdata = container_of(psy, + struct wiimote_data, battery); + int ret = 0, state; + unsigned long flags; + + if (psp == POWER_SUPPLY_PROP_SCOPE) { + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + return 0; + } + + 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); + + ret = wiimote_cmd_wait(wdata); + state = wdata->state.cmd_battery; + wiimote_cmd_release(wdata); + + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = state * 100 / 255; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int wiimote_init_ir(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 enum led_brightness wiimote_leds_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 wiimote_leds_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 wiimote_ff_play(struct input_dev *dev, void *data, + struct ff_effect *eff) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + __u8 value; + unsigned long flags; + + /* + * 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; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_req_rumble(wdata, value); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static int wiimote_input_open(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + + return hid_hw_open(wdata->hdev); +} + +static void wiimote_input_close(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + + hid_hw_close(wdata->hdev); +} + +static int wiimote_accel_open(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + int ret; + unsigned long flags; + + ret = hid_hw_open(wdata->hdev); + if (ret) + return ret; + + spin_lock_irqsave(&wdata->state.lock, flags); + wiiproto_req_accel(wdata, true); + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static void wiimote_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); + + hid_hw_close(wdata->hdev); +} + +static int wiimote_ir_open(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + int ret; + + ret = hid_hw_open(wdata->hdev); + if (ret) + return ret; + + ret = wiimote_init_ir(wdata, WIIPROTO_FLAG_IR_BASIC); + if (ret) { + hid_hw_close(wdata->hdev); + return ret; + } + + return 0; +} + +static void wiimote_ir_close(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + + wiimote_init_ir(wdata, 0); + hid_hw_close(wdata->hdev); +} + +static void handler_keys(struct wiimote_data *wdata, const __u8 *payload) +{ + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_LEFT], + !!(payload[0] & 0x01)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_RIGHT], + !!(payload[0] & 0x02)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_DOWN], + !!(payload[0] & 0x04)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_UP], + !!(payload[0] & 0x08)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_PLUS], + !!(payload[0] & 0x10)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_TWO], + !!(payload[1] & 0x01)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_ONE], + !!(payload[1] & 0x02)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_B], + !!(payload[1] & 0x04)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_A], + !!(payload[1] & 0x08)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_MINUS], + !!(payload[1] & 0x10)); + input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_HOME], + !!(payload[1] & 0x80)); + input_sync(wdata->input); +} + +static void handler_accel(struct wiimote_data *wdata, const __u8 *payload) +{ + __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 = payload[2] << 2; + y = payload[3] << 2; + z = payload[4] << 2; + + x |= (payload[0] >> 5) & 0x3; + y |= (payload[1] >> 4) & 0x2; + z |= (payload[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); +} + +#define ir_to_input0(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT0X, ABS_HAT0Y) +#define ir_to_input1(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT1X, ABS_HAT1Y) +#define ir_to_input2(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT2X, ABS_HAT2Y) +#define ir_to_input3(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \ + ABS_HAT3X, ABS_HAT3Y) + +static void __ir_to_input(struct wiimote_data *wdata, const __u8 *ir, + bool packed, __u8 xid, __u8 yid) +{ + __u16 x, y; + + if (!(wdata->state.flags & WIIPROTO_FLAGS_IR)) + 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_HATXY 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); +} + +static void handler_status(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); + + wiiext_event(wdata, payload[2] & 0x02); + + if (wiimote_cmd_pending(wdata, WIIPROTO_REQ_SREQ, 0)) { + wdata->state.cmd_battery = payload[5]; + wiimote_cmd_complete(wdata); + } +} + +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); + wiiext_handle(wdata, &payload[2]); +} + +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); + input_sync(wdata->ir); +} + +static void handler_drm_KEE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + wiiext_handle(wdata, &payload[2]); +} + +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); + input_sync(wdata->ir); + wiiext_handle(wdata, &payload[12]); +} + +static void handler_drm_KAE(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + handler_accel(wdata, payload); + wiiext_handle(wdata, &payload[5]); +} + +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); + input_sync(wdata->ir); + wiiext_handle(wdata, &payload[15]); +} + +static void handler_drm_E(struct wiimote_data *wdata, const __u8 *payload) +{ + wiiext_handle(wdata, payload); +} + +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); + input_sync(wdata->ir); +} + +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); + input_sync(wdata->ir); +} + +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_DATA, .size = 21, .func = handler_data }, + { .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return }, + { .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_KE, .size = 10, .func = handler_drm_KE }, + { .id = WIIPROTO_REQ_DRM_KAI, .size = 17, .func = handler_drm_KAI }, + { .id = WIIPROTO_REQ_DRM_KEE, .size = 21, .func = handler_drm_KEE }, + { .id = WIIPROTO_REQ_DRM_KAE, .size = 21, .func = handler_drm_KAE }, + { .id = WIIPROTO_REQ_DRM_KIE, .size = 21, .func = handler_drm_KIE }, + { .id = WIIPROTO_REQ_DRM_KAIE, .size = 21, .func = handler_drm_KAIE }, + { .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; + bool handled = false; + + 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]); + handled = true; + } + } + + if (!handled) + hid_warn(hdev, "Unhandled report %hhu size %d\n", raw_data[0], + size); + + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; +} + +static void wiimote_leds_destroy(struct wiimote_data *wdata) +{ + int i; + struct led_classdev *led; + + for (i = 0; i < 4; ++i) { + if (wdata->leds[i]) { + led = wdata->leds[i]; + wdata->leds[i] = NULL; + led_classdev_unregister(led); + kfree(led); + } + } +} + +static int wiimote_leds_create(struct wiimote_data *wdata) +{ + int i, ret; + struct device *dev = &wdata->hdev->dev; + size_t namesz = strlen(dev_name(dev)) + 9; + struct led_classdev *led; + char *name; + + for (i = 0; i < 4; ++i) { + led = kzalloc(sizeof(struct led_classdev) + namesz, GFP_KERNEL); + if (!led) { + ret = -ENOMEM; + goto err; + } + name = (void*)&led[1]; + snprintf(name, namesz, "%s:blue:p%d", dev_name(dev), i); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = wiimote_leds_get; + led->brightness_set = wiimote_leds_set; + + ret = led_classdev_register(dev, led); + if (ret) { + kfree(led); + goto err; + } + wdata->leds[i] = led; + } + + return 0; + +err: + wiimote_leds_destroy(wdata); + return ret; +} + +static struct wiimote_data *wiimote_create(struct hid_device *hdev) +{ + struct wiimote_data *wdata; + int i; + + wdata = kzalloc(sizeof(*wdata), GFP_KERNEL); + if (!wdata) + return NULL; + + wdata->input = input_allocate_device(); + if (!wdata->input) + goto err; + + wdata->hdev = hdev; + hid_set_drvdata(hdev, wdata); + + input_set_drvdata(wdata->input, wdata); + wdata->input->open = wiimote_input_open; + wdata->input->close = wiimote_input_close; + 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; + + set_bit(EV_KEY, wdata->input->evbit); + for (i = 0; i < WIIPROTO_KEY_COUNT; ++i) + set_bit(wiiproto_keymap[i], wdata->input->keybit); + + set_bit(FF_RUMBLE, wdata->input->ffbit); + if (input_ff_create_memless(wdata->input, NULL, wiimote_ff_play)) + goto err_input; + + wdata->accel = input_allocate_device(); + if (!wdata->accel) + goto err_input; + + input_set_drvdata(wdata->accel, wdata); + wdata->accel->open = wiimote_accel_open; + wdata->accel->close = wiimote_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); + + wdata->ir = input_allocate_device(); + if (!wdata->ir) + goto err_ir; + + input_set_drvdata(wdata->ir, wdata); + wdata->ir->open = wiimote_ir_open; + wdata->ir->close = wiimote_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); + + spin_lock_init(&wdata->qlock); + INIT_WORK(&wdata->worker, wiimote_worker); + + spin_lock_init(&wdata->state.lock); + init_completion(&wdata->state.ready); + mutex_init(&wdata->state.sync); + wdata->state.drm = WIIPROTO_REQ_DRM_K; + + return wdata; + +err_ir: + input_free_device(wdata->accel); +err_input: + input_free_device(wdata->input); +err: + kfree(wdata); + return NULL; +} + +static void wiimote_destroy(struct wiimote_data *wdata) +{ + wiidebug_deinit(wdata); + wiiext_deinit(wdata); + wiimote_leds_destroy(wdata); + + power_supply_unregister(&wdata->battery); + input_unregister_device(wdata->accel); + input_unregister_device(wdata->ir); + input_unregister_device(wdata->input); + cancel_work_sync(&wdata->worker); + 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 = input_register_device(wdata->accel); + if (ret) { + hid_err(hdev, "Cannot register input device\n"); + goto err_stop; + } + + ret = input_register_device(wdata->ir); + if (ret) { + hid_err(hdev, "Cannot register input device\n"); + goto err_ir; + } + + ret = input_register_device(wdata->input); + if (ret) { + hid_err(hdev, "Cannot register input device\n"); + goto err_input; + } + + wdata->battery.properties = wiimote_battery_props; + wdata->battery.num_properties = ARRAY_SIZE(wiimote_battery_props); + wdata->battery.get_property = wiimote_battery_get_property; + wdata->battery.name = "wiimote_battery"; + wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY; + wdata->battery.use_for_apm = 0; + + ret = power_supply_register(&wdata->hdev->dev, &wdata->battery); + if (ret) { + hid_err(hdev, "Cannot register battery device\n"); + goto err_battery; + } + + power_supply_powers(&wdata->battery, &hdev->dev); + + ret = wiimote_leds_create(wdata); + if (ret) + goto err_free; + + ret = wiiext_init(wdata); + if (ret) + goto err_free; + + ret = wiidebug_init(wdata); + if (ret) + goto err_free; + + hid_info(hdev, "New device registered\n"); + + /* by default set led1 after device initialization */ + spin_lock_irq(&wdata->state.lock); + wiiproto_req_leds(wdata, WIIPROTO_FLAG_LED1); + spin_unlock_irq(&wdata->state.lock); + + return 0; + +err_free: + wiimote_destroy(wdata); + return ret; + +err_battery: + input_unregister_device(wdata->input); + wdata->input = NULL; +err_input: + input_unregister_device(wdata->ir); + wdata->ir = NULL; +err_ir: + input_unregister_device(wdata->accel); + wdata->accel = NULL; +err_stop: + hid_hw_stop(hdev); +err: + input_free_device(wdata->ir); + input_free_device(wdata->accel); + input_free_device(wdata->input); + 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) }, + { } +}; +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, +}; + +static int __init wiimote_init(void) +{ + int ret; + + ret = hid_register_driver(&wiimote_hid_driver); + if (ret) + pr_err("Can't register wiimote hid driver\n"); + + return ret; +} + +static void __exit wiimote_exit(void) +{ + hid_unregister_driver(&wiimote_hid_driver); +} + +module_init(wiimote_init); +module_exit(wiimote_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>"); +MODULE_DESCRIPTION(WIIMOTE_NAME " Device Driver"); diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c new file mode 100644 index 00000000..eec32919 --- /dev/null +++ b/drivers/hid/hid-wiimote-debug.c @@ -0,0 +1,221 @@ +/* + * Debug support for HID Nintendo Wiimote devices + * Copyright (c) 2011 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/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; + + 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 wiimote_debug *dbg = f->private_data; + 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[15] = 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, 10); + + spin_lock_irqsave(&dbg->wdata->state.lock, flags); + wiiproto_req_drm(dbg->wdata, (__u8) i); + 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-ext.c b/drivers/hid/hid-wiimote-ext.c new file mode 100644 index 00000000..aa958706 --- /dev/null +++ b/drivers/hid/hid-wiimote-ext.c @@ -0,0 +1,752 @@ +/* + * HID driver for Nintendo Wiimote extension devices + * Copyright (c) 2011 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/module.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include "hid-wiimote.h" + +struct wiimote_ext { + struct wiimote_data *wdata; + struct work_struct worker; + struct input_dev *input; + struct input_dev *mp_input; + + atomic_t opened; + atomic_t mp_opened; + bool plugged; + bool mp_plugged; + bool motionp; + __u8 ext_type; +}; + +enum wiiext_type { + WIIEXT_NONE, /* placeholder */ + WIIEXT_CLASSIC, /* Nintendo classic controller */ + WIIEXT_NUNCHUCK, /* Nintendo nunchuck controller */ +}; + +enum wiiext_keys { + WIIEXT_KEY_C, + WIIEXT_KEY_Z, + WIIEXT_KEY_A, + WIIEXT_KEY_B, + WIIEXT_KEY_X, + WIIEXT_KEY_Y, + WIIEXT_KEY_ZL, + WIIEXT_KEY_ZR, + WIIEXT_KEY_PLUS, + WIIEXT_KEY_MINUS, + WIIEXT_KEY_HOME, + WIIEXT_KEY_LEFT, + WIIEXT_KEY_RIGHT, + WIIEXT_KEY_UP, + WIIEXT_KEY_DOWN, + WIIEXT_KEY_LT, + WIIEXT_KEY_RT, + WIIEXT_KEY_COUNT +}; + +static __u16 wiiext_keymap[] = { + BTN_C, /* WIIEXT_KEY_C */ + BTN_Z, /* WIIEXT_KEY_Z */ + BTN_A, /* WIIEXT_KEY_A */ + BTN_B, /* WIIEXT_KEY_B */ + BTN_X, /* WIIEXT_KEY_X */ + BTN_Y, /* WIIEXT_KEY_Y */ + BTN_TL2, /* WIIEXT_KEY_ZL */ + BTN_TR2, /* WIIEXT_KEY_ZR */ + KEY_NEXT, /* WIIEXT_KEY_PLUS */ + KEY_PREVIOUS, /* WIIEXT_KEY_MINUS */ + BTN_MODE, /* WIIEXT_KEY_HOME */ + KEY_LEFT, /* WIIEXT_KEY_LEFT */ + KEY_RIGHT, /* WIIEXT_KEY_RIGHT */ + KEY_UP, /* WIIEXT_KEY_UP */ + KEY_DOWN, /* WIIEXT_KEY_DOWN */ + BTN_TL, /* WIIEXT_KEY_LT */ + BTN_TR, /* WIIEXT_KEY_RT */ +}; + +/* diable all extensions */ +static void ext_disable(struct wiimote_ext *ext) +{ + unsigned long flags; + __u8 wmem = 0x55; + + if (!wiimote_cmd_acquire(ext->wdata)) { + wiimote_cmd_write(ext->wdata, 0xa400f0, &wmem, sizeof(wmem)); + wiimote_cmd_release(ext->wdata); + } + + spin_lock_irqsave(&ext->wdata->state.lock, flags); + ext->motionp = false; + ext->ext_type = WIIEXT_NONE; + wiiproto_req_drm(ext->wdata, WIIPROTO_REQ_NULL); + spin_unlock_irqrestore(&ext->wdata->state.lock, flags); +} + +static bool motionp_read(struct wiimote_ext *ext) +{ + __u8 rmem[2], wmem; + ssize_t ret; + bool avail = false; + + if (!atomic_read(&ext->mp_opened)) + return false; + + if (wiimote_cmd_acquire(ext->wdata)) + return false; + + /* initialize motion plus */ + wmem = 0x55; + ret = wiimote_cmd_write(ext->wdata, 0xa600f0, &wmem, sizeof(wmem)); + if (ret) + goto error; + + /* read motion plus ID */ + ret = wiimote_cmd_read(ext->wdata, 0xa600fe, rmem, 2); + if (ret == 2 || rmem[1] == 0x5) + avail = true; + +error: + wiimote_cmd_release(ext->wdata); + return avail; +} + +static __u8 ext_read(struct wiimote_ext *ext) +{ + ssize_t ret; + __u8 rmem[2], wmem; + __u8 type = WIIEXT_NONE; + + if (!ext->plugged || !atomic_read(&ext->opened)) + return WIIEXT_NONE; + + if (wiimote_cmd_acquire(ext->wdata)) + return WIIEXT_NONE; + + /* initialize extension */ + wmem = 0x55; + ret = wiimote_cmd_write(ext->wdata, 0xa400f0, &wmem, sizeof(wmem)); + if (!ret) { + /* disable encryption */ + wmem = 0x0; + wiimote_cmd_write(ext->wdata, 0xa400fb, &wmem, sizeof(wmem)); + } + + /* read extension ID */ + ret = wiimote_cmd_read(ext->wdata, 0xa400fe, rmem, 2); + if (ret == 2) { + if (rmem[0] == 0 && rmem[1] == 0) + type = WIIEXT_NUNCHUCK; + else if (rmem[0] == 0x01 && rmem[1] == 0x01) + type = WIIEXT_CLASSIC; + } + + wiimote_cmd_release(ext->wdata); + + return type; +} + +static void ext_enable(struct wiimote_ext *ext, bool motionp, __u8 ext_type) +{ + unsigned long flags; + __u8 wmem; + int ret; + + if (motionp) { + if (wiimote_cmd_acquire(ext->wdata)) + return; + + if (ext_type == WIIEXT_CLASSIC) + wmem = 0x07; + else if (ext_type == WIIEXT_NUNCHUCK) + wmem = 0x05; + else + wmem = 0x04; + + ret = wiimote_cmd_write(ext->wdata, 0xa600fe, &wmem, sizeof(wmem)); + wiimote_cmd_release(ext->wdata); + if (ret) + return; + } + + spin_lock_irqsave(&ext->wdata->state.lock, flags); + ext->motionp = motionp; + ext->ext_type = ext_type; + wiiproto_req_drm(ext->wdata, WIIPROTO_REQ_NULL); + spin_unlock_irqrestore(&ext->wdata->state.lock, flags); +} + +static void wiiext_worker(struct work_struct *work) +{ + struct wiimote_ext *ext = container_of(work, struct wiimote_ext, + worker); + bool motionp; + __u8 ext_type; + + ext_disable(ext); + motionp = motionp_read(ext); + ext_type = ext_read(ext); + ext_enable(ext, motionp, ext_type); +} + +/* schedule work only once, otherwise mark for reschedule */ +static void wiiext_schedule(struct wiimote_ext *ext) +{ + queue_work(system_nrt_wq, &ext->worker); +} + +/* + * Reacts on extension port events + * Whenever the driver gets an event from the wiimote that an extension has been + * plugged or unplugged, this funtion shall be called. It checks what extensions + * are connected and initializes and activates them. + * This can be called in atomic context. The initialization is done in a + * separate worker thread. The state.lock spinlock must be held by the caller. + */ +void wiiext_event(struct wiimote_data *wdata, bool plugged) +{ + if (!wdata->ext) + return; + + if (wdata->ext->plugged == plugged) + return; + + wdata->ext->plugged = plugged; + + if (!plugged) + wdata->ext->mp_plugged = false; + + /* + * We need to call wiiext_schedule(wdata->ext) here, however, the + * extension initialization logic is not fully understood and so + * automatic initialization is not supported, yet. + */ +} + +/* + * Returns true if the current DRM mode should contain extension data and false + * if there is no interest in extension data. + * All supported extensions send 6 byte extension data so any DRM that contains + * extension bytes is fine. + * The caller must hold the state.lock spinlock. + */ +bool wiiext_active(struct wiimote_data *wdata) +{ + if (!wdata->ext) + return false; + + return wdata->ext->motionp || wdata->ext->ext_type; +} + +static void handler_motionp(struct wiimote_ext *ext, const __u8 *payload) +{ + __s32 x, y, z; + bool plugged; + + /* | 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. + */ + + x = payload[0]; + y = payload[1]; + z = payload[2]; + + x |= (((__u16)payload[3]) << 6) & 0xff00; + y |= (((__u16)payload[4]) << 6) & 0xff00; + z |= (((__u16)payload[5]) << 6) & 0xff00; + + x -= 8192; + y -= 8192; + z -= 8192; + + if (!(payload[3] & 0x02)) + x *= 18; + else + x *= 9; + if (!(payload[4] & 0x02)) + y *= 18; + else + y *= 9; + if (!(payload[3] & 0x01)) + z *= 18; + else + z *= 9; + + input_report_abs(ext->mp_input, ABS_RX, x); + input_report_abs(ext->mp_input, ABS_RY, y); + input_report_abs(ext->mp_input, ABS_RZ, z); + input_sync(ext->mp_input); + + plugged = payload[5] & 0x01; + if (plugged != ext->mp_plugged) + ext->mp_plugged = plugged; +} + +static void handler_nunchuck(struct wiimote_ext *ext, const __u8 *payload) +{ + __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 = payload[0]; + by = payload[1]; + bx -= 128; + by -= 128; + + x = payload[2] << 2; + y = payload[3] << 2; + z = payload[4] << 2; + + if (ext->motionp) { + x |= (payload[5] >> 3) & 0x02; + y |= (payload[5] >> 4) & 0x02; + z &= ~0x4; + z |= (payload[5] >> 5) & 0x06; + } else { + x |= (payload[5] >> 2) & 0x03; + y |= (payload[5] >> 4) & 0x03; + z |= (payload[5] >> 6) & 0x03; + } + + x -= 0x200; + y -= 0x200; + z -= 0x200; + + input_report_abs(ext->input, ABS_HAT0X, bx); + input_report_abs(ext->input, ABS_HAT0Y, by); + + input_report_abs(ext->input, ABS_RX, x); + input_report_abs(ext->input, ABS_RY, y); + input_report_abs(ext->input, ABS_RZ, z); + + if (ext->motionp) { + input_report_key(ext->input, + wiiext_keymap[WIIEXT_KEY_Z], !!(payload[5] & 0x04)); + input_report_key(ext->input, + wiiext_keymap[WIIEXT_KEY_C], !!(payload[5] & 0x08)); + } else { + input_report_key(ext->input, + wiiext_keymap[WIIEXT_KEY_Z], !!(payload[5] & 0x01)); + input_report_key(ext->input, + wiiext_keymap[WIIEXT_KEY_C], !!(payload[5] & 0x02)); + } + + input_sync(ext->input); +} + +static void handler_classic(struct wiimote_ext *ext, const __u8 *payload) +{ + __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 <4:3> | LX <5:1> | BDU | + * 2 | RX <2:1> | LY <5:1> | BDL | + * -----+-----+-----+-----+-----------------------+-----+ + * 3 |RX<0>| LT <4:3> | RY <4:0> | + * -----+-----+-----------+-----------------------------+ + * 4 | LT <2:0> | RT <4:0> | + * -----+-----+-----+-----+-----+-----+-----+-----+-----+ + * 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 (ext->motionp) { + lx = payload[0] & 0x3e; + ly = payload[0] & 0x3e; + } else { + lx = payload[0] & 0x3f; + ly = payload[0] & 0x3f; + } + + rx = (payload[0] >> 3) & 0x14; + rx |= (payload[1] >> 5) & 0x06; + rx |= (payload[2] >> 7) & 0x01; + ry = payload[2] & 0x1f; + + rt = payload[3] & 0x1f; + lt = (payload[2] >> 2) & 0x18; + lt |= (payload[3] >> 5) & 0x07; + + rx <<= 1; + ry <<= 1; + rt <<= 1; + lt <<= 1; + + input_report_abs(ext->input, ABS_HAT1X, lx - 0x20); + input_report_abs(ext->input, ABS_HAT1Y, ly - 0x20); + input_report_abs(ext->input, ABS_HAT2X, rx - 0x20); + input_report_abs(ext->input, ABS_HAT2Y, ry - 0x20); + input_report_abs(ext->input, ABS_HAT3X, rt - 0x20); + input_report_abs(ext->input, ABS_HAT3Y, lt - 0x20); + + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_RIGHT], + !!(payload[4] & 0x80)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_DOWN], + !!(payload[4] & 0x40)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_LT], + !!(payload[4] & 0x20)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_MINUS], + !!(payload[4] & 0x10)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_HOME], + !!(payload[4] & 0x08)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_PLUS], + !!(payload[4] & 0x04)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_RT], + !!(payload[4] & 0x02)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_ZL], + !!(payload[5] & 0x80)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_B], + !!(payload[5] & 0x40)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_Y], + !!(payload[5] & 0x20)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_A], + !!(payload[5] & 0x10)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_X], + !!(payload[5] & 0x08)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_ZR], + !!(payload[5] & 0x04)); + + if (ext->motionp) { + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_UP], + !!(payload[0] & 0x01)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_LEFT], + !!(payload[1] & 0x01)); + } else { + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_UP], + !!(payload[5] & 0x01)); + input_report_key(ext->input, wiiext_keymap[WIIEXT_KEY_LEFT], + !!(payload[5] & 0x02)); + } + + input_sync(ext->input); +} + +/* call this with state.lock spinlock held */ +void wiiext_handle(struct wiimote_data *wdata, const __u8 *payload) +{ + struct wiimote_ext *ext = wdata->ext; + + if (!ext) + return; + + if (ext->motionp && (payload[5] & 0x02)) { + handler_motionp(ext, payload); + } else if (ext->ext_type == WIIEXT_NUNCHUCK) { + handler_nunchuck(ext, payload); + } else if (ext->ext_type == WIIEXT_CLASSIC) { + handler_classic(ext, payload); + } +} + +static ssize_t wiiext_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wiimote_data *wdata = dev_to_wii(dev); + __u8 type = WIIEXT_NONE; + bool motionp = false; + unsigned long flags; + + spin_lock_irqsave(&wdata->state.lock, flags); + if (wdata->ext) { + motionp = wdata->ext->motionp; + type = wdata->ext->ext_type; + } + spin_unlock_irqrestore(&wdata->state.lock, flags); + + if (type == WIIEXT_NUNCHUCK) { + if (motionp) + return sprintf(buf, "motionp+nunchuck\n"); + else + return sprintf(buf, "nunchuck\n"); + } else if (type == WIIEXT_CLASSIC) { + if (motionp) + return sprintf(buf, "motionp+classic\n"); + else + return sprintf(buf, "classic\n"); + } else { + if (motionp) + return sprintf(buf, "motionp\n"); + else + return sprintf(buf, "none\n"); + } +} + +static DEVICE_ATTR(extension, S_IRUGO, wiiext_show, NULL); + +static int wiiext_input_open(struct input_dev *dev) +{ + struct wiimote_ext *ext = input_get_drvdata(dev); + int ret; + + ret = hid_hw_open(ext->wdata->hdev); + if (ret) + return ret; + + atomic_inc(&ext->opened); + wiiext_schedule(ext); + + return 0; +} + +static void wiiext_input_close(struct input_dev *dev) +{ + struct wiimote_ext *ext = input_get_drvdata(dev); + + atomic_dec(&ext->opened); + wiiext_schedule(ext); + hid_hw_close(ext->wdata->hdev); +} + +static int wiiext_mp_open(struct input_dev *dev) +{ + struct wiimote_ext *ext = input_get_drvdata(dev); + int ret; + + ret = hid_hw_open(ext->wdata->hdev); + if (ret) + return ret; + + atomic_inc(&ext->mp_opened); + wiiext_schedule(ext); + + return 0; +} + +static void wiiext_mp_close(struct input_dev *dev) +{ + struct wiimote_ext *ext = input_get_drvdata(dev); + + atomic_dec(&ext->mp_opened); + wiiext_schedule(ext); + hid_hw_close(ext->wdata->hdev); +} + +/* Initializes the extension driver of a wiimote */ +int wiiext_init(struct wiimote_data *wdata) +{ + struct wiimote_ext *ext; + unsigned long flags; + int ret, i; + + ext = kzalloc(sizeof(*ext), GFP_KERNEL); + if (!ext) + return -ENOMEM; + + ext->wdata = wdata; + INIT_WORK(&ext->worker, wiiext_worker); + + ext->input = input_allocate_device(); + if (!ext->input) { + ret = -ENOMEM; + goto err_input; + } + + input_set_drvdata(ext->input, ext); + ext->input->open = wiiext_input_open; + ext->input->close = wiiext_input_close; + ext->input->dev.parent = &wdata->hdev->dev; + ext->input->id.bustype = wdata->hdev->bus; + ext->input->id.vendor = wdata->hdev->vendor; + ext->input->id.product = wdata->hdev->product; + ext->input->id.version = wdata->hdev->version; + ext->input->name = WIIMOTE_NAME " Extension"; + + set_bit(EV_KEY, ext->input->evbit); + for (i = 0; i < WIIEXT_KEY_COUNT; ++i) + set_bit(wiiext_keymap[i], ext->input->keybit); + + set_bit(EV_ABS, ext->input->evbit); + set_bit(ABS_HAT0X, ext->input->absbit); + set_bit(ABS_HAT0Y, ext->input->absbit); + set_bit(ABS_HAT1X, ext->input->absbit); + set_bit(ABS_HAT1Y, ext->input->absbit); + set_bit(ABS_HAT2X, ext->input->absbit); + set_bit(ABS_HAT2Y, ext->input->absbit); + set_bit(ABS_HAT3X, ext->input->absbit); + set_bit(ABS_HAT3Y, ext->input->absbit); + input_set_abs_params(ext->input, ABS_HAT0X, -120, 120, 2, 4); + input_set_abs_params(ext->input, ABS_HAT0Y, -120, 120, 2, 4); + input_set_abs_params(ext->input, ABS_HAT1X, -30, 30, 1, 1); + input_set_abs_params(ext->input, ABS_HAT1Y, -30, 30, 1, 1); + input_set_abs_params(ext->input, ABS_HAT2X, -30, 30, 1, 1); + input_set_abs_params(ext->input, ABS_HAT2Y, -30, 30, 1, 1); + input_set_abs_params(ext->input, ABS_HAT3X, -30, 30, 1, 1); + input_set_abs_params(ext->input, ABS_HAT3Y, -30, 30, 1, 1); + set_bit(ABS_RX, ext->input->absbit); + set_bit(ABS_RY, ext->input->absbit); + set_bit(ABS_RZ, ext->input->absbit); + input_set_abs_params(ext->input, ABS_RX, -500, 500, 2, 4); + input_set_abs_params(ext->input, ABS_RY, -500, 500, 2, 4); + input_set_abs_params(ext->input, ABS_RZ, -500, 500, 2, 4); + + ret = input_register_device(ext->input); + if (ret) { + input_free_device(ext->input); + goto err_input; + } + + ext->mp_input = input_allocate_device(); + if (!ext->mp_input) { + ret = -ENOMEM; + goto err_mp; + } + + input_set_drvdata(ext->mp_input, ext); + ext->mp_input->open = wiiext_mp_open; + ext->mp_input->close = wiiext_mp_close; + ext->mp_input->dev.parent = &wdata->hdev->dev; + ext->mp_input->id.bustype = wdata->hdev->bus; + ext->mp_input->id.vendor = wdata->hdev->vendor; + ext->mp_input->id.product = wdata->hdev->product; + ext->mp_input->id.version = wdata->hdev->version; + ext->mp_input->name = WIIMOTE_NAME " Motion+"; + + set_bit(EV_ABS, ext->mp_input->evbit); + set_bit(ABS_RX, ext->mp_input->absbit); + set_bit(ABS_RY, ext->mp_input->absbit); + set_bit(ABS_RZ, ext->mp_input->absbit); + input_set_abs_params(ext->mp_input, ABS_RX, -160000, 160000, 4, 8); + input_set_abs_params(ext->mp_input, ABS_RY, -160000, 160000, 4, 8); + input_set_abs_params(ext->mp_input, ABS_RZ, -160000, 160000, 4, 8); + + ret = input_register_device(ext->mp_input); + if (ret) { + input_free_device(ext->mp_input); + goto err_mp; + } + + ret = device_create_file(&wdata->hdev->dev, &dev_attr_extension); + if (ret) + goto err_dev; + + spin_lock_irqsave(&wdata->state.lock, flags); + wdata->ext = ext; + spin_unlock_irqrestore(&wdata->state.lock, flags); + + return 0; + +err_dev: + input_unregister_device(ext->mp_input); +err_mp: + input_unregister_device(ext->input); +err_input: + kfree(ext); + return ret; +} + +/* Deinitializes the extension driver of a wiimote */ +void wiiext_deinit(struct wiimote_data *wdata) +{ + struct wiimote_ext *ext = wdata->ext; + unsigned long flags; + + if (!ext) + return; + + /* + * We first unset wdata->ext to avoid further input from the wiimote + * core. The worker thread does not access this pointer so it is not + * affected by this. + * We kill the worker after this so it does not get respawned during + * deinitialization. + */ + + spin_lock_irqsave(&wdata->state.lock, flags); + wdata->ext = NULL; + spin_unlock_irqrestore(&wdata->state.lock, flags); + + device_remove_file(&wdata->hdev->dev, &dev_attr_extension); + input_unregister_device(ext->mp_input); + input_unregister_device(ext->input); + + cancel_work_sync(&ext->worker); + kfree(ext); +} diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h new file mode 100644 index 00000000..c81dbeb0 --- /dev/null +++ b/drivers/hid/hid-wiimote.h @@ -0,0 +1,208 @@ +#ifndef __HID_WIIMOTE_H +#define __HID_WIIMOTE_H + +/* + * HID driver for Nintendo Wiimote devices + * Copyright (c) 2011 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/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> + +#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_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)) + +struct wiimote_buf { + __u8 data[HID_MAX_BUFFER_SIZE]; + size_t size; +}; + +struct wiimote_state { + spinlock_t lock; + __u8 flags; + __u8 accel_split[2]; + __u8 drm; + + /* 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; +}; + +struct wiimote_data { + struct hid_device *hdev; + struct input_dev *input; + struct led_classdev *leds[4]; + struct input_dev *accel; + struct input_dev *ir; + struct power_supply battery; + struct wiimote_ext *ext; + struct wiimote_debug *debug; + + spinlock_t qlock; + __u8 head; + __u8 tail; + struct wiimote_buf outq[WIIMOTE_BUFSIZE]; + struct work_struct worker; + + struct wiimote_state state; +}; + +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, + WIIPROTO_REQ_DRM_K = 0x30, + WIIPROTO_REQ_DRM_KA = 0x31, + WIIPROTO_REQ_DRM_KE = 0x32, + WIIPROTO_REQ_DRM_KAI = 0x33, + WIIPROTO_REQ_DRM_KEE = 0x34, + WIIPROTO_REQ_DRM_KAE = 0x35, + WIIPROTO_REQ_DRM_KIE = 0x36, + WIIPROTO_REQ_DRM_KAIE = 0x37, + WIIPROTO_REQ_DRM_E = 0x3d, + WIIPROTO_REQ_DRM_SKAI1 = 0x3e, + WIIPROTO_REQ_DRM_SKAI2 = 0x3f, + WIIPROTO_REQ_MAX +}; + +#define dev_to_wii(pdev) hid_get_drvdata(container_of(pdev, struct hid_device, \ + dev)) + +extern void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm); +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_HID_WIIMOTE_EXT + +extern int wiiext_init(struct wiimote_data *wdata); +extern void wiiext_deinit(struct wiimote_data *wdata); +extern void wiiext_event(struct wiimote_data *wdata, bool plugged); +extern bool wiiext_active(struct wiimote_data *wdata); +extern void wiiext_handle(struct wiimote_data *wdata, const __u8 *payload); + +#else + +static inline int wiiext_init(void *u) { return 0; } +static inline void wiiext_deinit(void *u) { } +static inline void wiiext_event(void *u, bool p) { } +static inline bool wiiext_active(void *u) { return false; } +static inline void wiiext_handle(void *u, const __u8 *p) { } + +#endif + +#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); +} + +static inline int wiimote_cmd_acquire(struct wiimote_data *wdata) +{ + return mutex_lock_interruptible(&wdata->state.sync) ? -ERESTARTSYS : 0; +} + +/* requires the state.lock spinlock to be held */ +static inline void wiimote_cmd_set(struct wiimote_data *wdata, int cmd, + __u32 opt) +{ + INIT_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; + + ret = wait_for_completion_interruptible_timeout(&wdata->state.ready, HZ); + if (ret < 0) + return -ERESTARTSYS; + else if (ret == 0) + return -EIO; + else + return 0; +} + +#endif diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c new file mode 100644 index 00000000..f6ba81df --- /dev/null +++ b/drivers/hid/hid-zpff.c @@ -0,0 +1,168 @@ +/* + * 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/usb.h> +#include <linux/module.h> + +#include "hid-ids.h" + +#ifdef CONFIG_ZEROPLUS_FF +#include "usbhid/usbhid.h" + +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); + usbhid_submit_report(hid, zpff->report, USB_DIR_OUT); + + 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 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 < 4) { + hid_err(hid, "not enough fields in report\n"); + return -ENODEV; + } + + zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL); + if (!zpff) + return -ENOMEM; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, zpff, zpff_play); + if (error) { + kfree(zpff); + return error; + } + + zpff->report = report; + zpff->report->field[0]->value[0] = 0x00; + zpff->report->field[1]->value[0] = 0x02; + zpff->report->field[2]->value[0] = 0x00; + zpff->report->field[3]->value[0] = 0x00; + usbhid_submit_report(hid, zpff->report, USB_DIR_OUT); + + 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, +}; + +static int __init zp_init(void) +{ + return hid_register_driver(&zp_driver); +} + +static void __exit zp_exit(void) +{ + hid_unregister_driver(&zp_driver); +} + +module_init(zp_init); +module_exit(zp_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c new file mode 100644 index 00000000..1ad85f22 --- /dev/null +++ b/drivers/hid/hid-zydacron.c @@ -0,0 +1,235 @@ +/* +* 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 = kzalloc(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"); + 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: + kfree(zc); + + return ret; +} + +static void zc_remove(struct hid_device *hdev) +{ + struct zc_device *zc = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + kfree(zc); +} + +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, + .remove = zc_remove, +}; + +static int __init zc_init(void) +{ + return hid_register_driver(&zc_driver); +} + +static void __exit zc_exit(void) +{ + hid_unregister_driver(&zc_driver); +} + +module_init(zc_init); +module_exit(zc_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c new file mode 100644 index 00000000..cf7d6d58 --- /dev/null +++ b/drivers/hid/hidraw.c @@ -0,0 +1,570 @@ +/* + * 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 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 (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + if (!list->hidraw->exist) { + ret = -EIO; + 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 (copy_to_user(buffer, list->buffer[list->tail].value, len)) { + ret = -EFAULT; + goto out; + } + ret = len; + + kfree(list->buffer[list->tail].value); + list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); + } +out: + mutex_unlock(&list->read_mutex); + return ret; +} + +/* The first byte 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->f_path.dentry->d_inode); + struct hid_device *dev; + __u8 *buf; + int ret = 0; + + if (!hidraw_table[minor]) { + ret = -ENODEV; + goto out; + } + + dev = hidraw_table[minor]->hid; + + if (!dev->hid_output_raw_report) { + ret = -ENODEV; + goto out; + } + + 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; + } + + ret = dev->hid_output_raw_report(dev, buf, count, report_type); +out_free: + kfree(buf); +out: + return ret; +} + +/* the first byte is expected to be a report number */ +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->f_path.dentry->d_inode); + struct hid_device *dev; + __u8 *buf; + int ret = 0, len; + unsigned char report_number; + + dev = hidraw_table[minor]->hid; + + if (!dev->hid_get_raw_report) { + 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 dev->hid_get_raw_report(). */ + if (copy_from_user(&report_number, buffer, 1)) { + ret = -EFAULT; + goto out_free; + } + + ret = dev->hid_get_raw_report(dev, report_number, buf, count, report_type); + + 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; + int err = 0; + + if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { + err = -ENOMEM; + goto out; + } + + mutex_lock(&minors_lock); + if (!hidraw_table[minor]) { + err = -ENODEV; + goto out_unlock; + } + + list->hidraw = hidraw_table[minor]; + mutex_init(&list->read_mutex); + list_add_tail(&list->node, &hidraw_table[minor]->list); + file->private_data = list; + + 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--; + } + } + +out_unlock: + mutex_unlock(&minors_lock); +out: + if (err < 0) + kfree(list); + return err; + +} + +static int hidraw_release(struct inode * inode, struct file * file) +{ + unsigned int minor = iminor(inode); + struct hidraw *dev; + struct hidraw_list *list = file->private_data; + int ret; + + mutex_lock(&minors_lock); + if (!hidraw_table[minor]) { + ret = -ENODEV; + goto unlock; + } + + list_del(&list->node); + dev = hidraw_table[minor]; + if (!--dev->open) { + if (list->hidraw->exist) { + hid_hw_power(dev->hid, PM_HINT_NORMAL); + hid_hw_close(dev->hid); + } else { + kfree(list->hidraw); + } + } + kfree(list); + ret = 0; +unlock: + mutex_unlock(&minors_lock); + + return ret; +} + +static long hidraw_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct inode *inode = file->f_path.dentry->d_inode; + 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, +#ifdef CONFIG_COMPAT + .compat_ioctl = hidraw_ioctl, +#endif + .llseek = noop_llseek, +}; + +void hidraw_report_event(struct hid_device *hid, u8 *data, int len) +{ + struct hidraw *dev = hid->hidraw; + struct hidraw_list *list; + + list_for_each_entry(list, &dev->list, node) { + list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC); + list->buffer[list->head].len = len; + list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + + wake_up_interruptible(&dev->wait); +} +EXPORT_SYMBOL_GPL(hidraw_report_event); + +int hidraw_connect(struct hid_device *hid) +{ + int minor, result; + struct hidraw *dev; + + /* we accept any HID device, no matter the 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; + } + + mutex_unlock(&minors_lock); + init_waitqueue_head(&dev->wait); + INIT_LIST_HEAD(&dev->list); + + dev->hid = hid; + dev->minor = minor; + + dev->exist = 1; + hid->hidraw = dev; + +out: + return result; + +} +EXPORT_SYMBOL_GPL(hidraw_connect); + +void hidraw_disconnect(struct hid_device *hid) +{ + struct hidraw *hidraw = hid->hidraw; + + mutex_lock(&minors_lock); + hidraw->exist = 0; + + device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); + + hidraw_table[hidraw->minor] = NULL; + + if (hidraw->open) { + hid_hw_close(hid); + wake_up_interruptible(&hidraw->wait); + } else { + kfree(hidraw); + } + 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"); + result = 0; + goto out; + } + + hidraw_class = class_create(THIS_MODULE, "hidraw"); + if (IS_ERR(hidraw_class)) { + result = PTR_ERR(hidraw_class); + unregister_chrdev(hidraw_major, "hidraw"); + goto out; + } + + cdev_init(&hidraw_cdev, &hidraw_ops); + cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); +out: + return result; +} + +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/uhid.c b/drivers/hid/uhid.c new file mode 100644 index 00000000..714cd8cc --- /dev/null +++ b/drivers/hid/uhid.c @@ -0,0 +1,572 @@ +/* + * User-space I/O driver support for HID subsystem + * Copyright (c) 2012 David Herrmann + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/atomic.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/uhid.h> +#include <linux/wait.h> + +#define UHID_NAME "uhid" +#define UHID_BUFSIZE 32 + +struct uhid_device { + struct mutex devlock; + bool running; + + __u8 *rd_data; + uint rd_size; + + struct hid_device *hid; + struct uhid_event input_buf; + + wait_queue_head_t waitq; + spinlock_t qlock; + __u8 head; + __u8 tail; + struct uhid_event *outq[UHID_BUFSIZE]; + + struct mutex report_lock; + wait_queue_head_t report_wait; + atomic_t report_done; + atomic_t report_id; + struct uhid_event report_buf; +}; + +static struct miscdevice uhid_misc; + +static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev) +{ + __u8 newhead; + + newhead = (uhid->head + 1) % UHID_BUFSIZE; + + if (newhead != uhid->tail) { + uhid->outq[uhid->head] = ev; + uhid->head = newhead; + wake_up_interruptible(&uhid->waitq); + } else { + hid_warn(uhid->hid, "Output queue is full\n"); + kfree(ev); + } +} + +static int uhid_queue_event(struct uhid_device *uhid, __u32 event) +{ + unsigned long flags; + struct uhid_event *ev; + + ev = kzalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) + return -ENOMEM; + + ev->type = event; + + spin_lock_irqsave(&uhid->qlock, flags); + uhid_queue(uhid, ev); + spin_unlock_irqrestore(&uhid->qlock, flags); + + return 0; +} + +static int uhid_hid_start(struct hid_device *hid) +{ + struct uhid_device *uhid = hid->driver_data; + + return uhid_queue_event(uhid, UHID_START); +} + +static void uhid_hid_stop(struct hid_device *hid) +{ + struct uhid_device *uhid = hid->driver_data; + + hid->claimed = 0; + uhid_queue_event(uhid, UHID_STOP); +} + +static int uhid_hid_open(struct hid_device *hid) +{ + struct uhid_device *uhid = hid->driver_data; + + return uhid_queue_event(uhid, UHID_OPEN); +} + +static void uhid_hid_close(struct hid_device *hid) +{ + struct uhid_device *uhid = hid->driver_data; + + uhid_queue_event(uhid, UHID_CLOSE); +} + +static int uhid_hid_input(struct input_dev *input, unsigned int type, + unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(input); + struct uhid_device *uhid = hid->driver_data; + unsigned long flags; + struct uhid_event *ev; + + ev = kzalloc(sizeof(*ev), GFP_ATOMIC); + if (!ev) + return -ENOMEM; + + ev->type = UHID_OUTPUT_EV; + ev->u.output_ev.type = type; + ev->u.output_ev.code = code; + ev->u.output_ev.value = value; + + spin_lock_irqsave(&uhid->qlock, flags); + uhid_queue(uhid, ev); + spin_unlock_irqrestore(&uhid->qlock, flags); + + return 0; +} + +static int uhid_hid_parse(struct hid_device *hid) +{ + struct uhid_device *uhid = hid->driver_data; + + return hid_parse_report(hid, uhid->rd_data, uhid->rd_size); +} + +static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum, + __u8 *buf, size_t count, unsigned char rtype) +{ + struct uhid_device *uhid = hid->driver_data; + __u8 report_type; + struct uhid_event *ev; + unsigned long flags; + int ret; + size_t uninitialized_var(len); + struct uhid_feature_answer_req *req; + + if (!uhid->running) + return -EIO; + + switch (rtype) { + case HID_FEATURE_REPORT: + report_type = UHID_FEATURE_REPORT; + break; + case HID_OUTPUT_REPORT: + report_type = UHID_OUTPUT_REPORT; + break; + case HID_INPUT_REPORT: + report_type = UHID_INPUT_REPORT; + break; + default: + return -EINVAL; + } + + ret = mutex_lock_interruptible(&uhid->report_lock); + if (ret) + return ret; + + ev = kzalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) { + ret = -ENOMEM; + goto unlock; + } + + spin_lock_irqsave(&uhid->qlock, flags); + ev->type = UHID_FEATURE; + ev->u.feature.id = atomic_inc_return(&uhid->report_id); + ev->u.feature.rnum = rnum; + ev->u.feature.rtype = report_type; + + atomic_set(&uhid->report_done, 0); + uhid_queue(uhid, ev); + spin_unlock_irqrestore(&uhid->qlock, flags); + + ret = wait_event_interruptible_timeout(uhid->report_wait, + atomic_read(&uhid->report_done), 5 * HZ); + + /* + * Make sure "uhid->running" is cleared on shutdown before + * "uhid->report_done" is set. + */ + smp_rmb(); + if (!ret || !uhid->running) { + ret = -EIO; + } else if (ret < 0) { + ret = -ERESTARTSYS; + } else { + spin_lock_irqsave(&uhid->qlock, flags); + req = &uhid->report_buf.u.feature_answer; + + if (req->err) { + ret = -EIO; + } else { + ret = 0; + len = min(count, + min_t(size_t, req->size, UHID_DATA_MAX)); + memcpy(buf, req->data, len); + } + + spin_unlock_irqrestore(&uhid->qlock, flags); + } + + atomic_set(&uhid->report_done, 1); + +unlock: + mutex_unlock(&uhid->report_lock); + return ret ? ret : len; +} + +static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count, + unsigned char report_type) +{ + struct uhid_device *uhid = hid->driver_data; + __u8 rtype; + unsigned long flags; + struct uhid_event *ev; + + switch (report_type) { + case HID_FEATURE_REPORT: + rtype = UHID_FEATURE_REPORT; + break; + case HID_OUTPUT_REPORT: + rtype = UHID_OUTPUT_REPORT; + break; + default: + return -EINVAL; + } + + if (count < 1 || count > UHID_DATA_MAX) + return -EINVAL; + + ev = kzalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) + return -ENOMEM; + + ev->type = UHID_OUTPUT; + ev->u.output.size = count; + ev->u.output.rtype = rtype; + memcpy(ev->u.output.data, buf, count); + + spin_lock_irqsave(&uhid->qlock, flags); + uhid_queue(uhid, ev); + spin_unlock_irqrestore(&uhid->qlock, flags); + + return count; +} + +static struct hid_ll_driver uhid_hid_driver = { + .start = uhid_hid_start, + .stop = uhid_hid_stop, + .open = uhid_hid_open, + .close = uhid_hid_close, + .hidinput_input_event = uhid_hid_input, + .parse = uhid_hid_parse, +}; + +static int uhid_dev_create(struct uhid_device *uhid, + const struct uhid_event *ev) +{ + struct hid_device *hid; + int ret; + + if (uhid->running) + return -EALREADY; + + uhid->rd_size = ev->u.create.rd_size; + if (uhid->rd_size <= 0 || uhid->rd_size > HID_MAX_DESCRIPTOR_SIZE) + return -EINVAL; + + uhid->rd_data = kmalloc(uhid->rd_size, GFP_KERNEL); + if (!uhid->rd_data) + return -ENOMEM; + + if (copy_from_user(uhid->rd_data, ev->u.create.rd_data, + uhid->rd_size)) { + ret = -EFAULT; + goto err_free; + } + + hid = hid_allocate_device(); + if (IS_ERR(hid)) { + ret = PTR_ERR(hid); + goto err_free; + } + + strncpy(hid->name, ev->u.create.name, 127); + hid->name[127] = 0; + strncpy(hid->phys, ev->u.create.phys, 63); + hid->phys[63] = 0; + strncpy(hid->uniq, ev->u.create.uniq, 63); + hid->uniq[63] = 0; + + hid->ll_driver = &uhid_hid_driver; + hid->hid_get_raw_report = uhid_hid_get_raw; + hid->hid_output_raw_report = uhid_hid_output_raw; + hid->bus = ev->u.create.bus; + hid->vendor = ev->u.create.vendor; + hid->product = ev->u.create.product; + hid->version = ev->u.create.version; + hid->country = ev->u.create.country; + hid->driver_data = uhid; + hid->dev.parent = uhid_misc.this_device; + + uhid->hid = hid; + uhid->running = true; + + ret = hid_add_device(hid); + if (ret) { + hid_err(hid, "Cannot register HID device\n"); + goto err_hid; + } + + return 0; + +err_hid: + hid_destroy_device(hid); + uhid->hid = NULL; + uhid->running = false; +err_free: + kfree(uhid->rd_data); + return ret; +} + +static int uhid_dev_destroy(struct uhid_device *uhid) +{ + if (!uhid->running) + return -EINVAL; + + /* clear "running" before setting "report_done" */ + uhid->running = false; + smp_wmb(); + atomic_set(&uhid->report_done, 1); + wake_up_interruptible(&uhid->report_wait); + + hid_destroy_device(uhid->hid); + kfree(uhid->rd_data); + + return 0; +} + +static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev) +{ + if (!uhid->running) + return -EINVAL; + + hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, + min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0); + + return 0; +} + +static int uhid_dev_feature_answer(struct uhid_device *uhid, + struct uhid_event *ev) +{ + unsigned long flags; + + if (!uhid->running) + return -EINVAL; + + spin_lock_irqsave(&uhid->qlock, flags); + + /* id for old report; drop it silently */ + if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id) + goto unlock; + if (atomic_read(&uhid->report_done)) + goto unlock; + + memcpy(&uhid->report_buf, ev, sizeof(*ev)); + atomic_set(&uhid->report_done, 1); + wake_up_interruptible(&uhid->report_wait); + +unlock: + spin_unlock_irqrestore(&uhid->qlock, flags); + return 0; +} + +static int uhid_char_open(struct inode *inode, struct file *file) +{ + struct uhid_device *uhid; + + uhid = kzalloc(sizeof(*uhid), GFP_KERNEL); + if (!uhid) + return -ENOMEM; + + mutex_init(&uhid->devlock); + mutex_init(&uhid->report_lock); + spin_lock_init(&uhid->qlock); + init_waitqueue_head(&uhid->waitq); + init_waitqueue_head(&uhid->report_wait); + uhid->running = false; + atomic_set(&uhid->report_done, 1); + + file->private_data = uhid; + nonseekable_open(inode, file); + + return 0; +} + +static int uhid_char_release(struct inode *inode, struct file *file) +{ + struct uhid_device *uhid = file->private_data; + unsigned int i; + + uhid_dev_destroy(uhid); + + for (i = 0; i < UHID_BUFSIZE; ++i) + kfree(uhid->outq[i]); + + kfree(uhid); + + return 0; +} + +static ssize_t uhid_char_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct uhid_device *uhid = file->private_data; + int ret; + unsigned long flags; + size_t len; + + /* they need at least the "type" member of uhid_event */ + if (count < sizeof(__u32)) + return -EINVAL; + +try_again: + if (file->f_flags & O_NONBLOCK) { + if (uhid->head == uhid->tail) + return -EAGAIN; + } else { + ret = wait_event_interruptible(uhid->waitq, + uhid->head != uhid->tail); + if (ret) + return ret; + } + + ret = mutex_lock_interruptible(&uhid->devlock); + if (ret) + return ret; + + if (uhid->head == uhid->tail) { + mutex_unlock(&uhid->devlock); + goto try_again; + } else { + len = min(count, sizeof(**uhid->outq)); + if (copy_to_user(buffer, uhid->outq[uhid->tail], len)) { + ret = -EFAULT; + } else { + kfree(uhid->outq[uhid->tail]); + uhid->outq[uhid->tail] = NULL; + + spin_lock_irqsave(&uhid->qlock, flags); + uhid->tail = (uhid->tail + 1) % UHID_BUFSIZE; + spin_unlock_irqrestore(&uhid->qlock, flags); + } + } + + mutex_unlock(&uhid->devlock); + return ret ? ret : len; +} + +static ssize_t uhid_char_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct uhid_device *uhid = file->private_data; + int ret; + size_t len; + + /* we need at least the "type" member of uhid_event */ + if (count < sizeof(__u32)) + return -EINVAL; + + ret = mutex_lock_interruptible(&uhid->devlock); + if (ret) + return ret; + + memset(&uhid->input_buf, 0, sizeof(uhid->input_buf)); + len = min(count, sizeof(uhid->input_buf)); + if (copy_from_user(&uhid->input_buf, buffer, len)) { + ret = -EFAULT; + goto unlock; + } + + switch (uhid->input_buf.type) { + case UHID_CREATE: + ret = uhid_dev_create(uhid, &uhid->input_buf); + break; + case UHID_DESTROY: + ret = uhid_dev_destroy(uhid); + break; + case UHID_INPUT: + ret = uhid_dev_input(uhid, &uhid->input_buf); + break; + case UHID_FEATURE_ANSWER: + ret = uhid_dev_feature_answer(uhid, &uhid->input_buf); + break; + default: + ret = -EOPNOTSUPP; + } + +unlock: + mutex_unlock(&uhid->devlock); + + /* return "count" not "len" to not confuse the caller */ + return ret ? ret : count; +} + +static unsigned int uhid_char_poll(struct file *file, poll_table *wait) +{ + struct uhid_device *uhid = file->private_data; + + poll_wait(file, &uhid->waitq, wait); + + if (uhid->head != uhid->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static const struct file_operations uhid_fops = { + .owner = THIS_MODULE, + .open = uhid_char_open, + .release = uhid_char_release, + .read = uhid_char_read, + .write = uhid_char_write, + .poll = uhid_char_poll, + .llseek = no_llseek, +}; + +static struct miscdevice uhid_misc = { + .fops = &uhid_fops, + .minor = MISC_DYNAMIC_MINOR, + .name = UHID_NAME, +}; + +static int __init uhid_init(void) +{ + return misc_register(&uhid_misc); +} + +static void __exit uhid_exit(void) +{ + misc_deregister(&uhid_misc); +} + +module_init(uhid_init); +module_exit(uhid_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>"); +MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem"); diff --git a/drivers/hid/usbhid/Kconfig b/drivers/hid/usbhid/Kconfig new file mode 100644 index 00000000..0f20fd17 --- /dev/null +++ b/drivers/hid/usbhid/Kconfig @@ -0,0 +1,84 @@ +comment "USB Input Devices" + depends on USB + +config USB_HID + tristate "USB Human Interface Device (full HID) support" + default y + depends on USB && INPUT + select HID + ---help--- + Say Y here if you want full HID support to connect USB keyboards, + mice, joysticks, graphic tablets, or any other HID based devices + to your computer via USB, as well as Uninterruptible Power Supply + (UPS) and monitor control devices. + + You can't use this driver and the HIDBP (Boot Protocol) keyboard + and mouse drivers at the same time. More information is available: + <file:Documentation/input/input.txt>. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called usbhid. + +comment "Input core support is needed for USB HID input layer or HIDBP support" + depends on USB_HID && INPUT=n + +config 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 + + diff --git a/drivers/hid/usbhid/Makefile b/drivers/hid/usbhid/Makefile new file mode 100644 index 00000000..db3cf31c --- /dev/null +++ b/drivers/hid/usbhid/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for the USB input drivers +# + +# Multipart objects. +usbhid-y := hid-core.o hid-quirks.o + +# Optional parts of multipart objects. + +ifeq ($(CONFIG_USB_HIDDEV),y) + usbhid-y += hiddev.o +endif +ifeq ($(CONFIG_HID_PID),y) + usbhid-y += hid-pidff.o +endif + +obj-$(CONFIG_USB_HID) += usbhid.o +obj-$(CONFIG_USB_KBD) += usbkbd.o +obj-$(CONFIG_USB_MOUSE) += usbmouse.o + diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c new file mode 100644 index 00000000..4bbb883a --- /dev/null +++ b/drivers/hid/usbhid/hid-core.c @@ -0,0 +1,1609 @@ +/* + * 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/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] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL }; +module_param_array_named(quirks, quirks_param, charp, NULL, 0444); +MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying " + " quirks=vendorID:productID:quirks" + " where vendorID, productID, and quirks are all in" + " 0x-prefixed hex"); +/* + * Input submission and I/O error handler. + */ +static 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 && + !test_bit(HID_DISCONNECTED, &usbhid->iofl) && + !test_bit(HID_REPORTED_IDLE, &usbhid->iofl) && + !test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) { + rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC); + if (rc != 0) + clear_bit(HID_IN_RUNNING, &usbhid->iofl); + } + spin_unlock_irqrestore(&usbhid->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 = 0; + + if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) { + dev_dbg(&usbhid->intf->dev, "clear halt\n"); + rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe); + clear_bit(HID_CLEAR_HALT, &usbhid->iofl); + hid_start_in(hid); + } + + else if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) { + dev_dbg(&usbhid->intf->dev, "resetting device\n"); + rc = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf); + if (rc == 0) { + rc = usb_reset_device(hid_to_usb_dev(hid)); + usb_unlock_device(hid_to_usb_dev(hid)); + } + clear_bit(HID_RESET_PENDING, &usbhid->iofl); + } + + switch (rc) { + case 0: + if (!test_bit(HID_IN_RUNNING, &usbhid->iofl)) + hid_io_error(hid); + break; + default: + hid_err(hid, "can't reset device, %s-%s/input%d, status %d\n", + hid_to_usb_dev(hid)->bus->bus_name, + hid_to_usb_dev(hid)->devpath, + usbhid->ifnum, rc); + /* FALLTHROUGH */ + case -EHOSTUNREACH: + case -ENODEV: + case -EINTR: + break; + } +} + +/* Main I/O error handler */ +static void hid_io_error(struct hid_device *hid) +{ + unsigned long flags; + struct usbhid_device *usbhid = hid->driver_data; + + spin_lock_irqsave(&usbhid->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 */ + if (!test_and_set_bit(HID_RESET_PENDING, &usbhid->iofl)) { + schedule_work(&usbhid->reset_work); + goto done; + } + } + + mod_timer(&usbhid->io_retry, + jiffies + msecs_to_jiffies(usbhid->retry_delay)); +done: + spin_unlock_irqrestore(&usbhid->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) + return 0; + + if ((kicked = (usbhid->outhead != usbhid->outtail))) { + dbg("Kicking head %d tail %d", usbhid->outhead, usbhid->outtail); + + r = usb_autopm_get_interface_async(usbhid->intf); + if (r < 0) + 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) + return 0; + + if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) { + dbg("Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail); + + r = usb_autopm_get_interface_async(usbhid->intf); + if (r < 0) + 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; + 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 = ((report->size - 1) >> 3) + + 1 + (report->id > 0); + usbhid->urbout->dev = hid_to_usb_dev(hid); + memcpy(usbhid->outbuf, raw_report, + usbhid->urbout->transfer_buffer_length); + kfree(raw_report); + + 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; + memcpy(usbhid->ctrlbuf, raw_report, len); + kfree(raw_report); + } 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 int irq_out_pump_restart(struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + + if (usbhid->outhead != usbhid->outtail) + return hid_submit_out(hid); + else + return -1; +} + +static void hid_irq_out(struct urb *urb) +{ + struct hid_device *hid = urb->context; + 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 (!irq_out_pump_restart(hid)) { + /* 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 int ctrl_pump_restart(struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + + if (usbhid->ctrlhead != usbhid->ctrltail) + return hid_submit_ctrl(hid); + else + return -1; +} + +static void hid_ctrl(struct urb *urb) +{ + 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 (!ctrl_pump_restart(hid)) { + /* 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; + int len = ((report->size - 1) >> 3) + 1 + (report->id > 0); + + if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) + 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 = kmalloc(len, 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; + + /* Try to awake from autosuspend... */ + if (usb_autopm_get_interface_async(usbhid->intf) < 0) + return; + + /* + * But if still suspended, leave urb enqueued, don't submit. + * Submission will occur if/when resume() drains the queue. + */ + if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) + return; + + if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) { + if (hid_submit_out(hid)) { + clear_bit(HID_OUT_RUNNING, &usbhid->iofl); + usb_autopm_put_interface_async(usbhid->intf); + } + wake_up(&usbhid->wait); + } else { + /* + * the queue is known to run + * but an earlier request may be stuck + * we may need to time out + * no race because the URB is blocked under + * spinlock + */ + if (time_after(jiffies, usbhid->last_out + HZ * 5)) { + usb_block_urb(usbhid->urbout); + /* drop lock to not deadlock if the callback is called */ + spin_unlock(&usbhid->lock); + usb_unlink_urb(usbhid->urbout); + spin_lock(&usbhid->lock); + usb_unblock_urb(usbhid->urbout); + /* + * if the unlinking has already completed + * the pump will have been stopped + * it must be restarted now + */ + if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) + if (!irq_out_pump_restart(hid)) + set_bit(HID_OUT_RUNNING, &usbhid->iofl); + + + } + } + 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 = kmalloc(len, 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; + + /* Try to awake from autosuspend... */ + if (usb_autopm_get_interface_async(usbhid->intf) < 0) + return; + + /* + * If already suspended, leave urb enqueued, but don't submit. + * Submission will occur if/when resume() drains the queue. + */ + if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) + return; + + if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) { + if (hid_submit_ctrl(hid)) { + clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); + usb_autopm_put_interface_async(usbhid->intf); + } + wake_up(&usbhid->wait); + } else { + /* + * the queue is known to run + * but an earlier request may be stuck + * we may need to time out + * no race because the URB is blocked under + * spinlock + */ + if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) { + usb_block_urb(usbhid->urbctrl); + /* drop lock to not deadlock if the callback is called */ + spin_unlock(&usbhid->lock); + usb_unlink_urb(usbhid->urbctrl); + spin_lock(&usbhid->lock); + usb_unblock_urb(usbhid->urbctrl); + /* + * if the unlinking has already completed + * the pump will have been stopped + * it must be restarted now + */ + if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) + if (!ctrl_pump_restart(hid)) + set_bit(HID_CTRL_RUNNING, &usbhid->iofl); + } + } +} + +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); +} +EXPORT_SYMBOL_GPL(usbhid_submit_report); + +/* Workqueue routine to send requests to change LEDs */ +static void hid_led(struct work_struct *work) +{ + struct usbhid_device *usbhid = + container_of(work, struct usbhid_device, led_work); + struct hid_device *hid = usbhid->hid; + struct hid_field *field; + unsigned long flags; + + field = hidinput_get_led_field(hid); + if (!field) { + hid_warn(hid, "LED event field not found\n"); + return; + } + + spin_lock_irqsave(&usbhid->lock, flags); + if (!test_bit(HID_DISCONNECTED, &usbhid->iofl)) { + usbhid->ledcount = hidinput_count_leds(hid); + hid_dbg(usbhid->hid, "New ledcount = %u\n", usbhid->ledcount); + __usbhid_submit_report(hid, field->report, USB_DIR_OUT); + } + spin_unlock_irqrestore(&usbhid->lock, flags); +} + +static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct usbhid_device *usbhid = hid->driver_data; + struct hid_field *field; + unsigned long flags; + 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; + } + + spin_lock_irqsave(&usbhid->lock, flags); + hid_set_field(field, offset, value); + spin_unlock_irqrestore(&usbhid->lock, flags); + + /* + * Defer performing requested LED action. + * This is more likely gather all LED changes into a single URB. + */ + schedule_work(&usbhid->led_work); + + return 0; +} + +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; +} +EXPORT_SYMBOL_GPL(usbhid_wait_io); + +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; + + 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--; + mutex_unlock(&hid_open_mut); + return -EIO; + } + usbhid->intf->needs_remote_wakeup = 1; + if (hid_start_in(hid)) + hid_io_error(hid); + + usb_autopm_put_interface(usbhid->intf); + } + mutex_unlock(&hid_open_mut); + return 0; +} + +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); + 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; + int err, ret; + + list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list) + usbhid_submit_report(hid, report, USB_DIR_IN); + + list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list) + usbhid_submit_report(hid, report, USB_DIR_IN); + + err = 0; + ret = usbhid_wait_io(hid); + while (ret) { + err |= ret; + if (test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) + usb_kill_urb(usbhid->urbctrl); + if (test_bit(HID_OUT_RUNNING, &usbhid->iofl)) + usb_kill_urb(usbhid->urbout); + ret = usbhid_wait_io(hid); + } + + if (err) + 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; +} + +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); + } +} +EXPORT_SYMBOL_GPL(usbhid_set_leds); + +/* + * 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_output_raw_report(struct hid_device *hid, __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 ret; + + if (usbhid->urbout && report_type != HID_FEATURE_REPORT) { + int actual_length; + int skipped_report_id = 0; + + 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++; + } + } else { + int skipped_report_id = 0; + int report_id = buf[0]; + 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, + ((report_type + 1) << 8) | report_id, + 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 void usbhid_restart_queues(struct usbhid_device *usbhid) +{ + if (usbhid->urbout) + usbhid_restart_out_queue(usbhid); + 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); + + /* 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; + + 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 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, + .hidinput_input_event = usb_hidinput_input_event, +}; + +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->hid_get_raw_report = usbhid_get_raw_report; + hid->hid_output_raw_report = usbhid_output_raw_report; + 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); + + INIT_WORK(&usbhid->led_work, hid_led); + + 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; + 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); + cancel_work_sync(&usbhid->led_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; + int status; + + 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_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; + + if (PMSG_IS_AUTO(message)) { + 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) + && (!usbhid->ledcount || ignoreled)) + { + set_bit(HID_REPORTED_IDLE, &usbhid->iofl); + spin_unlock_irq(&usbhid->lock); + if (hid->driver && hid->driver->suspend) { + status = hid->driver->suspend(hid, message); + if (status < 0) + return status; + } + } else { + usbhid_mark_busy(usbhid); + spin_unlock_irq(&usbhid->lock); + return -EBUSY; + } + + } else { + if (hid->driver && hid->driver->suspend) { + status = hid->driver->suspend(hid, message); + if (status < 0) + return status; + } + spin_lock_irq(&usbhid->lock); + set_bit(HID_REPORTED_IDLE, &usbhid->iofl); + spin_unlock_irq(&usbhid->lock); + if (usbhid_wait_io(hid) < 0) + return -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 = hid_start_in(hid); + if (status < 0) + hid_io_error(hid); + usbhid_mark_busy(usbhid); + return -EBUSY; + } + dev_dbg(&intf->dev, "suspend\n"); + return 0; +} + +static int hid_resume(struct usb_interface *intf) +{ + struct hid_device *hid = usb_get_intfdata (intf); + struct usbhid_device *usbhid = hid->driver_data; + int status; + + if (!test_bit(HID_STARTED, &usbhid->iofl)) + return 0; + + clear_bit(HID_REPORTED_IDLE, &usbhid->iofl); + usbhid_mark_busy(usbhid); + + if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) || + test_bit(HID_RESET_PENDING, &usbhid->iofl)) + schedule_work(&usbhid->reset_work); + usbhid->retry_delay = 0; + status = hid_start_in(hid); + if (status < 0) + hid_io_error(hid); + usbhid_restart_queues(usbhid); + + if (status >= 0 && hid->driver && hid->driver->resume) { + int ret = hid->driver->resume(hid); + if (ret < 0) + status = ret; + } + 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_REPORTED_IDLE, &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, +}; + +static const struct hid_device_id hid_usb_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +struct usb_interface *usbhid_find_interface(int minor) +{ + return usb_find_interface(&hid_driver, minor); +} + +static struct hid_driver hid_usb_driver = { + .name = "generic-usb", + .id_table = hid_usb_table, +}; + +static int __init hid_init(void) +{ + int retval = -ENOMEM; + + retval = hid_register_driver(&hid_usb_driver); + if (retval) + goto hid_register_fail; + 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: + hid_unregister_driver(&hid_usb_driver); +hid_register_fail: + return retval; +} + +static void __exit hid_exit(void) +{ + usb_deregister(&hid_driver); + usbhid_quirks_exit(); + hid_unregister_driver(&hid_usb_driver); +} + +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 00000000..f91c1368 --- /dev/null +++ b/drivers/hid/usbhid/hid-pidff.c @@ -0,0 +1,1323 @@ +/* + * 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]); + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_ENVELOPE], + USB_DIR_OUT); +} + +/* + * Test if the new envelope differs from old one + */ +static int pidff_needs_set_envelope(struct ff_envelope *envelope, + struct ff_envelope *old) +{ + return envelope->attack_level != old->attack_level || + envelope->fade_level != old->fade_level || + envelope->attack_length != old->attack_length || + envelope->fade_length != old->fade_length; +} + +/* + * Send constant force report to the device + */ +static void pidff_set_constant_force_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff_set_signed(&pidff->set_constant[PID_MAGNITUDE], + effect->u.constant.level); + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONSTANT], + USB_DIR_OUT); +} + +/* + * Test if the constant parameters have changed between effects + */ +static int pidff_needs_set_constant(struct ff_effect *effect, + struct ff_effect *old) +{ + return effect->u.constant.level != old->u.constant.level; +} + +/* + * Send set effect report to the device + */ +static void pidff_set_effect_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff->set_effect_type->value[0] = + pidff->create_new_effect_type->value[0]; + pidff->set_effect[PID_DURATION].value[0] = effect->replay.length; + pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button; + pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = + effect->trigger.interval; + pidff->set_effect[PID_GAIN].value[0] = + pidff->set_effect[PID_GAIN].field->logical_maximum; + pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1; + pidff->effect_direction->value[0] = + pidff_rescale(effect->direction, 0xffff, + pidff->effect_direction); + pidff->set_effect[PID_START_DELAY].value[0] = effect->replay.delay; + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_EFFECT], + USB_DIR_OUT); +} + +/* + * Test if the values used in set_effect have changed + */ +static int pidff_needs_set_effect(struct ff_effect *effect, + struct ff_effect *old) +{ + return effect->replay.length != old->replay.length || + effect->trigger.interval != old->trigger.interval || + effect->trigger.button != old->trigger.button || + effect->direction != old->direction || + effect->replay.delay != old->replay.delay; +} + +/* + * Send periodic effect report to the device + */ +static void pidff_set_periodic_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_periodic[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff_set_signed(&pidff->set_periodic[PID_MAGNITUDE], + effect->u.periodic.magnitude); + pidff_set_signed(&pidff->set_periodic[PID_OFFSET], + effect->u.periodic.offset); + pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase); + pidff->set_periodic[PID_PERIOD].value[0] = effect->u.periodic.period; + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_PERIODIC], + USB_DIR_OUT); + +} + +/* + * Test if periodic effect parameters have changed + */ +static int pidff_needs_set_periodic(struct ff_effect *effect, + struct ff_effect *old) +{ + return effect->u.periodic.magnitude != old->u.periodic.magnitude || + effect->u.periodic.offset != old->u.periodic.offset || + effect->u.periodic.phase != old->u.periodic.phase || + effect->u.periodic.period != old->u.periodic.period; +} + +/* + * Send condition effect reports to the device + */ +static void pidff_set_condition_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + int i; + + pidff->set_condition[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + + for (i = 0; i < 2; i++) { + pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i; + pidff_set_signed(&pidff->set_condition[PID_CP_OFFSET], + effect->u.condition[i].center); + pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT], + effect->u.condition[i].right_coeff); + pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], + effect->u.condition[i].left_coeff); + pidff_set(&pidff->set_condition[PID_POS_SATURATION], + effect->u.condition[i].right_saturation); + pidff_set(&pidff->set_condition[PID_NEG_SATURATION], + effect->u.condition[i].left_saturation); + pidff_set(&pidff->set_condition[PID_DEAD_BAND], + effect->u.condition[i].deadband); + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION], + USB_DIR_OUT); + } +} + +/* + * Test if condition effect parameters have changed + */ +static int pidff_needs_set_condition(struct ff_effect *effect, + struct ff_effect *old) +{ + int i; + int ret = 0; + + for (i = 0; i < 2; i++) { + struct ff_condition_effect *cond = &effect->u.condition[i]; + struct ff_condition_effect *old_cond = &old->u.condition[i]; + + ret |= cond->center != old_cond->center || + cond->right_coeff != old_cond->right_coeff || + cond->left_coeff != old_cond->left_coeff || + cond->right_saturation != old_cond->right_saturation || + cond->left_saturation != old_cond->left_saturation || + cond->deadband != old_cond->deadband; + } + + return ret; +} + +/* + * Send ramp force report to the device + */ +static void pidff_set_ramp_force_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff_set_signed(&pidff->set_ramp[PID_RAMP_START], + effect->u.ramp.start_level); + pidff_set_signed(&pidff->set_ramp[PID_RAMP_END], + effect->u.ramp.end_level); + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_RAMP], + USB_DIR_OUT); +} + +/* + * Test if ramp force parameters have changed + */ +static int pidff_needs_set_ramp(struct ff_effect *effect, struct ff_effect *old) +{ + return effect->u.ramp.start_level != old->u.ramp.start_level || + effect->u.ramp.end_level != old->u.ramp.end_level; +} + +/* + * Send a request for effect upload to the device + * + * Returns 0 if device reported success, -ENOSPC if the device reported memory + * is full. Upon unknown response the function will retry for 60 times, if + * still unsuccessful -EIO is returned. + */ +static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) +{ + int j; + + pidff->create_new_effect_type->value[0] = efnum; + usbhid_submit_report(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT], + USB_DIR_OUT); + 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; + usbhid_wait_io(pidff->hid); + + for (j = 0; j < 60; j++) { + hid_dbg(pidff->hid, "pid_block_load requested\n"); + usbhid_submit_report(pidff->hid, pidff->reports[PID_BLOCK_LOAD], + USB_DIR_IN); + usbhid_wait_io(pidff->hid); + if (pidff->block_load_status->value[0] == + pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) { + 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; + } + + usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], + USB_DIR_OUT); +} + +/** + * Play the effect with effect id @effect_id for @value times + */ +static int pidff_playback(struct input_dev *dev, int effect_id, int value) +{ + struct pidff_device *pidff = dev->ff->private; + + pidff_playback_pid(pidff, pidff->pid_id[effect_id], value); + + return 0; +} + +/* + * Erase effect with PID id + */ +static void pidff_erase_pid(struct pidff_device *pidff, int pid_id) +{ + pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; + usbhid_submit_report(pidff->hid, pidff->reports[PID_BLOCK_FREE], + USB_DIR_OUT); +} + +/* + * Stop and erase effect with effect_id + */ +static int pidff_erase_effect(struct input_dev *dev, int effect_id) +{ + struct pidff_device *pidff = dev->ff->private; + int pid_id = pidff->pid_id[effect_id]; + + 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. */ + usbhid_wait_io(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; + + 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); + usbhid_submit_report(pidff->hid, pidff->reports[PID_DEVICE_GAIN], + USB_DIR_OUT); +} + +static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude) +{ + struct hid_field *field = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field; + + if (!magnitude) { + pidff_playback_pid(pidff, field->logical_minimum, 0); + return; + } + + pidff_playback_pid(pidff, field->logical_minimum, 1); + + pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum; + pidff->set_effect_type->value[0] = pidff->type_id[PID_SPRING]; + pidff->set_effect[PID_DURATION].value[0] = 0; + pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = 0; + pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0; + pidff_set(&pidff->set_effect[PID_GAIN], magnitude); + pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1; + pidff->set_effect[PID_START_DELAY].value[0] = 0; + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_EFFECT], + USB_DIR_OUT); +} + +/* + * pidff_set_autocenter() handler + */ +static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct pidff_device *pidff = dev->ff->private; + + pidff_autocenter(pidff, magnitude); +} + +/* + * Find fields from a report and fill a pidff_usage + */ +static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, + struct hid_report *report, int count, int strict) +{ + int i, j, k, found; + + for (k = 0; k < count; k++) { + found = 0; + for (i = 0; i < report->maxfield; i++) { + if (report->field[i]->maxusage != + report->field[i]->report_count) { + 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 */ + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT); + usbhid_wait_io(hid); + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT); + usbhid_wait_io(hid); + + pidff->device_control->value[0] = + pidff->control_id[PID_ENABLE_ACTUATORS]; + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT); + usbhid_wait_io(hid); + + /* pool report is sometimes messed up, refetch it */ + usbhid_submit_report(hid, pidff->reports[PID_POOL], USB_DIR_IN); + usbhid_wait_io(hid); + + if (pidff->pool[PID_SIMULTANEOUS_MAX].value) { + 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"); + usbhid_submit_report(hid, pidff->reports[PID_POOL], + USB_DIR_IN); + usbhid_wait_io(hid); + } + } +} + +/* + * Test if autocenter modification is using the supported method + */ +static int pidff_check_autocenter(struct pidff_device *pidff, + struct input_dev *dev) +{ + int error; + + /* + * Let's find out if autocenter modification is supported + * Specification doesn't specify anything, so we request an + * effect upload and cancel it immediately. If the approved + * effect id was one above the minimum, then we assume the first + * effect id is a built-in spring type effect used for autocenter + */ + + error = pidff_request_effect_upload(pidff, 1); + if (error) { + 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; + + 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); + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_GAIN], + USB_DIR_OUT); + } + + error = pidff_check_autocenter(pidff, dev); + if (error) + goto fail; + + max_effects = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_maximum - + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum + + 1; + 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"); + + return 0; + + fail: + 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 00000000..782c6395 --- /dev/null +++ b/drivers/hid/usbhid/hid-quirks.c @@ -0,0 +1,331 @@ +/* + * 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_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_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_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET }, + { 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_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008, 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_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_PF1209, HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U, HID_QUIRK_MULTI_INPUT }, + { 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_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U, HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U, 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_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_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT }, + { 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 00000000..b1ec0e2a --- /dev/null +++ b/drivers/hid/usbhid/hiddev.c @@ -0,0 +1,938 @@ +/* + * 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 "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); + kfree(list); + return 0; + } + } + + mutex_unlock(&list->hiddev->existancelock); + kfree(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 = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) + 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; + kfree(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 (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (!list->hiddev->exist) { + retval = -EIO; + 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 < 0 || 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; + + usbhid_submit_report(hid, report, USB_DIR_IN); + usbhid_wait_io(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; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + usbhid_wait_io(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 00000000..cb8f703e --- /dev/null +++ b/drivers/hid/usbhid/usbhid.h @@ -0,0 +1,107 @@ +#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 */ +int usbhid_wait_io(struct hid_device* hid); +void usbhid_close(struct hid_device *hid); +int usbhid_open(struct hid_device *hid); +void usbhid_init_reports(struct hid_device *hid); +void usbhid_submit_report +(struct hid_device *hid, struct hid_report *report, unsigned char dir); +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_REPORTED_IDLE 9 +#define HID_KEYS_PRESSED 10 + +/* + * 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 */ + int ledcount; /* counting the number of active leds */ + + struct work_struct led_work; /* Task context for setting LEDs */ +}; + +#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 00000000..79608698 --- /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) released.\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 00000000..0f6be45d --- /dev/null +++ b/drivers/hid/usbhid/usbmouse.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * USB HIDBP Mouse support + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <linux/hid.h> + +/* for apple IDs */ +#ifdef CONFIG_USB_HID_MODULE +#include "../hid-ids.h" +#endif + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.6" +#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>" +#define DRIVER_DESC "USB HID Boot Protocol mouse driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +struct usb_mouse { + char name[128]; + char phys[64]; + struct usb_device *usbdev; + struct input_dev *dev; + struct urb *irq; + + signed char *data; + dma_addr_t data_dma; +}; + +static void usb_mouse_irq(struct urb *urb) +{ + struct usb_mouse *mouse = urb->context; + signed char *data = mouse->data; + struct input_dev *dev = mouse->dev; + int status; + + switch (urb->status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + return; + /* -EPIPE: should clear the halt */ + default: /* error */ + goto resubmit; + } + + input_report_key(dev, BTN_LEFT, data[0] & 0x01); + input_report_key(dev, BTN_RIGHT, data[0] & 0x02); + input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); + input_report_key(dev, BTN_SIDE, data[0] & 0x08); + input_report_key(dev, BTN_EXTRA, data[0] & 0x10); + + input_report_rel(dev, REL_X, data[1]); + input_report_rel(dev, REL_Y, data[2]); + input_report_rel(dev, REL_WHEEL, data[3]); + + input_sync(dev); +resubmit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("can't resubmit intr, %s-%s/input0, status %d", + mouse->usbdev->bus->bus_name, + mouse->usbdev->devpath, status); +} + +static int usb_mouse_open(struct input_dev *dev) +{ + struct usb_mouse *mouse = 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); |