summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig15
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-appleir.c4
-rw-r--r--drivers/hid/hid-aureal.c3
-rw-r--r--drivers/hid/hid-core.c10
-rw-r--r--drivers/hid/hid-corsair.c673
-rw-r--r--drivers/hid/hid-dr.c58
-rw-r--r--drivers/hid/hid-elecom.c2
-rw-r--r--drivers/hid/hid-elo.c4
-rw-r--r--drivers/hid/hid-ids.h7
-rw-r--r--drivers/hid/hid-input.c10
-rw-r--r--drivers/hid/hid-lenovo.c4
-rw-r--r--drivers/hid/hid-lg.c9
-rw-r--r--drivers/hid/hid-lg4ff.c127
-rw-r--r--drivers/hid/hid-logitech-hidpp.c163
-rw-r--r--drivers/hid/hid-magicmouse.c8
-rw-r--r--drivers/hid/hid-microsoft.c2
-rw-r--r--drivers/hid/hid-multitouch.c68
-rw-r--r--drivers/hid/hid-ntrig.c6
-rw-r--r--drivers/hid/hid-prodikeys.c4
-rw-r--r--drivers/hid/hid-rmi.c11
-rw-r--r--drivers/hid/hid-saitek.c2
-rw-r--r--drivers/hid/hid-sensor-hub.c25
-rw-r--r--drivers/hid/hid-sony.c13
-rw-r--r--drivers/hid/hid-uclogic.c6
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.c1
-rw-r--r--drivers/hid/usbhid/hid-quirks.c2
-rw-r--r--drivers/hid/wacom_wac.c28
-rw-r--r--drivers/hid/wacom_wac.h1
29 files changed, 1172 insertions, 95 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 6ab51ae3c39d..4135055d7203 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -171,6 +171,16 @@ config HID_CHICONY
---help---
Support for Chicony Tactical pad.
+config HID_CORSAIR
+ tristate "Corsair devices"
+ depends on HID && USB && LEDS_CLASS
+ ---help---
+ Support for Corsair devices that are not fully compliant with the
+ HID standard.
+
+ Supported devices:
+ - Vengeance K90
+
config HID_PRODIKEYS
tristate "Prodikeys PC-MIDI Keyboard support"
depends on HID && SND
@@ -672,9 +682,8 @@ config HID_SAITEK
Supported devices:
- PS1000 Dual Analog Pad
- - R.A.T.9 Gaming Mouse
- - R.A.T.7 Gaming Mouse
- - M.M.O.7 Gaming Mouse
+ - Saitek R.A.T.7, R.A.T.9, M.M.O.7 Gaming Mice
+ - Mad Catz R.A.T.5, R.A.T.9 Gaming Mice
config HID_SAMSUNG
tristate "Samsung InfraRed remote control or keyboards"
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e6441bc7dae4..edaa0f264bde 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_HID_BELKIN) += hid-belkin.o
obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
+obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o
obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
obj-$(CONFIG_HID_DRAGONRISE) += hid-dr.o
diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c
index 0e6a42d37eb6..07cbc70f00e7 100644
--- a/drivers/hid/hid-appleir.c
+++ b/drivers/hid/hid-appleir.c
@@ -256,7 +256,7 @@ out:
return 0;
}
-static void appleir_input_configured(struct hid_device *hid,
+static int appleir_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
struct input_dev *input_dev = hidinput->input;
@@ -275,6 +275,8 @@ static void appleir_input_configured(struct hid_device *hid,
for (i = 0; i < ARRAY_SIZE(appleir_key_table); i++)
set_bit(appleir->keymap[i], input_dev->keybit);
clear_bit(KEY_RESERVED, input_dev->keybit);
+
+ return 0;
}
static int appleir_input_mapping(struct hid_device *hid,
diff --git a/drivers/hid/hid-aureal.c b/drivers/hid/hid-aureal.c
index 340ba9d394a0..3280aff28e90 100644
--- a/drivers/hid/hid-aureal.c
+++ b/drivers/hid/hid-aureal.c
@@ -23,7 +23,8 @@ static __u8 *aureal_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
dev_info(&hdev->dev, "fixing Aureal Cy se W-01RN USB_V3.1 report descriptor.\n");
rdesc[53] = 0x65;
- } return rdesc;
+ }
+ return rdesc;
}
static const struct hid_device_id aureal_devices[] = {
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index bcd914a63af2..0bd972b7614c 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -725,6 +725,7 @@ static void hid_scan_collection(struct hid_parser *parser, unsigned type)
if (hid->vendor == USB_VENDOR_ID_MICROSOFT &&
(hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_3 ||
+ hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2 ||
hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP ||
hid->product == USB_DEVICE_ID_MS_TYPE_COVER_3 ||
hid->product == USB_DEVICE_ID_MS_POWER_COVER) &&
@@ -1611,7 +1612,7 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
"Multi-Axis Controller"
};
const char *type, *bus;
- char buf[64];
+ char buf[64] = "";
unsigned int i;
int len;
int ret;
@@ -1678,6 +1679,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
case BUS_BLUETOOTH:
bus = "BLUETOOTH";
break;
+ case BUS_I2C:
+ bus = "I2C";
+ break;
default:
bus = "<UNKNOWN>";
}
@@ -1828,6 +1832,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_ACER_SWITCH12) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) },
@@ -1896,6 +1901,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_WHEEL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO) },
@@ -1928,6 +1934,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER) },
@@ -1981,6 +1988,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT5) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9) },
#endif
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c
new file mode 100644
index 000000000000..bcefb9ebb026
--- /dev/null
+++ b/drivers/hid/hid-corsair.c
@@ -0,0 +1,673 @@
+/*
+ * HID driver for Corsair devices
+ *
+ * Supported devices:
+ * - Vengeance K90 Keyboard
+ *
+ * Copyright (c) 2015 Clement Vuchener
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/leds.h>
+
+#include "hid-ids.h"
+
+#define CORSAIR_USE_K90_MACRO (1<<0)
+#define CORSAIR_USE_K90_BACKLIGHT (1<<1)
+
+struct k90_led {
+ struct led_classdev cdev;
+ int brightness;
+ struct work_struct work;
+ bool removed;
+};
+
+struct k90_drvdata {
+ struct k90_led record_led;
+};
+
+struct corsair_drvdata {
+ unsigned long quirks;
+ struct k90_drvdata *k90;
+ struct k90_led *backlight;
+};
+
+#define K90_GKEY_COUNT 18
+
+static int corsair_usage_to_gkey(unsigned int usage)
+{
+ /* G1 (0xd0) to G16 (0xdf) */
+ if (usage >= 0xd0 && usage <= 0xdf)
+ return usage - 0xd0 + 1;
+ /* G17 (0xe8) to G18 (0xe9) */
+ if (usage >= 0xe8 && usage <= 0xe9)
+ return usage - 0xe8 + 17;
+ return 0;
+}
+
+static unsigned short corsair_gkey_map[K90_GKEY_COUNT] = {
+ BTN_TRIGGER_HAPPY1,
+ BTN_TRIGGER_HAPPY2,
+ BTN_TRIGGER_HAPPY3,
+ BTN_TRIGGER_HAPPY4,
+ BTN_TRIGGER_HAPPY5,
+ BTN_TRIGGER_HAPPY6,
+ BTN_TRIGGER_HAPPY7,
+ BTN_TRIGGER_HAPPY8,
+ BTN_TRIGGER_HAPPY9,
+ BTN_TRIGGER_HAPPY10,
+ BTN_TRIGGER_HAPPY11,
+ BTN_TRIGGER_HAPPY12,
+ BTN_TRIGGER_HAPPY13,
+ BTN_TRIGGER_HAPPY14,
+ BTN_TRIGGER_HAPPY15,
+ BTN_TRIGGER_HAPPY16,
+ BTN_TRIGGER_HAPPY17,
+ BTN_TRIGGER_HAPPY18,
+};
+
+module_param_array_named(gkey_codes, corsair_gkey_map, ushort, NULL, S_IRUGO);
+MODULE_PARM_DESC(gkey_codes, "Key codes for the G-keys");
+
+static unsigned short corsair_record_keycodes[2] = {
+ BTN_TRIGGER_HAPPY19,
+ BTN_TRIGGER_HAPPY20
+};
+
+module_param_array_named(recordkey_codes, corsair_record_keycodes, ushort,
+ NULL, S_IRUGO);
+MODULE_PARM_DESC(recordkey_codes, "Key codes for the MR (start and stop record) button");
+
+static unsigned short corsair_profile_keycodes[3] = {
+ BTN_TRIGGER_HAPPY21,
+ BTN_TRIGGER_HAPPY22,
+ BTN_TRIGGER_HAPPY23
+};
+
+module_param_array_named(profilekey_codes, corsair_profile_keycodes, ushort,
+ NULL, S_IRUGO);
+MODULE_PARM_DESC(profilekey_codes, "Key codes for the profile buttons");
+
+#define CORSAIR_USAGE_SPECIAL_MIN 0xf0
+#define CORSAIR_USAGE_SPECIAL_MAX 0xff
+
+#define CORSAIR_USAGE_MACRO_RECORD_START 0xf6
+#define CORSAIR_USAGE_MACRO_RECORD_STOP 0xf7
+
+#define CORSAIR_USAGE_PROFILE 0xf1
+#define CORSAIR_USAGE_M1 0xf1
+#define CORSAIR_USAGE_M2 0xf2
+#define CORSAIR_USAGE_M3 0xf3
+#define CORSAIR_USAGE_PROFILE_MAX 0xf3
+
+#define CORSAIR_USAGE_META_OFF 0xf4
+#define CORSAIR_USAGE_META_ON 0xf5
+
+#define CORSAIR_USAGE_LIGHT 0xfa
+#define CORSAIR_USAGE_LIGHT_OFF 0xfa
+#define CORSAIR_USAGE_LIGHT_DIM 0xfb
+#define CORSAIR_USAGE_LIGHT_MEDIUM 0xfc
+#define CORSAIR_USAGE_LIGHT_BRIGHT 0xfd
+#define CORSAIR_USAGE_LIGHT_MAX 0xfd
+
+/* USB control protocol */
+
+#define K90_REQUEST_BRIGHTNESS 49
+#define K90_REQUEST_MACRO_MODE 2
+#define K90_REQUEST_STATUS 4
+#define K90_REQUEST_GET_MODE 5
+#define K90_REQUEST_PROFILE 20
+
+#define K90_MACRO_MODE_SW 0x0030
+#define K90_MACRO_MODE_HW 0x0001
+
+#define K90_MACRO_LED_ON 0x0020
+#define K90_MACRO_LED_OFF 0x0040
+
+/*
+ * LED class devices
+ */
+
+#define K90_BACKLIGHT_LED_SUFFIX "::backlight"
+#define K90_RECORD_LED_SUFFIX "::record"
+
+static enum led_brightness k90_backlight_get(struct led_classdev *led_cdev)
+{
+ int ret;
+ struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
+ struct device *dev = led->cdev.dev->parent;
+ struct usb_interface *usbif = to_usb_interface(dev->parent);
+ struct usb_device *usbdev = interface_to_usbdev(usbif);
+ int brightness;
+ char data[8];
+
+ ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+ K90_REQUEST_STATUS,
+ USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, 0, 0, data, 8,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret < 0) {
+ dev_warn(dev, "Failed to get K90 initial state (error %d).\n",
+ ret);
+ return -EIO;
+ }
+ brightness = data[4];
+ if (brightness < 0 || brightness > 3) {
+ dev_warn(dev,
+ "Read invalid backlight brightness: %02hhx.\n",
+ data[4]);
+ return -EIO;
+ }
+ return brightness;
+}
+
+static enum led_brightness k90_record_led_get(struct led_classdev *led_cdev)
+{
+ struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
+
+ return led->brightness;
+}
+
+static void k90_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
+
+ led->brightness = brightness;
+ schedule_work(&led->work);
+}
+
+static void k90_backlight_work(struct work_struct *work)
+{
+ int ret;
+ struct k90_led *led = container_of(work, struct k90_led, work);
+ struct device *dev;
+ struct usb_interface *usbif;
+ struct usb_device *usbdev;
+
+ if (led->removed)
+ return;
+
+ dev = led->cdev.dev->parent;
+ usbif = to_usb_interface(dev->parent);
+ usbdev = interface_to_usbdev(usbif);
+
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+ K90_REQUEST_BRIGHTNESS,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, led->brightness, 0,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (ret != 0)
+ dev_warn(dev, "Failed to set backlight brightness (error: %d).\n",
+ ret);
+}
+
+static void k90_record_led_work(struct work_struct *work)
+{
+ int ret;
+ struct k90_led *led = container_of(work, struct k90_led, work);
+ struct device *dev;
+ struct usb_interface *usbif;
+ struct usb_device *usbdev;
+ int value;
+
+ if (led->removed)
+ return;
+
+ dev = led->cdev.dev->parent;
+ usbif = to_usb_interface(dev->parent);
+ usbdev = interface_to_usbdev(usbif);
+
+ if (led->brightness > 0)
+ value = K90_MACRO_LED_ON;
+ else
+ value = K90_MACRO_LED_OFF;
+
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+ K90_REQUEST_MACRO_MODE,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, value, 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret != 0)
+ dev_warn(dev, "Failed to set record LED state (error: %d).\n",
+ ret);
+}
+
+/*
+ * Keyboard attributes
+ */
+
+static ssize_t k90_show_macro_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct usb_interface *usbif = to_usb_interface(dev->parent);
+ struct usb_device *usbdev = interface_to_usbdev(usbif);
+ const char *macro_mode;
+ char data[8];
+
+ ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+ K90_REQUEST_GET_MODE,
+ USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, 0, 0, data, 2,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret < 0) {
+ dev_warn(dev, "Failed to get K90 initial mode (error %d).\n",
+ ret);
+ return -EIO;
+ }
+
+ switch (data[0]) {
+ case K90_MACRO_MODE_HW:
+ macro_mode = "HW";
+ break;
+
+ case K90_MACRO_MODE_SW:
+ macro_mode = "SW";
+ break;
+ default:
+ dev_warn(dev, "K90 in unknown mode: %02hhx.\n",
+ data[0]);
+ return -EIO;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", macro_mode);
+}
+
+static ssize_t k90_store_macro_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ struct usb_interface *usbif = to_usb_interface(dev->parent);
+ struct usb_device *usbdev = interface_to_usbdev(usbif);
+ __u16 value;
+
+ if (strncmp(buf, "SW", 2) == 0)
+ value = K90_MACRO_MODE_SW;
+ else if (strncmp(buf, "HW", 2) == 0)
+ value = K90_MACRO_MODE_HW;
+ else
+ return -EINVAL;
+
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+ K90_REQUEST_MACRO_MODE,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, value, 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret != 0) {
+ dev_warn(dev, "Failed to set macro mode.\n");
+ return ret;
+ }
+
+ return count;
+}
+
+static ssize_t k90_show_current_profile(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct usb_interface *usbif = to_usb_interface(dev->parent);
+ struct usb_device *usbdev = interface_to_usbdev(usbif);
+ int current_profile;
+ char data[8];
+
+ ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+ K90_REQUEST_STATUS,
+ USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, 0, 0, data, 8,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret < 0) {
+ dev_warn(dev, "Failed to get K90 initial state (error %d).\n",
+ ret);
+ return -EIO;
+ }
+ current_profile = data[7];
+ if (current_profile < 1 || current_profile > 3) {
+ dev_warn(dev, "Read invalid current profile: %02hhx.\n",
+ data[7]);
+ return -EIO;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", current_profile);
+}
+
+static ssize_t k90_store_current_profile(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ struct usb_interface *usbif = to_usb_interface(dev->parent);
+ struct usb_device *usbdev = interface_to_usbdev(usbif);
+ int profile;
+
+ if (kstrtoint(buf, 10, &profile))
+ return -EINVAL;
+ if (profile < 1 || profile > 3)
+ return -EINVAL;
+
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+ K90_REQUEST_PROFILE,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, profile, 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (ret != 0) {
+ dev_warn(dev, "Failed to change current profile (error %d).\n",
+ ret);
+ return ret;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(macro_mode, 0644, k90_show_macro_mode, k90_store_macro_mode);
+static DEVICE_ATTR(current_profile, 0644, k90_show_current_profile,
+ k90_store_current_profile);
+
+static struct attribute *k90_attrs[] = {
+ &dev_attr_macro_mode.attr,
+ &dev_attr_current_profile.attr,
+ NULL
+};
+
+static const struct attribute_group k90_attr_group = {
+ .attrs = k90_attrs,
+};
+
+/*
+ * Driver functions
+ */
+
+static int k90_init_backlight(struct hid_device *dev)
+{
+ int ret;
+ struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+ size_t name_sz;
+ char *name;
+
+ drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL);
+ if (!drvdata->backlight) {
+ ret = -ENOMEM;
+ goto fail_backlight_alloc;
+ }
+
+ name_sz =
+ strlen(dev_name(&dev->dev)) + sizeof(K90_BACKLIGHT_LED_SUFFIX);
+ name = kzalloc(name_sz, GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto fail_name_alloc;
+ }
+ snprintf(name, name_sz, "%s" K90_BACKLIGHT_LED_SUFFIX,
+ dev_name(&dev->dev));
+ drvdata->backlight->removed = false;
+ drvdata->backlight->cdev.name = name;
+ drvdata->backlight->cdev.max_brightness = 3;
+ drvdata->backlight->cdev.brightness_set = k90_brightness_set;
+ drvdata->backlight->cdev.brightness_get = k90_backlight_get;
+ INIT_WORK(&drvdata->backlight->work, k90_backlight_work);
+ ret = led_classdev_register(&dev->dev, &drvdata->backlight->cdev);
+ if (ret != 0)
+ goto fail_register_cdev;
+
+ return 0;
+
+fail_register_cdev:
+ kfree(drvdata->backlight->cdev.name);
+fail_name_alloc:
+ kfree(drvdata->backlight);
+ drvdata->backlight = NULL;
+fail_backlight_alloc:
+ return ret;
+}
+
+static int k90_init_macro_functions(struct hid_device *dev)
+{
+ int ret;
+ struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+ struct k90_drvdata *k90;
+ size_t name_sz;
+ char *name;
+
+ k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL);
+ if (!k90) {
+ ret = -ENOMEM;
+ goto fail_drvdata;
+ }
+ drvdata->k90 = k90;
+
+ /* Init LED device for record LED */
+ name_sz = strlen(dev_name(&dev->dev)) + sizeof(K90_RECORD_LED_SUFFIX);
+ name = kzalloc(name_sz, GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto fail_record_led_alloc;
+ }
+ snprintf(name, name_sz, "%s" K90_RECORD_LED_SUFFIX,
+ dev_name(&dev->dev));
+ k90->record_led.removed = false;
+ k90->record_led.cdev.name = name;
+ k90->record_led.cdev.max_brightness = 1;
+ k90->record_led.cdev.brightness_set = k90_brightness_set;
+ k90->record_led.cdev.brightness_get = k90_record_led_get;
+ INIT_WORK(&k90->record_led.work, k90_record_led_work);
+ k90->record_led.brightness = 0;
+ ret = led_classdev_register(&dev->dev, &k90->record_led.cdev);
+ if (ret != 0)
+ goto fail_record_led;
+
+ /* Init attributes */
+ ret = sysfs_create_group(&dev->dev.kobj, &k90_attr_group);
+ if (ret != 0)
+ goto fail_sysfs;
+
+ return 0;
+
+fail_sysfs:
+ k90->record_led.removed = true;
+ led_classdev_unregister(&k90->record_led.cdev);
+ cancel_work_sync(&k90->record_led.work);
+fail_record_led:
+ kfree(k90->record_led.cdev.name);
+fail_record_led_alloc:
+ kfree(k90);
+fail_drvdata:
+ drvdata->k90 = NULL;
+ return ret;
+}
+
+static void k90_cleanup_backlight(struct hid_device *dev)
+{
+ struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+
+ if (drvdata->backlight) {
+ drvdata->backlight->removed = true;
+ led_classdev_unregister(&drvdata->backlight->cdev);
+ cancel_work_sync(&drvdata->backlight->work);
+ kfree(drvdata->backlight->cdev.name);
+ kfree(drvdata->backlight);
+ }
+}
+
+static void k90_cleanup_macro_functions(struct hid_device *dev)
+{
+ struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+ struct k90_drvdata *k90 = drvdata->k90;
+
+ if (k90) {
+ sysfs_remove_group(&dev->dev.kobj, &k90_attr_group);
+
+ k90->record_led.removed = true;
+ led_classdev_unregister(&k90->record_led.cdev);
+ cancel_work_sync(&k90->record_led.work);
+ kfree(k90->record_led.cdev.name);
+
+ kfree(k90);
+ }
+}
+
+static int corsair_probe(struct hid_device *dev, const struct hid_device_id *id)
+{
+ int ret;
+ unsigned long quirks = id->driver_data;
+ struct corsair_drvdata *drvdata;
+ struct usb_interface *usbif = to_usb_interface(dev->dev.parent);
+
+ drvdata = devm_kzalloc(&dev->dev, sizeof(struct corsair_drvdata),
+ GFP_KERNEL);
+ if (drvdata == NULL)
+ return -ENOMEM;
+ drvdata->quirks = quirks;
+ hid_set_drvdata(dev, drvdata);
+
+ ret = hid_parse(dev);
+ if (ret != 0) {
+ hid_err(dev, "parse failed\n");
+ return ret;
+ }
+ ret = hid_hw_start(dev, HID_CONNECT_DEFAULT);
+ if (ret != 0) {
+ hid_err(dev, "hw start failed\n");
+ return ret;
+ }
+
+ if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) {
+ if (quirks & CORSAIR_USE_K90_MACRO) {
+ ret = k90_init_macro_functions(dev);
+ if (ret != 0)
+ hid_warn(dev, "Failed to initialize K90 macro functions.\n");
+ }
+ if (quirks & CORSAIR_USE_K90_BACKLIGHT) {
+ ret = k90_init_backlight(dev);
+ if (ret != 0)
+ hid_warn(dev, "Failed to initialize K90 backlight.\n");
+ }
+ }
+
+ return 0;
+}
+
+static void corsair_remove(struct hid_device *dev)
+{
+ k90_cleanup_macro_functions(dev);
+ k90_cleanup_backlight(dev);
+
+ hid_hw_stop(dev);
+}
+
+static int corsair_event(struct hid_device *dev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+
+ if (!drvdata->k90)
+ return 0;
+
+ switch (usage->hid & HID_USAGE) {
+ case CORSAIR_USAGE_MACRO_RECORD_START:
+ drvdata->k90->record_led.brightness = 1;
+ break;
+ case CORSAIR_USAGE_MACRO_RECORD_STOP:
+ drvdata->k90->record_led.brightness = 0;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int corsair_input_mapping(struct hid_device *dev,
+ struct hid_input *input,
+ struct hid_field *field,
+ struct hid_usage *usage, unsigned long **bit,
+ int *max)
+{
+ int gkey;
+
+ gkey = corsair_usage_to_gkey(usage->hid & HID_USAGE);
+ if (gkey != 0) {
+ hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+ corsair_gkey_map[gkey - 1]);
+ return 1;
+ }
+ if ((usage->hid & HID_USAGE) >= CORSAIR_USAGE_SPECIAL_MIN &&
+ (usage->hid & HID_USAGE) <= CORSAIR_USAGE_SPECIAL_MAX) {
+ switch (usage->hid & HID_USAGE) {
+ case CORSAIR_USAGE_MACRO_RECORD_START:
+ hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+ corsair_record_keycodes[0]);
+ return 1;
+
+ case CORSAIR_USAGE_MACRO_RECORD_STOP:
+ hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+ corsair_record_keycodes[1]);
+ return 1;
+
+ case CORSAIR_USAGE_M1:
+ hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+ corsair_profile_keycodes[0]);
+ return 1;
+
+ case CORSAIR_USAGE_M2:
+ hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+ corsair_profile_keycodes[1]);
+ return 1;
+
+ case CORSAIR_USAGE_M3:
+ hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+ corsair_profile_keycodes[2]);
+ return 1;
+
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static const struct hid_device_id corsair_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90),
+ .driver_data = CORSAIR_USE_K90_MACRO |
+ CORSAIR_USE_K90_BACKLIGHT },
+ {}
+};
+
+MODULE_DEVICE_TABLE(hid, corsair_devices);
+
+static struct hid_driver corsair_driver = {
+ .name = "corsair",
+ .id_table = corsair_devices,
+ .probe = corsair_probe,
+ .event = corsair_event,
+ .remove = corsair_remove,
+ .input_mapping = corsair_input_mapping,
+};
+
+static int __init corsair_init(void)
+{
+ return hid_register_driver(&corsair_driver);
+}
+
+static void corsair_exit(void)
+{
+ hid_unregister_driver(&corsair_driver);
+}
+
+module_init(corsair_init);
+module_exit(corsair_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Clement Vuchener");
+MODULE_DESCRIPTION("HID driver for Corsair devices");
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
index ce0644424f58..1d78ba3b799e 100644
--- a/drivers/hid/hid-dr.c
+++ b/drivers/hid/hid-dr.c
@@ -234,6 +234,58 @@ static __u8 pid0011_rdesc_fixed[] = {
0xC0 /* End Collection */
};
+static __u8 pid0006_rdesc_fixed[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x04, /* Usage (Joystick) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0xA1, 0x02, /* Collection (Logical) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x95, 0x05, /* Report Count (5) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
+ 0x35, 0x00, /* Physical Minimum (0) */
+ 0x46, 0xFF, 0x00, /* Physical Maximum (255) */
+ 0x09, 0x30, /* Usage (X) */
+ 0x09, 0x33, /* Usage (Ry) */
+ 0x09, 0x32, /* Usage (Z) */
+ 0x09, 0x31, /* Usage (Y) */
+ 0x09, 0x34, /* Usage (Ry) */
+ 0x81, 0x02, /* Input (Variable) */
+ 0x75, 0x04, /* Report Size (4) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x25, 0x07, /* Logical Maximum (7) */
+ 0x46, 0x3B, 0x01, /* Physical Maximum (315) */
+ 0x65, 0x14, /* Unit (Centimeter) */
+ 0x09, 0x39, /* Usage (Hat switch) */
+ 0x81, 0x42, /* Input (Variable) */
+ 0x65, 0x00, /* Unit (None) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x0C, /* Report Count (12) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x45, 0x01, /* Physical Maximum (1) */
+ 0x05, 0x09, /* Usage Page (Button) */
+ 0x19, 0x01, /* Usage Minimum (0x01) */
+ 0x29, 0x0C, /* Usage Maximum (0x0C) */
+ 0x81, 0x02, /* Input (Variable) */
+ 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x08, /* Report Count (8) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x45, 0x01, /* Physical Maximum (1) */
+ 0x09, 0x01, /* Usage (0x01) */
+ 0x81, 0x02, /* Input (Variable) */
+ 0xC0, /* End Collection */
+ 0xA1, 0x02, /* Collection (Logical) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x95, 0x07, /* Report Count (7) */
+ 0x46, 0xFF, 0x00, /* Physical Maximum (255) */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
+ 0x09, 0x02, /* Usage (0x02) */
+ 0x91, 0x02, /* Output (Variable) */
+ 0xC0, /* End Collection */
+ 0xC0 /* End Collection */
+};
+
static __u8 *dr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
@@ -244,6 +296,12 @@ static __u8 *dr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*rsize = sizeof(pid0011_rdesc_fixed);
}
break;
+ case 0x0006:
+ if (*rsize == sizeof(pid0006_rdesc_fixed)) {
+ rdesc = pid0006_rdesc_fixed;
+ *rsize = sizeof(pid0006_rdesc_fixed);
+ }
+ break;
}
return rdesc;
}
diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c
index d0bd13b62dc2..6e3848a8d8dd 100644
--- a/drivers/hid/hid-elecom.c
+++ b/drivers/hid/hid-elecom.c
@@ -27,7 +27,7 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
hid_info(hdev, "Fixing up Elecom BM084 report descriptor\n");
rdesc[47] = 0x00;
}
- return rdesc;
+ return rdesc;
}
static const struct hid_device_id elecom_devices[] = {
diff --git a/drivers/hid/hid-elo.c b/drivers/hid/hid-elo.c
index 4e49462870ab..aad8c162a825 100644
--- a/drivers/hid/hid-elo.c
+++ b/drivers/hid/hid-elo.c
@@ -37,7 +37,7 @@ static bool use_fw_quirk = true;
module_param(use_fw_quirk, bool, S_IRUGO);
MODULE_PARM_DESC(use_fw_quirk, "Do periodic pokes for broken M firmwares (default = true)");
-static void elo_input_configured(struct hid_device *hdev,
+static int elo_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
struct input_dev *input = hidinput->input;
@@ -45,6 +45,8 @@ static void elo_input_configured(struct hid_device *hdev,
set_bit(BTN_TOUCH, input->keybit);
set_bit(ABS_PRESSURE, input->absbit);
input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 0);
+
+ return 0;
}
static void elo_process_data(struct input_dev *input, const u8 *data, int size)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index f769208276ae..ac1feea51be3 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -251,6 +251,9 @@
#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500
#define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff
+#define USB_VENDOR_ID_CORSAIR 0x1b1c
+#define USB_DEVICE_ID_CORSAIR_K90 0x1b02
+
#define USB_VENDOR_ID_CREATIVELABS 0x041e
#define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801
@@ -288,6 +291,7 @@
#define USB_DEVICE_ID_DMI_ENC 0x5fab
#define USB_VENDOR_ID_DRAGONRISE 0x0079
+#define USB_DEVICE_ID_DRAGONRISE_WIIU 0x1800
#define USB_VENDOR_ID_DWAV 0x0eef
#define USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER 0x0001
@@ -510,6 +514,7 @@
#define USB_VENDOR_ID_ITE 0x048d
#define USB_DEVICE_ID_ITE_LENOVO_YOGA 0x8386
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350
#define USB_VENDOR_ID_JABRA 0x0b0e
#define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412
@@ -646,6 +651,7 @@
#define USB_VENDOR_ID_MADCATZ 0x0738
#define USB_DEVICE_ID_MADCATZ_BEATPAD 0x4540
+#define USB_DEVICE_ID_MADCATZ_RAT5 0x1705
#define USB_DEVICE_ID_MADCATZ_RAT9 0x1709
#define USB_VENDOR_ID_MCC 0x09db
@@ -679,6 +685,7 @@
#define USB_DEVICE_ID_MS_TOUCH_COVER_2 0x07a7
#define USB_DEVICE_ID_MS_TYPE_COVER_2 0x07a9
#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3 0x07dc
+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2 0x07e2
#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP 0x07dd
#define USB_DEVICE_ID_MS_TYPE_COVER_3 0x07de
#define USB_DEVICE_ID_MS_POWER_COVER 0x07da
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 53aeaf6252c7..2ba6bf69b7d0 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -1510,8 +1510,9 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
* UGCI) cram a lot of unrelated inputs into the
* same interface. */
hidinput->report = report;
- if (drv->input_configured)
- drv->input_configured(hid, hidinput);
+ if (drv->input_configured &&
+ drv->input_configured(hid, hidinput))
+ goto out_cleanup;
if (input_register_device(hidinput->input))
goto out_cleanup;
hidinput = NULL;
@@ -1532,8 +1533,9 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
}
if (hidinput) {
- if (drv->input_configured)
- drv->input_configured(hid, hidinput);
+ if (drv->input_configured &&
+ drv->input_configured(hid, hidinput))
+ goto out_cleanup;
if (input_register_device(hidinput->input))
goto out_cleanup;
}
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index e4bc6cb6d7fa..8979f1fd5208 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -848,7 +848,7 @@ static void lenovo_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
}
-static void lenovo_input_configured(struct hid_device *hdev,
+static int lenovo_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
switch (hdev->product) {
@@ -863,6 +863,8 @@ static void lenovo_input_configured(struct hid_device *hdev,
}
break;
}
+
+ return 0;
}
diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index 5332fb7d072a..c20ac76c0a8c 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -620,6 +620,7 @@ static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
usage->code == ABS_Y || usage->code == ABS_Z ||
usage->code == ABS_RZ)) {
switch (hdev->product) {
+ case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
case USB_DEVICE_ID_LOGITECH_WHEEL:
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
@@ -658,10 +659,18 @@ static int lg_event(struct hid_device *hdev, struct hid_field *field,
static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
+ struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+ __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
struct lg_drv_data *drv_data;
int ret;
+ /* Only work with the 1st interface (G29 presents multiple) */
+ if (iface_num != 0) {
+ dbg_hid("%s: ignoring ifnum %d\n", __func__, iface_num);
+ return -ENODEV;
+ }
+
drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL);
if (!drv_data) {
hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 02cec83caac3..fbddcb37ae98 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -45,7 +45,8 @@
#define LG4FF_MODE_G25_IDX 3
#define LG4FF_MODE_DFGT_IDX 4
#define LG4FF_MODE_G27_IDX 5
-#define LG4FF_MODE_MAX_IDX 6
+#define LG4FF_MODE_G29_IDX 6
+#define LG4FF_MODE_MAX_IDX 7
#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
@@ -53,6 +54,7 @@
#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
+#define LG4FF_MODE_G29 BIT(LG4FF_MODE_G29_IDX)
#define LG4FF_DFEX_TAG "DF-EX"
#define LG4FF_DFEX_NAME "Driving Force / Formula EX"
@@ -62,6 +64,8 @@
#define LG4FF_G25_NAME "G25 Racing Wheel"
#define LG4FF_G27_TAG "G27"
#define LG4FF_G27_NAME "G27 Racing Wheel"
+#define LG4FF_G29_TAG "G29"
+#define LG4FF_G29_NAME "G29 Racing Wheel"
#define LG4FF_DFGT_TAG "DFGT"
#define LG4FF_DFGT_NAME "Driving Force GT"
@@ -114,16 +118,12 @@ struct lg4ff_compat_mode_switch {
};
struct lg4ff_wheel_ident_info {
+ const u32 modes;
const u16 mask;
const u16 result;
const u16 real_product_id;
};
-struct lg4ff_wheel_ident_checklist {
- const u32 count;
- const struct lg4ff_wheel_ident_info *models[];
-};
-
struct lg4ff_multimode_wheel {
const u16 product_id;
const u32 alternate_modes;
@@ -144,6 +144,7 @@ static const struct lg4ff_wheel lg4ff_devices[] = {
{USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
{USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
+ {USB_DEVICE_ID_LOGITECH_G29_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL},
{USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
};
@@ -161,6 +162,9 @@ static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
{USB_DEVICE_ID_LOGITECH_G27_WHEEL,
LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
LG4FF_G27_TAG, LG4FF_G27_NAME},
+ {USB_DEVICE_ID_LOGITECH_G29_WHEEL,
+ LG4FF_MODE_NATIVE | LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ LG4FF_G29_TAG, LG4FF_G29_NAME},
};
static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
@@ -169,41 +173,61 @@ static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
[LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
[LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
[LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
- [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME}
+ [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME},
+ [LG4FF_MODE_G29_IDX] = {USB_DEVICE_ID_LOGITECH_G29_WHEEL, LG4FF_G29_TAG, LG4FF_G29_NAME},
};
/* Multimode wheel identificators */
static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
+ LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
0xf000,
0x1000,
USB_DEVICE_ID_LOGITECH_DFP_WHEEL
};
static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
+ LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
0xff00,
0x1200,
USB_DEVICE_ID_LOGITECH_G25_WHEEL
};
static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
+ LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
0xfff0,
0x1230,
USB_DEVICE_ID_LOGITECH_G27_WHEEL
};
static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
+ LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
0xff00,
0x1300,
USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
};
+static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info = {
+ LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ 0xfff8,
+ 0x1350,
+ USB_DEVICE_ID_LOGITECH_G29_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info2 = {
+ LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+ 0xff00,
+ 0x8900,
+ USB_DEVICE_ID_LOGITECH_G29_WHEEL
+};
+
/* Multimode wheel identification checklists */
-static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
- 4,
- {&lg4ff_dfgt_ident_info,
- &lg4ff_g27_ident_info,
- &lg4ff_g25_ident_info,
- &lg4ff_dfp_ident_info}
+static const struct lg4ff_wheel_ident_info *lg4ff_main_checklist[] = {
+ &lg4ff_g29_ident_info,
+ &lg4ff_g29_ident_info2,
+ &lg4ff_dfgt_ident_info,
+ &lg4ff_g27_ident_info,
+ &lg4ff_g25_ident_info,
+ &lg4ff_dfp_ident_info
};
/* Compatibility mode switching commands */
@@ -238,6 +262,12 @@ static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
};
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g29 = {
+ 2,
+ {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
+ 0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00} /* Switch mode to G29 with detach */
+};
+
/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
1,
@@ -651,6 +681,23 @@ static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(cons
return NULL;
}
break;
+ case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+ switch (target_product_id) {
+ case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfp;
+ case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+ return &lg4ff_mode_switch_ext09_dfgt;
+ case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+ return &lg4ff_mode_switch_ext09_g25;
+ case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+ return &lg4ff_mode_switch_ext09_g27;
+ case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+ return &lg4ff_mode_switch_ext09_g29;
+ /* G29 can only be switched to DF-EX, DFP, DFGT, G25, G27 or its native mode */
+ default:
+ return NULL;
+ }
+ break;
case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
switch (target_product_id) {
case USB_DEVICE_ID_LOGITECH_WHEEL:
@@ -1037,41 +1084,28 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde
static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
{
- const struct lg4ff_wheel_ident_checklist *checklist;
- int i, from_idx, to_idx;
+ u32 current_mode;
+ int i;
- switch (reported_product_id) {
- case USB_DEVICE_ID_LOGITECH_WHEEL:
- case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
- checklist = &lg4ff_main_checklist;
- from_idx = 0;
- to_idx = checklist->count - 1;
- break;
- case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
- checklist = &lg4ff_main_checklist;
- from_idx = 0;
- to_idx = checklist->count - 2; /* End identity check at G25 */
- break;
- case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
- checklist = &lg4ff_main_checklist;
- from_idx = 1; /* Start identity check at G27 */
- to_idx = checklist->count - 3; /* End identity check at G27 */
- break;
- case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
- checklist = &lg4ff_main_checklist;
- from_idx = 0;
- to_idx = checklist->count - 4; /* End identity check at DFGT */
- break;
- default:
- return 0;
+ /* identify current mode from USB PID */
+ for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) {
+ dbg_hid("Testing whether PID is %X\n", lg4ff_alternate_modes[i].product_id);
+ if (reported_product_id == lg4ff_alternate_modes[i].product_id)
+ break;
}
- for (i = from_idx; i <= to_idx; i++) {
- const u16 mask = checklist->models[i]->mask;
- const u16 result = checklist->models[i]->result;
- const u16 real_product_id = checklist->models[i]->real_product_id;
+ if (i == ARRAY_SIZE(lg4ff_alternate_modes))
+ return 0;
+
+ current_mode = BIT(i);
+
+ for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) {
+ const u16 mask = lg4ff_main_checklist[i]->mask;
+ const u16 result = lg4ff_main_checklist[i]->result;
+ const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id;
- if ((bcdDevice & mask) == result) {
+ if ((current_mode & lg4ff_main_checklist[i]->modes) && \
+ (bcdDevice & mask) == result) {
dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
return real_product_id;
}
@@ -1246,12 +1280,13 @@ int lg4ff_init(struct hid_device *hid)
entry->wdata.set_range(hid, entry->wdata.range);
#ifdef CONFIG_LEDS_CLASS
- /* register led subsystem - G27 only */
+ /* register led subsystem - G27/G29 only */
entry->wdata.led_state = 0;
for (j = 0; j < 5; j++)
entry->wdata.led[j] = NULL;
- if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) {
+ if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL ||
+ lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) {
struct led_classdev *led;
size_t name_sz;
char *name;
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 484196459305..5fd97860aec4 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -33,6 +33,11 @@ module_param(disable_raw_mode, bool, 0644);
MODULE_PARM_DESC(disable_raw_mode,
"Disable Raw mode reporting for touchpads and keep firmware gestures.");
+static bool disable_tap_to_click;
+module_param(disable_tap_to_click, bool, 0644);
+MODULE_PARM_DESC(disable_tap_to_click,
+ "Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently).");
+
#define REPORT_ID_HIDPP_SHORT 0x10
#define REPORT_ID_HIDPP_LONG 0x11
@@ -41,10 +46,15 @@ MODULE_PARM_DESC(disable_raw_mode,
#define HIDPP_QUIRK_CLASS_WTP BIT(0)
#define HIDPP_QUIRK_CLASS_M560 BIT(1)
+#define HIDPP_QUIRK_CLASS_K400 BIT(2)
/* bits 2..20 are reserved for classes */
-#define HIDPP_QUIRK_DELAYED_INIT BIT(21)
+#define HIDPP_QUIRK_CONNECT_EVENTS BIT(21)
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
+#define HIDPP_QUIRK_NO_HIDINPUT BIT(23)
+
+#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \
+ HIDPP_QUIRK_CONNECT_EVENTS)
/*
* There are two hidpp protocols in use, the first version hidpp10 is known
@@ -553,6 +563,52 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
}
/* -------------------------------------------------------------------------- */
+/* 0x6010: Touchpad FW items */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS 0x6010
+
+#define CMD_TOUCHPAD_FW_ITEMS_SET 0x10
+
+struct hidpp_touchpad_fw_items {
+ uint8_t presence;
+ uint8_t desired_state;
+ uint8_t state;
+ uint8_t persistent;
+};
+
+/**
+ * send a set state command to the device by reading the current items->state
+ * field. items is then filled with the current state.
+ */
+static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
+ u8 feature_index,
+ struct hidpp_touchpad_fw_items *items)
+{
+ struct hidpp_report response;
+ int ret;
+ u8 *params = (u8 *)response.fap.params;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);
+
+ if (ret > 0) {
+ hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+ __func__, ret);
+ return -EPROTO;
+ }
+ if (ret)
+ return ret;
+
+ items->presence = params[0];
+ items->desired_state = params[1];
+ items->state = params[2];
+ items->persistent = params[3];
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
/* 0x6100: TouchPadRawXY */
/* -------------------------------------------------------------------------- */
@@ -1132,6 +1188,75 @@ static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return -1;
}
+/* ------------------------------------------------------------------------- */
+/* Logitech K400 devices */
+/* ------------------------------------------------------------------------- */
+
+/*
+ * The Logitech K400 keyboard has an embedded touchpad which is seen
+ * as a mouse from the OS point of view. There is a hardware shortcut to disable
+ * tap-to-click but the setting is not remembered accross reset, annoying some
+ * users.
+ *
+ * We can toggle this feature from the host by using the feature 0x6010:
+ * Touchpad FW items
+ */
+
+struct k400_private_data {
+ u8 feature_index;
+};
+
+static int k400_disable_tap_to_click(struct hidpp_device *hidpp)
+{
+ struct k400_private_data *k400 = hidpp->private_data;
+ struct hidpp_touchpad_fw_items items = {};
+ int ret;
+ u8 feature_type;
+
+ if (!k400->feature_index) {
+ ret = hidpp_root_get_feature(hidpp,
+ HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
+ &k400->feature_index, &feature_type);
+ if (ret)
+ /* means that the device is not powered up */
+ return ret;
+ }
+
+ ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int k400_allocate(struct hid_device *hdev)
+{
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ struct k400_private_data *k400;
+
+ k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data),
+ GFP_KERNEL);
+ if (!k400)
+ return -ENOMEM;
+
+ hidpp->private_data = k400;
+
+ return 0;
+};
+
+static int k400_connect(struct hid_device *hdev, bool connected)
+{
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+ if (!connected)
+ return 0;
+
+ if (!disable_tap_to_click)
+ return 0;
+
+ return k400_disable_tap_to_click(hidpp);
+}
+
/* -------------------------------------------------------------------------- */
/* Generic HID++ devices */
/* -------------------------------------------------------------------------- */
@@ -1160,13 +1285,15 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
m560_populate_input(hidpp, input, origin_is_hid_core);
}
-static void hidpp_input_configured(struct hid_device *hdev,
+static int hidpp_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
struct input_dev *input = hidinput->input;
hidpp_populate_input(hidpp, input, true);
+
+ return 0;
}
static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
@@ -1203,7 +1330,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
if (unlikely(hidpp_report_is_connect_event(report))) {
atomic_set(&hidpp->connected,
!(report->rap.params[0] & (1 << 6)));
- if ((hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) &&
+ if ((hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) &&
(schedule_work(&hidpp->work) == 0))
dbg_hid("%s: connect event already queued\n", __func__);
return 1;
@@ -1328,23 +1455,30 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
ret = m560_send_config_command(hdev, connected);
if (ret)
return;
+ } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
+ ret = k400_connect(hdev, connected);
+ if (ret)
+ return;
}
if (!connected || hidpp->delayed_input)
return;
+ /* the device is already connected, we can ask for its name and
+ * protocol */
if (!hidpp->protocol_major) {
ret = !hidpp_is_connected(hidpp);
if (ret) {
hid_err(hdev, "Can not get the protocol version.\n");
return;
}
+ hid_info(hdev, "HID++ %u.%u device connected.\n",
+ hidpp->protocol_major, hidpp->protocol_minor);
}
- /* the device is already connected, we can ask for its name and
- * protocol */
- hid_info(hdev, "HID++ %u.%u device connected.\n",
- hidpp->protocol_major, hidpp->protocol_minor);
+ if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT))
+ /* if HID created the input nodes for us, we can stop now */
+ return;
if (!hidpp->name || hidpp->name == hdev->name) {
name = hidpp_get_device_name(hidpp);
@@ -1397,7 +1531,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (disable_raw_mode) {
hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
- hidpp->quirks &= ~HIDPP_QUIRK_DELAYED_INIT;
+ hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS;
+ hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
@@ -1408,6 +1543,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = m560_allocate(hdev);
if (ret)
goto allocate_fail;
+ } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
+ ret = k400_allocate(hdev);
+ if (ret)
+ goto allocate_fail;
}
INIT_WORK(&hidpp->work, delayed_work_cb);
@@ -1448,7 +1587,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
/* Block incoming packets */
hid_device_io_stop(hdev);
- if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
+ if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
connect_mask &= ~HID_CONNECT_HIDINPUT;
ret = hid_hw_start(hdev, connect_mask);
@@ -1457,7 +1596,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto hid_hw_start_fail;
}
- if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) {
+ if (hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) {
/* Allow incoming packets */
hid_device_io_start(hdev);
@@ -1502,6 +1641,10 @@ static const struct hid_device_id hidpp_devices[] = {
HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
USB_VENDOR_ID_LOGITECH, 0x402d),
.driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
+ { /* Keyboard logitech K400 */
+ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+ USB_VENDOR_ID_LOGITECH, 0x4024),
+ .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_CLASS_K400 },
{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 29a74c1efcb8..d6fa496d0ca2 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -471,18 +471,22 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
return 0;
}
-static void magicmouse_input_configured(struct hid_device *hdev,
+static int magicmouse_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+ int ret;
- int ret = magicmouse_setup_input(msc->input, hdev);
+ ret = magicmouse_setup_input(msc->input, hdev);
if (ret) {
hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
/* clean msc->input to notify probe() of the failure */
msc->input = NULL;
+ return ret;
}
+
+ return 0;
}
diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c
index 9aa3515090a7..77a2cf3e4afe 100644
--- a/drivers/hid/hid-microsoft.c
+++ b/drivers/hid/hid-microsoft.c
@@ -278,6 +278,8 @@ static const struct hid_device_id ms_devices[] = {
.driver_data = MS_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3),
.driver_data = MS_HIDINPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2),
+ .driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP),
.driver_data = MS_HIDINPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3),
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 426b2f1a3450..3d664d01305e 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -309,6 +309,41 @@ static struct attribute_group mt_attribute_group = {
.attrs = sysfs_attrs
};
+static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
+{
+ struct mt_device *td = hid_get_drvdata(hdev);
+ int ret, size = hid_report_len(report);
+ u8 *buf;
+
+ /*
+ * Only fetch the feature report if initial reports are not already
+ * been retrieved. Currently this is only done for Windows 8 touch
+ * devices.
+ */
+ if (!(hdev->quirks & HID_QUIRK_NO_INIT_REPORTS))
+ return;
+ if (td->mtclass.name != MT_CLS_WIN_8)
+ return;
+
+ buf = hid_alloc_report_buf(report, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ ret = hid_hw_raw_request(hdev, report->id, buf, size,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret < 0) {
+ dev_warn(&hdev->dev, "failed to fetch feature %d\n",
+ report->id);
+ } else {
+ ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
+ size, 0);
+ if (ret)
+ dev_warn(&hdev->dev, "failed to report feature\n");
+ }
+
+ kfree(buf);
+}
+
static void mt_feature_mapping(struct hid_device *hdev,
struct hid_field *field, struct hid_usage *usage)
{
@@ -327,6 +362,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
break;
case HID_DG_CONTACTMAX:
+ mt_get_feature(hdev, field->report);
+
td->maxcontact_report_id = field->report->id;
td->maxcontacts = field->value[0];
if (!td->maxcontacts &&
@@ -343,6 +380,7 @@ static void mt_feature_mapping(struct hid_device *hdev,
break;
}
+ mt_get_feature(hdev, field->report);
if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD)
td->is_buttonpad = true;
@@ -725,12 +763,13 @@ static void mt_touch_report(struct hid_device *hid, struct hid_report *report)
mt_sync_frame(td, report->field[0]->hidinput->input);
}
-static void mt_touch_input_configured(struct hid_device *hdev,
+static int mt_touch_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
struct mt_device *td = hid_get_drvdata(hdev);
struct mt_class *cls = &td->mtclass;
struct input_dev *input = hi->input;
+ int ret;
if (!td->maxcontacts)
td->maxcontacts = MT_DEFAULT_MAXCONTACT;
@@ -752,9 +791,12 @@ static void mt_touch_input_configured(struct hid_device *hdev,
if (td->is_buttonpad)
__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
- input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
+ ret = input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
+ if (ret)
+ return ret;
td->mt_flags = 0;
+ return 0;
}
static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@@ -930,15 +972,19 @@ static void mt_post_parse(struct mt_device *td)
cls->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE;
}
-static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
+static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
{
struct mt_device *td = hid_get_drvdata(hdev);
char *name;
const char *suffix = NULL;
struct hid_field *field = hi->report->field[0];
+ int ret;
- if (hi->report->id == td->mt_report_id)
- mt_touch_input_configured(hdev, hi);
+ if (hi->report->id == td->mt_report_id) {
+ ret = mt_touch_input_configured(hdev, hi);
+ if (ret)
+ return ret;
+ }
/*
* some egalax touchscreens have "application == HID_DG_TOUCHSCREEN"
@@ -968,6 +1014,9 @@ static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
case HID_DG_TOUCHSCREEN:
/* we do not set suffix = "Touchscreen" */
break;
+ case HID_DG_TOUCHPAD:
+ suffix = "Touchpad";
+ break;
case HID_GD_SYSTEM_CONTROL:
suffix = "System Control";
break;
@@ -989,6 +1038,8 @@ static void mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
hi->input->name = name;
}
}
+
+ return 0;
}
static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
@@ -1026,8 +1077,13 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
* reports. Fortunately, the Win8 spec says that all touches
* should be sent during each report, making the initialization
* of input reports unnecessary.
+ *
+ * In addition some touchpads do not behave well if we read
+ * all feature reports from them. Instead we prevent
+ * initial report fetching and then selectively fetch each
+ * report we are interested in.
*/
- hdev->quirks |= HID_QUIRK_NO_INIT_INPUT_REPORTS;
+ hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
td = devm_kzalloc(&hdev->dev, sizeof(struct mt_device), GFP_KERNEL);
if (!td) {
diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c
index 600f2075512f..756d1ef9bd99 100644
--- a/drivers/hid/hid-ntrig.c
+++ b/drivers/hid/hid-ntrig.c
@@ -859,14 +859,14 @@ not_claimed_input:
return 1;
}
-static void ntrig_input_configured(struct hid_device *hid,
+static int ntrig_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
struct input_dev *input = hidinput->input;
if (hidinput->report->maxfield < 1)
- return;
+ return 0;
switch (hidinput->report->field[0]->application) {
case HID_DG_PEN:
@@ -890,6 +890,8 @@ static void ntrig_input_configured(struct hid_device *hid,
"N-Trig MultiTouch";
break;
}
+
+ return 0;
}
static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c
index e3e98ccf137b..3a207c0ac0e3 100644
--- a/drivers/hid/hid-prodikeys.c
+++ b/drivers/hid/hid-prodikeys.c
@@ -427,7 +427,7 @@ static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data)
pm->midi_octave = 2;
dbg_hid("pcmidi mode: %d octave: %d\n",
pm->midi_mode, pm->midi_octave);
- continue;
+ continue;
} else
key = KEY_MESSENGER;
break;
@@ -695,7 +695,7 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
if (err < 0) {
pk_error("failed to register pc-midi sound card: error %d\n",
err);
- goto fail_register;
+ goto fail_register;
}
dbg_hid("pcmidi_snd_initialise finished ok\n");
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 2c148129beb2..67cd059a8f46 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -1173,7 +1173,7 @@ static int rmi_populate(struct hid_device *hdev)
return 0;
}
-static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
+static int rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
{
struct rmi_data *data = hid_get_drvdata(hdev);
struct input_dev *input = hi->input;
@@ -1185,10 +1185,10 @@ static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
hid_dbg(hdev, "Opening low level driver\n");
ret = hid_hw_open(hdev);
if (ret)
- return;
+ return ret;
if (!(data->device_flags & RMI_DEVICE))
- return;
+ return 0;
/* Allow incoming hid reports */
hid_device_io_start(hdev);
@@ -1228,7 +1228,9 @@ static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
- input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
+ ret = input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
+ if (ret < 0)
+ goto exit;
if (data->button_count) {
__set_bit(EV_KEY, input->evbit);
@@ -1244,6 +1246,7 @@ static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
exit:
hid_device_io_stop(hdev);
hid_hw_close(hdev);
+ return ret;
}
static int rmi_input_mapping(struct hid_device *hdev,
diff --git a/drivers/hid/hid-saitek.c b/drivers/hid/hid-saitek.c
index a014f21275d8..2f84b26f1167 100644
--- a/drivers/hid/hid-saitek.c
+++ b/drivers/hid/hid-saitek.c
@@ -177,6 +177,8 @@ static int saitek_event(struct hid_device *hdev, struct hid_field *field,
static const struct hid_device_id saitek_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000),
.driver_data = SAITEK_FIX_PS1000 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT5),
+ .driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7),
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
index a76eb2a0a987..92870cdb52d9 100644
--- a/drivers/hid/hid-sensor-hub.c
+++ b/drivers/hid/hid-sensor-hub.c
@@ -593,6 +593,20 @@ static __u8 *sensor_hub_report_fixup(struct hid_device *hdev, __u8 *rdesc,
}
}
+ /* Checks if the report descriptor of Thinkpad Helix 2 has a logical
+ * minimum for magnetic flux axis greater than the maximum */
+ if (hdev->product == USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA &&
+ *rsize == 2558 && rdesc[913] == 0x17 && rdesc[914] == 0x40 &&
+ rdesc[915] == 0x81 && rdesc[916] == 0x08 &&
+ rdesc[917] == 0x00 && rdesc[918] == 0x27 &&
+ rdesc[921] == 0x07 && rdesc[922] == 0x00) {
+ /* Sets negative logical minimum for mag x, y and z */
+ rdesc[914] = rdesc[935] = rdesc[956] = 0xc0;
+ rdesc[915] = rdesc[936] = rdesc[957] = 0x7e;
+ rdesc[916] = rdesc[937] = rdesc[958] = 0xf7;
+ rdesc[917] = rdesc[938] = rdesc[959] = 0xff;
+ }
+
return rdesc;
}
@@ -646,8 +660,8 @@ static int sensor_hub_probe(struct hid_device *hdev,
GFP_KERNEL);
if (sd->hid_sensor_hub_client_devs == NULL) {
hid_err(hdev, "Failed to allocate memory for mfd cells\n");
- ret = -ENOMEM;
- goto err_stop_hw;
+ ret = -ENOMEM;
+ goto err_stop_hw;
}
for (i = 0; i < hdev->maxcollection; ++i) {
@@ -684,8 +698,8 @@ static int sensor_hub_probe(struct hid_device *hdev,
collection->usage);
if (name == NULL) {
hid_err(hdev, "Failed MFD device name\n");
- ret = -ENOMEM;
- goto err_stop_hw;
+ ret = -ENOMEM;
+ goto err_stop_hw;
}
sd->hid_sensor_hub_client_devs[
sd->hid_sensor_client_cnt].name = name;
@@ -777,6 +791,9 @@ static const struct hid_device_id sensor_hub_devices[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_ITE,
USB_DEVICE_ID_ITE_LENOVO_YOGA),
.driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_ITE,
+ USB_DEVICE_ID_ITE_LENOVO_YOGA2),
+ .driver_data = HID_SENSOR_HUB_ENUM_QUIRK},
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, HID_ANY_ID,
HID_ANY_ID) },
{ }
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 661f94f8ab8b..774cd2210566 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1360,20 +1360,27 @@ static int sony_register_touchpad(struct hid_input *hi, int touch_count,
return 0;
}
-static void sony_input_configured(struct hid_device *hdev,
+static int sony_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
struct sony_sc *sc = hid_get_drvdata(hdev);
+ int ret;
/*
* The Dualshock 4 touchpad supports 2 touches and has a
* resolution of 1920x942 (44.86 dots/mm).
*/
if (sc->quirks & DUALSHOCK4_CONTROLLER) {
- if (sony_register_touchpad(hidinput, 2, 1920, 942) != 0)
+ ret = sony_register_touchpad(hidinput, 2, 1920, 942);
+ if (ret) {
hid_err(sc->hdev,
- "Unable to initialize multi-touch slots\n");
+ "Unable to initialize multi-touch slots: %d\n",
+ ret);
+ return ret;
+ }
}
+
+ return 0;
}
/*
diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c
index b905d501e752..85ac43517e3f 100644
--- a/drivers/hid/hid-uclogic.c
+++ b/drivers/hid/hid-uclogic.c
@@ -731,7 +731,7 @@ static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
-static void uclogic_input_configured(struct hid_device *hdev,
+static int uclogic_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
char *name;
@@ -741,7 +741,7 @@ static void uclogic_input_configured(struct hid_device *hdev,
/* no report associated (HID_QUIRK_MULTI_INPUT not set) */
if (!hi->report)
- return;
+ return 0;
field = hi->report->field[0];
@@ -774,6 +774,8 @@ static void uclogic_input_configured(struct hid_device *hdev,
hi->input->name = name;
}
}
+
+ return 0;
}
/**
diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
index 2871f3c81a4c..10bd8e6e4c9c 100644
--- a/drivers/hid/i2c-hid/i2c-hid.c
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -1028,6 +1028,7 @@ static int i2c_hid_probe(struct i2c_client *client,
snprintf(hid->name, sizeof(hid->name), "%s %04hX:%04hX",
client->name, hid->vendor, hid->product);
+ strlcpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
ret = hid_add_device(hid);
if (ret) {
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
index 1dff8f0015ba..94bb137abe32 100644
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -71,6 +71,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_AXIS_295, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE, HID_QUIRK_ALWAYS_POLL },
{ USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET },
+ { USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU, HID_QUIRK_MULTI_INPUT },
{ USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN, HID_QUIRK_ALWAYS_POLL },
{ USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_009B, HID_QUIRK_ALWAYS_POLL },
{ USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_0103, HID_QUIRK_ALWAYS_POLL },
@@ -91,6 +92,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_2, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TOUCH_COVER_2, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER, HID_QUIRK_NO_INIT_REPORTS },
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index 9db6a8b1150c..8b29949507d1 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -1652,6 +1652,7 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev,
wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
break;
case HID_DG_CONTACTCOUNT:
+ wacom_wac->hid_data.cc_report = field->report->id;
wacom_wac->hid_data.cc_index = field->index;
wacom_wac->hid_data.cc_value_index = usage->usage_index;
break;
@@ -1739,7 +1740,32 @@ static void wacom_wac_finger_pre_report(struct hid_device *hdev,
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
struct hid_data* hid_data = &wacom_wac->hid_data;
- if (hid_data->cc_index >= 0) {
+ if (hid_data->cc_report != 0 &&
+ hid_data->cc_report != report->id) {
+ int i;
+
+ hid_data->cc_report = report->id;
+ hid_data->cc_index = -1;
+ hid_data->cc_value_index = -1;
+
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+ int j;
+
+ for (j = 0; j < field->maxusage; j++) {
+ if (field->usage[j].hid == HID_DG_CONTACTCOUNT) {
+ hid_data->cc_index = i;
+ hid_data->cc_value_index = j;
+
+ /* break */
+ i = report->maxfield;
+ j = field->maxusage;
+ }
+ }
+ }
+ }
+ if (hid_data->cc_report != 0 &&
+ hid_data->cc_index >= 0) {
struct hid_field *field = report->field[hid_data->cc_index];
int value = field->value[hid_data->cc_value_index];
if (value)
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
index d9e9515fd149..877c24a5df94 100644
--- a/drivers/hid/wacom_wac.h
+++ b/drivers/hid/wacom_wac.h
@@ -203,6 +203,7 @@ struct hid_data {
int width;
int height;
int id;
+ int cc_report;
int cc_index;
int cc_value_index;
int num_expected;