mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 17:25:38 +00:00
for-linus-2023083101
-----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEoEVH9lhNrxiMPSyI7MXwXhnZSjYFAmTworgTHGJlbnRpc3NA a2VybmVsLm9yZwAKCRDsxfBeGdlKNmIFD/wKBNvoGV8uEw/U+FeE++kpgsuc3Bnl Yr4O4gxE+MODOUCvNo9l24REsYXmQvBdMtYlvMbhPrQ0lPrfG73y3DRDagZdq5c7 Mfolrun72A46LnqznNZLbMD5SVBZzRRRQGjXUZaTwJF5Ss7iNNK+v/C5cd0l2ZaB PBJ4DGTjMMnDKnhbPua5HL5IvZqLOjxlI1G30TU//6MAt1iLlOiWNmv8Wus/nl6U h5kufTbyr1Dl3reGlgMFsvD94f4NlJITs5q5+9DtAZ4iJ185bSIk5NXb/lGh7umJ hB1YUVqvGNobQ6wbXSTD7M+A983vqREIo47qnkuOP/2KducRLcpPL2AZ9Eq8JDQS reTJeuE9c559SKzKDw6NC2Igs+yC4MAV5GfHps+vtavJxAKNI3RISGuoiv5J39Ms MSl5DAGg9fYjP4E7ZMp6hMwVt0fuMOrtQhi/Zh+YHOg0HubIgGHDTVLSpMLTb8NM Aovf28szSbM7qpseTsk6auCqBXST/f4z6iSsLJ1y/XCE1eqEEehfaEFlXG5s0XEU U0D2Z0bKh1AIe23ptluZDAvKLMlFDxnssJuXXk8UO9AzfNmcQcsxDmvq/AYh9nBc /iEh/yc5pPRCltTq/nAjCMee5N9UpfF6I3EQfrlEW2PxKFgM+Md9z+WlKF0esDT3 WUbYsEiAc+N7dg== =24SV -----END PGP SIGNATURE----- Merge tag 'for-linus-2023083101' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid Pull HID updates from Benjamin Tissoires: - devm fixes for problems that caused use-after-free reports (Rahul Rameshbabu) - Some extensive HID docs (Marco Morandini) - Constification of struct class (Ivan Orlov and Greg Kroah-Hartman) - Google Stadia Force Feedback support (Fabio Baltieri) - Various fixes and new device ID support * tag 'for-linus-2023083101' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (42 commits) HID: logitech-hidpp: rework one more time the retries attempts HID: nvidia-shield: Reference hid_device devm allocation of input_dev name HID: multitouch: Correct devm device reference for hidinput input_dev name HID: uclogic: Correct devm device reference for hidinput input_dev name HID: logitech-dj: Fix error handling in logi_dj_recv_switch_to_dj_mode() HID: i2c-hid: elan: Add ili9882t timing dt-bindings: input: i2c-hid: Introduce Ilitek ili9882t HID: apple: Add "Hailuck" to the list of non-apple keyboards HID: steelseries: arctis_1_battery_request[] should be static MAINTAINERS: update my email address HID: logitech-hidpp: Add support for Logitech MX Anywhere 3 mouse HID: wacom: struct name cleanup HID: wacom: remove unnecessary 'connected' variable from EKR HID: wacom: remove the battery when the EKR is off HID: nvidia-shield: Update Thunderstrike LED instance name to use id HID: nvidia-shield: Add battery support for Thunderstrike HID: nvidia-shield: Remove led_classdev_unregister in thunderstrike_create HID: hid-google-stadiaff: add support for Stadia force feedback HID: logitech-dj: Add support for a new lightspeed receiver iteration HID: logitech-hidpp: Add support for the Pro X Superlight ...
This commit is contained in:
commit
29aa98d0fe
3
.mailmap
3
.mailmap
@ -139,6 +139,9 @@ Daniel Borkmann <daniel@iogearbox.net> <dborkman@redhat.com>
|
||||
Daniel Borkmann <daniel@iogearbox.net> <dxchgb@gmail.com>
|
||||
David Brownell <david-b@pacbell.net>
|
||||
David Collins <quic_collinsd@quicinc.com> <collinsd@codeaurora.org>
|
||||
David Rheinsberg <david@readahead.eu> <dh.herrmann@gmail.com>
|
||||
David Rheinsberg <david@readahead.eu> <dh.herrmann@googlemail.com>
|
||||
David Rheinsberg <david@readahead.eu> <david.rheinsberg@gmail.com>
|
||||
David Woodhouse <dwmw2@shinybook.infradead.org>
|
||||
Dedy Lansky <quic_dlansky@quicinc.com> <dlansky@codeaurora.org>
|
||||
Deepak Kumar Singh <quic_deesin@quicinc.com> <deesin@codeaurora.org>
|
||||
|
67
Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml
Normal file
67
Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml
Normal file
@ -0,0 +1,67 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/input/ilitek,ili9882t.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Ilitek ili9882t touchscreen controller
|
||||
|
||||
maintainers:
|
||||
- Cong Yang <yangcong5@huaqin.corp-partner.google.com>
|
||||
|
||||
description:
|
||||
Supports the Ilitek ili9882t touchscreen controller.
|
||||
This touchscreen controller uses the i2c-hid protocol with a reset GPIO.
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/input/touchscreen/touchscreen.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ilitek,ili9882t
|
||||
|
||||
reg:
|
||||
const: 0x41
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
panel: true
|
||||
|
||||
reset-gpios:
|
||||
maxItems: 1
|
||||
description: Reset GPIO.
|
||||
|
||||
vccio-supply:
|
||||
description: The 1.8V supply to the touchscreen.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- panel
|
||||
- vccio-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
touchscreen: touchscreen@41 {
|
||||
compatible = "ilitek,ili9882t";
|
||||
reg = <0x41>;
|
||||
|
||||
interrupt-parent = <&pio>;
|
||||
interrupts = <12 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
panel = <&panel>;
|
||||
reset-gpios = <&pio 60 GPIO_ACTIVE_LOW>;
|
||||
vccio-supply = <&mt6366_vio18_reg>;
|
||||
};
|
||||
};
|
524
Documentation/hid/hidintro.rst
Normal file
524
Documentation/hid/hidintro.rst
Normal file
@ -0,0 +1,524 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
======================================
|
||||
Introduction to HID report descriptors
|
||||
======================================
|
||||
|
||||
This chapter is meant to give a broad overview of what HID report
|
||||
descriptors are, and of how a casual (non-kernel) programmer can deal
|
||||
with HID devices that are not working well with Linux.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
hidreport-parsing
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
HID stands for Human Interface Device, and can be whatever device you
|
||||
are using to interact with a computer, be it a mouse, a touchpad, a
|
||||
tablet, a microphone.
|
||||
|
||||
Many HID devices work out the box, even if their hardware is different.
|
||||
For example, mice can have any number of buttons; they may have a
|
||||
wheel; movement sensitivity differs between different models, and so
|
||||
on. Nonetheless, most of the time everything just works, without the
|
||||
need to have specialized code in the kernel for every mouse model
|
||||
developed since 1970.
|
||||
|
||||
This is because modern HID devices do advertise their capabilities
|
||||
through the *HID report descriptor*, a fixed set of bytes describing
|
||||
exactly what *HID reports* may be sent between the device and the host
|
||||
and the meaning of each individual bit in those reports. For example,
|
||||
a HID Report Descriptor may specify that "in a report with ID 3 the
|
||||
bits from 8 to 15 is the delta x coordinate of a mouse".
|
||||
|
||||
The HID report itself then merely carries the actual data values
|
||||
without any extra meta information. Note that HID reports may be sent
|
||||
from the device ("Input Reports", i.e. input events), to the device
|
||||
("Output Reports" to e.g. change LEDs) or used for device configuration
|
||||
("Feature reports"). A device may support one or more HID reports.
|
||||
|
||||
The HID subsystem is in charge of parsing the HID report descriptors,
|
||||
and converts HID events into normal input device interfaces (see
|
||||
Documentation/hid/hid-transport.rst). Devices may misbehave because the
|
||||
HID report descriptor provided by the device is wrong, or because it
|
||||
needs to be dealt with in a special way, or because some special
|
||||
device or interaction mode is not handled by the default code.
|
||||
|
||||
The format of HID report descriptors is described by two documents,
|
||||
available from the `USB Implementers Forum <https://www.usb.org/>`_
|
||||
`HID web page <https://www.usb.org/hid>`_ address:
|
||||
|
||||
* the `HID USB Device Class Definition
|
||||
<https://www.usb.org/document-library/device-class-definition-hid-111>`_ (HID Spec from now on)
|
||||
* the `HID Usage Tables <https://usb.org/document-library/hid-usage-tables-14>`_ (HUT from now on)
|
||||
|
||||
The HID subsystem can deal with different transport drivers
|
||||
(USB, I2C, Bluetooth, etc.). See Documentation/hid/hid-transport.rst.
|
||||
|
||||
Parsing HID report descriptors
|
||||
==============================
|
||||
|
||||
The current list of HID devices can be found at ``/sys/bus/hid/devices/``.
|
||||
For each device, say ``/sys/bus/hid/devices/0003\:093A\:2510.0002/``,
|
||||
one can read the corresponding report descriptor::
|
||||
|
||||
$ hexdump -C /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor
|
||||
00000000 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 |..............).|
|
||||
00000010 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 |..%.u.....u.....|
|
||||
00000020 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 |...0.1.8..%.u...|
|
||||
00000030 81 06 c0 c0 |....|
|
||||
00000034
|
||||
|
||||
Optional: the HID report descriptor can be read also by
|
||||
directly accessing the hidraw driver [#hidraw]_.
|
||||
|
||||
The basic structure of HID report descriptors is defined in the HID
|
||||
spec, while HUT "defines constants that can be interpreted by an
|
||||
application to identify the purpose and meaning of a data field in a
|
||||
HID report". Each entry is defined by at least two bytes, where the
|
||||
first one defines what type of value is following and is described in
|
||||
the HID spec, while the second one carries the actual value and is
|
||||
described in the HUT.
|
||||
|
||||
HID report descriptors can, in principle, be painstakingly parsed by
|
||||
hand, byte by byte.
|
||||
|
||||
A short introduction on how to do this is sketched in
|
||||
Documentation/hid/hidreport-parsing.rst; you only need to understand it
|
||||
if you need to patch HID report descriptors.
|
||||
|
||||
In practice you should not parse HID report descriptors by hand; rather,
|
||||
you should use an existing parser. Among all the available ones
|
||||
|
||||
* the online `USB Descriptor and Request Parser
|
||||
<http://eleccelerator.com/usbdescreqparser/>`_;
|
||||
* `hidrdd <https://github.com/abend0c1/hidrdd>`_,
|
||||
that provides very detailed and somewhat verbose descriptions
|
||||
(verbosity can be useful if you are not familiar with HID report
|
||||
descriptors);
|
||||
* `hid-tools <https://gitlab.freedesktop.org/libevdev/hid-tools>`_,
|
||||
a complete utility set that allows, among other things,
|
||||
to record and replay the raw HID reports and to debug
|
||||
and replay HID devices.
|
||||
It is being actively developed by the Linux HID subsystem maintainers.
|
||||
|
||||
Parsing the mouse HID report descriptor with `hid-tools
|
||||
<https://gitlab.freedesktop.org/libevdev/hid-tools>`_ leads to
|
||||
(explanations interposed)::
|
||||
|
||||
$ ./hid-decode /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor
|
||||
# device 0:0
|
||||
# 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
# 0x09, 0x02, // Usage (Mouse) 2
|
||||
# 0xa1, 0x01, // Collection (Application) 4
|
||||
# 0x09, 0x01, // Usage (Pointer) 6
|
||||
# 0xa1, 0x00, // Collection (Physical) 8
|
||||
# 0x05, 0x09, // Usage Page (Button) 10
|
||||
|
||||
what follows is a button ::
|
||||
|
||||
# 0x19, 0x01, // Usage Minimum (1) 12
|
||||
# 0x29, 0x03, // Usage Maximum (3) 14
|
||||
|
||||
first button is button number 1, last button is button number 3 ::
|
||||
|
||||
# 0x15, 0x00, // Logical Minimum (0) 16
|
||||
# 0x25, 0x01, // Logical Maximum (1) 18
|
||||
|
||||
each button can send values from 0 up to including 1
|
||||
(i.e. they are binary buttons) ::
|
||||
|
||||
# 0x75, 0x01, // Report Size (1) 20
|
||||
|
||||
each button is sent as exactly one bit ::
|
||||
|
||||
# 0x95, 0x03, // Report Count (3) 22
|
||||
|
||||
and there are three of those bits (matching the three buttons) ::
|
||||
|
||||
# 0x81, 0x02, // Input (Data,Var,Abs) 24
|
||||
|
||||
it's actual Data (not constant padding), they represent
|
||||
a single variable (Var) and their values are Absolute (not relative);
|
||||
See HID spec Sec. 6.2.2.5 "Input, Output, and Feature Items" ::
|
||||
|
||||
# 0x75, 0x05, // Report Size (5) 26
|
||||
|
||||
five additional padding bits, needed to reach a byte ::
|
||||
|
||||
# 0x95, 0x01, // Report Count (1) 28
|
||||
|
||||
those five bits are repeated only once ::
|
||||
|
||||
# 0x81, 0x01, // Input (Cnst,Arr,Abs) 30
|
||||
|
||||
and take Constant (Cnst) values i.e. they can be ignored. ::
|
||||
|
||||
# 0x05, 0x01, // Usage Page (Generic Desktop) 32
|
||||
# 0x09, 0x30, // Usage (X) 34
|
||||
# 0x09, 0x31, // Usage (Y) 36
|
||||
# 0x09, 0x38, // Usage (Wheel) 38
|
||||
|
||||
The mouse has also two physical positions (Usage (X), Usage (Y))
|
||||
and a wheel (Usage (Wheel)) ::
|
||||
|
||||
# 0x15, 0x81, // Logical Minimum (-127) 40
|
||||
# 0x25, 0x7f, // Logical Maximum (127) 42
|
||||
|
||||
each of them can send values ranging from -127 up to including 127 ::
|
||||
|
||||
# 0x75, 0x08, // Report Size (8) 44
|
||||
|
||||
which is represented by eight bits ::
|
||||
|
||||
# 0x95, 0x03, // Report Count (3) 46
|
||||
|
||||
and there are three of those eight bits, matching X, Y and Wheel. ::
|
||||
|
||||
# 0x81, 0x06, // Input (Data,Var,Rel) 48
|
||||
|
||||
This time the data values are Relative (Rel), i.e. they represent
|
||||
the change from the previously sent report (event) ::
|
||||
|
||||
# 0xc0, // End Collection 50
|
||||
# 0xc0, // End Collection 51
|
||||
#
|
||||
R: 52 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 c0 c0
|
||||
N: device 0:0
|
||||
I: 3 0001 0001
|
||||
|
||||
|
||||
This Report Descriptor tells us that the mouse input will be
|
||||
transmitted using four bytes: the first one for the buttons (three
|
||||
bits used, five for padding), the last three for the mouse X, Y and
|
||||
wheel changes, respectively.
|
||||
|
||||
Indeed, for any event, the mouse will send a *report* of four bytes.
|
||||
We can check the values sent by resorting e.g. to the `hid-recorder`
|
||||
tool, from `hid-tools <https://gitlab.freedesktop.org/libevdev/hid-tools>`_:
|
||||
The sequence of bytes sent by clicking and releasing button 1, then button 2, then button 3 is::
|
||||
|
||||
$ sudo ./hid-recorder /dev/hidraw1
|
||||
|
||||
....
|
||||
output of hid-decode
|
||||
....
|
||||
|
||||
# Button: 1 0 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000000.000000 4 01 00 00 00
|
||||
# Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000000.183949 4 00 00 00 00
|
||||
# Button: 0 1 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000001.959698 4 02 00 00 00
|
||||
# Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000002.103899 4 00 00 00 00
|
||||
# Button: 0 0 1 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000004.855799 4 04 00 00 00
|
||||
# Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000005.103864 4 00 00 00 00
|
||||
|
||||
This example shows that when button 2 is clicked,
|
||||
the bytes ``02 00 00 00`` are sent, and the immediately subsequent
|
||||
event (``00 00 00 00``) is the release of button 2 (no buttons are
|
||||
pressed, remember that the data values are *absolute*).
|
||||
|
||||
If instead one clicks and holds button 1, then clicks and holds button
|
||||
2, releases button 1, and finally releases button 2, the reports are::
|
||||
|
||||
# Button: 1 0 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000044.175830 4 01 00 00 00
|
||||
# Button: 1 1 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000045.975997 4 03 00 00 00
|
||||
# Button: 0 1 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000047.407930 4 02 00 00 00
|
||||
# Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0
|
||||
E: 000049.199919 4 00 00 00 00
|
||||
|
||||
where with ``03 00 00 00`` both buttons are pressed, and with the
|
||||
subsequent ``02 00 00 00`` button 1 is released while button 2 is still
|
||||
active.
|
||||
|
||||
Output, Input and Feature Reports
|
||||
---------------------------------
|
||||
|
||||
HID devices can have Input Reports, like in the mouse example, Output
|
||||
Reports, and Feature Reports. "Output" means that the information is
|
||||
sent to the device. For example, a joystick with force feedback will
|
||||
have some output; the led of a keyboard would need an output as well.
|
||||
"Input" means that data come from the device.
|
||||
|
||||
"Feature"s are not meant to be consumed by the end user and define
|
||||
configuration options for the device. They can be queried from the host;
|
||||
when declared as *Volatile* they should be changed by the host.
|
||||
|
||||
|
||||
Collections, Report IDs and Evdev events
|
||||
========================================
|
||||
|
||||
A single device can logically group data into different independent
|
||||
sets, called a *Collection*. Collections can be nested and there are
|
||||
different types of collections (see the HID spec 6.2.2.6
|
||||
"Collection, End Collection Items" for details).
|
||||
|
||||
Different reports are identified by means of different *Report ID*
|
||||
fields, i.e. a number identifying the structure of the immediately
|
||||
following report.
|
||||
Whenever a Report ID is needed it is transmitted as the first byte of
|
||||
any report. A device with only one supported HID report (like the mouse
|
||||
example above) may omit the report ID.
|
||||
|
||||
Consider the following HID report descriptor::
|
||||
|
||||
05 01 09 02 A1 01 85 01 05 09 19 01 29 05 15 00
|
||||
25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01
|
||||
09 30 09 31 16 00 F8 26 FF 07 75 0C 95 02 81 06
|
||||
09 38 15 80 25 7F 75 08 95 01 81 06 05 0C 0A 38
|
||||
02 15 80 25 7F 75 08 95 01 81 06 C0 05 01 09 02
|
||||
A1 01 85 02 05 09 19 01 29 05 15 00 25 01 95 05
|
||||
75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31
|
||||
16 00 F8 26 FF 07 75 0C 95 02 81 06 09 38 15 80
|
||||
25 7F 75 08 95 01 81 06 05 0C 0A 38 02 15 80 25
|
||||
7F 75 08 95 01 81 06 C0 05 01 09 07 A1 01 85 05
|
||||
05 07 15 00 25 01 09 29 09 3E 09 4B 09 4E 09 E3
|
||||
09 E8 09 E8 09 E8 75 01 95 08 81 02 95 00 81 01
|
||||
C0 05 0C 09 01 A1 01 85 06 15 00 25 01 75 01 95
|
||||
01 09 3F 81 06 09 3F 81 06 09 3F 81 06 09 3F 81
|
||||
06 09 3F 81 06 09 3F 81 06 09 3F 81 06 09 3F 81
|
||||
06 C0 05 0C 09 01 A1 01 85 03 09 05 15 00 26 FF
|
||||
00 75 08 95 02 B1 02 C0
|
||||
|
||||
After parsing it (try to parse it on your own using the suggested
|
||||
tools!) one can see that the device presents two ``Mouse`` Application
|
||||
Collections (with reports identified by Reports IDs 1 and 2,
|
||||
respectively), a ``Keypad`` Application Collection (whose report is
|
||||
identified by the Report ID 5) and two ``Consumer Controls`` Application
|
||||
Collections, (with Report IDs 6 and 3, respectively). Note, however,
|
||||
that a device can have different Report IDs for the same Application
|
||||
Collection.
|
||||
|
||||
The data sent will begin with the Report ID byte, and will be followed
|
||||
by the corresponding information. For example, the data transmitted for
|
||||
the last consumer control::
|
||||
|
||||
0x05, 0x0C, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x03, // Report ID (3)
|
||||
0x09, 0x05, // Usage (Headphone)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
|
||||
will be of three bytes: the first for the Report ID (3), the next two
|
||||
for the headphone, with two (``Report Count (2)``) bytes
|
||||
(``Report Size (8)``), each ranging from 0 (``Logical Minimum (0)``)
|
||||
to 255 (``Logical Maximum (255)``).
|
||||
|
||||
All the Input data sent by the device should be translated into
|
||||
corresponding Evdev events, so that the remaining part of the stack can
|
||||
know what is going on, e.g. the bit for the first button translates into
|
||||
the ``EV_KEY/BTN_LEFT`` evdev event and relative X movement translates
|
||||
into the ``EV_REL/REL_X`` evdev event".
|
||||
|
||||
Events
|
||||
======
|
||||
|
||||
In Linux, one ``/dev/input/event*`` is created for each ``Application
|
||||
Collection``. Going back to the mouse example, and repeating the
|
||||
sequence where one clicks and holds button 1, then clicks and holds
|
||||
button 2, releases button 1, and finally releases button 2, one gets::
|
||||
|
||||
$ sudo libinput record /dev/input/event1
|
||||
# libinput record
|
||||
version: 1
|
||||
ndevices: 1
|
||||
libinput:
|
||||
version: "1.23.0"
|
||||
git: "unknown"
|
||||
system:
|
||||
os: "opensuse-tumbleweed:20230619"
|
||||
kernel: "6.3.7-1-default"
|
||||
dmi: "dmi:bvnHP:bvrU77Ver.01.05.00:bd03/24/2022:br5.0:efr20.29:svnHP:pnHPEliteBook64514inchG9NotebookPC:pvr:rvnHP:rn89D2:rvrKBCVersion14.1D.00:cvnHP:ct10:cvr:sku5Y3J1EA#ABZ:"
|
||||
devices:
|
||||
- node: /dev/input/event1
|
||||
evdev:
|
||||
# Name: PixArt HP USB Optical Mouse
|
||||
# ID: bus 0x3 vendor 0x3f0 product 0x94a version 0x111
|
||||
# Supported Events:
|
||||
# Event type 0 (EV_SYN)
|
||||
# Event type 1 (EV_KEY)
|
||||
# Event code 272 (BTN_LEFT)
|
||||
# Event code 273 (BTN_RIGHT)
|
||||
# Event code 274 (BTN_MIDDLE)
|
||||
# Event type 2 (EV_REL)
|
||||
# Event code 0 (REL_X)
|
||||
# Event code 1 (REL_Y)
|
||||
# Event code 8 (REL_WHEEL)
|
||||
# Event code 11 (REL_WHEEL_HI_RES)
|
||||
# Event type 4 (EV_MSC)
|
||||
# Event code 4 (MSC_SCAN)
|
||||
# Properties:
|
||||
name: "PixArt HP USB Optical Mouse"
|
||||
id: [3, 1008, 2378, 273]
|
||||
codes:
|
||||
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] # EV_SYN
|
||||
1: [272, 273, 274] # EV_KEY
|
||||
2: [0, 1, 8, 11] # EV_REL
|
||||
4: [4] # EV_MSC
|
||||
properties: []
|
||||
hid: [
|
||||
0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
|
||||
0x15, 0x00, 0x25, 0x01, 0x95, 0x08, 0x75, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31,
|
||||
0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06, 0xc0, 0xc0
|
||||
]
|
||||
udev:
|
||||
properties:
|
||||
- ID_INPUT=1
|
||||
- ID_INPUT_MOUSE=1
|
||||
- LIBINPUT_DEVICE_GROUP=3/3f0/94a:usb-0000:05:00.3-2
|
||||
quirks:
|
||||
events:
|
||||
# Current time is 12:31:56
|
||||
- evdev:
|
||||
- [ 0, 0, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated)
|
||||
- [ 0, 0, 1, 272, 1] # EV_KEY / BTN_LEFT 1
|
||||
- [ 0, 0, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +0ms
|
||||
- evdev:
|
||||
- [ 1, 207892, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated)
|
||||
- [ 1, 207892, 1, 273, 1] # EV_KEY / BTN_RIGHT 1
|
||||
- [ 1, 207892, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +1207ms
|
||||
- evdev:
|
||||
- [ 2, 367823, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated)
|
||||
- [ 2, 367823, 1, 272, 0] # EV_KEY / BTN_LEFT 0
|
||||
- [ 2, 367823, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +1160ms
|
||||
# Current time is 12:32:00
|
||||
- evdev:
|
||||
- [ 3, 247617, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated)
|
||||
- [ 3, 247617, 1, 273, 0] # EV_KEY / BTN_RIGHT 0
|
||||
- [ 3, 247617, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +880ms
|
||||
|
||||
Note: if ``libinput record`` is not available on your system try using
|
||||
``evemu-record``.
|
||||
|
||||
When something does not work
|
||||
============================
|
||||
|
||||
There can be a number of reasons why a device does not behave
|
||||
correctly. For example
|
||||
|
||||
* The HID report descriptor provided by the HID device may be wrong
|
||||
because e.g.
|
||||
|
||||
* it does not follow the standard, so that the kernel
|
||||
will not able to make sense of the HID report descriptor;
|
||||
* the HID report descriptor *does not match* what is actually
|
||||
sent by the device (this can be verified by reading the raw HID
|
||||
data);
|
||||
* the HID report descriptor may need some "quirks" (see later on).
|
||||
|
||||
As a consequence, a ``/dev/input/event*`` may not be created
|
||||
for each Application Collection, and/or the events
|
||||
there may not match what you would expect.
|
||||
|
||||
|
||||
Quirks
|
||||
------
|
||||
|
||||
There are some known peculiarities of HID devices that the kernel
|
||||
knows how to fix - these are called the HID quirks and a list of those
|
||||
is available in `include/linux/hid.h`.
|
||||
|
||||
Should this be the case, it should be enough to add the required quirk
|
||||
in the kernel, for the HID device at hand. This can be done in the file
|
||||
`drivers/hid/hid-quirks.c`. How to do it should be relatively
|
||||
straightforward after looking into the file.
|
||||
|
||||
The list of currently defined quirks, from `include/linux/hid.h`, is
|
||||
|
||||
.. kernel-doc:: include/linux/hid.h
|
||||
:doc: HID quirks
|
||||
|
||||
Quirks for USB devices can be specified while loading the usbhid module,
|
||||
see ``modinfo usbhid``, although the proper fix should go into
|
||||
hid-quirks.c and **be submitted upstream**.
|
||||
See Documentation/process/submitting-patches.rst for guidelines on how
|
||||
to submit a patch. Quirks for other busses need to go into hid-quirks.c.
|
||||
|
||||
Fixing HID report descriptors
|
||||
-----------------------------
|
||||
|
||||
Should you need to patch HID report descriptors the easiest way is to
|
||||
resort to eBPF, as described in Documentation/hid/hid-bpf.rst.
|
||||
|
||||
Basically, you can change any byte of the original HID report
|
||||
descriptor. The examples in samples/hid should be a good starting point
|
||||
for your code, see e.g. `samples/hid/hid_mouse.bpf.c`::
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
....
|
||||
data[39] = 0x31;
|
||||
data[41] = 0x30;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Of course this can be also done within the kernel source code, see e.g.
|
||||
`drivers/hid/hid-aureal.c` or `drivers/hid/hid-samsung.c` for a slightly
|
||||
more complex file.
|
||||
|
||||
Check Documentation/hid/hidreport-parsing.rst if you need any help
|
||||
navigating the HID manuals and understanding the exact meaning of
|
||||
the HID report descriptor hex numbers.
|
||||
|
||||
Whatever solution you come up with, please remember to **submit the
|
||||
fix to the HID maintainers**, so that it can be directly integrated in
|
||||
the kernel and that particular HID device will start working for
|
||||
everyone else. See Documentation/process/submitting-patches.rst for
|
||||
guidelines on how to do this.
|
||||
|
||||
|
||||
Modifying the transmitted data on the fly
|
||||
-----------------------------------------
|
||||
|
||||
Using eBPF it is also possible to modify the data exchanged with the
|
||||
device. See again the examples in `samples/hid`.
|
||||
|
||||
Again, **please post your fix**, so that it can be integrated in the
|
||||
kernel!
|
||||
|
||||
Writing a specialized driver
|
||||
----------------------------
|
||||
|
||||
This should really be your last resort.
|
||||
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#hidraw] read hidraw: see Documentation/hid/hidraw.rst and
|
||||
file `samples/hidraw/hid-example.c` for an example.
|
||||
The output of ``hid-example`` would be, for the same mouse::
|
||||
|
||||
$ sudo ./hid-example
|
||||
Report Descriptor Size: 52
|
||||
Report Descriptor:
|
||||
5 1 9 2 a1 1 9 1 a1 0 5 9 19 1 29 3 15 0 25 1 75 1 95 3 81 2 75 5 95 1 81 1 5 1 9 30 9 31 9 38 15 81 25 7f 75 8 95 3 81 6 c0 c0
|
||||
|
||||
Raw Name: PixArt USB Optical Mouse
|
||||
Raw Phys: usb-0000:05:00.4-2.3/input0
|
||||
Raw Info:
|
||||
bustype: 3 (USB)
|
||||
vendor: 0x093a
|
||||
product: 0x2510
|
||||
...
|
49
Documentation/hid/hidreport-parsing.rst
Normal file
49
Documentation/hid/hidreport-parsing.rst
Normal file
@ -0,0 +1,49 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
========================================
|
||||
Manual parsing of HID report descriptors
|
||||
========================================
|
||||
|
||||
Consider again the mouse HID report descriptor
|
||||
introduced in Documentation/hid/hidintro.rst::
|
||||
|
||||
$ hexdump -C /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor
|
||||
00000000 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 |..............).|
|
||||
00000010 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 |..%.u.....u.....|
|
||||
00000020 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 |...0.1.8..%.u...|
|
||||
00000030 81 06 c0 c0 |....|
|
||||
00000034
|
||||
|
||||
and try to parse it by hand.
|
||||
|
||||
Start with the first number, 0x05: it carries 2 bits for the
|
||||
length of the item, 2 bits for the type of the item and 4 bits for the
|
||||
function::
|
||||
|
||||
+----------+
|
||||
| 00000101 |
|
||||
+----------+
|
||||
^^
|
||||
---- Length of data (see HID spec 6.2.2.2)
|
||||
^^
|
||||
------ Type of the item (see HID spec 6.2.2.2, then jump to 6.2.2.7)
|
||||
^^^^
|
||||
--------- Function of the item (see HID spec 6.2.2.7, then HUT Sec 3)
|
||||
|
||||
In our case, the length is 1 byte, the type is ``Global`` and the
|
||||
function is ``Usage Page``, thus for parsing the value 0x01 in the second byte
|
||||
we need to refer to HUT Sec 3.
|
||||
|
||||
The second number is the actual data, and its meaning can be found in
|
||||
the HUT. We have a ``Usage Page``, thus we need to refer to HUT
|
||||
Sec. 3, "Usage Pages"; from there, one sees that ``0x01`` stands for
|
||||
``Generic Desktop Page``.
|
||||
|
||||
Moving now to the second two bytes, and following the same scheme,
|
||||
``0x09`` (i.e. ``00001001``) will be followed by one byte (``01``)
|
||||
and is a ``Local`` item (``10``). Thus, the meaning of the remaining four bits
|
||||
(``0000``) is given in the HID spec Sec. 6.2.2.8 "Local Items", so that
|
||||
we have a ``Usage``. From HUT, Sec. 4, "Generic Desktop Page", we see that
|
||||
0x02 stands for ``Mouse``.
|
||||
|
||||
The following numbers can be parsed in the same way.
|
@ -7,6 +7,7 @@ Human Interface Devices (HID)
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
hidintro
|
||||
hiddev
|
||||
hidraw
|
||||
hid-sensor
|
||||
|
@ -21987,7 +21987,7 @@ F: Documentation/admin-guide/ufs.rst
|
||||
F: fs/ufs/
|
||||
|
||||
UHID USERSPACE HID IO DRIVER
|
||||
M: David Rheinsberg <david.rheinsberg@gmail.com>
|
||||
M: David Rheinsberg <david@readahead.eu>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/uhid.c
|
||||
@ -23159,7 +23159,7 @@ S: Maintained
|
||||
F: drivers/rtc/rtc-sd3078.c
|
||||
|
||||
WIIMOTE HID DRIVER
|
||||
M: David Rheinsberg <david.rheinsberg@gmail.com>
|
||||
M: David Rheinsberg <david@readahead.eu>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/hid-wiimote*
|
||||
|
@ -412,6 +412,13 @@ config HID_GOOGLE_HAMMER
|
||||
help
|
||||
Say Y here if you have a Google Hammer device.
|
||||
|
||||
config HID_GOOGLE_STADIA_FF
|
||||
tristate "Google Stadia force feedback"
|
||||
select INPUT_FF_MEMLESS
|
||||
help
|
||||
Say Y here if you want to enable force feedback support for the Google
|
||||
Stadia controller.
|
||||
|
||||
config HID_VIVALDI
|
||||
tristate "Vivaldi Keyboard"
|
||||
select HID_VIVALDI_COMMON
|
||||
@ -1066,9 +1073,11 @@ config STEAM_FF
|
||||
Deck.
|
||||
|
||||
config HID_STEELSERIES
|
||||
tristate "Steelseries SRW-S1 steering wheel support"
|
||||
tristate "Steelseries devices support"
|
||||
depends on USB_HID
|
||||
help
|
||||
Support for Steelseries SRW-S1 steering wheel
|
||||
Support for Steelseries SRW-S1 steering wheel, and the Steelseries
|
||||
Arctis 1 Wireless for XBox headset.
|
||||
|
||||
config HID_SUNPLUS
|
||||
tristate "Sunplus wireless desktop"
|
||||
|
@ -55,6 +55,7 @@ obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
|
||||
obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o
|
||||
obj-$(CONFIG_HID_VIVALDI_COMMON) += hid-vivaldi-common.o
|
||||
obj-$(CONFIG_HID_GOOGLE_HAMMER) += hid-google-hammer.o
|
||||
obj-$(CONFIG_HID_GOOGLE_STADIA_FF) += hid-google-stadiaff.o
|
||||
obj-$(CONFIG_HID_VIVALDI) += hid-vivaldi.o
|
||||
obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
|
||||
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
|
||||
|
@ -343,7 +343,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
|
||||
{ "SONiX USB DEVICE" },
|
||||
{ "Keychron" },
|
||||
{ "AONE" },
|
||||
{ "GANSS" }
|
||||
{ "GANSS" },
|
||||
{ "Hailuck" },
|
||||
};
|
||||
|
||||
static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
|
||||
|
@ -16,14 +16,14 @@
|
||||
* https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf
|
||||
*/
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/hidraw.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include "hid-ids.h"
|
||||
|
||||
@ -31,6 +31,8 @@
|
||||
#define CP2112_GPIO_CONFIG_LENGTH 5
|
||||
#define CP2112_GPIO_GET_LENGTH 2
|
||||
#define CP2112_GPIO_SET_LENGTH 3
|
||||
#define CP2112_GPIO_MAX_GPIO 8
|
||||
#define CP2112_GPIO_ALL_GPIO_MASK GENMASK(7, 0)
|
||||
|
||||
enum {
|
||||
CP2112_GPIO_CONFIG = 0x02,
|
||||
@ -163,19 +165,17 @@ struct cp2112_device {
|
||||
atomic_t read_avail;
|
||||
atomic_t xfer_avail;
|
||||
struct gpio_chip gc;
|
||||
struct irq_chip irq;
|
||||
u8 *in_out_buffer;
|
||||
struct mutex lock;
|
||||
|
||||
struct gpio_desc *desc[8];
|
||||
bool gpio_poll;
|
||||
struct delayed_work gpio_poll_worker;
|
||||
unsigned long irq_mask;
|
||||
u8 gpio_prev_state;
|
||||
};
|
||||
|
||||
static int gpio_push_pull = 0xFF;
|
||||
module_param(gpio_push_pull, int, S_IRUGO | S_IWUSR);
|
||||
static int gpio_push_pull = CP2112_GPIO_ALL_GPIO_MASK;
|
||||
module_param(gpio_push_pull, int, 0644);
|
||||
MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask");
|
||||
|
||||
static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
||||
@ -197,7 +197,7 @@ static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
buf[1] &= ~(1 << offset);
|
||||
buf[1] &= ~BIT(offset);
|
||||
buf[2] = gpio_push_pull;
|
||||
|
||||
ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
|
||||
@ -227,8 +227,8 @@ static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
mutex_lock(&dev->lock);
|
||||
|
||||
buf[0] = CP2112_GPIO_SET;
|
||||
buf[1] = value ? 0xff : 0;
|
||||
buf[2] = 1 << offset;
|
||||
buf[1] = value ? CP2112_GPIO_ALL_GPIO_MASK : 0;
|
||||
buf[2] = BIT(offset);
|
||||
|
||||
ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf,
|
||||
CP2112_GPIO_SET_LENGTH, HID_FEATURE_REPORT,
|
||||
@ -532,15 +532,13 @@ static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
||||
hid_dbg(hdev, "I2C %d messages\n", num);
|
||||
|
||||
if (num == 1) {
|
||||
hid_dbg(hdev, "I2C %s %#04x len %d\n",
|
||||
str_read_write(msgs->flags & I2C_M_RD), msgs->addr, msgs->len);
|
||||
if (msgs->flags & I2C_M_RD) {
|
||||
hid_dbg(hdev, "I2C read %#04x len %d\n",
|
||||
msgs->addr, msgs->len);
|
||||
read_length = msgs->len;
|
||||
read_buf = msgs->buf;
|
||||
count = cp2112_read_req(buf, msgs->addr, msgs->len);
|
||||
} else {
|
||||
hid_dbg(hdev, "I2C write %#04x len %d\n",
|
||||
msgs->addr, msgs->len);
|
||||
count = cp2112_i2c_write_req(buf, msgs->addr,
|
||||
msgs->buf, msgs->len);
|
||||
}
|
||||
@ -648,7 +646,7 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
int ret;
|
||||
|
||||
hid_dbg(hdev, "%s addr 0x%x flags 0x%x cmd 0x%x size %d\n",
|
||||
read_write == I2C_SMBUS_WRITE ? "write" : "read",
|
||||
str_write_read(read_write == I2C_SMBUS_WRITE),
|
||||
addr, flags, command, size);
|
||||
|
||||
switch (size) {
|
||||
@ -895,7 +893,7 @@ static ssize_t name##_show(struct device *kdev, \
|
||||
int ret = cp2112_get_usb_config(hdev, &cfg); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
return scnprintf(buf, PAGE_SIZE, format, ##__VA_ARGS__); \
|
||||
return sysfs_emit(buf, format, ##__VA_ARGS__); \
|
||||
} \
|
||||
static DEVICE_ATTR_RW(name);
|
||||
|
||||
@ -946,18 +944,10 @@ CP2112_CONFIG_ATTR(release_version, ({
|
||||
|
||||
#undef CP2112_CONFIG_ATTR
|
||||
|
||||
struct cp2112_pstring_attribute {
|
||||
struct device_attribute attr;
|
||||
unsigned char report;
|
||||
};
|
||||
|
||||
static ssize_t pstr_store(struct device *kdev,
|
||||
struct device_attribute *kattr, const char *buf,
|
||||
size_t count)
|
||||
static ssize_t pstr_store(struct device *kdev, struct device_attribute *kattr,
|
||||
const char *buf, size_t count, int number)
|
||||
{
|
||||
struct hid_device *hdev = to_hid_device(kdev);
|
||||
struct cp2112_pstring_attribute *attr =
|
||||
container_of(kattr, struct cp2112_pstring_attribute, attr);
|
||||
struct cp2112_string_report report;
|
||||
int ret;
|
||||
|
||||
@ -965,7 +955,7 @@ static ssize_t pstr_store(struct device *kdev,
|
||||
|
||||
ret = utf8s_to_utf16s(buf, count, UTF16_LITTLE_ENDIAN,
|
||||
report.string, ARRAY_SIZE(report.string));
|
||||
report.report = attr->report;
|
||||
report.report = number;
|
||||
report.length = ret * sizeof(report.string[0]) + 2;
|
||||
report.type = USB_DT_STRING;
|
||||
|
||||
@ -983,17 +973,15 @@ static ssize_t pstr_store(struct device *kdev,
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pstr_show(struct device *kdev,
|
||||
struct device_attribute *kattr, char *buf)
|
||||
static ssize_t pstr_show(struct device *kdev, struct device_attribute *kattr,
|
||||
char *buf, int number)
|
||||
{
|
||||
struct hid_device *hdev = to_hid_device(kdev);
|
||||
struct cp2112_pstring_attribute *attr =
|
||||
container_of(kattr, struct cp2112_pstring_attribute, attr);
|
||||
struct cp2112_string_report report;
|
||||
u8 length;
|
||||
int ret;
|
||||
|
||||
ret = cp2112_hid_get(hdev, attr->report, (u8 *)&report.contents,
|
||||
ret = cp2112_hid_get(hdev, number, (u8 *)&report.contents,
|
||||
sizeof(report.contents), HID_FEATURE_REPORT);
|
||||
if (ret < 3) {
|
||||
hid_err(hdev, "error reading %s string: %d\n", kattr->attr.name,
|
||||
@ -1018,10 +1006,16 @@ static ssize_t pstr_show(struct device *kdev,
|
||||
}
|
||||
|
||||
#define CP2112_PSTR_ATTR(name, _report) \
|
||||
static struct cp2112_pstring_attribute dev_attr_##name = { \
|
||||
.attr = __ATTR(name, (S_IWUSR | S_IRUGO), pstr_show, pstr_store), \
|
||||
.report = _report, \
|
||||
};
|
||||
static ssize_t name##_store(struct device *kdev, struct device_attribute *kattr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
return pstr_store(kdev, kattr, buf, count, _report); \
|
||||
} \
|
||||
static ssize_t name##_show(struct device *kdev, struct device_attribute *kattr, char *buf) \
|
||||
{ \
|
||||
return pstr_show(kdev, kattr, buf, _report); \
|
||||
} \
|
||||
static DEVICE_ATTR_RW(name);
|
||||
|
||||
CP2112_PSTR_ATTR(manufacturer, CP2112_MANUFACTURER_STRING);
|
||||
CP2112_PSTR_ATTR(product, CP2112_PRODUCT_STRING);
|
||||
@ -1036,9 +1030,9 @@ static const struct attribute_group cp2112_attr_group = {
|
||||
&dev_attr_max_power.attr,
|
||||
&dev_attr_power_mode.attr,
|
||||
&dev_attr_release_version.attr,
|
||||
&dev_attr_manufacturer.attr.attr,
|
||||
&dev_attr_product.attr.attr,
|
||||
&dev_attr_serial.attr.attr,
|
||||
&dev_attr_manufacturer.attr,
|
||||
&dev_attr_product.attr,
|
||||
&dev_attr_serial.attr,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
@ -1063,7 +1057,7 @@ static void chmod_sysfs_attrs(struct hid_device *hdev)
|
||||
}
|
||||
|
||||
for (attr = cp2112_attr_group.attrs; *attr; ++attr) {
|
||||
umode_t mode = (buf[1] & 1) ? S_IWUSR | S_IRUGO : S_IRUGO;
|
||||
umode_t mode = (buf[1] & 1) ? 0644 : 0444;
|
||||
ret = sysfs_chmod_file(&hdev->dev.kobj, *attr, mode);
|
||||
if (ret < 0)
|
||||
hid_err(hdev, "error chmoding sysfs file %s\n",
|
||||
@ -1080,16 +1074,20 @@ static void cp2112_gpio_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct cp2112_device *dev = gpiochip_get_data(gc);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
|
||||
__clear_bit(d->hwirq, &dev->irq_mask);
|
||||
__clear_bit(hwirq, &dev->irq_mask);
|
||||
gpiochip_disable_irq(gc, hwirq);
|
||||
}
|
||||
|
||||
static void cp2112_gpio_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct cp2112_device *dev = gpiochip_get_data(gc);
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
|
||||
__set_bit(d->hwirq, &dev->irq_mask);
|
||||
gpiochip_enable_irq(gc, hwirq);
|
||||
__set_bit(hwirq, &dev->irq_mask);
|
||||
}
|
||||
|
||||
static void cp2112_gpio_poll_callback(struct work_struct *work)
|
||||
@ -1098,7 +1096,6 @@ static void cp2112_gpio_poll_callback(struct work_struct *work)
|
||||
gpio_poll_worker.work);
|
||||
struct irq_data *d;
|
||||
u8 gpio_mask;
|
||||
u8 virqs = (u8)dev->irq_mask;
|
||||
u32 irq_type;
|
||||
int irq, virq, ret;
|
||||
|
||||
@ -1109,15 +1106,10 @@ static void cp2112_gpio_poll_callback(struct work_struct *work)
|
||||
goto exit;
|
||||
|
||||
gpio_mask = ret;
|
||||
|
||||
while (virqs) {
|
||||
virq = ffs(virqs) - 1;
|
||||
virqs &= ~BIT(virq);
|
||||
|
||||
if (!dev->gc.to_irq)
|
||||
break;
|
||||
|
||||
irq = dev->gc.to_irq(&dev->gc, virq);
|
||||
for_each_set_bit(virq, &dev->irq_mask, CP2112_GPIO_MAX_GPIO) {
|
||||
irq = irq_find_mapping(dev->gc.irq.domain, virq);
|
||||
if (!irq)
|
||||
continue;
|
||||
|
||||
d = irq_get_irq_data(irq);
|
||||
if (!d)
|
||||
@ -1175,6 +1167,7 @@ static void cp2112_gpio_irq_shutdown(struct irq_data *d)
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct cp2112_device *dev = gpiochip_get_data(gc);
|
||||
|
||||
cp2112_gpio_irq_mask(d);
|
||||
cancel_delayed_work_sync(&dev->gpio_poll_worker);
|
||||
}
|
||||
|
||||
@ -1183,50 +1176,17 @@ static int cp2112_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused cp2112_allocate_irq(struct cp2112_device *dev,
|
||||
int pin)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (dev->desc[pin])
|
||||
return -EINVAL;
|
||||
|
||||
dev->desc[pin] = gpiochip_request_own_desc(&dev->gc, pin,
|
||||
"HID/I2C:Event",
|
||||
GPIO_ACTIVE_HIGH,
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(dev->desc[pin])) {
|
||||
dev_err(dev->gc.parent, "Failed to request GPIO\n");
|
||||
return PTR_ERR(dev->desc[pin]);
|
||||
}
|
||||
|
||||
ret = cp2112_gpio_direction_input(&dev->gc, pin);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->gc.parent, "Failed to set GPIO to input dir\n");
|
||||
goto err_desc;
|
||||
}
|
||||
|
||||
ret = gpiochip_lock_as_irq(&dev->gc, pin);
|
||||
if (ret) {
|
||||
dev_err(dev->gc.parent, "Failed to lock GPIO as interrupt\n");
|
||||
goto err_desc;
|
||||
}
|
||||
|
||||
ret = gpiod_to_irq(dev->desc[pin]);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->gc.parent, "Failed to translate GPIO to IRQ\n");
|
||||
goto err_lock;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err_lock:
|
||||
gpiochip_unlock_as_irq(&dev->gc, pin);
|
||||
err_desc:
|
||||
gpiochip_free_own_desc(dev->desc[pin]);
|
||||
dev->desc[pin] = NULL;
|
||||
return ret;
|
||||
}
|
||||
static const struct irq_chip cp2112_gpio_irqchip = {
|
||||
.name = "cp2112-gpio",
|
||||
.irq_startup = cp2112_gpio_irq_startup,
|
||||
.irq_shutdown = cp2112_gpio_irq_shutdown,
|
||||
.irq_ack = cp2112_gpio_irq_ack,
|
||||
.irq_mask = cp2112_gpio_irq_mask,
|
||||
.irq_unmask = cp2112_gpio_irq_unmask,
|
||||
.irq_set_type = cp2112_gpio_irq_type,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
|
||||
GPIOCHIP_IRQ_RESOURCE_HELPERS,
|
||||
};
|
||||
|
||||
static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
@ -1333,21 +1293,12 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
dev->gc.set = cp2112_gpio_set;
|
||||
dev->gc.get = cp2112_gpio_get;
|
||||
dev->gc.base = -1;
|
||||
dev->gc.ngpio = 8;
|
||||
dev->gc.ngpio = CP2112_GPIO_MAX_GPIO;
|
||||
dev->gc.can_sleep = 1;
|
||||
dev->gc.parent = &hdev->dev;
|
||||
|
||||
dev->irq.name = "cp2112-gpio";
|
||||
dev->irq.irq_startup = cp2112_gpio_irq_startup;
|
||||
dev->irq.irq_shutdown = cp2112_gpio_irq_shutdown;
|
||||
dev->irq.irq_ack = cp2112_gpio_irq_ack;
|
||||
dev->irq.irq_mask = cp2112_gpio_irq_mask;
|
||||
dev->irq.irq_unmask = cp2112_gpio_irq_unmask;
|
||||
dev->irq.irq_set_type = cp2112_gpio_irq_type;
|
||||
dev->irq.flags = IRQCHIP_MASK_ON_SUSPEND;
|
||||
|
||||
girq = &dev->gc.irq;
|
||||
girq->chip = &dev->irq;
|
||||
gpio_irq_chip_set_chip(girq, &cp2112_gpio_irqchip);
|
||||
/* The event comes from the outside so no parent handler */
|
||||
girq->parent_handler = NULL;
|
||||
girq->num_parents = 0;
|
||||
@ -1389,7 +1340,6 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
static void cp2112_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct cp2112_device *dev = hid_get_drvdata(hdev);
|
||||
int i;
|
||||
|
||||
sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group);
|
||||
i2c_del_adapter(&dev->adap);
|
||||
@ -1399,11 +1349,6 @@ static void cp2112_remove(struct hid_device *hdev)
|
||||
cancel_delayed_work_sync(&dev->gpio_poll_worker);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dev->desc); i++) {
|
||||
gpiochip_unlock_as_irq(&dev->gc, i);
|
||||
gpiochip_free_own_desc(dev->desc[i]);
|
||||
}
|
||||
|
||||
gpiochip_remove(&dev->gc);
|
||||
/* i2c_del_adapter has finished removing all i2c devices from our
|
||||
* adapter. Well behaved devices should no longer call our cp2112_xfer
|
||||
|
158
drivers/hid/hid-google-stadiaff.c
Normal file
158
drivers/hid/hid-google-stadiaff.c
Normal file
@ -0,0 +1,158 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Stadia controller rumble support.
|
||||
*
|
||||
* Copyright 2023 Google LLC
|
||||
*/
|
||||
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define STADIA_FF_REPORT_ID 5
|
||||
|
||||
struct stadiaff_device {
|
||||
struct hid_device *hid;
|
||||
struct hid_report *report;
|
||||
spinlock_t lock;
|
||||
bool removed;
|
||||
uint16_t strong_magnitude;
|
||||
uint16_t weak_magnitude;
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
static void stadiaff_work(struct work_struct *work)
|
||||
{
|
||||
struct stadiaff_device *stadiaff =
|
||||
container_of(work, struct stadiaff_device, work);
|
||||
struct hid_field *rumble_field = stadiaff->report->field[0];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&stadiaff->lock, flags);
|
||||
rumble_field->value[0] = stadiaff->strong_magnitude;
|
||||
rumble_field->value[1] = stadiaff->weak_magnitude;
|
||||
spin_unlock_irqrestore(&stadiaff->lock, flags);
|
||||
|
||||
hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
||||
static int stadiaff_play(struct input_dev *dev, void *data,
|
||||
struct ff_effect *effect)
|
||||
{
|
||||
struct hid_device *hid = input_get_drvdata(dev);
|
||||
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&stadiaff->lock, flags);
|
||||
if (!stadiaff->removed) {
|
||||
stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude;
|
||||
stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude;
|
||||
schedule_work(&stadiaff->work);
|
||||
}
|
||||
spin_unlock_irqrestore(&stadiaff->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stadiaff_init(struct hid_device *hid)
|
||||
{
|
||||
struct stadiaff_device *stadiaff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *dev;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
|
||||
report = hid_validate_values(hid, HID_OUTPUT_REPORT,
|
||||
STADIA_FF_REPORT_ID, 0, 2);
|
||||
if (!report)
|
||||
return -ENODEV;
|
||||
|
||||
stadiaff = devm_kzalloc(&hid->dev, sizeof(struct stadiaff_device),
|
||||
GFP_KERNEL);
|
||||
if (!stadiaff)
|
||||
return -ENOMEM;
|
||||
|
||||
hid_set_drvdata(hid, stadiaff);
|
||||
|
||||
input_set_capability(dev, EV_FF, FF_RUMBLE);
|
||||
|
||||
error = input_ff_create_memless(dev, NULL, stadiaff_play);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
stadiaff->removed = false;
|
||||
stadiaff->hid = hid;
|
||||
stadiaff->report = report;
|
||||
INIT_WORK(&stadiaff->work, stadiaff_work);
|
||||
spin_lock_init(&stadiaff->lock);
|
||||
|
||||
hid_info(hid, "Force Feedback for Google Stadia controller\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stadiaff_init(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "force feedback init failed\n");
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stadia_remove(struct hid_device *hid)
|
||||
{
|
||||
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&stadiaff->lock, flags);
|
||||
stadiaff->removed = true;
|
||||
spin_unlock_irqrestore(&stadiaff->lock, flags);
|
||||
|
||||
cancel_work_sync(&stadiaff->work);
|
||||
hid_hw_stop(hid);
|
||||
}
|
||||
|
||||
static const struct hid_device_id stadia_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, stadia_devices);
|
||||
|
||||
static struct hid_driver stadia_driver = {
|
||||
.name = "stadia",
|
||||
.id_table = stadia_devices,
|
||||
.probe = stadia_probe,
|
||||
.remove = stadia_remove,
|
||||
};
|
||||
module_hid_driver(stadia_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
@ -531,6 +531,7 @@
|
||||
#define USB_DEVICE_ID_GOOGLE_DON 0x5050
|
||||
#define USB_DEVICE_ID_GOOGLE_EEL 0x5057
|
||||
#define USB_DEVICE_ID_GOOGLE_JEWEL 0x5061
|
||||
#define USB_DEVICE_ID_GOOGLE_STADIA 0x9400
|
||||
|
||||
#define USB_VENDOR_ID_GOTOP 0x08f2
|
||||
#define USB_DEVICE_ID_SUPER_Q2 0x007f
|
||||
@ -866,6 +867,7 @@
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc547
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
|
||||
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
|
||||
#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626
|
||||
|
@ -358,6 +358,9 @@ static const struct hid_device_id hid_battery_quirks[] = {
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
|
||||
USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
|
||||
HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
|
||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
|
||||
USB_DEVICE_ID_ELECOM_BM084),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
@ -988,6 +991,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
return;
|
||||
|
||||
case 0x3c: /* Invert */
|
||||
device->quirks &= ~HID_QUIRK_NOINVERT;
|
||||
map_key_clear(BTN_TOOL_RUBBER);
|
||||
break;
|
||||
|
||||
@ -1013,9 +1017,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
case 0x45: /* ERASER */
|
||||
/*
|
||||
* This event is reported when eraser tip touches the surface.
|
||||
* Actual eraser (BTN_TOOL_RUBBER) is set by Invert usage when
|
||||
* tool gets in proximity.
|
||||
* Actual eraser (BTN_TOOL_RUBBER) is set and released either
|
||||
* by Invert if tool reports proximity or by Eraser directly.
|
||||
*/
|
||||
if (!test_bit(BTN_TOOL_RUBBER, input->keybit)) {
|
||||
device->quirks |= HID_QUIRK_NOINVERT;
|
||||
set_bit(BTN_TOOL_RUBBER, input->keybit);
|
||||
}
|
||||
map_key_clear(BTN_TOUCH);
|
||||
break;
|
||||
|
||||
@ -1580,6 +1588,15 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
|
||||
else if (report->tool != BTN_TOOL_RUBBER)
|
||||
/* value is off, tool is not rubber, ignore */
|
||||
return;
|
||||
else if (*quirks & HID_QUIRK_NOINVERT &&
|
||||
!test_bit(BTN_TOUCH, input->key)) {
|
||||
/*
|
||||
* There is no invert to release the tool, let hid_input
|
||||
* send BTN_TOUCH with scancode and release the tool after.
|
||||
*/
|
||||
hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
|
||||
return;
|
||||
}
|
||||
|
||||
/* let hid-input set BTN_TOUCH */
|
||||
break;
|
||||
|
@ -1285,6 +1285,9 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
* 50 msec should gives enough time to the receiver to be ready.
|
||||
*/
|
||||
msleep(50);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1306,7 +1309,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
buf[5] = 0x09;
|
||||
buf[6] = 0x00;
|
||||
|
||||
hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf,
|
||||
retval = hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf,
|
||||
HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT,
|
||||
HID_REQ_SET_REPORT);
|
||||
|
||||
@ -1692,11 +1695,12 @@ static int logi_dj_raw_event(struct hid_device *hdev,
|
||||
}
|
||||
/*
|
||||
* Mouse-only receivers send unnumbered mouse data. The 27 MHz
|
||||
* receiver uses 6 byte packets, the nano receiver 8 bytes.
|
||||
* receiver uses 6 byte packets, the nano receiver 8 bytes,
|
||||
* the lightspeed receiver (Pro X Superlight) 13 bytes.
|
||||
*/
|
||||
if (djrcv_dev->unnumbered_application == HID_GD_MOUSE &&
|
||||
size <= 8) {
|
||||
u8 mouse_report[9];
|
||||
size <= 13){
|
||||
u8 mouse_report[14];
|
||||
|
||||
/* Prepend report id */
|
||||
mouse_report[0] = REPORT_TYPE_MOUSE;
|
||||
@ -1980,6 +1984,10 @@ static const struct hid_device_id logi_dj_receivers[] = {
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1),
|
||||
.driver_data = recvr_type_gaming_hidpp},
|
||||
{ /* Logitech lightspeed receiver (0xc547) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2),
|
||||
.driver_data = recvr_type_gaming_hidpp},
|
||||
|
||||
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
|
||||
|
@ -228,7 +228,7 @@ struct hidpp_device {
|
||||
#define HIDPP20_ERROR_INVALID_ARGS 0x02
|
||||
#define HIDPP20_ERROR_OUT_OF_RANGE 0x03
|
||||
#define HIDPP20_ERROR_HW_ERROR 0x04
|
||||
#define HIDPP20_ERROR_LOGITECH_INTERNAL 0x05
|
||||
#define HIDPP20_ERROR_NOT_ALLOWED 0x05
|
||||
#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06
|
||||
#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07
|
||||
#define HIDPP20_ERROR_BUSY 0x08
|
||||
@ -275,21 +275,22 @@ static int __hidpp_send_report(struct hid_device *hdev,
|
||||
}
|
||||
|
||||
/*
|
||||
* hidpp_send_message_sync() returns 0 in case of success, and something else
|
||||
* in case of a failure.
|
||||
* - If ' something else' is positive, that means that an error has been raised
|
||||
* by the protocol itself.
|
||||
* - If ' something else' is negative, that means that we had a classic error
|
||||
* (-ENOMEM, -EPIPE, etc...)
|
||||
* Effectively send the message to the device, waiting for its answer.
|
||||
*
|
||||
* Must be called with hidpp->send_mutex locked
|
||||
*
|
||||
* Same return protocol than hidpp_send_message_sync():
|
||||
* - success on 0
|
||||
* - negative error means transport error
|
||||
* - positive value means protocol error
|
||||
*/
|
||||
static int hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
struct hidpp_report *message,
|
||||
struct hidpp_report *response)
|
||||
{
|
||||
int ret = -1;
|
||||
int max_retries = 3;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&hidpp->send_mutex);
|
||||
__must_hold(&hidpp->send_mutex);
|
||||
|
||||
hidpp->send_receive_buf = response;
|
||||
hidpp->answer_available = false;
|
||||
@ -300,47 +301,74 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
*/
|
||||
*response = *message;
|
||||
|
||||
for (; max_retries != 0 && ret; max_retries--) {
|
||||
ret = __hidpp_send_report(hidpp->hid_dev, message);
|
||||
|
||||
if (ret) {
|
||||
dbg_hid("__hidpp_send_report returned err: %d\n", ret);
|
||||
memset(response, 0, sizeof(struct hidpp_report));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
|
||||
5*HZ)) {
|
||||
dbg_hid("%s:timeout waiting for response\n", __func__);
|
||||
memset(response, 0, sizeof(struct hidpp_report));
|
||||
ret = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
|
||||
response->rap.sub_id == HIDPP_ERROR) {
|
||||
ret = response->rap.params[1];
|
||||
dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
|
||||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
|
||||
response->fap.feature_index == HIDPP20_ERROR) {
|
||||
ret = response->fap.params[1];
|
||||
if (ret != HIDPP20_ERROR_BUSY) {
|
||||
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
|
||||
break;
|
||||
}
|
||||
dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
|
||||
}
|
||||
ret = __hidpp_send_report(hidpp->hid_dev, message);
|
||||
if (ret) {
|
||||
dbg_hid("__hidpp_send_report returned err: %d\n", ret);
|
||||
memset(response, 0, sizeof(struct hidpp_report));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
|
||||
5*HZ)) {
|
||||
dbg_hid("%s:timeout waiting for response\n", __func__);
|
||||
memset(response, 0, sizeof(struct hidpp_report));
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
|
||||
response->rap.sub_id == HIDPP_ERROR) {
|
||||
ret = response->rap.params[1];
|
||||
dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
|
||||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
|
||||
response->fap.feature_index == HIDPP20_ERROR) {
|
||||
ret = response->fap.params[1];
|
||||
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* hidpp_send_message_sync() returns 0 in case of success, and something else
|
||||
* in case of a failure.
|
||||
*
|
||||
* See __do_hidpp_send_message_sync() for a detailed explanation of the returned
|
||||
* value.
|
||||
*/
|
||||
static int hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
struct hidpp_report *message,
|
||||
struct hidpp_report *response)
|
||||
{
|
||||
int ret;
|
||||
int max_retries = 3;
|
||||
|
||||
mutex_lock(&hidpp->send_mutex);
|
||||
|
||||
do {
|
||||
ret = __do_hidpp_send_message_sync(hidpp, message, response);
|
||||
if (ret != HIDPP20_ERROR_BUSY)
|
||||
break;
|
||||
|
||||
dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
|
||||
} while (--max_retries);
|
||||
|
||||
mutex_unlock(&hidpp->send_mutex);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* hidpp_send_fap_command_sync() returns 0 in case of success, and something else
|
||||
* in case of a failure.
|
||||
*
|
||||
* See __do_hidpp_send_message_sync() for a detailed explanation of the returned
|
||||
* value.
|
||||
*/
|
||||
static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
|
||||
u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
|
||||
struct hidpp_report *response)
|
||||
@ -373,6 +401,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* hidpp_send_rap_command_sync() returns 0 in case of success, and something else
|
||||
* in case of a failure.
|
||||
*
|
||||
* See __do_hidpp_send_message_sync() for a detailed explanation of the returned
|
||||
* value.
|
||||
*/
|
||||
static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
|
||||
u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
|
||||
struct hidpp_report *response)
|
||||
@ -4620,6 +4655,8 @@ static const struct hid_device_id hidpp_devices[] = {
|
||||
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
|
||||
{ /* Logitech G Pro Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
|
||||
{ /* Logitech G Pro X Superlight Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) },
|
||||
|
||||
{ /* G935 Gaming Headset */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
|
||||
@ -4647,6 +4684,8 @@ static const struct hid_device_id hidpp_devices[] = {
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
|
||||
{ /* MX Master 3 mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
|
||||
{ /* MX Anywhere 3 mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) },
|
||||
{ /* MX Master 3S mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
|
||||
{}
|
||||
|
@ -1594,7 +1594,6 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app)
|
||||
static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
{
|
||||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
char *name;
|
||||
const char *suffix = NULL;
|
||||
struct mt_report_data *rdata;
|
||||
struct mt_application *mt_application = NULL;
|
||||
@ -1645,15 +1644,9 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
break;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
name = devm_kzalloc(&hi->input->dev,
|
||||
strlen(hdev->name) + strlen(suffix) + 2,
|
||||
GFP_KERNEL);
|
||||
if (name) {
|
||||
sprintf(name, "%s %s", hdev->name, suffix);
|
||||
hi->input->name = name;
|
||||
}
|
||||
}
|
||||
if (suffix)
|
||||
hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
|
||||
"%s %s", hdev->name, suffix);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -6,11 +6,15 @@
|
||||
*/
|
||||
|
||||
#include <linux/hid.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
@ -30,6 +34,8 @@ enum {
|
||||
enum {
|
||||
SHIELD_FW_VERSION_INITIALIZED = 0,
|
||||
SHIELD_BOARD_INFO_INITIALIZED,
|
||||
SHIELD_BATTERY_STATS_INITIALIZED,
|
||||
SHIELD_CHARGER_STATE_INITIALIZED,
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -37,6 +43,7 @@ enum {
|
||||
THUNDERSTRIKE_BOARD_INFO_UPDATE,
|
||||
THUNDERSTRIKE_HAPTICS_UPDATE,
|
||||
THUNDERSTRIKE_LED_UPDATE,
|
||||
THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE,
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -48,10 +55,46 @@ enum {
|
||||
enum {
|
||||
THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION = 1,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_LED = 6,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_BATTERY,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO = 16,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_USB_INIT = 53,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_HAPTICS = 57,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT = 58,
|
||||
THUNDERSTRIKE_HOSTCMD_ID_CHARGER,
|
||||
};
|
||||
|
||||
struct power_supply_dev {
|
||||
struct power_supply *psy;
|
||||
struct power_supply_desc desc;
|
||||
};
|
||||
|
||||
struct thunderstrike_psy_prop_values {
|
||||
int voltage_min;
|
||||
int voltage_now;
|
||||
int voltage_avg;
|
||||
int voltage_boot;
|
||||
int capacity;
|
||||
int status;
|
||||
int charge_type;
|
||||
int temp;
|
||||
};
|
||||
|
||||
static const enum power_supply_property thunderstrike_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MIN,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_AVG,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_BOOT,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_TEMP,
|
||||
POWER_SUPPLY_PROP_TEMP_MIN,
|
||||
POWER_SUPPLY_PROP_TEMP_MAX,
|
||||
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
|
||||
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
|
||||
};
|
||||
|
||||
enum thunderstrike_led_state {
|
||||
@ -60,6 +103,38 @@ enum thunderstrike_led_state {
|
||||
} __packed;
|
||||
static_assert(sizeof(enum thunderstrike_led_state) == 1);
|
||||
|
||||
struct thunderstrike_hostcmd_battery {
|
||||
__le16 voltage_avg;
|
||||
u8 reserved_at_10;
|
||||
__le16 thermistor;
|
||||
__le16 voltage_min;
|
||||
__le16 voltage_boot;
|
||||
__le16 voltage_now;
|
||||
u8 capacity;
|
||||
} __packed;
|
||||
|
||||
enum thunderstrike_charger_type {
|
||||
THUNDERSTRIKE_CHARGER_TYPE_NONE = 0,
|
||||
THUNDERSTRIKE_CHARGER_TYPE_TRICKLE,
|
||||
THUNDERSTRIKE_CHARGER_TYPE_NORMAL,
|
||||
} __packed;
|
||||
static_assert(sizeof(enum thunderstrike_charger_type) == 1);
|
||||
|
||||
enum thunderstrike_charger_state {
|
||||
THUNDERSTRIKE_CHARGER_STATE_UNKNOWN = 0,
|
||||
THUNDERSTRIKE_CHARGER_STATE_DISABLED,
|
||||
THUNDERSTRIKE_CHARGER_STATE_CHARGING,
|
||||
THUNDERSTRIKE_CHARGER_STATE_FULL,
|
||||
THUNDERSTRIKE_CHARGER_STATE_FAILED = 8,
|
||||
} __packed;
|
||||
static_assert(sizeof(enum thunderstrike_charger_state) == 1);
|
||||
|
||||
struct thunderstrike_hostcmd_charger {
|
||||
u8 connected;
|
||||
enum thunderstrike_charger_type type;
|
||||
enum thunderstrike_charger_state state;
|
||||
} __packed;
|
||||
|
||||
struct thunderstrike_hostcmd_board_info {
|
||||
__le16 revision;
|
||||
__le16 serial[7];
|
||||
@ -80,6 +155,8 @@ struct thunderstrike_hostcmd_resp_report {
|
||||
struct thunderstrike_hostcmd_haptics motors;
|
||||
__le16 fw_version;
|
||||
enum thunderstrike_led_state led_state;
|
||||
struct thunderstrike_hostcmd_battery battery;
|
||||
struct thunderstrike_hostcmd_charger charger;
|
||||
u8 payload[30];
|
||||
} __packed;
|
||||
} __packed;
|
||||
@ -109,6 +186,7 @@ static_assert(sizeof(struct thunderstrike_hostcmd_req_report) ==
|
||||
/* Common struct for shield accessories. */
|
||||
struct shield_device {
|
||||
struct hid_device *hdev;
|
||||
struct power_supply_dev battery_dev;
|
||||
|
||||
unsigned long initialized_flags;
|
||||
const char *codename;
|
||||
@ -119,9 +197,17 @@ struct shield_device {
|
||||
} board_info;
|
||||
};
|
||||
|
||||
/*
|
||||
* Non-trivial to uniquely identify Thunderstrike controllers at initialization
|
||||
* time. Use an ID allocator to help with this.
|
||||
*/
|
||||
static DEFINE_IDA(thunderstrike_ida);
|
||||
|
||||
struct thunderstrike {
|
||||
struct shield_device base;
|
||||
|
||||
int id;
|
||||
|
||||
/* Sub-devices */
|
||||
struct input_dev *haptics_dev;
|
||||
struct led_classdev led_dev;
|
||||
@ -133,6 +219,9 @@ struct thunderstrike {
|
||||
spinlock_t haptics_update_lock;
|
||||
u8 led_state : 1;
|
||||
enum thunderstrike_led_state led_value;
|
||||
struct thunderstrike_psy_prop_values psy_stats;
|
||||
spinlock_t psy_stats_lock;
|
||||
struct timer_list psy_stats_timer;
|
||||
struct work_struct hostcmd_req_work;
|
||||
};
|
||||
|
||||
@ -164,7 +253,7 @@ static struct input_dev *shield_allocate_input_dev(struct hid_device *hdev,
|
||||
idev->id.product = hdev->product;
|
||||
idev->id.version = hdev->version;
|
||||
idev->uniq = hdev->uniq;
|
||||
idev->name = devm_kasprintf(&idev->dev, GFP_KERNEL, "%s %s", hdev->name,
|
||||
idev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name,
|
||||
name_suffix);
|
||||
if (!idev->name)
|
||||
goto err_name;
|
||||
@ -247,6 +336,16 @@ static void thunderstrike_hostcmd_req_work_handler(struct work_struct *work)
|
||||
thunderstrike_send_hostcmd_request(ts);
|
||||
}
|
||||
|
||||
if (test_and_clear_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags)) {
|
||||
thunderstrike_hostcmd_req_report_init(
|
||||
report, THUNDERSTRIKE_HOSTCMD_ID_BATTERY);
|
||||
thunderstrike_send_hostcmd_request(ts);
|
||||
|
||||
thunderstrike_hostcmd_req_report_init(
|
||||
report, THUNDERSTRIKE_HOSTCMD_ID_CHARGER);
|
||||
thunderstrike_send_hostcmd_request(ts);
|
||||
}
|
||||
|
||||
if (test_and_clear_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags)) {
|
||||
thunderstrike_hostcmd_req_report_init(
|
||||
report, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO);
|
||||
@ -352,6 +451,93 @@ static void thunderstrike_led_set_brightness(struct led_classdev *led,
|
||||
schedule_work(&ts->hostcmd_req_work);
|
||||
}
|
||||
|
||||
static int thunderstrike_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct shield_device *shield_dev = power_supply_get_drvdata(psy);
|
||||
struct thunderstrike_psy_prop_values prop_values;
|
||||
struct thunderstrike *ts;
|
||||
int ret = 0;
|
||||
|
||||
ts = container_of(shield_dev, struct thunderstrike, base);
|
||||
spin_lock(&ts->psy_stats_lock);
|
||||
prop_values = ts->psy_stats;
|
||||
spin_unlock(&ts->psy_stats_lock);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
val->intval = prop_values.status;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
||||
val->intval = prop_values.charge_type;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
||||
val->intval = prop_values.voltage_min;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||||
val->intval = 2900000; /* 2.9 V */
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||||
val->intval = 2200000; /* 2.2 V */
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
val->intval = prop_values.voltage_now;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
|
||||
val->intval = prop_values.voltage_avg;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
|
||||
val->intval = prop_values.voltage_boot;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = prop_values.capacity;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
val->intval = prop_values.temp;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_MIN:
|
||||
val->intval = 0; /* 0 C */
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_MAX:
|
||||
val->intval = 400; /* 40 C */
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
||||
val->intval = 15; /* 1.5 C */
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
||||
val->intval = 380; /* 38 C */
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void thunderstrike_request_psy_stats(struct thunderstrike *ts)
|
||||
{
|
||||
set_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags);
|
||||
schedule_work(&ts->hostcmd_req_work);
|
||||
}
|
||||
|
||||
static void thunderstrike_psy_stats_timer_handler(struct timer_list *timer)
|
||||
{
|
||||
struct thunderstrike *ts =
|
||||
container_of(timer, struct thunderstrike, psy_stats_timer);
|
||||
|
||||
thunderstrike_request_psy_stats(ts);
|
||||
/* Query battery statistics from device every five minutes */
|
||||
mod_timer(timer, jiffies + 300 * HZ);
|
||||
}
|
||||
|
||||
static void
|
||||
thunderstrike_parse_fw_version_payload(struct shield_device *shield_dev,
|
||||
__le16 fw_version)
|
||||
@ -416,13 +602,138 @@ thunderstrike_parse_led_payload(struct shield_device *shield_dev,
|
||||
hid_dbg(shield_dev->hdev, "Thunderstrike led HOSTCMD response, 0x%02X\n", led_state);
|
||||
}
|
||||
|
||||
static void thunderstrike_parse_battery_payload(
|
||||
struct shield_device *shield_dev,
|
||||
struct thunderstrike_hostcmd_battery *battery)
|
||||
{
|
||||
struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
|
||||
u16 hostcmd_voltage_boot = le16_to_cpu(battery->voltage_boot);
|
||||
u16 hostcmd_voltage_avg = le16_to_cpu(battery->voltage_avg);
|
||||
u16 hostcmd_voltage_min = le16_to_cpu(battery->voltage_min);
|
||||
u16 hostcmd_voltage_now = le16_to_cpu(battery->voltage_now);
|
||||
u16 hostcmd_thermistor = le16_to_cpu(battery->thermistor);
|
||||
int voltage_boot, voltage_avg, voltage_min, voltage_now;
|
||||
struct hid_device *hdev = shield_dev->hdev;
|
||||
u8 capacity = battery->capacity;
|
||||
int temp;
|
||||
|
||||
/* Convert thunderstrike device values to µV and tenths of degree Celsius */
|
||||
voltage_boot = hostcmd_voltage_boot * 1000;
|
||||
voltage_avg = hostcmd_voltage_avg * 1000;
|
||||
voltage_min = hostcmd_voltage_min * 1000;
|
||||
voltage_now = hostcmd_voltage_now * 1000;
|
||||
temp = (1378 - (int)hostcmd_thermistor) * 10 / 19;
|
||||
|
||||
/* Copy converted values */
|
||||
spin_lock(&ts->psy_stats_lock);
|
||||
ts->psy_stats.voltage_boot = voltage_boot;
|
||||
ts->psy_stats.voltage_avg = voltage_avg;
|
||||
ts->psy_stats.voltage_min = voltage_min;
|
||||
ts->psy_stats.voltage_now = voltage_now;
|
||||
ts->psy_stats.capacity = capacity;
|
||||
ts->psy_stats.temp = temp;
|
||||
spin_unlock(&ts->psy_stats_lock);
|
||||
|
||||
set_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags);
|
||||
|
||||
hid_dbg(hdev,
|
||||
"Thunderstrike battery HOSTCMD response, voltage_avg: %u voltage_now: %u\n",
|
||||
hostcmd_voltage_avg, hostcmd_voltage_now);
|
||||
hid_dbg(hdev,
|
||||
"Thunderstrike battery HOSTCMD response, voltage_boot: %u voltage_min: %u\n",
|
||||
hostcmd_voltage_boot, hostcmd_voltage_min);
|
||||
hid_dbg(hdev,
|
||||
"Thunderstrike battery HOSTCMD response, thermistor: %u\n",
|
||||
hostcmd_thermistor);
|
||||
hid_dbg(hdev,
|
||||
"Thunderstrike battery HOSTCMD response, capacity: %u%%\n",
|
||||
capacity);
|
||||
}
|
||||
|
||||
static void thunderstrike_parse_charger_payload(
|
||||
struct shield_device *shield_dev,
|
||||
struct thunderstrike_hostcmd_charger *charger)
|
||||
{
|
||||
struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
|
||||
int charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
|
||||
struct hid_device *hdev = shield_dev->hdev;
|
||||
int status = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
|
||||
switch (charger->type) {
|
||||
case THUNDERSTRIKE_CHARGER_TYPE_NONE:
|
||||
charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||||
break;
|
||||
case THUNDERSTRIKE_CHARGER_TYPE_TRICKLE:
|
||||
charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
||||
break;
|
||||
case THUNDERSTRIKE_CHARGER_TYPE_NORMAL:
|
||||
charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
|
||||
break;
|
||||
default:
|
||||
hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD type, %u\n",
|
||||
charger->type);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (charger->state) {
|
||||
case THUNDERSTRIKE_CHARGER_STATE_UNKNOWN:
|
||||
status = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
break;
|
||||
case THUNDERSTRIKE_CHARGER_STATE_DISABLED:
|
||||
/* Indicates charger is disconnected */
|
||||
break;
|
||||
case THUNDERSTRIKE_CHARGER_STATE_CHARGING:
|
||||
status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
break;
|
||||
case THUNDERSTRIKE_CHARGER_STATE_FULL:
|
||||
status = POWER_SUPPLY_STATUS_FULL;
|
||||
break;
|
||||
case THUNDERSTRIKE_CHARGER_STATE_FAILED:
|
||||
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
hid_err(hdev, "Thunderstrike device failed to charge\n");
|
||||
break;
|
||||
default:
|
||||
hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD state, %u\n",
|
||||
charger->state);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!charger->connected)
|
||||
status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
spin_lock(&ts->psy_stats_lock);
|
||||
ts->psy_stats.charge_type = charge_type;
|
||||
ts->psy_stats.status = status;
|
||||
spin_unlock(&ts->psy_stats_lock);
|
||||
|
||||
set_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags);
|
||||
|
||||
hid_dbg(hdev,
|
||||
"Thunderstrike charger HOSTCMD response, connected: %u, type: %u, state: %u\n",
|
||||
charger->connected, charger->type, charger->state);
|
||||
}
|
||||
|
||||
static inline void thunderstrike_device_init_info(struct shield_device *shield_dev)
|
||||
{
|
||||
struct thunderstrike *ts =
|
||||
container_of(shield_dev, struct thunderstrike, base);
|
||||
|
||||
if (!test_bit(SHIELD_FW_VERSION_INITIALIZED, &shield_dev->initialized_flags))
|
||||
thunderstrike_request_firmware_version(ts);
|
||||
|
||||
if (!test_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags))
|
||||
thunderstrike_request_board_info(ts);
|
||||
|
||||
if (!test_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags) ||
|
||||
!test_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags))
|
||||
thunderstrike_psy_stats_timer_handler(&ts->psy_stats_timer);
|
||||
}
|
||||
|
||||
static int thunderstrike_parse_report(struct shield_device *shield_dev,
|
||||
struct hid_report *report, u8 *data,
|
||||
int size)
|
||||
{
|
||||
struct thunderstrike_hostcmd_resp_report *hostcmd_resp_report;
|
||||
struct thunderstrike *ts =
|
||||
container_of(shield_dev, struct thunderstrike, base);
|
||||
struct hid_device *hdev = shield_dev->hdev;
|
||||
|
||||
switch (report->id) {
|
||||
@ -445,6 +756,10 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev,
|
||||
case THUNDERSTRIKE_HOSTCMD_ID_LED:
|
||||
thunderstrike_parse_led_payload(shield_dev, hostcmd_resp_report->led_state);
|
||||
break;
|
||||
case THUNDERSTRIKE_HOSTCMD_ID_BATTERY:
|
||||
thunderstrike_parse_battery_payload(shield_dev,
|
||||
&hostcmd_resp_report->battery);
|
||||
break;
|
||||
case THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO:
|
||||
thunderstrike_parse_board_info_payload(
|
||||
shield_dev, &hostcmd_resp_report->board_info);
|
||||
@ -453,14 +768,17 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev,
|
||||
thunderstrike_parse_haptics_payload(
|
||||
shield_dev, &hostcmd_resp_report->motors);
|
||||
break;
|
||||
|
||||
case THUNDERSTRIKE_HOSTCMD_ID_USB_INIT:
|
||||
case THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT:
|
||||
/* May block HOSTCMD requests till received initially */
|
||||
thunderstrike_request_firmware_version(ts);
|
||||
thunderstrike_request_board_info(ts);
|
||||
/* Only HOSTCMD that can be triggered without a request */
|
||||
return 0;
|
||||
thunderstrike_device_init_info(shield_dev);
|
||||
break;
|
||||
case THUNDERSTRIKE_HOSTCMD_ID_CHARGER:
|
||||
/* May block HOSTCMD requests till received initially */
|
||||
thunderstrike_device_init_info(shield_dev);
|
||||
|
||||
thunderstrike_parse_charger_payload(
|
||||
shield_dev, &hostcmd_resp_report->charger);
|
||||
break;
|
||||
default:
|
||||
hid_warn(hdev,
|
||||
"Unhandled Thunderstrike HOSTCMD id %d\n",
|
||||
@ -480,7 +798,8 @@ static inline int thunderstrike_led_create(struct thunderstrike *ts)
|
||||
{
|
||||
struct led_classdev *led = &ts->led_dev;
|
||||
|
||||
led->name = "thunderstrike:blue:led";
|
||||
led->name = devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL,
|
||||
"thunderstrike%d:blue:led", ts->id);
|
||||
led->max_brightness = 1;
|
||||
led->flags = LED_CORE_SUSPENDRESUME;
|
||||
led->brightness_get = &thunderstrike_led_get_brightness;
|
||||
@ -489,6 +808,50 @@ static inline int thunderstrike_led_create(struct thunderstrike *ts)
|
||||
return led_classdev_register(&ts->base.hdev->dev, led);
|
||||
}
|
||||
|
||||
static inline int thunderstrike_psy_create(struct shield_device *shield_dev)
|
||||
{
|
||||
struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
|
||||
struct power_supply_config psy_cfg = { .drv_data = shield_dev, };
|
||||
struct hid_device *hdev = shield_dev->hdev;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Set an initial capacity and temperature value to avoid prematurely
|
||||
* triggering alerts. Will be replaced by values queried from initial
|
||||
* HOSTCMD requests.
|
||||
*/
|
||||
ts->psy_stats.capacity = 100;
|
||||
ts->psy_stats.temp = 182;
|
||||
|
||||
shield_dev->battery_dev.desc.properties = thunderstrike_battery_props;
|
||||
shield_dev->battery_dev.desc.num_properties =
|
||||
ARRAY_SIZE(thunderstrike_battery_props);
|
||||
shield_dev->battery_dev.desc.get_property = thunderstrike_battery_get_property;
|
||||
shield_dev->battery_dev.desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
shield_dev->battery_dev.desc.name =
|
||||
devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL,
|
||||
"thunderstrike_%d", ts->id);
|
||||
|
||||
shield_dev->battery_dev.psy = power_supply_register(
|
||||
&hdev->dev, &shield_dev->battery_dev.desc, &psy_cfg);
|
||||
if (IS_ERR(shield_dev->battery_dev.psy)) {
|
||||
hid_err(hdev, "Failed to register Thunderstrike battery device\n");
|
||||
return PTR_ERR(shield_dev->battery_dev.psy);
|
||||
}
|
||||
|
||||
ret = power_supply_powers(shield_dev->battery_dev.psy, &hdev->dev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to associate battery device to Thunderstrike\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
power_supply_unregister(shield_dev->battery_dev.psy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct shield_device *thunderstrike_create(struct hid_device *hdev)
|
||||
{
|
||||
struct shield_device *shield_dev;
|
||||
@ -509,26 +872,47 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev)
|
||||
shield_dev->codename = "Thunderstrike";
|
||||
|
||||
spin_lock_init(&ts->haptics_update_lock);
|
||||
spin_lock_init(&ts->psy_stats_lock);
|
||||
INIT_WORK(&ts->hostcmd_req_work, thunderstrike_hostcmd_req_work_handler);
|
||||
|
||||
hid_set_drvdata(hdev, shield_dev);
|
||||
|
||||
ts->id = ida_alloc(&thunderstrike_ida, GFP_KERNEL);
|
||||
if (ts->id < 0)
|
||||
return ERR_PTR(ts->id);
|
||||
|
||||
ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect);
|
||||
if (IS_ERR(ts->haptics_dev)) {
|
||||
hid_err(hdev, "Failed to create Thunderstrike haptics instance\n");
|
||||
ret = PTR_ERR(ts->haptics_dev);
|
||||
goto err_id;
|
||||
}
|
||||
|
||||
ret = thunderstrike_psy_create(shield_dev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to create Thunderstrike power supply instance\n");
|
||||
goto err_haptics;
|
||||
}
|
||||
|
||||
ret = thunderstrike_led_create(ts);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to create Thunderstrike LED instance\n");
|
||||
return ERR_PTR(ret);
|
||||
goto err_psy;
|
||||
}
|
||||
|
||||
ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect);
|
||||
if (IS_ERR(ts->haptics_dev))
|
||||
goto err;
|
||||
timer_setup(&ts->psy_stats_timer, thunderstrike_psy_stats_timer_handler, 0);
|
||||
|
||||
hid_info(hdev, "Registered Thunderstrike controller\n");
|
||||
return shield_dev;
|
||||
|
||||
err:
|
||||
led_classdev_unregister(&ts->led_dev);
|
||||
return ERR_CAST(ts->haptics_dev);
|
||||
err_psy:
|
||||
power_supply_unregister(shield_dev->battery_dev.psy);
|
||||
err_haptics:
|
||||
if (ts->haptics_dev)
|
||||
input_unregister_device(ts->haptics_dev);
|
||||
err_id:
|
||||
ida_free(&thunderstrike_ida, ts->id);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int android_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
@ -683,8 +1067,7 @@ static int shield_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
goto err_stop;
|
||||
}
|
||||
|
||||
thunderstrike_request_firmware_version(ts);
|
||||
thunderstrike_request_board_info(ts);
|
||||
thunderstrike_device_init_info(shield_dev);
|
||||
|
||||
return ret;
|
||||
|
||||
@ -704,9 +1087,12 @@ static void shield_remove(struct hid_device *hdev)
|
||||
ts = container_of(dev, struct thunderstrike, base);
|
||||
|
||||
hid_hw_close(hdev);
|
||||
led_classdev_unregister(&ts->led_dev);
|
||||
power_supply_unregister(dev->battery_dev.psy);
|
||||
if (ts->haptics_dev)
|
||||
input_unregister_device(ts->haptics_dev);
|
||||
led_classdev_unregister(&ts->led_dev);
|
||||
ida_free(&thunderstrike_ida, ts->id);
|
||||
del_timer_sync(&ts->psy_stats_timer);
|
||||
cancel_work_sync(&ts->hostcmd_req_work);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev,
|
||||
}
|
||||
INIT_LIST_HEAD(&hdev->inputs);
|
||||
|
||||
ret = hid_hw_start(hdev, 0);
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return ret;
|
||||
|
@ -1,8 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID driver for Steelseries SRW-S1
|
||||
* HID driver for Steelseries devices
|
||||
*
|
||||
* Copyright (c) 2013 Simon Wood
|
||||
* Copyright (c) 2023 Bastien Nocera
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -11,10 +12,28 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define STEELSERIES_SRWS1 BIT(0)
|
||||
#define STEELSERIES_ARCTIS_1 BIT(1)
|
||||
|
||||
struct steelseries_device {
|
||||
struct hid_device *hdev;
|
||||
unsigned long quirks;
|
||||
|
||||
struct delayed_work battery_work;
|
||||
spinlock_t lock;
|
||||
bool removed;
|
||||
|
||||
struct power_supply_desc battery_desc;
|
||||
struct power_supply *battery;
|
||||
uint8_t battery_capacity;
|
||||
bool headset_connected;
|
||||
};
|
||||
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
#define SRWS1_NUMBER_LEDS 15
|
||||
@ -353,9 +372,211 @@ static void steelseries_srws1_remove(struct hid_device *hdev)
|
||||
}
|
||||
#endif
|
||||
|
||||
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
|
||||
|
||||
#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
|
||||
static const char arctis_1_battery_request[] = { 0x06, 0x12 };
|
||||
|
||||
static int steelseries_headset_arctis_1_fetch_battery(struct hid_device *hdev)
|
||||
{
|
||||
u8 *write_buf;
|
||||
int ret;
|
||||
|
||||
/* Request battery information */
|
||||
write_buf = kmemdup(arctis_1_battery_request, sizeof(arctis_1_battery_request), GFP_KERNEL);
|
||||
if (!write_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_hw_raw_request(hdev, arctis_1_battery_request[0],
|
||||
write_buf, sizeof(arctis_1_battery_request),
|
||||
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
|
||||
if (ret < sizeof(arctis_1_battery_request)) {
|
||||
hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
|
||||
ret = -ENODATA;
|
||||
}
|
||||
kfree(write_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steelseries_headset_fetch_battery(struct hid_device *hdev)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
int ret = 0;
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_1)
|
||||
ret = steelseries_headset_arctis_1_fetch_battery(hdev);
|
||||
|
||||
if (ret < 0)
|
||||
hid_dbg(hdev,
|
||||
"Battery query failed (err: %d)\n", ret);
|
||||
}
|
||||
|
||||
static void steelseries_headset_battery_timer_tick(struct work_struct *work)
|
||||
{
|
||||
struct steelseries_device *sd = container_of(work,
|
||||
struct steelseries_device, battery_work.work);
|
||||
struct hid_device *hdev = sd->hdev;
|
||||
|
||||
steelseries_headset_fetch_battery(hdev);
|
||||
}
|
||||
|
||||
static int steelseries_headset_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct steelseries_device *sd = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
val->intval = sd->headset_connected ?
|
||||
POWER_SUPPLY_STATUS_DISCHARGING :
|
||||
POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = sd->battery_capacity;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
steelseries_headset_set_wireless_status(struct hid_device *hdev,
|
||||
bool connected)
|
||||
{
|
||||
struct usb_interface *intf;
|
||||
|
||||
if (!hid_is_usb(hdev))
|
||||
return;
|
||||
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
usb_set_wireless_status(intf, connected ?
|
||||
USB_WIRELESS_STATUS_CONNECTED :
|
||||
USB_WIRELESS_STATUS_DISCONNECTED);
|
||||
}
|
||||
|
||||
static enum power_supply_property steelseries_headset_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
};
|
||||
|
||||
static int steelseries_headset_battery_register(struct steelseries_device *sd)
|
||||
{
|
||||
static atomic_t battery_no = ATOMIC_INIT(0);
|
||||
struct power_supply_config battery_cfg = { .drv_data = sd, };
|
||||
unsigned long n;
|
||||
int ret;
|
||||
|
||||
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
sd->battery_desc.properties = steelseries_headset_battery_props;
|
||||
sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
|
||||
sd->battery_desc.get_property = steelseries_headset_battery_get_property;
|
||||
sd->battery_desc.use_for_apm = 0;
|
||||
n = atomic_inc_return(&battery_no) - 1;
|
||||
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
|
||||
"steelseries_headset_battery_%ld", n);
|
||||
if (!sd->battery_desc.name)
|
||||
return -ENOMEM;
|
||||
|
||||
/* avoid the warning of 0% battery while waiting for the first info */
|
||||
steelseries_headset_set_wireless_status(sd->hdev, false);
|
||||
sd->battery_capacity = 100;
|
||||
|
||||
sd->battery = devm_power_supply_register(&sd->hdev->dev,
|
||||
&sd->battery_desc, &battery_cfg);
|
||||
if (IS_ERR(sd->battery)) {
|
||||
ret = PTR_ERR(sd->battery);
|
||||
hid_err(sd->hdev,
|
||||
"%s:power_supply_register failed with error %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
power_supply_powers(sd->battery, &sd->hdev->dev);
|
||||
|
||||
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
|
||||
steelseries_headset_fetch_battery(sd->hdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
struct steelseries_device *sd;
|
||||
int ret;
|
||||
|
||||
sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
|
||||
if (!sd)
|
||||
return -ENOMEM;
|
||||
hid_set_drvdata(hdev, sd);
|
||||
sd->hdev = hdev;
|
||||
sd->quirks = id->driver_data;
|
||||
|
||||
if (sd->quirks & STEELSERIES_SRWS1) {
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
return steelseries_srws1_probe(hdev, id);
|
||||
#else
|
||||
return -ENODEV;
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock_init(&sd->lock);
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (steelseries_headset_battery_register(sd) < 0)
|
||||
hid_err(sd->hdev,
|
||||
"Failed to register battery for headset\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steelseries_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
unsigned long flags;
|
||||
|
||||
if (sd->quirks & STEELSERIES_SRWS1) {
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
steelseries_srws1_remove(hdev);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
sd->removed = true;
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
||||
cancel_delayed_work_sync(&sd->battery_work);
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
|
||||
hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
|
||||
return rdesc;
|
||||
|
||||
if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
|
||||
&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
|
||||
hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
|
||||
@ -365,22 +586,82 @@ static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
static const struct hid_device_id steelseries_srws1_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
|
||||
static int steelseries_headset_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *read_buf,
|
||||
int size)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
int capacity = sd->battery_capacity;
|
||||
bool connected = sd->headset_connected;
|
||||
unsigned long flags;
|
||||
|
||||
/* Not a headset */
|
||||
if (sd->quirks & STEELSERIES_SRWS1)
|
||||
return 0;
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_1) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
|
||||
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
|
||||
memcmp (read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request)))
|
||||
return 0;
|
||||
if (read_buf[2] == 0x01) {
|
||||
connected = false;
|
||||
capacity = 100;
|
||||
} else {
|
||||
connected = true;
|
||||
capacity = read_buf[3];
|
||||
}
|
||||
}
|
||||
|
||||
if (connected != sd->headset_connected) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Connected status changed from %sconnected to %sconnected\n",
|
||||
sd->headset_connected ? "" : "not ",
|
||||
connected ? "" : "not ");
|
||||
sd->headset_connected = connected;
|
||||
steelseries_headset_set_wireless_status(hdev, connected);
|
||||
}
|
||||
|
||||
if (capacity != sd->battery_capacity) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Battery capacity changed from %d%% to %d%%\n",
|
||||
sd->battery_capacity, capacity);
|
||||
sd->battery_capacity = capacity;
|
||||
power_supply_changed(sd->battery);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
if (!sd->removed)
|
||||
schedule_delayed_work(&sd->battery_work,
|
||||
msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id steelseries_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
|
||||
.driver_data = STEELSERIES_SRWS1 },
|
||||
|
||||
{ /* SteelSeries Arctis 1 Wireless for XBox */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6),
|
||||
.driver_data = STEELSERIES_ARCTIS_1 },
|
||||
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices);
|
||||
MODULE_DEVICE_TABLE(hid, steelseries_devices);
|
||||
|
||||
static struct hid_driver steelseries_srws1_driver = {
|
||||
.name = "steelseries_srws1",
|
||||
.id_table = steelseries_srws1_devices,
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
.probe = steelseries_srws1_probe,
|
||||
.remove = steelseries_srws1_remove,
|
||||
#endif
|
||||
.report_fixup = steelseries_srws1_report_fixup
|
||||
static struct hid_driver steelseries_driver = {
|
||||
.name = "steelseries",
|
||||
.id_table = steelseries_devices,
|
||||
.probe = steelseries_probe,
|
||||
.remove = steelseries_remove,
|
||||
.report_fixup = steelseries_srws1_report_fixup,
|
||||
.raw_event = steelseries_headset_raw_event,
|
||||
};
|
||||
|
||||
module_hid_driver(steelseries_srws1_driver);
|
||||
module_hid_driver(steelseries_driver);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
|
||||
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
|
||||
|
@ -85,10 +85,8 @@ static int uclogic_input_configured(struct hid_device *hdev,
|
||||
{
|
||||
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
struct uclogic_params *params = &drvdata->params;
|
||||
char *name;
|
||||
const char *suffix = NULL;
|
||||
struct hid_field *field;
|
||||
size_t len;
|
||||
size_t i;
|
||||
const struct uclogic_params_frame *frame;
|
||||
|
||||
@ -146,14 +144,9 @@ static int uclogic_input_configured(struct hid_device *hdev,
|
||||
}
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
len = strlen(hdev->name) + 2 + strlen(suffix);
|
||||
name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL);
|
||||
if (name) {
|
||||
snprintf(name, len, "%s %s", hdev->name, suffix);
|
||||
hi->input->name = name;
|
||||
}
|
||||
}
|
||||
if (suffix)
|
||||
hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
|
||||
"%s %s", hdev->name, suffix);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -173,7 +173,6 @@ int wiidebug_init(struct wiimote_data *wdata)
|
||||
{
|
||||
struct wiimote_debug *dbg;
|
||||
unsigned long flags;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
|
||||
if (!dbg)
|
||||
@ -183,13 +182,9 @@ int wiidebug_init(struct wiimote_data *wdata)
|
||||
|
||||
dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR,
|
||||
dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops);
|
||||
if (!dbg->eeprom)
|
||||
goto err;
|
||||
|
||||
dbg->drm = debugfs_create_file("drm", S_IRUSR,
|
||||
dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops);
|
||||
if (!dbg->drm)
|
||||
goto err_drm;
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wdata->debug = dbg;
|
||||
@ -197,11 +192,6 @@ int wiidebug_init(struct wiimote_data *wdata)
|
||||
|
||||
return 0;
|
||||
|
||||
err_drm:
|
||||
debugfs_remove(dbg->eeprom);
|
||||
err:
|
||||
kfree(dbg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void wiidebug_deinit(struct wiimote_data *wdata)
|
||||
|
@ -18,9 +18,11 @@
|
||||
#include "i2c-hid.h"
|
||||
|
||||
struct elan_i2c_hid_chip_data {
|
||||
unsigned int post_gpio_reset_delay_ms;
|
||||
unsigned int post_gpio_reset_on_delay_ms;
|
||||
unsigned int post_gpio_reset_off_delay_ms;
|
||||
unsigned int post_power_delay_ms;
|
||||
u16 hid_descriptor_address;
|
||||
const char *main_supply_name;
|
||||
};
|
||||
|
||||
struct i2c_hid_of_elan {
|
||||
@ -38,9 +40,11 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops)
|
||||
container_of(ops, struct i2c_hid_of_elan, ops);
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(ihid_elan->vcc33);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (ihid_elan->vcc33) {
|
||||
ret = regulator_enable(ihid_elan->vcc33);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regulator_enable(ihid_elan->vccio);
|
||||
if (ret) {
|
||||
@ -52,8 +56,8 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops)
|
||||
msleep(ihid_elan->chip_data->post_power_delay_ms);
|
||||
|
||||
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0);
|
||||
if (ihid_elan->chip_data->post_gpio_reset_delay_ms)
|
||||
msleep(ihid_elan->chip_data->post_gpio_reset_delay_ms);
|
||||
if (ihid_elan->chip_data->post_gpio_reset_on_delay_ms)
|
||||
msleep(ihid_elan->chip_data->post_gpio_reset_on_delay_ms);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -64,8 +68,12 @@ static void elan_i2c_hid_power_down(struct i2chid_ops *ops)
|
||||
container_of(ops, struct i2c_hid_of_elan, ops);
|
||||
|
||||
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1);
|
||||
if (ihid_elan->chip_data->post_gpio_reset_off_delay_ms)
|
||||
msleep(ihid_elan->chip_data->post_gpio_reset_off_delay_ms);
|
||||
|
||||
regulator_disable(ihid_elan->vccio);
|
||||
regulator_disable(ihid_elan->vcc33);
|
||||
if (ihid_elan->vcc33)
|
||||
regulator_disable(ihid_elan->vcc33);
|
||||
}
|
||||
|
||||
static int i2c_hid_of_elan_probe(struct i2c_client *client)
|
||||
@ -89,24 +97,42 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client)
|
||||
if (IS_ERR(ihid_elan->vccio))
|
||||
return PTR_ERR(ihid_elan->vccio);
|
||||
|
||||
ihid_elan->vcc33 = devm_regulator_get(&client->dev, "vcc33");
|
||||
if (IS_ERR(ihid_elan->vcc33))
|
||||
return PTR_ERR(ihid_elan->vcc33);
|
||||
|
||||
ihid_elan->chip_data = device_get_match_data(&client->dev);
|
||||
|
||||
if (ihid_elan->chip_data->main_supply_name) {
|
||||
ihid_elan->vcc33 = devm_regulator_get(&client->dev,
|
||||
ihid_elan->chip_data->main_supply_name);
|
||||
if (IS_ERR(ihid_elan->vcc33))
|
||||
return PTR_ERR(ihid_elan->vcc33);
|
||||
}
|
||||
|
||||
return i2c_hid_core_probe(client, &ihid_elan->ops,
|
||||
ihid_elan->chip_data->hid_descriptor_address, 0);
|
||||
}
|
||||
|
||||
static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = {
|
||||
.post_power_delay_ms = 1,
|
||||
.post_gpio_reset_delay_ms = 300,
|
||||
.post_gpio_reset_on_delay_ms = 300,
|
||||
.hid_descriptor_address = 0x0001,
|
||||
.main_supply_name = "vcc33",
|
||||
};
|
||||
|
||||
static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = {
|
||||
.post_power_delay_ms = 1,
|
||||
.post_gpio_reset_on_delay_ms = 200,
|
||||
.post_gpio_reset_off_delay_ms = 65,
|
||||
.hid_descriptor_address = 0x0001,
|
||||
/*
|
||||
* this touchscreen is tightly integrated with the panel and assumes
|
||||
* that the relevant power rails (other than the IO rail) have already
|
||||
* been turned on by the panel driver because we're a panel follower.
|
||||
*/
|
||||
.main_supply_name = NULL,
|
||||
};
|
||||
|
||||
static const struct of_device_id elan_i2c_hid_of_match[] = {
|
||||
{ .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data },
|
||||
{ .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match);
|
||||
|
@ -150,6 +150,7 @@ struct wacom_remote {
|
||||
struct input_dev *input;
|
||||
bool registered;
|
||||
struct wacom_battery battery;
|
||||
ktime_t active_time;
|
||||
} remotes[WACOM_MAX_REMOTES];
|
||||
};
|
||||
|
||||
|
@ -1997,7 +1997,7 @@ static int wacom_initialize_remotes(struct wacom *wacom)
|
||||
spin_lock_init(&remote->remote_lock);
|
||||
|
||||
error = kfifo_alloc(&remote->remote_fifo,
|
||||
5 * sizeof(struct wacom_remote_data),
|
||||
5 * sizeof(struct wacom_remote_work_data),
|
||||
GFP_KERNEL);
|
||||
if (error) {
|
||||
hid_err(wacom->hdev, "failed allocating remote_fifo\n");
|
||||
@ -2523,6 +2523,18 @@ static void wacom_wireless_work(struct work_struct *work)
|
||||
return;
|
||||
}
|
||||
|
||||
static void wacom_remote_destroy_battery(struct wacom *wacom, int index)
|
||||
{
|
||||
struct wacom_remote *remote = wacom->remote;
|
||||
|
||||
if (remote->remotes[index].battery.battery) {
|
||||
devres_release_group(&wacom->hdev->dev,
|
||||
&remote->remotes[index].battery.bat_desc);
|
||||
remote->remotes[index].battery.battery = NULL;
|
||||
remote->remotes[index].active_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
|
||||
{
|
||||
struct wacom_remote *remote = wacom->remote;
|
||||
@ -2537,9 +2549,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
|
||||
remote->remotes[i].registered = false;
|
||||
spin_unlock_irqrestore(&remote->remote_lock, flags);
|
||||
|
||||
if (remote->remotes[i].battery.battery)
|
||||
devres_release_group(&wacom->hdev->dev,
|
||||
&remote->remotes[i].battery.bat_desc);
|
||||
wacom_remote_destroy_battery(wacom, i);
|
||||
|
||||
if (remote->remotes[i].group.name)
|
||||
devres_release_group(&wacom->hdev->dev,
|
||||
@ -2547,7 +2557,6 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
|
||||
|
||||
remote->remotes[i].serial = 0;
|
||||
remote->remotes[i].group.name = NULL;
|
||||
remote->remotes[i].battery.battery = NULL;
|
||||
wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN;
|
||||
}
|
||||
}
|
||||
@ -2632,6 +2641,9 @@ static int wacom_remote_attach_battery(struct wacom *wacom, int index)
|
||||
if (remote->remotes[index].battery.battery)
|
||||
return 0;
|
||||
|
||||
if (!remote->remotes[index].active_time)
|
||||
return 0;
|
||||
|
||||
if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN)
|
||||
return 0;
|
||||
|
||||
@ -2647,17 +2659,19 @@ static void wacom_remote_work(struct work_struct *work)
|
||||
{
|
||||
struct wacom *wacom = container_of(work, struct wacom, remote_work);
|
||||
struct wacom_remote *remote = wacom->remote;
|
||||
struct wacom_remote_data data;
|
||||
ktime_t kt = ktime_get();
|
||||
struct wacom_remote_work_data remote_work_data;
|
||||
unsigned long flags;
|
||||
unsigned int count;
|
||||
u32 serial;
|
||||
u32 work_serial;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&remote->remote_lock, flags);
|
||||
|
||||
count = kfifo_out(&remote->remote_fifo, &data, sizeof(data));
|
||||
count = kfifo_out(&remote->remote_fifo, &remote_work_data,
|
||||
sizeof(remote_work_data));
|
||||
|
||||
if (count != sizeof(data)) {
|
||||
if (count != sizeof(remote_work_data)) {
|
||||
hid_err(wacom->hdev,
|
||||
"workitem triggered without status available\n");
|
||||
spin_unlock_irqrestore(&remote->remote_lock, flags);
|
||||
@ -2670,10 +2684,14 @@ static void wacom_remote_work(struct work_struct *work)
|
||||
spin_unlock_irqrestore(&remote->remote_lock, flags);
|
||||
|
||||
for (i = 0; i < WACOM_MAX_REMOTES; i++) {
|
||||
serial = data.remote[i].serial;
|
||||
if (data.remote[i].connected) {
|
||||
work_serial = remote_work_data.remote[i].serial;
|
||||
if (work_serial) {
|
||||
|
||||
if (remote->remotes[i].serial == serial) {
|
||||
if (kt - remote->remotes[i].active_time > WACOM_REMOTE_BATTERY_TIMEOUT
|
||||
&& remote->remotes[i].active_time != 0)
|
||||
wacom_remote_destroy_battery(wacom, i);
|
||||
|
||||
if (remote->remotes[i].serial == work_serial) {
|
||||
wacom_remote_attach_battery(wacom, i);
|
||||
continue;
|
||||
}
|
||||
@ -2681,7 +2699,7 @@ static void wacom_remote_work(struct work_struct *work)
|
||||
if (remote->remotes[i].serial)
|
||||
wacom_remote_destroy_one(wacom, i);
|
||||
|
||||
wacom_remote_create_one(wacom, serial, i);
|
||||
wacom_remote_create_one(wacom, work_serial, i);
|
||||
|
||||
} else if (remote->remotes[i].serial) {
|
||||
wacom_remote_destroy_one(wacom, i);
|
||||
|
@ -1134,6 +1134,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len)
|
||||
if (index < 0 || !remote->remotes[index].registered)
|
||||
goto out;
|
||||
|
||||
remote->remotes[i].active_time = ktime_get();
|
||||
input = remote->remotes[index].input;
|
||||
|
||||
input_report_key(input, BTN_0, (data[9] & 0x01));
|
||||
@ -1196,22 +1197,20 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len)
|
||||
struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
|
||||
unsigned char *data = wacom_wac->data;
|
||||
struct wacom_remote *remote = wacom->remote;
|
||||
struct wacom_remote_data remote_data;
|
||||
struct wacom_remote_work_data remote_data;
|
||||
unsigned long flags;
|
||||
int i, ret;
|
||||
|
||||
if (data[0] != WACOM_REPORT_DEVICE_LIST)
|
||||
return;
|
||||
|
||||
memset(&remote_data, 0, sizeof(struct wacom_remote_data));
|
||||
memset(&remote_data, 0, sizeof(struct wacom_remote_work_data));
|
||||
|
||||
for (i = 0; i < WACOM_MAX_REMOTES; i++) {
|
||||
int j = i * 6;
|
||||
int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4];
|
||||
bool connected = data[j+2];
|
||||
|
||||
remote_data.remote[i].serial = serial;
|
||||
remote_data.remote[i].connected = connected;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&remote->remote_lock, flags);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define WACOM_NAME_MAX 64
|
||||
#define WACOM_MAX_REMOTES 5
|
||||
#define WACOM_STATUS_UNKNOWN 255
|
||||
#define WACOM_REMOTE_BATTERY_TIMEOUT 21000000000ll
|
||||
|
||||
/* packet length for individual models */
|
||||
#define WACOM_PKGLEN_BBFUN 9
|
||||
@ -327,10 +328,9 @@ struct hid_data {
|
||||
ktime_t time_delayed;
|
||||
};
|
||||
|
||||
struct wacom_remote_data {
|
||||
struct wacom_remote_work_data {
|
||||
struct {
|
||||
u32 serial;
|
||||
bool connected;
|
||||
} remote[WACOM_MAX_REMOTES];
|
||||
};
|
||||
|
||||
|
@ -341,6 +341,29 @@ struct hid_item {
|
||||
*/
|
||||
#define MAX_USBHID_BOOT_QUIRKS 4
|
||||
|
||||
/**
|
||||
* DOC: HID quirks
|
||||
* | @HID_QUIRK_NOTOUCH:
|
||||
* | @HID_QUIRK_IGNORE: ignore this device
|
||||
* | @HID_QUIRK_NOGET:
|
||||
* | @HID_QUIRK_HIDDEV_FORCE:
|
||||
* | @HID_QUIRK_BADPAD:
|
||||
* | @HID_QUIRK_MULTI_INPUT:
|
||||
* | @HID_QUIRK_HIDINPUT_FORCE:
|
||||
* | @HID_QUIRK_ALWAYS_POLL:
|
||||
* | @HID_QUIRK_INPUT_PER_APP:
|
||||
* | @HID_QUIRK_X_INVERT:
|
||||
* | @HID_QUIRK_Y_INVERT:
|
||||
* | @HID_QUIRK_SKIP_OUTPUT_REPORTS:
|
||||
* | @HID_QUIRK_SKIP_OUTPUT_REPORT_ID:
|
||||
* | @HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP:
|
||||
* | @HID_QUIRK_HAVE_SPECIAL_DRIVER:
|
||||
* | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
|
||||
* | @HID_QUIRK_FULLSPEED_INTERVAL:
|
||||
* | @HID_QUIRK_NO_INIT_REPORTS:
|
||||
* | @HID_QUIRK_NO_IGNORE:
|
||||
* | @HID_QUIRK_NO_INPUT_SYNC:
|
||||
*/
|
||||
/* BIT(0) reserved for backward compatibility, was HID_QUIRK_INVERT */
|
||||
#define HID_QUIRK_NOTOUCH BIT(1)
|
||||
#define HID_QUIRK_IGNORE BIT(2)
|
||||
@ -360,6 +383,7 @@ struct hid_item {
|
||||
#define HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP BIT(18)
|
||||
#define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19)
|
||||
#define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20)
|
||||
#define HID_QUIRK_NOINVERT BIT(21)
|
||||
#define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
|
||||
#define HID_QUIRK_NO_INIT_REPORTS BIT(29)
|
||||
#define HID_QUIRK_NO_IGNORE BIT(30)
|
||||
@ -555,9 +579,9 @@ struct hid_input {
|
||||
struct hid_report *report;
|
||||
struct input_dev *input;
|
||||
const char *name;
|
||||
bool registered;
|
||||
struct list_head reports; /* the list of reports */
|
||||
unsigned int application; /* application usage for this input */
|
||||
bool registered;
|
||||
};
|
||||
|
||||
enum hid_type {
|
||||
|
@ -30,6 +30,7 @@ static inline const char *str_read_write(bool v)
|
||||
{
|
||||
return v ? "read" : "write";
|
||||
}
|
||||
#define str_write_read(v) str_read_write(!(v))
|
||||
|
||||
static inline const char *str_on_off(bool v)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user