Merge branch 'for-5.5/logitech' into for-linus
authorJiri Kosina <jkosina@suse.cz>
Fri, 29 Nov 2019 19:37:55 +0000 (20:37 +0100)
committerJiri Kosina <jkosina@suse.cz>
Fri, 29 Nov 2019 19:37:55 +0000 (20:37 +0100)
- Support for Logitech G15 (Hans de Goede)
- silencing of non-informative error flow in dmesg from
  logitechi-hiddpp (Hans de Goede)

MAINTAINERS
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-ids.h
drivers/hid/hid-lg-g15.c [new file with mode: 0644]
drivers/hid/hid-logitech-hidpp.c
include/uapi/linux/input-event-codes.h

index 2a427d1..c179529 100644 (file)
@@ -9599,6 +9599,13 @@ S:       Maintained
 F:     Documentation/admin-guide/ldm.rst
 F:     block/partitions/ldm.*
 
+LOGITECH HID GAMING KEYBOARDS
+M:     Hans de Goede <hdegoede@redhat.com>
+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 <sathya.prakash@broadcom.com>
 M:     Chaitra P B <chaitra.basappa@broadcom.com>
index 1ecb512..494a39e 100644 (file)
@@ -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.
index 0c03308..bfefa36 100644 (file)
@@ -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
index 6273e71..7e1689e 100644 (file)
 #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_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
new file mode 100644 (file)
index 0000000..8a9268a
--- /dev/null
@@ -0,0 +1,899 @@
+// 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 <hdegoede@redhat.com>
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include <linux/sched.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+
+#include "hid-ids.h"
+
+#define LG_G15_TRANSFER_BUF_SIZE       20
+
+#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,
+       LG_G510,
+       LG_G510_USB_AUDIO,
+};
+
+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 {
+       struct led_classdev cdev;
+       enum led_brightness brightness;
+       enum lg_g15_led_type led;
+       u8 red, green, blue;
+};
+
+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_LED_MAX];
+       bool game_mode_enabled;
+};
+
+/******** G15 and G15 v2 LED functions ********/
+
+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];
+
+       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;
+}
+
+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);
+       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);
+
+       g15->transfer_buf[0] = LG_G15_FEATURE_REPORT;
+       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);
+       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]);
+       }
+}
+
+/******** 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);
+}
+
+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)
+{
+       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 lg_g510_update_mkey_led_brightness(g15);
+       }
+       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)
+{
+       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);
+       }
+
+       /* Backlight cycle button pressed? */
+       if (data[1] & 0x80)
+               schedule_work(&g15->work);
+
+       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);
+       }
+
+       /* Backlight cycle button pressed? */
+       if (data[2] & 0x01)
+               schedule_work(&g15->work);
+
+       input_sync(g15->input);
+       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_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)
+{
+       struct lg_g15_data *g15 = hid_get_drvdata(hdev);
+
+       if (!g15)
+               return 0;
+
+       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);
+               if (data[0] == 0x04 && size == 2)
+                       return lg_g510_leds_event(g15, data, size);
+               break;
+       }
+
+       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_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];
+
+       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:
+                       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;
+       }
+
+       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;
+       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;
+
+       mutex_init(&g15->mutex);
+
+       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:
+               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
+                * 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:
+               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;
+               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 (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;
+       }
+
+       /* Get initial brightness levels */
+       ret = lg_g15_get_initial_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;
+       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);
+
+       /*
+        * 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);
+
+       ret = input_register_device(input);
+       if (ret)
+               goto error_hw_stop;
+
+       /* Register LED devices */
+       for (i = 0; i < LG_G15_LED_MAX; i++) {
+               ret = lg_g15_register_led(g15, i);
+               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 },
+       /* 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);
+
+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");
index 8e91e2f..cd91930 100644 (file)
@@ -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);
index 85387c7..00aebea 100644 (file)
 #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