Commit 29aa98d0 authored by Linus Torvalds's avatar Linus Torvalds

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

Pull HID updates from Benjamin Tissoires:

 - devm fixes for problems that caused use-after-free reports (Rahul
   Rameshbabu)

 - Some extensive HID docs (Marco Morandini)

 - Constification of struct class (Ivan Orlov and Greg Kroah-Hartman)

 - Google Stadia Force Feedback support (Fabio Baltieri)

 - Various fixes and new device ID support

* tag 'for-linus-2023083101' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (42 commits)
  HID: logitech-hidpp: rework one more time the retries attempts
  HID: nvidia-shield: Reference hid_device devm allocation of input_dev name
  HID: multitouch: Correct devm device reference for hidinput input_dev name
  HID: uclogic: Correct devm device reference for hidinput input_dev name
  HID: logitech-dj: Fix error handling in logi_dj_recv_switch_to_dj_mode()
  HID: i2c-hid: elan: Add ili9882t timing
  dt-bindings: input: i2c-hid: Introduce Ilitek ili9882t
  HID: apple: Add "Hailuck" to the list of non-apple keyboards
  HID: steelseries: arctis_1_battery_request[] should be static
  MAINTAINERS: update my email address
  HID: logitech-hidpp: Add support for Logitech MX Anywhere 3 mouse
  HID: wacom: struct name cleanup
  HID: wacom: remove unnecessary 'connected' variable from EKR
  HID: wacom: remove the battery when the EKR is off
  HID: nvidia-shield: Update Thunderstrike LED instance name to use id
  HID: nvidia-shield: Add battery support for Thunderstrike
  HID: nvidia-shield: Remove led_classdev_unregister in thunderstrike_create
  HID: hid-google-stadiaff: add support for Stadia force feedback
  HID: logitech-dj: Add support for a new lightspeed receiver iteration
  HID: logitech-hidpp: Add support for the Pro X Superlight
  ...
