mirror of
https://github.com/joel16/android_kernel_sony_msm8994.git
synced 2024-11-27 14:11:04 +00:00
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina: "The list of changes worth pointing out explicitly: - We are getting 'UHID', which is a new framework for implementing HID transport drivers in userspace (this is different from HIDRAW, which is transport-independent and provides report parsing facilities; uhid is for the other (transport) part of the pipeline). It's needed for (and currently being used by) Bluetooth-LowEnergy, as its specification mandates things we don't want in the kernel. Written by David Herrmann. - there have been quite a few bugs in runtime suspend/resume paths (probably never reported to actually happen in the wild, but still). Alan Stern fixed those. - a few other driver updates and fixes and random new device support." * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (45 commits) HID: add ASUS AIO keyboard model AK1D HID: add support for Cypress barcode scanner 04B4:ED81 HID: Allow drivers to be their own listener HID: usbhid: fix error paths in suspend HID: usbhid: check for suspend or reset before restarting HID: usbhid: replace HID_REPORTED_IDLE with HID_SUSPENDED HID: usbhid: inline some simple routines HID: usbhid: fix autosuspend calls HID: usbhid: fix use-after-free bug HID: hid-core: optimize in case of hidraw HID: hidraw: fix list->buffer memleak HID: uhid: Fix sending events with invalid data HID: roccat: added sensor sysfs attribute for Savu HID: Add driver for Holtek based keyboards with broken HID HID: Add suport for the brightness control keys on HP keyboards HID: magicmouse: Implement Multi-touch Protocol B (MT-B) HID: magicmouse: Removing report_touches switch HID: roccat: rename roccat_common functions to roccat_common2 HID: roccat: fix wrong hid_err usage on struct usb_device HID: roccat: move functionality to roccat-common ...
This commit is contained in:
commit
e8ff13b0bf
38
Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
Normal file
38
Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
Normal file
@ -0,0 +1,38 @@
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_to_select
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be
|
||||
is being controlled by press_speed.
|
||||
Values are 0 or 1.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled.
|
||||
Values are 0 or 1.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html
|
||||
Values are 0 or 1.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate
|
||||
a left or right mouse button click.
|
||||
Values are 0 or 1.
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This file contains the trackpoint sensitivity.
|
||||
Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity).
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed
|
||||
Date: July 2011
|
||||
Contact: linux-input@vger.kernel.org
|
||||
Description: This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled.
|
||||
Values are decimal integers from 1 (slowest) to 255 (fastest).
|
||||
|
77
Documentation/ABI/testing/sysfs-driver-hid-roccat-savu
Normal file
77
Documentation/ABI/testing/sysfs-driver-hid-roccat-savu
Normal file
@ -0,0 +1,77 @@
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/buttons
|
||||
Date: Mai 2012
|
||||
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 is split into general settings and
|
||||
button settings. buttons holds informations about button layout.
|
||||
When written, this file lets one write the respective profile
|
||||
buttons to the mouse. The data has to be 47 bytes long.
|
||||
The mouse will reject invalid data.
|
||||
Which profile to write is determined by the profile number
|
||||
contained in the data.
|
||||
Before reading this file, control has to be written to select
|
||||
which profile to read.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/control
|
||||
Date: Mai 2012
|
||||
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
|
||||
Description: When written, this file lets one select which data from which
|
||||
profile will be read next. The data has to be 3 bytes long.
|
||||
This file is writeonly.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/general
|
||||
Date: Mai 2012
|
||||
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 is split into general settings and
|
||||
button settings. profile holds informations like resolution, sensitivity
|
||||
and light effects.
|
||||
When written, this file lets one write the respective profile
|
||||
settings back to the mouse. The data has to be 43 bytes long.
|
||||
The mouse will reject invalid data.
|
||||
Which profile to write is determined by the profile number
|
||||
contained in the data.
|
||||
This file is writeonly.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/info
|
||||
Date: Mai 2012
|
||||
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
|
||||
Description: When read, this file returns general data like firmware version.
|
||||
The data is 8 bytes long.
|
||||
This file is readonly.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/macro
|
||||
Date: Mai 2012
|
||||
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
|
||||
Description: When written, this file lets one store macros with max 500
|
||||
keystrokes for a specific button for a specific profile.
|
||||
Button and profile numbers are included in written data.
|
||||
The data has to be 2083 bytes long.
|
||||
Before reading this file, control has to be written to select
|
||||
which profile and key to read.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/profile
|
||||
Date: Mai 2012
|
||||
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. profile holds number of actual profile.
|
||||
This value is persistent, so its value determines the profile
|
||||
that's active when the mouse is powered on next time.
|
||||
When written, the mouse activates the set profile immediately.
|
||||
The data has to be 3 bytes long.
|
||||
The mouse will reject invalid data.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
||||
What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/sensor
|
||||
Date: July 2012
|
||||
Contact: Stefan Achatz <erazor_de@users.sourceforge.net>
|
||||
Description: The mouse has a Avago ADNS-3090 sensor.
|
||||
This file allows reading and writing of the mouse sensors registers.
|
||||
The data has to be 4 bytes long.
|
||||
Users: http://roccat.sourceforge.net
|
||||
|
169
Documentation/hid/uhid.txt
Normal file
169
Documentation/hid/uhid.txt
Normal file
@ -0,0 +1,169 @@
|
||||
UHID - User-space I/O driver support for HID subsystem
|
||||
========================================================
|
||||
|
||||
The HID subsystem needs two kinds of drivers. In this document we call them:
|
||||
|
||||
1. The "HID I/O Driver" is the driver that performs raw data I/O to the
|
||||
low-level device. Internally, they register an hid_ll_driver structure with
|
||||
the HID core. They perform device setup, read raw data from the device and
|
||||
push it into the HID subsystem and they provide a callback so the HID
|
||||
subsystem can send data to the device.
|
||||
|
||||
2. The "HID Device Driver" is the driver that parses HID reports and reacts on
|
||||
them. There are generic drivers like "generic-usb" and "generic-bluetooth"
|
||||
which adhere to the HID specification and provide the standardizes features.
|
||||
But there may be special drivers and quirks for each non-standard device out
|
||||
there. Internally, they use the hid_driver structure.
|
||||
|
||||
Historically, the USB stack was the first subsystem to provide an HID I/O
|
||||
Driver. However, other standards like Bluetooth have adopted the HID specs and
|
||||
may provide HID I/O Drivers, too. The UHID driver allows to implement HID I/O
|
||||
Drivers in user-space and feed the data into the kernel HID-subsystem.
|
||||
|
||||
This allows user-space to operate on the same level as USB-HID, Bluetooth-HID
|
||||
and similar. It does not provide a way to write HID Device Drivers, though. Use
|
||||
hidraw for this purpose.
|
||||
|
||||
There is an example user-space application in ./samples/uhid/uhid-example.c
|
||||
|
||||
The UHID API
|
||||
------------
|
||||
|
||||
UHID is accessed through a character misc-device. The minor-number is allocated
|
||||
dynamically so you need to rely on udev (or similar) to create the device node.
|
||||
This is /dev/uhid by default.
|
||||
|
||||
If a new device is detected by your HID I/O Driver and you want to register this
|
||||
device with the HID subsystem, then you need to open /dev/uhid once for each
|
||||
device you want to register. All further communication is done by read()'ing or
|
||||
write()'ing "struct uhid_event" objects. Non-blocking operations are supported
|
||||
by setting O_NONBLOCK.
|
||||
|
||||
struct uhid_event {
|
||||
__u32 type;
|
||||
union {
|
||||
struct uhid_create_req create;
|
||||
struct uhid_data_req data;
|
||||
...
|
||||
} u;
|
||||
};
|
||||
|
||||
The "type" field contains the ID of the event. Depending on the ID different
|
||||
payloads are sent. You must not split a single event across multiple read()'s or
|
||||
multiple write()'s. A single event must always be sent as a whole. Furthermore,
|
||||
only a single event can be sent per read() or write(). Pending data is ignored.
|
||||
If you want to handle multiple events in a single syscall, then use vectored
|
||||
I/O with readv()/writev().
|
||||
|
||||
The first thing you should do is sending an UHID_CREATE event. This will
|
||||
register the device. UHID will respond with an UHID_START event. You can now
|
||||
start sending data to and reading data from UHID. However, unless UHID sends the
|
||||
UHID_OPEN event, the internally attached HID Device Driver has no user attached.
|
||||
That is, you might put your device asleep unless you receive the UHID_OPEN
|
||||
event. If you receive the UHID_OPEN event, you should start I/O. If the last
|
||||
user closes the HID device, you will receive an UHID_CLOSE event. This may be
|
||||
followed by an UHID_OPEN event again and so on. There is no need to perform
|
||||
reference-counting in user-space. That is, you will never receive multiple
|
||||
UHID_OPEN events without an UHID_CLOSE event. The HID subsystem performs
|
||||
ref-counting for you.
|
||||
You may decide to ignore UHID_OPEN/UHID_CLOSE, though. I/O is allowed even
|
||||
though the device may have no users.
|
||||
|
||||
If you want to send data to the HID subsystem, you send an HID_INPUT event with
|
||||
your raw data payload. If the kernel wants to send data to the device, you will
|
||||
read an UHID_OUTPUT or UHID_OUTPUT_EV event.
|
||||
|
||||
If your device disconnects, you should send an UHID_DESTROY event. This will
|
||||
unregister the device. You can now send UHID_CREATE again to register a new
|
||||
device.
|
||||
If you close() the fd, the device is automatically unregistered and destroyed
|
||||
internally.
|
||||
|
||||
write()
|
||||
-------
|
||||
write() allows you to modify the state of the device and feed input data into
|
||||
the kernel. The following types are supported: UHID_CREATE, UHID_DESTROY and
|
||||
UHID_INPUT. The kernel will parse the event immediately and if the event ID is
|
||||
not supported, it will return -EOPNOTSUPP. If the payload is invalid, then
|
||||
-EINVAL is returned, otherwise, the amount of data that was read is returned and
|
||||
the request was handled successfully.
|
||||
|
||||
UHID_CREATE:
|
||||
This creates the internal HID device. No I/O is possible until you send this
|
||||
event to the kernel. The payload is of type struct uhid_create_req and
|
||||
contains information about your device. You can start I/O now.
|
||||
|
||||
UHID_DESTROY:
|
||||
This destroys the internal HID device. No further I/O will be accepted. There
|
||||
may still be pending messages that you can receive with read() but no further
|
||||
UHID_INPUT events can be sent to the kernel.
|
||||
You can create a new device by sending UHID_CREATE again. There is no need to
|
||||
reopen the character device.
|
||||
|
||||
UHID_INPUT:
|
||||
You must send UHID_CREATE before sending input to the kernel! This event
|
||||
contains a data-payload. This is the raw data that you read from your device.
|
||||
The kernel will parse the HID reports and react on it.
|
||||
|
||||
UHID_FEATURE_ANSWER:
|
||||
If you receive a UHID_FEATURE request you must answer with this request. You
|
||||
must copy the "id" field from the request into the answer. Set the "err" field
|
||||
to 0 if no error occured or to EIO if an I/O error occurred.
|
||||
If "err" is 0 then you should fill the buffer of the answer with the results
|
||||
of the feature request and set "size" correspondingly.
|
||||
|
||||
read()
|
||||
------
|
||||
read() will return a queued ouput report. These output reports can be of type
|
||||
UHID_START, UHID_STOP, UHID_OPEN, UHID_CLOSE, UHID_OUTPUT or UHID_OUTPUT_EV. No
|
||||
reaction is required to any of them but you should handle them according to your
|
||||
needs. Only UHID_OUTPUT and UHID_OUTPUT_EV have payloads.
|
||||
|
||||
UHID_START:
|
||||
This is sent when the HID device is started. Consider this as an answer to
|
||||
UHID_CREATE. This is always the first event that is sent.
|
||||
|
||||
UHID_STOP:
|
||||
This is sent when the HID device is stopped. Consider this as an answer to
|
||||
UHID_DESTROY.
|
||||
If the kernel HID device driver closes the device manually (that is, you
|
||||
didn't send UHID_DESTROY) then you should consider this device closed and send
|
||||
an UHID_DESTROY event. You may want to reregister your device, though. This is
|
||||
always the last message that is sent to you unless you reopen the device with
|
||||
UHID_CREATE.
|
||||
|
||||
UHID_OPEN:
|
||||
This is sent when the HID device is opened. That is, the data that the HID
|
||||
device provides is read by some other process. You may ignore this event but
|
||||
it is useful for power-management. As long as you haven't received this event
|
||||
there is actually no other process that reads your data so there is no need to
|
||||
send UHID_INPUT events to the kernel.
|
||||
|
||||
UHID_CLOSE:
|
||||
This is sent when there are no more processes which read the HID data. It is
|
||||
the counterpart of UHID_OPEN and you may as well ignore this event.
|
||||
|
||||
UHID_OUTPUT:
|
||||
This is sent if the HID device driver wants to send raw data to the I/O
|
||||
device. You should read the payload and forward it to the device. The payload
|
||||
is of type "struct uhid_data_req".
|
||||
This may be received even though you haven't received UHID_OPEN, yet.
|
||||
|
||||
UHID_OUTPUT_EV:
|
||||
Same as UHID_OUTPUT but this contains a "struct input_event" as payload. This
|
||||
is called for force-feedback, LED or similar events which are received through
|
||||
an input device by the HID subsystem. You should convert this into raw reports
|
||||
and send them to your device similar to events of type UHID_OUTPUT.
|
||||
|
||||
UHID_FEATURE:
|
||||
This event is sent if the kernel driver wants to perform a feature request as
|
||||
described in the HID specs. The report-type and report-number are available in
|
||||
the payload.
|
||||
The kernel serializes feature requests so there will never be two in parallel.
|
||||
However, if you fail to respond with a UHID_FEATURE_ANSWER in a time-span of 5
|
||||
seconds, then the requests will be dropped and a new one might be sent.
|
||||
Therefore, the payload also contains an "id" field that identifies every
|
||||
request.
|
||||
|
||||
Document by:
|
||||
David Herrmann <dh.herrmann@googlemail.com>
|
@ -6965,6 +6965,13 @@ S: Maintained
|
||||
F: Documentation/filesystems/ufs.txt
|
||||
F: fs/ufs/
|
||||
|
||||
UHID USERSPACE HID IO DRIVER:
|
||||
M: David Herrmann <dh.herrmann@googlemail.com>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/uhid.c
|
||||
F: include/linux/uhid.h
|
||||
|
||||
ULTRA-WIDEBAND (UWB) SUBSYSTEM:
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Orphan
|
||||
|
@ -53,6 +53,27 @@ config HIDRAW
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config UHID
|
||||
tristate "User-space I/O driver support for HID subsystem"
|
||||
depends on HID
|
||||
default n
|
||||
---help---
|
||||
Say Y here if you want to provide HID I/O Drivers from user-space.
|
||||
This allows to write I/O drivers in user-space and feed the data from
|
||||
the device into the kernel. The kernel parses the HID reports, loads the
|
||||
corresponding HID Device Driver or provides input devices on top of your
|
||||
user-space device.
|
||||
|
||||
This driver cannot be used to parse HID-reports in user-space and write
|
||||
special HID-drivers. You should use hidraw for that.
|
||||
Instead, this driver allows to write the transport-layer driver in
|
||||
user-space like USB-HID and Bluetooth-HID do in kernel-space.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called uhid.
|
||||
|
||||
config HID_GENERIC
|
||||
tristate "Generic HID driver"
|
||||
depends on HID
|
||||
@ -193,10 +214,12 @@ config HID_EZKEY
|
||||
Support for Ezkey BTC 8193 keyboard.
|
||||
|
||||
config HID_HOLTEK
|
||||
tristate "Holtek On Line Grip based game controller support"
|
||||
tristate "Holtek HID devices"
|
||||
depends on USB_HID
|
||||
---help---
|
||||
Say Y here if you have a Holtek On Line Grip based game controller.
|
||||
Support for Holtek based devices:
|
||||
- Holtek On Line Grip based game controller
|
||||
- Trust GXT 18 Gaming Keyboard
|
||||
|
||||
config HOLTEK_FF
|
||||
bool "Holtek On Line Grip force feedback support"
|
||||
@ -261,6 +284,19 @@ config HID_LCPOWER
|
||||
---help---
|
||||
Support for LC-Power RC1000MCE RF remote control.
|
||||
|
||||
config HID_LENOVO_TPKBD
|
||||
tristate "Lenovo ThinkPad USB Keyboard with TrackPoint"
|
||||
depends on USB_HID
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
---help---
|
||||
Support for the Lenovo ThinkPad USB Keyboard with TrackPoint.
|
||||
|
||||
Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint
|
||||
and would like to use device-specific features like changing the
|
||||
sensitivity of the trackpoint, using the microphone mute button or
|
||||
controlling the mute and microphone mute LEDs.
|
||||
|
||||
config HID_LOGITECH
|
||||
tristate "Logitech devices" if EXPERT
|
||||
depends on USB_HID
|
||||
|
@ -8,6 +8,7 @@ ifdef CONFIG_DEBUG_FS
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_HID) += hid.o
|
||||
obj-$(CONFIG_UHID) += uhid.o
|
||||
|
||||
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
|
||||
|
||||
@ -48,12 +49,14 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
|
||||
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
|
||||
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
|
||||
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
|
||||
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
|
||||
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
|
||||
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
|
||||
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
|
||||
obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
|
||||
obj-$(CONFIG_HID_KYE) += hid-kye.o
|
||||
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
|
||||
obj-$(CONFIG_HID_LENOVO_TPKBD) += hid-lenovo-tpkbd.o
|
||||
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
|
||||
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o
|
||||
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
|
||||
@ -69,7 +72,8 @@ obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o
|
||||
obj-$(CONFIG_HID_PRIMAX) += hid-primax.o
|
||||
obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
|
||||
hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
|
||||
hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o
|
||||
hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o \
|
||||
hid-roccat-savu.o
|
||||
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
|
||||
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
|
||||
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
|
||||
|
@ -60,6 +60,7 @@ static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
static const struct hid_device_id ch_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, ch_devices);
|
||||
|
@ -1194,8 +1194,10 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size,
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (a = 0; a < report->maxfield; a++)
|
||||
hid_input_field(hid, report->field[a], cdata, interrupt);
|
||||
if (hid->claimed != HID_CLAIMED_HIDRAW) {
|
||||
for (a = 0; a < report->maxfield; a++)
|
||||
hid_input_field(hid, report->field[a], cdata, interrupt);
|
||||
}
|
||||
|
||||
if (hid->claimed & HID_CLAIMED_INPUT)
|
||||
hidinput_report_event(hid, report);
|
||||
@ -1243,6 +1245,10 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* Avoid unnecessary overhead if debugfs is disabled */
|
||||
if (list_empty(&hid->debug_list))
|
||||
goto nomem;
|
||||
|
||||
buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC);
|
||||
|
||||
if (!buf)
|
||||
@ -1373,8 +1379,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
|
||||
if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev))
|
||||
hdev->claimed |= HID_CLAIMED_HIDRAW;
|
||||
|
||||
if (!hdev->claimed) {
|
||||
hid_err(hdev, "claimed by neither input, hiddev nor hidraw\n");
|
||||
/* Drivers with the ->raw_event callback set are not required to connect
|
||||
* to any other listener. */
|
||||
if (!hdev->claimed && !hdev->driver->raw_event) {
|
||||
hid_err(hdev, "device has no listeners, quitting\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -1521,10 +1529,12 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) },
|
||||
@ -1539,6 +1549,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
|
||||
@ -1547,6 +1558,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
|
||||
@ -1620,6 +1632,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
|
||||
|
@ -129,6 +129,8 @@ static const struct hid_device_id cp_devices[] = {
|
||||
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3),
|
||||
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4),
|
||||
.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE),
|
||||
.driver_data = CP_2WHEEL_MOUSE_HACK },
|
||||
{ }
|
||||
|
183
drivers/hid/hid-holtek-kbd.c
Normal file
183
drivers/hid/hid-holtek-kbd.c
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* HID driver for Holtek keyboard
|
||||
* Copyright (c) 2012 Tom Harwood
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
#include "usbhid/usbhid.h"
|
||||
|
||||
/* Holtek based keyboards (USB ID 04d9:a055) have the following issues:
|
||||
* - The report descriptor specifies an excessively large number of consumer
|
||||
* usages (2^15), which is more than HID_MAX_USAGES. This prevents proper
|
||||
* parsing of the report descriptor.
|
||||
* - The report descriptor reports on caps/scroll/num lock key presses, but
|
||||
* doesn't have an LED output usage block.
|
||||
*
|
||||
* The replacement descriptor below fixes the number of consumer usages,
|
||||
* and provides an LED output usage block. LED output events are redirected
|
||||
* to the boot interface.
|
||||
*/
|
||||
|
||||
static __u8 holtek_kbd_rdesc_fixed[] = {
|
||||
/* Original report descriptor, with reduced number of consumer usages */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x80, /* Usage (Sys Control), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0x85, 0x01, /* Report ID (1), */
|
||||
0x19, 0x81, /* Usage Minimum (Sys Power Down), */
|
||||
0x29, 0x83, /* Usage Maximum (Sys Wake Up), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x25, 0x01, /* Logical Maximum (1), */
|
||||
0x95, 0x03, /* Report Count (3), */
|
||||
0x75, 0x01, /* Report Size (1), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0x95, 0x01, /* Report Count (1), */
|
||||
0x75, 0x05, /* Report Size (5), */
|
||||
0x81, 0x01, /* Input (Constant), */
|
||||
0xC0, /* End Collection, */
|
||||
0x05, 0x0C, /* Usage Page (Consumer), */
|
||||
0x09, 0x01, /* Usage (Consumer Control), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0x85, 0x02, /* Report ID (2), */
|
||||
0x19, 0x00, /* Usage Minimum (00h), */
|
||||
0x2A, 0xFF, 0x2F, /* Usage Maximum (0x2FFF), previously 0x7FFF */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x26, 0xFF, 0x2F, /* Logical Maximum (0x2FFF),previously 0x7FFF*/
|
||||
0x95, 0x01, /* Report Count (1), */
|
||||
0x75, 0x10, /* Report Size (16), */
|
||||
0x81, 0x00, /* Input, */
|
||||
0xC0, /* End Collection, */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x06, /* Usage (Keyboard), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0x85, 0x03, /* Report ID (3), */
|
||||
0x95, 0x38, /* Report Count (56), */
|
||||
0x75, 0x01, /* Report Size (1), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x25, 0x01, /* Logical Maximum (1), */
|
||||
0x05, 0x07, /* Usage Page (Keyboard), */
|
||||
0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */
|
||||
0x29, 0xE7, /* Usage Maximum (KB Right GUI), */
|
||||
0x19, 0x00, /* Usage Minimum (None), */
|
||||
0x29, 0x2F, /* Usage Maximum (KB Lboxbracket And Lbrace),*/
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x06, /* Usage (Keyboard), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0x85, 0x04, /* Report ID (4), */
|
||||
0x95, 0x38, /* Report Count (56), */
|
||||
0x75, 0x01, /* Report Size (1), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x25, 0x01, /* Logical Maximum (1), */
|
||||
0x05, 0x07, /* Usage Page (Keyboard), */
|
||||
0x19, 0x30, /* Usage Minimum (KB Rboxbracket And Rbrace),*/
|
||||
0x29, 0x67, /* Usage Maximum (KP Equals), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xC0, /* End Collection */
|
||||
|
||||
/* LED usage for the boot protocol interface */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x06, /* Usage (Keyboard), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0x05, 0x08, /* Usage Page (LED), */
|
||||
0x19, 0x01, /* Usage Minimum (01h), */
|
||||
0x29, 0x03, /* Usage Maximum (03h), */
|
||||
0x15, 0x00, /* Logical Minimum (0), */
|
||||
0x25, 0x01, /* Logical Maximum (1), */
|
||||
0x75, 0x01, /* Report Size (1), */
|
||||
0x95, 0x03, /* Report Count (3), */
|
||||
0x91, 0x02, /* Output (Variable), */
|
||||
0x95, 0x05, /* Report Count (5), */
|
||||
0x91, 0x01, /* Output (Constant), */
|
||||
0xC0, /* End Collection */
|
||||
};
|
||||
|
||||
static __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
|
||||
rdesc = holtek_kbd_rdesc_fixed;
|
||||
*rsize = sizeof(holtek_kbd_rdesc_fixed);
|
||||
}
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
static int holtek_kbd_input_event(struct input_dev *dev, unsigned int type,
|
||||
unsigned int code,
|
||||
int value)
|
||||
{
|
||||
struct hid_device *hid = input_get_drvdata(dev);
|
||||
struct usb_device *usb_dev = hid_to_usb_dev(hid);
|
||||
|
||||
/* Locate the boot interface, to receive the LED change events */
|
||||
struct usb_interface *boot_interface = usb_ifnum_to_if(usb_dev, 0);
|
||||
|
||||
struct hid_device *boot_hid = usb_get_intfdata(boot_interface);
|
||||
struct hid_input *boot_hid_input = list_first_entry(&boot_hid->inputs,
|
||||
struct hid_input, list);
|
||||
|
||||
return boot_hid_input->input->event(boot_hid_input->input, type, code,
|
||||
value);
|
||||
}
|
||||
|
||||
static int holtek_kbd_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
int ret = hid_parse(hdev);
|
||||
|
||||
if (!ret)
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
|
||||
if (!ret && intf->cur_altsetting->desc.bInterfaceNumber == 1) {
|
||||
struct hid_input *hidinput;
|
||||
list_for_each_entry(hidinput, &hdev->inputs, list) {
|
||||
hidinput->input->event = holtek_kbd_input_event;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id holtek_kbd_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
|
||||
USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, holtek_kbd_devices);
|
||||
|
||||
static struct hid_driver holtek_kbd_driver = {
|
||||
.name = "holtek_kbd",
|
||||
.id_table = holtek_kbd_devices,
|
||||
.report_fixup = holtek_kbd_report_fixup,
|
||||
.probe = holtek_kbd_probe
|
||||
};
|
||||
|
||||
static int __init holtek_kbd_init(void)
|
||||
{
|
||||
return hid_register_driver(&holtek_kbd_driver);
|
||||
}
|
||||
|
||||
static void __exit holtek_kbd_exit(void)
|
||||
{
|
||||
hid_unregister_driver(&holtek_kbd_driver);
|
||||
}
|
||||
|
||||
module_exit(holtek_kbd_exit);
|
||||
module_init(holtek_kbd_init);
|
||||
MODULE_LICENSE("GPL");
|
@ -208,6 +208,7 @@
|
||||
#define USB_DEVICE_ID_CHICONY_MULTI_TOUCH 0xb19d
|
||||
#define USB_DEVICE_ID_CHICONY_WIRELESS 0x0618
|
||||
#define USB_DEVICE_ID_CHICONY_WIRELESS2 0x1123
|
||||
#define USB_DEVICE_ID_CHICONY_AK1D 0x1125
|
||||
|
||||
#define USB_VENDOR_ID_CHUNGHWAT 0x2247
|
||||
#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001
|
||||
@ -237,6 +238,7 @@
|
||||
#define USB_DEVICE_ID_CYPRESS_BARCODE_1 0xde61
|
||||
#define USB_DEVICE_ID_CYPRESS_BARCODE_2 0xde64
|
||||
#define USB_DEVICE_ID_CYPRESS_BARCODE_3 0xbca1
|
||||
#define USB_DEVICE_ID_CYPRESS_BARCODE_4 0xed81
|
||||
#define USB_DEVICE_ID_CYPRESS_TRUETOUCH 0xc001
|
||||
|
||||
#define USB_VENDOR_ID_DEALEXTREAME 0x10c5
|
||||
@ -410,6 +412,9 @@
|
||||
#define USB_VENDOR_ID_HOLTEK 0x1241
|
||||
#define USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP 0x5015
|
||||
|
||||
#define USB_VENDOR_ID_HOLTEK_ALT 0x04d9
|
||||
#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD 0xa055
|
||||
|
||||
#define USB_VENDOR_ID_IMATION 0x0718
|
||||
#define USB_DEVICE_ID_DISC_STAKKA 0xd000
|
||||
|
||||
@ -479,6 +484,9 @@
|
||||
#define USB_DEVICE_ID_LD_HYBRID 0x2090
|
||||
#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0
|
||||
|
||||
#define USB_VENDOR_ID_LENOVO 0x17ef
|
||||
#define USB_DEVICE_ID_LENOVO_TPKBD 0x6009
|
||||
|
||||
#define USB_VENDOR_ID_LG 0x1fd2
|
||||
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
|
||||
|
||||
@ -573,6 +581,9 @@
|
||||
#define USB_VENDOR_ID_NINTENDO 0x057e
|
||||
#define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306
|
||||
|
||||
#define USB_VENDOR_ID_NOVATEK 0x0603
|
||||
#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
|
||||
|
||||
#define USB_VENDOR_ID_NTRIG 0x1b96
|
||||
#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN 0x0001
|
||||
#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1 0x0003
|
||||
@ -650,6 +661,7 @@
|
||||
#define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50
|
||||
#define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24
|
||||
#define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6
|
||||
#define USB_DEVICE_ID_ROCCAT_SAVU 0x2d5a
|
||||
|
||||
#define USB_VENDOR_ID_SAITEK 0x06a3
|
||||
#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17
|
||||
|
@ -837,6 +837,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_UP_HPVENDOR2:
|
||||
set_bit(EV_REP, input->evbit);
|
||||
switch (usage->hid & HID_USAGE) {
|
||||
case 0x003: map_key_clear(KEY_BRIGHTNESSDOWN); break;
|
||||
case 0x004: map_key_clear(KEY_BRIGHTNESSUP); break;
|
||||
default: goto ignore;
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_UP_MSVENDOR:
|
||||
goto ignore;
|
||||
|
||||
|
564
drivers/hid/hid-lenovo-tpkbd.c
Normal file
564
drivers/hid/hid-lenovo-tpkbd.c
Normal file
@ -0,0 +1,564 @@
|
||||
/*
|
||||
* HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
|
||||
*
|
||||
* Copyright (c) 2012 Bernhard Seibold
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/leds.h>
|
||||
#include "usbhid/usbhid.h"
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
/* This is only used for the trackpoint part of the driver, hence _tp */
|
||||
struct tpkbd_data_pointer {
|
||||
int led_state;
|
||||
struct led_classdev led_mute;
|
||||
struct led_classdev led_micmute;
|
||||
int press_to_select;
|
||||
int dragging;
|
||||
int release_to_select;
|
||||
int select_right;
|
||||
int sensitivity;
|
||||
int press_speed;
|
||||
};
|
||||
|
||||
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
|
||||
|
||||
static int tpkbd_input_mapping(struct hid_device *hdev,
|
||||
struct hid_input *hi, struct hid_field *field,
|
||||
struct hid_usage *usage, unsigned long **bit, int *max)
|
||||
{
|
||||
struct usbhid_device *uhdev;
|
||||
|
||||
uhdev = (struct usbhid_device *) hdev->driver_data;
|
||||
if (uhdev->ifnum == 1 && usage->hid == (HID_UP_BUTTON | 0x0010)) {
|
||||
map_key_clear(KEY_MICMUTE);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef map_key_clear
|
||||
|
||||
static int tpkbd_features_set(struct hid_device *hdev)
|
||||
{
|
||||
struct hid_report *report;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
|
||||
|
||||
report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02;
|
||||
report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08;
|
||||
report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
|
||||
report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40;
|
||||
report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
|
||||
report->field[2]->value[0] = data_pointer->sensitivity;
|
||||
report->field[3]->value[0] = data_pointer->press_speed;
|
||||
|
||||
usbhid_submit_report(hdev, report, USB_DIR_OUT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_to_select_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_to_select_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int value;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->press_to_select = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_dragging_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
|
||||
}
|
||||
|
||||
static ssize_t pointer_dragging_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int value;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->dragging = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_release_to_select_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
|
||||
}
|
||||
|
||||
static ssize_t pointer_release_to_select_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int value;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->release_to_select = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_select_right_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
|
||||
}
|
||||
|
||||
static ssize_t pointer_select_right_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int value;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (kstrtoint(buf, 10, &value))
|
||||
return -EINVAL;
|
||||
if (value < 0 || value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->select_right = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_sensitivity_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t pointer_sensitivity_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int value;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->sensitivity = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_speed_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->press_speed);
|
||||
}
|
||||
|
||||
static ssize_t pointer_press_speed_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int value;
|
||||
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
if (hdev == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
|
||||
return -EINVAL;
|
||||
|
||||
data_pointer->press_speed = value;
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute dev_attr_pointer_press_to_select =
|
||||
__ATTR(press_to_select, S_IWUSR | S_IRUGO,
|
||||
pointer_press_to_select_show,
|
||||
pointer_press_to_select_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_dragging =
|
||||
__ATTR(dragging, S_IWUSR | S_IRUGO,
|
||||
pointer_dragging_show,
|
||||
pointer_dragging_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_release_to_select =
|
||||
__ATTR(release_to_select, S_IWUSR | S_IRUGO,
|
||||
pointer_release_to_select_show,
|
||||
pointer_release_to_select_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_select_right =
|
||||
__ATTR(select_right, S_IWUSR | S_IRUGO,
|
||||
pointer_select_right_show,
|
||||
pointer_select_right_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_sensitivity =
|
||||
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
|
||||
pointer_sensitivity_show,
|
||||
pointer_sensitivity_store);
|
||||
|
||||
static struct device_attribute dev_attr_pointer_press_speed =
|
||||
__ATTR(press_speed, S_IWUSR | S_IRUGO,
|
||||
pointer_press_speed_show,
|
||||
pointer_press_speed_store);
|
||||
|
||||
static struct attribute *tpkbd_attributes_pointer[] = {
|
||||
&dev_attr_pointer_press_to_select.attr,
|
||||
&dev_attr_pointer_dragging.attr,
|
||||
&dev_attr_pointer_release_to_select.attr,
|
||||
&dev_attr_pointer_select_right.attr,
|
||||
&dev_attr_pointer_sensitivity.attr,
|
||||
&dev_attr_pointer_press_speed.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tpkbd_attr_group_pointer = {
|
||||
.attrs = tpkbd_attributes_pointer,
|
||||
};
|
||||
|
||||
static enum led_brightness tpkbd_led_brightness_get(
|
||||
struct led_classdev *led_cdev)
|
||||
{
|
||||
struct device *dev;
|
||||
struct hid_device *hdev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int led_nr = 0;
|
||||
|
||||
dev = led_cdev->dev->parent;
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (led_cdev == &data_pointer->led_micmute)
|
||||
led_nr = 1;
|
||||
|
||||
return data_pointer->led_state & (1 << led_nr)
|
||||
? LED_FULL
|
||||
: LED_OFF;
|
||||
}
|
||||
|
||||
static void tpkbd_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct device *dev;
|
||||
struct hid_device *hdev;
|
||||
struct hid_report *report;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
int led_nr = 0;
|
||||
|
||||
dev = led_cdev->dev->parent;
|
||||
hdev = container_of(dev, struct hid_device, dev);
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
if (led_cdev == &data_pointer->led_micmute)
|
||||
led_nr = 1;
|
||||
|
||||
if (value == LED_OFF)
|
||||
data_pointer->led_state &= ~(1 << led_nr);
|
||||
else
|
||||
data_pointer->led_state |= 1 << led_nr;
|
||||
|
||||
report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
|
||||
report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
|
||||
report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
|
||||
usbhid_submit_report(hdev, report, USB_DIR_OUT);
|
||||
}
|
||||
|
||||
static int tpkbd_probe_tp(struct hid_device *hdev)
|
||||
{
|
||||
struct device *dev = &hdev->dev;
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
size_t name_sz = strlen(dev_name(dev)) + 16;
|
||||
char *name_mute, *name_micmute;
|
||||
int ret;
|
||||
|
||||
if (sysfs_create_group(&hdev->dev.kobj,
|
||||
&tpkbd_attr_group_pointer)) {
|
||||
hid_warn(hdev, "Could not create sysfs group\n");
|
||||
}
|
||||
|
||||
data_pointer = kzalloc(sizeof(struct tpkbd_data_pointer), GFP_KERNEL);
|
||||
if (data_pointer == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for driver data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// set same default values as windows driver
|
||||
data_pointer->sensitivity = 0xa0;
|
||||
data_pointer->press_speed = 0x38;
|
||||
|
||||
name_mute = kzalloc(name_sz, GFP_KERNEL);
|
||||
if (name_mute == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for led data\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
|
||||
|
||||
name_micmute = kzalloc(name_sz, GFP_KERNEL);
|
||||
if (name_micmute == NULL) {
|
||||
hid_err(hdev, "Could not allocate memory for led data\n");
|
||||
ret = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
|
||||
|
||||
hid_set_drvdata(hdev, data_pointer);
|
||||
|
||||
data_pointer->led_mute.name = name_mute;
|
||||
data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get;
|
||||
data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set;
|
||||
data_pointer->led_mute.dev = dev;
|
||||
led_classdev_register(dev, &data_pointer->led_mute);
|
||||
|
||||
data_pointer->led_micmute.name = name_micmute;
|
||||
data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get;
|
||||
data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set;
|
||||
data_pointer->led_micmute.dev = dev;
|
||||
led_classdev_register(dev, &data_pointer->led_micmute);
|
||||
|
||||
tpkbd_features_set(hdev);
|
||||
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
kfree(name_mute);
|
||||
err:
|
||||
kfree(data_pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tpkbd_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct usbhid_device *uhdev;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hid_parse failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hid_hw_start failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
uhdev = (struct usbhid_device *) hdev->driver_data;
|
||||
|
||||
if (uhdev->ifnum == 1)
|
||||
return tpkbd_probe_tp(hdev);
|
||||
|
||||
return 0;
|
||||
err_free:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tpkbd_remove_tp(struct hid_device *hdev)
|
||||
{
|
||||
struct tpkbd_data_pointer *data_pointer;
|
||||
|
||||
sysfs_remove_group(&hdev->dev.kobj,
|
||||
&tpkbd_attr_group_pointer);
|
||||
|
||||
data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
|
||||
|
||||
led_classdev_unregister(&data_pointer->led_micmute);
|
||||
led_classdev_unregister(&data_pointer->led_mute);
|
||||
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
kfree(data_pointer);
|
||||
}
|
||||
|
||||
static void tpkbd_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct usbhid_device *uhdev;
|
||||
|
||||
uhdev = (struct usbhid_device *) hdev->driver_data;
|
||||
if (uhdev->ifnum == 1)
|
||||
tpkbd_remove_tp(hdev);
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
static const struct hid_device_id tpkbd_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, tpkbd_devices);
|
||||
|
||||
static struct hid_driver tpkbd_driver = {
|
||||
.name = "lenovo_tpkbd",
|
||||
.id_table = tpkbd_devices,
|
||||
.input_mapping = tpkbd_input_mapping,
|
||||
.probe = tpkbd_probe,
|
||||
.remove = tpkbd_remove,
|
||||
};
|
||||
|
||||
static int __init tpkbd_init(void)
|
||||
{
|
||||
return hid_register_driver(&tpkbd_driver);
|
||||
}
|
||||
|
||||
static void __exit tpkbd_exit(void)
|
||||
{
|
||||
hid_unregister_driver(&tpkbd_driver);
|
||||
}
|
||||
|
||||
module_init(tpkbd_init);
|
||||
module_exit(tpkbd_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
@ -48,10 +49,6 @@ static bool scroll_acceleration = false;
|
||||
module_param(scroll_acceleration, bool, 0644);
|
||||
MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events");
|
||||
|
||||
static bool report_touches = true;
|
||||
module_param(report_touches, bool, 0644);
|
||||
MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)");
|
||||
|
||||
static bool report_undeciphered;
|
||||
module_param(report_undeciphered, bool, 0644);
|
||||
MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
|
||||
@ -72,15 +69,6 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
|
||||
|
||||
#define SCROLL_ACCEL_DEFAULT 7
|
||||
|
||||
/* Single touch emulation should only begin when no touches are currently down.
|
||||
* This is true when single_touch_id is equal to NO_TOUCHES. If multiple touches
|
||||
* are down and the touch providing for single touch emulation is lifted,
|
||||
* single_touch_id is equal to SINGLE_TOUCH_UP. While single touch emulation is
|
||||
* occurring, single_touch_id corresponds with the tracking id of the touch used.
|
||||
*/
|
||||
#define NO_TOUCHES -1
|
||||
#define SINGLE_TOUCH_UP -2
|
||||
|
||||
/* Touch surface information. Dimension is in hundredths of a mm, min and max
|
||||
* are in units. */
|
||||
#define MOUSE_DIMENSION_X (float)9056
|
||||
@ -129,7 +117,6 @@ struct magicmouse_sc {
|
||||
u8 size;
|
||||
} touches[16];
|
||||
int tracking_ids[16];
|
||||
int single_touch_id;
|
||||
};
|
||||
|
||||
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
|
||||
@ -268,16 +255,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
||||
}
|
||||
}
|
||||
|
||||
if (down) {
|
||||
if (down)
|
||||
msc->ntouches++;
|
||||
if (msc->single_touch_id == NO_TOUCHES)
|
||||
msc->single_touch_id = id;
|
||||
} else if (msc->single_touch_id == id)
|
||||
msc->single_touch_id = SINGLE_TOUCH_UP;
|
||||
|
||||
input_mt_slot(input, id);
|
||||
input_mt_report_slot_state(input, MT_TOOL_FINGER, down);
|
||||
|
||||
/* Generate the input events for this touch. */
|
||||
if (report_touches && down) {
|
||||
input_report_abs(input, ABS_MT_TRACKING_ID, id);
|
||||
if (down) {
|
||||
input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major << 2);
|
||||
input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor << 2);
|
||||
input_report_abs(input, ABS_MT_ORIENTATION, -orientation);
|
||||
@ -290,8 +275,6 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
||||
else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
|
||||
input_event(input, EV_MSC, MSC_RAW, tdata[8]);
|
||||
}
|
||||
|
||||
input_mt_sync(input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,12 +295,6 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
for (ii = 0; ii < npoints; ii++)
|
||||
magicmouse_emit_touch(msc, ii, data + ii * 9 + 4);
|
||||
|
||||
/* We don't need an MT sync here because trackpad emits a
|
||||
* BTN_TOUCH event in a new frame when all touches are released.
|
||||
*/
|
||||
if (msc->ntouches == 0)
|
||||
msc->single_touch_id = NO_TOUCHES;
|
||||
|
||||
clicks = data[1];
|
||||
|
||||
/* The following bits provide a device specific timestamp. They
|
||||
@ -335,9 +312,6 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
for (ii = 0; ii < npoints; ii++)
|
||||
magicmouse_emit_touch(msc, ii, data + ii * 8 + 6);
|
||||
|
||||
if (report_touches && msc->ntouches == 0)
|
||||
input_mt_sync(input);
|
||||
|
||||
/* When emulating three-button mode, it is important
|
||||
* to have the current touch information before
|
||||
* generating a click event.
|
||||
@ -370,25 +344,17 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
input_report_rel(input, REL_Y, y);
|
||||
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
|
||||
input_report_key(input, BTN_MOUSE, clicks & 1);
|
||||
input_report_key(input, BTN_TOUCH, msc->ntouches > 0);
|
||||
input_report_key(input, BTN_TOOL_FINGER, msc->ntouches == 1);
|
||||
input_report_key(input, BTN_TOOL_DOUBLETAP, msc->ntouches == 2);
|
||||
input_report_key(input, BTN_TOOL_TRIPLETAP, msc->ntouches == 3);
|
||||
input_report_key(input, BTN_TOOL_QUADTAP, msc->ntouches == 4);
|
||||
if (msc->single_touch_id >= 0) {
|
||||
input_report_abs(input, ABS_X,
|
||||
msc->touches[msc->single_touch_id].x);
|
||||
input_report_abs(input, ABS_Y,
|
||||
msc->touches[msc->single_touch_id].y);
|
||||
}
|
||||
input_mt_report_pointer_emulation(input, true);
|
||||
}
|
||||
|
||||
input_sync(input);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
|
||||
static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
|
||||
{
|
||||
int error;
|
||||
|
||||
__set_bit(EV_KEY, input->evbit);
|
||||
|
||||
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
|
||||
@ -417,62 +383,66 @@ static void magicmouse_setup_input(struct input_dev *input, struct hid_device *h
|
||||
__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
|
||||
__set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
|
||||
__set_bit(BTN_TOOL_QUADTAP, input->keybit);
|
||||
__set_bit(BTN_TOOL_QUINTTAP, input->keybit);
|
||||
__set_bit(BTN_TOUCH, input->keybit);
|
||||
__set_bit(INPUT_PROP_POINTER, input->propbit);
|
||||
__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
|
||||
}
|
||||
|
||||
if (report_touches) {
|
||||
__set_bit(EV_ABS, input->evbit);
|
||||
|
||||
input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 15, 0, 0);
|
||||
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
|
||||
4, 0);
|
||||
input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
|
||||
4, 0);
|
||||
input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
|
||||
__set_bit(EV_ABS, input->evbit);
|
||||
|
||||
/* Note: Touch Y position from the device is inverted relative
|
||||
* to how pointer motion is reported (and relative to how USB
|
||||
* HID recommends the coordinates work). This driver keeps
|
||||
* the origin at the same position, and just uses the additive
|
||||
* inverse of the reported Y.
|
||||
*/
|
||||
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
|
||||
input_set_abs_params(input, ABS_MT_POSITION_X,
|
||||
MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
|
||||
input_set_abs_params(input, ABS_MT_POSITION_Y,
|
||||
MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0);
|
||||
error = input_mt_init_slots(input, 16);
|
||||
if (error)
|
||||
return error;
|
||||
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
|
||||
4, 0);
|
||||
input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
|
||||
4, 0);
|
||||
input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
|
||||
|
||||
input_abs_set_res(input, ABS_MT_POSITION_X,
|
||||
MOUSE_RES_X);
|
||||
input_abs_set_res(input, ABS_MT_POSITION_Y,
|
||||
MOUSE_RES_Y);
|
||||
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
|
||||
input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
|
||||
TRACKPAD_MAX_X, 4, 0);
|
||||
input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
|
||||
TRACKPAD_MAX_Y, 4, 0);
|
||||
input_set_abs_params(input, ABS_MT_POSITION_X,
|
||||
TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0);
|
||||
input_set_abs_params(input, ABS_MT_POSITION_Y,
|
||||
TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0);
|
||||
/* Note: Touch Y position from the device is inverted relative
|
||||
* to how pointer motion is reported (and relative to how USB
|
||||
* HID recommends the coordinates work). This driver keeps
|
||||
* the origin at the same position, and just uses the additive
|
||||
* inverse of the reported Y.
|
||||
*/
|
||||
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
|
||||
input_set_abs_params(input, ABS_MT_POSITION_X,
|
||||
MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
|
||||
input_set_abs_params(input, ABS_MT_POSITION_Y,
|
||||
MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0);
|
||||
|
||||
input_abs_set_res(input, ABS_X, TRACKPAD_RES_X);
|
||||
input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y);
|
||||
input_abs_set_res(input, ABS_MT_POSITION_X,
|
||||
TRACKPAD_RES_X);
|
||||
input_abs_set_res(input, ABS_MT_POSITION_Y,
|
||||
TRACKPAD_RES_Y);
|
||||
}
|
||||
input_abs_set_res(input, ABS_MT_POSITION_X,
|
||||
MOUSE_RES_X);
|
||||
input_abs_set_res(input, ABS_MT_POSITION_Y,
|
||||
MOUSE_RES_Y);
|
||||
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
|
||||
input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
|
||||
TRACKPAD_MAX_X, 4, 0);
|
||||
input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
|
||||
TRACKPAD_MAX_Y, 4, 0);
|
||||
input_set_abs_params(input, ABS_MT_POSITION_X,
|
||||
TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0);
|
||||
input_set_abs_params(input, ABS_MT_POSITION_Y,
|
||||
TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0);
|
||||
|
||||
input_set_events_per_packet(input, 60);
|
||||
input_abs_set_res(input, ABS_X, TRACKPAD_RES_X);
|
||||
input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y);
|
||||
input_abs_set_res(input, ABS_MT_POSITION_X,
|
||||
TRACKPAD_RES_X);
|
||||
input_abs_set_res(input, ABS_MT_POSITION_Y,
|
||||
TRACKPAD_RES_Y);
|
||||
}
|
||||
|
||||
input_set_events_per_packet(input, 60);
|
||||
|
||||
if (report_undeciphered) {
|
||||
__set_bit(EV_MSC, input->evbit);
|
||||
__set_bit(MSC_RAW, input->mscbit);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int magicmouse_input_mapping(struct hid_device *hdev,
|
||||
@ -511,8 +481,6 @@ static int magicmouse_probe(struct hid_device *hdev,
|
||||
msc->quirks = id->driver_data;
|
||||
hid_set_drvdata(hdev, msc);
|
||||
|
||||
msc->single_touch_id = NO_TOUCHES;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "magicmouse hid parse failed\n");
|
||||
@ -528,8 +496,13 @@ static int magicmouse_probe(struct hid_device *hdev,
|
||||
/* We do this after hid-input is done parsing reports so that
|
||||
* hid-input uses the most natural button and axis IDs.
|
||||
*/
|
||||
if (msc->input)
|
||||
magicmouse_setup_input(msc->input, hdev);
|
||||
if (msc->input) {
|
||||
ret = magicmouse_setup_input(msc->input, hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
|
||||
goto err_stop_hw;
|
||||
}
|
||||
}
|
||||
|
||||
if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
|
||||
report = hid_register_report(hdev, HID_INPUT_REPORT,
|
||||
|
@ -83,6 +83,7 @@ struct mt_device {
|
||||
unsigned last_field_index; /* last field index of the report */
|
||||
unsigned last_slot_field; /* the last field of a slot */
|
||||
__s8 inputmode; /* InputMode HID feature, -1 if non-existent */
|
||||
__s8 inputmode_index; /* InputMode HID feature index in the report */
|
||||
__s8 maxcontact_report_id; /* Maximum Contact Number HID feature,
|
||||
-1 if non-existent */
|
||||
__u8 num_received; /* how many contacts we received */
|
||||
@ -260,10 +261,20 @@ static void mt_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_field *field, struct hid_usage *usage)
|
||||
{
|
||||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
int i;
|
||||
|
||||
switch (usage->hid) {
|
||||
case HID_DG_INPUTMODE:
|
||||
td->inputmode = field->report->id;
|
||||
td->inputmode_index = 0; /* has to be updated below */
|
||||
|
||||
for (i=0; i < field->maxusage; i++) {
|
||||
if (field->usage[i].hid == usage->hid) {
|
||||
td->inputmode_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case HID_DG_CONTACTMAX:
|
||||
td->maxcontact_report_id = field->report->id;
|
||||
@ -618,7 +629,7 @@ static void mt_set_input_mode(struct hid_device *hdev)
|
||||
re = &(hdev->report_enum[HID_FEATURE_REPORT]);
|
||||
r = re->report_id_hash[td->inputmode];
|
||||
if (r) {
|
||||
r->field[0]->value[0] = 0x02;
|
||||
r->field[0]->value[td->inputmode_index] = 0x02;
|
||||
usbhid_submit_report(hdev, r, USB_DIR_OUT);
|
||||
}
|
||||
}
|
||||
@ -951,6 +962,11 @@ static const struct hid_device_id mt_devices[] = {
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_PANASONIC,
|
||||
USB_DEVICE_ID_PANABOARD_UBT880) },
|
||||
|
||||
/* Novatek Panel */
|
||||
{ .driver_data = MT_CLS_DEFAULT,
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_NOVATEK,
|
||||
USB_DEVICE_ID_NOVATEK_PCT) },
|
||||
|
||||
/* PenMount panels */
|
||||
{ .driver_data = MT_CLS_CONFIDENCE,
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_PENMOUNT,
|
||||
|
@ -1846,7 +1846,7 @@ static void picolcd_debug_out_report(struct picolcd_data *data,
|
||||
#define BUFF_SZ 256
|
||||
|
||||
/* Avoid unnecessary overhead if debugfs is disabled */
|
||||
if (!hdev->debug_events)
|
||||
if (list_empty(&hdev->debug_list))
|
||||
return;
|
||||
|
||||
buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
|
||||
@ -2613,11 +2613,7 @@ static int picolcd_probe(struct hid_device *hdev,
|
||||
goto err_cleanup_data;
|
||||
}
|
||||
|
||||
/* We don't use hidinput but hid_hw_start() fails if nothing is
|
||||
* claimed. So spoof claimed input. */
|
||||
hdev->claimed = HID_CLAIMED_INPUT;
|
||||
error = hid_hw_start(hdev, 0);
|
||||
hdev->claimed = 0;
|
||||
if (error) {
|
||||
hid_err(hdev, "hardware start failed\n");
|
||||
goto err_cleanup_data;
|
||||
|
@ -39,7 +39,7 @@ static ssize_t arvo_sysfs_show_mode_key(struct device *dev,
|
||||
int retval;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_receive(usb_dev, ARVO_COMMAND_MODE_KEY,
|
||||
retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_MODE_KEY,
|
||||
&temp_buf, sizeof(struct arvo_mode_key));
|
||||
mutex_unlock(&arvo->arvo_lock);
|
||||
if (retval)
|
||||
@ -67,7 +67,7 @@ static ssize_t arvo_sysfs_set_mode_key(struct device *dev,
|
||||
temp_buf.state = state;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_send(usb_dev, ARVO_COMMAND_MODE_KEY,
|
||||
retval = roccat_common2_send(usb_dev, ARVO_COMMAND_MODE_KEY,
|
||||
&temp_buf, sizeof(struct arvo_mode_key));
|
||||
mutex_unlock(&arvo->arvo_lock);
|
||||
if (retval)
|
||||
@ -87,7 +87,7 @@ static ssize_t arvo_sysfs_show_key_mask(struct device *dev,
|
||||
int retval;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_receive(usb_dev, ARVO_COMMAND_KEY_MASK,
|
||||
retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_KEY_MASK,
|
||||
&temp_buf, sizeof(struct arvo_key_mask));
|
||||
mutex_unlock(&arvo->arvo_lock);
|
||||
if (retval)
|
||||
@ -115,7 +115,7 @@ static ssize_t arvo_sysfs_set_key_mask(struct device *dev,
|
||||
temp_buf.key_mask = key_mask;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_send(usb_dev, ARVO_COMMAND_KEY_MASK,
|
||||
retval = roccat_common2_send(usb_dev, ARVO_COMMAND_KEY_MASK,
|
||||
&temp_buf, sizeof(struct arvo_key_mask));
|
||||
mutex_unlock(&arvo->arvo_lock);
|
||||
if (retval)
|
||||
@ -130,7 +130,7 @@ static int arvo_get_actual_profile(struct usb_device *usb_dev)
|
||||
struct arvo_actual_profile temp_buf;
|
||||
int retval;
|
||||
|
||||
retval = roccat_common_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
|
||||
retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
|
||||
&temp_buf, sizeof(struct arvo_actual_profile));
|
||||
|
||||
if (retval)
|
||||
@ -170,7 +170,7 @@ static ssize_t arvo_sysfs_set_actual_profile(struct device *dev,
|
||||
temp_buf.actual_profile = profile;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
|
||||
retval = roccat_common2_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
|
||||
&temp_buf, sizeof(struct arvo_actual_profile));
|
||||
if (!retval) {
|
||||
arvo->actual_profile = profile;
|
||||
@ -194,7 +194,7 @@ static ssize_t arvo_sysfs_write(struct file *fp,
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_send(usb_dev, command, buf, real_size);
|
||||
retval = roccat_common2_send(usb_dev, command, buf, real_size);
|
||||
mutex_unlock(&arvo->arvo_lock);
|
||||
|
||||
return (retval ? retval : real_size);
|
||||
@ -217,7 +217,7 @@ static ssize_t arvo_sysfs_read(struct file *fp,
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&arvo->arvo_lock);
|
||||
retval = roccat_common_receive(usb_dev, command, buf, real_size);
|
||||
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
|
||||
mutex_unlock(&arvo->arvo_lock);
|
||||
|
||||
return (retval ? retval : real_size);
|
||||
|
@ -16,12 +16,12 @@
|
||||
#include <linux/module.h>
|
||||
#include "hid-roccat-common.h"
|
||||
|
||||
static inline uint16_t roccat_common_feature_report(uint8_t report_id)
|
||||
static inline uint16_t roccat_common2_feature_report(uint8_t report_id)
|
||||
{
|
||||
return 0x300 | report_id;
|
||||
}
|
||||
|
||||
int roccat_common_receive(struct usb_device *usb_dev, uint report_id,
|
||||
int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
|
||||
void *data, uint size)
|
||||
{
|
||||
char *buf;
|
||||
@ -34,16 +34,16 @@ int roccat_common_receive(struct usb_device *usb_dev, uint report_id,
|
||||
len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
|
||||
HID_REQ_GET_REPORT,
|
||||
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
|
||||
roccat_common_feature_report(report_id),
|
||||
roccat_common2_feature_report(report_id),
|
||||
0, buf, size, USB_CTRL_SET_TIMEOUT);
|
||||
|
||||
memcpy(data, buf, size);
|
||||
kfree(buf);
|
||||
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(roccat_common_receive);
|
||||
EXPORT_SYMBOL_GPL(roccat_common2_receive);
|
||||
|
||||
int roccat_common_send(struct usb_device *usb_dev, uint report_id,
|
||||
int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
|
||||
void const *data, uint size)
|
||||
{
|
||||
char *buf;
|
||||
@ -56,13 +56,71 @@ int roccat_common_send(struct usb_device *usb_dev, uint report_id,
|
||||
len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
HID_REQ_SET_REPORT,
|
||||
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
|
||||
roccat_common_feature_report(report_id),
|
||||
roccat_common2_feature_report(report_id),
|
||||
0, buf, size, USB_CTRL_SET_TIMEOUT);
|
||||
|
||||
kfree(buf);
|
||||
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(roccat_common_send);
|
||||
EXPORT_SYMBOL_GPL(roccat_common2_send);
|
||||
|
||||
enum roccat_common2_control_states {
|
||||
ROCCAT_COMMON_CONTROL_STATUS_OVERLOAD = 0,
|
||||
ROCCAT_COMMON_CONTROL_STATUS_OK = 1,
|
||||
ROCCAT_COMMON_CONTROL_STATUS_INVALID = 2,
|
||||
ROCCAT_COMMON_CONTROL_STATUS_WAIT = 3,
|
||||
};
|
||||
|
||||
static int roccat_common2_receive_control_status(struct usb_device *usb_dev)
|
||||
{
|
||||
int retval;
|
||||
struct roccat_common2_control control;
|
||||
|
||||
do {
|
||||
msleep(50);
|
||||
retval = roccat_common2_receive(usb_dev,
|
||||
ROCCAT_COMMON_COMMAND_CONTROL,
|
||||
&control, sizeof(struct roccat_common2_control));
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
switch (control.value) {
|
||||
case ROCCAT_COMMON_CONTROL_STATUS_OK:
|
||||
return 0;
|
||||
case ROCCAT_COMMON_CONTROL_STATUS_WAIT:
|
||||
msleep(500);
|
||||
continue;
|
||||
case ROCCAT_COMMON_CONTROL_STATUS_INVALID:
|
||||
|
||||
case ROCCAT_COMMON_CONTROL_STATUS_OVERLOAD:
|
||||
/* seems to be critical - replug necessary */
|
||||
return -EINVAL;
|
||||
default:
|
||||
dev_err(&usb_dev->dev,
|
||||
"roccat_common2_receive_control_status: "
|
||||
"unknown response value 0x%x\n",
|
||||
control.value);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
} while (1);
|
||||
}
|
||||
|
||||
int roccat_common2_send_with_status(struct usb_device *usb_dev,
|
||||
uint command, void const *buf, uint size)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = roccat_common2_send(usb_dev, command, buf, size);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
msleep(100);
|
||||
|
||||
return roccat_common2_receive_control_status(usb_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(roccat_common2_send_with_status);
|
||||
|
||||
MODULE_AUTHOR("Stefan Achatz");
|
||||
MODULE_DESCRIPTION("USB Roccat common driver");
|
||||
|
@ -15,9 +15,21 @@
|
||||
#include <linux/usb.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
int roccat_common_receive(struct usb_device *usb_dev, uint report_id,
|
||||
enum roccat_common2_commands {
|
||||
ROCCAT_COMMON_COMMAND_CONTROL = 0x4,
|
||||
};
|
||||
|
||||
struct roccat_common2_control {
|
||||
uint8_t command;
|
||||
uint8_t value;
|
||||
uint8_t request; /* always 0 on requesting write check */
|
||||
} __packed;
|
||||
|
||||
int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
|
||||
void *data, uint size);
|
||||
int roccat_common_send(struct usb_device *usb_dev, uint report_id,
|
||||
int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
|
||||
void const *data, uint size);
|
||||
int roccat_common2_send_with_status(struct usb_device *usb_dev,
|
||||
uint command, void const *buf, uint size);
|
||||
|
||||
#endif
|
||||
|
@ -36,51 +36,7 @@ static void isku_profile_activated(struct isku_device *isku, uint new_profile)
|
||||
static int isku_receive(struct usb_device *usb_dev, uint command,
|
||||
void *buf, uint size)
|
||||
{
|
||||
return roccat_common_receive(usb_dev, command, buf, size);
|
||||
}
|
||||
|
||||
static int isku_receive_control_status(struct usb_device *usb_dev)
|
||||
{
|
||||
int retval;
|
||||
struct isku_control control;
|
||||
|
||||
do {
|
||||
msleep(50);
|
||||
retval = isku_receive(usb_dev, ISKU_COMMAND_CONTROL,
|
||||
&control, sizeof(struct isku_control));
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
switch (control.value) {
|
||||
case ISKU_CONTROL_VALUE_STATUS_OK:
|
||||
return 0;
|
||||
case ISKU_CONTROL_VALUE_STATUS_WAIT:
|
||||
continue;
|
||||
case ISKU_CONTROL_VALUE_STATUS_INVALID:
|
||||
/* seems to be critical - replug necessary */
|
||||
case ISKU_CONTROL_VALUE_STATUS_OVERLOAD:
|
||||
return -EINVAL;
|
||||
default:
|
||||
hid_err(usb_dev, "isku_receive_control_status: "
|
||||
"unknown response value 0x%x\n",
|
||||
control.value);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int isku_send(struct usb_device *usb_dev, uint command,
|
||||
void const *buf, uint size)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = roccat_common_send(usb_dev, command, buf, size);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return isku_receive_control_status(usb_dev);
|
||||
return roccat_common2_receive(usb_dev, command, buf, size);
|
||||
}
|
||||
|
||||
static int isku_get_actual_profile(struct usb_device *usb_dev)
|
||||
@ -100,7 +56,8 @@ static int isku_set_actual_profile(struct usb_device *usb_dev, int new_profile)
|
||||
buf.command = ISKU_COMMAND_ACTUAL_PROFILE;
|
||||
buf.size = sizeof(struct isku_actual_profile);
|
||||
buf.actual_profile = new_profile;
|
||||
return isku_send(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE, &buf,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
ISKU_COMMAND_ACTUAL_PROFILE, &buf,
|
||||
sizeof(struct isku_actual_profile));
|
||||
}
|
||||
|
||||
@ -197,7 +154,8 @@ static ssize_t isku_sysfs_write(struct file *fp, struct kobject *kobj,
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&isku->isku_lock);
|
||||
retval = isku_send(usb_dev, command, (void *)buf, real_size);
|
||||
retval = roccat_common2_send_with_status(usb_dev, command,
|
||||
(void *)buf, real_size);
|
||||
mutex_unlock(&isku->isku_lock);
|
||||
|
||||
return retval ? retval : real_size;
|
||||
|
@ -25,13 +25,6 @@ struct isku_control {
|
||||
uint8_t request;
|
||||
} __packed;
|
||||
|
||||
enum isku_control_values {
|
||||
ISKU_CONTROL_VALUE_STATUS_OVERLOAD = 0,
|
||||
ISKU_CONTROL_VALUE_STATUS_OK = 1,
|
||||
ISKU_CONTROL_VALUE_STATUS_INVALID = 2,
|
||||
ISKU_CONTROL_VALUE_STATUS_WAIT = 3,
|
||||
};
|
||||
|
||||
struct isku_actual_profile {
|
||||
uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */
|
||||
uint8_t size; /* always 3 */
|
||||
|
@ -138,7 +138,7 @@ static int kone_check_write(struct usb_device *usb_dev)
|
||||
return 0;
|
||||
|
||||
/* unknown answer */
|
||||
hid_err(usb_dev, "got retval %d when checking write\n", data);
|
||||
dev_err(&usb_dev->dev, "got retval %d when checking write\n", data);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
@ -503,7 +503,7 @@ static ssize_t kone_sysfs_set_tcu(struct device *dev,
|
||||
|
||||
retval = kone_set_settings(usb_dev, &kone->settings);
|
||||
if (retval) {
|
||||
hid_err(usb_dev, "couldn't set tcu state\n");
|
||||
dev_err(&usb_dev->dev, "couldn't set tcu state\n");
|
||||
/*
|
||||
* try to reread valid settings into buffer overwriting
|
||||
* first error code
|
||||
@ -519,7 +519,7 @@ static ssize_t kone_sysfs_set_tcu(struct device *dev,
|
||||
|
||||
retval = size;
|
||||
exit_no_settings:
|
||||
hid_err(usb_dev, "couldn't read settings\n");
|
||||
dev_err(&usb_dev->dev, "couldn't read settings\n");
|
||||
exit_unlock:
|
||||
mutex_unlock(&kone->kone_lock);
|
||||
return retval;
|
||||
|
@ -39,88 +39,26 @@ static void koneplus_profile_activated(struct koneplus_device *koneplus,
|
||||
static int koneplus_send_control(struct usb_device *usb_dev, uint value,
|
||||
enum koneplus_control_requests request)
|
||||
{
|
||||
struct koneplus_control control;
|
||||
struct roccat_common2_control control;
|
||||
|
||||
if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
|
||||
request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
|
||||
value > 4)
|
||||
return -EINVAL;
|
||||
|
||||
control.command = KONEPLUS_COMMAND_CONTROL;
|
||||
control.command = ROCCAT_COMMON_COMMAND_CONTROL;
|
||||
control.value = value;
|
||||
control.request = request;
|
||||
|
||||
return roccat_common_send(usb_dev, KONEPLUS_COMMAND_CONTROL,
|
||||
&control, sizeof(struct koneplus_control));
|
||||
}
|
||||
|
||||
static int koneplus_receive_control_status(struct usb_device *usb_dev)
|
||||
{
|
||||
int retval;
|
||||
struct koneplus_control control;
|
||||
|
||||
do {
|
||||
retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_CONTROL,
|
||||
&control, sizeof(struct koneplus_control));
|
||||
|
||||
/* check if we get a completely wrong answer */
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OK)
|
||||
return 0;
|
||||
|
||||
/* indicates that hardware needs some more time to complete action */
|
||||
if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_WAIT) {
|
||||
msleep(500); /* windows driver uses 1000 */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* seems to be critical - replug necessary */
|
||||
if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD)
|
||||
return -EINVAL;
|
||||
|
||||
hid_err(usb_dev, "koneplus_receive_control_status: "
|
||||
"unknown response value 0x%x\n", control.value);
|
||||
return -EINVAL;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int koneplus_send(struct usb_device *usb_dev, uint command,
|
||||
void const *buf, uint size)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = roccat_common_send(usb_dev, command, buf, size);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return koneplus_receive_control_status(usb_dev);
|
||||
}
|
||||
|
||||
static int koneplus_select_profile(struct usb_device *usb_dev, uint number,
|
||||
enum koneplus_control_requests request)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = koneplus_send_control(usb_dev, number, request);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
/* allow time to settle things - windows driver uses 500 */
|
||||
msleep(100);
|
||||
|
||||
retval = koneplus_receive_control_status(usb_dev);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return 0;
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
ROCCAT_COMMON_COMMAND_CONTROL,
|
||||
&control, sizeof(struct roccat_common2_control));
|
||||
}
|
||||
|
||||
static int koneplus_get_info(struct usb_device *usb_dev,
|
||||
struct koneplus_info *buf)
|
||||
{
|
||||
return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_INFO,
|
||||
return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_INFO,
|
||||
buf, sizeof(struct koneplus_info));
|
||||
}
|
||||
|
||||
@ -129,19 +67,20 @@ static int koneplus_get_profile_settings(struct usb_device *usb_dev,
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = koneplus_select_profile(usb_dev, number,
|
||||
retval = koneplus_send_control(usb_dev, number,
|
||||
KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
buf, sizeof(struct koneplus_profile_settings));
|
||||
}
|
||||
|
||||
static int koneplus_set_profile_settings(struct usb_device *usb_dev,
|
||||
struct koneplus_profile_settings const *settings)
|
||||
{
|
||||
return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
KONEPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
settings, sizeof(struct koneplus_profile_settings));
|
||||
}
|
||||
|
||||
@ -150,19 +89,20 @@ static int koneplus_get_profile_buttons(struct usb_device *usb_dev,
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = koneplus_select_profile(usb_dev, number,
|
||||
retval = koneplus_send_control(usb_dev, number,
|
||||
KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
buf, sizeof(struct koneplus_profile_buttons));
|
||||
}
|
||||
|
||||
static int koneplus_set_profile_buttons(struct usb_device *usb_dev,
|
||||
struct koneplus_profile_buttons const *buttons)
|
||||
{
|
||||
return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
KONEPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
buttons, sizeof(struct koneplus_profile_buttons));
|
||||
}
|
||||
|
||||
@ -172,7 +112,7 @@ static int koneplus_get_actual_profile(struct usb_device *usb_dev)
|
||||
struct koneplus_actual_profile buf;
|
||||
int retval;
|
||||
|
||||
retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
retval = roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
&buf, sizeof(struct koneplus_actual_profile));
|
||||
|
||||
return retval ? retval : buf.actual_profile;
|
||||
@ -187,7 +127,8 @@ static int koneplus_set_actual_profile(struct usb_device *usb_dev,
|
||||
buf.size = sizeof(struct koneplus_actual_profile);
|
||||
buf.actual_profile = new_profile;
|
||||
|
||||
return koneplus_send(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
KONEPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
&buf, sizeof(struct koneplus_actual_profile));
|
||||
}
|
||||
|
||||
@ -208,7 +149,7 @@ static ssize_t koneplus_sysfs_read(struct file *fp, struct kobject *kobj,
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&koneplus->koneplus_lock);
|
||||
retval = roccat_common_receive(usb_dev, command, buf, real_size);
|
||||
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
|
||||
mutex_unlock(&koneplus->koneplus_lock);
|
||||
|
||||
if (retval)
|
||||
@ -231,7 +172,8 @@ static ssize_t koneplus_sysfs_write(struct file *fp, struct kobject *kobj,
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&koneplus->koneplus_lock);
|
||||
retval = koneplus_send(usb_dev, command, buf, real_size);
|
||||
retval = roccat_common2_send_with_status(usb_dev, command,
|
||||
buf, real_size);
|
||||
mutex_unlock(&koneplus->koneplus_lock);
|
||||
|
||||
if (retval)
|
||||
|
@ -20,32 +20,11 @@ struct koneplus_talk {
|
||||
uint8_t data[14];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* case 1: writes request 80 and reads value 1
|
||||
*
|
||||
*/
|
||||
struct koneplus_control {
|
||||
uint8_t command; /* KONEPLUS_COMMAND_CONTROL */
|
||||
/*
|
||||
* value is profile number in range 0-4 for requesting settings and buttons
|
||||
* 1 if status ok for requesting status
|
||||
*/
|
||||
uint8_t value;
|
||||
uint8_t request;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
enum koneplus_control_requests {
|
||||
KONEPLUS_CONTROL_REQUEST_STATUS = 0x00,
|
||||
KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x80,
|
||||
KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x90,
|
||||
};
|
||||
|
||||
enum koneplus_control_values {
|
||||
KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0,
|
||||
KONEPLUS_CONTROL_REQUEST_STATUS_OK = 1,
|
||||
KONEPLUS_CONTROL_REQUEST_STATUS_WAIT = 3,
|
||||
};
|
||||
|
||||
struct koneplus_actual_profile {
|
||||
uint8_t command; /* KONEPLUS_COMMAND_ACTUAL_PROFILE */
|
||||
uint8_t size; /* always 3 */
|
||||
@ -137,7 +116,6 @@ struct koneplus_tcu_image {
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
enum koneplus_commands {
|
||||
KONEPLUS_COMMAND_CONTROL = 0x4,
|
||||
KONEPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
|
||||
KONEPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
|
||||
KONEPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
|
||||
|
@ -47,69 +47,23 @@ static int kovaplus_send_control(struct usb_device *usb_dev, uint value,
|
||||
enum kovaplus_control_requests request)
|
||||
{
|
||||
int retval;
|
||||
struct kovaplus_control control;
|
||||
struct roccat_common2_control control;
|
||||
|
||||
if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
|
||||
request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
|
||||
value > 4)
|
||||
return -EINVAL;
|
||||
|
||||
control.command = KOVAPLUS_COMMAND_CONTROL;
|
||||
control.command = ROCCAT_COMMON_COMMAND_CONTROL;
|
||||
control.value = value;
|
||||
control.request = request;
|
||||
|
||||
retval = roccat_common_send(usb_dev, KOVAPLUS_COMMAND_CONTROL,
|
||||
&control, sizeof(struct kovaplus_control));
|
||||
retval = roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
|
||||
&control, sizeof(struct roccat_common2_control));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int kovaplus_receive_control_status(struct usb_device *usb_dev)
|
||||
{
|
||||
int retval;
|
||||
struct kovaplus_control control;
|
||||
|
||||
do {
|
||||
retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_CONTROL,
|
||||
&control, sizeof(struct kovaplus_control));
|
||||
|
||||
/* check if we get a completely wrong answer */
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OK)
|
||||
return 0;
|
||||
|
||||
/* indicates that hardware needs some more time to complete action */
|
||||
if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT) {
|
||||
msleep(500); /* windows driver uses 1000 */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* seems to be critical - replug necessary */
|
||||
if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD)
|
||||
return -EINVAL;
|
||||
|
||||
hid_err(usb_dev, "roccat_common_receive_control_status: "
|
||||
"unknown response value 0x%x\n", control.value);
|
||||
return -EINVAL;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int kovaplus_send(struct usb_device *usb_dev, uint command,
|
||||
void const *buf, uint size)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = roccat_common_send(usb_dev, command, buf, size);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
msleep(100);
|
||||
|
||||
return kovaplus_receive_control_status(usb_dev);
|
||||
}
|
||||
|
||||
static int kovaplus_select_profile(struct usb_device *usb_dev, uint number,
|
||||
enum kovaplus_control_requests request)
|
||||
{
|
||||
@ -119,7 +73,7 @@ static int kovaplus_select_profile(struct usb_device *usb_dev, uint number,
|
||||
static int kovaplus_get_info(struct usb_device *usb_dev,
|
||||
struct kovaplus_info *buf)
|
||||
{
|
||||
return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_INFO,
|
||||
return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_INFO,
|
||||
buf, sizeof(struct kovaplus_info));
|
||||
}
|
||||
|
||||
@ -133,14 +87,15 @@ static int kovaplus_get_profile_settings(struct usb_device *usb_dev,
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
buf, sizeof(struct kovaplus_profile_settings));
|
||||
}
|
||||
|
||||
static int kovaplus_set_profile_settings(struct usb_device *usb_dev,
|
||||
struct kovaplus_profile_settings const *settings)
|
||||
{
|
||||
return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
KOVAPLUS_COMMAND_PROFILE_SETTINGS,
|
||||
settings, sizeof(struct kovaplus_profile_settings));
|
||||
}
|
||||
|
||||
@ -154,14 +109,15 @@ static int kovaplus_get_profile_buttons(struct usb_device *usb_dev,
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
buf, sizeof(struct kovaplus_profile_buttons));
|
||||
}
|
||||
|
||||
static int kovaplus_set_profile_buttons(struct usb_device *usb_dev,
|
||||
struct kovaplus_profile_buttons const *buttons)
|
||||
{
|
||||
return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
KOVAPLUS_COMMAND_PROFILE_BUTTONS,
|
||||
buttons, sizeof(struct kovaplus_profile_buttons));
|
||||
}
|
||||
|
||||
@ -171,7 +127,7 @@ static int kovaplus_get_actual_profile(struct usb_device *usb_dev)
|
||||
struct kovaplus_actual_profile buf;
|
||||
int retval;
|
||||
|
||||
retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
retval = roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
&buf, sizeof(struct kovaplus_actual_profile));
|
||||
|
||||
return retval ? retval : buf.actual_profile;
|
||||
@ -186,7 +142,8 @@ static int kovaplus_set_actual_profile(struct usb_device *usb_dev,
|
||||
buf.size = sizeof(struct kovaplus_actual_profile);
|
||||
buf.actual_profile = new_profile;
|
||||
|
||||
return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
KOVAPLUS_COMMAND_ACTUAL_PROFILE,
|
||||
&buf, sizeof(struct kovaplus_actual_profile));
|
||||
}
|
||||
|
||||
|
@ -14,27 +14,13 @@
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct kovaplus_control {
|
||||
uint8_t command; /* KOVAPLUS_COMMAND_CONTROL */
|
||||
uint8_t value;
|
||||
uint8_t request;
|
||||
} __packed;
|
||||
|
||||
enum kovaplus_control_requests {
|
||||
/* read after write; value = 1 */
|
||||
KOVAPLUS_CONTROL_REQUEST_STATUS = 0x0,
|
||||
/* write; value = profile number range 0-4 */
|
||||
KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
|
||||
/* write; value = profile number range 0-4 */
|
||||
KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20,
|
||||
};
|
||||
|
||||
enum kovaplus_control_values {
|
||||
KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, /* supposed */
|
||||
KOVAPLUS_CONTROL_REQUEST_STATUS_OK = 1,
|
||||
KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, /* supposed */
|
||||
};
|
||||
|
||||
struct kovaplus_actual_profile {
|
||||
uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */
|
||||
uint8_t size; /* always 3 */
|
||||
@ -75,7 +61,6 @@ struct kovaplus_a {
|
||||
} __packed;
|
||||
|
||||
enum kovaplus_commands {
|
||||
KOVAPLUS_COMMAND_CONTROL = 0x4,
|
||||
KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
|
||||
KOVAPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
|
||||
KOVAPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
|
||||
|
@ -42,43 +42,19 @@ static void profile_activated(struct pyra_device *pyra,
|
||||
static int pyra_send_control(struct usb_device *usb_dev, int value,
|
||||
enum pyra_control_requests request)
|
||||
{
|
||||
struct pyra_control control;
|
||||
struct roccat_common2_control control;
|
||||
|
||||
if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS ||
|
||||
request == PYRA_CONTROL_REQUEST_PROFILE_BUTTONS) &&
|
||||
(value < 0 || value > 4))
|
||||
return -EINVAL;
|
||||
|
||||
control.command = PYRA_COMMAND_CONTROL;
|
||||
control.command = ROCCAT_COMMON_COMMAND_CONTROL;
|
||||
control.value = value;
|
||||
control.request = request;
|
||||
|
||||
return roccat_common_send(usb_dev, PYRA_COMMAND_CONTROL,
|
||||
&control, sizeof(struct pyra_control));
|
||||
}
|
||||
|
||||
static int pyra_receive_control_status(struct usb_device *usb_dev)
|
||||
{
|
||||
int retval;
|
||||
struct pyra_control control;
|
||||
|
||||
do {
|
||||
msleep(10);
|
||||
retval = roccat_common_receive(usb_dev, PYRA_COMMAND_CONTROL,
|
||||
&control, sizeof(struct pyra_control));
|
||||
|
||||
/* requested too early, try again */
|
||||
} while (retval == -EPROTO);
|
||||
|
||||
if (!retval && control.command == PYRA_COMMAND_CONTROL &&
|
||||
control.request == PYRA_CONTROL_REQUEST_STATUS &&
|
||||
control.value == 1)
|
||||
return 0;
|
||||
else {
|
||||
hid_err(usb_dev, "receive control status: unknown response 0x%x 0x%x\n",
|
||||
control.request, control.value);
|
||||
return retval ? retval : -EINVAL;
|
||||
}
|
||||
return roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
|
||||
&control, sizeof(struct roccat_common2_control));
|
||||
}
|
||||
|
||||
static int pyra_get_profile_settings(struct usb_device *usb_dev,
|
||||
@ -89,7 +65,7 @@ static int pyra_get_profile_settings(struct usb_device *usb_dev,
|
||||
PYRA_CONTROL_REQUEST_PROFILE_SETTINGS);
|
||||
if (retval)
|
||||
return retval;
|
||||
return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS,
|
||||
return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS,
|
||||
buf, sizeof(struct pyra_profile_settings));
|
||||
}
|
||||
|
||||
@ -101,51 +77,44 @@ static int pyra_get_profile_buttons(struct usb_device *usb_dev,
|
||||
PYRA_CONTROL_REQUEST_PROFILE_BUTTONS);
|
||||
if (retval)
|
||||
return retval;
|
||||
return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS,
|
||||
return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS,
|
||||
buf, sizeof(struct pyra_profile_buttons));
|
||||
}
|
||||
|
||||
static int pyra_get_settings(struct usb_device *usb_dev,
|
||||
struct pyra_settings *buf)
|
||||
{
|
||||
return roccat_common_receive(usb_dev, PYRA_COMMAND_SETTINGS,
|
||||
return roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS,
|
||||
buf, sizeof(struct pyra_settings));
|
||||
}
|
||||
|
||||
static int pyra_get_info(struct usb_device *usb_dev, struct pyra_info *buf)
|
||||
{
|
||||
return roccat_common_receive(usb_dev, PYRA_COMMAND_INFO,
|
||||
return roccat_common2_receive(usb_dev, PYRA_COMMAND_INFO,
|
||||
buf, sizeof(struct pyra_info));
|
||||
}
|
||||
|
||||
static int pyra_send(struct usb_device *usb_dev, uint command,
|
||||
void const *buf, uint size)
|
||||
{
|
||||
int retval;
|
||||
retval = roccat_common_send(usb_dev, command, buf, size);
|
||||
if (retval)
|
||||
return retval;
|
||||
return pyra_receive_control_status(usb_dev);
|
||||
}
|
||||
|
||||
static int pyra_set_profile_settings(struct usb_device *usb_dev,
|
||||
struct pyra_profile_settings const *settings)
|
||||
{
|
||||
return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, settings,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
PYRA_COMMAND_PROFILE_SETTINGS, settings,
|
||||
sizeof(struct pyra_profile_settings));
|
||||
}
|
||||
|
||||
static int pyra_set_profile_buttons(struct usb_device *usb_dev,
|
||||
struct pyra_profile_buttons const *buttons)
|
||||
{
|
||||
return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, buttons,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
PYRA_COMMAND_PROFILE_BUTTONS, buttons,
|
||||
sizeof(struct pyra_profile_buttons));
|
||||
}
|
||||
|
||||
static int pyra_set_settings(struct usb_device *usb_dev,
|
||||
struct pyra_settings const *settings)
|
||||
{
|
||||
return pyra_send(usb_dev, PYRA_COMMAND_SETTINGS, settings,
|
||||
return roccat_common2_send_with_status(usb_dev,
|
||||
PYRA_COMMAND_SETTINGS, settings,
|
||||
sizeof(struct pyra_settings));
|
||||
}
|
||||
|
||||
|
@ -20,18 +20,7 @@ struct pyra_b {
|
||||
uint8_t unknown; /* 1 */
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct pyra_control {
|
||||
uint8_t command; /* PYRA_COMMAND_CONTROL */
|
||||
/*
|
||||
* value is profile number for request_settings and request_buttons
|
||||
* 1 if status ok for request_status
|
||||
*/
|
||||
uint8_t value; /* Range 0-4 */
|
||||
uint8_t request;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
enum pyra_control_requests {
|
||||
PYRA_CONTROL_REQUEST_STATUS = 0x00,
|
||||
PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
|
||||
PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20
|
||||
};
|
||||
@ -75,7 +64,6 @@ struct pyra_info {
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
enum pyra_commands {
|
||||
PYRA_COMMAND_CONTROL = 0x4,
|
||||
PYRA_COMMAND_SETTINGS = 0x5,
|
||||
PYRA_COMMAND_PROFILE_SETTINGS = 0x6,
|
||||
PYRA_COMMAND_PROFILE_BUTTONS = 0x7,
|
||||
|
316
drivers/hid/hid-roccat-savu.c
Normal file
316
drivers/hid/hid-roccat-savu.c
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Roccat Savu driver for Linux
|
||||
*
|
||||
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
/* Roccat Savu is a gamer mouse with macro keys that can be configured in
|
||||
* 5 profiles.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hid-roccat.h>
|
||||
#include "hid-ids.h"
|
||||
#include "hid-roccat-common.h"
|
||||
#include "hid-roccat-savu.h"
|
||||
|
||||
static struct class *savu_class;
|
||||
|
||||
static ssize_t savu_sysfs_read(struct file *fp, struct kobject *kobj,
|
||||
char *buf, loff_t off, size_t count,
|
||||
size_t real_size, uint command)
|
||||
{
|
||||
struct device *dev =
|
||||
container_of(kobj, struct device, kobj)->parent->parent;
|
||||
struct savu_device *savu = hid_get_drvdata(dev_get_drvdata(dev));
|
||||
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
|
||||
int retval;
|
||||
|
||||
if (off >= real_size)
|
||||
return 0;
|
||||
|
||||
if (off != 0 || count != real_size)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&savu->savu_lock);
|
||||
retval = roccat_common2_receive(usb_dev, command, buf, real_size);
|
||||
mutex_unlock(&savu->savu_lock);
|
||||
|
||||
return retval ? retval : real_size;
|
||||
}
|
||||
|
||||
static ssize_t savu_sysfs_write(struct file *fp, struct kobject *kobj,
|
||||
void const *buf, loff_t off, size_t count,
|
||||
size_t real_size, uint command)
|
||||
{
|
||||
struct device *dev =
|
||||
container_of(kobj, struct device, kobj)->parent->parent;
|
||||
struct savu_device *savu = hid_get_drvdata(dev_get_drvdata(dev));
|
||||
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
|
||||
int retval;
|
||||
|
||||
if (off != 0 || count != real_size)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&savu->savu_lock);
|
||||
retval = roccat_common2_send_with_status(usb_dev, command,
|
||||
(void *)buf, real_size);
|
||||
mutex_unlock(&savu->savu_lock);
|
||||
|
||||
return retval ? retval : real_size;
|
||||
}
|
||||
|
||||
#define SAVU_SYSFS_W(thingy, THINGY) \
|
||||
static ssize_t savu_sysfs_write_ ## thingy(struct file *fp, \
|
||||
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
|
||||
loff_t off, size_t count) \
|
||||
{ \
|
||||
return savu_sysfs_write(fp, kobj, buf, off, count, \
|
||||
SAVU_SIZE_ ## THINGY, SAVU_COMMAND_ ## THINGY); \
|
||||
}
|
||||
|
||||
#define SAVU_SYSFS_R(thingy, THINGY) \
|
||||
static ssize_t savu_sysfs_read_ ## thingy(struct file *fp, \
|
||||
struct kobject *kobj, struct bin_attribute *attr, char *buf, \
|
||||
loff_t off, size_t count) \
|
||||
{ \
|
||||
return savu_sysfs_read(fp, kobj, buf, off, count, \
|
||||
SAVU_SIZE_ ## THINGY, SAVU_COMMAND_ ## THINGY); \
|
||||
}
|
||||
|
||||
#define SAVU_SYSFS_RW(thingy, THINGY) \
|
||||
SAVU_SYSFS_W(thingy, THINGY) \
|
||||
SAVU_SYSFS_R(thingy, THINGY)
|
||||
|
||||
#define SAVU_BIN_ATTRIBUTE_RW(thingy, THINGY) \
|
||||
{ \
|
||||
.attr = { .name = #thingy, .mode = 0660 }, \
|
||||
.size = SAVU_SIZE_ ## THINGY, \
|
||||
.read = savu_sysfs_read_ ## thingy, \
|
||||
.write = savu_sysfs_write_ ## thingy \
|
||||
}
|
||||
|
||||
#define SAVU_BIN_ATTRIBUTE_R(thingy, THINGY) \
|
||||
{ \
|
||||
.attr = { .name = #thingy, .mode = 0440 }, \
|
||||
.size = SAVU_SIZE_ ## THINGY, \
|
||||
.read = savu_sysfs_read_ ## thingy, \
|
||||
}
|
||||
|
||||
#define SAVU_BIN_ATTRIBUTE_W(thingy, THINGY) \
|
||||
{ \
|
||||
.attr = { .name = #thingy, .mode = 0220 }, \
|
||||
.size = SAVU_SIZE_ ## THINGY, \
|
||||
.write = savu_sysfs_write_ ## thingy \
|
||||
}
|
||||
|
||||
SAVU_SYSFS_W(control, CONTROL)
|
||||
SAVU_SYSFS_RW(profile, PROFILE)
|
||||
SAVU_SYSFS_RW(general, GENERAL)
|
||||
SAVU_SYSFS_RW(buttons, BUTTONS)
|
||||
SAVU_SYSFS_RW(macro, MACRO)
|
||||
SAVU_SYSFS_R(info, INFO)
|
||||
SAVU_SYSFS_RW(sensor, SENSOR)
|
||||
|
||||
static struct bin_attribute savu_bin_attributes[] = {
|
||||
SAVU_BIN_ATTRIBUTE_W(control, CONTROL),
|
||||
SAVU_BIN_ATTRIBUTE_RW(profile, PROFILE),
|
||||
SAVU_BIN_ATTRIBUTE_RW(general, GENERAL),
|
||||
SAVU_BIN_ATTRIBUTE_RW(buttons, BUTTONS),
|
||||
SAVU_BIN_ATTRIBUTE_RW(macro, MACRO),
|
||||
SAVU_BIN_ATTRIBUTE_R(info, INFO),
|
||||
SAVU_BIN_ATTRIBUTE_RW(sensor, SENSOR),
|
||||
__ATTR_NULL
|
||||
};
|
||||
|
||||
static int savu_init_savu_device_struct(struct usb_device *usb_dev,
|
||||
struct savu_device *savu)
|
||||
{
|
||||
mutex_init(&savu->savu_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int savu_init_specials(struct hid_device *hdev)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct usb_device *usb_dev = interface_to_usbdev(intf);
|
||||
struct savu_device *savu;
|
||||
int retval;
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
!= USB_INTERFACE_PROTOCOL_MOUSE) {
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
savu = kzalloc(sizeof(*savu), GFP_KERNEL);
|
||||
if (!savu) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hid_set_drvdata(hdev, savu);
|
||||
|
||||
retval = savu_init_savu_device_struct(usb_dev, savu);
|
||||
if (retval) {
|
||||
hid_err(hdev, "couldn't init struct savu_device\n");
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
retval = roccat_connect(savu_class, hdev,
|
||||
sizeof(struct savu_roccat_report));
|
||||
if (retval < 0) {
|
||||
hid_err(hdev, "couldn't init char dev\n");
|
||||
} else {
|
||||
savu->chrdev_minor = retval;
|
||||
savu->roccat_claimed = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
exit_free:
|
||||
kfree(savu);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void savu_remove_specials(struct hid_device *hdev)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct savu_device *savu;
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
!= USB_INTERFACE_PROTOCOL_MOUSE)
|
||||
return;
|
||||
|
||||
savu = hid_get_drvdata(hdev);
|
||||
if (savu->roccat_claimed)
|
||||
roccat_disconnect(savu->chrdev_minor);
|
||||
kfree(savu);
|
||||
}
|
||||
|
||||
static int savu_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = hid_parse(hdev);
|
||||
if (retval) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (retval) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
retval = savu_init_specials(hdev);
|
||||
if (retval) {
|
||||
hid_err(hdev, "couldn't install mouse\n");
|
||||
goto exit_stop;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_stop:
|
||||
hid_hw_stop(hdev);
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void savu_remove(struct hid_device *hdev)
|
||||
{
|
||||
savu_remove_specials(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
static void savu_report_to_chrdev(struct savu_device const *savu,
|
||||
u8 const *data)
|
||||
{
|
||||
struct savu_roccat_report roccat_report;
|
||||
struct savu_mouse_report_special const *special_report;
|
||||
|
||||
if (data[0] != SAVU_MOUSE_REPORT_NUMBER_SPECIAL)
|
||||
return;
|
||||
|
||||
special_report = (struct savu_mouse_report_special const *)data;
|
||||
|
||||
roccat_report.type = special_report->type;
|
||||
roccat_report.data[0] = special_report->data[0];
|
||||
roccat_report.data[1] = special_report->data[1];
|
||||
roccat_report_event(savu->chrdev_minor,
|
||||
(uint8_t const *)&roccat_report);
|
||||
}
|
||||
|
||||
static int savu_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct savu_device *savu = hid_get_drvdata(hdev);
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
!= USB_INTERFACE_PROTOCOL_MOUSE)
|
||||
return 0;
|
||||
|
||||
if (savu == NULL)
|
||||
return 0;
|
||||
|
||||
if (savu->roccat_claimed)
|
||||
savu_report_to_chrdev(savu, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id savu_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, savu_devices);
|
||||
|
||||
static struct hid_driver savu_driver = {
|
||||
.name = "savu",
|
||||
.id_table = savu_devices,
|
||||
.probe = savu_probe,
|
||||
.remove = savu_remove,
|
||||
.raw_event = savu_raw_event
|
||||
};
|
||||
|
||||
static int __init savu_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
savu_class = class_create(THIS_MODULE, "savu");
|
||||
if (IS_ERR(savu_class))
|
||||
return PTR_ERR(savu_class);
|
||||
savu_class->dev_bin_attrs = savu_bin_attributes;
|
||||
|
||||
retval = hid_register_driver(&savu_driver);
|
||||
if (retval)
|
||||
class_destroy(savu_class);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void __exit savu_exit(void)
|
||||
{
|
||||
hid_unregister_driver(&savu_driver);
|
||||
class_destroy(savu_class);
|
||||
}
|
||||
|
||||
module_init(savu_init);
|
||||
module_exit(savu_exit);
|
||||
|
||||
MODULE_AUTHOR("Stefan Achatz");
|
||||
MODULE_DESCRIPTION("USB Roccat Savu driver");
|
||||
MODULE_LICENSE("GPL v2");
|
87
drivers/hid/hid-roccat-savu.h
Normal file
87
drivers/hid/hid-roccat-savu.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef __HID_ROCCAT_SAVU_H
|
||||
#define __HID_ROCCAT_SAVU_H
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
enum {
|
||||
SAVU_SIZE_CONTROL = 0x03,
|
||||
SAVU_SIZE_PROFILE = 0x03,
|
||||
SAVU_SIZE_GENERAL = 0x10,
|
||||
SAVU_SIZE_BUTTONS = 0x2f,
|
||||
SAVU_SIZE_MACRO = 0x0823,
|
||||
SAVU_SIZE_INFO = 0x08,
|
||||
SAVU_SIZE_SENSOR = 0x04,
|
||||
};
|
||||
|
||||
enum savu_control_requests {
|
||||
SAVU_CONTROL_REQUEST_GENERAL = 0x80,
|
||||
SAVU_CONTROL_REQUEST_BUTTONS = 0x90,
|
||||
};
|
||||
|
||||
enum savu_commands {
|
||||
SAVU_COMMAND_CONTROL = 0x4,
|
||||
SAVU_COMMAND_PROFILE = 0x5,
|
||||
SAVU_COMMAND_GENERAL = 0x6,
|
||||
SAVU_COMMAND_BUTTONS = 0x7,
|
||||
SAVU_COMMAND_MACRO = 0x8,
|
||||
SAVU_COMMAND_INFO = 0x9,
|
||||
SAVU_COMMAND_SENSOR = 0xc,
|
||||
};
|
||||
|
||||
struct savu_mouse_report_special {
|
||||
uint8_t report_number; /* always 3 */
|
||||
uint8_t zero;
|
||||
uint8_t type;
|
||||
uint8_t data[2];
|
||||
} __packed;
|
||||
|
||||
enum {
|
||||
SAVU_MOUSE_REPORT_NUMBER_SPECIAL = 3,
|
||||
};
|
||||
|
||||
enum savu_mouse_report_button_types {
|
||||
/* data1 = new profile range 1-5 */
|
||||
SAVU_MOUSE_REPORT_BUTTON_TYPE_PROFILE = 0x20,
|
||||
|
||||
/* data1 = button number range 1-24; data2 = action */
|
||||
SAVU_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
|
||||
|
||||
/* data1 = button number range 1-24; data2 = action */
|
||||
SAVU_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
|
||||
|
||||
/* data1 = setting number range 1-5 */
|
||||
SAVU_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
|
||||
|
||||
/* data1 and data2 = range 0x1-0xb */
|
||||
SAVU_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
|
||||
|
||||
/* data1 = 22 = next track...
|
||||
* data2 = action
|
||||
*/
|
||||
SAVU_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
|
||||
};
|
||||
|
||||
struct savu_roccat_report {
|
||||
uint8_t type;
|
||||
uint8_t data[2];
|
||||
} __packed;
|
||||
|
||||
struct savu_device {
|
||||
int roccat_claimed;
|
||||
int chrdev_minor;
|
||||
|
||||
struct mutex savu_lock;
|
||||
};
|
||||
|
||||
#endif
|
@ -77,7 +77,7 @@ static __u16 wiiext_keymap[] = {
|
||||
BTN_TR, /* WIIEXT_KEY_RT */
|
||||
};
|
||||
|
||||
/* diable all extensions */
|
||||
/* disable all extensions */
|
||||
static void ext_disable(struct wiimote_ext *ext)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
@ -96,6 +96,7 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,
|
||||
}
|
||||
|
||||
kfree(list->buffer[list->tail].value);
|
||||
list->buffer[list->tail].value = NULL;
|
||||
list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
|
||||
}
|
||||
out:
|
||||
@ -300,6 +301,7 @@ static int hidraw_release(struct inode * inode, struct file * file)
|
||||
struct hidraw *dev;
|
||||
struct hidraw_list *list = file->private_data;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
mutex_lock(&minors_lock);
|
||||
if (!hidraw_table[minor]) {
|
||||
@ -317,6 +319,9 @@ static int hidraw_release(struct inode * inode, struct file * file)
|
||||
kfree(list->hidraw);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < HIDRAW_BUFFER_SIZE; ++i)
|
||||
kfree(list->buffer[i].value);
|
||||
kfree(list);
|
||||
ret = 0;
|
||||
unlock:
|
||||
@ -446,12 +451,17 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
|
||||
int ret = 0;
|
||||
|
||||
list_for_each_entry(list, &dev->list, node) {
|
||||
int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
|
||||
|
||||
if (new_head == list->tail)
|
||||
continue;
|
||||
|
||||
if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
list->buffer[list->head].len = len;
|
||||
list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
|
||||
list->head = new_head;
|
||||
kill_fasync(&list->fasync, SIGIO, POLL_IN);
|
||||
}
|
||||
|
||||
|
572
drivers/hid/uhid.c
Normal file
572
drivers/hid/uhid.c
Normal file
@ -0,0 +1,572 @@
|
||||
/*
|
||||
* User-space I/O driver support for HID subsystem
|
||||
* Copyright (c) 2012 David Herrmann
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uhid.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#define UHID_NAME "uhid"
|
||||
#define UHID_BUFSIZE 32
|
||||
|
||||
struct uhid_device {
|
||||
struct mutex devlock;
|
||||
bool running;
|
||||
|
||||
__u8 *rd_data;
|
||||
uint rd_size;
|
||||
|
||||
struct hid_device *hid;
|
||||
struct uhid_event input_buf;
|
||||
|
||||
wait_queue_head_t waitq;
|
||||
spinlock_t qlock;
|
||||
__u8 head;
|
||||
__u8 tail;
|
||||
struct uhid_event *outq[UHID_BUFSIZE];
|
||||
|
||||
struct mutex report_lock;
|
||||
wait_queue_head_t report_wait;
|
||||
atomic_t report_done;
|
||||
atomic_t report_id;
|
||||
struct uhid_event report_buf;
|
||||
};
|
||||
|
||||
static struct miscdevice uhid_misc;
|
||||
|
||||
static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
|
||||
{
|
||||
__u8 newhead;
|
||||
|
||||
newhead = (uhid->head + 1) % UHID_BUFSIZE;
|
||||
|
||||
if (newhead != uhid->tail) {
|
||||
uhid->outq[uhid->head] = ev;
|
||||
uhid->head = newhead;
|
||||
wake_up_interruptible(&uhid->waitq);
|
||||
} else {
|
||||
hid_warn(uhid->hid, "Output queue is full\n");
|
||||
kfree(ev);
|
||||
}
|
||||
}
|
||||
|
||||
static int uhid_queue_event(struct uhid_device *uhid, __u32 event)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct uhid_event *ev;
|
||||
|
||||
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
|
||||
if (!ev)
|
||||
return -ENOMEM;
|
||||
|
||||
ev->type = event;
|
||||
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
uhid_queue(uhid, ev);
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_hid_start(struct hid_device *hid)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
|
||||
return uhid_queue_event(uhid, UHID_START);
|
||||
}
|
||||
|
||||
static void uhid_hid_stop(struct hid_device *hid)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
|
||||
hid->claimed = 0;
|
||||
uhid_queue_event(uhid, UHID_STOP);
|
||||
}
|
||||
|
||||
static int uhid_hid_open(struct hid_device *hid)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
|
||||
return uhid_queue_event(uhid, UHID_OPEN);
|
||||
}
|
||||
|
||||
static void uhid_hid_close(struct hid_device *hid)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
|
||||
uhid_queue_event(uhid, UHID_CLOSE);
|
||||
}
|
||||
|
||||
static int uhid_hid_input(struct input_dev *input, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
struct hid_device *hid = input_get_drvdata(input);
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
unsigned long flags;
|
||||
struct uhid_event *ev;
|
||||
|
||||
ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
|
||||
if (!ev)
|
||||
return -ENOMEM;
|
||||
|
||||
ev->type = UHID_OUTPUT_EV;
|
||||
ev->u.output_ev.type = type;
|
||||
ev->u.output_ev.code = code;
|
||||
ev->u.output_ev.value = value;
|
||||
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
uhid_queue(uhid, ev);
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_hid_parse(struct hid_device *hid)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
|
||||
return hid_parse_report(hid, uhid->rd_data, uhid->rd_size);
|
||||
}
|
||||
|
||||
static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum,
|
||||
__u8 *buf, size_t count, unsigned char rtype)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
__u8 report_type;
|
||||
struct uhid_event *ev;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
size_t uninitialized_var(len);
|
||||
struct uhid_feature_answer_req *req;
|
||||
|
||||
if (!uhid->running)
|
||||
return -EIO;
|
||||
|
||||
switch (rtype) {
|
||||
case HID_FEATURE_REPORT:
|
||||
report_type = UHID_FEATURE_REPORT;
|
||||
break;
|
||||
case HID_OUTPUT_REPORT:
|
||||
report_type = UHID_OUTPUT_REPORT;
|
||||
break;
|
||||
case HID_INPUT_REPORT:
|
||||
report_type = UHID_INPUT_REPORT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = mutex_lock_interruptible(&uhid->report_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
|
||||
if (!ev) {
|
||||
ret = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
ev->type = UHID_FEATURE;
|
||||
ev->u.feature.id = atomic_inc_return(&uhid->report_id);
|
||||
ev->u.feature.rnum = rnum;
|
||||
ev->u.feature.rtype = report_type;
|
||||
|
||||
atomic_set(&uhid->report_done, 0);
|
||||
uhid_queue(uhid, ev);
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
|
||||
ret = wait_event_interruptible_timeout(uhid->report_wait,
|
||||
atomic_read(&uhid->report_done), 5 * HZ);
|
||||
|
||||
/*
|
||||
* Make sure "uhid->running" is cleared on shutdown before
|
||||
* "uhid->report_done" is set.
|
||||
*/
|
||||
smp_rmb();
|
||||
if (!ret || !uhid->running) {
|
||||
ret = -EIO;
|
||||
} else if (ret < 0) {
|
||||
ret = -ERESTARTSYS;
|
||||
} else {
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
req = &uhid->report_buf.u.feature_answer;
|
||||
|
||||
if (req->err) {
|
||||
ret = -EIO;
|
||||
} else {
|
||||
ret = 0;
|
||||
len = min(count,
|
||||
min_t(size_t, req->size, UHID_DATA_MAX));
|
||||
memcpy(buf, req->data, len);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
}
|
||||
|
||||
atomic_set(&uhid->report_done, 1);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&uhid->report_lock);
|
||||
return ret ? ret : len;
|
||||
}
|
||||
|
||||
static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,
|
||||
unsigned char report_type)
|
||||
{
|
||||
struct uhid_device *uhid = hid->driver_data;
|
||||
__u8 rtype;
|
||||
unsigned long flags;
|
||||
struct uhid_event *ev;
|
||||
|
||||
switch (report_type) {
|
||||
case HID_FEATURE_REPORT:
|
||||
rtype = UHID_FEATURE_REPORT;
|
||||
break;
|
||||
case HID_OUTPUT_REPORT:
|
||||
rtype = UHID_OUTPUT_REPORT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (count < 1 || count > UHID_DATA_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
|
||||
if (!ev)
|
||||
return -ENOMEM;
|
||||
|
||||
ev->type = UHID_OUTPUT;
|
||||
ev->u.output.size = count;
|
||||
ev->u.output.rtype = rtype;
|
||||
memcpy(ev->u.output.data, buf, count);
|
||||
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
uhid_queue(uhid, ev);
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct hid_ll_driver uhid_hid_driver = {
|
||||
.start = uhid_hid_start,
|
||||
.stop = uhid_hid_stop,
|
||||
.open = uhid_hid_open,
|
||||
.close = uhid_hid_close,
|
||||
.hidinput_input_event = uhid_hid_input,
|
||||
.parse = uhid_hid_parse,
|
||||
};
|
||||
|
||||
static int uhid_dev_create(struct uhid_device *uhid,
|
||||
const struct uhid_event *ev)
|
||||
{
|
||||
struct hid_device *hid;
|
||||
int ret;
|
||||
|
||||
if (uhid->running)
|
||||
return -EALREADY;
|
||||
|
||||
uhid->rd_size = ev->u.create.rd_size;
|
||||
if (uhid->rd_size <= 0 || uhid->rd_size > HID_MAX_DESCRIPTOR_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
uhid->rd_data = kmalloc(uhid->rd_size, GFP_KERNEL);
|
||||
if (!uhid->rd_data)
|
||||
return -ENOMEM;
|
||||
|
||||
if (copy_from_user(uhid->rd_data, ev->u.create.rd_data,
|
||||
uhid->rd_size)) {
|
||||
ret = -EFAULT;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
hid = hid_allocate_device();
|
||||
if (IS_ERR(hid)) {
|
||||
ret = PTR_ERR(hid);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
strncpy(hid->name, ev->u.create.name, 127);
|
||||
hid->name[127] = 0;
|
||||
strncpy(hid->phys, ev->u.create.phys, 63);
|
||||
hid->phys[63] = 0;
|
||||
strncpy(hid->uniq, ev->u.create.uniq, 63);
|
||||
hid->uniq[63] = 0;
|
||||
|
||||
hid->ll_driver = &uhid_hid_driver;
|
||||
hid->hid_get_raw_report = uhid_hid_get_raw;
|
||||
hid->hid_output_raw_report = uhid_hid_output_raw;
|
||||
hid->bus = ev->u.create.bus;
|
||||
hid->vendor = ev->u.create.vendor;
|
||||
hid->product = ev->u.create.product;
|
||||
hid->version = ev->u.create.version;
|
||||
hid->country = ev->u.create.country;
|
||||
hid->driver_data = uhid;
|
||||
hid->dev.parent = uhid_misc.this_device;
|
||||
|
||||
uhid->hid = hid;
|
||||
uhid->running = true;
|
||||
|
||||
ret = hid_add_device(hid);
|
||||
if (ret) {
|
||||
hid_err(hid, "Cannot register HID device\n");
|
||||
goto err_hid;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_hid:
|
||||
hid_destroy_device(hid);
|
||||
uhid->hid = NULL;
|
||||
uhid->running = false;
|
||||
err_free:
|
||||
kfree(uhid->rd_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uhid_dev_destroy(struct uhid_device *uhid)
|
||||
{
|
||||
if (!uhid->running)
|
||||
return -EINVAL;
|
||||
|
||||
/* clear "running" before setting "report_done" */
|
||||
uhid->running = false;
|
||||
smp_wmb();
|
||||
atomic_set(&uhid->report_done, 1);
|
||||
wake_up_interruptible(&uhid->report_wait);
|
||||
|
||||
hid_destroy_device(uhid->hid);
|
||||
kfree(uhid->rd_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
|
||||
{
|
||||
if (!uhid->running)
|
||||
return -EINVAL;
|
||||
|
||||
hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data,
|
||||
min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_dev_feature_answer(struct uhid_device *uhid,
|
||||
struct uhid_event *ev)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!uhid->running)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
|
||||
/* id for old report; drop it silently */
|
||||
if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id)
|
||||
goto unlock;
|
||||
if (atomic_read(&uhid->report_done))
|
||||
goto unlock;
|
||||
|
||||
memcpy(&uhid->report_buf, ev, sizeof(*ev));
|
||||
atomic_set(&uhid->report_done, 1);
|
||||
wake_up_interruptible(&uhid->report_wait);
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_char_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct uhid_device *uhid;
|
||||
|
||||
uhid = kzalloc(sizeof(*uhid), GFP_KERNEL);
|
||||
if (!uhid)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&uhid->devlock);
|
||||
mutex_init(&uhid->report_lock);
|
||||
spin_lock_init(&uhid->qlock);
|
||||
init_waitqueue_head(&uhid->waitq);
|
||||
init_waitqueue_head(&uhid->report_wait);
|
||||
uhid->running = false;
|
||||
atomic_set(&uhid->report_done, 1);
|
||||
|
||||
file->private_data = uhid;
|
||||
nonseekable_open(inode, file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_char_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct uhid_device *uhid = file->private_data;
|
||||
unsigned int i;
|
||||
|
||||
uhid_dev_destroy(uhid);
|
||||
|
||||
for (i = 0; i < UHID_BUFSIZE; ++i)
|
||||
kfree(uhid->outq[i]);
|
||||
|
||||
kfree(uhid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t uhid_char_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct uhid_device *uhid = file->private_data;
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
size_t len;
|
||||
|
||||
/* they need at least the "type" member of uhid_event */
|
||||
if (count < sizeof(__u32))
|
||||
return -EINVAL;
|
||||
|
||||
try_again:
|
||||
if (file->f_flags & O_NONBLOCK) {
|
||||
if (uhid->head == uhid->tail)
|
||||
return -EAGAIN;
|
||||
} else {
|
||||
ret = wait_event_interruptible(uhid->waitq,
|
||||
uhid->head != uhid->tail);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mutex_lock_interruptible(&uhid->devlock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (uhid->head == uhid->tail) {
|
||||
mutex_unlock(&uhid->devlock);
|
||||
goto try_again;
|
||||
} else {
|
||||
len = min(count, sizeof(**uhid->outq));
|
||||
if (copy_to_user(buffer, uhid->outq[uhid->tail], len)) {
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
kfree(uhid->outq[uhid->tail]);
|
||||
uhid->outq[uhid->tail] = NULL;
|
||||
|
||||
spin_lock_irqsave(&uhid->qlock, flags);
|
||||
uhid->tail = (uhid->tail + 1) % UHID_BUFSIZE;
|
||||
spin_unlock_irqrestore(&uhid->qlock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&uhid->devlock);
|
||||
return ret ? ret : len;
|
||||
}
|
||||
|
||||
static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct uhid_device *uhid = file->private_data;
|
||||
int ret;
|
||||
size_t len;
|
||||
|
||||
/* we need at least the "type" member of uhid_event */
|
||||
if (count < sizeof(__u32))
|
||||
return -EINVAL;
|
||||
|
||||
ret = mutex_lock_interruptible(&uhid->devlock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(&uhid->input_buf, 0, sizeof(uhid->input_buf));
|
||||
len = min(count, sizeof(uhid->input_buf));
|
||||
if (copy_from_user(&uhid->input_buf, buffer, len)) {
|
||||
ret = -EFAULT;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
switch (uhid->input_buf.type) {
|
||||
case UHID_CREATE:
|
||||
ret = uhid_dev_create(uhid, &uhid->input_buf);
|
||||
break;
|
||||
case UHID_DESTROY:
|
||||
ret = uhid_dev_destroy(uhid);
|
||||
break;
|
||||
case UHID_INPUT:
|
||||
ret = uhid_dev_input(uhid, &uhid->input_buf);
|
||||
break;
|
||||
case UHID_FEATURE_ANSWER:
|
||||
ret = uhid_dev_feature_answer(uhid, &uhid->input_buf);
|
||||
break;
|
||||
default:
|
||||
ret = -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&uhid->devlock);
|
||||
|
||||
/* return "count" not "len" to not confuse the caller */
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static unsigned int uhid_char_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct uhid_device *uhid = file->private_data;
|
||||
|
||||
poll_wait(file, &uhid->waitq, wait);
|
||||
|
||||
if (uhid->head != uhid->tail)
|
||||
return POLLIN | POLLRDNORM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations uhid_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = uhid_char_open,
|
||||
.release = uhid_char_release,
|
||||
.read = uhid_char_read,
|
||||
.write = uhid_char_write,
|
||||
.poll = uhid_char_poll,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static struct miscdevice uhid_misc = {
|
||||
.fops = &uhid_fops,
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = UHID_NAME,
|
||||
};
|
||||
|
||||
static int __init uhid_init(void)
|
||||
{
|
||||
return misc_register(&uhid_misc);
|
||||
}
|
||||
|
||||
static void __exit uhid_exit(void)
|
||||
{
|
||||
misc_deregister(&uhid_misc);
|
||||
}
|
||||
|
||||
module_init(uhid_init);
|
||||
module_exit(uhid_exit);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
|
||||
MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem");
|
@ -84,7 +84,7 @@ static int hid_start_in(struct hid_device *hid)
|
||||
spin_lock_irqsave(&usbhid->lock, flags);
|
||||
if (hid->open > 0 &&
|
||||
!test_bit(HID_DISCONNECTED, &usbhid->iofl) &&
|
||||
!test_bit(HID_REPORTED_IDLE, &usbhid->iofl) &&
|
||||
!test_bit(HID_SUSPENDED, &usbhid->iofl) &&
|
||||
!test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) {
|
||||
rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC);
|
||||
if (rc != 0) {
|
||||
@ -207,15 +207,27 @@ static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
|
||||
int kicked;
|
||||
int r;
|
||||
|
||||
if (!hid)
|
||||
if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) ||
|
||||
test_bit(HID_SUSPENDED, &usbhid->iofl))
|
||||
return 0;
|
||||
|
||||
if ((kicked = (usbhid->outhead != usbhid->outtail))) {
|
||||
hid_dbg(hid, "Kicking head %d tail %d", usbhid->outhead, usbhid->outtail);
|
||||
|
||||
/* Try to wake up from autosuspend... */
|
||||
r = usb_autopm_get_interface_async(usbhid->intf);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/*
|
||||
* If still suspended, don't submit. Submission will
|
||||
* occur if/when resume drains the queue.
|
||||
*/
|
||||
if (test_bit(HID_SUSPENDED, &usbhid->iofl)) {
|
||||
usb_autopm_put_interface_no_suspend(usbhid->intf);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Asynchronously flush queue. */
|
||||
set_bit(HID_OUT_RUNNING, &usbhid->iofl);
|
||||
if (hid_submit_out(hid)) {
|
||||
@ -234,15 +246,27 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
|
||||
int r;
|
||||
|
||||
WARN_ON(hid == NULL);
|
||||
if (!hid)
|
||||
if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) ||
|
||||
test_bit(HID_SUSPENDED, &usbhid->iofl))
|
||||
return 0;
|
||||
|
||||
if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
|
||||
hid_dbg(hid, "Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail);
|
||||
|
||||
/* Try to wake up from autosuspend... */
|
||||
r = usb_autopm_get_interface_async(usbhid->intf);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/*
|
||||
* If still suspended, don't submit. Submission will
|
||||
* occur if/when resume drains the queue.
|
||||
*/
|
||||
if (test_bit(HID_SUSPENDED, &usbhid->iofl)) {
|
||||
usb_autopm_put_interface_no_suspend(usbhid->intf);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Asynchronously flush queue. */
|
||||
set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
|
||||
if (hid_submit_ctrl(hid)) {
|
||||
@ -331,9 +355,12 @@ static int hid_submit_out(struct hid_device *hid)
|
||||
usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) +
|
||||
1 + (report->id > 0);
|
||||
usbhid->urbout->dev = hid_to_usb_dev(hid);
|
||||
memcpy(usbhid->outbuf, raw_report,
|
||||
usbhid->urbout->transfer_buffer_length);
|
||||
kfree(raw_report);
|
||||
if (raw_report) {
|
||||
memcpy(usbhid->outbuf, raw_report,
|
||||
usbhid->urbout->transfer_buffer_length);
|
||||
kfree(raw_report);
|
||||
usbhid->out[usbhid->outtail].raw_report = NULL;
|
||||
}
|
||||
|
||||
dbg_hid("submitting out urb\n");
|
||||
|
||||
@ -362,8 +389,11 @@ static int hid_submit_ctrl(struct hid_device *hid)
|
||||
if (dir == USB_DIR_OUT) {
|
||||
usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
|
||||
usbhid->urbctrl->transfer_buffer_length = len;
|
||||
memcpy(usbhid->ctrlbuf, raw_report, len);
|
||||
kfree(raw_report);
|
||||
if (raw_report) {
|
||||
memcpy(usbhid->ctrlbuf, raw_report, len);
|
||||
kfree(raw_report);
|
||||
usbhid->ctrl[usbhid->ctrltail].raw_report = NULL;
|
||||
}
|
||||
} else {
|
||||
int maxpacket, padlen;
|
||||
|
||||
@ -407,16 +437,6 @@ static int hid_submit_ctrl(struct hid_device *hid)
|
||||
* Output interrupt completion handler.
|
||||
*/
|
||||
|
||||
static int irq_out_pump_restart(struct hid_device *hid)
|
||||
{
|
||||
struct usbhid_device *usbhid = hid->driver_data;
|
||||
|
||||
if (usbhid->outhead != usbhid->outtail)
|
||||
return hid_submit_out(hid);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void hid_irq_out(struct urb *urb)
|
||||
{
|
||||
struct hid_device *hid = urb->context;
|
||||
@ -441,15 +461,17 @@ static void hid_irq_out(struct urb *urb)
|
||||
|
||||
spin_lock_irqsave(&usbhid->lock, flags);
|
||||
|
||||
if (unplug)
|
||||
if (unplug) {
|
||||
usbhid->outtail = usbhid->outhead;
|
||||
else
|
||||
} else {
|
||||
usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
|
||||
|
||||
if (!irq_out_pump_restart(hid)) {
|
||||
/* Successfully submitted next urb in queue */
|
||||
spin_unlock_irqrestore(&usbhid->lock, flags);
|
||||
return;
|
||||
if (usbhid->outhead != usbhid->outtail &&
|
||||
hid_submit_out(hid) == 0) {
|
||||
/* Successfully submitted next urb in queue */
|
||||
spin_unlock_irqrestore(&usbhid->lock, flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
|
||||
@ -461,15 +483,6 @@ static void hid_irq_out(struct urb *urb)
|
||||
/*
|
||||
* Control pipe completion handler.
|
||||
*/
|
||||
static int ctrl_pump_restart(struct hid_device *hid)
|
||||
{
|
||||
struct usbhid_device *usbhid = hid->driver_data;
|
||||
|
||||
if (usbhid->ctrlhead != usbhid->ctrltail)
|
||||
return hid_submit_ctrl(hid);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void hid_ctrl(struct urb *urb)
|
||||
{
|
||||
@ -498,15 +511,17 @@ static void hid_ctrl(struct urb *urb)
|
||||
hid_warn(urb->dev, "ctrl urb status %d received\n", status);
|
||||
}
|
||||
|
||||
if (unplug)
|
||||
if (unplug) {
|
||||
usbhid->ctrltail = usbhid->ctrlhead;
|
||||
else
|
||||
} else {
|
||||
usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
|
||||
|
||||
if (!ctrl_pump_restart(hid)) {
|
||||
/* Successfully submitted next urb in queue */
|
||||
spin_unlock(&usbhid->lock);
|
||||
return;
|
||||
if (usbhid->ctrlhead != usbhid->ctrltail &&
|
||||
hid_submit_ctrl(hid) == 0) {
|
||||
/* Successfully submitted next urb in queue */
|
||||
spin_unlock(&usbhid->lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
|
||||
@ -540,49 +555,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
|
||||
usbhid->out[usbhid->outhead].report = report;
|
||||
usbhid->outhead = head;
|
||||
|
||||
/* Try to awake from autosuspend... */
|
||||
if (usb_autopm_get_interface_async(usbhid->intf) < 0)
|
||||
return;
|
||||
/* If the queue isn't running, restart it */
|
||||
if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
|
||||
usbhid_restart_out_queue(usbhid);
|
||||
|
||||
/*
|
||||
* But if still suspended, leave urb enqueued, don't submit.
|
||||
* Submission will occur if/when resume() drains the queue.
|
||||
*/
|
||||
if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl))
|
||||
return;
|
||||
/* Otherwise see if an earlier request has timed out */
|
||||
} else if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
|
||||
|
||||
/* Prevent autosuspend following the unlink */
|
||||
usb_autopm_get_interface_no_resume(usbhid->intf);
|
||||
|
||||
if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
|
||||
if (hid_submit_out(hid)) {
|
||||
clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
|
||||
usb_autopm_put_interface_async(usbhid->intf);
|
||||
}
|
||||
wake_up(&usbhid->wait);
|
||||
} else {
|
||||
/*
|
||||
* the queue is known to run
|
||||
* but an earlier request may be stuck
|
||||
* we may need to time out
|
||||
* no race because the URB is blocked under
|
||||
* spinlock
|
||||
* Prevent resubmission in case the URB completes
|
||||
* before we can unlink it. We don't want to cancel
|
||||
* the wrong transfer!
|
||||
*/
|
||||
if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
|
||||
usb_block_urb(usbhid->urbout);
|
||||
/* drop lock to not deadlock if the callback is called */
|
||||
spin_unlock(&usbhid->lock);
|
||||
usb_unlink_urb(usbhid->urbout);
|
||||
spin_lock(&usbhid->lock);
|
||||
usb_unblock_urb(usbhid->urbout);
|
||||
/*
|
||||
* if the unlinking has already completed
|
||||
* the pump will have been stopped
|
||||
* it must be restarted now
|
||||
*/
|
||||
if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl))
|
||||
if (!irq_out_pump_restart(hid))
|
||||
set_bit(HID_OUT_RUNNING, &usbhid->iofl);
|
||||
usb_block_urb(usbhid->urbout);
|
||||
|
||||
/* Drop lock to avoid deadlock if the callback runs */
|
||||
spin_unlock(&usbhid->lock);
|
||||
|
||||
}
|
||||
usb_unlink_urb(usbhid->urbout);
|
||||
spin_lock(&usbhid->lock);
|
||||
usb_unblock_urb(usbhid->urbout);
|
||||
|
||||
/* Unlink might have stopped the queue */
|
||||
if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl))
|
||||
usbhid_restart_out_queue(usbhid);
|
||||
|
||||
/* Now we can allow autosuspend again */
|
||||
usb_autopm_put_interface_async(usbhid->intf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -604,47 +606,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
|
||||
usbhid->ctrl[usbhid->ctrlhead].dir = dir;
|
||||
usbhid->ctrlhead = head;
|
||||
|
||||
/* Try to awake from autosuspend... */
|
||||
if (usb_autopm_get_interface_async(usbhid->intf) < 0)
|
||||
return;
|
||||
/* If the queue isn't running, restart it */
|
||||
if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
|
||||
usbhid_restart_ctrl_queue(usbhid);
|
||||
|
||||
/*
|
||||
* If already suspended, leave urb enqueued, but don't submit.
|
||||
* Submission will occur if/when resume() drains the queue.
|
||||
*/
|
||||
if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl))
|
||||
return;
|
||||
/* Otherwise see if an earlier request has timed out */
|
||||
} else if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
|
||||
|
||||
/* Prevent autosuspend following the unlink */
|
||||
usb_autopm_get_interface_no_resume(usbhid->intf);
|
||||
|
||||
if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
|
||||
if (hid_submit_ctrl(hid)) {
|
||||
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
|
||||
usb_autopm_put_interface_async(usbhid->intf);
|
||||
}
|
||||
wake_up(&usbhid->wait);
|
||||
} else {
|
||||
/*
|
||||
* the queue is known to run
|
||||
* but an earlier request may be stuck
|
||||
* we may need to time out
|
||||
* no race because the URB is blocked under
|
||||
* spinlock
|
||||
* Prevent resubmission in case the URB completes
|
||||
* before we can unlink it. We don't want to cancel
|
||||
* the wrong transfer!
|
||||
*/
|
||||
if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
|
||||
usb_block_urb(usbhid->urbctrl);
|
||||
/* drop lock to not deadlock if the callback is called */
|
||||
spin_unlock(&usbhid->lock);
|
||||
usb_unlink_urb(usbhid->urbctrl);
|
||||
spin_lock(&usbhid->lock);
|
||||
usb_unblock_urb(usbhid->urbctrl);
|
||||
/*
|
||||
* if the unlinking has already completed
|
||||
* the pump will have been stopped
|
||||
* it must be restarted now
|
||||
*/
|
||||
if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
|
||||
if (!ctrl_pump_restart(hid))
|
||||
set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
|
||||
}
|
||||
usb_block_urb(usbhid->urbctrl);
|
||||
|
||||
/* Drop lock to avoid deadlock if the callback runs */
|
||||
spin_unlock(&usbhid->lock);
|
||||
|
||||
usb_unlink_urb(usbhid->urbctrl);
|
||||
spin_lock(&usbhid->lock);
|
||||
usb_unblock_urb(usbhid->urbctrl);
|
||||
|
||||
/* Unlink might have stopped the queue */
|
||||
if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
|
||||
usbhid_restart_ctrl_queue(usbhid);
|
||||
|
||||
/* Now we can allow autosuspend again */
|
||||
usb_autopm_put_interface_async(usbhid->intf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1002,9 +993,10 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co
|
||||
|
||||
static void usbhid_restart_queues(struct usbhid_device *usbhid)
|
||||
{
|
||||
if (usbhid->urbout)
|
||||
if (usbhid->urbout && !test_bit(HID_OUT_RUNNING, &usbhid->iofl))
|
||||
usbhid_restart_out_queue(usbhid);
|
||||
usbhid_restart_ctrl_queue(usbhid);
|
||||
if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
|
||||
usbhid_restart_ctrl_queue(usbhid);
|
||||
}
|
||||
|
||||
static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
|
||||
@ -1471,11 +1463,38 @@ void usbhid_put_power(struct hid_device *hid)
|
||||
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int hid_resume_common(struct hid_device *hid, bool driver_suspended)
|
||||
{
|
||||
struct usbhid_device *usbhid = hid->driver_data;
|
||||
int status;
|
||||
|
||||
spin_lock_irq(&usbhid->lock);
|
||||
clear_bit(HID_SUSPENDED, &usbhid->iofl);
|
||||
usbhid_mark_busy(usbhid);
|
||||
|
||||
if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
|
||||
test_bit(HID_RESET_PENDING, &usbhid->iofl))
|
||||
schedule_work(&usbhid->reset_work);
|
||||
usbhid->retry_delay = 0;
|
||||
|
||||
usbhid_restart_queues(usbhid);
|
||||
spin_unlock_irq(&usbhid->lock);
|
||||
|
||||
status = hid_start_in(hid);
|
||||
if (status < 0)
|
||||
hid_io_error(hid);
|
||||
|
||||
if (driver_suspended && hid->driver && hid->driver->resume)
|
||||
status = hid->driver->resume(hid);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int hid_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
{
|
||||
struct hid_device *hid = usb_get_intfdata(intf);
|
||||
struct usbhid_device *usbhid = hid->driver_data;
|
||||
int status;
|
||||
bool driver_suspended = false;
|
||||
|
||||
if (PMSG_IS_AUTO(message)) {
|
||||
spin_lock_irq(&usbhid->lock); /* Sync with error handler */
|
||||
@ -1486,13 +1505,14 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
&& !test_bit(HID_KEYS_PRESSED, &usbhid->iofl)
|
||||
&& (!usbhid->ledcount || ignoreled))
|
||||
{
|
||||
set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
|
||||
set_bit(HID_SUSPENDED, &usbhid->iofl);
|
||||
spin_unlock_irq(&usbhid->lock);
|
||||
if (hid->driver && hid->driver->suspend) {
|
||||
status = hid->driver->suspend(hid, message);
|
||||
if (status < 0)
|
||||
return status;
|
||||
goto failed;
|
||||
}
|
||||
driver_suspended = true;
|
||||
} else {
|
||||
usbhid_mark_busy(usbhid);
|
||||
spin_unlock_irq(&usbhid->lock);
|
||||
@ -1505,11 +1525,14 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
if (status < 0)
|
||||
return status;
|
||||
}
|
||||
driver_suspended = true;
|
||||
spin_lock_irq(&usbhid->lock);
|
||||
set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
|
||||
set_bit(HID_SUSPENDED, &usbhid->iofl);
|
||||
spin_unlock_irq(&usbhid->lock);
|
||||
if (usbhid_wait_io(hid) < 0)
|
||||
return -EIO;
|
||||
if (usbhid_wait_io(hid) < 0) {
|
||||
status = -EIO;
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
hid_cancel_delayed_stuff(usbhid);
|
||||
@ -1517,14 +1540,15 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
|
||||
if (PMSG_IS_AUTO(message) && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) {
|
||||
/* lost race against keypresses */
|
||||
status = hid_start_in(hid);
|
||||
if (status < 0)
|
||||
hid_io_error(hid);
|
||||
usbhid_mark_busy(usbhid);
|
||||
return -EBUSY;
|
||||
status = -EBUSY;
|
||||
goto failed;
|
||||
}
|
||||
dev_dbg(&intf->dev, "suspend\n");
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
hid_resume_common(hid, driver_suspended);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int hid_resume(struct usb_interface *intf)
|
||||
@ -1536,23 +1560,7 @@ static int hid_resume(struct usb_interface *intf)
|
||||
if (!test_bit(HID_STARTED, &usbhid->iofl))
|
||||
return 0;
|
||||
|
||||
clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
|
||||
usbhid_mark_busy(usbhid);
|
||||
|
||||
if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
|
||||
test_bit(HID_RESET_PENDING, &usbhid->iofl))
|
||||
schedule_work(&usbhid->reset_work);
|
||||
usbhid->retry_delay = 0;
|
||||
status = hid_start_in(hid);
|
||||
if (status < 0)
|
||||
hid_io_error(hid);
|
||||
usbhid_restart_queues(usbhid);
|
||||
|
||||
if (status >= 0 && hid->driver && hid->driver->resume) {
|
||||
int ret = hid->driver->resume(hid);
|
||||
if (ret < 0)
|
||||
status = ret;
|
||||
}
|
||||
status = hid_resume_common(hid, true);
|
||||
dev_dbg(&intf->dev, "resume status %d\n", status);
|
||||
return 0;
|
||||
}
|
||||
@ -1563,7 +1571,7 @@ static int hid_reset_resume(struct usb_interface *intf)
|
||||
struct usbhid_device *usbhid = hid->driver_data;
|
||||
int status;
|
||||
|
||||
clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
|
||||
clear_bit(HID_SUSPENDED, &usbhid->iofl);
|
||||
status = hid_post_reset(intf);
|
||||
if (status >= 0 && hid->driver && hid->driver->reset_resume) {
|
||||
int ret = hid->driver->reset_resume(hid);
|
||||
|
@ -53,7 +53,6 @@ struct usb_interface *usbhid_find_interface(int minor);
|
||||
#define HID_CLEAR_HALT 6
|
||||
#define HID_DISCONNECTED 7
|
||||
#define HID_STARTED 8
|
||||
#define HID_REPORTED_IDLE 9
|
||||
#define HID_KEYS_PRESSED 10
|
||||
#define HID_NO_BANDWIDTH 11
|
||||
|
||||
|
@ -376,6 +376,7 @@ header-y += tty.h
|
||||
header-y += types.h
|
||||
header-y += udf_fs_i.h
|
||||
header-y += udp.h
|
||||
header-y += uhid.h
|
||||
header-y += uinput.h
|
||||
header-y += uio.h
|
||||
header-y += ultrasound.h
|
||||
|
@ -200,6 +200,7 @@ struct hid_item {
|
||||
#define HID_UP_DIGITIZER 0x000d0000
|
||||
#define HID_UP_PID 0x000f0000
|
||||
#define HID_UP_HPVENDOR 0xff7f0000
|
||||
#define HID_UP_HPVENDOR2 0xff010000
|
||||
#define HID_UP_MSVENDOR 0xff000000
|
||||
#define HID_UP_CUSTOM 0x00ff0000
|
||||
#define HID_UP_LOGIVENDOR 0xffbc0000
|
||||
|
104
include/linux/uhid.h
Normal file
104
include/linux/uhid.h
Normal file
@ -0,0 +1,104 @@
|
||||
#ifndef __UHID_H_
|
||||
#define __UHID_H_
|
||||
|
||||
/*
|
||||
* User-space I/O driver support for HID subsystem
|
||||
* Copyright (c) 2012 David Herrmann
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Public header for user-space communication. We try to keep every structure
|
||||
* aligned but to be safe we also use __attribute__((__packed__)). Therefore,
|
||||
* the communication should be ABI compatible even between architectures.
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
enum uhid_event_type {
|
||||
UHID_CREATE,
|
||||
UHID_DESTROY,
|
||||
UHID_START,
|
||||
UHID_STOP,
|
||||
UHID_OPEN,
|
||||
UHID_CLOSE,
|
||||
UHID_OUTPUT,
|
||||
UHID_OUTPUT_EV,
|
||||
UHID_INPUT,
|
||||
UHID_FEATURE,
|
||||
UHID_FEATURE_ANSWER,
|
||||
};
|
||||
|
||||
struct uhid_create_req {
|
||||
__u8 name[128];
|
||||
__u8 phys[64];
|
||||
__u8 uniq[64];
|
||||
__u8 __user *rd_data;
|
||||
__u16 rd_size;
|
||||
|
||||
__u16 bus;
|
||||
__u32 vendor;
|
||||
__u32 product;
|
||||
__u32 version;
|
||||
__u32 country;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
#define UHID_DATA_MAX 4096
|
||||
|
||||
enum uhid_report_type {
|
||||
UHID_FEATURE_REPORT,
|
||||
UHID_OUTPUT_REPORT,
|
||||
UHID_INPUT_REPORT,
|
||||
};
|
||||
|
||||
struct uhid_input_req {
|
||||
__u8 data[UHID_DATA_MAX];
|
||||
__u16 size;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct uhid_output_req {
|
||||
__u8 data[UHID_DATA_MAX];
|
||||
__u16 size;
|
||||
__u8 rtype;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct uhid_output_ev_req {
|
||||
__u16 type;
|
||||
__u16 code;
|
||||
__s32 value;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct uhid_feature_req {
|
||||
__u32 id;
|
||||
__u8 rnum;
|
||||
__u8 rtype;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct uhid_feature_answer_req {
|
||||
__u32 id;
|
||||
__u16 err;
|
||||
__u16 size;
|
||||
__u8 data[UHID_DATA_MAX];
|
||||
};
|
||||
|
||||
struct uhid_event {
|
||||
__u32 type;
|
||||
|
||||
union {
|
||||
struct uhid_create_req create;
|
||||
struct uhid_input_req input;
|
||||
struct uhid_output_req output;
|
||||
struct uhid_output_ev_req output_ev;
|
||||
struct uhid_feature_req feature;
|
||||
struct uhid_feature_answer_req feature_answer;
|
||||
} u;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
#endif /* __UHID_H_ */
|
10
samples/uhid/Makefile
Normal file
10
samples/uhid/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
# kbuild trick to avoid linker error. Can be omitted if a module is built.
|
||||
obj- := dummy.o
|
||||
|
||||
# List of programs to build
|
||||
hostprogs-y := uhid-example
|
||||
|
||||
# Tell kbuild to always build the programs
|
||||
always := $(hostprogs-y)
|
||||
|
||||
HOSTCFLAGS_uhid-example.o += -I$(objtree)/usr/include
|
381
samples/uhid/uhid-example.c
Normal file
381
samples/uhid/uhid-example.c
Normal file
@ -0,0 +1,381 @@
|
||||
/*
|
||||
* UHID Example
|
||||
*
|
||||
* Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
|
||||
*
|
||||
* The code may be used by anyone for any purpose,
|
||||
* and can serve as a starting point for developing
|
||||
* applications using uhid.
|
||||
*/
|
||||
|
||||
/* UHID Example
|
||||
* This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
|
||||
* program as root and then use the following keys to control the mouse:
|
||||
* q: Quit the application
|
||||
* 1: Toggle left button (down, up, ...)
|
||||
* 2: Toggle right button
|
||||
* 3: Toggle middle button
|
||||
* a: Move mouse left
|
||||
* d: Move mouse right
|
||||
* w: Move mouse up
|
||||
* s: Move mouse down
|
||||
* r: Move wheel up
|
||||
* f: Move wheel down
|
||||
*
|
||||
* If uhid is not available as /dev/uhid, then you can pass a different path as
|
||||
* first argument.
|
||||
* If <linux/uhid.h> is not installed in /usr, then compile this with:
|
||||
* gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c
|
||||
* And ignore the warning about kernel headers. However, it is recommended to
|
||||
* use the installed uhid.h if available.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/uhid.h>
|
||||
|
||||
/* HID Report Desciptor
|
||||
* We emulate a basic 3 button mouse with wheel. This is the report-descriptor
|
||||
* as the kernel will parse it:
|
||||
*
|
||||
* INPUT[INPUT]
|
||||
* Field(0)
|
||||
* Physical(GenericDesktop.Pointer)
|
||||
* Application(GenericDesktop.Mouse)
|
||||
* Usage(3)
|
||||
* Button.0001
|
||||
* Button.0002
|
||||
* Button.0003
|
||||
* Logical Minimum(0)
|
||||
* Logical Maximum(1)
|
||||
* Report Size(1)
|
||||
* Report Count(3)
|
||||
* Report Offset(0)
|
||||
* Flags( Variable Absolute )
|
||||
* Field(1)
|
||||
* Physical(GenericDesktop.Pointer)
|
||||
* Application(GenericDesktop.Mouse)
|
||||
* Usage(3)
|
||||
* GenericDesktop.X
|
||||
* GenericDesktop.Y
|
||||
* GenericDesktop.Wheel
|
||||
* Logical Minimum(-128)
|
||||
* Logical Maximum(127)
|
||||
* Report Size(8)
|
||||
* Report Count(3)
|
||||
* Report Offset(8)
|
||||
* Flags( Variable Relative )
|
||||
*
|
||||
* This is the mapping that we expect:
|
||||
* Button.0001 ---> Key.LeftBtn
|
||||
* Button.0002 ---> Key.RightBtn
|
||||
* Button.0003 ---> Key.MiddleBtn
|
||||
* GenericDesktop.X ---> Relative.X
|
||||
* GenericDesktop.Y ---> Relative.Y
|
||||
* GenericDesktop.Wheel ---> Relative.Wheel
|
||||
*
|
||||
* This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
|
||||
* This file should print the same information as showed above.
|
||||
*/
|
||||
|
||||
static unsigned char rdesc[] = {
|
||||
0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
|
||||
0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
|
||||
0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
|
||||
0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
|
||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
|
||||
0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
|
||||
0x81, 0x06, 0xc0, 0xc0,
|
||||
};
|
||||
|
||||
static int uhid_write(int fd, const struct uhid_event *ev)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
ret = write(fd, ev, sizeof(*ev));
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Cannot write to uhid: %m\n");
|
||||
return -errno;
|
||||
} else if (ret != sizeof(*ev)) {
|
||||
fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n",
|
||||
ret, sizeof(ev));
|
||||
return -EFAULT;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int create(int fd)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = UHID_CREATE;
|
||||
strcpy((char*)ev.u.create.name, "test-uhid-device");
|
||||
ev.u.create.rd_data = rdesc;
|
||||
ev.u.create.rd_size = sizeof(rdesc);
|
||||
ev.u.create.bus = BUS_USB;
|
||||
ev.u.create.vendor = 0x15d9;
|
||||
ev.u.create.product = 0x0a37;
|
||||
ev.u.create.version = 0;
|
||||
ev.u.create.country = 0;
|
||||
|
||||
return uhid_write(fd, &ev);
|
||||
}
|
||||
|
||||
static void destroy(int fd)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = UHID_DESTROY;
|
||||
|
||||
uhid_write(fd, &ev);
|
||||
}
|
||||
|
||||
static int event(int fd)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
ssize_t ret;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ret = read(fd, &ev, sizeof(ev));
|
||||
if (ret == 0) {
|
||||
fprintf(stderr, "Read HUP on uhid-cdev\n");
|
||||
return -EFAULT;
|
||||
} else if (ret < 0) {
|
||||
fprintf(stderr, "Cannot read uhid-cdev: %m\n");
|
||||
return -errno;
|
||||
} else if (ret != sizeof(ev)) {
|
||||
fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n",
|
||||
ret, sizeof(ev));
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (ev.type) {
|
||||
case UHID_START:
|
||||
fprintf(stderr, "UHID_START from uhid-dev\n");
|
||||
break;
|
||||
case UHID_STOP:
|
||||
fprintf(stderr, "UHID_STOP from uhid-dev\n");
|
||||
break;
|
||||
case UHID_OPEN:
|
||||
fprintf(stderr, "UHID_OPEN from uhid-dev\n");
|
||||
break;
|
||||
case UHID_CLOSE:
|
||||
fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
|
||||
break;
|
||||
case UHID_OUTPUT:
|
||||
fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
|
||||
break;
|
||||
case UHID_OUTPUT_EV:
|
||||
fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool btn1_down;
|
||||
static bool btn2_down;
|
||||
static bool btn3_down;
|
||||
static signed char abs_hor;
|
||||
static signed char abs_ver;
|
||||
static signed char wheel;
|
||||
|
||||
static int send_event(int fd)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = UHID_INPUT;
|
||||
ev.u.input.size = 4;
|
||||
|
||||
if (btn1_down)
|
||||
ev.u.input.data[0] |= 0x1;
|
||||
if (btn2_down)
|
||||
ev.u.input.data[0] |= 0x2;
|
||||
if (btn3_down)
|
||||
ev.u.input.data[0] |= 0x4;
|
||||
|
||||
ev.u.input.data[1] = abs_hor;
|
||||
ev.u.input.data[2] = abs_ver;
|
||||
ev.u.input.data[3] = wheel;
|
||||
|
||||
return uhid_write(fd, &ev);
|
||||
}
|
||||
|
||||
static int keyboard(int fd)
|
||||
{
|
||||
char buf[128];
|
||||
ssize_t ret, i;
|
||||
|
||||
ret = read(STDIN_FILENO, buf, sizeof(buf));
|
||||
if (ret == 0) {
|
||||
fprintf(stderr, "Read HUP on stdin\n");
|
||||
return -EFAULT;
|
||||
} else if (ret < 0) {
|
||||
fprintf(stderr, "Cannot read stdin: %m\n");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
for (i = 0; i < ret; ++i) {
|
||||
switch (buf[i]) {
|
||||
case '1':
|
||||
btn1_down = !btn1_down;
|
||||
ret = send_event(fd);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case '2':
|
||||
btn2_down = !btn2_down;
|
||||
ret = send_event(fd);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case '3':
|
||||
btn3_down = !btn3_down;
|
||||
ret = send_event(fd);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 'a':
|
||||
abs_hor = -20;
|
||||
ret = send_event(fd);
|
||||
abs_hor = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 'd':
|
||||
abs_hor = 20;
|
||||
ret = send_event(fd);
|
||||
abs_hor = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 'w':
|
||||
abs_ver = -20;
|
||||
ret = send_event(fd);
|
||||
abs_ver = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 's':
|
||||
abs_ver = 20;
|
||||
ret = send_event(fd);
|
||||
abs_ver = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 'r':
|
||||
wheel = 1;
|
||||
ret = send_event(fd);
|
||||
wheel = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 'f':
|
||||
wheel = -1;
|
||||
ret = send_event(fd);
|
||||
wheel = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case 'q':
|
||||
return -ECANCELED;
|
||||
default:
|
||||
fprintf(stderr, "Invalid input: %c\n", buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int fd;
|
||||
const char *path = "/dev/uhid";
|
||||
struct pollfd pfds[2];
|
||||
int ret;
|
||||
struct termios state;
|
||||
|
||||
ret = tcgetattr(STDIN_FILENO, &state);
|
||||
if (ret) {
|
||||
fprintf(stderr, "Cannot get tty state\n");
|
||||
} else {
|
||||
state.c_lflag &= ~ICANON;
|
||||
state.c_cc[VMIN] = 1;
|
||||
ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
|
||||
if (ret)
|
||||
fprintf(stderr, "Cannot set tty state\n");
|
||||
}
|
||||
|
||||
if (argc >= 2) {
|
||||
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
|
||||
fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
path = argv[1];
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Open uhid-cdev %s\n", path);
|
||||
fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Create uhid device\n");
|
||||
ret = create(fd);
|
||||
if (ret) {
|
||||
close(fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
pfds[0].fd = STDIN_FILENO;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = fd;
|
||||
pfds[1].events = POLLIN;
|
||||
|
||||
fprintf(stderr, "Press 'q' to quit...\n");
|
||||
while (1) {
|
||||
ret = poll(pfds, 2, -1);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Cannot poll for fds: %m\n");
|
||||
break;
|
||||
}
|
||||
if (pfds[0].revents & POLLHUP) {
|
||||
fprintf(stderr, "Received HUP on stdin\n");
|
||||
break;
|
||||
}
|
||||
if (pfds[1].revents & POLLHUP) {
|
||||
fprintf(stderr, "Received HUP on uhid-cdev\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (pfds[0].revents & POLLIN) {
|
||||
ret = keyboard(fd);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
if (pfds[1].revents & POLLIN) {
|
||||
ret = event(fd);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Destroy uhid device\n");
|
||||
destroy(fd);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue
Block a user