提交 8b108c60 编写于 作者: L Linus Torvalds

Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (59 commits)
  HID: fix up 'EMBEDDED' mess in Kconfig
  HID: roccat: cleanup preprocessor macros
  HID: roccat: refactor special event handling
  HID: roccat: fix special button support
  HID: roccat: Correctly mark init and exit functions
  HID: hidraw: Use Interrupt Endpoint for OUT Transfers if Available
  HID: hid-samsung: remove redundant key mappings
  HID: add omitted hid-zydacron.c file
  HID: hid-samsung: add support for Creative Desktop Wireless 6000
  HID: picolcd: Eliminate use after free
  HID: Zydacron Remote Control driver
  HID: Use kmemdup
  HID: magicmouse: fix input registration
  HID: make Prodikeys driver standalone config option
  HID: Prodikeys PC-MIDI HID Driver
  HID: hidraw: fix indentation
  HID: ntrig: add filtering module parameters
  HID: ntrig: add sysfs access to filter parameters
  HID: ntrig: add sensitivity and responsiveness support
  HID: add multi-input quirk for eGalax Touchcontroller
  ...
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/operation_mode
Date: March 2010
Contact: Bruno Prémont <bonbons@linux-vserver.org>
Description: Make it possible to switch the PicoLCD device between LCD
(firmware) and bootloader (flasher) operation modes.
Reading: returns list of available modes, the active mode being
enclosed in brackets ('[' and ']')
Writing: causes operation mode switch. Permitted values are
the non-active mode names listed when read.
Note: when switching mode the current PicoLCD HID device gets
disconnected and reconnects after above delay (see attribute
operation_mode_delay for its value).
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/operation_mode_delay
Date: April 2010
Contact: Bruno Prémont <bonbons@linux-vserver.org>
Description: Delay PicoLCD waits before restarting in new mode when
operation_mode has changed.
Reading/Writing: It is expressed in ms and permitted range is
0..30000ms.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/fb_update_rate
Date: March 2010
Contact: Bruno Prémont <bonbons@linux-vserver.org>
Description: Make it possible to adjust defio refresh rate.
Reading: returns list of available refresh rates (expressed in Hz),
the active refresh rate being enclosed in brackets ('[' and ']')
Writing: accepts new refresh rate expressed in integer Hz
within permitted rates.
Note: As device can barely do 2 complete refreshes a second
it only makes sense to adjust this value if only one or two
tiles get changed and it's not appropriate to expect the application
to flush it's tiny changes explicitely at higher than default rate.
What: /sys/bus/hid/drivers/prodikeys/.../channel
Date: April 2010
KernelVersion: 2.6.34
Contact: Don Prince <dhprince.devel@yahoo.co.uk>
Description:
Allows control (via software) the midi channel to which
that the pc-midi keyboard will output.midi data.
Range: 0..15
Type: Read/write
What: /sys/bus/hid/drivers/prodikeys/.../sustain
Date: April 2010
KernelVersion: 2.6.34
Contact: Don Prince <dhprince.devel@yahoo.co.uk>
Description:
Allows control (via software) the sustain duration of a
note held by the pc-midi driver.
0 means sustain mode is disabled.
Range: 0..5000 (milliseconds)
Type: Read/write
What: /sys/bus/hid/drivers/prodikeys/.../octave
Date: April 2010
KernelVersion: 2.6.34
Contact: Don Prince <dhprince.devel@yahoo.co.uk>
Description:
Controls the octave shift modifier in the pc-midi driver.
The octave can be shifted via software up/down 2 octaves.
0 means the no ocatve shift.
Range: -2..2 (minus 2 to plus 2)
Type: Read/Write
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/actual_dpi
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: It is possible to switch the dpi setting of the mouse with the
press of a button.
When read, this file returns the raw number of the actual dpi
setting reported by the mouse. This number has to be further
processed to receive the real dpi value.
VALUE DPI
1 800
2 1200
3 1600
4 2000
5 2400
6 3200
This file is readonly.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/actual_profile
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: When read, this file returns the number of the actual profile.
This file is readonly.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/firmware_version
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: When read, this file returns the raw integer version number of the
firmware reported by the mouse. Using the integer value eases
further usage in other programs. To receive the real version
number the decimal point has to be shifted 2 positions to the
left. E.g. a returned value of 138 means 1.38
This file is readonly.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/kone_driver_version
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: When read, this file returns the driver version.
The format of the string is "v<major>.<minor>.<patchlevel>".
This attribute is used by the userland tools to find the sysfs-
paths of installed kone-mice and determine the capabilites of
the driver. Versions of this driver for old kernels replace
usbhid instead of generic-usb. The way to scan for this file
has been chosen to provide a consistent way for all supported
kernel versions.
This file is readonly.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/profile[1-5]
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: The mouse can store 5 profiles which can be switched by the
press of a button. A profile holds informations like button
mappings, sensitivity, the colors of the 5 leds and light
effects.
When read, these files return the respective profile. The
returned data is 975 bytes in size.
When written, this file lets one write the respective profile
data back to the mouse. The data has to be 975 bytes long.
The mouse will reject invalid data, whereas the profile number
stored in the profile doesn't need to fit the number of the
store.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/settings
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: When read, this file returns the settings stored in the mouse.
The size of the data is 36 bytes and holds information like the
startup_profile, tcu state and calibration_data.
When written, this file lets write settings back to the mouse.
The data has to be 36 bytes long. The mouse will reject invalid
data.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/startup_profile
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: The integer value of this attribute ranges from 1 to 5.
When read, this attribute returns the number of the profile
that's active when the mouse is powered on.
When written, this file sets the number of the startup profile
and the mouse activates this profile immediately.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/tcu
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: The mouse has a "Tracking Control Unit" which lets the user
calibrate the laser power to fit the mousepad surface.
When read, this file returns the current state of the TCU,
where 0 means off and 1 means on.
Writing 0 in this file will switch the TCU off.
Writing 1 in this file will start the calibration which takes
around 6 seconds to complete and activates the TCU.
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/weight
Date: March 2010
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
Description: The mouse can be equipped with one of four supplied weights
ranging from 5 to 20 grams which are recognized by the mouse
and its value can be read out. When read, this file returns the
raw value returned by the mouse which eases further processing
in other software.
The values map to the weights as follows:
VALUE WEIGHT
0 none
1 5g
2 10g
3 15g
4 20g
This file is readonly.
What: /sys/class/hidraw/hidraw*/device/speed
Date: April 2010
Kernel Version: 2.6.35
Contact: linux-bluetooth@vger.kernel.org
Description:
The /sys/class/hidraw/hidraw*/device/speed file controls
reporting speed of wacom bluetooth tablet. Reading from
this file returns 1 if tablet reports in high speed mode
or 0 otherwise. Writing to this file one of these values
switches reporting speed.
...@@ -86,6 +86,12 @@ config HID_BELKIN ...@@ -86,6 +86,12 @@ config HID_BELKIN
---help--- ---help---
Support for Belkin Flip KVM and Wireless keyboard. Support for Belkin Flip KVM and Wireless keyboard.
config HID_CANDO
tristate "Cando dual touch panel"
depends on USB_HID
---help---
Support for Cando dual touch panel.
config HID_CHERRY config HID_CHERRY
tristate "Cherry" if EMBEDDED tristate "Cherry" if EMBEDDED
depends on USB_HID depends on USB_HID
...@@ -100,6 +106,21 @@ config HID_CHICONY ...@@ -100,6 +106,21 @@ config HID_CHICONY
---help--- ---help---
Support for Chicony Tactical pad. Support for Chicony Tactical pad.
config HID_PRODIKEYS
tristate "Prodikeys PC-MIDI Keyboard support"
depends on USB_HID && SND
select SND_RAWMIDI
---help---
Support for Prodikeys PC-MIDI Keyboard device support.
Say Y here to enable support for this device.
- Prodikeys PC-MIDI keyboard.
The Prodikeys PC-MIDI acts as a USB Audio device, with one MIDI
input and one MIDI output. These MIDI jacks appear as
a sound "card" in the ALSA sound system.
Note: if you say N here, this device will still function as a basic
multimedia keyboard, but will lack support for the musical keyboard
and some additional multimedia keys.
config HID_CYPRESS config HID_CYPRESS
tristate "Cypress" if EMBEDDED tristate "Cypress" if EMBEDDED
depends on USB_HID depends on USB_HID
...@@ -108,9 +129,8 @@ config HID_CYPRESS ...@@ -108,9 +129,8 @@ config HID_CYPRESS
Support for cypress mouse and barcode readers. Support for cypress mouse and barcode readers.
config HID_DRAGONRISE config HID_DRAGONRISE
tristate "DragonRise Inc. support" if EMBEDDED tristate "DragonRise Inc. support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Say Y here if you have DragonRise Inc.game controllers. Say Y here if you have DragonRise Inc.game controllers.
...@@ -122,6 +142,12 @@ config DRAGONRISE_FF ...@@ -122,6 +142,12 @@ config DRAGONRISE_FF
Say Y here if you want to enable force feedback support for DragonRise Inc. Say Y here if you want to enable force feedback support for DragonRise Inc.
game controllers. game controllers.
config HID_EGALAX
tristate "eGalax multi-touch panel"
depends on USB_HID
---help---
Support for the eGalax dual-touch panel.
config HID_EZKEY config HID_EZKEY
tristate "Ezkey" if EMBEDDED tristate "Ezkey" if EMBEDDED
depends on USB_HID depends on USB_HID
...@@ -137,16 +163,14 @@ config HID_KYE ...@@ -137,16 +163,14 @@ config HID_KYE
Support for Kye/Genius Ergo Mouse. Support for Kye/Genius Ergo Mouse.
config HID_GYRATION config HID_GYRATION
tristate "Gyration" if EMBEDDED tristate "Gyration"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Gyration remote control. Support for Gyration remote control.
config HID_TWINHAN config HID_TWINHAN
tristate "Twinhan" if EMBEDDED tristate "Twinhan"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Twinhan IR remote control. Support for Twinhan IR remote control.
...@@ -233,16 +257,14 @@ config HID_NTRIG ...@@ -233,16 +257,14 @@ config HID_NTRIG
Support for N-Trig touch screen. Support for N-Trig touch screen.
config HID_ORTEK config HID_ORTEK
tristate "Ortek" if EMBEDDED tristate "Ortek"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Ortek WKB-2000 wireless keyboard + mouse trackpad. Support for Ortek WKB-2000 wireless keyboard + mouse trackpad.
config HID_PANTHERLORD config HID_PANTHERLORD
tristate "Pantherlord support" if EMBEDDED tristate "Pantherlord support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Say Y here if you have a PantherLord/GreenAsia based game controller Say Y here if you have a PantherLord/GreenAsia based game controller
or adapter. or adapter.
...@@ -256,29 +278,90 @@ config PANTHERLORD_FF ...@@ -256,29 +278,90 @@ config PANTHERLORD_FF
or adapter and want to enable force feedback support for it. or adapter and want to enable force feedback support for it.
config HID_PETALYNX config HID_PETALYNX
tristate "Petalynx" if EMBEDDED tristate "Petalynx"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Petalynx Maxter remote control. Support for Petalynx Maxter remote control.
config HID_PICOLCD
tristate "PicoLCD (graphic version)"
depends on USB_HID
---help---
This provides support for Minibox PicoLCD devices, currently
only the graphical ones are supported.
This includes support for the following device features:
- Keypad
- Switching between Firmware and Flash mode
- EEProm / Flash access (via debugfs)
Features selectively enabled:
- Framebuffer for monochrome 256x64 display
- Backlight control
- Contrast control
- General purpose outputs
Features that are not (yet) supported:
- IR
config HID_PICOLCD_FB
bool "Framebuffer support" if EMBEDDED
default !EMBEDDED
depends on HID_PICOLCD
depends on HID_PICOLCD=FB || FB=y
select FB_DEFERRED_IO
select FB_SYS_FILLRECT
select FB_SYS_COPYAREA
select FB_SYS_IMAGEBLIT
select FB_SYS_FOPS
---help---
Provide access to PicoLCD's 256x64 monochrome display via a
frambuffer device.
config HID_PICOLCD_BACKLIGHT
bool "Backlight control" if EMBEDDED
default !EMBEDDED
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 EMBEDDED
default !EMBEDDED
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 EMBEDDED
default !EMBEDDED
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_QUANTA config HID_QUANTA
tristate "Quanta Optical Touch" tristate "Quanta Optical Touch"
depends on USB_HID depends on USB_HID
---help--- ---help---
Support for Quanta Optical Touch dual-touch panels. Support for Quanta Optical Touch dual-touch panels.
config HID_ROCCAT_KONE
tristate "Roccat Kone Mouse support"
depends on USB_HID
---help---
Support for Roccat Kone mouse.
config HID_SAMSUNG config HID_SAMSUNG
tristate "Samsung" if EMBEDDED tristate "Samsung"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Samsung InfraRed remote control. Support for Samsung InfraRed remote control or keyboards.
config HID_SONY config HID_SONY
tristate "Sony" if EMBEDDED tristate "Sony"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Sony PS3 controller. Support for Sony PS3 controller.
...@@ -289,16 +372,14 @@ config HID_STANTUM ...@@ -289,16 +372,14 @@ config HID_STANTUM
Support for Stantum multitouch panel. Support for Stantum multitouch panel.
config HID_SUNPLUS config HID_SUNPLUS
tristate "Sunplus" if EMBEDDED tristate "Sunplus"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for Sunplus wireless desktop. Support for Sunplus wireless desktop.
config HID_GREENASIA config HID_GREENASIA
tristate "GreenAsia (Product ID 0x12) support" if EMBEDDED tristate "GreenAsia (Product ID 0x12) support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Say Y here if you have a GreenAsia (Product ID 0x12) based game Say Y here if you have a GreenAsia (Product ID 0x12) based game
controller or adapter. controller or adapter.
...@@ -313,9 +394,8 @@ config GREENASIA_FF ...@@ -313,9 +394,8 @@ config GREENASIA_FF
and want to enable force feedback support for it. and want to enable force feedback support for it.
config HID_SMARTJOYPLUS config HID_SMARTJOYPLUS
tristate "SmartJoy PLUS PS2/USB adapter support" if EMBEDDED tristate "SmartJoy PLUS PS2/USB adapter support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Support for SmartJoy PLUS PS2/USB adapter. Support for SmartJoy PLUS PS2/USB adapter.
...@@ -328,16 +408,14 @@ config SMARTJOYPLUS_FF ...@@ -328,16 +408,14 @@ config SMARTJOYPLUS_FF
enable force feedback support for it. enable force feedback support for it.
config HID_TOPSEED config HID_TOPSEED
tristate "TopSeed Cyberlink remote control support" if EMBEDDED tristate "TopSeed Cyberlink remote control support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Say Y if you have a TopSeed Cyberlink remote control. Say Y if you have a TopSeed Cyberlink or BTC Emprex remote control.
config HID_THRUSTMASTER config HID_THRUSTMASTER
tristate "ThrustMaster devices support" if EMBEDDED tristate "ThrustMaster devices support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or
a THRUSTMASTER Ferrari GT Rumble Wheel. a THRUSTMASTER Ferrari GT Rumble Wheel.
...@@ -357,10 +435,17 @@ config HID_WACOM ...@@ -357,10 +435,17 @@ config HID_WACOM
---help--- ---help---
Support for Wacom Graphire Bluetooth tablet. Support for Wacom Graphire Bluetooth tablet.
config HID_WACOM_POWER_SUPPLY
bool "Wacom Bluetooth devices power supply status support"
depends on HID_WACOM
select POWER_SUPPLY
---help---
Say Y here if you want to enable power supply status monitoring for
Wacom Bluetooth devices.
config HID_ZEROPLUS config HID_ZEROPLUS
tristate "Zeroplus based game controller support" if EMBEDDED tristate "Zeroplus based game controller support"
depends on USB_HID depends on USB_HID
default !EMBEDDED
---help--- ---help---
Say Y here if you have a Zeroplus based game controller. Say Y here if you have a Zeroplus based game controller.
...@@ -372,6 +457,12 @@ config ZEROPLUS_FF ...@@ -372,6 +457,12 @@ config ZEROPLUS_FF
Say Y here if you have a Zeroplus based game controller and want Say Y here if you have a Zeroplus based game controller and want
to have force feedback support for it. to have force feedback support for it.
config HID_ZYDACRON
tristate "Zydacron remote control support"
depends on USB_HID
---help---
Support for Zydacron remote control.
endmenu endmenu
endif # HID_SUPPORT endif # HID_SUPPORT
...@@ -26,10 +26,12 @@ obj-$(CONFIG_HID_3M_PCT) += hid-3m-pct.o ...@@ -26,10 +26,12 @@ obj-$(CONFIG_HID_3M_PCT) += hid-3m-pct.o
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o
obj-$(CONFIG_HID_APPLE) += hid-apple.o obj-$(CONFIG_HID_APPLE) += hid-apple.o
obj-$(CONFIG_HID_BELKIN) += hid-belkin.o obj-$(CONFIG_HID_BELKIN) += hid-belkin.o
obj-$(CONFIG_HID_CANDO) += hid-cando.o
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
obj-$(CONFIG_HID_CHICONY) += hid-chicony.o obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
obj-$(CONFIG_HID_DRAGONRISE) += hid-drff.o obj-$(CONFIG_HID_DRAGONRISE) += hid-drff.o
obj-$(CONFIG_HID_EGALAX) += hid-egalax.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
...@@ -41,9 +43,12 @@ obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o ...@@ -41,9 +43,12 @@ obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
obj-$(CONFIG_HID_MOSART) += hid-mosart.o obj-$(CONFIG_HID_MOSART) += hid-mosart.o
obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o
obj-$(CONFIG_HID_ORTEK) += hid-ortek.o obj-$(CONFIG_HID_ORTEK) += hid-ortek.o
obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o
obj-$(CONFIG_HID_QUANTA) += hid-quanta.o obj-$(CONFIG_HID_QUANTA) += hid-quanta.o
obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o
obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o
obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o
obj-$(CONFIG_HID_ROCCAT_KONE) += hid-roccat-kone.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SONY) += hid-sony.o
...@@ -54,6 +59,7 @@ obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o ...@@ -54,6 +59,7 @@ obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
obj-$(CONFIG_HID_WACOM) += hid-wacom.o obj-$(CONFIG_HID_WACOM) += hid-wacom.o
obj-$(CONFIG_USB_HID) += usbhid/ obj-$(CONFIG_USB_HID) += usbhid/
......
/* /*
* HID driver for 3M PCT multitouch panels * HID driver for 3M PCT multitouch panels
* *
* Copyright (c) 2009 Stephane Chatty <chatty@enac.fr> * Copyright (c) 2009-2010 Stephane Chatty <chatty@enac.fr>
* *
*/ */
...@@ -25,7 +25,7 @@ MODULE_LICENSE("GPL"); ...@@ -25,7 +25,7 @@ MODULE_LICENSE("GPL");
#include "hid-ids.h" #include "hid-ids.h"
struct mmm_finger { struct mmm_finger {
__s32 x, y; __s32 x, y, w, h;
__u8 rank; __u8 rank;
bool touch, valid; bool touch, valid;
}; };
...@@ -82,7 +82,18 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, ...@@ -82,7 +82,18 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi,
/* touchscreen emulation */ /* touchscreen emulation */
hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
return 1; return 1;
case HID_DG_WIDTH:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_TOUCH_MAJOR);
return 1;
case HID_DG_HEIGHT:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_TOUCH_MINOR);
input_set_abs_params(hi->input, ABS_MT_ORIENTATION,
1, 1, 0, 0);
return 1;
case HID_DG_CONTACTID: case HID_DG_CONTACTID:
field->logical_maximum = 59;
hid_map_usage(hi, usage, bit, max, hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_TRACKING_ID); EV_ABS, ABS_MT_TRACKING_ID);
return 1; return 1;
...@@ -128,9 +139,15 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) ...@@ -128,9 +139,15 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input)
/* this finger is just placeholder data, ignore */ /* this finger is just placeholder data, ignore */
} else if (f->touch) { } else if (f->touch) {
/* this finger is on the screen */ /* this finger is on the screen */
int wide = (f->w > f->h);
input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i); input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i);
input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x); input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y); input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y);
input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide);
input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR,
wide ? f->w : f->h);
input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR,
wide ? f->h : f->w);
input_mt_sync(input); input_mt_sync(input);
/* /*
* touchscreen emulation: maintain the age rank * touchscreen emulation: maintain the age rank
...@@ -197,6 +214,14 @@ static int mmm_event(struct hid_device *hid, struct hid_field *field, ...@@ -197,6 +214,14 @@ static int mmm_event(struct hid_device *hid, struct hid_field *field,
case HID_DG_CONFIDENCE: case HID_DG_CONFIDENCE:
md->valid = value; md->valid = value;
break; break;
case HID_DG_WIDTH:
if (md->valid)
md->f[md->curid].w = value;
break;
case HID_DG_HEIGHT:
if (md->valid)
md->f[md->curid].h = value;
break;
case HID_DG_CONTACTID: case HID_DG_CONTACTID:
if (md->valid) { if (md->valid) {
md->curid = value; md->curid = value;
...@@ -255,6 +280,7 @@ static void mmm_remove(struct hid_device *hdev) ...@@ -255,6 +280,7 @@ static void mmm_remove(struct hid_device *hdev)
static const struct hid_device_id mmm_devices[] = { static const struct hid_device_id mmm_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) },
{ HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) },
{ } { }
}; };
MODULE_DEVICE_TABLE(hid, mmm_devices); MODULE_DEVICE_TABLE(hid, mmm_devices);
...@@ -287,5 +313,4 @@ static void __exit mmm_exit(void) ...@@ -287,5 +313,4 @@ static void __exit mmm_exit(void)
module_init(mmm_init); module_init(mmm_init);
module_exit(mmm_exit); module_exit(mmm_exit);
MODULE_LICENSE("GPL");
/*
* HID driver for Cando dual-touch panels
*
* Copyright (c) 2010 Stephane Chatty <chatty@enac.fr>
*
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/slab.h>
MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>");
MODULE_DESCRIPTION("Cando dual-touch panel");
MODULE_LICENSE("GPL");
#include "hid-ids.h"
struct cando_data {
__u16 x, y;
__u8 id;
__s8 oldest; /* id of the oldest finger in previous frame */
bool valid; /* valid finger data, or just placeholder? */
bool first; /* is this the first finger in this frame? */
__s8 firstid; /* id of the first finger in the frame */
__u16 firstx, firsty; /* (x, y) of the first finger in the frame */
};
static int cando_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_GENDESK:
switch (usage->hid) {
case HID_GD_X:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_POSITION_X);
/* touchscreen emulation */
input_set_abs_params(hi->input, ABS_X,
field->logical_minimum,
field->logical_maximum, 0, 0);
return 1;
case HID_GD_Y:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_POSITION_Y);
/* touchscreen emulation */
input_set_abs_params(hi->input, ABS_Y,
field->logical_minimum,
field->logical_maximum, 0, 0);
return 1;
}
return 0;
case HID_UP_DIGITIZER:
switch (usage->hid) {
case HID_DG_TIPSWITCH:
case HID_DG_CONTACTMAX:
return -1;
case HID_DG_INRANGE:
/* touchscreen emulation */
hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
return 1;
case HID_DG_CONTACTID:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_TRACKING_ID);
return 1;
}
return 0;
}
return 0;
}
static int cando_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if (usage->type == EV_KEY || usage->type == EV_ABS)
clear_bit(usage->code, *bit);
return 0;
}
/*
* this function is called when a whole finger has been parsed,
* so that it can decide what to send to the input layer.
*/
static void cando_filter_event(struct cando_data *td, struct input_dev *input)
{
td->first = !td->first; /* touchscreen emulation */
if (!td->valid) {
/*
* touchscreen emulation: if this is the second finger and
* the first was valid, the first was the oldest; if the
* first was not valid and there was a valid finger in the
* previous frame, this is a release.
*/
if (td->first) {
td->firstid = -1;
} else if (td->firstid >= 0) {
input_event(input, EV_ABS, ABS_X, td->firstx);
input_event(input, EV_ABS, ABS_Y, td->firsty);
td->oldest = td->firstid;
} else if (td->oldest >= 0) {
input_event(input, EV_KEY, BTN_TOUCH, 0);
td->oldest = -1;
}
return;
}
input_event(input, EV_ABS, ABS_MT_TRACKING_ID, td->id);
input_event(input, EV_ABS, ABS_MT_POSITION_X, td->x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, td->y);
input_mt_sync(input);
/*
* touchscreen emulation: if there was no touching finger previously,
* emit touch event
*/
if (td->oldest < 0) {
input_event(input, EV_KEY, BTN_TOUCH, 1);
td->oldest = td->id;
}
/*
* touchscreen emulation: if this is the first finger, wait for the
* second; the oldest is then the second if it was the oldest already
* or if there was no first, the first otherwise.
*/
if (td->first) {
td->firstx = td->x;
td->firsty = td->y;
td->firstid = td->id;
} else {
int x, y, oldest;
if (td->id == td->oldest || td->firstid < 0) {
x = td->x;
y = td->y;
oldest = td->id;
} else {
x = td->firstx;
y = td->firsty;
oldest = td->firstid;
}
input_event(input, EV_ABS, ABS_X, x);
input_event(input, EV_ABS, ABS_Y, y);
td->oldest = oldest;
}
}
static int cando_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct cando_data *td = hid_get_drvdata(hid);
if (hid->claimed & HID_CLAIMED_INPUT) {
struct input_dev *input = field->hidinput->input;
switch (usage->hid) {
case HID_DG_INRANGE:
td->valid = value;
break;
case HID_DG_CONTACTID:
td->id = value;
break;
case HID_GD_X:
td->x = value;
break;
case HID_GD_Y:
td->y = value;
cando_filter_event(td, input);
break;
case HID_DG_TIPSWITCH:
/* avoid interference from generic hidinput handling */
break;
default:
/* fallback to the generic hidinput handling */
return 0;
}
}
/* we have handled the hidinput part, now remains hiddev */
if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event)
hid->hiddev_hid_event(hid, field, usage, value);
return 1;
}
static int cando_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct cando_data *td;
td = kmalloc(sizeof(struct cando_data), GFP_KERNEL);
if (!td) {
dev_err(&hdev->dev, "cannot allocate Cando Touch data\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, td);
td->first = false;
td->oldest = -1;
td->valid = false;
ret = hid_parse(hdev);
if (!ret)
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
kfree(td);
return ret;
}
static void cando_remove(struct hid_device *hdev)
{
hid_hw_stop(hdev);
kfree(hid_get_drvdata(hdev));
hid_set_drvdata(hdev, NULL);
}
static const struct hid_device_id cando_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CANDO,
USB_DEVICE_ID_CANDO_MULTI_TOUCH) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CANDO,
USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6) },
{ }
};
MODULE_DEVICE_TABLE(hid, cando_devices);
static const struct hid_usage_id cando_grabbed_usages[] = {
{ HID_ANY_ID, HID_ANY_ID, HID_ANY_ID },
{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
};
static struct hid_driver cando_driver = {
.name = "cando-touch",
.id_table = cando_devices,
.probe = cando_probe,
.remove = cando_remove,
.input_mapping = cando_input_mapping,
.input_mapped = cando_input_mapped,
.usage_table = cando_grabbed_usages,
.event = cando_event,
};
static int __init cando_init(void)
{
return hid_register_driver(&cando_driver);
}
static void __exit cando_exit(void)
{
hid_unregister_driver(&cando_driver);
}
module_init(cando_init);
module_exit(cando_exit);
...@@ -653,10 +653,9 @@ int hid_parse_report(struct hid_device *device, __u8 *start, ...@@ -653,10 +653,9 @@ int hid_parse_report(struct hid_device *device, __u8 *start,
if (device->driver->report_fixup) if (device->driver->report_fixup)
device->driver->report_fixup(device, start, size); device->driver->report_fixup(device, start, size);
device->rdesc = kmalloc(size, GFP_KERNEL); device->rdesc = kmemdup(start, size, GFP_KERNEL);
if (device->rdesc == NULL) if (device->rdesc == NULL)
return -ENOMEM; return -ENOMEM;
memcpy(device->rdesc, start, size);
device->rsize = size; device->rsize = size;
parser = vmalloc(sizeof(struct hid_parser)); parser = vmalloc(sizeof(struct hid_parser));
...@@ -940,13 +939,8 @@ static void hid_output_field(struct hid_field *field, __u8 *data) ...@@ -940,13 +939,8 @@ static void hid_output_field(struct hid_field *field, __u8 *data)
unsigned count = field->report_count; unsigned count = field->report_count;
unsigned offset = field->report_offset; unsigned offset = field->report_offset;
unsigned size = field->report_size; unsigned size = field->report_size;
unsigned bitsused = offset + count * size;
unsigned n; unsigned n;
/* make sure the unused bits in the last byte are zeros */
if (count > 0 && size > 0 && (bitsused % 8) != 0)
data[(bitsused-1)/8] &= (1 << (bitsused % 8)) - 1;
for (n = 0; n < count; n++) { for (n = 0; n < count; n++) {
if (field->logical_minimum < 0) /* signed values */ if (field->logical_minimum < 0) /* signed values */
implement(data, offset + n * size, size, s32ton(field->value[n], size)); implement(data, offset + n * size, size, s32ton(field->value[n], size));
...@@ -966,6 +960,7 @@ void hid_output_report(struct hid_report *report, __u8 *data) ...@@ -966,6 +960,7 @@ void hid_output_report(struct hid_report *report, __u8 *data)
if (report->id > 0) if (report->id > 0)
*data++ = report->id; *data++ = report->id;
memset(data, 0, ((report->size - 1) >> 3) + 1);
for (n = 0; n < report->maxfield; n++) for (n = 0; n < report->maxfield; n++)
hid_output_field(report->field[n], data); hid_output_field(report->field[n], data);
} }
...@@ -1086,35 +1081,28 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i ...@@ -1086,35 +1081,28 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC);
if (!buf) { if (!buf)
report = hid_get_report(report_enum, data);
goto nomem; goto nomem;
}
snprintf(buf, HID_DEBUG_BUFSIZE - 1,
"\nreport (size %u) (%snumbered)\n", size, report_enum->numbered ? "" : "un");
hid_debug_event(hid, buf);
report = hid_get_report(report_enum, data);
if (!report) {
kfree(buf);
return -1;
}
/* dump the report */ /* dump the report */
snprintf(buf, HID_DEBUG_BUFSIZE - 1, snprintf(buf, HID_DEBUG_BUFSIZE - 1,
"report %d (size %u) = ", report->id, size); "\nreport (size %u) (%snumbered) = ", size, report_enum->numbered ? "" : "un");
hid_debug_event(hid, buf); hid_debug_event(hid, buf);
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
snprintf(buf, HID_DEBUG_BUFSIZE - 1, snprintf(buf, HID_DEBUG_BUFSIZE - 1,
" %02x", data[i]); " %02x", data[i]);
hid_debug_event(hid, buf); hid_debug_event(hid, buf);
} }
hid_debug_event(hid, "\n"); hid_debug_event(hid, "\n");
kfree(buf); kfree(buf);
nomem: nomem:
report = hid_get_report(report_enum, data);
if (!report)
return -1;
if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) { if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) {
ret = hdrv->raw_event(hid, report, data, size); ret = hdrv->raw_event(hid, report, data, size);
if (ret != 0) if (ret != 0)
...@@ -1167,6 +1155,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) ...@@ -1167,6 +1155,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
unsigned int i; unsigned int i;
int len; int len;
if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)
connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);
if (hdev->bus != BUS_USB) if (hdev->bus != BUS_USB)
connect_mask &= ~HID_CONNECT_HIDDEV; connect_mask &= ~HID_CONNECT_HIDDEV;
if (hid_hiddev(hdev)) if (hid_hiddev(hdev))
...@@ -1246,6 +1236,7 @@ EXPORT_SYMBOL_GPL(hid_disconnect); ...@@ -1246,6 +1236,7 @@ EXPORT_SYMBOL_GPL(hid_disconnect);
/* a list of devices for which there is a specialized driver on HID bus */ /* a list of devices for which there is a specialized driver on HID bus */
static const struct hid_device_id hid_blacklist[] = { static const struct hid_device_id hid_blacklist[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, { HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) },
{ HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) },
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) }, { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) },
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) }, { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ATV_IRCONTROL) },
...@@ -1290,14 +1281,19 @@ static const struct hid_device_id hid_blacklist[] = { ...@@ -1290,14 +1281,19 @@ static const struct hid_device_id hid_blacklist[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) }, { HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) },
{ HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH) },
{ HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) }, { HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) },
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR) }, { HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR) }, { HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR) },
...@@ -1331,6 +1327,8 @@ static const struct hid_device_id hid_blacklist[] = { ...@@ -1331,6 +1327,8 @@ static const struct hid_device_id hid_blacklist[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K) },
...@@ -1342,7 +1340,9 @@ static const struct hid_device_id hid_blacklist[] = { ...@@ -1342,7 +1340,9 @@ static const struct hid_device_id hid_blacklist[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) },
{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) },
...@@ -1359,8 +1359,10 @@ static const struct hid_device_id hid_blacklist[] = { ...@@ -1359,8 +1359,10 @@ static const struct hid_device_id hid_blacklist[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) }, { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) }, { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) },
{ } { }
...@@ -1757,7 +1759,7 @@ int hid_add_device(struct hid_device *hdev) ...@@ -1757,7 +1759,7 @@ int hid_add_device(struct hid_device *hdev)
/* we need to kill them here, otherwise they will stay allocated to /* we need to kill them here, otherwise they will stay allocated to
* wait for coming driver */ * wait for coming driver */
if (hid_ignore(hdev)) if (!(hdev->quirks & HID_QUIRK_NO_IGNORE) && hid_ignore(hdev))
return -ENODEV; return -ENODEV;
/* XXX hack, any other cleaner solution after the driver core /* XXX hack, any other cleaner solution after the driver core
...@@ -1765,11 +1767,12 @@ int hid_add_device(struct hid_device *hdev) ...@@ -1765,11 +1767,12 @@ int hid_add_device(struct hid_device *hdev)
dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus, dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus,
hdev->vendor, hdev->product, atomic_inc_return(&id)); hdev->vendor, hdev->product, atomic_inc_return(&id));
hid_debug_register(hdev, dev_name(&hdev->dev));
ret = device_add(&hdev->dev); ret = device_add(&hdev->dev);
if (!ret) if (!ret)
hdev->status |= HID_STAT_ADDED; hdev->status |= HID_STAT_ADDED;
else
hid_debug_register(hdev, dev_name(&hdev->dev)); hid_debug_unregister(hdev);
return ret; return ret;
} }
......
/*
* HID driver for eGalax dual-touch panels
*
* Copyright (c) 2010 Stephane Chatty <chatty@enac.fr>
*
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the 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 <linux/slab.h>
#include "usbhid/usbhid.h"
MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>");
MODULE_DESCRIPTION("eGalax dual-touch panel");
MODULE_LICENSE("GPL");
#include "hid-ids.h"
struct egalax_data {
__u16 x, y, z;
__u8 id;
bool first; /* is this the first finger in the frame? */
bool valid; /* valid finger data, or just placeholder? */
bool activity; /* at least one active finger previously? */
__u16 lastx, lasty; /* latest valid (x, y) in the frame */
};
static int egalax_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_GENDESK:
switch (usage->hid) {
case HID_GD_X:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_POSITION_X);
/* touchscreen emulation */
input_set_abs_params(hi->input, ABS_X,
field->logical_minimum,
field->logical_maximum, 0, 0);
return 1;
case HID_GD_Y:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_POSITION_Y);
/* touchscreen emulation */
input_set_abs_params(hi->input, ABS_Y,
field->logical_minimum,
field->logical_maximum, 0, 0);
return 1;
}
return 0;
case HID_UP_DIGITIZER:
switch (usage->hid) {
case HID_DG_TIPSWITCH:
/* touchscreen emulation */
hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
return 1;
case HID_DG_INRANGE:
case HID_DG_CONFIDENCE:
case HID_DG_CONTACTCOUNT:
case HID_DG_CONTACTMAX:
return -1;
case HID_DG_CONTACTID:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_TRACKING_ID);
return 1;
case HID_DG_TIPPRESSURE:
hid_map_usage(hi, usage, bit, max,
EV_ABS, ABS_MT_PRESSURE);
return 1;
}
return 0;
}
/* ignore others (from other reports we won't get anyway) */
return -1;
}
static int egalax_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if (usage->type == EV_KEY || usage->type == EV_ABS)
clear_bit(usage->code, *bit);
return 0;
}
/*
* this function is called when a whole finger has been parsed,
* so that it can decide what to send to the input layer.
*/
static void egalax_filter_event(struct egalax_data *td, struct input_dev *input)
{
td->first = !td->first; /* touchscreen emulation */
if (td->valid) {
/* emit multitouch events */
input_event(input, EV_ABS, ABS_MT_TRACKING_ID, td->id);
input_event(input, EV_ABS, ABS_MT_POSITION_X, td->x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, td->y);
input_event(input, EV_ABS, ABS_MT_PRESSURE, td->z);
input_mt_sync(input);
/*
* touchscreen emulation: store (x, y) as
* the last valid values in this frame
*/
td->lastx = td->x;
td->lasty = td->y;
}
/*
* touchscreen emulation: if this is the second finger and at least
* one in this frame is valid, the latest valid in the frame is
* the oldest on the panel, the one we want for single touch
*/
if (!td->first && td->activity) {
input_event(input, EV_ABS, ABS_X, td->lastx);
input_event(input, EV_ABS, ABS_Y, td->lasty);
}
if (!td->valid) {
/*
* touchscreen emulation: if the first finger is invalid
* and there previously was finger activity, this is a release
*/
if (td->first && td->activity) {
input_event(input, EV_KEY, BTN_TOUCH, 0);
td->activity = false;
}
return;
}
/* touchscreen emulation: if no previous activity, emit touch event */
if (!td->activity) {
input_event(input, EV_KEY, BTN_TOUCH, 1);
td->activity = true;
}
}
static int egalax_event(struct hid_device *hid, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct egalax_data *td = hid_get_drvdata(hid);
if (hid->claimed & HID_CLAIMED_INPUT) {
struct input_dev *input = field->hidinput->input;
switch (usage->hid) {
case HID_DG_INRANGE:
case HID_DG_CONFIDENCE:
/* avoid interference from generic hidinput handling */
break;
case HID_DG_TIPSWITCH:
td->valid = value;
break;
case HID_DG_TIPPRESSURE:
td->z = value;
break;
case HID_DG_CONTACTID:
td->id = value;
break;
case HID_GD_X:
td->x = value;
break;
case HID_GD_Y:
td->y = value;
/* this is the last field in a finger */
egalax_filter_event(td, input);
break;
case HID_DG_CONTACTCOUNT:
/* touch emulation: this is the last field in a frame */
td->first = false;
break;
default:
/* fallback to the generic hidinput handling */
return 0;
}
}
/* we have handled the hidinput part, now remains hiddev */
if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event)
hid->hiddev_hid_event(hid, field, usage, value);
return 1;
}
static int egalax_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct egalax_data *td;
struct hid_report *report;
td = kmalloc(sizeof(struct egalax_data), GFP_KERNEL);
if (!td) {
dev_err(&hdev->dev, "cannot allocate eGalax data\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, td);
ret = hid_parse(hdev);
if (ret)
goto end;
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
goto end;
report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[5];
if (report) {
report->field[0]->value[0] = 2;
usbhid_submit_report(hdev, report, USB_DIR_OUT);
}
end:
if (ret)
kfree(td);
return ret;
}
static void egalax_remove(struct hid_device *hdev)
{
hid_hw_stop(hdev);
kfree(hid_get_drvdata(hdev));
hid_set_drvdata(hdev, NULL);
}
static const struct hid_device_id egalax_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH) },
{ }
};
MODULE_DEVICE_TABLE(hid, egalax_devices);
static const struct hid_usage_id egalax_grabbed_usages[] = {
{ HID_ANY_ID, HID_ANY_ID, HID_ANY_ID },
{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
};
static struct hid_driver egalax_driver = {
.name = "egalax-touch",
.id_table = egalax_devices,
.probe = egalax_probe,
.remove = egalax_remove,
.input_mapping = egalax_input_mapping,
.input_mapped = egalax_input_mapped,
.usage_table = egalax_grabbed_usages,
.event = egalax_event,
};
static int __init egalax_init(void)
{
return hid_register_driver(&egalax_driver);
}
static void __exit egalax_exit(void)
{
hid_unregister_driver(&egalax_driver);
}
module_init(egalax_init);
module_exit(egalax_exit);
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#define USB_VENDOR_ID_3M 0x0596 #define USB_VENDOR_ID_3M 0x0596
#define USB_DEVICE_ID_3M1968 0x0500 #define USB_DEVICE_ID_3M1968 0x0500
#define USB_DEVICE_ID_3M2256 0x0502
#define USB_VENDOR_ID_A4TECH 0x09da #define USB_VENDOR_ID_A4TECH 0x09da
#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 #define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006
...@@ -123,6 +124,13 @@ ...@@ -123,6 +124,13 @@
#define USB_VENDOR_ID_BERKSHIRE 0x0c98 #define USB_VENDOR_ID_BERKSHIRE 0x0c98
#define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140 #define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140
#define USB_VENDOR_ID_BTC 0x046e
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578
#define USB_VENDOR_ID_CANDO 0x2087
#define USB_DEVICE_ID_CANDO_MULTI_TOUCH 0x0a01
#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6 0x0b03
#define USB_VENDOR_ID_CH 0x068e #define USB_VENDOR_ID_CH 0x068e
#define USB_DEVICE_ID_CH_PRO_PEDALS 0x00f2 #define USB_DEVICE_ID_CH_PRO_PEDALS 0x00f2
#define USB_DEVICE_ID_CH_COMBATSTICK 0x00f4 #define USB_DEVICE_ID_CH_COMBATSTICK 0x00f4
...@@ -148,6 +156,9 @@ ...@@ -148,6 +156,9 @@
#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500 #define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500
#define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff #define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff
#define USB_VENDOR_ID_CREATIVELABS 0x041e
#define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801
#define USB_VENDOR_ID_CYGNAL 0x10c4 #define USB_VENDOR_ID_CYGNAL 0x10c4
#define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a #define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a
...@@ -171,6 +182,10 @@ ...@@ -171,6 +182,10 @@
#define USB_VENDOR_ID_DRAGONRISE 0x0079 #define USB_VENDOR_ID_DRAGONRISE 0x0079
#define USB_VENDOR_ID_DWAV 0x0eef
#define USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER 0x0001
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH 0x480d
#define USB_VENDOR_ID_ELO 0x04E7 #define USB_VENDOR_ID_ELO 0x04E7
#define USB_DEVICE_ID_ELO_TS2700 0x0020 #define USB_DEVICE_ID_ELO_TS2700 0x0020
...@@ -342,6 +357,8 @@ ...@@ -342,6 +357,8 @@
#define USB_VENDOR_ID_MICROCHIP 0x04d8 #define USB_VENDOR_ID_MICROCHIP 0x04d8
#define USB_DEVICE_ID_PICKIT1 0x0032 #define USB_DEVICE_ID_PICKIT1 0x0032
#define USB_DEVICE_ID_PICKIT2 0x0033 #define USB_DEVICE_ID_PICKIT2 0x0033
#define USB_DEVICE_ID_PICOLCD 0xc002
#define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002
#define USB_VENDOR_ID_MICROSOFT 0x045e #define USB_VENDOR_ID_MICROSOFT 0x045e
#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b #define USB_DEVICE_ID_SIDEWINDER_GV 0x003b
...@@ -400,6 +417,9 @@ ...@@ -400,6 +417,9 @@
#define USB_VENDOR_ID_PRODIGE 0x05af #define USB_VENDOR_ID_PRODIGE 0x05af
#define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062 #define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062
#define USB_VENDOR_ID_ROCCAT 0x1e7d
#define USB_DEVICE_ID_ROCCAT_KONE 0x2ced
#define USB_VENDOR_ID_SAITEK 0x06a3 #define USB_VENDOR_ID_SAITEK 0x06a3
#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 #define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17
...@@ -409,6 +429,7 @@ ...@@ -409,6 +429,7 @@
#define USB_VENDOR_ID_SAMSUNG 0x0419 #define USB_VENDOR_ID_SAMSUNG 0x0419
#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001 #define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001
#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE 0x0600
#define USB_VENDOR_ID_SONY 0x054c #define USB_VENDOR_ID_SONY 0x054c
#define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b
...@@ -457,6 +478,7 @@ ...@@ -457,6 +478,7 @@
#define USB_VENDOR_ID_WACOM 0x056a #define USB_VENDOR_ID_WACOM 0x056a
#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81 #define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81
#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0xbd
#define USB_VENDOR_ID_WISEGROUP 0x0925 #define USB_VENDOR_ID_WISEGROUP 0x0925
#define USB_DEVICE_ID_SMARTJOY_PLUS 0x0005 #define USB_DEVICE_ID_SMARTJOY_PLUS 0x0005
...@@ -475,6 +497,9 @@ ...@@ -475,6 +497,9 @@
#define USB_VENDOR_ID_ZEROPLUS 0x0c12 #define USB_VENDOR_ID_ZEROPLUS 0x0c12
#define USB_VENDOR_ID_ZYDACRON 0x13EC
#define USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL 0x0006
#define USB_VENDOR_ID_KYE 0x0458 #define USB_VENDOR_ID_KYE 0x0458
#define USB_DEVICE_ID_KYE_ERGO_525V 0x0087 #define USB_DEVICE_ID_KYE_ERGO_525V 0x0087
#define USB_DEVICE_ID_KYE_GPEN_560 0x5003 #define USB_DEVICE_ID_KYE_GPEN_560 0x5003
......
...@@ -126,6 +126,9 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, ...@@ -126,6 +126,9 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
case 0x1004: lg_map_key_clear(KEY_VIDEO); break; case 0x1004: lg_map_key_clear(KEY_VIDEO); break;
case 0x1005: lg_map_key_clear(KEY_AUDIO); break; case 0x1005: lg_map_key_clear(KEY_AUDIO); break;
case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); 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 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break;
case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break; case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break;
case 0x1013: lg_map_key_clear(KEY_CAMERA); break; case 0x1013: lg_map_key_clear(KEY_CAMERA); break;
...@@ -137,6 +140,7 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, ...@@ -137,6 +140,7 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
case 0x1019: lg_map_key_clear(KEY_PROG1); break; case 0x1019: lg_map_key_clear(KEY_PROG1); break;
case 0x101a: lg_map_key_clear(KEY_PROG2); break; case 0x101a: lg_map_key_clear(KEY_PROG2); break;
case 0x101b: lg_map_key_clear(KEY_PROG3); 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 0x101f: lg_map_key_clear(KEY_ZOOMIN); break;
case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break; case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break;
case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break; case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break;
...@@ -147,6 +151,11 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, ...@@ -147,6 +151,11 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break; case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break;
case 0x102a: lg_map_key_clear(KEY_BACK); break; case 0x102a: lg_map_key_clear(KEY_BACK); break;
case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); 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 0x1041: lg_map_key_clear(KEY_BATTERY); break;
case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break; case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break;
case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break; case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break;
......
...@@ -354,12 +354,15 @@ static int magicmouse_probe(struct hid_device *hdev, ...@@ -354,12 +354,15 @@ static int magicmouse_probe(struct hid_device *hdev,
goto err_free; goto err_free;
} }
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_HIDINPUT); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) { if (ret) {
dev_err(&hdev->dev, "magicmouse hw start failed\n"); dev_err(&hdev->dev, "magicmouse hw start failed\n");
goto err_free; goto err_free;
} }
/* we are handling the input ourselves */
hidinput_disconnect(hdev);
report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID); report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID);
if (!report) { if (!report) {
dev_err(&hdev->dev, "unable to register touch report\n"); dev_err(&hdev->dev, "unable to register touch report\n");
......
...@@ -24,6 +24,34 @@ ...@@ -24,6 +24,34 @@
#define NTRIG_DUPLICATE_USAGES 0x001 #define NTRIG_DUPLICATE_USAGES 0x001
static unsigned int min_width;
module_param(min_width, uint, 0644);
MODULE_PARM_DESC(min_width, "Minimum touch contact width to accept.");
static unsigned int min_height;
module_param(min_height, uint, 0644);
MODULE_PARM_DESC(min_height, "Minimum touch contact height to accept.");
static unsigned int activate_slack = 1;
module_param(activate_slack, uint, 0644);
MODULE_PARM_DESC(activate_slack, "Number of touch frames to ignore at "
"the start of touch input.");
static unsigned int deactivate_slack = 4;
module_param(deactivate_slack, uint, 0644);
MODULE_PARM_DESC(deactivate_slack, "Number of empty frames to ignore before "
"deactivating touch.");
static unsigned int activation_width = 64;
module_param(activation_width, uint, 0644);
MODULE_PARM_DESC(activation_width, "Width threshold to immediately start "
"processing touch events.");
static unsigned int activation_height = 32;
module_param(activation_height, uint, 0644);
MODULE_PARM_DESC(activation_height, "Height threshold to immediately start "
"processing touch events.");
struct ntrig_data { struct ntrig_data {
/* Incoming raw values for a single contact */ /* Incoming raw values for a single contact */
__u16 x, y, w, h; __u16 x, y, w, h;
...@@ -37,6 +65,309 @@ struct ntrig_data { ...@@ -37,6 +65,309 @@ struct ntrig_data {
__u8 mt_footer[4]; __u8 mt_footer[4];
__u8 mt_foot_count; __u8 mt_foot_count;
/* The current activation state. */
__s8 act_state;
/* Empty frames to ignore before recognizing the end of activity */
__s8 deactivate_slack;
/* Frames to ignore before acknowledging the start of activity */
__s8 activate_slack;
/* Minimum size contact to accept */
__u16 min_width;
__u16 min_height;
/* Threshold to override activation slack */
__u16 activation_width;
__u16 activation_height;
__u16 sensor_logical_width;
__u16 sensor_logical_height;
__u16 sensor_physical_width;
__u16 sensor_physical_height;
};
static ssize_t show_phys_width(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->sensor_physical_width);
}
static DEVICE_ATTR(sensor_physical_width, S_IRUGO, show_phys_width, NULL);
static ssize_t show_phys_height(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->sensor_physical_height);
}
static DEVICE_ATTR(sensor_physical_height, S_IRUGO, show_phys_height, NULL);
static ssize_t show_log_width(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->sensor_logical_width);
}
static DEVICE_ATTR(sensor_logical_width, S_IRUGO, show_log_width, NULL);
static ssize_t show_log_height(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->sensor_logical_height);
}
static DEVICE_ATTR(sensor_logical_height, S_IRUGO, show_log_height, NULL);
static ssize_t show_min_width(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->min_width *
nd->sensor_physical_width /
nd->sensor_logical_width);
}
static ssize_t set_min_width(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
unsigned long val;
if (strict_strtoul(buf, 0, &val))
return -EINVAL;
if (val > nd->sensor_physical_width)
return -EINVAL;
nd->min_width = val * nd->sensor_logical_width /
nd->sensor_physical_width;
return count;
}
static DEVICE_ATTR(min_width, S_IWUSR | S_IRUGO, show_min_width, set_min_width);
static ssize_t show_min_height(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->min_height *
nd->sensor_physical_height /
nd->sensor_logical_height);
}
static ssize_t set_min_height(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
unsigned long val;
if (strict_strtoul(buf, 0, &val))
return -EINVAL;
if (val > nd->sensor_physical_height)
return -EINVAL;
nd->min_height = val * nd->sensor_logical_height /
nd->sensor_physical_height;
return count;
}
static DEVICE_ATTR(min_height, S_IWUSR | S_IRUGO, show_min_height,
set_min_height);
static ssize_t show_activate_slack(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->activate_slack);
}
static ssize_t set_activate_slack(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
unsigned long val;
if (strict_strtoul(buf, 0, &val))
return -EINVAL;
if (val > 0x7f)
return -EINVAL;
nd->activate_slack = val;
return count;
}
static DEVICE_ATTR(activate_slack, S_IWUSR | S_IRUGO, show_activate_slack,
set_activate_slack);
static ssize_t show_activation_width(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->activation_width *
nd->sensor_physical_width /
nd->sensor_logical_width);
}
static ssize_t set_activation_width(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
unsigned long val;
if (strict_strtoul(buf, 0, &val))
return -EINVAL;
if (val > nd->sensor_physical_width)
return -EINVAL;
nd->activation_width = val * nd->sensor_logical_width /
nd->sensor_physical_width;
return count;
}
static DEVICE_ATTR(activation_width, S_IWUSR | S_IRUGO, show_activation_width,
set_activation_width);
static ssize_t show_activation_height(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", nd->activation_height *
nd->sensor_physical_height /
nd->sensor_logical_height);
}
static ssize_t set_activation_height(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
unsigned long val;
if (strict_strtoul(buf, 0, &val))
return -EINVAL;
if (val > nd->sensor_physical_height)
return -EINVAL;
nd->activation_height = val * nd->sensor_logical_height /
nd->sensor_physical_height;
return count;
}
static DEVICE_ATTR(activation_height, S_IWUSR | S_IRUGO,
show_activation_height, set_activation_height);
static ssize_t show_deactivate_slack(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
return sprintf(buf, "%d\n", -nd->deactivate_slack);
}
static ssize_t set_deactivate_slack(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct ntrig_data *nd = hid_get_drvdata(hdev);
unsigned long val;
if (strict_strtoul(buf, 0, &val))
return -EINVAL;
/*
* No more than 8 terminal frames have been observed so far
* and higher slack is highly likely to leave the single
* touch emulation stuck down.
*/
if (val > 7)
return -EINVAL;
nd->deactivate_slack = -val;
return count;
}
static DEVICE_ATTR(deactivate_slack, S_IWUSR | S_IRUGO, show_deactivate_slack,
set_deactivate_slack);
static struct attribute *sysfs_attrs[] = {
&dev_attr_sensor_physical_width.attr,
&dev_attr_sensor_physical_height.attr,
&dev_attr_sensor_logical_width.attr,
&dev_attr_sensor_logical_height.attr,
&dev_attr_min_height.attr,
&dev_attr_min_width.attr,
&dev_attr_activate_slack.attr,
&dev_attr_activation_width.attr,
&dev_attr_activation_height.attr,
&dev_attr_deactivate_slack.attr,
NULL
};
static struct attribute_group ntrig_attribute_group = {
.attrs = sysfs_attrs
}; };
/* /*
...@@ -49,6 +380,8 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, ...@@ -49,6 +380,8 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage, struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max) unsigned long **bit, int *max)
{ {
struct ntrig_data *nd = hid_get_drvdata(hdev);
/* No special mappings needed for the pen and single touch */ /* No special mappings needed for the pen and single touch */
if (field->physical) if (field->physical)
return 0; return 0;
...@@ -62,6 +395,21 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, ...@@ -62,6 +395,21 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi,
input_set_abs_params(hi->input, ABS_X, input_set_abs_params(hi->input, ABS_X,
field->logical_minimum, field->logical_minimum,
field->logical_maximum, 0, 0); field->logical_maximum, 0, 0);
if (!nd->sensor_logical_width) {
nd->sensor_logical_width =
field->logical_maximum -
field->logical_minimum;
nd->sensor_physical_width =
field->physical_maximum -
field->physical_minimum;
nd->activation_width = activation_width *
nd->sensor_logical_width /
nd->sensor_physical_width;
nd->min_width = min_width *
nd->sensor_logical_width /
nd->sensor_physical_width;
}
return 1; return 1;
case HID_GD_Y: case HID_GD_Y:
hid_map_usage(hi, usage, bit, max, hid_map_usage(hi, usage, bit, max,
...@@ -69,6 +417,21 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, ...@@ -69,6 +417,21 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi,
input_set_abs_params(hi->input, ABS_Y, input_set_abs_params(hi->input, ABS_Y,
field->logical_minimum, field->logical_minimum,
field->logical_maximum, 0, 0); field->logical_maximum, 0, 0);
if (!nd->sensor_logical_height) {
nd->sensor_logical_height =
field->logical_maximum -
field->logical_minimum;
nd->sensor_physical_height =
field->physical_maximum -
field->physical_minimum;
nd->activation_height = activation_height *
nd->sensor_logical_height /
nd->sensor_physical_height;
nd->min_height = min_height *
nd->sensor_logical_height /
nd->sensor_physical_height;
}
return 1; return 1;
} }
return 0; return 0;
...@@ -201,20 +564,68 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, ...@@ -201,20 +564,68 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field,
if (nd->mt_foot_count != 4) if (nd->mt_foot_count != 4)
break; break;
/* Pen activity signal, trigger end of touch. */ /* Pen activity signal. */
if (nd->mt_footer[2]) { if (nd->mt_footer[2]) {
/*
* When the pen deactivates touch, we see a
* bogus frame with ContactCount > 0.
* We can
* save a bit of work by ensuring act_state < 0
* even if deactivation slack is turned off.
*/
nd->act_state = deactivate_slack - 1;
nd->confidence = 0; nd->confidence = 0;
break; break;
} }
/* If the contact was invalid */ /*
if (!(nd->confidence && nd->mt_footer[0]) * The first footer value indicates the presence of a
|| nd->w <= 250 * finger.
|| nd->h <= 190) { */
if (nd->mt_footer[0]) {
/*
* We do not want to process contacts under
* the size threshold, but do not want to
* ignore them for activation state
*/
if (nd->w < nd->min_width ||
nd->h < nd->min_height)
nd->confidence = 0; nd->confidence = 0;
} else
break;
if (nd->act_state > 0) {
/*
* Contact meets the activation size threshold
*/
if (nd->w >= nd->activation_width &&
nd->h >= nd->activation_height) {
if (nd->id)
/*
* first contact, activate now
*/
nd->act_state = 0;
else {
/*
* avoid corrupting this frame
* but ensure next frame will
* be active
*/
nd->act_state = 1;
break;
}
} else
/*
* Defer adjusting the activation state
* until the end of the frame.
*/
break; break;
} }
/* Discarding this contact */
if (!nd->confidence)
break;
/* emit a normal (X, Y) for the first point only */ /* emit a normal (X, Y) for the first point only */
if (nd->id == 0) { if (nd->id == 0) {
/* /*
...@@ -227,8 +638,15 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, ...@@ -227,8 +638,15 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field,
input_event(input, EV_ABS, ABS_X, nd->x); input_event(input, EV_ABS, ABS_X, nd->x);
input_event(input, EV_ABS, ABS_Y, nd->y); input_event(input, EV_ABS, ABS_Y, nd->y);
} }
/* Emit MT events */
input_event(input, EV_ABS, ABS_MT_POSITION_X, nd->x); input_event(input, EV_ABS, ABS_MT_POSITION_X, nd->x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, nd->y); input_event(input, EV_ABS, ABS_MT_POSITION_Y, nd->y);
/*
* Translate from height and width to size
* and orientation.
*/
if (nd->w > nd->h) { if (nd->w > nd->h) {
input_event(input, EV_ABS, input_event(input, EV_ABS,
ABS_MT_ORIENTATION, 1); ABS_MT_ORIENTATION, 1);
...@@ -248,12 +666,88 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, ...@@ -248,12 +666,88 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field,
break; break;
case HID_DG_CONTACTCOUNT: /* End of a multitouch group */ case HID_DG_CONTACTCOUNT: /* End of a multitouch group */
if (!nd->reading_mt) if (!nd->reading_mt) /* Just to be sure */
break; break;
nd->reading_mt = 0; nd->reading_mt = 0;
if (nd->first_contact_touch) {
/*
* Activation state machine logic:
*
* Fundamental states:
* state > 0: Inactive
* state <= 0: Active
* state < -deactivate_slack:
* Pen termination of touch
*
* Specific values of interest
* state == activate_slack
* no valid input since the last reset
*
* state == 0
* general operational state
*
* state == -deactivate_slack
* read sufficient empty frames to accept
* the end of input and reset
*/
if (nd->act_state > 0) { /* Currently inactive */
if (value)
/*
* Consider each live contact as
* evidence of intentional activity.
*/
nd->act_state = (nd->act_state > value)
? nd->act_state - value
: 0;
else
/*
* Empty frame before we hit the
* activity threshold, reset.
*/
nd->act_state = nd->activate_slack;
/*
* Entered this block inactive and no
* coordinates sent this frame, so hold off
* on button state.
*/
break;
} else { /* Currently active */
if (value && nd->act_state >=
nd->deactivate_slack)
/*
* Live point: clear accumulated
* deactivation count.
*/
nd->act_state = 0;
else if (nd->act_state <= nd->deactivate_slack)
/*
* We've consumed the deactivation
* slack, time to deactivate and reset.
*/
nd->act_state =
nd->activate_slack;
else { /* Move towards deactivation */
nd->act_state--;
break;
}
}
if (nd->first_contact_touch && nd->act_state <= 0) {
/*
* Check to see if we're ready to start
* emitting touch events.
*
* Note: activation slack will decrease over
* the course of the frame, and it will be
* inconsistent from the start to the end of
* the frame. However if the frame starts
* with slack, first_contact_touch will still
* be 0 and we will not get to this point.
*/
input_report_key(input, BTN_TOOL_DOUBLETAP, 1); input_report_key(input, BTN_TOOL_DOUBLETAP, 1);
input_report_key(input, BTN_TOUCH, 1); input_report_key(input, BTN_TOUCH, 1);
} else { } else {
...@@ -263,7 +757,7 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field, ...@@ -263,7 +757,7 @@ static int ntrig_event (struct hid_device *hid, struct hid_field *field,
break; break;
default: default:
/* fallback to the generic hidinput handling */ /* fall-back to the generic hidinput handling */
return 0; return 0;
} }
} }
...@@ -293,6 +787,16 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -293,6 +787,16 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
} }
nd->reading_mt = 0; nd->reading_mt = 0;
nd->min_width = 0;
nd->min_height = 0;
nd->activate_slack = activate_slack;
nd->act_state = activate_slack;
nd->deactivate_slack = -deactivate_slack;
nd->sensor_logical_width = 0;
nd->sensor_logical_height = 0;
nd->sensor_physical_width = 0;
nd->sensor_physical_height = 0;
hid_set_drvdata(hdev, nd); hid_set_drvdata(hdev, nd);
ret = hid_parse(hdev); ret = hid_parse(hdev);
...@@ -344,6 +848,8 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -344,6 +848,8 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (report) if (report)
usbhid_submit_report(hdev, report, USB_DIR_OUT); usbhid_submit_report(hdev, report, USB_DIR_OUT);
ret = sysfs_create_group(&hdev->dev.kobj,
&ntrig_attribute_group);
return 0; return 0;
err_free: err_free:
...@@ -353,6 +859,8 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -353,6 +859,8 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
static void ntrig_remove(struct hid_device *hdev) static void ntrig_remove(struct hid_device *hdev)
{ {
sysfs_remove_group(&hdev->dev.kobj,
&ntrig_attribute_group);
hid_hw_stop(hdev); hid_hw_stop(hdev);
kfree(hid_get_drvdata(hdev)); kfree(hid_get_drvdata(hdev));
} }
......
/***************************************************************************
* Copyright (C) 2010 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/hid-debug.h>
#include <linux/input.h>
#include "hid-ids.h"
#include "usbhid/usbhid.h"
#include <linux/usb.h>
#include <linux/fb.h>
#include <linux/vmalloc.h>
#include <linux/backlight.h>
#include <linux/lcd.h>
#include <linux/leds.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/completion.h>
#include <linux/uaccess.h>
#define PICOLCD_NAME "PicoLCD (graphic)"
/* Report numbers */
#define REPORT_ERROR_CODE 0x10 /* LCD: IN[16] */
#define ERR_SUCCESS 0x00
#define ERR_PARAMETER_MISSING 0x01
#define ERR_DATA_MISSING 0x02
#define ERR_BLOCK_READ_ONLY 0x03
#define ERR_BLOCK_NOT_ERASABLE 0x04
#define ERR_BLOCK_TOO_BIG 0x05
#define ERR_SECTION_OVERFLOW 0x06
#define ERR_INVALID_CMD_LEN 0x07
#define ERR_INVALID_DATA_LEN 0x08
#define REPORT_KEY_STATE 0x11 /* LCD: IN[2] */
#define REPORT_IR_DATA 0x21 /* LCD: IN[63] */
#define REPORT_EE_DATA 0x32 /* LCD: IN[63] */
#define REPORT_MEMORY 0x41 /* LCD: IN[63] */
#define REPORT_LED_STATE 0x81 /* LCD: OUT[1] */
#define REPORT_BRIGHTNESS 0x91 /* LCD: OUT[1] */
#define REPORT_CONTRAST 0x92 /* LCD: OUT[1] */
#define REPORT_RESET 0x93 /* LCD: OUT[2] */
#define REPORT_LCD_CMD 0x94 /* LCD: OUT[63] */
#define REPORT_LCD_DATA 0x95 /* LCD: OUT[63] */
#define REPORT_LCD_CMD_DATA 0x96 /* LCD: OUT[63] */
#define REPORT_EE_READ 0xa3 /* LCD: OUT[63] */
#define REPORT_EE_WRITE 0xa4 /* LCD: OUT[63] */
#define REPORT_ERASE_MEMORY 0xb2 /* LCD: OUT[2] */
#define REPORT_READ_MEMORY 0xb3 /* LCD: OUT[3] */
#define REPORT_WRITE_MEMORY 0xb4 /* LCD: OUT[63] */
#define REPORT_SPLASH_RESTART 0xc1 /* LCD: OUT[1] */
#define REPORT_EXIT_KEYBOARD 0xef /* LCD: OUT[2] */
#define REPORT_VERSION 0xf1 /* LCD: IN[2],OUT[1] Bootloader: IN[2],OUT[1] */
#define REPORT_BL_ERASE_MEMORY 0xf2 /* Bootloader: IN[36],OUT[4] */
#define REPORT_BL_READ_MEMORY 0xf3 /* Bootloader: IN[36],OUT[4] */
#define REPORT_BL_WRITE_MEMORY 0xf4 /* Bootloader: IN[36],OUT[36] */
#define REPORT_DEVID 0xf5 /* LCD: IN[5], OUT[1] Bootloader: IN[5],OUT[1] */
#define REPORT_SPLASH_SIZE 0xf6 /* LCD: IN[4], OUT[1] */
#define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */
#define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */
#ifdef CONFIG_HID_PICOLCD_FB
/* Framebuffer
*
* The PicoLCD use a Topway LCD module of 256x64 pixel
* This display area is tiled over 4 controllers with 8 tiles
* each. Each tile has 8x64 pixel, each data byte representing
* a 1-bit wide vertical line of the tile.
*
* The display can be updated at a tile granularity.
*
* Chip 1 Chip 2 Chip 3 Chip 4
* +----------------+----------------+----------------+----------------+
* | Tile 1 | Tile 1 | Tile 1 | Tile 1 |
* +----------------+----------------+----------------+----------------+
* | Tile 2 | Tile 2 | Tile 2 | Tile 2 |
* +----------------+----------------+----------------+----------------+
* ...
* +----------------+----------------+----------------+----------------+
* | Tile 8 | Tile 8 | Tile 8 | Tile 8 |
* +----------------+----------------+----------------+----------------+
*/
#define PICOLCDFB_NAME "picolcdfb"
#define PICOLCDFB_WIDTH (256)
#define PICOLCDFB_HEIGHT (64)
#define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8)
#define PICOLCDFB_UPDATE_RATE_LIMIT 10
#define PICOLCDFB_UPDATE_RATE_DEFAULT 2
/* Framebuffer visual structures */
static const struct fb_fix_screeninfo picolcdfb_fix = {
.id = PICOLCDFB_NAME,
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_MONO01,
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
.line_length = PICOLCDFB_WIDTH / 8,
.accel = FB_ACCEL_NONE,
};
static const struct fb_var_screeninfo picolcdfb_var = {
.xres = PICOLCDFB_WIDTH,
.yres = PICOLCDFB_HEIGHT,
.xres_virtual = PICOLCDFB_WIDTH,
.yres_virtual = PICOLCDFB_HEIGHT,
.width = 103,
.height = 26,
.bits_per_pixel = 1,
.grayscale = 1,
};
#endif /* CONFIG_HID_PICOLCD_FB */
/* Input device
*
* The PicoLCD has an IR receiver header, a built-in keypad with 5 keys
* and header for 4x4 key matrix. The built-in keys are part of the matrix.
*/
static const unsigned short def_keymap[] = {
KEY_RESERVED, /* none */
KEY_BACK, /* col 4 + row 1 */
KEY_HOMEPAGE, /* col 3 + row 1 */
KEY_RESERVED, /* col 2 + row 1 */
KEY_RESERVED, /* col 1 + row 1 */
KEY_SCROLLUP, /* col 4 + row 2 */
KEY_OK, /* col 3 + row 2 */
KEY_SCROLLDOWN, /* col 2 + row 2 */
KEY_RESERVED, /* col 1 + row 2 */
KEY_RESERVED, /* col 4 + row 3 */
KEY_RESERVED, /* col 3 + row 3 */
KEY_RESERVED, /* col 2 + row 3 */
KEY_RESERVED, /* col 1 + row 3 */
KEY_RESERVED, /* col 4 + row 4 */
KEY_RESERVED, /* col 3 + row 4 */
KEY_RESERVED, /* col 2 + row 4 */
KEY_RESERVED, /* col 1 + row 4 */
};
#define PICOLCD_KEYS ARRAY_SIZE(def_keymap)
/* Description of in-progress IO operation, used for operations
* that trigger response from device */
struct picolcd_pending {
struct hid_report *out_report;
struct hid_report *in_report;
struct completion ready;
int raw_size;
u8 raw_data[64];
};
/* Per device data structure */
struct picolcd_data {
struct hid_device *hdev;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_reset;
struct dentry *debug_eeprom;
struct dentry *debug_flash;
struct mutex mutex_flash;
int addr_sz;
#endif
u8 version[2];
unsigned short opmode_delay;
/* input stuff */
u8 pressed_keys[2];
struct input_dev *input_keys;
struct input_dev *input_cir;
unsigned short keycode[PICOLCD_KEYS];
#ifdef CONFIG_HID_PICOLCD_FB
/* Framebuffer stuff */
u8 fb_update_rate;
u8 fb_bpp;
u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */
u8 *fb_bitmap; /* framebuffer */
struct fb_info *fb_info;
struct fb_deferred_io fb_defio;
#endif /* CONFIG_HID_PICOLCD_FB */
#ifdef CONFIG_HID_PICOLCD_LCD
struct lcd_device *lcd;
u8 lcd_contrast;
#endif /* CONFIG_HID_PICOLCD_LCD */
#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
struct backlight_device *backlight;
u8 lcd_brightness;
u8 lcd_power;
#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */
#ifdef CONFIG_HID_PICOLCD_LEDS
/* LED stuff */
u8 led_state;
struct led_classdev *led[8];
#endif /* CONFIG_HID_PICOLCD_LEDS */
/* Housekeeping stuff */
spinlock_t lock;
struct mutex mutex;
struct picolcd_pending *pending;
int status;
#define PICOLCD_BOOTLOADER 1
#define PICOLCD_FAILED 2
#define PICOLCD_READY_FB 4
};
/* Find a given report */
#define picolcd_in_report(id, dev) picolcd_report(id, dev, HID_INPUT_REPORT)
#define picolcd_out_report(id, dev) picolcd_report(id, dev, HID_OUTPUT_REPORT)
static struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir)
{
struct list_head *feature_report_list = &hdev->report_enum[dir].report_list;
struct hid_report *report = NULL;
list_for_each_entry(report, feature_report_list, list) {
if (report->id == id)
return report;
}
dev_warn(&hdev->dev, "No report with id 0x%x found\n", id);
return NULL;
}
#ifdef CONFIG_DEBUG_FS
static void picolcd_debug_out_report(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report);
#define usbhid_submit_report(a, b, c) \
do { \
picolcd_debug_out_report(hid_get_drvdata(a), a, b); \
usbhid_submit_report(a, b, c); \
} while (0)
#endif
/* Submit a report and wait for a reply from device - if device fades away
* or does not respond in time, return NULL */
static struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
int report_id, const u8 *raw_data, int size)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct picolcd_pending *work;
struct hid_report *report = picolcd_out_report(report_id, hdev);
unsigned long flags;
int i, j, k;
if (!report || !data)
return NULL;
if (data->status & PICOLCD_FAILED)
return NULL;
work = kzalloc(sizeof(*work), GFP_KERNEL);
if (!work)
return NULL;
init_completion(&work->ready);
work->out_report = report;
work->in_report = NULL;
work->raw_size = 0;
mutex_lock(&data->mutex);
spin_lock_irqsave(&data->lock, flags);
for (i = k = 0; i < report->maxfield; i++)
for (j = 0; j < report->field[i]->report_count; j++) {
hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0);
k++;
}
data->pending = work;
usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
wait_for_completion_interruptible_timeout(&work->ready, HZ*2);
spin_lock_irqsave(&data->lock, flags);
data->pending = NULL;
spin_unlock_irqrestore(&data->lock, flags);
mutex_unlock(&data->mutex);
return work;
}
#ifdef CONFIG_HID_PICOLCD_FB
/* Send a given tile to PicoLCD */
static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int tile)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct hid_report *report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, hdev);
struct hid_report *report2 = picolcd_out_report(REPORT_LCD_DATA, hdev);
unsigned long flags;
u8 *tdata;
int i;
if (!report1 || report1->maxfield != 1 || !report2 || report2->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report1->field[0], 0, chip << 2);
hid_set_field(report1->field[0], 1, 0x02);
hid_set_field(report1->field[0], 2, 0x00);
hid_set_field(report1->field[0], 3, 0x00);
hid_set_field(report1->field[0], 4, 0xb8 | tile);
hid_set_field(report1->field[0], 5, 0x00);
hid_set_field(report1->field[0], 6, 0x00);
hid_set_field(report1->field[0], 7, 0x40);
hid_set_field(report1->field[0], 8, 0x00);
hid_set_field(report1->field[0], 9, 0x00);
hid_set_field(report1->field[0], 10, 32);
hid_set_field(report2->field[0], 0, (chip << 2) | 0x01);
hid_set_field(report2->field[0], 1, 0x00);
hid_set_field(report2->field[0], 2, 0x00);
hid_set_field(report2->field[0], 3, 32);
tdata = data->fb_vbitmap + (tile * 4 + chip) * 64;
for (i = 0; i < 64; i++)
if (i < 32)
hid_set_field(report1->field[0], 11 + i, tdata[i]);
else
hid_set_field(report2->field[0], 4 + i - 32, tdata[i]);
usbhid_submit_report(data->hdev, report1, USB_DIR_OUT);
usbhid_submit_report(data->hdev, report2, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
/* Translate a single tile*/
static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
int chip, int tile)
{
int i, b, changed = 0;
u8 tdata[64];
u8 *vdata = vbitmap + (tile * 4 + chip) * 64;
if (bpp == 1) {
for (b = 7; b >= 0; b--) {
const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
for (i = 0; i < 64; i++) {
tdata[i] <<= 1;
tdata[i] |= (bdata[i/8] >> (7 - i % 8)) & 0x01;
}
}
} else if (bpp == 8) {
for (b = 7; b >= 0; b--) {
const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8;
for (i = 0; i < 64; i++) {
tdata[i] <<= 1;
tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00;
}
}
} else {
/* Oops, we should never get here! */
WARN_ON(1);
return 0;
}
for (i = 0; i < 64; i++)
if (tdata[i] != vdata[i]) {
changed = 1;
vdata[i] = tdata[i];
}
return changed;
}
/* Reconfigure LCD display */
static int picolcd_fb_reset(struct picolcd_data *data, int clear)
{
struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
int i, j;
unsigned long flags;
static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 };
if (!report || report->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
for (i = 0; i < 4; i++) {
for (j = 0; j < report->field[0]->maxusage; j++)
if (j == 0)
hid_set_field(report->field[0], j, i << 2);
else if (j < sizeof(mapcmd))
hid_set_field(report->field[0], j, mapcmd[j]);
else
hid_set_field(report->field[0], j, 0);
usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
}
data->status |= PICOLCD_READY_FB;
spin_unlock_irqrestore(&data->lock, flags);
if (data->fb_bitmap) {
if (clear) {
memset(data->fb_vbitmap, 0xff, PICOLCDFB_SIZE);
memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp);
} else {
/* invert 1 byte in each tile to force resend */
for (i = 0; i < PICOLCDFB_SIZE; i += 64)
data->fb_vbitmap[i] = ~data->fb_vbitmap[i];
}
}
/* schedule first output of framebuffer */
if (data->fb_info)
schedule_delayed_work(&data->fb_info->deferred_work, 0);
return 0;
}
/* Update fb_vbitmap from the screen_base and send changed tiles to device */
static void picolcd_fb_update(struct picolcd_data *data)
{
int chip, tile, n;
unsigned long flags;
spin_lock_irqsave(&data->lock, flags);
if (!(data->status & PICOLCD_READY_FB)) {
spin_unlock_irqrestore(&data->lock, flags);
picolcd_fb_reset(data, 0);
} else {
spin_unlock_irqrestore(&data->lock, flags);
}
/*
* Translate the framebuffer into the format needed by the PicoLCD.
* See display layout above.
* Do this one tile after the other and push those tiles that changed.
*
* Wait for our IO to complete as otherwise we might flood the queue!
*/
n = 0;
for (chip = 0; chip < 4; chip++)
for (tile = 0; tile < 8; tile++)
if (picolcd_fb_update_tile(data->fb_vbitmap,
data->fb_bitmap, data->fb_bpp, chip, tile)) {
n += 2;
if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
usbhid_wait_io(data->hdev);
n = 0;
}
picolcd_fb_send_tile(data->hdev, chip, tile);
}
if (n)
usbhid_wait_io(data->hdev);
}
/* Stub to call the system default and update the image on the picoLCD */
static void picolcd_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
if (!info->par)
return;
sys_fillrect(info, rect);
schedule_delayed_work(&info->deferred_work, 0);
}
/* Stub to call the system default and update the image on the picoLCD */
static void picolcd_fb_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
if (!info->par)
return;
sys_copyarea(info, area);
schedule_delayed_work(&info->deferred_work, 0);
}
/* Stub to call the system default and update the image on the picoLCD */
static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
{
if (!info->par)
return;
sys_imageblit(info, image);
schedule_delayed_work(&info->deferred_work, 0);
}
/*
* this is the slow path from userspace. they can seek and write to
* the fb. it's inefficient to do anything less than a full screen draw
*/
static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
ssize_t ret;
if (!info->par)
return -ENODEV;
ret = fb_sys_write(info, buf, count, ppos);
if (ret >= 0)
schedule_delayed_work(&info->deferred_work, 0);
return ret;
}
static int picolcd_fb_blank(int blank, struct fb_info *info)
{
if (!info->par)
return -ENODEV;
/* We let fb notification do this for us via lcd/backlight device */
return 0;
}
static void picolcd_fb_destroy(struct fb_info *info)
{
struct picolcd_data *data = info->par;
info->par = NULL;
if (data)
data->fb_info = NULL;
fb_deferred_io_cleanup(info);
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;
else
var->bits_per_pixel = 1;
return 0;
}
static int picolcd_set_par(struct fb_info *info)
{
struct picolcd_data *data = info->par;
u8 *o_fb, *n_fb;
if (info->var.bits_per_pixel == data->fb_bpp)
return 0;
/* switch between 1/8 bit depths */
if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
return -EINVAL;
o_fb = data->fb_bitmap;
n_fb = vmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel);
if (!n_fb)
return -ENOMEM;
fb_deferred_io_cleanup(info);
/* 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;
}
}
info->fix.visual = FB_VISUAL_MONO01;
info->fix.line_length = PICOLCDFB_WIDTH / 8;
} else {
int i;
for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
n_fb[i] = o_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.line_length = PICOLCDFB_WIDTH;
}
data->fb_bitmap = n_fb;
data->fb_bpp = info->var.bits_per_pixel;
info->screen_base = (char __force __iomem *)n_fb;
info->fix.smem_start = (unsigned long)n_fb;
info->fix.smem_len = PICOLCDFB_SIZE*data->fb_bpp;
fb_deferred_io_init(info);
vfree(o_fb);
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->par);
}
static const struct fb_deferred_io picolcd_fb_defio = {
.delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
.deferred_io = picolcd_fb_deferred_io,
};
/*
* The "fb_update_rate" sysfs attribute
*/
static ssize_t picolcd_fb_update_rate_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct picolcd_data *data = dev_get_drvdata(dev);
unsigned i, fb_update_rate = data->fb_update_rate;
size_t ret = 0;
for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
if (ret >= PAGE_SIZE)
break;
else if (i == fb_update_rate)
ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
else
ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
if (ret > 0)
buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
return ret;
}
static ssize_t picolcd_fb_update_rate_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct picolcd_data *data = dev_get_drvdata(dev);
int i;
unsigned u;
if (count < 1 || count > 10)
return -EINVAL;
i = sscanf(buf, "%u", &u);
if (i != 1)
return -EINVAL;
if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
return -ERANGE;
else if (u == 0)
u = PICOLCDFB_UPDATE_RATE_DEFAULT;
data->fb_update_rate = u;
data->fb_defio.delay = HZ / data->fb_update_rate;
return count;
}
static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show,
picolcd_fb_update_rate_store);
/* initialize Framebuffer device */
static int picolcd_init_framebuffer(struct picolcd_data *data)
{
struct device *dev = &data->hdev->dev;
struct fb_info *info = NULL;
int error = -ENOMEM;
u8 *fb_vbitmap = NULL;
u8 *fb_bitmap = NULL;
fb_bitmap = vmalloc(PICOLCDFB_SIZE*picolcdfb_var.bits_per_pixel);
if (fb_bitmap == NULL) {
dev_err(dev, "can't get a free page for framebuffer\n");
goto err_nomem;
}
fb_vbitmap = kmalloc(PICOLCDFB_SIZE, GFP_KERNEL);
if (fb_vbitmap == NULL) {
dev_err(dev, "can't alloc vbitmap image buffer\n");
goto err_nomem;
}
data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
data->fb_defio = picolcd_fb_defio;
info = framebuffer_alloc(0, dev);
if (info == NULL) {
dev_err(dev, "failed to allocate a framebuffer\n");
goto err_nomem;
}
info->fbdefio = &data->fb_defio;
info->screen_base = (char __force __iomem *)fb_bitmap;
info->fbops = &picolcdfb_ops;
info->var = picolcdfb_var;
info->fix = picolcdfb_fix;
info->fix.smem_len = PICOLCDFB_SIZE;
info->fix.smem_start = (unsigned long)fb_bitmap;
info->par = data;
info->flags = FBINFO_FLAG_DEFAULT;
data->fb_vbitmap = fb_vbitmap;
data->fb_bitmap = fb_bitmap;
data->fb_bpp = picolcdfb_var.bits_per_pixel;
error = picolcd_fb_reset(data, 1);
if (error) {
dev_err(dev, "failed to configure display\n");
goto err_cleanup;
}
error = device_create_file(dev, &dev_attr_fb_update_rate);
if (error) {
dev_err(dev, "failed to create sysfs attributes\n");
goto err_cleanup;
}
data->fb_info = info;
error = register_framebuffer(info);
if (error) {
dev_err(dev, "failed to register framebuffer\n");
goto err_sysfs;
}
fb_deferred_io_init(info);
/* schedule first output of framebuffer */
schedule_delayed_work(&info->deferred_work, 0);
return 0;
err_sysfs:
device_remove_file(dev, &dev_attr_fb_update_rate);
err_cleanup:
data->fb_vbitmap = NULL;
data->fb_bitmap = NULL;
data->fb_bpp = 0;
data->fb_info = NULL;
err_nomem:
framebuffer_release(info);
vfree(fb_bitmap);
kfree(fb_vbitmap);
return error;
}
static void picolcd_exit_framebuffer(struct picolcd_data *data)
{
struct fb_info *info = data->fb_info;
u8 *fb_vbitmap = data->fb_vbitmap;
u8 *fb_bitmap = data->fb_bitmap;
if (!info)
return;
data->fb_vbitmap = NULL;
data->fb_bitmap = NULL;
data->fb_bpp = 0;
data->fb_info = NULL;
device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
fb_deferred_io_cleanup(info);
unregister_framebuffer(info);
vfree(fb_bitmap);
kfree(fb_vbitmap);
}
#define picolcd_fbinfo(d) ((d)->fb_info)
#else
static inline int picolcd_fb_reset(struct picolcd_data *data, int clear)
{
return 0;
}
static inline int picolcd_init_framebuffer(struct picolcd_data *data)
{
return 0;
}
static inline void picolcd_exit_framebuffer(struct picolcd_data *data)
{
}
#define picolcd_fbinfo(d) NULL
#endif /* CONFIG_HID_PICOLCD_FB */
#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
/*
* backlight class device
*/
static int picolcd_get_brightness(struct backlight_device *bdev)
{
struct picolcd_data *data = bl_get_data(bdev);
return data->lcd_brightness;
}
static int picolcd_set_brightness(struct backlight_device *bdev)
{
struct picolcd_data *data = bl_get_data(bdev);
struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev);
unsigned long flags;
if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
return -ENODEV;
data->lcd_brightness = bdev->props.brightness & 0x0ff;
data->lcd_power = bdev->props.power;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0);
usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb)
{
return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev));
}
static const struct backlight_ops picolcd_blops = {
.update_status = picolcd_set_brightness,
.get_brightness = picolcd_get_brightness,
.check_fb = picolcd_check_bl_fb,
};
static int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report)
{
struct device *dev = &data->hdev->dev;
struct backlight_device *bdev;
struct backlight_properties props;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
report->field[0]->report_size != 8) {
dev_err(dev, "unsupported BRIGHTNESS report");
return -EINVAL;
}
memset(&props, 0, sizeof(props));
props.max_brightness = 0xff;
bdev = backlight_device_register(dev_name(dev), dev, data,
&picolcd_blops, &props);
if (IS_ERR(bdev)) {
dev_err(dev, "failed to register backlight\n");
return PTR_ERR(bdev);
}
bdev->props.brightness = 0xff;
data->lcd_brightness = 0xff;
data->backlight = bdev;
picolcd_set_brightness(bdev);
return 0;
}
static void picolcd_exit_backlight(struct picolcd_data *data)
{
struct backlight_device *bdev = data->backlight;
data->backlight = NULL;
if (bdev)
backlight_device_unregister(bdev);
}
static inline int picolcd_resume_backlight(struct picolcd_data *data)
{
if (!data->backlight)
return 0;
return picolcd_set_brightness(data->backlight);
}
#ifdef CONFIG_PM
static void picolcd_suspend_backlight(struct picolcd_data *data)
{
int bl_power = data->lcd_power;
if (!data->backlight)
return;
data->backlight->props.power = FB_BLANK_POWERDOWN;
picolcd_set_brightness(data->backlight);
data->lcd_power = data->backlight->props.power = bl_power;
}
#endif /* CONFIG_PM */
#else
static inline int picolcd_init_backlight(struct picolcd_data *data,
struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_backlight(struct picolcd_data *data)
{
}
static inline int picolcd_resume_backlight(struct picolcd_data *data)
{
return 0;
}
static inline void picolcd_suspend_backlight(struct picolcd_data *data)
{
}
#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */
#ifdef CONFIG_HID_PICOLCD_LCD
/*
* lcd class device
*/
static int picolcd_get_contrast(struct lcd_device *ldev)
{
struct picolcd_data *data = lcd_get_data(ldev);
return data->lcd_contrast;
}
static int picolcd_set_contrast(struct lcd_device *ldev, int contrast)
{
struct picolcd_data *data = lcd_get_data(ldev);
struct hid_report *report = picolcd_out_report(REPORT_CONTRAST, data->hdev);
unsigned long flags;
if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
return -ENODEV;
data->lcd_contrast = contrast & 0x0ff;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, data->lcd_contrast);
usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb)
{
return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev));
}
static struct lcd_ops picolcd_lcdops = {
.get_contrast = picolcd_get_contrast,
.set_contrast = picolcd_set_contrast,
.check_fb = picolcd_check_lcd_fb,
};
static int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report)
{
struct device *dev = &data->hdev->dev;
struct lcd_device *ldev;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
report->field[0]->report_size != 8) {
dev_err(dev, "unsupported CONTRAST report");
return -EINVAL;
}
ldev = lcd_device_register(dev_name(dev), dev, data, &picolcd_lcdops);
if (IS_ERR(ldev)) {
dev_err(dev, "failed to register LCD\n");
return PTR_ERR(ldev);
}
ldev->props.max_contrast = 0x0ff;
data->lcd_contrast = 0xe5;
data->lcd = ldev;
picolcd_set_contrast(ldev, 0xe5);
return 0;
}
static void picolcd_exit_lcd(struct picolcd_data *data)
{
struct lcd_device *ldev = data->lcd;
data->lcd = NULL;
if (ldev)
lcd_device_unregister(ldev);
}
static inline int picolcd_resume_lcd(struct picolcd_data *data)
{
if (!data->lcd)
return 0;
return picolcd_set_contrast(data->lcd, data->lcd_contrast);
}
#else
static inline int picolcd_init_lcd(struct picolcd_data *data,
struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_lcd(struct picolcd_data *data)
{
}
static inline int picolcd_resume_lcd(struct picolcd_data *data)
{
return 0;
}
#endif /* CONFIG_HID_PICOLCD_LCD */
#ifdef CONFIG_HID_PICOLCD_LEDS
/**
* LED class device
*/
static void picolcd_leds_set(struct picolcd_data *data)
{
struct hid_report *report;
unsigned long flags;
if (!data->led[0])
return;
report = picolcd_out_report(REPORT_LED_STATE, data->hdev);
if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
return;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, data->led_state);
usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
}
static void picolcd_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev;
struct hid_device *hdev;
struct picolcd_data *data;
int i, state = 0;
dev = led_cdev->dev->parent;
hdev = container_of(dev, struct hid_device, dev);
data = hid_get_drvdata(hdev);
for (i = 0; i < 8; i++) {
if (led_cdev != data->led[i])
continue;
state = (data->led_state >> i) & 1;
if (value == LED_OFF && state) {
data->led_state &= ~(1 << i);
picolcd_leds_set(data);
} else if (value != LED_OFF && !state) {
data->led_state |= 1 << i;
picolcd_leds_set(data);
}
break;
}
}
static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev)
{
struct device *dev;
struct hid_device *hdev;
struct picolcd_data *data;
int i, value = 0;
dev = led_cdev->dev->parent;
hdev = container_of(dev, struct hid_device, dev);
data = hid_get_drvdata(hdev);
for (i = 0; i < 8; i++)
if (led_cdev == data->led[i]) {
value = (data->led_state >> i) & 1;
break;
}
return value ? LED_FULL : LED_OFF;
}
static int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report)
{
struct device *dev = &data->hdev->dev;
struct led_classdev *led;
size_t name_sz = strlen(dev_name(dev)) + 8;
char *name;
int i, ret = 0;
if (!report)
return -ENODEV;
if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
report->field[0]->report_size != 8) {
dev_err(dev, "unsupported LED_STATE report");
return -EINVAL;
}
for (i = 0; i < 8; i++) {
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
dev_err(dev, "can't allocate memory for LED %d\n", i);
ret = -ENOMEM;
goto err;
}
name = (void *)(&led[1]);
snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i);
led->name = name;
led->brightness = 0;
led->max_brightness = 1;
led->brightness_get = picolcd_led_get_brightness;
led->brightness_set = picolcd_led_set_brightness;
data->led[i] = led;
ret = led_classdev_register(dev, data->led[i]);
if (ret) {
data->led[i] = NULL;
kfree(led);
dev_err(dev, "can't register LED %d\n", i);
goto err;
}
}
return 0;
err:
for (i = 0; i < 8; i++)
if (data->led[i]) {
led = data->led[i];
data->led[i] = NULL;
led_classdev_unregister(led);
kfree(led);
}
return ret;
}
static void picolcd_exit_leds(struct picolcd_data *data)
{
struct led_classdev *led;
int i;
for (i = 0; i < 8; i++) {
led = data->led[i];
data->led[i] = NULL;
if (!led)
continue;
led_classdev_unregister(led);
kfree(led);
}
}
#else
static inline int picolcd_init_leds(struct picolcd_data *data,
struct hid_report *report)
{
return 0;
}
static inline void picolcd_exit_leds(struct picolcd_data *data)
{
}
static inline int picolcd_leds_set(struct picolcd_data *data)
{
return 0;
}
#endif /* CONFIG_HID_PICOLCD_LEDS */
/*
* input class device
*/
static int picolcd_raw_keypad(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size)
{
/*
* Keypad event
* First and second data bytes list currently pressed keys,
* 0x00 means no key and at most 2 keys may be pressed at same time
*/
int i, j;
/* determine newly pressed keys */
for (i = 0; i < size; i++) {
unsigned int key_code;
if (raw_data[i] == 0)
continue;
for (j = 0; j < sizeof(data->pressed_keys); j++)
if (data->pressed_keys[j] == raw_data[i])
goto key_already_down;
for (j = 0; j < sizeof(data->pressed_keys); j++)
if (data->pressed_keys[j] == 0) {
data->pressed_keys[j] = raw_data[i];
break;
}
input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]);
if (raw_data[i] < PICOLCD_KEYS)
key_code = data->keycode[raw_data[i]];
else
key_code = KEY_UNKNOWN;
if (key_code != KEY_UNKNOWN) {
dbg_hid(PICOLCD_NAME " got key press for %u:%d",
raw_data[i], key_code);
input_report_key(data->input_keys, key_code, 1);
}
input_sync(data->input_keys);
key_already_down:
continue;
}
/* determine newly released keys */
for (j = 0; j < sizeof(data->pressed_keys); j++) {
unsigned int key_code;
if (data->pressed_keys[j] == 0)
continue;
for (i = 0; i < size; i++)
if (data->pressed_keys[j] == raw_data[i])
goto key_still_down;
input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]);
if (data->pressed_keys[j] < PICOLCD_KEYS)
key_code = data->keycode[data->pressed_keys[j]];
else
key_code = KEY_UNKNOWN;
if (key_code != KEY_UNKNOWN) {
dbg_hid(PICOLCD_NAME " got key release for %u:%d",
data->pressed_keys[j], key_code);
input_report_key(data->input_keys, key_code, 0);
}
input_sync(data->input_keys);
data->pressed_keys[j] = 0;
key_still_down:
continue;
}
return 1;
}
static int picolcd_raw_cir(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size)
{
/* Need understanding of CIR data format to implement ... */
return 1;
}
static int picolcd_check_version(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct picolcd_pending *verinfo;
int ret = 0;
if (!data)
return -ENODEV;
verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0);
if (!verinfo) {
dev_err(&hdev->dev, "no version response from PicoLCD");
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) {
dev_info(&hdev->dev, "PicoLCD, bootloader version %d.%d\n",
verinfo->raw_data[1], verinfo->raw_data[0]);
} else {
dev_info(&hdev->dev, "PicoLCD, firmware version %d.%d\n",
verinfo->raw_data[1], verinfo->raw_data[0]);
}
} else {
dev_err(&hdev->dev, "confused, got unexpected version response from PicoLCD\n");
ret = -EINVAL;
}
kfree(verinfo);
return ret;
}
/*
* Reset our device and wait for answer to VERSION request
*/
static int picolcd_reset(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev);
unsigned long flags;
int error;
if (!data || !report || report->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER)
data->status |= PICOLCD_BOOTLOADER;
/* perform the reset */
hid_set_field(report->field[0], 0, 1);
usbhid_submit_report(hdev, report, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
error = picolcd_check_version(hdev);
if (error)
return error;
picolcd_resume_lcd(data);
picolcd_resume_backlight(data);
#ifdef CONFIG_HID_PICOLCD_FB
if (data->fb_info)
schedule_delayed_work(&data->fb_info->deferred_work, 0);
#endif /* CONFIG_HID_PICOLCD_FB */
picolcd_leds_set(data);
return 0;
}
/*
* The "operation_mode" sysfs attribute
*/
static ssize_t picolcd_operation_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct picolcd_data *data = dev_get_drvdata(dev);
if (data->status & PICOLCD_BOOTLOADER)
return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n");
else
return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n");
}
static ssize_t picolcd_operation_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct picolcd_data *data = dev_get_drvdata(dev);
struct hid_report *report = NULL;
size_t cnt = count;
int timeout = data->opmode_delay;
unsigned long flags;
if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) {
if (data->status & PICOLCD_BOOTLOADER)
report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev);
buf += 3;
cnt -= 3;
} else if (cnt >= 10 && strncmp("bootloader", buf, 10) == 0) {
if (!(data->status & PICOLCD_BOOTLOADER))
report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev);
buf += 10;
cnt -= 10;
}
if (!report)
return -EINVAL;
while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r'))
cnt--;
if (cnt != 0)
return -EINVAL;
spin_lock_irqsave(&data->lock, flags);
hid_set_field(report->field[0], 0, timeout & 0xff);
hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff);
usbhid_submit_report(data->hdev, report, USB_DIR_OUT);
spin_unlock_irqrestore(&data->lock, flags);
return count;
}
static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
picolcd_operation_mode_store);
/*
* The "operation_mode_delay" sysfs attribute
*/
static ssize_t picolcd_operation_mode_delay_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct picolcd_data *data = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay);
}
static ssize_t picolcd_operation_mode_delay_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct picolcd_data *data = dev_get_drvdata(dev);
unsigned u;
if (sscanf(buf, "%u", &u) != 1)
return -EINVAL;
if (u > 30000)
return -EINVAL;
else
data->opmode_delay = u;
return count;
}
static DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show,
picolcd_operation_mode_delay_store);
#ifdef CONFIG_DEBUG_FS
/*
* The "reset" file
*/
static int picolcd_debug_reset_show(struct seq_file *f, void *p)
{
if (picolcd_fbinfo((struct picolcd_data *)f->private))
seq_printf(f, "all fb\n");
else
seq_printf(f, "all\n");
return 0;
}
static int picolcd_debug_reset_open(struct inode *inode, struct file *f)
{
return single_open(f, picolcd_debug_reset_show, inode->i_private);
}
static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
char buf[32];
size_t cnt = min(count, sizeof(buf)-1);
if (copy_from_user(buf, user_buf, cnt))
return -EFAULT;
while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
cnt--;
buf[cnt] = '\0';
if (strcmp(buf, "all") == 0) {
picolcd_reset(data->hdev);
picolcd_fb_reset(data, 1);
} else if (strcmp(buf, "fb") == 0) {
picolcd_fb_reset(data, 1);
} else {
return -EINVAL;
}
return count;
}
static const struct file_operations picolcd_debug_reset_fops = {
.owner = THIS_MODULE,
.open = picolcd_debug_reset_open,
.read = seq_read,
.llseek = seq_lseek,
.write = picolcd_debug_reset_write,
.release = single_release,
};
/*
* The "eeprom" file
*/
static int picolcd_debug_eeprom_open(struct inode *i, struct file *f)
{
f->private_data = i->i_private;
return 0;
}
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] = s < 20 ? s : 20;
if (*off + raw_data[2] > 0xff)
raw_data[2] = 0x100 - *off;
if (copy_from_user(raw_data+3, u, 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 = picolcd_debug_eeprom_open,
.read = picolcd_debug_eeprom_read,
.write = picolcd_debug_eeprom_write,
.llseek = generic_file_llseek,
};
/*
* The "flash" file
*/
static int picolcd_debug_flash_open(struct inode *i, struct file *f)
{
f->private_data = i->i_private;
return 0;
}
/* 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 preceeded 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 happend without special restriction
*/
static const struct file_operations picolcd_debug_flash_fops = {
.owner = THIS_MODULE,
.open = picolcd_debug_flash_open,
.read = picolcd_debug_flash_read,
.write = picolcd_debug_flash_write,
.llseek = generic_file_llseek,
};
/*
* Helper code for HID report level dumping/debugging
*/
static const char *error_codes[] = {
"success", "parameter missing", "data_missing", "block readonly",
"block not erasable", "block too big", "section overflow",
"invalid command length", "invalid data length",
};
static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data,
const size_t data_len)
{
int i, j;
for (i = j = 0; i < data_len && j + 3 < dst_sz; i++) {
dst[j++] = hex_asc[(data[i] >> 4) & 0x0f];
dst[j++] = hex_asc[data[i] & 0x0f];
dst[j++] = ' ';
}
if (j < dst_sz) {
dst[j--] = '\0';
dst[j] = '\n';
} else
dst[j] = '\0';
}
static void picolcd_debug_out_report(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report)
{
u8 raw_data[70];
int raw_size = (report->size >> 3) + 1;
char *buff;
#define BUFF_SZ 256
/* Avoid unnecessary overhead if debugfs is disabled */
if (!hdev->debug_events)
return;
buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
if (!buff)
return;
snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ",
report->id, raw_size);
hid_debug_event(hdev, buff);
if (raw_size + 5 > sizeof(raw_data)) {
hid_debug_event(hdev, " TOO BIG\n");
return;
} else {
raw_data[0] = report->id;
hid_output_report(report, raw_data);
dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size);
hid_debug_event(hdev, buff);
}
switch (report->id) {
case REPORT_LED_STATE:
/* 1 data byte with GPO state */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LED_STATE", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tGPO state: 0x%02x\n", raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_BRIGHTNESS:
/* 1 data byte with brightness */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_BRIGHTNESS", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tBrightness: 0x%02x\n", raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_CONTRAST:
/* 1 data byte with contrast */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_CONTRAST", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tContrast: 0x%02x\n", raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_RESET:
/* 2 data bytes with reset duration in ms */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_RESET", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tDuration: 0x%02x%02x (%dms)\n",
raw_data[2], raw_data[1], raw_data[2] << 8 | raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_LCD_CMD:
/* 63 data bytes with LCD commands */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LCD_CMD", report->id, raw_size-1);
hid_debug_event(hdev, buff);
/* TODO: format decoding */
break;
case REPORT_LCD_DATA:
/* 63 data bytes with LCD data */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LCD_CMD", report->id, raw_size-1);
/* TODO: format decoding */
hid_debug_event(hdev, buff);
break;
case REPORT_LCD_CMD_DATA:
/* 63 data bytes with LCD commands and data */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_LCD_CMD", report->id, raw_size-1);
/* TODO: format decoding */
hid_debug_event(hdev, buff);
break;
case REPORT_EE_READ:
/* 3 data bytes with read area description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_EE_READ", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
break;
case REPORT_EE_WRITE:
/* 3+1..20 data bytes with write area description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_EE_WRITE", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[3] + 4 <= raw_size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_ERASE_MEMORY:
case REPORT_BL_ERASE_MEMORY:
/* 3 data bytes with pointer inside erase block */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_ERASE_MEMORY", report->id, raw_size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
break;
case 3:
snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_READ_MEMORY:
case REPORT_BL_READ_MEMORY:
/* 4 data bytes with read area description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_READ_MEMORY", report->id, raw_size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
break;
case 3:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_WRITE_MEMORY:
case REPORT_BL_WRITE_MEMORY:
/* 4+1..32 data bytes with write adrea description */
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_WRITE_MEMORY", report->id, raw_size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[3] + 4 <= raw_size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
case 3:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
hid_debug_event(hdev, buff);
if (raw_data[4] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[4] + 5 <= raw_size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_SPLASH_RESTART:
/* TODO */
break;
case REPORT_EXIT_KEYBOARD:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_EXIT_KEYBOARD", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n",
raw_data[1] | (raw_data[2] << 8),
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_VERSION:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_VERSION", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_DEVID:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_DEVID", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_SPLASH_SIZE:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_SPLASH_SIZE", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_HOOK_VERSION:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_HOOK_VERSION", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
case REPORT_EXIT_FLASHER:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"REPORT_VERSION", report->id, raw_size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n",
raw_data[1] | (raw_data[2] << 8),
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
default:
snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
"<unknown>", report->id, raw_size-1);
hid_debug_event(hdev, buff);
break;
}
wake_up_interruptible(&hdev->debug_wait);
kfree(buff);
}
static void picolcd_debug_raw_event(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report,
u8 *raw_data, int size)
{
char *buff;
#define BUFF_SZ 256
/* Avoid unnecessary overhead if debugfs is disabled */
if (!hdev->debug_events)
return;
buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
if (!buff)
return;
switch (report->id) {
case REPORT_ERROR_CODE:
/* 2 data bytes with affected report and error code */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_ERROR_CODE", report->id, size-1);
hid_debug_event(hdev, buff);
if (raw_data[2] < ARRAY_SIZE(error_codes))
snprintf(buff, BUFF_SZ, "\tError code 0x%02x (%s) in reply to report 0x%02x\n",
raw_data[2], error_codes[raw_data[2]], raw_data[1]);
else
snprintf(buff, BUFF_SZ, "\tError code 0x%02x in reply to report 0x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_KEY_STATE:
/* 2 data bytes with key state */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_KEY_STATE", report->id, size-1);
hid_debug_event(hdev, buff);
if (raw_data[1] == 0)
snprintf(buff, BUFF_SZ, "\tNo key pressed\n");
else if (raw_data[2] == 0)
snprintf(buff, BUFF_SZ, "\tOne key pressed: 0x%02x (%d)\n",
raw_data[1], raw_data[1]);
else
snprintf(buff, BUFF_SZ, "\tTwo keys pressed: 0x%02x (%d), 0x%02x (%d)\n",
raw_data[1], raw_data[1], raw_data[2], raw_data[2]);
hid_debug_event(hdev, buff);
break;
case REPORT_IR_DATA:
/* Up to 20 byes of IR scancode data */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_IR_DATA", report->id, size-1);
hid_debug_event(hdev, buff);
if (raw_data[1] == 0) {
snprintf(buff, BUFF_SZ, "\tUnexpectedly 0 data length\n");
hid_debug_event(hdev, buff);
} else if (raw_data[1] + 1 <= size) {
snprintf(buff, BUFF_SZ, "\tData length: %d\n\tIR Data: ",
raw_data[1]-1);
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]-1);
hid_debug_event(hdev, buff);
} else {
snprintf(buff, BUFF_SZ, "\tOverflowing data length: %d\n",
raw_data[1]-1);
hid_debug_event(hdev, buff);
}
break;
case REPORT_EE_DATA:
/* Data buffer in response to REPORT_EE_READ or REPORT_EE_WRITE */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_EE_DATA", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
hid_debug_event(hdev, buff);
} else if (raw_data[3] + 4 <= size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
hid_debug_event(hdev, buff);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
hid_debug_event(hdev, buff);
}
break;
case REPORT_MEMORY:
/* Data buffer in response to REPORT_READ_MEMORY or REPORT_WRTIE_MEMORY */
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
switch (data->addr_sz) {
case 2:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
hid_debug_event(hdev, buff);
if (raw_data[3] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[3] + 4 <= size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
case 3:
snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
raw_data[3], raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
hid_debug_event(hdev, buff);
if (raw_data[4] == 0) {
snprintf(buff, BUFF_SZ, "\tNo data\n");
} else if (raw_data[4] + 5 <= size) {
snprintf(buff, BUFF_SZ, "\tData: ");
hid_debug_event(hdev, buff);
dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]);
} else {
snprintf(buff, BUFF_SZ, "\tData overflowed\n");
}
break;
default:
snprintf(buff, BUFF_SZ, "\tNot supported\n");
}
hid_debug_event(hdev, buff);
break;
case REPORT_VERSION:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_VERSION", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n",
raw_data[2], raw_data[1]);
hid_debug_event(hdev, buff);
break;
case REPORT_BL_ERASE_MEMORY:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_BL_ERASE_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
/* TODO */
break;
case REPORT_BL_READ_MEMORY:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_BL_READ_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
/* TODO */
break;
case REPORT_BL_WRITE_MEMORY:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_BL_WRITE_MEMORY", report->id, size-1);
hid_debug_event(hdev, buff);
/* TODO */
break;
case REPORT_DEVID:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_DEVID", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tSerial: 0x%02x%02x%02x%02x\n",
raw_data[1], raw_data[2], raw_data[3], raw_data[4]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tType: 0x%02x\n",
raw_data[5]);
hid_debug_event(hdev, buff);
break;
case REPORT_SPLASH_SIZE:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_SPLASH_SIZE", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tTotal splash space: %d\n",
(raw_data[2] << 8) | raw_data[1]);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tUsed splash space: %d\n",
(raw_data[4] << 8) | raw_data[3]);
hid_debug_event(hdev, buff);
break;
case REPORT_HOOK_VERSION:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"REPORT_HOOK_VERSION", report->id, size-1);
hid_debug_event(hdev, buff);
snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n",
raw_data[1], raw_data[2]);
hid_debug_event(hdev, buff);
break;
default:
snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
"<unknown>", report->id, size-1);
hid_debug_event(hdev, buff);
break;
}
wake_up_interruptible(&hdev->debug_wait);
kfree(buff);
}
static void picolcd_init_devfs(struct picolcd_data *data,
struct hid_report *eeprom_r, struct hid_report *eeprom_w,
struct hid_report *flash_r, struct hid_report *flash_w,
struct hid_report *reset)
{
struct hid_device *hdev = data->hdev;
mutex_init(&data->mutex_flash);
/* reset */
if (reset)
data->debug_reset = debugfs_create_file("reset", 0600,
hdev->debug_dir, data, &picolcd_debug_reset_fops);
/* eeprom */
if (eeprom_r || eeprom_w)
data->debug_eeprom = debugfs_create_file("eeprom",
(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
/* flash */
if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
data->addr_sz = flash_r->field[0]->report_count - 1;
else
data->addr_sz = -1;
if (data->addr_sz == 2 || data->addr_sz == 3) {
data->debug_flash = debugfs_create_file("flash",
(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
hdev->debug_dir, data, &picolcd_debug_flash_fops);
} else if (flash_r || flash_w)
dev_warn(&hdev->dev, "Unexpected FLASH access reports, "
"please submit rdesc for review\n");
}
static void picolcd_exit_devfs(struct picolcd_data *data)
{
struct dentry *dent;
dent = data->debug_reset;
data->debug_reset = NULL;
if (dent)
debugfs_remove(dent);
dent = data->debug_eeprom;
data->debug_eeprom = NULL;
if (dent)
debugfs_remove(dent);
dent = data->debug_flash;
data->debug_flash = NULL;
if (dent)
debugfs_remove(dent);
mutex_destroy(&data->mutex_flash);
}
#else
static inline void picolcd_debug_raw_event(struct picolcd_data *data,
struct hid_device *hdev, struct hid_report *report,
u8 *raw_data, int size)
{
}
static inline void picolcd_init_devfs(struct picolcd_data *data,
struct hid_report *eeprom_r, struct hid_report *eeprom_w,
struct hid_report *flash_r, struct hid_report *flash_w,
struct hid_report *reset)
{
}
static inline void picolcd_exit_devfs(struct picolcd_data *data)
{
}
#endif /* CONFIG_DEBUG_FS */
/*
* Handle raw report as sent by device
*/
static int picolcd_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *raw_data, int size)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
unsigned long flags;
int ret = 0;
if (!data)
return 1;
if (report->id == REPORT_KEY_STATE) {
if (data->input_keys)
ret = picolcd_raw_keypad(data, report, raw_data+1, size-1);
} else if (report->id == REPORT_IR_DATA) {
if (data->input_cir)
ret = picolcd_raw_cir(data, report, raw_data+1, size-1);
} else {
spin_lock_irqsave(&data->lock, flags);
/*
* We let the caller of picolcd_send_and_wait() check if the
* report we got is one of the expected ones or not.
*/
if (data->pending) {
memcpy(data->pending->raw_data, raw_data+1, size-1);
data->pending->raw_size = size-1;
data->pending->in_report = report;
complete(&data->pending->ready);
}
spin_unlock_irqrestore(&data->lock, flags);
}
picolcd_debug_raw_event(data, hdev, report, raw_data, size);
return 1;
}
#ifdef CONFIG_PM
static int picolcd_suspend(struct hid_device *hdev, pm_message_t message)
{
if (message.event & PM_EVENT_AUTO)
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) {
dev_err(&hdev->dev, "unsupported KEY_STATE report");
return -EINVAL;
}
idev = input_allocate_device();
if (idev == NULL) {
dev_err(&hdev->dev, "failed to allocate input device");
return -ENOMEM;
}
input_set_drvdata(idev, hdev);
memcpy(data->keycode, def_keymap, sizeof(def_keymap));
idev->name = hdev->name;
idev->phys = hdev->phys;
idev->uniq = hdev->uniq;
idev->id.bustype = hdev->bus;
idev->id.vendor = hdev->vendor;
idev->id.product = hdev->product;
idev->id.version = hdev->version;
idev->dev.parent = hdev->dev.parent;
idev->keycode = &data->keycode;
idev->keycodemax = PICOLCD_KEYS;
idev->keycodesize = sizeof(data->keycode[0]);
input_set_capability(idev, EV_MSC, MSC_SCAN);
set_bit(EV_REP, idev->evbit);
for (i = 0; i < PICOLCD_KEYS; i++)
input_set_capability(idev, EV_KEY, data->keycode[i]);
error = input_register_device(idev);
if (error) {
dev_err(&hdev->dev, "error registering the input device");
input_free_device(idev);
return error;
}
data->input_keys = idev;
return 0;
}
static void picolcd_exit_keys(struct picolcd_data *data)
{
struct input_dev *idev = data->input_keys;
data->input_keys = NULL;
if (idev)
input_unregister_device(idev);
}
/* initialize CIR input device */
static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
{
/* support not implemented yet */
return 0;
}
static inline void picolcd_exit_cir(struct picolcd_data *data)
{
}
static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
{
int error;
error = picolcd_check_version(hdev);
if (error)
return error;
if (data->version[0] != 0 && data->version[1] != 3)
dev_info(&hdev->dev, "Device with untested firmware revision, "
"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
dev_name(&hdev->dev));
/* Setup keypad input device */
error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev));
if (error)
goto err;
/* Setup CIR input device */
error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev));
if (error)
goto err;
/* Set up the framebuffer device */
error = picolcd_init_framebuffer(data);
if (error)
goto err;
/* Setup lcd class device */
error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev));
if (error)
goto err;
/* Setup backlight class device */
error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev));
if (error)
goto err;
/* Setup the LED class devices */
error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev));
if (error)
goto err;
picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
picolcd_out_report(REPORT_EE_WRITE, hdev),
picolcd_out_report(REPORT_READ_MEMORY, hdev),
picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
picolcd_out_report(REPORT_RESET, hdev));
return 0;
err:
picolcd_exit_leds(data);
picolcd_exit_backlight(data);
picolcd_exit_lcd(data);
picolcd_exit_framebuffer(data);
picolcd_exit_cir(data);
picolcd_exit_keys(data);
return error;
}
static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
{
int error;
error = picolcd_check_version(hdev);
if (error)
return error;
if (data->version[0] != 1 && data->version[1] != 0)
dev_info(&hdev->dev, "Device with untested bootloader revision, "
"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
dev_name(&hdev->dev));
picolcd_init_devfs(data, NULL, NULL,
picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
return 0;
}
static int picolcd_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
struct picolcd_data *data;
int error = -ENOMEM;
dbg_hid(PICOLCD_NAME " hardware probe...\n");
/*
* Let's allocate the picolcd data structure, set some reasonable
* defaults, and associate it with the device
*/
data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL);
if (data == NULL) {
dev_err(&hdev->dev, "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) {
dev_err(&hdev->dev, "device report parse failed\n");
goto err_cleanup_data;
}
/* We don't use hidinput but hid_hw_start() fails if nothing is
* claimed. So spoof claimed input. */
hdev->claimed = HID_CLAIMED_INPUT;
error = hid_hw_start(hdev, 0);
hdev->claimed = 0;
if (error) {
dev_err(&hdev->dev, "hardware start failed\n");
goto err_cleanup_data;
}
error = hdev->ll_driver->open(hdev);
if (error) {
dev_err(&hdev->dev, "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) {
dev_err(&hdev->dev, "failed to create sysfs attributes\n");
goto err_cleanup_hid_ll;
}
error = device_create_file(&hdev->dev, &dev_attr_operation_mode);
if (error) {
dev_err(&hdev->dev, "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:
hdev->ll_driver->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);
hdev->ll_driver->close(hdev);
hid_hw_stop(hdev);
hid_set_drvdata(hdev, NULL);
/* Shortcut potential pending reply that will never arrive */
spin_lock_irqsave(&data->lock, flags);
if (data->pending)
complete(&data->pending->ready);
spin_unlock_irqrestore(&data->lock, flags);
/* Cleanup LED */
picolcd_exit_leds(data);
/* Clean up the framebuffer */
picolcd_exit_backlight(data);
picolcd_exit_lcd(data);
picolcd_exit_framebuffer(data);
/* Cleanup input */
picolcd_exit_cir(data);
picolcd_exit_keys(data);
mutex_destroy(&data->mutex);
/* Finally, clean up the picolcd data itself */
kfree(data);
}
static const struct hid_device_id picolcd_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
{ }
};
MODULE_DEVICE_TABLE(hid, picolcd_devices);
static struct hid_driver picolcd_driver = {
.name = "hid-picolcd",
.id_table = picolcd_devices,
.probe = picolcd_probe,
.remove = picolcd_remove,
.raw_event = picolcd_raw_event,
#ifdef CONFIG_PM
.suspend = picolcd_suspend,
.resume = picolcd_resume,
.reset_resume = picolcd_reset_resume,
#endif
};
static int __init picolcd_init(void)
{
return hid_register_driver(&picolcd_driver);
}
static void __exit picolcd_exit(void)
{
hid_unregister_driver(&picolcd_driver);
}
module_init(picolcd_init);
module_exit(picolcd_exit);
MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver");
MODULE_LICENSE("GPL v2");
/*
* 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.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/mutex.h>
#include <linux/hid.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include "usbhid/usbhid.h"
#include "hid-ids.h"
#define pk_debug(format, arg...) \
pr_debug("hid-prodikeys: " format "\n" , ## arg)
#define pk_error(format, arg...) \
pr_err("hid-prodikeys: " format "\n" , ## arg)
struct pcmidi_snd;
struct pk_device {
unsigned long quirks;
struct hid_device *hdev;
struct pcmidi_snd *pm; /* pcmidi device context */
};
struct pcmidi_snd;
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 int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
module_param_array(id, charp, NULL, 0444);
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the PC-MIDI virtual audio driver");
MODULE_PARM_DESC(id, "ID string for the PC-MIDI virtual audio driver");
MODULE_PARM_DESC(enable, "Enable for the PC-MIDI virtual audio driver");
/* Output routine for the sysfs channel file */
static ssize_t show_channel(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
dbg_hid("pcmidi sysfs read channel=%u\n", pk->pm->midi_channel);
return sprintf(buf, "%u (min:%u, max:%u)\n", pk->pm->midi_channel,
PCMIDI_CHANNEL_MIN, PCMIDI_CHANNEL_MAX);
}
/* Input routine for the sysfs channel file */
static ssize_t store_channel(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
unsigned channel = 0;
if (sscanf(buf, "%u", &channel) > 0 && channel <= PCMIDI_CHANNEL_MAX) {
dbg_hid("pcmidi sysfs write channel=%u\n", channel);
pk->pm->midi_channel = channel;
return strlen(buf);
}
return -EINVAL;
}
static DEVICE_ATTR(channel, S_IRUGO | S_IWUGO, show_channel,
store_channel);
static struct device_attribute *sysfs_device_attr_channel = {
&dev_attr_channel,
};
/* Output routine for the sysfs sustain file */
static ssize_t show_sustain(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
dbg_hid("pcmidi sysfs read sustain=%u\n", pk->pm->midi_sustain);
return sprintf(buf, "%u (off:%u, max:%u (ms))\n", pk->pm->midi_sustain,
PCMIDI_SUSTAIN_MIN, PCMIDI_SUSTAIN_MAX);
}
/* Input routine for the sysfs sustain file */
static ssize_t store_sustain(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
unsigned sustain = 0;
if (sscanf(buf, "%u", &sustain) > 0 && sustain <= PCMIDI_SUSTAIN_MAX) {
dbg_hid("pcmidi sysfs write sustain=%u\n", sustain);
pk->pm->midi_sustain = sustain;
pk->pm->midi_sustain_mode =
(0 == sustain || !pk->pm->midi_mode) ? 0 : 1;
return strlen(buf);
}
return -EINVAL;
}
static DEVICE_ATTR(sustain, S_IRUGO | S_IWUGO, show_sustain,
store_sustain);
static struct device_attribute *sysfs_device_attr_sustain = {
&dev_attr_sustain,
};
/* Output routine for the sysfs octave file */
static ssize_t show_octave(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
dbg_hid("pcmidi sysfs read octave=%d\n", pk->pm->midi_octave);
return sprintf(buf, "%d (min:%d, max:%d)\n", pk->pm->midi_octave,
PCMIDI_OCTAVE_MIN, PCMIDI_OCTAVE_MAX);
}
/* Input routine for the sysfs octave file */
static ssize_t store_octave(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
int octave = 0;
if (sscanf(buf, "%d", &octave) > 0 &&
octave >= PCMIDI_OCTAVE_MIN && octave <= PCMIDI_OCTAVE_MAX) {
dbg_hid("pcmidi sysfs write octave=%d\n", octave);
pk->pm->midi_octave = octave;
return strlen(buf);
}
return -EINVAL;
}
static DEVICE_ATTR(octave, S_IRUGO | S_IWUGO, 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;
}
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;
}
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);
}
}
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) {
dev_err(&hdev->dev, "output report is empty\n");
break;
}
if (report->field[0]->report_count != 2) {
dev_err(&hdev->dev, "field count too low\n");
break;
}
pm->pcmidi_report6 = report;
return 0;
}
/* should never get here */
return -ENODEV;
}
static void pcmidi_submit_output_report(struct pcmidi_snd *pm, int state)
{
struct hid_device *hdev = pm->pk->hdev;
struct hid_report *report = pm->pcmidi_report6;
report->field[0]->value[0] = 0x01;
report->field[0]->value[1] = state;
usbhid_submit_report(hdev, report, USB_DIR_OUT);
}
static int pcmidi_handle_report1(struct pcmidi_snd *pm, u8 *data)
{
u32 bit_mask;
bit_mask = data[1];
bit_mask = (bit_mask << 8) | data[2];
bit_mask = (bit_mask << 8) | data[3];
dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
/*KEY_MAIL or octave down*/
if (pm->midi_mode && bit_mask == 0x004000) {
/* octave down */
pm->midi_octave--;
if (pm->midi_octave < -2)
pm->midi_octave = -2;
dbg_hid("pcmidi mode: %d octave: %d\n",
pm->midi_mode, pm->midi_octave);
return 1;
}
/*KEY_WWW or sustain*/
else if (pm->midi_mode && bit_mask == 0x000004) {
/* sustain on/off*/
pm->midi_sustain_mode ^= 0x1;
return 1;
}
return 0; /* continue key processing */
}
static int pcmidi_handle_report3(struct pcmidi_snd *pm, u8 *data, int size)
{
struct pcmidi_sustain *pms;
unsigned i, j;
unsigned char status, note, velocity;
unsigned num_notes = (size-1)/2;
for (j = 0; j < num_notes; j++) {
note = data[j*2+1];
velocity = data[j*2+2];
if (note < 0x81) { /* note on */
status = 128 + 16 + pm->midi_channel; /* 1001nnnn */
note = note - 0x54 + PCMIDI_MIDDLE_C +
(pm->midi_octave * 12);
if (0 == velocity)
velocity = 1; /* force note on */
} else { /* note off */
status = 128 + pm->midi_channel; /* 1000nnnn */
note = note - 0x94 + PCMIDI_MIDDLE_C +
(pm->midi_octave*12);
if (pm->midi_sustain_mode) {
for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
pms = &pm->sustained_notes[i];
if (!pms->in_use) {
pms->status = status;
pms->note = note;
pms->velocity = velocity;
pms->in_use = 1;
mod_timer(&pms->timer,
jiffies +
msecs_to_jiffies(pm->midi_sustain));
return 1;
}
}
}
}
pcmidi_send_note(pm, status, note, velocity);
}
return 1;
}
static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data)
{
unsigned key;
u32 bit_mask;
u32 bit_index;
bit_mask = data[1];
bit_mask = (bit_mask << 8) | data[2];
bit_mask = (bit_mask << 8) | data[3];
/* break keys */
for (bit_index = 0; bit_index < 24; bit_index++) {
key = pm->last_key[bit_index];
if (!((0x01 << bit_index) & bit_mask)) {
input_event(pm->input_ep82, EV_KEY,
pm->last_key[bit_index], 0);
pm->last_key[bit_index] = 0;
}
}
/* make keys */
for (bit_index = 0; bit_index < 24; bit_index++) {
key = 0;
switch ((0x01 << bit_index) & bit_mask) {
case 0x000010: /* Fn lock*/
pm->fn_state ^= 0x000010;
if (pm->fn_state)
pcmidi_submit_output_report(pm, 0xc5);
else
pcmidi_submit_output_report(pm, 0xc6);
continue;
case 0x020000: /* midi launcher..send a key (qwerty) or not? */
pcmidi_submit_output_report(pm, 0xc1);
pm->midi_mode ^= 0x01;
dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
continue;
case 0x100000: /* KEY_MESSENGER or octave up */
dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
if (pm->midi_mode) {
pm->midi_octave++;
if (pm->midi_octave > 2)
pm->midi_octave = 2;
dbg_hid("pcmidi mode: %d octave: %d\n",
pm->midi_mode, pm->midi_octave);
continue;
} else
key = KEY_MESSENGER;
break;
case 0x400000:
key = KEY_CALENDAR;
break;
case 0x080000:
key = KEY_ADDRESSBOOK;
break;
case 0x040000:
key = KEY_DOCUMENTS;
break;
case 0x800000:
key = KEY_WORDPROCESSOR;
break;
case 0x200000:
key = KEY_SPREADSHEET;
break;
case 0x010000:
key = KEY_COFFEE;
break;
case 0x000100:
key = KEY_HELP;
break;
case 0x000200:
key = KEY_SEND;
break;
case 0x000400:
key = KEY_REPLY;
break;
case 0x000800:
key = KEY_FORWARDMAIL;
break;
case 0x001000:
key = KEY_NEW;
break;
case 0x002000:
key = KEY_OPEN;
break;
case 0x004000:
key = KEY_CLOSE;
break;
case 0x008000:
key = KEY_SAVE;
break;
case 0x000001:
key = KEY_UNDO;
break;
case 0x000002:
key = KEY_REDO;
break;
case 0x000004:
key = KEY_SPELLCHECK;
break;
case 0x000008:
key = KEY_PRINT;
break;
}
if (key) {
input_event(pm->input_ep82, EV_KEY, key, 1);
pm->last_key[bit_index] = key;
}
}
return 1;
}
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;
}
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
};
int pcmidi_snd_initialise(struct pcmidi_snd *pm)
{
static int dev;
struct snd_card *card;
struct snd_rawmidi *rwmidi;
int err;
static struct snd_device_ops ops = {
.dev_free = pcmidi_snd_free,
};
if (pm->ifnum != 1)
return 0; /* only set up midi device ONCE for interace 1 */
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
/* Setup sound card */
err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
if (err < 0) {
pk_error("failed to create pc-midi sound card\n");
err = -ENOMEM;
goto fail;
}
pm->card = card;
/* Setup sound device */
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pm, &ops);
if (err < 0) {
pk_error("failed to create pc-midi sound device: error %d\n",
err);
goto fail;
}
strncpy(card->driver, shortname, sizeof(card->driver));
strncpy(card->shortname, shortname, sizeof(card->shortname));
strncpy(card->longname, longname, sizeof(card->longname));
/* Set up rawmidi */
err = snd_rawmidi_new(card, card->shortname, 0,
0, 1, &rwmidi);
if (err < 0) {
pk_error("failed to create pc-midi rawmidi device: error %d\n",
err);
goto fail;
}
pm->rwmidi = rwmidi;
strncpy(rwmidi->name, card->shortname, sizeof(rwmidi->name));
rwmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT;
rwmidi->private_data = pm;
snd_rawmidi_set_ops(rwmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&pcmidi_in_ops);
snd_card_set_dev(card, &pm->pk->hdev->dev);
/* create sysfs variables */
err = device_create_file(&pm->pk->hdev->dev,
sysfs_device_attr_channel);
if (err < 0) {
pk_error("failed to create sysfs attribute channel: error %d\n",
err);
goto fail;
}
err = device_create_file(&pm->pk->hdev->dev,
sysfs_device_attr_sustain);
if (err < 0) {
pk_error("failed to create sysfs attribute sustain: error %d\n",
err);
goto fail_attr_sustain;
}
err = device_create_file(&pm->pk->hdev->dev,
sysfs_device_attr_octave);
if (err < 0) {
pk_error("failed to create sysfs attribute octave: error %d\n",
err);
goto fail_attr_octave;
}
spin_lock_init(&pm->rawmidi_in_lock);
init_sustain_timers(pm);
pcmidi_set_operational(pm);
/* register it */
err = snd_card_register(card);
if (err < 0) {
pk_error("failed to register pc-midi sound card: error %d\n",
err);
goto fail_register;
}
dbg_hid("pcmidi_snd_initialise finished ok\n");
return 0;
fail_register:
stop_sustain_timers(pm);
device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_octave);
fail_attr_octave:
device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_sustain);
fail_attr_sustain:
device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_channel);
fail:
if (pm->card) {
snd_card_free(pm->card);
pm->card = NULL;
}
return err;
}
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 void pk_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int rsize)
{
if (rsize == 178 &&
rdesc[111] == 0x06 && rdesc[112] == 0x00 &&
rdesc[113] == 0xff) {
dev_info(&hdev->dev, "fixing up pc-midi keyboard report "
"descriptor\n");
rdesc[144] = 0x18; /* report 4: was 0x10 report count */
}
}
static int pk_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
struct pcmidi_snd *pm;
pm = pk->pm;
if (HID_UP_MSVENDOR == (usage->hid & HID_USAGE_PAGE) &&
1 == pm->ifnum) {
pcmidi_setup_extra_keys(pm, hi->input);
return 0;
}
return 0;
}
static int pk_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
int ret = 0;
if (1 == pk->pm->ifnum) {
if (report->id == data[0])
switch (report->id) {
case 0x01: /* midi keys (qwerty)*/
case 0x03: /* midi keyboard (musical)*/
case 0x04: /* extra/midi keys (qwerty)*/
ret = pcmidi_handle_report(pk->pm,
report->id, data, size);
break;
}
}
return ret;
}
static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
unsigned long quirks = id->driver_data;
struct pk_device *pk;
struct pcmidi_snd *pm = NULL;
pk = kzalloc(sizeof(*pk), GFP_KERNEL);
if (pk == NULL) {
dev_err(&hdev->dev, "prodikeys: can't alloc descriptor\n");
return -ENOMEM;
}
pk->hdev = hdev;
pm = kzalloc(sizeof(*pm), GFP_KERNEL);
if (pm == NULL) {
dev_err(&hdev->dev,
"prodikeys: can't alloc descriptor\n");
ret = -ENOMEM;
goto err_free;
}
pm->pk = pk;
pk->pm = pm;
pm->ifnum = ifnum;
hid_set_drvdata(hdev, pk);
ret = hid_parse(hdev);
if (ret) {
dev_err(&hdev->dev, "prodikeys: 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) {
dev_err(&hdev->dev, "prodikeys: 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:
if (pm != NULL)
kfree(pm);
kfree(pk);
return ret;
}
static void pk_remove(struct hid_device *hdev)
{
struct pk_device *pk = (struct pk_device *)hid_get_drvdata(hdev);
struct pcmidi_snd *pm;
pm = pk->pm;
if (pm) {
pcmidi_snd_terminate(pm);
kfree(pm);
}
hid_hw_stop(hdev);
kfree(pk);
}
static const struct hid_device_id pk_devices[] = {
{HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS,
USB_DEVICE_ID_PRODIKEYS_PCMIDI),
.driver_data = PK_QUIRK_NOGET},
{ }
};
MODULE_DEVICE_TABLE(hid, pk_devices);
static struct hid_driver pk_driver = {
.name = "prodikeys",
.id_table = pk_devices,
.report_fixup = pk_report_fixup,
.input_mapping = pk_input_mapping,
.raw_event = pk_raw_event,
.probe = pk_probe,
.remove = pk_remove,
};
static int pk_init(void)
{
int ret;
ret = hid_register_driver(&pk_driver);
if (ret)
printk(KERN_ERR "can't register prodikeys driver\n");
return ret;
}
static void pk_exit(void)
{
hid_unregister_driver(&pk_driver);
}
module_init(pk_init);
module_exit(pk_exit);
MODULE_LICENSE("GPL");
/*
* 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 implement notification mechanism for overlong macro execution
* If user wants to execute an overlong macro only the names of macroset
* and macro are given. Should userland tap hidraw or is there an
* additional streaming mechanism?
*
* 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/usb.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "hid-ids.h"
#include "hid-roccat-kone.h"
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 len;
unsigned char *data;
data = kmalloc(1, GFP_KERNEL);
if (!data)
return -ENOMEM;
do {
/*
* Mouse needs 50 msecs until it says ok, but there are
* 30 more msecs needed for next write to work.
*/
msleep(80);
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_confirm_write, 0, data, 1,
USB_CTRL_SET_TIMEOUT);
if (len != 1) {
kfree(data);
return -EIO;
}
/*
* 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 */
kfree(data);
return 0;
} else { /* unknown answer */
dev_err(&usb_dev->dev, "got retval %d when checking write\n",
*data);
kfree(data);
return -EIO;
}
}
/*
* Reads settings from mouse and stores it in @buf
* @buf has to be alloced with GFP_KERNEL
* On success returns 0
* On failure returns errno
*/
static int kone_get_settings(struct usb_device *usb_dev,
struct kone_settings *buf)
{
int len;
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_settings, 0, buf,
sizeof(struct kone_settings), USB_CTRL_SET_TIMEOUT);
if (len != sizeof(struct kone_settings))
return -EIO;
return 0;
}
/*
* 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 len;
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_settings, 0, (char *)settings,
sizeof(struct kone_settings),
USB_CTRL_SET_TIMEOUT);
if (len != sizeof(struct kone_settings))
return -EIO;
if (kone_check_write(usb_dev))
return -EIO;
return 0;
}
/*
* 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, (char *)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 len;
uint8_t *data;
data = kmalloc(1, GFP_KERNEL);
if (!data)
return -ENOMEM;
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_weight, 0, data, 1, USB_CTRL_SET_TIMEOUT);
if (len != 1) {
kfree(data);
return -EIO;
}
*result = (int)*data;
kfree(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 len;
unsigned char *data;
data = kmalloc(2, GFP_KERNEL);
if (!data)
return -ENOMEM;
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_firmware_version, 0, data, 2,
USB_CTRL_SET_TIMEOUT);
if (len != 2) {
kfree(data);
return -EIO;
}
*result = le16_to_cpu(*data);
kfree(data);
return 0;
}
static ssize_t kone_sysfs_read_settings(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
struct device *dev = container_of(kobj, struct device, kobj);
struct 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, &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 kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
struct device *dev = container_of(kobj, struct device, kobj);
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;
/* 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)
memcpy(&kone->settings, buf,
sizeof(struct kone_settings));
}
mutex_unlock(&kone->kone_lock);
if (retval)
return retval;
/*
* If we get here, treat settings as okay and update actual values
* according to startup_profile
*/
kone->actual_profile = kone->settings.startup_profile;
kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi;
return sizeof(struct kone_settings);
}
static ssize_t kone_sysfs_read_profilex(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count, int number) {
struct device *dev = container_of(kobj, struct device, kobj);
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, &kone->profiles[number - 1], sizeof(struct kone_profile));
mutex_unlock(&kone->kone_lock);
return count;
}
static ssize_t kone_sysfs_read_profile1(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 1);
}
static ssize_t kone_sysfs_read_profile2(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 2);
}
static ssize_t kone_sysfs_read_profile3(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 3);
}
static ssize_t kone_sysfs_read_profile4(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 4);
}
static ssize_t kone_sysfs_read_profile5(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_read_profilex(kobj, attr, buf, off, count, 5);
}
/* Writes data only if different to stored data */
static ssize_t kone_sysfs_write_profilex(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count, int number) {
struct device *dev = container_of(kobj, struct device, kobj);
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[number - 1];
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, number);
if (!retval)
memcpy(profile, buf, sizeof(struct kone_profile));
}
mutex_unlock(&kone->kone_lock);
if (retval)
return retval;
return sizeof(struct kone_profile);
}
static ssize_t kone_sysfs_write_profile1(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 1);
}
static ssize_t kone_sysfs_write_profile2(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 2);
}
static ssize_t kone_sysfs_write_profile3(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 3);
}
static ssize_t kone_sysfs_write_profile4(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 4);
}
static ssize_t kone_sysfs_write_profile5(struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
return kone_sysfs_write_profilex(kobj, attr, buf, off, count, 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));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile);
}
static ssize_t kone_sysfs_show_actual_dpi(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi);
}
/* weight is read each time, since we don't get informed when it's changed */
static ssize_t kone_sysfs_show_weight(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int weight = 0;
int retval;
mutex_lock(&kone->kone_lock);
retval = kone_get_weight(usb_dev, &weight);
mutex_unlock(&kone->kone_lock);
if (retval)
return retval;
return snprintf(buf, PAGE_SIZE, "%d\n", weight);
}
static ssize_t kone_sysfs_show_firmware_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version);
}
static ssize_t kone_sysfs_show_tcu(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu);
}
static int kone_tcu_command(struct usb_device *usb_dev, int number)
{
int len;
char *value;
value = kmalloc(1, GFP_KERNEL);
if (!value)
return -ENOMEM;
*value = number;
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_calibrate, 0, value, 1,
USB_CTRL_SET_TIMEOUT);
kfree(value);
return ((len != 1) ? -EIO : 0);
}
/*
* 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 = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
unsigned long state;
retval = strict_strtoul(buf, 10, &state);
if (retval)
return retval;
if (state != 0 && state != 1)
return -EINVAL;
mutex_lock(&kone->kone_lock);
if (state == 1) { /* state activate */
retval = kone_tcu_command(usb_dev, 1);
if (retval)
goto exit_unlock;
retval = kone_tcu_command(usb_dev, 2);
if (retval)
goto exit_unlock;
ssleep(5); /* tcu needs this time for calibration */
retval = kone_tcu_command(usb_dev, 3);
if (retval)
goto exit_unlock;
retval = kone_tcu_command(usb_dev, 0);
if (retval)
goto exit_unlock;
retval = kone_tcu_command(usb_dev, 4);
if (retval)
goto exit_unlock;
/*
* Kone needs this time to settle things.
* Reading settings too early will result in invalid data.
* Roccat's driver waits 1 sec, maybe this time could be
* shortened.
*/
ssleep(1);
}
/* calibration changes values in settings, so reread */
retval = kone_get_settings(usb_dev, &kone->settings);
if (retval)
goto exit_no_settings;
/* only write settings back if activation state is different */
if (kone->settings.tcu != state) {
kone->settings.tcu = state;
kone_set_settings_checksum(&kone->settings);
retval = kone_set_settings(usb_dev, &kone->settings);
if (retval) {
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;
}
}
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 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));
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 = hid_get_drvdata(dev_get_drvdata(dev));
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
int retval;
unsigned long new_startup_profile;
retval = strict_strtoul(buf, 10, &new_startup_profile);
if (retval)
return retval;
if (new_startup_profile < 1 || new_startup_profile > 5)
return -EINVAL;
mutex_lock(&kone->kone_lock);
kone->settings.startup_profile = new_startup_profile;
kone_set_settings_checksum(&kone->settings);
retval = kone_set_settings(usb_dev, &kone->settings);
mutex_unlock(&kone->kone_lock);
if (retval)
return retval;
/* changing the startup profile immediately activates this profile */
kone->actual_profile = new_startup_profile;
kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi;
return size;
}
/*
* This file is used by userland software to find devices that are handled by
* this driver. This provides a consistent way for actual and older kernels
* where this driver replaced usbhid instead of generic-usb.
* Driver capabilities are determined by version number.
*/
static ssize_t kone_sysfs_show_driver_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, ROCCAT_KONE_DRIVER_VERSION "\n");
}
/*
* Read actual dpi settings.
* Returns raw value for further processing. Refer to enum kone_polling_rates to
* get real value.
*/
static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL);
static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL);
/*
* The mouse can be equipped with one of four supplied weights from 5 to 20
* grams which are recognized and its value can be read out.
* This returns the raw value reported by the mouse for easy evaluation by
* software. Refer to enum kone_weights to get corresponding real weight.
*/
static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL);
/*
* Prints firmware version stored in mouse as integer.
* The raw value reported by the mouse is returned for easy evaluation, to get
* the real version number the decimal point has to be shifted 2 positions to
* the left. E.g. a value of 138 means 1.38.
*/
static DEVICE_ATTR(firmware_version, 0440,
kone_sysfs_show_firmware_version, NULL);
/*
* Prints state of Tracking Control Unit as number where 0 = off and 1 = on
* Writing 0 deactivates tcu and writing 1 calibrates and activates the tcu
*/
static DEVICE_ATTR(tcu, 0660, kone_sysfs_show_tcu, kone_sysfs_set_tcu);
/* Prints and takes the number of the profile the mouse starts with */
static DEVICE_ATTR(startup_profile, 0660,
kone_sysfs_show_startup_profile,
kone_sysfs_set_startup_profile);
static DEVICE_ATTR(kone_driver_version, 0440,
kone_sysfs_show_driver_version, NULL);
static struct attribute *kone_attributes[] = {
&dev_attr_actual_dpi.attr,
&dev_attr_actual_profile.attr,
&dev_attr_weight.attr,
&dev_attr_firmware_version.attr,
&dev_attr_tcu.attr,
&dev_attr_startup_profile.attr,
&dev_attr_kone_driver_version.attr,
NULL
};
static struct attribute_group kone_attribute_group = {
.attrs = kone_attributes
};
static struct bin_attribute kone_settings_attr = {
.attr = { .name = "settings", .mode = 0660 },
.size = sizeof(struct kone_settings),
.read = kone_sysfs_read_settings,
.write = kone_sysfs_write_settings
};
static struct bin_attribute kone_profile1_attr = {
.attr = { .name = "profile1", .mode = 0660 },
.size = sizeof(struct kone_profile),
.read = kone_sysfs_read_profile1,
.write = kone_sysfs_write_profile1
};
static struct bin_attribute kone_profile2_attr = {
.attr = { .name = "profile2", .mode = 0660 },
.size = sizeof(struct kone_profile),
.read = kone_sysfs_read_profile2,
.write = kone_sysfs_write_profile2
};
static struct bin_attribute kone_profile3_attr = {
.attr = { .name = "profile3", .mode = 0660 },
.size = sizeof(struct kone_profile),
.read = kone_sysfs_read_profile3,
.write = kone_sysfs_write_profile3
};
static struct bin_attribute kone_profile4_attr = {
.attr = { .name = "profile4", .mode = 0660 },
.size = sizeof(struct kone_profile),
.read = kone_sysfs_read_profile4,
.write = kone_sysfs_write_profile4
};
static struct bin_attribute kone_profile5_attr = {
.attr = { .name = "profile5", .mode = 0660 },
.size = sizeof(struct kone_profile),
.read = kone_sysfs_read_profile5,
.write = kone_sysfs_write_profile5
};
static int kone_create_sysfs_attributes(struct usb_interface *intf)
{
int retval;
retval = sysfs_create_group(&intf->dev.kobj, &kone_attribute_group);
if (retval)
goto exit_1;
retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_settings_attr);
if (retval)
goto exit_2;
retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile1_attr);
if (retval)
goto exit_3;
retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile2_attr);
if (retval)
goto exit_4;
retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile3_attr);
if (retval)
goto exit_5;
retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile4_attr);
if (retval)
goto exit_6;
retval = sysfs_create_bin_file(&intf->dev.kobj, &kone_profile5_attr);
if (retval)
goto exit_7;
return 0;
exit_7:
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile4_attr);
exit_6:
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile3_attr);
exit_5:
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile2_attr);
exit_4:
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile1_attr);
exit_3:
sysfs_remove_bin_file(&intf->dev.kobj, &kone_settings_attr);
exit_2:
sysfs_remove_group(&intf->dev.kobj, &kone_attribute_group);
exit_1:
return retval;
}
static void kone_remove_sysfs_attributes(struct usb_interface *intf)
{
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile5_attr);
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile4_attr);
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile3_attr);
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile2_attr);
sysfs_remove_bin_file(&intf->dev.kobj, &kone_profile1_attr);
sysfs_remove_bin_file(&intf->dev.kobj, &kone_settings_attr);
sysfs_remove_group(&intf->dev.kobj, &kone_attribute_group);
}
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->actual_profile = kone->settings.startup_profile;
kone->actual_dpi = kone->profiles[kone->actual_profile].startup_dpi;
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) {
dev_err(&hdev->dev, "can't alloc device descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, kone);
retval = kone_init_kone_device_struct(usb_dev, kone);
if (retval) {
dev_err(&hdev->dev,
"couldn't init struct kone_device\n");
goto exit_free;
}
retval = kone_create_sysfs_attributes(intf);
if (retval) {
dev_err(&hdev->dev, "cannot create sysfs files\n");
goto exit_free;
}
} 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);
if (intf->cur_altsetting->desc.bInterfaceProtocol
== USB_INTERFACE_PROTOCOL_MOUSE) {
kone_remove_sysfs_attributes(intf);
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) {
dev_err(&hdev->dev, "parse failed\n");
goto exit;
}
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (retval) {
dev_err(&hdev->dev, "hw start failed\n");
goto exit;
}
retval = kone_init_specials(hdev);
if (retval) {
dev_err(&hdev->dev, "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:
case kone_mouse_event_osd_profile:
kone->actual_profile = event->value;
kone->actual_dpi = kone->profiles[kone->actual_profile - 1].
startup_dpi;
break;
case kone_mouse_event_switch_dpi:
case kone_mouse_event_osd_dpi:
kone->actual_dpi = event->value;
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;
/*
* 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);
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)
{
return hid_register_driver(&kone_driver);
}
static void __exit kone_exit(void)
{
hid_unregister_driver(&kone_driver);
}
module_init(kone_init);
module_exit(kone_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kone driver");
MODULE_LICENSE("GPL v2");
#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>
#define ROCCAT_KONE_DRIVER_VERSION "v0.3.1"
#pragma pack(push)
#pragma pack(1)
struct kone_keystroke {
uint8_t key;
uint8_t action;
uint16_t period; /* in milliseconds */
};
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];
};
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 */
};
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 */
};
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;
};
/*
* 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 */
};
enum kone_mouse_events {
/* osd events are thought to be display on screen */
kone_mouse_event_osd_dpi = 0xa0,
kone_mouse_event_osd_profile = 0xb0,
/* TODO clarify meaning and occurence of kone_mouse_event_calibration */
kone_mouse_event_calibration = 0xc0,
kone_mouse_event_call_overlong_macro = 0xe0,
/* switch events notify if user changed values with mousebutton click */
kone_mouse_event_switch_dpi = 0xf0,
kone_mouse_event_switch_profile = 0xf1
};
enum kone_commands {
kone_command_profile = 0x5a,
kone_command_settings = 0x15a,
kone_command_firmware_version = 0x25a,
kone_command_weight = 0x45a,
kone_command_calibrate = 0x55a,
kone_command_confirm_write = 0x65a,
kone_command_firmware = 0xe5a
};
#pragma pack(pop)
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;
};
#endif
...@@ -7,6 +7,18 @@ ...@@ -7,6 +7,18 @@
* Copyright (c) 2006-2007 Jiri Kosina * Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2007 Paul Walmsley * Copyright (c) 2007 Paul Walmsley
* Copyright (c) 2008 Jiri Slaby * 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.
*
*/ */
/* /*
...@@ -17,14 +29,13 @@ ...@@ -17,14 +29,13 @@
*/ */
#include <linux/device.h> #include <linux/device.h>
#include <linux/usb.h>
#include <linux/hid.h> #include <linux/hid.h>
#include <linux/module.h> #include <linux/module.h>
#include "hid-ids.h" #include "hid-ids.h"
/* /*
* Samsung IrDA remote controller (reports as Cypress USB Mouse).
*
* There are several variants for 0419:0001: * There are several variants for 0419:0001:
* *
* 1. 184 byte report descriptor * 1. 184 byte report descriptor
...@@ -43,21 +54,21 @@ ...@@ -43,21 +54,21 @@
* 4. 171 byte report descriptor * 4. 171 byte report descriptor
* Report #3 has an array field with logical range 0..1 instead of 1..3. * Report #3 has an array field with logical range 0..1 instead of 1..3.
*/ */
static inline void samsung_dev_trace(struct hid_device *hdev, static inline void samsung_irda_dev_trace(struct hid_device *hdev,
unsigned int rsize) unsigned int rsize)
{ {
dev_info(&hdev->dev, "fixing up Samsung IrDA %d byte report " dev_info(&hdev->dev, "fixing up Samsung IrDA %d byte report "
"descriptor\n", rsize); "descriptor\n", rsize);
} }
static void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc, static void samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int rsize) unsigned int rsize)
{ {
if (rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 && if (rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 &&
rdesc[177] == 0x75 && rdesc[178] == 0x30 && rdesc[177] == 0x75 && rdesc[178] == 0x30 &&
rdesc[179] == 0x95 && rdesc[180] == 0x01 && rdesc[179] == 0x95 && rdesc[180] == 0x01 &&
rdesc[182] == 0x40) { rdesc[182] == 0x40) {
samsung_dev_trace(hdev, 184); samsung_irda_dev_trace(hdev, 184);
rdesc[176] = 0xff; rdesc[176] = 0xff;
rdesc[178] = 0x08; rdesc[178] = 0x08;
rdesc[180] = 0x06; rdesc[180] = 0x06;
...@@ -65,24 +76,80 @@ static void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc, ...@@ -65,24 +76,80 @@ static void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
} else } else
if (rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 && if (rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 &&
rdesc[194] == 0x25 && rdesc[195] == 0x12) { rdesc[194] == 0x25 && rdesc[195] == 0x12) {
samsung_dev_trace(hdev, 203); samsung_irda_dev_trace(hdev, 203);
rdesc[193] = 0x1; rdesc[193] = 0x1;
rdesc[195] = 0xf; rdesc[195] = 0xf;
} else } else
if (rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 && if (rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 &&
rdesc[126] == 0x25 && rdesc[127] == 0x11) { rdesc[126] == 0x25 && rdesc[127] == 0x11) {
samsung_dev_trace(hdev, 135); samsung_irda_dev_trace(hdev, 135);
rdesc[125] = 0x1; rdesc[125] = 0x1;
rdesc[127] = 0xe; rdesc[127] = 0xe;
} else } else
if (rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 && if (rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 &&
rdesc[162] == 0x25 && rdesc[163] == 0x01) { rdesc[162] == 0x25 && rdesc[163] == 0x01) {
samsung_dev_trace(hdev, 171); samsung_irda_dev_trace(hdev, 171);
rdesc[161] = 0x1; rdesc[161] = 0x1;
rdesc[163] = 0x3; rdesc[163] = 0x3;
} }
} }
#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 void samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int rsize)
{
if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product)
samsung_irda_report_fixup(hdev, rdesc, rsize);
}
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, static int samsung_probe(struct hid_device *hdev,
const struct hid_device_id *id) const struct hid_device_id *id)
{ {
...@@ -95,11 +162,13 @@ static int samsung_probe(struct hid_device *hdev, ...@@ -95,11 +162,13 @@ static int samsung_probe(struct hid_device *hdev,
goto err_free; goto err_free;
} }
if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) {
if (hdev->rsize == 184) { if (hdev->rsize == 184) {
/* disable hidinput, force hiddev */ /* disable hidinput, force hiddev */
cmask = (cmask & ~HID_CONNECT_HIDINPUT) | cmask = (cmask & ~HID_CONNECT_HIDINPUT) |
HID_CONNECT_HIDDEV_FORCE; HID_CONNECT_HIDDEV_FORCE;
} }
}
ret = hid_hw_start(hdev, cmask); ret = hid_hw_start(hdev, cmask);
if (ret) { if (ret) {
...@@ -114,6 +183,7 @@ static int samsung_probe(struct hid_device *hdev, ...@@ -114,6 +183,7 @@ static int samsung_probe(struct hid_device *hdev,
static const struct hid_device_id samsung_devices[] = { 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_IR_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
{ } { }
}; };
MODULE_DEVICE_TABLE(hid, samsung_devices); MODULE_DEVICE_TABLE(hid, samsung_devices);
...@@ -122,6 +192,7 @@ static struct hid_driver samsung_driver = { ...@@ -122,6 +192,7 @@ static struct hid_driver samsung_driver = {
.name = "samsung", .name = "samsung",
.id_table = samsung_devices, .id_table = samsung_devices,
.report_fixup = samsung_report_fixup, .report_fixup = samsung_report_fixup,
.input_mapping = samsung_input_mapping,
.probe = samsung_probe, .probe = samsung_probe,
}; };
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
* *
* Copyright (c) 2008 Lev Babiev * Copyright (c) 2008 Lev Babiev
* based on hid-cherry driver * based on hid-cherry driver
*
* Modified to also support BTC "Emprex 3009URF III Vista MCE Remote" by
* Wayne Thomas 2010.
*/ */
/* /*
...@@ -24,23 +27,29 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi, ...@@ -24,23 +27,29 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage, struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max) unsigned long **bit, int *max)
{ {
if ((usage->hid & HID_USAGE_PAGE) != 0x0ffbc0000) if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
return 0; return 0;
switch (usage->hid & HID_USAGE) { switch (usage->hid & HID_USAGE) {
case 0x00d: ts_map_key_clear(KEY_HOME); break; case 0x00d: ts_map_key_clear(KEY_MEDIA); break;
case 0x024: ts_map_key_clear(KEY_MENU); break; case 0x024: ts_map_key_clear(KEY_MENU); break;
case 0x025: ts_map_key_clear(KEY_TV); break; case 0x025: ts_map_key_clear(KEY_TV); break;
case 0x048: ts_map_key_clear(KEY_RED); break;
case 0x047: ts_map_key_clear(KEY_GREEN); break;
case 0x049: ts_map_key_clear(KEY_YELLOW); break;
case 0x04a: ts_map_key_clear(KEY_BLUE); break;
case 0x04b: ts_map_key_clear(KEY_ANGLE); break;
case 0x04c: ts_map_key_clear(KEY_LANGUAGE); break;
case 0x04d: ts_map_key_clear(KEY_SUBTITLE); break;
case 0x031: ts_map_key_clear(KEY_AUDIO); break; case 0x031: ts_map_key_clear(KEY_AUDIO); break;
case 0x032: ts_map_key_clear(KEY_TEXT); break; case 0x032: ts_map_key_clear(KEY_TEXT); break;
case 0x033: ts_map_key_clear(KEY_CHANNEL); break; case 0x033: ts_map_key_clear(KEY_CHANNEL); break;
case 0x047: ts_map_key_clear(KEY_MP3); break;
case 0x048: ts_map_key_clear(KEY_TV2); break;
case 0x049: ts_map_key_clear(KEY_CAMERA); break;
case 0x04a: ts_map_key_clear(KEY_VIDEO); break;
case 0x04b: ts_map_key_clear(KEY_ANGLE); break;
case 0x04c: ts_map_key_clear(KEY_LANGUAGE); break;
case 0x04d: ts_map_key_clear(KEY_SUBTITLE); break;
case 0x050: ts_map_key_clear(KEY_RADIO); break;
case 0x05a: ts_map_key_clear(KEY_TEXT); break;
case 0x05b: ts_map_key_clear(KEY_RED); break;
case 0x05c: ts_map_key_clear(KEY_GREEN); break;
case 0x05d: ts_map_key_clear(KEY_YELLOW); break;
case 0x05e: ts_map_key_clear(KEY_BLUE); break;
default: default:
return 0; return 0;
} }
...@@ -50,6 +59,7 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi, ...@@ -50,6 +59,7 @@ static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi,
static const struct hid_device_id ts_devices[] = { static const struct hid_device_id ts_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) }, { HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE) },
{ } { }
}; };
MODULE_DEVICE_TABLE(hid, ts_devices); MODULE_DEVICE_TABLE(hid, ts_devices);
......
...@@ -22,14 +22,159 @@ ...@@ -22,14 +22,159 @@
#include <linux/hid.h> #include <linux/hid.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/slab.h> #include <linux/slab.h>
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
#include <linux/power_supply.h>
#endif
#include "hid-ids.h" #include "hid-ids.h"
struct wacom_data { struct wacom_data {
__u16 tool; __u16 tool;
unsigned char butstate; unsigned char butstate;
unsigned char high_speed;
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
int battery_capacity;
struct power_supply battery;
struct power_supply ac;
#endif
}; };
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
/*percent of battery capacity, 0 means AC online*/
static unsigned short batcap[8] = { 1, 15, 25, 35, 50, 70, 100, 0 };
static enum power_supply_property wacom_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY
};
static enum power_supply_property wacom_ac_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE
};
static int wacom_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct wacom_data *wdata = container_of(psy,
struct wacom_data, battery);
int power_state = batcap[wdata->battery_capacity];
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CAPACITY:
/* show 100% battery capacity when charging */
if (power_state == 0)
val->intval = 100;
else
val->intval = power_state;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int wacom_ac_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct wacom_data *wdata = container_of(psy, struct wacom_data, ac);
int power_state = batcap[wdata->battery_capacity];
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
/* fall through */
case POWER_SUPPLY_PROP_ONLINE:
if (power_state == 0)
val->intval = 1;
else
val->intval = 0;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
#endif
static void wacom_poke(struct hid_device *hdev, u8 speed)
{
struct wacom_data *wdata = hid_get_drvdata(hdev);
int limit, ret;
char rep_data[2];
rep_data[0] = 0x03 ; rep_data[1] = 0x00;
limit = 3;
do {
ret = hdev->hid_output_raw_report(hdev, rep_data, 2,
HID_FEATURE_REPORT);
} while (ret < 0 && limit-- > 0);
if (ret >= 0) {
if (speed == 0)
rep_data[0] = 0x05;
else
rep_data[0] = 0x06;
rep_data[1] = 0x00;
limit = 3;
do {
ret = hdev->hid_output_raw_report(hdev, rep_data, 2,
HID_FEATURE_REPORT);
} while (ret < 0 && limit-- > 0);
if (ret >= 0) {
wdata->high_speed = speed;
return;
}
}
/*
* Note that if the raw queries fail, it's not a hard failure and it
* is safe to continue
*/
dev_warn(&hdev->dev, "failed to poke device, command %d, err %d\n",
rep_data[0], ret);
return;
}
static ssize_t wacom_show_speed(struct device *dev,
struct device_attribute
*attr, char *buf)
{
struct wacom_data *wdata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%i\n", wdata->high_speed);
}
static ssize_t wacom_store_speed(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
int new_speed;
if (sscanf(buf, "%1d", &new_speed ) != 1)
return -EINVAL;
if (new_speed == 0 || new_speed == 1) {
wacom_poke(hdev, new_speed);
return strnlen(buf, PAGE_SIZE);
} else
return -EINVAL;
}
static DEVICE_ATTR(speed, S_IRUGO | S_IWUGO,
wacom_show_speed, wacom_store_speed);
static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report, static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *raw_data, int size) u8 *raw_data, int size)
{ {
...@@ -148,6 +293,12 @@ static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report, ...@@ -148,6 +293,12 @@ static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report,
input_sync(input); input_sync(input);
} }
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
/* Store current battery capacity */
rw = (data[7] >> 2 & 0x07);
if (rw != wdata->battery_capacity)
wdata->battery_capacity = rw;
#endif
return 1; return 1;
} }
...@@ -157,9 +308,7 @@ static int wacom_probe(struct hid_device *hdev, ...@@ -157,9 +308,7 @@ static int wacom_probe(struct hid_device *hdev,
struct hid_input *hidinput; struct hid_input *hidinput;
struct input_dev *input; struct input_dev *input;
struct wacom_data *wdata; struct wacom_data *wdata;
char rep_data[2];
int ret; int ret;
int limit;
wdata = kzalloc(sizeof(*wdata), GFP_KERNEL); wdata = kzalloc(sizeof(*wdata), GFP_KERNEL);
if (wdata == NULL) { if (wdata == NULL) {
...@@ -182,31 +331,53 @@ static int wacom_probe(struct hid_device *hdev, ...@@ -182,31 +331,53 @@ static int wacom_probe(struct hid_device *hdev,
goto err_free; goto err_free;
} }
ret = device_create_file(&hdev->dev, &dev_attr_speed);
if (ret)
dev_warn(&hdev->dev,
"can't create sysfs speed attribute err: %d\n", ret);
/* Set Wacom mode 2 with high reporting speed */
wacom_poke(hdev, 1);
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
wdata->battery.properties = wacom_battery_props;
wdata->battery.num_properties = ARRAY_SIZE(wacom_battery_props);
wdata->battery.get_property = wacom_battery_get_property;
wdata->battery.name = "wacom_battery";
wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY;
wdata->battery.use_for_apm = 0;
ret = power_supply_register(&hdev->dev, &wdata->battery);
if (ret) {
dev_warn(&hdev->dev,
"can't create sysfs battery attribute, err: %d\n", ret);
/* /*
* Note that if the raw queries fail, it's not a hard failure and it * battery attribute is not critical for the tablet, but if it
* is safe to continue * failed then there is no need to create ac attribute
*/ */
goto move_on;
}
/* Set Wacom mode2 */ wdata->ac.properties = wacom_ac_props;
rep_data[0] = 0x03; rep_data[1] = 0x00; wdata->ac.num_properties = ARRAY_SIZE(wacom_ac_props);
limit = 3; wdata->ac.get_property = wacom_ac_get_property;
do { wdata->ac.name = "wacom_ac";
ret = hdev->hid_output_raw_report(hdev, rep_data, 2, wdata->ac.type = POWER_SUPPLY_TYPE_MAINS;
HID_FEATURE_REPORT); wdata->ac.use_for_apm = 0;
} while (ret < 0 && limit-- > 0);
if (ret < 0)
dev_warn(&hdev->dev, "failed to poke device #1, %d\n", ret);
/* 0x06 - high reporting speed, 0x05 - low speed */ ret = power_supply_register(&hdev->dev, &wdata->ac);
rep_data[0] = 0x06; rep_data[1] = 0x00; if (ret) {
limit = 3; dev_warn(&hdev->dev,
do { "can't create ac battery attribute, err: %d\n", ret);
ret = hdev->hid_output_raw_report(hdev, rep_data, 2, /*
HID_FEATURE_REPORT); * ac attribute is not critical for the tablet, but if it
} while (ret < 0 && limit-- > 0); * failed then we don't want to battery attribute to exist
if (ret < 0) */
dev_warn(&hdev->dev, "failed to poke device #2, %d\n", ret); power_supply_unregister(&wdata->battery);
}
move_on:
#endif
hidinput = list_entry(hdev->inputs.next, struct hid_input, list); hidinput = list_entry(hdev->inputs.next, struct hid_input, list);
input = hidinput->input; input = hidinput->input;
...@@ -251,13 +422,21 @@ static int wacom_probe(struct hid_device *hdev, ...@@ -251,13 +422,21 @@ static int wacom_probe(struct hid_device *hdev,
static void wacom_remove(struct hid_device *hdev) static void wacom_remove(struct hid_device *hdev)
{ {
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
struct wacom_data *wdata = hid_get_drvdata(hdev);
#endif
hid_hw_stop(hdev); hid_hw_stop(hdev);
#ifdef CONFIG_HID_WACOM_POWER_SUPPLY
power_supply_unregister(&wdata->battery);
power_supply_unregister(&wdata->ac);
#endif
kfree(hid_get_drvdata(hdev)); kfree(hid_get_drvdata(hdev));
} }
static const struct hid_device_id wacom_devices[] = { static const struct hid_device_id wacom_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_WACOM, USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH) },
{ } { }
}; };
MODULE_DEVICE_TABLE(hid, wacom_devices); MODULE_DEVICE_TABLE(hid, wacom_devices);
......
/*
* HID driver for zydacron remote control
*
* Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk>
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
struct zc_device {
struct input_dev *input_ep81;
unsigned short last_key[4];
};
/*
* Zydacron remote control has an invalid HID report descriptor,
* that needs fixing before we can parse it.
*/
static void zc_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int rsize)
{
if (rsize >= 253 &&
rdesc[0x96] == 0xbc && rdesc[0x97] == 0xff &&
rdesc[0xca] == 0xbc && rdesc[0xcb] == 0xff &&
rdesc[0xe1] == 0xbc && rdesc[0xe2] == 0xff) {
dev_info(&hdev->dev,
"fixing up zydacron remote control report "
"descriptor\n");
rdesc[0x96] = rdesc[0xca] = rdesc[0xe1] = 0x0c;
rdesc[0x97] = rdesc[0xcb] = rdesc[0xe2] = 0x00;
}
}
#define zc_map_key_clear(c) \
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
static int zc_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
int i;
struct zc_device *zc = hid_get_drvdata(hdev);
zc->input_ep81 = hi->input;
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
dbg_hid("zynacron input mapping event [0x%x]\n",
usage->hid & HID_USAGE);
switch (usage->hid & HID_USAGE) {
/* report 2 */
case 0x10:
zc_map_key_clear(KEY_MODE);
break;
case 0x30:
zc_map_key_clear(KEY_SCREEN);
break;
case 0x70:
zc_map_key_clear(KEY_INFO);
break;
/* report 3 */
case 0x04:
zc_map_key_clear(KEY_RADIO);
break;
/* report 4 */
case 0x0d:
zc_map_key_clear(KEY_PVR);
break;
case 0x25:
zc_map_key_clear(KEY_TV);
break;
case 0x47:
zc_map_key_clear(KEY_AUDIO);
break;
case 0x49:
zc_map_key_clear(KEY_AUX);
break;
case 0x4a:
zc_map_key_clear(KEY_VIDEO);
break;
case 0x48:
zc_map_key_clear(KEY_DVD);
break;
case 0x24:
zc_map_key_clear(KEY_MENU);
break;
case 0x32:
zc_map_key_clear(KEY_TEXT);
break;
default:
return 0;
}
for (i = 0; i < 4; i++)
zc->last_key[i] = 0;
return 1;
}
static int zc_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
struct zc_device *zc = hid_get_drvdata(hdev);
int ret = 0;
unsigned key;
unsigned short index;
if (report->id == data[0]) {
/* break keys */
for (index = 0; index < 4; index++) {
key = zc->last_key[index];
if (key) {
input_event(zc->input_ep81, EV_KEY, key, 0);
zc->last_key[index] = 0;
}
}
key = 0;
switch (report->id) {
case 0x02:
case 0x03:
switch (data[1]) {
case 0x10:
key = KEY_MODE;
index = 0;
break;
case 0x30:
key = KEY_SCREEN;
index = 1;
break;
case 0x70:
key = KEY_INFO;
index = 2;
break;
case 0x04:
key = KEY_RADIO;
index = 3;
break;
}
if (key) {
input_event(zc->input_ep81, EV_KEY, key, 1);
zc->last_key[index] = key;
}
ret = 1;
break;
}
}
return ret;
}
static int zc_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct zc_device *zc;
zc = kzalloc(sizeof(*zc), GFP_KERNEL);
if (zc == NULL) {
dev_err(&hdev->dev, "zydacron: can't alloc descriptor\n");
return -ENOMEM;
}
hid_set_drvdata(hdev, zc);
ret = hid_parse(hdev);
if (ret) {
dev_err(&hdev->dev, "zydacron: parse failed\n");
goto err_free;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
dev_err(&hdev->dev, "zydacron: hw start failed\n");
goto err_free;
}
return 0;
err_free:
kfree(zc);
return ret;
}
static void zc_remove(struct hid_device *hdev)
{
struct zc_device *zc = hid_get_drvdata(hdev);
hid_hw_stop(hdev);
if (NULL != zc)
kfree(zc);
}
static const struct hid_device_id zc_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) },
{ }
};
MODULE_DEVICE_TABLE(hid, zc_devices);
static struct hid_driver zc_driver = {
.name = "zydacron",
.id_table = zc_devices,
.report_fixup = zc_report_fixup,
.input_mapping = zc_input_mapping,
.raw_event = zc_raw_event,
.probe = zc_probe,
.remove = zc_remove,
};
static int __init zc_init(void)
{
return hid_register_driver(&zc_driver);
}
static void __exit zc_exit(void)
{
hid_unregister_driver(&zc_driver);
}
module_init(zc_init);
module_exit(zc_exit);
MODULE_LICENSE("GPL");
...@@ -106,38 +106,48 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, ...@@ -106,38 +106,48 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,
static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{ {
unsigned int minor = iminor(file->f_path.dentry->d_inode); unsigned int minor = iminor(file->f_path.dentry->d_inode);
/* FIXME: What stops hidraw_table going NULL */ struct hid_device *dev;
struct hid_device *dev = hidraw_table[minor]->hid;
__u8 *buf; __u8 *buf;
int ret = 0; int ret = 0;
if (!dev->hid_output_raw_report) mutex_lock(&minors_lock);
return -ENODEV; dev = hidraw_table[minor]->hid;
if (!dev->hid_output_raw_report) {
ret = -ENODEV;
goto out;
}
if (count > HID_MAX_BUFFER_SIZE) { if (count > HID_MAX_BUFFER_SIZE) {
printk(KERN_WARNING "hidraw: pid %d passed too large report\n", printk(KERN_WARNING "hidraw: pid %d passed too large report\n",
task_pid_nr(current)); task_pid_nr(current));
return -EINVAL; ret = -EINVAL;
goto out;
} }
if (count < 2) { if (count < 2) {
printk(KERN_WARNING "hidraw: pid %d passed too short report\n", printk(KERN_WARNING "hidraw: pid %d passed too short report\n",
task_pid_nr(current)); task_pid_nr(current));
return -EINVAL; ret = -EINVAL;
goto out;
} }
buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); buf = kmalloc(count * sizeof(__u8), GFP_KERNEL);
if (!buf) if (!buf) {
return -ENOMEM; ret = -ENOMEM;
goto out;
}
if (copy_from_user(buf, buffer, count)) { if (copy_from_user(buf, buffer, count)) {
ret = -EFAULT; ret = -EFAULT;
goto out; goto out_free;
} }
ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT); ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT);
out: out_free:
kfree(buf); kfree(buf);
out:
mutex_unlock(&minors_lock);
return ret; return ret;
} }
...@@ -165,11 +175,8 @@ static int hidraw_open(struct inode *inode, struct file *file) ...@@ -165,11 +175,8 @@ static int hidraw_open(struct inode *inode, struct file *file)
goto out; goto out;
} }
lock_kernel();
mutex_lock(&minors_lock); mutex_lock(&minors_lock);
if (!hidraw_table[minor]) { if (!hidraw_table[minor]) {
printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n",
minor);
kfree(list); kfree(list);
err = -ENODEV; err = -ENODEV;
goto out_unlock; goto out_unlock;
...@@ -197,7 +204,6 @@ static int hidraw_open(struct inode *inode, struct file *file) ...@@ -197,7 +204,6 @@ static int hidraw_open(struct inode *inode, struct file *file)
out_unlock: out_unlock:
mutex_unlock(&minors_lock); mutex_unlock(&minors_lock);
unlock_kernel();
out: out:
return err; return err;
...@@ -209,11 +215,8 @@ static int hidraw_release(struct inode * inode, struct file * file) ...@@ -209,11 +215,8 @@ static int hidraw_release(struct inode * inode, struct file * file)
struct hidraw *dev; struct hidraw *dev;
struct hidraw_list *list = file->private_data; struct hidraw_list *list = file->private_data;
if (!hidraw_table[minor]) { if (!hidraw_table[minor])
printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n",
minor);
return -ENODEV; return -ENODEV;
}
list_del(&list->node); list_del(&list->node);
dev = hidraw_table[minor]; dev = hidraw_table[minor];
...@@ -238,11 +241,12 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, ...@@ -238,11 +241,12 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
struct inode *inode = file->f_path.dentry->d_inode; struct inode *inode = file->f_path.dentry->d_inode;
unsigned int minor = iminor(inode); unsigned int minor = iminor(inode);
long ret = 0; long ret = 0;
/* FIXME: What stops hidraw_table going NULL */ struct hidraw *dev;
struct hidraw *dev = hidraw_table[minor];
void __user *user_arg = (void __user*) arg; void __user *user_arg = (void __user*) arg;
lock_kernel(); mutex_lock(&minors_lock);
dev = hidraw_table[minor];
switch (cmd) { switch (cmd) {
case HIDIOCGRDESCSIZE: case HIDIOCGRDESCSIZE:
if (put_user(dev->hid->rsize, (int __user *)arg)) if (put_user(dev->hid->rsize, (int __user *)arg))
...@@ -315,7 +319,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, ...@@ -315,7 +319,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
ret = -ENOTTY; ret = -ENOTTY;
} }
unlock_kernel(); mutex_unlock(&minors_lock);
return ret; return ret;
} }
......
...@@ -623,6 +623,7 @@ int usbhid_wait_io(struct hid_device *hid) ...@@ -623,6 +623,7 @@ int usbhid_wait_io(struct hid_device *hid)
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(usbhid_wait_io);
static int hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle) static int hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle)
{ {
...@@ -806,16 +807,36 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co ...@@ -806,16 +807,36 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co
struct usb_host_interface *interface = intf->cur_altsetting; struct usb_host_interface *interface = intf->cur_altsetting;
int ret; int ret;
if (usbhid->urbout) {
int actual_length;
int skipped_report_id = 0;
if (buf[0] == 0x0) {
/* Don't send the Report ID */
buf++;
count--;
skipped_report_id = 1;
}
ret = usb_interrupt_msg(dev, usbhid->urbout->pipe,
buf, count, &actual_length,
USB_CTRL_SET_TIMEOUT);
/* return the number of bytes transferred */
if (ret == 0) {
ret = actual_length;
/* count also the report id */
if (skipped_report_id)
ret++;
}
} else {
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
HID_REQ_SET_REPORT, HID_REQ_SET_REPORT,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
((report_type + 1) << 8) | *buf, ((report_type + 1) << 8) | *buf,
interface->desc.bInterfaceNumber, buf + 1, count - 1, interface->desc.bInterfaceNumber, buf + 1, count - 1,
USB_CTRL_SET_TIMEOUT); USB_CTRL_SET_TIMEOUT);
/* count also the report id */ /* count also the report id */
if (ret > 0) if (ret > 0)
ret++; ret++;
}
return ret; return ret;
} }
...@@ -1017,12 +1038,15 @@ static int usbhid_start(struct hid_device *hid) ...@@ -1017,12 +1038,15 @@ static int usbhid_start(struct hid_device *hid)
/* Some keyboards don't work until their LEDs have been set. /* Some keyboards don't work until their LEDs have been set.
* Since BIOSes do set the LEDs, it must be safe for any device * Since BIOSes do set the LEDs, it must be safe for any device
* that supports the keyboard boot protocol. * that supports the keyboard boot protocol.
* In addition, enable remote wakeup by default for all keyboard
* devices supporting the boot protocol.
*/ */
if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT && if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT &&
interface->desc.bInterfaceProtocol == interface->desc.bInterfaceProtocol ==
USB_INTERFACE_PROTOCOL_KEYBOARD) USB_INTERFACE_PROTOCOL_KEYBOARD) {
usbhid_set_leds(hid); usbhid_set_leds(hid);
device_set_wakeup_enable(&dev->dev, 1);
}
return 0; return 0;
fail: fail:
...@@ -1131,6 +1155,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * ...@@ -1131,6 +1155,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
hid->vendor = le16_to_cpu(dev->descriptor.idVendor); hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
hid->product = le16_to_cpu(dev->descriptor.idProduct); hid->product = le16_to_cpu(dev->descriptor.idProduct);
hid->name[0] = 0; hid->name[0] = 0;
hid->quirks = usbhid_lookup_quirk(hid->vendor, hid->product);
if (intf->cur_altsetting->desc.bInterfaceProtocol == if (intf->cur_altsetting->desc.bInterfaceProtocol ==
USB_INTERFACE_PROTOCOL_MOUSE) USB_INTERFACE_PROTOCOL_MOUSE)
hid->type = HID_TYPE_USBMOUSE; hid->type = HID_TYPE_USBMOUSE;
...@@ -1287,6 +1312,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) ...@@ -1287,6 +1312,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
{ {
set_bit(HID_REPORTED_IDLE, &usbhid->iofl); set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock); spin_unlock_irq(&usbhid->lock);
if (hid->driver && hid->driver->suspend) {
status = hid->driver->suspend(hid, message);
if (status < 0)
return status;
}
} else { } else {
usbhid_mark_busy(usbhid); usbhid_mark_busy(usbhid);
spin_unlock_irq(&usbhid->lock); spin_unlock_irq(&usbhid->lock);
...@@ -1294,6 +1324,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) ...@@ -1294,6 +1324,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
} }
} else { } else {
if (hid->driver && hid->driver->suspend) {
status = hid->driver->suspend(hid, message);
if (status < 0)
return status;
}
spin_lock_irq(&usbhid->lock); spin_lock_irq(&usbhid->lock);
set_bit(HID_REPORTED_IDLE, &usbhid->iofl); set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock); spin_unlock_irq(&usbhid->lock);
...@@ -1348,6 +1383,11 @@ static int hid_resume(struct usb_interface *intf) ...@@ -1348,6 +1383,11 @@ static int hid_resume(struct usb_interface *intf)
hid_io_error(hid); hid_io_error(hid);
usbhid_restart_queues(usbhid); usbhid_restart_queues(usbhid);
if (status >= 0 && hid->driver && hid->driver->resume) {
int ret = hid->driver->resume(hid);
if (ret < 0)
status = ret;
}
dev_dbg(&intf->dev, "resume status %d\n", status); dev_dbg(&intf->dev, "resume status %d\n", status);
return 0; return 0;
} }
...@@ -1356,9 +1396,16 @@ static int hid_reset_resume(struct usb_interface *intf) ...@@ -1356,9 +1396,16 @@ static int hid_reset_resume(struct usb_interface *intf)
{ {
struct hid_device *hid = usb_get_intfdata(intf); struct hid_device *hid = usb_get_intfdata(intf);
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
int status;
clear_bit(HID_REPORTED_IDLE, &usbhid->iofl); clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
return hid_post_reset(intf); status = hid_post_reset(intf);
if (status >= 0 && hid->driver && hid->driver->reset_resume) {
int ret = hid->driver->reset_resume(hid);
if (ret < 0)
status = ret;
}
return status;
} }
#endif /* CONFIG_PM */ #endif /* CONFIG_PM */
......
...@@ -33,6 +33,7 @@ static const struct hid_blacklist { ...@@ -33,6 +33,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
......
...@@ -267,6 +267,7 @@ static int hiddev_open(struct inode *inode, struct file *file) ...@@ -267,6 +267,7 @@ static int hiddev_open(struct inode *inode, struct file *file)
struct hiddev_list *list; struct hiddev_list *list;
int res, i; int res, i;
/* See comment in hiddev_connect() for BKL explanation */
lock_kernel(); lock_kernel();
i = iminor(inode) - HIDDEV_MINOR_BASE; i = iminor(inode) - HIDDEV_MINOR_BASE;
...@@ -894,8 +895,22 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) ...@@ -894,8 +895,22 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
hiddev->hid = hid; hiddev->hid = hid;
hiddev->exist = 1; hiddev->exist = 1;
/* when lock_kernel() usage is fixed in usb_open(), /*
* we could also fix it here */ * BKL here is used to avoid race after usb_register_dev().
* Once the device node has been created, open() could happen on it.
* The code below will then fail, as hiddev_table hasn't been
* updated.
*
* The obvious fix -- introducing mutex to guard hiddev_table[]
* doesn't work, as usb_open() and usb_register_dev() both take
* minor_rwsem, thus we'll have ABBA deadlock.
*
* Before BKL pushdown, usb_open() had been acquiring it in right
* order, so _open() was safe to use it to protect from this race.
* Now the order is different, but AB-BA deadlock still doesn't occur
* as BKL is dropped on schedule() (i.e. while sleeping on
* minor_rwsem). Fugly.
*/
lock_kernel(); lock_kernel();
retval = usb_register_dev(usbhid->intf, &hiddev_class); retval = usb_register_dev(usbhid->intf, &hiddev_class);
if (retval) { if (retval) {
......
...@@ -311,6 +311,7 @@ static int usb_kbd_probe(struct usb_interface *iface, ...@@ -311,6 +311,7 @@ static int usb_kbd_probe(struct usb_interface *iface,
goto fail2; goto fail2;
usb_set_intfdata(iface, kbd); usb_set_intfdata(iface, kbd);
device_set_wakeup_enable(&dev->dev, 1);
return 0; return 0;
fail2: fail2:
......
...@@ -308,11 +308,13 @@ struct hid_item { ...@@ -308,11 +308,13 @@ struct hid_item {
#define HID_QUIRK_NOTOUCH 0x00000002 #define HID_QUIRK_NOTOUCH 0x00000002
#define HID_QUIRK_IGNORE 0x00000004 #define HID_QUIRK_IGNORE 0x00000004
#define HID_QUIRK_NOGET 0x00000008 #define HID_QUIRK_NOGET 0x00000008
#define HID_QUIRK_HIDDEV_FORCE 0x00000010
#define HID_QUIRK_BADPAD 0x00000020 #define HID_QUIRK_BADPAD 0x00000020
#define HID_QUIRK_MULTI_INPUT 0x00000040 #define HID_QUIRK_MULTI_INPUT 0x00000040
#define HID_QUIRK_SKIP_OUTPUT_REPORTS 0x00010000 #define HID_QUIRK_SKIP_OUTPUT_REPORTS 0x00010000
#define HID_QUIRK_FULLSPEED_INTERVAL 0x10000000 #define HID_QUIRK_FULLSPEED_INTERVAL 0x10000000
#define HID_QUIRK_NO_INIT_REPORTS 0x20000000 #define HID_QUIRK_NO_INIT_REPORTS 0x20000000
#define HID_QUIRK_NO_IGNORE 0x40000000
/* /*
* This is the global environment of the parser. This information is * This is the global environment of the parser. This information is
...@@ -589,6 +591,9 @@ struct hid_usage_id { ...@@ -589,6 +591,9 @@ struct hid_usage_id {
* @report_fixup: called before report descriptor parsing (NULL means nop) * @report_fixup: called before report descriptor parsing (NULL means nop)
* @input_mapping: invoked on input registering before mapping an usage * @input_mapping: invoked on input registering before mapping an usage
* @input_mapped: invoked on input registering after mapping an usage * @input_mapped: invoked on input registering after mapping an usage
* @suspend: invoked on suspend (NULL means nop)
* @resume: invoked on resume if device was not reset (NULL means nop)
* @reset_resume: invoked on resume if device was reset (NULL means nop)
* *
* raw_event and event should return 0 on no action performed, 1 when no * raw_event and event should return 0 on no action performed, 1 when no
* further processing should be done and negative on error * further processing should be done and negative on error
...@@ -629,6 +634,11 @@ struct hid_driver { ...@@ -629,6 +634,11 @@ struct hid_driver {
int (*input_mapped)(struct hid_device *hdev, int (*input_mapped)(struct hid_device *hdev,
struct hid_input *hidinput, struct hid_field *field, struct hid_input *hidinput, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max); struct hid_usage *usage, unsigned long **bit, int *max);
#ifdef CONFIG_PM
int (*suspend)(struct hid_device *hdev, pm_message_t message);
int (*resume)(struct hid_device *hdev);
int (*reset_resume)(struct hid_device *hdev);
#endif
/* private: */ /* private: */
struct device_driver driver; struct device_driver driver;
}; };
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册