diff options
28 files changed, 1799 insertions, 263 deletions
@@ -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> diff --git a/Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml b/Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml new file mode 100644 index 000000000000..c5d9e0e919f9 --- /dev/null +++ b/Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml @@ -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>; + }; + }; diff --git a/Documentation/hid/hidintro.rst b/Documentation/hid/hidintro.rst new file mode 100644 index 000000000000..73523e315ebd --- /dev/null +++ b/Documentation/hid/hidintro.rst @@ -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 + ... diff --git a/Documentation/hid/hidreport-parsing.rst b/Documentation/hid/hidreport-parsing.rst new file mode 100644 index 000000000000..1d3c17f29f2b --- /dev/null +++ b/Documentation/hid/hidreport-parsing.rst @@ -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. diff --git a/Documentation/hid/index.rst b/Documentation/hid/index.rst index b2028f382f11..af02cf7cfa82 100644 --- a/Documentation/hid/index.rst +++ b/Documentation/hid/index.rst @@ -7,6 +7,7 @@ Human Interface Devices (HID) .. toctree:: :maxdepth: 1 + hidintro hiddev hidraw hid-sensor diff --git a/MAINTAINERS b/MAINTAINERS index f85a3b41f1ea..fcbb106aaa57 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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* diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e11c1c803676..0cea301cc9a9 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -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" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 7a9e160158f7..8a06d0f840bc 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -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 diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index d7b932925730..3ca45975c686 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -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) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 27cadadda7c9..54c33a24f844 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -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 @@ err_hid_stop: 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 diff --git a/drivers/hid/hid-google-stadiaff.c b/drivers/hid/hid-google-stadiaff.c new file mode 100644 index 000000000000..3731575562ab --- /dev/null +++ b/drivers/hid/hid-google-stadiaff.c @@ -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"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8a310f8ff20f..7e499992a793 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -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 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 851ee86eff32..0235cc1690a1 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -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; diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 62180414efcc..8afe3be683ba 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -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), diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 129b01be488d..05f5b5f588a2 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -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); + 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 (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)); + return -ETIMEDOUT; + } - 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); + return ret; + } - 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); + 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; - } - 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); - } - } + 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) }, {} diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index e31be0cb8b85..521b2ffb4244 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -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; } diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index a928ad2be62d..9a3576dbf421 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -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); } diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 83237b86c8ff..2eba152e8b90 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -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; diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index aae3afc4107a..43d2cf7153d7 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -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); - -static struct hid_driver steelseries_srws1_driver = { - .name = "steelseries_srws1", - .id_table = steelseries_srws1_devices, -#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ - (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) - .probe = steelseries_srws1_probe, - .remove = steelseries_srws1_remove, -#endif - .report_fixup = steelseries_srws1_report_fixup +MODULE_DEVICE_TABLE(hid, steelseries_devices); + +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>"); diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index f67835f9ed4c..ad74cbc9a0aa 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -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; } diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c index a99dcca2e099..00f9be55f148 100644 --- a/drivers/hid/hid-wiimote-debug.c +++ b/drivers/hid/hid-wiimote-debug.c @@ -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) diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index 029045d9661c..31abab57ad44 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -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); diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 4da50e19808e..166a76c9bcad 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -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]; }; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 76e5353aca0c..3f704b8072e8 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -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 @@ fail: 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 (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 == serial) { + 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); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 174bf03908d7..471db78dbbf0 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -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); diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index ee21bb260f22..57e185f18d53 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -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]; }; diff --git a/include/linux/hid.h b/include/linux/hid.h index 39e21e3815ad..964ca1f15e3f 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -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 { diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index 48120222b9b2..3c1091941eb8 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -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) { |