parents 307d5903 9fe5167a
...@@ -139,6 +139,9 @@ Daniel Borkmann <daniel@iogearbox.net> <dborkman@redhat.com> ...@@ -139,6 +139,9 @@ Daniel Borkmann <daniel@iogearbox.net> <dborkman@redhat.com>
Daniel Borkmann <daniel@iogearbox.net> <dxchgb@gmail.com> Daniel Borkmann <daniel@iogearbox.net> <dxchgb@gmail.com>
David Brownell <david-b@pacbell.net> David Brownell <david-b@pacbell.net>
David Collins <quic_collinsd@quicinc.com> <collinsd@codeaurora.org> 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> David Woodhouse <dwmw2@shinybook.infradead.org>
Dedy Lansky <quic_dlansky@quicinc.com> <dlansky@codeaurora.org> Dedy Lansky <quic_dlansky@quicinc.com> <dlansky@codeaurora.org>
Deepak Kumar Singh <quic_deesin@quicinc.com> <deesin@codeaurora.org> Deepak Kumar Singh <quic_deesin@quicinc.com> <deesin@codeaurora.org>
......
# 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>;
};
};
This diff is collapsed.
.. SPDX-License-Identifier: GPL-2.0
========================================
Manual parsing of HID report descriptors
========================================
Consider again the mouse HID report descriptor
introduced in Documentation/hid/hidintro.rst::
$ hexdump -C /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor
00000000 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 |..............).|
00000010 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 |..%.u.....u.....|
00000020 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 |...0.1.8..%.u...|
00000030 81 06 c0 c0 |....|
00000034
and try to parse it by hand.
Start with the first number, 0x05: it carries 2 bits for the
length of the item, 2 bits for the type of the item and 4 bits for the
function::
+----------+
| 00000101 |
+----------+
^^
---- Length of data (see HID spec 6.2.2.2)
^^
------ Type of the item (see HID spec 6.2.2.2, then jump to 6.2.2.7)
^^^^
--------- Function of the item (see HID spec 6.2.2.7, then HUT Sec 3)
In our case, the length is 1 byte, the type is ``Global`` and the
function is ``Usage Page``, thus for parsing the value 0x01 in the second byte
we need to refer to HUT Sec 3.
The second number is the actual data, and its meaning can be found in
the HUT. We have a ``Usage Page``, thus we need to refer to HUT
Sec. 3, "Usage Pages"; from there, one sees that ``0x01`` stands for
``Generic Desktop Page``.
Moving now to the second two bytes, and following the same scheme,
``0x09`` (i.e. ``00001001``) will be followed by one byte (``01``)
and is a ``Local`` item (``10``). Thus, the meaning of the remaining four bits
(``0000``) is given in the HID spec Sec. 6.2.2.8 "Local Items", so that
we have a ``Usage``. From HUT, Sec. 4, "Generic Desktop Page", we see that
0x02 stands for ``Mouse``.
The following numbers can be parsed in the same way.
...@@ -7,6 +7,7 @@ Human Interface Devices (HID) ...@@ -7,6 +7,7 @@ Human Interface Devices (HID)
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
hidintro
hiddev hiddev
hidraw hidraw
hid-sensor hid-sensor
......
...@@ -21987,7 +21987,7 @@ F: Documentation/admin-guide/ufs.rst ...@@ -21987,7 +21987,7 @@ F: Documentation/admin-guide/ufs.rst
F: fs/ufs/ F: fs/ufs/
UHID USERSPACE HID IO DRIVER UHID USERSPACE HID IO DRIVER
M: David Rheinsberg <david.rheinsberg@gmail.com> M: David Rheinsberg <david@readahead.eu>
L: linux-input@vger.kernel.org L: linux-input@vger.kernel.org
S: Maintained S: Maintained
F: drivers/hid/uhid.c F: drivers/hid/uhid.c
...@@ -23159,7 +23159,7 @@ S: Maintained ...@@ -23159,7 +23159,7 @@ S: Maintained
F: drivers/rtc/rtc-sd3078.c F: drivers/rtc/rtc-sd3078.c
WIIMOTE HID DRIVER WIIMOTE HID DRIVER
M: David Rheinsberg <david.rheinsberg@gmail.com> M: David Rheinsberg <david@readahead.eu>
L: linux-input@vger.kernel.org L: linux-input@vger.kernel.org
S: Maintained S: Maintained
F: drivers/hid/hid-wiimote* F: drivers/hid/hid-wiimote*
......
...@@ -412,6 +412,13 @@ config HID_GOOGLE_HAMMER ...@@ -412,6 +412,13 @@ config HID_GOOGLE_HAMMER
help help
Say Y here if you have a Google Hammer device. 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 config HID_VIVALDI
tristate "Vivaldi Keyboard" tristate "Vivaldi Keyboard"
select HID_VIVALDI_COMMON select HID_VIVALDI_COMMON
...@@ -1066,9 +1073,11 @@ config STEAM_FF ...@@ -1066,9 +1073,11 @@ config STEAM_FF
Deck. Deck.
config HID_STEELSERIES config HID_STEELSERIES
tristate "Steelseries SRW-S1 steering wheel support" tristate "Steelseries devices support"
depends on USB_HID
help 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 config HID_SUNPLUS
tristate "Sunplus wireless desktop" tristate "Sunplus wireless desktop"
......
...@@ -55,6 +55,7 @@ obj-$(CONFIG_HID_GFRM) += hid-gfrm.o ...@@ -55,6 +55,7 @@ obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o
obj-$(CONFIG_HID_VIVALDI_COMMON) += hid-vivaldi-common.o obj-$(CONFIG_HID_VIVALDI_COMMON) += hid-vivaldi-common.o
obj-$(CONFIG_HID_GOOGLE_HAMMER) += hid-google-hammer.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_VIVALDI) += hid-vivaldi.o
obj-$(CONFIG_HID_GT683R) += hid-gt683r.o obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
......
...@@ -343,7 +343,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = { ...@@ -343,7 +343,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
{ "SONiX USB DEVICE" }, { "SONiX USB DEVICE" },
{ "Keychron" }, { "Keychron" },
{ "AONE" }, { "AONE" },
{ "GANSS" } { "GANSS" },
{ "Hailuck" },
}; };
static bool apple_is_non_apple_keyboard(struct hid_device *hdev) static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
......
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Stadia controller rumble support.
*
* Copyright 2023 Google LLC
*/
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
#include "hid-ids.h"
#define STADIA_FF_REPORT_ID 5
struct stadiaff_device {
struct hid_device *hid;
struct hid_report *report;
spinlock_t lock;
bool removed;
uint16_t strong_magnitude;
uint16_t weak_magnitude;
struct work_struct work;
};
static void stadiaff_work(struct work_struct *work)
{
struct stadiaff_device *stadiaff =
container_of(work, struct stadiaff_device, work);
struct hid_field *rumble_field = stadiaff->report->field[0];
unsigned long flags;
spin_lock_irqsave(&stadiaff->lock, flags);
rumble_field->value[0] = stadiaff->strong_magnitude;
rumble_field->value[1] = stadiaff->weak_magnitude;
spin_unlock_irqrestore(&stadiaff->lock, flags);
hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT);
}
static int stadiaff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct hid_device *hid = input_get_drvdata(dev);
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
unsigned long flags;
spin_lock_irqsave(&stadiaff->lock, flags);
if (!stadiaff->removed) {
stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude;
stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude;
schedule_work(&stadiaff->work);
}
spin_unlock_irqrestore(&stadiaff->lock, flags);
return 0;
}
static int stadiaff_init(struct hid_device *hid)
{
struct stadiaff_device *stadiaff;
struct hid_report *report;
struct hid_input *hidinput;
struct input_dev *dev;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
report = hid_validate_values(hid, HID_OUTPUT_REPORT,
STADIA_FF_REPORT_ID, 0, 2);
if (!report)
return -ENODEV;
stadiaff = devm_kzalloc(&hid->dev, sizeof(struct stadiaff_device),
GFP_KERNEL);
if (!stadiaff)
return -ENOMEM;
hid_set_drvdata(hid, stadiaff);
input_set_capability(dev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(dev, NULL, stadiaff_play);
if (error)
return error;
stadiaff->removed = false;
stadiaff->hid = hid;
stadiaff->report = report;
INIT_WORK(&stadiaff->work, stadiaff_work);
spin_lock_init(&stadiaff->lock);
hid_info(hid, "Force Feedback for Google Stadia controller\n");
return 0;
}
static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
ret = stadiaff_init(hdev);
if (ret) {
hid_err(hdev, "force feedback init failed\n");
hid_hw_stop(hdev);
return ret;
}
return 0;
}
static void stadia_remove(struct hid_device *hid)
{
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
unsigned long flags;
spin_lock_irqsave(&stadiaff->lock, flags);
stadiaff->removed = true;
spin_unlock_irqrestore(&stadiaff->lock, flags);
cancel_work_sync(&stadiaff->work);
hid_hw_stop(hid);
}
static const struct hid_device_id stadia_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
{ }
};
MODULE_DEVICE_TABLE(hid, stadia_devices);
static struct hid_driver stadia_driver = {
.name = "stadia",
.id_table = stadia_devices,
.probe = stadia_probe,
.remove = stadia_remove,
};
module_hid_driver(stadia_driver);
MODULE_LICENSE("GPL");
...@@ -531,6 +531,7 @@ ...@@ -531,6 +531,7 @@
#define USB_DEVICE_ID_GOOGLE_DON 0x5050 #define USB_DEVICE_ID_GOOGLE_DON 0x5050
#define USB_DEVICE_ID_GOOGLE_EEL 0x5057 #define USB_DEVICE_ID_GOOGLE_EEL 0x5057
#define USB_DEVICE_ID_GOOGLE_JEWEL 0x5061 #define USB_DEVICE_ID_GOOGLE_JEWEL 0x5061
#define USB_DEVICE_ID_GOOGLE_STADIA 0x9400
#define USB_VENDOR_ID_GOTOP 0x08f2 #define USB_VENDOR_ID_GOTOP 0x08f2
#define USB_DEVICE_ID_SUPER_Q2 0x007f #define USB_DEVICE_ID_SUPER_Q2 0x007f
...@@ -866,6 +867,7 @@ ...@@ -866,6 +867,7 @@
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534 #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 0xc539
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f #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_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623 #define USB_DEVICE_ID_SPACETRAVELLER 0xc623
#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626 #define USB_DEVICE_ID_SPACENAVIGATOR 0xc626
......
...@@ -358,6 +358,9 @@ static const struct hid_device_id hid_battery_quirks[] = { ...@@ -358,6 +358,9 @@ static const struct hid_device_id hid_battery_quirks[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI), USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE }, 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, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
USB_DEVICE_ID_ELECOM_BM084), USB_DEVICE_ID_ELECOM_BM084),
HID_BATTERY_QUIRK_IGNORE }, HID_BATTERY_QUIRK_IGNORE },
...@@ -988,6 +991,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel ...@@ -988,6 +991,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
return; return;
case 0x3c: /* Invert */ case 0x3c: /* Invert */
device->quirks &= ~HID_QUIRK_NOINVERT;
map_key_clear(BTN_TOOL_RUBBER); map_key_clear(BTN_TOOL_RUBBER);
break; break;
...@@ -1013,9 +1017,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel ...@@ -1013,9 +1017,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x45: /* ERASER */ case 0x45: /* ERASER */
/* /*
* This event is reported when eraser tip touches the surface. * This event is reported when eraser tip touches the surface.
* Actual eraser (BTN_TOOL_RUBBER) is set by Invert usage when * Actual eraser (BTN_TOOL_RUBBER) is set and released either
* tool gets in proximity. * 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); map_key_clear(BTN_TOUCH);
break; break;
...@@ -1580,6 +1588,15 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct ...@@ -1580,6 +1588,15 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
else if (report->tool != BTN_TOOL_RUBBER) else if (report->tool != BTN_TOOL_RUBBER)
/* value is off, tool is not rubber, ignore */ /* value is off, tool is not rubber, ignore */
return; 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 */ /* let hid-input set BTN_TOUCH */
break; break;
......
...@@ -1285,6 +1285,9 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, ...@@ -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. * 50 msec should gives enough time to the receiver to be ready.
*/ */
msleep(50); 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, ...@@ -1306,7 +1309,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
buf[5] = 0x09; buf[5] = 0x09;
buf[6] = 0x00; 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, HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT,
HID_REQ_SET_REPORT); HID_REQ_SET_REPORT);
...@@ -1692,11 +1695,12 @@ static int logi_dj_raw_event(struct hid_device *hdev, ...@@ -1692,11 +1695,12 @@ static int logi_dj_raw_event(struct hid_device *hdev,
} }
/* /*
* Mouse-only receivers send unnumbered mouse data. The 27 MHz * 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 && if (djrcv_dev->unnumbered_application == HID_GD_MOUSE &&
size <= 8) { size <= 13){
u8 mouse_report[9]; u8 mouse_report[14];
/* Prepend report id */ /* Prepend report id */
mouse_report[0] = REPORT_TYPE_MOUSE; mouse_report[0] = REPORT_TYPE_MOUSE;
...@@ -1980,6 +1984,10 @@ static const struct hid_device_id logi_dj_receivers[] = { ...@@ -1980,6 +1984,10 @@ static const struct hid_device_id logi_dj_receivers[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1), USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1),
.driver_data = recvr_type_gaming_hidpp}, .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) */ { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER), HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
......
...@@ -228,7 +228,7 @@ struct hidpp_device { ...@@ -228,7 +228,7 @@ struct hidpp_device {
#define HIDPP20_ERROR_INVALID_ARGS 0x02 #define HIDPP20_ERROR_INVALID_ARGS 0x02
#define HIDPP20_ERROR_OUT_OF_RANGE 0x03 #define HIDPP20_ERROR_OUT_OF_RANGE 0x03
#define HIDPP20_ERROR_HW_ERROR 0x04 #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_FEATURE_INDEX 0x06
#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07 #define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07
#define HIDPP20_ERROR_BUSY 0x08 #define HIDPP20_ERROR_BUSY 0x08
...@@ -275,21 +275,22 @@ static int __hidpp_send_report(struct hid_device *hdev, ...@@ -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 * Effectively send the message to the device, waiting for its answer.
* in case of a failure. *
* - If ' something else' is positive, that means that an error has been raised * Must be called with hidpp->send_mutex locked
* by the protocol itself. *
* - If ' something else' is negative, that means that we had a classic error * Same return protocol than hidpp_send_message_sync():
* (-ENOMEM, -EPIPE, etc...) * - 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 *message,
struct hidpp_report *response) struct hidpp_report *response)
{ {
int ret = -1; int ret;
int max_retries = 3;
mutex_lock(&hidpp->send_mutex); __must_hold(&hidpp->send_mutex);
hidpp->send_receive_buf = response; hidpp->send_receive_buf = response;
hidpp->answer_available = false; hidpp->answer_available = false;
...@@ -300,47 +301,74 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, ...@@ -300,47 +301,74 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
*/ */
*response = *message; *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) { if (ret) {
dbg_hid("__hidpp_send_report returned err: %d\n", ret); dbg_hid("__hidpp_send_report returned err: %d\n", ret);
memset(response, 0, sizeof(struct hidpp_report)); memset(response, 0, sizeof(struct hidpp_report));
break; return ret;
} }
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
5*HZ)) { 5*HZ)) {
dbg_hid("%s:timeout waiting for response\n", __func__); dbg_hid("%s:timeout waiting for response\n", __func__);
memset(response, 0, sizeof(struct hidpp_report)); memset(response, 0, sizeof(struct hidpp_report));
ret = -ETIMEDOUT; return -ETIMEDOUT;
break;
} }
if (response->report_id == REPORT_ID_HIDPP_SHORT && if (response->report_id == REPORT_ID_HIDPP_SHORT &&
response->rap.sub_id == HIDPP_ERROR) { response->rap.sub_id == HIDPP_ERROR) {
ret = response->rap.params[1]; ret = response->rap.params[1];
dbg_hid("%s:got hidpp error %02X\n", __func__, ret); dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
break; return ret;
} }
if ((response->report_id == REPORT_ID_HIDPP_LONG || if ((response->report_id == REPORT_ID_HIDPP_LONG ||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) && response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
response->fap.feature_index == HIDPP20_ERROR) { response->fap.feature_index == HIDPP20_ERROR) {
ret = response->fap.params[1]; ret = response->fap.params[1];
if (ret != HIDPP20_ERROR_BUSY) {
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
break; return ret;
} }
return 0;
}
/*
* hidpp_send_message_sync() returns 0 in case of success, and something else
* in case of a failure.
*
* See __do_hidpp_send_message_sync() for a detailed explanation of the returned
* value.
*/
static int hidpp_send_message_sync(struct hidpp_device *hidpp,
struct hidpp_report *message,
struct hidpp_report *response)
{
int ret;
int max_retries = 3;
mutex_lock(&hidpp->send_mutex);
do {
ret = __do_hidpp_send_message_sync(hidpp, message, response);
if (ret != HIDPP20_ERROR_BUSY)
break;
dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret); dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
} } while (--max_retries);
}
mutex_unlock(&hidpp->send_mutex); mutex_unlock(&hidpp->send_mutex);
return ret; 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, static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
struct hidpp_report *response) struct hidpp_report *response)
...@@ -373,6 +401,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, ...@@ -373,6 +401,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
return ret; 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, 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, u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
struct hidpp_report *response) struct hidpp_report *response)
...@@ -4620,6 +4655,8 @@ static const struct hid_device_id hidpp_devices[] = { ...@@ -4620,6 +4655,8 @@ static const struct hid_device_id hidpp_devices[] = {
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS }, .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
{ /* Logitech G Pro Gaming Mouse over USB */ { /* Logitech G Pro Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) }, 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 */ { /* G935 Gaming Headset */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87), HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
...@@ -4647,6 +4684,8 @@ static const struct hid_device_id hidpp_devices[] = { ...@@ -4647,6 +4684,8 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) }, HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
{ /* MX Master 3 mouse over Bluetooth */ { /* MX Master 3 mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, 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 */ { /* MX Master 3S mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) }, HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
{} {}
......
...@@ -1594,7 +1594,6 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) ...@@ -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) static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
{ {
struct mt_device *td = hid_get_drvdata(hdev); struct mt_device *td = hid_get_drvdata(hdev);
char *name;
const char *suffix = NULL; const char *suffix = NULL;
struct mt_report_data *rdata; struct mt_report_data *rdata;
struct mt_application *mt_application = NULL; struct mt_application *mt_application = NULL;
...@@ -1645,15 +1644,9 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) ...@@ -1645,15 +1644,9 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
break; break;
} }
if (suffix) { if (suffix)
name = devm_kzalloc(&hi->input->dev, hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
strlen(hdev->name) + strlen(suffix) + 2, "%s %s", hdev->name, suffix);
GFP_KERNEL);
if (name) {
sprintf(name, "%s %s", hdev->name, suffix);
hi->input->name = name;
}
}
return 0; return 0;
} }
......
This diff is collapsed.
...@@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev, ...@@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev,
} }
INIT_LIST_HEAD(&hdev->inputs); INIT_LIST_HEAD(&hdev->inputs);
ret = hid_hw_start(hdev, 0); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) { if (ret) {
hid_err(hdev, "hw start failed\n"); hid_err(hdev, "hw start failed\n");
return ret; return ret;
......
// SPDX-License-Identifier: GPL-2.0-or-later // 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) 2013 Simon Wood
* Copyright (c) 2023 Bastien Nocera
*/ */
/* /*
...@@ -11,10 +12,28 @@ ...@@ -11,10 +12,28 @@
#include <linux/device.h> #include <linux/device.h>
#include <linux/hid.h> #include <linux/hid.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h> #include <linux/leds.h>
#include "hid-ids.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) || \ #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
#define SRWS1_NUMBER_LEDS 15 #define SRWS1_NUMBER_LEDS 15
...@@ -353,9 +372,211 @@ static void steelseries_srws1_remove(struct hid_device *hdev) ...@@ -353,9 +372,211 @@ static void steelseries_srws1_remove(struct hid_device *hdev)
} }
#endif #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, static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize) 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 if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) { && rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); 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 ...@@ -365,22 +586,82 @@ static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc
return rdesc; return rdesc;
} }
static const struct hid_device_id steelseries_srws1_devices[] = { static int steelseries_headset_raw_event(struct hid_device *hdev,
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, struct hid_report *report, u8 *read_buf,
int size)
{
struct steelseries_device *sd = hid_get_drvdata(hdev);
int capacity = sd->battery_capacity;
bool connected = sd->headset_connected;
unsigned long flags;
/* Not a headset */
if (sd->quirks & STEELSERIES_SRWS1)
return 0;
if (sd->quirks & STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
memcmp (read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request)))
return 0;
if (read_buf[2] == 0x01) {
connected = false;
capacity = 100;
} else {
connected = true;
capacity = read_buf[3];
}
}
if (connected != sd->headset_connected) {
hid_dbg(sd->hdev,
"Connected status changed from %sconnected to %sconnected\n",
sd->headset_connected ? "" : "not ",
connected ? "" : "not ");
sd->headset_connected = connected;
steelseries_headset_set_wireless_status(hdev, connected);
}
if (capacity != sd->battery_capacity) {
hid_dbg(sd->hdev,
"Battery capacity changed from %d%% to %d%%\n",
sd->battery_capacity, capacity);
sd->battery_capacity = capacity;
power_supply_changed(sd->battery);
}
spin_lock_irqsave(&sd->lock, flags);
if (!sd->removed)
schedule_delayed_work(&sd->battery_work,
msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
spin_unlock_irqrestore(&sd->lock, flags);
return 0;
}
static const struct hid_device_id steelseries_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
.driver_data = STEELSERIES_SRWS1 },
{ /* SteelSeries Arctis 1 Wireless for XBox */
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6),
.driver_data = STEELSERIES_ARCTIS_1 },
{ } { }
}; };
MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); MODULE_DEVICE_TABLE(hid, steelseries_devices);
static struct hid_driver steelseries_srws1_driver = { static struct hid_driver steelseries_driver = {
.name = "steelseries_srws1", .name = "steelseries",
.id_table = steelseries_srws1_devices, .id_table = steelseries_devices,
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ .probe = steelseries_probe,
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) .remove = steelseries_remove,
.probe = steelseries_srws1_probe, .report_fixup = steelseries_srws1_report_fixup,
.remove = steelseries_srws1_remove, .raw_event = steelseries_headset_raw_event,
#endif
.report_fixup = steelseries_srws1_report_fixup
}; };
module_hid_driver(steelseries_srws1_driver); module_hid_driver(steelseries_driver);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
...@@ -85,10 +85,8 @@ static int uclogic_input_configured(struct hid_device *hdev, ...@@ -85,10 +85,8 @@ static int uclogic_input_configured(struct hid_device *hdev,
{ {
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
struct uclogic_params *params = &drvdata->params; struct uclogic_params *params = &drvdata->params;
char *name;
const char *suffix = NULL; const char *suffix = NULL;
struct hid_field *field; struct hid_field *field;
size_t len;
size_t i; size_t i;
const struct uclogic_params_frame *frame; const struct uclogic_params_frame *frame;
...@@ -146,14 +144,9 @@ static int uclogic_input_configured(struct hid_device *hdev, ...@@ -146,14 +144,9 @@ static int uclogic_input_configured(struct hid_device *hdev,
} }
} }
if (suffix) { if (suffix)
len = strlen(hdev->name) + 2 + strlen(suffix); hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL); "%s %s", hdev->name, suffix);
if (name) {
snprintf(name, len, "%s %s", hdev->name, suffix);
hi->input->name = name;
}
}
return 0; return 0;
} }
......
...@@ -173,7 +173,6 @@ int wiidebug_init(struct wiimote_data *wdata) ...@@ -173,7 +173,6 @@ int wiidebug_init(struct wiimote_data *wdata)
{ {
struct wiimote_debug *dbg; struct wiimote_debug *dbg;
unsigned long flags; unsigned long flags;
int ret = -ENOMEM;
dbg = kzalloc(sizeof(*dbg), GFP_KERNEL); dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
if (!dbg) if (!dbg)
...@@ -183,13 +182,9 @@ int wiidebug_init(struct wiimote_data *wdata) ...@@ -183,13 +182,9 @@ int wiidebug_init(struct wiimote_data *wdata)
dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR, dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR,
dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops); dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops);
if (!dbg->eeprom)
goto err;
dbg->drm = debugfs_create_file("drm", S_IRUSR, dbg->drm = debugfs_create_file("drm", S_IRUSR,
dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops); dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops);
if (!dbg->drm)
goto err_drm;
spin_lock_irqsave(&wdata->state.lock, flags); spin_lock_irqsave(&wdata->state.lock, flags);
wdata->debug = dbg; wdata->debug = dbg;
...@@ -197,11 +192,6 @@ int wiidebug_init(struct wiimote_data *wdata) ...@@ -197,11 +192,6 @@ int wiidebug_init(struct wiimote_data *wdata)
return 0; return 0;
err_drm:
debugfs_remove(dbg->eeprom);
err:
kfree(dbg);
return ret;
} }
void wiidebug_deinit(struct wiimote_data *wdata) void wiidebug_deinit(struct wiimote_data *wdata)
......
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
#include "i2c-hid.h" #include "i2c-hid.h"
struct elan_i2c_hid_chip_data { 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; unsigned int post_power_delay_ms;
u16 hid_descriptor_address; u16 hid_descriptor_address;
const char *main_supply_name;
}; };
struct i2c_hid_of_elan { struct i2c_hid_of_elan {
...@@ -38,9 +40,11 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops) ...@@ -38,9 +40,11 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops)
container_of(ops, struct i2c_hid_of_elan, ops); container_of(ops, struct i2c_hid_of_elan, ops);
int ret; int ret;
if (ihid_elan->vcc33) {
ret = regulator_enable(ihid_elan->vcc33); ret = regulator_enable(ihid_elan->vcc33);
if (ret) if (ret)
return ret; return ret;
}
ret = regulator_enable(ihid_elan->vccio); ret = regulator_enable(ihid_elan->vccio);
if (ret) { if (ret) {
...@@ -52,8 +56,8 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops) ...@@ -52,8 +56,8 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops)
msleep(ihid_elan->chip_data->post_power_delay_ms); msleep(ihid_elan->chip_data->post_power_delay_ms);
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0); gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0);
if (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_delay_ms); msleep(ihid_elan->chip_data->post_gpio_reset_on_delay_ms);
return 0; return 0;
} }
...@@ -64,7 +68,11 @@ static void elan_i2c_hid_power_down(struct i2chid_ops *ops) ...@@ -64,7 +68,11 @@ static void elan_i2c_hid_power_down(struct i2chid_ops *ops)
container_of(ops, struct i2c_hid_of_elan, ops); container_of(ops, struct i2c_hid_of_elan, ops);
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1); 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->vccio);
if (ihid_elan->vcc33)
regulator_disable(ihid_elan->vcc33); regulator_disable(ihid_elan->vcc33);
} }
...@@ -89,11 +97,14 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client) ...@@ -89,11 +97,14 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client)
if (IS_ERR(ihid_elan->vccio)) if (IS_ERR(ihid_elan->vccio))
return PTR_ERR(ihid_elan->vccio); return PTR_ERR(ihid_elan->vccio);
ihid_elan->vcc33 = devm_regulator_get(&client->dev, "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)) if (IS_ERR(ihid_elan->vcc33))
return PTR_ERR(ihid_elan->vcc33); return PTR_ERR(ihid_elan->vcc33);
}
ihid_elan->chip_data = device_get_match_data(&client->dev);
return i2c_hid_core_probe(client, &ihid_elan->ops, return i2c_hid_core_probe(client, &ihid_elan->ops,
ihid_elan->chip_data->hid_descriptor_address, 0); ihid_elan->chip_data->hid_descriptor_address, 0);
...@@ -101,12 +112,27 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client) ...@@ -101,12 +112,27 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client)
static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = { static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = {
.post_power_delay_ms = 1, .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, .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[] = { static const struct of_device_id elan_i2c_hid_of_match[] = {
{ .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data }, { .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); MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match);
......
...@@ -150,6 +150,7 @@ struct wacom_remote { ...@@ -150,6 +150,7 @@ struct wacom_remote {
struct input_dev *input; struct input_dev *input;
bool registered; bool registered;
struct wacom_battery battery; struct wacom_battery battery;
ktime_t active_time;
} remotes[WACOM_MAX_REMOTES]; } remotes[WACOM_MAX_REMOTES];
}; };
......
...@@ -1997,7 +1997,7 @@ static int wacom_initialize_remotes(struct wacom *wacom) ...@@ -1997,7 +1997,7 @@ static int wacom_initialize_remotes(struct wacom *wacom)
spin_lock_init(&remote->remote_lock); spin_lock_init(&remote->remote_lock);
error = kfifo_alloc(&remote->remote_fifo, error = kfifo_alloc(&remote->remote_fifo,
5 * sizeof(struct wacom_remote_data), 5 * sizeof(struct wacom_remote_work_data),
GFP_KERNEL); GFP_KERNEL);
if (error) { if (error) {
hid_err(wacom->hdev, "failed allocating remote_fifo\n"); hid_err(wacom->hdev, "failed allocating remote_fifo\n");
...@@ -2523,6 +2523,18 @@ static void wacom_wireless_work(struct work_struct *work) ...@@ -2523,6 +2523,18 @@ static void wacom_wireless_work(struct work_struct *work)
return; 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) static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
{ {
struct wacom_remote *remote = wacom->remote; struct wacom_remote *remote = wacom->remote;
...@@ -2537,9 +2549,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) ...@@ -2537,9 +2549,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
remote->remotes[i].registered = false; remote->remotes[i].registered = false;
spin_unlock_irqrestore(&remote->remote_lock, flags); spin_unlock_irqrestore(&remote->remote_lock, flags);
if (remote->remotes[i].battery.battery) wacom_remote_destroy_battery(wacom, i);
devres_release_group(&wacom->hdev->dev,
&remote->remotes[i].battery.bat_desc);
if (remote->remotes[i].group.name) if (remote->remotes[i].group.name)
devres_release_group(&wacom->hdev->dev, devres_release_group(&wacom->hdev->dev,
...@@ -2547,7 +2557,6 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) ...@@ -2547,7 +2557,6 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
remote->remotes[i].serial = 0; remote->remotes[i].serial = 0;
remote->remotes[i].group.name = NULL; remote->remotes[i].group.name = NULL;
remote->remotes[i].battery.battery = NULL;
wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN;
} }
} }
...@@ -2632,6 +2641,9 @@ static int wacom_remote_attach_battery(struct wacom *wacom, int index) ...@@ -2632,6 +2641,9 @@ static int wacom_remote_attach_battery(struct wacom *wacom, int index)
if (remote->remotes[index].battery.battery) if (remote->remotes[index].battery.battery)
return 0; return 0;
if (!remote->remotes[index].active_time)
return 0;
if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN) if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN)
return 0; return 0;
...@@ -2647,17 +2659,19 @@ static void wacom_remote_work(struct work_struct *work) ...@@ -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 *wacom = container_of(work, struct wacom, remote_work);
struct wacom_remote *remote = wacom->remote; 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 long flags;
unsigned int count; unsigned int count;
u32 serial; u32 work_serial;
int i; int i;
spin_lock_irqsave(&remote->remote_lock, flags); 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, hid_err(wacom->hdev,
"workitem triggered without status available\n"); "workitem triggered without status available\n");
spin_unlock_irqrestore(&remote->remote_lock, flags); spin_unlock_irqrestore(&remote->remote_lock, flags);
...@@ -2670,10 +2684,14 @@ static void wacom_remote_work(struct work_struct *work) ...@@ -2670,10 +2684,14 @@ static void wacom_remote_work(struct work_struct *work)
spin_unlock_irqrestore(&remote->remote_lock, flags); spin_unlock_irqrestore(&remote->remote_lock, flags);
for (i = 0; i < WACOM_MAX_REMOTES; i++) { for (i = 0; i < WACOM_MAX_REMOTES; i++) {
serial = data.remote[i].serial; work_serial = remote_work_data.remote[i].serial;
if (data.remote[i].connected) { if (work_serial) {
if (remote->remotes[i].serial == serial) { if (kt - remote->remotes[i].active_time > WACOM_REMOTE_BATTERY_TIMEOUT
&& remote->remotes[i].active_time != 0)
wacom_remote_destroy_battery(wacom, i);
if (remote->remotes[i].serial == work_serial) {
wacom_remote_attach_battery(wacom, i); wacom_remote_attach_battery(wacom, i);
continue; continue;
} }
...@@ -2681,7 +2699,7 @@ static void wacom_remote_work(struct work_struct *work) ...@@ -2681,7 +2699,7 @@ static void wacom_remote_work(struct work_struct *work)
if (remote->remotes[i].serial) if (remote->remotes[i].serial)
wacom_remote_destroy_one(wacom, i); 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) { } else if (remote->remotes[i].serial) {
wacom_remote_destroy_one(wacom, i); wacom_remote_destroy_one(wacom, i);
......
...@@ -1134,6 +1134,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) ...@@ -1134,6 +1134,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len)
if (index < 0 || !remote->remotes[index].registered) if (index < 0 || !remote->remotes[index].registered)
goto out; goto out;
remote->remotes[i].active_time = ktime_get();
input = remote->remotes[index].input; input = remote->remotes[index].input;
input_report_key(input, BTN_0, (data[9] & 0x01)); 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) ...@@ -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); struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
unsigned char *data = wacom_wac->data; unsigned char *data = wacom_wac->data;
struct wacom_remote *remote = wacom->remote; struct wacom_remote *remote = wacom->remote;
struct wacom_remote_data remote_data; struct wacom_remote_work_data remote_data;
unsigned long flags; unsigned long flags;
int i, ret; int i, ret;
if (data[0] != WACOM_REPORT_DEVICE_LIST) if (data[0] != WACOM_REPORT_DEVICE_LIST)
return; 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++) { for (i = 0; i < WACOM_MAX_REMOTES; i++) {
int j = i * 6; int j = i * 6;
int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4]; 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].serial = serial;
remote_data.remote[i].connected = connected;
} }
spin_lock_irqsave(&remote->remote_lock, flags); spin_lock_irqsave(&remote->remote_lock, flags);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#define WACOM_NAME_MAX 64 #define WACOM_NAME_MAX 64
#define WACOM_MAX_REMOTES 5 #define WACOM_MAX_REMOTES 5
#define WACOM_STATUS_UNKNOWN 255 #define WACOM_STATUS_UNKNOWN 255
#define WACOM_REMOTE_BATTERY_TIMEOUT 21000000000ll
/* packet length for individual models */ /* packet length for individual models */
#define WACOM_PKGLEN_BBFUN 9 #define WACOM_PKGLEN_BBFUN 9
...@@ -327,10 +328,9 @@ struct hid_data { ...@@ -327,10 +328,9 @@ struct hid_data {
ktime_t time_delayed; ktime_t time_delayed;
}; };
struct wacom_remote_data { struct wacom_remote_work_data {
struct { struct {
u32 serial; u32 serial;
bool connected;
} remote[WACOM_MAX_REMOTES]; } remote[WACOM_MAX_REMOTES];
}; };
......
...@@ -341,6 +341,29 @@ struct hid_item { ...@@ -341,6 +341,29 @@ struct hid_item {
*/ */
#define MAX_USBHID_BOOT_QUIRKS 4 #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 */ /* BIT(0) reserved for backward compatibility, was HID_QUIRK_INVERT */
#define HID_QUIRK_NOTOUCH BIT(1) #define HID_QUIRK_NOTOUCH BIT(1)
#define HID_QUIRK_IGNORE BIT(2) #define HID_QUIRK_IGNORE BIT(2)
...@@ -360,6 +383,7 @@ struct hid_item { ...@@ -360,6 +383,7 @@ struct hid_item {
#define HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP BIT(18) #define HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP BIT(18)
#define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19) #define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19)
#define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20) #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_FULLSPEED_INTERVAL BIT(28)
#define HID_QUIRK_NO_INIT_REPORTS BIT(29) #define HID_QUIRK_NO_INIT_REPORTS BIT(29)
#define HID_QUIRK_NO_IGNORE BIT(30) #define HID_QUIRK_NO_IGNORE BIT(30)
...@@ -555,9 +579,9 @@ struct hid_input { ...@@ -555,9 +579,9 @@ struct hid_input {
struct hid_report *report; struct hid_report *report;
struct input_dev *input; struct input_dev *input;
const char *name; const char *name;
bool registered;
struct list_head reports; /* the list of reports */ struct list_head reports; /* the list of reports */
unsigned int application; /* application usage for this input */ unsigned int application; /* application usage for this input */
bool registered;
}; };
enum hid_type { enum hid_type {
......
...@@ -30,6 +30,7 @@ static inline const char *str_read_write(bool v) ...@@ -30,6 +30,7 @@ static inline const char *str_read_write(bool v)
{ {
return v ? "read" : "write"; return v ? "read" : "write";
} }
#define str_write_read(v) str_read_write(!(v))
static inline const char *str_on_off(bool v) static inline const char *str_on_off(bool v)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment