9 part
This commit is contained in:
parent
63eb5f9c6c
commit
977e5217b6
|
@ -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
|
|
@ -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/
|
|
@ -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
|
|
@ -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/
|
|
@ -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");
|
|
@ -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");
|
|
@ -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);
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
||||
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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[] = {
|
||||
>683r_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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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);
|
|
@ -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);
|
|
@ -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");
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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);
|
|
@ -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 */
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
|
@ -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
Loading…
Reference in New Issue