From patchwork Wed Aug 21 20:59:31 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108171 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 2FF2114DE for ; Wed, 21 Aug 2019 20:59:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 18DE122CF7 for ; Wed, 21 Aug 2019 20:59:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730460AbfHUU7o (ORCPT ); Wed, 21 Aug 2019 16:59:44 -0400 Received: from mx1.redhat.com ([209.132.183.28]:55780 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7o (ORCPT ); Wed, 21 Aug 2019 16:59:44 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id CAD5410C6961; Wed, 21 Aug 2019 20:59:43 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id B136717DE3; Wed, 21 Aug 2019 20:59:42 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 1/7] Input: Add event-codes for macro keys found on various keyboards Date: Wed, 21 Aug 2019 22:59:31 +0200 Message-Id: <20190821205937.4929-2-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.6.2 (mx1.redhat.com [10.5.110.65]); Wed, 21 Aug 2019 20:59:43 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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_LCD_MENU1 - KEY_LCD_MENU5, KEY_MACRO_RECORD, KEY_MACRO_PRESET_CYCLE, KEY_MACRO_PRESET1 - KEY_MACRO_PRESET3, KEY_MACRO1 - KEY_MACRO30. 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 --- include/uapi/linux/input-event-codes.h | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 85387c76c24f..80df3a6d6bcb 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -650,6 +650,60 @@ #define KEY_DATA 0x277 #define KEY_ONSCREEN_KEYBOARD 0x278 +/* + * 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 labelling so we just name them KEY_LCD_MENU# + */ +#define KEY_LCD_MENU1 0x280 +#define KEY_LCD_MENU2 0x281 +#define KEY_LCD_MENU3 0x282 +#define KEY_LCD_MENU4 0x283 +#define KEY_LCD_MENU5 0x284 + +/* Macro preset selection and recording keys, typically found on gaming kbds */ +#define KEY_MACRO_RECORD_START 0x288 +#define KEY_MACRO_RECORD_STOP 0x289 +#define KEY_MACRO_PRESET_CYCLE 0x28a +#define KEY_MACRO_PRESET1 0x28b +#define KEY_MACRO_PRESET2 0x28c +#define KEY_MACRO_PRESET3 0x28d + +/* Gaming "macro" (macros handled in software/userspace) keys on gaming kbds */ +#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 + +/* Note: 0x2ae - 0x2bf are served to extend the MACRO keys if necessary */ + #define BTN_TRIGGER_HAPPY 0x2c0 #define BTN_TRIGGER_HAPPY1 0x2c0 #define BTN_TRIGGER_HAPPY2 0x2c1 From patchwork Wed Aug 21 20:59:32 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108173 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9959313A4 for ; Wed, 21 Aug 2019 20:59:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 78A7C22CF7 for ; Wed, 21 Aug 2019 20:59:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727976AbfHUU7q (ORCPT ); Wed, 21 Aug 2019 16:59:46 -0400 Received: from mx1.redhat.com ([209.132.183.28]:33336 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7p (ORCPT ); Wed, 21 Aug 2019 16:59:45 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 35976309175F; Wed, 21 Aug 2019 20:59:45 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id 1D03C18150; Wed, 21 Aug 2019 20:59:43 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 2/7] HID: Add driver for Logitech gaming keyboards (G15, G15 v2) Date: Wed, 21 Aug 2019 22:59:32 +0200 Message-Id: <20190821205937.4929-3-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.41]); Wed, 21 Aug 2019 20:59:45 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- 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 1c48322e1cb1..636932afd21f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9478,6 +9478,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 cc5d827c9164..666f4358207e 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -63,6 +63,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 0a00be19f7a0..912c1f9bb957 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -745,6 +745,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..1e35739a461b --- /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_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_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_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_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_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 patchwork Wed Aug 21 20:59:33 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108175 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 04B6C14DE for ; Wed, 21 Aug 2019 20:59:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D81C722CF7 for ; Wed, 21 Aug 2019 20:59:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730440AbfHUU7r (ORCPT ); Wed, 21 Aug 2019 16:59:47 -0400 Received: from mx1.redhat.com ([209.132.183.28]:44722 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7r (ORCPT ); Wed, 21 Aug 2019 16:59:47 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 97A05307D928; Wed, 21 Aug 2019 20:59:46 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7B90017D29; Wed, 21 Aug 2019 20:59:45 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 3/7] HID: lg-g15: Add keyboard and LCD backlight control Date: Wed, 21 Aug 2019 22:59:33 +0200 Message-Id: <20190821205937.4929-4-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.48]); Wed, 21 Aug 2019 20:59:46 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- Changes in v2: - Add missing mutex_init call --- 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 1e35739a461b..e6a5d66e1412 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_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_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 patchwork Wed Aug 21 20:59:34 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108177 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 03A5E14DE for ; Wed, 21 Aug 2019 20:59:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id E0D5C22CF7 for ; Wed, 21 Aug 2019 20:59:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730472AbfHUU7s (ORCPT ); Wed, 21 Aug 2019 16:59:48 -0400 Received: from mx1.redhat.com ([209.132.183.28]:45212 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7s (ORCPT ); Wed, 21 Aug 2019 16:59:48 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 03449300CB24; Wed, 21 Aug 2019 20:59:48 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id DDC0B5DA32; Wed, 21 Aug 2019 20:59:46 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 4/7] HID: lg-g15: Add support for the M1-M3 and MR LEDs Date: Wed, 21 Aug 2019 22:59:34 +0200 Message-Id: <20190821205937.4929-5-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Wed, 21 Aug 2019 20:59:48 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org Add support for controlling the LEDs below the M1-M3 and MR keys. Signed-off-by: Hans de Goede --- 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 e6a5d66e1412..dc37c34dc8fb 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 patchwork Wed Aug 21 20:59:35 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108179 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id F012D13A4 for ; Wed, 21 Aug 2019 20:59:50 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id CFB7822CF7 for ; Wed, 21 Aug 2019 20:59:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730572AbfHUU7u (ORCPT ); Wed, 21 Aug 2019 16:59:50 -0400 Received: from mx1.redhat.com ([209.132.183.28]:52766 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7u (ORCPT ); Wed, 21 Aug 2019 16:59:50 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 637DD7EB8A; Wed, 21 Aug 2019 20:59:49 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id 48C7A17D29; Wed, 21 Aug 2019 20:59:48 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 5/7] HID: lg-g15: Add support for the G510 keyboards' gaming keys Date: Wed, 21 Aug 2019 22:59:35 +0200 Message-Id: <20190821205937.4929-6-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.6.2 (mx1.redhat.com [10.5.110.71]); Wed, 21 Aug 2019 20:59:49 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- Note the 2 added: if (g15->model == LG_G510 || g15->model == LG_G510_USB_AUDIO) return 0; Code-blocks are because of the lack of led-class support, which is temporary, these will be removed again in a follow-up commit. --- 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 912c1f9bb957..7360cf2838bb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -747,6 +747,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 dc37c34dc8fb..62b3ae8a59a6 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_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_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 patchwork Wed Aug 21 20:59:36 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108181 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 29CFE14DE for ; Wed, 21 Aug 2019 20:59:52 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 091BA22CF7 for ; Wed, 21 Aug 2019 20:59:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730536AbfHUU7v (ORCPT ); Wed, 21 Aug 2019 16:59:51 -0400 Received: from mx1.redhat.com ([209.132.183.28]:58428 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7v (ORCPT ); Wed, 21 Aug 2019 16:59:51 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id C23BD3082B6D; Wed, 21 Aug 2019 20:59:50 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id A89ED17D29; Wed, 21 Aug 2019 20:59:49 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 6/7] HID: lg-g15: Add support for controlling the G510's RGB backlight Date: Wed, 21 Aug 2019 22:59:36 +0200 Message-Id: <20190821205937.4929-7-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.45]); Wed, 21 Aug 2019 20:59:50 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org 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 --- 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 62b3ae8a59a6..57640417d49d 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 ********/ +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 patchwork Wed Aug 21 20:59:37 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 11108183 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 3149F14DE for ; Wed, 21 Aug 2019 20:59:53 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 1AA5822CF7 for ; Wed, 21 Aug 2019 20:59:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730596AbfHUU7w (ORCPT ); Wed, 21 Aug 2019 16:59:52 -0400 Received: from mx1.redhat.com ([209.132.183.28]:52778 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728485AbfHUU7w (ORCPT ); Wed, 21 Aug 2019 16:59:52 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 2C9A17EB8A; Wed, 21 Aug 2019 20:59:52 +0000 (UTC) Received: from shalem.localdomain.com (ovpn-116-29.ams2.redhat.com [10.36.116.29]) by smtp.corp.redhat.com (Postfix) with ESMTP id 1365A5DA32; Wed, 21 Aug 2019 20:59:50 +0000 (UTC) From: Hans de Goede To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov Cc: Hans de Goede , linux-input@vger.kernel.org Subject: [PATCH v2 7/7] HID: lg-g15: Add support for the G510's M1-M3 and MR LEDs Date: Wed, 21 Aug 2019 22:59:37 +0200 Message-Id: <20190821205937.4929-8-hdegoede@redhat.com> In-Reply-To: <20190821205937.4929-1-hdegoede@redhat.com> References: <20190821205937.4929-1-hdegoede@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.6.2 (mx1.redhat.com [10.5.110.71]); Wed, 21 Aug 2019 20:59:52 +0000 (UTC) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org Add support for controlling the LEDs below the M1-M3 and MR keys on the G510. Signed-off-by: Hans de Goede --- 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 57640417d49d..83614353e2ab 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 ********/ int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15) { @@ -372,7 +456,7 @@ 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; }