From 38e57f0693ad1b9a785b4f542ec019c5fdf91d57 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 13 Sep 2019 15:03:15 -0700 Subject: [PATCH 01/23] HID: google: whiskers: more robust tablet mode detection The USB interface may get detected before the platform/EC one, so let's note the state of the base (if we receive event) and use it to correctly initialize the tablet mode switch state. Also let's start the HID interface immediately when probing, this will ensure that we correctly process "base folded" events that may be sent as we initialize the base. Note that this requires us to add a remove() function where we stop and close the hardware and switch the LED registration away from devm interface as we need to make sure that we destroy the LED instance before we stop the hardware. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 73 +++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 84f8c127ebdc..4f64f93ddfcb 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -35,6 +35,7 @@ struct cbas_ec { struct device *dev; /* The platform device (EC) */ struct input_dev *input; bool base_present; + bool base_folded; struct notifier_block notifier; }; @@ -208,7 +209,14 @@ static int __cbas_ec_probe(struct platform_device *pdev) return error; } - input_report_switch(input, SW_TABLET_MODE, !cbas_ec.base_present); + if (!cbas_ec.base_present) + cbas_ec.base_folded = false; + + dev_dbg(&pdev->dev, "%s: base: %d, folded: %d\n", __func__, + cbas_ec.base_present, cbas_ec.base_folded); + + input_report_switch(input, SW_TABLET_MODE, + !cbas_ec.base_present || cbas_ec.base_folded); cbas_ec_set_input(input); @@ -322,10 +330,9 @@ static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev, static int hammer_register_leds(struct hid_device *hdev) { struct hammer_kbd_leds *kbd_backlight; + int error; - kbd_backlight = devm_kzalloc(&hdev->dev, - sizeof(*kbd_backlight), - GFP_KERNEL); + kbd_backlight = kzalloc(sizeof(*kbd_backlight), GFP_KERNEL); if (!kbd_backlight) return -ENOMEM; @@ -339,7 +346,26 @@ static int hammer_register_leds(struct hid_device *hdev) /* Set backlight to 0% initially. */ hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0); - return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev); + error = led_classdev_register(&hdev->dev, &kbd_backlight->cdev); + if (error) + goto err_free_mem; + + hid_set_drvdata(hdev, kbd_backlight); + return 0; + +err_free_mem: + kfree(kbd_backlight); + return error; +} + +static void hammer_unregister_leds(struct hid_device *hdev) +{ + struct hammer_kbd_leds *kbd_backlight = hid_get_drvdata(hdev); + + if (kbd_backlight) { + led_classdev_unregister(&kbd_backlight->cdev); + kfree(kbd_backlight); + } } #define HID_UP_GOOGLEVENDOR 0xffd10000 @@ -376,8 +402,9 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field, usage->hid == WHISKERS_KBD_FOLDED) { spin_lock_irqsave(&cbas_ec_lock, flags); + cbas_ec.base_folded = value; hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__, - cbas_ec.base_present, value); + cbas_ec.base_present, cbas_ec.base_folded); /* * We should not get event if base is detached, but in case @@ -436,16 +463,6 @@ static int hammer_probe(struct hid_device *hdev, { int error; - /* - * We always want to poll for, and handle tablet mode events from - * Whiskers, even when nobody has opened the input device. This also - * prevents the hid core from dropping early tablet mode events from - * the device. - */ - if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) - hdev->quirks |= HID_QUIRK_ALWAYS_POLL; - error = hid_parse(hdev); if (error) return error; @@ -454,6 +471,20 @@ static int hammer_probe(struct hid_device *hdev, if (error) return error; + /* + * We always want to poll for, and handle tablet mode events from + * Whiskers, even when nobody has opened the input device. This also + * prevents the hid core from dropping early tablet mode events from + * the device. + */ + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && + hammer_is_keyboard_interface(hdev)) { + hdev->quirks |= HID_QUIRK_ALWAYS_POLL; + error = hid_hw_open(hdev); + if (error) + return error; + } + if (hammer_has_backlight_control(hdev)) { error = hammer_register_leds(hdev); if (error) @@ -465,6 +496,15 @@ static int hammer_probe(struct hid_device *hdev, return 0; } +static void hammer_remove(struct hid_device *hdev) +{ + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && + hammer_is_keyboard_interface(hdev)) + hid_hw_close(hdev); + + hammer_unregister_leds(hdev); + hid_hw_stop(hdev); +} static const struct hid_device_id hammer_devices[] = { { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, @@ -483,6 +523,7 @@ static struct hid_driver hammer_driver = { .name = "hammer", .id_table = hammer_devices, .probe = hammer_probe, + .remove = hammer_remove, .input_mapping = hammer_input_mapping, .event = hammer_event, }; From 79085c7dd24b7acd546f5b283d3f2319a9edbbd8 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 13 Sep 2019 15:03:16 -0700 Subject: [PATCH 02/23] HID: google: whiskers: signal tablet mode switch on disconnect Currently, the tablet mode switch that takes two signals as its input: base attached switch from the EC and some GMR signal from whiskers when it's folded over. This tablet mode switch is then sent to Chrome, which changes the UI. However, there are some units which have a lot of leakage on the ADC pins that the EC uses to determine whether or not a base is attached. This can result in the base being physically detached, but the EC thinking that it's still attached. The user would then be stuck in laptop mode and wouldn't be able to rotate their display. To work around this let's send "tablet mode" signal when we remove HID interface, which normally happens when we physically disconnect whiskers. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 4f64f93ddfcb..3dc6116c8f76 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -498,11 +498,33 @@ static int hammer_probe(struct hid_device *hdev, static void hammer_remove(struct hid_device *hdev) { + unsigned long flags; + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) + hammer_is_keyboard_interface(hdev)) { hid_hw_close(hdev); + /* + * If we are disconnecting then most likely Whiskers is + * being removed. Even if it is not removed, without proper + * keyboard we should not stay in clamshell mode. + * + * The reason for doing it here and not waiting for signal + * from EC, is that on some devices there are high leakage + * on Whiskers pins and we do not detect disconnect reliably, + * resulting in devices being stuck in clamshell mode. + */ + spin_lock_irqsave(&cbas_ec_lock, flags); + if (cbas_ec.input && cbas_ec.base_present) { + input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1); + input_sync(cbas_ec.input); + } + cbas_ec.base_present = false; + spin_unlock_irqrestore(&cbas_ec_lock, flags); + } + hammer_unregister_leds(hdev); + hid_hw_stop(hdev); } From b543db46b47dc075535a5adf93460b611d669598 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 13 Sep 2019 15:03:17 -0700 Subject: [PATCH 03/23] HID: google: whiskers: signal tablet mode on connect When we receive "keyboard position" event from Whiskers we can conclude that the base is attached, even if we did not get EC event for that. We may miss EC event because there are some units which have a lot of leakage on the ADC pins that the EC uses to determine whether or not a base is attached. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 3dc6116c8f76..31e4a39946f5 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -402,16 +402,16 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field, usage->hid == WHISKERS_KBD_FOLDED) { spin_lock_irqsave(&cbas_ec_lock, flags); + /* + * If we are getting events from Whiskers that means that it + * is attached to the lid. + */ + cbas_ec.base_present = true; cbas_ec.base_folded = value; hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__, cbas_ec.base_present, cbas_ec.base_folded); - /* - * We should not get event if base is detached, but in case - * we happen to service HID and EC notifications out of order - * let's still check the "base present" flag. - */ - if (cbas_ec.input && cbas_ec.base_present) { + if (cbas_ec.input) { input_report_switch(cbas_ec.input, SW_TABLET_MODE, value); input_sync(cbas_ec.input); From b05cec61c703afc64abc42cd79dda8e4ddef7659 Mon Sep 17 00:00:00 2001 From: Rishi Gupta Date: Fri, 20 Sep 2019 08:05:01 +0530 Subject: [PATCH 04/23] HID: hidraw: replace printk() with corresponding pr_xx() variant This commit replaces direct invocations of printk with their appropriate pr_info/warn() variant. Signed-off-by: Rishi Gupta Signed-off-by: Jiri Kosina --- drivers/hid/hidraw.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index bbc6ec1aa5cb..9432670671fe 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -197,15 +197,15 @@ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t } if (count > HID_MAX_BUFFER_SIZE) { - printk(KERN_WARNING "hidraw: pid %d passed too large report\n", - task_pid_nr(current)); + hid_warn(dev, "pid %d passed too large report\n", + task_pid_nr(current)); ret = -EINVAL; goto out; } if (count < 2) { - printk(KERN_WARNING "hidraw: pid %d passed too short report\n", - task_pid_nr(current)); + hid_warn(dev, "pid %d passed too short report\n", + task_pid_nr(current)); ret = -EINVAL; goto out; } @@ -597,7 +597,7 @@ int __init hidraw_init(void) if (result < 0) goto error_class; - printk(KERN_INFO "hidraw: raw HID events driver (C) Jiri Kosina\n"); + pr_info("raw HID events driver (C) Jiri Kosina\n"); out: return result; From b5625db9d23e58a573eb10a7f6d0c2ae060bc0e8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:24 +0200 Subject: [PATCH 05/23] Input: Add event-codes for macro keys found on various keyboards Various keyboards have macro keys, which are intended to have user programmable actions / key-sequences bound to them. In some cases these macro keys are actually programmable in hardware, but more often they basically are just extra keys and the playback of the key-sequence is done by software running on the host. One example of keyboards with macro-keys are various "internet" / "office" keyboards have a set of so-called "Smart Keys", typically a set of 4 keys labeled "[A]" - "[D]". Another example are gaming keyboards, such as the Logitech G15 Gaming keyboard, which has 18 "G"aming keys labeled "G1" to G18", 3 keys to select macro presets labeled "M1" - "M3" and a key to start recording a macro called "MR" note that even though there us a record key everything is handled in sw on the host. Besides macro keys the G15 (and other gaming keyboards) also has a buildin LCD panel where the contents are controlled by the host. There are 5 keys directly below the LCD intended for controlling a menu shown on the LCD. The Microsoft SideWinder X6 keyboard is another gaming keyboard example, this keyboard has 30 "S"idewinder keys and a key to cycle through macro-presets. After discussion between various involved userspace people we've come to the conclusion that since these are all really just extra keys we should simply treat them as such and give them their own event-codes, see: https://github.com/libratbag/libratbag/issues/172 This commit adds the following new KEY_ defines for this: KEY_MACRO1 - KEY_MACRO30. KEY_MACRO_RECORD_START/-STOP, KEY_MACRO_PRESET_CYCLE, KEY_MACRO_PRESET1 - KEY_MACRO_PRESET3, KEY_KBD_LCD_MENU1 - KEY_KBD_LCD_MENU5. The defines leave room for adding some more LCD-menu, preset or macro keys, the maximum values above are based on the maximum values to support all currently known internet, office and gaming keyboards. BugLink: https://github.com/libratbag/libratbag/issues/172 Signed-off-by: Hans de Goede Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- include/uapi/linux/input-event-codes.h | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 85387c76c24f..00aebeaae090 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -650,6 +650,81 @@ #define KEY_DATA 0x277 #define KEY_ONSCREEN_KEYBOARD 0x278 +/* + * Some keyboards have keys which do not have a defined meaning, these keys + * are intended to be programmed / bound to macros by the user. For most + * keyboards with these macro-keys the key-sequence to inject, or action to + * take, is all handled by software on the host side. So from the kernel's + * point of view these are just normal keys. + * + * The KEY_MACRO# codes below are intended for such keys, which may be labeled + * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys + * where the marking on the key does indicate a defined meaning / purpose. + * + * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing + * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO + * define MUST be added. + */ +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x29a +#define KEY_MACRO12 0x29b +#define KEY_MACRO13 0x29c +#define KEY_MACRO14 0x29d +#define KEY_MACRO15 0x29e +#define KEY_MACRO16 0x29f +#define KEY_MACRO17 0x2a0 +#define KEY_MACRO18 0x2a1 +#define KEY_MACRO19 0x2a2 +#define KEY_MACRO20 0x2a3 +#define KEY_MACRO21 0x2a4 +#define KEY_MACRO22 0x2a5 +#define KEY_MACRO23 0x2a6 +#define KEY_MACRO24 0x2a7 +#define KEY_MACRO25 0x2a8 +#define KEY_MACRO26 0x2a9 +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad + +/* + * Some keyboards with the macro-keys described above have some extra keys + * for controlling the host-side software responsible for the macro handling: + * -A macro recording start/stop key. Note that not all keyboards which emit + * KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if + * KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START + * should be interpreted as a recording start/stop toggle; + * -Keys for switching between different macro (pre)sets, either a key for + * cycling through the configured presets or keys to directly select a preset. + */ +#define KEY_MACRO_RECORD_START 0x2b0 +#define KEY_MACRO_RECORD_STOP 0x2b1 +#define KEY_MACRO_PRESET_CYCLE 0x2b2 +#define KEY_MACRO_PRESET1 0x2b3 +#define KEY_MACRO_PRESET2 0x2b4 +#define KEY_MACRO_PRESET3 0x2b5 + +/* + * Some keyboards have a buildin LCD panel where the contents are controlled + * by the host. Often these have a number of keys directly below the LCD + * intended for controlling a menu shown on the LCD. These keys often don't + * have any labeling so we just name them KEY_KBD_LCD_MENU# + */ +#define KEY_KBD_LCD_MENU1 0x2b8 +#define KEY_KBD_LCD_MENU2 0x2b9 +#define KEY_KBD_LCD_MENU3 0x2ba +#define KEY_KBD_LCD_MENU4 0x2bb +#define KEY_KBD_LCD_MENU5 0x2bc + #define BTN_TRIGGER_HAPPY 0x2c0 #define BTN_TRIGGER_HAPPY1 0x2c0 #define BTN_TRIGGER_HAPPY2 0x2c1 From 3a0275384c2716d3bdb9c65ec31cf45f53274ac6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:25 +0200 Subject: [PATCH 06/23] HID: Add driver for Logitech gaming keyboards (G15, G15 v2) Add a driver to stop the extra "G" keys from sending F1 - F12 instead making them send KEY_GKEY# and also make the non-functional M1 - M3 and MR keys and the non-functional buttons below the LCD panel properly generated key events. Note the connect_mask and gkeys_settings_output_report variables may seem unnecessary since they are always set to the same value, these are there in preparation of adding support for the G, M and LCD keys on the G510 kbd. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- MAINTAINERS | 7 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 2 + drivers/hid/hid-lg-g15.c | 256 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 drivers/hid/hid-lg-g15.c diff --git a/MAINTAINERS b/MAINTAINERS index 7ea0c11b8e8d..29129623ceed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9587,6 +9587,13 @@ S: Maintained F: Documentation/admin-guide/ldm.rst F: block/partitions/ldm.* +LOGITECH HID GAMING KEYBOARDS +M: Hans de Goede +L: linux-input@vger.kernel.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git +S: Maintained +F: drivers/hid/hid-lg-g15.c + LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI) M: Sathya Prakash M: Chaitra P B diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 0c03308cfb08..bfefa365b1ce 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_HID_KYE) += hid-kye.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o +obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o obj-$(CONFIG_HID_MACALLY) += hid-macally.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 76969a22b0f2..df37a0fd4efa 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -747,6 +747,8 @@ #define USB_DEVICE_ID_LOGITECH_DUAL_ACTION 0xc216 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219 +#define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222 +#define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227 #define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f #define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262 #define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283 diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c new file mode 100644 index 000000000000..724dc060983f --- /dev/null +++ b/drivers/hid/hid-lg-g15.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for gaming keys on Logitech gaming keyboards (such as the G15) + * + * Copyright (c) 2019 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define LG_G15_TRANSFER_BUF_SIZE 20 + +enum lg_g15_model { + LG_G15, + LG_G15_V2, +}; + +struct lg_g15_data { + /* Must be first for proper dma alignment */ + u8 transfer_buf[LG_G15_TRANSFER_BUF_SIZE]; + struct input_dev *input; + struct hid_device *hdev; + enum lg_g15_model model; +}; + +/* On the G15 Mark I Logitech has been quite creative with which bit is what */ +static int lg_g15_event(struct lg_g15_data *g15, u8 *data, int size) +{ + int i, val; + + /* G1 - G6 */ + for (i = 0; i < 6; i++) { + val = data[i + 1] & (1 << i); + input_report_key(g15->input, KEY_MACRO1 + i, val); + } + /* G7 - G12 */ + for (i = 0; i < 6; i++) { + val = data[i + 2] & (1 << i); + input_report_key(g15->input, KEY_MACRO7 + i, val); + } + /* G13 - G17 */ + for (i = 0; i < 5; i++) { + val = data[i + 1] & (4 << i); + input_report_key(g15->input, KEY_MACRO13 + i, val); + } + /* G18 */ + input_report_key(g15->input, KEY_MACRO18, data[8] & 0x40); + + /* M1 - M3 */ + for (i = 0; i < 3; i++) { + val = data[i + 6] & (1 << i); + input_report_key(g15->input, KEY_MACRO_PRESET1 + i, val); + } + /* MR */ + input_report_key(g15->input, KEY_MACRO_RECORD_START, data[7] & 0x40); + + /* Most left (round) button below the LCD */ + input_report_key(g15->input, KEY_KBD_LCD_MENU1, data[8] & 0x80); + /* 4 other buttons below the LCD */ + for (i = 0; i < 4; i++) { + val = data[i + 2] & 0x80; + input_report_key(g15->input, KEY_KBD_LCD_MENU2 + i, val); + } + + input_sync(g15->input); + return 0; +} + +static int lg_g15_v2_event(struct lg_g15_data *g15, u8 *data, int size) +{ + int i, val; + + /* G1 - G6 */ + for (i = 0; i < 6; i++) { + val = data[1] & (1 << i); + input_report_key(g15->input, KEY_MACRO1 + i, val); + } + + /* M1 - M3 + MR */ + input_report_key(g15->input, KEY_MACRO_PRESET1, data[1] & 0x40); + input_report_key(g15->input, KEY_MACRO_PRESET2, data[1] & 0x80); + input_report_key(g15->input, KEY_MACRO_PRESET3, data[2] & 0x20); + input_report_key(g15->input, KEY_MACRO_RECORD_START, data[2] & 0x40); + + /* Round button to the left of the LCD */ + input_report_key(g15->input, KEY_KBD_LCD_MENU1, data[2] & 0x80); + /* 4 buttons below the LCD */ + for (i = 0; i < 4; i++) { + val = data[2] & (2 << i); + input_report_key(g15->input, KEY_KBD_LCD_MENU2 + i, val); + } + + input_sync(g15->input); + return 0; +} + +static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct lg_g15_data *g15 = hid_get_drvdata(hdev); + + if (g15->model == LG_G15 && data[0] == 0x02 && size == 9) + return lg_g15_event(g15, data, size); + + if (g15->model == LG_G15_V2 && data[0] == 0x02 && size == 5) + return lg_g15_v2_event(g15, data, size); + + return 0; +} + +static int lg_g15_input_open(struct input_dev *dev) +{ + struct hid_device *hdev = input_get_drvdata(dev); + + return hid_hw_open(hdev); +} + +static void lg_g15_input_close(struct input_dev *dev) +{ + struct hid_device *hdev = input_get_drvdata(dev); + + hid_hw_close(hdev); +} + +static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + u8 gkeys_settings_output_report = 0; + unsigned int connect_mask = 0; + struct lg_g15_data *g15; + struct input_dev *input; + int ret, i, gkeys = 0; + + ret = hid_parse(hdev); + if (ret) + return ret; + + g15 = devm_kzalloc(&hdev->dev, sizeof(*g15), GFP_KERNEL); + if (!g15) + return -ENOMEM; + + input = devm_input_allocate_device(&hdev->dev); + if (!input) + return -ENOMEM; + + g15->hdev = hdev; + g15->model = id->driver_data; + hid_set_drvdata(hdev, (void *)g15); + + switch (g15->model) { + case LG_G15: + /* + * The G15 and G15 v2 use a separate usb-device (on a builtin + * hub) which emulates a keyboard for the F1 - F12 emulation + * on the G-keys, which we disable, rendering the emulated kbd + * non-functional, so we do not let hid-input connect. + */ + connect_mask = HID_CONNECT_HIDRAW; + gkeys_settings_output_report = 0x02; + gkeys = 18; + break; + case LG_G15_V2: + connect_mask = HID_CONNECT_HIDRAW; + gkeys_settings_output_report = 0x02; + gkeys = 6; + break; + } + + ret = hid_hw_start(hdev, connect_mask); + if (ret) + return ret; + + /* Tell the keyboard to stop sending F1-F12 + 1-6 for G1 - G18 */ + if (gkeys_settings_output_report) { + g15->transfer_buf[0] = gkeys_settings_output_report; + memset(g15->transfer_buf + 1, 0, gkeys); + /* + * The kbd ignores our output report if we do not queue + * an URB on the USB input endpoint first... + */ + ret = hid_hw_open(hdev); + if (ret) + goto error_hw_stop; + ret = hid_hw_output_report(hdev, g15->transfer_buf, gkeys + 1); + hid_hw_close(hdev); + } + + if (ret < 0) { + hid_err(hdev, "Error disabling keyboard emulation for the G-keys\n"); + goto error_hw_stop; + } + + input->name = "Logitech Gaming Keyboard Gaming Keys"; + input->phys = hdev->phys; + input->uniq = hdev->uniq; + input->id.bustype = hdev->bus; + input->id.vendor = hdev->vendor; + input->id.product = hdev->product; + input->id.version = hdev->version; + input->dev.parent = &hdev->dev; + input->open = lg_g15_input_open; + input->close = lg_g15_input_close; + + /* G-keys */ + for (i = 0; i < gkeys; i++) + input_set_capability(input, EV_KEY, KEY_MACRO1 + i); + + /* M1 - M3 and MR keys */ + for (i = 0; i < 3; i++) + input_set_capability(input, EV_KEY, KEY_MACRO_PRESET1 + i); + input_set_capability(input, EV_KEY, KEY_MACRO_RECORD_START); + + /* Keys below the LCD, intended for controlling a menu on the LCD */ + for (i = 0; i < 5; i++) + input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i); + + g15->input = input; + input_set_drvdata(input, hdev); + + ret = input_register_device(input); + if (ret) + goto error_hw_stop; + + return 0; + +error_hw_stop: + hid_hw_stop(hdev); + return ret; +} + +static const struct hid_device_id lg_g15_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_G15_LCD), + .driver_data = LG_G15 }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_G15_V2_LCD), + .driver_data = LG_G15_V2 }, + { } +}; +MODULE_DEVICE_TABLE(hid, lg_g15_devices); + +static struct hid_driver lg_g15_driver = { + .name = "lg-g15", + .id_table = lg_g15_devices, + .raw_event = lg_g15_raw_event, + .probe = lg_g15_probe, +}; +module_hid_driver(lg_g15_driver); + +MODULE_LICENSE("GPL"); From 97b741aba918c4143f4208d2421d08ff215c1b49 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:26 +0200 Subject: [PATCH 07/23] HID: lg-g15: Add keyboard and LCD backlight control Register a led_classdev for the keyboard backlight and another one for the LCD backlight. This commit also includes LED_BRIGHT_HW_CHANGED support, which together with a desktop-environment which supports this, such as GNOME3 leads to the kbd-backlight OSD being show with the new level when changing the backlight setting through the hotkey on the keyboard. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg-g15.c | 156 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index 724dc060983f..1b6af5241aa6 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -17,19 +17,134 @@ #define LG_G15_TRANSFER_BUF_SIZE 20 +#define LG_G15_FEATURE_REPORT 0x02 + enum lg_g15_model { LG_G15, LG_G15_V2, }; +enum lg_g15_led_type { + LG_G15_KBD_BRIGHTNESS, + LG_G15_LCD_BRIGHTNESS, + LG_G15_BRIGHTNESS_MAX, +}; + +struct lg_g15_led { + struct led_classdev cdev; + enum led_brightness brightness; + enum lg_g15_led_type led; +}; + struct lg_g15_data { /* Must be first for proper dma alignment */ u8 transfer_buf[LG_G15_TRANSFER_BUF_SIZE]; + /* Protects the transfer_buf and led brightness */ + struct mutex mutex; + struct work_struct work; struct input_dev *input; struct hid_device *hdev; enum lg_g15_model model; + struct lg_g15_led leds[LG_G15_BRIGHTNESS_MAX]; }; +static int lg_g15_update_led_brightness(struct lg_g15_data *g15) +{ + int ret; + + ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT, + g15->transfer_buf, 4, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != 4) { + hid_err(g15->hdev, "Error getting LED brightness: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + + g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = g15->transfer_buf[1]; + g15->leds[LG_G15_LCD_BRIGHTNESS].brightness = g15->transfer_buf[2]; + return 0; +} + +static enum led_brightness lg_g15_led_get(struct led_classdev *led_cdev) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + enum led_brightness brightness; + + mutex_lock(&g15->mutex); + lg_g15_update_led_brightness(g15); + brightness = g15->leds[g15_led->led].brightness; + mutex_unlock(&g15->mutex); + + return brightness; +} + +static int lg_g15_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + int ret; + + /* Ignore LED off on unregister / keyboard unplug */ + if (led_cdev->flags & LED_UNREGISTERING) + return 0; + + mutex_lock(&g15->mutex); + + g15->transfer_buf[0] = LG_G15_FEATURE_REPORT; + g15->transfer_buf[1] = g15_led->led + 1; + g15->transfer_buf[2] = brightness << (g15_led->led * 4); + g15->transfer_buf[3] = 0; + + ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT, + g15->transfer_buf, 4, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret == 4) { + /* Success */ + g15_led->brightness = brightness; + ret = 0; + } else { + hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret); + ret = (ret < 0) ? ret : -EIO; + } + + mutex_unlock(&g15->mutex); + + return ret; +} + +static void lg_g15_leds_changed_work(struct work_struct *work) +{ + struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work); + enum led_brightness old_brightness[LG_G15_BRIGHTNESS_MAX]; + enum led_brightness brightness[LG_G15_BRIGHTNESS_MAX]; + int i, ret; + + mutex_lock(&g15->mutex); + for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) + old_brightness[i] = g15->leds[i].brightness; + + ret = lg_g15_update_led_brightness(g15); + + for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) + brightness[i] = g15->leds[i].brightness; + mutex_unlock(&g15->mutex); + + if (ret) + return; + + for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) { + if (brightness[i] == old_brightness[i]) + continue; + + led_classdev_notify_brightness_hw_changed(&g15->leds[i].cdev, + brightness[i]); + } +} + /* On the G15 Mark I Logitech has been quite creative with which bit is what */ static int lg_g15_event(struct lg_g15_data *g15, u8 *data, int size) { @@ -69,6 +184,10 @@ static int lg_g15_event(struct lg_g15_data *g15, u8 *data, int size) input_report_key(g15->input, KEY_KBD_LCD_MENU2 + i, val); } + /* Backlight cycle button pressed? */ + if (data[1] & 0x80) + schedule_work(&g15->work); + input_sync(g15->input); return 0; } @@ -97,6 +216,10 @@ static int lg_g15_v2_event(struct lg_g15_data *g15, u8 *data, int size) input_report_key(g15->input, KEY_KBD_LCD_MENU2 + i, val); } + /* Backlight cycle button pressed? */ + if (data[2] & 0x01) + schedule_work(&g15->work); + input_sync(g15->input); return 0; } @@ -129,6 +252,23 @@ static void lg_g15_input_close(struct input_dev *dev) hid_hw_close(hdev); } +static int lg_g15_register_led(struct lg_g15_data *g15, int i) +{ + const char * const led_names[] = { + "g15::kbd_backlight", + "g15::lcd_backlight", + }; + + g15->leds[i].led = i; + g15->leds[i].cdev.name = led_names[i]; + g15->leds[i].cdev.brightness_set_blocking = lg_g15_led_set; + g15->leds[i].cdev.brightness_get = lg_g15_led_get; + g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED; + g15->leds[i].cdev.max_brightness = 2; + + return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev); +} + static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) { u8 gkeys_settings_output_report = 0; @@ -145,12 +285,15 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) if (!g15) return -ENOMEM; + mutex_init(&g15->mutex); + input = devm_input_allocate_device(&hdev->dev); if (!input) return -ENOMEM; g15->hdev = hdev; g15->model = id->driver_data; + INIT_WORK(&g15->work, lg_g15_leds_changed_work); hid_set_drvdata(hdev, (void *)g15); switch (g15->model) { @@ -196,6 +339,12 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) goto error_hw_stop; } + /* Get initial brightness levels */ + ret = lg_g15_update_led_brightness(g15); + if (ret) + goto error_hw_stop; + + /* Setup and register input device */ input->name = "Logitech Gaming Keyboard Gaming Keys"; input->phys = hdev->phys; input->uniq = hdev->uniq; @@ -227,6 +376,13 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto error_hw_stop; + /* Register LED devices */ + for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) { + ret = lg_g15_register_led(g15, i); + if (ret) + goto error_hw_stop; + } + return 0; error_hw_stop: From d5b5fc8c89525d5e6f44a85f40db458af938e330 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:27 +0200 Subject: [PATCH 08/23] HID: lg-g15: Add support for the M1-M3 and MR LEDs Add support for controlling the LEDs below the M1-M3 and MR keys. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg-g15.c | 54 ++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index 1b6af5241aa6..ef06a85de60b 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -28,6 +28,11 @@ enum lg_g15_led_type { LG_G15_KBD_BRIGHTNESS, LG_G15_LCD_BRIGHTNESS, LG_G15_BRIGHTNESS_MAX, + LG_G15_MACRO_PRESET1 = 2, + LG_G15_MACRO_PRESET2, + LG_G15_MACRO_PRESET3, + LG_G15_MACRO_RECORD, + LG_G15_LED_MAX }; struct lg_g15_led { @@ -45,7 +50,7 @@ struct lg_g15_data { struct input_dev *input; struct hid_device *hdev; enum lg_g15_model model; - struct lg_g15_led leds[LG_G15_BRIGHTNESS_MAX]; + struct lg_g15_led leds[LG_G15_LED_MAX]; }; static int lg_g15_update_led_brightness(struct lg_g15_data *g15) @@ -62,6 +67,16 @@ static int lg_g15_update_led_brightness(struct lg_g15_data *g15) g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = g15->transfer_buf[1]; g15->leds[LG_G15_LCD_BRIGHTNESS].brightness = g15->transfer_buf[2]; + + g15->leds[LG_G15_MACRO_PRESET1].brightness = + !(g15->transfer_buf[3] & 0x01); + g15->leds[LG_G15_MACRO_PRESET2].brightness = + !(g15->transfer_buf[3] & 0x02); + g15->leds[LG_G15_MACRO_PRESET3].brightness = + !(g15->transfer_buf[3] & 0x04); + g15->leds[LG_G15_MACRO_RECORD].brightness = + !(g15->transfer_buf[3] & 0x08); + return 0; } @@ -86,7 +101,8 @@ static int lg_g15_led_set(struct led_classdev *led_cdev, struct lg_g15_led *g15_led = container_of(led_cdev, struct lg_g15_led, cdev); struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); - int ret; + u8 val, mask = 0; + int i, ret; /* Ignore LED off on unregister / keyboard unplug */ if (led_cdev->flags & LED_UNREGISTERING) @@ -95,10 +111,26 @@ static int lg_g15_led_set(struct led_classdev *led_cdev, mutex_lock(&g15->mutex); g15->transfer_buf[0] = LG_G15_FEATURE_REPORT; - g15->transfer_buf[1] = g15_led->led + 1; - g15->transfer_buf[2] = brightness << (g15_led->led * 4); g15->transfer_buf[3] = 0; + if (g15_led->led < LG_G15_BRIGHTNESS_MAX) { + g15->transfer_buf[1] = g15_led->led + 1; + g15->transfer_buf[2] = brightness << (g15_led->led * 4); + } else { + for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; i++) { + if (i == g15_led->led) + val = brightness; + else + val = g15->leds[i].brightness; + + if (val) + mask |= 1 << (i - LG_G15_MACRO_PRESET1); + } + + g15->transfer_buf[1] = 0x04; + g15->transfer_buf[2] = ~mask; + } + ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT, g15->transfer_buf, 4, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); @@ -257,14 +289,22 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i) const char * const led_names[] = { "g15::kbd_backlight", "g15::lcd_backlight", + "g15::macro_preset1", + "g15::macro_preset2", + "g15::macro_preset3", + "g15::macro_record", }; g15->leds[i].led = i; g15->leds[i].cdev.name = led_names[i]; g15->leds[i].cdev.brightness_set_blocking = lg_g15_led_set; g15->leds[i].cdev.brightness_get = lg_g15_led_get; - g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED; - g15->leds[i].cdev.max_brightness = 2; + if (i < LG_G15_BRIGHTNESS_MAX) { + g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED; + g15->leds[i].cdev.max_brightness = 2; + } else { + g15->leds[i].cdev.max_brightness = 1; + } return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev); } @@ -377,7 +417,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) goto error_hw_stop; /* Register LED devices */ - for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) { + for (i = 0; i < LG_G15_LED_MAX; i++) { ret = lg_g15_register_led(g15, i); if (ret) goto error_hw_stop; From ad4203f5a2435efef161516958f743982c9c2ee8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:28 +0200 Subject: [PATCH 09/23] HID: lg-g15: Add support for the G510 keyboards' gaming keys Add support for the gaming and LCD menu keys on the G510 keyboard. Note this commit does not add support for the keyboard and LCD backlight and the status LEDs, this is done in a follow up commit. Note the G510 generates 4 different input reports on its second (Consumer Keys) interface: -input report 1 is standard bootclass keyboard input report, mirroring normal keyboard interface -input report 2 is consumer page keys -input report 3 is gkeys, etc. -input report 4 is LED status, single byte, bits: bit 2: kbd and LCD backlight is *off* when set, toggled by the light key bit 3: headphone mute LED bit 4: mic mute LED Input-report 1 we ignore since this is a duplicate report from the first interface, report 2 is handled by the regular hid-input code. In this commit we add handling for input report 3. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 2 + drivers/hid/hid-lg-g15.c | 126 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index df37a0fd4efa..75d8cf7c29bc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -749,6 +749,8 @@ #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219 #define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222 #define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227 +#define USB_DEVICE_ID_LOGITECH_G510 0xc22d +#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e #define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f #define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262 #define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283 diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index ef06a85de60b..b0891503fbcf 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -22,6 +22,8 @@ enum lg_g15_model { LG_G15, LG_G15_V2, + LG_G510, + LG_G510_USB_AUDIO, }; enum lg_g15_led_type { @@ -51,12 +53,16 @@ struct lg_g15_data { struct hid_device *hdev; enum lg_g15_model model; struct lg_g15_led leds[LG_G15_LED_MAX]; + bool game_mode_enabled; }; static int lg_g15_update_led_brightness(struct lg_g15_data *g15) { int ret; + if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) + return 0; + ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT, g15->transfer_buf, 4, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); @@ -256,16 +262,73 @@ static int lg_g15_v2_event(struct lg_g15_data *g15, u8 *data, int size) return 0; } +static int lg_g510_event(struct lg_g15_data *g15, u8 *data, int size) +{ + bool game_mode_enabled; + int i, val; + + /* G1 - G18 */ + for (i = 0; i < 18; i++) { + val = data[i / 8 + 1] & (1 << (i % 8)); + input_report_key(g15->input, KEY_MACRO1 + i, val); + } + + /* Game mode on/off slider */ + game_mode_enabled = data[3] & 0x04; + if (game_mode_enabled != g15->game_mode_enabled) { + if (game_mode_enabled) + hid_info(g15->hdev, "Game Mode enabled, Windows (super) key is disabled\n"); + else + hid_info(g15->hdev, "Game Mode disabled\n"); + g15->game_mode_enabled = game_mode_enabled; + } + + /* M1 - M3 */ + for (i = 0; i < 3; i++) { + val = data[3] & (0x10 << i); + input_report_key(g15->input, KEY_MACRO_PRESET1 + i, val); + } + /* MR */ + input_report_key(g15->input, KEY_MACRO_RECORD_START, data[3] & 0x80); + + /* LCD menu keys */ + for (i = 0; i < 5; i++) { + val = data[4] & (1 << i); + input_report_key(g15->input, KEY_KBD_LCD_MENU1 + i, val); + } + + /* Headphone Mute */ + input_report_key(g15->input, KEY_MUTE, data[4] & 0x20); + /* Microphone Mute */ + input_report_key(g15->input, KEY_F20, data[4] & 0x40); + + input_sync(g15->input); + return 0; +} + static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct lg_g15_data *g15 = hid_get_drvdata(hdev); - if (g15->model == LG_G15 && data[0] == 0x02 && size == 9) - return lg_g15_event(g15, data, size); + if (!g15) + return 0; - if (g15->model == LG_G15_V2 && data[0] == 0x02 && size == 5) - return lg_g15_v2_event(g15, data, size); + switch (g15->model) { + case LG_G15: + if (data[0] == 0x02 && size == 9) + return lg_g15_event(g15, data, size); + break; + case LG_G15_V2: + if (data[0] == 0x02 && size == 5) + return lg_g15_v2_event(g15, data, size); + break; + case LG_G510: + case LG_G510_USB_AUDIO: + if (data[0] == 0x03 && size == 5) + return lg_g510_event(g15, data, size); + break; + } return 0; } @@ -312,15 +375,33 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i) static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) { u8 gkeys_settings_output_report = 0; + u8 gkeys_settings_feature_report = 0; + struct hid_report_enum *rep_enum; unsigned int connect_mask = 0; + bool has_ff000000 = false; struct lg_g15_data *g15; struct input_dev *input; + struct hid_report *rep; int ret, i, gkeys = 0; + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + ret = hid_parse(hdev); if (ret) return ret; + /* + * Some models have multiple interfaces, we want the interface with + * with the f000.0000 application input report. + */ + rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; + list_for_each_entry(rep, &rep_enum->report_list, list) { + if (rep->application == 0xff000000) + has_ff000000 = true; + } + if (!has_ff000000) + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); + g15 = devm_kzalloc(&hdev->dev, sizeof(*g15), GFP_KERNEL); if (!g15) return -ENOMEM; @@ -353,6 +434,12 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) gkeys_settings_output_report = 0x02; gkeys = 6; break; + case LG_G510: + case LG_G510_USB_AUDIO: + connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW; + gkeys_settings_feature_report = 0x01; + gkeys = 18; + break; } ret = hid_hw_start(hdev, connect_mask); @@ -374,6 +461,15 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_hw_close(hdev); } + if (gkeys_settings_feature_report) { + g15->transfer_buf[0] = gkeys_settings_feature_report; + memset(g15->transfer_buf + 1, 0, gkeys); + ret = hid_hw_raw_request(g15->hdev, + gkeys_settings_feature_report, + g15->transfer_buf, gkeys + 1, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + } + if (ret < 0) { hid_err(hdev, "Error disabling keyboard emulation for the G-keys\n"); goto error_hw_stop; @@ -409,6 +505,17 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) for (i = 0; i < 5; i++) input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i); + /* + * On the G510 only report headphone and mic mute keys when *not* using + * the builtin USB audio device. When the builtin audio is used these + * keys directly toggle mute (and the LEDs) on/off. + */ + if (g15->model == LG_G510) { + input_set_capability(input, EV_KEY, KEY_MUTE); + /* Userspace expects F20 for micmute */ + input_set_capability(input, EV_KEY, KEY_F20); + } + g15->input = input; input_set_drvdata(input, hdev); @@ -416,6 +523,9 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto error_hw_stop; + if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) + return 0; + /* Register LED devices */ for (i = 0; i < LG_G15_LED_MAX; i++) { ret = lg_g15_register_led(g15, i); @@ -437,6 +547,14 @@ static const struct hid_device_id lg_g15_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G15_V2_LCD), .driver_data = LG_G15_V2 }, + /* G510 without a headset plugged in */ + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_G510), + .driver_data = LG_G510 }, + /* G510 with headset plugged in / with extra USB audio interface */ + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO), + .driver_data = LG_G510_USB_AUDIO }, { } }; MODULE_DEVICE_TABLE(hid, lg_g15_devices); From 1f8cde2a538b9bff65c57432beb5e58c91b373e9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:29 +0200 Subject: [PATCH 10/23] HID: lg-g15: Add support for controlling the G510's RGB backlight Note that the keyboard has a backlight on/off toggle button. If the backlight is turned off through that button, then any changes we make will be ignored and we cannot turn it back on again from the host. To workaround this we write the last set RGB values when we receive an event indicating that the backlight has been turned on again. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg-g15.c | 272 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 257 insertions(+), 15 deletions(-) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index b0891503fbcf..b8ea059c46f3 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -19,6 +19,10 @@ #define LG_G15_FEATURE_REPORT 0x02 +#define LG_G510_FEATURE_M_KEYS_LEDS 0x04 +#define LG_G510_FEATURE_BACKLIGHT_RGB 0x05 +#define LG_G510_FEATURE_POWER_ON_RGB 0x06 + enum lg_g15_model { LG_G15, LG_G15_V2, @@ -41,6 +45,7 @@ struct lg_g15_led { struct led_classdev cdev; enum led_brightness brightness; enum lg_g15_led_type led; + u8 red, green, blue; }; struct lg_g15_data { @@ -56,13 +61,12 @@ struct lg_g15_data { bool game_mode_enabled; }; +/******** G15 and G15 v2 LED functions ********/ + static int lg_g15_update_led_brightness(struct lg_g15_data *g15) { int ret; - if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) - return 0; - ret = hid_hw_raw_request(g15->hdev, LG_G15_FEATURE_REPORT, g15->transfer_buf, 4, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); @@ -183,6 +187,198 @@ static void lg_g15_leds_changed_work(struct work_struct *work) } } +/******** G510 LED functions ********/ + +static int lg_g510_get_initial_led_brightness(struct lg_g15_data *g15, int i) +{ + int ret, high; + + ret = hid_hw_raw_request(g15->hdev, LG_G510_FEATURE_BACKLIGHT_RGB + i, + g15->transfer_buf, 4, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != 4) { + hid_err(g15->hdev, "Error getting LED brightness: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + + high = max3(g15->transfer_buf[1], g15->transfer_buf[2], + g15->transfer_buf[3]); + + if (high) { + g15->leds[i].red = + DIV_ROUND_CLOSEST(g15->transfer_buf[1] * 255, high); + g15->leds[i].green = + DIV_ROUND_CLOSEST(g15->transfer_buf[2] * 255, high); + g15->leds[i].blue = + DIV_ROUND_CLOSEST(g15->transfer_buf[3] * 255, high); + g15->leds[i].brightness = high; + } else { + g15->leds[i].red = 255; + g15->leds[i].green = 255; + g15->leds[i].blue = 255; + g15->leds[i].brightness = 0; + } + + return 0; +} + +/* Must be called with g15->mutex locked */ +static int lg_g510_kbd_led_write(struct lg_g15_data *g15, + struct lg_g15_led *g15_led, + enum led_brightness brightness) +{ + int ret; + + g15->transfer_buf[0] = 5 + g15_led->led; + g15->transfer_buf[1] = + DIV_ROUND_CLOSEST(g15_led->red * brightness, 255); + g15->transfer_buf[2] = + DIV_ROUND_CLOSEST(g15_led->green * brightness, 255); + g15->transfer_buf[3] = + DIV_ROUND_CLOSEST(g15_led->blue * brightness, 255); + + ret = hid_hw_raw_request(g15->hdev, + LG_G510_FEATURE_BACKLIGHT_RGB + g15_led->led, + g15->transfer_buf, 4, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret == 4) { + /* Success */ + g15_led->brightness = brightness; + ret = 0; + } else { + hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret); + ret = (ret < 0) ? ret : -EIO; + } + + return ret; +} + +static int lg_g510_kbd_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + int ret; + + /* Ignore LED off on unregister / keyboard unplug */ + if (led_cdev->flags & LED_UNREGISTERING) + return 0; + + mutex_lock(&g15->mutex); + ret = lg_g510_kbd_led_write(g15, g15_led, brightness); + mutex_unlock(&g15->mutex); + + return ret; +} + +static enum led_brightness lg_g510_kbd_led_get(struct led_classdev *led_cdev) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + + return g15_led->brightness; +} + +static ssize_t color_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + unsigned long value; + int ret; + + if (count < 7 || (count == 8 && buf[7] != '\n') || count > 8) + return -EINVAL; + + if (buf[0] != '#') + return -EINVAL; + + ret = kstrtoul(buf + 1, 16, &value); + if (ret) + return ret; + + mutex_lock(&g15->mutex); + g15_led->red = (value & 0xff0000) >> 16; + g15_led->green = (value & 0x00ff00) >> 8; + g15_led->blue = (value & 0x0000ff); + ret = lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness); + mutex_unlock(&g15->mutex); + + return (ret < 0) ? ret : count; +} + +static ssize_t color_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + ssize_t ret; + + mutex_lock(&g15->mutex); + ret = sprintf(buf, "#%02x%02x%02x\n", + g15_led->red, g15_led->green, g15_led->blue); + mutex_unlock(&g15->mutex); + + return ret; +} + +static DEVICE_ATTR_RW(color); + +static struct attribute *lg_g510_kbd_led_attrs[] = { + &dev_attr_color.attr, + NULL, +}; + +static const struct attribute_group lg_g510_kbd_led_group = { + .attrs = lg_g510_kbd_led_attrs, +}; + +static const struct attribute_group *lg_g510_kbd_led_groups[] = { + &lg_g510_kbd_led_group, + NULL, +}; + +static void lg_g510_leds_sync_work(struct work_struct *work) +{ + struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work); + + mutex_lock(&g15->mutex); + lg_g510_kbd_led_write(g15, &g15->leds[LG_G15_KBD_BRIGHTNESS], + g15->leds[LG_G15_KBD_BRIGHTNESS].brightness); + mutex_unlock(&g15->mutex); +} + +/******** Generic LED functions ********/ +static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15) +{ + int ret; + + switch (g15->model) { + case LG_G15: + case LG_G15_V2: + return lg_g15_update_led_brightness(g15); + case LG_G510: + case LG_G510_USB_AUDIO: + ret = lg_g510_get_initial_led_brightness(g15, 0); + if (ret) + return ret; + + ret = lg_g510_get_initial_led_brightness(g15, 1); + if (ret) + return ret; + + return 0; + } + return -EINVAL; /* Never reached */ +} + +/******** Input functions ********/ + /* On the G15 Mark I Logitech has been quite creative with which bit is what */ static int lg_g15_event(struct lg_g15_data *g15, u8 *data, int size) { @@ -306,6 +502,22 @@ static int lg_g510_event(struct lg_g15_data *g15, u8 *data, int size) return 0; } +static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data, int size) +{ + bool backlight_disabled; + + /* + * The G510 ignores backlight updates when the backlight is turned off + * through the light toggle button on the keyboard, to work around this + * we queue a workitem to sync values when the backlight is turned on. + */ + backlight_disabled = data[1] & 0x04; + if (!backlight_disabled) + schedule_work(&g15->work); + + return 0; +} + static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { @@ -327,6 +539,8 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report, case LG_G510_USB_AUDIO: if (data[0] == 0x03 && size == 5) return lg_g510_event(g15, data, size); + if (data[0] == 0x04 && size == 2) + return lg_g510_leds_event(g15, data, size); break; } @@ -360,13 +574,42 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i) g15->leds[i].led = i; g15->leds[i].cdev.name = led_names[i]; - g15->leds[i].cdev.brightness_set_blocking = lg_g15_led_set; - g15->leds[i].cdev.brightness_get = lg_g15_led_get; - if (i < LG_G15_BRIGHTNESS_MAX) { - g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED; - g15->leds[i].cdev.max_brightness = 2; - } else { - g15->leds[i].cdev.max_brightness = 1; + + switch (g15->model) { + case LG_G15: + case LG_G15_V2: + g15->leds[i].cdev.brightness_set_blocking = lg_g15_led_set; + g15->leds[i].cdev.brightness_get = lg_g15_led_get; + if (i < LG_G15_BRIGHTNESS_MAX) { + g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED; + g15->leds[i].cdev.max_brightness = 2; + } else { + g15->leds[i].cdev.max_brightness = 1; + } + break; + case LG_G510: + case LG_G510_USB_AUDIO: + switch (i) { + case LG_G15_LCD_BRIGHTNESS: + /* + * The G510 does not have a separate LCD brightness, + * but it does have a separate power-on (reset) value. + */ + g15->leds[i].cdev.name = "g15::power_on_backlight_val"; + /* fall through */ + case LG_G15_KBD_BRIGHTNESS: + g15->leds[i].cdev.brightness_set_blocking = + lg_g510_kbd_led_set; + g15->leds[i].cdev.brightness_get = + lg_g510_kbd_led_get; + g15->leds[i].cdev.max_brightness = 255; + g15->leds[i].cdev.groups = lg_g510_kbd_led_groups; + break; + default: + /* TODO: Add support for M1 - M3 and MR leds */ + return 0; + } + break; } return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev); @@ -414,11 +657,11 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) g15->hdev = hdev; g15->model = id->driver_data; - INIT_WORK(&g15->work, lg_g15_leds_changed_work); hid_set_drvdata(hdev, (void *)g15); switch (g15->model) { case LG_G15: + INIT_WORK(&g15->work, lg_g15_leds_changed_work); /* * The G15 and G15 v2 use a separate usb-device (on a builtin * hub) which emulates a keyboard for the F1 - F12 emulation @@ -430,12 +673,14 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) gkeys = 18; break; case LG_G15_V2: + INIT_WORK(&g15->work, lg_g15_leds_changed_work); connect_mask = HID_CONNECT_HIDRAW; gkeys_settings_output_report = 0x02; gkeys = 6; break; case LG_G510: case LG_G510_USB_AUDIO: + INIT_WORK(&g15->work, lg_g510_leds_sync_work); connect_mask = HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW; gkeys_settings_feature_report = 0x01; gkeys = 18; @@ -476,7 +721,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) } /* Get initial brightness levels */ - ret = lg_g15_update_led_brightness(g15); + ret = lg_g15_get_initial_led_brightness(g15); if (ret) goto error_hw_stop; @@ -523,9 +768,6 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto error_hw_stop; - if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) - return 0; - /* Register LED devices */ for (i = 0; i < LG_G15_LED_MAX; i++) { ret = lg_g15_register_led(g15, i); From 4682bb8e050e2f2f1eb5527b85bbe95366de5427 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 28 Aug 2019 14:41:30 +0200 Subject: [PATCH 11/23] HID: lg-g15: Add support for the G510's M1-M3 and MR LEDs Add support for controlling the LEDs below the M1-M3 and MR keys on the G510. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg-g15.c | 93 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index b8ea059c46f3..8a9268a5c66a 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -353,6 +353,90 @@ static void lg_g510_leds_sync_work(struct work_struct *work) mutex_unlock(&g15->mutex); } +static int lg_g510_update_mkey_led_brightness(struct lg_g15_data *g15) +{ + int ret; + + ret = hid_hw_raw_request(g15->hdev, LG_G510_FEATURE_M_KEYS_LEDS, + g15->transfer_buf, 2, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != 2) { + hid_err(g15->hdev, "Error getting LED brightness: %d\n", ret); + ret = (ret < 0) ? ret : -EIO; + } + + g15->leds[LG_G15_MACRO_PRESET1].brightness = + !!(g15->transfer_buf[1] & 0x80); + g15->leds[LG_G15_MACRO_PRESET2].brightness = + !!(g15->transfer_buf[1] & 0x40); + g15->leds[LG_G15_MACRO_PRESET3].brightness = + !!(g15->transfer_buf[1] & 0x20); + g15->leds[LG_G15_MACRO_RECORD].brightness = + !!(g15->transfer_buf[1] & 0x10); + + return 0; +} + +static enum led_brightness lg_g510_mkey_led_get(struct led_classdev *led_cdev) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + enum led_brightness brightness; + + mutex_lock(&g15->mutex); + lg_g510_update_mkey_led_brightness(g15); + brightness = g15->leds[g15_led->led].brightness; + mutex_unlock(&g15->mutex); + + return brightness; +} + +static int lg_g510_mkey_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + u8 val, mask = 0; + int i, ret; + + /* Ignore LED off on unregister / keyboard unplug */ + if (led_cdev->flags & LED_UNREGISTERING) + return 0; + + mutex_lock(&g15->mutex); + + for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; i++) { + if (i == g15_led->led) + val = brightness; + else + val = g15->leds[i].brightness; + + if (val) + mask |= 0x80 >> (i - LG_G15_MACRO_PRESET1); + } + + g15->transfer_buf[0] = LG_G510_FEATURE_M_KEYS_LEDS; + g15->transfer_buf[1] = mask; + + ret = hid_hw_raw_request(g15->hdev, LG_G510_FEATURE_M_KEYS_LEDS, + g15->transfer_buf, 2, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret == 2) { + /* Success */ + g15_led->brightness = brightness; + ret = 0; + } else { + hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret); + ret = (ret < 0) ? ret : -EIO; + } + + mutex_unlock(&g15->mutex); + + return ret; +} + /******** Generic LED functions ********/ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15) { @@ -372,7 +456,7 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15) if (ret) return ret; - return 0; + return lg_g510_update_mkey_led_brightness(g15); } return -EINVAL; /* Never reached */ } @@ -606,8 +690,11 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i) g15->leds[i].cdev.groups = lg_g510_kbd_led_groups; break; default: - /* TODO: Add support for M1 - M3 and MR leds */ - return 0; + g15->leds[i].cdev.brightness_set_blocking = + lg_g510_mkey_led_set; + g15->leds[i].cdev.brightness_get = + lg_g510_mkey_led_get; + g15->leds[i].cdev.max_brightness = 1; } break; } From bdd08fff4915a9705ea3ec20c82ab8f745bdf225 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Oct 2019 09:37:15 +0200 Subject: [PATCH 12/23] HID: logitech: Add depends on LEDS_CLASS to Logitech Kconfig entry Commit 97b741aba918 ("HID: lg-g15: Add keyboard and LCD backlight control") makes the hid-lg15 kernel module, which gets configured through config HID_LOGITECH depends on symbols from the led class. Add a depends on LEDS_CLASS to HID_LOGITECH to avoid undefined reference errors on the led class symbols. Fixes: 97b741aba918 ("HID: lg-g15: Add keyboard and LCD backlight control") Reported-by: kbuild test robot Reported-by: Stephen Rothwell Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1ecb5124421c..494a39e74939 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -525,6 +525,7 @@ config HID_LENOVO config HID_LOGITECH tristate "Logitech devices" depends on HID + depends on LEDS_CLASS default !EXPERT ---help--- Support for Logitech devices that are not fully compliant with HID standard. From 20c55f250618d4d110b27410a8ffd2c02a0e6911 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 3 Oct 2019 11:18:00 +0800 Subject: [PATCH 13/23] HID: google: Detect base folded usage instead of hard-coding whiskers Some other hammer-like device will emit a similar code, let's look for the folded event in HID usage table, instead of hard-coding whiskers in many places. Signed-off-by: Nicolas Boichat Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 53 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 31e4a39946f5..5d1544c1f691 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -370,7 +370,7 @@ static void hammer_unregister_leds(struct hid_device *hdev) #define HID_UP_GOOGLEVENDOR 0xffd10000 #define HID_VD_KBD_FOLDED 0x00000019 -#define WHISKERS_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED) +#define HID_USAGE_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED) /* HID usage for keyboard backlight (Alphanumeric display brightness) */ #define HID_AD_BRIGHTNESS 0x00140046 @@ -380,8 +380,7 @@ static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_usage *usage, unsigned long **bit, int *max) { - if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - usage->hid == WHISKERS_KBD_FOLDED) { + if (usage->hid == HID_USAGE_KBD_FOLDED) { /* * We do not want to have this usage mapped as it will get * mixed in with "base attached" signal and delivered over @@ -398,8 +397,7 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field, { unsigned long flags; - if (hid->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - usage->hid == WHISKERS_KBD_FOLDED) { + if (usage->hid == HID_USAGE_KBD_FOLDED) { spin_lock_irqsave(&cbas_ec_lock, flags); /* @@ -424,33 +422,22 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field, return 0; } -static bool hammer_is_keyboard_interface(struct hid_device *hdev) +static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type, + unsigned application, unsigned usage) { - struct hid_report_enum *re = &hdev->report_enum[HID_INPUT_REPORT]; - struct hid_report *report; - - list_for_each_entry(report, &re->report_list, list) - if (report->application == HID_GD_KEYBOARD) - return true; - - return false; -} - -static bool hammer_has_backlight_control(struct hid_device *hdev) -{ - struct hid_report_enum *re = &hdev->report_enum[HID_OUTPUT_REPORT]; + struct hid_report_enum *re = &hdev->report_enum[report_type]; struct hid_report *report; int i, j; list_for_each_entry(report, &re->report_list, list) { - if (report->application != HID_GD_KEYBOARD) + if (report->application != application) continue; for (i = 0; i < report->maxfield; i++) { struct hid_field *field = report->field[i]; for (j = 0; j < field->maxusage; j++) - if (field->usage[j].hid == HID_AD_BRIGHTNESS) + if (field->usage[j].hid == usage) return true; } } @@ -458,6 +445,18 @@ static bool hammer_has_backlight_control(struct hid_device *hdev) return false; } +static bool hammer_has_folded_event(struct hid_device *hdev) +{ + return hammer_has_usage(hdev, HID_INPUT_REPORT, + HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED); +} + +static bool hammer_has_backlight_control(struct hid_device *hdev) +{ + return hammer_has_usage(hdev, HID_OUTPUT_REPORT, + HID_GD_KEYBOARD, HID_AD_BRIGHTNESS); +} + static int hammer_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -473,12 +472,11 @@ static int hammer_probe(struct hid_device *hdev, /* * We always want to poll for, and handle tablet mode events from - * Whiskers, even when nobody has opened the input device. This also - * prevents the hid core from dropping early tablet mode events from - * the device. + * devices that have folded usage, even when nobody has opened the input + * device. This also prevents the hid core from dropping early tablet + * mode events from the device. */ - if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) { + if (hammer_has_folded_event(hdev)) { hdev->quirks |= HID_QUIRK_ALWAYS_POLL; error = hid_hw_open(hdev); if (error) @@ -500,8 +498,7 @@ static void hammer_remove(struct hid_device *hdev) { unsigned long flags; - if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) { + if (hammer_has_folded_event(hdev)) { hid_hw_close(hdev); /* From 76f8cf6e0cfdbc5ad88d75c25e7879f0986a7ce0 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 24 Oct 2019 17:18:37 +0200 Subject: [PATCH 14/23] HID: intel-ish-hid: Spelling s/diconnect/disconnect/ Fix misspelling of "disconnect". Signed-off-by: Geert Uytterhoeven Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/hbm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index c6c9ac09dac3..30a91d068306 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -402,7 +402,7 @@ static void ishtp_hbm_cl_connect_res(struct ishtp_device *dev, * @dev: ISHTP device instance * @disconnect_req: disconnect request structure * - * Disconnect request bus message from the fw. Send diconnect response. + * Disconnect request bus message from the fw. Send disconnect response. */ static void ishtp_hbm_fw_disconnect_req(struct ishtp_device *dev, struct hbm_client_connect_request *disconnect_req) From 1cb0d2aee26335d0bccf29100c7bed00ebece851 Mon Sep 17 00:00:00 2001 From: Candle Sun Date: Tue, 22 Oct 2019 22:21:39 +0800 Subject: [PATCH 15/23] HID: core: check whether Usage Page item is after Usage ID items Upstream commit 58e75155009c ("HID: core: move Usage Page concatenation to Main item") adds support for Usage Page item after Usage ID items (such as keyboards manufactured by Primax). Usage Page concatenation in Main item works well for following report descriptor patterns: USAGE_PAGE (Keyboard) 05 07 USAGE_MINIMUM (Keyboard LeftControl) 19 E0 USAGE_MAXIMUM (Keyboard Right GUI) 29 E7 LOGICAL_MINIMUM (0) 15 00 LOGICAL_MAXIMUM (1) 25 01 REPORT_SIZE (1) 75 01 REPORT_COUNT (8) 95 08 INPUT (Data,Var,Abs) 81 02 ------------- USAGE_MINIMUM (Keyboard LeftControl) 19 E0 USAGE_MAXIMUM (Keyboard Right GUI) 29 E7 LOGICAL_MINIMUM (0) 15 00 LOGICAL_MAXIMUM (1) 25 01 REPORT_SIZE (1) 75 01 REPORT_COUNT (8) 95 08 USAGE_PAGE (Keyboard) 05 07 INPUT (Data,Var,Abs) 81 02 But it makes the parser act wrong for the following report descriptor pattern(such as some Gamepads): USAGE_PAGE (Button) 05 09 USAGE (Button 1) 09 01 USAGE (Button 2) 09 02 USAGE (Button 4) 09 04 USAGE (Button 5) 09 05 USAGE (Button 7) 09 07 USAGE (Button 8) 09 08 USAGE (Button 14) 09 0E USAGE (Button 15) 09 0F USAGE (Button 13) 09 0D USAGE_PAGE (Consumer Devices) 05 0C USAGE (Back) 0a 24 02 USAGE (HomePage) 0a 23 02 LOGICAL_MINIMUM (0) 15 00 LOGICAL_MAXIMUM (1) 25 01 REPORT_SIZE (1) 75 01 REPORT_COUNT (11) 95 0B INPUT (Data,Var,Abs) 81 02 With Usage Page concatenation in Main item, parser recognizes all the 11 Usages as consumer keys, it is not the HID device's real intention. This patch checks whether Usage Page is really defined after Usage ID items by comparing usage page using status. Usage Page concatenation on currently defined Usage Page will always do in local parsing when Usage ID items encountered. When Main item is parsing, concatenation will do again with last defined Usage Page if this page has not been used in the previous usages concatenation. Signed-off-by: Candle Sun Signed-off-by: Nianfu Bai Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 51 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 63fdbf09b044..2fa3587d974f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -211,6 +211,18 @@ static unsigned hid_lookup_collection(struct hid_parser *parser, unsigned type) return 0; /* we know nothing about this usage type */ } +/* + * Concatenate usage which defines 16 bits or less with the + * currently defined usage page to form a 32 bit usage + */ + +static void complete_usage(struct hid_parser *parser, unsigned int index) +{ + parser->local.usage[index] &= 0xFFFF; + parser->local.usage[index] |= + (parser->global.usage_page & 0xFFFF) << 16; +} + /* * Add a usage to the temporary parser table. */ @@ -222,6 +234,14 @@ static int hid_add_usage(struct hid_parser *parser, unsigned usage, u8 size) return -1; } parser->local.usage[parser->local.usage_index] = usage; + + /* + * If Usage item only includes usage id, concatenate it with + * currently defined usage page + */ + if (size <= 2) + complete_usage(parser, parser->local.usage_index); + parser->local.usage_size[parser->local.usage_index] = size; parser->local.collection_index[parser->local.usage_index] = parser->collection_stack_ptr ? @@ -543,13 +563,32 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item) * usage value." */ -static void hid_concatenate_usage_page(struct hid_parser *parser) +static void hid_concatenate_last_usage_page(struct hid_parser *parser) { int i; + unsigned int usage_page; + unsigned int current_page; - for (i = 0; i < parser->local.usage_index; i++) - if (parser->local.usage_size[i] <= 2) - parser->local.usage[i] += parser->global.usage_page << 16; + if (!parser->local.usage_index) + return; + + usage_page = parser->global.usage_page; + + /* + * Concatenate usage page again only if last declared Usage Page + * has not been already used in previous usages concatenation + */ + for (i = parser->local.usage_index - 1; i >= 0; i--) { + if (parser->local.usage_size[i] > 2) + /* Ignore extended usages */ + continue; + + current_page = parser->local.usage[i] >> 16; + if (current_page == usage_page) + break; + + complete_usage(parser, i); + } } /* @@ -561,7 +600,7 @@ static int hid_parser_main(struct hid_parser *parser, struct hid_item *item) __u32 data; int ret; - hid_concatenate_usage_page(parser); + hid_concatenate_last_usage_page(parser); data = item_udata(item); @@ -772,7 +811,7 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item) __u32 data; int i; - hid_concatenate_usage_page(parser); + hid_concatenate_last_usage_page(parser); data = item_udata(item); From f1a0094cbbe97a5f8aca7bdc64bfe43ac9dc6879 Mon Sep 17 00:00:00 2001 From: Jinke Fan Date: Wed, 13 Nov 2019 11:38:41 +0800 Subject: [PATCH 16/23] HID: quirks: Add quirk for HP MSU1465 PIXART OEM mouse The PixArt OEM mouse disconnets/reconnects every minute on Linux. All contents of dmesg are repetitive: [ 1465.810014] usb 1-2.2: USB disconnect, device number 20 [ 1467.431509] usb 1-2.2: new low-speed USB device number 21 using xhci_hcd [ 1467.654982] usb 1-2.2: New USB device found, idVendor=03f0,idProduct=1f4a, bcdDevice= 1.00 [ 1467.654985] usb 1-2.2: New USB device strings: Mfr=1, Product=2,SerialNumber=0 [ 1467.654987] usb 1-2.2: Product: HP USB Optical Mouse [ 1467.654988] usb 1-2.2: Manufacturer: PixArt [ 1467.699722] input: PixArt HP USB Optical Mouse as /devices/pci0000:00/0000:00:07.1/0000:05:00.3/usb1/1-2/1-2.2/1-2.2:1.0/0003:03F0:1F4A.0012/input/input19 [ 1467.700124] hid-generic 0003:03F0:1F4A.0012: input,hidraw0: USB HID v1.11 Mouse [PixArt HP USB Optical Mouse] on usb-0000:05:00.3-2.2/input0 So add HID_QUIRK_ALWAYS_POLL for this one as well. Test the patch, the mouse is no longer disconnected and there are no duplicate logs in dmesg. Reference: https://github.com/sriemer/fix-linux-mouse Signed-off-by: Jinke Fan Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 447e8db21174..00904537e17c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -573,6 +573,7 @@ #define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A 0x094a #define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941 0x0941 #define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641 +#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a #define USB_VENDOR_ID_HUION 0x256c #define USB_DEVICE_ID_HUION_TABLET 0x006e diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c50bcd967d99..9a35af1e2662 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -94,6 +94,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X), HID_QUIRK_MULTI_INPUT }, From 4f6585815084dc42089449ff32849b8069ea2604 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 15 Nov 2019 13:36:46 +0800 Subject: [PATCH 17/23] HID: i2c-hid: remove orphaned member sleep_delay This was introduced in commit 00b790ea545b ("HID: i2c-hid: Add a small delay after sleep command for Raydium touchpanel") which has been effectively reverted by commit 67b18dfb8cfc ("HID: i2c-hid: Remove runtime power management"). Signed-off-by: You-Sheng Yang Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 04c088131e04..adfe344bc786 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -157,8 +157,6 @@ struct i2c_hid { bool irq_wake_enabled; struct mutex reset_lock; - - unsigned long sleep_delay; }; static const struct i2c_hid_quirks { From 61005d65b6c7dcf61c19516e6ebe5acc02d2cdda Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 14 Nov 2019 15:30:46 +0100 Subject: [PATCH 18/23] HID: logitech-hidpp: Silence intermittent get_battery_capacity errors My Logitech M185 (PID:4038) 2.4 GHz wireless HID++ mouse is causing intermittent errors like these in the log: [11091.034857] logitech-hidpp-device 0003:046D:4038.0006: hidpp20_batterylevel_get_battery_capacity: received protocol error 0x09 [12388.031260] logitech-hidpp-device 0003:046D:4038.0006: hidpp20_batterylevel_get_battery_capacity: received protocol error 0x09 [16613.718543] logitech-hidpp-device 0003:046D:4038.0006: hidpp20_batterylevel_get_battery_capacity: received protocol error 0x09 [23529.938728] logitech-hidpp-device 0003:046D:4038.0006: hidpp20_batterylevel_get_battery_capacity: received protocol error 0x09 We are already silencing error-code 0x09 (HIDPP_ERROR_RESOURCE_ERROR) errors in other places, lets do the same in hidpp20_batterylevel_get_battery_capacity to remove these harmless, but scary looking errors from the dmesg output. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 0179f7ed77e5..d2421f36b0b7 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -1102,6 +1102,9 @@ static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp, ret = hidpp_send_fap_command_sync(hidpp, feature_index, CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS, NULL, 0, &response); + /* Ignore these intermittent errors */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; if (ret > 0) { hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", __func__, ret); From 0c8432236dea20a95f68fa17989ea3f8af0186a5 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Thu, 14 Nov 2019 03:12:47 +0800 Subject: [PATCH 19/23] HID: i2c-hid: fix no irq after reset on raydium 3118 On some ThinkPad L390 some raydium 3118 touchscreen devices doesn't response any data after reset, but some does. Add this ID to no irq quirk, then don't wait for any response alike on these touchscreens. All kinds of raydium 3118 devices work fine. BugLink: https://bugs.launchpad.net/bugs/1849721 Signed-off-by: Aaron Ma Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/i2c-hid/i2c-hid-core.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 00904537e17c..6273e7178e78 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -960,6 +960,7 @@ #define I2C_VENDOR_ID_RAYDIUM 0x2386 #define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33 +#define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118 #define USB_VENDOR_ID_RAZER 0x1532 #define USB_DEVICE_ID_RAZER_BLADE_14 0x011D diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 04c088131e04..7608ee053114 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -170,6 +170,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV }, { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ELAN, HID_ANY_ID, I2C_HID_QUIRK_BOGUS_IRQ }, { 0, 0 } From fd70466d37bf3fe0118d18c56ddde85b428f86cf Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Thu, 7 Nov 2019 22:28:11 +0800 Subject: [PATCH 20/23] HID: i2c-hid: Reset ALPS touchpads on resume Commit 52cf93e63ee6 ("HID: i2c-hid: Don't reset device upon system resume") fixes many touchpads and touchscreens, however ALPS touchpads start to trigger IRQ storm after system resume. Since it's total silence from ALPS, let's bring the old behavior back to ALPS touchpads. Fixes: 52cf93e63ee6 ("HID: i2c-hid: Don't reset device upon system resume") Signed-off-by: Kai-Heng Feng Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 7608ee053114..ac44bf752ff1 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -48,6 +48,7 @@ #define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0) #define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1) #define I2C_HID_QUIRK_BOGUS_IRQ BIT(4) +#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(5) /* flags */ #define I2C_HID_STARTED 0 @@ -174,6 +175,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ELAN, HID_ANY_ID, I2C_HID_QUIRK_BOGUS_IRQ }, + { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, + I2C_HID_QUIRK_RESET_ON_RESUME }, { 0, 0 } }; @@ -1214,8 +1217,15 @@ static int i2c_hid_resume(struct device *dev) * solves "incomplete reports" on Raydium devices 2386:3118 and * 2386:4B33 and fixes various SIS touchscreens no longer sending * data after a suspend/resume. + * + * However some ALPS touchpads generate IRQ storm without reset, so + * let's still reset them here. */ - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) + ret = i2c_hid_hwreset(client); + else + ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + if (ret) return ret; From 2dbc6f113acd74c66b04bf49fb027efd830b1c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 6 Nov 2019 20:02:46 +0900 Subject: [PATCH 21/23] HID: Improve Windows Precision Touchpad detection. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Microsoft spec, usage 0xC5 (page 0xFF) returns a blob containing data used to verify the touchpad as a Windows Precision Touchpad. 0x85, REPORTID_PTPHQA, // REPORT_ID (PTPHQA) 0x09, 0xC5, // USAGE (Vendor Usage 0xC5) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (0xff) 0x75, 0x08, // REPORT_SIZE (8) 0x96, 0x00, 0x01, // REPORT_COUNT (0x100 (256)) 0xb1, 0x02, // FEATURE (Data,Var,Abs) However, some devices, namely Microsoft's Surface line of products instead implement a "segmented device certification report" (usage 0xC6) which returns the same report, but in smaller chunks. 0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined) 0x85, REPORTID_PTPHQA, // REPORT_ID (PTPHQA) 0x09, 0xC6, // USAGE (Vendor usage for segment #) 0x25, 0x08, // LOGICAL_MAXIMUM (8) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x01, // REPORT_COUNT (1) 0xb1, 0x02, // FEATURE (Data,Var,Abs) 0x09, 0xC7, // USAGE (Vendor Usage) 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (0xff) 0x95, 0x20, // REPORT_COUNT (32) 0xb1, 0x02, // FEATURE (Data,Var,Abs) By expanding Win8 touchpad detection to also look for the segmented report, all Surface touchpads are now properly recognized by hid-multitouch. Signed-off-by: Blaž Hrastnik Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 2fa3587d974f..e0b241bd3070 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -781,6 +781,10 @@ static void hid_scan_feature_usage(struct hid_parser *parser, u32 usage) if (usage == 0xff0000c5 && parser->global.report_count == 256 && parser->global.report_size == 8) parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8; + + if (usage == 0xff0000c6 && parser->global.report_count == 1 && + parser->global.report_size == 8) + parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8; } static void hid_scan_collection(struct hid_parser *parser, unsigned type) From b03e5774d51fe003e1020060c403a1d085b128eb Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Mon, 11 Nov 2019 22:38:11 +0100 Subject: [PATCH 22/23] HID: quirks: remove hid-led devices from hid_have_special_driver Since e04a0442d33b ("HID: core: remove the absolute need of hid_have_special_driver[]") it's no longer needed to list these LED devices in hid_have_special_driver[]. This allows libraries needing access to the hidraw device to work properly. Signed-off-by: Heiner Kallweit Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-quirks.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c50bcd967d99..bdaab79f7a16 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -419,13 +419,6 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_LCPOWER) { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) }, #endif -#if IS_ENABLED(CONFIG_HID_LED) - { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_WN) }, - { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_FA) }, - { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_LUXAFOR) }, - { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) }, - { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, -#endif #if IS_ENABLED(CONFIG_HID_LENOVO) { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) }, From 8725aa4fa7ded30211ebd28bb1c9bae806eb3841 Mon Sep 17 00:00:00 2001 From: Andrew Duggan Date: Wed, 23 Oct 2019 01:24:03 +0000 Subject: [PATCH 23/23] HID: rmi: Check that the RMI_STARTED bit is set before unregistering the RMI transport device In the event that the RMI device is unreachable, the calls to rmi_set_mode() or rmi_set_page() will fail before registering the RMI transport device. When the device is removed, rmi_remove() will call rmi_unregister_transport_device() which will attempt to access the rmi_dev pointer which was not set. This patch adds a check of the RMI_STARTED bit before calling rmi_unregister_transport_device(). The RMI_STARTED bit is only set after rmi_register_transport_device() completes successfully. The kernel oops was reported in this message: https://www.spinics.net/lists/linux-input/msg58433.html [jkosina@suse.cz: reworded changelog as agreed with Andrew] Signed-off-by: Andrew Duggan Reported-by: Federico Cerutti Signed-off-by: Jiri Kosina --- drivers/hid/hid-rmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c index 7c6abd7e0979..9ce22acdfaca 100644 --- a/drivers/hid/hid-rmi.c +++ b/drivers/hid/hid-rmi.c @@ -744,7 +744,8 @@ static void rmi_remove(struct hid_device *hdev) { struct rmi_data *hdata = hid_get_drvdata(hdev); - if (hdata->device_flags & RMI_DEVICE) { + if ((hdata->device_flags & RMI_DEVICE) + && test_bit(RMI_STARTED, &hdata->flags)) { clear_bit(RMI_STARTED, &hdata->flags); cancel_work_sync(&hdata->reset_work); rmi_unregister_transport_device(&hdata->xport);