This commit is contained in:
useyourtruth 2022-07-14 22:29:26 +03:00
parent 63eb5f9c6c
commit 977e5217b6
4786 changed files with 2630622 additions and 0 deletions

226
drivers/Kconfig Executable file
View File

@ -0,0 +1,226 @@
menu "Device Drivers"
source "drivers/amba/Kconfig"
source "drivers/base/Kconfig"
source "drivers/bus/Kconfig"
source "drivers/connector/Kconfig"
source "drivers/mtd/Kconfig"
source "drivers/of/Kconfig"
source "drivers/parport/Kconfig"
source "drivers/pnp/Kconfig"
source "drivers/block/Kconfig"
source "drivers/nvme/Kconfig"
# misc before ide - BLK_DEV_SGIIOC4 depends on SGI_IOC4
source "drivers/misc/Kconfig"
source "drivers/ide/Kconfig"
source "drivers/scsi/Kconfig"
source "drivers/ata/Kconfig"
source "drivers/md/Kconfig"
source "drivers/target/Kconfig"
source "drivers/message/fusion/Kconfig"
source "drivers/firewire/Kconfig"
source "drivers/macintosh/Kconfig"
source "drivers/net/Kconfig"
source "drivers/isdn/Kconfig"
source "drivers/lightnvm/Kconfig"
# input before char - char/joystick depends on it. As does USB.
source "drivers/input/Kconfig"
source "drivers/char/Kconfig"
source "drivers/i2c/Kconfig"
source "drivers/spi/Kconfig"
source "drivers/spmi/Kconfig"
source "drivers/hsi/Kconfig"
source "drivers/pps/Kconfig"
source "drivers/ptp/Kconfig"
source "drivers/pinctrl/Kconfig"
source "drivers/gpio/Kconfig"
source "drivers/w1/Kconfig"
source "drivers/power/Kconfig"
source "drivers/hwmon/Kconfig"
source "drivers/thermal/Kconfig"
source "drivers/watchdog/Kconfig"
source "drivers/ssb/Kconfig"
source "drivers/bcma/Kconfig"
source "drivers/mfd/Kconfig"
source "drivers/regulator/Kconfig"
source "drivers/media/Kconfig"
source "drivers/video/Kconfig"
source "sound/Kconfig"
source "drivers/hid/Kconfig"
source "drivers/usb/Kconfig"
source "drivers/uwb/Kconfig"
source "drivers/mmc/Kconfig"
source "drivers/memstick/Kconfig"
source "drivers/leds/Kconfig"
source "drivers/accessibility/Kconfig"
source "drivers/infiniband/Kconfig"
source "drivers/edac/Kconfig"
source "drivers/rtc/Kconfig"
source "drivers/dma/Kconfig"
source "drivers/dca/Kconfig"
source "drivers/auxdisplay/Kconfig"
source "drivers/uio/Kconfig"
source "drivers/vfio/Kconfig"
source "drivers/vlynq/Kconfig"
source "drivers/virt/Kconfig"
source "drivers/virtio/Kconfig"
source "drivers/hv/Kconfig"
source "drivers/xen/Kconfig"
source "drivers/staging/Kconfig"
source "drivers/platform/Kconfig"
source "drivers/clk/Kconfig"
source "drivers/hwspinlock/Kconfig"
source "drivers/clocksource/Kconfig"
source "drivers/mailbox/Kconfig"
source "drivers/iommu/Kconfig"
source "drivers/remoteproc/Kconfig"
source "drivers/rpmsg/Kconfig"
source "drivers/soc/Kconfig"
source "drivers/devfreq/Kconfig"
source "drivers/extcon/Kconfig"
source "drivers/memory/Kconfig"
source "drivers/iio/Kconfig"
source "drivers/ntb/Kconfig"
source "drivers/vme/Kconfig"
source "drivers/pwm/Kconfig"
source "drivers/irqchip/Kconfig"
source "drivers/ipack/Kconfig"
source "drivers/reset/Kconfig"
source "drivers/fmc/Kconfig"
source "drivers/phy/Kconfig"
source "drivers/powercap/Kconfig"
source "drivers/mcb/Kconfig"
source "drivers/perf/Kconfig"
source "drivers/ras/Kconfig"
source "drivers/thunderbolt/Kconfig"
source "drivers/android/Kconfig"
source "drivers/nvdimm/Kconfig"
source "drivers/nvmem/Kconfig"
source "drivers/hwtracing/stm/Kconfig"
source "drivers/hwtracing/intel_th/Kconfig"
source "drivers/fpga/Kconfig"
source "drivers/cpuhotplug/Kconfig"
source "drivers/trusty/Kconfig"
source "drivers/tee/Kconfig"
source "drivers/udc/Kconfig"
config ZYT_ALSPS_LIST
string "ZYT_ALSPS_LIST"
help
To choose kernel ALSPS driver name
config ZYT_GSENSOR_LIST
string "ZYT_GSENSOR_LIST"
help
To choose kernel GSENSOR driver name
config ZYT_TP_LIST
string "ZYT_TP_LIST"
help
To choose kernel TP driver name
config ZYT_LCM_LIST
string "ZYT_LCM_LIST"
help
To choose kernel LCM driver name
endmenu

180
drivers/Makefile Executable file
View File

@ -0,0 +1,180 @@
#
# Makefile for the Linux kernel device drivers.
#
# 15 Sep 2000, Christoph Hellwig <hch@infradead.org>
# Rewritten to use lists instead of if-statements.
#
obj-y += irqchip/
obj-y += bus/
obj-y += zyt_info/
obj-$(CONFIG_UDC) += udc/
obj-$(CONFIG_GENERIC_PHY) += phy/
# GPIO must come after pinctrl as gpios may need to mux pins etc
obj-$(CONFIG_PINCTRL) += pinctrl/
obj-y += gpio/
obj-y += pwm/
obj-$(CONFIG_PCI) += pci/
obj-$(CONFIG_PARISC) += parisc/
obj-$(CONFIG_RAPIDIO) += rapidio/
obj-y += video/
obj-y += idle/
# IPMI must come before ACPI in order to provide IPMI opregion support
obj-$(CONFIG_IPMI_HANDLER) += char/ipmi/
obj-$(CONFIG_ACPI) += acpi/
obj-$(CONFIG_SFI) += sfi/
# PnP must come after ACPI since it will eventually need to check if acpi
# was used and do nothing if so
obj-$(CONFIG_PNP) += pnp/
obj-y += amba/
# Many drivers will want to use DMA so this has to be made available
# really early.
obj-$(CONFIG_DMADEVICES) += dma/
# SOC specific infrastructure drivers.
obj-y += soc/
obj-$(CONFIG_VIRTIO) += virtio/
obj-$(CONFIG_XEN) += xen/
# regulators early, since some subsystems rely on them to initialize
obj-$(CONFIG_REGULATOR) += regulator/
# reset controllers early, since gpu drivers might rely on them to initialize
obj-$(CONFIG_RESET_CONTROLLER) += reset/
# tty/ comes before char/ so that the VT console is the boot-time
# default.
obj-y += tty/
obj-y += char/
# iommu/ comes before gpu as gpu are using iommu controllers
obj-$(CONFIG_IOMMU_SUPPORT) += iommu/
# gpu/ comes after char for AGP vs DRM startup and after iommu
obj-y += gpu/
obj-$(CONFIG_CONNECTOR) += connector/
# i810fb and intelfb depend on char/agp/
obj-$(CONFIG_FB_I810) += video/fbdev/i810/
obj-$(CONFIG_FB_INTEL) += video/fbdev/intelfb/
obj-$(CONFIG_PARPORT) += parport/
obj-$(CONFIG_NVM) += lightnvm/
obj-y += base/ block/ misc/ mfd/ nfc/
obj-$(CONFIG_LIBNVDIMM) += nvdimm/
obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf/
obj-$(CONFIG_NUBUS) += nubus/
obj-y += macintosh/
obj-$(CONFIG_IDE) += ide/
obj-$(CONFIG_SCSI) += scsi/
obj-y += nvme/
obj-$(CONFIG_ATA) += ata/
obj-$(CONFIG_TARGET_CORE) += target/
obj-$(CONFIG_MTD) += mtd/
obj-$(CONFIG_SPI) += spi/
obj-$(CONFIG_SPMI) += spmi/
obj-y += hsi/
obj-y += net/
obj-$(CONFIG_ATM) += atm/
obj-$(CONFIG_FUSION) += message/
obj-y += firewire/
obj-$(CONFIG_UIO) += uio/
obj-$(CONFIG_VFIO) += vfio/
obj-y += cdrom/
obj-y += auxdisplay/
obj-$(CONFIG_PCCARD) += pcmcia/
obj-$(CONFIG_DIO) += dio/
obj-$(CONFIG_SBUS) += sbus/
obj-$(CONFIG_ZORRO) += zorro/
obj-$(CONFIG_ATA_OVER_ETH) += block/aoe/
obj-$(CONFIG_PARIDE) += block/paride/
obj-$(CONFIG_TC) += tc/
obj-$(CONFIG_UWB) += uwb/
obj-$(CONFIG_USB_PHY) += usb/
obj-$(CONFIG_USB) += usb/
obj-$(CONFIG_USB_SUPPORT) += usb/
obj-$(CONFIG_PCI) += usb/
obj-$(CONFIG_USB_GADGET) += usb/
obj-$(CONFIG_OF) += usb/
obj-$(CONFIG_SERIO) += input/serio/
obj-$(CONFIG_GAMEPORT) += input/gameport/
obj-$(CONFIG_INPUT) += input/
obj-$(CONFIG_RTC_LIB) += rtc/
obj-y += i2c/ media/
obj-$(CONFIG_PPS) += pps/
obj-$(CONFIG_PTP_1588_CLOCK) += ptp/
obj-$(CONFIG_W1) += w1/
obj-$(CONFIG_POWER_SUPPLY) += power/
obj-$(CONFIG_HWMON) += hwmon/
obj-$(CONFIG_THERMAL) += thermal/
obj-$(CONFIG_WATCHDOG) += watchdog/
obj-$(CONFIG_MD) += md/
obj-$(CONFIG_BT) += bluetooth/
obj-$(CONFIG_ACCESSIBILITY) += accessibility/
obj-$(CONFIG_ISDN) += isdn/
obj-$(CONFIG_EDAC) += edac/
obj-$(CONFIG_EISA) += eisa/
obj-y += lguest/
obj-$(CONFIG_CPU_FREQ) += cpufreq/
obj-y += cpuhotplug/
obj-$(CONFIG_CPU_IDLE) += cpuidle/
obj-y += mmc/
obj-$(CONFIG_MEMSTICK) += memstick/
obj-y += leds/
obj-$(CONFIG_INFINIBAND) += infiniband/
obj-$(CONFIG_SGI_SN) += sn/
obj-y += firmware/
obj-$(CONFIG_CRYPTO) += crypto/
obj-$(CONFIG_SUPERH) += sh/
obj-$(CONFIG_ARCH_SHMOBILE) += sh/
ifndef CONFIG_ARCH_USES_GETTIMEOFFSET
obj-y += clocksource/
endif
obj-$(CONFIG_DCA) += dca/
obj-$(CONFIG_HID) += hid/
obj-$(CONFIG_PPC_PS3) += ps3/
obj-$(CONFIG_OF) += of/
obj-$(CONFIG_SSB) += ssb/
obj-$(CONFIG_BCMA) += bcma/
obj-$(CONFIG_VHOST_RING) += vhost/
obj-$(CONFIG_VLYNQ) += vlynq/
obj-$(CONFIG_STAGING) += staging/
obj-y += platform/
#common clk code
obj-y += clk/
obj-$(CONFIG_MAILBOX) += mailbox/
obj-$(CONFIG_HWSPINLOCK) += hwspinlock/
obj-$(CONFIG_REMOTEPROC) += remoteproc/
obj-$(CONFIG_RPMSG) += rpmsg/
# Virtualization drivers
obj-$(CONFIG_VIRT_DRIVERS) += virt/
obj-$(CONFIG_HYPERV) += hv/
obj-$(CONFIG_PM_DEVFREQ) += devfreq/
obj-$(CONFIG_EXTCON) += extcon/
obj-$(CONFIG_MEMORY) += memory/
obj-$(CONFIG_IIO) += iio/
obj-$(CONFIG_VME_BUS) += vme/
obj-$(CONFIG_IPACK_BUS) += ipack/
obj-$(CONFIG_NTB) += ntb/
obj-$(CONFIG_FMC) += fmc/
obj-$(CONFIG_POWERCAP) += powercap/
obj-$(CONFIG_MCB) += mcb/
obj-$(CONFIG_PERF_EVENTS) += perf/
obj-$(CONFIG_RAS) += ras/
obj-$(CONFIG_THUNDERBOLT) += thunderbolt/
obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/
obj-y += hwtracing/intel_th/
obj-$(CONFIG_STM) += hwtracing/stm/
obj-$(CONFIG_ANDROID) += android/
obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
obj-$(CONFIG_TRUSTY) += trusty/
obj-$(CONFIG_TEE) += tee/

931
drivers/hid/Kconfig Executable file
View File

@ -0,0 +1,931 @@
#
# HID driver configuration
#
menu "HID support"
depends on INPUT
config HID
tristate "HID bus support"
depends on INPUT
default y
---help---
A human interface device (HID) is a type of computer device that
interacts directly with and takes input from humans. The term "HID"
most commonly used to refer to the USB-HID specification, but other
devices (such as, but not strictly limited to, Bluetooth) are
designed using HID specification (this involves certain keyboards,
mice, tablets, etc). This option adds the HID bus to the kernel,
together with generic HID layer code. The HID devices are added and
removed from the HID bus by the transport-layer drivers, such as
usbhid (USB_HID) and hidp (BT_HIDP).
For docs and specs, see http://www.usb.org/developers/hidpage/
If unsure, say Y.
if HID
config HID_BATTERY_STRENGTH
bool "Battery level reporting for HID devices"
depends on HID
select POWER_SUPPLY
default n
---help---
This option adds support of reporting battery strength (for HID devices
that support this feature) through power_supply class so that userspace
tools, such as upower, can display it.
config HIDRAW
bool "/dev/hidraw raw HID device support"
depends on HID
---help---
Say Y here if you want to support HID devices (from the USB
specification standpoint) that aren't strictly user interface
devices, like monitor controls and Uninterruptable Power Supplies.
This module supports these devices separately using a separate
event interface on /dev/hidraw.
There is also a /dev/hiddev configuration option in the USB HID
configuration menu. In comparison to hiddev, this device does not process
the hid events at all (no parsing, no lookups). This lets applications
to work on raw hid events when they want to, and avoid using transport-specific
userspace libhid/libusb libraries.
If unsure, say Y.
config UHID
tristate "User-space I/O driver support for HID subsystem"
depends on HID
default n
---help---
Say Y here if you want to provide HID I/O Drivers from user-space.
This allows to write I/O drivers in user-space and feed the data from
the device into the kernel. The kernel parses the HID reports, loads the
corresponding HID Device Driver or provides input devices on top of your
user-space device.
This driver cannot be used to parse HID-reports in user-space and write
special HID-drivers. You should use hidraw for that.
Instead, this driver allows to write the transport-layer driver in
user-space like USB-HID and Bluetooth-HID do in kernel-space.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called uhid.
config HID_GENERIC
tristate "Generic HID driver"
depends on HID
default HID
---help---
Support for generic devices on the HID bus. This includes most
keyboards and mice, joysticks, tablets and digitizers.
To compile this driver as a module, choose M here: the module
will be called hid-generic.
If unsure, say Y.
menu "Special HID drivers"
depends on HID
config HID_A4TECH
tristate "A4 tech mice"
depends on HID
default !EXPERT
---help---
Support for A4 tech X5 and WOP-35 / Trust 450L mice.
config HID_ACRUX
tristate "ACRUX game controller support"
depends on HID
---help---
Say Y here if you want to enable support for ACRUX game controllers.
config HID_ACRUX_FF
bool "ACRUX force feedback support"
depends on HID_ACRUX
select INPUT_FF_MEMLESS
---help---
Say Y here if you want to enable force feedback support for ACRUX
game controllers.
config HID_APPLE
tristate "Apple {i,Power,Mac}Books"
depends on HID
default !EXPERT
---help---
Support for some Apple devices which less or more break
HID specification.
Say Y here if you want support for keyboards of Apple iBooks, PowerBooks,
MacBooks, MacBook Pros and Apple Aluminum.
config HID_APPLEIR
tristate "Apple infrared receiver"
depends on (USB_HID)
---help---
Support for Apple infrared remote control. All the Apple computers from
2005 onwards include such a port, except the unibody Macbook (2009),
and Mac Pros. This receiver is also used in the Apple TV set-top box
prior to the 2010 model.
Say Y here if you want support for Apple infrared remote control.
config HID_AUREAL
tristate "Aureal"
depends on HID
---help---
Support for Aureal Cy se W-01RN Remote Controller and other Aureal derived remotes.
config HID_BELKIN
tristate "Belkin Flip KVM and Wireless keyboard"
depends on HID
default !EXPERT
---help---
Support for Belkin Flip KVM and Wireless keyboard.
config HID_BETOP_FF
tristate "Betop Production Inc. force feedback support"
depends on USB_HID
select INPUT_FF_MEMLESS
---help---
Say Y here if you want to enable force feedback support for devices by
BETOP Production Ltd.
Currently the following devices are known to be supported:
- BETOP 2185 PC & BFM MODE
config HID_CHERRY
tristate "Cherry Cymotion keyboard"
depends on HID
default !EXPERT
---help---
Support for Cherry Cymotion keyboard.
config HID_CHICONY
tristate "Chicony devices"
depends on HID
default !EXPERT
---help---
Support for Chicony Tactical pad and special keys on Chicony keyboards.
config HID_CORSAIR
tristate "Corsair devices"
depends on HID && USB && LEDS_CLASS
---help---
Support for Corsair devices that are not fully compliant with the
HID standard.
Supported devices:
- Vengeance K90
config HID_PRODIKEYS
tristate "Prodikeys PC-MIDI Keyboard support"
depends on HID && SND
select SND_RAWMIDI
---help---
Support for Prodikeys PC-MIDI Keyboard device support.
Say Y here to enable support for this device.
- Prodikeys PC-MIDI keyboard.
The Prodikeys PC-MIDI acts as a USB Audio device, with one MIDI
input and one MIDI output. These MIDI jacks appear as
a sound "card" in the ALSA sound system.
Note: if you say N here, this device will still function as a basic
multimedia keyboard, but will lack support for the musical keyboard
and some additional multimedia keys.
config HID_CP2112
tristate "Silicon Labs CP2112 HID USB-to-SMBus Bridge support"
depends on USB_HID && I2C && GPIOLIB
---help---
Support for Silicon Labs CP2112 HID USB to SMBus Master Bridge.
This is a HID device driver which registers as an i2c adapter
and gpiochip to expose these functions of the CP2112. The
customizable USB descriptor fields are exposed as sysfs attributes.
config HID_CYPRESS
tristate "Cypress mouse and barcode readers"
depends on HID
default !EXPERT
---help---
Support for cypress mouse and barcode readers.
config HID_DRAGONRISE
tristate "DragonRise Inc. game controller"
depends on HID
---help---
Say Y here if you have DragonRise Inc. game controllers.
These might be branded as:
- Tesun USB-703
- Media-tech MT1504 "Rogue"
- DVTech JS19 "Gear"
- Defender Game Master
config DRAGONRISE_FF
bool "DragonRise Inc. force feedback"
depends on HID_DRAGONRISE
select INPUT_FF_MEMLESS
---help---
Say Y here if you want to enable force feedback support for DragonRise Inc.
game controllers.
config HID_EMS_FF
tristate "EMS Production Inc. force feedback support"
depends on HID
select INPUT_FF_MEMLESS
---help---
Say Y here if you want to enable force feedback support for devices by
EMS Production Ltd.
Currently the following devices are known to be supported:
- Trio Linker Plus II
config HID_ELECOM
tristate "ELECOM BM084 bluetooth mouse"
depends on HID
---help---
Support for the ELECOM BM084 (bluetooth mouse).
config HID_ELO
tristate "ELO USB 4000/4500 touchscreen"
depends on USB_HID
---help---
Support for the ELO USB 4000/4500 touchscreens. Note that this is for
different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO.
config HID_EZKEY
tristate "Ezkey BTC 8193 keyboard"
depends on HID
default !EXPERT
---help---
Support for Ezkey BTC 8193 keyboard.
config HID_GEMBIRD
tristate "Gembird Joypad"
depends on HID
---help---
Support for Gembird JPD-DualForce 2.
config HID_GFRM
tristate "Google Fiber TV Box remote control support"
depends on HID
---help---
Support for Google Fiber TV Box remote controls
config HID_HOLTEK
tristate "Holtek HID devices"
depends on USB_HID
---help---
Support for Holtek based devices:
- Holtek On Line Grip based game controller
- Trust GXT 18 Gaming Keyboard
- Sharkoon Drakonia / Perixx MX-2000 gaming mice
- Tracer Sniper TRM-503 / NOVA Gaming Slider X200 /
Zalman ZM-GM1
- SHARKOON DarkGlider Gaming mouse
- LEETGION Hellion Gaming Mouse
config HOLTEK_FF
bool "Holtek On Line Grip force feedback support"
depends on HID_HOLTEK
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a Holtek On Line Grip based game controller
and want to have force feedback support for it.
config HID_GT683R
tristate "MSI GT68xR LED support"
depends on LEDS_CLASS && USB_HID
---help---
Say Y here if you want to enable support for the three MSI GT68xR LEDs
This driver support following modes:
- Normal: LEDs are fully on when enabled
- Audio: LEDs brightness depends on sound level
- Breathing: LEDs brightness varies at human breathing rate
Currently the following devices are know to be supported:
- MSI GT683R
config HID_KEYTOUCH
tristate "Keytouch HID devices"
depends on HID
---help---
Support for Keytouch HID devices not fully compliant with
the specification. Currently supported:
- Keytouch IEC 60945
config HID_KYE
tristate "KYE/Genius devices"
depends on HID
---help---
Support for KYE/Genius devices not fully compliant with HID standard:
- Ergo Mouse
- EasyPen i405X tablet
- MousePen i608X tablet
- EasyPen M610X tablet
config HID_UCLOGIC
tristate "UC-Logic"
depends on USB_HID
---help---
Support for UC-Logic and Huion tablets.
config HID_WALTOP
tristate "Waltop"
depends on HID
---help---
Support for Waltop tablets.
config HID_GYRATION
tristate "Gyration remote control"
depends on HID
---help---
Support for Gyration remote control.
config HID_ICADE
tristate "ION iCade arcade controller"
depends on HID
---help---
Support for the ION iCade arcade controller to work as a joystick.
To compile this driver as a module, choose M here: the
module will be called hid-icade.
config HID_TWINHAN
tristate "Twinhan IR remote control"
depends on HID
---help---
Support for Twinhan IR remote control.
config HID_KENSINGTON
tristate "Kensington Slimblade Trackball"
depends on HID
default !EXPERT
---help---
Support for Kensington Slimblade Trackball.
config HID_LCPOWER
tristate "LC-Power"
depends on HID
---help---
Support for LC-Power RC1000MCE RF remote control.
config HID_LENOVO
tristate "Lenovo / Thinkpad devices"
depends on HID
select NEW_LEDS
select LEDS_CLASS
---help---
Support for Lenovo devices that are not fully compliant with HID standard.
Say Y if you want support for the non-compliant features of the Lenovo
Thinkpad standalone keyboards, e.g:
- ThinkPad USB Keyboard with TrackPoint (supports extra LEDs and trackpoint
configuration)
- ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys)
- ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
config HID_LOGITECH
tristate "Logitech devices"
depends on HID
default !EXPERT
---help---
Support for Logitech devices that are not fully compliant with HID standard.
config HID_LOGITECH_DJ
tristate "Logitech Unifying receivers full support"
depends on HIDRAW
depends on HID_LOGITECH
select HID_LOGITECH_HIDPP
---help---
Say Y if you want support for Logitech Unifying receivers and devices.
Unifying receivers are capable of pairing up to 6 Logitech compliant
devices to the same receiver. Without this driver it will be handled by
generic USB_HID driver and all incoming events will be multiplexed
into a single mouse and a single keyboard device.
config HID_LOGITECH_HIDPP
tristate "Logitech HID++ devices support"
depends on HID_LOGITECH
---help---
Support for Logitech devices relyingon the HID++ Logitech specification
Say Y if you want support for Logitech devices relying on the HID++
specification. Such devices are the various Logitech Touchpads (T650,
T651, TK820), some mice (Zone Touch mouse), or even keyboards (Solar
Keyboard).
config LOGITECH_FF
bool "Logitech force feedback support"
depends on HID_LOGITECH
select INPUT_FF_MEMLESS
help
Say Y here if you have one of these devices:
- Logitech WingMan Cordless RumblePad
- Logitech WingMan Cordless RumblePad 2
- Logitech WingMan Force 3D
- Logitech Formula Force EX
- Logitech WingMan Formula Force GP
and if you want to enable force feedback for them.
Note: if you say N here, this device will still be supported, but without
force feedback.
config LOGIRUMBLEPAD2_FF
bool "Logitech force feedback support (variant 2)"
depends on HID_LOGITECH
select INPUT_FF_MEMLESS
help
Say Y here if you want to enable force feedback support for:
- Logitech RumblePad
- Logitech Rumblepad 2
- Logitech Formula Vibration Feedback Wheel
config LOGIG940_FF
bool "Logitech Flight System G940 force feedback support"
depends on HID_LOGITECH
select INPUT_FF_MEMLESS
help
Say Y here if you want to enable force feedback support for Logitech
Flight System G940 devices.
config LOGIWHEELS_FF
bool "Logitech wheels configuration and force feedback support"
depends on HID_LOGITECH
select INPUT_FF_MEMLESS
default LOGITECH_FF
help
Say Y here if you want to enable force feedback and range setting
support for following Logitech wheels:
- Logitech Driving Force
- Logitech Driving Force Pro
- Logitech Driving Force GT
- Logitech G25
- Logitech G27
- Logitech MOMO/MOMO 2
- Logitech Formula Force EX
config HID_MAGICMOUSE
tristate "Apple Magic Mouse/Trackpad multi-touch support"
depends on HID
---help---
Support for the Apple Magic Mouse/Trackpad multi-touch.
Say Y here if you want support for the multi-touch features of the
Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
config HID_MICROSOFT
tristate "Microsoft non-fully HID-compliant devices"
depends on HID
default !EXPERT
---help---
Support for Microsoft devices that are not fully compliant with HID standard.
config HID_MONTEREY
tristate "Monterey Genius KB29E keyboard"
depends on HID
default !EXPERT
---help---
Support for Monterey Genius KB29E.
config HID_MULTITOUCH
tristate "HID Multitouch panels"
depends on HID
---help---
Generic support for HID multitouch panels.
Say Y here if you have one of the following devices:
- 3M PCT touch screens
- ActionStar dual touch panels
- Atmel panels
- Cando dual touch panels
- Chunghwa panels
- CJTouch panels
- CVTouch panels
- Cypress TrueTouch panels
- Elan Microelectronics touch panels
- Elo TouchSystems IntelliTouch Plus panels
- GeneralTouch 'Sensing Win7-TwoFinger' panels
- GoodTouch panels
- Hanvon dual touch panels
- Ilitek dual touch panels
- IrTouch Infrared USB panels
- LG Display panels (Dell ST2220Tc)
- Lumio CrystalTouch panels
- MosArt dual-touch panels
- Panasonic multitouch panels
- PenMount dual touch panels
- Perixx Peripad 701 touchpad
- PixArt optical touch screen
- Pixcir dual touch panels
- Quanta panels
- eGalax dual-touch panels, including the Joojoo and Wetab tablets
- SiS multitouch panels
- Stantum multitouch panels
- Touch International Panels
- Unitec Panels
- Wistron optical touch panels
- XAT optical touch panels
- Xiroku optical touch panels
- Zytronic touch panels
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called hid-multitouch.
config HID_NTRIG
tristate "N-Trig touch screen"
depends on USB_HID
---help---
Support for N-Trig touch screen.
config HID_ORTEK
tristate "Ortek PKB-1700/WKB-2000/Skycable wireless keyboard and mouse trackpad"
depends on HID
---help---
There are certain devices which have LogicalMaximum wrong in the keyboard
usage page of their report descriptor. The most prevailing ones so far
are manufactured by Ortek, thus the name of the driver. Currently
supported devices by this driver are
- Ortek PKB-1700
- Ortek WKB-2000
- Skycable wireless presenter
config HID_PANTHERLORD
tristate "Pantherlord/GreenAsia game controller"
depends on HID
---help---
Say Y here if you have a PantherLord/GreenAsia based game controller
or adapter.
config PANTHERLORD_FF
bool "Pantherlord force feedback support"
depends on HID_PANTHERLORD
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a PantherLord/GreenAsia based game controller
or adapter and want to enable force feedback support for it.
config HID_PENMOUNT
tristate "Penmount touch device"
depends on USB_HID
---help---
This selects a driver for the PenMount 6000 touch controller.
The driver works around a problem in the report descript allowing
the userspace to touch events instead of mouse events.
Say Y here if you have a Penmount based touch controller.
config HID_PETALYNX
tristate "Petalynx Maxter remote control"
depends on HID
---help---
Support for Petalynx Maxter remote control.
config HID_PICOLCD
tristate "PicoLCD (graphic version)"
depends on HID
---help---
This provides support for Minibox PicoLCD devices, currently
only the graphical ones are supported.
This includes support for the following device features:
- Keypad
- Switching between Firmware and Flash mode
- EEProm / Flash access (via debugfs)
Features selectively enabled:
- Framebuffer for monochrome 256x64 display
- Backlight control
- Contrast control
- General purpose outputs
Features that are not (yet) supported:
- IR
config HID_PICOLCD_FB
bool "Framebuffer support" if EXPERT
default !EXPERT
depends on HID_PICOLCD
depends on HID_PICOLCD=FB || FB=y
select FB_DEFERRED_IO
select FB_SYS_FILLRECT
select FB_SYS_COPYAREA
select FB_SYS_IMAGEBLIT
select FB_SYS_FOPS
---help---
Provide access to PicoLCD's 256x64 monochrome display via a
framebuffer device.
config HID_PICOLCD_BACKLIGHT
bool "Backlight control" if EXPERT
default !EXPERT
depends on HID_PICOLCD
depends on HID_PICOLCD=BACKLIGHT_CLASS_DEVICE || BACKLIGHT_CLASS_DEVICE=y
---help---
Provide access to PicoLCD's backlight control via backlight
class.
config HID_PICOLCD_LCD
bool "Contrast control" if EXPERT
default !EXPERT
depends on HID_PICOLCD
depends on HID_PICOLCD=LCD_CLASS_DEVICE || LCD_CLASS_DEVICE=y
---help---
Provide access to PicoLCD's LCD contrast via lcd class.
config HID_PICOLCD_LEDS
bool "GPO via leds class" if EXPERT
default !EXPERT
depends on HID_PICOLCD
depends on HID_PICOLCD=LEDS_CLASS || LEDS_CLASS=y
---help---
Provide access to PicoLCD's GPO pins via leds class.
config HID_PICOLCD_CIR
bool "CIR via RC class" if EXPERT
default !EXPERT
depends on HID_PICOLCD
depends on HID_PICOLCD=RC_CORE || RC_CORE=y
---help---
Provide access to PicoLCD's CIR interface via remote control (LIRC).
config HID_PLANTRONICS
tristate "Plantronics USB HID Driver"
depends on HID
---help---
Provides HID support for Plantronics USB audio devices.
Correctly maps vendor unique volume up/down HID usages to
KEY_VOLUMEUP and KEY_VOLUMEDOWN events and prevents core mapping
of other vendor unique HID usages to random mouse events.
Say M here if you may ever plug in a Plantronics USB audio device.
config HID_PRIMAX
tristate "Primax non-fully HID-compliant devices"
depends on HID
---help---
Support for Primax devices that are not fully compliant with the
HID standard.
config HID_ROCCAT
tristate "Roccat device support"
depends on USB_HID
---help---
Support for Roccat devices.
Say Y here if you have a Roccat mouse or keyboard and want
support for its special functionalities.
config HID_SAITEK
tristate "Saitek (Mad Catz) non-fully HID-compliant devices"
depends on HID
---help---
Support for Saitek devices that are not fully compliant with the
HID standard.
Supported devices:
- PS1000 Dual Analog Pad
- Saitek R.A.T.7, R.A.T.9, M.M.O.7 Gaming Mice
- Mad Catz R.A.T.5, R.A.T.9 Gaming Mice
config HID_SAMSUNG
tristate "Samsung InfraRed remote control or keyboards"
depends on HID
---help---
Support for Samsung InfraRed remote control or keyboards.
config HID_SONY
tristate "Sony PS2/3/4 accessories"
depends on USB_HID
depends on NEW_LEDS
depends on LEDS_CLASS
select POWER_SUPPLY
---help---
Support for
* Sony PS3 6-axis controllers
* Sony PS4 DualShock 4 controllers
* Buzz controllers
* Sony PS3 Blue-ray Disk Remote Control (Bluetooth)
* Logitech Harmony adapter for Sony Playstation 3 (Bluetooth)
config SONY_FF
bool "Sony PS2/3/4 accessories force feedback support"
depends on HID_SONY
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a Sony PS2/3/4 accessory and want to enable
force feedback support for it.
config HID_SPEEDLINK
tristate "Speedlink VAD Cezanne mouse support"
depends on HID
---help---
Support for Speedlink Vicious and Divine Cezanne mouse.
config HID_STEELSERIES
tristate "Steelseries SRW-S1 steering wheel support"
depends on HID
---help---
Support for Steelseries SRW-S1 steering wheel
config HID_SUNPLUS
tristate "Sunplus wireless desktop"
depends on HID
---help---
Support for Sunplus wireless desktop.
config HID_RMI
tristate "Synaptics RMI4 device support"
depends on HID
---help---
Support for Synaptics RMI4 touchpads.
Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
and want support for its special functionalities.
config HID_GREENASIA
tristate "GreenAsia (Product ID 0x12) game controller support"
depends on HID
---help---
Say Y here if you have a GreenAsia (Product ID 0x12) based game
controller or adapter.
config GREENASIA_FF
bool "GreenAsia (Product ID 0x12) force feedback support"
depends on HID_GREENASIA
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a GreenAsia (Product ID 0x12) based game controller
(like MANTA Warrior MM816 and SpeedLink Strike2 SL-6635) or adapter
and want to enable force feedback support for it.
config HID_HYPERV_MOUSE
tristate "Microsoft Hyper-V mouse driver"
depends on HYPERV
---help---
Select this option to enable the Hyper-V mouse driver.
config HID_SMARTJOYPLUS
tristate "SmartJoy PLUS PS2/USB adapter support"
depends on HID
---help---
Support for SmartJoy PLUS PS2/USB adapter, Super Dual Box,
Super Joy Box 3 Pro, Super Dual Box Pro, and Super Joy Box 5 Pro.
Note that DDR (Dance Dance Revolution) mode is not supported, nor
is pressure sensitive buttons on the pro models.
config SMARTJOYPLUS_FF
bool "SmartJoy PLUS PS2/USB adapter force feedback support"
depends on HID_SMARTJOYPLUS
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a SmartJoy PLUS PS2/USB adapter and want to
enable force feedback support for it.
config HID_TIVO
tristate "TiVo Slide Bluetooth remote control support"
depends on HID
---help---
Say Y if you have a TiVo Slide Bluetooth remote control.
config HID_TOPSEED
tristate "TopSeed Cyberlink, BTC Emprex, Conceptronic remote control support"
depends on HID
---help---
Say Y if you have a TopSeed Cyberlink or BTC Emprex or Conceptronic
CLLRCMCE remote control.
config HID_THINGM
tristate "ThingM blink(1) USB RGB LED"
depends on HID
depends on LEDS_CLASS
---help---
Support for the ThingM blink(1) USB RGB LED. This driver registers a
Linux LED class instance, plus additional sysfs attributes to control
RGB colors, fade time and playing. The device is exposed through hidraw
to access other functions.
config HID_THRUSTMASTER
tristate "ThrustMaster devices support"
depends on HID
---help---
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or
a THRUSTMASTER Ferrari GT Rumble Wheel.
config THRUSTMASTER_FF
bool "ThrustMaster devices force feedback support"
depends on HID_THRUSTMASTER
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or 3,
a THRUSTMASTER Dual Trigger 3-in-1 or a THRUSTMASTER Ferrari GT
Rumble Force or Force Feedback Wheel.
config HID_WACOM
tristate "Wacom Intuos/Graphire tablet support (USB)"
depends on HID
select POWER_SUPPLY
select NEW_LEDS
select LEDS_CLASS
help
Say Y here if you want to use the USB or BT version of the Wacom Intuos
or Graphire tablet.
To compile this driver as a module, choose M here: the
module will be called wacom.
config HID_WIIMOTE
tristate "Nintendo Wii / Wii U peripherals"
depends on HID
depends on LEDS_CLASS
select POWER_SUPPLY
select INPUT_FF_MEMLESS
---help---
Support for Nintendo Wii and Wii U Bluetooth peripherals. Supported
devices are the Wii Remote and its extension devices, but also devices
based on the Wii Remote like the Wii U Pro Controller or the
Wii Balance Board.
Support for all official Nintendo extensions is available, however, 3rd
party extensions might not be supported. Please report these devices to:
http://github.com/dvdhrm/xwiimote/issues
Other Nintendo Wii U peripherals that are IEEE 802.11 based (including
the Wii U Gamepad) might be supported in the future. But currently
support is limited to Bluetooth based devices.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called hid-wiimote.
config HID_XINMO
tristate "Xin-Mo non-fully compliant devices"
depends on HID
---help---
Support for Xin-Mo devices that are not fully compliant with the HID
standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
if you have a Xin-Mo Dual Arcade controller.
config HID_ZEROPLUS
tristate "Zeroplus based game controller support"
depends on HID
---help---
Say Y here if you have a Zeroplus based game controller.
config ZEROPLUS_FF
bool "Zeroplus based game controller force feedback support"
depends on HID_ZEROPLUS
select INPUT_FF_MEMLESS
---help---
Say Y here if you have a Zeroplus based game controller and want
to have force feedback support for it.
config HID_ZYDACRON
tristate "Zydacron remote control support"
depends on HID
---help---
Support for Zydacron remote control.
config HID_SENSOR_HUB
tristate "HID Sensors framework support"
depends on HID && HAS_IOMEM
select MFD_CORE
default n
---help---
Support for HID Sensor framework. This creates a MFD instance
for a sensor hub and identifies all the sensors connected to it.
Each sensor is registered as a MFD cell, so that sensor specific
processing can be done in a separate driver. Each sensor
drivers can use the service provided by this driver to register
for events and handle data streams. Each sensor driver can format
data and present to user mode using input or IIO interface.
config HID_SENSOR_CUSTOM_SENSOR
tristate "HID Sensors hub custom sensor support"
depends on HID_SENSOR_HUB
default n
---help---
HID Sensor hub specification allows definition of some custom and
generic sensors. Unlike other HID sensors, they can't be exported
via Linux IIO because of custom fields. This is up to the manufacturer
to decide how to interpret these special sensor ids and process in
the user space. Currently some manufacturers are using these ids for
sensor calibration and debugging other sensors. Manufacturers
should't use these special custom sensor ids to export any of the
standard sensors.
Select this config option for custom/generic sensor support.
endmenu
endif # HID
source "drivers/hid/usbhid/Kconfig"
source "drivers/hid/i2c-hid/Kconfig"
endmenu

112
drivers/hid/Makefile Executable file
View File

@ -0,0 +1,112 @@
#
# Makefile for the HID driver
#
hid-y := hid-core.o hid-input.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
hid-$(CONFIG_HIDRAW) += hidraw.o
hid-logitech-y := hid-lg.o
hid-logitech-$(CONFIG_LOGITECH_FF) += hid-lgff.o
hid-logitech-$(CONFIG_LOGIRUMBLEPAD2_FF) += hid-lg2ff.o
hid-logitech-$(CONFIG_LOGIG940_FF) += hid-lg3ff.o
hid-logitech-$(CONFIG_LOGIWHEELS_FF) += hid-lg4ff.o
hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o
hid-wiimote-$(CONFIG_DEBUG_FS) += hid-wiimote-debug.o
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o
obj-$(CONFIG_HID_ACRUX) += hid-axff.o
obj-$(CONFIG_HID_APPLE) += hid-apple.o
obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o
obj-$(CONFIG_HID_AUREAL) += hid-aureal.o
obj-$(CONFIG_HID_BELKIN) += hid-belkin.o
obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o
obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
obj-$(CONFIG_HID_DRAGONRISE) += hid-dr.o
obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
obj-$(CONFIG_HID_ICADE) += hid-icade.o
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
obj-$(CONFIG_HID_KYE) += hid-kye.o
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o
obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o
obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o
obj-$(CONFIG_HID_ORTEK) += hid-ortek.o
obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o
obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o
obj-$(CONFIG_HID_PENMOUNT) += hid-penmount.o
obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o
obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o
hid-picolcd-y += hid-picolcd_core.o
hid-picolcd-$(CONFIG_HID_PICOLCD_FB) += hid-picolcd_fb.o
hid-picolcd-$(CONFIG_HID_PICOLCD_BACKLIGHT) += hid-picolcd_backlight.o
hid-picolcd-$(CONFIG_HID_PICOLCD_LCD) += hid-picolcd_lcd.o
hid-picolcd-$(CONFIG_HID_PICOLCD_LEDS) += hid-picolcd_leds.o
hid-picolcd-$(CONFIG_HID_PICOLCD_CIR) += hid-picolcd_cir.o
hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o
obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o
obj-$(CONFIG_HID_PRIMAX) += hid-primax.o
obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o
obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
obj-$(CONFIG_HID_THINGM) += hid-thingm.o
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
obj-$(CONFIG_HID_TIVO) += hid-tivo.o
obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
wacom-objs := wacom_wac.o wacom_sys.o
obj-$(CONFIG_HID_WACOM) += wacom.o
obj-$(CONFIG_HID_WALTOP) += hid-waltop.o
obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o
obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o
obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o
obj-$(CONFIG_USB_HID) += usbhid/
obj-$(CONFIG_USB_MOUSE) += usbhid/
obj-$(CONFIG_USB_KBD) += usbhid/
obj-$(CONFIG_I2C_HID) += i2c-hid/

138
drivers/hid/hid-a4tech.c Executable file
View File

@ -0,0 +1,138 @@
/*
* HID driver for some a4tech "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "hid-ids.h"
#define A4_2WHEEL_MOUSE_HACK_7 0x01
#define A4_2WHEEL_MOUSE_HACK_B8 0x02
struct a4tech_sc {
unsigned long quirks;
unsigned int hw_wheel;
__s32 delayed_value;
};
static int a4_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct a4tech_sc *a4 = hid_get_drvdata(hdev);
if (usage->type == EV_REL && usage->code == REL_WHEEL)
set_bit(REL_HWHEEL, *bit);
if ((a4->quirks & A4_2WHEEL_MOUSE_HACK_7) && usage->hid == 0x00090007)
return -1;
return 0;
}
static int a4_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct a4tech_sc *a4 = hid_get_drvdata(hdev);
struct input_dev *input;
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
!usage->type)
return 0;
input = field->hidinput->input;
if (a4->quirks & A4_2WHEEL_MOUSE_HACK_B8) {
if (usage->type == EV_REL && usage->code == REL_WHEEL) {
a4->delayed_value = value;
return 1;
}
if (usage->hid == 0x000100b8) {
input_event(input, EV_REL, value ? REL_HWHEEL :
REL_WHEEL, a4->delayed_value);
return 1;
}
}
if ((a4->quirks & A4_2WHEEL_MOUSE_HACK_7) && usage->hid == 0x00090007) {
a4->hw_wheel = !!value;
return 1;
}
if (usage->code == REL_WHEEL && a4->hw_wheel) {
input_event(input, usage->type, REL_HWHEEL, value);
return 1;
}
return 0;
}
static int a4_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct a4tech_sc *a4;
int ret;
a4 = devm_kzalloc(&hdev->dev, sizeof(*a4), GFP_KERNEL);
if (a4 == NULL) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
a4->quirks = id->driver_data;
hid_set_drvdata(hdev, a4);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
return 0;
}
static const struct hid_device_id a4_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU),
.driver_data = A4_2WHEEL_MOUSE_HACK_7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D),
.driver_data = A4_2WHEEL_MOUSE_HACK_B8 },
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_RP_649),
.driver_data = A4_2WHEEL_MOUSE_HACK_B8 },
{ }
};
MODULE_DEVICE_TABLE(hid, a4_devices);
static struct hid_driver a4_driver = {
.name = "a4tech",
.id_table = a4_devices,
.input_mapped = a4_input_mapped,
.event = a4_event,
.probe = a4_probe,
};
module_hid_driver(a4_driver);
MODULE_LICENSE("GPL");

582
drivers/hid/hid-apple.c Executable file
View File

@ -0,0 +1,582 @@
/*
* USB HID quirks support for Linux
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "hid-ids.h"
#define APPLE_RDESC_JIS 0x0001
#define APPLE_IGNORE_MOUSE 0x0002
#define APPLE_HAS_FN 0x0004
#define APPLE_HIDDEV 0x0008
#define APPLE_ISO_KEYBOARD 0x0010
#define APPLE_MIGHTYMOUSE 0x0020
#define APPLE_INVERT_HWHEEL 0x0040
#define APPLE_IGNORE_HIDINPUT 0x0080
#define APPLE_NUMLOCK_EMULATION 0x0100
#define APPLE_FLAG_FKEY 0x01
static unsigned int fnmode = 1;
module_param(fnmode, uint, 0644);
MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, "
"[1] = fkeyslast, 2 = fkeysfirst)");
static unsigned int iso_layout = 1;
module_param(iso_layout, uint, 0644);
MODULE_PARM_DESC(iso_layout, "Enable/Disable hardcoded ISO-layout of the keyboard. "
"(0 = disabled, [1] = enabled)");
static unsigned int swap_opt_cmd;
module_param(swap_opt_cmd, uint, 0644);
MODULE_PARM_DESC(swap_opt_cmd, "Swap the Option (\"Alt\") and Command (\"Flag\") keys. "
"(For people who want to keep Windows PC keyboard muscle memory. "
"[0] = as-is, Mac layout. 1 = swapped, Windows layout.)");
struct apple_sc {
unsigned long quirks;
unsigned int fn_on;
DECLARE_BITMAP(pressed_fn, KEY_CNT);
DECLARE_BITMAP(pressed_numlock, KEY_CNT);
};
struct apple_key_translation {
u16 from;
u16 to;
u8 flags;
};
static const struct apple_key_translation macbookair_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
{ KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
{ KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
{ KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
{ KEY_F6, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
{ KEY_F7, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
{ KEY_F8, KEY_NEXTSONG, APPLE_FLAG_FKEY },
{ KEY_F9, KEY_MUTE, APPLE_FLAG_FKEY },
{ KEY_F10, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
{ KEY_F11, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
{ KEY_F12, KEY_EJECTCD, APPLE_FLAG_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
static const struct apple_key_translation apple_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
{ KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
{ KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
{ KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
{ KEY_F5, KEY_KBDILLUMDOWN, APPLE_FLAG_FKEY },
{ KEY_F6, KEY_KBDILLUMUP, APPLE_FLAG_FKEY },
{ KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
{ KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
{ KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
{ KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
{ KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
{ KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
static const struct apple_key_translation powerbook_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
{ KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
{ KEY_F3, KEY_MUTE, APPLE_FLAG_FKEY },
{ KEY_F4, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
{ KEY_F5, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
{ KEY_F6, KEY_NUMLOCK, APPLE_FLAG_FKEY },
{ KEY_F7, KEY_SWITCHVIDEOMODE, APPLE_FLAG_FKEY },
{ KEY_F8, KEY_KBDILLUMTOGGLE, APPLE_FLAG_FKEY },
{ KEY_F9, KEY_KBDILLUMDOWN, APPLE_FLAG_FKEY },
{ KEY_F10, KEY_KBDILLUMUP, APPLE_FLAG_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
static const struct apple_key_translation powerbook_numlock_keys[] = {
{ KEY_J, KEY_KP1 },
{ KEY_K, KEY_KP2 },
{ KEY_L, KEY_KP3 },
{ KEY_U, KEY_KP4 },
{ KEY_I, KEY_KP5 },
{ KEY_O, KEY_KP6 },
{ KEY_7, KEY_KP7 },
{ KEY_8, KEY_KP8 },
{ KEY_9, KEY_KP9 },
{ KEY_M, KEY_KP0 },
{ KEY_DOT, KEY_KPDOT },
{ KEY_SLASH, KEY_KPPLUS },
{ KEY_SEMICOLON, KEY_KPMINUS },
{ KEY_P, KEY_KPASTERISK },
{ KEY_MINUS, KEY_KPEQUAL },
{ KEY_0, KEY_KPSLASH },
{ KEY_F6, KEY_NUMLOCK },
{ KEY_KPENTER, KEY_KPENTER },
{ KEY_BACKSPACE, KEY_BACKSPACE },
{ }
};
static const struct apple_key_translation apple_iso_keyboard[] = {
{ KEY_GRAVE, KEY_102ND },
{ KEY_102ND, KEY_GRAVE },
{ }
};
static const struct apple_key_translation swapped_option_cmd_keys[] = {
{ KEY_LEFTALT, KEY_LEFTMETA },
{ KEY_LEFTMETA, KEY_LEFTALT },
{ KEY_RIGHTALT, KEY_RIGHTMETA },
{ KEY_RIGHTMETA,KEY_RIGHTALT },
{ }
};
static const struct apple_key_translation *apple_find_translation(
const struct apple_key_translation *table, u16 from)
{
const struct apple_key_translation *trans;
/* Look for the translation */
for (trans = table; trans->from; trans++)
if (trans->from == from)
return trans;
return NULL;
}
static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
struct hid_usage *usage, __s32 value)
{
struct apple_sc *asc = hid_get_drvdata(hid);
const struct apple_key_translation *trans, *table;
if (usage->code == KEY_FN) {
asc->fn_on = !!value;
input_event(input, usage->type, usage->code, value);
return 1;
}
if (fnmode) {
int do_translate;
if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
table = macbookair_fn_keys;
else if (hid->product < 0x21d || hid->product >= 0x300)
table = powerbook_fn_keys;
else
table = apple_fn_keys;
trans = apple_find_translation (table, usage->code);
if (trans) {
if (test_bit(usage->code, asc->pressed_fn))
do_translate = 1;
else if (trans->flags & APPLE_FLAG_FKEY)
do_translate = (fnmode == 2 && asc->fn_on) ||
(fnmode == 1 && !asc->fn_on);
else
do_translate = asc->fn_on;
if (do_translate) {
if (value)
set_bit(usage->code, asc->pressed_fn);
else
clear_bit(usage->code, asc->pressed_fn);
input_event(input, usage->type, trans->to,
value);
return 1;
}
}
if (asc->quirks & APPLE_NUMLOCK_EMULATION &&
(test_bit(usage->code, asc->pressed_numlock) ||
test_bit(LED_NUML, input->led))) {
trans = apple_find_translation(powerbook_numlock_keys,
usage->code);
if (trans) {
if (value)
set_bit(usage->code,
asc->pressed_numlock);
else
clear_bit(usage->code,
asc->pressed_numlock);
input_event(input, usage->type, trans->to,
value);
}
return 1;
}
}
if (iso_layout) {
if (asc->quirks & APPLE_ISO_KEYBOARD) {
trans = apple_find_translation(apple_iso_keyboard, usage->code);
if (trans) {
input_event(input, usage->type, trans->to, value);
return 1;
}
}
}
if (swap_opt_cmd) {
trans = apple_find_translation(swapped_option_cmd_keys, usage->code);
if (trans) {
input_event(input, usage->type, trans->to, value);
return 1;
}
}
return 0;
}
static int apple_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
!usage->type)
return 0;
if ((asc->quirks & APPLE_INVERT_HWHEEL) &&
usage->code == REL_HWHEEL) {
input_event(field->hidinput->input, usage->type, usage->code,
-value);
return 1;
}
if ((asc->quirks & APPLE_HAS_FN) &&
hidinput_apple_event(hdev, field->hidinput->input,
usage, value))
return 1;
return 0;
}
/*
* MacBook JIS keyboard has wrong logical maximum
*/
static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
if ((asc->quirks & APPLE_RDESC_JIS) && *rsize >= 60 &&
rdesc[53] == 0x65 && rdesc[59] == 0x65) {
hid_info(hdev,
"fixing up MacBook JIS keyboard report descriptor\n");
rdesc[53] = rdesc[59] = 0xe7;
}
return rdesc;
}
static void apple_setup_input(struct input_dev *input)
{
const struct apple_key_translation *trans;
set_bit(KEY_NUMLOCK, input->keybit);
/* Enable all needed keys */
for (trans = apple_fn_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
for (trans = powerbook_fn_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
for (trans = powerbook_numlock_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
for (trans = apple_iso_keyboard; trans->from; trans++)
set_bit(trans->to, input->keybit);
}
static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if (usage->hid == (HID_UP_CUSTOM | 0x0003)) {
/* The fn key on Apple USB keyboards */
set_bit(EV_REP, hi->input->evbit);
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_FN);
apple_setup_input(hi->input);
return 1;
}
/* we want the hid layer to go through standard path (set and ignore) */
return 0;
}
static int apple_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
if (asc->quirks & APPLE_MIGHTYMOUSE) {
if (usage->hid == HID_GD_Z)
hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
else if (usage->code == BTN_1)
hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_2);
else if (usage->code == BTN_2)
hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_1);
}
return 0;
}
static int apple_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
unsigned long quirks = id->driver_data;
struct apple_sc *asc;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
int ret;
asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL);
if (asc == NULL) {
hid_err(hdev, "can't alloc apple descriptor\n");
return -ENOMEM;
}
asc->quirks = quirks;
hid_set_drvdata(hdev, asc);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
if (quirks & APPLE_HIDDEV)
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
if (quirks & APPLE_IGNORE_HIDINPUT)
connect_mask &= ~HID_CONNECT_HIDINPUT;
ret = hid_hw_start(hdev, connect_mask);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
return 0;
}
static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE),
.driver_data = APPLE_MIGHTYMOUSE | APPLE_INVERT_HWHEEL },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_JIS),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_JIS),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_JIS),
.driver_data = APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO),
.driver_data = APPLE_HAS_FN | APPLE_ISO_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
APPLE_ISO_KEYBOARD },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ }
};
MODULE_DEVICE_TABLE(hid, apple_devices);
static struct hid_driver apple_driver = {
.name = "apple",
.id_table = apple_devices,
.report_fixup = apple_report_fixup,
.probe = apple_probe,
.event = apple_event,
.input_mapping = apple_input_mapping,
.input_mapped = apple_input_mapped,
};
module_hid_driver(apple_driver);
MODULE_LICENSE("GPL");

357
drivers/hid/hid-appleir.c Executable file
View File

@ -0,0 +1,357 @@
/*
* HID driver for the apple ir device
*
* Original driver written by James McKenzie
* Ported to recent 2.6 kernel versions by Greg Kroah-Hartman <gregkh@suse.de>
* Updated to support newer remotes by Bastien Nocera <hadess@hadess.net>
* Ported to HID subsystem by Benjamin Tissoires <benjamin.tissoires@gmail.com>
*
* Copyright (C) 2006 James McKenzie
* Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2008 Novell Inc.
* Copyright (C) 2010, 2012 Bastien Nocera <hadess@hadess.net>
* Copyright (C) 2013 Benjamin Tissoires <benjamin.tissoires@gmail.com>
* Copyright (C) 2013 Red Hat Inc. All Rights Reserved
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
MODULE_AUTHOR("James McKenzie");
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>");
MODULE_DESCRIPTION("HID Apple IR remote controls");
MODULE_LICENSE("GPL");
#define KEY_MASK 0x0F
#define TWO_PACKETS_MASK 0x40
/*
* James McKenzie has two devices both of which report the following
* 25 87 ee 83 0a +
* 25 87 ee 83 0c -
* 25 87 ee 83 09 <<
* 25 87 ee 83 06 >>
* 25 87 ee 83 05 >"
* 25 87 ee 83 03 menu
* 26 00 00 00 00 for key repeat
*/
/*
* Thomas Glanzmann reports the following responses
* 25 87 ee ca 0b +
* 25 87 ee ca 0d -
* 25 87 ee ca 08 <<
* 25 87 ee ca 07 >>
* 25 87 ee ca 04 >"
* 25 87 ee ca 02 menu
* 26 00 00 00 00 for key repeat
*
* He also observes the following event sometimes
* sent after a key is release, which I interpret
* as a flat battery message
* 25 87 e0 ca 06 flat battery
*/
/*
* Alexandre Karpenko reports the following responses for Device ID 0x8242
* 25 87 ee 47 0b +
* 25 87 ee 47 0d -
* 25 87 ee 47 08 <<
* 25 87 ee 47 07 >>
* 25 87 ee 47 04 >"
* 25 87 ee 47 02 menu
* 26 87 ee 47 ** for key repeat (** is the code of the key being held)
*/
/*
* Bastien Nocera's remote
* 25 87 ee 91 5f followed by
* 25 87 ee 91 05 gives you >"
*
* 25 87 ee 91 5c followed by
* 25 87 ee 91 05 gives you the middle button
*/
/*
* Fabien Andre's remote
* 25 87 ee a3 5e followed by
* 25 87 ee a3 04 gives you >"
*
* 25 87 ee a3 5d followed by
* 25 87 ee a3 04 gives you the middle button
*/
static const unsigned short appleir_key_table[] = {
KEY_RESERVED,
KEY_MENU,
KEY_PLAYPAUSE,
KEY_FORWARD,
KEY_BACK,
KEY_VOLUMEUP,
KEY_VOLUMEDOWN,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_ENTER,
KEY_PLAYPAUSE,
KEY_RESERVED,
};
struct appleir {
struct input_dev *input_dev;
struct hid_device *hid;
unsigned short keymap[ARRAY_SIZE(appleir_key_table)];
struct timer_list key_up_timer; /* timer for key up */
spinlock_t lock; /* protects .current_key */
int current_key; /* the currently pressed key */
int prev_key_idx; /* key index in a 2 packets message */
};
static int get_key(int data)
{
/*
* The key is coded accross bits 2..9:
*
* 0x00 or 0x01 ( ) key: 0 -> KEY_RESERVED
* 0x02 or 0x03 ( menu ) key: 1 -> KEY_MENU
* 0x04 or 0x05 ( >" ) key: 2 -> KEY_PLAYPAUSE
* 0x06 or 0x07 ( >> ) key: 3 -> KEY_FORWARD
* 0x08 or 0x09 ( << ) key: 4 -> KEY_BACK
* 0x0a or 0x0b ( + ) key: 5 -> KEY_VOLUMEUP
* 0x0c or 0x0d ( - ) key: 6 -> KEY_VOLUMEDOWN
* 0x0e or 0x0f ( ) key: 7 -> KEY_RESERVED
* 0x50 or 0x51 ( ) key: 8 -> KEY_RESERVED
* 0x52 or 0x53 ( ) key: 9 -> KEY_RESERVED
* 0x54 or 0x55 ( ) key: 10 -> KEY_RESERVED
* 0x56 or 0x57 ( ) key: 11 -> KEY_RESERVED
* 0x58 or 0x59 ( ) key: 12 -> KEY_RESERVED
* 0x5a or 0x5b ( ) key: 13 -> KEY_RESERVED
* 0x5c or 0x5d ( middle ) key: 14 -> KEY_ENTER
* 0x5e or 0x5f ( >" ) key: 15 -> KEY_PLAYPAUSE
*
* Packets starting with 0x5 are part of a two-packets message,
* we notify the caller by sending a negative value.
*/
int key = (data >> 1) & KEY_MASK;
if ((data & TWO_PACKETS_MASK))
/* Part of a 2 packets-command */
key = -key;
return key;
}
static void key_up(struct hid_device *hid, struct appleir *appleir, int key)
{
input_report_key(appleir->input_dev, key, 0);
input_sync(appleir->input_dev);
}
static void key_down(struct hid_device *hid, struct appleir *appleir, int key)
{
input_report_key(appleir->input_dev, key, 1);
input_sync(appleir->input_dev);
}
static void battery_flat(struct appleir *appleir)
{
dev_err(&appleir->input_dev->dev, "possible flat battery?\n");
}
static void key_up_tick(unsigned long data)
{
struct appleir *appleir = (struct appleir *)data;
struct hid_device *hid = appleir->hid;
unsigned long flags;
spin_lock_irqsave(&appleir->lock, flags);
if (appleir->current_key) {
key_up(hid, appleir, appleir->current_key);
appleir->current_key = 0;
}
spin_unlock_irqrestore(&appleir->lock, flags);
}
static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
u8 *data, int len)
{
struct appleir *appleir = hid_get_drvdata(hid);
static const u8 keydown[] = { 0x25, 0x87, 0xee };
static const u8 keyrepeat[] = { 0x26, };
static const u8 flatbattery[] = { 0x25, 0x87, 0xe0 };
unsigned long flags;
if (len != 5)
goto out;
if (!memcmp(data, keydown, sizeof(keydown))) {
int index;
spin_lock_irqsave(&appleir->lock, flags);
/*
* If we already have a key down, take it up before marking
* this one down
*/
if (appleir->current_key)
key_up(hid, appleir, appleir->current_key);
/* Handle dual packet commands */
if (appleir->prev_key_idx > 0)
index = appleir->prev_key_idx;
else
index = get_key(data[4]);
if (index >= 0) {
appleir->current_key = appleir->keymap[index];
key_down(hid, appleir, appleir->current_key);
/*
* Remote doesn't do key up, either pull them up, in
* the test above, or here set a timer which pulls
* them up after 1/8 s
*/
mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
appleir->prev_key_idx = 0;
} else
/* Remember key for next packet */
appleir->prev_key_idx = -index;
spin_unlock_irqrestore(&appleir->lock, flags);
goto out;
}
appleir->prev_key_idx = 0;
if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) {
key_down(hid, appleir, appleir->current_key);
/*
* Remote doesn't do key up, either pull them up, in the test
* above, or here set a timer which pulls them up after 1/8 s
*/
mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
goto out;
}
if (!memcmp(data, flatbattery, sizeof(flatbattery))) {
battery_flat(appleir);
/* Fall through */
}
out:
/* let hidraw and hiddev handle the report */
return 0;
}
static int appleir_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
struct input_dev *input_dev = hidinput->input;
struct appleir *appleir = hid_get_drvdata(hid);
int i;
appleir->input_dev = input_dev;
input_dev->keycode = appleir->keymap;
input_dev->keycodesize = sizeof(unsigned short);
input_dev->keycodemax = ARRAY_SIZE(appleir->keymap);
input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
memcpy(appleir->keymap, appleir_key_table, sizeof(appleir->keymap));
for (i = 0; i < ARRAY_SIZE(appleir_key_table); i++)
set_bit(appleir->keymap[i], input_dev->keybit);
clear_bit(KEY_RESERVED, input_dev->keybit);
return 0;
}
static int appleir_input_mapping(struct hid_device *hid,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
return -1;
}
static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id)
{
int ret;
struct appleir *appleir;
appleir = kzalloc(sizeof(struct appleir), GFP_KERNEL);
if (!appleir) {
ret = -ENOMEM;
goto allocfail;
}
appleir->hid = hid;
/* force input as some remotes bypass the input registration */
hid->quirks |= HID_QUIRK_HIDINPUT_FORCE;
spin_lock_init(&appleir->lock);
setup_timer(&appleir->key_up_timer,
key_up_tick, (unsigned long) appleir);
hid_set_drvdata(hid, appleir);
ret = hid_parse(hid);
if (ret) {
hid_err(hid, "parse failed\n");
goto fail;
}
ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE);
if (ret) {
hid_err(hid, "hw start failed\n");
goto fail;
}
return 0;
fail:
kfree(appleir);
allocfail:
return ret;
}
static void appleir_remove(struct hid_device *hid)
{
struct appleir *appleir = hid_get_drvdata(hid);
hid_hw_stop(hid);
del_timer_sync(&appleir->key_up_timer);
kfree(appleir);
}
static const struct hid_device_id appleir_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL3) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL5) },
{ }
};
MODULE_DEVICE_TABLE(hid, appleir_devices);
static struct hid_driver appleir_driver = {
.name = "appleir",
.id_table = appleir_devices,
.raw_event = appleir_raw_event,
.input_configured = appleir_input_configured,
.probe = appleir_probe,
.remove = appleir_remove,
.input_mapping = appleir_input_mapping,
};
module_hid_driver(appleir_driver);

43
drivers/hid/hid-aureal.c Executable file
View File

@ -0,0 +1,43 @@
/*
* HID driver for Aureal Cy se W-01RN USB_V3.1 devices
*
* Copyright (c) 2010 Franco Catrin <fcatrin@gmail.com>
* Copyright (c) 2010 Ben Cropley <bcropley@internode.on.net>
*
* Based on HID sunplus driver by
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static __u8 *aureal_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
dev_info(&hdev->dev, "fixing Aureal Cy se W-01RN USB_V3.1 report descriptor.\n");
rdesc[53] = 0x65;
}
return rdesc;
}
static const struct hid_device_id aureal_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) },
{ }
};
MODULE_DEVICE_TABLE(hid, aureal_devices);
static struct hid_driver aureal_driver = {
.name = "aureal",
.id_table = aureal_devices,
.report_fixup = aureal_report_fixup,
};
module_hid_driver(aureal_driver);
MODULE_LICENSE("GPL");

198
drivers/hid/hid-axff.c Executable file
View File

@ -0,0 +1,198 @@
/*
* Force feedback support for ACRUX game controllers
*
* From what I have gathered, these devices are mass produced in China
* by several vendors. They often share the same design as the original
* Xbox 360 controller.
*
* 1a34:0802 "ACRUX USB GAMEPAD 8116"
* - tested with an EXEQ EQ-PCU-02090 game controller.
*
* Copyright (c) 2010 Sergei Kolzun <x0r@dv-life.ru>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#ifdef CONFIG_HID_ACRUX_FF
struct axff_device {
struct hid_report *report;
};
static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct axff_device *axff = data;
struct hid_report *report = axff->report;
int field_count = 0;
int left, right;
int i, j;
left = effect->u.rumble.strong_magnitude;
right = effect->u.rumble.weak_magnitude;
dbg_hid("called with 0x%04x 0x%04x", left, right);
left = left * 0xff / 0xffff;
right = right * 0xff / 0xffff;
for (i = 0; i < report->maxfield; i++) {
for (j = 0; j < report->field[i]->report_count; j++) {
report->field[i]->value[j] =
field_count % 2 ? right : left;
field_count++;
}
}
dbg_hid("running with 0x%02x 0x%02x", left, right);
hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
return 0;
}
static int axff_init(struct hid_device *hid)
{
struct axff_device *axff;
struct hid_report *report;
struct hid_input *hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev = hidinput->input;
int field_count = 0;
int i, j;
int error;
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
for (i = 0; i < report->maxfield; i++) {
for (j = 0; j < report->field[i]->report_count; j++) {
report->field[i]->value[j] = 0x00;
field_count++;
}
}
if (field_count < 4 && hid->product != 0xf705) {
hid_err(hid, "not enough fields in the report: %d\n",
field_count);
return -ENODEV;
}
axff = kzalloc(sizeof(struct axff_device), GFP_KERNEL);
if (!axff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, axff, axff_play);
if (error)
goto err_free_mem;
axff->report = report;
hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
return 0;
err_free_mem:
kfree(axff);
return error;
}
#else
static inline int axff_init(struct hid_device *hid)
{
return 0;
}
#endif
static int ax_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int error;
dev_dbg(&hdev->dev, "ACRUX HID hardware probe...\n");
error = hid_parse(hdev);
if (error) {
hid_err(hdev, "parse failed\n");
return error;
}
error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (error) {
hid_err(hdev, "hw start failed\n");
return error;
}
error = axff_init(hdev);
if (error) {
/*
* Do not fail device initialization completely as device
* may still be partially operable, just warn.
*/
hid_warn(hdev,
"Failed to enable force feedback support, error: %d\n",
error);
}
/*
* We need to start polling device right away, otherwise
* it will go into a coma.
*/
error = hid_hw_open(hdev);
if (error) {
dev_err(&hdev->dev, "hw open failed\n");
hid_hw_stop(hdev);
return error;
}
return 0;
}
static void ax_remove(struct hid_device *hdev)
{
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
static const struct hid_device_id ax_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0x0802), },
{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0xf705), },
{ }
};
MODULE_DEVICE_TABLE(hid, ax_devices);
static struct hid_driver ax_driver = {
.name = "acrux",
.id_table = ax_devices,
.probe = ax_probe,
.remove = ax_remove,
};
module_hid_driver(ax_driver);
MODULE_AUTHOR("Sergei Kolzun");
MODULE_DESCRIPTION("Force feedback support for ACRUX game controllers");
MODULE_LICENSE("GPL");

91
drivers/hid/hid-belkin.c Executable file
View File

@ -0,0 +1,91 @@
/*
* HID driver for some belkin "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define BELKIN_HIDDEV 0x01
#define BELKIN_WKBD 0x02
#define belkin_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int belkin_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER ||
!(quirks & BELKIN_WKBD))
return 0;
switch (usage->hid & HID_USAGE) {
case 0x03a: belkin_map_key_clear(KEY_SOUND); break;
case 0x03b: belkin_map_key_clear(KEY_CAMERA); break;
case 0x03c: belkin_map_key_clear(KEY_DOCUMENTS); break;
default:
return 0;
}
return 1;
}
static int belkin_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
unsigned long quirks = id->driver_data;
int ret;
hid_set_drvdata(hdev, (void *)quirks);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
((quirks & BELKIN_HIDDEV) ? HID_CONNECT_HIDDEV_FORCE : 0));
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
return 0;
err_free:
return ret;
}
static const struct hid_device_id belkin_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM),
.driver_data = BELKIN_HIDDEV },
{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD),
.driver_data = BELKIN_WKBD },
{ }
};
MODULE_DEVICE_TABLE(hid, belkin_devices);
static struct hid_driver belkin_driver = {
.name = "belkin",
.id_table = belkin_devices,
.input_mapping = belkin_input_mapping,
.probe = belkin_probe,
};
module_hid_driver(belkin_driver);
MODULE_LICENSE("GPL");

160
drivers/hid/hid-betopff.c Executable file
View File

@ -0,0 +1,160 @@
/*
* Force feedback support for Betop based devices
*
* The devices are distributed under various names and the same USB device ID
* can be used in both adapters and actual game controllers.
*
* 0x11c2:0x2208 "BTP2185 BFM mode Joystick"
* - tested with BTP2185 BFM Mode.
*
* 0x11C0:0x5506 "BTP2185 PC mode Joystick"
* - tested with BTP2185 PC Mode.
*
* 0x8380:0x1850 "BTP2185 V2 PC mode USB Gamepad"
* - tested with BTP2185 PC Mode with another version.
*
* 0x20bc:0x5500 "BTP2185 V2 BFM mode Joystick"
* - tested with BTP2171s.
* Copyright (c) 2014 Huang Bo <huangbobupt@163.com>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/hid.h>
#include "hid-ids.h"
struct betopff_device {
struct hid_report *report;
};
static int hid_betopff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct betopff_device *betopff = data;
__u16 left, right;
left = effect->u.rumble.strong_magnitude;
right = effect->u.rumble.weak_magnitude;
betopff->report->field[2]->value[0] = left / 256;
betopff->report->field[3]->value[0] = right / 256;
hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
return 0;
}
static int betopff_init(struct hid_device *hid)
{
struct betopff_device *betopff;
struct hid_report *report;
struct hid_input *hidinput =
list_first_entry(&hid->inputs, struct hid_input, list);
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev = hidinput->input;
int field_count = 0;
int error;
int i, j;
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
/*
* Actually there are 4 fields for 4 Bytes as below:
* -----------------------------------------
* Byte0 Byte1 Byte2 Byte3
* 0x00 0x00 left_motor right_motor
* -----------------------------------------
* Do init them with default value.
*/
for (i = 0; i < report->maxfield; i++) {
for (j = 0; j < report->field[i]->report_count; j++) {
report->field[i]->value[j] = 0x00;
field_count++;
}
}
if (field_count < 4) {
hid_err(hid, "not enough fields in the report: %d\n",
field_count);
return -ENODEV;
}
betopff = kzalloc(sizeof(*betopff), GFP_KERNEL);
if (!betopff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, betopff, hid_betopff_play);
if (error) {
kfree(betopff);
return error;
}
betopff->report = report;
hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force feedback for betop devices by huangbo <huangbobupt@163.com>\n");
return 0;
}
static int betop_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
if (id->driver_data)
hdev->quirks |= HID_QUIRK_MULTI_INPUT;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
betopff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id betop_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185BFM, 0x2208) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185PC, 0x5506) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185V2PC, 0x1850) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185V2BFM, 0x5500) },
{ }
};
MODULE_DEVICE_TABLE(hid, betop_devices);
static struct hid_driver betop_driver = {
.name = "betop",
.id_table = betop_devices,
.probe = betop_probe,
};
module_hid_driver(betop_driver);
MODULE_LICENSE("GPL");

74
drivers/hid/hid-cherry.c Executable file
View File

@ -0,0 +1,74 @@
/*
* HID driver for some cherry "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
/*
* Cherry Cymotion keyboard have an invalid HID report descriptor,
* that needs fixing before we can parse it.
*/
static __u8 *ch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 18 && rdesc[11] == 0x3c && rdesc[12] == 0x02) {
hid_info(hdev, "fixing up Cherry Cymotion report descriptor\n");
rdesc[11] = rdesc[16] = 0xff;
rdesc[12] = rdesc[17] = 0x03;
}
return rdesc;
}
#define ch_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x301: ch_map_key_clear(KEY_PROG1); break;
case 0x302: ch_map_key_clear(KEY_PROG2); break;
case 0x303: ch_map_key_clear(KEY_PROG3); break;
default:
return 0;
}
return 1;
}
static const struct hid_device_id ch_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) },
{ }
};
MODULE_DEVICE_TABLE(hid, ch_devices);
static struct hid_driver ch_driver = {
.name = "cherry",
.id_table = ch_devices,
.report_fixup = ch_report_fixup,
.input_mapping = ch_input_mapping,
};
module_hid_driver(ch_driver);
MODULE_LICENSE("GPL");

102
drivers/hid/hid-chicony.c Executable file
View File

@ -0,0 +1,102 @@
/*
* HID driver for some chicony "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2007 Paul Walmsley
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include "hid-ids.h"
#define ch_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
return 0;
set_bit(EV_REP, hi->input->evbit);
switch (usage->hid & HID_USAGE) {
case 0xff01: ch_map_key_clear(BTN_1); break;
case 0xff02: ch_map_key_clear(BTN_2); break;
case 0xff03: ch_map_key_clear(BTN_3); break;
case 0xff04: ch_map_key_clear(BTN_4); break;
case 0xff05: ch_map_key_clear(BTN_5); break;
case 0xff06: ch_map_key_clear(BTN_6); break;
case 0xff07: ch_map_key_clear(BTN_7); break;
case 0xff08: ch_map_key_clear(BTN_8); break;
case 0xff09: ch_map_key_clear(BTN_9); break;
case 0xff0a: ch_map_key_clear(BTN_A); break;
case 0xff0b: ch_map_key_clear(BTN_B); break;
case 0x00f1: ch_map_key_clear(KEY_WLAN); break;
case 0x00f2: ch_map_key_clear(KEY_BRIGHTNESSDOWN); break;
case 0x00f3: ch_map_key_clear(KEY_BRIGHTNESSUP); break;
case 0x00f4: ch_map_key_clear(KEY_DISPLAY_OFF); break;
case 0x00f7: ch_map_key_clear(KEY_CAMERA); break;
case 0x00f8: ch_map_key_clear(KEY_PROG1); break;
default:
return 0;
}
return 1;
}
static __u8 *ch_switch12_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
/* Change usage maximum and logical maximum from 0x7fff to
* 0x2fff, so they don't exceed HID_MAX_USAGES */
switch (hdev->product) {
case USB_DEVICE_ID_CHICONY_ACER_SWITCH12:
if (*rsize >= 128 && rdesc[64] == 0xff && rdesc[65] == 0x7f
&& rdesc[69] == 0xff && rdesc[70] == 0x7f) {
hid_info(hdev, "Fixing up report descriptor\n");
rdesc[65] = rdesc[70] = 0x2f;
}
break;
}
}
return rdesc;
}
static const struct hid_device_id ch_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_ACER_SWITCH12) },
{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_JESS_ZEN_AIO_KBD) },
{ }
};
MODULE_DEVICE_TABLE(hid, ch_devices);
static struct hid_driver ch_driver = {
.name = "chicony",
.id_table = ch_devices,
.report_fixup = ch_switch12_report_fixup,
.input_mapping = ch_input_mapping,
};
module_hid_driver(ch_driver);
MODULE_LICENSE("GPL");

2817
drivers/hid/hid-core.c Executable file

File diff suppressed because it is too large Load Diff

703
drivers/hid/hid-corsair.c Executable file
View File

@ -0,0 +1,703 @@
/*
* HID driver for Corsair devices
*
* Supported devices:
* - Vengeance K90 Keyboard
*
* Copyright (c) 2015 Clement Vuchener
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
#include "hid-ids.h"
#define CORSAIR_USE_K90_MACRO (1<<0)
#define CORSAIR_USE_K90_BACKLIGHT (1<<1)
struct k90_led {
struct led_classdev cdev;
int brightness;
struct work_struct work;
bool removed;
};
struct k90_drvdata {
struct k90_led record_led;
};
struct corsair_drvdata {
unsigned long quirks;
struct k90_drvdata *k90;
struct k90_led *backlight;
};
#define K90_GKEY_COUNT 18
static int corsair_usage_to_gkey(unsigned int usage)
{
/* G1 (0xd0) to G16 (0xdf) */
if (usage >= 0xd0 && usage <= 0xdf)
return usage - 0xd0 + 1;
/* G17 (0xe8) to G18 (0xe9) */
if (usage >= 0xe8 && usage <= 0xe9)
return usage - 0xe8 + 17;
return 0;
}
static unsigned short corsair_gkey_map[K90_GKEY_COUNT] = {
BTN_TRIGGER_HAPPY1,
BTN_TRIGGER_HAPPY2,
BTN_TRIGGER_HAPPY3,
BTN_TRIGGER_HAPPY4,
BTN_TRIGGER_HAPPY5,
BTN_TRIGGER_HAPPY6,
BTN_TRIGGER_HAPPY7,
BTN_TRIGGER_HAPPY8,
BTN_TRIGGER_HAPPY9,
BTN_TRIGGER_HAPPY10,
BTN_TRIGGER_HAPPY11,
BTN_TRIGGER_HAPPY12,
BTN_TRIGGER_HAPPY13,
BTN_TRIGGER_HAPPY14,
BTN_TRIGGER_HAPPY15,
BTN_TRIGGER_HAPPY16,
BTN_TRIGGER_HAPPY17,
BTN_TRIGGER_HAPPY18,
};
module_param_array_named(gkey_codes, corsair_gkey_map, ushort, NULL, S_IRUGO);
MODULE_PARM_DESC(gkey_codes, "Key codes for the G-keys");
static unsigned short corsair_record_keycodes[2] = {
BTN_TRIGGER_HAPPY19,
BTN_TRIGGER_HAPPY20
};
module_param_array_named(recordkey_codes, corsair_record_keycodes, ushort,
NULL, S_IRUGO);
MODULE_PARM_DESC(recordkey_codes, "Key codes for the MR (start and stop record) button");
static unsigned short corsair_profile_keycodes[3] = {
BTN_TRIGGER_HAPPY21,
BTN_TRIGGER_HAPPY22,
BTN_TRIGGER_HAPPY23
};
module_param_array_named(profilekey_codes, corsair_profile_keycodes, ushort,
NULL, S_IRUGO);
MODULE_PARM_DESC(profilekey_codes, "Key codes for the profile buttons");
#define CORSAIR_USAGE_SPECIAL_MIN 0xf0
#define CORSAIR_USAGE_SPECIAL_MAX 0xff
#define CORSAIR_USAGE_MACRO_RECORD_START 0xf6
#define CORSAIR_USAGE_MACRO_RECORD_STOP 0xf7
#define CORSAIR_USAGE_PROFILE 0xf1
#define CORSAIR_USAGE_M1 0xf1
#define CORSAIR_USAGE_M2 0xf2
#define CORSAIR_USAGE_M3 0xf3
#define CORSAIR_USAGE_PROFILE_MAX 0xf3
#define CORSAIR_USAGE_META_OFF 0xf4
#define CORSAIR_USAGE_META_ON 0xf5
#define CORSAIR_USAGE_LIGHT 0xfa
#define CORSAIR_USAGE_LIGHT_OFF 0xfa
#define CORSAIR_USAGE_LIGHT_DIM 0xfb
#define CORSAIR_USAGE_LIGHT_MEDIUM 0xfc
#define CORSAIR_USAGE_LIGHT_BRIGHT 0xfd
#define CORSAIR_USAGE_LIGHT_MAX 0xfd
/* USB control protocol */
#define K90_REQUEST_BRIGHTNESS 49
#define K90_REQUEST_MACRO_MODE 2
#define K90_REQUEST_STATUS 4
#define K90_REQUEST_GET_MODE 5
#define K90_REQUEST_PROFILE 20
#define K90_MACRO_MODE_SW 0x0030
#define K90_MACRO_MODE_HW 0x0001
#define K90_MACRO_LED_ON 0x0020
#define K90_MACRO_LED_OFF 0x0040
/*
* LED class devices
*/
#define K90_BACKLIGHT_LED_SUFFIX "::backlight"
#define K90_RECORD_LED_SUFFIX "::record"
static enum led_brightness k90_backlight_get(struct led_classdev *led_cdev)
{
int ret;
struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
struct device *dev = led->cdev.dev->parent;
struct usb_interface *usbif = to_usb_interface(dev->parent);
struct usb_device *usbdev = interface_to_usbdev(usbif);
int brightness;
char *data;
data = kmalloc(8, GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
K90_REQUEST_STATUS,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, 0, 0, data, 8,
USB_CTRL_SET_TIMEOUT);
if (ret < 5) {
dev_warn(dev, "Failed to get K90 initial state (error %d).\n",
ret);
ret = -EIO;
goto out;
}
brightness = data[4];
if (brightness < 0 || brightness > 3) {
dev_warn(dev,
"Read invalid backlight brightness: %02hhx.\n",
data[4]);
ret = -EIO;
goto out;
}
ret = brightness;
out:
kfree(data);
return ret;
}
static enum led_brightness k90_record_led_get(struct led_classdev *led_cdev)
{
struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
return led->brightness;
}
static void k90_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
led->brightness = brightness;
schedule_work(&led->work);
}
static void k90_backlight_work(struct work_struct *work)
{
int ret;
struct k90_led *led = container_of(work, struct k90_led, work);
struct device *dev;
struct usb_interface *usbif;
struct usb_device *usbdev;
if (led->removed)
return;
dev = led->cdev.dev->parent;
usbif = to_usb_interface(dev->parent);
usbdev = interface_to_usbdev(usbif);
ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
K90_REQUEST_BRIGHTNESS,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, led->brightness, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT);
if (ret != 0)
dev_warn(dev, "Failed to set backlight brightness (error: %d).\n",
ret);
}
static void k90_record_led_work(struct work_struct *work)
{
int ret;
struct k90_led *led = container_of(work, struct k90_led, work);
struct device *dev;
struct usb_interface *usbif;
struct usb_device *usbdev;
int value;
if (led->removed)
return;
dev = led->cdev.dev->parent;
usbif = to_usb_interface(dev->parent);
usbdev = interface_to_usbdev(usbif);
if (led->brightness > 0)
value = K90_MACRO_LED_ON;
else
value = K90_MACRO_LED_OFF;
ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
K90_REQUEST_MACRO_MODE,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, value, 0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (ret != 0)
dev_warn(dev, "Failed to set record LED state (error: %d).\n",
ret);
}
/*
* Keyboard attributes
*/
static ssize_t k90_show_macro_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
struct usb_interface *usbif = to_usb_interface(dev->parent);
struct usb_device *usbdev = interface_to_usbdev(usbif);
const char *macro_mode;
char *data;
data = kmalloc(2, GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
K90_REQUEST_GET_MODE,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, 0, 0, data, 2,
USB_CTRL_SET_TIMEOUT);
if (ret < 1) {
dev_warn(dev, "Failed to get K90 initial mode (error %d).\n",
ret);
ret = -EIO;
goto out;
}
switch (data[0]) {
case K90_MACRO_MODE_HW:
macro_mode = "HW";
break;
case K90_MACRO_MODE_SW:
macro_mode = "SW";
break;
default:
dev_warn(dev, "K90 in unknown mode: %02hhx.\n",
data[0]);
ret = -EIO;
goto out;
}
ret = snprintf(buf, PAGE_SIZE, "%s\n", macro_mode);
out:
kfree(data);
return ret;
}
static ssize_t k90_store_macro_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
struct usb_interface *usbif = to_usb_interface(dev->parent);
struct usb_device *usbdev = interface_to_usbdev(usbif);
__u16 value;
if (strncmp(buf, "SW", 2) == 0)
value = K90_MACRO_MODE_SW;
else if (strncmp(buf, "HW", 2) == 0)
value = K90_MACRO_MODE_HW;
else
return -EINVAL;
ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
K90_REQUEST_MACRO_MODE,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, value, 0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (ret != 0) {
dev_warn(dev, "Failed to set macro mode.\n");
return ret;
}
return count;
}
static ssize_t k90_show_current_profile(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
struct usb_interface *usbif = to_usb_interface(dev->parent);
struct usb_device *usbdev = interface_to_usbdev(usbif);
int current_profile;
char *data;
data = kmalloc(8, GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
K90_REQUEST_STATUS,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, 0, 0, data, 8,
USB_CTRL_SET_TIMEOUT);
if (ret < 8) {
dev_warn(dev, "Failed to get K90 initial state (error %d).\n",
ret);
ret = -EIO;
goto out;
}
current_profile = data[7];
if (current_profile < 1 || current_profile > 3) {
dev_warn(dev, "Read invalid current profile: %02hhx.\n",
data[7]);
ret = -EIO;
goto out;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", current_profile);
out:
kfree(data);
return ret;
}
static ssize_t k90_store_current_profile(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
struct usb_interface *usbif = to_usb_interface(dev->parent);
struct usb_device *usbdev = interface_to_usbdev(usbif);
int profile;
if (kstrtoint(buf, 10, &profile))
return -EINVAL;
if (profile < 1 || profile > 3)
return -EINVAL;
ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
K90_REQUEST_PROFILE,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, profile, 0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (ret != 0) {
dev_warn(dev, "Failed to change current profile (error %d).\n",
ret);
return ret;
}
return count;
}
static DEVICE_ATTR(macro_mode, 0644, k90_show_macro_mode, k90_store_macro_mode);
static DEVICE_ATTR(current_profile, 0644, k90_show_current_profile,
k90_store_current_profile);
static struct attribute *k90_attrs[] = {
&dev_attr_macro_mode.attr,
&dev_attr_current_profile.attr,
NULL
};
static const struct attribute_group k90_attr_group = {
.attrs = k90_attrs,
};
/*
* Driver functions
*/
static int k90_init_backlight(struct hid_device *dev)
{
int ret;
struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
size_t name_sz;
char *name;
drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL);
if (!drvdata->backlight) {
ret = -ENOMEM;
goto fail_backlight_alloc;
}
name_sz =
strlen(dev_name(&dev->dev)) + sizeof(K90_BACKLIGHT_LED_SUFFIX);
name = kzalloc(name_sz, GFP_KERNEL);
if (!name) {
ret = -ENOMEM;
goto fail_name_alloc;
}
snprintf(name, name_sz, "%s" K90_BACKLIGHT_LED_SUFFIX,
dev_name(&dev->dev));
drvdata->backlight->removed = false;
drvdata->backlight->cdev.name = name;
drvdata->backlight->cdev.max_brightness = 3;
drvdata->backlight->cdev.brightness_set = k90_brightness_set;
drvdata->backlight->cdev.brightness_get = k90_backlight_get;
INIT_WORK(&drvdata->backlight->work, k90_backlight_work);
ret = led_classdev_register(&dev->dev, &drvdata->backlight->cdev);
if (ret != 0)
goto fail_register_cdev;
return 0;
fail_register_cdev:
kfree(drvdata->backlight->cdev.name);
fail_name_alloc:
kfree(drvdata->backlight);
drvdata->backlight = NULL;
fail_backlight_alloc:
return ret;
}
static int k90_init_macro_functions(struct hid_device *dev)
{
int ret;
struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
struct k90_drvdata *k90;
size_t name_sz;
char *name;
k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL);
if (!k90) {
ret = -ENOMEM;
goto fail_drvdata;
}
drvdata->k90 = k90;
/* Init LED device for record LED */
name_sz = strlen(dev_name(&dev->dev)) + sizeof(K90_RECORD_LED_SUFFIX);
name = kzalloc(name_sz, GFP_KERNEL);
if (!name) {
ret = -ENOMEM;
goto fail_record_led_alloc;
}
snprintf(name, name_sz, "%s" K90_RECORD_LED_SUFFIX,
dev_name(&dev->dev));
k90->record_led.removed = false;
k90->record_led.cdev.name = name;
k90->record_led.cdev.max_brightness = 1;
k90->record_led.cdev.brightness_set = k90_brightness_set;
k90->record_led.cdev.brightness_get = k90_record_led_get;
INIT_WORK(&k90->record_led.work, k90_record_led_work);
k90->record_led.brightness = 0;
ret = led_classdev_register(&dev->dev, &k90->record_led.cdev);
if (ret != 0)
goto fail_record_led;
/* Init attributes */
ret = sysfs_create_group(&dev->dev.kobj, &k90_attr_group);
if (ret != 0)
goto fail_sysfs;
return 0;
fail_sysfs:
k90->record_led.removed = true;
led_classdev_unregister(&k90->record_led.cdev);
cancel_work_sync(&k90->record_led.work);
fail_record_led:
kfree(k90->record_led.cdev.name);
fail_record_led_alloc:
kfree(k90);
fail_drvdata:
drvdata->k90 = NULL;
return ret;
}
static void k90_cleanup_backlight(struct hid_device *dev)
{
struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
if (drvdata->backlight) {
drvdata->backlight->removed = true;
led_classdev_unregister(&drvdata->backlight->cdev);
cancel_work_sync(&drvdata->backlight->work);
kfree(drvdata->backlight->cdev.name);
kfree(drvdata->backlight);
}
}
static void k90_cleanup_macro_functions(struct hid_device *dev)
{
struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
struct k90_drvdata *k90 = drvdata->k90;
if (k90) {
sysfs_remove_group(&dev->dev.kobj, &k90_attr_group);
k90->record_led.removed = true;
led_classdev_unregister(&k90->record_led.cdev);
cancel_work_sync(&k90->record_led.work);
kfree(k90->record_led.cdev.name);
kfree(k90);
}
}
static int corsair_probe(struct hid_device *dev, const struct hid_device_id *id)
{
int ret;
unsigned long quirks = id->driver_data;
struct corsair_drvdata *drvdata;
struct usb_interface *usbif = to_usb_interface(dev->dev.parent);
drvdata = devm_kzalloc(&dev->dev, sizeof(struct corsair_drvdata),
GFP_KERNEL);
if (drvdata == NULL)
return -ENOMEM;
drvdata->quirks = quirks;
hid_set_drvdata(dev, drvdata);
ret = hid_parse(dev);
if (ret != 0) {
hid_err(dev, "parse failed\n");
return ret;
}
ret = hid_hw_start(dev, HID_CONNECT_DEFAULT);
if (ret != 0) {
hid_err(dev, "hw start failed\n");
return ret;
}
if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) {
if (quirks & CORSAIR_USE_K90_MACRO) {
ret = k90_init_macro_functions(dev);
if (ret != 0)
hid_warn(dev, "Failed to initialize K90 macro functions.\n");
}
if (quirks & CORSAIR_USE_K90_BACKLIGHT) {
ret = k90_init_backlight(dev);
if (ret != 0)
hid_warn(dev, "Failed to initialize K90 backlight.\n");
}
}
return 0;
}
static void corsair_remove(struct hid_device *dev)
{
k90_cleanup_macro_functions(dev);
k90_cleanup_backlight(dev);
hid_hw_stop(dev);
}
static int corsair_event(struct hid_device *dev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
if (!drvdata->k90)
return 0;
switch (usage->hid & HID_USAGE) {
case CORSAIR_USAGE_MACRO_RECORD_START:
drvdata->k90->record_led.brightness = 1;
break;
case CORSAIR_USAGE_MACRO_RECORD_STOP:
drvdata->k90->record_led.brightness = 0;
break;
default:
break;
}
return 0;
}
static int corsair_input_mapping(struct hid_device *dev,
struct hid_input *input,
struct hid_field *field,
struct hid_usage *usage, unsigned long **bit,
int *max)
{
int gkey;
gkey = corsair_usage_to_gkey(usage->hid & HID_USAGE);
if (gkey != 0) {
hid_map_usage_clear(input, usage, bit, max, EV_KEY,
corsair_gkey_map[gkey - 1]);
return 1;
}
if ((usage->hid & HID_USAGE) >= CORSAIR_USAGE_SPECIAL_MIN &&
(usage->hid & HID_USAGE) <= CORSAIR_USAGE_SPECIAL_MAX) {
switch (usage->hid & HID_USAGE) {
case CORSAIR_USAGE_MACRO_RECORD_START:
hid_map_usage_clear(input, usage, bit, max, EV_KEY,
corsair_record_keycodes[0]);
return 1;
case CORSAIR_USAGE_MACRO_RECORD_STOP:
hid_map_usage_clear(input, usage, bit, max, EV_KEY,
corsair_record_keycodes[1]);
return 1;
case CORSAIR_USAGE_M1:
hid_map_usage_clear(input, usage, bit, max, EV_KEY,
corsair_profile_keycodes[0]);
return 1;
case CORSAIR_USAGE_M2:
hid_map_usage_clear(input, usage, bit, max, EV_KEY,
corsair_profile_keycodes[1]);
return 1;
case CORSAIR_USAGE_M3:
hid_map_usage_clear(input, usage, bit, max, EV_KEY,
corsair_profile_keycodes[2]);
return 1;
default:
return -1;
}
}
return 0;
}
static const struct hid_device_id corsair_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90),
.driver_data = CORSAIR_USE_K90_MACRO |
CORSAIR_USE_K90_BACKLIGHT },
{}
};
MODULE_DEVICE_TABLE(hid, corsair_devices);
static struct hid_driver corsair_driver = {
.name = "corsair",
.id_table = corsair_devices,
.probe = corsair_probe,
.event = corsair_event,
.remove = corsair_remove,
.input_mapping = corsair_input_mapping,
};
static int __init corsair_init(void)
{
return hid_register_driver(&corsair_driver);
}
static void corsair_exit(void)
{
hid_unregister_driver(&corsair_driver);
}
module_init(corsair_init);
module_exit(corsair_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Clement Vuchener");
MODULE_DESCRIPTION("HID driver for Corsair devices");

1231
drivers/hid/hid-cp2112.c Executable file

File diff suppressed because it is too large Load Diff

148
drivers/hid/hid-cypress.c Executable file
View File

@ -0,0 +1,148 @@
/*
* HID driver for some cypress "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/module.h>
#include "hid-ids.h"
#define CP_RDESC_SWAPPED_MIN_MAX 0x01
#define CP_2WHEEL_MOUSE_HACK 0x02
#define CP_2WHEEL_MOUSE_HACK_ON 0x04
/*
* Some USB barcode readers from cypress have usage min and usage max in
* the wrong order
*/
static __u8 *cp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
unsigned int i;
if (!(quirks & CP_RDESC_SWAPPED_MIN_MAX))
return rdesc;
if (*rsize < 4)
return rdesc;
for (i = 0; i < *rsize - 4; i++)
if (rdesc[i] == 0x29 && rdesc[i + 2] == 0x19) {
rdesc[i] = 0x19;
rdesc[i + 2] = 0x29;
swap(rdesc[i + 3], rdesc[i + 1]);
}
return rdesc;
}
static int cp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
if (!(quirks & CP_2WHEEL_MOUSE_HACK))
return 0;
if (usage->type == EV_REL && usage->code == REL_WHEEL)
set_bit(REL_HWHEEL, *bit);
if (usage->hid == 0x00090005)
return -1;
return 0;
}
static int cp_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
!usage->type || !(quirks & CP_2WHEEL_MOUSE_HACK))
return 0;
if (usage->hid == 0x00090005) {
if (value)
quirks |= CP_2WHEEL_MOUSE_HACK_ON;
else
quirks &= ~CP_2WHEEL_MOUSE_HACK_ON;
hid_set_drvdata(hdev, (void *)quirks);
return 1;
}
if (usage->code == REL_WHEEL && (quirks & CP_2WHEEL_MOUSE_HACK_ON)) {
struct input_dev *input = field->hidinput->input;
input_event(input, usage->type, REL_HWHEEL, value);
return 1;
}
return 0;
}
static int cp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
unsigned long quirks = id->driver_data;
int ret;
hid_set_drvdata(hdev, (void *)quirks);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
return 0;
err_free:
return ret;
}
static const struct hid_device_id cp_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1),
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2),
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3),
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4),
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE),
.driver_data = CP_2WHEEL_MOUSE_HACK },
{ }
};
MODULE_DEVICE_TABLE(hid, cp_devices);
static struct hid_driver cp_driver = {
.name = "cypress",
.id_table = cp_devices,
.report_fixup = cp_report_fixup,
.input_mapped = cp_input_mapped,
.event = cp_event,
.probe = cp_probe,
};
module_hid_driver(cp_driver);
MODULE_LICENSE("GPL");

1257
drivers/hid/hid-debug.c Executable file

File diff suppressed because it is too large Load Diff

358
drivers/hid/hid-dr.c Executable file
View File

@ -0,0 +1,358 @@
/*
* Force feedback support for DragonRise Inc. game controllers
*
* From what I have gathered, these devices are mass produced in China and are
* distributed under several vendors. They often share the same design as
* the original PlayStation DualShock controller.
*
* 0079:0006 "DragonRise Inc. Generic USB Joystick "
* - tested with a Tesun USB-703 game controller.
*
* Copyright (c) 2009 Richard Walmsley <richwalm@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#ifdef CONFIG_DRAGONRISE_FF
struct drff_device {
struct hid_report *report;
};
static int drff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct drff_device *drff = data;
int strong, weak;
strong = effect->u.rumble.strong_magnitude;
weak = effect->u.rumble.weak_magnitude;
dbg_hid("called with 0x%04x 0x%04x", strong, weak);
if (strong || weak) {
strong = strong * 0xff / 0xffff;
weak = weak * 0xff / 0xffff;
/* While reverse engineering this device, I found that when
this value is set, it causes the strong rumble to function
at a near maximum speed, so we'll bypass it. */
if (weak == 0x0a)
weak = 0x0b;
drff->report->field[0]->value[0] = 0x51;
drff->report->field[0]->value[1] = 0x00;
drff->report->field[0]->value[2] = weak;
drff->report->field[0]->value[4] = strong;
hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
drff->report->field[0]->value[0] = 0xfa;
drff->report->field[0]->value[1] = 0xfe;
} else {
drff->report->field[0]->value[0] = 0xf3;
drff->report->field[0]->value[1] = 0x00;
}
drff->report->field[0]->value[2] = 0x00;
drff->report->field[0]->value[4] = 0x00;
dbg_hid("running with 0x%02x 0x%02x", strong, weak);
hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
return 0;
}
static int drff_init(struct hid_device *hid)
{
struct drff_device *drff;
struct hid_report *report;
struct hid_input *hidinput = list_first_entry(&hid->inputs,
struct hid_input, list);
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
if (report->field[0]->report_count < 7) {
hid_err(hid, "not enough values in the field\n");
return -ENODEV;
}
drff = kzalloc(sizeof(struct drff_device), GFP_KERNEL);
if (!drff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, drff, drff_play);
if (error) {
kfree(drff);
return error;
}
drff->report = report;
drff->report->field[0]->value[0] = 0xf3;
drff->report->field[0]->value[1] = 0x00;
drff->report->field[0]->value[2] = 0x00;
drff->report->field[0]->value[3] = 0x00;
drff->report->field[0]->value[4] = 0x00;
drff->report->field[0]->value[5] = 0x00;
drff->report->field[0]->value[6] = 0x00;
hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force Feedback for DragonRise Inc. "
"game controllers by Richard Walmsley <richwalm@gmail.com>\n");
return 0;
}
#else
static inline int drff_init(struct hid_device *hid)
{
return 0;
}
#endif
/*
* The original descriptor of joystick with PID 0x0011, represented by DVTech PC
* JS19. It seems both copied from another device and a result of confusion
* either about the specification or about the program used to create the
* descriptor. In any case, it's a wonder it works on Windows.
*
* Usage Page (Desktop), ; Generic desktop controls (01h)
* Usage (Joystik), ; Joystik (04h, application collection)
* Collection (Application),
* Collection (Logical),
* Report Size (8),
* Report Count (5),
* Logical Minimum (0),
* Logical Maximum (255),
* Physical Minimum (0),
* Physical Maximum (255),
* Usage (X), ; X (30h, dynamic value)
* Usage (X), ; X (30h, dynamic value)
* Usage (X), ; X (30h, dynamic value)
* Usage (X), ; X (30h, dynamic value)
* Usage (Y), ; Y (31h, dynamic value)
* Input (Variable),
* Report Size (4),
* Report Count (1),
* Logical Maximum (7),
* Physical Maximum (315),
* Unit (Degrees),
* Usage (00h),
* Input (Variable, Null State),
* Unit,
* Report Size (1),
* Report Count (10),
* Logical Maximum (1),
* Physical Maximum (1),
* Usage Page (Button), ; Button (09h)
* Usage Minimum (01h),
* Usage Maximum (0Ah),
* Input (Variable),
* Usage Page (FF00h), ; FF00h, vendor-defined
* Report Size (1),
* Report Count (10),
* Logical Maximum (1),
* Physical Maximum (1),
* Usage (01h),
* Input (Variable),
* End Collection,
* Collection (Logical),
* Report Size (8),
* Report Count (4),
* Physical Maximum (255),
* Logical Maximum (255),
* Usage (02h),
* Output (Variable),
* End Collection,
* End Collection
*/
/* Size of the original descriptor of the PID 0x0011 joystick */
#define PID0011_RDESC_ORIG_SIZE 101
/* Fixed report descriptor for PID 0x011 joystick */
static __u8 pid0011_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x14, /* Logical Minimum (0), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x01, /* Input (Constant), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x95, 0x02, /* Report Count (2), */
0x09, 0x30, /* Usage (X), */
0x09, 0x31, /* Usage (Y), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x01, /* Input (Constant), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x0A, /* Report Count (10), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x0A, /* Usage Maximum (0Ah), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x0A, /* Report Count (10), */
0x81, 0x01, /* Input (Constant), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
static __u8 pid0006_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x04, /* Usage (Joystick) */
0xA1, 0x01, /* Collection (Application) */
0xA1, 0x02, /* Collection (Logical) */
0x75, 0x08, /* Report Size (8) */
0x95, 0x05, /* Report Count (5) */
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xFF, 0x00, /* Logical Maximum (255) */
0x35, 0x00, /* Physical Minimum (0) */
0x46, 0xFF, 0x00, /* Physical Maximum (255) */
0x09, 0x30, /* Usage (X) */
0x09, 0x33, /* Usage (Ry) */
0x09, 0x32, /* Usage (Z) */
0x09, 0x31, /* Usage (Y) */
0x09, 0x34, /* Usage (Ry) */
0x81, 0x02, /* Input (Variable) */
0x75, 0x04, /* Report Size (4) */
0x95, 0x01, /* Report Count (1) */
0x25, 0x07, /* Logical Maximum (7) */
0x46, 0x3B, 0x01, /* Physical Maximum (315) */
0x65, 0x14, /* Unit (Centimeter) */
0x09, 0x39, /* Usage (Hat switch) */
0x81, 0x42, /* Input (Variable) */
0x65, 0x00, /* Unit (None) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x0C, /* Report Count (12) */
0x25, 0x01, /* Logical Maximum (1) */
0x45, 0x01, /* Physical Maximum (1) */
0x05, 0x09, /* Usage Page (Button) */
0x19, 0x01, /* Usage Minimum (0x01) */
0x29, 0x0C, /* Usage Maximum (0x0C) */
0x81, 0x02, /* Input (Variable) */
0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x08, /* Report Count (8) */
0x25, 0x01, /* Logical Maximum (1) */
0x45, 0x01, /* Physical Maximum (1) */
0x09, 0x01, /* Usage (0x01) */
0x81, 0x02, /* Input (Variable) */
0xC0, /* End Collection */
0xA1, 0x02, /* Collection (Logical) */
0x75, 0x08, /* Report Size (8) */
0x95, 0x07, /* Report Count (7) */
0x46, 0xFF, 0x00, /* Physical Maximum (255) */
0x26, 0xFF, 0x00, /* Logical Maximum (255) */
0x09, 0x02, /* Usage (0x02) */
0x91, 0x02, /* Output (Variable) */
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;
case 0x0006:
if (*rsize == sizeof(pid0006_rdesc_fixed)) {
rdesc = pid0006_rdesc_fixed;
*rsize = sizeof(pid0006_rdesc_fixed);
}
break;
}
return rdesc;
}
static int dr_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
dev_dbg(&hdev->dev, "DragonRise Inc. HID hardware probe...");
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
switch (hdev->product) {
case 0x0006:
ret = drff_init(hdev);
if (ret) {
dev_err(&hdev->dev, "force feedback init failed\n");
hid_hw_stop(hdev);
goto err;
}
break;
}
return 0;
err:
return ret;
}
static const struct hid_device_id dr_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
{ }
};
MODULE_DEVICE_TABLE(hid, dr_devices);
static struct hid_driver dr_driver = {
.name = "dragonrise",
.id_table = dr_devices,
.report_fixup = dr_report_fixup,
.probe = dr_probe,
};
module_hid_driver(dr_driver);
MODULE_LICENSE("GPL");

46
drivers/hid/hid-elecom.c Executable file
View File

@ -0,0 +1,46 @@
/*
* HID driver for Elecom BM084 (bluetooth mouse).
* Removes a non-existing horizontal wheel from
* the HID descriptor.
* (This module is based on "hid-ortek".)
*
* Copyright (c) 2010 Richard Nauber <Richard.Nauber@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 48 && rdesc[46] == 0x05 && rdesc[47] == 0x0c) {
hid_info(hdev, "Fixing up Elecom BM084 report descriptor\n");
rdesc[47] = 0x00;
}
return rdesc;
}
static const struct hid_device_id elecom_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084)},
{ }
};
MODULE_DEVICE_TABLE(hid, elecom_devices);
static struct hid_driver elecom_driver = {
.name = "elecom",
.id_table = elecom_devices,
.report_fixup = elecom_report_fixup
};
module_hid_driver(elecom_driver);
MODULE_LICENSE("GPL");

314
drivers/hid/hid-elo.c Executable file
View File

@ -0,0 +1,314 @@
/*
* HID driver for ELO usb touchscreen 4000/4500
*
* Copyright (c) 2013 Jiri Slaby
*
* Data parsing taken from elousb driver by Vojtech Pavlik.
*
* This driver is licensed under the terms of GPLv2.
*/
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include "hid-ids.h"
#define ELO_PERIODIC_READ_INTERVAL HZ
#define ELO_SMARTSET_CMD_TIMEOUT 2000 /* msec */
/* Elo SmartSet commands */
#define ELO_FLUSH_SMARTSET_RESPONSES 0x02 /* Flush all pending smartset responses */
#define ELO_SEND_SMARTSET_COMMAND 0x05 /* Send a smartset command */
#define ELO_GET_SMARTSET_RESPONSE 0x06 /* Get a smartset response */
#define ELO_DIAG 0x64 /* Diagnostics command */
#define ELO_SMARTSET_PACKET_SIZE 8
struct elo_priv {
struct usb_device *usbdev;
struct delayed_work work;
unsigned char buffer[ELO_SMARTSET_PACKET_SIZE];
};
static struct workqueue_struct *wq;
static bool use_fw_quirk = true;
module_param(use_fw_quirk, bool, S_IRUGO);
MODULE_PARM_DESC(use_fw_quirk, "Do periodic pokes for broken M firmwares (default = true)");
static int elo_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
struct input_dev *input = hidinput->input;
/*
* ELO devices have one Button usage in GenDesk field, which makes
* hid-input map it to BTN_LEFT; that confuses userspace, which then
* considers the device to be a mouse/touchpad instead of touchscreen.
*/
clear_bit(BTN_LEFT, input->keybit);
set_bit(BTN_TOUCH, input->keybit);
set_bit(ABS_PRESSURE, input->absbit);
input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 0);
return 0;
}
static void elo_process_data(struct input_dev *input, const u8 *data, int size)
{
int press;
input_report_abs(input, ABS_X, (data[3] << 8) | data[2]);
input_report_abs(input, ABS_Y, (data[5] << 8) | data[4]);
press = 0;
if (data[1] & 0x80)
press = (data[7] << 8) | data[6];
input_report_abs(input, ABS_PRESSURE, press);
if (data[1] & 0x03) {
input_report_key(input, BTN_TOUCH, 1);
input_sync(input);
}
if (data[1] & 0x04)
input_report_key(input, BTN_TOUCH, 0);
input_sync(input);
}
static int elo_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
struct hid_input *hidinput;
if (!(hdev->claimed & HID_CLAIMED_INPUT) || list_empty(&hdev->inputs))
return 0;
hidinput = list_first_entry(&hdev->inputs, struct hid_input, list);
switch (report->id) {
case 0:
if (data[0] == 'T') { /* Mandatory ELO packet marker */
elo_process_data(hidinput->input, data, size);
return 1;
}
break;
default: /* unknown report */
/* Unknown report type; pass upstream */
hid_info(hdev, "unknown report type %d\n", report->id);
break;
}
return 0;
}
static int elo_smartset_send_get(struct usb_device *dev, u8 command,
void *data)
{
unsigned int pipe;
u8 dir;
if (command == ELO_SEND_SMARTSET_COMMAND) {
pipe = usb_sndctrlpipe(dev, 0);
dir = USB_DIR_OUT;
} else if (command == ELO_GET_SMARTSET_RESPONSE) {
pipe = usb_rcvctrlpipe(dev, 0);
dir = USB_DIR_IN;
} else
return -EINVAL;
return usb_control_msg(dev, pipe, command,
dir | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0, 0, data, ELO_SMARTSET_PACKET_SIZE,
ELO_SMARTSET_CMD_TIMEOUT);
}
static int elo_flush_smartset_responses(struct usb_device *dev)
{
return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
ELO_FLUSH_SMARTSET_RESPONSES,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
}
static void elo_work(struct work_struct *work)
{
struct elo_priv *priv = container_of(work, struct elo_priv, work.work);
struct usb_device *dev = priv->usbdev;
unsigned char *buffer = priv->buffer;
int ret;
ret = elo_flush_smartset_responses(dev);
if (ret < 0) {
dev_err(&dev->dev, "initial FLUSH_SMARTSET_RESPONSES failed, error %d\n",
ret);
goto fail;
}
/* send Diagnostics command */
*buffer = ELO_DIAG;
ret = elo_smartset_send_get(dev, ELO_SEND_SMARTSET_COMMAND, buffer);
if (ret < 0) {
dev_err(&dev->dev, "send Diagnostics Command failed, error %d\n",
ret);
goto fail;
}
/* get the result */
ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE, buffer);
if (ret < 0) {
dev_err(&dev->dev, "get Diagnostics Command response failed, error %d\n",
ret);
goto fail;
}
/* read the ack */
if (*buffer != 'A') {
ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE,
buffer);
if (ret < 0) {
dev_err(&dev->dev, "get acknowledge response failed, error %d\n",
ret);
goto fail;
}
}
fail:
ret = elo_flush_smartset_responses(dev);
if (ret < 0)
dev_err(&dev->dev, "final FLUSH_SMARTSET_RESPONSES failed, error %d\n",
ret);
queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL);
}
/*
* Not all Elo devices need the periodic HID descriptor reads.
* Only firmware version M needs this.
*/
static bool elo_broken_firmware(struct usb_device *dev)
{
struct usb_device *hub = dev->parent;
struct usb_device *child = NULL;
u16 fw_lvl = le16_to_cpu(dev->descriptor.bcdDevice);
u16 child_vid, child_pid;
int i;
if (!use_fw_quirk)
return false;
if (fw_lvl != 0x10d)
return false;
/* iterate sibling devices of the touch controller */
usb_hub_for_each_child(hub, i, child) {
child_vid = le16_to_cpu(child->descriptor.idVendor);
child_pid = le16_to_cpu(child->descriptor.idProduct);
/*
* If one of the devices below is present attached as a sibling of
* the touch controller then this is a newer IBM 4820 monitor that
* does not need the IBM-requested workaround if fw level is
* 0x010d - aka 'M'.
* No other HW can have this combination.
*/
if (child_vid==0x04b3) {
switch (child_pid) {
case 0x4676: /* 4820 21x Video */
case 0x4677: /* 4820 51x Video */
case 0x4678: /* 4820 2Lx Video */
case 0x4679: /* 4820 5Lx Video */
return false;
}
}
}
return true;
}
static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct elo_priv *priv;
int ret;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
INIT_DELAYED_WORK(&priv->work, elo_work);
priv->usbdev = interface_to_usbdev(to_usb_interface(hdev->dev.parent));
hid_set_drvdata(hdev, priv);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
if (elo_broken_firmware(priv->usbdev)) {
hid_info(hdev, "broken firmware found, installing workaround\n");
queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL);
}
return 0;
err_free:
kfree(priv);
return ret;
}
static void elo_remove(struct hid_device *hdev)
{
struct elo_priv *priv = hid_get_drvdata(hdev);
hid_hw_stop(hdev);
cancel_delayed_work_sync(&priv->work);
kfree(priv);
}
static const struct hid_device_id elo_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009), },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030), },
{ }
};
MODULE_DEVICE_TABLE(hid, elo_devices);
static struct hid_driver elo_driver = {
.name = "elo",
.id_table = elo_devices,
.probe = elo_probe,
.remove = elo_remove,
.raw_event = elo_raw_event,
.input_configured = elo_input_configured,
};
static int __init elo_driver_init(void)
{
int ret;
wq = create_singlethread_workqueue("elousb");
if (!wq)
return -ENOMEM;
ret = hid_register_driver(&elo_driver);
if (ret)
destroy_workqueue(wq);
return ret;
}
module_init(elo_driver_init);
static void __exit elo_driver_exit(void)
{
hid_unregister_driver(&elo_driver);
destroy_workqueue(wq);
}
module_exit(elo_driver_exit);
MODULE_AUTHOR("Jiri Slaby <jslaby@suse.cz>");
MODULE_LICENSE("GPL");

154
drivers/hid/hid-emsff.c Executable file
View File

@ -0,0 +1,154 @@
/*
* Force feedback support for EMS Trio Linker Plus II
*
* Copyright (c) 2010 Ignaz Forster <ignaz.forster@gmx.de>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/module.h>
#include "hid-ids.h"
struct emsff_device {
struct hid_report *report;
};
static int emsff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct emsff_device *emsff = data;
int weak, strong;
weak = effect->u.rumble.weak_magnitude;
strong = effect->u.rumble.strong_magnitude;
dbg_hid("called with 0x%04x 0x%04x\n", strong, weak);
weak = weak * 0xff / 0xffff;
strong = strong * 0xff / 0xffff;
emsff->report->field[0]->value[1] = weak;
emsff->report->field[0]->value[2] = strong;
dbg_hid("running with 0x%02x 0x%02x\n", strong, weak);
hid_hw_request(hid, emsff->report, HID_REQ_SET_REPORT);
return 0;
}
static int emsff_init(struct hid_device *hid)
{
struct emsff_device *emsff;
struct hid_report *report;
struct hid_input *hidinput = list_first_entry(&hid->inputs,
struct hid_input, list);
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
if (report->field[0]->report_count < 7) {
hid_err(hid, "not enough values in the field\n");
return -ENODEV;
}
emsff = kzalloc(sizeof(struct emsff_device), GFP_KERNEL);
if (!emsff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, emsff, emsff_play);
if (error) {
kfree(emsff);
return error;
}
emsff->report = report;
emsff->report->field[0]->value[0] = 0x01;
emsff->report->field[0]->value[1] = 0x00;
emsff->report->field[0]->value[2] = 0x00;
emsff->report->field[0]->value[3] = 0x00;
emsff->report->field[0]->value[4] = 0x00;
emsff->report->field[0]->value[5] = 0x00;
emsff->report->field[0]->value[6] = 0x00;
hid_hw_request(hid, emsff->report, HID_REQ_SET_REPORT);
hid_info(hid, "force feedback for EMS based devices by Ignaz Forster <ignaz.forster@gmx.de>\n");
return 0;
}
static int ems_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
ret = emsff_init(hdev);
if (ret) {
dev_err(&hdev->dev, "force feedback init failed\n");
hid_hw_stop(hdev);
goto err;
}
return 0;
err:
return ret;
}
static const struct hid_device_id ems_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
{ }
};
MODULE_DEVICE_TABLE(hid, ems_devices);
static struct hid_driver ems_driver = {
.name = "hkems",
.id_table = ems_devices,
.probe = ems_probe,
};
module_hid_driver(ems_driver);
MODULE_LICENSE("GPL");

81
drivers/hid/hid-ezkey.c Executable file
View File

@ -0,0 +1,81 @@
/*
* HID driver for some ezkey "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define ez_map_rel(c) hid_map_usage(hi, usage, bit, max, EV_REL, (c))
#define ez_map_key(c) hid_map_usage(hi, usage, bit, max, EV_KEY, (c))
static int ez_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x230: ez_map_key(BTN_MOUSE); break;
case 0x231: ez_map_rel(REL_WHEEL); break;
/*
* this keyboard has a scrollwheel implemented in
* totally broken way. We map this usage temporarily
* to HWHEEL and handle it in the event quirk handler
*/
case 0x232: ez_map_rel(REL_HWHEEL); break;
default:
return 0;
}
return 1;
}
static int ez_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
!usage->type)
return 0;
/* handle the temporary quirky mapping to HWHEEL */
if (usage->type == EV_REL && usage->code == REL_HWHEEL) {
struct input_dev *input = field->hidinput->input;
input_event(input, usage->type, REL_WHEEL, -value);
return 1;
}
return 0;
}
static const struct hid_device_id ez_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) },
{ }
};
MODULE_DEVICE_TABLE(hid, ez_devices);
static struct hid_driver ez_driver = {
.name = "ezkey",
.id_table = ez_devices,
.input_mapping = ez_input_mapping,
.event = ez_event,
};
module_hid_driver(ez_driver);
MODULE_LICENSE("GPL");

179
drivers/hid/hid-gaff.c Executable file
View File

@ -0,0 +1,179 @@
/*
* Force feedback support for GreenAsia (Product ID 0x12) based devices
*
* The devices are distributed under various names and the same USB device ID
* can be used in many game controllers.
*
*
* 0e8f:0012 "GreenAsia Inc. USB Joystick "
* - tested with MANTA Warior MM816 and SpeedLink Strike2 SL-6635.
*
* Copyright (c) 2008 Lukasz Lubojanski <lukasz@lubojanski.info>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#ifdef CONFIG_GREENASIA_FF
struct gaff_device {
struct hid_report *report;
};
static int hid_gaff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct gaff_device *gaff = data;
int left, right;
left = effect->u.rumble.strong_magnitude;
right = effect->u.rumble.weak_magnitude;
dbg_hid("called with 0x%04x 0x%04x", left, right);
left = left * 0xfe / 0xffff;
right = right * 0xfe / 0xffff;
gaff->report->field[0]->value[0] = 0x51;
gaff->report->field[0]->value[1] = 0x0;
gaff->report->field[0]->value[2] = right;
gaff->report->field[0]->value[3] = 0;
gaff->report->field[0]->value[4] = left;
gaff->report->field[0]->value[5] = 0;
dbg_hid("running with 0x%02x 0x%02x", left, right);
hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
gaff->report->field[0]->value[0] = 0xfa;
gaff->report->field[0]->value[1] = 0xfe;
gaff->report->field[0]->value[2] = 0x0;
gaff->report->field[0]->value[4] = 0x0;
hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
return 0;
}
static int gaff_init(struct hid_device *hid)
{
struct gaff_device *gaff;
struct hid_report *report;
struct hid_input *hidinput = list_entry(hid->inputs.next,
struct hid_input, list);
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report_ptr = report_ptr->next;
report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
if (report->field[0]->report_count < 6) {
hid_err(hid, "not enough values in the field\n");
return -ENODEV;
}
gaff = kzalloc(sizeof(struct gaff_device), GFP_KERNEL);
if (!gaff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, gaff, hid_gaff_play);
if (error) {
kfree(gaff);
return error;
}
gaff->report = report;
gaff->report->field[0]->value[0] = 0x51;
gaff->report->field[0]->value[1] = 0x00;
gaff->report->field[0]->value[2] = 0x00;
gaff->report->field[0]->value[3] = 0x00;
hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
gaff->report->field[0]->value[0] = 0xfa;
gaff->report->field[0]->value[1] = 0xfe;
hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force Feedback for GreenAsia 0x12 devices by Lukasz Lubojanski <lukasz@lubojanski.info>\n");
return 0;
}
#else
static inline int gaff_init(struct hid_device *hdev)
{
return 0;
}
#endif
static int ga_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
dev_dbg(&hdev->dev, "Greenasia HID hardware probe...");
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
gaff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id ga_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012), },
{ }
};
MODULE_DEVICE_TABLE(hid, ga_devices);
static struct hid_driver ga_driver = {
.name = "greenasia",
.id_table = ga_devices,
.probe = ga_probe,
};
module_hid_driver(ga_driver);
MODULE_LICENSE("GPL");

116
drivers/hid/hid-gembird.c Executable file
View File

@ -0,0 +1,116 @@
/*
* HID driver for Gembird Joypad, "PC Game Controller"
*
* Copyright (c) 2015 Red Hat, Inc
* Copyright (c) 2015 Benjamin Tissoires
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define GEMBIRD_START_FAULTY_RDESC 8
static const __u8 gembird_jpd_faulty_rdesc[] = {
0x75, 0x08, /* Report Size (8) */
0x95, 0x05, /* Report Count (5) */
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xff, 0x00, /* Logical Maximum (255) */
0x35, 0x00, /* Physical Minimum (0) */
0x46, 0xff, 0x00, /* Physical Maximum (255) */
0x09, 0x30, /* Usage (X) */
0x09, 0x31, /* Usage (Y) */
0x09, 0x32, /* Usage (Z) */
0x09, 0x32, /* Usage (Z) */
0x09, 0x35, /* Usage (Rz) */
0x81, 0x02, /* Input (Data,Var,Abs) */
};
/*
* we fix the report descriptor by:
* - marking the first Z axis as constant (so it is ignored by HID)
* - assign the original second Z to Rx
* - assign the original Rz to Ry
*/
static const __u8 gembird_jpd_fixed_rdesc[] = {
0x75, 0x08, /* Report Size (8) */
0x95, 0x02, /* Report Count (2) */
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xff, 0x00, /* Logical Maximum (255) */
0x35, 0x00, /* Physical Minimum (0) */
0x46, 0xff, 0x00, /* Physical Maximum (255) */
0x09, 0x30, /* Usage (X) */
0x09, 0x31, /* Usage (Y) */
0x81, 0x02, /* Input (Data,Var,Abs) */
0x95, 0x01, /* Report Count (1) */
0x09, 0x32, /* Usage (Z) */
0x81, 0x01, /* Input (Cnst,Arr,Abs) */
0x95, 0x02, /* Report Count (2) */
0x09, 0x33, /* Usage (Rx) */
0x09, 0x34, /* Usage (Ry) */
0x81, 0x02, /* Input (Data,Var,Abs) */
};
static __u8 *gembird_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
__u8 *new_rdesc;
/* delta_size is > 0 */
size_t delta_size = sizeof(gembird_jpd_fixed_rdesc) -
sizeof(gembird_jpd_faulty_rdesc);
size_t new_size = *rsize + delta_size;
if (*rsize >= 31 && !memcmp(&rdesc[GEMBIRD_START_FAULTY_RDESC],
gembird_jpd_faulty_rdesc,
sizeof(gembird_jpd_faulty_rdesc))) {
new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
if (new_rdesc == NULL)
return rdesc;
dev_info(&hdev->dev,
"fixing Gembird JPD-DualForce 2 report descriptor.\n");
/* start by copying the end of the rdesc */
memcpy(new_rdesc + delta_size, rdesc, *rsize);
/* add the correct beginning */
memcpy(new_rdesc, rdesc, GEMBIRD_START_FAULTY_RDESC);
/* replace the faulty part with the fixed one */
memcpy(new_rdesc + GEMBIRD_START_FAULTY_RDESC,
gembird_jpd_fixed_rdesc,
sizeof(gembird_jpd_fixed_rdesc));
*rsize = new_size;
rdesc = new_rdesc;
}
return rdesc;
}
static const struct hid_device_id gembird_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GEMBIRD,
USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2) },
{ }
};
MODULE_DEVICE_TABLE(hid, gembird_devices);
static struct hid_driver gembird_driver = {
.name = "gembird",
.id_table = gembird_devices,
.report_fixup = gembird_report_fixup,
};
module_hid_driver(gembird_driver);
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
MODULE_DESCRIPTION("HID Gembird joypad driver");
MODULE_LICENSE("GPL");

41
drivers/hid/hid-generic.c Executable file
View File

@ -0,0 +1,41 @@
/*
* HID support for Linux
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2007-2008 Oliver Neukum
* Copyright (c) 2006-2012 Jiri Kosina
* Copyright (c) 2012 Henrik Rydberg
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <asm/unaligned.h>
#include <asm/byteorder.h>
#include <linux/hid.h>
static const struct hid_device_id hid_table[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_GENERIC, HID_ANY_ID, HID_ANY_ID) },
{ }
};
MODULE_DEVICE_TABLE(hid, hid_table);
static struct hid_driver hid_generic = {
.name = "hid-generic",
.id_table = hid_table,
};
module_hid_driver(hid_generic);
MODULE_AUTHOR("Henrik Rydberg");
MODULE_DESCRIPTION("HID generic driver");
MODULE_LICENSE("GPL");

159
drivers/hid/hid-gfrm.c Executable file
View File

@ -0,0 +1,159 @@
/*
* HID driver for Google Fiber TV Box remote controls
*
* Copyright (c) 2014-2015 Google Inc.
*
* Author: Petri Gynther <pgynther@google.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/module.h>
#include "hid-ids.h"
#define GFRM100 1 /* Google Fiber GFRM100 (Bluetooth classic) */
#define GFRM200 2 /* Google Fiber GFRM200 (Bluetooth LE) */
#define GFRM100_SEARCH_KEY_REPORT_ID 0xF7
#define GFRM100_SEARCH_KEY_DOWN 0x0
#define GFRM100_SEARCH_KEY_AUDIO_DATA 0x1
#define GFRM100_SEARCH_KEY_UP 0x2
static u8 search_key_dn[3] = {0x40, 0x21, 0x02};
static u8 search_key_up[3] = {0x40, 0x00, 0x00};
static int gfrm_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
unsigned long hdev_type = (unsigned long) hid_get_drvdata(hdev);
if (hdev_type == GFRM100) {
if (usage->hid == (HID_UP_CONSUMER | 0x4)) {
/* Consumer.0004 -> KEY_INFO */
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_INFO);
return 1;
}
if (usage->hid == (HID_UP_CONSUMER | 0x41)) {
/* Consumer.0041 -> KEY_OK */
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_OK);
return 1;
}
}
return 0;
}
static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
unsigned long hdev_type = (unsigned long) hid_get_drvdata(hdev);
int ret = 0;
if (hdev_type != GFRM100)
return 0;
if (size < 2 || data[0] != GFRM100_SEARCH_KEY_REPORT_ID)
return 0;
/*
* Convert GFRM100 Search key reports into Consumer.0221 (Key.Search)
* reports. Ignore audio data.
*/
switch (data[1]) {
case GFRM100_SEARCH_KEY_DOWN:
ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
sizeof(search_key_dn), 1);
break;
case GFRM100_SEARCH_KEY_AUDIO_DATA:
break;
case GFRM100_SEARCH_KEY_UP:
ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
sizeof(search_key_up), 1);
break;
default:
break;
}
return (ret < 0) ? ret : -1;
}
static int gfrm_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
/*
* Enable software autorepeat with:
* - repeat delay: 400 msec
* - repeat period: 100 msec
*/
input_enable_softrepeat(hidinput->input, 400, 100);
return 0;
}
static int gfrm_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
hid_set_drvdata(hdev, (void *) id->driver_data);
ret = hid_parse(hdev);
if (ret)
goto done;
if (id->driver_data == GFRM100) {
/*
* GFRM100 HID Report Descriptor does not describe the Search
* key reports. Thus, we need to add it manually here, so that
* those reports reach gfrm_raw_event() from hid_input_report().
*/
if (!hid_register_report(hdev, HID_INPUT_REPORT,
GFRM100_SEARCH_KEY_REPORT_ID)) {
ret = -ENOMEM;
goto done;
}
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
done:
return ret;
}
static void gfrm_remove(struct hid_device *hdev)
{
hid_hw_stop(hdev);
hid_set_drvdata(hdev, NULL);
}
static const struct hid_device_id gfrm_devices[] = {
{ HID_BLUETOOTH_DEVICE(0x58, 0x2000),
.driver_data = GFRM100 },
{ HID_BLUETOOTH_DEVICE(0x471, 0x2210),
.driver_data = GFRM200 },
{ }
};
MODULE_DEVICE_TABLE(hid, gfrm_devices);
static struct hid_driver gfrm_driver = {
.name = "gfrm",
.id_table = gfrm_devices,
.probe = gfrm_probe,
.remove = gfrm_remove,
.input_mapping = gfrm_input_mapping,
.raw_event = gfrm_raw_event,
.input_configured = gfrm_input_configured,
};
module_hid_driver(gfrm_driver);
MODULE_AUTHOR("Petri Gynther <pgynther@google.com>");
MODULE_DESCRIPTION("Google Fiber TV Box remote control driver");
MODULE_LICENSE("GPL");

321
drivers/hid/hid-gt683r.c Executable file
View File

@ -0,0 +1,321 @@
/*
* MSI GT683R led driver
*
* Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include "hid-ids.h"
#define GT683R_BUFFER_SIZE 8
/*
* GT683R_LED_OFF: all LEDs are off
* GT683R_LED_AUDIO: LEDs brightness depends on sound level
* GT683R_LED_BREATHING: LEDs brightness varies at human breathing rate
* GT683R_LED_NORMAL: LEDs are fully on when enabled
*/
enum gt683r_led_mode {
GT683R_LED_OFF = 0,
GT683R_LED_AUDIO = 2,
GT683R_LED_BREATHING = 3,
GT683R_LED_NORMAL = 5
};
enum gt683r_panels {
GT683R_LED_BACK = 0,
GT683R_LED_SIDE = 1,
GT683R_LED_FRONT = 2,
GT683R_LED_COUNT,
};
static const char * const gt683r_panel_names[] = {
"back",
"side",
"front",
};
struct gt683r_led {
struct hid_device *hdev;
struct led_classdev led_devs[GT683R_LED_COUNT];
struct mutex lock;
struct work_struct work;
enum led_brightness brightnesses[GT683R_LED_COUNT];
enum gt683r_led_mode mode;
};
static const struct hid_device_id gt683r_led_id[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
{ }
};
static void gt683r_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
int i;
struct device *dev = led_cdev->dev->parent;
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct gt683r_led *led = hid_get_drvdata(hdev);
for (i = 0; i < GT683R_LED_COUNT; i++) {
if (led_cdev == &led->led_devs[i])
break;
}
if (i < GT683R_LED_COUNT) {
led->brightnesses[i] = brightness;
schedule_work(&led->work);
}
}
static ssize_t mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
u8 sysfs_mode;
struct hid_device *hdev = container_of(dev->parent,
struct hid_device, dev);
struct gt683r_led *led = hid_get_drvdata(hdev);
if (led->mode == GT683R_LED_NORMAL)
sysfs_mode = 0;
else if (led->mode == GT683R_LED_AUDIO)
sysfs_mode = 1;
else
sysfs_mode = 2;
return scnprintf(buf, PAGE_SIZE, "%u\n", sysfs_mode);
}
static ssize_t mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
u8 sysfs_mode;
struct hid_device *hdev = container_of(dev->parent,
struct hid_device, dev);
struct gt683r_led *led = hid_get_drvdata(hdev);
if (kstrtou8(buf, 10, &sysfs_mode) || sysfs_mode > 2)
return -EINVAL;
mutex_lock(&led->lock);
if (sysfs_mode == 0)
led->mode = GT683R_LED_NORMAL;
else if (sysfs_mode == 1)
led->mode = GT683R_LED_AUDIO;
else
led->mode = GT683R_LED_BREATHING;
mutex_unlock(&led->lock);
schedule_work(&led->work);
return count;
}
static int gt683r_led_snd_msg(struct gt683r_led *led, u8 *msg)
{
int ret;
ret = hid_hw_raw_request(led->hdev, msg[0], msg, GT683R_BUFFER_SIZE,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret != GT683R_BUFFER_SIZE) {
hid_err(led->hdev,
"failed to send set report request: %i\n", ret);
if (ret < 0)
return ret;
return -EIO;
}
return 0;
}
static int gt683r_leds_set(struct gt683r_led *led, u8 leds)
{
int ret;
u8 *buffer;
buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
buffer[0] = 0x01;
buffer[1] = 0x02;
buffer[2] = 0x30;
buffer[3] = leds;
ret = gt683r_led_snd_msg(led, buffer);
kfree(buffer);
return ret;
}
static int gt683r_mode_set(struct gt683r_led *led, u8 mode)
{
int ret;
u8 *buffer;
buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
buffer[0] = 0x01;
buffer[1] = 0x02;
buffer[2] = 0x20;
buffer[3] = mode;
buffer[4] = 0x01;
ret = gt683r_led_snd_msg(led, buffer);
kfree(buffer);
return ret;
}
static void gt683r_led_work(struct work_struct *work)
{
int i;
u8 leds = 0;
u8 mode;
struct gt683r_led *led = container_of(work, struct gt683r_led, work);
mutex_lock(&led->lock);
for (i = 0; i < GT683R_LED_COUNT; i++) {
if (led->brightnesses[i])
leds |= BIT(i);
}
if (gt683r_leds_set(led, leds))
goto fail;
if (leds)
mode = led->mode;
else
mode = GT683R_LED_OFF;
gt683r_mode_set(led, mode);
fail:
mutex_unlock(&led->lock);
}
static DEVICE_ATTR_RW(mode);
static struct attribute *gt683r_led_attrs[] = {
&dev_attr_mode.attr,
NULL
};
static const struct attribute_group gt683r_led_group = {
.name = "gt683r",
.attrs = gt683r_led_attrs,
};
static const struct attribute_group *gt683r_led_groups[] = {
&gt683r_led_group,
NULL
};
static int gt683r_led_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int i;
int ret;
int name_sz;
char *name;
struct gt683r_led *led;
led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
mutex_init(&led->lock);
INIT_WORK(&led->work, gt683r_led_work);
led->mode = GT683R_LED_NORMAL;
led->hdev = hdev;
hid_set_drvdata(hdev, led);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "hid parsing failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
for (i = 0; i < GT683R_LED_COUNT; i++) {
name_sz = strlen(dev_name(&hdev->dev)) +
strlen(gt683r_panel_names[i]) + 3;
name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
if (!name) {
ret = -ENOMEM;
goto fail;
}
snprintf(name, name_sz, "%s::%s",
dev_name(&hdev->dev), gt683r_panel_names[i]);
led->led_devs[i].name = name;
led->led_devs[i].max_brightness = 1;
led->led_devs[i].brightness_set = gt683r_brightness_set;
led->led_devs[i].groups = gt683r_led_groups;
ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
if (ret) {
hid_err(hdev, "could not register led device\n");
goto fail;
}
}
return 0;
fail:
for (i = i - 1; i >= 0; i--)
led_classdev_unregister(&led->led_devs[i]);
hid_hw_stop(hdev);
return ret;
}
static void gt683r_led_remove(struct hid_device *hdev)
{
int i;
struct gt683r_led *led = hid_get_drvdata(hdev);
for (i = 0; i < GT683R_LED_COUNT; i++)
led_classdev_unregister(&led->led_devs[i]);
flush_work(&led->work);
hid_hw_stop(hdev);
}
static struct hid_driver gt683r_led_driver = {
.probe = gt683r_led_probe,
.remove = gt683r_led_remove,
.name = "gt683r_led",
.id_table = gt683r_led_id,
};
module_hid_driver(gt683r_led_driver);
MODULE_AUTHOR("Janne Kanniainen");
MODULE_DESCRIPTION("MSI GT683R led driver");
MODULE_LICENSE("GPL");

93
drivers/hid/hid-gyration.c Executable file
View File

@ -0,0 +1,93 @@
/*
* HID driver for some gyration "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2008 Jiri Slaby
* Copyright (c) 2006-2008 Jiri Kosina
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define gy_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int gyration_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
return 0;
set_bit(EV_REP, hi->input->evbit);
switch (usage->hid & HID_USAGE) {
/* Reported on Gyration MCE Remote */
case 0x00d: gy_map_key_clear(KEY_HOME); break;
case 0x024: gy_map_key_clear(KEY_DVD); break;
case 0x025: gy_map_key_clear(KEY_PVR); break;
case 0x046: gy_map_key_clear(KEY_MEDIA); break;
case 0x047: gy_map_key_clear(KEY_MP3); break;
case 0x048: gy_map_key_clear(KEY_MEDIA); break;
case 0x049: gy_map_key_clear(KEY_CAMERA); break;
case 0x04a: gy_map_key_clear(KEY_VIDEO); break;
case 0x05a: gy_map_key_clear(KEY_TEXT); break;
case 0x05b: gy_map_key_clear(KEY_RED); break;
case 0x05c: gy_map_key_clear(KEY_GREEN); break;
case 0x05d: gy_map_key_clear(KEY_YELLOW); break;
case 0x05e: gy_map_key_clear(KEY_BLUE); break;
default:
return 0;
}
return 1;
}
static int gyration_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput)
return 0;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK &&
(usage->hid & 0xff) == 0x82) {
struct input_dev *input = field->hidinput->input;
input_event(input, usage->type, usage->code, 1);
input_sync(input);
input_event(input, usage->type, usage->code, 0);
input_sync(input);
return 1;
}
return 0;
}
static const struct hid_device_id gyration_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) },
{ }
};
MODULE_DEVICE_TABLE(hid, gyration_devices);
static struct hid_driver gyration_driver = {
.name = "gyration",
.id_table = gyration_devices,
.input_mapping = gyration_input_mapping,
.event = gyration_event,
};
module_hid_driver(gyration_driver);
MODULE_LICENSE("GPL");

172
drivers/hid/hid-holtek-kbd.c Executable file
View File

@ -0,0 +1,172 @@
/*
* HID driver for Holtek keyboard
* Copyright (c) 2012 Tom Harwood
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include "hid-ids.h"
#include "usbhid/usbhid.h"
/* Holtek based keyboards (USB ID 04d9:a055) have the following issues:
* - The report descriptor specifies an excessively large number of consumer
* usages (2^15), which is more than HID_MAX_USAGES. This prevents proper
* parsing of the report descriptor.
* - The report descriptor reports on caps/scroll/num lock key presses, but
* doesn't have an LED output usage block.
*
* The replacement descriptor below fixes the number of consumer usages,
* and provides an LED output usage block. LED output events are redirected
* to the boot interface.
*/
static __u8 holtek_kbd_rdesc_fixed[] = {
/* Original report descriptor, with reduced number of consumer usages */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x80, /* Usage (Sys Control), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x01, /* Report ID (1), */
0x19, 0x81, /* Usage Minimum (Sys Power Down), */
0x29, 0x83, /* Usage Maximum (Sys Wake Up), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x03, /* Report Count (3), */
0x75, 0x01, /* Report Size (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x05, /* Report Size (5), */
0x81, 0x01, /* Input (Constant), */
0xC0, /* End Collection, */
0x05, 0x0C, /* Usage Page (Consumer), */
0x09, 0x01, /* Usage (Consumer Control), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x02, /* Report ID (2), */
0x19, 0x00, /* Usage Minimum (00h), */
0x2A, 0xFF, 0x2F, /* Usage Maximum (0x2FFF), previously 0x7FFF */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x2F, /* Logical Maximum (0x2FFF),previously 0x7FFF*/
0x95, 0x01, /* Report Count (1), */
0x75, 0x10, /* Report Size (16), */
0x81, 0x00, /* Input, */
0xC0, /* End Collection, */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x03, /* Report ID (3), */
0x95, 0x38, /* Report Count (56), */
0x75, 0x01, /* Report Size (1), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x05, 0x07, /* Usage Page (Keyboard), */
0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */
0x29, 0xE7, /* Usage Maximum (KB Right GUI), */
0x19, 0x00, /* Usage Minimum (None), */
0x29, 0x2F, /* Usage Maximum (KB Lboxbracket And Lbrace),*/
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x04, /* Report ID (4), */
0x95, 0x38, /* Report Count (56), */
0x75, 0x01, /* Report Size (1), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x05, 0x07, /* Usage Page (Keyboard), */
0x19, 0x30, /* Usage Minimum (KB Rboxbracket And Rbrace),*/
0x29, 0x67, /* Usage Maximum (KP Equals), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection */
/* LED usage for the boot protocol interface */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
0x05, 0x08, /* Usage Page (LED), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x03, /* Usage Maximum (03h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x03, /* Report Count (3), */
0x91, 0x02, /* Output (Variable), */
0x95, 0x05, /* Report Count (5), */
0x91, 0x01, /* Output (Constant), */
0xC0, /* End Collection */
};
static __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
rdesc = holtek_kbd_rdesc_fixed;
*rsize = sizeof(holtek_kbd_rdesc_fixed);
}
return rdesc;
}
static int holtek_kbd_input_event(struct input_dev *dev, unsigned int type,
unsigned int code,
int value)
{
struct hid_device *hid = input_get_drvdata(dev);
struct usb_device *usb_dev = hid_to_usb_dev(hid);
/* Locate the boot interface, to receive the LED change events */
struct usb_interface *boot_interface = usb_ifnum_to_if(usb_dev, 0);
struct hid_device *boot_hid = usb_get_intfdata(boot_interface);
struct hid_input *boot_hid_input = list_first_entry(&boot_hid->inputs,
struct hid_input, list);
return boot_hid_input->input->event(boot_hid_input->input, type, code,
value);
}
static int holtek_kbd_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
int ret = hid_parse(hdev);
if (!ret)
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (!ret && intf->cur_altsetting->desc.bInterfaceNumber == 1) {
struct hid_input *hidinput;
list_for_each_entry(hidinput, &hdev->inputs, list) {
hidinput->input->event = holtek_kbd_input_event;
}
}
return ret;
}
static const struct hid_device_id holtek_kbd_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) },
{ }
};
MODULE_DEVICE_TABLE(hid, holtek_kbd_devices);
static struct hid_driver holtek_kbd_driver = {
.name = "holtek_kbd",
.id_table = holtek_kbd_devices,
.report_fixup = holtek_kbd_report_fixup,
.probe = holtek_kbd_probe
};
module_hid_driver(holtek_kbd_driver);
MODULE_LICENSE("GPL");

92
drivers/hid/hid-holtek-mouse.c Executable file
View File

@ -0,0 +1,92 @@
/*
* HID driver for Holtek gaming mice
* Copyright (c) 2013 Christian Ohm
* Heavily inspired by various other HID drivers that adjust the report
* descriptor.
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include "hid-ids.h"
/*
* The report descriptor of some Holtek based gaming mice specifies an
* excessively large number of consumer usages (2^15), which is more than
* HID_MAX_USAGES. This prevents proper parsing of the report descriptor.
*
* This driver fixes the report descriptor for:
* - USB ID 04d9:a067, sold as Sharkoon Drakonia and Perixx MX-2000
* - USB ID 04d9:a04a, sold as Tracer Sniper TRM-503, NOVA Gaming Slider X200
* and Zalman ZM-GM1
* - USB ID 04d9:a081, sold as SHARKOON DarkGlider Gaming mouse
* - USB ID 04d9:a072, sold as LEETGION Hellion Gaming Mouse
* - USB ID 04d9:a0c2, sold as ETEKCITY Scroll T-140 Gaming Mouse
*/
static __u8 *holtek_mouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
/* Change usage maximum and logical maximum from 0x7fff to
* 0x2fff, so they don't exceed HID_MAX_USAGES */
switch (hdev->product) {
case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067:
case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072:
case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2:
if (*rsize >= 122 && rdesc[115] == 0xff && rdesc[116] == 0x7f
&& rdesc[120] == 0xff && rdesc[121] == 0x7f) {
hid_info(hdev, "Fixing up report descriptor\n");
rdesc[116] = rdesc[121] = 0x2f;
}
break;
case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A:
case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070:
case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081:
if (*rsize >= 113 && rdesc[106] == 0xff && rdesc[107] == 0x7f
&& rdesc[111] == 0xff && rdesc[112] == 0x7f) {
hid_info(hdev, "Fixing up report descriptor\n");
rdesc[107] = rdesc[112] = 0x2f;
}
break;
}
}
return rdesc;
}
static const struct hid_device_id holtek_mouse_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067) },
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070) },
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A) },
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072) },
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081) },
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2) },
{ }
};
MODULE_DEVICE_TABLE(hid, holtek_mouse_devices);
static struct hid_driver holtek_mouse_driver = {
.name = "holtek_mouse",
.id_table = holtek_mouse_devices,
.report_fixup = holtek_mouse_report_fixup,
};
module_hid_driver(holtek_mouse_driver);
MODULE_LICENSE("GPL");

225
drivers/hid/hid-holtekff.c Executable file
View File

@ -0,0 +1,225 @@
/*
* Force feedback support for Holtek On Line Grip based gamepads
*
* These include at least a Brazilian "Clone Joypad Super Power Fire"
* which uses vendor ID 0x1241 and identifies as "HOLTEK On Line Grip".
*
* Copyright (c) 2011 Anssi Hannula <anssi.hannula@iki.fi>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "hid-ids.h"
#ifdef CONFIG_HOLTEK_FF
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anssi Hannula <anssi.hannula@iki.fi>");
MODULE_DESCRIPTION("Force feedback support for Holtek On Line Grip based devices");
/*
* These commands and parameters are currently known:
*
* byte 0: command id:
* 01 set effect parameters
* 02 play specified effect
* 03 stop specified effect
* 04 stop all effects
* 06 stop all effects
* (the difference between 04 and 06 isn't known; win driver
* sends 06,04 on application init, and 06 otherwise)
*
* Commands 01 and 02 need to be sent as pairs, i.e. you need to send 01
* before each 02.
*
* The rest of the bytes are parameters. Command 01 takes all of them, and
* commands 02,03 take only the effect id.
*
* byte 1:
* bits 0-3: effect id:
* 1: very strong rumble
* 2: periodic rumble, short intervals
* 3: very strong rumble
* 4: periodic rumble, long intervals
* 5: weak periodic rumble, long intervals
* 6: weak periodic rumble, short intervals
* 7: periodic rumble, short intervals
* 8: strong periodic rumble, short intervals
* 9: very strong rumble
* a: causes an error
* b: very strong periodic rumble, very short intervals
* c-f: nothing
* bit 6: right (weak) motor enabled
* bit 7: left (strong) motor enabled
*
* bytes 2-3: time in milliseconds, big-endian
* bytes 5-6: unknown (win driver seems to use at least 10e0 with effect 1
* and 0014 with effect 6)
* byte 7:
* bits 0-3: effect magnitude
*/
#define HOLTEKFF_MSG_LENGTH 7
static const u8 start_effect_1[] = { 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u8 stop_all4[] = { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u8 stop_all6[] = { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
struct holtekff_device {
struct hid_field *field;
};
static void holtekff_send(struct holtekff_device *holtekff,
struct hid_device *hid,
const u8 data[HOLTEKFF_MSG_LENGTH])
{
int i;
for (i = 0; i < HOLTEKFF_MSG_LENGTH; i++) {
holtekff->field->value[i] = data[i];
}
dbg_hid("sending %7ph\n", data);
hid_hw_request(hid, holtekff->field->report, HID_REQ_SET_REPORT);
}
static int holtekff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct holtekff_device *holtekff = data;
int left, right;
/* effect type 1, length 65535 msec */
u8 buf[HOLTEKFF_MSG_LENGTH] =
{ 0x01, 0x01, 0xff, 0xff, 0x10, 0xe0, 0x00 };
left = effect->u.rumble.strong_magnitude;
right = effect->u.rumble.weak_magnitude;
dbg_hid("called with 0x%04x 0x%04x\n", left, right);
if (!left && !right) {
holtekff_send(holtekff, hid, stop_all6);
return 0;
}
if (left)
buf[1] |= 0x80;
if (right)
buf[1] |= 0x40;
/* The device takes a single magnitude, so we just sum them up. */
buf[6] = min(0xf, (left >> 12) + (right >> 12));
holtekff_send(holtekff, hid, buf);
holtekff_send(holtekff, hid, start_effect_1);
return 0;
}
static int holtekff_init(struct hid_device *hid)
{
struct holtekff_device *holtekff;
struct hid_report *report;
struct hid_input *hidinput = list_entry(hid->inputs.next,
struct hid_input, list);
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(report_list)) {
hid_err(hid, "no output report found\n");
return -ENODEV;
}
report = list_entry(report_list->next, struct hid_report, list);
if (report->maxfield < 1 || report->field[0]->report_count != 7) {
hid_err(hid, "unexpected output report layout\n");
return -ENODEV;
}
holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL);
if (!holtekff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
holtekff->field = report->field[0];
/* initialize the same way as win driver does */
holtekff_send(holtekff, hid, stop_all4);
holtekff_send(holtekff, hid, stop_all6);
error = input_ff_create_memless(dev, holtekff, holtekff_play);
if (error) {
kfree(holtekff);
return error;
}
hid_info(hid, "Force feedback for Holtek On Line Grip based devices by Anssi Hannula <anssi.hannula@iki.fi>\n");
return 0;
}
#else
static inline int holtekff_init(struct hid_device *hid)
{
return 0;
}
#endif
static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
holtekff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id holtek_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
{ }
};
MODULE_DEVICE_TABLE(hid, holtek_devices);
static struct hid_driver holtek_driver = {
.name = "holtek",
.id_table = holtek_devices,
.probe = holtek_probe,
};
module_hid_driver(holtek_driver);

615
drivers/hid/hid-hyperv.c Executable file
View File

@ -0,0 +1,615 @@
/*
* Copyright (c) 2009, Citrix Systems, Inc.
* Copyright (c) 2010, Microsoft Corporation.
* Copyright (c) 2011, Novell Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/completion.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/hiddev.h>
#include <linux/hyperv.h>
struct hv_input_dev_info {
unsigned int size;
unsigned short vendor;
unsigned short product;
unsigned short version;
unsigned short reserved[11];
};
/* The maximum size of a synthetic input message. */
#define SYNTHHID_MAX_INPUT_REPORT_SIZE 16
/*
* Current version
*
* History:
* Beta, RC < 2008/1/22 1,0
* RC > 2008/1/22 2,0
*/
#define SYNTHHID_INPUT_VERSION_MAJOR 2
#define SYNTHHID_INPUT_VERSION_MINOR 0
#define SYNTHHID_INPUT_VERSION (SYNTHHID_INPUT_VERSION_MINOR | \
(SYNTHHID_INPUT_VERSION_MAJOR << 16))
#pragma pack(push, 1)
/*
* Message types in the synthetic input protocol
*/
enum synthhid_msg_type {
SYNTH_HID_PROTOCOL_REQUEST,
SYNTH_HID_PROTOCOL_RESPONSE,
SYNTH_HID_INITIAL_DEVICE_INFO,
SYNTH_HID_INITIAL_DEVICE_INFO_ACK,
SYNTH_HID_INPUT_REPORT,
SYNTH_HID_MAX
};
/*
* Basic message structures.
*/
struct synthhid_msg_hdr {
enum synthhid_msg_type type;
u32 size;
};
struct synthhid_msg {
struct synthhid_msg_hdr header;
char data[1]; /* Enclosed message */
};
union synthhid_version {
struct {
u16 minor_version;
u16 major_version;
};
u32 version;
};
/*
* Protocol messages
*/
struct synthhid_protocol_request {
struct synthhid_msg_hdr header;
union synthhid_version version_requested;
};
struct synthhid_protocol_response {
struct synthhid_msg_hdr header;
union synthhid_version version_requested;
unsigned char approved;
};
struct synthhid_device_info {
struct synthhid_msg_hdr header;
struct hv_input_dev_info hid_dev_info;
struct hid_descriptor hid_descriptor;
};
struct synthhid_device_info_ack {
struct synthhid_msg_hdr header;
unsigned char reserved;
};
struct synthhid_input_report {
struct synthhid_msg_hdr header;
char buffer[1];
};
#pragma pack(pop)
#define INPUTVSC_SEND_RING_BUFFER_SIZE (10*PAGE_SIZE)
#define INPUTVSC_RECV_RING_BUFFER_SIZE (10*PAGE_SIZE)
enum pipe_prot_msg_type {
PIPE_MESSAGE_INVALID,
PIPE_MESSAGE_DATA,
PIPE_MESSAGE_MAXIMUM
};
struct pipe_prt_msg {
enum pipe_prot_msg_type type;
u32 size;
char data[1];
};
struct mousevsc_prt_msg {
enum pipe_prot_msg_type type;
u32 size;
union {
struct synthhid_protocol_request request;
struct synthhid_protocol_response response;
struct synthhid_device_info_ack ack;
};
};
/*
* Represents an mousevsc device
*/
struct mousevsc_dev {
struct hv_device *device;
bool init_complete;
bool connected;
struct mousevsc_prt_msg protocol_req;
struct mousevsc_prt_msg protocol_resp;
/* Synchronize the request/response if needed */
struct completion wait_event;
int dev_info_status;
struct hid_descriptor *hid_desc;
unsigned char *report_desc;
u32 report_desc_size;
struct hv_input_dev_info hid_dev_info;
struct hid_device *hid_device;
u8 input_buf[HID_MAX_BUFFER_SIZE];
};
static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device)
{
struct mousevsc_dev *input_dev;
input_dev = kzalloc(sizeof(struct mousevsc_dev), GFP_KERNEL);
if (!input_dev)
return NULL;
input_dev->device = device;
hv_set_drvdata(device, input_dev);
init_completion(&input_dev->wait_event);
input_dev->init_complete = false;
return input_dev;
}
static void mousevsc_free_device(struct mousevsc_dev *device)
{
kfree(device->hid_desc);
kfree(device->report_desc);
hv_set_drvdata(device->device, NULL);
kfree(device);
}
static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
struct synthhid_device_info *device_info)
{
int ret = 0;
struct hid_descriptor *desc;
struct mousevsc_prt_msg ack;
input_device->dev_info_status = -ENOMEM;
input_device->hid_dev_info = device_info->hid_dev_info;
desc = &device_info->hid_descriptor;
if (desc->bLength == 0)
goto cleanup;
input_device->hid_desc = kmemdup(desc, desc->bLength, GFP_ATOMIC);
if (!input_device->hid_desc)
goto cleanup;
input_device->report_desc_size = desc->desc[0].wDescriptorLength;
if (input_device->report_desc_size == 0) {
input_device->dev_info_status = -EINVAL;
goto cleanup;
}
input_device->report_desc = kzalloc(input_device->report_desc_size,
GFP_ATOMIC);
if (!input_device->report_desc) {
input_device->dev_info_status = -ENOMEM;
goto cleanup;
}
memcpy(input_device->report_desc,
((unsigned char *)desc) + desc->bLength,
desc->desc[0].wDescriptorLength);
/* Send the ack */
memset(&ack, 0, sizeof(struct mousevsc_prt_msg));
ack.type = PIPE_MESSAGE_DATA;
ack.size = sizeof(struct synthhid_device_info_ack);
ack.ack.header.type = SYNTH_HID_INITIAL_DEVICE_INFO_ACK;
ack.ack.header.size = 1;
ack.ack.reserved = 0;
ret = vmbus_sendpacket(input_device->device->channel,
&ack,
sizeof(struct pipe_prt_msg) - sizeof(unsigned char) +
sizeof(struct synthhid_device_info_ack),
(unsigned long)&ack,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (!ret)
input_device->dev_info_status = 0;
cleanup:
complete(&input_device->wait_event);
return;
}
static void mousevsc_on_receive(struct hv_device *device,
struct vmpacket_descriptor *packet)
{
struct pipe_prt_msg *pipe_msg;
struct synthhid_msg *hid_msg;
struct mousevsc_dev *input_dev = hv_get_drvdata(device);
struct synthhid_input_report *input_report;
size_t len;
pipe_msg = (struct pipe_prt_msg *)((unsigned long)packet +
(packet->offset8 << 3));
if (pipe_msg->type != PIPE_MESSAGE_DATA)
return;
hid_msg = (struct synthhid_msg *)pipe_msg->data;
switch (hid_msg->header.type) {
case SYNTH_HID_PROTOCOL_RESPONSE:
/*
* While it will be impossible for us to protect against
* malicious/buggy hypervisor/host, add a check here to
* ensure we don't corrupt memory.
*/
if ((pipe_msg->size + sizeof(struct pipe_prt_msg)
- sizeof(unsigned char))
> sizeof(struct mousevsc_prt_msg)) {
WARN_ON(1);
break;
}
memcpy(&input_dev->protocol_resp, pipe_msg,
pipe_msg->size + sizeof(struct pipe_prt_msg) -
sizeof(unsigned char));
complete(&input_dev->wait_event);
break;
case SYNTH_HID_INITIAL_DEVICE_INFO:
WARN_ON(pipe_msg->size < sizeof(struct hv_input_dev_info));
/*
* Parse out the device info into device attr,
* hid desc and report desc
*/
mousevsc_on_receive_device_info(input_dev,
(struct synthhid_device_info *)pipe_msg->data);
break;
case SYNTH_HID_INPUT_REPORT:
input_report =
(struct synthhid_input_report *)pipe_msg->data;
if (!input_dev->init_complete)
break;
len = min(input_report->header.size,
(u32)sizeof(input_dev->input_buf));
memcpy(input_dev->input_buf, input_report->buffer, len);
hid_input_report(input_dev->hid_device, HID_INPUT_REPORT,
input_dev->input_buf, len, 1);
pm_wakeup_event(&input_dev->device->device, 0);
break;
default:
pr_err("unsupported hid msg type - type %d len %d",
hid_msg->header.type, hid_msg->header.size);
break;
}
}
static void mousevsc_on_channel_callback(void *context)
{
const int packet_size = 0x100;
int ret;
struct hv_device *device = context;
u32 bytes_recvd;
u64 req_id;
struct vmpacket_descriptor *desc;
unsigned char *buffer;
int bufferlen = packet_size;
buffer = kmalloc(bufferlen, GFP_ATOMIC);
if (!buffer)
return;
do {
ret = vmbus_recvpacket_raw(device->channel, buffer,
bufferlen, &bytes_recvd, &req_id);
switch (ret) {
case 0:
if (bytes_recvd <= 0) {
kfree(buffer);
return;
}
desc = (struct vmpacket_descriptor *)buffer;
switch (desc->type) {
case VM_PKT_COMP:
break;
case VM_PKT_DATA_INBAND:
mousevsc_on_receive(device, desc);
break;
default:
pr_err("unhandled packet type %d, tid %llx len %d\n",
desc->type, req_id, bytes_recvd);
break;
}
break;
case -ENOBUFS:
kfree(buffer);
/* Handle large packet */
bufferlen = bytes_recvd;
buffer = kmalloc(bytes_recvd, GFP_ATOMIC);
if (!buffer)
return;
break;
}
} while (1);
}
static int mousevsc_connect_to_vsp(struct hv_device *device)
{
int ret = 0;
unsigned long t;
struct mousevsc_dev *input_dev = hv_get_drvdata(device);
struct mousevsc_prt_msg *request;
struct mousevsc_prt_msg *response;
request = &input_dev->protocol_req;
memset(request, 0, sizeof(struct mousevsc_prt_msg));
request->type = PIPE_MESSAGE_DATA;
request->size = sizeof(struct synthhid_protocol_request);
request->request.header.type = SYNTH_HID_PROTOCOL_REQUEST;
request->request.header.size = sizeof(unsigned int);
request->request.version_requested.version = SYNTHHID_INPUT_VERSION;
ret = vmbus_sendpacket(device->channel, request,
sizeof(struct pipe_prt_msg) -
sizeof(unsigned char) +
sizeof(struct synthhid_protocol_request),
(unsigned long)request,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (ret)
goto cleanup;
t = wait_for_completion_timeout(&input_dev->wait_event, 5*HZ);
if (!t) {
ret = -ETIMEDOUT;
goto cleanup;
}
response = &input_dev->protocol_resp;
if (!response->response.approved) {
pr_err("synthhid protocol request failed (version %d)\n",
SYNTHHID_INPUT_VERSION);
ret = -ENODEV;
goto cleanup;
}
t = wait_for_completion_timeout(&input_dev->wait_event, 5*HZ);
if (!t) {
ret = -ETIMEDOUT;
goto cleanup;
}
/*
* We should have gotten the device attr, hid desc and report
* desc at this point
*/
ret = input_dev->dev_info_status;
cleanup:
return ret;
}
static int mousevsc_hid_parse(struct hid_device *hid)
{
struct hv_device *dev = hid_get_drvdata(hid);
struct mousevsc_dev *input_dev = hv_get_drvdata(dev);
return hid_parse_report(hid, input_dev->report_desc,
input_dev->report_desc_size);
}
static int mousevsc_hid_open(struct hid_device *hid)
{
return 0;
}
static int mousevsc_hid_start(struct hid_device *hid)
{
return 0;
}
static void mousevsc_hid_close(struct hid_device *hid)
{
}
static void mousevsc_hid_stop(struct hid_device *hid)
{
}
static int mousevsc_hid_raw_request(struct hid_device *hid,
unsigned char report_num,
__u8 *buf, size_t len,
unsigned char rtype,
int reqtype)
{
return 0;
}
static struct hid_ll_driver mousevsc_ll_driver = {
.parse = mousevsc_hid_parse,
.open = mousevsc_hid_open,
.close = mousevsc_hid_close,
.start = mousevsc_hid_start,
.stop = mousevsc_hid_stop,
.raw_request = mousevsc_hid_raw_request,
};
static struct hid_driver mousevsc_hid_driver;
static int mousevsc_probe(struct hv_device *device,
const struct hv_vmbus_device_id *dev_id)
{
int ret;
struct mousevsc_dev *input_dev;
struct hid_device *hid_dev;
input_dev = mousevsc_alloc_device(device);
if (!input_dev)
return -ENOMEM;
ret = vmbus_open(device->channel,
INPUTVSC_SEND_RING_BUFFER_SIZE,
INPUTVSC_RECV_RING_BUFFER_SIZE,
NULL,
0,
mousevsc_on_channel_callback,
device
);
if (ret)
goto probe_err0;
ret = mousevsc_connect_to_vsp(device);
if (ret)
goto probe_err1;
/* workaround SA-167 */
if (input_dev->report_desc[14] == 0x25)
input_dev->report_desc[14] = 0x29;
hid_dev = hid_allocate_device();
if (IS_ERR(hid_dev)) {
ret = PTR_ERR(hid_dev);
goto probe_err1;
}
hid_dev->ll_driver = &mousevsc_ll_driver;
hid_dev->driver = &mousevsc_hid_driver;
hid_dev->bus = BUS_VIRTUAL;
hid_dev->vendor = input_dev->hid_dev_info.vendor;
hid_dev->product = input_dev->hid_dev_info.product;
hid_dev->version = input_dev->hid_dev_info.version;
input_dev->hid_device = hid_dev;
sprintf(hid_dev->name, "%s", "Microsoft Vmbus HID-compliant Mouse");
hid_set_drvdata(hid_dev, device);
ret = hid_add_device(hid_dev);
if (ret)
goto probe_err1;
ret = hid_parse(hid_dev);
if (ret) {
hid_err(hid_dev, "parse failed\n");
goto probe_err2;
}
ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV);
if (ret) {
hid_err(hid_dev, "hw start failed\n");
goto probe_err2;
}
device_init_wakeup(&device->device, true);
input_dev->connected = true;
input_dev->init_complete = true;
return ret;
probe_err2:
hid_destroy_device(hid_dev);
probe_err1:
vmbus_close(device->channel);
probe_err0:
mousevsc_free_device(input_dev);
return ret;
}
static int mousevsc_remove(struct hv_device *dev)
{
struct mousevsc_dev *input_dev = hv_get_drvdata(dev);
device_init_wakeup(&dev->device, false);
vmbus_close(dev->channel);
hid_hw_stop(input_dev->hid_device);
hid_destroy_device(input_dev->hid_device);
mousevsc_free_device(input_dev);
return 0;
}
static const struct hv_vmbus_device_id id_table[] = {
/* Mouse guid */
{ HV_MOUSE_GUID, },
{ },
};
MODULE_DEVICE_TABLE(vmbus, id_table);
static struct hv_driver mousevsc_drv = {
.name = KBUILD_MODNAME,
.id_table = id_table,
.probe = mousevsc_probe,
.remove = mousevsc_remove,
};
static int __init mousevsc_init(void)
{
return vmbus_driver_register(&mousevsc_drv);
}
static void __exit mousevsc_exit(void)
{
vmbus_driver_unregister(&mousevsc_drv);
}
MODULE_LICENSE("GPL");
module_init(mousevsc_init);
module_exit(mousevsc_exit);

242
drivers/hid/hid-icade.c Executable file
View File

@ -0,0 +1,242 @@
/*
* ION iCade input driver
*
* Copyright (c) 2012 Bastien Nocera <hadess@hadess.net>
* Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
/*
* A C Y L
*
* B X Z R
*
*
* UP ON,OFF = w,e
* RT ON,OFF = d,c
* DN ON,OFF = x,z
* LT ON,OFF = a,q
* A ON,OFF = y,t
* B ON,OFF = h,r
* C ON,OFF = u,f
* X ON,OFF = j,n
* Y ON,OFF = i,m
* Z ON,OFF = k,p
* L ON,OFF = o,g
* R ON,OFF = l,v
*/
/* The translation code uses HID usage instead of input layer
* keys. This code generates a lookup table that makes
* translation quick.
*
* #include <linux/input.h>
* #include <stdio.h>
* #include <assert.h>
*
* #define unk KEY_UNKNOWN
*
* < copy of hid_keyboard[] from hid-input.c >
*
* struct icade_key_translation {
* int from;
* const char *to;
* int press;
* };
*
* static const struct icade_key_translation icade_keys[] = {
* { KEY_W, "KEY_UP", 1 },
* { KEY_E, "KEY_UP", 0 },
* { KEY_D, "KEY_RIGHT", 1 },
* { KEY_C, "KEY_RIGHT", 0 },
* { KEY_X, "KEY_DOWN", 1 },
* { KEY_Z, "KEY_DOWN", 0 },
* { KEY_A, "KEY_LEFT", 1 },
* { KEY_Q, "KEY_LEFT", 0 },
* { KEY_Y, "BTN_A", 1 },
* { KEY_T, "BTN_A", 0 },
* { KEY_H, "BTN_B", 1 },
* { KEY_R, "BTN_B", 0 },
* { KEY_U, "BTN_C", 1 },
* { KEY_F, "BTN_C", 0 },
* { KEY_J, "BTN_X", 1 },
* { KEY_N, "BTN_X", 0 },
* { KEY_I, "BTN_Y", 1 },
* { KEY_M, "BTN_Y", 0 },
* { KEY_K, "BTN_Z", 1 },
* { KEY_P, "BTN_Z", 0 },
* { KEY_O, "BTN_THUMBL", 1 },
* { KEY_G, "BTN_THUMBL", 0 },
* { KEY_L, "BTN_THUMBR", 1 },
* { KEY_V, "BTN_THUMBR", 0 },
*
* { }
* };
*
* static int
* usage_for_key (int key)
* {
* int i;
* for (i = 0; i < 256; i++) {
* if (hid_keyboard[i] == key)
* return i;
* }
* assert(0);
* }
*
* int main (int argc, char **argv)
* {
* const struct icade_key_translation *trans;
* int max_usage = 0;
*
* for (trans = icade_keys; trans->from; trans++) {
* int usage = usage_for_key (trans->from);
* max_usage = usage > max_usage ? usage : max_usage;
* }
*
* printf ("#define ICADE_MAX_USAGE %d\n\n", max_usage);
* printf ("struct icade_key {\n");
* printf ("\tu16 to;\n");
* printf ("\tu8 press:1;\n");
* printf ("};\n\n");
* printf ("static const struct icade_key "
* "icade_usage_table[%d] = {\n", max_usage + 1);
* for (trans = icade_keys; trans->from; trans++) {
* printf ("\t[%d] = { %s, %d },\n",
* usage_for_key (trans->from), trans->to, trans->press);
* }
* printf ("};\n");
*
* return 0;
* }
*/
#define ICADE_MAX_USAGE 29
struct icade_key {
u16 to;
u8 press:1;
};
static const struct icade_key icade_usage_table[30] = {
[26] = { KEY_UP, 1 },
[8] = { KEY_UP, 0 },
[7] = { KEY_RIGHT, 1 },
[6] = { KEY_RIGHT, 0 },
[27] = { KEY_DOWN, 1 },
[29] = { KEY_DOWN, 0 },
[4] = { KEY_LEFT, 1 },
[20] = { KEY_LEFT, 0 },
[28] = { BTN_A, 1 },
[23] = { BTN_A, 0 },
[11] = { BTN_B, 1 },
[21] = { BTN_B, 0 },
[24] = { BTN_C, 1 },
[9] = { BTN_C, 0 },
[13] = { BTN_X, 1 },
[17] = { BTN_X, 0 },
[12] = { BTN_Y, 1 },
[16] = { BTN_Y, 0 },
[14] = { BTN_Z, 1 },
[19] = { BTN_Z, 0 },
[18] = { BTN_THUMBL, 1 },
[10] = { BTN_THUMBL, 0 },
[15] = { BTN_THUMBR, 1 },
[25] = { BTN_THUMBR, 0 },
};
static const struct icade_key *icade_find_translation(u16 from)
{
if (from > ICADE_MAX_USAGE)
return NULL;
return &icade_usage_table[from];
}
static int icade_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
const struct icade_key *trans;
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
!usage->type)
return 0;
/* We ignore the fake key up, and act only on key down */
if (!value)
return 1;
trans = icade_find_translation(usage->hid & HID_USAGE);
if (!trans)
return 1;
input_event(field->hidinput->input, usage->type,
trans->to, trans->press);
return 1;
}
static int icade_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
const struct icade_key *trans;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD) {
trans = icade_find_translation(usage->hid & HID_USAGE);
if (!trans)
return -1;
hid_map_usage(hi, usage, bit, max, EV_KEY, trans->to);
set_bit(trans->to, hi->input->keybit);
return 1;
}
/* ignore others */
return -1;
}
static int icade_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if (usage->type == EV_KEY)
set_bit(usage->type, hi->input->evbit);
return -1;
}
static const struct hid_device_id icade_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) },
{ }
};
MODULE_DEVICE_TABLE(hid, icade_devices);
static struct hid_driver icade_driver = {
.name = "icade",
.id_table = icade_devices,
.event = icade_event,
.input_mapped = icade_input_mapped,
.input_mapping = icade_input_mapping,
};
module_hid_driver(icade_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_DESCRIPTION("ION iCade input driver");

1069
drivers/hid/hid-ids.h Executable file

File diff suppressed because it is too large Load Diff

1585
drivers/hid/hid-input.c Executable file

File diff suppressed because it is too large Load Diff

52
drivers/hid/hid-kensington.c Executable file
View File

@ -0,0 +1,52 @@
/*
* HID driver for Kensigton Slimblade Trackball
*
* Copyright (c) 2009 Jiri Kosina
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define ks_map_key(c) hid_map_usage(hi, usage, bit, max, EV_KEY, (c))
static int ks_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x01: ks_map_key(BTN_MIDDLE); break;
case 0x02: ks_map_key(BTN_SIDE); break;
default:
return 0;
}
return 1;
}
static const struct hid_device_id ks_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
{ }
};
MODULE_DEVICE_TABLE(hid, ks_devices);
static struct hid_driver ks_driver = {
.name = "kensington",
.id_table = ks_devices,
.input_mapping = ks_input_mapping,
};
module_hid_driver(ks_driver);
MODULE_LICENSE("GPL");

55
drivers/hid/hid-keytouch.c Executable file
View File

@ -0,0 +1,55 @@
/*
* HID driver for Keytouch devices not fully compliant with HID standard
*
* Copyright (c) 2011 Jiri Kosina
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
/* Replace the broken report descriptor of this device with rather
* a default one */
static __u8 keytouch_fixed_rdesc[] = {
0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15,
0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08,
0x81, 0x01, 0x95, 0x03, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91,
0x02, 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00,
0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0
};
static __u8 *keytouch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
hid_info(hdev, "fixing up Keytouch IEC report descriptor\n");
rdesc = keytouch_fixed_rdesc;
*rsize = sizeof(keytouch_fixed_rdesc);
return rdesc;
}
static const struct hid_device_id keytouch_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) },
{ }
};
MODULE_DEVICE_TABLE(hid, keytouch_devices);
static struct hid_driver keytouch_driver = {
.name = "keytouch",
.id_table = keytouch_devices,
.report_fixup = keytouch_report_fixup,
};
module_hid_driver(keytouch_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jiri Kosina");

612
drivers/hid/hid-kye.c Executable file
View File

@ -0,0 +1,612 @@
/*
* HID driver for Kye/Genius devices not fully compliant with HID standard
*
* Copyright (c) 2009 Jiri Kosina
* Copyright (c) 2009 Tomas Hanak
* Copyright (c) 2012 Nikolai Kondrashov
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
/*
* See EasyPen i405X description, device and HID report descriptors at
* http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_i405X
*/
/* Original EasyPen i405X report descriptor size */
#define EASYPEN_I405X_RDESC_ORIG_SIZE 476
/* Fixed EasyPen i405X report descriptor */
static __u8 easypen_i405x_rdesc_fixed[] = {
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x05, /* Report ID (5), */
0x09, 0x01, /* Usage (01h), */
0x15, 0x80, /* Logical Minimum (-128), */
0x25, 0x7F, /* Logical Maximum (127), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x07, /* Report Count (7), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x10, /* Report ID (16), */
0x09, 0x20, /* Usage (Stylus), */
0xA0, /* Collection (Physical), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x09, 0x42, /* Usage (Tip Switch), */
0x09, 0x44, /* Usage (Barrel Switch), */
0x09, 0x46, /* Usage (Tablet Pick), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x32, /* Usage (In Range), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0xA4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x55, 0xFD, /* Unit Exponent (-3), */
0x65, 0x13, /* Unit (Inch), */
0x34, /* Physical Minimum (0), */
0x09, 0x30, /* Usage (X), */
0x46, 0x7C, 0x15, /* Physical Maximum (5500), */
0x26, 0x00, 0x37, /* Logical Maximum (14080), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x46, 0xA0, 0x0F, /* Physical Maximum (4000), */
0x26, 0x00, 0x28, /* Logical Maximum (10240), */
0x81, 0x02, /* Input (Variable), */
0xB4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
/*
* See MousePen i608X description, device and HID report descriptors at
* http://sf.net/apps/mediawiki/digimend/?title=KYE_MousePen_i608X
*/
/* Original MousePen i608X report descriptor size */
#define MOUSEPEN_I608X_RDESC_ORIG_SIZE 476
/* Fixed MousePen i608X report descriptor */
static __u8 mousepen_i608x_rdesc_fixed[] = {
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x05, /* Report ID (5), */
0x09, 0x01, /* Usage (01h), */
0x15, 0x80, /* Logical Minimum (-128), */
0x25, 0x7F, /* Logical Maximum (127), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x07, /* Report Count (7), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x10, /* Report ID (16), */
0x09, 0x20, /* Usage (Stylus), */
0xA0, /* Collection (Physical), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x09, 0x42, /* Usage (Tip Switch), */
0x09, 0x44, /* Usage (Barrel Switch), */
0x09, 0x46, /* Usage (Tablet Pick), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x32, /* Usage (In Range), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0xA4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x55, 0xFD, /* Unit Exponent (-3), */
0x65, 0x13, /* Unit (Inch), */
0x34, /* Physical Minimum (0), */
0x09, 0x30, /* Usage (X), */
0x46, 0x40, 0x1F, /* Physical Maximum (8000), */
0x26, 0x00, 0x50, /* Logical Maximum (20480), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x46, 0x70, 0x17, /* Physical Maximum (6000), */
0x26, 0x00, 0x3C, /* Logical Maximum (15360), */
0x81, 0x02, /* Input (Variable), */
0xB4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0, /* End Collection, */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x02, /* Usage (Mouse), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x11, /* Report ID (17), */
0x09, 0x01, /* Usage (Pointer), */
0xA0, /* Collection (Physical), */
0x14, /* Logical Minimum (0), */
0xA4, /* Push, */
0x05, 0x09, /* Usage Page (Button), */
0x75, 0x01, /* Report Size (1), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x03, /* Usage Maximum (03h), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x05, /* Report Count (5), */
0x81, 0x01, /* Input (Constant), */
0xB4, /* Pop, */
0x95, 0x01, /* Report Count (1), */
0xA4, /* Push, */
0x55, 0xFD, /* Unit Exponent (-3), */
0x65, 0x13, /* Unit (Inch), */
0x34, /* Physical Minimum (0), */
0x75, 0x10, /* Report Size (16), */
0x09, 0x30, /* Usage (X), */
0x46, 0x40, 0x1F, /* Physical Maximum (8000), */
0x26, 0x00, 0x50, /* Logical Maximum (20480), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x46, 0x70, 0x17, /* Physical Maximum (6000), */
0x26, 0x00, 0x3C, /* Logical Maximum (15360), */
0x81, 0x02, /* Input (Variable), */
0xB4, /* Pop, */
0x75, 0x08, /* Report Size (8), */
0x09, 0x38, /* Usage (Wheel), */
0x15, 0xFF, /* Logical Minimum (-1), */
0x25, 0x01, /* Logical Maximum (1), */
0x81, 0x06, /* Input (Variable, Relative), */
0x81, 0x01, /* Input (Constant), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
/*
* See EasyPen M610X description, device and HID report descriptors at
* http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_M610X
*/
/* Original EasyPen M610X report descriptor size */
#define EASYPEN_M610X_RDESC_ORIG_SIZE 476
/* Fixed EasyPen M610X report descriptor */
static __u8 easypen_m610x_rdesc_fixed[] = {
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x05, /* Report ID (5), */
0x09, 0x01, /* Usage (01h), */
0x15, 0x80, /* Logical Minimum (-128), */
0x25, 0x7F, /* Logical Maximum (127), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x07, /* Report Count (7), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x10, /* Report ID (16), */
0x09, 0x20, /* Usage (Stylus), */
0xA0, /* Collection (Physical), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x09, 0x42, /* Usage (Tip Switch), */
0x09, 0x44, /* Usage (Barrel Switch), */
0x09, 0x46, /* Usage (Tablet Pick), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x32, /* Usage (In Range), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0xA4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x55, 0xFD, /* Unit Exponent (-3), */
0x65, 0x13, /* Unit (Inch), */
0x34, /* Physical Minimum (0), */
0x09, 0x30, /* Usage (X), */
0x46, 0x10, 0x27, /* Physical Maximum (10000), */
0x27, 0x00, 0xA0, 0x00, 0x00, /* Logical Maximum (40960), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x46, 0x6A, 0x18, /* Physical Maximum (6250), */
0x26, 0x00, 0x64, /* Logical Maximum (25600), */
0x81, 0x02, /* Input (Variable), */
0xB4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0, /* End Collection, */
0x05, 0x0C, /* Usage Page (Consumer), */
0x09, 0x01, /* Usage (Consumer Control), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x12, /* Report ID (18), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x04, /* Report Count (4), */
0x0A, 0x1A, 0x02, /* Usage (AC Undo), */
0x0A, 0x79, 0x02, /* Usage (AC Redo Or Repeat), */
0x0A, 0x2D, 0x02, /* Usage (AC Zoom In), */
0x0A, 0x2E, 0x02, /* Usage (AC Zoom Out), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x14, /* Report Size (20), */
0x81, 0x03, /* Input (Constant, Variable), */
0x75, 0x20, /* Report Size (32), */
0x81, 0x03, /* Input (Constant, Variable), */
0xC0 /* End Collection */
};
/* Original PenSketch M912 report descriptor size */
#define PENSKETCH_M912_RDESC_ORIG_SIZE 482
/* Fixed PenSketch M912 report descriptor */
static __u8 pensketch_m912_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x08, /* Usage (00h), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x05, /* Report ID (5), */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0x15, 0x81, /* Logical Minimum (-127), */
0x25, 0x7F, /* Logical Maximum (127), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x07, /* Report Count (7), */
0xB1, 0x02, /* Feature (Variable), */
0xC0, /* End Collection, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x10, /* Report ID (16), */
0x09, 0x20, /* Usage (Stylus), */
0xA0, /* Collection (Physical), */
0x09, 0x42, /* Usage (Tip Switch), */
0x09, 0x44, /* Usage (Barrel Switch), */
0x09, 0x46, /* Usage (Tablet Pick), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x32, /* Usage (In Range), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0xA4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x55, 0xFD, /* Unit Exponent (-3), */
0x65, 0x13, /* Unit (Inch), */
0x14, /* Logical Minimum (0), */
0x34, /* Physical Minimum (0), */
0x09, 0x30, /* Usage (X), */
0x27, 0x00, 0xF0, 0x00, 0x00, /* Logical Maximum (61440), */
0x46, 0xE0, 0x2E, /* Physical Maximum (12000), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x27, 0x00, 0xB4, 0x00, 0x00, /* Logical Maximum (46080), */
0x46, 0x28, 0x23, /* Physical Maximum (9000), */
0x81, 0x02, /* Input (Variable), */
0xB4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
0x14, /* Logical Minimum (0), */
0x26, 0xFF, 0x07, /* Logical Maximum (2047), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0, /* End Collection, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x21, /* Usage (Puck), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x11, /* Report ID (17), */
0x09, 0x21, /* Usage (Puck), */
0xA0, /* Collection (Physical), */
0x05, 0x09, /* Usage Page (Button), */
0x75, 0x01, /* Report Size (1), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x03, /* Usage Maximum (03h), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x01, /* Input (Constant), */
0x95, 0x01, /* Report Count (1), */
0x0B, 0x32, 0x00, 0x0D, 0x00, /* Usage (Digitizer In Range), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0xA4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0x55, 0xFD, /* Unit Exponent (-3), */
0x65, 0x13, /* Unit (Inch), */
0x14, /* Logical Minimum (0), */
0x34, /* Physical Minimum (0), */
0x09, 0x30, /* Usage (X), */
0x27, 0x00, 0xF0, 0x00, 0x00, /* Logical Maximum (61440), */
0x46, 0xE0, 0x2E, /* Physical Maximum (12000), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x27, 0x00, 0xB4, 0x00, 0x00, /* Logical Maximum (46080), */
0x46, 0x28, 0x23, /* Physical Maximum (9000), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x38, /* Usage (Wheel), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x01, /* Report Count (1), */
0x15, 0xFF, /* Logical Minimum (-1), */
0x25, 0x01, /* Logical Maximum (1), */
0x34, /* Physical Minimum (0), */
0x44, /* Physical Maximum (0), */
0x81, 0x06, /* Input (Variable, Relative), */
0xB4, /* Pop, */
0xC0, /* End Collection, */
0xC0, /* End Collection, */
0x05, 0x0C, /* Usage Page (Consumer), */
0x09, 0x01, /* Usage (Consumer Control), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x12, /* Report ID (18), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x08, /* Report Count (8), */
0x05, 0x0C, /* Usage Page (Consumer), */
0x0A, 0x6A, 0x02, /* Usage (AC Delete), */
0x0A, 0x1A, 0x02, /* Usage (AC Undo), */
0x0A, 0x01, 0x02, /* Usage (AC New), */
0x0A, 0x2F, 0x02, /* Usage (AC Zoom), */
0x0A, 0x25, 0x02, /* Usage (AC Forward), */
0x0A, 0x24, 0x02, /* Usage (AC Back), */
0x0A, 0x2D, 0x02, /* Usage (AC Zoom In), */
0x0A, 0x2E, 0x02, /* Usage (AC Zoom Out), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x30, /* Report Count (48), */
0x81, 0x03, /* Input (Constant, Variable), */
0xC0 /* End Collection */
};
static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize, int offset, const char *device_name) {
/*
* the fixup that need to be done:
* - change Usage Maximum in the Comsumer Control
* (report ID 3) to a reasonable value
*/
if (*rsize >= offset + 31 &&
/* Usage Page (Consumer Devices) */
rdesc[offset] == 0x05 && rdesc[offset + 1] == 0x0c &&
/* Usage (Consumer Control) */
rdesc[offset + 2] == 0x09 && rdesc[offset + 3] == 0x01 &&
/* Usage Maximum > 12287 */
rdesc[offset + 10] == 0x2a && rdesc[offset + 12] > 0x2f) {
hid_info(hdev, "fixing up %s report descriptor\n", device_name);
rdesc[offset + 12] = 0x2f;
}
return rdesc;
}
static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
case USB_DEVICE_ID_KYE_ERGO_525V:
/* the fixups that need to be done:
* - change led usage page to button for extra buttons
* - report size 8 count 1 must be size 1 count 8 for button
* bitfield
* - change the button usage range to 4-7 for the extra
* buttons
*/
if (*rsize >= 75 &&
rdesc[61] == 0x05 && rdesc[62] == 0x08 &&
rdesc[63] == 0x19 && rdesc[64] == 0x08 &&
rdesc[65] == 0x29 && rdesc[66] == 0x0f &&
rdesc[71] == 0x75 && rdesc[72] == 0x08 &&
rdesc[73] == 0x95 && rdesc[74] == 0x01) {
hid_info(hdev,
"fixing up Kye/Genius Ergo Mouse "
"report descriptor\n");
rdesc[62] = 0x09;
rdesc[64] = 0x04;
rdesc[66] = 0x07;
rdesc[72] = 0x01;
rdesc[74] = 0x08;
}
break;
case USB_DEVICE_ID_KYE_EASYPEN_I405X:
if (*rsize == EASYPEN_I405X_RDESC_ORIG_SIZE) {
rdesc = easypen_i405x_rdesc_fixed;
*rsize = sizeof(easypen_i405x_rdesc_fixed);
}
break;
case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2:
if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) {
rdesc = mousepen_i608x_rdesc_fixed;
*rsize = sizeof(mousepen_i608x_rdesc_fixed);
}
break;
case USB_DEVICE_ID_KYE_EASYPEN_M610X:
if (*rsize == EASYPEN_M610X_RDESC_ORIG_SIZE) {
rdesc = easypen_m610x_rdesc_fixed;
*rsize = sizeof(easypen_m610x_rdesc_fixed);
}
break;
case USB_DEVICE_ID_KYE_PENSKETCH_M912:
if (*rsize == PENSKETCH_M912_RDESC_ORIG_SIZE) {
rdesc = pensketch_m912_rdesc_fixed;
*rsize = sizeof(pensketch_m912_rdesc_fixed);
}
break;
case USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE:
rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
"Genius Gila Gaming Mouse");
break;
case USB_DEVICE_ID_GENIUS_GX_IMPERATOR:
rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 83,
"Genius Gx Imperator Keyboard");
break;
case USB_DEVICE_ID_GENIUS_MANTICORE:
rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
"Genius Manticore Keyboard");
break;
}
return rdesc;
}
/**
* Enable fully-functional tablet mode by setting a special feature report.
*
* @hdev: HID device
*
* The specific report ID and data were discovered by sniffing the
* Windows driver traffic.
*/
static int kye_tablet_enable(struct hid_device *hdev)
{
struct list_head *list;
struct list_head *head;
struct hid_report *report;
__s32 *value;
list = &hdev->report_enum[HID_FEATURE_REPORT].report_list;
list_for_each(head, list) {
report = list_entry(head, struct hid_report, list);
if (report->id == 5)
break;
}
if (head == list) {
hid_err(hdev, "tablet-enabling feature report not found\n");
return -ENODEV;
}
if (report->maxfield < 1 || report->field[0]->report_count < 7) {
hid_err(hdev, "invalid tablet-enabling feature report\n");
return -ENODEV;
}
value = report->field[0]->value;
value[0] = 0x12;
value[1] = 0x10;
value[2] = 0x11;
value[3] = 0x12;
value[4] = 0x00;
value[5] = 0x00;
value[6] = 0x00;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
return 0;
}
static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
switch (id->product) {
case USB_DEVICE_ID_KYE_EASYPEN_I405X:
case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2:
case USB_DEVICE_ID_KYE_EASYPEN_M610X:
case USB_DEVICE_ID_KYE_PENSKETCH_M912:
ret = kye_tablet_enable(hdev);
if (ret) {
hid_err(hdev, "tablet enabling failed\n");
goto enabling_err;
}
break;
case USB_DEVICE_ID_GENIUS_MANTICORE:
/*
* The manticore keyboard needs to have all the interfaces
* opened at least once to be fully functional.
*/
if (hid_hw_open(hdev))
hid_hw_close(hdev);
break;
}
return 0;
enabling_err:
hid_hw_stop(hdev);
err:
return ret;
}
static const struct hid_device_id kye_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_KYE_EASYPEN_I405X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_KYE_MOUSEPEN_I608X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_KYE_EASYPEN_M610X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_GENIUS_MANTICORE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
USB_DEVICE_ID_KYE_PENSKETCH_M912) },
{ }
};
MODULE_DEVICE_TABLE(hid, kye_devices);
static struct hid_driver kye_driver = {
.name = "kye",
.id_table = kye_devices,
.probe = kye_probe,
.report_fixup = kye_report_fixup,
};
module_hid_driver(kye_driver);
MODULE_LICENSE("GPL");

59
drivers/hid/hid-lcpower.c Executable file
View File

@ -0,0 +1,59 @@
/*
* HID driver for LC Power Model RC1000MCE
*
* Copyright (c) 2011 Chris Schlund
* based on hid-topseed module
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define ts_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x046: ts_map_key_clear(KEY_YELLOW); break;
case 0x047: ts_map_key_clear(KEY_GREEN); break;
case 0x049: ts_map_key_clear(KEY_BLUE); break;
case 0x04a: ts_map_key_clear(KEY_RED); break;
case 0x00d: ts_map_key_clear(KEY_HOME); break;
case 0x025: ts_map_key_clear(KEY_TV); break;
case 0x048: ts_map_key_clear(KEY_VCR); break;
case 0x024: ts_map_key_clear(KEY_MENU); break;
default:
return 0;
}
return 1;
}
static const struct hid_device_id ts_devices[] = {
{ HID_USB_DEVICE( USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) },
{ }
};
MODULE_DEVICE_TABLE(hid, ts_devices);
static struct hid_driver ts_driver = {
.name = "LC RC1000MCE",
.id_table = ts_devices,
.input_mapping = ts_input_mapping,
};
module_hid_driver(ts_driver);
MODULE_LICENSE("GPL");

894
drivers/hid/hid-lenovo.c Executable file
View File

@ -0,0 +1,894 @@
/*
* HID driver for Lenovo:
* - ThinkPad USB Keyboard with TrackPoint (tpkbd)
* - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd)
* - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd)
*
* Copyright (c) 2012 Bernhard Seibold
* Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/leds.h>
#include "hid-ids.h"
struct lenovo_drvdata_tpkbd {
int led_state;
struct led_classdev led_mute;
struct led_classdev led_micmute;
int press_to_select;
int dragging;
int release_to_select;
int select_right;
int sensitivity;
int press_speed;
};
struct lenovo_drvdata_cptkbd {
u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
bool fn_lock;
int sensitivity;
};
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
0x05, 0x88, /* Usage Page (Vendor Usage Page 0x88) */
0x09, 0x01, /* Usage (Vendor Usage 0x01) */
0xa1, 0x01, /* Collection (Application) */
0x85, 0x04, /* Report ID (4) */
0x19, 0x00, /* Usage Minimum (0) */
0x2a, 0xff, 0xff, /* Usage Maximum (65535) */
};
static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPPRODOCK:
/* the fixups that need to be done:
* - get a reasonable usage max for the vendor collection
* 0x8801 from the report ID 4
*/
if (*rsize >= 153 &&
memcmp(&rdesc[140], lenovo_pro_dock_need_fixup_collection,
sizeof(lenovo_pro_dock_need_fixup_collection)) == 0) {
rdesc[151] = 0x01;
rdesc[152] = 0x00;
}
break;
}
return rdesc;
}
static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
/* This sub-device contains trackpoint, mark it */
hid_set_drvdata(hdev, (void *)1);
map_key_clear(KEY_MICMUTE);
return 1;
}
return 0;
}
static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
/* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
(usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
switch (usage->hid & HID_USAGE) {
case 0x00f1: /* Fn-F4: Mic mute */
map_key_clear(KEY_MICMUTE);
return 1;
case 0x00f2: /* Fn-F5: Brightness down */
map_key_clear(KEY_BRIGHTNESSDOWN);
return 1;
case 0x00f3: /* Fn-F6: Brightness up */
map_key_clear(KEY_BRIGHTNESSUP);
return 1;
case 0x00f4: /* Fn-F7: External display (projector) */
map_key_clear(KEY_SWITCHVIDEOMODE);
return 1;
case 0x00f5: /* Fn-F8: Wireless */
map_key_clear(KEY_WLAN);
return 1;
case 0x00f6: /* Fn-F9: Control panel */
map_key_clear(KEY_CONFIG);
return 1;
case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
map_key_clear(KEY_SCALE);
return 1;
case 0x00f9: /* Fn-F12: Open My computer (6 boxes) USB-only */
/* NB: This mapping is invented in raw_event below */
map_key_clear(KEY_FILE);
return 1;
case 0x00fa: /* Fn-Esc: Fn-lock toggle */
map_key_clear(KEY_FN_ESC);
return 1;
case 0x00fb: /* Middle mouse button (in native mode) */
map_key_clear(BTN_MIDDLE);
return 1;
}
}
/* Compatibility middle/wheel mappings should be ignored */
if (usage->hid == HID_GD_WHEEL)
return -1;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON &&
(usage->hid & HID_USAGE) == 0x003)
return -1;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER &&
(usage->hid & HID_USAGE) == 0x238)
return -1;
/* Map wheel emulation reports: 0xffa1 = USB, 0xff10 = BT */
if ((usage->hid & HID_USAGE_PAGE) == 0xff100000 ||
(usage->hid & HID_USAGE_PAGE) == 0xffa10000) {
field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE;
field->logical_minimum = -127;
field->logical_maximum = 127;
switch (usage->hid & HID_USAGE) {
case 0x0000:
hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
return 1;
case 0x0001:
hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL);
return 1;
default:
return -1;
}
}
return 0;
}
static int lenovo_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
return lenovo_input_mapping_tpkbd(hdev, hi, field,
usage, bit, max);
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
return lenovo_input_mapping_cptkbd(hdev, hi, field,
usage, bit, max);
default:
return 0;
}
}
#undef map_key_clear
/* Send a config command to the keyboard */
static int lenovo_send_cmd_cptkbd(struct hid_device *hdev,
unsigned char byte2, unsigned char byte3)
{
int ret;
unsigned char buf[] = {0x18, byte2, byte3};
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_CUSBKBD:
ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf),
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
break;
case USB_DEVICE_ID_LENOVO_CBTKBD:
ret = hid_hw_output_report(hdev, buf, sizeof(buf));
break;
default:
ret = -EINVAL;
break;
}
return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
}
static void lenovo_features_set_cptkbd(struct hid_device *hdev)
{
int ret;
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
if (ret)
hid_err(hdev, "Fn-lock setting failed: %d\n", ret);
ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity);
if (ret)
hid_err(hdev, "Sensitivity setting failed: %d\n", ret);
}
static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock);
}
static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value))
return -EINVAL;
if (value < 0 || value > 1)
return -EINVAL;
cptkbd_data->fn_lock = !!value;
lenovo_features_set_cptkbd(hdev);
return count;
}
static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
cptkbd_data->sensitivity);
}
static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
return -EINVAL;
cptkbd_data->sensitivity = value;
lenovo_features_set_cptkbd(hdev);
return count;
}
static struct device_attribute dev_attr_fn_lock_cptkbd =
__ATTR(fn_lock, S_IWUSR | S_IRUGO,
attr_fn_lock_show_cptkbd,
attr_fn_lock_store_cptkbd);
static struct device_attribute dev_attr_sensitivity_cptkbd =
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
attr_sensitivity_show_cptkbd,
attr_sensitivity_store_cptkbd);
static struct attribute *lenovo_attributes_cptkbd[] = {
&dev_attr_fn_lock_cptkbd.attr,
&dev_attr_sensitivity_cptkbd.attr,
NULL
};
static const struct attribute_group lenovo_attr_group_cptkbd = {
.attrs = lenovo_attributes_cptkbd,
};
static int lenovo_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
/*
* Compact USB keyboard's Fn-F12 report holds down many other keys, and
* its own key is outside the usage page range. Remove extra
* keypresses and remap to inside usage page.
*/
if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
&& size == 3
&& data[0] == 0x15
&& data[1] == 0x94
&& data[2] == 0x01)) {
data[1] = 0x00;
data[2] = 0x01;
}
return 0;
}
static int lenovo_event_cptkbd(struct hid_device *hdev,
struct hid_field *field, struct hid_usage *usage, __s32 value)
{
struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
/* "wheel" scroll events */
if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
usage->code == REL_HWHEEL)) {
/* Scroll events disable middle-click event */
cptkbd_data->middlebutton_state = 2;
return 0;
}
/* Middle click events */
if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
if (value == 1) {
cptkbd_data->middlebutton_state = 1;
} else if (value == 0) {
if (cptkbd_data->middlebutton_state == 1) {
/* No scrolling inbetween, send middle-click */
input_event(field->hidinput->input,
EV_KEY, BTN_MIDDLE, 1);
input_sync(field->hidinput->input);
input_event(field->hidinput->input,
EV_KEY, BTN_MIDDLE, 0);
input_sync(field->hidinput->input);
}
cptkbd_data->middlebutton_state = 0;
}
return 1;
}
return 0;
}
static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
return lenovo_event_cptkbd(hdev, field, usage, value);
default:
return 0;
}
}
static int lenovo_features_set_tpkbd(struct hid_device *hdev)
{
struct hid_report *report;
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02;
report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08;
report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40;
report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
report->field[2]->value[0] = data_pointer->sensitivity;
report->field[3]->value[0] = data_pointer->press_speed;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
return 0;
}
static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
}
static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value))
return -EINVAL;
if (value < 0 || value > 1)
return -EINVAL;
data_pointer->press_to_select = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_dragging_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
}
static ssize_t attr_dragging_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value))
return -EINVAL;
if (value < 0 || value > 1)
return -EINVAL;
data_pointer->dragging = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
}
static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value))
return -EINVAL;
if (value < 0 || value > 1)
return -EINVAL;
data_pointer->release_to_select = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_select_right_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
}
static ssize_t attr_select_right_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value))
return -EINVAL;
if (value < 0 || value > 1)
return -EINVAL;
data_pointer->select_right = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
data_pointer->sensitivity);
}
static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
return -EINVAL;
data_pointer->sensitivity = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
data_pointer->press_speed);
}
static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
return -EINVAL;
data_pointer->press_speed = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static struct device_attribute dev_attr_press_to_select_tpkbd =
__ATTR(press_to_select, S_IWUSR | S_IRUGO,
attr_press_to_select_show_tpkbd,
attr_press_to_select_store_tpkbd);
static struct device_attribute dev_attr_dragging_tpkbd =
__ATTR(dragging, S_IWUSR | S_IRUGO,
attr_dragging_show_tpkbd,
attr_dragging_store_tpkbd);
static struct device_attribute dev_attr_release_to_select_tpkbd =
__ATTR(release_to_select, S_IWUSR | S_IRUGO,
attr_release_to_select_show_tpkbd,
attr_release_to_select_store_tpkbd);
static struct device_attribute dev_attr_select_right_tpkbd =
__ATTR(select_right, S_IWUSR | S_IRUGO,
attr_select_right_show_tpkbd,
attr_select_right_store_tpkbd);
static struct device_attribute dev_attr_sensitivity_tpkbd =
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
attr_sensitivity_show_tpkbd,
attr_sensitivity_store_tpkbd);
static struct device_attribute dev_attr_press_speed_tpkbd =
__ATTR(press_speed, S_IWUSR | S_IRUGO,
attr_press_speed_show_tpkbd,
attr_press_speed_store_tpkbd);
static struct attribute *lenovo_attributes_tpkbd[] = {
&dev_attr_press_to_select_tpkbd.attr,
&dev_attr_dragging_tpkbd.attr,
&dev_attr_release_to_select_tpkbd.attr,
&dev_attr_select_right_tpkbd.attr,
&dev_attr_sensitivity_tpkbd.attr,
&dev_attr_press_speed_tpkbd.attr,
NULL
};
static const struct attribute_group lenovo_attr_group_tpkbd = {
.attrs = lenovo_attributes_tpkbd,
};
static enum led_brightness lenovo_led_brightness_get_tpkbd(
struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
int led_nr = 0;
if (led_cdev == &data_pointer->led_micmute)
led_nr = 1;
return data_pointer->led_state & (1 << led_nr)
? LED_FULL
: LED_OFF;
}
static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
struct hid_report *report;
int led_nr = 0;
if (led_cdev == &data_pointer->led_micmute)
led_nr = 1;
if (value == LED_OFF)
data_pointer->led_state &= ~(1 << led_nr);
else
data_pointer->led_state |= 1 << led_nr;
report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
}
static int lenovo_probe_tpkbd(struct hid_device *hdev)
{
struct device *dev = &hdev->dev;
struct lenovo_drvdata_tpkbd *data_pointer;
size_t name_sz = strlen(dev_name(dev)) + 16;
char *name_mute, *name_micmute;
int i;
int ret;
/*
* Only register extra settings against subdevice where input_mapping
* set drvdata to 1, i.e. the trackpoint.
*/
if (!hid_get_drvdata(hdev))
return 0;
hid_set_drvdata(hdev, NULL);
/* Validate required reports. */
for (i = 0; i < 4; i++) {
if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
return -ENODEV;
}
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
return -ENODEV;
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
if (ret)
hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
data_pointer = devm_kzalloc(&hdev->dev,
sizeof(struct lenovo_drvdata_tpkbd),
GFP_KERNEL);
if (data_pointer == NULL) {
hid_err(hdev, "Could not allocate memory for driver data\n");
ret = -ENOMEM;
goto err;
}
// set same default values as windows driver
data_pointer->sensitivity = 0xa0;
data_pointer->press_speed = 0x38;
name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
if (name_mute == NULL || name_micmute == NULL) {
hid_err(hdev, "Could not allocate memory for led data\n");
ret = -ENOMEM;
goto err;
}
snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
hid_set_drvdata(hdev, data_pointer);
data_pointer->led_mute.name = name_mute;
data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd;
data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
data_pointer->led_mute.dev = dev;
led_classdev_register(dev, &data_pointer->led_mute);
data_pointer->led_micmute.name = name_micmute;
data_pointer->led_micmute.brightness_get =
lenovo_led_brightness_get_tpkbd;
data_pointer->led_micmute.brightness_set =
lenovo_led_brightness_set_tpkbd;
data_pointer->led_micmute.dev = dev;
led_classdev_register(dev, &data_pointer->led_micmute);
lenovo_features_set_tpkbd(hdev);
return 0;
err:
sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
return ret;
}
static int lenovo_probe_cptkbd(struct hid_device *hdev)
{
int ret;
struct lenovo_drvdata_cptkbd *cptkbd_data;
/* All the custom action happens on the USBMOUSE device for USB */
if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
&& hdev->type != HID_TYPE_USBMOUSE) {
hid_dbg(hdev, "Ignoring keyboard half of device\n");
return 0;
}
cptkbd_data = devm_kzalloc(&hdev->dev,
sizeof(*cptkbd_data),
GFP_KERNEL);
if (cptkbd_data == NULL) {
hid_err(hdev, "can't alloc keyboard descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, cptkbd_data);
/*
* Tell the keyboard a driver understands it, and turn F7, F9, F11 into
* regular keys
*/
ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
if (ret)
hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret);
/* Switch middle button to native mode */
ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01);
if (ret)
hid_warn(hdev, "Failed to switch middle button: %d\n", ret);
/* Set keyboard settings to known state */
cptkbd_data->middlebutton_state = 0;
cptkbd_data->fn_lock = true;
cptkbd_data->sensitivity = 0x05;
lenovo_features_set_cptkbd(hdev);
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd);
if (ret)
hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
return 0;
}
static int lenovo_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "hid_parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hid_hw_start failed\n");
goto err;
}
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
ret = lenovo_probe_tpkbd(hdev);
break;
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
ret = lenovo_probe_cptkbd(hdev);
break;
default:
ret = 0;
break;
}
if (ret)
goto err_hid;
return 0;
err_hid:
hid_hw_stop(hdev);
err:
return ret;
}
static void lenovo_remove_tpkbd(struct hid_device *hdev)
{
struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
/*
* Only the trackpoint half of the keyboard has drvdata and stuff that
* needs unregistering.
*/
if (data_pointer == NULL)
return;
sysfs_remove_group(&hdev->dev.kobj,
&lenovo_attr_group_tpkbd);
led_classdev_unregister(&data_pointer->led_micmute);
led_classdev_unregister(&data_pointer->led_mute);
hid_set_drvdata(hdev, NULL);
}
static void lenovo_remove_cptkbd(struct hid_device *hdev)
{
sysfs_remove_group(&hdev->dev.kobj,
&lenovo_attr_group_cptkbd);
}
static void lenovo_remove(struct hid_device *hdev)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
lenovo_remove_tpkbd(hdev);
break;
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
lenovo_remove_cptkbd(hdev);
break;
}
hid_hw_stop(hdev);
}
static int lenovo_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
if (test_bit(EV_REL, hi->input->evbit)) {
/* set only for trackpoint device */
__set_bit(INPUT_PROP_POINTER, hi->input->propbit);
__set_bit(INPUT_PROP_POINTING_STICK,
hi->input->propbit);
}
break;
}
return 0;
}
static const struct hid_device_id lenovo_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) },
{ }
};
MODULE_DEVICE_TABLE(hid, lenovo_devices);
static struct hid_driver lenovo_driver = {
.name = "lenovo",
.id_table = lenovo_devices,
.input_configured = lenovo_input_configured,
.input_mapping = lenovo_input_mapping,
.probe = lenovo_probe,
.remove = lenovo_remove,
.raw_event = lenovo_raw_event,
.event = lenovo_event,
.report_fixup = lenovo_report_fixup,
};
module_hid_driver(lenovo_driver);
MODULE_LICENSE("GPL");

844
drivers/hid/hid-lg.c Executable file
View File

@ -0,0 +1,844 @@
/*
* HID driver for some logitech "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
* Copyright (c) 2010 Hendrik Iben
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/usb.h>
#include <linux/wait.h>
#include "usbhid/usbhid.h"
#include "hid-ids.h"
#include "hid-lg.h"
#include "hid-lg4ff.h"
#define LG_RDESC 0x001
#define LG_BAD_RELATIVE_KEYS 0x002
#define LG_DUPLICATE_USAGES 0x004
#define LG_EXPANDED_KEYMAP 0x010
#define LG_IGNORE_DOUBLED_WHEEL 0x020
#define LG_WIRELESS 0x040
#define LG_INVERT_HWHEEL 0x080
#define LG_NOGET 0x100
#define LG_FF 0x200
#define LG_FF2 0x400
#define LG_RDESC_REL_ABS 0x800
#define LG_FF3 0x1000
#define LG_FF4 0x2000
/* Size of the original descriptors of the Driving Force (and Pro) wheels */
#define DF_RDESC_ORIG_SIZE 130
#define DFP_RDESC_ORIG_SIZE 97
#define FV_RDESC_ORIG_SIZE 130
#define MOMO_RDESC_ORIG_SIZE 87
#define MOMO2_RDESC_ORIG_SIZE 87
/* Fixed report descriptors for Logitech Driving Force (and Pro)
* wheel controllers
*
* The original descriptors hide the separate throttle and brake axes in
* a custom vendor usage page, providing only a combined value as
* GenericDesktop.Y.
* These descriptors remove the combined Y axis and instead report
* separate throttle (Y) and brake (RZ).
*/
static __u8 df_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x0A, /* Report Size (10), */
0x14, /* Logical Minimum (0), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x34, /* Physical Minimum (0), */
0x46, 0xFF, 0x03, /* Physical Maximum (1023), */
0x09, 0x30, /* Usage (X), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x0C, /* Report Count (12), */
0x75, 0x01, /* Report Size (1), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x05, 0x09, /* Usage (Buttons), */
0x19, 0x01, /* Usage Minimum (1), */
0x29, 0x0c, /* Usage Maximum (12), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x02, /* Report Count (2), */
0x06, 0x00, 0xFF, /* Usage Page (Vendor: 65280), */
0x09, 0x01, /* Usage (?: 1), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x08, /* Report Size (8), */
0x81, 0x02, /* Input (Variable), */
0x25, 0x07, /* Logical Maximum (7), */
0x46, 0x3B, 0x01, /* Physical Maximum (315), */
0x75, 0x04, /* Report Size (4), */
0x65, 0x14, /* Unit (Degrees), */
0x09, 0x39, /* Usage (Hat Switch), */
0x81, 0x42, /* Input (Variable, Null State), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x04, /* Report Count (4), */
0x65, 0x00, /* Unit (none), */
0x06, 0x00, 0xFF, /* Usage Page (Vendor: 65280), */
0x09, 0x01, /* Usage (?: 1), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x08, /* Report Size (8), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x09, 0x31, /* Usage (Y), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x35, /* Usage (Rz), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x95, 0x07, /* Report Count (7), */
0x75, 0x08, /* Report Size (8), */
0x09, 0x03, /* Usage (?: 3), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
static __u8 dfp_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x0E, /* Report Size (14), */
0x14, /* Logical Minimum (0), */
0x26, 0xFF, 0x3F, /* Logical Maximum (16383), */
0x34, /* Physical Minimum (0), */
0x46, 0xFF, 0x3F, /* Physical Maximum (16383), */
0x09, 0x30, /* Usage (X), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x0E, /* Report Count (14), */
0x75, 0x01, /* Report Size (1), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x0E, /* Usage Maximum (0Eh), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x04, /* Report Size (4), */
0x25, 0x07, /* Logical Maximum (7), */
0x46, 0x3B, 0x01, /* Physical Maximum (315), */
0x65, 0x14, /* Unit (Degrees), */
0x09, 0x39, /* Usage (Hat Switch), */
0x81, 0x42, /* Input (Variable, Nullstate), */
0x65, 0x00, /* Unit, */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x75, 0x08, /* Report Size (8), */
0x81, 0x01, /* Input (Constant), */
0x09, 0x31, /* Usage (Y), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x35, /* Usage (Rz), */
0x81, 0x02, /* Input (Variable), */
0x81, 0x01, /* Input (Constant), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x09, 0x02, /* Usage (02h), */
0x95, 0x07, /* Report Count (7), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
static __u8 fv_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x0A, /* Report Size (10), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x35, 0x00, /* Physical Minimum (0), */
0x46, 0xFF, 0x03, /* Physical Maximum (1023), */
0x09, 0x30, /* Usage (X), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x0C, /* Report Count (12), */
0x75, 0x01, /* Report Size (1), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x0C, /* Usage Maximum (0Ch), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x02, /* Report Count (2), */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x02, /* Usage (02h), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x08, /* Report Size (8), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x25, 0x07, /* Logical Maximum (7), */
0x46, 0x3B, 0x01, /* Physical Maximum (315), */
0x75, 0x04, /* Report Size (4), */
0x65, 0x14, /* Unit (Degrees), */
0x09, 0x39, /* Usage (Hat Switch), */
0x81, 0x42, /* Input (Variable, Null State), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x04, /* Report Count (4), */
0x65, 0x00, /* Unit, */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x08, /* Report Size (8), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x09, 0x31, /* Usage (Y), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x32, /* Usage (Z), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x95, 0x07, /* Report Count (7), */
0x75, 0x08, /* Report Size (8), */
0x09, 0x03, /* Usage (03h), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
static __u8 momo_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x0A, /* Report Size (10), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x35, 0x00, /* Physical Minimum (0), */
0x46, 0xFF, 0x03, /* Physical Maximum (1023), */
0x09, 0x30, /* Usage (X), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x08, /* Report Count (8), */
0x75, 0x01, /* Report Size (1), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x08, /* Usage Maximum (08h), */
0x81, 0x02, /* Input (Variable), */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x75, 0x0E, /* Report Size (14), */
0x95, 0x01, /* Report Count (1), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x09, 0x00, /* Usage (00h), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x75, 0x08, /* Report Size (8), */
0x09, 0x31, /* Usage (Y), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x32, /* Usage (Z), */
0x81, 0x02, /* Input (Variable), */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x09, 0x02, /* Usage (02h), */
0x95, 0x07, /* Report Count (7), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
static __u8 momo2_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x0A, /* Report Size (10), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x35, 0x00, /* Physical Minimum (0), */
0x46, 0xFF, 0x03, /* Physical Maximum (1023), */
0x09, 0x30, /* Usage (X), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x0A, /* Report Count (10), */
0x75, 0x01, /* Report Size (1), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x0A, /* Usage Maximum (0Ah), */
0x81, 0x02, /* Input (Variable), */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x00, /* Usage (00h), */
0x95, 0x04, /* Report Count (4), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x01, /* Report Count (1), */
0x75, 0x08, /* Report Size (8), */
0x26, 0xFF, 0x00, /* Logical Maximum (255), */
0x46, 0xFF, 0x00, /* Physical Maximum (255), */
0x09, 0x01, /* Usage (01h), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x31, /* Usage (Y), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x32, /* Usage (Z), */
0x81, 0x02, /* Input (Variable), */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x00, /* Usage (00h), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x09, 0x02, /* Usage (02h), */
0x95, 0x07, /* Report Count (7), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
/*
* Certain Logitech keyboards send in report #3 keys which are far
* above the logical maximum described in descriptor. This extends
* the original value of 0x28c of logical maximum to 0x104d
*/
static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
struct usb_device_descriptor *udesc;
__u16 bcdDevice, rev_maj, rev_min;
if ((drv_data->quirks & LG_RDESC) && *rsize >= 91 && rdesc[83] == 0x26 &&
rdesc[84] == 0x8c && rdesc[85] == 0x02) {
hid_info(hdev,
"fixing up Logitech keyboard report descriptor\n");
rdesc[84] = rdesc[89] = 0x4d;
rdesc[85] = rdesc[90] = 0x10;
}
if ((drv_data->quirks & LG_RDESC_REL_ABS) && *rsize >= 51 &&
rdesc[32] == 0x81 && rdesc[33] == 0x06 &&
rdesc[49] == 0x81 && rdesc[50] == 0x06) {
hid_info(hdev,
"fixing up rel/abs in Logitech report descriptor\n");
rdesc[33] = rdesc[50] = 0x02;
}
switch (hdev->product) {
/* Several wheels report as this id when operating in emulation mode. */
case USB_DEVICE_ID_LOGITECH_WHEEL:
udesc = &(hid_to_usb_dev(hdev)->descriptor);
if (!udesc) {
hid_err(hdev, "NULL USB device descriptor\n");
break;
}
bcdDevice = le16_to_cpu(udesc->bcdDevice);
rev_maj = bcdDevice >> 8;
rev_min = bcdDevice & 0xff;
/* Update the report descriptor for only the Driving Force wheel */
if (rev_maj == 1 && rev_min == 2 &&
*rsize == DF_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Driving Force report descriptor\n");
rdesc = df_rdesc_fixed;
*rsize = sizeof(df_rdesc_fixed);
}
break;
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
if (*rsize == MOMO_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Momo Force (Red) report descriptor\n");
rdesc = momo_rdesc_fixed;
*rsize = sizeof(momo_rdesc_fixed);
}
break;
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
if (*rsize == MOMO2_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Momo Racing Force (Black) report descriptor\n");
rdesc = momo2_rdesc_fixed;
*rsize = sizeof(momo2_rdesc_fixed);
}
break;
case USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL:
if (*rsize == FV_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Formula Vibration report descriptor\n");
rdesc = fv_rdesc_fixed;
*rsize = sizeof(fv_rdesc_fixed);
}
break;
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
if (*rsize == DFP_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Driving Force Pro report descriptor\n");
rdesc = dfp_rdesc_fixed;
*rsize = sizeof(dfp_rdesc_fixed);
}
break;
case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
if (*rsize >= 101 && rdesc[41] == 0x95 && rdesc[42] == 0x0B &&
rdesc[47] == 0x05 && rdesc[48] == 0x09) {
hid_info(hdev, "fixing up Logitech Speed Force Wireless report descriptor\n");
rdesc[41] = 0x05;
rdesc[42] = 0x09;
rdesc[47] = 0x95;
rdesc[48] = 0x0B;
}
break;
}
return rdesc;
}
#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int lg_ultrax_remote_mapping(struct hid_input *hi,
struct hid_usage *usage, unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
return 0;
set_bit(EV_REP, hi->input->evbit);
switch (usage->hid & HID_USAGE) {
/* Reported on Logitech Ultra X Media Remote */
case 0x004: lg_map_key_clear(KEY_AGAIN); break;
case 0x00d: lg_map_key_clear(KEY_HOME); break;
case 0x024: lg_map_key_clear(KEY_SHUFFLE); break;
case 0x025: lg_map_key_clear(KEY_TV); break;
case 0x026: lg_map_key_clear(KEY_MENU); break;
case 0x031: lg_map_key_clear(KEY_AUDIO); break;
case 0x032: lg_map_key_clear(KEY_TEXT); break;
case 0x033: lg_map_key_clear(KEY_LAST); break;
case 0x047: lg_map_key_clear(KEY_MP3); break;
case 0x048: lg_map_key_clear(KEY_DVD); break;
case 0x049: lg_map_key_clear(KEY_MEDIA); break;
case 0x04a: lg_map_key_clear(KEY_VIDEO); break;
case 0x04b: lg_map_key_clear(KEY_ANGLE); break;
case 0x04c: lg_map_key_clear(KEY_LANGUAGE); break;
case 0x04d: lg_map_key_clear(KEY_SUBTITLE); break;
case 0x051: lg_map_key_clear(KEY_RED); break;
case 0x052: lg_map_key_clear(KEY_CLOSE); break;
default:
return 0;
}
return 1;
}
static int lg_dinovo_mapping(struct hid_input *hi, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x00d: lg_map_key_clear(KEY_MEDIA); break;
default:
return 0;
}
return 1;
}
static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x1001: lg_map_key_clear(KEY_MESSENGER); break;
case 0x1003: lg_map_key_clear(KEY_SOUND); break;
case 0x1004: lg_map_key_clear(KEY_VIDEO); break;
case 0x1005: lg_map_key_clear(KEY_AUDIO); break;
case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); break;
/* The following two entries are Playlist 1 and 2 on the MX3200 */
case 0x100f: lg_map_key_clear(KEY_FN_1); break;
case 0x1010: lg_map_key_clear(KEY_FN_2); break;
case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break;
case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break;
case 0x1013: lg_map_key_clear(KEY_CAMERA); break;
case 0x1014: lg_map_key_clear(KEY_MESSENGER); break;
case 0x1015: lg_map_key_clear(KEY_RECORD); break;
case 0x1016: lg_map_key_clear(KEY_PLAYER); break;
case 0x1017: lg_map_key_clear(KEY_EJECTCD); break;
case 0x1018: lg_map_key_clear(KEY_MEDIA); break;
case 0x1019: lg_map_key_clear(KEY_PROG1); break;
case 0x101a: lg_map_key_clear(KEY_PROG2); break;
case 0x101b: lg_map_key_clear(KEY_PROG3); break;
case 0x101c: lg_map_key_clear(KEY_CYCLEWINDOWS); break;
case 0x101f: lg_map_key_clear(KEY_ZOOMIN); break;
case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break;
case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break;
case 0x1023: lg_map_key_clear(KEY_CLOSE); break;
case 0x1027: lg_map_key_clear(KEY_MENU); break;
/* this one is marked as 'Rotate' */
case 0x1028: lg_map_key_clear(KEY_ANGLE); break;
case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break;
case 0x102a: lg_map_key_clear(KEY_BACK); break;
case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); break;
case 0x102d: lg_map_key_clear(KEY_WWW); break;
/* The following two are 'Start/answer call' and 'End/reject call'
on the MX3200 */
case 0x1031: lg_map_key_clear(KEY_OK); break;
case 0x1032: lg_map_key_clear(KEY_CANCEL); break;
case 0x1041: lg_map_key_clear(KEY_BATTERY); break;
case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break;
case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break;
case 0x1044: lg_map_key_clear(KEY_PRESENTATION); break;
case 0x1045: lg_map_key_clear(KEY_UNDO); break;
case 0x1046: lg_map_key_clear(KEY_REDO); break;
case 0x1047: lg_map_key_clear(KEY_PRINT); break;
case 0x1048: lg_map_key_clear(KEY_SAVE); break;
case 0x1049: lg_map_key_clear(KEY_PROG1); break;
case 0x104a: lg_map_key_clear(KEY_PROG2); break;
case 0x104b: lg_map_key_clear(KEY_PROG3); break;
case 0x104c: lg_map_key_clear(KEY_PROG4); break;
default:
return 0;
}
return 1;
}
static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
/* extended mapping for certain Logitech hardware (Logitech cordless
desktop LX500) */
static const u8 e_keymap[] = {
0,216, 0,213,175,156, 0, 0, 0, 0,
144, 0, 0, 0, 0, 0, 0, 0, 0,212,
174,167,152,161,112, 0, 0, 0,154, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,183,184,185,186,187,
188,189,190,191,192,193,194, 0, 0, 0
};
struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
unsigned int hid = usage->hid;
if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER &&
lg_ultrax_remote_mapping(hi, usage, bit, max))
return 1;
if (hdev->product == USB_DEVICE_ID_DINOVO_MINI &&
lg_dinovo_mapping(hi, usage, bit, max))
return 1;
if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
return 1;
if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
return 0;
hid &= HID_USAGE;
/* Special handling for Logitech Cordless Desktop */
if (field->application == HID_GD_MOUSE) {
if ((drv_data->quirks & LG_IGNORE_DOUBLED_WHEEL) &&
(hid == 7 || hid == 8))
return -1;
} else {
if ((drv_data->quirks & LG_EXPANDED_KEYMAP) &&
hid < ARRAY_SIZE(e_keymap) &&
e_keymap[hid] != 0) {
hid_map_usage(hi, usage, bit, max, EV_KEY,
e_keymap[hid]);
return 1;
}
}
return 0;
}
static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
if ((drv_data->quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY &&
(field->flags & HID_MAIN_ITEM_RELATIVE))
field->flags &= ~HID_MAIN_ITEM_RELATIVE;
if ((drv_data->quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY ||
usage->type == EV_REL || usage->type == EV_ABS))
clear_bit(usage->code, *bit);
/* Ensure that Logitech wheels are not given a default fuzz/flat value */
if (usage->type == EV_ABS && (usage->code == ABS_X ||
usage->code == ABS_Y || usage->code == ABS_Z ||
usage->code == ABS_RZ)) {
switch (hdev->product) {
case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
case USB_DEVICE_ID_LOGITECH_WHEEL:
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
case USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL:
field->application = HID_GD_MULTIAXIS;
break;
default:
break;
}
}
return 0;
}
static int lg_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
if ((drv_data->quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) {
input_event(field->hidinput->input, usage->type, usage->code,
-value);
return 1;
}
if (drv_data->quirks & LG_FF4) {
return lg4ff_adjust_input_event(hdev, field, usage, value, drv_data);
}
return 0;
}
static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
__u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
struct lg_drv_data *drv_data;
int ret;
/* G29 only work with the 1st interface */
if ((hdev->product == USB_DEVICE_ID_LOGITECH_G29_WHEEL) &&
(iface_num != 0)) {
dbg_hid("%s: ignoring ifnum %d\n", __func__, iface_num);
return -ENODEV;
}
drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL);
if (!drv_data) {
hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
return -ENOMEM;
}
drv_data->quirks = id->driver_data;
hid_set_drvdata(hdev, (void *)drv_data);
if (drv_data->quirks & LG_NOGET)
hdev->quirks |= HID_QUIRK_NOGET;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
if (drv_data->quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4))
connect_mask &= ~HID_CONNECT_FF;
ret = hid_hw_start(hdev, connect_mask);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
/* Setup wireless link with Logitech Wii wheel */
if (hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
unsigned char buf[] = { 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf),
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret >= 0) {
/* insert a little delay of 10 jiffies ~ 40ms */
wait_queue_head_t wait;
init_waitqueue_head (&wait);
wait_event_interruptible_timeout(wait, 0,
msecs_to_jiffies(40));
/* Select random Address */
buf[1] = 0xB2;
get_random_bytes(&buf[2], 2);
ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf),
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
}
}
if (drv_data->quirks & LG_FF)
ret = lgff_init(hdev);
else if (drv_data->quirks & LG_FF2)
ret = lg2ff_init(hdev);
else if (drv_data->quirks & LG_FF3)
ret = lg3ff_init(hdev);
else if (drv_data->quirks & LG_FF4)
ret = lg4ff_init(hdev);
if (ret)
goto err_free;
return 0;
err_free:
kfree(drv_data);
return ret;
}
static void lg_remove(struct hid_device *hdev)
{
struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
if (drv_data->quirks & LG_FF4)
lg4ff_deinit(hdev);
else
hid_hw_stop(hdev);
kfree(drv_data);
}
static const struct hid_device_id lg_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
.driver_data = LG_RDESC | LG_WIRELESS },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER),
.driver_data = LG_RDESC | LG_WIRELESS },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2),
.driver_data = LG_RDESC | LG_WIRELESS },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER),
.driver_data = LG_BAD_RELATIVE_KEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
.driver_data = LG_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
.driver_data = LG_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
.driver_data = LG_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
.driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500),
.driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D),
.driver_data = LG_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DUAL_ACTION),
.driver_data = LG_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL),
.driver_data = LG_NOGET | LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD),
.driver_data = LG_FF2 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD),
.driver_data = LG_FF },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2),
.driver_data = LG_FF },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_WHEEL),
.driver_data = LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D),
.driver_data = LG_FF },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO),
.driver_data = LG_FF },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL),
.driver_data = LG_NOGET | LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2),
.driver_data = LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL),
.driver_data = LG_FF2 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL),
.driver_data = LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL),
.driver_data = LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL),
.driver_data = LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL),
.driver_data = LG_NOGET | LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
.driver_data = LG_FF4 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG),
.driver_data = LG_FF },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
.driver_data = LG_FF2 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940),
.driver_data = LG_FF3 },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR),
.driver_data = LG_RDESC_REL_ABS },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER),
.driver_data = LG_RDESC_REL_ABS },
{ }
};
MODULE_DEVICE_TABLE(hid, lg_devices);
static struct hid_driver lg_driver = {
.name = "logitech",
.id_table = lg_devices,
.report_fixup = lg_report_fixup,
.input_mapping = lg_input_mapping,
.input_mapped = lg_input_mapped,
.event = lg_event,
.probe = lg_probe,
.remove = lg_remove,
};
module_hid_driver(lg_driver);
#ifdef CONFIG_LOGIWHEELS_FF
int lg4ff_no_autoswitch = 0;
module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
#endif
MODULE_LICENSE("GPL");

27
drivers/hid/hid-lg.h Executable file
View File

@ -0,0 +1,27 @@
#ifndef __HID_LG_H
#define __HID_LG_H
struct lg_drv_data {
unsigned long quirks;
void *device_props; /* Device specific properties */
};
#ifdef CONFIG_LOGITECH_FF
int lgff_init(struct hid_device *hdev);
#else
static inline int lgff_init(struct hid_device *hdev) { return -1; }
#endif
#ifdef CONFIG_LOGIRUMBLEPAD2_FF
int lg2ff_init(struct hid_device *hdev);
#else
static inline int lg2ff_init(struct hid_device *hdev) { return -1; }
#endif
#ifdef CONFIG_LOGIG940_FF
int lg3ff_init(struct hid_device *hdev);
#else
static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
#endif
#endif

101
drivers/hid/hid-lg2ff.c Executable file
View File

@ -0,0 +1,101 @@
/*
* Force feedback support for Logitech RumblePad and Rumblepad 2
*
* Copyright (c) 2008 Anssi Hannula <anssi.hannula@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include "hid-lg.h"
struct lg2ff_device {
struct hid_report *report;
};
static int play_effect(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct lg2ff_device *lg2ff = data;
int weak, strong;
strong = effect->u.rumble.strong_magnitude;
weak = effect->u.rumble.weak_magnitude;
if (weak || strong) {
weak = weak * 0xff / 0xffff;
strong = strong * 0xff / 0xffff;
lg2ff->report->field[0]->value[0] = 0x51;
lg2ff->report->field[0]->value[2] = weak;
lg2ff->report->field[0]->value[4] = strong;
} else {
lg2ff->report->field[0]->value[0] = 0xf3;
lg2ff->report->field[0]->value[2] = 0x00;
lg2ff->report->field[0]->value[4] = 0x00;
}
hid_hw_request(hid, lg2ff->report, HID_REQ_SET_REPORT);
return 0;
}
int lg2ff_init(struct hid_device *hid)
{
struct lg2ff_device *lg2ff;
struct hid_report *report;
struct hid_input *hidinput = list_entry(hid->inputs.next,
struct hid_input, list);
struct input_dev *dev = hidinput->input;
int error;
/* Check that the report looks ok */
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7);
if (!report)
return -ENODEV;
lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL);
if (!lg2ff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, lg2ff, play_effect);
if (error) {
kfree(lg2ff);
return error;
}
lg2ff->report = report;
report->field[0]->value[0] = 0xf3;
report->field[0]->value[1] = 0x00;
report->field[0]->value[2] = 0x00;
report->field[0]->value[3] = 0x00;
report->field[0]->value[4] = 0x00;
report->field[0]->value[5] = 0x00;
report->field[0]->value[6] = 0x00;
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
hid_info(hid, "Force feedback for Logitech variant 2 rumble devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
return 0;
}

156
drivers/hid/hid-lg3ff.c Executable file
View File

@ -0,0 +1,156 @@
/*
* Force feedback support for Logitech Flight System G940
*
* Copyright (c) 2009 Gary Stein <LordCnidarian@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/input.h>
#include <linux/hid.h>
#include "hid-lg.h"
/*
* G940 Theory of Operation (from experimentation)
*
* There are 63 fields (only 3 of them currently used)
* 0 - seems to be command field
* 1 - 30 deal with the x axis
* 31 -60 deal with the y axis
*
* Field 1 is x axis constant force
* Field 31 is y axis constant force
*
* other interesting fields 1,2,3,4 on x axis
* (same for 31,32,33,34 on y axis)
*
* 0 0 127 127 makes the joystick autocenter hard
*
* 127 0 127 127 makes the joystick loose on the right,
* but stops all movemnt left
*
* -127 0 -127 -127 makes the joystick loose on the left,
* but stops all movement right
*
* 0 0 -127 -127 makes the joystick rattle very hard
*
* I'm sure these are effects that I don't know enough about them
*/
struct lg3ff_device {
struct hid_report *report;
};
static int hid_lg3ff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
int x, y;
/*
* Available values in the field should always be 63, but we only use up to
* 35. Instead, clear the entire area, however big it is.
*/
memset(report->field[0]->value, 0,
sizeof(__s32) * report->field[0]->report_count);
switch (effect->type) {
case FF_CONSTANT:
/*
* Already clamped in ff_memless
* 0 is center (different then other logitech)
*/
x = effect->u.ramp.start_level;
y = effect->u.ramp.end_level;
/* send command byte */
report->field[0]->value[0] = 0x51;
/*
* Sign backwards from other Force3d pro
* which get recast here in two's complement 8 bits
*/
report->field[0]->value[1] = (unsigned char)(-x);
report->field[0]->value[31] = (unsigned char)(-y);
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
break;
}
return 0;
}
static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude)
{
struct hid_device *hid = input_get_drvdata(dev);
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
/*
* Auto Centering probed from device
* NOTE: deadman's switch on G940 must be covered
* for effects to work
*/
report->field[0]->value[0] = 0x51;
report->field[0]->value[1] = 0x00;
report->field[0]->value[2] = 0x00;
report->field[0]->value[3] = 0x7F;
report->field[0]->value[4] = 0x7F;
report->field[0]->value[31] = 0x00;
report->field[0]->value[32] = 0x00;
report->field[0]->value[33] = 0x7F;
report->field[0]->value[34] = 0x7F;
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
}
static const signed short ff3_joystick_ac[] = {
FF_CONSTANT,
FF_AUTOCENTER,
-1
};
int lg3ff_init(struct hid_device *hid)
{
struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
const signed short *ff_bits = ff3_joystick_ac;
int error;
int i;
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35))
return -ENODEV;
/* Assume single fixed device G940 */
for (i = 0; ff_bits[i] >= 0; i++)
set_bit(ff_bits[i], dev->ffbit);
error = input_ff_create_memless(dev, NULL, hid_lg3ff_play);
if (error)
return error;
if (test_bit(FF_AUTOCENTER, dev->ffbit))
dev->ff->set_autocenter = hid_lg3ff_set_autocenter;
hid_info(hid, "Force feedback for Logitech Flight System G940 by Gary Stein <LordCnidarian@gmail.com>\n");
return 0;
}

1388
drivers/hid/hid-lg4ff.c Executable file

File diff suppressed because it is too large Load Diff

18
drivers/hid/hid-lg4ff.h Executable file
View File

@ -0,0 +1,18 @@
#ifndef __HID_LG4FF_H
#define __HID_LG4FF_H
#ifdef CONFIG_LOGIWHEELS_FF
extern int lg4ff_no_autoswitch; /* From hid-lg.c */
int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data);
int lg4ff_init(struct hid_device *hdev);
int lg4ff_deinit(struct hid_device *hdev);
#else
static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data) { return 0; }
static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
#endif
#endif

161
drivers/hid/hid-lgff.c Executable file
View File

@ -0,0 +1,161 @@
/*
* Force feedback support for hid-compliant for some of the devices from
* Logitech, namely:
* - WingMan Cordless RumblePad
* - WingMan Force 3D
*
* Copyright (c) 2002-2004 Johann Deneux
* Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Should you need to contact me, the author, you can do so by
* e-mail - mail your message to <johann.deneux@it.uu.se>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/input.h>
#include <linux/hid.h>
#include "hid-lg.h"
struct dev_type {
u16 idVendor;
u16 idProduct;
const signed short *ff;
};
static const signed short ff_rumble[] = {
FF_RUMBLE,
-1
};
static const signed short ff_joystick[] = {
FF_CONSTANT,
-1
};
static const signed short ff_joystick_ac[] = {
FF_CONSTANT,
FF_AUTOCENTER,
-1
};
static const struct dev_type devices[] = {
{ 0x046d, 0xc211, ff_rumble },
{ 0x046d, 0xc219, ff_rumble },
{ 0x046d, 0xc283, ff_joystick },
{ 0x046d, 0xc286, ff_joystick_ac },
{ 0x046d, 0xc287, ff_joystick_ac },
{ 0x046d, 0xc293, ff_joystick },
{ 0x046d, 0xc295, ff_joystick },
};
static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
int x, y;
unsigned int left, right;
#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
switch (effect->type) {
case FF_CONSTANT:
x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */
y = effect->u.ramp.end_level + 0x7f;
CLAMP(x);
CLAMP(y);
report->field[0]->value[0] = 0x51;
report->field[0]->value[1] = 0x08;
report->field[0]->value[2] = x;
report->field[0]->value[3] = y;
dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
break;
case FF_RUMBLE:
right = effect->u.rumble.strong_magnitude;
left = effect->u.rumble.weak_magnitude;
right = right * 0xff / 0xffff;
left = left * 0xff / 0xffff;
CLAMP(left);
CLAMP(right);
report->field[0]->value[0] = 0x42;
report->field[0]->value[1] = 0x00;
report->field[0]->value[2] = left;
report->field[0]->value[3] = right;
dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
break;
}
return 0;
}
static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude)
{
struct hid_device *hid = input_get_drvdata(dev);
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
__s32 *value = report->field[0]->value;
magnitude = (magnitude >> 12) & 0xf;
*value++ = 0xfe;
*value++ = 0x0d;
*value++ = magnitude; /* clockwise strength */
*value++ = magnitude; /* counter-clockwise strength */
*value++ = 0x80;
*value++ = 0x00;
*value = 0x00;
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
}
int lgff_init(struct hid_device* hid)
{
struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
const signed short *ff_bits = ff_joystick;
int error;
int i;
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
return -ENODEV;
for (i = 0; i < ARRAY_SIZE(devices); i++) {
if (dev->id.vendor == devices[i].idVendor &&
dev->id.product == devices[i].idProduct) {
ff_bits = devices[i].ff;
break;
}
}
for (i = 0; ff_bits[i] >= 0; i++)
set_bit(ff_bits[i], dev->ffbit);
error = input_ff_create_memless(dev, NULL, hid_lgff_play);
if (error)
return error;
if ( test_bit(FF_AUTOCENTER, dev->ffbit) )
dev->ff->set_autocenter = hid_lgff_set_autocenter;
pr_info("Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n");
return 0;
}

1174
drivers/hid/hid-logitech-dj.c Executable file

File diff suppressed because it is too large Load Diff

1666
drivers/hid/hid-logitech-hidpp.c Executable file

File diff suppressed because it is too large Load Diff

587
drivers/hid/hid-magicmouse.c Executable file
View File

@ -0,0 +1,587 @@
/*
* Apple "Magic" Wireless Mouse driver
*
* Copyright (c) 2010 Michael Poole <mdpoole@troilus.org>
* Copyright (c) 2010 Chase Douglas <chase.douglas@canonical.com>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/input/mt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "hid-ids.h"
static bool emulate_3button = true;
module_param(emulate_3button, bool, 0644);
MODULE_PARM_DESC(emulate_3button, "Emulate a middle button");
static int middle_button_start = -350;
static int middle_button_stop = +350;
static bool emulate_scroll_wheel = true;
module_param(emulate_scroll_wheel, bool, 0644);
MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel");
static unsigned int scroll_speed = 32;
static int param_set_scroll_speed(const char *val, struct kernel_param *kp) {
unsigned long speed;
if (!val || kstrtoul(val, 0, &speed) || speed > 63)
return -EINVAL;
scroll_speed = speed;
return 0;
}
module_param_call(scroll_speed, param_set_scroll_speed, param_get_uint, &scroll_speed, 0644);
MODULE_PARM_DESC(scroll_speed, "Scroll speed, value from 0 (slow) to 63 (fast)");
static bool scroll_acceleration = false;
module_param(scroll_acceleration, bool, 0644);
MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events");
static bool report_undeciphered;
module_param(report_undeciphered, bool, 0644);
MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
#define TRACKPAD_REPORT_ID 0x28
#define MOUSE_REPORT_ID 0x29
#define DOUBLE_REPORT_ID 0xf7
/* These definitions are not precise, but they're close enough. (Bits
* 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
* to be some kind of bit mask -- 0x20 may be a near-field reading,
* and 0x40 is actual contact, and 0x10 may be a start/stop or change
* indication.)
*/
#define TOUCH_STATE_MASK 0xf0
#define TOUCH_STATE_NONE 0x00
#define TOUCH_STATE_START 0x30
#define TOUCH_STATE_DRAG 0x40
#define SCROLL_ACCEL_DEFAULT 7
/* Touch surface information. Dimension is in hundredths of a mm, min and max
* are in units. */
#define MOUSE_DIMENSION_X (float)9056
#define MOUSE_MIN_X -1100
#define MOUSE_MAX_X 1258
#define MOUSE_RES_X ((MOUSE_MAX_X - MOUSE_MIN_X) / (MOUSE_DIMENSION_X / 100))
#define MOUSE_DIMENSION_Y (float)5152
#define MOUSE_MIN_Y -1589
#define MOUSE_MAX_Y 2047
#define MOUSE_RES_Y ((MOUSE_MAX_Y - MOUSE_MIN_Y) / (MOUSE_DIMENSION_Y / 100))
#define TRACKPAD_DIMENSION_X (float)13000
#define TRACKPAD_MIN_X -2909
#define TRACKPAD_MAX_X 3167
#define TRACKPAD_RES_X \
((TRACKPAD_MAX_X - TRACKPAD_MIN_X) / (TRACKPAD_DIMENSION_X / 100))
#define TRACKPAD_DIMENSION_Y (float)11000
#define TRACKPAD_MIN_Y -2456
#define TRACKPAD_MAX_Y 2565
#define TRACKPAD_RES_Y \
((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100))
/**
* struct magicmouse_sc - Tracks Magic Mouse-specific data.
* @input: Input device through which we report events.
* @quirks: Currently unused.
* @ntouches: Number of touches in most recent touch report.
* @scroll_accel: Number of consecutive scroll motions.
* @scroll_jiffies: Time of last scroll motion.
* @touches: Most recent data for a touch, indexed by tracking ID.
* @tracking_ids: Mapping of current touch input data to @touches.
*/
struct magicmouse_sc {
struct input_dev *input;
unsigned long quirks;
int ntouches;
int scroll_accel;
unsigned long scroll_jiffies;
struct {
short x;
short y;
short scroll_x;
short scroll_y;
u8 size;
} touches[16];
int tracking_ids[16];
};
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
{
int touch = -1;
int ii;
/* If there is only one "firm" touch, set touch to its
* tracking ID.
*/
for (ii = 0; ii < msc->ntouches; ii++) {
int idx = msc->tracking_ids[ii];
if (msc->touches[idx].size < 8) {
/* Ignore this touch. */
} else if (touch >= 0) {
touch = -1;
break;
} else {
touch = idx;
}
}
return touch;
}
static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state)
{
int last_state = test_bit(BTN_LEFT, msc->input->key) << 0 |
test_bit(BTN_RIGHT, msc->input->key) << 1 |
test_bit(BTN_MIDDLE, msc->input->key) << 2;
if (emulate_3button) {
int id;
/* If some button was pressed before, keep it held
* down. Otherwise, if there's exactly one firm
* touch, use that to override the mouse's guess.
*/
if (state == 0) {
/* The button was released. */
} else if (last_state != 0) {
state = last_state;
} else if ((id = magicmouse_firm_touch(msc)) >= 0) {
int x = msc->touches[id].x;
if (x < middle_button_start)
state = 1;
else if (x > middle_button_stop)
state = 2;
else
state = 4;
} /* else: we keep the mouse's guess */
input_report_key(msc->input, BTN_MIDDLE, state & 4);
}
input_report_key(msc->input, BTN_LEFT, state & 1);
input_report_key(msc->input, BTN_RIGHT, state & 2);
if (state != last_state)
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
}
static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata)
{
struct input_dev *input = msc->input;
int id, x, y, size, orientation, touch_major, touch_minor, state, down;
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
x = (tdata[1] << 28 | tdata[0] << 20) >> 20;
y = -((tdata[2] << 24 | tdata[1] << 16) >> 20);
size = tdata[5] & 0x3f;
orientation = (tdata[6] >> 2) - 32;
touch_major = tdata[3];
touch_minor = tdata[4];
state = tdata[7] & TOUCH_STATE_MASK;
down = state != TOUCH_STATE_NONE;
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;
x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
size = tdata[6] & 0x3f;
orientation = (tdata[7] >> 2) - 32;
touch_major = tdata[4];
touch_minor = tdata[5];
state = tdata[8] & TOUCH_STATE_MASK;
down = state != TOUCH_STATE_NONE;
}
/* Store tracking ID and other fields. */
msc->tracking_ids[raw_id] = id;
msc->touches[id].x = x;
msc->touches[id].y = y;
msc->touches[id].size = size;
/* If requested, emulate a scroll wheel by detecting small
* vertical touch motions.
*/
if (emulate_scroll_wheel) {
unsigned long now = jiffies;
int step_x = msc->touches[id].scroll_x - x;
int step_y = msc->touches[id].scroll_y - y;
/* Calculate and apply the scroll motion. */
switch (state) {
case TOUCH_STATE_START:
msc->touches[id].scroll_x = x;
msc->touches[id].scroll_y = y;
/* Reset acceleration after half a second. */
if (scroll_acceleration && time_before(now,
msc->scroll_jiffies + HZ / 2))
msc->scroll_accel = max_t(int,
msc->scroll_accel - 1, 1);
else
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
break;
case TOUCH_STATE_DRAG:
step_x /= (64 - (int)scroll_speed) * msc->scroll_accel;
if (step_x != 0) {
msc->touches[id].scroll_x -= step_x *
(64 - scroll_speed) * msc->scroll_accel;
msc->scroll_jiffies = now;
input_report_rel(input, REL_HWHEEL, -step_x);
}
step_y /= (64 - (int)scroll_speed) * msc->scroll_accel;
if (step_y != 0) {
msc->touches[id].scroll_y -= step_y *
(64 - scroll_speed) * msc->scroll_accel;
msc->scroll_jiffies = now;
input_report_rel(input, REL_WHEEL, step_y);
}
break;
}
}
if (down)
msc->ntouches++;
input_mt_slot(input, id);
input_mt_report_slot_state(input, MT_TOOL_FINGER, down);
/* Generate the input events for this touch. */
if (down) {
input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major << 2);
input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor << 2);
input_report_abs(input, ABS_MT_ORIENTATION, -orientation);
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
if (report_undeciphered) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
input_event(input, EV_MSC, MSC_RAW, tdata[7]);
else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
input_event(input, EV_MSC, MSC_RAW, tdata[8]);
}
}
}
static int magicmouse_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
struct input_dev *input = msc->input;
int x = 0, y = 0, ii, clicks = 0, npoints;
switch (data[0]) {
case TRACKPAD_REPORT_ID:
/* Expect four bytes of prefix, and N*9 bytes of touch data. */
if (size < 4 || ((size - 4) % 9) != 0)
return 0;
npoints = (size - 4) / 9;
if (npoints > 15) {
hid_warn(hdev, "invalid size value (%d) for TRACKPAD_REPORT_ID\n",
size);
return 0;
}
msc->ntouches = 0;
for (ii = 0; ii < npoints; ii++)
magicmouse_emit_touch(msc, ii, data + ii * 9 + 4);
clicks = data[1];
/* The following bits provide a device specific timestamp. They
* are unused here.
*
* ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;
*/
break;
case MOUSE_REPORT_ID:
/* Expect six bytes of prefix, and N*8 bytes of touch data. */
if (size < 6 || ((size - 6) % 8) != 0)
return 0;
npoints = (size - 6) / 8;
if (npoints > 15) {
hid_warn(hdev, "invalid size value (%d) for MOUSE_REPORT_ID\n",
size);
return 0;
}
msc->ntouches = 0;
for (ii = 0; ii < npoints; ii++)
magicmouse_emit_touch(msc, ii, data + ii * 8 + 6);
/* When emulating three-button mode, it is important
* to have the current touch information before
* generating a click event.
*/
x = (int)(((data[3] & 0x0c) << 28) | (data[1] << 22)) >> 22;
y = (int)(((data[3] & 0x30) << 26) | (data[2] << 22)) >> 22;
clicks = data[3];
/* The following bits provide a device specific timestamp. They
* are unused here.
*
* ts = data[3] >> 6 | data[4] << 2 | data[5] << 10;
*/
break;
case DOUBLE_REPORT_ID:
/* Sometimes the trackpad sends two touch reports in one
* packet.
*/
magicmouse_raw_event(hdev, report, data + 2, data[1]);
magicmouse_raw_event(hdev, report, data + 2 + data[1],
size - 2 - data[1]);
break;
default:
return 0;
}
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
magicmouse_emit_buttons(msc, clicks & 3);
input_report_rel(input, REL_X, x);
input_report_rel(input, REL_Y, y);
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
input_report_key(input, BTN_MOUSE, clicks & 1);
input_mt_report_pointer_emulation(input, true);
}
input_sync(input);
return 1;
}
static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
{
int error;
__set_bit(EV_KEY, input->evbit);
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
__set_bit(BTN_LEFT, input->keybit);
__set_bit(BTN_RIGHT, input->keybit);
if (emulate_3button)
__set_bit(BTN_MIDDLE, input->keybit);
__set_bit(EV_REL, input->evbit);
__set_bit(REL_X, input->relbit);
__set_bit(REL_Y, input->relbit);
if (emulate_scroll_wheel) {
__set_bit(REL_WHEEL, input->relbit);
__set_bit(REL_HWHEEL, input->relbit);
}
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
/* input->keybit is initialized with incorrect button info
* for Magic Trackpad. There really is only one physical
* button (BTN_LEFT == BTN_MOUSE). Make sure we don't
* advertise buttons that don't exist...
*/
__clear_bit(BTN_RIGHT, input->keybit);
__clear_bit(BTN_MIDDLE, input->keybit);
__set_bit(BTN_MOUSE, input->keybit);
__set_bit(BTN_TOOL_FINGER, input->keybit);
__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
__set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
__set_bit(BTN_TOOL_QUADTAP, input->keybit);
__set_bit(BTN_TOOL_QUINTTAP, input->keybit);
__set_bit(BTN_TOUCH, input->keybit);
__set_bit(INPUT_PROP_POINTER, input->propbit);
__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
}
__set_bit(EV_ABS, input->evbit);
error = input_mt_init_slots(input, 16, 0);
if (error)
return error;
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
4, 0);
input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
4, 0);
input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
/* Note: Touch Y position from the device is inverted relative
* to how pointer motion is reported (and relative to how USB
* HID recommends the coordinates work). This driver keeps
* the origin at the same position, and just uses the additive
* inverse of the reported Y.
*/
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
input_set_abs_params(input, ABS_MT_POSITION_X,
MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,
MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0);
input_abs_set_res(input, ABS_MT_POSITION_X,
MOUSE_RES_X);
input_abs_set_res(input, ABS_MT_POSITION_Y,
MOUSE_RES_Y);
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
TRACKPAD_MAX_X, 4, 0);
input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
TRACKPAD_MAX_Y, 4, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,
TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,
TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0);
input_abs_set_res(input, ABS_X, TRACKPAD_RES_X);
input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y);
input_abs_set_res(input, ABS_MT_POSITION_X,
TRACKPAD_RES_X);
input_abs_set_res(input, ABS_MT_POSITION_Y,
TRACKPAD_RES_Y);
}
input_set_events_per_packet(input, 60);
if (report_undeciphered) {
__set_bit(EV_MSC, input->evbit);
__set_bit(MSC_RAW, input->mscbit);
}
return 0;
}
static int magicmouse_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
if (!msc->input)
msc->input = hi->input;
/* Magic Trackpad does not give relative data after switching to MT */
if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
field->flags & HID_MAIN_ITEM_RELATIVE)
return -1;
return 0;
}
static int magicmouse_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
int ret;
ret = magicmouse_setup_input(msc->input, hdev);
if (ret) {
hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
/* clean msc->input to notify probe() of the failure */
msc->input = NULL;
return ret;
}
return 0;
}
static int magicmouse_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
__u8 feature[] = { 0xd7, 0x01 };
struct magicmouse_sc *msc;
struct hid_report *report;
int ret;
msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
if (msc == NULL) {
hid_err(hdev, "can't alloc magicmouse descriptor\n");
return -ENOMEM;
}
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
msc->quirks = id->driver_data;
hid_set_drvdata(hdev, msc);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "magicmouse hid parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "magicmouse hw start failed\n");
return ret;
}
if (!msc->input) {
hid_err(hdev, "magicmouse input not registered\n");
ret = -ENOMEM;
goto err_stop_hw;
}
if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
report = hid_register_report(hdev, HID_INPUT_REPORT,
MOUSE_REPORT_ID);
else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD_REPORT_ID);
report = hid_register_report(hdev, HID_INPUT_REPORT,
DOUBLE_REPORT_ID);
}
if (!report) {
hid_err(hdev, "unable to register touch report\n");
ret = -ENOMEM;
goto err_stop_hw;
}
report->size = 6;
/*
* Some devices repond with 'invalid report id' when feature
* report switching it into multitouch mode is sent to it.
*
* This results in -EIO from the _raw low-level transport callback,
* but there seems to be no other way of switching the mode.
* Thus the super-ugly hacky success check below.
*/
ret = hid_hw_raw_request(hdev, feature[0], feature, sizeof(feature),
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret != -EIO && ret != sizeof(feature)) {
hid_err(hdev, "unable to request touch data (%d)\n", ret);
goto err_stop_hw;
}
return 0;
err_stop_hw:
hid_hw_stop(hdev);
return ret;
}
static const struct hid_device_id magic_mice[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
{ }
};
MODULE_DEVICE_TABLE(hid, magic_mice);
static struct hid_driver magicmouse_driver = {
.name = "magicmouse",
.id_table = magic_mice,
.probe = magicmouse_probe,
.raw_event = magicmouse_raw_event,
.input_mapping = magicmouse_input_mapping,
.input_configured = magicmouse_input_configured,
};
module_hid_driver(magicmouse_driver);
MODULE_LICENSE("GPL");

307
drivers/hid/hid-microsoft.c Executable file
View File

@ -0,0 +1,307 @@
/*
* HID driver for some microsoft "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#define MS_HIDINPUT 0x01
#define MS_ERGONOMY 0x02
#define MS_PRESENTER 0x04
#define MS_RDESC 0x08
#define MS_NOGET 0x10
#define MS_DUPLICATE_USAGES 0x20
#define MS_RDESC_3K 0x40
static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
/*
* Microsoft Wireless Desktop Receiver (Model 1028) has
* 'Usage Min/Max' where it ought to have 'Physical Min/Max'
*/
if ((quirks & MS_RDESC) && *rsize == 571 && rdesc[557] == 0x19 &&
rdesc[559] == 0x29) {
hid_info(hdev, "fixing up Microsoft Wireless Receiver Model 1028 report descriptor\n");
rdesc[557] = 0x35;
rdesc[559] = 0x45;
}
/* the same as above (s/usage/physical/) */
if ((quirks & MS_RDESC_3K) && *rsize == 106 && rdesc[94] == 0x19 &&
rdesc[95] == 0x00 && rdesc[96] == 0x29 &&
rdesc[97] == 0xff) {
rdesc[94] = 0x35;
rdesc[96] = 0x45;
}
return rdesc;
}
#define ms_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int ms_ergonomy_kb_quirk(struct hid_input *hi, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct input_dev *input = hi->input;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
switch (usage->hid & HID_USAGE) {
/*
* Microsoft uses these 2 reserved usage ids for 2 keys on
* the MS office kb labelled "Office Home" and "Task Pane".
*/
case 0x29d:
ms_map_key_clear(KEY_PROG1);
return 1;
case 0x29e:
ms_map_key_clear(KEY_PROG2);
return 1;
}
return 0;
}
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
return 0;
switch (usage->hid & HID_USAGE) {
case 0xfd06: ms_map_key_clear(KEY_CHAT); break;
case 0xfd07: ms_map_key_clear(KEY_PHONE); break;
case 0xff00:
/* Special keypad keys */
ms_map_key_clear(KEY_KPEQUAL);
set_bit(KEY_KPLEFTPAREN, input->keybit);
set_bit(KEY_KPRIGHTPAREN, input->keybit);
break;
case 0xff01:
/* Scroll wheel */
hid_map_usage_clear(hi, usage, bit, max, EV_REL, REL_WHEEL);
break;
case 0xff02:
/*
* This byte contains a copy of the modifier keys byte of a
* standard hid keyboard report, as send by interface 0
* (this usage is found on interface 1).
*
* This byte only gets send when another key in the same report
* changes state, and as such is useless, ignore it.
*/
return -1;
case 0xff05:
set_bit(EV_REP, input->evbit);
ms_map_key_clear(KEY_F13);
set_bit(KEY_F14, input->keybit);
set_bit(KEY_F15, input->keybit);
set_bit(KEY_F16, input->keybit);
set_bit(KEY_F17, input->keybit);
set_bit(KEY_F18, input->keybit);
break;
default:
return 0;
}
return 1;
}
static int ms_presenter_8k_quirk(struct hid_input *hi, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
return 0;
set_bit(EV_REP, hi->input->evbit);
switch (usage->hid & HID_USAGE) {
case 0xfd08: ms_map_key_clear(KEY_FORWARD); break;
case 0xfd09: ms_map_key_clear(KEY_BACK); break;
case 0xfd0b: ms_map_key_clear(KEY_PLAYPAUSE); break;
case 0xfd0e: ms_map_key_clear(KEY_CLOSE); break;
case 0xfd0f: ms_map_key_clear(KEY_PLAY); break;
default:
return 0;
}
return 1;
}
static int ms_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
if (quirks & MS_ERGONOMY) {
int ret = ms_ergonomy_kb_quirk(hi, usage, bit, max);
if (ret)
return ret;
}
if ((quirks & MS_PRESENTER) &&
ms_presenter_8k_quirk(hi, usage, bit, max))
return 1;
return 0;
}
static int ms_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
if (quirks & MS_DUPLICATE_USAGES)
clear_bit(usage->code, *bit);
return 0;
}
static int ms_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
struct input_dev *input;
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
!usage->type)
return 0;
input = field->hidinput->input;
/* Handling MS keyboards special buttons */
if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff00)) {
/* Special keypad keys */
input_report_key(input, KEY_KPEQUAL, value & 0x01);
input_report_key(input, KEY_KPLEFTPAREN, value & 0x02);
input_report_key(input, KEY_KPRIGHTPAREN, value & 0x04);
return 1;
}
if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff01)) {
/* Scroll wheel */
int step = ((value & 0x60) >> 5) + 1;
switch (value & 0x1f) {
case 0x01:
input_report_rel(input, REL_WHEEL, step);
break;
case 0x1f:
input_report_rel(input, REL_WHEEL, -step);
break;
}
return 1;
}
if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff05)) {
static unsigned int last_key = 0;
unsigned int key = 0;
switch (value) {
case 0x01: key = KEY_F14; break;
case 0x02: key = KEY_F15; break;
case 0x04: key = KEY_F16; break;
case 0x08: key = KEY_F17; break;
case 0x10: key = KEY_F18; break;
}
if (key) {
input_event(input, usage->type, key, 1);
last_key = key;
} else
input_event(input, usage->type, last_key, 0);
return 1;
}
return 0;
}
static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
unsigned long quirks = id->driver_data;
int ret;
hid_set_drvdata(hdev, (void *)quirks);
if (quirks & MS_NOGET)
hdev->quirks |= HID_QUIRK_NOGET;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | ((quirks & MS_HIDINPUT) ?
HID_CONNECT_HIDINPUT_FORCE : 0));
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
return 0;
err_free:
return ret;
}
static const struct hid_device_id ms_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV),
.driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB),
.driver_data = MS_ERGONOMY },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K),
.driver_data = MS_ERGONOMY },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K_JP),
.driver_data = MS_ERGONOMY },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE7K),
.driver_data = MS_ERGONOMY },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K),
.driver_data = MS_ERGONOMY | MS_RDESC },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB),
.driver_data = MS_PRESENTER },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K),
.driver_data = MS_ERGONOMY | MS_RDESC_3K },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0),
.driver_data = MS_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500),
.driver_data = MS_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3),
.driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2),
.driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP),
.driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3),
.driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER),
.driver_data = MS_HIDINPUT },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT),
.driver_data = MS_PRESENTER },
{ }
};
MODULE_DEVICE_TABLE(hid, ms_devices);
static struct hid_driver ms_driver = {
.name = "microsoft",
.id_table = ms_devices,
.report_fixup = ms_report_fixup,
.input_mapping = ms_input_mapping,
.input_mapped = ms_input_mapped,
.event = ms_event,
.probe = ms_probe,
};
module_hid_driver(ms_driver);
MODULE_LICENSE("GPL");

68
drivers/hid/hid-monterey.c Executable file
View File

@ -0,0 +1,68 @@
/*
* HID driver for some monterey "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static __u8 *mr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 31 && rdesc[29] == 0x05 && rdesc[30] == 0x09) {
hid_info(hdev, "fixing up button/consumer in HID report descriptor\n");
rdesc[30] = 0x0c;
}
return rdesc;
}
#define mr_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int mr_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x156: mr_map_key_clear(KEY_WORDPROCESSOR); break;
case 0x157: mr_map_key_clear(KEY_SPREADSHEET); break;
case 0x158: mr_map_key_clear(KEY_PRESENTATION); break;
case 0x15c: mr_map_key_clear(KEY_STOP); break;
default:
return 0;
}
return 1;
}
static const struct hid_device_id mr_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
{ }
};
MODULE_DEVICE_TABLE(hid, mr_devices);
static struct hid_driver mr_driver = {
.name = "monterey",
.id_table = mr_devices,
.report_fixup = mr_report_fixup,
.input_mapping = mr_input_mapping,
};
module_hid_driver(mr_driver);
MODULE_LICENSE("GPL");

1507
drivers/hid/hid-multitouch.c Executable file

File diff suppressed because it is too large Load Diff

1034
drivers/hid/hid-ntrig.c Executable file

File diff suppressed because it is too large Load Diff

55
drivers/hid/hid-ortek.c Executable file
View File

@ -0,0 +1,55 @@
/*
* HID driver for various devices which are apparently based on the same chipset
* from certain vendor which produces chips that contain wrong LogicalMaximum
* value in their HID report descriptor. Currently supported devices are:
*
* Ortek PKB-1700
* Ortek WKB-2000
* Skycable wireless presenter
*
* Copyright (c) 2010 Johnathon Harris <jmharris@gmail.com>
* Copyright (c) 2011 Jiri Kosina
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static __u8 *ortek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x01) {
hid_info(hdev, "Fixing up logical minimum in report descriptor (Ortek)\n");
rdesc[55] = 0x92;
} else if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
hid_info(hdev, "Fixing up logical minimum in report descriptor (Skycable)\n");
rdesc[53] = 0x65;
}
return rdesc;
}
static const struct hid_device_id ortek_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_PKB1700) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_WKB2000) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
{ }
};
MODULE_DEVICE_TABLE(hid, ortek_devices);
static struct hid_driver ortek_driver = {
.name = "ortek",
.id_table = ortek_devices,
.report_fixup = ortek_report_fixup
};
module_hid_driver(ortek_driver);
MODULE_LICENSE("GPL");

49
drivers/hid/hid-penmount.c Executable file
View File

@ -0,0 +1,49 @@
/*
* HID driver for PenMount touchscreens
*
* Copyright (c) 2014 Christian Gmeiner <christian.gmeiner <at> gmail.com>
*
* based on hid-penmount copyrighted by
* PenMount Touch Solutions <penmount <at> seed.net.tw>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/module.h>
#include <linux/hid.h>
#include "hid-ids.h"
static int penmount_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
return 1;
}
return 0;
}
static const struct hid_device_id penmount_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_6000) },
{ }
};
MODULE_DEVICE_TABLE(hid, penmount_devices);
static struct hid_driver penmount_driver = {
.name = "hid-penmount",
.id_table = penmount_devices,
.input_mapping = penmount_input_mapping,
};
module_hid_driver(penmount_driver);
MODULE_AUTHOR("Christian Gmeiner <christian.gmeiner@gmail.com>");
MODULE_DESCRIPTION("PenMount HID TouchScreen driver");
MODULE_LICENSE("GPL");

108
drivers/hid/hid-petalynx.c Executable file
View File

@ -0,0 +1,108 @@
/*
* HID driver for some petalynx "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
/* Petalynx Maxter Remote has maximum for consumer page set too low */
static __u8 *pl_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 62 && rdesc[39] == 0x2a && rdesc[40] == 0xf5 &&
rdesc[41] == 0x00 && rdesc[59] == 0x26 &&
rdesc[60] == 0xf9 && rdesc[61] == 0x00) {
hid_info(hdev, "fixing up Petalynx Maxter Remote report descriptor\n");
rdesc[60] = 0xfa;
rdesc[40] = 0xfa;
}
return rdesc;
}
#define pl_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
EV_KEY, (c))
static int pl_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_LOGIVENDOR) {
switch (usage->hid & HID_USAGE) {
case 0x05a: pl_map_key_clear(KEY_TEXT); break;
case 0x05b: pl_map_key_clear(KEY_RED); break;
case 0x05c: pl_map_key_clear(KEY_GREEN); break;
case 0x05d: pl_map_key_clear(KEY_YELLOW); break;
case 0x05e: pl_map_key_clear(KEY_BLUE); break;
default:
return 0;
}
return 1;
}
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
switch (usage->hid & HID_USAGE) {
case 0x0f6: pl_map_key_clear(KEY_NEXT); break;
case 0x0fa: pl_map_key_clear(KEY_BACK); break;
default:
return 0;
}
return 1;
}
return 0;
}
static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
hdev->quirks |= HID_QUIRK_NOGET;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
return 0;
err_free:
return ret;
}
static const struct hid_device_id pl_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
{ }
};
MODULE_DEVICE_TABLE(hid, pl_devices);
static struct hid_driver pl_driver = {
.name = "petalynx",
.id_table = pl_devices,
.report_fixup = pl_report_fixup,
.input_mapping = pl_input_mapping,
.probe = pl_probe,
};
module_hid_driver(pl_driver);
MODULE_LICENSE("GPL");

309
drivers/hid/hid-picolcd.h Executable file
View File

@ -0,0 +1,309 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#define PICOLCD_NAME "PicoLCD (graphic)"
/* Report numbers */
#define REPORT_ERROR_CODE 0x10 /* LCD: IN[16] */
#define ERR_SUCCESS 0x00
#define ERR_PARAMETER_MISSING 0x01
#define ERR_DATA_MISSING 0x02
#define ERR_BLOCK_READ_ONLY 0x03
#define ERR_BLOCK_NOT_ERASABLE 0x04
#define ERR_BLOCK_TOO_BIG 0x05
#define ERR_SECTION_OVERFLOW 0x06
#define ERR_INVALID_CMD_LEN 0x07
#define ERR_INVALID_DATA_LEN 0x08
#define REPORT_KEY_STATE 0x11 /* LCD: IN[2] */
#define REPORT_IR_DATA 0x21 /* LCD: IN[63] */
#define REPORT_EE_DATA 0x32 /* LCD: IN[63] */
#define REPORT_MEMORY 0x41 /* LCD: IN[63] */
#define REPORT_LED_STATE 0x81 /* LCD: OUT[1] */
#define REPORT_BRIGHTNESS 0x91 /* LCD: OUT[1] */
#define REPORT_CONTRAST 0x92 /* LCD: OUT[1] */
#define REPORT_RESET 0x93 /* LCD: OUT[2] */
#define REPORT_LCD_CMD 0x94 /* LCD: OUT[63] */
#define REPORT_LCD_DATA 0x95 /* LCD: OUT[63] */
#define REPORT_LCD_CMD_DATA 0x96 /* LCD: OUT[63] */
#define REPORT_EE_READ 0xa3 /* LCD: OUT[63] */
#define REPORT_EE_WRITE 0xa4 /* LCD: OUT[63] */
#define REPORT_ERASE_MEMORY 0xb2 /* LCD: OUT[2] */
#define REPORT_READ_MEMORY 0xb3 /* LCD: OUT[3] */
#define REPORT_WRITE_MEMORY 0xb4 /* LCD: OUT[63] */
#define REPORT_SPLASH_RESTART 0xc1 /* LCD: OUT[1] */
#define REPORT_EXIT_KEYBOARD 0xef /* LCD: OUT[2] */
#define REPORT_VERSION 0xf1 /* LCD: IN[2],OUT[1] Bootloader: IN[2],OUT[1] */
#define REPORT_BL_ERASE_MEMORY 0xf2 /* Bootloader: IN[36],OUT[4] */
#define REPORT_BL_READ_MEMORY 0xf3 /* Bootloader: IN[36],OUT[4] */
#define REPORT_BL_WRITE_MEMORY 0xf4 /* Bootloader: IN[36],OUT[36] */
#define REPORT_DEVID 0xf5 /* LCD: IN[5], OUT[1] Bootloader: IN[5],OUT[1] */
#define REPORT_SPLASH_SIZE 0xf6 /* LCD: IN[4], OUT[1] */
#define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */
#define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */
/* Description of in-progress IO operation, used for operations
* that trigger response from device */
struct picolcd_pending {
struct hid_report *out_report;
struct hid_report *in_report;
struct completion ready;
int raw_size;
u8 raw_data[64];
};
#define PICOLCD_KEYS 17
/* Per device data structure */
struct picolcd_data {
struct hid_device *hdev;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_reset;
struct dentry *debug_eeprom;
struct dentry *debug_flash;
struct mutex mutex_flash;
int addr_sz;
#endif
u8 version[2];
unsigned short opmode_delay;
/* input stuff */
u8 pressed_keys[2];
struct input_dev *input_keys;
#ifdef CONFIG_HID_PICOLCD_CIR
struct rc_dev *rc_dev;
#endif
unsigned short keycode[PICOLCD_KEYS];
#ifdef CONFIG_HID_PICOLCD_FB
/* Framebuffer stuff */
struct fb_info *fb_info;
#endif /* CONFIG_HID_PICOLCD_FB */
#ifdef CONFIG_HID_PICOLCD_LCD
struct lcd_device *lcd;
u8 lcd_contrast;
#endif /* CONFIG_HID_PICOLCD_LCD */
#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
struct backlight_device *backlight;
u8 lcd_brightness;
u8 lcd_power;
#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */
#ifdef CONFIG_HID_PICOLCD_LEDS
/* LED stuff */
u8 led_state;
struct led_classdev *led[8];
#endif /* CONFIG_HID_PICOLCD_LEDS */
/* Housekeeping stuff */
spinlock_t lock;
struct mutex mutex;
struct picolcd_pending *pending;
int status;
#define PICOLCD_BOOTLOADER 1
#define PICOLCD_FAILED 2
#define PICOLCD_CIR_SHUN 4
};
#ifdef CONFIG_HID_PICOLCD_FB
struct picolcd_fb_data {
/* Framebuffer stuff */
spinlock_t lock;
struct picolcd_data *picolcd;
u8 update_rate;
u8 bpp;
u8 force;
u8 ready;
u8 *vbitmap; /* local copy of what was sent to PicoLCD */
u8 *bitmap; /* framebuffer */
};
#endif /* CONFIG_HID_PICOLCD_FB */
/* Find a given report */
#define picolcd_in_report(id, dev) picolcd_report(id, dev, HID_INPUT_REPORT)
#define picolcd_out_report(id, dev) picolcd_report(id, dev, HID_OUTPUT_REPORT)
struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir);
#ifdef CONFIG_DEBUG_FS
void picolcd_debug_out_report(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report);
#define hid_hw_request(a, b, c) \
do { \
picolcd_debug_out_report(hid_get_drvdata(a), a, b); \
hid_hw_request(a, b, c); \
} while (0)
void picolcd_debug_raw_event(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report,
u8 *raw_data, int size);
void picolcd_init_devfs(struct picolcd_data *data,
struct hid_report *eeprom_r, struct hid_report *eeprom_w,
struct hid_report *flash_r, struct hid_report *flash_w,
struct hid_report *reset);
void picolcd_exit_devfs(struct picolcd_data *data);
#else
static inline void picolcd_debug_out_report(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report)
{
}
static inline void picolcd_debug_raw_event(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report,
u8 *raw_data, int size)
{
}
static inline void picolcd_init_devfs(struct picolcd_data *data,
struct hid_report *eeprom_r, struct hid_report *eeprom_w,
struct hid_report *flash_r, struct hid_report *flash_w,
struct hid_report *reset)
{
}
static inline void picolcd_exit_devfs(struct picolcd_data *data)
{
}
#endif /* CONFIG_DEBUG_FS */
#ifdef CONFIG_HID_PICOLCD_FB
int picolcd_fb_reset(struct picolcd_data *data, int clear);
int picolcd_init_framebuffer(struct picolcd_data *data);
void picolcd_exit_framebuffer(struct picolcd_data *data);
void picolcd_fb_refresh(struct picolcd_data *data);
#define picolcd_fbinfo(d) ((d)->fb_info)
#else
static inline int picolcd_fb_reset(struct picolcd_data *data, int clear)
{
return 0;
}
static inline int picolcd_init_framebuffer(struct picolcd_data *data)
{
return 0;
}
static inline void picolcd_exit_framebuffer(struct picolcd_data *data)
{
}
static inline void picolcd_fb_refresh(struct picolcd_data *data)
{
}
#define picolcd_fbinfo(d) NULL
#endif /* CONFIG_HID_PICOLCD_FB */
#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
int picolcd_init_backlight(struct picolcd_data *data,
struct hid_report *report);
void picolcd_exit_backlight(struct picolcd_data *data);
int picolcd_resume_backlight(struct picolcd_data *data);
void picolcd_suspend_backlight(struct picolcd_data *data);
#else
static inline int picolcd_init_backlight(struct picolcd_data *data,
struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_backlight(struct picolcd_data *data)
{
}
static inline int picolcd_resume_backlight(struct picolcd_data *data)
{
return 0;
}
static inline void picolcd_suspend_backlight(struct picolcd_data *data)
{
}
#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */
#ifdef CONFIG_HID_PICOLCD_LCD
int picolcd_init_lcd(struct picolcd_data *data,
struct hid_report *report);
void picolcd_exit_lcd(struct picolcd_data *data);
int picolcd_resume_lcd(struct picolcd_data *data);
#else
static inline int picolcd_init_lcd(struct picolcd_data *data,
struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_lcd(struct picolcd_data *data)
{
}
static inline int picolcd_resume_lcd(struct picolcd_data *data)
{
return 0;
}
#endif /* CONFIG_HID_PICOLCD_LCD */
#ifdef CONFIG_HID_PICOLCD_LEDS
int picolcd_init_leds(struct picolcd_data *data,
struct hid_report *report);
void picolcd_exit_leds(struct picolcd_data *data);
void picolcd_leds_set(struct picolcd_data *data);
#else
static inline int picolcd_init_leds(struct picolcd_data *data,
struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_leds(struct picolcd_data *data)
{
}
static inline void picolcd_leds_set(struct picolcd_data *data)
{
}
#endif /* CONFIG_HID_PICOLCD_LEDS */
#ifdef CONFIG_HID_PICOLCD_CIR
int picolcd_raw_cir(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size);
int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report);
void picolcd_exit_cir(struct picolcd_data *data);
#else
static inline int picolcd_raw_cir(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size)
{
return 1;
}
static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_cir(struct picolcd_data *data)
{
}
#endif /* CONFIG_HID_PICOLCD_CIR */
int picolcd_reset(struct hid_device *hdev);
struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
int report_id, const u8 *raw_data, int size);

View File

@ -0,0 +1,119 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include "hid-picolcd.h"
static int picolcd_get_brightness(struct backlight_device *bdev)
{
struct picolcd_data *data = bl_get_data(bdev);
return data->lcd_brightness;
}
static int picolcd_set_brightness(struct backlight_device *bdev)
{
struct picolcd_data *data = bl_get_data(bdev);
struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev);
unsigned long flags;
if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
return -ENODEV;
data->lcd_brightness = bdev->props.brightness & 0x0ff;
data->lcd_power = bdev->props.power;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0);
if (!(data->status & PICOLCD_FAILED))
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb)
{
return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev));
}
static const struct backlight_ops picolcd_blops = {
.update_status = picolcd_set_brightness,
.get_brightness = picolcd_get_brightness,
.check_fb = picolcd_check_bl_fb,
};
int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report)
{
struct device *dev = &data->hdev->dev;
struct backlight_device *bdev;
struct backlight_properties props;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
report->field[0]->report_size != 8) {
dev_err(dev, "unsupported BRIGHTNESS report");
return -EINVAL;
}
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = 0xff;
bdev = backlight_device_register(dev_name(dev), dev, data,
&picolcd_blops, &props);
if (IS_ERR(bdev)) {
dev_err(dev, "failed to register backlight\n");
return PTR_ERR(bdev);
}
bdev->props.brightness = 0xff;
data->lcd_brightness = 0xff;
data->backlight = bdev;
picolcd_set_brightness(bdev);
return 0;
}
void picolcd_exit_backlight(struct picolcd_data *data)
{
struct backlight_device *bdev = data->backlight;
data->backlight = NULL;
backlight_device_unregister(bdev);
}
int picolcd_resume_backlight(struct picolcd_data *data)
{
if (!data->backlight)
return 0;
return picolcd_set_brightness(data->backlight);
}
#ifdef CONFIG_PM
void picolcd_suspend_backlight(struct picolcd_data *data)
{
int bl_power = data->lcd_power;
if (!data->backlight)
return;
data->backlight->props.power = FB_BLANK_POWERDOWN;
picolcd_set_brightness(data->backlight);
data->lcd_power = data->backlight->props.power = bl_power;
}
#endif /* CONFIG_PM */

150
drivers/hid/hid-picolcd_cir.c Executable file
View File

@ -0,0 +1,150 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/hid-debug.h>
#include <linux/input.h>
#include "hid-ids.h"
#include <linux/fb.h>
#include <linux/vmalloc.h>
#include <linux/backlight.h>
#include <linux/lcd.h>
#include <linux/leds.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/completion.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <media/rc-core.h>
#include "hid-picolcd.h"
int picolcd_raw_cir(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size)
{
unsigned long flags;
int i, w, sz;
DEFINE_IR_RAW_EVENT(rawir);
/* ignore if rc_dev is NULL or status is shunned */
spin_lock_irqsave(&data->lock, flags);
if (!data->rc_dev || (data->status & PICOLCD_CIR_SHUN)) {
spin_unlock_irqrestore(&data->lock, flags);
return 1;
}
spin_unlock_irqrestore(&data->lock, flags);
/* PicoLCD USB packets contain 16-bit intervals in network order,
* with value negated for pulse. Intervals are in microseconds.
*
* Note: some userspace LIRC code for PicoLCD says negated values
* for space - is it a matter of IR chip? (pulse for my TSOP2236)
*
* In addition, the first interval seems to be around 15000 + base
* interval for non-first report of IR data - thus the quirk below
* to get RC_CODE to understand Sony and JVC remotes I have at hand
*/
sz = size > 0 ? min((int)raw_data[0], size-1) : 0;
for (i = 0; i+1 < sz; i += 2) {
init_ir_raw_event(&rawir);
w = (raw_data[i] << 8) | (raw_data[i+1]);
rawir.pulse = !!(w & 0x8000);
rawir.duration = US_TO_NS(rawir.pulse ? (65536 - w) : w);
/* Quirk!! - see above */
if (i == 0 && rawir.duration > 15000000)
rawir.duration -= 15000000;
ir_raw_event_store(data->rc_dev, &rawir);
}
ir_raw_event_handle(data->rc_dev);
return 1;
}
static int picolcd_cir_open(struct rc_dev *dev)
{
struct picolcd_data *data = dev->priv;
unsigned long flags;
spin_lock_irqsave(&data->lock, flags);
data->status &= ~PICOLCD_CIR_SHUN;
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static void picolcd_cir_close(struct rc_dev *dev)
{
struct picolcd_data *data = dev->priv;
unsigned long flags;
spin_lock_irqsave(&data->lock, flags);
data->status |= PICOLCD_CIR_SHUN;
spin_unlock_irqrestore(&data->lock, flags);
}
/* initialize CIR input device */
int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
{
struct rc_dev *rdev;
int ret = 0;
rdev = rc_allocate_device();
if (!rdev)
return -ENOMEM;
rdev->priv = data;
rdev->driver_type = RC_DRIVER_IR_RAW;
rdev->allowed_protocols = RC_BIT_ALL;
rdev->open = picolcd_cir_open;
rdev->close = picolcd_cir_close;
rdev->input_name = data->hdev->name;
rdev->input_phys = data->hdev->phys;
rdev->input_id.bustype = data->hdev->bus;
rdev->input_id.vendor = data->hdev->vendor;
rdev->input_id.product = data->hdev->product;
rdev->input_id.version = data->hdev->version;
rdev->dev.parent = &data->hdev->dev;
rdev->driver_name = PICOLCD_NAME;
rdev->map_name = RC_MAP_RC6_MCE;
rdev->timeout = MS_TO_NS(100);
rdev->rx_resolution = US_TO_NS(1);
ret = rc_register_device(rdev);
if (ret)
goto err;
data->rc_dev = rdev;
return 0;
err:
rc_free_device(rdev);
return ret;
}
void picolcd_exit_cir(struct picolcd_data *data)
{
struct rc_dev *rdev = data->rc_dev;
data->rc_dev = NULL;
rc_unregister_device(rdev);
}

682
drivers/hid/hid-picolcd_core.c Executable file
View File

@ -0,0 +1,682 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/hid-debug.h>
#include <linux/input.h>
#include "hid-ids.h"
#include <linux/fb.h>
#include <linux/vmalloc.h>
#include <linux/completion.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include "hid-picolcd.h"
/* Input device
*
* The PicoLCD has an IR receiver header, a built-in keypad with 5 keys
* and header for 4x4 key matrix. The built-in keys are part of the matrix.
*/
static const unsigned short def_keymap[PICOLCD_KEYS] = {
KEY_RESERVED, /* none */
KEY_BACK, /* col 4 + row 1 */
KEY_HOMEPAGE, /* col 3 + row 1 */
KEY_RESERVED, /* col 2 + row 1 */
KEY_RESERVED, /* col 1 + row 1 */
KEY_SCROLLUP, /* col 4 + row 2 */
KEY_OK, /* col 3 + row 2 */
KEY_SCROLLDOWN, /* col 2 + row 2 */
KEY_RESERVED, /* col 1 + row 2 */
KEY_RESERVED, /* col 4 + row 3 */
KEY_RESERVED, /* col 3 + row 3 */
KEY_RESERVED, /* col 2 + row 3 */
KEY_RESERVED, /* col 1 + row 3 */
KEY_RESERVED, /* col 4 + row 4 */
KEY_RESERVED, /* col 3 + row 4 */
KEY_RESERVED, /* col 2 + row 4 */
KEY_RESERVED, /* col 1 + row 4 */
};
/* Find a given report */
struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir)
{
struct list_head *feature_report_list = &hdev->report_enum[dir].report_list;
struct hid_report *report = NULL;
list_for_each_entry(report, feature_report_list, list) {
if (report->id == id)
return report;
}
hid_warn(hdev, "No report with id 0x%x found\n", id);
return NULL;
}
/* Submit a report and wait for a reply from device - if device fades away
* or does not respond in time, return NULL */
struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
int report_id, const u8 *raw_data, int size)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct picolcd_pending *work;
struct hid_report *report = picolcd_out_report(report_id, hdev);
unsigned long flags;
int i, j, k;
if (!report || !data)
return NULL;
if (data->status & PICOLCD_FAILED)
return NULL;
work = kzalloc(sizeof(*work), GFP_KERNEL);
if (!work)
return NULL;
init_completion(&work->ready);
work->out_report = report;
work->in_report = NULL;
work->raw_size = 0;
mutex_lock(&data->mutex);
spin_lock_irqsave(&data->lock, flags);
for (i = k = 0; i < report->maxfield; i++)
for (j = 0; j < report->field[i]->report_count; j++) {
hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0);
k++;
}
if (data->status & PICOLCD_FAILED) {
kfree(work);
work = NULL;
} else {
data->pending = work;
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
wait_for_completion_interruptible_timeout(&work->ready, HZ*2);
spin_lock_irqsave(&data->lock, flags);
data->pending = NULL;
}
spin_unlock_irqrestore(&data->lock, flags);
mutex_unlock(&data->mutex);
return work;
}
/*
* input class device
*/
static int picolcd_raw_keypad(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size)
{
/*
* Keypad event
* First and second data bytes list currently pressed keys,
* 0x00 means no key and at most 2 keys may be pressed at same time
*/
int i, j;
/* determine newly pressed keys */
for (i = 0; i < size; i++) {
unsigned int key_code;
if (raw_data[i] == 0)
continue;
for (j = 0; j < sizeof(data->pressed_keys); j++)
if (data->pressed_keys[j] == raw_data[i])
goto key_already_down;
for (j = 0; j < sizeof(data->pressed_keys); j++)
if (data->pressed_keys[j] == 0) {
data->pressed_keys[j] = raw_data[i];
break;
}
input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]);
if (raw_data[i] < PICOLCD_KEYS)
key_code = data->keycode[raw_data[i]];
else
key_code = KEY_UNKNOWN;
if (key_code != KEY_UNKNOWN) {
dbg_hid(PICOLCD_NAME " got key press for %u:%d",
raw_data[i], key_code);
input_report_key(data->input_keys, key_code, 1);
}
input_sync(data->input_keys);
key_already_down:
continue;
}
/* determine newly released keys */
for (j = 0; j < sizeof(data->pressed_keys); j++) {
unsigned int key_code;
if (data->pressed_keys[j] == 0)
continue;
for (i = 0; i < size; i++)
if (data->pressed_keys[j] == raw_data[i])
goto key_still_down;
input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]);
if (data->pressed_keys[j] < PICOLCD_KEYS)
key_code = data->keycode[data->pressed_keys[j]];
else
key_code = KEY_UNKNOWN;
if (key_code != KEY_UNKNOWN) {
dbg_hid(PICOLCD_NAME " got key release for %u:%d",
data->pressed_keys[j], key_code);
input_report_key(data->input_keys, key_code, 0);
}
input_sync(data->input_keys);
data->pressed_keys[j] = 0;
key_still_down:
continue;
}
return 1;
}
static int picolcd_check_version(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct picolcd_pending *verinfo;
int ret = 0;
if (!data)
return -ENODEV;
verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0);
if (!verinfo) {
hid_err(hdev, "no version response from PicoLCD\n");
return -ENODEV;
}
if (verinfo->raw_size == 2) {
data->version[0] = verinfo->raw_data[1];
data->version[1] = verinfo->raw_data[0];
if (data->status & PICOLCD_BOOTLOADER) {
hid_info(hdev, "PicoLCD, bootloader version %d.%d\n",
verinfo->raw_data[1], verinfo->raw_data[0]);
} else {
hid_info(hdev, "PicoLCD, firmware version %d.%d\n",
verinfo->raw_data[1], verinfo->raw_data[0]);
}
} else {
hid_err(hdev, "confused, got unexpected version response from PicoLCD\n");
ret = -EINVAL;
}
kfree(verinfo);
return ret;
}
/*
* Reset our device and wait for answer to VERSION request
*/
int picolcd_reset(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev);
unsigned long flags;
int error;
if (!data || !report || report->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER)
data->status |= PICOLCD_BOOTLOADER;
/* perform the reset */
hid_set_field(report->field[0], 0, 1);
if (data->status & PICOLCD_FAILED) {
spin_unlock_irqrestore(&data->lock, flags);
return -ENODEV;
}
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
error = picolcd_check_version(hdev);
if (error)
return error;
picolcd_resume_lcd(data);
picolcd_resume_backlight(data);
picolcd_fb_refresh(data);
picolcd_leds_set(data);
return 0;
}
/*
* The "operation_mode" sysfs attribute
*/
static ssize_t picolcd_operation_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct picolcd_data *data = dev_get_drvdata(dev);
if (data->status & PICOLCD_BOOTLOADER)
return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n");
else
return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n");
}
static ssize_t picolcd_operation_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct picolcd_data *data = dev_get_drvdata(dev);
struct hid_report *report = NULL;
size_t cnt = count;
int timeout = data->opmode_delay;
unsigned long flags;
if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) {
if (data->status & PICOLCD_BOOTLOADER)
report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev);
buf += 3;
cnt -= 3;
} else if (cnt >= 10 && strncmp("bootloader", buf, 10) == 0) {
if (!(data->status & PICOLCD_BOOTLOADER))
report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev);
buf += 10;
cnt -= 10;
}
if (!report || report->maxfield != 1)
return -EINVAL;
while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r'))
cnt--;
if (cnt != 0)
return -EINVAL;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, timeout & 0xff);
hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff);
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
return count;
}
static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
picolcd_operation_mode_store);
/*
* The "operation_mode_delay" sysfs attribute
*/
static ssize_t picolcd_operation_mode_delay_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct picolcd_data *data = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay);
}
static ssize_t picolcd_operation_mode_delay_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct picolcd_data *data = dev_get_drvdata(dev);
unsigned u;
if (sscanf(buf, "%u", &u) != 1)
return -EINVAL;
if (u > 30000)
return -EINVAL;
else
data->opmode_delay = u;
return count;
}
static DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show,
picolcd_operation_mode_delay_store);
/*
* Handle raw report as sent by device
*/
static int picolcd_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *raw_data, int size)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
unsigned long flags;
int ret = 0;
if (!data)
return 1;
if (size > 64) {
hid_warn(hdev, "invalid size value (%d) for picolcd raw event (%d)\n",
size, report->id);
return 0;
}
if (report->id == REPORT_KEY_STATE) {
if (data->input_keys)
ret = picolcd_raw_keypad(data, report, raw_data+1, size-1);
} else if (report->id == REPORT_IR_DATA) {
ret = picolcd_raw_cir(data, report, raw_data+1, size-1);
} else {
spin_lock_irqsave(&data->lock, flags);
/*
* We let the caller of picolcd_send_and_wait() check if the
* report we got is one of the expected ones or not.
*/
if (data->pending) {
memcpy(data->pending->raw_data, raw_data+1, size-1);
data->pending->raw_size = size-1;
data->pending->in_report = report;
complete(&data->pending->ready);
}
spin_unlock_irqrestore(&data->lock, flags);
}
picolcd_debug_raw_event(data, hdev, report, raw_data, size);
return 1;
}
#ifdef CONFIG_PM
static int picolcd_suspend(struct hid_device *hdev, pm_message_t message)
{
if (PMSG_IS_AUTO(message))
return 0;
picolcd_suspend_backlight(hid_get_drvdata(hdev));
dbg_hid(PICOLCD_NAME " device ready for suspend\n");
return 0;
}
static int picolcd_resume(struct hid_device *hdev)
{
int ret;
ret = picolcd_resume_backlight(hid_get_drvdata(hdev));
if (ret)
dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret);
return 0;
}
static int picolcd_reset_resume(struct hid_device *hdev)
{
int ret;
ret = picolcd_reset(hdev);
if (ret)
dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret);
ret = picolcd_fb_reset(hid_get_drvdata(hdev), 0);
if (ret)
dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", ret);
ret = picolcd_resume_lcd(hid_get_drvdata(hdev));
if (ret)
dbg_hid(PICOLCD_NAME " restoring lcd failed: %d\n", ret);
ret = picolcd_resume_backlight(hid_get_drvdata(hdev));
if (ret)
dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret);
picolcd_leds_set(hid_get_drvdata(hdev));
return 0;
}
#endif
/* initialize keypad input device */
static int picolcd_init_keys(struct picolcd_data *data,
struct hid_report *report)
{
struct hid_device *hdev = data->hdev;
struct input_dev *idev;
int error, i;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 2 ||
report->field[0]->report_size != 8) {
hid_err(hdev, "unsupported KEY_STATE report\n");
return -EINVAL;
}
idev = input_allocate_device();
if (idev == NULL) {
hid_err(hdev, "failed to allocate input device\n");
return -ENOMEM;
}
input_set_drvdata(idev, hdev);
memcpy(data->keycode, def_keymap, sizeof(def_keymap));
idev->name = hdev->name;
idev->phys = hdev->phys;
idev->uniq = hdev->uniq;
idev->id.bustype = hdev->bus;
idev->id.vendor = hdev->vendor;
idev->id.product = hdev->product;
idev->id.version = hdev->version;
idev->dev.parent = &hdev->dev;
idev->keycode = &data->keycode;
idev->keycodemax = PICOLCD_KEYS;
idev->keycodesize = sizeof(data->keycode[0]);
input_set_capability(idev, EV_MSC, MSC_SCAN);
set_bit(EV_REP, idev->evbit);
for (i = 0; i < PICOLCD_KEYS; i++)
input_set_capability(idev, EV_KEY, data->keycode[i]);
error = input_register_device(idev);
if (error) {
hid_err(hdev, "error registering the input device\n");
input_free_device(idev);
return error;
}
data->input_keys = idev;
return 0;
}
static void picolcd_exit_keys(struct picolcd_data *data)
{
struct input_dev *idev = data->input_keys;
data->input_keys = NULL;
if (idev)
input_unregister_device(idev);
}
static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
{
int error;
/* Setup keypad input device */
error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev));
if (error)
goto err;
/* Setup CIR input device */
error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev));
if (error)
goto err;
/* Set up the framebuffer device */
error = picolcd_init_framebuffer(data);
if (error)
goto err;
/* Setup lcd class device */
error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev));
if (error)
goto err;
/* Setup backlight class device */
error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev));
if (error)
goto err;
/* Setup the LED class devices */
error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev));
if (error)
goto err;
picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
picolcd_out_report(REPORT_EE_WRITE, hdev),
picolcd_out_report(REPORT_READ_MEMORY, hdev),
picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
picolcd_out_report(REPORT_RESET, hdev));
return 0;
err:
picolcd_exit_leds(data);
picolcd_exit_backlight(data);
picolcd_exit_lcd(data);
picolcd_exit_framebuffer(data);
picolcd_exit_cir(data);
picolcd_exit_keys(data);
return error;
}
static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
{
picolcd_init_devfs(data, NULL, NULL,
picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
return 0;
}
static int picolcd_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
struct picolcd_data *data;
int error = -ENOMEM;
dbg_hid(PICOLCD_NAME " hardware probe...\n");
/*
* Let's allocate the picolcd data structure, set some reasonable
* defaults, and associate it with the device
*/
data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL);
if (data == NULL) {
hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n");
error = -ENOMEM;
goto err_no_cleanup;
}
spin_lock_init(&data->lock);
mutex_init(&data->mutex);
data->hdev = hdev;
data->opmode_delay = 5000;
if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER)
data->status |= PICOLCD_BOOTLOADER;
hid_set_drvdata(hdev, data);
/* Parse the device reports and start it up */
error = hid_parse(hdev);
if (error) {
hid_err(hdev, "device report parse failed\n");
goto err_cleanup_data;
}
error = hid_hw_start(hdev, 0);
if (error) {
hid_err(hdev, "hardware start failed\n");
goto err_cleanup_data;
}
error = hid_hw_open(hdev);
if (error) {
hid_err(hdev, "failed to open input interrupt pipe for key and IR events\n");
goto err_cleanup_hid_hw;
}
error = device_create_file(&hdev->dev, &dev_attr_operation_mode_delay);
if (error) {
hid_err(hdev, "failed to create sysfs attributes\n");
goto err_cleanup_hid_ll;
}
error = device_create_file(&hdev->dev, &dev_attr_operation_mode);
if (error) {
hid_err(hdev, "failed to create sysfs attributes\n");
goto err_cleanup_sysfs1;
}
if (data->status & PICOLCD_BOOTLOADER)
error = picolcd_probe_bootloader(hdev, data);
else
error = picolcd_probe_lcd(hdev, data);
if (error)
goto err_cleanup_sysfs2;
dbg_hid(PICOLCD_NAME " activated and initialized\n");
return 0;
err_cleanup_sysfs2:
device_remove_file(&hdev->dev, &dev_attr_operation_mode);
err_cleanup_sysfs1:
device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay);
err_cleanup_hid_ll:
hid_hw_close(hdev);
err_cleanup_hid_hw:
hid_hw_stop(hdev);
err_cleanup_data:
kfree(data);
err_no_cleanup:
hid_set_drvdata(hdev, NULL);
return error;
}
static void picolcd_remove(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
unsigned long flags;
dbg_hid(PICOLCD_NAME " hardware remove...\n");
spin_lock_irqsave(&data->lock, flags);
data->status |= PICOLCD_FAILED;
spin_unlock_irqrestore(&data->lock, flags);
picolcd_exit_devfs(data);
device_remove_file(&hdev->dev, &dev_attr_operation_mode);
device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay);
hid_hw_close(hdev);
hid_hw_stop(hdev);
/* Shortcut potential pending reply that will never arrive */
spin_lock_irqsave(&data->lock, flags);
if (data->pending)
complete(&data->pending->ready);
spin_unlock_irqrestore(&data->lock, flags);
/* Cleanup LED */
picolcd_exit_leds(data);
/* Clean up the framebuffer */
picolcd_exit_backlight(data);
picolcd_exit_lcd(data);
picolcd_exit_framebuffer(data);
/* Cleanup input */
picolcd_exit_cir(data);
picolcd_exit_keys(data);
hid_set_drvdata(hdev, NULL);
mutex_destroy(&data->mutex);
/* Finally, clean up the picolcd data itself */
kfree(data);
}
static const struct hid_device_id picolcd_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
{ }
};
MODULE_DEVICE_TABLE(hid, picolcd_devices);
static struct hid_driver picolcd_driver = {
.name = "hid-picolcd",
.id_table = picolcd_devices,
.probe = picolcd_probe,
.remove = picolcd_remove,
.raw_event = picolcd_raw_event,
#ifdef CONFIG_PM
.suspend = picolcd_suspend,
.resume = picolcd_resume,
.reset_resume = picolcd_reset_resume,
#endif
};
module_hid_driver(picolcd_driver);
MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver");
MODULE_LICENSE("GPL v2");

895
drivers/hid/hid-picolcd_debugfs.c Executable file
View File

@ -0,0 +1,895 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/hid-debug.h>
#include <linux/fb.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include "hid-picolcd.h"
static int picolcd_debug_reset_show(struct seq_file *f, void *p)
{
if (picolcd_fbinfo((struct picolcd_data *)f->private))
seq_printf(f, "all fb\n");
else
seq_printf(f, "all\n");
return 0;
}
static int picolcd_debug_reset_open(struct inode *inode, struct file *f)
{
return single_open(f, picolcd_debug_reset_show, inode->i_private);
}
static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
char buf[32];
size_t cnt = min(count, sizeof(buf)-1);
if (copy_from_user(buf, user_buf, cnt))
return -EFAULT;
while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
cnt--;
buf[cnt] = '\0';
if (strcmp(buf, "all") == 0) {
picolcd_reset(data->hdev);
picolcd_fb_reset(data, 1);
} else if (strcmp(buf, "fb") == 0) {
picolcd_fb_reset(data, 1);
} else {
return -EINVAL;
}
return count;
}
static const struct file_operations picolcd_debug_reset_fops = {
.owner = THIS_MODULE,
.open = picolcd_debug_reset_open,
.read = seq_read,
.llseek = seq_lseek,
.write = picolcd_debug_reset_write,
.release = single_release,
};
/*
* The "eeprom" file
*/
static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
struct picolcd_pending *resp;
u8 raw_data[3];
ssize_t ret = -EIO;
if (s == 0)
return -EINVAL;
if (*off > 0x0ff)
return 0;
/* prepare buffer with info about what we want to read (addr & len) */
raw_data[0] = *off & 0xff;
raw_data[1] = (*off >> 8) & 0xff;
raw_data[2] = s < 20 ? s : 20;
if (*off + raw_data[2] > 0xff)
raw_data[2] = 0x100 - *off;
resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
sizeof(raw_data));
if (!resp)
return -EIO;
if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
/* successful read :) */
ret = resp->raw_data[2];
if (ret > s)
ret = s;
if (copy_to_user(u, resp->raw_data+3, ret))
ret = -EFAULT;
else
*off += ret;
} /* anything else is some kind of IO error */
kfree(resp);
return ret;
}
static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
struct picolcd_pending *resp;
ssize_t ret = -EIO;
u8 raw_data[23];
if (s == 0)
return -EINVAL;
if (*off > 0x0ff)
return -ENOSPC;
memset(raw_data, 0, sizeof(raw_data));
raw_data[0] = *off & 0xff;
raw_data[1] = (*off >> 8) & 0xff;
raw_data[2] = min_t(size_t, 20, s);
if (*off + raw_data[2] > 0xff)
raw_data[2] = 0x100 - *off;
if (copy_from_user(raw_data+3, u, min((u8)20, raw_data[2])))
return -EFAULT;
resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
sizeof(raw_data));
if (!resp)
return -EIO;
if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
/* check if written data matches */
if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) {
*off += raw_data[2];
ret = raw_data[2];
}
}
kfree(resp);
return ret;
}
/*
* Notes:
* - read/write happens in chunks of at most 20 bytes, it's up to userspace
* to loop in order to get more data.
* - on write errors on otherwise correct write request the bytes
* that should have been written are in undefined state.
*/
static const struct file_operations picolcd_debug_eeprom_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = picolcd_debug_eeprom_read,
.write = picolcd_debug_eeprom_write,
.llseek = generic_file_llseek,
};
/*
* The "flash" file
*/
/* record a flash address to buf (bounds check to be done by caller) */
static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
{
buf[0] = off & 0xff;
buf[1] = (off >> 8) & 0xff;
if (data->addr_sz == 3)
buf[2] = (off >> 16) & 0xff;
return data->addr_sz == 2 ? 2 : 3;
}
/* read a given size of data (bounds check to be done by caller) */
static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
char __user *u, size_t s, loff_t *off)
{
struct picolcd_pending *resp;
u8 raw_data[4];
ssize_t ret = 0;
int len_off, err = -EIO;
while (s > 0) {
err = -EIO;
len_off = _picolcd_flash_setaddr(data, raw_data, *off);
raw_data[len_off] = s > 32 ? 32 : s;
resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
if (!resp || !resp->in_report)
goto skip;
if (resp->in_report->id == REPORT_MEMORY ||
resp->in_report->id == REPORT_BL_READ_MEMORY) {
if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
goto skip;
if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
err = -EFAULT;
goto skip;
}
*off += raw_data[len_off];
s -= raw_data[len_off];
ret += raw_data[len_off];
err = 0;
}
skip:
kfree(resp);
if (err)
return ret > 0 ? ret : err;
}
return ret;
}
static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
if (s == 0)
return -EINVAL;
if (*off > 0x05fff)
return 0;
if (*off + s > 0x05fff)
s = 0x06000 - *off;
if (data->status & PICOLCD_BOOTLOADER)
return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
else
return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
}
/* erase block aligned to 64bytes boundary */
static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
loff_t *off)
{
struct picolcd_pending *resp;
u8 raw_data[3];
int len_off;
ssize_t ret = -EIO;
if (*off & 0x3f)
return -EINVAL;
len_off = _picolcd_flash_setaddr(data, raw_data, *off);
resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
if (!resp || !resp->in_report)
goto skip;
if (resp->in_report->id == REPORT_MEMORY ||
resp->in_report->id == REPORT_BL_ERASE_MEMORY) {
if (memcmp(raw_data, resp->raw_data, len_off) != 0)
goto skip;
ret = 0;
}
skip:
kfree(resp);
return ret;
}
/* write a given size of data (bounds check to be done by caller) */
static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
const char __user *u, size_t s, loff_t *off)
{
struct picolcd_pending *resp;
u8 raw_data[36];
ssize_t ret = 0;
int len_off, err = -EIO;
while (s > 0) {
err = -EIO;
len_off = _picolcd_flash_setaddr(data, raw_data, *off);
raw_data[len_off] = s > 32 ? 32 : s;
if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
err = -EFAULT;
break;
}
resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
len_off+1+raw_data[len_off]);
if (!resp || !resp->in_report)
goto skip;
if (resp->in_report->id == REPORT_MEMORY ||
resp->in_report->id == REPORT_BL_WRITE_MEMORY) {
if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
goto skip;
*off += raw_data[len_off];
s -= raw_data[len_off];
ret += raw_data[len_off];
err = 0;
}
skip:
kfree(resp);
if (err)
break;
}
return ret > 0 ? ret : err;
}
static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
ssize_t err, ret = 0;
int report_erase, report_write;
if (s == 0)
return -EINVAL;
if (*off > 0x5fff)
return -ENOSPC;
if (s & 0x3f)
return -EINVAL;
if (*off & 0x3f)
return -EINVAL;
if (data->status & PICOLCD_BOOTLOADER) {
report_erase = REPORT_BL_ERASE_MEMORY;
report_write = REPORT_BL_WRITE_MEMORY;
} else {
report_erase = REPORT_ERASE_MEMORY;
report_write = REPORT_WRITE_MEMORY;
}
mutex_lock(&data->mutex_flash);
while (s > 0) {
err = _picolcd_flash_erase64(data, report_erase, off);
if (err)
break;
err = _picolcd_flash_write(data, report_write, u, 64, off);
if (err < 0)
break;
ret += err;
*off += err;
s -= err;
if (err != 64)
break;
}
mutex_unlock(&data->mutex_flash);
return ret > 0 ? ret : err;
}
/*
* Notes:
* - concurrent writing is prevented by mutex and all writes must be
* n*64 bytes and 64-byte aligned, each write being preceded by an
* ERASE which erases a 64byte block.
* If less than requested was written or an error is returned for an
* otherwise correct write request the next 64-byte block which should
* have been written is in undefined state (mostly: original, erased,
* (half-)written with write error)
* - reading can happen without special restriction
*/
static const struct file_operations picolcd_debug_flash_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = picolcd_debug_flash_read,
.write = picolcd_debug_flash_write,
.llseek = generic_file_llseek,
};
/*
* Helper code for HID report level dumping/debugging
*/
static const char * const error_codes[] = {
"success", "parameter missing", "data_missing", "block readonly",
"block not erasable", "block too big", "section overflow",
"invalid command length", "invalid data length",
};
static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data,
const size_t data_len)
{
int i, j;
for (i = j = 0; i < data_len && j + 4 < dst_sz; i++) {
dst[j++] = hex_asc[(data[i] >> 4) & 0x0f];
dst[j++] = hex_asc[data[i] & 0x0f];
dst[j++] = ' ';
}
dst[j] = '\0';
if (j > 0)
dst[j-1] = '\n';
if (i < data_len && j > 2)
dst[j-2] = dst[j-3] = '.';
}
void picolcd_debug_out_report(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report)
{
u8 *raw_data;
int raw_size = (report->size >> 3) + 1;
char *buff;
#define BUFF_SZ 256
/* Avoid unnecessary overhead if debugfs is disabled */
if (list_empty(&hdev->debug_list))
return;
buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
if (!buff)
return;
raw_data = hid_alloc_report_buf(report, GFP_ATOMIC);
if (!raw_data) {
kfree(buff);
return;
}
snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ",
report->id, raw_size);
hid_debug_event(hdev, buff);
raw_data[0] = report->id;
hid_output_report(report, raw_data);
dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size);
hid_debug_event(hdev, buff);
switch (report->id) {
case REPORT_LED_STATE:
/* 1 data byte with GPO state */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LED_STATE", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tGPO state: 0x%02x\n", raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_BRIGHTNESS:
/* 1 data byte with brightness */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_BRIGHTNESS", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tBrightness: 0x%02x\n", raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_CONTRAST:
/* 1 data byte with contrast */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_CONTRAST", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tContrast: 0x%02x\n", raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_RESET:
/* 2 data bytes with reset duration in ms */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_RESET", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tDuration: 0x%02x%02x (%dms)\n",
raw_data[2], raw_data[1], raw_data[2] << 8 | raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_LCD_CMD:
/* 63 data bytes with LCD commands */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LCD_CMD", report->id, raw_size-1);
hid_debug_event(hdev, buff);
/* TODO: format decoding */
break;
case REPORT_LCD_DATA:
/* 63 data bytes with LCD data */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LCD_CMD", report->id, raw_size-1);
/* TODO: format decoding */
hid_debug_event(hdev, buff);
break;
case REPORT_LCD_CMD_DATA:
/* 63 data bytes with LCD commands and data */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LCD_CMD", report->id, raw_size-1);
/* TODO: format decoding */
hid_debug_event(hdev, buff);
break;
case REPORT_EE_READ:
/* 3 data bytes with read area description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_EE_READ", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
break;
case REPORT_EE_WRITE:
/* 3+1..20 data bytes with write area description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_EE_WRITE", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[3] + 4 <= raw_size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_ERASE_MEMORY:
case REPORT_BL_ERASE_MEMORY:
/* 3 data bytes with pointer inside erase block */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_ERASE_MEMORY", report->id, raw_size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
break;
case 3:
snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_READ_MEMORY:
case REPORT_BL_READ_MEMORY:
/* 4 data bytes with read area description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_READ_MEMORY", report->id, raw_size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
break;
case 3:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_WRITE_MEMORY:
case REPORT_BL_WRITE_MEMORY:
/* 4+1..32 data bytes with write adrea description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_WRITE_MEMORY", report->id, raw_size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[3] + 4 <= raw_size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
case 3:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
hid_debug_event(hdev, buff);
if (raw_data[4] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[4] + 5 <= raw_size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_SPLASH_RESTART:
/* TODO */
break;
case REPORT_EXIT_KEYBOARD:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_EXIT_KEYBOARD", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n",
raw_data[1] | (raw_data[2] << 8),
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_VERSION:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_VERSION", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_DEVID:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_DEVID", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_SPLASH_SIZE:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_SPLASH_SIZE", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_HOOK_VERSION:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_HOOK_VERSION", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_EXIT_FLASHER:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_VERSION", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n",
raw_data[1] | (raw_data[2] << 8),
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
default:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"<unknown>", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
}
wake_up_interruptible(&hdev->debug_wait);
kfree(raw_data);
kfree(buff);
}
void picolcd_debug_raw_event(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report,
u8 *raw_data, int size)
{
char *buff;
#define BUFF_SZ 256
/* Avoid unnecessary overhead if debugfs is disabled */
if (list_empty(&hdev->debug_list))
return;
buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
if (!buff)
return;
switch (report->id) {
case REPORT_ERROR_CODE:
/* 2 data bytes with affected report and error code */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_ERROR_CODE", report->id, size-1);
hid_debug_event(hdev, buff);
if (raw_data[2] < ARRAY_SIZE(error_codes))
snprintf(buff, BUFF_SZ, "\tError code 0x%02x (%s) in reply to report 0x%02x\n",
raw_data[2], error_codes[raw_data[2]], raw_data[1]);
else
snprintf(buff, BUFF_SZ, "\tError code 0x%02x in reply to report 0x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_KEY_STATE:
/* 2 data bytes with key state */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_KEY_STATE", report->id, size-1);
hid_debug_event(hdev, buff);
if (raw_data[1] == 0)
snprintf(buff, BUFF_SZ, "\tNo key pressed\n");
else if (raw_data[2] == 0)
snprintf(buff, BUFF_SZ, "\tOne key pressed: 0x%02x (%d)\n",
raw_data[1], raw_data[1]);
else
snprintf(buff, BUFF_SZ, "\tTwo keys pressed: 0x%02x (%d), 0x%02x (%d)\n",
raw_data[1], raw_data[1], raw_data[2], raw_data[2]);
hid_debug_event(hdev, buff);
break;
case REPORT_IR_DATA:
/* Up to 20 byes of IR scancode data */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_IR_DATA", report->id, size-1);
hid_debug_event(hdev, buff);
if (raw_data[1] == 0) {
snprintf(buff, BUFF_SZ, "\tUnexpectedly 0 data length\n");
hid_debug_event(hdev, buff);
} else if (raw_data[1] + 1 <= size) {
snprintf(buff, BUFF_SZ, "\tData length: %d\n\tIR Data: ",
raw_data[1]);
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]);
hid_debug_event(hdev, buff);
} else {
snprintf(buff, BUFF_SZ, "\tOverflowing data length: %d\n",
raw_data[1]-1);
hid_debug_event(hdev, buff);
}
break;
case REPORT_EE_DATA:
/* Data buffer in response to REPORT_EE_READ or REPORT_EE_WRITE */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_EE_DATA", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
hid_debug_event(hdev, buff);
} else if (raw_data[3] + 4 <= size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
hid_debug_event(hdev, buff);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
hid_debug_event(hdev, buff);
}
break;
case REPORT_MEMORY:
/* Data buffer in response to REPORT_READ_MEMORY or REPORT_WRTIE_MEMORY */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[3] + 4 <= size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
case 3:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
hid_debug_event(hdev, buff);
if (raw_data[4] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[4] + 5 <= size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_VERSION:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_VERSION", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_BL_ERASE_MEMORY:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_BL_ERASE_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
/* TODO */
break;
case REPORT_BL_READ_MEMORY:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_BL_READ_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
/* TODO */
break;
case REPORT_BL_WRITE_MEMORY:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_BL_WRITE_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
/* TODO */
break;
case REPORT_DEVID:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_DEVID", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tSerial: 0x%02x%02x%02x%02x\n",
raw_data[1], raw_data[2], raw_data[3], raw_data[4]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tType: 0x%02x\n",
raw_data[5]);
hid_debug_event(hdev, buff);
break;
case REPORT_SPLASH_SIZE:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_SPLASH_SIZE", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tTotal splash space: %d\n",
(raw_data[2] << 8) | raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tUsed splash space: %d\n",
(raw_data[4] << 8) | raw_data[3]);
hid_debug_event(hdev, buff);
break;
case REPORT_HOOK_VERSION:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_HOOK_VERSION", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n",
raw_data[1], raw_data[2]);
hid_debug_event(hdev, buff);
break;
default:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"<unknown>", report->id, size-1);
hid_debug_event(hdev, buff);
break;
}
wake_up_interruptible(&hdev->debug_wait);
kfree(buff);
}
void picolcd_init_devfs(struct picolcd_data *data,
struct hid_report *eeprom_r, struct hid_report *eeprom_w,
struct hid_report *flash_r, struct hid_report *flash_w,
struct hid_report *reset)
{
struct hid_device *hdev = data->hdev;
mutex_init(&data->mutex_flash);
/* reset */
if (reset)
data->debug_reset = debugfs_create_file("reset", 0600,
hdev->debug_dir, data, &picolcd_debug_reset_fops);
/* eeprom */
if (eeprom_r || eeprom_w)
data->debug_eeprom = debugfs_create_file("eeprom",
(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
/* flash */
if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
data->addr_sz = flash_r->field[0]->report_count - 1;
else
data->addr_sz = -1;
if (data->addr_sz == 2 || data->addr_sz == 3) {
data->debug_flash = debugfs_create_file("flash",
(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
hdev->debug_dir, data, &picolcd_debug_flash_fops);
} else if (flash_r || flash_w)
hid_warn(hdev, "Unexpected FLASH access reports, please submit rdesc for review\n");
}
void picolcd_exit_devfs(struct picolcd_data *data)
{
struct dentry *dent;
dent = data->debug_reset;
data->debug_reset = NULL;
debugfs_remove(dent);
dent = data->debug_eeprom;
data->debug_eeprom = NULL;
debugfs_remove(dent);
dent = data->debug_flash;
data->debug_flash = NULL;
debugfs_remove(dent);
mutex_destroy(&data->mutex_flash);
}

617
drivers/hid/hid-picolcd_fb.c Executable file
View File

@ -0,0 +1,617 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/vmalloc.h>
#include <linux/fb.h>
#include <linux/module.h>
#include "hid-picolcd.h"
/* Framebuffer
*
* The PicoLCD use a Topway LCD module of 256x64 pixel
* This display area is tiled over 4 controllers with 8 tiles
* each. Each tile has 8x64 pixel, each data byte representing
* a 1-bit wide vertical line of the tile.
*
* The display can be updated at a tile granularity.
*
* Chip 1 Chip 2 Chip 3 Chip 4
* +----------------+----------------+----------------+----------------+
* | Tile 1 | Tile 1 | Tile 1 | Tile 1 |
* +----------------+----------------+----------------+----------------+
* | Tile 2 | Tile 2 | Tile 2 | Tile 2 |
* +----------------+----------------+----------------+----------------+
* ...
* +----------------+----------------+----------------+----------------+
* | Tile 8 | Tile 8 | Tile 8 | Tile 8 |
* +----------------+----------------+----------------+----------------+
*/
#define PICOLCDFB_NAME "picolcdfb"
#define PICOLCDFB_WIDTH (256)
#define PICOLCDFB_HEIGHT (64)
#define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8)
#define PICOLCDFB_UPDATE_RATE_LIMIT 10
#define PICOLCDFB_UPDATE_RATE_DEFAULT 2
/* Framebuffer visual structures */
static const struct fb_fix_screeninfo picolcdfb_fix = {
.id = PICOLCDFB_NAME,
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_MONO01,
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
.line_length = PICOLCDFB_WIDTH / 8,
.accel = FB_ACCEL_NONE,
};
static const struct fb_var_screeninfo picolcdfb_var = {
.xres = PICOLCDFB_WIDTH,
.yres = PICOLCDFB_HEIGHT,
.xres_virtual = PICOLCDFB_WIDTH,
.yres_virtual = PICOLCDFB_HEIGHT,
.width = 103,
.height = 26,
.bits_per_pixel = 1,
.grayscale = 1,
.red = {
.offset = 0,
.length = 1,
.msb_right = 0,
},
.green = {
.offset = 0,
.length = 1,
.msb_right = 0,
},
.blue = {
.offset = 0,
.length = 1,
.msb_right = 0,
},
.transp = {
.offset = 0,
.length = 0,
.msb_right = 0,
},
};
/* Send a given tile to PicoLCD */
static int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap,
int chip, int tile)
{
struct hid_report *report1, *report2;
unsigned long flags;
u8 *tdata;
int i;
report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev);
if (!report1 || report1->maxfield != 1)
return -ENODEV;
report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev);
if (!report2 || report2->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
if ((data->status & PICOLCD_FAILED)) {
spin_unlock_irqrestore(&data->lock, flags);
return -ENODEV;
}
hid_set_field(report1->field[0], 0, chip << 2);
hid_set_field(report1->field[0], 1, 0x02);
hid_set_field(report1->field[0], 2, 0x00);
hid_set_field(report1->field[0], 3, 0x00);
hid_set_field(report1->field[0], 4, 0xb8 | tile);
hid_set_field(report1->field[0], 5, 0x00);
hid_set_field(report1->field[0], 6, 0x00);
hid_set_field(report1->field[0], 7, 0x40);
hid_set_field(report1->field[0], 8, 0x00);
hid_set_field(report1->field[0], 9, 0x00);
hid_set_field(report1->field[0], 10, 32);
hid_set_field(report2->field[0], 0, (chip << 2) | 0x01);
hid_set_field(report2->field[0], 1, 0x00);
hid_set_field(report2->field[0], 2, 0x00);
hid_set_field(report2->field[0], 3, 32);
tdata = vbitmap + (tile * 4 + chip) * 64;
for (i = 0; i < 64; i++)
if (i < 32)
hid_set_field(report1->field[0], 11 + i, tdata[i]);
else
hid_set_field(report2->field[0], 4 + i - 32, tdata[i]);
hid_hw_request(data->hdev, report1, HID_REQ_SET_REPORT);
hid_hw_request(data->hdev, report2, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
/* Translate a single tile*/
static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
int chip, int tile)
{
int i, b, changed = 0;
u8 tdata[64];
u8 *vdata = vbitmap + (tile * 4 + chip) * 64;
if (bpp == 1) {
for (b = 7; b >= 0; b--) {
const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
for (i = 0; i < 64; i++) {
tdata[i] <<= 1;
tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01;
}
}
} else if (bpp == 8) {
for (b = 7; b >= 0; b--) {
const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8;
for (i = 0; i < 64; i++) {
tdata[i] <<= 1;
tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00;
}
}
} else {
/* Oops, we should never get here! */
WARN_ON(1);
return 0;
}
for (i = 0; i < 64; i++)
if (tdata[i] != vdata[i]) {
changed = 1;
vdata[i] = tdata[i];
}
return changed;
}
void picolcd_fb_refresh(struct picolcd_data *data)
{
if (data->fb_info)
schedule_delayed_work(&data->fb_info->deferred_work, 0);
}
/* Reconfigure LCD display */
int picolcd_fb_reset(struct picolcd_data *data, int clear)
{
struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
struct picolcd_fb_data *fbdata = data->fb_info->par;
int i, j;
unsigned long flags;
static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 };
if (!report || report->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
for (i = 0; i < 4; i++) {
for (j = 0; j < report->field[0]->maxusage; j++)
if (j == 0)
hid_set_field(report->field[0], j, i << 2);
else if (j < sizeof(mapcmd))
hid_set_field(report->field[0], j, mapcmd[j]);
else
hid_set_field(report->field[0], j, 0);
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
}
spin_unlock_irqrestore(&data->lock, flags);
if (clear) {
memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE);
memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp);
}
fbdata->force = 1;
/* schedule first output of framebuffer */
if (fbdata->ready)
schedule_delayed_work(&data->fb_info->deferred_work, 0);
else
fbdata->ready = 1;
return 0;
}
/* Update fb_vbitmap from the screen_base and send changed tiles to device */
static void picolcd_fb_update(struct fb_info *info)
{
int chip, tile, n;
unsigned long flags;
struct picolcd_fb_data *fbdata = info->par;
struct picolcd_data *data;
mutex_lock(&info->lock);
spin_lock_irqsave(&fbdata->lock, flags);
if (!fbdata->ready && fbdata->picolcd)
picolcd_fb_reset(fbdata->picolcd, 0);
spin_unlock_irqrestore(&fbdata->lock, flags);
/*
* Translate the framebuffer into the format needed by the PicoLCD.
* See display layout above.
* Do this one tile after the other and push those tiles that changed.
*
* Wait for our IO to complete as otherwise we might flood the queue!
*/
n = 0;
for (chip = 0; chip < 4; chip++)
for (tile = 0; tile < 8; tile++) {
if (!fbdata->force && !picolcd_fb_update_tile(
fbdata->vbitmap, fbdata->bitmap,
fbdata->bpp, chip, tile))
continue;
n += 2;
if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
spin_lock_irqsave(&fbdata->lock, flags);
data = fbdata->picolcd;
spin_unlock_irqrestore(&fbdata->lock, flags);
mutex_unlock(&info->lock);
if (!data)
return;
hid_hw_wait(data->hdev);
mutex_lock(&info->lock);
n = 0;
}
spin_lock_irqsave(&fbdata->lock, flags);
data = fbdata->picolcd;
spin_unlock_irqrestore(&fbdata->lock, flags);
if (!data || picolcd_fb_send_tile(data,
fbdata->vbitmap, chip, tile))
goto out;
}
fbdata->force = false;
if (n) {
spin_lock_irqsave(&fbdata->lock, flags);
data = fbdata->picolcd;
spin_unlock_irqrestore(&fbdata->lock, flags);
mutex_unlock(&info->lock);
if (data)
hid_hw_wait(data->hdev);
return;
}
out:
mutex_unlock(&info->lock);
}
/* Stub to call the system default and update the image on the picoLCD */
static void picolcd_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
if (!info->par)
return;
sys_fillrect(info, rect);
schedule_delayed_work(&info->deferred_work, 0);
}
/* Stub to call the system default and update the image on the picoLCD */
static void picolcd_fb_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
if (!info->par)
return;
sys_copyarea(info, area);
schedule_delayed_work(&info->deferred_work, 0);
}
/* Stub to call the system default and update the image on the picoLCD */
static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
{
if (!info->par)
return;
sys_imageblit(info, image);
schedule_delayed_work(&info->deferred_work, 0);
}
/*
* this is the slow path from userspace. they can seek and write to
* the fb. it's inefficient to do anything less than a full screen draw
*/
static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
ssize_t ret;
if (!info->par)
return -ENODEV;
ret = fb_sys_write(info, buf, count, ppos);
if (ret >= 0)
schedule_delayed_work(&info->deferred_work, 0);
return ret;
}
static int picolcd_fb_blank(int blank, struct fb_info *info)
{
/* We let fb notification do this for us via lcd/backlight device */
return 0;
}
static void picolcd_fb_destroy(struct fb_info *info)
{
struct picolcd_fb_data *fbdata = info->par;
/* make sure no work is deferred */
fb_deferred_io_cleanup(info);
/* No thridparty should ever unregister our framebuffer! */
WARN_ON(fbdata->picolcd != NULL);
vfree((u8 *)info->fix.smem_start);
framebuffer_release(info);
}
static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
__u32 bpp = var->bits_per_pixel;
__u32 activate = var->activate;
/* only allow 1/8 bit depth (8-bit is grayscale) */
*var = picolcdfb_var;
var->activate = activate;
if (bpp >= 8) {
var->bits_per_pixel = 8;
var->red.length = 8;
var->green.length = 8;
var->blue.length = 8;
} else {
var->bits_per_pixel = 1;
var->red.length = 1;
var->green.length = 1;
var->blue.length = 1;
}
return 0;
}
static int picolcd_set_par(struct fb_info *info)
{
struct picolcd_fb_data *fbdata = info->par;
u8 *tmp_fb, *o_fb;
if (info->var.bits_per_pixel == fbdata->bpp)
return 0;
/* switch between 1/8 bit depths */
if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
return -EINVAL;
o_fb = fbdata->bitmap;
tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL);
if (!tmp_fb)
return -ENOMEM;
/* translate FB content to new bits-per-pixel */
if (info->var.bits_per_pixel == 1) {
int i, b;
for (i = 0; i < PICOLCDFB_SIZE; i++) {
u8 p = 0;
for (b = 0; b < 8; b++) {
p <<= 1;
p |= o_fb[i*8+b] ? 0x01 : 0x00;
}
tmp_fb[i] = p;
}
memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
info->fix.visual = FB_VISUAL_MONO01;
info->fix.line_length = PICOLCDFB_WIDTH / 8;
} else {
int i;
memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
info->fix.visual = FB_VISUAL_DIRECTCOLOR;
info->fix.line_length = PICOLCDFB_WIDTH;
}
kfree(tmp_fb);
fbdata->bpp = info->var.bits_per_pixel;
return 0;
}
/* Note this can't be const because of struct fb_info definition */
static struct fb_ops picolcdfb_ops = {
.owner = THIS_MODULE,
.fb_destroy = picolcd_fb_destroy,
.fb_read = fb_sys_read,
.fb_write = picolcd_fb_write,
.fb_blank = picolcd_fb_blank,
.fb_fillrect = picolcd_fb_fillrect,
.fb_copyarea = picolcd_fb_copyarea,
.fb_imageblit = picolcd_fb_imageblit,
.fb_check_var = picolcd_fb_check_var,
.fb_set_par = picolcd_set_par,
};
/* Callback from deferred IO workqueue */
static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist)
{
picolcd_fb_update(info);
}
static const struct fb_deferred_io picolcd_fb_defio = {
.delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
.deferred_io = picolcd_fb_deferred_io,
};
/*
* The "fb_update_rate" sysfs attribute
*/
static ssize_t picolcd_fb_update_rate_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct picolcd_data *data = dev_get_drvdata(dev);
struct picolcd_fb_data *fbdata = data->fb_info->par;
unsigned i, fb_update_rate = fbdata->update_rate;
size_t ret = 0;
for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
if (ret >= PAGE_SIZE)
break;
else if (i == fb_update_rate)
ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
else
ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
if (ret > 0)
buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
return ret;
}
static ssize_t picolcd_fb_update_rate_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct picolcd_data *data = dev_get_drvdata(dev);
struct picolcd_fb_data *fbdata = data->fb_info->par;
int i;
unsigned u;
if (count < 1 || count > 10)
return -EINVAL;
i = sscanf(buf, "%u", &u);
if (i != 1)
return -EINVAL;
if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
return -ERANGE;
else if (u == 0)
u = PICOLCDFB_UPDATE_RATE_DEFAULT;
fbdata->update_rate = u;
data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
return count;
}
static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show,
picolcd_fb_update_rate_store);
/* initialize Framebuffer device */
int picolcd_init_framebuffer(struct picolcd_data *data)
{
struct device *dev = &data->hdev->dev;
struct fb_info *info = NULL;
struct picolcd_fb_data *fbdata = NULL;
int i, error = -ENOMEM;
u32 *palette;
/* The extra memory is:
* - 256*u32 for pseudo_palette
* - struct fb_deferred_io
*/
info = framebuffer_alloc(256 * sizeof(u32) +
sizeof(struct fb_deferred_io) +
sizeof(struct picolcd_fb_data) +
PICOLCDFB_SIZE, dev);
if (info == NULL) {
dev_err(dev, "failed to allocate a framebuffer\n");
goto err_nomem;
}
info->fbdefio = info->par;
*info->fbdefio = picolcd_fb_defio;
info->par += sizeof(struct fb_deferred_io);
palette = info->par;
info->par += 256 * sizeof(u32);
for (i = 0; i < 256; i++)
palette[i] = i > 0 && i < 16 ? 0xff : 0;
info->pseudo_palette = palette;
info->fbops = &picolcdfb_ops;
info->var = picolcdfb_var;
info->fix = picolcdfb_fix;
info->fix.smem_len = PICOLCDFB_SIZE*8;
info->flags = FBINFO_FLAG_DEFAULT;
fbdata = info->par;
spin_lock_init(&fbdata->lock);
fbdata->picolcd = data;
fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
fbdata->bpp = picolcdfb_var.bits_per_pixel;
fbdata->force = 1;
fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
fbdata->bitmap = vmalloc(PICOLCDFB_SIZE*8);
if (fbdata->bitmap == NULL) {
dev_err(dev, "can't get a free page for framebuffer\n");
goto err_nomem;
}
info->screen_base = (char __force __iomem *)fbdata->bitmap;
info->fix.smem_start = (unsigned long)fbdata->bitmap;
memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
data->fb_info = info;
error = picolcd_fb_reset(data, 1);
if (error) {
dev_err(dev, "failed to configure display\n");
goto err_cleanup;
}
error = device_create_file(dev, &dev_attr_fb_update_rate);
if (error) {
dev_err(dev, "failed to create sysfs attributes\n");
goto err_cleanup;
}
fb_deferred_io_init(info);
error = register_framebuffer(info);
if (error) {
dev_err(dev, "failed to register framebuffer\n");
goto err_sysfs;
}
return 0;
err_sysfs:
device_remove_file(dev, &dev_attr_fb_update_rate);
fb_deferred_io_cleanup(info);
err_cleanup:
data->fb_info = NULL;
err_nomem:
if (fbdata)
vfree(fbdata->bitmap);
framebuffer_release(info);
return error;
}
void picolcd_exit_framebuffer(struct picolcd_data *data)
{
struct fb_info *info = data->fb_info;
struct picolcd_fb_data *fbdata;
unsigned long flags;
if (!info)
return;
device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
fbdata = info->par;
/* disconnect framebuffer from HID dev */
spin_lock_irqsave(&fbdata->lock, flags);
fbdata->picolcd = NULL;
spin_unlock_irqrestore(&fbdata->lock, flags);
/* make sure there is no running update - thus that fbdata->picolcd
* once obtained under lock is guaranteed not to get free() under
* the feet of the deferred work */
flush_delayed_work(&info->deferred_work);
data->fb_info = NULL;
unregister_framebuffer(info);
}

104
drivers/hid/hid-picolcd_lcd.c Executable file
View File

@ -0,0 +1,104 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/fb.h>
#include <linux/lcd.h>
#include "hid-picolcd.h"
/*
* lcd class device
*/
static int picolcd_get_contrast(struct lcd_device *ldev)
{
struct picolcd_data *data = lcd_get_data(ldev);
return data->lcd_contrast;
}
static int picolcd_set_contrast(struct lcd_device *ldev, int contrast)
{
struct picolcd_data *data = lcd_get_data(ldev);
struct hid_report *report = picolcd_out_report(REPORT_CONTRAST, data->hdev);
unsigned long flags;
if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
return -ENODEV;
data->lcd_contrast = contrast & 0x0ff;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, data->lcd_contrast);
if (!(data->status & PICOLCD_FAILED))
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb)
{
return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev));
}
static struct lcd_ops picolcd_lcdops = {
.get_contrast = picolcd_get_contrast,
.set_contrast = picolcd_set_contrast,
.check_fb = picolcd_check_lcd_fb,
};
int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report)
{
struct device *dev = &data->hdev->dev;
struct lcd_device *ldev;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
report->field[0]->report_size != 8) {
dev_err(dev, "unsupported CONTRAST report");
return -EINVAL;
}
ldev = lcd_device_register(dev_name(dev), dev, data, &picolcd_lcdops);
if (IS_ERR(ldev)) {
dev_err(dev, "failed to register LCD\n");
return PTR_ERR(ldev);
}
ldev->props.max_contrast = 0x0ff;
data->lcd_contrast = 0xe5;
data->lcd = ldev;
picolcd_set_contrast(ldev, 0xe5);
return 0;
}
void picolcd_exit_lcd(struct picolcd_data *data)
{
struct lcd_device *ldev = data->lcd;
data->lcd = NULL;
lcd_device_unregister(ldev);
}
int picolcd_resume_lcd(struct picolcd_data *data)
{
if (!data->lcd)
return 0;
return picolcd_set_contrast(data->lcd, data->lcd_contrast);
}

173
drivers/hid/hid-picolcd_leds.c Executable file
View File

@ -0,0 +1,173 @@
/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/hid-debug.h>
#include <linux/input.h>
#include "hid-ids.h"
#include <linux/fb.h>
#include <linux/vmalloc.h>
#include <linux/backlight.h>
#include <linux/lcd.h>
#include <linux/leds.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/completion.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include "hid-picolcd.h"
void picolcd_leds_set(struct picolcd_data *data)
{
struct hid_report *report;
unsigned long flags;
if (!data->led[0])
return;
report = picolcd_out_report(REPORT_LED_STATE, data->hdev);
if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
return;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, data->led_state);
if (!(data->status & PICOLCD_FAILED))
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
}
static void picolcd_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev;
struct hid_device *hdev;
struct picolcd_data *data;
int i, state = 0;
dev = led_cdev->dev->parent;
hdev = container_of(dev, struct hid_device, dev);
data = hid_get_drvdata(hdev);
if (!data)
return;
for (i = 0; i < 8; i++) {
if (led_cdev != data->led[i])
continue;
state = (data->led_state >> i) & 1;
if (value == LED_OFF && state) {
data->led_state &= ~(1 << i);
picolcd_leds_set(data);
} else if (value != LED_OFF && !state) {
data->led_state |= 1 << i;
picolcd_leds_set(data);
}
break;
}
}
static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev)
{
struct device *dev;
struct hid_device *hdev;
struct picolcd_data *data;
int i, value = 0;
dev = led_cdev->dev->parent;
hdev = container_of(dev, struct hid_device, dev);
data = hid_get_drvdata(hdev);
for (i = 0; i < 8; i++)
if (led_cdev == data->led[i]) {
value = (data->led_state >> i) & 1;
break;
}
return value ? LED_FULL : LED_OFF;
}
int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report)
{
struct device *dev = &data->hdev->dev;
struct led_classdev *led;
size_t name_sz = strlen(dev_name(dev)) + 8;
char *name;
int i, ret = 0;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
report->field[0]->report_size != 8) {
dev_err(dev, "unsupported LED_STATE report");
return -EINVAL;
}
for (i = 0; i < 8; i++) {
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
dev_err(dev, "can't allocate memory for LED %d\n", i);
ret = -ENOMEM;
goto err;
}
name = (void *)(&led[1]);
snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i);
led->name = name;
led->brightness = 0;
led->max_brightness = 1;
led->brightness_get = picolcd_led_get_brightness;
led->brightness_set = picolcd_led_set_brightness;
data->led[i] = led;
ret = led_classdev_register(dev, data->led[i]);
if (ret) {
data->led[i] = NULL;
kfree(led);
dev_err(dev, "can't register LED %d\n", i);
goto err;
}
}
return 0;
err:
for (i = 0; i < 8; i++)
if (data->led[i]) {
led = data->led[i];
data->led[i] = NULL;
led_classdev_unregister(led);
kfree(led);
}
return ret;
}
void picolcd_exit_leds(struct picolcd_data *data)
{
struct led_classdev *led;
int i;
for (i = 0; i < 8; i++) {
led = data->led[i];
data->led[i] = NULL;
if (!led)
continue;
led_classdev_unregister(led);
kfree(led);
}
}

234
drivers/hid/hid-pl.c Executable file
View File

@ -0,0 +1,234 @@
/*
* Force feedback support for PantherLord/GreenAsia based devices
*
* The devices are distributed under various names and the same USB device ID
* can be used in both adapters and actual game controllers.
*
* 0810:0001 "Twin USB Joystick"
* - tested with PantherLord USB/PS2 2in1 Adapter
* - contains two reports, one for each port (HID_QUIRK_MULTI_INPUT)
*
* 0e8f:0003 "GreenAsia Inc. USB Joystick "
* - tested with König Gaming gamepad
*
* 0e8f:0003 "GASIA USB Gamepad"
* - another version of the König gamepad
*
* 0f30:0111 "Saitek Color Rumble Pad"
*
* Copyright (c) 2007, 2009 Anssi Hannula <anssi.hannula@gmail.com>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* #define DEBUG */
#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg)
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/hid.h>
#include "hid-ids.h"
#ifdef CONFIG_PANTHERLORD_FF
struct plff_device {
struct hid_report *report;
s32 maxval;
s32 *strong;
s32 *weak;
};
static int hid_plff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct plff_device *plff = data;
int left, right;
left = effect->u.rumble.strong_magnitude;
right = effect->u.rumble.weak_magnitude;
debug("called with 0x%04x 0x%04x", left, right);
left = left * plff->maxval / 0xffff;
right = right * plff->maxval / 0xffff;
*plff->strong = left;
*plff->weak = right;
debug("running with 0x%02x 0x%02x", left, right);
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
return 0;
}
static int plff_init(struct hid_device *hid)
{
struct plff_device *plff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
struct input_dev *dev;
int error;
s32 maxval;
s32 *strong;
s32 *weak;
/* The device contains one output report per physical device, all
containing 1 field, which contains 4 ff00.0002 usages and 4 16bit
absolute values.
The input reports also contain a field which contains
8 ff00.0001 usages and 8 boolean values. Their meaning is
currently unknown.
A version of the 0e8f:0003 exists that has all the values in
separate fields and misses the extra input field, thus resembling
Zeroplus (hid-zpff) devices.
*/
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
list_for_each_entry(hidinput, &hid->inputs, list) {
report_ptr = report_ptr->next;
if (report_ptr == report_list) {
hid_err(hid, "required output report is missing\n");
return -ENODEV;
}
report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
maxval = 0x7f;
if (report->field[0]->report_count >= 4) {
report->field[0]->value[0] = 0x00;
report->field[0]->value[1] = 0x00;
strong = &report->field[0]->value[2];
weak = &report->field[0]->value[3];
debug("detected single-field device");
} else if (report->field[0]->maxusage == 1 &&
report->field[0]->usage[0].hid ==
(HID_UP_LED | 0x43) &&
report->maxfield >= 4 &&
report->field[0]->report_count >= 1 &&
report->field[1]->report_count >= 1 &&
report->field[2]->report_count >= 1 &&
report->field[3]->report_count >= 1) {
report->field[0]->value[0] = 0x00;
report->field[1]->value[0] = 0x00;
strong = &report->field[2]->value[0];
weak = &report->field[3]->value[0];
if (hid->vendor == USB_VENDOR_ID_JESS2)
maxval = 0xff;
debug("detected 4-field device");
} else {
hid_err(hid, "not enough fields or values\n");
return -ENODEV;
}
plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL);
if (!plff)
return -ENOMEM;
dev = hidinput->input;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, plff, hid_plff_play);
if (error) {
kfree(plff);
return error;
}
plff->report = report;
plff->strong = strong;
plff->weak = weak;
plff->maxval = maxval;
*strong = 0x00;
*weak = 0x00;
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
}
hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
return 0;
}
#else
static inline int plff_init(struct hid_device *hid)
{
return 0;
}
#endif
static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
if (id->driver_data)
hdev->quirks |= HID_QUIRK_MULTI_INPUT;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
plff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id pl_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
.driver_data = 1 }, /* Twin USB Joystick */
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR),
.driver_data = 1 }, /* Twin USB Joystick */
{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003), },
{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD), },
{ }
};
MODULE_DEVICE_TABLE(hid, pl_devices);
static struct hid_driver pl_driver = {
.name = "pantherlord",
.id_table = pl_devices,
.probe = pl_probe,
};
module_hid_driver(pl_driver);
MODULE_LICENSE("GPL");

175
drivers/hid/hid-plantronics.c Executable file
View File

@ -0,0 +1,175 @@
/*
* Plantronics USB HID Driver
*
* Copyright (c) 2014 JD Cole <jd.cole@plantronics.com>
* Copyright (c) 2015-2018 Terry Junge <terry.junge@plantronics.com>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include "hid-ids.h"
#include <linux/hid.h>
#include <linux/module.h>
#define PLT_HID_1_0_PAGE 0xffa00000
#define PLT_HID_2_0_PAGE 0xffa20000
#define PLT_BASIC_TELEPHONY 0x0003
#define PLT_BASIC_EXCEPTION 0x0005
#define PLT_VOL_UP 0x00b1
#define PLT_VOL_DOWN 0x00b2
#define PLT1_VOL_UP (PLT_HID_1_0_PAGE | PLT_VOL_UP)
#define PLT1_VOL_DOWN (PLT_HID_1_0_PAGE | PLT_VOL_DOWN)
#define PLT2_VOL_UP (PLT_HID_2_0_PAGE | PLT_VOL_UP)
#define PLT2_VOL_DOWN (PLT_HID_2_0_PAGE | PLT_VOL_DOWN)
#define PLT_DA60 0xda60
#define PLT_BT300_MIN 0x0413
#define PLT_BT300_MAX 0x0418
#define PLT_ALLOW_CONSUMER (field->application == HID_CP_CONSUMERCONTROL && \
(usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER)
static int plantronics_input_mapping(struct hid_device *hdev,
struct hid_input *hi,
struct hid_field *field,
struct hid_usage *usage,
unsigned long **bit, int *max)
{
unsigned short mapped_key;
unsigned long plt_type = (unsigned long)hid_get_drvdata(hdev);
/* special case for PTT products */
if (field->application == HID_GD_JOYSTICK)
goto defaulted;
/* handle volume up/down mapping */
/* non-standard types or multi-HID interfaces - plt_type is PID */
if (!(plt_type & HID_USAGE_PAGE)) {
switch (plt_type) {
case PLT_DA60:
if (PLT_ALLOW_CONSUMER)
goto defaulted;
goto ignored;
default:
if (PLT_ALLOW_CONSUMER)
goto defaulted;
}
}
/* handle standard types - plt_type is 0xffa0uuuu or 0xffa2uuuu */
/* 'basic telephony compliant' - allow default consumer page map */
else if ((plt_type & HID_USAGE) >= PLT_BASIC_TELEPHONY &&
(plt_type & HID_USAGE) != PLT_BASIC_EXCEPTION) {
if (PLT_ALLOW_CONSUMER)
goto defaulted;
}
/* not 'basic telephony' - apply legacy mapping */
/* only map if the field is in the device's primary vendor page */
else if (!((field->application ^ plt_type) & HID_USAGE_PAGE)) {
switch (usage->hid) {
case PLT1_VOL_UP:
case PLT2_VOL_UP:
mapped_key = KEY_VOLUMEUP;
goto mapped;
case PLT1_VOL_DOWN:
case PLT2_VOL_DOWN:
mapped_key = KEY_VOLUMEDOWN;
goto mapped;
}
}
/*
* Future mapping of call control or other usages,
* if and when keys are defined would go here
* otherwise, ignore everything else that was not mapped
*/
ignored:
return -1;
defaulted:
hid_dbg(hdev, "usage: %08x (appl: %08x) - defaulted\n",
usage->hid, field->application);
return 0;
mapped:
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, mapped_key);
hid_dbg(hdev, "usage: %08x (appl: %08x) - mapped to key %d\n",
usage->hid, field->application, mapped_key);
return 1;
}
static unsigned long plantronics_device_type(struct hid_device *hdev)
{
unsigned i, col_page;
unsigned long plt_type = hdev->product;
/* multi-HID interfaces? - plt_type is PID */
if (plt_type >= PLT_BT300_MIN && plt_type <= PLT_BT300_MAX)
goto exit;
/* determine primary vendor page */
for (i = 0; i < hdev->maxcollection; i++) {
col_page = hdev->collection[i].usage & HID_USAGE_PAGE;
if (col_page == PLT_HID_2_0_PAGE) {
plt_type = hdev->collection[i].usage;
break;
}
if (col_page == PLT_HID_1_0_PAGE)
plt_type = hdev->collection[i].usage;
}
exit:
hid_dbg(hdev, "plt_type decoded as: %08lx\n", plt_type);
return plt_type;
}
static int plantronics_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
hid_set_drvdata(hdev, (void *)plantronics_device_type(hdev));
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
HID_CONNECT_HIDINPUT_FORCE | HID_CONNECT_HIDDEV_FORCE);
if (ret)
hid_err(hdev, "hw start failed\n");
err:
return ret;
}
static const struct hid_device_id plantronics_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
{ }
};
MODULE_DEVICE_TABLE(hid, plantronics_devices);
static struct hid_driver plantronics_driver = {
.name = "plantronics",
.id_table = plantronics_devices,
.input_mapping = plantronics_input_mapping,
.probe = plantronics_probe,
};
module_hid_driver(plantronics_driver);
MODULE_AUTHOR("JD Cole <jd.cole@plantronics.com>");
MODULE_AUTHOR("Terry Junge <terry.junge@plantronics.com>");
MODULE_DESCRIPTION("Plantronics USB HID Driver");
MODULE_LICENSE("GPL");

81
drivers/hid/hid-primax.c Executable file
View File

@ -0,0 +1,81 @@
/*
* HID driver for primax and similar keyboards with in-band modifiers
*
* Copyright 2011 Google Inc. All Rights Reserved
*
* Author:
* Terry Lambert <tlambert@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static int px_raw_event(struct hid_device *hid, struct hid_report *report,
u8 *data, int size)
{
int idx = size;
switch (report->id) {
case 0: /* keyboard input */
/*
* Convert in-band modifier key values into out of band
* modifier bits and pull the key strokes from the report.
* Thus a report data set which looked like:
*
* [00][00][E0][30][00][00][00][00]
* (no modifier bits + "Left Shift" key + "1" key)
*
* Would be converted to:
*
* [01][00][00][30][00][00][00][00]
* (Left Shift modifier bit + "1" key)
*
* As long as it's in the size range, the upper level
* drivers don't particularly care if there are in-band
* 0-valued keys, so they don't stop parsing.
*/
while (--idx > 1) {
if (data[idx] < 0xE0 || data[idx] > 0xE7)
continue;
data[0] |= (1 << (data[idx] - 0xE0));
data[idx] = 0;
}
hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0);
return 1;
default: /* unknown report */
/* Unknown report type; pass upstream */
hid_info(hid, "unknown report type %d\n", report->id);
break;
}
return 0;
}
static const struct hid_device_id px_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
{ }
};
MODULE_DEVICE_TABLE(hid, px_devices);
static struct hid_driver px_driver = {
.name = "primax",
.id_table = px_devices,
.raw_event = px_raw_event,
};
module_hid_driver(px_driver);
MODULE_AUTHOR("Terry Lambert <tlambert@google.com>");
MODULE_LICENSE("GPL");

891
drivers/hid/hid-prodikeys.c Executable file
View File

@ -0,0 +1,891 @@
/*
* HID driver for the Prodikeys PC-MIDI Keyboard
* providing midi & extra multimedia keys functionality
*
* Copyright (c) 2009 Don Prince <dhprince.devel@yahoo.co.uk>
*
* Controls for Octave Shift Up/Down, Channel, and
* Sustain Duration available via sysfs.
*
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/mutex.h>
#include <linux/hid.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include "hid-ids.h"
#define pk_debug(format, arg...) \
pr_debug("hid-prodikeys: " format "\n" , ## arg)
#define pk_error(format, arg...) \
pr_err("hid-prodikeys: " format "\n" , ## arg)
struct pcmidi_snd;
struct pk_device {
unsigned long quirks;
struct hid_device *hdev;
struct pcmidi_snd *pm; /* pcmidi device context */
};
struct pcmidi_sustain {
unsigned long in_use;
struct pcmidi_snd *pm;
struct timer_list timer;
unsigned char status;
unsigned char note;
unsigned char velocity;
};
#define PCMIDI_SUSTAINED_MAX 32
struct pcmidi_snd {
struct pk_device *pk;
unsigned short ifnum;
struct hid_report *pcmidi_report6;
struct input_dev *input_ep82;
unsigned short midi_mode;
unsigned short midi_sustain_mode;
unsigned short midi_sustain;
unsigned short midi_channel;
short midi_octave;
struct pcmidi_sustain sustained_notes[PCMIDI_SUSTAINED_MAX];
unsigned short fn_state;
unsigned short last_key[24];
spinlock_t rawmidi_in_lock;
struct snd_card *card;
struct snd_rawmidi *rwmidi;
struct snd_rawmidi_substream *in_substream;
struct snd_rawmidi_substream *out_substream;
unsigned long in_triggered;
unsigned long out_active;
};
#define PK_QUIRK_NOGET 0x00010000
#define PCMIDI_MIDDLE_C 60
#define PCMIDI_CHANNEL_MIN 0
#define PCMIDI_CHANNEL_MAX 15
#define PCMIDI_OCTAVE_MIN (-2)
#define PCMIDI_OCTAVE_MAX 2
#define PCMIDI_SUSTAIN_MIN 0
#define PCMIDI_SUSTAIN_MAX 5000
static const char shortname[] = "PC-MIDI";
static const char longname[] = "Prodikeys PC-MIDI Keyboard";
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
module_param_array(id, charp, NULL, 0444);
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the PC-MIDI virtual audio driver");
MODULE_PARM_DESC(id, "ID string for the PC-MIDI virtual audio driver");
MODULE_PARM_DESC(enable, "Enable for the PC-MIDI virtual audio driver");
/* Output routine for the sysfs channel file */
static ssize_t show_channel(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = hid_get_drvdata(hdev);
dbg_hid("pcmidi sysfs read channel=%u\n", pk->pm->midi_channel);
return sprintf(buf, "%u (min:%u, max:%u)\n", pk->pm->midi_channel,
PCMIDI_CHANNEL_MIN, PCMIDI_CHANNEL_MAX);
}
/* Input routine for the sysfs channel file */
static ssize_t store_channel(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = hid_get_drvdata(hdev);
unsigned channel = 0;
if (sscanf(buf, "%u", &channel) > 0 && channel <= PCMIDI_CHANNEL_MAX) {
dbg_hid("pcmidi sysfs write channel=%u\n", channel);
pk->pm->midi_channel = channel;
return strlen(buf);
}
return -EINVAL;
}
static DEVICE_ATTR(channel, S_IRUGO | S_IWUSR | S_IWGRP , show_channel,
store_channel);
static struct device_attribute *sysfs_device_attr_channel = {
&dev_attr_channel,
};
/* Output routine for the sysfs sustain file */
static ssize_t show_sustain(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = hid_get_drvdata(hdev);
dbg_hid("pcmidi sysfs read sustain=%u\n", pk->pm->midi_sustain);
return sprintf(buf, "%u (off:%u, max:%u (ms))\n", pk->pm->midi_sustain,
PCMIDI_SUSTAIN_MIN, PCMIDI_SUSTAIN_MAX);
}
/* Input routine for the sysfs sustain file */
static ssize_t store_sustain(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = hid_get_drvdata(hdev);
unsigned sustain = 0;
if (sscanf(buf, "%u", &sustain) > 0 && sustain <= PCMIDI_SUSTAIN_MAX) {
dbg_hid("pcmidi sysfs write sustain=%u\n", sustain);
pk->pm->midi_sustain = sustain;
pk->pm->midi_sustain_mode =
(0 == sustain || !pk->pm->midi_mode) ? 0 : 1;
return strlen(buf);
}
return -EINVAL;
}
static DEVICE_ATTR(sustain, S_IRUGO | S_IWUSR | S_IWGRP, show_sustain,
store_sustain);
static struct device_attribute *sysfs_device_attr_sustain = {
&dev_attr_sustain,
};
/* Output routine for the sysfs octave file */
static ssize_t show_octave(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = hid_get_drvdata(hdev);
dbg_hid("pcmidi sysfs read octave=%d\n", pk->pm->midi_octave);
return sprintf(buf, "%d (min:%d, max:%d)\n", pk->pm->midi_octave,
PCMIDI_OCTAVE_MIN, PCMIDI_OCTAVE_MAX);
}
/* Input routine for the sysfs octave file */
static ssize_t store_octave(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = hid_get_drvdata(hdev);
int octave = 0;
if (sscanf(buf, "%d", &octave) > 0 &&
octave >= PCMIDI_OCTAVE_MIN && octave <= PCMIDI_OCTAVE_MAX) {
dbg_hid("pcmidi sysfs write octave=%d\n", octave);
pk->pm->midi_octave = octave;
return strlen(buf);
}
return -EINVAL;
}
static DEVICE_ATTR(octave, S_IRUGO | S_IWUSR | S_IWGRP, show_octave,
store_octave);
static struct device_attribute *sysfs_device_attr_octave = {
&dev_attr_octave,
};
static void pcmidi_send_note(struct pcmidi_snd *pm,
unsigned char status, unsigned char note, unsigned char velocity)
{
unsigned long flags;
unsigned char buffer[3];
buffer[0] = status;
buffer[1] = note;
buffer[2] = velocity;
spin_lock_irqsave(&pm->rawmidi_in_lock, flags);
if (!pm->in_substream)
goto drop_note;
if (!test_bit(pm->in_substream->number, &pm->in_triggered))
goto drop_note;
snd_rawmidi_receive(pm->in_substream, buffer, 3);
drop_note:
spin_unlock_irqrestore(&pm->rawmidi_in_lock, flags);
return;
}
static void pcmidi_sustained_note_release(unsigned long data)
{
struct pcmidi_sustain *pms = (struct pcmidi_sustain *)data;
pcmidi_send_note(pms->pm, pms->status, pms->note, pms->velocity);
pms->in_use = 0;
}
static void init_sustain_timers(struct pcmidi_snd *pm)
{
struct pcmidi_sustain *pms;
unsigned i;
for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
pms = &pm->sustained_notes[i];
pms->in_use = 0;
pms->pm = pm;
setup_timer(&pms->timer, pcmidi_sustained_note_release,
(unsigned long)pms);
}
}
static void stop_sustain_timers(struct pcmidi_snd *pm)
{
struct pcmidi_sustain *pms;
unsigned i;
for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
pms = &pm->sustained_notes[i];
pms->in_use = 1;
del_timer_sync(&pms->timer);
}
}
static int pcmidi_get_output_report(struct pcmidi_snd *pm)
{
struct hid_device *hdev = pm->pk->hdev;
struct hid_report *report;
list_for_each_entry(report,
&hdev->report_enum[HID_OUTPUT_REPORT].report_list, list) {
if (!(6 == report->id))
continue;
if (report->maxfield < 1) {
hid_err(hdev, "output report is empty\n");
break;
}
if (report->field[0]->report_count != 2) {
hid_err(hdev, "field count too low\n");
break;
}
pm->pcmidi_report6 = report;
return 0;
}
/* should never get here */
return -ENODEV;
}
static void pcmidi_submit_output_report(struct pcmidi_snd *pm, int state)
{
struct hid_device *hdev = pm->pk->hdev;
struct hid_report *report = pm->pcmidi_report6;
report->field[0]->value[0] = 0x01;
report->field[0]->value[1] = state;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
}
static int pcmidi_handle_report1(struct pcmidi_snd *pm, u8 *data)
{
u32 bit_mask;
bit_mask = data[1];
bit_mask = (bit_mask << 8) | data[2];
bit_mask = (bit_mask << 8) | data[3];
dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
/*KEY_MAIL or octave down*/
if (pm->midi_mode && bit_mask == 0x004000) {
/* octave down */
pm->midi_octave--;
if (pm->midi_octave < -2)
pm->midi_octave = -2;
dbg_hid("pcmidi mode: %d octave: %d\n",
pm->midi_mode, pm->midi_octave);
return 1;
}
/*KEY_WWW or sustain*/
else if (pm->midi_mode && bit_mask == 0x000004) {
/* sustain on/off*/
pm->midi_sustain_mode ^= 0x1;
return 1;
}
return 0; /* continue key processing */
}
static int pcmidi_handle_report3(struct pcmidi_snd *pm, u8 *data, int size)
{
struct pcmidi_sustain *pms;
unsigned i, j;
unsigned char status, note, velocity;
unsigned num_notes = (size-1)/2;
for (j = 0; j < num_notes; j++) {
note = data[j*2+1];
velocity = data[j*2+2];
if (note < 0x81) { /* note on */
status = 128 + 16 + pm->midi_channel; /* 1001nnnn */
note = note - 0x54 + PCMIDI_MIDDLE_C +
(pm->midi_octave * 12);
if (0 == velocity)
velocity = 1; /* force note on */
} else { /* note off */
status = 128 + pm->midi_channel; /* 1000nnnn */
note = note - 0x94 + PCMIDI_MIDDLE_C +
(pm->midi_octave*12);
if (pm->midi_sustain_mode) {
for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
pms = &pm->sustained_notes[i];
if (!pms->in_use) {
pms->status = status;
pms->note = note;
pms->velocity = velocity;
pms->in_use = 1;
mod_timer(&pms->timer,
jiffies +
msecs_to_jiffies(pm->midi_sustain));
return 1;
}
}
}
}
pcmidi_send_note(pm, status, note, velocity);
}
return 1;
}
static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data)
{
unsigned key;
u32 bit_mask;
u32 bit_index;
bit_mask = data[1];
bit_mask = (bit_mask << 8) | data[2];
bit_mask = (bit_mask << 8) | data[3];
/* break keys */
for (bit_index = 0; bit_index < 24; bit_index++) {
if (!((0x01 << bit_index) & bit_mask)) {
input_event(pm->input_ep82, EV_KEY,
pm->last_key[bit_index], 0);
pm->last_key[bit_index] = 0;
}
}
/* make keys */
for (bit_index = 0; bit_index < 24; bit_index++) {
key = 0;
switch ((0x01 << bit_index) & bit_mask) {
case 0x000010: /* Fn lock*/
pm->fn_state ^= 0x000010;
if (pm->fn_state)
pcmidi_submit_output_report(pm, 0xc5);
else
pcmidi_submit_output_report(pm, 0xc6);
continue;
case 0x020000: /* midi launcher..send a key (qwerty) or not? */
pcmidi_submit_output_report(pm, 0xc1);
pm->midi_mode ^= 0x01;
dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
continue;
case 0x100000: /* KEY_MESSENGER or octave up */
dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
if (pm->midi_mode) {
pm->midi_octave++;
if (pm->midi_octave > 2)
pm->midi_octave = 2;
dbg_hid("pcmidi mode: %d octave: %d\n",
pm->midi_mode, pm->midi_octave);
continue;
} else
key = KEY_MESSENGER;
break;
case 0x400000:
key = KEY_CALENDAR;
break;
case 0x080000:
key = KEY_ADDRESSBOOK;
break;
case 0x040000:
key = KEY_DOCUMENTS;
break;
case 0x800000:
key = KEY_WORDPROCESSOR;
break;
case 0x200000:
key = KEY_SPREADSHEET;
break;
case 0x010000:
key = KEY_COFFEE;
break;
case 0x000100:
key = KEY_HELP;
break;
case 0x000200:
key = KEY_SEND;
break;
case 0x000400:
key = KEY_REPLY;
break;
case 0x000800:
key = KEY_FORWARDMAIL;
break;
case 0x001000:
key = KEY_NEW;
break;
case 0x002000:
key = KEY_OPEN;
break;
case 0x004000:
key = KEY_CLOSE;
break;
case 0x008000:
key = KEY_SAVE;
break;
case 0x000001:
key = KEY_UNDO;
break;
case 0x000002:
key = KEY_REDO;
break;
case 0x000004:
key = KEY_SPELLCHECK;
break;
case 0x000008:
key = KEY_PRINT;
break;
}
if (key) {
input_event(pm->input_ep82, EV_KEY, key, 1);
pm->last_key[bit_index] = key;
}
}
return 1;
}
static int pcmidi_handle_report(
struct pcmidi_snd *pm, unsigned report_id, u8 *data, int size)
{
int ret = 0;
switch (report_id) {
case 0x01: /* midi keys (qwerty)*/
ret = pcmidi_handle_report1(pm, data);
break;
case 0x03: /* midi keyboard (musical)*/
ret = pcmidi_handle_report3(pm, data, size);
break;
case 0x04: /* multimedia/midi keys (qwerty)*/
ret = pcmidi_handle_report4(pm, data);
break;
}
return ret;
}
static void pcmidi_setup_extra_keys(
struct pcmidi_snd *pm, struct input_dev *input)
{
/* reassigned functionality for N/A keys
MY PICTURES => KEY_WORDPROCESSOR
MY MUSIC=> KEY_SPREADSHEET
*/
unsigned int keys[] = {
KEY_FN,
KEY_MESSENGER, KEY_CALENDAR,
KEY_ADDRESSBOOK, KEY_DOCUMENTS,
KEY_WORDPROCESSOR,
KEY_SPREADSHEET,
KEY_COFFEE,
KEY_HELP, KEY_SEND,
KEY_REPLY, KEY_FORWARDMAIL,
KEY_NEW, KEY_OPEN,
KEY_CLOSE, KEY_SAVE,
KEY_UNDO, KEY_REDO,
KEY_SPELLCHECK, KEY_PRINT,
0
};
unsigned int *pkeys = &keys[0];
unsigned short i;
if (pm->ifnum != 1) /* only set up ONCE for interace 1 */
return;
pm->input_ep82 = input;
for (i = 0; i < 24; i++)
pm->last_key[i] = 0;
while (*pkeys != 0) {
set_bit(*pkeys, pm->input_ep82->keybit);
++pkeys;
}
}
static int pcmidi_set_operational(struct pcmidi_snd *pm)
{
if (pm->ifnum != 1)
return 0; /* only set up ONCE for interace 1 */
pcmidi_get_output_report(pm);
pcmidi_submit_output_report(pm, 0xc1);
return 0;
}
static int pcmidi_snd_free(struct snd_device *dev)
{
return 0;
}
static int pcmidi_in_open(struct snd_rawmidi_substream *substream)
{
struct pcmidi_snd *pm = substream->rmidi->private_data;
dbg_hid("pcmidi in open\n");
pm->in_substream = substream;
return 0;
}
static int pcmidi_in_close(struct snd_rawmidi_substream *substream)
{
dbg_hid("pcmidi in close\n");
return 0;
}
static void pcmidi_in_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct pcmidi_snd *pm = substream->rmidi->private_data;
dbg_hid("pcmidi in trigger %d\n", up);
pm->in_triggered = up;
}
static struct snd_rawmidi_ops pcmidi_in_ops = {
.open = pcmidi_in_open,
.close = pcmidi_in_close,
.trigger = pcmidi_in_trigger
};
static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
{
static int dev;
struct snd_card *card;
struct snd_rawmidi *rwmidi;
int err;
static struct snd_device_ops ops = {
.dev_free = pcmidi_snd_free,
};
if (pm->ifnum != 1)
return 0; /* only set up midi device ONCE for interace 1 */
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
/* Setup sound card */
err = snd_card_new(&pm->pk->hdev->dev, index[dev], id[dev],
THIS_MODULE, 0, &card);
if (err < 0) {
pk_error("failed to create pc-midi sound card\n");
err = -ENOMEM;
goto fail;
}
pm->card = card;
/* Setup sound device */
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pm, &ops);
if (err < 0) {
pk_error("failed to create pc-midi sound device: error %d\n",
err);
goto fail;
}
strncpy(card->driver, shortname, sizeof(card->driver));
strncpy(card->shortname, shortname, sizeof(card->shortname));
strncpy(card->longname, longname, sizeof(card->longname));
/* Set up rawmidi */
err = snd_rawmidi_new(card, card->shortname, 0,
0, 1, &rwmidi);
if (err < 0) {
pk_error("failed to create pc-midi rawmidi device: error %d\n",
err);
goto fail;
}
pm->rwmidi = rwmidi;
strncpy(rwmidi->name, card->shortname, sizeof(rwmidi->name));
rwmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT;
rwmidi->private_data = pm;
snd_rawmidi_set_ops(rwmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&pcmidi_in_ops);
/* create sysfs variables */
err = device_create_file(&pm->pk->hdev->dev,
sysfs_device_attr_channel);
if (err < 0) {
pk_error("failed to create sysfs attribute channel: error %d\n",
err);
goto fail;
}
err = device_create_file(&pm->pk->hdev->dev,
sysfs_device_attr_sustain);
if (err < 0) {
pk_error("failed to create sysfs attribute sustain: error %d\n",
err);
goto fail_attr_sustain;
}
err = device_create_file(&pm->pk->hdev->dev,
sysfs_device_attr_octave);
if (err < 0) {
pk_error("failed to create sysfs attribute octave: error %d\n",
err);
goto fail_attr_octave;
}
spin_lock_init(&pm->rawmidi_in_lock);
init_sustain_timers(pm);
pcmidi_set_operational(pm);
/* register it */
err = snd_card_register(card);
if (err < 0) {
pk_error("failed to register pc-midi sound card: error %d\n",
err);
goto fail_register;
}
dbg_hid("pcmidi_snd_initialise finished ok\n");
return 0;
fail_register:
stop_sustain_timers(pm);
device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_octave);
fail_attr_octave:
device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_sustain);
fail_attr_sustain:
device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_channel);
fail:
if (pm->card) {
snd_card_free(pm->card);
pm->card = NULL;
}
return err;
}
static int pcmidi_snd_terminate(struct pcmidi_snd *pm)
{
if (pm->card) {
stop_sustain_timers(pm);
device_remove_file(&pm->pk->hdev->dev,
sysfs_device_attr_channel);
device_remove_file(&pm->pk->hdev->dev,
sysfs_device_attr_sustain);
device_remove_file(&pm->pk->hdev->dev,
sysfs_device_attr_octave);
snd_card_disconnect(pm->card);
snd_card_free_when_closed(pm->card);
}
return 0;
}
/*
* PC-MIDI report descriptor for report id is wrong.
*/
static __u8 *pk_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == 178 &&
rdesc[111] == 0x06 && rdesc[112] == 0x00 &&
rdesc[113] == 0xff) {
hid_info(hdev,
"fixing up pc-midi keyboard report descriptor\n");
rdesc[144] = 0x18; /* report 4: was 0x10 report count */
}
return rdesc;
}
static int pk_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct pk_device *pk = hid_get_drvdata(hdev);
struct pcmidi_snd *pm;
pm = pk->pm;
if (HID_UP_MSVENDOR == (usage->hid & HID_USAGE_PAGE) &&
1 == pm->ifnum) {
pcmidi_setup_extra_keys(pm, hi->input);
return 0;
}
return 0;
}
static int pk_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
struct pk_device *pk = hid_get_drvdata(hdev);
int ret = 0;
if (1 == pk->pm->ifnum) {
if (report->id == data[0])
switch (report->id) {
case 0x01: /* midi keys (qwerty)*/
case 0x03: /* midi keyboard (musical)*/
case 0x04: /* extra/midi keys (qwerty)*/
ret = pcmidi_handle_report(pk->pm,
report->id, data, size);
break;
}
}
return ret;
}
static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
unsigned long quirks = id->driver_data;
struct pk_device *pk;
struct pcmidi_snd *pm = NULL;
pk = kzalloc(sizeof(*pk), GFP_KERNEL);
if (pk == NULL) {
hid_err(hdev, "can't alloc descriptor\n");
return -ENOMEM;
}
pk->hdev = hdev;
pm = kzalloc(sizeof(*pm), GFP_KERNEL);
if (pm == NULL) {
hid_err(hdev, "can't alloc descriptor\n");
ret = -ENOMEM;
goto err_free_pk;
}
pm->pk = pk;
pk->pm = pm;
pm->ifnum = ifnum;
hid_set_drvdata(hdev, pk);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "hid parse failed\n");
goto err_free;
}
if (quirks & PK_QUIRK_NOGET) { /* hid_parse cleared all the quirks */
hdev->quirks |= HID_QUIRK_NOGET;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
ret = pcmidi_snd_initialise(pm);
if (ret < 0)
goto err_stop;
return 0;
err_stop:
hid_hw_stop(hdev);
err_free:
kfree(pm);
err_free_pk:
kfree(pk);
return ret;
}
static void pk_remove(struct hid_device *hdev)
{
struct pk_device *pk = hid_get_drvdata(hdev);
struct pcmidi_snd *pm;
pm = pk->pm;
if (pm) {
pcmidi_snd_terminate(pm);
kfree(pm);
}
hid_hw_stop(hdev);
kfree(pk);
}
static const struct hid_device_id pk_devices[] = {
{HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS,
USB_DEVICE_ID_PRODIKEYS_PCMIDI),
.driver_data = PK_QUIRK_NOGET},
{ }
};
MODULE_DEVICE_TABLE(hid, pk_devices);
static struct hid_driver pk_driver = {
.name = "prodikeys",
.id_table = pk_devices,
.report_fixup = pk_report_fixup,
.input_mapping = pk_input_mapping,
.raw_event = pk_raw_event,
.probe = pk_probe,
.remove = pk_remove,
};
module_hid_driver(pk_driver);
MODULE_LICENSE("GPL");

1419
drivers/hid/hid-rmi.c Executable file

File diff suppressed because it is too large Load Diff

460
drivers/hid/hid-roccat-arvo.c Executable file
View File

@ -0,0 +1,460 @@
/*
* Roccat Arvo driver for Linux
*
* Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Arvo is a gamer keyboard with 5 macro keys that can be configured in
* 5 profiles.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-arvo.h"
static struct class *arvo_class;
static ssize_t arvo_sysfs_show_mode_key(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
struct usb_device *usb_dev =
interface_to_usbdev(to_usb_interface(dev->parent->parent));
struct arvo_mode_key temp_buf;
int retval;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_MODE_KEY,
&temp_buf, sizeof(struct arvo_mode_key));
mutex_unlock(&arvo->arvo_lock);
if (retval)
return retval;
return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.state);
}
static ssize_t arvo_sysfs_set_mode_key(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
struct usb_device *usb_dev =
interface_to_usbdev(to_usb_interface(dev->parent->parent));
struct arvo_mode_key temp_buf;
unsigned long state;
int retval;
retval = kstrtoul(buf, 10, &state);
if (retval)
return retval;
temp_buf.command = ARVO_COMMAND_MODE_KEY;
temp_buf.state = state;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_send(usb_dev, ARVO_COMMAND_MODE_KEY,
&temp_buf, sizeof(struct arvo_mode_key));
mutex_unlock(&arvo->arvo_lock);
if (retval)
return retval;
return size;
}
static DEVICE_ATTR(mode_key, 0660,
arvo_sysfs_show_mode_key, arvo_sysfs_set_mode_key);
static ssize_t arvo_sysfs_show_key_mask(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
struct usb_device *usb_dev =
interface_to_usbdev(to_usb_interface(dev->parent->parent));
struct arvo_key_mask temp_buf;
int retval;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_KEY_MASK,
&temp_buf, sizeof(struct arvo_key_mask));
mutex_unlock(&arvo->arvo_lock);
if (retval)
return retval;
return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.key_mask);
}
static ssize_t arvo_sysfs_set_key_mask(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
struct usb_device *usb_dev =
interface_to_usbdev(to_usb_interface(dev->parent->parent));
struct arvo_key_mask temp_buf;
unsigned long key_mask;
int retval;
retval = kstrtoul(buf, 10, &key_mask);
if (retval)
return retval;
temp_buf.command = ARVO_COMMAND_KEY_MASK;
temp_buf.key_mask = key_mask;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_send(usb_dev, ARVO_COMMAND_KEY_MASK,
&temp_buf, sizeof(struct arvo_key_mask));
mutex_unlock(&arvo->arvo_lock);
if (retval)
return retval;
return size;
}
static DEVICE_ATTR(key_mask, 0660,
arvo_sysfs_show_key_mask, arvo_sysfs_set_key_mask);
/* retval is 1-5 on success, < 0 on error */
static int arvo_get_actual_profile(struct usb_device *usb_dev)
{
struct arvo_actual_profile temp_buf;
int retval;
retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
&temp_buf, sizeof(struct arvo_actual_profile));
if (retval)
return retval;
return temp_buf.actual_profile;
}
static ssize_t arvo_sysfs_show_actual_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", arvo->actual_profile);
}
static ssize_t arvo_sysfs_set_actual_profile(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct arvo_device *arvo =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
struct usb_device *usb_dev =
interface_to_usbdev(to_usb_interface(dev->parent->parent));
struct arvo_actual_profile temp_buf;
unsigned long profile;
int retval;
retval = kstrtoul(buf, 10, &profile);
if (retval)
return retval;
if (profile < 1 || profile > 5)
return -EINVAL;
temp_buf.command = ARVO_COMMAND_ACTUAL_PROFILE;
temp_buf.actual_profile = profile;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
&temp_buf, sizeof(struct arvo_actual_profile));
if (!retval) {
arvo->actual_profile = profile;
retval = size;
}
mutex_unlock(&arvo->arvo_lock);
return retval;
}
static DEVICE_ATTR(actual_profile, 0660,
arvo_sysfs_show_actual_profile,
arvo_sysfs_set_actual_profile);
static ssize_t arvo_sysfs_write(struct file *fp,
struct kobject *kobj, void const *buf,
loff_t off, size_t count, size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct arvo_device *arvo = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_send(usb_dev, command, buf, real_size);
mutex_unlock(&arvo->arvo_lock);
return (retval ? retval : real_size);
}
static ssize_t arvo_sysfs_read(struct file *fp,
struct kobject *kobj, void *buf, loff_t off,
size_t count, size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct arvo_device *arvo = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&arvo->arvo_lock);
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
mutex_unlock(&arvo->arvo_lock);
return (retval ? retval : real_size);
}
static ssize_t arvo_sysfs_write_button(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
return arvo_sysfs_write(fp, kobj, buf, off, count,
sizeof(struct arvo_button), ARVO_COMMAND_BUTTON);
}
static BIN_ATTR(button, 0220, NULL, arvo_sysfs_write_button,
sizeof(struct arvo_button));
static ssize_t arvo_sysfs_read_info(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
return arvo_sysfs_read(fp, kobj, buf, off, count,
sizeof(struct arvo_info), ARVO_COMMAND_INFO);
}
static BIN_ATTR(info, 0440, arvo_sysfs_read_info, NULL,
sizeof(struct arvo_info));
static struct attribute *arvo_attrs[] = {
&dev_attr_mode_key.attr,
&dev_attr_key_mask.attr,
&dev_attr_actual_profile.attr,
NULL,
};
static struct bin_attribute *arvo_bin_attributes[] = {
&bin_attr_button,
&bin_attr_info,
NULL,
};
static const struct attribute_group arvo_group = {
.attrs = arvo_attrs,
.bin_attrs = arvo_bin_attributes,
};
static const struct attribute_group *arvo_groups[] = {
&arvo_group,
NULL,
};
static int arvo_init_arvo_device_struct(struct usb_device *usb_dev,
struct arvo_device *arvo)
{
int retval;
mutex_init(&arvo->arvo_lock);
retval = arvo_get_actual_profile(usb_dev);
if (retval < 0)
return retval;
arvo->actual_profile = retval;
return 0;
}
static int arvo_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct arvo_device *arvo;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_KEYBOARD) {
hid_set_drvdata(hdev, NULL);
return 0;
}
arvo = kzalloc(sizeof(*arvo), GFP_KERNEL);
if (!arvo) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, arvo);
retval = arvo_init_arvo_device_struct(usb_dev, arvo);
if (retval) {
hid_err(hdev, "couldn't init struct arvo_device\n");
goto exit_free;
}
retval = roccat_connect(arvo_class, hdev,
sizeof(struct arvo_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
arvo->chrdev_minor = retval;
arvo->roccat_claimed = 1;
}
return 0;
exit_free:
kfree(arvo);
return retval;
}
static void arvo_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct arvo_device *arvo;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_KEYBOARD)
return;
arvo = hid_get_drvdata(hdev);
if (arvo->roccat_claimed)
roccat_disconnect(arvo->chrdev_minor);
kfree(arvo);
}
static int arvo_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = arvo_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install keyboard\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void arvo_remove(struct hid_device *hdev)
{
arvo_remove_specials(hdev);
hid_hw_stop(hdev);
}
static void arvo_report_to_chrdev(struct arvo_device const *arvo,
u8 const *data)
{
struct arvo_special_report const *special_report;
struct arvo_roccat_report roccat_report;
special_report = (struct arvo_special_report const *)data;
roccat_report.profile = arvo->actual_profile;
roccat_report.button = special_report->event &
ARVO_SPECIAL_REPORT_EVENT_MASK_BUTTON;
if ((special_report->event & ARVO_SPECIAL_REPORT_EVENT_MASK_ACTION) ==
ARVO_SPECIAL_REPORT_EVENT_ACTION_PRESS)
roccat_report.action = ARVO_ROCCAT_REPORT_ACTION_PRESS;
else
roccat_report.action = ARVO_ROCCAT_REPORT_ACTION_RELEASE;
roccat_report_event(arvo->chrdev_minor,
(uint8_t const *)&roccat_report);
}
static int arvo_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct arvo_device *arvo = hid_get_drvdata(hdev);
if (size != 3)
return 0;
if (arvo && arvo->roccat_claimed)
arvo_report_to_chrdev(arvo, data);
return 0;
}
static const struct hid_device_id arvo_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) },
{ }
};
MODULE_DEVICE_TABLE(hid, arvo_devices);
static struct hid_driver arvo_driver = {
.name = "arvo",
.id_table = arvo_devices,
.probe = arvo_probe,
.remove = arvo_remove,
.raw_event = arvo_raw_event
};
static int __init arvo_init(void)
{
int retval;
arvo_class = class_create(THIS_MODULE, "arvo");
if (IS_ERR(arvo_class))
return PTR_ERR(arvo_class);
arvo_class->dev_groups = arvo_groups;
retval = hid_register_driver(&arvo_driver);
if (retval)
class_destroy(arvo_class);
return retval;
}
static void __exit arvo_exit(void)
{
hid_unregister_driver(&arvo_driver);
class_destroy(arvo_class);
}
module_init(arvo_init);
module_exit(arvo_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Arvo driver");
MODULE_LICENSE("GPL v2");

85
drivers/hid/hid-roccat-arvo.h Executable file
View File

@ -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

180
drivers/hid/hid-roccat-common.c Executable file
View File

@ -0,0 +1,180 @@
/*
* Roccat common functions for device specific drivers
*
* Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/hid.h>
#include <linux/slab.h>
#include <linux/module.h>
#include "hid-roccat-common.h"
static inline uint16_t roccat_common2_feature_report(uint8_t report_id)
{
return 0x300 | report_id;
}
int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
void *data, uint size)
{
char *buf;
int len;
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
HID_REQ_GET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
roccat_common2_feature_report(report_id),
0, buf, size, USB_CTRL_SET_TIMEOUT);
memcpy(data, buf, size);
kfree(buf);
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
}
EXPORT_SYMBOL_GPL(roccat_common2_receive);
int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
void const *data, uint size)
{
char *buf;
int len;
buf = kmemdup(data, size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
HID_REQ_SET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
roccat_common2_feature_report(report_id),
0, buf, size, USB_CTRL_SET_TIMEOUT);
kfree(buf);
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
}
EXPORT_SYMBOL_GPL(roccat_common2_send);
enum roccat_common2_control_states {
ROCCAT_COMMON_CONTROL_STATUS_CRITICAL = 0,
ROCCAT_COMMON_CONTROL_STATUS_OK = 1,
ROCCAT_COMMON_CONTROL_STATUS_INVALID = 2,
ROCCAT_COMMON_CONTROL_STATUS_BUSY = 3,
ROCCAT_COMMON_CONTROL_STATUS_CRITICAL_NEW = 4,
};
static int roccat_common2_receive_control_status(struct usb_device *usb_dev)
{
int retval;
struct roccat_common2_control control;
do {
msleep(50);
retval = roccat_common2_receive(usb_dev,
ROCCAT_COMMON_COMMAND_CONTROL,
&control, sizeof(struct roccat_common2_control));
if (retval)
return retval;
switch (control.value) {
case ROCCAT_COMMON_CONTROL_STATUS_OK:
return 0;
case ROCCAT_COMMON_CONTROL_STATUS_BUSY:
msleep(500);
continue;
case ROCCAT_COMMON_CONTROL_STATUS_INVALID:
case ROCCAT_COMMON_CONTROL_STATUS_CRITICAL:
case ROCCAT_COMMON_CONTROL_STATUS_CRITICAL_NEW:
return -EINVAL;
default:
dev_err(&usb_dev->dev,
"roccat_common2_receive_control_status: "
"unknown response value 0x%x\n",
control.value);
return -EINVAL;
}
} while (1);
}
int roccat_common2_send_with_status(struct usb_device *usb_dev,
uint command, void const *buf, uint size)
{
int retval;
retval = roccat_common2_send(usb_dev, command, buf, size);
if (retval)
return retval;
msleep(100);
return roccat_common2_receive_control_status(usb_dev);
}
EXPORT_SYMBOL_GPL(roccat_common2_send_with_status);
int roccat_common2_device_init_struct(struct usb_device *usb_dev,
struct roccat_common2_device *dev)
{
mutex_init(&dev->lock);
return 0;
}
EXPORT_SYMBOL_GPL(roccat_common2_device_init_struct);
ssize_t roccat_common2_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct roccat_common2_device *roccat_dev = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&roccat_dev->lock);
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
mutex_unlock(&roccat_dev->lock);
return retval ? retval : real_size;
}
EXPORT_SYMBOL_GPL(roccat_common2_sysfs_read);
ssize_t roccat_common2_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct roccat_common2_device *roccat_dev = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&roccat_dev->lock);
retval = roccat_common2_send_with_status(usb_dev, command, buf, real_size);
mutex_unlock(&roccat_dev->lock);
return retval ? retval : real_size;
}
EXPORT_SYMBOL_GPL(roccat_common2_sysfs_write);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat common driver");
MODULE_LICENSE("GPL v2");

97
drivers/hid/hid-roccat-common.h Executable file
View File

@ -0,0 +1,97 @@
#ifndef __HID_ROCCAT_COMMON_H
#define __HID_ROCCAT_COMMON_H
/*
* Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/usb.h>
#include <linux/types.h>
enum roccat_common2_commands {
ROCCAT_COMMON_COMMAND_CONTROL = 0x4,
};
struct roccat_common2_control {
uint8_t command;
uint8_t value;
uint8_t request; /* always 0 on requesting write check */
} __packed;
int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
void *data, uint size);
int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
void const *data, uint size);
int roccat_common2_send_with_status(struct usb_device *usb_dev,
uint command, void const *buf, uint size);
struct roccat_common2_device {
int roccat_claimed;
int chrdev_minor;
struct mutex lock;
};
int roccat_common2_device_init_struct(struct usb_device *usb_dev,
struct roccat_common2_device *dev);
ssize_t roccat_common2_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command);
ssize_t roccat_common2_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command);
#define ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE) \
static ssize_t roccat_common2_sysfs_write_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return roccat_common2_sysfs_write(fp, kobj, buf, off, count, \
SIZE, COMMAND); \
}
#define ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE) \
static ssize_t roccat_common2_sysfs_read_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return roccat_common2_sysfs_read(fp, kobj, buf, off, count, \
SIZE, COMMAND); \
}
#define ROCCAT_COMMON2_SYSFS_RW(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE)
#define ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_RW(thingy, COMMAND, SIZE); \
static struct bin_attribute bin_attr_ ## thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = SIZE, \
.read = roccat_common2_sysfs_read_ ## thingy, \
.write = roccat_common2_sysfs_write_ ## thingy \
}
#define ROCCAT_COMMON2_BIN_ATTRIBUTE_R(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE); \
static struct bin_attribute bin_attr_ ## thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = SIZE, \
.read = roccat_common2_sysfs_read_ ## thingy, \
}
#define ROCCAT_COMMON2_BIN_ATTRIBUTE_W(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE); \
static struct bin_attribute bin_attr_ ## thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = SIZE, \
.write = roccat_common2_sysfs_write_ ## thingy \
}
#endif

462
drivers/hid/hid-roccat-isku.c Executable file
View File

@ -0,0 +1,462 @@
/*
* Roccat Isku driver for Linux
*
* Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Isku is a gamer keyboard with macro keys that can be configured in
* 5 profiles.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-isku.h"
static struct class *isku_class;
static void isku_profile_activated(struct isku_device *isku, uint new_profile)
{
isku->actual_profile = new_profile;
}
static int isku_receive(struct usb_device *usb_dev, uint command,
void *buf, uint size)
{
return roccat_common2_receive(usb_dev, command, buf, size);
}
static int isku_get_actual_profile(struct usb_device *usb_dev)
{
struct isku_actual_profile buf;
int retval;
retval = isku_receive(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE,
&buf, sizeof(struct isku_actual_profile));
return retval ? retval : buf.actual_profile;
}
static int isku_set_actual_profile(struct usb_device *usb_dev, int new_profile)
{
struct isku_actual_profile buf;
buf.command = ISKU_COMMAND_ACTUAL_PROFILE;
buf.size = sizeof(struct isku_actual_profile);
buf.actual_profile = new_profile;
return roccat_common2_send_with_status(usb_dev,
ISKU_COMMAND_ACTUAL_PROFILE, &buf,
sizeof(struct isku_actual_profile));
}
static ssize_t isku_sysfs_show_actual_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct isku_device *isku =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", isku->actual_profile);
}
static ssize_t isku_sysfs_set_actual_profile(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct isku_device *isku;
struct usb_device *usb_dev;
unsigned long profile;
int retval;
struct isku_roccat_report roccat_report;
dev = dev->parent->parent;
isku = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
retval = kstrtoul(buf, 10, &profile);
if (retval)
return retval;
if (profile > 4)
return -EINVAL;
mutex_lock(&isku->isku_lock);
retval = isku_set_actual_profile(usb_dev, profile);
if (retval) {
mutex_unlock(&isku->isku_lock);
return retval;
}
isku_profile_activated(isku, profile);
roccat_report.event = ISKU_REPORT_BUTTON_EVENT_PROFILE;
roccat_report.data1 = profile + 1;
roccat_report.data2 = 0;
roccat_report.profile = profile + 1;
roccat_report_event(isku->chrdev_minor, (uint8_t const *)&roccat_report);
mutex_unlock(&isku->isku_lock);
return size;
}
static DEVICE_ATTR(actual_profile, 0660, isku_sysfs_show_actual_profile,
isku_sysfs_set_actual_profile);
static struct attribute *isku_attrs[] = {
&dev_attr_actual_profile.attr,
NULL,
};
static ssize_t isku_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct isku_device *isku = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count > real_size)
return -EINVAL;
mutex_lock(&isku->isku_lock);
retval = isku_receive(usb_dev, command, buf, count);
mutex_unlock(&isku->isku_lock);
return retval ? retval : count;
}
static ssize_t isku_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct isku_device *isku = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count > real_size)
return -EINVAL;
mutex_lock(&isku->isku_lock);
retval = roccat_common2_send_with_status(usb_dev, command,
(void *)buf, count);
mutex_unlock(&isku->isku_lock);
return retval ? retval : count;
}
#define ISKU_SYSFS_W(thingy, THINGY) \
static ssize_t isku_sysfs_write_ ## thingy(struct file *fp, struct kobject *kobj, \
struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return isku_sysfs_write(fp, kobj, buf, off, count, \
ISKU_SIZE_ ## THINGY, ISKU_COMMAND_ ## THINGY); \
}
#define ISKU_SYSFS_R(thingy, THINGY) \
static ssize_t isku_sysfs_read_ ## thingy(struct file *fp, struct kobject *kobj, \
struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return isku_sysfs_read(fp, kobj, buf, off, count, \
ISKU_SIZE_ ## THINGY, ISKU_COMMAND_ ## THINGY); \
}
#define ISKU_SYSFS_RW(thingy, THINGY) \
ISKU_SYSFS_R(thingy, THINGY) \
ISKU_SYSFS_W(thingy, THINGY)
#define ISKU_BIN_ATTR_RW(thingy, THINGY) \
ISKU_SYSFS_RW(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = ISKU_SIZE_ ## THINGY, \
.read = isku_sysfs_read_ ## thingy, \
.write = isku_sysfs_write_ ## thingy \
}
#define ISKU_BIN_ATTR_R(thingy, THINGY) \
ISKU_SYSFS_R(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = ISKU_SIZE_ ## THINGY, \
.read = isku_sysfs_read_ ## thingy, \
}
#define ISKU_BIN_ATTR_W(thingy, THINGY) \
ISKU_SYSFS_W(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = ISKU_SIZE_ ## THINGY, \
.write = isku_sysfs_write_ ## thingy \
}
ISKU_BIN_ATTR_RW(macro, MACRO);
ISKU_BIN_ATTR_RW(keys_function, KEYS_FUNCTION);
ISKU_BIN_ATTR_RW(keys_easyzone, KEYS_EASYZONE);
ISKU_BIN_ATTR_RW(keys_media, KEYS_MEDIA);
ISKU_BIN_ATTR_RW(keys_thumbster, KEYS_THUMBSTER);
ISKU_BIN_ATTR_RW(keys_macro, KEYS_MACRO);
ISKU_BIN_ATTR_RW(keys_capslock, KEYS_CAPSLOCK);
ISKU_BIN_ATTR_RW(light, LIGHT);
ISKU_BIN_ATTR_RW(key_mask, KEY_MASK);
ISKU_BIN_ATTR_RW(last_set, LAST_SET);
ISKU_BIN_ATTR_W(talk, TALK);
ISKU_BIN_ATTR_W(talkfx, TALKFX);
ISKU_BIN_ATTR_W(control, CONTROL);
ISKU_BIN_ATTR_W(reset, RESET);
ISKU_BIN_ATTR_R(info, INFO);
static struct bin_attribute *isku_bin_attributes[] = {
&bin_attr_macro,
&bin_attr_keys_function,
&bin_attr_keys_easyzone,
&bin_attr_keys_media,
&bin_attr_keys_thumbster,
&bin_attr_keys_macro,
&bin_attr_keys_capslock,
&bin_attr_light,
&bin_attr_key_mask,
&bin_attr_last_set,
&bin_attr_talk,
&bin_attr_talkfx,
&bin_attr_control,
&bin_attr_reset,
&bin_attr_info,
NULL,
};
static const struct attribute_group isku_group = {
.attrs = isku_attrs,
.bin_attrs = isku_bin_attributes,
};
static const struct attribute_group *isku_groups[] = {
&isku_group,
NULL,
};
static int isku_init_isku_device_struct(struct usb_device *usb_dev,
struct isku_device *isku)
{
int retval;
mutex_init(&isku->isku_lock);
retval = isku_get_actual_profile(usb_dev);
if (retval < 0)
return retval;
isku_profile_activated(isku, retval);
return 0;
}
static int isku_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct isku_device *isku;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= ISKU_USB_INTERFACE_PROTOCOL) {
hid_set_drvdata(hdev, NULL);
return 0;
}
isku = kzalloc(sizeof(*isku), GFP_KERNEL);
if (!isku) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, isku);
retval = isku_init_isku_device_struct(usb_dev, isku);
if (retval) {
hid_err(hdev, "couldn't init struct isku_device\n");
goto exit_free;
}
retval = roccat_connect(isku_class, hdev,
sizeof(struct isku_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
isku->chrdev_minor = retval;
isku->roccat_claimed = 1;
}
return 0;
exit_free:
kfree(isku);
return retval;
}
static void isku_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct isku_device *isku;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= ISKU_USB_INTERFACE_PROTOCOL)
return;
isku = hid_get_drvdata(hdev);
if (isku->roccat_claimed)
roccat_disconnect(isku->chrdev_minor);
kfree(isku);
}
static int isku_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = isku_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install keyboard\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void isku_remove(struct hid_device *hdev)
{
isku_remove_specials(hdev);
hid_hw_stop(hdev);
}
static void isku_keep_values_up_to_date(struct isku_device *isku,
u8 const *data)
{
struct isku_report_button const *button_report;
switch (data[0]) {
case ISKU_REPORT_NUMBER_BUTTON:
button_report = (struct isku_report_button const *)data;
switch (button_report->event) {
case ISKU_REPORT_BUTTON_EVENT_PROFILE:
isku_profile_activated(isku, button_report->data1 - 1);
break;
}
break;
}
}
static void isku_report_to_chrdev(struct isku_device const *isku,
u8 const *data)
{
struct isku_roccat_report roccat_report;
struct isku_report_button const *button_report;
if (data[0] != ISKU_REPORT_NUMBER_BUTTON)
return;
button_report = (struct isku_report_button const *)data;
roccat_report.event = button_report->event;
roccat_report.data1 = button_report->data1;
roccat_report.data2 = button_report->data2;
roccat_report.profile = isku->actual_profile + 1;
roccat_report_event(isku->chrdev_minor,
(uint8_t const *)&roccat_report);
}
static int isku_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct isku_device *isku = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= ISKU_USB_INTERFACE_PROTOCOL)
return 0;
if (isku == NULL)
return 0;
isku_keep_values_up_to_date(isku, data);
if (isku->roccat_claimed)
isku_report_to_chrdev(isku, data);
return 0;
}
static const struct hid_device_id isku_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKUFX) },
{ }
};
MODULE_DEVICE_TABLE(hid, isku_devices);
static struct hid_driver isku_driver = {
.name = "isku",
.id_table = isku_devices,
.probe = isku_probe,
.remove = isku_remove,
.raw_event = isku_raw_event
};
static int __init isku_init(void)
{
int retval;
isku_class = class_create(THIS_MODULE, "isku");
if (IS_ERR(isku_class))
return PTR_ERR(isku_class);
isku_class->dev_groups = isku_groups;
retval = hid_register_driver(&isku_driver);
if (retval)
class_destroy(isku_class);
return retval;
}
static void __exit isku_exit(void)
{
hid_unregister_driver(&isku_driver);
class_destroy(isku_class);
}
module_init(isku_init);
module_exit(isku_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Isku/FX driver");
MODULE_LICENSE("GPL v2");

100
drivers/hid/hid-roccat-isku.h Executable file
View File

@ -0,0 +1,100 @@
#ifndef __HID_ROCCAT_ISKU_H
#define __HID_ROCCAT_ISKU_H
/*
* Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
enum {
ISKU_SIZE_CONTROL = 0x03,
ISKU_SIZE_INFO = 0x06,
ISKU_SIZE_KEY_MASK = 0x06,
ISKU_SIZE_KEYS_FUNCTION = 0x29,
ISKU_SIZE_KEYS_EASYZONE = 0x41,
ISKU_SIZE_KEYS_MEDIA = 0x1d,
ISKU_SIZE_KEYS_THUMBSTER = 0x17,
ISKU_SIZE_KEYS_MACRO = 0x23,
ISKU_SIZE_KEYS_CAPSLOCK = 0x06,
ISKU_SIZE_LAST_SET = 0x14,
ISKU_SIZE_LIGHT = 0x10,
ISKU_SIZE_MACRO = 0x823,
ISKU_SIZE_RESET = 0x03,
ISKU_SIZE_TALK = 0x10,
ISKU_SIZE_TALKFX = 0x10,
};
enum {
ISKU_PROFILE_NUM = 5,
ISKU_USB_INTERFACE_PROTOCOL = 0,
};
struct isku_actual_profile {
uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */
uint8_t size; /* always 3 */
uint8_t actual_profile;
} __packed;
enum isku_commands {
ISKU_COMMAND_CONTROL = 0x4,
ISKU_COMMAND_ACTUAL_PROFILE = 0x5,
ISKU_COMMAND_KEY_MASK = 0x7,
ISKU_COMMAND_KEYS_FUNCTION = 0x8,
ISKU_COMMAND_KEYS_EASYZONE = 0x9,
ISKU_COMMAND_KEYS_MEDIA = 0xa,
ISKU_COMMAND_KEYS_THUMBSTER = 0xb,
ISKU_COMMAND_KEYS_MACRO = 0xd,
ISKU_COMMAND_MACRO = 0xe,
ISKU_COMMAND_INFO = 0xf,
ISKU_COMMAND_LIGHT = 0x10,
ISKU_COMMAND_RESET = 0x11,
ISKU_COMMAND_KEYS_CAPSLOCK = 0x13,
ISKU_COMMAND_LAST_SET = 0x14,
ISKU_COMMAND_15 = 0x15,
ISKU_COMMAND_TALK = 0x16,
ISKU_COMMAND_TALKFX = 0x17,
ISKU_COMMAND_FIRMWARE_WRITE = 0x1b,
ISKU_COMMAND_FIRMWARE_WRITE_CONTROL = 0x1c,
};
struct isku_report_button {
uint8_t number; /* ISKU_REPORT_NUMBER_BUTTON */
uint8_t zero;
uint8_t event;
uint8_t data1;
uint8_t data2;
};
enum isku_report_numbers {
ISKU_REPORT_NUMBER_BUTTON = 3,
};
enum isku_report_button_events {
ISKU_REPORT_BUTTON_EVENT_PROFILE = 0x2,
};
struct isku_roccat_report {
uint8_t event;
uint8_t data1;
uint8_t data2;
uint8_t profile;
} __packed;
struct isku_device {
int roccat_claimed;
int chrdev_minor;
struct mutex isku_lock;
int actual_profile;
};
#endif

909
drivers/hid/hid-roccat-kone.c Executable file
View File

@ -0,0 +1,909 @@
/*
* Roccat Kone driver for Linux
*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Kone is a gamer mouse which consists of a mouse part and a keyboard
* part. The keyboard part enables the mouse to execute stored macros with mixed
* key- and button-events.
*
* TODO implement on-the-fly polling-rate change
* The windows driver has the ability to change the polling rate of the
* device on the press of a mousebutton.
* Is it possible to remove and reinstall the urb in raw-event- or any
* other handler, or to defer this action to be executed somewhere else?
*
* TODO is it possible to overwrite group for sysfs attributes via udev?
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-kone.h"
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
static void kone_profile_activated(struct kone_device *kone, uint new_profile)
{
kone->actual_profile = new_profile;
kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi;
}
static void kone_profile_report(struct kone_device *kone, uint new_profile)
{
struct kone_roccat_report roccat_report;
roccat_report.event = kone_mouse_event_switch_profile;
roccat_report.value = new_profile;
roccat_report.key = 0;
roccat_report_event(kone->chrdev_minor, (uint8_t *)&roccat_report);
}
static int kone_receive(struct usb_device *usb_dev, uint usb_command,
void *data, uint size)
{
char *buf;
int len;
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
HID_REQ_GET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT);
memcpy(data, buf, size);
kfree(buf);
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
}
static int kone_send(struct usb_device *usb_dev, uint usb_command,
void const *data, uint size)
{
char *buf;
int len;
buf = kmemdup(data, size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
HID_REQ_SET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT);
kfree(buf);
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
}
/* kone_class is used for creating sysfs attributes via roccat char device */
static struct class *kone_class;
static void kone_set_settings_checksum(struct kone_settings *settings)
{
uint16_t checksum = 0;
unsigned char *address = (unsigned char *)settings;
int i;
for (i = 0; i < sizeof(struct kone_settings) - 2; ++i, ++address)
checksum += *address;
settings->checksum = cpu_to_le16(checksum);
}
/*
* Checks success after writing data to mouse
* On success returns 0
* On failure returns errno
*/
static int kone_check_write(struct usb_device *usb_dev)
{
int retval;
uint8_t data;
do {
/*
* Mouse needs 50 msecs until it says ok, but there are
* 30 more msecs needed for next write to work.
*/
msleep(80);
retval = kone_receive(usb_dev,
kone_command_confirm_write, &data, 1);
if (retval)
return retval;
/*
* value of 3 seems to mean something like
* "not finished yet, but it looks good"
* So check again after a moment.
*/
} while (data == 3);
if (data == 1) /* everything alright */
return 0;
/* unknown answer */
dev_err(&usb_dev->dev, "got retval %d when checking write\n", data);
return -EIO;
}
/*
* Reads settings from mouse and stores it in @buf
* On success returns 0
* On failure returns errno
*/
static int kone_get_settings(struct usb_device *usb_dev,
struct kone_settings *buf)
{
return kone_receive(usb_dev, kone_command_settings, buf,
sizeof(struct kone_settings));
}
/*
* Writes settings from @buf to mouse
* On success returns 0
* On failure returns errno
*/
static int kone_set_settings(struct usb_device *usb_dev,
struct kone_settings const *settings)
{
int retval;
retval = kone_send(usb_dev, kone_command_settings,
settings, sizeof(struct kone_settings));
if (retval)
return retval;
return kone_check_write(usb_dev);
}
/*
* Reads profile data from mouse and stores it in @buf
* @number: profile number to read
* On success returns 0
* On failure returns errno
*/
static int kone_get_profile(struct usb_device *usb_dev,
struct kone_profile *buf, int number)
{
int len;
if (number < 1 || number > 5)
return -EINVAL;
len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
USB_REQ_CLEAR_FEATURE,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
kone_command_profile, number, buf,
sizeof(struct kone_profile), USB_CTRL_SET_TIMEOUT);
if (len != sizeof(struct kone_profile))
return -EIO;
return 0;
}
/*
* Writes profile data to mouse.
* @number: profile number to write
* On success returns 0
* On failure returns errno
*/
static int kone_set_profile(struct usb_device *usb_dev,
struct kone_profile const *profile, int number)
{
int len;
if (number < 1 || number > 5)
return -EINVAL;
len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
USB_REQ_SET_CONFIGURATION,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
kone_command_profile, number, (void *)profile,
sizeof(struct kone_profile),
USB_CTRL_SET_TIMEOUT);
if (len != sizeof(struct kone_profile))
return len;
if (kone_check_write(usb_dev))
return -EIO;
return 0;
}
/*
* Reads value of "fast-clip-weight" and stores it in @result
* On success returns 0
* On failure returns errno
*/
static int kone_get_weight(struct usb_device *usb_dev, int *result)
{
int retval;
uint8_t data;
retval = kone_receive(usb_dev, kone_command_weight, &data, 1);
if (retval)
return retval;
*result = (int)data;
return 0;
}
/*
* Reads firmware_version of mouse and stores it in @result
* On success returns 0
* On failure returns errno
*/
static int kone_get_firmware_version(struct usb_device *usb_dev, int *result)
{
int retval;
uint16_t data;
retval = kone_receive(usb_dev, kone_command_firmware_version,
&data, 2);
if (retval)
return retval;
*result = le16_to_cpu(data);
return 0;
}
static ssize_t kone_sysfs_read_settings(struct file *fp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
if (off >= sizeof(struct kone_settings))
return 0;
if (off + count > sizeof(struct kone_settings))
count = sizeof(struct kone_settings) - off;
mutex_lock(&kone->kone_lock);
memcpy(buf, ((char const *)&kone->settings) + off, count);
mutex_unlock(&kone->kone_lock);
return count;
}
/*
* Writing settings automatically activates startup_profile.
* This function keeps values in kone_device up to date and assumes that in
* case of error the old data is still valid
*/
static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval = 0, difference, old_profile;
/* I need to get my data in one piece */
if (off != 0 || count != sizeof(struct kone_settings))
return -EINVAL;
mutex_lock(&kone->kone_lock);
difference = memcmp(buf, &kone->settings, sizeof(struct kone_settings));
if (difference) {
retval = kone_set_settings(usb_dev,
(struct kone_settings const *)buf);
if (retval) {
mutex_unlock(&kone->kone_lock);
return retval;
}
old_profile = kone->settings.startup_profile;
memcpy(&kone->settings, buf, sizeof(struct kone_settings));
kone_profile_activated(kone, kone->settings.startup_profile);
if (kone->settings.startup_profile != old_profile)
kone_profile_report(kone, kone->settings.startup_profile);
}
mutex_unlock(&kone->kone_lock);
return sizeof(struct kone_settings);
}
static BIN_ATTR(settings, 0660, kone_sysfs_read_settings,
kone_sysfs_write_settings, sizeof(struct kone_settings));
static ssize_t kone_sysfs_read_profilex(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t off, size_t count) {
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
if (off >= sizeof(struct kone_profile))
return 0;
if (off + count > sizeof(struct kone_profile))
count = sizeof(struct kone_profile) - off;
mutex_lock(&kone->kone_lock);
memcpy(buf, ((char const *)&kone->profiles[*(uint *)(attr->private)]) + off, count);
mutex_unlock(&kone->kone_lock);
return count;
}
/* Writes data only if different to stored data */
static ssize_t kone_sysfs_write_profilex(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t off, size_t count) {
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
struct kone_profile *profile;
int retval = 0, difference;
/* I need to get my data in one piece */
if (off != 0 || count != sizeof(struct kone_profile))
return -EINVAL;
profile = &kone->profiles[*(uint *)(attr->private)];
mutex_lock(&kone->kone_lock);
difference = memcmp(buf, profile, sizeof(struct kone_profile));
if (difference) {
retval = kone_set_profile(usb_dev,
(struct kone_profile const *)buf,
*(uint *)(attr->private) + 1);
if (!retval)
memcpy(profile, buf, sizeof(struct kone_profile));
}
mutex_unlock(&kone->kone_lock);
if (retval)
return retval;
return sizeof(struct kone_profile);
}
#define PROFILE_ATTR(number) \
static struct bin_attribute bin_attr_profile##number = { \
.attr = { .name = "profile" #number, .mode = 0660 }, \
.size = sizeof(struct kone_profile), \
.read = kone_sysfs_read_profilex, \
.write = kone_sysfs_write_profilex, \
.private = &profile_numbers[number-1], \
}
PROFILE_ATTR(1);
PROFILE_ATTR(2);
PROFILE_ATTR(3);
PROFILE_ATTR(4);
PROFILE_ATTR(5);
static ssize_t kone_sysfs_show_actual_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile);
}
static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL);
static ssize_t kone_sysfs_show_actual_dpi(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi);
}
static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL);
/* weight is read each time, since we don't get informed when it's changed */
static ssize_t kone_sysfs_show_weight(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone;
struct usb_device *usb_dev;
int weight = 0;
int retval;
dev = dev->parent->parent;
kone = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
mutex_lock(&kone->kone_lock);
retval = kone_get_weight(usb_dev, &weight);
mutex_unlock(&kone->kone_lock);
if (retval)
return retval;
return snprintf(buf, PAGE_SIZE, "%d\n", weight);
}
static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL);
static ssize_t kone_sysfs_show_firmware_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440, kone_sysfs_show_firmware_version,
NULL);
static ssize_t kone_sysfs_show_tcu(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu);
}
static int kone_tcu_command(struct usb_device *usb_dev, int number)
{
unsigned char value;
value = number;
return kone_send(usb_dev, kone_command_calibrate, &value, 1);
}
/*
* Calibrating the tcu is the only action that changes settings data inside the
* mouse, so this data needs to be reread
*/
static ssize_t kone_sysfs_set_tcu(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct kone_device *kone;
struct usb_device *usb_dev;
int retval;
unsigned long state;
dev = dev->parent->parent;
kone = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
retval = kstrtoul(buf, 10, &state);
if (retval)
return retval;
if (state != 0 && state != 1)
return -EINVAL;
mutex_lock(&kone->kone_lock);
if (state == 1) { /* state activate */
retval = kone_tcu_command(usb_dev, 1);
if (retval)
goto exit_unlock;
retval = kone_tcu_command(usb_dev, 2);
if (retval)
goto exit_unlock;
ssleep(5); /* tcu needs this time for calibration */
retval = kone_tcu_command(usb_dev, 3);
if (retval)
goto exit_unlock;
retval = kone_tcu_command(usb_dev, 0);
if (retval)
goto exit_unlock;
retval = kone_tcu_command(usb_dev, 4);
if (retval)
goto exit_unlock;
/*
* Kone needs this time to settle things.
* Reading settings too early will result in invalid data.
* Roccat's driver waits 1 sec, maybe this time could be
* shortened.
*/
ssleep(1);
}
/* calibration changes values in settings, so reread */
retval = kone_get_settings(usb_dev, &kone->settings);
if (retval)
goto exit_no_settings;
/* only write settings back if activation state is different */
if (kone->settings.tcu != state) {
kone->settings.tcu = state;
kone_set_settings_checksum(&kone->settings);
retval = kone_set_settings(usb_dev, &kone->settings);
if (retval) {
dev_err(&usb_dev->dev, "couldn't set tcu state\n");
/*
* try to reread valid settings into buffer overwriting
* first error code
*/
retval = kone_get_settings(usb_dev, &kone->settings);
if (retval)
goto exit_no_settings;
goto exit_unlock;
}
/* calibration resets profile */
kone_profile_activated(kone, kone->settings.startup_profile);
}
retval = size;
exit_no_settings:
dev_err(&usb_dev->dev, "couldn't read settings\n");
exit_unlock:
mutex_unlock(&kone->kone_lock);
return retval;
}
static DEVICE_ATTR(tcu, 0660, kone_sysfs_show_tcu, kone_sysfs_set_tcu);
static ssize_t kone_sysfs_show_startup_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.startup_profile);
}
static ssize_t kone_sysfs_set_startup_profile(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct kone_device *kone;
struct usb_device *usb_dev;
int retval;
unsigned long new_startup_profile;
dev = dev->parent->parent;
kone = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
retval = kstrtoul(buf, 10, &new_startup_profile);
if (retval)
return retval;
if (new_startup_profile < 1 || new_startup_profile > 5)
return -EINVAL;
mutex_lock(&kone->kone_lock);
kone->settings.startup_profile = new_startup_profile;
kone_set_settings_checksum(&kone->settings);
retval = kone_set_settings(usb_dev, &kone->settings);
if (retval) {
mutex_unlock(&kone->kone_lock);
return retval;
}
/* changing the startup profile immediately activates this profile */
kone_profile_activated(kone, new_startup_profile);
kone_profile_report(kone, new_startup_profile);
mutex_unlock(&kone->kone_lock);
return size;
}
static DEVICE_ATTR(startup_profile, 0660, kone_sysfs_show_startup_profile,
kone_sysfs_set_startup_profile);
static struct attribute *kone_attrs[] = {
/*
* Read actual dpi settings.
* Returns raw value for further processing. Refer to enum
* kone_polling_rates to get real value.
*/
&dev_attr_actual_dpi.attr,
&dev_attr_actual_profile.attr,
/*
* The mouse can be equipped with one of four supplied weights from 5
* to 20 grams which are recognized and its value can be read out.
* This returns the raw value reported by the mouse for easy evaluation
* by software. Refer to enum kone_weights to get corresponding real
* weight.
*/
&dev_attr_weight.attr,
/*
* Prints firmware version stored in mouse as integer.
* The raw value reported by the mouse is returned for easy evaluation,
* to get the real version number the decimal point has to be shifted 2
* positions to the left. E.g. a value of 138 means 1.38.
*/
&dev_attr_firmware_version.attr,
/*
* Prints state of Tracking Control Unit as number where 0 = off and
* 1 = on. Writing 0 deactivates tcu and writing 1 calibrates and
* activates the tcu
*/
&dev_attr_tcu.attr,
/* Prints and takes the number of the profile the mouse starts with */
&dev_attr_startup_profile.attr,
NULL,
};
static struct bin_attribute *kone_bin_attributes[] = {
&bin_attr_settings,
&bin_attr_profile1,
&bin_attr_profile2,
&bin_attr_profile3,
&bin_attr_profile4,
&bin_attr_profile5,
NULL,
};
static const struct attribute_group kone_group = {
.attrs = kone_attrs,
.bin_attrs = kone_bin_attributes,
};
static const struct attribute_group *kone_groups[] = {
&kone_group,
NULL,
};
static int kone_init_kone_device_struct(struct usb_device *usb_dev,
struct kone_device *kone)
{
uint i;
int retval;
mutex_init(&kone->kone_lock);
for (i = 0; i < 5; ++i) {
retval = kone_get_profile(usb_dev, &kone->profiles[i], i + 1);
if (retval)
return retval;
}
retval = kone_get_settings(usb_dev, &kone->settings);
if (retval)
return retval;
retval = kone_get_firmware_version(usb_dev, &kone->firmware_version);
if (retval)
return retval;
kone_profile_activated(kone, kone->settings.startup_profile);
return 0;
}
/*
* Since IGNORE_MOUSE quirk moved to hid-apple, there is no way to bind only to
* mousepart if usb_hid is compiled into the kernel and kone is compiled as
* module.
* Secial behaviour is bound only to mousepart since only mouseevents contain
* additional notifications.
*/
static int kone_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct kone_device *kone;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
kone = kzalloc(sizeof(*kone), GFP_KERNEL);
if (!kone)
return -ENOMEM;
hid_set_drvdata(hdev, kone);
retval = kone_init_kone_device_struct(usb_dev, kone);
if (retval) {
hid_err(hdev, "couldn't init struct kone_device\n");
goto exit_free;
}
retval = roccat_connect(kone_class, hdev,
sizeof(struct kone_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
/* be tolerant about not getting chrdev */
} else {
kone->roccat_claimed = 1;
kone->chrdev_minor = retval;
}
} else {
hid_set_drvdata(hdev, NULL);
}
return 0;
exit_free:
kfree(kone);
return retval;
}
static void kone_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct kone_device *kone;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
kone = hid_get_drvdata(hdev);
if (kone->roccat_claimed)
roccat_disconnect(kone->chrdev_minor);
kfree(hid_get_drvdata(hdev));
}
}
static int kone_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = kone_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void kone_remove(struct hid_device *hdev)
{
kone_remove_specials(hdev);
hid_hw_stop(hdev);
}
/* handle special events and keep actual profile and dpi values up to date */
static void kone_keep_values_up_to_date(struct kone_device *kone,
struct kone_mouse_event const *event)
{
switch (event->event) {
case kone_mouse_event_switch_profile:
kone->actual_dpi = kone->profiles[event->value - 1].
startup_dpi;
case kone_mouse_event_osd_profile:
kone->actual_profile = event->value;
break;
case kone_mouse_event_switch_dpi:
case kone_mouse_event_osd_dpi:
kone->actual_dpi = event->value;
break;
}
}
static void kone_report_to_chrdev(struct kone_device const *kone,
struct kone_mouse_event const *event)
{
struct kone_roccat_report roccat_report;
switch (event->event) {
case kone_mouse_event_switch_profile:
case kone_mouse_event_switch_dpi:
case kone_mouse_event_osd_profile:
case kone_mouse_event_osd_dpi:
roccat_report.event = event->event;
roccat_report.value = event->value;
roccat_report.key = 0;
roccat_report_event(kone->chrdev_minor,
(uint8_t *)&roccat_report);
break;
case kone_mouse_event_call_overlong_macro:
case kone_mouse_event_multimedia:
if (event->value == kone_keystroke_action_press) {
roccat_report.event = event->event;
roccat_report.value = kone->actual_profile;
roccat_report.key = event->macro_key;
roccat_report_event(kone->chrdev_minor,
(uint8_t *)&roccat_report);
}
break;
}
}
/*
* Is called for keyboard- and mousepart.
* Only mousepart gets informations about special events in its extended event
* structure.
*/
static int kone_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
struct kone_device *kone = hid_get_drvdata(hdev);
struct kone_mouse_event *event = (struct kone_mouse_event *)data;
/* keyboard events are always processed by default handler */
if (size != sizeof(struct kone_mouse_event))
return 0;
if (kone == NULL)
return 0;
/*
* Firmware 1.38 introduced new behaviour for tilt and special buttons.
* Pressed button is reported in each movement event.
* Workaround sends only one event per press.
*/
if (memcmp(&kone->last_mouse_event.tilt, &event->tilt, 5))
memcpy(&kone->last_mouse_event, event,
sizeof(struct kone_mouse_event));
else
memset(&event->tilt, 0, 5);
kone_keep_values_up_to_date(kone, event);
if (kone->roccat_claimed)
kone_report_to_chrdev(kone, event);
return 0; /* always do further processing */
}
static const struct hid_device_id kone_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) },
{ }
};
MODULE_DEVICE_TABLE(hid, kone_devices);
static struct hid_driver kone_driver = {
.name = "kone",
.id_table = kone_devices,
.probe = kone_probe,
.remove = kone_remove,
.raw_event = kone_raw_event
};
static int __init kone_init(void)
{
int retval;
/* class name has to be same as driver name */
kone_class = class_create(THIS_MODULE, "kone");
if (IS_ERR(kone_class))
return PTR_ERR(kone_class);
kone_class->dev_groups = kone_groups;
retval = hid_register_driver(&kone_driver);
if (retval)
class_destroy(kone_class);
return retval;
}
static void __exit kone_exit(void)
{
hid_unregister_driver(&kone_driver);
class_destroy(kone_class);
}
module_init(kone_init);
module_exit(kone_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kone driver");
MODULE_LICENSE("GPL v2");

227
drivers/hid/hid-roccat-kone.h Executable file
View File

@ -0,0 +1,227 @@
#ifndef __HID_ROCCAT_KONE_H
#define __HID_ROCCAT_KONE_H
/*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
struct kone_keystroke {
uint8_t key;
uint8_t action;
uint16_t period; /* in milliseconds */
} __attribute__ ((__packed__));
enum kone_keystroke_buttons {
kone_keystroke_button_1 = 0xf0, /* left mouse button */
kone_keystroke_button_2 = 0xf1, /* right mouse button */
kone_keystroke_button_3 = 0xf2, /* wheel */
kone_keystroke_button_9 = 0xf3, /* side button up */
kone_keystroke_button_8 = 0xf4 /* side button down */
};
enum kone_keystroke_actions {
kone_keystroke_action_press = 0,
kone_keystroke_action_release = 1
};
struct kone_button_info {
uint8_t number; /* range 1-8 */
uint8_t type;
uint8_t macro_type; /* 0 = short, 1 = overlong */
uint8_t macro_set_name[16]; /* can be max 15 chars long */
uint8_t macro_name[16]; /* can be max 15 chars long */
uint8_t count;
struct kone_keystroke keystrokes[20];
} __attribute__ ((__packed__));
enum kone_button_info_types {
/* valid button types until firmware 1.32 */
kone_button_info_type_button_1 = 0x1, /* click (left mouse button) */
kone_button_info_type_button_2 = 0x2, /* menu (right mouse button)*/
kone_button_info_type_button_3 = 0x3, /* scroll (wheel) */
kone_button_info_type_double_click = 0x4,
kone_button_info_type_key = 0x5,
kone_button_info_type_macro = 0x6,
kone_button_info_type_off = 0x7,
/* TODO clarify function and rename */
kone_button_info_type_osd_xy_prescaling = 0x8,
kone_button_info_type_osd_dpi = 0x9,
kone_button_info_type_osd_profile = 0xa,
kone_button_info_type_button_9 = 0xb, /* ie forward */
kone_button_info_type_button_8 = 0xc, /* ie backward */
kone_button_info_type_dpi_up = 0xd, /* internal */
kone_button_info_type_dpi_down = 0xe, /* internal */
kone_button_info_type_button_7 = 0xf, /* tilt left */
kone_button_info_type_button_6 = 0x10, /* tilt right */
kone_button_info_type_profile_up = 0x11, /* internal */
kone_button_info_type_profile_down = 0x12, /* internal */
/* additional valid button types since firmware 1.38 */
kone_button_info_type_multimedia_open_player = 0x20,
kone_button_info_type_multimedia_next_track = 0x21,
kone_button_info_type_multimedia_prev_track = 0x22,
kone_button_info_type_multimedia_play_pause = 0x23,
kone_button_info_type_multimedia_stop = 0x24,
kone_button_info_type_multimedia_mute = 0x25,
kone_button_info_type_multimedia_volume_up = 0x26,
kone_button_info_type_multimedia_volume_down = 0x27
};
enum kone_button_info_numbers {
kone_button_top = 1,
kone_button_wheel_tilt_left = 2,
kone_button_wheel_tilt_right = 3,
kone_button_forward = 4,
kone_button_backward = 5,
kone_button_middle = 6,
kone_button_plus = 7,
kone_button_minus = 8,
};
struct kone_light_info {
uint8_t number; /* number of light 1-5 */
uint8_t mod; /* 1 = on, 2 = off */
uint8_t red; /* range 0x00-0xff */
uint8_t green; /* range 0x00-0xff */
uint8_t blue; /* range 0x00-0xff */
} __attribute__ ((__packed__));
struct kone_profile {
uint16_t size; /* always 975 */
uint16_t unused; /* always 0 */
/*
* range 1-5
* This number does not need to correspond with location where profile
* saved
*/
uint8_t profile; /* range 1-5 */
uint16_t main_sensitivity; /* range 100-1000 */
uint8_t xy_sensitivity_enabled; /* 1 = on, 2 = off */
uint16_t x_sensitivity; /* range 100-1000 */
uint16_t y_sensitivity; /* range 100-1000 */
uint8_t dpi_rate; /* bit 1 = 800, ... */
uint8_t startup_dpi; /* range 1-6 */
uint8_t polling_rate; /* 1 = 125Hz, 2 = 500Hz, 3 = 1000Hz */
/* kone has no dcu
* value is always 2 in firmwares <= 1.32 and
* 1 in firmwares > 1.32
*/
uint8_t dcu_flag;
uint8_t light_effect_1; /* range 1-3 */
uint8_t light_effect_2; /* range 1-5 */
uint8_t light_effect_3; /* range 1-4 */
uint8_t light_effect_speed; /* range 0-255 */
struct kone_light_info light_infos[5];
/* offset is kone_button_info_numbers - 1 */
struct kone_button_info button_infos[8];
uint16_t checksum; /* \brief holds checksum of struct */
} __attribute__ ((__packed__));
enum kone_polling_rates {
kone_polling_rate_125 = 1,
kone_polling_rate_500 = 2,
kone_polling_rate_1000 = 3
};
struct kone_settings {
uint16_t size; /* always 36 */
uint8_t startup_profile; /* 1-5 */
uint8_t unknown1;
uint8_t tcu; /* 0 = off, 1 = on */
uint8_t unknown2[23];
uint8_t calibration_data[4];
uint8_t unknown3[2];
uint16_t checksum;
} __attribute__ ((__packed__));
/*
* 12 byte mouse event read by interrupt_read
*/
struct kone_mouse_event {
uint8_t report_number; /* always 1 */
uint8_t button;
uint16_t x;
uint16_t y;
uint8_t wheel; /* up = 1, down = -1 */
uint8_t tilt; /* right = 1, left = -1 */
uint8_t unknown;
uint8_t event;
uint8_t value; /* press = 0, release = 1 */
uint8_t macro_key; /* 0 to 8 */
} __attribute__ ((__packed__));
enum kone_mouse_events {
/* osd events are thought to be display on screen */
kone_mouse_event_osd_dpi = 0xa0,
kone_mouse_event_osd_profile = 0xb0,
/* TODO clarify meaning and occurence of kone_mouse_event_calibration */
kone_mouse_event_calibration = 0xc0,
kone_mouse_event_call_overlong_macro = 0xe0,
kone_mouse_event_multimedia = 0xe1,
/* switch events notify if user changed values with mousebutton click */
kone_mouse_event_switch_dpi = 0xf0,
kone_mouse_event_switch_profile = 0xf1
};
enum kone_commands {
kone_command_profile = 0x5a,
kone_command_settings = 0x15a,
kone_command_firmware_version = 0x25a,
kone_command_weight = 0x45a,
kone_command_calibrate = 0x55a,
kone_command_confirm_write = 0x65a,
kone_command_firmware = 0xe5a
};
struct kone_roccat_report {
uint8_t event;
uint8_t value; /* holds dpi or profile value */
uint8_t key; /* macro key on overlong macro execution */
} __attribute__ ((__packed__));
struct kone_device {
/*
* Storing actual values when we get informed about changes since there
* is no way of getting this information from the device on demand
*/
int actual_profile, actual_dpi;
/* Used for neutralizing abnormal button behaviour */
struct kone_mouse_event last_mouse_event;
/*
* It's unlikely that multiple sysfs attributes are accessed at a time,
* so only one mutex is used to secure hardware access and profiles and
* settings of this struct.
*/
struct mutex kone_lock;
/*
* Storing the data here reduces IO and ensures that data is available
* when its needed (E.g. interrupt handler).
*/
struct kone_profile profiles[5];
struct kone_settings settings;
/*
* firmware doesn't change unless firmware update is implemented,
* so it's read only once
*/
int firmware_version;
int roccat_claimed;
int chrdev_minor;
};
#endif

578
drivers/hid/hid-roccat-koneplus.c Executable file
View File

@ -0,0 +1,578 @@
/*
* Roccat Kone[+] driver for Linux
*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Kone[+] is an updated/improved version of the Kone with more memory
* and functionality and without the non-standard behaviours the Kone had.
* KoneXTD has same capabilities but updated sensor.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-koneplus.h"
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
static struct class *koneplus_class;
static void koneplus_profile_activated(struct koneplus_device *koneplus,
uint new_profile)
{
koneplus->actual_profile = new_profile;
}
static int koneplus_send_control(struct usb_device *usb_dev, uint value,
enum koneplus_control_requests request)
{
struct roccat_common2_control control;
if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
value > 4)
return -EINVAL;
control.command = ROCCAT_COMMON_COMMAND_CONTROL;
control.value = value;
control.request = request;
return roccat_common2_send_with_status(usb_dev,
ROCCAT_COMMON_COMMAND_CONTROL,
&control, sizeof(struct roccat_common2_control));
}
/* retval is 0-4 on success, < 0 on error */
static int koneplus_get_actual_profile(struct usb_device *usb_dev)
{
struct koneplus_actual_profile buf;
int retval;
retval = roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
&buf, KONEPLUS_SIZE_ACTUAL_PROFILE);
return retval ? retval : buf.actual_profile;
}
static int koneplus_set_actual_profile(struct usb_device *usb_dev,
int new_profile)
{
struct koneplus_actual_profile buf;
buf.command = KONEPLUS_COMMAND_ACTUAL_PROFILE;
buf.size = KONEPLUS_SIZE_ACTUAL_PROFILE;
buf.actual_profile = new_profile;
return roccat_common2_send_with_status(usb_dev,
KONEPLUS_COMMAND_ACTUAL_PROFILE,
&buf, KONEPLUS_SIZE_ACTUAL_PROFILE);
}
static ssize_t koneplus_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct koneplus_device *koneplus = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&koneplus->koneplus_lock);
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
mutex_unlock(&koneplus->koneplus_lock);
if (retval)
return retval;
return real_size;
}
static ssize_t koneplus_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct koneplus_device *koneplus = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&koneplus->koneplus_lock);
retval = roccat_common2_send_with_status(usb_dev, command,
buf, real_size);
mutex_unlock(&koneplus->koneplus_lock);
if (retval)
return retval;
return real_size;
}
#define KONEPLUS_SYSFS_W(thingy, THINGY) \
static ssize_t koneplus_sysfs_write_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return koneplus_sysfs_write(fp, kobj, buf, off, count, \
KONEPLUS_SIZE_ ## THINGY, KONEPLUS_COMMAND_ ## THINGY); \
}
#define KONEPLUS_SYSFS_R(thingy, THINGY) \
static ssize_t koneplus_sysfs_read_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return koneplus_sysfs_read(fp, kobj, buf, off, count, \
KONEPLUS_SIZE_ ## THINGY, KONEPLUS_COMMAND_ ## THINGY); \
}
#define KONEPLUS_SYSFS_RW(thingy, THINGY) \
KONEPLUS_SYSFS_W(thingy, THINGY) \
KONEPLUS_SYSFS_R(thingy, THINGY)
#define KONEPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \
KONEPLUS_SYSFS_RW(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = KONEPLUS_SIZE_ ## THINGY, \
.read = koneplus_sysfs_read_ ## thingy, \
.write = koneplus_sysfs_write_ ## thingy \
}
#define KONEPLUS_BIN_ATTRIBUTE_R(thingy, THINGY) \
KONEPLUS_SYSFS_R(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = KONEPLUS_SIZE_ ## THINGY, \
.read = koneplus_sysfs_read_ ## thingy, \
}
#define KONEPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \
KONEPLUS_SYSFS_W(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = KONEPLUS_SIZE_ ## THINGY, \
.write = koneplus_sysfs_write_ ## thingy \
}
KONEPLUS_BIN_ATTRIBUTE_W(control, CONTROL);
KONEPLUS_BIN_ATTRIBUTE_W(talk, TALK);
KONEPLUS_BIN_ATTRIBUTE_W(macro, MACRO);
KONEPLUS_BIN_ATTRIBUTE_R(tcu_image, TCU_IMAGE);
KONEPLUS_BIN_ATTRIBUTE_RW(info, INFO);
KONEPLUS_BIN_ATTRIBUTE_RW(sensor, SENSOR);
KONEPLUS_BIN_ATTRIBUTE_RW(tcu, TCU);
KONEPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
KONEPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
static ssize_t koneplus_sysfs_read_profilex_settings(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
ssize_t retval;
retval = koneplus_send_control(usb_dev, *(uint *)(attr->private),
KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
if (retval)
return retval;
return koneplus_sysfs_read(fp, kobj, buf, off, count,
KONEPLUS_SIZE_PROFILE_SETTINGS,
KONEPLUS_COMMAND_PROFILE_SETTINGS);
}
static ssize_t koneplus_sysfs_read_profilex_buttons(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
ssize_t retval;
retval = koneplus_send_control(usb_dev, *(uint *)(attr->private),
KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
if (retval)
return retval;
return koneplus_sysfs_read(fp, kobj, buf, off, count,
KONEPLUS_SIZE_PROFILE_BUTTONS,
KONEPLUS_COMMAND_PROFILE_BUTTONS);
}
#define PROFILE_ATTR(number) \
static struct bin_attribute bin_attr_profile##number##_settings = { \
.attr = { .name = "profile" #number "_settings", .mode = 0440 }, \
.size = KONEPLUS_SIZE_PROFILE_SETTINGS, \
.read = koneplus_sysfs_read_profilex_settings, \
.private = &profile_numbers[number-1], \
}; \
static struct bin_attribute bin_attr_profile##number##_buttons = { \
.attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \
.size = KONEPLUS_SIZE_PROFILE_BUTTONS, \
.read = koneplus_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
PROFILE_ATTR(2);
PROFILE_ATTR(3);
PROFILE_ATTR(4);
PROFILE_ATTR(5);
static ssize_t koneplus_sysfs_show_actual_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct koneplus_device *koneplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", koneplus->actual_profile);
}
static ssize_t koneplus_sysfs_set_actual_profile(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct koneplus_device *koneplus;
struct usb_device *usb_dev;
unsigned long profile;
int retval;
struct koneplus_roccat_report roccat_report;
dev = dev->parent->parent;
koneplus = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
retval = kstrtoul(buf, 10, &profile);
if (retval)
return retval;
if (profile > 4)
return -EINVAL;
mutex_lock(&koneplus->koneplus_lock);
retval = koneplus_set_actual_profile(usb_dev, profile);
if (retval) {
mutex_unlock(&koneplus->koneplus_lock);
return retval;
}
koneplus_profile_activated(koneplus, profile);
roccat_report.type = KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE;
roccat_report.data1 = profile + 1;
roccat_report.data2 = 0;
roccat_report.profile = profile + 1;
roccat_report_event(koneplus->chrdev_minor,
(uint8_t const *)&roccat_report);
mutex_unlock(&koneplus->koneplus_lock);
return size;
}
static DEVICE_ATTR(actual_profile, 0660,
koneplus_sysfs_show_actual_profile,
koneplus_sysfs_set_actual_profile);
static DEVICE_ATTR(startup_profile, 0660,
koneplus_sysfs_show_actual_profile,
koneplus_sysfs_set_actual_profile);
static ssize_t koneplus_sysfs_show_firmware_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct koneplus_device *koneplus;
struct usb_device *usb_dev;
struct koneplus_info info;
dev = dev->parent->parent;
koneplus = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
mutex_lock(&koneplus->koneplus_lock);
roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_INFO,
&info, KONEPLUS_SIZE_INFO);
mutex_unlock(&koneplus->koneplus_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440,
koneplus_sysfs_show_firmware_version, NULL);
static struct attribute *koneplus_attrs[] = {
&dev_attr_actual_profile.attr,
&dev_attr_startup_profile.attr,
&dev_attr_firmware_version.attr,
NULL,
};
static struct bin_attribute *koneplus_bin_attributes[] = {
&bin_attr_control,
&bin_attr_talk,
&bin_attr_macro,
&bin_attr_tcu_image,
&bin_attr_info,
&bin_attr_sensor,
&bin_attr_tcu,
&bin_attr_profile_settings,
&bin_attr_profile_buttons,
&bin_attr_profile1_settings,
&bin_attr_profile2_settings,
&bin_attr_profile3_settings,
&bin_attr_profile4_settings,
&bin_attr_profile5_settings,
&bin_attr_profile1_buttons,
&bin_attr_profile2_buttons,
&bin_attr_profile3_buttons,
&bin_attr_profile4_buttons,
&bin_attr_profile5_buttons,
NULL,
};
static const struct attribute_group koneplus_group = {
.attrs = koneplus_attrs,
.bin_attrs = koneplus_bin_attributes,
};
static const struct attribute_group *koneplus_groups[] = {
&koneplus_group,
NULL,
};
static int koneplus_init_koneplus_device_struct(struct usb_device *usb_dev,
struct koneplus_device *koneplus)
{
int retval;
mutex_init(&koneplus->koneplus_lock);
retval = koneplus_get_actual_profile(usb_dev);
if (retval < 0)
return retval;
koneplus_profile_activated(koneplus, retval);
return 0;
}
static int koneplus_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct koneplus_device *koneplus;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
koneplus = kzalloc(sizeof(*koneplus), GFP_KERNEL);
if (!koneplus) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, koneplus);
retval = koneplus_init_koneplus_device_struct(usb_dev, koneplus);
if (retval) {
hid_err(hdev, "couldn't init struct koneplus_device\n");
goto exit_free;
}
retval = roccat_connect(koneplus_class, hdev,
sizeof(struct koneplus_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
koneplus->chrdev_minor = retval;
koneplus->roccat_claimed = 1;
}
} else {
hid_set_drvdata(hdev, NULL);
}
return 0;
exit_free:
kfree(koneplus);
return retval;
}
static void koneplus_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct koneplus_device *koneplus;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
koneplus = hid_get_drvdata(hdev);
if (koneplus->roccat_claimed)
roccat_disconnect(koneplus->chrdev_minor);
kfree(koneplus);
}
}
static int koneplus_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = koneplus_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void koneplus_remove(struct hid_device *hdev)
{
koneplus_remove_specials(hdev);
hid_hw_stop(hdev);
}
static void koneplus_keep_values_up_to_date(struct koneplus_device *koneplus,
u8 const *data)
{
struct koneplus_mouse_report_button const *button_report;
switch (data[0]) {
case KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON:
button_report = (struct koneplus_mouse_report_button const *)data;
switch (button_report->type) {
case KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE:
koneplus_profile_activated(koneplus, button_report->data1 - 1);
break;
}
break;
}
}
static void koneplus_report_to_chrdev(struct koneplus_device const *koneplus,
u8 const *data)
{
struct koneplus_roccat_report roccat_report;
struct koneplus_mouse_report_button const *button_report;
if (data[0] != KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON)
return;
button_report = (struct koneplus_mouse_report_button const *)data;
if ((button_report->type == KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH ||
button_report->type == KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER) &&
button_report->data2 != KONEPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS)
return;
roccat_report.type = button_report->type;
roccat_report.data1 = button_report->data1;
roccat_report.data2 = button_report->data2;
roccat_report.profile = koneplus->actual_profile + 1;
roccat_report_event(koneplus->chrdev_minor,
(uint8_t const *)&roccat_report);
}
static int koneplus_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct koneplus_device *koneplus = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return 0;
if (koneplus == NULL)
return 0;
koneplus_keep_values_up_to_date(koneplus, data);
if (koneplus->roccat_claimed)
koneplus_report_to_chrdev(koneplus, data);
return 0;
}
static const struct hid_device_id koneplus_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPLUS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEXTD) },
{ }
};
MODULE_DEVICE_TABLE(hid, koneplus_devices);
static struct hid_driver koneplus_driver = {
.name = "koneplus",
.id_table = koneplus_devices,
.probe = koneplus_probe,
.remove = koneplus_remove,
.raw_event = koneplus_raw_event
};
static int __init koneplus_init(void)
{
int retval;
/* class name has to be same as driver name */
koneplus_class = class_create(THIS_MODULE, "koneplus");
if (IS_ERR(koneplus_class))
return PTR_ERR(koneplus_class);
koneplus_class->dev_groups = koneplus_groups;
retval = hid_register_driver(&koneplus_driver);
if (retval)
class_destroy(koneplus_class);
return retval;
}
static void __exit koneplus_exit(void)
{
hid_unregister_driver(&koneplus_driver);
class_destroy(koneplus_class);
}
module_init(koneplus_init);
module_exit(koneplus_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kone[+]/XTD driver");
MODULE_LICENSE("GPL v2");

125
drivers/hid/hid-roccat-koneplus.h Executable file
View File

@ -0,0 +1,125 @@
#ifndef __HID_ROCCAT_KONEPLUS_H
#define __HID_ROCCAT_KONEPLUS_H
/*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
enum {
KONEPLUS_SIZE_ACTUAL_PROFILE = 0x03,
KONEPLUS_SIZE_CONTROL = 0x03,
KONEPLUS_SIZE_FIRMWARE_WRITE = 0x0402,
KONEPLUS_SIZE_INFO = 0x06,
KONEPLUS_SIZE_MACRO = 0x0822,
KONEPLUS_SIZE_PROFILE_SETTINGS = 0x2b,
KONEPLUS_SIZE_PROFILE_BUTTONS = 0x4d,
KONEPLUS_SIZE_SENSOR = 0x06,
KONEPLUS_SIZE_TALK = 0x10,
KONEPLUS_SIZE_TCU = 0x04,
KONEPLUS_SIZE_TCU_IMAGE = 0x0404,
};
enum koneplus_control_requests {
KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x80,
KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x90,
};
struct koneplus_actual_profile {
uint8_t command; /* KONEPLUS_COMMAND_ACTUAL_PROFILE */
uint8_t size; /* always 3 */
uint8_t actual_profile; /* Range 0-4! */
} __attribute__ ((__packed__));
struct koneplus_info {
uint8_t command; /* KONEPLUS_COMMAND_INFO */
uint8_t size; /* always 6 */
uint8_t firmware_version;
uint8_t unknown[3];
} __attribute__ ((__packed__));
enum koneplus_commands {
KONEPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
KONEPLUS_COMMAND_CONTROL = 0x4,
KONEPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
KONEPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
KONEPLUS_COMMAND_MACRO = 0x8,
KONEPLUS_COMMAND_INFO = 0x9,
KONEPLUS_COMMAND_TCU = 0xc,
KONEPLUS_COMMAND_TCU_IMAGE = 0xc,
KONEPLUS_COMMAND_E = 0xe,
KONEPLUS_COMMAND_SENSOR = 0xf,
KONEPLUS_COMMAND_TALK = 0x10,
KONEPLUS_COMMAND_FIRMWARE_WRITE = 0x1b,
KONEPLUS_COMMAND_FIRMWARE_WRITE_CONTROL = 0x1c,
};
enum koneplus_mouse_report_numbers {
KONEPLUS_MOUSE_REPORT_NUMBER_HID = 1,
KONEPLUS_MOUSE_REPORT_NUMBER_AUDIO = 2,
KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON = 3,
};
struct koneplus_mouse_report_button {
uint8_t report_number; /* always KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON */
uint8_t zero1;
uint8_t type;
uint8_t data1;
uint8_t data2;
uint8_t zero2;
uint8_t unknown[2];
} __attribute__ ((__packed__));
enum koneplus_mouse_report_button_types {
/* data1 = new profile range 1-5 */
KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE = 0x20,
/* data1 = button number range 1-24; data2 = action */
KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
/* data1 = button number range 1-24; data2 = action */
KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
/* data1 = setting number range 1-5 */
KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
/* data1 and data2 = range 0x1-0xb */
KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
/* data1 = 22 = next track...
* data2 = action
*/
KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
KONEPLUS_MOUSE_REPORT_TALK = 0xff,
};
enum koneplus_mouse_report_button_action {
KONEPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS = 0,
KONEPLUS_MOUSE_REPORT_BUTTON_ACTION_RELEASE = 1,
};
struct koneplus_roccat_report {
uint8_t type;
uint8_t data1;
uint8_t data2;
uint8_t profile;
} __attribute__ ((__packed__));
struct koneplus_device {
int actual_profile;
int roccat_claimed;
int chrdev_minor;
struct mutex koneplus_lock;
};
#endif

232
drivers/hid/hid-roccat-konepure.c Executable file
View File

@ -0,0 +1,232 @@
/*
* Roccat KonePure driver for Linux
*
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat KonePure is a smaller version of KoneXTD with less buttons and lights.
*/
#include <linux/types.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
enum {
KONEPURE_MOUSE_REPORT_NUMBER_BUTTON = 3,
};
struct konepure_mouse_report_button {
uint8_t report_number; /* always KONEPURE_MOUSE_REPORT_NUMBER_BUTTON */
uint8_t zero;
uint8_t type;
uint8_t data1;
uint8_t data2;
uint8_t zero2;
uint8_t unknown[2];
} __packed;
static struct class *konepure_class;
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(actual_profile, 0x05, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile_settings, 0x06, 0x1f);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile_buttons, 0x07, 0x3b);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(macro, 0x08, 0x0822);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(info, 0x09, 0x06);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(tcu, 0x0c, 0x04);
ROCCAT_COMMON2_BIN_ATTRIBUTE_R(tcu_image, 0x0c, 0x0404);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0x0f, 0x06);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(talk, 0x10, 0x10);
static struct bin_attribute *konepure_bin_attrs[] = {
&bin_attr_actual_profile,
&bin_attr_control,
&bin_attr_info,
&bin_attr_talk,
&bin_attr_macro,
&bin_attr_sensor,
&bin_attr_tcu,
&bin_attr_tcu_image,
&bin_attr_profile_settings,
&bin_attr_profile_buttons,
NULL,
};
static const struct attribute_group konepure_group = {
.bin_attrs = konepure_bin_attrs,
};
static const struct attribute_group *konepure_groups[] = {
&konepure_group,
NULL,
};
static int konepure_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct roccat_common2_device *konepure;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE) {
hid_set_drvdata(hdev, NULL);
return 0;
}
konepure = kzalloc(sizeof(*konepure), GFP_KERNEL);
if (!konepure) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, konepure);
retval = roccat_common2_device_init_struct(usb_dev, konepure);
if (retval) {
hid_err(hdev, "couldn't init KonePure device\n");
goto exit_free;
}
retval = roccat_connect(konepure_class, hdev,
sizeof(struct konepure_mouse_report_button));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
konepure->chrdev_minor = retval;
konepure->roccat_claimed = 1;
}
return 0;
exit_free:
kfree(konepure);
return retval;
}
static void konepure_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct roccat_common2_device *konepure;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return;
konepure = hid_get_drvdata(hdev);
if (konepure->roccat_claimed)
roccat_disconnect(konepure->chrdev_minor);
kfree(konepure);
}
static int konepure_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = konepure_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void konepure_remove(struct hid_device *hdev)
{
konepure_remove_specials(hdev);
hid_hw_stop(hdev);
}
static int konepure_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct roccat_common2_device *konepure = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return 0;
if (data[0] != KONEPURE_MOUSE_REPORT_NUMBER_BUTTON)
return 0;
if (konepure != NULL && konepure->roccat_claimed)
roccat_report_event(konepure->chrdev_minor, data);
return 0;
}
static const struct hid_device_id konepure_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPURE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPURE_OPTICAL) },
{ }
};
MODULE_DEVICE_TABLE(hid, konepure_devices);
static struct hid_driver konepure_driver = {
.name = "konepure",
.id_table = konepure_devices,
.probe = konepure_probe,
.remove = konepure_remove,
.raw_event = konepure_raw_event
};
static int __init konepure_init(void)
{
int retval;
konepure_class = class_create(THIS_MODULE, "konepure");
if (IS_ERR(konepure_class))
return PTR_ERR(konepure_class);
konepure_class->dev_groups = konepure_groups;
retval = hid_register_driver(&konepure_driver);
if (retval)
class_destroy(konepure_class);
return retval;
}
static void __exit konepure_exit(void)
{
hid_unregister_driver(&konepure_driver);
class_destroy(konepure_class);
}
module_init(konepure_init);
module_exit(konepure_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat KonePure/Optical driver");
MODULE_LICENSE("GPL v2");

667
drivers/hid/hid-roccat-kovaplus.c Executable file
View File

@ -0,0 +1,667 @@
/*
* Roccat Kova[+] driver for Linux
*
* Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Kova[+] is a bigger version of the Pyra with two more side buttons.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-kovaplus.h"
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
static struct class *kovaplus_class;
static uint kovaplus_convert_event_cpi(uint value)
{
return (value == 7 ? 4 : (value == 4 ? 3 : value));
}
static void kovaplus_profile_activated(struct kovaplus_device *kovaplus,
uint new_profile_index)
{
if (new_profile_index >= ARRAY_SIZE(kovaplus->profile_settings))
return;
kovaplus->actual_profile = new_profile_index;
kovaplus->actual_cpi = kovaplus->profile_settings[new_profile_index].cpi_startup_level;
kovaplus->actual_x_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_x;
kovaplus->actual_y_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_y;
}
static int kovaplus_send_control(struct usb_device *usb_dev, uint value,
enum kovaplus_control_requests request)
{
int retval;
struct roccat_common2_control control;
if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
value > 4)
return -EINVAL;
control.command = ROCCAT_COMMON_COMMAND_CONTROL;
control.value = value;
control.request = request;
retval = roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
&control, sizeof(struct roccat_common2_control));
return retval;
}
static int kovaplus_select_profile(struct usb_device *usb_dev, uint number,
enum kovaplus_control_requests request)
{
return kovaplus_send_control(usb_dev, number, request);
}
static int kovaplus_get_profile_settings(struct usb_device *usb_dev,
struct kovaplus_profile_settings *buf, uint number)
{
int retval;
retval = kovaplus_select_profile(usb_dev, number,
KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
if (retval)
return retval;
return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
buf, KOVAPLUS_SIZE_PROFILE_SETTINGS);
}
static int kovaplus_get_profile_buttons(struct usb_device *usb_dev,
struct kovaplus_profile_buttons *buf, int number)
{
int retval;
retval = kovaplus_select_profile(usb_dev, number,
KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
if (retval)
return retval;
return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
buf, KOVAPLUS_SIZE_PROFILE_BUTTONS);
}
/* retval is 0-4 on success, < 0 on error */
static int kovaplus_get_actual_profile(struct usb_device *usb_dev)
{
struct kovaplus_actual_profile buf;
int retval;
retval = roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
&buf, sizeof(struct kovaplus_actual_profile));
return retval ? retval : buf.actual_profile;
}
static int kovaplus_set_actual_profile(struct usb_device *usb_dev,
int new_profile)
{
struct kovaplus_actual_profile buf;
buf.command = KOVAPLUS_COMMAND_ACTUAL_PROFILE;
buf.size = sizeof(struct kovaplus_actual_profile);
buf.actual_profile = new_profile;
return roccat_common2_send_with_status(usb_dev,
KOVAPLUS_COMMAND_ACTUAL_PROFILE,
&buf, sizeof(struct kovaplus_actual_profile));
}
static ssize_t kovaplus_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&kovaplus->kovaplus_lock);
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
mutex_unlock(&kovaplus->kovaplus_lock);
if (retval)
return retval;
return real_size;
}
static ssize_t kovaplus_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&kovaplus->kovaplus_lock);
retval = roccat_common2_send_with_status(usb_dev, command,
buf, real_size);
mutex_unlock(&kovaplus->kovaplus_lock);
if (retval)
return retval;
return real_size;
}
#define KOVAPLUS_SYSFS_W(thingy, THINGY) \
static ssize_t kovaplus_sysfs_write_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return kovaplus_sysfs_write(fp, kobj, buf, off, count, \
KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \
}
#define KOVAPLUS_SYSFS_R(thingy, THINGY) \
static ssize_t kovaplus_sysfs_read_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return kovaplus_sysfs_read(fp, kobj, buf, off, count, \
KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \
}
#define KOVAPLUS_SYSFS_RW(thingy, THINGY) \
KOVAPLUS_SYSFS_W(thingy, THINGY) \
KOVAPLUS_SYSFS_R(thingy, THINGY)
#define KOVAPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \
KOVAPLUS_SYSFS_RW(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = KOVAPLUS_SIZE_ ## THINGY, \
.read = kovaplus_sysfs_read_ ## thingy, \
.write = kovaplus_sysfs_write_ ## thingy \
}
#define KOVAPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \
KOVAPLUS_SYSFS_W(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = KOVAPLUS_SIZE_ ## THINGY, \
.write = kovaplus_sysfs_write_ ## thingy \
}
KOVAPLUS_BIN_ATTRIBUTE_W(control, CONTROL);
KOVAPLUS_BIN_ATTRIBUTE_RW(info, INFO);
KOVAPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
KOVAPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
ssize_t retval;
retval = kovaplus_select_profile(usb_dev, *(uint *)(attr->private),
KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
if (retval)
return retval;
return kovaplus_sysfs_read(fp, kobj, buf, off, count,
KOVAPLUS_SIZE_PROFILE_SETTINGS,
KOVAPLUS_COMMAND_PROFILE_SETTINGS);
}
static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
ssize_t retval;
retval = kovaplus_select_profile(usb_dev, *(uint *)(attr->private),
KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
if (retval)
return retval;
return kovaplus_sysfs_read(fp, kobj, buf, off, count,
KOVAPLUS_SIZE_PROFILE_BUTTONS,
KOVAPLUS_COMMAND_PROFILE_BUTTONS);
}
#define PROFILE_ATTR(number) \
static struct bin_attribute bin_attr_profile##number##_settings = { \
.attr = { .name = "profile" #number "_settings", .mode = 0440 }, \
.size = KOVAPLUS_SIZE_PROFILE_SETTINGS, \
.read = kovaplus_sysfs_read_profilex_settings, \
.private = &profile_numbers[number-1], \
}; \
static struct bin_attribute bin_attr_profile##number##_buttons = { \
.attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \
.size = KOVAPLUS_SIZE_PROFILE_BUTTONS, \
.read = kovaplus_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
PROFILE_ATTR(2);
PROFILE_ATTR(3);
PROFILE_ATTR(4);
PROFILE_ATTR(5);
static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile);
}
static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
struct device_attribute *attr, char const *buf, size_t size)
{
struct kovaplus_device *kovaplus;
struct usb_device *usb_dev;
unsigned long profile;
int retval;
struct kovaplus_roccat_report roccat_report;
dev = dev->parent->parent;
kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
retval = kstrtoul(buf, 10, &profile);
if (retval)
return retval;
if (profile >= 5)
return -EINVAL;
mutex_lock(&kovaplus->kovaplus_lock);
retval = kovaplus_set_actual_profile(usb_dev, profile);
if (retval) {
mutex_unlock(&kovaplus->kovaplus_lock);
return retval;
}
kovaplus_profile_activated(kovaplus, profile);
roccat_report.type = KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1;
roccat_report.profile = profile + 1;
roccat_report.button = 0;
roccat_report.data1 = profile + 1;
roccat_report.data2 = 0;
roccat_report_event(kovaplus->chrdev_minor,
(uint8_t const *)&roccat_report);
mutex_unlock(&kovaplus->kovaplus_lock);
return size;
}
static DEVICE_ATTR(actual_profile, 0660,
kovaplus_sysfs_show_actual_profile,
kovaplus_sysfs_set_actual_profile);
static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi);
}
static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL);
static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity);
}
static DEVICE_ATTR(actual_sensitivity_x, 0440,
kovaplus_sysfs_show_actual_sensitivity_x, NULL);
static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity);
}
static DEVICE_ATTR(actual_sensitivity_y, 0440,
kovaplus_sysfs_show_actual_sensitivity_y, NULL);
static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kovaplus_device *kovaplus;
struct usb_device *usb_dev;
struct kovaplus_info info;
dev = dev->parent->parent;
kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
mutex_lock(&kovaplus->kovaplus_lock);
roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_INFO,
&info, KOVAPLUS_SIZE_INFO);
mutex_unlock(&kovaplus->kovaplus_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440,
kovaplus_sysfs_show_firmware_version, NULL);
static struct attribute *kovaplus_attrs[] = {
&dev_attr_actual_cpi.attr,
&dev_attr_firmware_version.attr,
&dev_attr_actual_profile.attr,
&dev_attr_actual_sensitivity_x.attr,
&dev_attr_actual_sensitivity_y.attr,
NULL,
};
static struct bin_attribute *kovaplus_bin_attributes[] = {
&bin_attr_control,
&bin_attr_info,
&bin_attr_profile_settings,
&bin_attr_profile_buttons,
&bin_attr_profile1_settings,
&bin_attr_profile2_settings,
&bin_attr_profile3_settings,
&bin_attr_profile4_settings,
&bin_attr_profile5_settings,
&bin_attr_profile1_buttons,
&bin_attr_profile2_buttons,
&bin_attr_profile3_buttons,
&bin_attr_profile4_buttons,
&bin_attr_profile5_buttons,
NULL,
};
static const struct attribute_group kovaplus_group = {
.attrs = kovaplus_attrs,
.bin_attrs = kovaplus_bin_attributes,
};
static const struct attribute_group *kovaplus_groups[] = {
&kovaplus_group,
NULL,
};
static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev,
struct kovaplus_device *kovaplus)
{
int retval, i;
static uint wait = 70; /* device will freeze with just 60 */
mutex_init(&kovaplus->kovaplus_lock);
for (i = 0; i < 5; ++i) {
msleep(wait);
retval = kovaplus_get_profile_settings(usb_dev,
&kovaplus->profile_settings[i], i);
if (retval)
return retval;
msleep(wait);
retval = kovaplus_get_profile_buttons(usb_dev,
&kovaplus->profile_buttons[i], i);
if (retval)
return retval;
}
msleep(wait);
retval = kovaplus_get_actual_profile(usb_dev);
if (retval < 0)
return retval;
kovaplus_profile_activated(kovaplus, retval);
return 0;
}
static int kovaplus_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct kovaplus_device *kovaplus;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL);
if (!kovaplus) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, kovaplus);
retval = kovaplus_init_kovaplus_device_struct(usb_dev, kovaplus);
if (retval) {
hid_err(hdev, "couldn't init struct kovaplus_device\n");
goto exit_free;
}
retval = roccat_connect(kovaplus_class, hdev,
sizeof(struct kovaplus_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
kovaplus->chrdev_minor = retval;
kovaplus->roccat_claimed = 1;
}
} else {
hid_set_drvdata(hdev, NULL);
}
return 0;
exit_free:
kfree(kovaplus);
return retval;
}
static void kovaplus_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct kovaplus_device *kovaplus;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
kovaplus = hid_get_drvdata(hdev);
if (kovaplus->roccat_claimed)
roccat_disconnect(kovaplus->chrdev_minor);
kfree(kovaplus);
}
}
static int kovaplus_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = kovaplus_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void kovaplus_remove(struct hid_device *hdev)
{
kovaplus_remove_specials(hdev);
hid_hw_stop(hdev);
}
static void kovaplus_keep_values_up_to_date(struct kovaplus_device *kovaplus,
u8 const *data)
{
struct kovaplus_mouse_report_button const *button_report;
if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON)
return;
button_report = (struct kovaplus_mouse_report_button const *)data;
switch (button_report->type) {
case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1:
kovaplus_profile_activated(kovaplus, button_report->data1 - 1);
break;
case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI:
kovaplus->actual_cpi = kovaplus_convert_event_cpi(button_report->data1);
break;
case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY:
kovaplus->actual_x_sensitivity = button_report->data1;
kovaplus->actual_y_sensitivity = button_report->data2;
break;
default:
break;
}
}
static void kovaplus_report_to_chrdev(struct kovaplus_device const *kovaplus,
u8 const *data)
{
struct kovaplus_roccat_report roccat_report;
struct kovaplus_mouse_report_button const *button_report;
if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON)
return;
button_report = (struct kovaplus_mouse_report_button const *)data;
if (button_report->type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2)
return;
roccat_report.type = button_report->type;
roccat_report.profile = kovaplus->actual_profile + 1;
if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO ||
roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT ||
roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH ||
roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER)
roccat_report.button = button_report->data1;
else
roccat_report.button = 0;
if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI)
roccat_report.data1 = kovaplus_convert_event_cpi(button_report->data1);
else
roccat_report.data1 = button_report->data1;
roccat_report.data2 = button_report->data2;
roccat_report_event(kovaplus->chrdev_minor,
(uint8_t const *)&roccat_report);
}
static int kovaplus_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct kovaplus_device *kovaplus = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return 0;
if (kovaplus == NULL)
return 0;
kovaplus_keep_values_up_to_date(kovaplus, data);
if (kovaplus->roccat_claimed)
kovaplus_report_to_chrdev(kovaplus, data);
return 0;
}
static const struct hid_device_id kovaplus_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },
{ }
};
MODULE_DEVICE_TABLE(hid, kovaplus_devices);
static struct hid_driver kovaplus_driver = {
.name = "kovaplus",
.id_table = kovaplus_devices,
.probe = kovaplus_probe,
.remove = kovaplus_remove,
.raw_event = kovaplus_raw_event
};
static int __init kovaplus_init(void)
{
int retval;
kovaplus_class = class_create(THIS_MODULE, "kovaplus");
if (IS_ERR(kovaplus_class))
return PTR_ERR(kovaplus_class);
kovaplus_class->dev_groups = kovaplus_groups;
retval = hid_register_driver(&kovaplus_driver);
if (retval)
class_destroy(kovaplus_class);
return retval;
}
static void __exit kovaplus_exit(void)
{
hid_unregister_driver(&kovaplus_driver);
class_destroy(kovaplus_class);
}
module_init(kovaplus_init);
module_exit(kovaplus_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kova[+] driver");
MODULE_LICENSE("GPL v2");

133
drivers/hid/hid-roccat-kovaplus.h Executable file
View File

@ -0,0 +1,133 @@
#ifndef __HID_ROCCAT_KOVAPLUS_H
#define __HID_ROCCAT_KOVAPLUS_H
/*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
enum {
KOVAPLUS_SIZE_CONTROL = 0x03,
KOVAPLUS_SIZE_INFO = 0x06,
KOVAPLUS_SIZE_PROFILE_SETTINGS = 0x10,
KOVAPLUS_SIZE_PROFILE_BUTTONS = 0x17,
};
enum kovaplus_control_requests {
/* write; value = profile number range 0-4 */
KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
/* write; value = profile number range 0-4 */
KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20,
};
struct kovaplus_actual_profile {
uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */
uint8_t size; /* always 3 */
uint8_t actual_profile; /* Range 0-4! */
} __packed;
struct kovaplus_profile_settings {
uint8_t command; /* KOVAPLUS_COMMAND_PROFILE_SETTINGS */
uint8_t size; /* 16 */
uint8_t profile_index; /* range 0-4 */
uint8_t unknown1;
uint8_t sensitivity_x; /* range 1-10 */
uint8_t sensitivity_y; /* range 1-10 */
uint8_t cpi_levels_enabled;
uint8_t cpi_startup_level; /* range 1-4 */
uint8_t data[8];
} __packed;
struct kovaplus_profile_buttons {
uint8_t command; /* KOVAPLUS_COMMAND_PROFILE_BUTTONS */
uint8_t size; /* 23 */
uint8_t profile_index; /* range 0-4 */
uint8_t data[20];
} __packed;
struct kovaplus_info {
uint8_t command; /* KOVAPLUS_COMMAND_INFO */
uint8_t size; /* 6 */
uint8_t firmware_version;
uint8_t unknown[3];
} __packed;
enum kovaplus_commands {
KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
KOVAPLUS_COMMAND_CONTROL = 0x4,
KOVAPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
KOVAPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
KOVAPLUS_COMMAND_INFO = 0x9,
KOVAPLUS_COMMAND_A = 0xa,
};
enum kovaplus_mouse_report_numbers {
KOVAPLUS_MOUSE_REPORT_NUMBER_MOUSE = 1,
KOVAPLUS_MOUSE_REPORT_NUMBER_AUDIO = 2,
KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON = 3,
KOVAPLUS_MOUSE_REPORT_NUMBER_KBD = 4,
};
struct kovaplus_mouse_report_button {
uint8_t report_number; /* KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON */
uint8_t unknown1;
uint8_t type;
uint8_t data1;
uint8_t data2;
} __packed;
enum kovaplus_mouse_report_button_types {
/* data1 = profile_number range 1-5; no release event */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1 = 0x20,
/* data1 = profile_number range 1-5; no release event */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2 = 0x30,
/* data1 = button_number range 1-18; data2 = action */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO = 0x40,
/* data1 = button_number range 1-18; data2 = action */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT = 0x50,
/* data1 = button_number range 1-18; data2 = action */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
/* data1 = button_number range 1-18; data2 = action */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
/* data1 = 1 = 400, 2 = 800, 4 = 1600, 7 = 3200; no release event */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
/* data1 + data2 = sense range 1-10; no release event */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
/* data1 = type as in profile_buttons; data2 = action */
KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
};
enum kovaplus_mouse_report_button_actions {
KOVAPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS = 0,
KOVAPLUS_MOUSE_REPORT_BUTTON_ACTION_RELEASE = 1,
};
struct kovaplus_roccat_report {
uint8_t type;
uint8_t profile;
uint8_t button;
uint8_t data1;
uint8_t data2;
} __packed;
struct kovaplus_device {
int actual_profile;
int actual_cpi;
int actual_x_sensitivity;
int actual_y_sensitivity;
int roccat_claimed;
int chrdev_minor;
struct mutex kovaplus_lock;
struct kovaplus_profile_settings profile_settings[5];
struct kovaplus_profile_buttons profile_buttons[5];
};
#endif

215
drivers/hid/hid-roccat-lua.c Executable file
View File

@ -0,0 +1,215 @@
/*
* Roccat Lua driver for Linux
*
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Lua is a gamer mouse which cpi, button and light settings can be
* configured.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-lua.h"
static ssize_t lua_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct lua_device *lua = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&lua->lua_lock);
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
mutex_unlock(&lua->lua_lock);
return retval ? retval : real_size;
}
static ssize_t lua_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct lua_device *lua = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&lua->lua_lock);
retval = roccat_common2_send(usb_dev, command, buf, real_size);
mutex_unlock(&lua->lua_lock);
return retval ? retval : real_size;
}
#define LUA_SYSFS_W(thingy, THINGY) \
static ssize_t lua_sysfs_write_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, \
char *buf, loff_t off, size_t count) \
{ \
return lua_sysfs_write(fp, kobj, buf, off, count, \
LUA_SIZE_ ## THINGY, LUA_COMMAND_ ## THINGY); \
}
#define LUA_SYSFS_R(thingy, THINGY) \
static ssize_t lua_sysfs_read_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, \
char *buf, loff_t off, size_t count) \
{ \
return lua_sysfs_read(fp, kobj, buf, off, count, \
LUA_SIZE_ ## THINGY, LUA_COMMAND_ ## THINGY); \
}
#define LUA_BIN_ATTRIBUTE_RW(thingy, THINGY) \
LUA_SYSFS_W(thingy, THINGY) \
LUA_SYSFS_R(thingy, THINGY) \
static struct bin_attribute lua_ ## thingy ## _attr = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = LUA_SIZE_ ## THINGY, \
.read = lua_sysfs_read_ ## thingy, \
.write = lua_sysfs_write_ ## thingy \
};
LUA_BIN_ATTRIBUTE_RW(control, CONTROL)
static int lua_create_sysfs_attributes(struct usb_interface *intf)
{
return sysfs_create_bin_file(&intf->dev.kobj, &lua_control_attr);
}
static void lua_remove_sysfs_attributes(struct usb_interface *intf)
{
sysfs_remove_bin_file(&intf->dev.kobj, &lua_control_attr);
}
static int lua_init_lua_device_struct(struct usb_device *usb_dev,
struct lua_device *lua)
{
mutex_init(&lua->lua_lock);
return 0;
}
static int lua_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct lua_device *lua;
int retval;
lua = kzalloc(sizeof(*lua), GFP_KERNEL);
if (!lua) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, lua);
retval = lua_init_lua_device_struct(usb_dev, lua);
if (retval) {
hid_err(hdev, "couldn't init struct lua_device\n");
goto exit;
}
retval = lua_create_sysfs_attributes(intf);
if (retval) {
hid_err(hdev, "cannot create sysfs files\n");
goto exit;
}
return 0;
exit:
kfree(lua);
return retval;
}
static void lua_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct lua_device *lua;
lua_remove_sysfs_attributes(intf);
lua = hid_get_drvdata(hdev);
kfree(lua);
}
static int lua_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = lua_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void lua_remove(struct hid_device *hdev)
{
lua_remove_specials(hdev);
hid_hw_stop(hdev);
}
static const struct hid_device_id lua_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_LUA) },
{ }
};
MODULE_DEVICE_TABLE(hid, lua_devices);
static struct hid_driver lua_driver = {
.name = "lua",
.id_table = lua_devices,
.probe = lua_probe,
.remove = lua_remove
};
module_hid_driver(lua_driver);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Lua driver");
MODULE_LICENSE("GPL v2");

29
drivers/hid/hid-roccat-lua.h Executable file
View File

@ -0,0 +1,29 @@
#ifndef __HID_ROCCAT_LUA_H
#define __HID_ROCCAT_LUA_H
/*
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
enum {
LUA_SIZE_CONTROL = 8,
};
enum lua_commands {
LUA_COMMAND_CONTROL = 3,
};
struct lua_device {
struct mutex lua_lock;
};
#endif

615
drivers/hid/hid-roccat-pyra.c Executable file
View File

@ -0,0 +1,615 @@
/*
* Roccat Pyra driver for Linux
*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Roccat Pyra is a mobile gamer mouse which comes in wired and wireless
* variant. Wireless variant is not tested.
* Userland tools can be found at http://sourceforge.net/projects/roccat
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-pyra.h"
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
/* pyra_class is used for creating sysfs attributes via roccat char device */
static struct class *pyra_class;
static void profile_activated(struct pyra_device *pyra,
unsigned int new_profile)
{
if (new_profile >= ARRAY_SIZE(pyra->profile_settings))
return;
pyra->actual_profile = new_profile;
pyra->actual_cpi = pyra->profile_settings[pyra->actual_profile].y_cpi;
}
static int pyra_send_control(struct usb_device *usb_dev, int value,
enum pyra_control_requests request)
{
struct roccat_common2_control control;
if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS ||
request == PYRA_CONTROL_REQUEST_PROFILE_BUTTONS) &&
(value < 0 || value > 4))
return -EINVAL;
control.command = ROCCAT_COMMON_COMMAND_CONTROL;
control.value = value;
control.request = request;
return roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
&control, sizeof(struct roccat_common2_control));
}
static int pyra_get_profile_settings(struct usb_device *usb_dev,
struct pyra_profile_settings *buf, int number)
{
int retval;
retval = pyra_send_control(usb_dev, number,
PYRA_CONTROL_REQUEST_PROFILE_SETTINGS);
if (retval)
return retval;
return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS,
buf, PYRA_SIZE_PROFILE_SETTINGS);
}
static int pyra_get_settings(struct usb_device *usb_dev,
struct pyra_settings *buf)
{
return roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS,
buf, PYRA_SIZE_SETTINGS);
}
static int pyra_set_settings(struct usb_device *usb_dev,
struct pyra_settings const *settings)
{
return roccat_common2_send_with_status(usb_dev,
PYRA_COMMAND_SETTINGS, settings,
PYRA_SIZE_SETTINGS);
}
static ssize_t pyra_sysfs_read(struct file *fp, struct kobject *kobj,
char *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off >= real_size)
return 0;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&pyra->pyra_lock);
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
mutex_unlock(&pyra->pyra_lock);
if (retval)
return retval;
return real_size;
}
static ssize_t pyra_sysfs_write(struct file *fp, struct kobject *kobj,
void const *buf, loff_t off, size_t count,
size_t real_size, uint command)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
if (off != 0 || count != real_size)
return -EINVAL;
mutex_lock(&pyra->pyra_lock);
retval = roccat_common2_send_with_status(usb_dev, command, (void *)buf, real_size);
mutex_unlock(&pyra->pyra_lock);
if (retval)
return retval;
return real_size;
}
#define PYRA_SYSFS_W(thingy, THINGY) \
static ssize_t pyra_sysfs_write_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return pyra_sysfs_write(fp, kobj, buf, off, count, \
PYRA_SIZE_ ## THINGY, PYRA_COMMAND_ ## THINGY); \
}
#define PYRA_SYSFS_R(thingy, THINGY) \
static ssize_t pyra_sysfs_read_ ## thingy(struct file *fp, \
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return pyra_sysfs_read(fp, kobj, buf, off, count, \
PYRA_SIZE_ ## THINGY, PYRA_COMMAND_ ## THINGY); \
}
#define PYRA_SYSFS_RW(thingy, THINGY) \
PYRA_SYSFS_W(thingy, THINGY) \
PYRA_SYSFS_R(thingy, THINGY)
#define PYRA_BIN_ATTRIBUTE_RW(thingy, THINGY) \
PYRA_SYSFS_RW(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = PYRA_SIZE_ ## THINGY, \
.read = pyra_sysfs_read_ ## thingy, \
.write = pyra_sysfs_write_ ## thingy \
}
#define PYRA_BIN_ATTRIBUTE_R(thingy, THINGY) \
PYRA_SYSFS_R(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = PYRA_SIZE_ ## THINGY, \
.read = pyra_sysfs_read_ ## thingy, \
}
#define PYRA_BIN_ATTRIBUTE_W(thingy, THINGY) \
PYRA_SYSFS_W(thingy, THINGY); \
static struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = PYRA_SIZE_ ## THINGY, \
.write = pyra_sysfs_write_ ## thingy \
}
PYRA_BIN_ATTRIBUTE_W(control, CONTROL);
PYRA_BIN_ATTRIBUTE_RW(info, INFO);
PYRA_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
PYRA_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
static ssize_t pyra_sysfs_read_profilex_settings(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
ssize_t retval;
retval = pyra_send_control(usb_dev, *(uint *)(attr->private),
PYRA_CONTROL_REQUEST_PROFILE_SETTINGS);
if (retval)
return retval;
return pyra_sysfs_read(fp, kobj, buf, off, count,
PYRA_SIZE_PROFILE_SETTINGS,
PYRA_COMMAND_PROFILE_SETTINGS);
}
static ssize_t pyra_sysfs_read_profilex_buttons(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
ssize_t retval;
retval = pyra_send_control(usb_dev, *(uint *)(attr->private),
PYRA_CONTROL_REQUEST_PROFILE_BUTTONS);
if (retval)
return retval;
return pyra_sysfs_read(fp, kobj, buf, off, count,
PYRA_SIZE_PROFILE_BUTTONS,
PYRA_COMMAND_PROFILE_BUTTONS);
}
#define PROFILE_ATTR(number) \
static struct bin_attribute bin_attr_profile##number##_settings = { \
.attr = { .name = "profile" #number "_settings", .mode = 0440 }, \
.size = PYRA_SIZE_PROFILE_SETTINGS, \
.read = pyra_sysfs_read_profilex_settings, \
.private = &profile_numbers[number-1], \
}; \
static struct bin_attribute bin_attr_profile##number##_buttons = { \
.attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \
.size = PYRA_SIZE_PROFILE_BUTTONS, \
.read = pyra_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
PROFILE_ATTR(2);
PROFILE_ATTR(3);
PROFILE_ATTR(4);
PROFILE_ATTR(5);
static ssize_t pyra_sysfs_write_settings(struct file *fp,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev =
container_of(kobj, struct device, kobj)->parent->parent;
struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval = 0;
struct pyra_roccat_report roccat_report;
struct pyra_settings const *settings;
if (off != 0 || count != PYRA_SIZE_SETTINGS)
return -EINVAL;
settings = (struct pyra_settings const *)buf;
if (settings->startup_profile >= ARRAY_SIZE(pyra->profile_settings))
return -EINVAL;
mutex_lock(&pyra->pyra_lock);
retval = pyra_set_settings(usb_dev, settings);
if (retval) {
mutex_unlock(&pyra->pyra_lock);
return retval;
}
profile_activated(pyra, settings->startup_profile);
roccat_report.type = PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2;
roccat_report.value = settings->startup_profile + 1;
roccat_report.key = 0;
roccat_report_event(pyra->chrdev_minor,
(uint8_t const *)&roccat_report);
mutex_unlock(&pyra->pyra_lock);
return PYRA_SIZE_SETTINGS;
}
PYRA_SYSFS_R(settings, SETTINGS);
static struct bin_attribute bin_attr_settings =
__BIN_ATTR(settings, (S_IWUSR | S_IRUGO),
pyra_sysfs_read_settings, pyra_sysfs_write_settings,
PYRA_SIZE_SETTINGS);
static ssize_t pyra_sysfs_show_actual_cpi(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pyra_device *pyra =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_cpi);
}
static DEVICE_ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL);
static ssize_t pyra_sysfs_show_actual_profile(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pyra_device *pyra =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
struct pyra_settings settings;
mutex_lock(&pyra->pyra_lock);
roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS,
&settings, PYRA_SIZE_SETTINGS);
mutex_unlock(&pyra->pyra_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", settings.startup_profile);
}
static DEVICE_ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
static DEVICE_ATTR(startup_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
static ssize_t pyra_sysfs_show_firmware_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pyra_device *pyra;
struct usb_device *usb_dev;
struct pyra_info info;
dev = dev->parent->parent;
pyra = hid_get_drvdata(dev_get_drvdata(dev));
usb_dev = interface_to_usbdev(to_usb_interface(dev));
mutex_lock(&pyra->pyra_lock);
roccat_common2_receive(usb_dev, PYRA_COMMAND_INFO,
&info, PYRA_SIZE_INFO);
mutex_unlock(&pyra->pyra_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440, pyra_sysfs_show_firmware_version,
NULL);
static struct attribute *pyra_attrs[] = {
&dev_attr_actual_cpi.attr,
&dev_attr_actual_profile.attr,
&dev_attr_firmware_version.attr,
&dev_attr_startup_profile.attr,
NULL,
};
static struct bin_attribute *pyra_bin_attributes[] = {
&bin_attr_control,
&bin_attr_info,
&bin_attr_profile_settings,
&bin_attr_profile_buttons,
&bin_attr_settings,
&bin_attr_profile1_settings,
&bin_attr_profile2_settings,
&bin_attr_profile3_settings,
&bin_attr_profile4_settings,
&bin_attr_profile5_settings,
&bin_attr_profile1_buttons,
&bin_attr_profile2_buttons,
&bin_attr_profile3_buttons,
&bin_attr_profile4_buttons,
&bin_attr_profile5_buttons,
NULL,
};
static const struct attribute_group pyra_group = {
.attrs = pyra_attrs,
.bin_attrs = pyra_bin_attributes,
};
static const struct attribute_group *pyra_groups[] = {
&pyra_group,
NULL,
};
static int pyra_init_pyra_device_struct(struct usb_device *usb_dev,
struct pyra_device *pyra)
{
struct pyra_settings settings;
int retval, i;
mutex_init(&pyra->pyra_lock);
retval = pyra_get_settings(usb_dev, &settings);
if (retval)
return retval;
for (i = 0; i < 5; ++i) {
retval = pyra_get_profile_settings(usb_dev,
&pyra->profile_settings[i], i);
if (retval)
return retval;
}
profile_activated(pyra, settings.startup_profile);
return 0;
}
static int pyra_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct pyra_device *pyra;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
pyra = kzalloc(sizeof(*pyra), GFP_KERNEL);
if (!pyra) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, pyra);
retval = pyra_init_pyra_device_struct(usb_dev, pyra);
if (retval) {
hid_err(hdev, "couldn't init struct pyra_device\n");
goto exit_free;
}
retval = roccat_connect(pyra_class, hdev,
sizeof(struct pyra_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
pyra->chrdev_minor = retval;
pyra->roccat_claimed = 1;
}
} else {
hid_set_drvdata(hdev, NULL);
}
return 0;
exit_free:
kfree(pyra);
return retval;
}
static void pyra_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct pyra_device *pyra;
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
pyra = hid_get_drvdata(hdev);
if (pyra->roccat_claimed)
roccat_disconnect(pyra->chrdev_minor);
kfree(hid_get_drvdata(hdev));
}
}
static int pyra_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = pyra_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void pyra_remove(struct hid_device *hdev)
{
pyra_remove_specials(hdev);
hid_hw_stop(hdev);
}
static void pyra_keep_values_up_to_date(struct pyra_device *pyra,
u8 const *data)
{
struct pyra_mouse_event_button const *button_event;
switch (data[0]) {
case PYRA_MOUSE_REPORT_NUMBER_BUTTON:
button_event = (struct pyra_mouse_event_button const *)data;
switch (button_event->type) {
case PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2:
profile_activated(pyra, button_event->data1 - 1);
break;
case PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI:
pyra->actual_cpi = button_event->data1;
break;
}
break;
}
}
static void pyra_report_to_chrdev(struct pyra_device const *pyra,
u8 const *data)
{
struct pyra_roccat_report roccat_report;
struct pyra_mouse_event_button const *button_event;
if (data[0] != PYRA_MOUSE_REPORT_NUMBER_BUTTON)
return;
button_event = (struct pyra_mouse_event_button const *)data;
switch (button_event->type) {
case PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2:
case PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI:
roccat_report.type = button_event->type;
roccat_report.value = button_event->data1;
roccat_report.key = 0;
roccat_report_event(pyra->chrdev_minor,
(uint8_t const *)&roccat_report);
break;
case PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO:
case PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT:
case PYRA_MOUSE_EVENT_BUTTON_TYPE_QUICKLAUNCH:
if (button_event->data2 == PYRA_MOUSE_EVENT_BUTTON_PRESS) {
roccat_report.type = button_event->type;
roccat_report.key = button_event->data1;
/*
* pyra reports profile numbers with range 1-5.
* Keeping this behaviour.
*/
roccat_report.value = pyra->actual_profile + 1;
roccat_report_event(pyra->chrdev_minor,
(uint8_t const *)&roccat_report);
}
break;
}
}
static int pyra_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct pyra_device *pyra = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return 0;
if (pyra == NULL)
return 0;
pyra_keep_values_up_to_date(pyra, data);
if (pyra->roccat_claimed)
pyra_report_to_chrdev(pyra, data);
return 0;
}
static const struct hid_device_id pyra_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT,
USB_DEVICE_ID_ROCCAT_PYRA_WIRED) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT,
USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS) },
{ }
};
MODULE_DEVICE_TABLE(hid, pyra_devices);
static struct hid_driver pyra_driver = {
.name = "pyra",
.id_table = pyra_devices,
.probe = pyra_probe,
.remove = pyra_remove,
.raw_event = pyra_raw_event
};
static int __init pyra_init(void)
{
int retval;
/* class name has to be same as driver name */
pyra_class = class_create(THIS_MODULE, "pyra");
if (IS_ERR(pyra_class))
return PTR_ERR(pyra_class);
pyra_class->dev_groups = pyra_groups;
retval = hid_register_driver(&pyra_driver);
if (retval)
class_destroy(pyra_class);
return retval;
}
static void __exit pyra_exit(void)
{
hid_unregister_driver(&pyra_driver);
class_destroy(pyra_class);
}
module_init(pyra_init);
module_exit(pyra_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Pyra driver");
MODULE_LICENSE("GPL v2");

152
drivers/hid/hid-roccat-pyra.h Executable file
View File

@ -0,0 +1,152 @@
#ifndef __HID_ROCCAT_PYRA_H
#define __HID_ROCCAT_PYRA_H
/*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
enum {
PYRA_SIZE_CONTROL = 0x03,
PYRA_SIZE_INFO = 0x06,
PYRA_SIZE_PROFILE_SETTINGS = 0x0d,
PYRA_SIZE_PROFILE_BUTTONS = 0x13,
PYRA_SIZE_SETTINGS = 0x03,
};
enum pyra_control_requests {
PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20
};
struct pyra_settings {
uint8_t command; /* PYRA_COMMAND_SETTINGS */
uint8_t size; /* always 3 */
uint8_t startup_profile; /* Range 0-4! */
} __attribute__ ((__packed__));
struct pyra_profile_settings {
uint8_t command; /* PYRA_COMMAND_PROFILE_SETTINGS */
uint8_t size; /* always 0xd */
uint8_t number; /* Range 0-4 */
uint8_t xysync;
uint8_t x_sensitivity; /* 0x1-0xa */
uint8_t y_sensitivity;
uint8_t x_cpi; /* unused */
uint8_t y_cpi; /* this value is for x and y */
uint8_t lightswitch; /* 0 = off, 1 = on */
uint8_t light_effect;
uint8_t handedness;
uint16_t checksum; /* byte sum */
} __attribute__ ((__packed__));
struct pyra_info {
uint8_t command; /* PYRA_COMMAND_INFO */
uint8_t size; /* always 6 */
uint8_t firmware_version;
uint8_t unknown1; /* always 0 */
uint8_t unknown2; /* always 1 */
uint8_t unknown3; /* always 0 */
} __attribute__ ((__packed__));
enum pyra_commands {
PYRA_COMMAND_CONTROL = 0x4,
PYRA_COMMAND_SETTINGS = 0x5,
PYRA_COMMAND_PROFILE_SETTINGS = 0x6,
PYRA_COMMAND_PROFILE_BUTTONS = 0x7,
PYRA_COMMAND_INFO = 0x9,
PYRA_COMMAND_B = 0xb
};
enum pyra_mouse_report_numbers {
PYRA_MOUSE_REPORT_NUMBER_HID = 1,
PYRA_MOUSE_REPORT_NUMBER_AUDIO = 2,
PYRA_MOUSE_REPORT_NUMBER_BUTTON = 3,
};
struct pyra_mouse_event_button {
uint8_t report_number; /* always 3 */
uint8_t unknown; /* always 0 */
uint8_t type;
uint8_t data1;
uint8_t data2;
} __attribute__ ((__packed__));
struct pyra_mouse_event_audio {
uint8_t report_number; /* always 2 */
uint8_t type;
uint8_t unused; /* always 0 */
} __attribute__ ((__packed__));
/* hid audio controls */
enum pyra_mouse_event_audio_types {
PYRA_MOUSE_EVENT_AUDIO_TYPE_MUTE = 0xe2,
PYRA_MOUSE_EVENT_AUDIO_TYPE_VOLUME_UP = 0xe9,
PYRA_MOUSE_EVENT_AUDIO_TYPE_VOLUME_DOWN = 0xea,
};
enum pyra_mouse_event_button_types {
/*
* Mouse sends tilt events on report_number 1 and 3
* Tilt events are sent repeatedly with 0.94s between first and second
* event and 0.22s on subsequent
*/
PYRA_MOUSE_EVENT_BUTTON_TYPE_TILT = 0x10,
/*
* These are sent sequentially
* data1 contains new profile number in range 1-5
*/
PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_1 = 0x20,
PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2 = 0x30,
/*
* data1 = button_number (rmp index)
* data2 = pressed/released
*/
PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO = 0x40,
PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT = 0x50,
/*
* data1 = button_number (rmp index)
*/
PYRA_MOUSE_EVENT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
/* data1 = new cpi */
PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI = 0xb0,
/* data1 and data2 = new sensitivity */
PYRA_MOUSE_EVENT_BUTTON_TYPE_SENSITIVITY = 0xc0,
PYRA_MOUSE_EVENT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
};
enum {
PYRA_MOUSE_EVENT_BUTTON_PRESS = 0,
PYRA_MOUSE_EVENT_BUTTON_RELEASE = 1,
};
struct pyra_roccat_report {
uint8_t type;
uint8_t value;
uint8_t key;
} __attribute__ ((__packed__));
struct pyra_device {
int actual_profile;
int actual_cpi;
int roccat_claimed;
int chrdev_minor;
struct mutex pyra_lock;
struct pyra_profile_settings profile_settings[5];
};
#endif

241
drivers/hid/hid-roccat-ryos.c Executable file
View File

@ -0,0 +1,241 @@
/*
* Roccat Ryos driver for Linux
*
* Copyright (c) 2013 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
enum {
RYOS_REPORT_NUMBER_SPECIAL = 3,
RYOS_USB_INTERFACE_PROTOCOL = 0,
};
struct ryos_report_special {
uint8_t number; /* RYOS_REPORT_NUMBER_SPECIAL */
uint8_t data[4];
} __packed;
static struct class *ryos_class;
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x05, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_primary, 0x06, 0x7d);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_function, 0x07, 0x5f);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_macro, 0x08, 0x23);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_thumbster, 0x09, 0x17);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_extra, 0x0a, 0x08);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_easyzone, 0x0b, 0x126);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(key_mask, 0x0c, 0x06);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(light, 0x0d, 0x10);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(macro, 0x0e, 0x7d2);
ROCCAT_COMMON2_BIN_ATTRIBUTE_R(info, 0x0f, 0x08);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(reset, 0x11, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(light_control, 0x13, 0x08);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(talk, 0x16, 0x10);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(stored_lights, 0x17, 0x0566);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(custom_lights, 0x18, 0x14);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(light_macro, 0x19, 0x07d2);
static struct bin_attribute *ryos_bin_attrs[] = {
&bin_attr_control,
&bin_attr_profile,
&bin_attr_keys_primary,
&bin_attr_keys_function,
&bin_attr_keys_macro,
&bin_attr_keys_thumbster,
&bin_attr_keys_extra,
&bin_attr_keys_easyzone,
&bin_attr_key_mask,
&bin_attr_light,
&bin_attr_macro,
&bin_attr_info,
&bin_attr_reset,
&bin_attr_light_control,
&bin_attr_talk,
&bin_attr_stored_lights,
&bin_attr_custom_lights,
&bin_attr_light_macro,
NULL,
};
static const struct attribute_group ryos_group = {
.bin_attrs = ryos_bin_attrs,
};
static const struct attribute_group *ryos_groups[] = {
&ryos_group,
NULL,
};
static int ryos_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct roccat_common2_device *ryos;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= RYOS_USB_INTERFACE_PROTOCOL) {
hid_set_drvdata(hdev, NULL);
return 0;
}
ryos = kzalloc(sizeof(*ryos), GFP_KERNEL);
if (!ryos) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, ryos);
retval = roccat_common2_device_init_struct(usb_dev, ryos);
if (retval) {
hid_err(hdev, "couldn't init Ryos device\n");
goto exit_free;
}
retval = roccat_connect(ryos_class, hdev,
sizeof(struct ryos_report_special));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
ryos->chrdev_minor = retval;
ryos->roccat_claimed = 1;
}
return 0;
exit_free:
kfree(ryos);
return retval;
}
static void ryos_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct roccat_common2_device *ryos;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= RYOS_USB_INTERFACE_PROTOCOL)
return;
ryos = hid_get_drvdata(hdev);
if (ryos->roccat_claimed)
roccat_disconnect(ryos->chrdev_minor);
kfree(ryos);
}
static int ryos_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = ryos_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void ryos_remove(struct hid_device *hdev)
{
ryos_remove_specials(hdev);
hid_hw_stop(hdev);
}
static int ryos_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct roccat_common2_device *ryos = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= RYOS_USB_INTERFACE_PROTOCOL)
return 0;
if (data[0] != RYOS_REPORT_NUMBER_SPECIAL)
return 0;
if (ryos != NULL && ryos->roccat_claimed)
roccat_report_event(ryos->chrdev_minor, data);
return 0;
}
static const struct hid_device_id ryos_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_GLOW) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO) },
{ }
};
MODULE_DEVICE_TABLE(hid, ryos_devices);
static struct hid_driver ryos_driver = {
.name = "ryos",
.id_table = ryos_devices,
.probe = ryos_probe,
.remove = ryos_remove,
.raw_event = ryos_raw_event
};
static int __init ryos_init(void)
{
int retval;
ryos_class = class_create(THIS_MODULE, "ryos");
if (IS_ERR(ryos_class))
return PTR_ERR(ryos_class);
ryos_class->dev_groups = ryos_groups;
retval = hid_register_driver(&ryos_driver);
if (retval)
class_destroy(ryos_class);
return retval;
}
static void __exit ryos_exit(void)
{
hid_unregister_driver(&ryos_driver);
class_destroy(ryos_class);
}
module_init(ryos_init);
module_exit(ryos_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Ryos MK/Glow/Pro driver");
MODULE_LICENSE("GPL v2");

229
drivers/hid/hid-roccat-savu.c Executable file
View File

@ -0,0 +1,229 @@
/*
* Roccat Savu driver for Linux
*
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/* Roccat Savu is a gamer mouse with macro keys that can be configured in
* 5 profiles.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hid-roccat.h>
#include "hid-ids.h"
#include "hid-roccat-common.h"
#include "hid-roccat-savu.h"
static struct class *savu_class;
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x4, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x5, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(general, 0x6, 0x10);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(buttons, 0x7, 0x2f);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(macro, 0x8, 0x0823);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(info, 0x9, 0x08);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0xc, 0x04);
static struct bin_attribute *savu_bin_attrs[] = {
&bin_attr_control,
&bin_attr_profile,
&bin_attr_general,
&bin_attr_buttons,
&bin_attr_macro,
&bin_attr_info,
&bin_attr_sensor,
NULL,
};
static const struct attribute_group savu_group = {
.bin_attrs = savu_bin_attrs,
};
static const struct attribute_group *savu_groups[] = {
&savu_group,
NULL,
};
static int savu_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct roccat_common2_device *savu;
int retval;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE) {
hid_set_drvdata(hdev, NULL);
return 0;
}
savu = kzalloc(sizeof(*savu), GFP_KERNEL);
if (!savu) {
hid_err(hdev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, savu);
retval = roccat_common2_device_init_struct(usb_dev, savu);
if (retval) {
hid_err(hdev, "couldn't init Savu device\n");
goto exit_free;
}
retval = roccat_connect(savu_class, hdev,
sizeof(struct savu_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
savu->chrdev_minor = retval;
savu->roccat_claimed = 1;
}
return 0;
exit_free:
kfree(savu);
return retval;
}
static void savu_remove_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct roccat_common2_device *savu;
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return;
savu = hid_get_drvdata(hdev);
if (savu->roccat_claimed)
roccat_disconnect(savu->chrdev_minor);
kfree(savu);
}
static int savu_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int retval;
retval = hid_parse(hdev);
if (retval) {
hid_err(hdev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
hid_err(hdev, "hw start failed\n");
goto exit;
}
retval = savu_init_specials(hdev);
if (retval) {
hid_err(hdev, "couldn't install mouse\n");
goto exit_stop;
}
return 0;
exit_stop:
hid_hw_stop(hdev);
exit:
return retval;
}
static void savu_remove(struct hid_device *hdev)
{
savu_remove_specials(hdev);
hid_hw_stop(hdev);
}
static void savu_report_to_chrdev(struct roccat_common2_device const *savu,
u8 const *data)
{
struct savu_roccat_report roccat_report;
struct savu_mouse_report_special const *special_report;
if (data[0] != SAVU_MOUSE_REPORT_NUMBER_SPECIAL)
return;
special_report = (struct savu_mouse_report_special const *)data;
roccat_report.type = special_report->type;
roccat_report.data[0] = special_report->data[0];
roccat_report.data[1] = special_report->data[1];
roccat_report_event(savu->chrdev_minor,
(uint8_t const *)&roccat_report);
}
static int savu_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct roccat_common2_device *savu = hid_get_drvdata(hdev);
if (intf->cur_altsetting->desc.bInterfaceProtocol
!= USB_INTERFACE_PROTOCOL_MOUSE)
return 0;
if (savu == NULL)
return 0;
if (savu->roccat_claimed)
savu_report_to_chrdev(savu, data);
return 0;
}
static const struct hid_device_id savu_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
{ }
};
MODULE_DEVICE_TABLE(hid, savu_devices);
static struct hid_driver savu_driver = {
.name = "savu",
.id_table = savu_devices,
.probe = savu_probe,
.remove = savu_remove,
.raw_event = savu_raw_event
};
static int __init savu_init(void)
{
int retval;
savu_class = class_create(THIS_MODULE, "savu");
if (IS_ERR(savu_class))
return PTR_ERR(savu_class);
savu_class->dev_groups = savu_groups;
retval = hid_register_driver(&savu_driver);
if (retval)
class_destroy(savu_class);
return retval;
}
static void __exit savu_exit(void)
{
hid_unregister_driver(&savu_driver);
class_destroy(savu_class);
}
module_init(savu_init);
module_exit(savu_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Savu driver");
MODULE_LICENSE("GPL v2");

55
drivers/hid/hid-roccat-savu.h Executable file
View File

@ -0,0 +1,55 @@
#ifndef __HID_ROCCAT_SAVU_H
#define __HID_ROCCAT_SAVU_H
/*
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/types.h>
struct savu_mouse_report_special {
uint8_t report_number; /* always 3 */
uint8_t zero;
uint8_t type;
uint8_t data[2];
} __packed;
enum {
SAVU_MOUSE_REPORT_NUMBER_SPECIAL = 3,
};
enum savu_mouse_report_button_types {
/* data1 = new profile range 1-5 */
SAVU_MOUSE_REPORT_BUTTON_TYPE_PROFILE = 0x20,
/* data1 = button number range 1-24; data2 = action */
SAVU_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
/* data1 = button number range 1-24; data2 = action */
SAVU_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
/* data1 = setting number range 1-5 */
SAVU_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
/* data1 and data2 = range 0x1-0xb */
SAVU_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
/* data1 = 22 = next track...
* data2 = action
*/
SAVU_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
};
struct savu_roccat_report {
uint8_t type;
uint8_t data[2];
} __packed;
#endif

461
drivers/hid/hid-roccat.c Executable file
View File

@ -0,0 +1,461 @@
/*
* Roccat driver for Linux
*
* Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
/*
* Module roccat is a char device used to report special events of roccat
* hardware to userland. These events include requests for on-screen-display of
* profile or dpi settings or requests for execution of macro sequences that are
* not stored in device. The information in these events depends on hid device
* implementation and contains data that is not available in a single hid event
* or else hidraw could have been used.
* It is inspired by hidraw, but uses only one circular buffer for all readers.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/hid-roccat.h>
#include <linux/module.h>
#define ROCCAT_FIRST_MINOR 0
#define ROCCAT_MAX_DEVICES 8
/* should be a power of 2 for performance reason */
#define ROCCAT_CBUF_SIZE 16
struct roccat_report {
uint8_t *value;
};
struct roccat_device {
unsigned int minor;
int report_size;
int open;
int exist;
wait_queue_head_t wait;
struct device *dev;
struct hid_device *hid;
struct list_head readers;
/* protects modifications of readers list */
struct mutex readers_lock;
/*
* circular_buffer has one writer and multiple readers with their own
* read pointers
*/
struct roccat_report cbuf[ROCCAT_CBUF_SIZE];
int cbuf_end;
struct mutex cbuf_lock;
};
struct roccat_reader {
struct list_head node;
struct roccat_device *device;
int cbuf_start;
};
static int roccat_major;
static struct cdev roccat_cdev;
static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
/* protects modifications of devices array */
static DEFINE_MUTEX(devices_lock);
static ssize_t roccat_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct roccat_reader *reader = file->private_data;
struct roccat_device *device = reader->device;
struct roccat_report *report;
ssize_t retval = 0, len;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&device->cbuf_lock);
/* no data? */
if (reader->cbuf_start == device->cbuf_end) {
add_wait_queue(&device->wait, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/* wait for data */
while (reader->cbuf_start == device->cbuf_end) {
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (!device->exist) {
retval = -EIO;
break;
}
mutex_unlock(&device->cbuf_lock);
schedule();
mutex_lock(&device->cbuf_lock);
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&device->wait, &wait);
}
/* here we either have data or a reason to return if retval is set */
if (retval)
goto exit_unlock;
report = &device->cbuf[reader->cbuf_start];
/*
* If report is larger than requested amount of data, rest of report
* is lost!
*/
len = device->report_size > count ? count : device->report_size;
if (copy_to_user(buffer, report->value, len)) {
retval = -EFAULT;
goto exit_unlock;
}
retval += len;
reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE;
exit_unlock:
mutex_unlock(&device->cbuf_lock);
return retval;
}
static unsigned int roccat_poll(struct file *file, poll_table *wait)
{
struct roccat_reader *reader = file->private_data;
poll_wait(file, &reader->device->wait, wait);
if (reader->cbuf_start != reader->device->cbuf_end)
return POLLIN | POLLRDNORM;
if (!reader->device->exist)
return POLLERR | POLLHUP;
return 0;
}
static int roccat_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct roccat_reader *reader;
struct roccat_device *device;
int error = 0;
reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL);
if (!reader)
return -ENOMEM;
mutex_lock(&devices_lock);
device = devices[minor];
if (!device) {
pr_emerg("roccat device with minor %d doesn't exist\n", minor);
error = -ENODEV;
goto exit_err_devices;
}
mutex_lock(&device->readers_lock);
if (!device->open++) {
/* power on device on adding first reader */
error = hid_hw_power(device->hid, PM_HINT_FULLON);
if (error < 0) {
--device->open;
goto exit_err_readers;
}
error = hid_hw_open(device->hid);
if (error < 0) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
--device->open;
goto exit_err_readers;
}
}
reader->device = device;
/* new reader doesn't get old events */
reader->cbuf_start = device->cbuf_end;
list_add_tail(&reader->node, &device->readers);
file->private_data = reader;
exit_err_readers:
mutex_unlock(&device->readers_lock);
exit_err_devices:
mutex_unlock(&devices_lock);
if (error)
kfree(reader);
return error;
}
static int roccat_release(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct roccat_reader *reader = file->private_data;
struct roccat_device *device;
mutex_lock(&devices_lock);
device = devices[minor];
if (!device) {
mutex_unlock(&devices_lock);
pr_emerg("roccat device with minor %d doesn't exist\n", minor);
return -ENODEV;
}
mutex_lock(&device->readers_lock);
list_del(&reader->node);
mutex_unlock(&device->readers_lock);
kfree(reader);
if (!--device->open) {
/* removing last reader */
if (device->exist) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
hid_hw_close(device->hid);
} else {
kfree(device);
}
}
mutex_unlock(&devices_lock);
return 0;
}
/*
* roccat_report_event() - output data to readers
* @minor: minor device number returned by roccat_connect()
* @data: pointer to data
*
* Return value is zero on success, a negative error code on failure.
*
* This is called from interrupt handler.
*/
int roccat_report_event(int minor, u8 const *data)
{
struct roccat_device *device;
struct roccat_reader *reader;
struct roccat_report *report;
uint8_t *new_value;
device = devices[minor];
new_value = kmemdup(data, device->report_size, GFP_ATOMIC);
if (!new_value)
return -ENOMEM;
report = &device->cbuf[device->cbuf_end];
/* passing NULL is safe */
kfree(report->value);
report->value = new_value;
device->cbuf_end = (device->cbuf_end + 1) % ROCCAT_CBUF_SIZE;
list_for_each_entry(reader, &device->readers, node) {
/*
* As we already inserted one element, the buffer can't be
* empty. If start and end are equal, buffer is full and we
* increase start, so that slow reader misses one event, but
* gets the newer ones in the right order.
*/
if (reader->cbuf_start == device->cbuf_end)
reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE;
}
wake_up_interruptible(&device->wait);
return 0;
}
EXPORT_SYMBOL_GPL(roccat_report_event);
/*
* roccat_connect() - create a char device for special event output
* @class: the class thats used to create the device. Meant to hold device
* specific sysfs attributes.
* @hid: the hid device the char device should be connected to.
* @report_size: size of reports
*
* Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on
* success, a negative error code on failure.
*/
int roccat_connect(struct class *klass, struct hid_device *hid, int report_size)
{
unsigned int minor;
struct roccat_device *device;
int temp;
device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL);
if (!device)
return -ENOMEM;
mutex_lock(&devices_lock);
for (minor = 0; minor < ROCCAT_MAX_DEVICES; ++minor) {
if (devices[minor])
continue;
break;
}
if (minor < ROCCAT_MAX_DEVICES) {
devices[minor] = device;
} else {
mutex_unlock(&devices_lock);
kfree(device);
return -EINVAL;
}
device->dev = device_create(klass, &hid->dev,
MKDEV(roccat_major, minor), NULL,
"%s%s%d", "roccat", hid->driver->name, minor);
if (IS_ERR(device->dev)) {
devices[minor] = NULL;
mutex_unlock(&devices_lock);
temp = PTR_ERR(device->dev);
kfree(device);
return temp;
}
mutex_unlock(&devices_lock);
init_waitqueue_head(&device->wait);
INIT_LIST_HEAD(&device->readers);
mutex_init(&device->readers_lock);
mutex_init(&device->cbuf_lock);
device->minor = minor;
device->hid = hid;
device->exist = 1;
device->cbuf_end = 0;
device->report_size = report_size;
return minor;
}
EXPORT_SYMBOL_GPL(roccat_connect);
/* roccat_disconnect() - remove char device from hid device
* @minor: the minor device number returned by roccat_connect()
*/
void roccat_disconnect(int minor)
{
struct roccat_device *device;
mutex_lock(&devices_lock);
device = devices[minor];
mutex_unlock(&devices_lock);
device->exist = 0; /* TODO exist maybe not needed */
device_destroy(device->dev->class, MKDEV(roccat_major, minor));
mutex_lock(&devices_lock);
devices[minor] = NULL;
mutex_unlock(&devices_lock);
if (device->open) {
hid_hw_close(device->hid);
wake_up_interruptible(&device->wait);
} else {
kfree(device);
}
}
EXPORT_SYMBOL_GPL(roccat_disconnect);
static long roccat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(file);
struct roccat_device *device;
unsigned int minor = iminor(inode);
long retval = 0;
mutex_lock(&devices_lock);
device = devices[minor];
if (!device) {
retval = -ENODEV;
goto out;
}
switch (cmd) {
case ROCCATIOCGREPSIZE:
if (put_user(device->report_size, (int __user *)arg))
retval = -EFAULT;
break;
default:
retval = -ENOTTY;
}
out:
mutex_unlock(&devices_lock);
return retval;
}
static const struct file_operations roccat_ops = {
.owner = THIS_MODULE,
.read = roccat_read,
.poll = roccat_poll,
.open = roccat_open,
.release = roccat_release,
.llseek = noop_llseek,
.unlocked_ioctl = roccat_ioctl,
};
static int __init roccat_init(void)
{
int retval;
dev_t dev_id;
retval = alloc_chrdev_region(&dev_id, ROCCAT_FIRST_MINOR,
ROCCAT_MAX_DEVICES, "roccat");
roccat_major = MAJOR(dev_id);
if (retval < 0) {
pr_warn("can't get major number\n");
goto error;
}
cdev_init(&roccat_cdev, &roccat_ops);
retval = cdev_add(&roccat_cdev, dev_id, ROCCAT_MAX_DEVICES);
if (retval < 0) {
pr_warn("cannot add cdev\n");
goto cleanup_alloc_chrdev_region;
}
return 0;
cleanup_alloc_chrdev_region:
unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES);
error:
return retval;
}
static void __exit roccat_exit(void)
{
dev_t dev_id = MKDEV(roccat_major, 0);
cdev_del(&roccat_cdev);
unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES);
}
module_init(roccat_init);
module_exit(roccat_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat char device");
MODULE_LICENSE("GPL v2");

205
drivers/hid/hid-saitek.c Executable file
View File

@ -0,0 +1,205 @@
/*
* HID driver for Saitek devices.
*
* PS1000 (USB gamepad):
* Fixes the HID report descriptor by removing a non-existent axis and
* clearing the constant bit on the input reports for buttons and d-pad.
* (This module is based on "hid-ortek".)
* Copyright (c) 2012 Andreas Hübner
*
* R.A.T.7, R.A.T.9, M.M.O.7 (USB gaming mice):
* Fixes the mode button which cycles through three constantly pressed
* buttons. All three press events are mapped to one button and the
* missing release event is generated immediately.
*
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include "hid-ids.h"
#define SAITEK_FIX_PS1000 0x0001
#define SAITEK_RELEASE_MODE_RAT7 0x0002
#define SAITEK_RELEASE_MODE_MMO7 0x0004
struct saitek_sc {
unsigned long quirks;
int mode;
};
static int saitek_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
unsigned long quirks = id->driver_data;
struct saitek_sc *ssc;
int ret;
ssc = devm_kzalloc(&hdev->dev, sizeof(*ssc), GFP_KERNEL);
if (ssc == NULL) {
hid_err(hdev, "can't alloc saitek descriptor\n");
return -ENOMEM;
}
ssc->quirks = quirks;
ssc->mode = -1;
hid_set_drvdata(hdev, ssc);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
return 0;
}
static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct saitek_sc *ssc = hid_get_drvdata(hdev);
if ((ssc->quirks & SAITEK_FIX_PS1000) && *rsize == 137 &&
rdesc[20] == 0x09 && rdesc[21] == 0x33 &&
rdesc[94] == 0x81 && rdesc[95] == 0x03 &&
rdesc[110] == 0x81 && rdesc[111] == 0x03) {
hid_info(hdev, "Fixing up Saitek PS1000 report descriptor\n");
/* convert spurious axis to a "noop" Logical Minimum (0) */
rdesc[20] = 0x15;
rdesc[21] = 0x00;
/* clear constant bit on buttons and d-pad */
rdesc[95] = 0x02;
rdesc[111] = 0x02;
}
return rdesc;
}
static int saitek_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *raw_data, int size)
{
struct saitek_sc *ssc = hid_get_drvdata(hdev);
if (ssc->quirks & SAITEK_RELEASE_MODE_RAT7 && size == 7) {
/* R.A.T.7 uses bits 13, 14, 15 for the mode */
int mode = -1;
if (raw_data[1] & 0x01)
mode = 0;
else if (raw_data[1] & 0x02)
mode = 1;
else if (raw_data[1] & 0x04)
mode = 2;
/* clear mode bits */
raw_data[1] &= ~0x07;
if (mode != ssc->mode) {
hid_dbg(hdev, "entered mode %d\n", mode);
if (ssc->mode != -1) {
/* use bit 13 as the mode button */
raw_data[1] |= 0x04;
}
ssc->mode = mode;
}
} else if (ssc->quirks & SAITEK_RELEASE_MODE_MMO7 && size == 8) {
/* M.M.O.7 uses bits 8, 22, 23 for the mode */
int mode = -1;
if (raw_data[1] & 0x80)
mode = 0;
else if (raw_data[2] & 0x01)
mode = 1;
else if (raw_data[2] & 0x02)
mode = 2;
/* clear mode bits */
raw_data[1] &= ~0x80;
raw_data[2] &= ~0x03;
if (mode != ssc->mode) {
hid_dbg(hdev, "entered mode %d\n", mode);
if (ssc->mode != -1) {
/* use bit 8 as the mode button, bits 22
* and 23 do not represent buttons
* according to the HID report descriptor
*/
raw_data[1] |= 0x80;
}
ssc->mode = mode;
}
}
return 0;
}
static int saitek_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct saitek_sc *ssc = hid_get_drvdata(hdev);
struct input_dev *input = field->hidinput->input;
if (usage->type == EV_KEY && value &&
(((ssc->quirks & SAITEK_RELEASE_MODE_RAT7) &&
usage->code - BTN_MOUSE == 10) ||
((ssc->quirks & SAITEK_RELEASE_MODE_MMO7) &&
usage->code - BTN_MOUSE == 15))) {
input_report_key(input, usage->code, 1);
/* report missing release event */
input_report_key(input, usage->code, 0);
return 1;
}
return 0;
}
static const struct hid_device_id saitek_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000),
.driver_data = SAITEK_FIX_PS1000 },
{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT5),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7),
.driver_data = SAITEK_RELEASE_MODE_MMO7 },
{ }
};
MODULE_DEVICE_TABLE(hid, saitek_devices);
static struct hid_driver saitek_driver = {
.name = "saitek",
.id_table = saitek_devices,
.probe = saitek_probe,
.report_fixup = saitek_report_fixup,
.raw_event = saitek_raw_event,
.event = saitek_event,
};
module_hid_driver(saitek_driver);
MODULE_LICENSE("GPL");

201
drivers/hid/hid-samsung.c Executable file
View File

@ -0,0 +1,201 @@
/*
* HID driver for some samsung "special" devices
*
* Copyright (c) 1999 Andreas Gal
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby
* Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk>
*
*
* This driver supports several HID devices:
*
* [0419:0001] Samsung IrDA remote controller (reports as Cypress USB Mouse).
* various hid report fixups for different variants.
*
* [0419:0600] Creative Desktop Wireless 6000 keyboard/mouse combo
* several key mappings used from the consumer usage page
* deviate from the USB HUT 1.12 standard.
*
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
/*
* There are several variants for 0419:0001:
*
* 1. 184 byte report descriptor
* Vendor specific report #4 has a size of 48 bit,
* and therefore is not accepted when inspecting the descriptors.
* As a workaround we reinterpret the report as:
* Variable type, count 6, size 8 bit, log. maximum 255
* The burden to reconstruct the data is moved into user space.
*
* 2. 203 byte report descriptor
* Report #4 has an array field with logical range 0..18 instead of 1..15.
*
* 3. 135 byte report descriptor
* Report #4 has an array field with logical range 0..17 instead of 1..14.
*
* 4. 171 byte report descriptor
* Report #3 has an array field with logical range 0..1 instead of 1..3.
*/
static inline void samsung_irda_dev_trace(struct hid_device *hdev,
unsigned int rsize)
{
hid_info(hdev, "fixing up Samsung IrDA %d byte report descriptor\n",
rsize);
}
static __u8 *samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 &&
rdesc[177] == 0x75 && rdesc[178] == 0x30 &&
rdesc[179] == 0x95 && rdesc[180] == 0x01 &&
rdesc[182] == 0x40) {
samsung_irda_dev_trace(hdev, 184);
rdesc[176] = 0xff;
rdesc[178] = 0x08;
rdesc[180] = 0x06;
rdesc[182] = 0x42;
} else
if (*rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 &&
rdesc[194] == 0x25 && rdesc[195] == 0x12) {
samsung_irda_dev_trace(hdev, 203);
rdesc[193] = 0x1;
rdesc[195] = 0xf;
} else
if (*rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 &&
rdesc[126] == 0x25 && rdesc[127] == 0x11) {
samsung_irda_dev_trace(hdev, 135);
rdesc[125] = 0x1;
rdesc[127] = 0xe;
} else
if (*rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 &&
rdesc[162] == 0x25 && rdesc[163] == 0x01) {
samsung_irda_dev_trace(hdev, 171);
rdesc[161] = 0x1;
rdesc[163] = 0x3;
}
return rdesc;
}
#define samsung_kbd_mouse_map_key_clear(c) \
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
if (1 != ifnum || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE))
return 0;
dbg_hid("samsung wireless keyboard/mouse input mapping event [0x%x]\n",
usage->hid & HID_USAGE);
switch (usage->hid & HID_USAGE) {
/* report 2 */
case 0x183: samsung_kbd_mouse_map_key_clear(KEY_MEDIA); break;
case 0x195: samsung_kbd_mouse_map_key_clear(KEY_EMAIL); break;
case 0x196: samsung_kbd_mouse_map_key_clear(KEY_CALC); break;
case 0x197: samsung_kbd_mouse_map_key_clear(KEY_COMPUTER); break;
case 0x22b: samsung_kbd_mouse_map_key_clear(KEY_SEARCH); break;
case 0x22c: samsung_kbd_mouse_map_key_clear(KEY_WWW); break;
case 0x22d: samsung_kbd_mouse_map_key_clear(KEY_BACK); break;
case 0x22e: samsung_kbd_mouse_map_key_clear(KEY_FORWARD); break;
case 0x22f: samsung_kbd_mouse_map_key_clear(KEY_FAVORITES); break;
case 0x230: samsung_kbd_mouse_map_key_clear(KEY_REFRESH); break;
case 0x231: samsung_kbd_mouse_map_key_clear(KEY_STOP); break;
default:
return 0;
}
return 1;
}
static __u8 *samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product)
rdesc = samsung_irda_report_fixup(hdev, rdesc, rsize);
return rdesc;
}
static int samsung_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
int ret = 0;
if (USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE == hdev->product)
ret = samsung_kbd_mouse_input_mapping(hdev,
hi, field, usage, bit, max);
return ret;
}
static int samsung_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
unsigned int cmask = HID_CONNECT_DEFAULT;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) {
if (hdev->rsize == 184) {
/* disable hidinput, force hiddev */
cmask = (cmask & ~HID_CONNECT_HIDINPUT) |
HID_CONNECT_HIDDEV_FORCE;
}
}
ret = hid_hw_start(hdev, cmask);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
return 0;
err_free:
return ret;
}
static const struct hid_device_id samsung_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
{ }
};
MODULE_DEVICE_TABLE(hid, samsung_devices);
static struct hid_driver samsung_driver = {
.name = "samsung",
.id_table = samsung_devices,
.report_fixup = samsung_report_fixup,
.input_mapping = samsung_input_mapping,
.probe = samsung_probe,
};
module_hid_driver(samsung_driver);
MODULE_LICENSE("GPL");

849
drivers/hid/hid-sensor-custom.c Executable file
View File

@ -0,0 +1,849 @@
/*
* hid-sensor-custom.c
* Copyright (c) 2015, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/bsearch.h>
#include <linux/platform_device.h>
#include <linux/hid-sensor-hub.h>
#define HID_CUSTOM_NAME_LENGTH 64
#define HID_CUSTOM_MAX_CORE_ATTRS 10
#define HID_CUSTOM_TOTAL_ATTRS (HID_CUSTOM_MAX_CORE_ATTRS + 1)
#define HID_CUSTOM_FIFO_SIZE 4096
#define HID_CUSTOM_MAX_FEATURE_BYTES 64
struct hid_sensor_custom_field {
int report_id;
char group_name[HID_CUSTOM_NAME_LENGTH];
struct hid_sensor_hub_attribute_info attribute;
struct device_attribute sd_attrs[HID_CUSTOM_MAX_CORE_ATTRS];
char attr_name[HID_CUSTOM_TOTAL_ATTRS][HID_CUSTOM_NAME_LENGTH];
struct attribute *attrs[HID_CUSTOM_TOTAL_ATTRS];
struct attribute_group hid_custom_attribute_group;
};
struct hid_sensor_custom {
struct mutex mutex;
struct platform_device *pdev;
struct hid_sensor_hub_device *hsdev;
struct hid_sensor_hub_callbacks callbacks;
int sensor_field_count;
struct hid_sensor_custom_field *fields;
int input_field_count;
int input_report_size;
int input_report_recd_size;
bool input_skip_sample;
bool enable;
struct hid_sensor_custom_field *power_state;
struct hid_sensor_custom_field *report_state;
struct miscdevice custom_dev;
struct kfifo data_fifo;
unsigned long misc_opened;
wait_queue_head_t wait;
};
/* Header for each sample to user space via dev interface */
struct hid_sensor_sample {
u32 usage_id;
u64 timestamp;
u32 raw_len;
} __packed;
static struct attribute hid_custom_attrs[] = {
{.name = "name", .mode = S_IRUGO},
{.name = "units", .mode = S_IRUGO},
{.name = "unit-expo", .mode = S_IRUGO},
{.name = "minimum", .mode = S_IRUGO},
{.name = "maximum", .mode = S_IRUGO},
{.name = "size", .mode = S_IRUGO},
{.name = "value", .mode = S_IWUSR | S_IRUGO},
{.name = NULL}
};
static const struct hid_custom_usage_desc {
int usage_id;
char *desc;
} hid_custom_usage_desc_table[] = {
{0x200201, "event-sensor-state"},
{0x200202, "event-sensor-event"},
{0x200301, "property-friendly-name"},
{0x200302, "property-persistent-unique-id"},
{0x200303, "property-sensor-status"},
{0x200304, "property-min-report-interval"},
{0x200305, "property-sensor-manufacturer"},
{0x200306, "property-sensor-model"},
{0x200307, "property-sensor-serial-number"},
{0x200308, "property-sensor-description"},
{0x200309, "property-sensor-connection-type"},
{0x20030A, "property-sensor-device-path"},
{0x20030B, "property-hardware-revision"},
{0x20030C, "property-firmware-version"},
{0x20030D, "property-release-date"},
{0x20030E, "property-report-interval"},
{0x20030F, "property-change-sensitivity-absolute"},
{0x200310, "property-change-sensitivity-percent-range"},
{0x200311, "property-change-sensitivity-percent-relative"},
{0x200312, "property-accuracy"},
{0x200313, "property-resolution"},
{0x200314, "property-maximum"},
{0x200315, "property-minimum"},
{0x200316, "property-reporting-state"},
{0x200317, "property-sampling-rate"},
{0x200318, "property-response-curve"},
{0x200319, "property-power-state"},
{0x200540, "data-field-custom"},
{0x200541, "data-field-custom-usage"},
{0x200542, "data-field-custom-boolean-array"},
{0x200543, "data-field-custom-value"},
{0x200544, "data-field-custom-value_1"},
{0x200545, "data-field-custom-value_2"},
{0x200546, "data-field-custom-value_3"},
{0x200547, "data-field-custom-value_4"},
{0x200548, "data-field-custom-value_5"},
{0x200549, "data-field-custom-value_6"},
{0x20054A, "data-field-custom-value_7"},
{0x20054B, "data-field-custom-value_8"},
{0x20054C, "data-field-custom-value_9"},
{0x20054D, "data-field-custom-value_10"},
{0x20054E, "data-field-custom-value_11"},
{0x20054F, "data-field-custom-value_12"},
{0x200550, "data-field-custom-value_13"},
{0x200551, "data-field-custom-value_14"},
{0x200552, "data-field-custom-value_15"},
{0x200553, "data-field-custom-value_16"},
{0x200554, "data-field-custom-value_17"},
{0x200555, "data-field-custom-value_18"},
{0x200556, "data-field-custom-value_19"},
{0x200557, "data-field-custom-value_20"},
{0x200558, "data-field-custom-value_21"},
{0x200559, "data-field-custom-value_22"},
{0x20055A, "data-field-custom-value_23"},
{0x20055B, "data-field-custom-value_24"},
{0x20055C, "data-field-custom-value_25"},
{0x20055D, "data-field-custom-value_26"},
{0x20055E, "data-field-custom-value_27"},
{0x20055F, "data-field-custom-value_28"},
};
static int usage_id_cmp(const void *p1, const void *p2)
{
if (*(int *)p1 < *(int *)p2)
return -1;
if (*(int *)p1 > *(int *)p2)
return 1;
return 0;
}
static ssize_t enable_sensor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
return sprintf(buf, "%d\n", sensor_inst->enable);
}
static int set_power_report_state(struct hid_sensor_custom *sensor_inst,
bool state)
{
int power_val = -1;
int report_val = -1;
u32 power_state_usage_id;
u32 report_state_usage_id;
int ret;
/*
* It is possible that the power/report state ids are not present.
* In this case this function will return success. But if the
* ids are present, then it will return error if set fails.
*/
if (state) {
power_state_usage_id =
HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM;
report_state_usage_id =
HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM;
} else {
power_state_usage_id =
HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM;
report_state_usage_id =
HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM;
}
if (sensor_inst->power_state)
power_val = hid_sensor_get_usage_index(sensor_inst->hsdev,
sensor_inst->power_state->attribute.report_id,
sensor_inst->power_state->attribute.index,
power_state_usage_id);
if (sensor_inst->report_state)
report_val = hid_sensor_get_usage_index(sensor_inst->hsdev,
sensor_inst->report_state->attribute.report_id,
sensor_inst->report_state->attribute.index,
report_state_usage_id);
if (power_val >= 0) {
power_val +=
sensor_inst->power_state->attribute.logical_minimum;
ret = sensor_hub_set_feature(sensor_inst->hsdev,
sensor_inst->power_state->attribute.report_id,
sensor_inst->power_state->attribute.index,
sizeof(power_val),
&power_val);
if (ret) {
hid_err(sensor_inst->hsdev->hdev,
"Set power state failed\n");
return ret;
}
}
if (report_val >= 0) {
report_val +=
sensor_inst->report_state->attribute.logical_minimum;
ret = sensor_hub_set_feature(sensor_inst->hsdev,
sensor_inst->report_state->attribute.report_id,
sensor_inst->report_state->attribute.index,
sizeof(report_val),
&report_val);
if (ret) {
hid_err(sensor_inst->hsdev->hdev,
"Set report state failed\n");
return ret;
}
}
return 0;
}
static ssize_t enable_sensor_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
int value;
int ret = -EINVAL;
if (kstrtoint(buf, 0, &value) != 0)
return -EINVAL;
mutex_lock(&sensor_inst->mutex);
if (value && !sensor_inst->enable) {
ret = sensor_hub_device_open(sensor_inst->hsdev);
if (ret)
goto unlock_state;
ret = set_power_report_state(sensor_inst, true);
if (ret) {
sensor_hub_device_close(sensor_inst->hsdev);
goto unlock_state;
}
sensor_inst->enable = true;
} else if (!value && sensor_inst->enable) {
ret = set_power_report_state(sensor_inst, false);
sensor_hub_device_close(sensor_inst->hsdev);
sensor_inst->enable = false;
}
unlock_state:
mutex_unlock(&sensor_inst->mutex);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_RW(enable_sensor);
static struct attribute *enable_sensor_attrs[] = {
&dev_attr_enable_sensor.attr,
NULL,
};
static struct attribute_group enable_sensor_attr_group = {
.attrs = enable_sensor_attrs,
};
static ssize_t show_value(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
struct hid_sensor_hub_attribute_info *attribute;
int index, usage, field_index;
char name[HID_CUSTOM_NAME_LENGTH];
bool feature = false;
bool input = false;
int value = 0;
if (sscanf(attr->attr.name, "feature-%d-%x-%s", &index, &usage,
name) == 3) {
feature = true;
field_index = index + sensor_inst->input_field_count;
} else if (sscanf(attr->attr.name, "input-%d-%x-%s", &index, &usage,
name) == 3) {
input = true;
field_index = index;
} else
return -EINVAL;
if (!strncmp(name, "value", strlen("value"))) {
u32 report_id;
int ret;
attribute = &sensor_inst->fields[field_index].attribute;
report_id = attribute->report_id;
if (feature) {
u8 values[HID_CUSTOM_MAX_FEATURE_BYTES];
int len = 0;
u64 value = 0;
int i = 0;
ret = sensor_hub_get_feature(sensor_inst->hsdev,
report_id,
index,
sizeof(values), values);
if (ret < 0)
return ret;
while (i < ret) {
if (i + attribute->size > ret) {
len += snprintf(&buf[len],
PAGE_SIZE - len,
"%d ", values[i]);
break;
}
switch (attribute->size) {
case 2:
value = (u64) *(u16 *)&values[i];
i += attribute->size;
break;
case 4:
value = (u64) *(u32 *)&values[i];
i += attribute->size;
break;
case 8:
value = *(u64 *)&values[i];
i += attribute->size;
break;
default:
value = (u64) values[i];
++i;
break;
}
len += snprintf(&buf[len], PAGE_SIZE - len,
"%lld ", value);
}
len += snprintf(&buf[len], PAGE_SIZE - len, "\n");
return len;
} else if (input)
value = sensor_hub_input_attr_get_raw_value(
sensor_inst->hsdev,
sensor_inst->hsdev->usage,
usage, report_id,
SENSOR_HUB_SYNC);
} else if (!strncmp(name, "units", strlen("units")))
value = sensor_inst->fields[field_index].attribute.units;
else if (!strncmp(name, "unit-expo", strlen("unit-expo")))
value = sensor_inst->fields[field_index].attribute.unit_expo;
else if (!strncmp(name, "size", strlen("size")))
value = sensor_inst->fields[field_index].attribute.size;
else if (!strncmp(name, "minimum", strlen("minimum")))
value = sensor_inst->fields[field_index].attribute.
logical_minimum;
else if (!strncmp(name, "maximum", strlen("maximum")))
value = sensor_inst->fields[field_index].attribute.
logical_maximum;
else if (!strncmp(name, "name", strlen("name"))) {
struct hid_custom_usage_desc *usage_desc;
usage_desc = bsearch(&usage, hid_custom_usage_desc_table,
ARRAY_SIZE(hid_custom_usage_desc_table),
sizeof(struct hid_custom_usage_desc),
usage_id_cmp);
if (usage_desc)
return snprintf(buf, PAGE_SIZE, "%s\n",
usage_desc->desc);
else
return sprintf(buf, "not-specified\n");
} else
return -EINVAL;
return sprintf(buf, "%d\n", value);
}
static ssize_t store_value(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
int index, field_index, usage;
char name[HID_CUSTOM_NAME_LENGTH];
int value;
if (sscanf(attr->attr.name, "feature-%d-%x-%s", &index, &usage,
name) == 3) {
field_index = index + sensor_inst->input_field_count;
} else
return -EINVAL;
if (!strncmp(name, "value", strlen("value"))) {
u32 report_id;
int ret;
if (kstrtoint(buf, 0, &value) != 0)
return -EINVAL;
report_id = sensor_inst->fields[field_index].attribute.
report_id;
ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id,
index, sizeof(value), &value);
} else
return -EINVAL;
return count;
}
static int hid_sensor_capture_sample(struct hid_sensor_hub_device *hsdev,
unsigned usage_id, size_t raw_len,
char *raw_data, void *priv)
{
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv);
struct hid_sensor_sample header;
/* If any error occurs in a sample, rest of the fields are ignored */
if (sensor_inst->input_skip_sample) {
hid_err(sensor_inst->hsdev->hdev, "Skipped remaining data\n");
return 0;
}
hid_dbg(sensor_inst->hsdev->hdev, "%s received %d of %d\n", __func__,
(int) (sensor_inst->input_report_recd_size + raw_len),
sensor_inst->input_report_size);
if (!test_bit(0, &sensor_inst->misc_opened))
return 0;
if (!sensor_inst->input_report_recd_size) {
int required_size = sizeof(struct hid_sensor_sample) +
sensor_inst->input_report_size;
header.usage_id = hsdev->usage;
header.raw_len = sensor_inst->input_report_size;
header.timestamp = ktime_get_real_ns();
if (kfifo_avail(&sensor_inst->data_fifo) >= required_size) {
kfifo_in(&sensor_inst->data_fifo,
(unsigned char *)&header,
sizeof(header));
} else
sensor_inst->input_skip_sample = true;
}
if (kfifo_avail(&sensor_inst->data_fifo) >= raw_len)
kfifo_in(&sensor_inst->data_fifo, (unsigned char *)raw_data,
raw_len);
sensor_inst->input_report_recd_size += raw_len;
return 0;
}
static int hid_sensor_send_event(struct hid_sensor_hub_device *hsdev,
unsigned usage_id, void *priv)
{
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv);
if (!test_bit(0, &sensor_inst->misc_opened))
return 0;
sensor_inst->input_report_recd_size = 0;
sensor_inst->input_skip_sample = false;
wake_up(&sensor_inst->wait);
return 0;
}
static int hid_sensor_custom_add_field(struct hid_sensor_custom *sensor_inst,
int index, int report_type,
struct hid_report *report,
struct hid_field *field)
{
struct hid_sensor_custom_field *sensor_field;
void *fields;
fields = krealloc(sensor_inst->fields,
(sensor_inst->sensor_field_count + 1) *
sizeof(struct hid_sensor_custom_field), GFP_KERNEL);
if (!fields) {
kfree(sensor_inst->fields);
return -ENOMEM;
}
sensor_inst->fields = fields;
sensor_field = &sensor_inst->fields[sensor_inst->sensor_field_count];
sensor_field->attribute.usage_id = sensor_inst->hsdev->usage;
if (field->logical)
sensor_field->attribute.attrib_id = field->logical;
else
sensor_field->attribute.attrib_id = field->usage[0].hid;
sensor_field->attribute.index = index;
sensor_field->attribute.report_id = report->id;
sensor_field->attribute.units = field->unit;
sensor_field->attribute.unit_expo = field->unit_exponent;
sensor_field->attribute.size = (field->report_size / 8);
sensor_field->attribute.logical_minimum = field->logical_minimum;
sensor_field->attribute.logical_maximum = field->logical_maximum;
if (report_type == HID_FEATURE_REPORT)
snprintf(sensor_field->group_name,
sizeof(sensor_field->group_name), "feature-%x-%x",
sensor_field->attribute.index,
sensor_field->attribute.attrib_id);
else if (report_type == HID_INPUT_REPORT) {
snprintf(sensor_field->group_name,
sizeof(sensor_field->group_name),
"input-%x-%x", sensor_field->attribute.index,
sensor_field->attribute.attrib_id);
sensor_inst->input_field_count++;
sensor_inst->input_report_size += (field->report_size *
field->report_count) / 8;
}
memset(&sensor_field->hid_custom_attribute_group, 0,
sizeof(struct attribute_group));
sensor_inst->sensor_field_count++;
return 0;
}
static int hid_sensor_custom_add_fields(struct hid_sensor_custom *sensor_inst,
struct hid_report_enum *report_enum,
int report_type)
{
int i;
int ret;
struct hid_report *report;
struct hid_field *field;
struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev;
list_for_each_entry(report, &report_enum->report_list, list) {
for (i = 0; i < report->maxfield; ++i) {
field = report->field[i];
if (field->maxusage &&
((field->usage[0].collection_index >=
hsdev->start_collection_index) &&
(field->usage[0].collection_index <
hsdev->end_collection_index))) {
ret = hid_sensor_custom_add_field(sensor_inst,
i,
report_type,
report,
field);
if (ret)
return ret;
}
}
}
return 0;
}
static int hid_sensor_custom_add_attributes(struct hid_sensor_custom
*sensor_inst)
{
struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev;
struct hid_device *hdev = hsdev->hdev;
int ret = -1;
int i, j;
for (j = 0; j < HID_REPORT_TYPES; ++j) {
if (j == HID_OUTPUT_REPORT)
continue;
ret = hid_sensor_custom_add_fields(sensor_inst,
&hdev->report_enum[j], j);
if (ret)
return ret;
}
/* Create sysfs attributes */
for (i = 0; i < sensor_inst->sensor_field_count; ++i) {
j = 0;
while (j < HID_CUSTOM_TOTAL_ATTRS &&
hid_custom_attrs[j].name) {
struct device_attribute *device_attr;
device_attr = &sensor_inst->fields[i].sd_attrs[j];
snprintf((char *)&sensor_inst->fields[i].attr_name[j],
HID_CUSTOM_NAME_LENGTH, "%s-%s",
sensor_inst->fields[i].group_name,
hid_custom_attrs[j].name);
sysfs_attr_init(&device_attr->attr);
device_attr->attr.name =
(char *)&sensor_inst->fields[i].attr_name[j];
device_attr->attr.mode = hid_custom_attrs[j].mode;
device_attr->show = show_value;
if (hid_custom_attrs[j].mode & S_IWUSR)
device_attr->store = store_value;
sensor_inst->fields[i].attrs[j] = &device_attr->attr;
++j;
}
sensor_inst->fields[i].attrs[j] = NULL;
sensor_inst->fields[i].hid_custom_attribute_group.attrs =
sensor_inst->fields[i].attrs;
sensor_inst->fields[i].hid_custom_attribute_group.name =
sensor_inst->fields[i].group_name;
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
&sensor_inst->fields[i].
hid_custom_attribute_group);
if (ret)
break;
/* For power or report field store indexes */
if (sensor_inst->fields[i].attribute.attrib_id ==
HID_USAGE_SENSOR_PROY_POWER_STATE)
sensor_inst->power_state = &sensor_inst->fields[i];
else if (sensor_inst->fields[i].attribute.attrib_id ==
HID_USAGE_SENSOR_PROP_REPORT_STATE)
sensor_inst->report_state = &sensor_inst->fields[i];
}
return ret;
}
static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom *
sensor_inst)
{
int i;
for (i = 0; i < sensor_inst->sensor_field_count; ++i)
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&sensor_inst->fields[i].
hid_custom_attribute_group);
kfree(sensor_inst->fields);
}
static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,
size_t count, loff_t *f_ps)
{
struct hid_sensor_custom *sensor_inst;
unsigned int copied;
int ret;
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
if (count < sizeof(struct hid_sensor_sample))
return -EINVAL;
do {
if (kfifo_is_empty(&sensor_inst->data_fifo)) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = wait_event_interruptible(sensor_inst->wait,
!kfifo_is_empty(&sensor_inst->data_fifo));
if (ret)
return ret;
}
ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count,
&copied);
if (ret)
return ret;
} while (copied == 0);
return copied;
}
static int hid_sensor_custom_release(struct inode *inode, struct file *file)
{
struct hid_sensor_custom *sensor_inst;
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
clear_bit(0, &sensor_inst->misc_opened);
return 0;
}
static int hid_sensor_custom_open(struct inode *inode, struct file *file)
{
struct hid_sensor_custom *sensor_inst;
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
/* We essentially have single reader and writer */
if (test_and_set_bit(0, &sensor_inst->misc_opened))
return -EBUSY;
return nonseekable_open(inode, file);
}
static unsigned int hid_sensor_custom_poll(struct file *file,
struct poll_table_struct *wait)
{
struct hid_sensor_custom *sensor_inst;
unsigned int mask = 0;
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
poll_wait(file, &sensor_inst->wait, wait);
if (!kfifo_is_empty(&sensor_inst->data_fifo))
mask = POLLIN | POLLRDNORM;
return mask;
}
static const struct file_operations hid_sensor_custom_fops = {
.open = hid_sensor_custom_open,
.read = hid_sensor_custom_read,
.release = hid_sensor_custom_release,
.poll = hid_sensor_custom_poll,
.llseek = noop_llseek,
};
static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
{
int ret;
ret = kfifo_alloc(&sensor_inst->data_fifo, HID_CUSTOM_FIFO_SIZE,
GFP_KERNEL);
if (ret)
return ret;
init_waitqueue_head(&sensor_inst->wait);
sensor_inst->custom_dev.minor = MISC_DYNAMIC_MINOR;
sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev);
sensor_inst->custom_dev.fops = &hid_sensor_custom_fops,
ret = misc_register(&sensor_inst->custom_dev);
if (ret) {
kfifo_free(&sensor_inst->data_fifo);
return ret;
}
return 0;
}
static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
*sensor_inst)
{
wake_up(&sensor_inst->wait);
misc_deregister(&sensor_inst->custom_dev);
kfifo_free(&sensor_inst->data_fifo);
}
static int hid_sensor_custom_probe(struct platform_device *pdev)
{
struct hid_sensor_custom *sensor_inst;
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
int ret;
sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
GFP_KERNEL);
if (!sensor_inst)
return -ENOMEM;
sensor_inst->callbacks.capture_sample = hid_sensor_capture_sample;
sensor_inst->callbacks.send_event = hid_sensor_send_event;
sensor_inst->callbacks.pdev = pdev;
sensor_inst->hsdev = hsdev;
sensor_inst->pdev = pdev;
mutex_init(&sensor_inst->mutex);
platform_set_drvdata(pdev, sensor_inst);
ret = sensor_hub_register_callback(hsdev, hsdev->usage,
&sensor_inst->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
return ret;
}
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
if (ret)
goto err_remove_callback;
ret = hid_sensor_custom_add_attributes(sensor_inst);
if (ret)
goto err_remove_group;
ret = hid_sensor_custom_dev_if_add(sensor_inst);
if (ret)
goto err_remove_attributes;
return 0;
err_remove_attributes:
hid_sensor_custom_remove_attributes(sensor_inst);
err_remove_group:
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
err_remove_callback:
sensor_hub_remove_callback(hsdev, hsdev->usage);
return ret;
}
static int hid_sensor_custom_remove(struct platform_device *pdev)
{
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
hid_sensor_custom_dev_if_remove(sensor_inst);
hid_sensor_custom_remove_attributes(sensor_inst);
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
sensor_hub_remove_callback(hsdev, hsdev->usage);
return 0;
}
static struct platform_device_id hid_sensor_custom_ids[] = {
{
.name = "HID-SENSOR-2000e1",
},
{
.name = "HID-SENSOR-2000e2",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, hid_sensor_custom_ids);
static struct platform_driver hid_sensor_custom_platform_driver = {
.id_table = hid_sensor_custom_ids,
.driver = {
.name = KBUILD_MODNAME,
},
.probe = hid_sensor_custom_probe,
.remove = hid_sensor_custom_remove,
};
module_platform_driver(hid_sensor_custom_platform_driver);
MODULE_DESCRIPTION("HID Sensor Custom and Generic sensor Driver");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL");

821
drivers/hid/hid-sensor-hub.c Executable file
View File

@ -0,0 +1,821 @@
/*
* HID Sensors Driver
* Copyright (c) 2012, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mfd/core.h>
#include <linux/list.h>
#include <linux/hid-sensor-ids.h>
#include <linux/hid-sensor-hub.h>
#include "hid-ids.h"
#define HID_SENSOR_HUB_ENUM_QUIRK 0x01
/**
* struct sensor_hub_data - Hold a instance data for a HID hub device
* @hsdev: Stored hid instance for current hub device.
* @mutex: Mutex to serialize synchronous request.
* @lock: Spin lock to protect pending request structure.
* @dyn_callback_list: Holds callback function
* @dyn_callback_lock: spin lock to protect callback list
* @hid_sensor_hub_client_devs: Stores all MFD cells for a hub instance.
* @hid_sensor_client_cnt: Number of MFD cells, (no of sensors attached).
* @ref_cnt: Number of MFD clients have opened this device
*/
struct sensor_hub_data {
struct mutex mutex;
spinlock_t lock;
struct list_head dyn_callback_list;
spinlock_t dyn_callback_lock;
struct mfd_cell *hid_sensor_hub_client_devs;
int hid_sensor_client_cnt;
unsigned long quirks;
int ref_cnt;
};
/**
* struct hid_sensor_hub_callbacks_list - Stores callback list
* @list: list head.
* @usage_id: usage id for a physical device.
* @usage_callback: Stores registered callback functions.
* @priv: Private data for a physical device.
*/
struct hid_sensor_hub_callbacks_list {
struct list_head list;
u32 usage_id;
struct hid_sensor_hub_device *hsdev;
struct hid_sensor_hub_callbacks *usage_callback;
void *priv;
};
static struct hid_report *sensor_hub_report(int id, struct hid_device *hdev,
int dir)
{
struct hid_report *report;
list_for_each_entry(report, &hdev->report_enum[dir].report_list, list) {
if (report->id == id)
return report;
}
hid_warn(hdev, "No report with id 0x%x found\n", id);
return NULL;
}
static int sensor_hub_get_physical_device_count(struct hid_device *hdev)
{
int i;
int count = 0;
for (i = 0; i < hdev->maxcollection; ++i) {
struct hid_collection *collection = &hdev->collection[i];
if (collection->type == HID_COLLECTION_PHYSICAL ||
collection->type == HID_COLLECTION_APPLICATION)
++count;
}
return count;
}
static void sensor_hub_fill_attr_info(
struct hid_sensor_hub_attribute_info *info,
s32 index, s32 report_id, struct hid_field *field)
{
info->index = index;
info->report_id = report_id;
info->units = field->unit;
info->unit_expo = field->unit_exponent;
info->size = (field->report_size * field->report_count)/8;
info->logical_minimum = field->logical_minimum;
info->logical_maximum = field->logical_maximum;
}
static struct hid_sensor_hub_callbacks *sensor_hub_get_callback(
struct hid_device *hdev,
u32 usage_id,
int collection_index,
struct hid_sensor_hub_device **hsdev,
void **priv)
{
struct hid_sensor_hub_callbacks_list *callback;
struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
unsigned long flags;
spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
list_for_each_entry(callback, &pdata->dyn_callback_list, list)
if ((callback->usage_id == usage_id ||
callback->usage_id == HID_USAGE_SENSOR_COLLECTION) &&
(collection_index >=
callback->hsdev->start_collection_index) &&
(collection_index <
callback->hsdev->end_collection_index)) {
*priv = callback->priv;
*hsdev = callback->hsdev;
spin_unlock_irqrestore(&pdata->dyn_callback_lock,
flags);
return callback->usage_callback;
}
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return NULL;
}
int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev,
u32 usage_id,
struct hid_sensor_hub_callbacks *usage_callback)
{
struct hid_sensor_hub_callbacks_list *callback;
struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev);
unsigned long flags;
spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
list_for_each_entry(callback, &pdata->dyn_callback_list, list)
if (callback->usage_id == usage_id &&
callback->hsdev == hsdev) {
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return -EINVAL;
}
callback = kzalloc(sizeof(*callback), GFP_ATOMIC);
if (!callback) {
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return -ENOMEM;
}
callback->hsdev = hsdev;
callback->usage_callback = usage_callback;
callback->usage_id = usage_id;
callback->priv = NULL;
/*
* If there is a handler registered for the collection type, then
* it will handle all reports for sensors in this collection. If
* there is also an individual sensor handler registration, then
* we want to make sure that the reports are directed to collection
* handler, as this may be a fusion sensor. So add collection handlers
* to the beginning of the list, so that they are matched first.
*/
if (usage_id == HID_USAGE_SENSOR_COLLECTION)
list_add(&callback->list, &pdata->dyn_callback_list);
else
list_add_tail(&callback->list, &pdata->dyn_callback_list);
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(sensor_hub_register_callback);
int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev,
u32 usage_id)
{
struct hid_sensor_hub_callbacks_list *callback;
struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev);
unsigned long flags;
spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
list_for_each_entry(callback, &pdata->dyn_callback_list, list)
if (callback->usage_id == usage_id &&
callback->hsdev == hsdev) {
list_del(&callback->list);
kfree(callback);
break;
}
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(sensor_hub_remove_callback);
int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
u32 field_index, int buffer_size, void *buffer)
{
struct hid_report *report;
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
__s32 *buf32 = buffer;
int i = 0;
int remaining_bytes;
__s32 value;
int ret = 0;
mutex_lock(&data->mutex);
report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT);
if (!report || (field_index >= report->maxfield)) {
ret = -EINVAL;
goto done_proc;
}
remaining_bytes = buffer_size % sizeof(__s32);
buffer_size = buffer_size / sizeof(__s32);
if (buffer_size) {
for (i = 0; i < buffer_size; ++i) {
hid_set_field(report->field[field_index], i,
(__force __s32)cpu_to_le32(*buf32));
++buf32;
}
}
if (remaining_bytes) {
value = 0;
memcpy(&value, (u8 *)buf32, remaining_bytes);
hid_set_field(report->field[field_index], i,
(__force __s32)cpu_to_le32(value));
}
hid_hw_request(hsdev->hdev, report, HID_REQ_SET_REPORT);
hid_hw_wait(hsdev->hdev);
done_proc:
mutex_unlock(&data->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(sensor_hub_set_feature);
int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
u32 field_index, int buffer_size, void *buffer)
{
struct hid_report *report;
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
int report_size;
int ret = 0;
mutex_lock(&data->mutex);
report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT);
if (!report || (field_index >= report->maxfield) ||
report->field[field_index]->report_count < 1) {
ret = -EINVAL;
goto done_proc;
}
hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT);
hid_hw_wait(hsdev->hdev);
/* calculate number of bytes required to read this field */
report_size = DIV_ROUND_UP(report->field[field_index]->report_size,
8) *
report->field[field_index]->report_count;
if (!report_size) {
ret = -EINVAL;
goto done_proc;
}
ret = min(report_size, buffer_size);
memcpy(buffer, report->field[field_index]->value, ret);
done_proc:
mutex_unlock(&data->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(sensor_hub_get_feature);
int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev,
u32 usage_id,
u32 attr_usage_id, u32 report_id,
enum sensor_hub_read_flags flag)
{
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
unsigned long flags;
struct hid_report *report;
int ret_val = 0;
report = sensor_hub_report(report_id, hsdev->hdev,
HID_INPUT_REPORT);
if (!report)
return -EINVAL;
mutex_lock(hsdev->mutex_ptr);
if (flag == SENSOR_HUB_SYNC) {
memset(&hsdev->pending, 0, sizeof(hsdev->pending));
init_completion(&hsdev->pending.ready);
hsdev->pending.usage_id = usage_id;
hsdev->pending.attr_usage_id = attr_usage_id;
hsdev->pending.raw_size = 0;
spin_lock_irqsave(&data->lock, flags);
hsdev->pending.status = true;
spin_unlock_irqrestore(&data->lock, flags);
}
mutex_lock(&data->mutex);
hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT);
mutex_unlock(&data->mutex);
if (flag == SENSOR_HUB_SYNC) {
wait_for_completion_interruptible_timeout(
&hsdev->pending.ready, HZ*5);
switch (hsdev->pending.raw_size) {
case 1:
ret_val = *(u8 *)hsdev->pending.raw_data;
break;
case 2:
ret_val = *(u16 *)hsdev->pending.raw_data;
break;
case 4:
ret_val = *(u32 *)hsdev->pending.raw_data;
break;
default:
ret_val = 0;
}
kfree(hsdev->pending.raw_data);
hsdev->pending.status = false;
}
mutex_unlock(hsdev->mutex_ptr);
return ret_val;
}
EXPORT_SYMBOL_GPL(sensor_hub_input_attr_get_raw_value);
int hid_sensor_get_usage_index(struct hid_sensor_hub_device *hsdev,
u32 report_id, int field_index, u32 usage_id)
{
struct hid_report *report;
struct hid_field *field;
int i;
report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT);
if (!report || (field_index >= report->maxfield))
goto done_proc;
field = report->field[field_index];
for (i = 0; i < field->maxusage; ++i) {
if (field->usage[i].hid == usage_id)
return field->usage[i].usage_index;
}
done_proc:
return -EINVAL;
}
EXPORT_SYMBOL_GPL(hid_sensor_get_usage_index);
int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev,
u8 type,
u32 usage_id,
u32 attr_usage_id,
struct hid_sensor_hub_attribute_info *info)
{
int ret = -1;
int i;
struct hid_report *report;
struct hid_field *field;
struct hid_report_enum *report_enum;
struct hid_device *hdev = hsdev->hdev;
/* Initialize with defaults */
info->usage_id = usage_id;
info->attrib_id = attr_usage_id;
info->report_id = -1;
info->index = -1;
info->units = -1;
info->unit_expo = -1;
report_enum = &hdev->report_enum[type];
list_for_each_entry(report, &report_enum->report_list, list) {
for (i = 0; i < report->maxfield; ++i) {
field = report->field[i];
if (field->maxusage) {
if (field->physical == usage_id &&
(field->logical == attr_usage_id ||
field->usage[0].hid ==
attr_usage_id) &&
(field->usage[0].collection_index >=
hsdev->start_collection_index) &&
(field->usage[0].collection_index <
hsdev->end_collection_index)) {
sensor_hub_fill_attr_info(info, i,
report->id,
field);
ret = 0;
break;
}
}
}
}
return ret;
}
EXPORT_SYMBOL_GPL(sensor_hub_input_get_attribute_info);
#ifdef CONFIG_PM
static int sensor_hub_suspend(struct hid_device *hdev, pm_message_t message)
{
struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
struct hid_sensor_hub_callbacks_list *callback;
unsigned long flags;
hid_dbg(hdev, " sensor_hub_suspend\n");
spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
list_for_each_entry(callback, &pdata->dyn_callback_list, list) {
if (callback->usage_callback->suspend)
callback->usage_callback->suspend(
callback->hsdev, callback->priv);
}
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return 0;
}
static int sensor_hub_resume(struct hid_device *hdev)
{
struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
struct hid_sensor_hub_callbacks_list *callback;
unsigned long flags;
hid_dbg(hdev, " sensor_hub_resume\n");
spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
list_for_each_entry(callback, &pdata->dyn_callback_list, list) {
if (callback->usage_callback->resume)
callback->usage_callback->resume(
callback->hsdev, callback->priv);
}
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
return 0;
}
static int sensor_hub_reset_resume(struct hid_device *hdev)
{
return 0;
}
#endif
/*
* Handle raw report as sent by device
*/
static int sensor_hub_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *raw_data, int size)
{
int i;
u8 *ptr;
int sz;
struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
unsigned long flags;
struct hid_sensor_hub_callbacks *callback = NULL;
struct hid_collection *collection = NULL;
void *priv = NULL;
struct hid_sensor_hub_device *hsdev = NULL;
hid_dbg(hdev, "sensor_hub_raw_event report id:0x%x size:%d type:%d\n",
report->id, size, report->type);
hid_dbg(hdev, "maxfield:%d\n", report->maxfield);
if (report->type != HID_INPUT_REPORT)
return 1;
ptr = raw_data;
ptr++; /* Skip report id */
spin_lock_irqsave(&pdata->lock, flags);
for (i = 0; i < report->maxfield; ++i) {
hid_dbg(hdev, "%d collection_index:%x hid:%x sz:%x\n",
i, report->field[i]->usage->collection_index,
report->field[i]->usage->hid,
(report->field[i]->report_size *
report->field[i]->report_count)/8);
sz = (report->field[i]->report_size *
report->field[i]->report_count)/8;
collection = &hdev->collection[
report->field[i]->usage->collection_index];
hid_dbg(hdev, "collection->usage %x\n",
collection->usage);
callback = sensor_hub_get_callback(hdev,
report->field[i]->physical,
report->field[i]->usage[0].collection_index,
&hsdev, &priv);
if (!callback) {
ptr += sz;
continue;
}
if (hsdev->pending.status && (hsdev->pending.attr_usage_id ==
report->field[i]->usage->hid ||
hsdev->pending.attr_usage_id ==
report->field[i]->logical)) {
hid_dbg(hdev, "data was pending ...\n");
hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC);
if (hsdev->pending.raw_data)
hsdev->pending.raw_size = sz;
else
hsdev->pending.raw_size = 0;
complete(&hsdev->pending.ready);
}
if (callback->capture_sample) {
if (report->field[i]->logical)
callback->capture_sample(hsdev,
report->field[i]->logical, sz, ptr,
callback->pdev);
else
callback->capture_sample(hsdev,
report->field[i]->usage->hid, sz, ptr,
callback->pdev);
}
ptr += sz;
}
if (callback && collection && callback->send_event)
callback->send_event(hsdev, collection->usage,
callback->pdev);
spin_unlock_irqrestore(&pdata->lock, flags);
return 1;
}
int sensor_hub_device_open(struct hid_sensor_hub_device *hsdev)
{
int ret = 0;
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
mutex_lock(&data->mutex);
if (!data->ref_cnt) {
ret = hid_hw_open(hsdev->hdev);
if (ret) {
hid_err(hsdev->hdev, "failed to open hid device\n");
mutex_unlock(&data->mutex);
return ret;
}
}
data->ref_cnt++;
mutex_unlock(&data->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(sensor_hub_device_open);
void sensor_hub_device_close(struct hid_sensor_hub_device *hsdev)
{
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
mutex_lock(&data->mutex);
data->ref_cnt--;
if (!data->ref_cnt)
hid_hw_close(hsdev->hdev);
mutex_unlock(&data->mutex);
}
EXPORT_SYMBOL_GPL(sensor_hub_device_close);
static __u8 *sensor_hub_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
int index;
struct sensor_hub_data *sd = hid_get_drvdata(hdev);
unsigned char report_block[] = {
0x0a, 0x16, 0x03, 0x15, 0x00, 0x25, 0x05};
unsigned char power_block[] = {
0x0a, 0x19, 0x03, 0x15, 0x00, 0x25, 0x05};
if (!(sd->quirks & HID_SENSOR_HUB_ENUM_QUIRK)) {
hid_dbg(hdev, "No Enum quirks\n");
return rdesc;
}
/* Looks for power and report state usage id and force to 1 */
for (index = 0; index < *rsize; ++index) {
if (((*rsize - index) > sizeof(report_block)) &&
!memcmp(&rdesc[index], report_block,
sizeof(report_block))) {
rdesc[index + 4] = 0x01;
index += sizeof(report_block);
}
if (((*rsize - index) > sizeof(power_block)) &&
!memcmp(&rdesc[index], power_block,
sizeof(power_block))) {
rdesc[index + 4] = 0x01;
index += sizeof(power_block);
}
}
/* Checks if the report descriptor of Thinkpad Helix 2 has a logical
* minimum for magnetic flux axis greater than the maximum */
if (hdev->product == USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA &&
*rsize == 2558 && rdesc[913] == 0x17 && rdesc[914] == 0x40 &&
rdesc[915] == 0x81 && rdesc[916] == 0x08 &&
rdesc[917] == 0x00 && rdesc[918] == 0x27 &&
rdesc[921] == 0x07 && rdesc[922] == 0x00) {
/* Sets negative logical minimum for mag x, y and z */
rdesc[914] = rdesc[935] = rdesc[956] = 0xc0;
rdesc[915] = rdesc[936] = rdesc[957] = 0x7e;
rdesc[916] = rdesc[937] = rdesc[958] = 0xf7;
rdesc[917] = rdesc[938] = rdesc[959] = 0xff;
}
return rdesc;
}
static int sensor_hub_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
struct sensor_hub_data *sd;
int i;
char *name;
int dev_cnt;
struct hid_sensor_hub_device *hsdev;
struct hid_sensor_hub_device *last_hsdev = NULL;
struct hid_sensor_hub_device *collection_hsdev = NULL;
sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
if (!sd) {
hid_err(hdev, "cannot allocate Sensor data\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, sd);
sd->quirks = id->driver_data;
spin_lock_init(&sd->lock);
spin_lock_init(&sd->dyn_callback_lock);
mutex_init(&sd->mutex);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
INIT_LIST_HEAD(&hdev->inputs);
ret = hid_hw_start(hdev, 0);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
INIT_LIST_HEAD(&sd->dyn_callback_list);
sd->hid_sensor_client_cnt = 0;
dev_cnt = sensor_hub_get_physical_device_count(hdev);
if (dev_cnt > HID_MAX_PHY_DEVICES) {
hid_err(hdev, "Invalid Physical device count\n");
ret = -EINVAL;
goto err_stop_hw;
}
sd->hid_sensor_hub_client_devs = devm_kzalloc(&hdev->dev, dev_cnt *
sizeof(struct mfd_cell),
GFP_KERNEL);
if (sd->hid_sensor_hub_client_devs == NULL) {
hid_err(hdev, "Failed to allocate memory for mfd cells\n");
ret = -ENOMEM;
goto err_stop_hw;
}
for (i = 0; i < hdev->maxcollection; ++i) {
struct hid_collection *collection = &hdev->collection[i];
if (collection->type == HID_COLLECTION_PHYSICAL ||
collection->type == HID_COLLECTION_APPLICATION) {
hsdev = devm_kzalloc(&hdev->dev, sizeof(*hsdev),
GFP_KERNEL);
if (!hsdev) {
hid_err(hdev, "cannot allocate hid_sensor_hub_device\n");
ret = -ENOMEM;
goto err_stop_hw;
}
hsdev->hdev = hdev;
hsdev->vendor_id = hdev->vendor;
hsdev->product_id = hdev->product;
hsdev->usage = collection->usage;
hsdev->mutex_ptr = devm_kzalloc(&hdev->dev,
sizeof(struct mutex),
GFP_KERNEL);
if (!hsdev->mutex_ptr) {
ret = -ENOMEM;
goto err_stop_hw;
}
mutex_init(hsdev->mutex_ptr);
hsdev->start_collection_index = i;
if (last_hsdev)
last_hsdev->end_collection_index = i;
last_hsdev = hsdev;
name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
"HID-SENSOR-%x",
collection->usage);
if (name == NULL) {
hid_err(hdev, "Failed MFD device name\n");
ret = -ENOMEM;
goto err_stop_hw;
}
sd->hid_sensor_hub_client_devs[
sd->hid_sensor_client_cnt].name = name;
sd->hid_sensor_hub_client_devs[
sd->hid_sensor_client_cnt].platform_data =
hsdev;
sd->hid_sensor_hub_client_devs[
sd->hid_sensor_client_cnt].pdata_size =
sizeof(*hsdev);
hid_dbg(hdev, "Adding %s:%d\n", name,
hsdev->start_collection_index);
sd->hid_sensor_client_cnt++;
if (collection_hsdev)
collection_hsdev->end_collection_index = i;
if (collection->type == HID_COLLECTION_APPLICATION &&
collection->usage == HID_USAGE_SENSOR_COLLECTION)
collection_hsdev = hsdev;
}
}
if (last_hsdev)
last_hsdev->end_collection_index = i;
if (collection_hsdev)
collection_hsdev->end_collection_index = i;
ret = mfd_add_hotplug_devices(&hdev->dev,
sd->hid_sensor_hub_client_devs,
sd->hid_sensor_client_cnt);
if (ret < 0)
goto err_stop_hw;
return ret;
err_stop_hw:
hid_hw_stop(hdev);
return ret;
}
static void sensor_hub_remove(struct hid_device *hdev)
{
struct sensor_hub_data *data = hid_get_drvdata(hdev);
unsigned long flags;
int i;
hid_dbg(hdev, " hardware removed\n");
hid_hw_close(hdev);
hid_hw_stop(hdev);
spin_lock_irqsave(&data->lock, flags);
for (i = 0; i < data->hid_sensor_client_cnt; ++i) {
struct hid_sensor_hub_device *hsdev =
data->hid_sensor_hub_client_devs[i].platform_data;
if (hsdev->pending.status)
complete(&hsdev->pending.ready);
}
spin_unlock_irqrestore(&data->lock, flags);
mfd_remove_devices(&hdev->dev);
hid_set_drvdata(hdev, NULL);
mutex_destroy(&data->mutex);
}
static const struct hid_device_id sensor_hub_devices[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_INTEL_0,
USB_DEVICE_ID_INTEL_HID_SENSOR_0),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_INTEL_1,
USB_DEVICE_ID_INTEL_HID_SENSOR_0),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_INTEL_1,
USB_DEVICE_ID_INTEL_HID_SENSOR_1),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_MICROSOFT,
USB_DEVICE_ID_MS_SURFACE_PRO_2),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_MICROSOFT,
USB_DEVICE_ID_MS_TOUCH_COVER_2),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_MICROSOFT,
USB_DEVICE_ID_MS_TYPE_COVER_2),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_STM_0,
USB_DEVICE_ID_STM_HID_SENSOR),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_STM_0,
USB_DEVICE_ID_STM_HID_SENSOR_1),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_TEXAS_INSTRUMENTS,
USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_ITE,
USB_DEVICE_ID_ITE_LENOVO_YOGA),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_ITE,
USB_DEVICE_ID_ITE_LENOVO_YOGA2),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, HID_ANY_ID,
HID_ANY_ID) },
{ }
};
MODULE_DEVICE_TABLE(hid, sensor_hub_devices);
static struct hid_driver sensor_hub_driver = {
.name = "hid-sensor-hub",
.id_table = sensor_hub_devices,
.probe = sensor_hub_probe,
.remove = sensor_hub_remove,
.raw_event = sensor_hub_raw_event,
.report_fixup = sensor_hub_report_fixup,
#ifdef CONFIG_PM
.suspend = sensor_hub_suspend,
.resume = sensor_hub_resume,
.reset_resume = sensor_hub_reset_resume,
#endif
};
module_hid_driver(sensor_hub_driver);
MODULE_DESCRIPTION("HID Sensor Hub driver");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
MODULE_LICENSE("GPL");

185
drivers/hid/hid-sjoy.c Executable file
View File

@ -0,0 +1,185 @@
/*
* Force feedback support for SmartJoy PLUS PS2->USB adapter
*
* Copyright (c) 2009 Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
*
* Based of hid-pl.c and hid-gaff.c
* Copyright (c) 2007, 2009 Anssi Hannula <anssi.hannula@gmail.com>
* Copyright (c) 2008 Lukasz Lubojanski <lukasz@lubojanski.info>
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* #define DEBUG */
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#ifdef CONFIG_SMARTJOYPLUS_FF
struct sjoyff_device {
struct hid_report *report;
};
static int hid_sjoyff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct sjoyff_device *sjoyff = data;
u32 left, right;
left = effect->u.rumble.strong_magnitude;
right = effect->u.rumble.weak_magnitude;
dev_dbg(&dev->dev, "called with 0x%08x 0x%08x\n", left, right);
left = left * 0xff / 0xffff;
right = (right != 0); /* on/off only */
sjoyff->report->field[0]->value[1] = right;
sjoyff->report->field[0]->value[2] = left;
dev_dbg(&dev->dev, "running with 0x%02x 0x%02x\n", left, right);
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
return 0;
}
static int sjoyff_init(struct hid_device *hid)
{
struct sjoyff_device *sjoyff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
struct input_dev *dev;
int error;
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
list_for_each_entry(hidinput, &hid->inputs, list) {
report_ptr = report_ptr->next;
if (report_ptr == report_list) {
hid_err(hid, "required output report is missing\n");
return -ENODEV;
}
report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
if (report->field[0]->report_count < 3) {
hid_err(hid, "not enough values in the field\n");
return -ENODEV;
}
sjoyff = kzalloc(sizeof(struct sjoyff_device), GFP_KERNEL);
if (!sjoyff)
return -ENOMEM;
dev = hidinput->input;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
if (error) {
kfree(sjoyff);
return error;
}
sjoyff->report = report;
sjoyff->report->field[0]->value[0] = 0x01;
sjoyff->report->field[0]->value[1] = 0x00;
sjoyff->report->field[0]->value[2] = 0x00;
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
}
hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n");
return 0;
}
#else
static inline int sjoyff_init(struct hid_device *hid)
{
return 0;
}
#endif
static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
hdev->quirks |= id->driver_data;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
sjoyff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id sjoy_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO),
.driver_data = HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_DUAL_BOX_PRO),
.driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET |
HID_QUIRK_SKIP_OUTPUT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO),
.driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET |
HID_QUIRK_SKIP_OUTPUT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SUPER_JOY_BOX_3) },
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD),
.driver_data = HID_QUIRK_MULTI_INPUT |
HID_QUIRK_SKIP_OUTPUT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_PLAYDOTCOM, USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII),
.driver_data = HID_QUIRK_MULTI_INPUT |
HID_QUIRK_SKIP_OUTPUT_REPORTS },
{ }
};
MODULE_DEVICE_TABLE(hid, sjoy_devices);
static struct hid_driver sjoy_driver = {
.name = "smartjoyplus",
.id_table = sjoy_devices,
.probe = sjoy_probe,
};
module_hid_driver(sjoy_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jussi Kivilinna");

2495
drivers/hid/hid-sony.c Executable file

File diff suppressed because it is too large Load Diff

81
drivers/hid/hid-speedlink.c Executable file
View File

@ -0,0 +1,81 @@
/*
* HID driver for Speedlink Vicious and Divine Cezanne (USB mouse).
* Fixes "jumpy" cursor and removes nonexistent keyboard LEDS from
* the HID descriptor.
*
* Copyright (c) 2011, 2013 Stefan Kriwanek <dev@stefankriwanek.de>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static const struct hid_device_id speedlink_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE)},
{ }
};
static int speedlink_input_mapping(struct hid_device *hdev,
struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
/*
* The Cezanne mouse has a second "keyboard" USB endpoint for it is
* able to map keyboard events to the button presses.
* It sends a standard keyboard report descriptor, though, whose
* LEDs we ignore.
*/
switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_LED:
return -1;
}
return 0;
}
static int speedlink_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
/* No other conditions due to usage_table. */
/* This fixes the "jumpy" cursor occuring due to invalid events sent
* by the device. Some devices only send them with value==+256, others
* don't. However, catching abs(value)>=256 is restrictive enough not
* to interfere with devices that were bug-free (has been tested).
*/
if (abs(value) >= 256)
return 1;
/* Drop useless distance 0 events (on button clicks etc.) as well */
if (value == 0)
return 1;
return 0;
}
MODULE_DEVICE_TABLE(hid, speedlink_devices);
static const struct hid_usage_id speedlink_grabbed_usages[] = {
{ HID_GD_X, EV_REL, 0 },
{ HID_GD_Y, EV_REL, 1 },
{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
};
static struct hid_driver speedlink_driver = {
.name = "speedlink",
.id_table = speedlink_devices,
.usage_table = speedlink_grabbed_usages,
.input_mapping = speedlink_input_mapping,
.event = speedlink_event,
};
module_hid_driver(speedlink_driver);
MODULE_LICENSE("GPL");

388
drivers/hid/hid-steelseries.c Executable file
View File

@ -0,0 +1,388 @@
/*
* HID driver for Steelseries SRW-S1
*
* Copyright (c) 2013 Simon Wood
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
#define SRWS1_NUMBER_LEDS 15
struct steelseries_srws1_data {
__u16 led_state;
/* the last element is used for setting all leds simultaneously */
struct led_classdev *led[SRWS1_NUMBER_LEDS + 1];
};
#endif
/* Fixed report descriptor for Steelseries SRW-S1 wheel controller
*
* The original descriptor hides the sensitivity and assists dials
* a custom vendor usage page. This inserts a patch to make them
* appear in the 'Generic Desktop' usage.
*/
static __u8 steelseries_srws1_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop) */
0x09, 0x08, /* Usage (MultiAxis), Changed */
0xA1, 0x01, /* Collection (Application), */
0xA1, 0x02, /* Collection (Logical), */
0x95, 0x01, /* Report Count (1), */
0x05, 0x01, /* Changed Usage Page (Desktop), */
0x09, 0x30, /* Changed Usage (X), */
0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */
0x26, 0x08, 0x07, /* Logical Maximum (1800), */
0x65, 0x14, /* Unit (Degrees), */
0x55, 0x0F, /* Unit Exponent (15), */
0x75, 0x10, /* Report Size (16), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Changed Usage (Y), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x75, 0x0C, /* Report Size (12), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x32, /* Changed Usage (Z), */
0x15, 0x00, /* Logical Minimum (0), */
0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
0x75, 0x0C, /* Report Size (12), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x39, /* Usage (Hat Switch), */
0x25, 0x07, /* Logical Maximum (7), */
0x35, 0x00, /* Physical Minimum (0), */
0x46, 0x3B, 0x01, /* Physical Maximum (315), */
0x65, 0x14, /* Unit (Degrees), */
0x75, 0x04, /* Report Size (4), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x25, 0x01, /* Logical Maximum (1), */
0x45, 0x01, /* Physical Maximum (1), */
0x65, 0x00, /* Unit, */
0x75, 0x01, /* Report Size (1), */
0x95, 0x03, /* Report Count (3), */
0x81, 0x01, /* Input (Constant), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x11, /* Usage Maximum (11h), */
0x95, 0x11, /* Report Count (17), */
0x81, 0x02, /* Input (Variable), */
/* ---- Dial patch starts here ---- */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x33, /* Usage (RX), */
0x75, 0x04, /* Report Size (4), */
0x95, 0x02, /* Report Count (2), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x0b, /* Logical Maximum (b), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x35, /* Usage (RZ), */
0x75, 0x04, /* Report Size (4), */
0x95, 0x01, /* Report Count (1), */
0x25, 0x03, /* Logical Maximum (3), */
0x81, 0x02, /* Input (Variable), */
/* ---- Dial patch ends here ---- */
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
0x75, 0x04, /* Changed Report Size (4), */
0x95, 0x0D, /* Changed Report Count (13), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xA1, 0x02, /* Collection (Logical), */
0x09, 0x02, /* Usage (02h), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x10, /* Report Count (16), */
0x91, 0x02, /* Output (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds)
{
struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
__s32 *value = report->field[0]->value;
value[0] = 0x40;
value[1] = leds & 0xFF;
value[2] = leds >> 8;
value[3] = 0x00;
value[4] = 0x00;
value[5] = 0x00;
value[6] = 0x00;
value[7] = 0x00;
value[8] = 0x00;
value[9] = 0x00;
value[10] = 0x00;
value[11] = 0x00;
value[12] = 0x00;
value[13] = 0x00;
value[14] = 0x00;
value[15] = 0x00;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
/* Note: LED change does not show on device until the device is read/polled */
}
static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = container_of(dev, struct hid_device, dev);
struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Device data not found.");
return;
}
if (value == LED_OFF)
drv_data->led_state = 0;
else
drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1;
steelseries_srws1_set_leds(hid, drv_data->led_state);
}
static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = container_of(dev, struct hid_device, dev);
struct steelseries_srws1_data *drv_data;
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Device data not found.");
return LED_OFF;
}
return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF;
}
static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = container_of(dev, struct hid_device, dev);
struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
int i, state = 0;
if (!drv_data) {
hid_err(hid, "Device data not found.");
return;
}
for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
if (led_cdev != drv_data->led[i])
continue;
state = (drv_data->led_state >> i) & 1;
if (value == LED_OFF && state) {
drv_data->led_state &= ~(1 << i);
steelseries_srws1_set_leds(hid, drv_data->led_state);
} else if (value != LED_OFF && !state) {
drv_data->led_state |= 1 << i;
steelseries_srws1_set_leds(hid, drv_data->led_state);
}
break;
}
}
static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = container_of(dev, struct hid_device, dev);
struct steelseries_srws1_data *drv_data;
int i, value = 0;
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Device data not found.");
return LED_OFF;
}
for (i = 0; i < SRWS1_NUMBER_LEDS; i++)
if (led_cdev == drv_data->led[i]) {
value = (drv_data->led_state >> i) & 1;
break;
}
return value ? LED_FULL : LED_OFF;
}
static int steelseries_srws1_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret, i;
struct led_classdev *led;
size_t name_sz;
char *name;
struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
if (drv_data == NULL) {
hid_err(hdev, "can't alloc SRW-S1 memory\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, drv_data);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
ret = -ENODEV;
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
/* register led subsystem */
drv_data->led_state = 0;
for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++)
drv_data->led[i] = NULL;
steelseries_srws1_set_leds(hdev, 0);
name_sz = strlen(hdev->uniq) + 16;
/* 'ALL', for setting all LEDs simultaneously */
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED ALL\n");
goto err_led;
}
name = (void *)(&led[1]);
snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq);
led->name = name;
led->brightness = 0;
led->max_brightness = 1;
led->brightness_get = steelseries_srws1_led_all_get_brightness;
led->brightness_set = steelseries_srws1_led_all_set_brightness;
drv_data->led[SRWS1_NUMBER_LEDS] = led;
ret = led_classdev_register(&hdev->dev, led);
if (ret)
goto err_led;
/* Each individual LED */
for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED %d\n", i);
goto err_led;
}
name = (void *)(&led[1]);
snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1);
led->name = name;
led->brightness = 0;
led->max_brightness = 1;
led->brightness_get = steelseries_srws1_led_get_brightness;
led->brightness_set = steelseries_srws1_led_set_brightness;
drv_data->led[i] = led;
ret = led_classdev_register(&hdev->dev, led);
if (ret) {
hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
err_led:
/* Deregister all LEDs (if any) */
for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
led = drv_data->led[i];
drv_data->led[i] = NULL;
if (!led)
continue;
led_classdev_unregister(led);
kfree(led);
}
goto out; /* but let the driver continue without LEDs */
}
}
out:
return 0;
err_free:
kfree(drv_data);
return ret;
}
static void steelseries_srws1_remove(struct hid_device *hdev)
{
int i;
struct led_classdev *led;
struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev);
if (drv_data) {
/* Deregister LEDs (if any) */
for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
led = drv_data->led[i];
drv_data->led[i] = NULL;
if (!led)
continue;
led_classdev_unregister(led);
kfree(led);
}
}
hid_hw_stop(hdev);
kfree(drv_data);
return;
}
#endif
static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
rdesc = steelseries_srws1_rdesc_fixed;
*rsize = sizeof(steelseries_srws1_rdesc_fixed);
}
return rdesc;
}
static const struct hid_device_id steelseries_srws1_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices);
static struct hid_driver steelseries_srws1_driver = {
.name = "steelseries_srws1",
.id_table = steelseries_srws1_devices,
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
.probe = steelseries_srws1_probe,
.remove = steelseries_srws1_remove,
#endif
.report_fixup = steelseries_srws1_report_fixup
};
module_hid_driver(steelseries_srws1_driver);
MODULE_LICENSE("GPL");

Some files were not shown because too many files have changed in this diff Show More