aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig1
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-axff.c11
-rw-r--r--drivers/hid/hid-core.c62
-rw-r--r--drivers/hid/hid-dr.c12
-rw-r--r--drivers/hid/hid-emsff.c12
-rw-r--r--drivers/hid/hid-gaff.c12
-rw-r--r--drivers/hid/hid-google-hammer.c150
-rw-r--r--drivers/hid/hid-holtekff.c12
-rw-r--r--drivers/hid/hid-hyperv.c90
-rw-r--r--drivers/hid/hid-ids.h8
-rw-r--r--drivers/hid/hid-lg-g15.c899
-rw-r--r--drivers/hid/hid-lg2ff.c12
-rw-r--r--drivers/hid/hid-lg3ff.c11
-rw-r--r--drivers/hid/hid-lg4ff.c11
-rw-r--r--drivers/hid/hid-lgff.c11
-rw-r--r--drivers/hid/hid-logitech-hidpp.c251
-rw-r--r--drivers/hid/hid-microsoft.c12
-rw-r--r--drivers/hid/hid-prodikeys.c4
-rw-r--r--drivers/hid/hid-quirks.c8
-rw-r--r--drivers/hid/hid-rmi.c3
-rw-r--r--drivers/hid/hid-sony.c12
-rw-r--r--drivers/hid/hid-tmff.c12
-rw-r--r--drivers/hid/hid-zpff.c12
-rw-r--r--drivers/hid/hidraw.c14
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c138
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c19
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client-buffers.c2
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/hbm.c2
-rw-r--r--drivers/hid/usbhid/hiddev.c11
-rw-r--r--drivers/hid/wacom.h15
-rw-r--r--drivers/hid/wacom_wac.c10
32 files changed, 1452 insertions, 388 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 1ecb5124421c..494a39e74939 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -525,6 +525,7 @@ config HID_LENOVO
config HID_LOGITECH
tristate "Logitech devices"
depends on HID
+ depends on LEDS_CLASS
default !EXPERT
---help---
Support for Logitech devices that are not fully compliant with HID standard.
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 0c03308cfb08..bfefa365b1ce 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_HID_KYE) += hid-kye.o
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
+obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o
obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o
obj-$(CONFIG_HID_MACALLY) += hid-macally.o
diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c
index 6654c1550e2e..fbe4e16ab029 100644
--- a/drivers/hid/hid-axff.c
+++ b/drivers/hid/hid-axff.c
@@ -63,13 +63,20 @@ static int axff_init(struct hid_device *hid)
{
struct axff_device *axff;
struct hid_report *report;
- struct hid_input *hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ struct hid_input *hidinput;
struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev = hidinput->input;
+ struct input_dev *dev;
int field_count = 0;
int i, j;
int error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 3eaee2c37931..e0b241bd3070 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -212,6 +212,18 @@ static unsigned hid_lookup_collection(struct hid_parser *parser, unsigned type)
}
/*
+ * Concatenate usage which defines 16 bits or less with the
+ * currently defined usage page to form a 32 bit usage
+ */
+
+static void complete_usage(struct hid_parser *parser, unsigned int index)
+{
+ parser->local.usage[index] &= 0xFFFF;
+ parser->local.usage[index] |=
+ (parser->global.usage_page & 0xFFFF) << 16;
+}
+
+/*
* Add a usage to the temporary parser table.
*/
@@ -222,6 +234,14 @@ static int hid_add_usage(struct hid_parser *parser, unsigned usage, u8 size)
return -1;
}
parser->local.usage[parser->local.usage_index] = usage;
+
+ /*
+ * If Usage item only includes usage id, concatenate it with
+ * currently defined usage page
+ */
+ if (size <= 2)
+ complete_usage(parser, parser->local.usage_index);
+
parser->local.usage_size[parser->local.usage_index] = size;
parser->local.collection_index[parser->local.usage_index] =
parser->collection_stack_ptr ?
@@ -543,13 +563,32 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
* usage value."
*/
-static void hid_concatenate_usage_page(struct hid_parser *parser)
+static void hid_concatenate_last_usage_page(struct hid_parser *parser)
{
int i;
+ unsigned int usage_page;
+ unsigned int current_page;
+
+ if (!parser->local.usage_index)
+ return;
- for (i = 0; i < parser->local.usage_index; i++)
- if (parser->local.usage_size[i] <= 2)
- parser->local.usage[i] += parser->global.usage_page << 16;
+ usage_page = parser->global.usage_page;
+
+ /*
+ * Concatenate usage page again only if last declared Usage Page
+ * has not been already used in previous usages concatenation
+ */
+ for (i = parser->local.usage_index - 1; i >= 0; i--) {
+ if (parser->local.usage_size[i] > 2)
+ /* Ignore extended usages */
+ continue;
+
+ current_page = parser->local.usage[i] >> 16;
+ if (current_page == usage_page)
+ break;
+
+ complete_usage(parser, i);
+ }
}
/*
@@ -561,7 +600,7 @@ static int hid_parser_main(struct hid_parser *parser, struct hid_item *item)
__u32 data;
int ret;
- hid_concatenate_usage_page(parser);
+ hid_concatenate_last_usage_page(parser);
data = item_udata(item);
@@ -742,6 +781,10 @@ static void hid_scan_feature_usage(struct hid_parser *parser, u32 usage)
if (usage == 0xff0000c5 && parser->global.report_count == 256 &&
parser->global.report_size == 8)
parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8;
+
+ if (usage == 0xff0000c6 && parser->global.report_count == 1 &&
+ parser->global.report_size == 8)
+ parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8;
}
static void hid_scan_collection(struct hid_parser *parser, unsigned type)
@@ -772,7 +815,7 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
__u32 data;
int i;
- hid_concatenate_usage_page(parser);
+ hid_concatenate_last_usage_page(parser);
data = item_udata(item);
@@ -1139,6 +1182,7 @@ int hid_open_report(struct hid_device *device)
__u8 *start;
__u8 *buf;
__u8 *end;
+ __u8 *next;
int ret;
static int (*dispatch_type[])(struct hid_parser *parser,
struct hid_item *item) = {
@@ -1192,7 +1236,8 @@ int hid_open_report(struct hid_device *device)
device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
ret = -EINVAL;
- while ((start = fetch_item(start, end, &item)) != NULL) {
+ while ((next = fetch_item(start, end, &item)) != NULL) {
+ start = next;
if (item.format != HID_ITEM_FORMAT_SHORT) {
hid_err(device, "unexpected long global item\n");
@@ -1230,7 +1275,8 @@ int hid_open_report(struct hid_device *device)
}
}
- hid_err(device, "item fetching failed at offset %d\n", (int)(end - start));
+ hid_err(device, "item fetching failed at offset %u/%u\n",
+ size - (unsigned int)(end - start), size);
err:
kfree(parser->collection_stack);
alloc_err:
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
index 17e17f9a597b..947f19f8685f 100644
--- a/drivers/hid/hid-dr.c
+++ b/drivers/hid/hid-dr.c
@@ -75,13 +75,19 @@ static int drff_init(struct hid_device *hid)
{
struct drff_device *drff;
struct hid_report *report;
- struct hid_input *hidinput = list_first_entry(&hid->inputs,
- struct hid_input, list);
+ struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev = hidinput->input;
+ struct input_dev *dev;
int error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c
index 7cd5651872d3..c34f2e5a049f 100644
--- a/drivers/hid/hid-emsff.c
+++ b/drivers/hid/hid-emsff.c
@@ -47,13 +47,19 @@ static int emsff_init(struct hid_device *hid)
{
struct emsff_device *emsff;
struct hid_report *report;
- struct hid_input *hidinput = list_first_entry(&hid->inputs,
- struct hid_input, list);
+ struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev = hidinput->input;
+ struct input_dev *dev;
int error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c
index 0f95c96b70f8..ecbd3995a4eb 100644
--- a/drivers/hid/hid-gaff.c
+++ b/drivers/hid/hid-gaff.c
@@ -64,14 +64,20 @@ static int gaff_init(struct hid_device *hid)
{
struct gaff_device *gaff;
struct hid_report *report;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
+ struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
- struct input_dev *dev = hidinput->input;
+ struct input_dev *dev;
int error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
if (list_empty(report_list)) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
index 84f8c127ebdc..2aa4ed157aec 100644
--- a/drivers/hid/hid-google-hammer.c
+++ b/drivers/hid/hid-google-hammer.c
@@ -35,6 +35,7 @@ struct cbas_ec {
struct device *dev; /* The platform device (EC) */
struct input_dev *input;
bool base_present;
+ bool base_folded;
struct notifier_block notifier;
};
@@ -208,7 +209,14 @@ static int __cbas_ec_probe(struct platform_device *pdev)
return error;
}
- input_report_switch(input, SW_TABLET_MODE, !cbas_ec.base_present);
+ if (!cbas_ec.base_present)
+ cbas_ec.base_folded = false;
+
+ dev_dbg(&pdev->dev, "%s: base: %d, folded: %d\n", __func__,
+ cbas_ec.base_present, cbas_ec.base_folded);
+
+ input_report_switch(input, SW_TABLET_MODE,
+ !cbas_ec.base_present || cbas_ec.base_folded);
cbas_ec_set_input(input);
@@ -322,10 +330,9 @@ static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev,
static int hammer_register_leds(struct hid_device *hdev)
{
struct hammer_kbd_leds *kbd_backlight;
+ int error;
- kbd_backlight = devm_kzalloc(&hdev->dev,
- sizeof(*kbd_backlight),
- GFP_KERNEL);
+ kbd_backlight = kzalloc(sizeof(*kbd_backlight), GFP_KERNEL);
if (!kbd_backlight)
return -ENOMEM;
@@ -339,12 +346,31 @@ static int hammer_register_leds(struct hid_device *hdev)
/* Set backlight to 0% initially. */
hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0);
- return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
+ error = led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
+ if (error)
+ goto err_free_mem;
+
+ hid_set_drvdata(hdev, kbd_backlight);
+ return 0;
+
+err_free_mem:
+ kfree(kbd_backlight);
+ return error;
+}
+
+static void hammer_unregister_leds(struct hid_device *hdev)
+{
+ struct hammer_kbd_leds *kbd_backlight = hid_get_drvdata(hdev);
+
+ if (kbd_backlight) {
+ led_classdev_unregister(&kbd_backlight->cdev);
+ kfree(kbd_backlight);
+ }
}
#define HID_UP_GOOGLEVENDOR 0xffd10000
#define HID_VD_KBD_FOLDED 0x00000019
-#define WHISKERS_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
+#define HID_USAGE_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
/* HID usage for keyboard backlight (Alphanumeric display brightness) */
#define HID_AD_BRIGHTNESS 0x00140046
@@ -354,8 +380,7 @@ static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_usage *usage,
unsigned long **bit, int *max)
{
- if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
- usage->hid == WHISKERS_KBD_FOLDED) {
+ if (usage->hid == HID_USAGE_KBD_FOLDED) {
/*
* We do not want to have this usage mapped as it will get
* mixed in with "base attached" signal and delivered over
@@ -372,19 +397,19 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field,
{
unsigned long flags;
- if (hid->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
- usage->hid == WHISKERS_KBD_FOLDED) {
+ if (usage->hid == HID_USAGE_KBD_FOLDED) {
spin_lock_irqsave(&cbas_ec_lock, flags);
- hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
- cbas_ec.base_present, value);
-
/*
- * We should not get event if base is detached, but in case
- * we happen to service HID and EC notifications out of order
- * let's still check the "base present" flag.
+ * If we are getting events from Whiskers that means that it
+ * is attached to the lid.
*/
- if (cbas_ec.input && cbas_ec.base_present) {
+ cbas_ec.base_present = true;
+ cbas_ec.base_folded = value;
+ hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
+ cbas_ec.base_present, cbas_ec.base_folded);
+
+ if (cbas_ec.input) {
input_report_switch(cbas_ec.input,
SW_TABLET_MODE, value);
input_sync(cbas_ec.input);
@@ -397,33 +422,22 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field,
return 0;
}
-static bool hammer_is_keyboard_interface(struct hid_device *hdev)
+static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type,
+ unsigned application, unsigned usage)
{
- struct hid_report_enum *re = &hdev->report_enum[HID_INPUT_REPORT];
- struct hid_report *report;
-
- list_for_each_entry(report, &re->report_list, list)
- if (report->application == HID_GD_KEYBOARD)
- return true;
-
- return false;
-}
-
-static bool hammer_has_backlight_control(struct hid_device *hdev)
-{
- struct hid_report_enum *re = &hdev->report_enum[HID_OUTPUT_REPORT];
+ struct hid_report_enum *re = &hdev->report_enum[report_type];
struct hid_report *report;
int i, j;
list_for_each_entry(report, &re->report_list, list) {
- if (report->application != HID_GD_KEYBOARD)
+ if (report->application != application)
continue;
for (i = 0; i < report->maxfield; i++) {
struct hid_field *field = report->field[i];
for (j = 0; j < field->maxusage; j++)
- if (field->usage[j].hid == HID_AD_BRIGHTNESS)
+ if (field->usage[j].hid == usage)
return true;
}
}
@@ -431,21 +445,23 @@ static bool hammer_has_backlight_control(struct hid_device *hdev)
return false;
}
+static bool hammer_has_folded_event(struct hid_device *hdev)
+{
+ return hammer_has_usage(hdev, HID_INPUT_REPORT,
+ HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED);
+}
+
+static bool hammer_has_backlight_control(struct hid_device *hdev)
+{
+ return hammer_has_usage(hdev, HID_OUTPUT_REPORT,
+ HID_GD_KEYBOARD, HID_AD_BRIGHTNESS);
+}
+
static int hammer_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int error;
- /*
- * We always want to poll for, and handle tablet mode events from
- * Whiskers, even when nobody has opened the input device. This also
- * prevents the hid core from dropping early tablet mode events from
- * the device.
- */
- if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
- hammer_is_keyboard_interface(hdev))
- hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
-
error = hid_parse(hdev);
if (error)
return error;
@@ -454,6 +470,19 @@ static int hammer_probe(struct hid_device *hdev,
if (error)
return error;
+ /*
+ * We always want to poll for, and handle tablet mode events from
+ * devices that have folded usage, even when nobody has opened the input
+ * device. This also prevents the hid core from dropping early tablet
+ * mode events from the device.
+ */
+ if (hammer_has_folded_event(hdev)) {
+ hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
+ error = hid_hw_open(hdev);
+ if (error)
+ return error;
+ }
+
if (hammer_has_backlight_control(hdev)) {
error = hammer_register_leds(hdev);
if (error)
@@ -465,11 +494,45 @@ static int hammer_probe(struct hid_device *hdev,
return 0;
}
+static void hammer_remove(struct hid_device *hdev)
+{
+ unsigned long flags;
+
+ if (hammer_has_folded_event(hdev)) {
+ hid_hw_close(hdev);
+
+ /*
+ * If we are disconnecting then most likely Whiskers is
+ * being removed. Even if it is not removed, without proper
+ * keyboard we should not stay in clamshell mode.
+ *
+ * The reason for doing it here and not waiting for signal
+ * from EC, is that on some devices there are high leakage
+ * on Whiskers pins and we do not detect disconnect reliably,
+ * resulting in devices being stuck in clamshell mode.
+ */
+ spin_lock_irqsave(&cbas_ec_lock, flags);
+ if (cbas_ec.input && cbas_ec.base_present) {
+ input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1);
+ input_sync(cbas_ec.input);
+ }
+ cbas_ec.base_present = false;
+ spin_unlock_irqrestore(&cbas_ec_lock, flags);
+ }
+
+ hammer_unregister_leds(hdev);
+
+ hid_hw_stop(hdev);
+}
static const struct hid_device_id hammer_devices[] = {
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MAGNEMITE) },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MASTERBALL) },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
@@ -483,6 +546,7 @@ static struct hid_driver hammer_driver = {
.name = "hammer",
.id_table = hammer_devices,
.probe = hammer_probe,
+ .remove = hammer_remove,
.input_mapping = hammer_input_mapping,
.event = hammer_event,
};
diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c
index 10a720558830..8619b80c834c 100644
--- a/drivers/hid/hid-holtekff.c
+++ b/drivers/hid/hid-holtekff.c
@@ -124,13 +124,19 @@ static int holtekff_init(struct hid_device *hid)
{
struct holtekff_device *holtekff;
struct hid_report *report;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
+ struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev = hidinput->input;
+ struct input_dev *dev;
int error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
if (list_empty(report_list)) {
hid_err(hid, "no output report found\n");
return -ENODEV;
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c
index cc5b09b87ab0..dddfca555df9 100644
--- a/drivers/hid/hid-hyperv.c
+++ b/drivers/hid/hid-hyperv.c
@@ -192,6 +192,9 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
if (desc->bLength == 0)
goto cleanup;
+ /* The pointer is not NULL when we resume from hibernation */
+ if (input_device->hid_desc != NULL)
+ kfree(input_device->hid_desc);
input_device->hid_desc = kmemdup(desc, desc->bLength, GFP_ATOMIC);
if (!input_device->hid_desc)
@@ -203,6 +206,9 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
goto cleanup;
}
+ /* The pointer is not NULL when we resume from hibernation */
+ if (input_device->report_desc != NULL)
+ kfree(input_device->report_desc);
input_device->report_desc = kzalloc(input_device->report_desc_size,
GFP_ATOMIC);
@@ -314,60 +320,24 @@ static void mousevsc_on_receive(struct hv_device *device,
static void mousevsc_on_channel_callback(void *context)
{
- const int packet_size = 0x100;
- int ret;
struct hv_device *device = context;
- u32 bytes_recvd;
- u64 req_id;
struct vmpacket_descriptor *desc;
- unsigned char *buffer;
- int bufferlen = packet_size;
-
- buffer = kmalloc(bufferlen, GFP_ATOMIC);
- if (!buffer)
- return;
-
- do {
- ret = vmbus_recvpacket_raw(device->channel, buffer,
- bufferlen, &bytes_recvd, &req_id);
-
- switch (ret) {
- case 0:
- if (bytes_recvd <= 0) {
- kfree(buffer);
- return;
- }
- desc = (struct vmpacket_descriptor *)buffer;
-
- switch (desc->type) {
- case VM_PKT_COMP:
- break;
-
- case VM_PKT_DATA_INBAND:
- mousevsc_on_receive(device, desc);
- break;
-
- default:
- pr_err("unhandled packet type %d, tid %llx len %d\n",
- desc->type, req_id, bytes_recvd);
- break;
- }
+ foreach_vmbus_pkt(desc, device->channel) {
+ switch (desc->type) {
+ case VM_PKT_COMP:
break;
- case -ENOBUFS:
- kfree(buffer);
- /* Handle large packet */
- bufferlen = bytes_recvd;
- buffer = kmalloc(bytes_recvd, GFP_ATOMIC);
-
- if (!buffer)
- return;
+ case VM_PKT_DATA_INBAND:
+ mousevsc_on_receive(device, desc);
+ break;
+ default:
+ pr_err("Unhandled packet type %d, tid %llx len %d\n",
+ desc->type, desc->trans_id, desc->len8 * 8);
break;
}
- } while (1);
-
+ }
}
static int mousevsc_connect_to_vsp(struct hv_device *device)
@@ -378,6 +348,8 @@ static int mousevsc_connect_to_vsp(struct hv_device *device)
struct mousevsc_prt_msg *request;
struct mousevsc_prt_msg *response;
+ reinit_completion(&input_dev->wait_event);
+
request = &input_dev->protocol_req;
memset(request, 0, sizeof(struct mousevsc_prt_msg));
@@ -577,6 +549,30 @@ static int mousevsc_remove(struct hv_device *dev)
return 0;
}
+static int mousevsc_suspend(struct hv_device *dev)
+{
+ vmbus_close(dev->channel);
+
+ return 0;
+}
+
+static int mousevsc_resume(struct hv_device *dev)
+{
+ int ret;
+
+ ret = vmbus_open(dev->channel,
+ INPUTVSC_SEND_RING_BUFFER_SIZE,
+ INPUTVSC_RECV_RING_BUFFER_SIZE,
+ NULL, 0,
+ mousevsc_on_channel_callback,
+ dev);
+ if (ret)
+ return ret;
+
+ ret = mousevsc_connect_to_vsp(dev);
+ return ret;
+}
+
static const struct hv_vmbus_device_id id_table[] = {
/* Mouse guid */
{ HV_MOUSE_GUID, },
@@ -590,6 +586,8 @@ static struct hv_driver mousevsc_drv = {
.id_table = id_table,
.probe = mousevsc_probe,
.remove = mousevsc_remove,
+ .suspend = mousevsc_suspend,
+ .resume = mousevsc_resume,
.driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 76969a22b0f2..7e1689ef35f5 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -476,6 +476,8 @@
#define USB_DEVICE_ID_GOOGLE_STAFF 0x502b
#define USB_DEVICE_ID_GOOGLE_WAND 0x502d
#define USB_DEVICE_ID_GOOGLE_WHISKERS 0x5030
+#define USB_DEVICE_ID_GOOGLE_MASTERBALL 0x503c
+#define USB_DEVICE_ID_GOOGLE_MAGNEMITE 0x503d
#define USB_VENDOR_ID_GOTOP 0x08f2
#define USB_DEVICE_ID_SUPER_Q2 0x007f
@@ -571,6 +573,7 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A 0x094a
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941 0x0941
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
@@ -747,6 +750,10 @@
#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
@@ -957,6 +964,7 @@
#define I2C_VENDOR_ID_RAYDIUM 0x2386
#define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33
+#define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118
#define USB_VENDOR_ID_RAZER 0x1532
#define USB_DEVICE_ID_RAZER_BLADE_14 0x011D
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
new file mode 100644
index 000000000000..8a9268a5c66a
--- /dev/null
+++ b/drivers/hid/hid-lg-g15.c
@@ -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");
diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c
index dd1a6c3a7de6..73d07e35f12a 100644
--- a/drivers/hid/hid-lg2ff.c
+++ b/drivers/hid/hid-lg2ff.c
@@ -50,11 +50,17 @@ int lg2ff_init(struct hid_device *hid)
{
struct lg2ff_device *lg2ff;
struct hid_report *report;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
- struct input_dev *dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
int error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
/* Check that the report looks ok */
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7);
if (!report)
diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c
index 9ecb6fd06203..b7e1949f3cf7 100644
--- a/drivers/hid/hid-lg3ff.c
+++ b/drivers/hid/hid-lg3ff.c
@@ -117,12 +117,19 @@ static const signed short ff3_joystick_ac[] = {
int lg3ff_init(struct hid_device *hid)
{
- struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- struct input_dev *dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
const signed short *ff_bits = ff3_joystick_ac;
int error;
int i;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35))
return -ENODEV;
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 03f0220062ca..5e6a0cef2a06 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -1253,8 +1253,8 @@ static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_produc
int lg4ff_init(struct hid_device *hid)
{
- struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- struct input_dev *dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
@@ -1266,6 +1266,13 @@ int lg4ff_init(struct hid_device *hid)
int mmode_ret, mmode_idx = -1;
u16 real_product_id;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
return -1;
diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c
index c79a6ec43745..aed4ddc397a9 100644
--- a/drivers/hid/hid-lgff.c
+++ b/drivers/hid/hid-lgff.c
@@ -115,12 +115,19 @@ static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude)
int lgff_init(struct hid_device* hid)
{
- struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- struct input_dev *dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
const signed short *ff_bits = ff_joystick;
int error;
int i;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
/* Check that the report looks ok */
if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
return -ENODEV;
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 0179f7ed77e5..cd9193078525 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -1102,6 +1102,9 @@ static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp,
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
NULL, 0, &response);
+ /* Ignore these intermittent errors */
+ if (ret == HIDPP_ERROR_RESOURCE_ERROR)
+ return -EIO;
if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret);
@@ -1669,6 +1672,7 @@ static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
#define HIDPP_FF_EFFECTID_NONE -1
#define HIDPP_FF_EFFECTID_AUTOCENTER -2
+#define HIDPP_AUTOCENTER_PARAMS_LENGTH 18
#define HIDPP_FF_MAX_PARAMS 20
#define HIDPP_FF_RESERVED_SLOTS 1
@@ -2009,7 +2013,7 @@ static int hidpp_ff_erase_effect(struct input_dev *dev, int effect_id)
static void hidpp_ff_set_autocenter(struct input_dev *dev, u16 magnitude)
{
struct hidpp_ff_private_data *data = dev->ff->private;
- u8 params[18];
+ u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH];
dbg_hid("Setting autocenter to %d.\n", magnitude);
@@ -2077,23 +2081,34 @@ static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp
static void hidpp_ff_destroy(struct ff_device *ff)
{
struct hidpp_ff_private_data *data = ff->private;
+ struct hid_device *hid = data->hidpp->hid_dev;
+
+ hid_info(hid, "Unloading HID++ force feedback.\n");
+ device_remove_file(&hid->dev, &dev_attr_range);
+ destroy_workqueue(data->wq);
kfree(data->effect_ids);
}
-static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
+static int hidpp_ff_init(struct hidpp_device *hidpp,
+ struct hidpp_ff_private_data *data)
{
struct hid_device *hid = hidpp->hid_dev;
- struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- struct input_dev *dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
struct ff_device *ff;
- struct hidpp_report response;
- struct hidpp_ff_private_data *data;
- int error, j, num_slots;
+ int error, j, num_slots = data->num_effects;
u8 version;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
if (!dev) {
hid_err(hid, "Struct input_dev not set!\n");
return -EINVAL;
@@ -2109,27 +2124,17 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
for (j = 0; hidpp_ff_effects_v2[j] >= 0; j++)
set_bit(hidpp_ff_effects_v2[j], dev->ffbit);
- /* Read number of slots available in device */
- error = hidpp_send_fap_command_sync(hidpp, feature_index,
- HIDPP_FF_GET_INFO, NULL, 0, &response);
- if (error) {
- if (error < 0)
- return error;
- hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
- __func__, error);
- return -EPROTO;
- }
-
- num_slots = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS;
-
error = input_ff_create(dev, num_slots);
if (error) {
hid_err(dev, "Failed to create FF device!\n");
return error;
}
-
- data = kzalloc(sizeof(*data), GFP_KERNEL);
+ /*
+ * Create a copy of passed data, so we can transfer memory
+ * ownership to FF core
+ */
+ data = kmemdup(data, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL);
@@ -2145,10 +2150,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
}
data->hidpp = hidpp;
- data->feature_index = feature_index;
data->version = version;
- data->slot_autocenter = 0;
- data->num_effects = num_slots;
for (j = 0; j < num_slots; j++)
data->effect_ids[j] = -1;
@@ -2162,68 +2164,20 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
ff->set_autocenter = hidpp_ff_set_autocenter;
ff->destroy = hidpp_ff_destroy;
-
- /* reset all forces */
- error = hidpp_send_fap_command_sync(hidpp, feature_index,
- HIDPP_FF_RESET_ALL, NULL, 0, &response);
-
- /* Read current Range */
- error = hidpp_send_fap_command_sync(hidpp, feature_index,
- HIDPP_FF_GET_APERTURE, NULL, 0, &response);
- if (error)
- hid_warn(hidpp->hid_dev, "Failed to read range from device!\n");
- data->range = error ? 900 : get_unaligned_be16(&response.fap.params[0]);
-
/* Create sysfs interface */
error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
if (error)
hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error);
- /* Read the current gain values */
- error = hidpp_send_fap_command_sync(hidpp, feature_index,
- HIDPP_FF_GET_GLOBAL_GAINS, NULL, 0, &response);
- if (error)
- hid_warn(hidpp->hid_dev, "Failed to read gain values from device!\n");
- data->gain = error ? 0xffff : get_unaligned_be16(&response.fap.params[0]);
- /* ignore boost value at response.fap.params[2] */
-
/* init the hardware command queue */
atomic_set(&data->workqueue_size, 0);
- /* initialize with zero autocenter to get wheel in usable state */
- hidpp_ff_set_autocenter(dev, 0);
-
hid_info(hid, "Force feedback support loaded (firmware release %d).\n",
version);
return 0;
}
-static int hidpp_ff_deinit(struct hid_device *hid)
-{
- struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- struct input_dev *dev = hidinput->input;
- struct hidpp_ff_private_data *data;
-
- if (!dev) {
- hid_err(hid, "Struct input_dev not found!\n");
- return -EINVAL;
- }
-
- hid_info(hid, "Unloading HID++ force feedback.\n");
- data = dev->ff->private;
- if (!data) {
- hid_err(hid, "Private data not found!\n");
- return -EINVAL;
- }
-
- destroy_workqueue(data->wq);
- device_remove_file(&hid->dev, &dev_attr_range);
-
- return 0;
-}
-
-
/* ************************************************************************** */
/* */
/* Device Support */
@@ -2725,24 +2679,93 @@ static int k400_connect(struct hid_device *hdev, bool connected)
#define HIDPP_PAGE_G920_FORCE_FEEDBACK 0x8123
-static int g920_get_config(struct hidpp_device *hidpp)
+static int g920_ff_set_autocenter(struct hidpp_device *hidpp,
+ struct hidpp_ff_private_data *data)
{
+ struct hidpp_report response;
+ u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH] = {
+ [1] = HIDPP_FF_EFFECT_SPRING | HIDPP_FF_EFFECT_AUTOSTART,
+ };
+ int ret;
+
+ /* initialize with zero autocenter to get wheel in usable state */
+
+ dbg_hid("Setting autocenter to 0.\n");
+ ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+ HIDPP_FF_DOWNLOAD_EFFECT,
+ params, ARRAY_SIZE(params),
+ &response);
+ if (ret)
+ hid_warn(hidpp->hid_dev, "Failed to autocenter device!\n");
+ else
+ data->slot_autocenter = response.fap.params[0];
+
+ return ret;
+}
+
+static int g920_get_config(struct hidpp_device *hidpp,
+ struct hidpp_ff_private_data *data)
+{
+ struct hidpp_report response;
u8 feature_type;
- u8 feature_index;
int ret;
+ memset(data, 0, sizeof(*data));
+
/* Find feature and store for later use */
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
- &feature_index, &feature_type);
+ &data->feature_index, &feature_type);
if (ret)
return ret;
- ret = hidpp_ff_init(hidpp, feature_index);
+ /* Read number of slots available in device */
+ ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+ HIDPP_FF_GET_INFO,
+ NULL, 0,
+ &response);
+ if (ret) {
+ if (ret < 0)
+ return ret;
+ hid_err(hidpp->hid_dev,
+ "%s: received protocol error 0x%02x\n", __func__, ret);
+ return -EPROTO;
+ }
+
+ data->num_effects = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS;
+
+ /* reset all forces */
+ ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+ HIDPP_FF_RESET_ALL,
+ NULL, 0,
+ &response);
if (ret)
- hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n",
- ret);
+ hid_warn(hidpp->hid_dev, "Failed to reset all forces!\n");
- return 0;
+ ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+ HIDPP_FF_GET_APERTURE,
+ NULL, 0,
+ &response);
+ if (ret) {
+ hid_warn(hidpp->hid_dev,
+ "Failed to read range from device!\n");
+ }
+ data->range = ret ?
+ 900 : get_unaligned_be16(&response.fap.params[0]);
+
+ /* Read the current gain values */
+ ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+ HIDPP_FF_GET_GLOBAL_GAINS,
+ NULL, 0,
+ &response);
+ if (ret)
+ hid_warn(hidpp->hid_dev,
+ "Failed to read gain values from device!\n");
+ data->gain = ret ?
+ 0xffff : get_unaligned_be16(&response.fap.params[0]);
+
+ /* ignore boost value at response.fap.params[2] */
+
+ return g920_ff_set_autocenter(hidpp, data);
}
/* -------------------------------------------------------------------------- */
@@ -3458,34 +3481,45 @@ static int hidpp_get_report_length(struct hid_device *hdev, int id)
return report->field[0]->report_count + 1;
}
-static bool hidpp_validate_report(struct hid_device *hdev, int id,
- int expected_length, bool optional)
+static bool hidpp_validate_device(struct hid_device *hdev)
{
- int report_length;
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ int id, report_length, supported_reports = 0;
- if (id >= HID_MAX_IDS || id < 0) {
- hid_err(hdev, "invalid HID report id %u\n", id);
- return false;
+ id = REPORT_ID_HIDPP_SHORT;
+ report_length = hidpp_get_report_length(hdev, id);
+ if (report_length) {
+ if (report_length < HIDPP_REPORT_SHORT_LENGTH)
+ goto bad_device;
+
+ supported_reports++;
}
+ id = REPORT_ID_HIDPP_LONG;
report_length = hidpp_get_report_length(hdev, id);
- if (!report_length)
- return optional;
+ if (report_length) {
+ if (report_length < HIDPP_REPORT_LONG_LENGTH)
+ goto bad_device;
- if (report_length < expected_length) {
- hid_warn(hdev, "not enough values in hidpp report %d\n", id);
- return false;
+ supported_reports++;
}
- return true;
-}
+ id = REPORT_ID_HIDPP_VERY_LONG;
+ report_length = hidpp_get_report_length(hdev, id);
+ if (report_length) {
+ if (report_length < HIDPP_REPORT_LONG_LENGTH ||
+ report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH)
+ goto bad_device;
-static bool hidpp_validate_device(struct hid_device *hdev)
-{
- return hidpp_validate_report(hdev, REPORT_ID_HIDPP_SHORT,
- HIDPP_REPORT_SHORT_LENGTH, false) &&
- hidpp_validate_report(hdev, REPORT_ID_HIDPP_LONG,
- HIDPP_REPORT_LONG_LENGTH, true);
+ supported_reports++;
+ hidpp->very_long_report_length = report_length;
+ }
+
+ return supported_reports;
+
+bad_device:
+ hid_warn(hdev, "not enough values in hidpp report %d\n", id);
+ return false;
}
static bool hidpp_application_equals(struct hid_device *hdev,
@@ -3505,6 +3539,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
int ret;
bool connected;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
+ struct hidpp_ff_private_data data;
/* report_fixup needs drvdata to be set before we call hid_parse */
hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
@@ -3531,11 +3566,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
}
- hidpp->very_long_report_length =
- hidpp_get_report_length(hdev, REPORT_ID_HIDPP_VERY_LONG);
- if (hidpp->very_long_report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH)
- hidpp->very_long_report_length = HIDPP_REPORT_VERY_LONG_MAX_LENGTH;
-
if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
hidpp->quirks |= HIDPP_QUIRK_UNIFYING;
@@ -3614,7 +3644,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret)
goto hid_hw_init_fail;
} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
- ret = g920_get_config(hidpp);
+ ret = g920_get_config(hidpp, &data);
if (ret)
goto hid_hw_init_fail;
}
@@ -3636,6 +3666,14 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto hid_hw_start_fail;
}
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+ ret = hidpp_ff_init(hidpp, &data);
+ if (ret)
+ hid_warn(hidpp->hid_dev,
+ "Unable to initialize force feedback support, errno %d\n",
+ ret);
+ }
+
return ret;
hid_hw_init_fail:
@@ -3658,9 +3696,6 @@ static void hidpp_remove(struct hid_device *hdev)
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
- if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)
- hidpp_ff_deinit(hdev);
-
hid_hw_stop(hdev);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c
index 2cf83856f2e4..2d8b589201a4 100644
--- a/drivers/hid/hid-microsoft.c
+++ b/drivers/hid/hid-microsoft.c
@@ -328,11 +328,17 @@ static int ms_play_effect(struct input_dev *dev, void *data,
static int ms_init_ff(struct hid_device *hdev)
{
- struct hid_input *hidinput = list_entry(hdev->inputs.next,
- struct hid_input, list);
- struct input_dev *input_dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *input_dev;
struct ms_data *ms = hid_get_drvdata(hdev);
+ if (list_empty(&hdev->inputs)) {
+ hid_err(hdev, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hdev->inputs.next, struct hid_input, list);
+ input_dev = hidinput->input;
+
if (!(ms->quirks & MS_QUIRK_FF))
return 0;
diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c
index 5a3b3d974d84..2666af02d5c1 100644
--- a/drivers/hid/hid-prodikeys.c
+++ b/drivers/hid/hid-prodikeys.c
@@ -516,7 +516,7 @@ static void pcmidi_setup_extra_keys(
MY PICTURES => KEY_WORDPROCESSOR
MY MUSIC=> KEY_SPREADSHEET
*/
- unsigned int keys[] = {
+ static const unsigned int keys[] = {
KEY_FN,
KEY_MESSENGER, KEY_CALENDAR,
KEY_ADDRESSBOOK, KEY_DOCUMENTS,
@@ -532,7 +532,7 @@ static void pcmidi_setup_extra_keys(
0
};
- unsigned int *pkeys = &keys[0];
+ const unsigned int *pkeys = &keys[0];
unsigned short i;
if (pm->ifnum != 1) /* only set up ONCE for interace 1 */
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index c50bcd967d99..d1b39c29e353 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -94,6 +94,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641), HID_QUIRK_ALWAYS_POLL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X), HID_QUIRK_MULTI_INPUT },
@@ -419,13 +420,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
#if IS_ENABLED(CONFIG_HID_LCPOWER)
{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) },
#endif
-#if IS_ENABLED(CONFIG_HID_LED)
- { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_WN) },
- { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_FA) },
- { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_LUXAFOR) },
- { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) },
- { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
-#endif
#if IS_ENABLED(CONFIG_HID_LENOVO)
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 7c6abd7e0979..9ce22acdfaca 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -744,7 +744,8 @@ static void rmi_remove(struct hid_device *hdev)
{
struct rmi_data *hdata = hid_get_drvdata(hdev);
- if (hdata->device_flags & RMI_DEVICE) {
+ if ((hdata->device_flags & RMI_DEVICE)
+ && test_bit(RMI_STARTED, &hdata->flags)) {
clear_bit(RMI_STARTED, &hdata->flags);
cancel_work_sync(&hdata->reset_work);
rmi_unregister_transport_device(&hdata->xport);
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 73c0f7a95e2d..4c6ed6ef31f1 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -2254,9 +2254,15 @@ static int sony_play_effect(struct input_dev *dev, void *data,
static int sony_init_ff(struct sony_sc *sc)
{
- struct hid_input *hidinput = list_entry(sc->hdev->inputs.next,
- struct hid_input, list);
- struct input_dev *input_dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *input_dev;
+
+ if (list_empty(&sc->hdev->inputs)) {
+ hid_err(sc->hdev, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(sc->hdev->inputs.next, struct hid_input, list);
+ input_dev = hidinput->input;
input_set_capability(input_dev, EV_FF, FF_RUMBLE);
return input_ff_create_memless(input_dev, NULL, sony_play_effect);
diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c
index bdfc5ff3b2c5..90acef304536 100644
--- a/drivers/hid/hid-tmff.c
+++ b/drivers/hid/hid-tmff.c
@@ -124,12 +124,18 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
struct tmff_device *tmff;
struct hid_report *report;
struct list_head *report_list;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
- struct input_dev *input_dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *input_dev;
int error;
int i;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ input_dev = hidinput->input;
+
tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL);
if (!tmff)
return -ENOMEM;
diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c
index f90959e94028..3abaca045869 100644
--- a/drivers/hid/hid-zpff.c
+++ b/drivers/hid/hid-zpff.c
@@ -54,11 +54,17 @@ static int zpff_init(struct hid_device *hid)
{
struct zpff_device *zpff;
struct hid_report *report;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
- struct input_dev *dev = hidinput->input;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
int i, error;
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
for (i = 0; i < 4; i++) {
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
if (!report)
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index bbc6ec1aa5cb..c3fc0ceb8096 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -197,15 +197,15 @@ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t
}
if (count > HID_MAX_BUFFER_SIZE) {
- printk(KERN_WARNING "hidraw: pid %d passed too large report\n",
- task_pid_nr(current));
+ hid_warn(dev, "pid %d passed too large report\n",
+ task_pid_nr(current));
ret = -EINVAL;
goto out;
}
if (count < 2) {
- printk(KERN_WARNING "hidraw: pid %d passed too short report\n",
- task_pid_nr(current));
+ hid_warn(dev, "pid %d passed too short report\n",
+ task_pid_nr(current));
ret = -EINVAL;
goto out;
}
@@ -468,9 +468,7 @@ static const struct file_operations hidraw_ops = {
.release = hidraw_release,
.unlocked_ioctl = hidraw_ioctl,
.fasync = hidraw_fasync,
-#ifdef CONFIG_COMPAT
- .compat_ioctl = hidraw_ioctl,
-#endif
+ .compat_ioctl = compat_ptr_ioctl,
.llseek = noop_llseek,
};
@@ -597,7 +595,7 @@ int __init hidraw_init(void)
if (result < 0)
goto error_class;
- printk(KERN_INFO "hidraw: raw HID events driver (C) Jiri Kosina\n");
+ pr_info("raw HID events driver (C) Jiri Kosina\n");
out:
return result;
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 2a7c6e33bb1c..a358e61fbc82 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -26,7 +26,6 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pm.h>
-#include <linux/pm_runtime.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/err.h>
@@ -48,9 +47,8 @@
/* quirks to control the device */
#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0)
#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1)
-#define I2C_HID_QUIRK_NO_RUNTIME_PM BIT(2)
-#define I2C_HID_QUIRK_DELAY_AFTER_SLEEP BIT(3)
#define I2C_HID_QUIRK_BOGUS_IRQ BIT(4)
+#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(5)
/* flags */
#define I2C_HID_STARTED 0
@@ -160,8 +158,6 @@ struct i2c_hid {
bool irq_wake_enabled;
struct mutex reset_lock;
-
- unsigned long sleep_delay;
};
static const struct i2c_hid_quirks {
@@ -172,16 +168,13 @@ static const struct i2c_hid_quirks {
{ USB_VENDOR_ID_WEIDA, HID_ANY_ID,
I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
{ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
- I2C_HID_QUIRK_NO_IRQ_AFTER_RESET |
- I2C_HID_QUIRK_NO_RUNTIME_PM },
- { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_4B33,
- I2C_HID_QUIRK_DELAY_AFTER_SLEEP },
- { USB_VENDOR_ID_LG, I2C_DEVICE_ID_LG_8001,
- I2C_HID_QUIRK_NO_RUNTIME_PM },
- { I2C_VENDOR_ID_GOODIX, I2C_DEVICE_ID_GOODIX_01F0,
- I2C_HID_QUIRK_NO_RUNTIME_PM },
+ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
+ { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118,
+ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
{ USB_VENDOR_ID_ELAN, HID_ANY_ID,
I2C_HID_QUIRK_BOGUS_IRQ },
+ { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID,
+ I2C_HID_QUIRK_RESET_ON_RESUME },
{ 0, 0 }
};
@@ -397,7 +390,6 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
int ret;
- unsigned long now, delay;
i2c_hid_dbg(ihid, "%s\n", __func__);
@@ -415,22 +407,9 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
goto set_pwr_exit;
}
- if (ihid->quirks & I2C_HID_QUIRK_DELAY_AFTER_SLEEP &&
- power_state == I2C_HID_PWR_ON) {
- now = jiffies;
- if (time_after(ihid->sleep_delay, now)) {
- delay = jiffies_to_usecs(ihid->sleep_delay - now);
- usleep_range(delay, delay + 1);
- }
- }
-
ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
0, NULL, 0, NULL, 0);
- if (ihid->quirks & I2C_HID_QUIRK_DELAY_AFTER_SLEEP &&
- power_state == I2C_HID_PWR_SLEEP)
- ihid->sleep_delay = jiffies + msecs_to_jiffies(20);
-
if (ret)
dev_err(&client->dev, "failed to change power setting.\n");
@@ -471,8 +450,12 @@ static int i2c_hid_hwreset(struct i2c_client *client)
if (ret) {
dev_err(&client->dev, "failed to reset device.\n");
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+ goto out_unlock;
}
+ /* At least some SIS devices need this after reset */
+ ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
+
out_unlock:
mutex_unlock(&ihid->reset_lock);
return ret;
@@ -791,11 +774,6 @@ static int i2c_hid_open(struct hid_device *hid)
{
struct i2c_client *client = hid->driver_data;
struct i2c_hid *ihid = i2c_get_clientdata(client);
- int ret = 0;
-
- ret = pm_runtime_get_sync(&client->dev);
- if (ret < 0)
- return ret;
set_bit(I2C_HID_STARTED, &ihid->flags);
return 0;
@@ -807,27 +785,6 @@ static void i2c_hid_close(struct hid_device *hid)
struct i2c_hid *ihid = i2c_get_clientdata(client);
clear_bit(I2C_HID_STARTED, &ihid->flags);
-
- /* Save some power */
- pm_runtime_put(&client->dev);
-}
-
-static int i2c_hid_power(struct hid_device *hid, int lvl)
-{
- struct i2c_client *client = hid->driver_data;
- struct i2c_hid *ihid = i2c_get_clientdata(client);
-
- i2c_hid_dbg(ihid, "%s lvl:%d\n", __func__, lvl);
-
- switch (lvl) {
- case PM_HINT_FULLON:
- pm_runtime_get_sync(&client->dev);
- break;
- case PM_HINT_NORMAL:
- pm_runtime_put(&client->dev);
- break;
- }
- return 0;
}
struct hid_ll_driver i2c_hid_ll_driver = {
@@ -836,7 +793,6 @@ struct hid_ll_driver i2c_hid_ll_driver = {
.stop = i2c_hid_stop,
.open = i2c_hid_open,
.close = i2c_hid_close,
- .power = i2c_hid_power,
.output_report = i2c_hid_output_report,
.raw_request = i2c_hid_raw_request,
};
@@ -1104,9 +1060,6 @@ static int i2c_hid_probe(struct i2c_client *client,
i2c_hid_acpi_fix_up_power(&client->dev);
- pm_runtime_get_noresume(&client->dev);
- pm_runtime_set_active(&client->dev);
- pm_runtime_enable(&client->dev);
device_enable_async_suspend(&client->dev);
/* Make sure there is something at this address */
@@ -1114,16 +1067,16 @@ static int i2c_hid_probe(struct i2c_client *client,
if (ret < 0) {
dev_dbg(&client->dev, "nothing at this address: %d\n", ret);
ret = -ENXIO;
- goto err_pm;
+ goto err_regulator;
}
ret = i2c_hid_fetch_hid_descriptor(ihid);
if (ret < 0)
- goto err_pm;
+ goto err_regulator;
ret = i2c_hid_init_irq(client);
if (ret < 0)
- goto err_pm;
+ goto err_regulator;
hid = hid_allocate_device();
if (IS_ERR(hid)) {
@@ -1154,9 +1107,6 @@ static int i2c_hid_probe(struct i2c_client *client,
goto err_mem_free;
}
- if (!(ihid->quirks & I2C_HID_QUIRK_NO_RUNTIME_PM))
- pm_runtime_put(&client->dev);
-
return 0;
err_mem_free:
@@ -1165,10 +1115,6 @@ err_mem_free:
err_irq:
free_irq(client->irq, ihid);
-err_pm:
- pm_runtime_put_noidle(&client->dev);
- pm_runtime_disable(&client->dev);
-
err_regulator:
regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
ihid->pdata.supplies);
@@ -1181,12 +1127,6 @@ static int i2c_hid_remove(struct i2c_client *client)
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid;
- if (!(ihid->quirks & I2C_HID_QUIRK_NO_RUNTIME_PM))
- pm_runtime_get_sync(&client->dev);
- pm_runtime_disable(&client->dev);
- pm_runtime_set_suspended(&client->dev);
- pm_runtime_put_noidle(&client->dev);
-
hid = ihid->hid;
hid_destroy_device(hid);
@@ -1219,25 +1159,15 @@ static int i2c_hid_suspend(struct device *dev)
int wake_status;
if (hid->driver && hid->driver->suspend) {
- /*
- * Wake up the device so that IO issues in
- * HID driver's suspend code can succeed.
- */
- ret = pm_runtime_resume(dev);
- if (ret < 0)
- return ret;
-
ret = hid->driver->suspend(hid, PMSG_SUSPEND);
if (ret < 0)
return ret;
}
- if (!pm_runtime_suspended(dev)) {
- /* Save some power */
- i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+ /* Save some power */
+ i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- disable_irq(client->irq);
- }
+ disable_irq(client->irq);
if (device_may_wakeup(&client->dev)) {
wake_status = enable_irq_wake(client->irq);
@@ -1279,19 +1209,21 @@ static int i2c_hid_resume(struct device *dev)
wake_status);
}
- /* We'll resume to full power */
- pm_runtime_disable(dev);
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev);
-
enable_irq(client->irq);
/* Instead of resetting device, simply powers the device on. This
* solves "incomplete reports" on Raydium devices 2386:3118 and
* 2386:4B33 and fixes various SIS touchscreens no longer sending
* data after a suspend/resume.
+ *
+ * However some ALPS touchpads generate IRQ storm without reset, so
+ * let's still reset them here.
*/
- ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
+ if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME)
+ ret = i2c_hid_hwreset(client);
+ else
+ ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
+
if (ret)
return ret;
@@ -1304,30 +1236,8 @@ static int i2c_hid_resume(struct device *dev)
}
#endif
-#ifdef CONFIG_PM
-static int i2c_hid_runtime_suspend(struct device *dev)
-{
- struct i2c_client *client = to_i2c_client(dev);
-
- i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- disable_irq(client->irq);
- return 0;
-}
-
-static int i2c_hid_runtime_resume(struct device *dev)
-{
- struct i2c_client *client = to_i2c_client(dev);
-
- enable_irq(client->irq);
- i2c_hid_set_power(client, I2C_HID_PWR_ON);
- return 0;
-}
-#endif
-
static const struct dev_pm_ops i2c_hid_pm = {
SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_suspend, i2c_hid_resume)
- SET_RUNTIME_PM_OPS(i2c_hid_runtime_suspend, i2c_hid_runtime_resume,
- NULL)
};
static const struct i2c_device_id i2c_hid_id_table[] = {
diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
index 75078c83be1a..d31ea82b84c1 100644
--- a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
+++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
@@ -323,6 +323,25 @@ static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = {
.driver_data = (void *)&sipodev_desc
},
{
+ /*
+ * There are at least 2 Primebook C11B versions, the older
+ * version has a product-name of "Primebook C11B", and a
+ * bios version / release / firmware revision of:
+ * V2.1.2 / 05/03/2018 / 18.2
+ * The new version has "PRIMEBOOK C11B" as product-name and a
+ * bios version / release / firmware revision of:
+ * CFALKSW05_BIOS_V1.1.2 / 11/19/2018 / 19.2
+ * Only the older version needs this quirk, note the newer
+ * version will not match as it has a different product-name.
+ */
+ .ident = "Trekstor Primebook C11B",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TREKSTOR"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Primebook C11B"),
+ },
+ .driver_data = (void *)&sipodev_desc
+ },
+ {
.ident = "Direkt-Tek DTLAPY116-2",
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Direkt-Tek"),
diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
index 1b0a0cc605e7..513d7a4a1b8a 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
+++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
@@ -84,7 +84,7 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl)
return 0;
out:
dev_err(&cl->device->dev, "error in allocating Tx pool\n");
- ishtp_cl_free_rx_ring(cl);
+ ishtp_cl_free_tx_ring(cl);
return -ENOMEM;
}
diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c
index c6c9ac09dac3..30a91d068306 100644
--- a/drivers/hid/intel-ish-hid/ishtp/hbm.c
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c
@@ -402,7 +402,7 @@ static void ishtp_hbm_cl_connect_res(struct ishtp_device *dev,
* @dev: ISHTP device instance
* @disconnect_req: disconnect request structure
*
- * Disconnect request bus message from the fw. Send diconnect response.
+ * Disconnect request bus message from the fw. Send disconnect response.
*/
static void ishtp_hbm_fw_disconnect_req(struct ishtp_device *dev,
struct hbm_client_connect_request *disconnect_req)
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
index 1f9bc4483465..e421cdf2d1a4 100644
--- a/drivers/hid/usbhid/hiddev.c
+++ b/drivers/hid/usbhid/hiddev.c
@@ -854,13 +854,6 @@ ret_unlock:
return r;
}
-#ifdef CONFIG_COMPAT
-static long hiddev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
-{
- return hiddev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
-}
-#endif
-
static const struct file_operations hiddev_fops = {
.owner = THIS_MODULE,
.read = hiddev_read,
@@ -870,9 +863,7 @@ static const struct file_operations hiddev_fops = {
.release = hiddev_release,
.unlocked_ioctl = hiddev_ioctl,
.fasync = hiddev_fasync,
-#ifdef CONFIG_COMPAT
- .compat_ioctl = hiddev_compat_ioctl,
-#endif
+ .compat_ioctl = compat_ptr_ioctl,
.llseek = noop_llseek,
};
diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h
index 4a7f8d363220..203d27d198b8 100644
--- a/drivers/hid/wacom.h
+++ b/drivers/hid/wacom.h
@@ -202,6 +202,21 @@ static inline void wacom_schedule_work(struct wacom_wac *wacom_wac,
}
}
+/*
+ * Convert a signed 32-bit integer to an unsigned n-bit integer. Undoes
+ * the normally-helpful work of 'hid_snto32' for fields that use signed
+ * ranges for questionable reasons.
+ */
+static inline __u32 wacom_s32tou(s32 value, __u8 n)
+{
+ switch (n) {
+ case 8: return ((__u8)value);
+ case 16: return ((__u16)value);
+ case 32: return ((__u32)value);
+ }
+ return value & (1 << (n - 1)) ? value & (~(~0U << n)) : value;
+}
+
extern const struct hid_device_id wacom_ids[];
void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len);
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index 2b0a5b8ca6e6..ccb74529bc78 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -2303,7 +2303,7 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
case HID_DG_TOOLSERIALNUMBER:
if (value) {
wacom_wac->serial[0] = (wacom_wac->serial[0] & ~0xFFFFFFFFULL);
- wacom_wac->serial[0] |= (__u32)value;
+ wacom_wac->serial[0] |= wacom_s32tou(value, field->report_size);
}
return;
case HID_DG_TWIST:
@@ -2319,15 +2319,17 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
return;
case WACOM_HID_WD_SERIALHI:
if (value) {
+ __u32 raw_value = wacom_s32tou(value, field->report_size);
+
wacom_wac->serial[0] = (wacom_wac->serial[0] & 0xFFFFFFFF);
- wacom_wac->serial[0] |= ((__u64)value) << 32;
+ wacom_wac->serial[0] |= ((__u64)raw_value) << 32;
/*
* Non-USI EMR devices may contain additional tool type
* information here. See WACOM_HID_WD_TOOLTYPE case for
* more details.
*/
if (value >> 20 == 1) {
- wacom_wac->id[0] |= value & 0xFFFFF;
+ wacom_wac->id[0] |= raw_value & 0xFFFFF;
}
}
return;
@@ -2339,7 +2341,7 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
* bitwise OR so the complete value can be built
* up over time :(
*/
- wacom_wac->id[0] |= value;
+ wacom_wac->id[0] |= wacom_s32tou(value, field->report_size);
return;
case WACOM_HID_WD_OFFSETLEFT:
if (features->offset_left && value != features->offset_left)