From 5a2b190cddb9aa69b9037f5b1fd1c2cc8a1d68b9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 29 Jun 2016 19:28:01 +1000 Subject: HID: logitech-hidpp: add battery support for HID++ 2.0 devices If the 0x1000 Unified Battery Level Status feature exists, expose the battery level. The main drawback is that while a device is plugged in its battery level is 0. To avoid exposing that as 0% charge we make up a number based on the charging status. Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 238 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 237 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 2e2515a4c070..1ead9f697d8c 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -62,6 +62,8 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) +#define HIDPP_QUIRK_HIDPP20_BATTERY BIT(25) +#define HIDPP_QUIRK_HIDPP10_BATTERY BIT(26) #define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ HIDPP_QUIRK_CONNECT_EVENTS) @@ -110,6 +112,15 @@ struct hidpp_report { }; } __packed; +struct hidpp_battery { + u8 feature_index; + struct power_supply_desc desc; + struct power_supply *ps; + char name[64]; + int status; + int level; +}; + struct hidpp_device { struct hid_device *hid_dev; struct mutex send_mutex; @@ -128,8 +139,9 @@ struct hidpp_device { struct input_dev *delayed_input; unsigned long quirks; -}; + struct hidpp_battery battery; +}; /* HID++ 1.0 error codes */ #define HIDPP_ERROR 0x8f @@ -606,6 +618,222 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp) return name; } +/* -------------------------------------------------------------------------- */ +/* 0x1000: Battery level status */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_BATTERY_LEVEL_STATUS 0x1000 + +#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS 0x00 +#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY 0x10 + +#define EVENT_BATTERY_LEVEL_STATUS_BROADCAST 0x00 + +static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, + int *next_level) +{ + int status; + int level_override; + + *level = data[0]; + *next_level = data[1]; + + /* When discharging, we can rely on the device reported level. + * For all other states the device reports level 0 (unknown). Make up + * a number instead + */ + switch (data[2]) { + case 0: /* discharging (in use) */ + status = POWER_SUPPLY_STATUS_DISCHARGING; + level_override = 0; + break; + case 1: /* recharging */ + status = POWER_SUPPLY_STATUS_CHARGING; + level_override = 80; + break; + case 2: /* charge in final stage */ + status = POWER_SUPPLY_STATUS_CHARGING; + level_override = 90; + break; + case 3: /* charge complete */ + status = POWER_SUPPLY_STATUS_FULL; + level_override = 100; + break; + case 4: /* recharging below optimal speed */ + status = POWER_SUPPLY_STATUS_CHARGING; + level_override = 50; + break; + /* 5 = invalid battery type + 6 = thermal error + 7 = other charging error */ + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + level_override = 0; + break; + } + + if (level_override != 0 && *level == 0) + *level = level_override; + + return status; +} + +static int hidpp20_batterylevel_get_battery_level(struct hidpp_device *hidpp, + u8 feature_index, + int *status, + int *level, + int *next_level) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS, + NULL, 0, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + *status = hidpp20_batterylevel_map_status_level(params, level, + next_level); + + return 0; +} + +static int hidpp20_query_battery_info(struct hidpp_device *hidpp) +{ + u8 feature_type; + int ret; + int status, level, next_level; + + if (hidpp->battery.feature_index == 0) { + ret = hidpp_root_get_feature(hidpp, + HIDPP_PAGE_BATTERY_LEVEL_STATUS, + &hidpp->battery.feature_index, + &feature_type); + if (ret) + return ret; + } + + ret = hidpp20_batterylevel_get_battery_level(hidpp, + hidpp->battery.feature_index, + &status, &level, &next_level); + if (ret) + return ret; + + hidpp->battery.status = status; + hidpp->battery.level = level; + + return 0; +} + +static int hidpp20_battery_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + int status, level, next_level; + bool changed; + + if (report->fap.feature_index != hidpp->battery.feature_index || + report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST) + return 0; + + status = hidpp20_batterylevel_map_status_level(report->fap.params, + &level, &next_level); + + changed = level != hidpp->battery.level || + status != hidpp->battery.status; + + if (changed) { + hidpp->battery.level = level; + hidpp->battery.status = status; + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + + return 0; +} + +static enum power_supply_property hidpp_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int hidpp_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct hidpp_device *hidpp = power_supply_get_drvdata(psy); + int ret = 0; + + switch(psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = hidpp->battery.status; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = hidpp->battery.level; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int hidpp20_initialize_battery(struct hidpp_device *hidpp) +{ + static atomic_t battery_no = ATOMIC_INIT(0); + struct power_supply_config cfg = { .drv_data = hidpp }; + struct power_supply_desc *desc = &hidpp->battery.desc; + struct hidpp_battery *battery; + unsigned long n; + int ret; + + ret = hidpp20_query_battery_info(hidpp); + if (ret) + return ret; + + battery = &hidpp->battery; + + n = atomic_inc_return(&battery_no) - 1; + desc->properties = hidpp_battery_props; + desc->num_properties = ARRAY_SIZE(hidpp_battery_props); + desc->get_property = hidpp_battery_get_property; + sprintf(battery->name, "hidpp_battery_%ld", n); + desc->name = battery->name; + desc->type = POWER_SUPPLY_TYPE_BATTERY; + desc->use_for_apm = 0; + + battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev, + &battery->desc, + &cfg); + if (IS_ERR(battery->ps)) + return PTR_ERR(battery->ps); + + power_supply_powers(battery->ps, &hidpp->hid_dev->dev); + + return 0; +} + +static int hidpp_initialize_battery(struct hidpp_device *hidpp) +{ + int ret; + + if (hidpp->protocol_major >= 2) { + ret = hidpp20_initialize_battery(hidpp); + if (ret == 0) + hidpp->quirks |= HIDPP_QUIRK_HIDPP20_BATTERY; + } + + return ret; +} + /* -------------------------------------------------------------------------- */ /* 0x6010: Touchpad FW items */ /* -------------------------------------------------------------------------- */ @@ -2050,6 +2278,12 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, if (ret != 0) return ret; + if (hidpp->quirks & HIDPP_QUIRK_HIDPP20_BATTERY) { + ret = hidpp20_battery_event(hidpp, data, size); + if (ret != 0) + return ret; + } + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) return wtp_raw_event(hdev, data, size); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) @@ -2158,6 +2392,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp->protocol_major, hidpp->protocol_minor); } + hidpp_initialize_battery(hidpp); + if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) /* if HID created the input nodes for us, we can stop now */ return; -- cgit v1.2.3 From 6bd4e65d521f924b9f17c6a285b5e1ea7a2bd4d4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 29 Jun 2016 19:28:02 +1000 Subject: HID: logitech-hidpp: remove HIDPP_QUIRK_CONNECT_EVENTS Now that we can create battery power_supply sources, it's better to enable the connect_event callback unconditionally. Signed-off-by: Benjamin Tissoires Tested-by: Peter Hutterer Signed-off-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1ead9f697d8c..4eeb550617bf 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -58,15 +58,14 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_CLASS_G920 BIT(3) /* bits 2..20 are reserved for classes */ -#define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) +/* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */ #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) #define HIDPP_QUIRK_HIDPP20_BATTERY BIT(25) #define HIDPP_QUIRK_HIDPP10_BATTERY BIT(26) -#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ - HIDPP_QUIRK_CONNECT_EVENTS) +#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT /* * There are two hidpp protocols in use, the first version hidpp10 is known @@ -2230,8 +2229,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_CONNECT_EVENTS) && - (schedule_work(&hidpp->work) == 0)) + if (schedule_work(&hidpp->work) == 0) dbg_hid("%s: connect event already queued\n", __func__); return 1; } @@ -2449,7 +2447,6 @@ 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_CONNECT_EVENTS; hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; } @@ -2535,12 +2532,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) } } - if (hidpp->quirks & HIDPP_QUIRK_CONNECT_EVENTS) { - /* Allow incoming packets */ - hid_device_io_start(hdev); + /* Allow incoming packets */ + hid_device_io_start(hdev); - hidpp_connect_event(hidpp); - } + hidpp_connect_event(hidpp); return ret; @@ -2593,7 +2588,7 @@ static const struct hid_device_id hidpp_devices[] = { { /* 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 }, + .driver_data = HIDPP_QUIRK_CLASS_K400 }, { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, -- cgit v1.2.3 From 351744aa0f0366f17118db58b011cd3b8678f923 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 11 Jul 2016 23:49:20 +0200 Subject: HID: logitech-hidpp: select CONFIG_POWER_SUPPLY A recent commit added barry support to this driver, but that causes a link failure when CONFIG_POWER_SUPPLY is not set: drivers/hid/built-in.o: In function `hidpp_battery_get_property': :(.text+0x1a834): undefined reference to `power_supply_get_drvdata' drivers/hid/built-in.o: In function `hidpp_raw_event': :(.text+0x1b10c): undefined reference to `power_supply_changed' drivers/hid/built-in.o: In function `hidpp_connect_event': :(.text+0x1bd88): undefined reference to `devm_power_supply_register' :(.text+0x1be30): undefined reference to `power_supply_powers' This adds a dependency, identically to the other HID drivers that need this. Signed-off-by: Arnd Bergmann Reviewed-by: Benjamin Tissoires Fixes: 5a2b190cddb9 ("HID: logitech-hidpp: add battery support for HID++ 2.0 devices") Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 5646ca4b95de..820bc73ac058 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -425,6 +425,7 @@ config HID_LOGITECH_DJ config HID_LOGITECH_HIDPP tristate "Logitech HID++ devices support" depends on HID_LOGITECH + select POWER_SUPPLY ---help--- Support for Logitech devices relyingon the HID++ Logitech specification -- cgit v1.2.3 From 8c34b5c4c82e060de0d8bbf26b978c68bffe5a18 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sat, 3 Dec 2016 08:55:56 -0200 Subject: [media] rc: raw IR drivers cannot handle cec, unknown or other MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit unknown and other are for IR protocols for which we have no decoder, so the raw IR drivers have no chance of generating them. cec is not an IR protocol. Signed-off-by: Sean Young Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Bruno Prémont Signed-off-by: Mauro Carvalho Chehab --- drivers/hid/hid-picolcd_cir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c index 96286510f42e..90add97cc54d 100644 --- a/drivers/hid/hid-picolcd_cir.c +++ b/drivers/hid/hid-picolcd_cir.c @@ -114,7 +114,7 @@ int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) rdev->priv = data; rdev->driver_type = RC_DRIVER_IR_RAW; - rdev->allowed_protocols = RC_BIT_ALL; + rdev->allowed_protocols = RC_BIT_ALL_IR_DECODER; rdev->open = picolcd_cir_open; rdev->close = picolcd_cir_close; rdev->input_name = data->hdev->name; -- cgit v1.2.3 From 0f7499fddb153a333dff3c1dc4280c178b9b5a80 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Fri, 16 Dec 2016 06:50:58 -0200 Subject: [media] rc-main: assign driver type during allocation The driver type can be assigned immediately when an RC device requests to the framework to allocate the device. This is an 'enum rc_driver_type' data type and specifies whether the device is a raw receiver or scancode receiver. The type will be given as parameter to the rc_allocate_device device. Change accordingly all the drivers calling rc_allocate_device() so that the device type is specified during the rc device allocation. Whenever the device type is not specified, it will be set as RC_DRIVER_SCANCODE which was the default '0' value. Suggested-by: Sean Young Signed-off-by: Andi Shyti Signed-off-by: Sean Young Signed-off-by: Mauro Carvalho Chehab --- drivers/hid/hid-picolcd_cir.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c index 90add97cc54d..8ffbb6f65a65 100644 --- a/drivers/hid/hid-picolcd_cir.c +++ b/drivers/hid/hid-picolcd_cir.c @@ -108,12 +108,11 @@ int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) struct rc_dev *rdev; int ret = 0; - rdev = rc_allocate_device(); + rdev = rc_allocate_device(RC_DRIVER_IR_RAW); if (!rdev) return -ENOMEM; rdev->priv = data; - rdev->driver_type = RC_DRIVER_IR_RAW; rdev->allowed_protocols = RC_BIT_ALL_IR_DECODER; rdev->open = picolcd_cir_open; rdev->close = picolcd_cir_close; -- cgit v1.2.3 From 07e88a35dcea7d606caae797dff6e82c642d3f68 Mon Sep 17 00:00:00 2001 From: Jonathan Tomer Date: Wed, 1 Feb 2017 11:54:57 -0800 Subject: HID: Add quirk driver for NTI USB-SUN adapter These adapters allow pre-USB Sun keyboards to be connected to USB-only machines, but include the wrong maximum keycode in their report descriptor, making most of the keys present on Sun keyboards but not 101-key PC keyboards nonfunctional. This patch implements a quirk that overrides the maximum keycode in the report descriptor. Signed-off-by: Jonathan Tomer Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 6 +++++ drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-nti.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+) create mode 100644 drivers/hid/hid-nti.c (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1aeb80e52424..627452fe250d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -580,6 +580,12 @@ config HID_MULTITOUCH To compile this driver as a module, choose M here: the module will be called hid-multitouch. +config HID_NTI + tristate "NTI keyboard adapters" + ---help--- + Support for the "extra" Sun keyboard keys on keyboards attached + through Network Technologies USB-SUN keyboard adapters. + config HID_NTRIG tristate "N-Trig touch screen" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 4d111f23e801..c0a844b43dcc 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o +obj-$(CONFIG_HID_NTI) += hid-nti.o obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o obj-$(CONFIG_HID_ORTEK) += hid-ortek.o obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index e9e87d337446..e9a9d8a424d3 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1990,6 +1990,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) }, { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) }, { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN) }, { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1) }, { HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 86c95d30ac80..026739d5ec42 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -767,6 +767,9 @@ #define USB_DEVICE_ID_NOVATEK_PCT 0x0600 #define USB_DEVICE_ID_NOVATEK_MOUSE 0x1602 +#define USB_VENDOR_ID_NTI 0x0757 +#define USB_DEVICE_ID_USB_SUN 0x0a00 + #define USB_VENDOR_ID_NTRIG 0x1b96 #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN 0x0001 #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1 0x0003 diff --git a/drivers/hid/hid-nti.c b/drivers/hid/hid-nti.c new file mode 100644 index 000000000000..5bb827b223ba --- /dev/null +++ b/drivers/hid/hid-nti.c @@ -0,0 +1,59 @@ +/* + * USB HID quirks support for Network Technologies, Inc. "USB-SUN" USB + * adapter for pre-USB Sun keyboards + * + * Copyright (c) 2011 Google, Inc. + * + * Based on HID apple driver by + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2006-2007 Jiri Kosina + * Copyright (c) 2008 Jiri Slaby + */ + +/* + * 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 +#include +#include +#include + +#include "hid-ids.h" + +MODULE_AUTHOR("Jonathan Klabunde Tomer "); +MODULE_DESCRIPTION("HID driver for Network Technologies USB-SUN keyboard adapter"); + +/* + * NTI Sun keyboard adapter has wrong logical maximum in report descriptor + */ +static __u8 *nti_usbsun_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (*rsize >= 60 && rdesc[53] == 0x65 && rdesc[59] == 0x65) { + hid_info(hdev, "fixing up NTI USB-SUN keyboard adapter report descriptor\n"); + rdesc[53] = rdesc[59] = 0xe7; + } + return rdesc; +} + +static const struct hid_device_id nti_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) }, + { } +}; +MODULE_DEVICE_TABLE(hid, nti_devices); + +static struct hid_driver nti_driver = { + .name = "nti", + .id_table = nti_devices, + .report_fixup = nti_usbsun_report_fixup +}; + +module_hid_driver(nti_driver); + +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 572d3c6444979a6a49c6b464110563f578e8dece Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Thu, 9 Feb 2017 18:03:57 -0800 Subject: HID: i2c-hid: support regulator power on/off On some boards, we need to enable a regulator before using the HID, and it's also nice to save power in suspend by disabling it. Support an optional "vdd-supply" and a companion initialization delay. Signed-off-by: Brian Norris Signed-off-by: Caesar Wang Acked-by: Benjamin Tissoires Reviewed-by: Dmitry Torokhov Cc: Jiri Kosina Cc: linux-input@vger.kernel.org Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index ea3c3546cef7..a3f6daf0886b 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -994,6 +994,11 @@ static int i2c_hid_of_probe(struct i2c_client *client, } pdata->hid_descriptor_address = val; + ret = of_property_read_u32(dev->of_node, "post-power-on-delay-ms", + &val); + if (!ret) + pdata->post_power_delay_ms = val; + return 0; } @@ -1053,6 +1058,24 @@ static int i2c_hid_probe(struct i2c_client *client, ihid->pdata = *platform_data; } + ihid->pdata.supply = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(ihid->pdata.supply)) { + ret = PTR_ERR(ihid->pdata.supply); + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "Failed to get regulator: %d\n", + ret); + return ret; + } + + ret = regulator_enable(ihid->pdata.supply); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable regulator: %d\n", + ret); + goto err; + } + if (ihid->pdata.post_power_delay_ms) + msleep(ihid->pdata.post_power_delay_ms); + i2c_set_clientdata(client, ihid); ihid->client = client; @@ -1068,7 +1091,7 @@ static int i2c_hid_probe(struct i2c_client *client, * real computation later. */ ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE); if (ret < 0) - goto err; + goto err_regulator; pm_runtime_get_noresume(&client->dev); pm_runtime_set_active(&client->dev); @@ -1125,6 +1148,9 @@ err_pm: pm_runtime_put_noidle(&client->dev); pm_runtime_disable(&client->dev); +err_regulator: + regulator_disable(ihid->pdata.supply); + err: i2c_hid_free_buffers(ihid); kfree(ihid); @@ -1149,6 +1175,8 @@ static int i2c_hid_remove(struct i2c_client *client) if (ihid->bufsize) i2c_hid_free_buffers(ihid); + regulator_disable(ihid->pdata.supply); + kfree(ihid); return 0; @@ -1199,6 +1227,10 @@ static int i2c_hid_suspend(struct device *dev) else hid_warn(hid, "Failed to enable irq wake: %d\n", wake_status); + } else { + ret = regulator_disable(ihid->pdata.supply); + if (ret < 0) + hid_warn(hid, "Failed to disable supply: %d\n", ret); } return 0; @@ -1212,7 +1244,13 @@ static int i2c_hid_resume(struct device *dev) struct hid_device *hid = ihid->hid; int wake_status; - if (device_may_wakeup(&client->dev) && ihid->irq_wake_enabled) { + if (!device_may_wakeup(&client->dev)) { + ret = regulator_enable(ihid->pdata.supply); + if (ret < 0) + hid_warn(hid, "Failed to enable supply: %d\n", ret); + if (ihid->pdata.post_power_delay_ms) + msleep(ihid->pdata.post_power_delay_ms); + } else if (ihid->irq_wake_enabled) { wake_status = disable_irq_wake(client->irq); if (!wake_status) ihid->irq_wake_enabled = false; -- cgit v1.2.3 From 3f3752705dbd50b66b66ad7b4d54fe33d2f746ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valtteri=20Heikkil=C3=A4?= Date: Tue, 14 Feb 2017 23:14:32 +0000 Subject: HID: reject input outside logical range only if null state is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes an issue in drivers/hid/hid-input.c where USB HID control null state flag is not checked upon rejecting inputs outside logical minimum-maximum range. The check should be made according to USB HID specification 1.11, section 6.2.2.5, p.31. The fix will resolve issues with some game controllers, such as: https://bugzilla.kernel.org/show_bug.cgi?id=68621 [tk@the-tk.com: shortened and fixed spelling in commit message] Signed-off-by: Valtteri Heikkilä Signed-off-by: Tomasz Kramkowski Acked-By: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index d05f903c7614..cf8256aac2bd 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1157,6 +1157,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct * don't specify logical min and max. */ if ((field->flags & HID_MAIN_ITEM_VARIABLE) && + (field->flags & HID_MAIN_ITEM_NULL_STATE) && (field->logical_minimum < field->logical_maximum) && (value < field->logical_minimum || value > field->logical_maximum)) { -- cgit v1.2.3 From 9547837bdccb4af127528b36a73377150658b4ac Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Tue, 14 Feb 2017 23:14:33 +0000 Subject: HID: usbhid: add quirk for innomedia INNEX GENESIS/ATARI adapter The (1292:4745) Innomedia INNEX GENESIS/ATARI adapter needs HID_QUIRK_MULTI_INPUT to split the device up into two controllers instead of inputs from both being merged into one. Signed-off-by: Tomasz Kramkowski Acked-By: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/usbhid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 86c95d30ac80..758e16037869 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -545,6 +545,9 @@ #define USB_VENDOR_ID_IRTOUCHSYSTEMS 0x6615 #define USB_DEVICE_ID_IRTOUCH_INFRARED_USB 0x0070 +#define USB_VENDOR_ID_INNOMEDIA 0x1292 +#define USB_DEVICE_ID_INNEX_GENESIS_ATARI 0x4745 + #define USB_VENDOR_ID_ITE 0x048d #define USB_DEVICE_ID_ITE_LENOVO_YOGA 0x8386 #define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350 diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index d6847a664446..f27921efe8fc 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -162,6 +162,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES, HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI, HID_QUIRK_MULTI_INPUT }, { 0, 0 } }; -- cgit v1.2.3 From 4eb220cb35a9c4f69a2438b987bb3d509d56cc80 Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Tue, 14 Feb 2017 21:26:21 -0800 Subject: HID: wacom: generic: add 3 tablet touch keys This patch add support to the 3 touch keys on Wacom Cintiq Pro. These touch keys are in the middle of the other two keys on the top edge of the tablet. Signed-off-by: Ping Cheng Reviewed-by: Benjamin Tissoires Tested-by: Aaron Armstrong Skomra Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 12 ++++++++++++ drivers/hid/wacom_wac.h | 3 +++ 2 files changed, 15 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 4aa3de9f1163..dbda99272374 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1768,6 +1768,18 @@ static void wacom_wac_pad_usage_mapping(struct hid_device *hdev, wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0); features->device_type |= WACOM_DEVICETYPE_PAD; break; + case WACOM_HID_WD_BUTTONCONFIG: + wacom_map_usage(input, usage, field, EV_KEY, KEY_BUTTONCONFIG, 0); + features->device_type |= WACOM_DEVICETYPE_PAD; + break; + case WACOM_HID_WD_ONSCREEN_KEYBOARD: + wacom_map_usage(input, usage, field, EV_KEY, KEY_ONSCREEN_KEYBOARD, 0); + features->device_type |= WACOM_DEVICETYPE_PAD; + break; + case WACOM_HID_WD_CONTROLPANEL: + wacom_map_usage(input, usage, field, EV_KEY, KEY_CONTROLPANEL, 0); + features->device_type |= WACOM_DEVICETYPE_PAD; + break; } switch (equivalent_usage & 0xfffffff0) { diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 857ccee16f38..5eba31d6c46a 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -120,6 +120,9 @@ #define WACOM_HID_WD_BATTERY_LEVEL (WACOM_HID_UP_WACOMDIGITIZER | 0x043b) #define WACOM_HID_WD_EXPRESSKEY00 (WACOM_HID_UP_WACOMDIGITIZER | 0x0910) #define WACOM_HID_WD_EXPRESSKEYCAP00 (WACOM_HID_UP_WACOMDIGITIZER | 0x0950) +#define WACOM_HID_WD_CONTROLPANEL (WACOM_HID_UP_WACOMDIGITIZER | 0x0982) +#define WACOM_HID_WD_ONSCREEN_KEYBOARD (WACOM_HID_UP_WACOMDIGITIZER | 0x0983) +#define WACOM_HID_WD_BUTTONCONFIG (WACOM_HID_UP_WACOMDIGITIZER | 0x0986) #define WACOM_HID_WD_BUTTONHOME (WACOM_HID_UP_WACOMDIGITIZER | 0x0990) #define WACOM_HID_WD_BUTTONUP (WACOM_HID_UP_WACOMDIGITIZER | 0x0991) #define WACOM_HID_WD_BUTTONDOWN (WACOM_HID_UP_WACOMDIGITIZER | 0x0992) -- cgit v1.2.3 From 4082da80f46a6683439bb0357faadb18f0f5b2a6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 14 Feb 2017 21:27:18 -0800 Subject: HID: wacom: generic: add mode change touch key Wacom Cintiq Pro added a touch key to switch the tablet between display and opaque mode. This patch informs the change by removing the old devices and creating new ones with proper properties. Signed-off-by: Benjamin Tissoires Signed-off-by: Ping Cheng Tested-by: Aaron Armstrong Skomra Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 5 +++++ drivers/hid/wacom_sys.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ drivers/hid/wacom_wac.c | 17 ++++++++++++++++- drivers/hid/wacom_wac.h | 4 ++++ 4 files changed, 74 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 38ee2125412f..c7b9ab1907d8 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -110,6 +110,7 @@ enum wacom_worker { WACOM_WORKER_WIRELESS, WACOM_WORKER_BATTERY, WACOM_WORKER_REMOTE, + WACOM_WORKER_MODE_CHANGE, }; struct wacom; @@ -167,6 +168,7 @@ struct wacom { struct work_struct remote_work; struct delayed_work init_work; struct wacom_remote *remote; + struct work_struct mode_change_work; bool generic_has_leds; struct wacom_leds { struct wacom_group_leds *groups; @@ -196,6 +198,9 @@ static inline void wacom_schedule_work(struct wacom_wac *wacom_wac, case WACOM_WORKER_REMOTE: schedule_work(&wacom->remote_work); break; + case WACOM_WORKER_MODE_CHANGE: + schedule_work(&wacom->mode_change_work); + break; } } diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index be8f7e2a026f..9aec5b3a45bd 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -325,6 +325,13 @@ static void wacom_post_parse_hid(struct hid_device *hdev, if (features->type == HID_GENERIC) { /* Any last-minute generic device setup */ + if (wacom_wac->has_mode_change) { + if (wacom_wac->is_direct_mode) + features->device_type |= WACOM_DEVICETYPE_DIRECT; + else + features->device_type &= ~WACOM_DEVICETYPE_DIRECT; + } + if (features->touch_max > 1) { if (features->device_type & WACOM_DEVICETYPE_DIRECT) input_mt_init_slots(wacom_wac->touch_input, @@ -2488,6 +2495,46 @@ static void wacom_remote_work(struct work_struct *work) } } +static void wacom_mode_change_work(struct work_struct *work) +{ + struct wacom *wacom = container_of(work, struct wacom, mode_change_work); + struct wacom_shared *shared = wacom->wacom_wac.shared; + struct wacom *wacom1 = NULL; + struct wacom *wacom2 = NULL; + bool is_direct = wacom->wacom_wac.is_direct_mode; + int error = 0; + + if (shared->pen) { + wacom1 = hid_get_drvdata(shared->pen); + wacom_release_resources(wacom1); + hid_hw_stop(wacom1->hdev); + wacom1->wacom_wac.has_mode_change = true; + wacom1->wacom_wac.is_direct_mode = is_direct; + } + + if (shared->touch) { + wacom2 = hid_get_drvdata(shared->touch); + wacom_release_resources(wacom2); + hid_hw_stop(wacom2->hdev); + wacom2->wacom_wac.has_mode_change = true; + wacom2->wacom_wac.is_direct_mode = is_direct; + } + + if (wacom1) { + error = wacom_parse_and_register(wacom1, false); + if (error) + return; + } + + if (wacom2) { + error = wacom_parse_and_register(wacom2, false); + if (error) + return; + } + + return; +} + static int wacom_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -2532,6 +2579,7 @@ static int wacom_probe(struct hid_device *hdev, INIT_WORK(&wacom->wireless_work, wacom_wireless_work); INIT_WORK(&wacom->battery_work, wacom_battery_work); INIT_WORK(&wacom->remote_work, wacom_remote_work); + INIT_WORK(&wacom->mode_change_work, wacom_mode_change_work); /* ask for the report descriptor to be loaded by HID */ error = hid_parse(hdev); @@ -2574,6 +2622,7 @@ static void wacom_remove(struct hid_device *hdev) cancel_work_sync(&wacom->wireless_work); cancel_work_sync(&wacom->battery_work); cancel_work_sync(&wacom->remote_work); + cancel_work_sync(&wacom->mode_change_work); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index dbda99272374..2c399a423957 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1780,6 +1780,14 @@ static void wacom_wac_pad_usage_mapping(struct hid_device *hdev, wacom_map_usage(input, usage, field, EV_KEY, KEY_CONTROLPANEL, 0); features->device_type |= WACOM_DEVICETYPE_PAD; break; + case WACOM_HID_WD_MODE_CHANGE: + /* do not overwrite previous data */ + if (!wacom_wac->has_mode_change) { + wacom_wac->has_mode_change = true; + wacom_wac->is_direct_mode = true; + } + features->device_type |= WACOM_DEVICETYPE_PAD; + break; } switch (equivalent_usage & 0xfffffff0) { @@ -1828,7 +1836,7 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field * Avoid reporting this event and setting inrange_state if this usage * hasn't been mapped. */ - if (!usage->type) + if (!usage->type && equivalent_usage != WACOM_HID_WD_MODE_CHANGE) return; if (wacom_equivalent_usage(field->physical) == HID_DG_TABLETFUNCTIONKEY) { @@ -1850,6 +1858,13 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field } break; + case WACOM_HID_WD_MODE_CHANGE: + if (wacom_wac->is_direct_mode != value) { + wacom_wac->is_direct_mode = value; + wacom_schedule_work(&wacom->wacom_wac, WACOM_WORKER_MODE_CHANGE); + } + break; + case WACOM_HID_WD_BUTTONCENTER: for (i = 0; i < wacom->led.count; i++) wacom_update_led(wacom, features->numbered_buttons, diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 5eba31d6c46a..d9669c6116b7 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -120,6 +120,7 @@ #define WACOM_HID_WD_BATTERY_LEVEL (WACOM_HID_UP_WACOMDIGITIZER | 0x043b) #define WACOM_HID_WD_EXPRESSKEY00 (WACOM_HID_UP_WACOMDIGITIZER | 0x0910) #define WACOM_HID_WD_EXPRESSKEYCAP00 (WACOM_HID_UP_WACOMDIGITIZER | 0x0950) +#define WACOM_HID_WD_MODE_CHANGE (WACOM_HID_UP_WACOMDIGITIZER | 0x0980) #define WACOM_HID_WD_CONTROLPANEL (WACOM_HID_UP_WACOMDIGITIZER | 0x0982) #define WACOM_HID_WD_ONSCREEN_KEYBOARD (WACOM_HID_UP_WACOMDIGITIZER | 0x0983) #define WACOM_HID_WD_BUTTONCONFIG (WACOM_HID_UP_WACOMDIGITIZER | 0x0986) @@ -330,6 +331,9 @@ struct wacom_wac { int mode_value; struct hid_data hid_data; bool has_mute_touch_switch; + bool has_mode_change; + bool is_direct_mode; + }; #endif -- cgit v1.2.3 From d793ff81879a5747109f3106bd18e77662cb5187 Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Tue, 14 Feb 2017 21:27:45 -0800 Subject: HID: wacom: generic: support touch on/off softkey Wacom Cintiq Pro has a softkey to turn touch on/off. Since it is a softkey, hardware/firmware still reports touch events no matter what state the softkey is. We need to ignore touch events when the key is in off mode. Signed-off-by: Ping Cheng Reviewed-by: Benjamin Tissoires Tested-by: Aaron Armstrong Skomra Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 4 +++- drivers/hid/wacom_wac.c | 18 +++++++++++++++++- drivers/hid/wacom_wac.h | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 9aec5b3a45bd..037b9c04745a 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -2100,8 +2100,10 @@ static void wacom_set_shared_values(struct wacom_wac *wacom_wac) wacom_wac->shared->touch_input = wacom_wac->touch_input; } - if (wacom_wac->has_mute_touch_switch) + if (wacom_wac->has_mute_touch_switch) { wacom_wac->shared->has_mute_touch_switch = true; + wacom_wac->shared->is_touch_on = true; + } if (wacom_wac->shared->has_mute_touch_switch && wacom_wac->shared->touch_input) { diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 2c399a423957..68fb9e114a0b 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1739,6 +1739,7 @@ static void wacom_wac_pad_usage_mapping(struct hid_device *hdev, features->device_type |= WACOM_DEVICETYPE_PAD; break; case WACOM_HID_WD_TOUCHONOFF: + case WACOM_HID_WD_MUTE_DEVICE: /* * This usage, which is used to mute touch events, comes * from the pad packet, but is reported on the touch @@ -1831,6 +1832,7 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field struct wacom_features *features = &wacom_wac->features; unsigned equivalent_usage = wacom_equivalent_usage(usage->hid); int i; + bool is_touch_on = value; /* * Avoid reporting this event and setting inrange_state if this usage @@ -1850,10 +1852,17 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field input_event(input, usage->type, usage->code, 0); break; + case WACOM_HID_WD_MUTE_DEVICE: + if (wacom_wac->shared->touch_input && value) { + wacom_wac->shared->is_touch_on = !wacom_wac->shared->is_touch_on; + is_touch_on = wacom_wac->shared->is_touch_on; + } + + /* fall through*/ case WACOM_HID_WD_TOUCHONOFF: if (wacom_wac->shared->touch_input) { input_report_switch(wacom_wac->shared->touch_input, - SW_MUTE_DEVICE, !value); + SW_MUTE_DEVICE, !is_touch_on); input_sync(wacom_wac->shared->touch_input); } break; @@ -2212,6 +2221,13 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac, bool prox = hid_data->tipswitch && report_touch_events(wacom_wac); + if (wacom_wac->shared->has_mute_touch_switch && + !wacom_wac->shared->is_touch_on) { + if (!wacom_wac->shared->touch_down) + return; + prox = 0; + } + wacom_wac->hid_data.num_received++; if (wacom_wac->hid_data.num_received > wacom_wac->hid_data.num_expected) return; diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index d9669c6116b7..839bd4b6388c 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -121,6 +121,7 @@ #define WACOM_HID_WD_EXPRESSKEY00 (WACOM_HID_UP_WACOMDIGITIZER | 0x0910) #define WACOM_HID_WD_EXPRESSKEYCAP00 (WACOM_HID_UP_WACOMDIGITIZER | 0x0950) #define WACOM_HID_WD_MODE_CHANGE (WACOM_HID_UP_WACOMDIGITIZER | 0x0980) +#define WACOM_HID_WD_MUTE_DEVICE (WACOM_HID_UP_WACOMDIGITIZER | 0x0981) #define WACOM_HID_WD_CONTROLPANEL (WACOM_HID_UP_WACOMDIGITIZER | 0x0982) #define WACOM_HID_WD_ONSCREEN_KEYBOARD (WACOM_HID_UP_WACOMDIGITIZER | 0x0983) #define WACOM_HID_WD_BUTTONCONFIG (WACOM_HID_UP_WACOMDIGITIZER | 0x0986) @@ -274,6 +275,7 @@ struct wacom_shared { struct hid_device *pen; struct hid_device *touch; bool has_mute_touch_switch; + bool is_touch_on; }; struct hid_data { -- cgit v1.2.3 From d3d9adfe3059cb5cb330a2da74ea0bad49b482c0 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 19 Feb 2017 13:07:59 +0100 Subject: HID: i2c-hid: Fix error handling According to error handling in this function, it is likely that some resources should be freed before returning. Replace 'return ret', with 'goto err'. While at it, remove some spaces at the beginning of the lines to be more consistent. Fixes: ead0687fe304a ("HID: i2c-hid: support regulator power on/off") Signed-off-by: Christophe JAILLET Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index a3f6daf0886b..a83814949467 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -1064,7 +1064,7 @@ static int i2c_hid_probe(struct i2c_client *client, if (ret != -EPROBE_DEFER) dev_err(&client->dev, "Failed to get regulator: %d\n", ret); - return ret; + goto err; } ret = regulator_enable(ihid->pdata.supply); -- cgit v1.2.3 From 933bfe4d271ef5931bc7513a1239751ed251db04 Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Sat, 25 Feb 2017 20:27:27 +0100 Subject: HID: usbhid: extend polling interval configuration to joysticks For mouse devices we can currently change the polling interval via usbhid.mousepoll. Implement the same thing for joysticks, so users can reduce input latency this way. This has been tested with a Logitech RumblePad 2 with jspoll=2, resulting in a polling rate of 500Hz (verified with evhz). Signed-off-by: Tobias Jakobi Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 961bc6fdd2d9..b06fee1b8e47 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -52,6 +52,10 @@ static unsigned int hid_mousepoll_interval; module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644); MODULE_PARM_DESC(mousepoll, "Polling interval of mice"); +static unsigned int hid_jspoll_interval; +module_param_named(jspoll, hid_jspoll_interval, uint, 0644); +MODULE_PARM_DESC(jspoll, "Polling interval of joysticks"); + static unsigned int ignoreled; module_param_named(ignoreled, ignoreled, uint, 0644); MODULE_PARM_DESC(ignoreled, "Autosuspend with active leds"); @@ -1081,9 +1085,17 @@ static int usbhid_start(struct hid_device *hid) hid->name, endpoint->bInterval, interval); } - /* Change the polling interval of mice. */ - if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0) - interval = hid_mousepoll_interval; + /* Change the polling interval of mice and joysticks. */ + switch (hid->collection->usage) { + case HID_GD_MOUSE: + if (hid_mousepoll_interval > 0) + interval = hid_mousepoll_interval; + break; + case HID_GD_JOYSTICK: + if (hid_jspoll_interval > 0) + interval = hid_jspoll_interval; + break; + } ret = -ENOMEM; if (usb_endpoint_dir_in(endpoint)) { -- cgit v1.2.3 From 52150c78270db56ef5c0de0bf9d13d1d437d1236 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Wed, 1 Mar 2017 17:35:07 -0800 Subject: HID: usbhid: Use pr_ and remove unnecessary OOM messages Use a more common logging style and remove the unnecessary OOM messages as there is default dump_stack when OOM. Miscellanea: o Hoist an assignment in an if o Realign arguments o Realign a deeply indented if descendent above a printk Signed-off-by: Joe Perches Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 16 +++++++--------- drivers/hid/usbhid/hid-quirks.c | 11 ++++------- 2 files changed, 11 insertions(+), 16 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index b06fee1b8e47..9ef9c0837d95 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1008,10 +1008,9 @@ static int usbhid_parse(struct hid_device *hid) return -EINVAL; } - if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) { - dbg_hid("couldn't allocate rdesc memory\n"); + rdesc = kmalloc(rsize, GFP_KERNEL); + if (!rdesc) return -ENOMEM; - } hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0); @@ -1081,8 +1080,8 @@ static int usbhid_start(struct hid_device *hid) if (hid->quirks & HID_QUIRK_FULLSPEED_INTERVAL && dev->speed == USB_SPEED_HIGH) { interval = fls(endpoint->bInterval*8); - printk(KERN_INFO "%s: Fixing fullspeed to highspeed interval: %d -> %d\n", - hid->name, endpoint->bInterval, interval); + pr_info("%s: Fixing fullspeed to highspeed interval: %d -> %d\n", + hid->name, endpoint->bInterval, interval); } /* Change the polling interval of mice and joysticks. */ @@ -1468,10 +1467,9 @@ static int hid_post_reset(struct usb_interface *intf) * the size of the HID report descriptor has not changed. */ rdesc = kmalloc(hid->dev_rsize, GFP_KERNEL); - if (!rdesc) { - dbg_hid("couldn't allocate rdesc memory (post_reset)\n"); + if (!rdesc) return -ENOMEM; - } + status = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, hid->dev_rsize); @@ -1649,7 +1647,7 @@ static int __init hid_init(void) retval = usb_register(&hid_driver); if (retval) goto usb_register_fail; - printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + pr_info(KBUILD_MODNAME ": " DRIVER_DESC "\n"); return 0; usb_register_fail: diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index d6847a664446..9287ab03e117 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -237,10 +237,8 @@ static int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct, } q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL); - if (!q_new) { - dbg_hid("Could not allocate quirks_list_struct\n"); + if (!q_new) return -ENOMEM; - } q_new->hid_bl_item.idVendor = idVendor; q_new->hid_bl_item.idProduct = idProduct; @@ -306,10 +304,9 @@ int usbhid_quirks_init(char **quirks_param) &idVendor, &idProduct, &quirks); if (m != 3 || - usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) { - printk(KERN_WARNING - "Could not parse HID quirk module param %s\n", - quirks_param[n]); + usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) { + pr_warn("Could not parse HID quirk module param %s\n", + quirks_param[n]); } } -- cgit v1.2.3 From e9d0a26d34818e0f9340316f8016129231560966 Mon Sep 17 00:00:00 2001 From: HungNien Chen Date: Mon, 6 Mar 2017 11:30:14 +0800 Subject: HID: multitouch: change for touch height/width Quoting from Jonathan Clarke in previous thread(2017/01/24): "This division by 2 was added along with the touch width/height fields 6 years ago so that those fields 'match the visual scale of the touch' for a specific device (3M PCT)" "The scaling is also discarding information about touch size (1 bit for each of width/height) which is useful for any application that wants to know about it." Jonathan mentioned just what I thought in a new project recently. It dosen't make sense to discard 1 bit width/height in general case according to the spec in multi-touch-protocol.txt so I would like to make a slight change here. A quirk MT_QUIRK_TOUCH_SIZE_SCALING was added to service devices like 3M PCT with a special visual scale and the division by 2 only take effect with devices like that. [jkosina@suse.cz: reformat changelog] Signed-off-by: HungNien Chen Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 692647485a53..24d5b6deb571 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -69,6 +69,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_CONTACT_CNT_ACCURATE (1 << 12) #define MT_QUIRK_FORCE_GET_FEATURE (1 << 13) #define MT_QUIRK_FIX_CONST_CONTACT_ID (1 << 14) +#define MT_QUIRK_TOUCH_SIZE_SCALING (1 << 15) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -222,7 +223,8 @@ static struct mt_class mt_classes[] = { */ { .name = MT_CLS_3M, .quirks = MT_QUIRK_VALID_IS_CONFIDENCE | - MT_QUIRK_SLOT_IS_CONTACTID, + MT_QUIRK_SLOT_IS_CONTACTID | + MT_QUIRK_TOUCH_SIZE_SCALING, .sn_move = 2048, .sn_width = 128, .sn_height = 128, @@ -658,9 +660,17 @@ static void mt_complete_slot(struct mt_device *td, struct input_dev *input) if (active) { /* this finger is in proximity of the sensor */ int wide = (s->w > s->h); - /* divided by two to match visual scale of touch */ - int major = max(s->w, s->h) >> 1; - int minor = min(s->w, s->h) >> 1; + int major = max(s->w, s->h); + int minor = min(s->w, s->h); + + /* + * divided by two to match visual scale of touch + * for devices with this quirk + */ + if (td->mtclass.quirks & MT_QUIRK_TOUCH_SIZE_SCALING) { + major = major >> 1; + minor = minor >> 1; + } input_event(input, EV_ABS, ABS_MT_POSITION_X, s->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, s->y); -- cgit v1.2.3 From c3883fe06488a483658ba5d849b70e49bee15e7c Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Tue, 14 Mar 2017 13:29:13 +0000 Subject: HID: clamp input to logical range if no null state This patch fixes an issue in drivers/hid/hid-input.c where values outside of the logical range are not clamped when "null state" bit of the input control is not set. This was discussed on the lists [1] and this change stems from the fact due to the ambiguity of the HID specification it might be appropriate to follow Microsoft's own interpretation of the specification. As noted in Microsoft's documentation [2] in the section titled "Required HID usages for digitizers" it is noted that values reported outside the logical range "will be considered as invalid data and the value will be changed to the nearest boundary value (logical min/max)." This patch fixes an issue where the (1292:4745) Innomedia INNEX GENESIS/ATARI reports out of range values for its X and Y axis of the DPad which, due to the null state bit being unset, are forwarded to userspace as is. Now these values will get clamped to the logical range before being forwarded to userspace. This device was also used to test this patch. This patch expands on commit 3f3752705dbd ("HID: reject input outside logical range only if null state is set"). [1]: http://lkml.kernel.org/r/20170307131036.GA853@gaia.local [2]: https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp Signed-off-by: Tomasz Kramkowski Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index cf8256aac2bd..a1ebdd7d4d4d 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1150,19 +1150,26 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct /* * Ignore out-of-range values as per HID specification, - * section 5.10 and 6.2.25. + * section 5.10 and 6.2.25, when NULL state bit is present. + * When it's not, clamp the value to match Microsoft's input + * driver as mentioned in "Required HID usages for digitizers": + * https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp * * The logical_minimum < logical_maximum check is done so that we * don't unintentionally discard values sent by devices which * don't specify logical min and max. */ if ((field->flags & HID_MAIN_ITEM_VARIABLE) && - (field->flags & HID_MAIN_ITEM_NULL_STATE) && - (field->logical_minimum < field->logical_maximum) && - (value < field->logical_minimum || - value > field->logical_maximum)) { - dbg_hid("Ignoring out-of-range value %x\n", value); - return; + (field->logical_minimum < field->logical_maximum)) { + if (field->flags & HID_MAIN_ITEM_NULL_STATE && + (value < field->logical_minimum || + value > field->logical_maximum)) { + dbg_hid("Ignoring out-of-range value %x\n", value); + return; + } + value = clamp(value, + field->logical_minimum, + field->logical_maximum); } /* -- cgit v1.2.3 From 9143059fafd4eebed2d43ffb5455178d4010e60a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 8 Mar 2017 15:11:14 +0100 Subject: HID: remove initial reading of reports at connect It looks like a bunch of devices do not like to be polled for their reports at init time. When you look into the details, it seems that for those that are requiring the quirk HID_QUIRK_NO_INIT_REPORTS, the driver fails to retrieve part of the features/inputs while others (more generic) work. IMO, it should be acceptable to remove the need for the quirk in the general case. On the small amount of cases where we actually need to read the current values, the driver in charge (hid-mt or wacom) already retrieves the features manually. There are 2 cases where we might need to retrieve the reports at init: 1. hiddev devices with specific use-space tool 2. a device that would require the driver to fetch a specific feature/input at plug For case 2, I have seen this a few time on hid-multitouch. It is solved in hid-multitouch directly by fetching the feature. I hope it won't be too common and this can be solved on a per-case basis (crossing fingers). For case 1, we moved the implementation of HID_QUIRK_NO_INIT_REPORTS in hiddev. When somebody starts calling ioctls that needs an initial update, the hiddev device will fetch the initial state of the reports to mimic the current behavior. This adds a small amount of time during the first HIDIOCGUSAGE(S), but it should be acceptable in most cases. To keep the currently known broken devices, we have to keep around HID_QUIRK_NO_INIT_REPORTS, but the scope will only be for hiddev. Note that I don't think hidraw would be affected and I checked that the FF drivers that need to interact with the report fields are all using output reports, which are not initialized by usbhid_init_reports(). NO_INIT_INPUT_REPORTS is then replaced by HID_QUIRK_NO_INIT_REPORTS: there is no point keeping it for just one device. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid.c | 63 ----------------------------------------- drivers/hid/usbhid/hid-core.c | 11 ++----- drivers/hid/usbhid/hid-quirks.c | 2 +- drivers/hid/usbhid/hiddev.c | 13 +++++++++ 4 files changed, 17 insertions(+), 72 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index ea3c3546cef7..042b90d451ee 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -508,66 +508,6 @@ static int i2c_hid_get_report_length(struct hid_report *report) report->device->report_enum[report->type].numbered + 2; } -static void i2c_hid_init_report(struct hid_report *report, u8 *buffer, - size_t bufsize) -{ - struct hid_device *hid = report->device; - struct i2c_client *client = hid->driver_data; - struct i2c_hid *ihid = i2c_get_clientdata(client); - unsigned int size, ret_size; - - size = i2c_hid_get_report_length(report); - if (i2c_hid_get_report(client, - report->type == HID_FEATURE_REPORT ? 0x03 : 0x01, - report->id, buffer, size)) - return; - - i2c_hid_dbg(ihid, "report (len=%d): %*ph\n", size, size, buffer); - - ret_size = buffer[0] | (buffer[1] << 8); - - if (ret_size != size) { - dev_err(&client->dev, "error in %s size:%d / ret_size:%d\n", - __func__, size, ret_size); - return; - } - - /* hid->driver_lock is held as we are in probe function, - * we just need to setup the input fields, so using - * hid_report_raw_event is safe. */ - hid_report_raw_event(hid, report->type, buffer + 2, size - 2, 1); -} - -/* - * Initialize all reports - */ -static void i2c_hid_init_reports(struct hid_device *hid) -{ - struct hid_report *report; - struct i2c_client *client = hid->driver_data; - struct i2c_hid *ihid = i2c_get_clientdata(client); - u8 *inbuf = kzalloc(ihid->bufsize, GFP_KERNEL); - - if (!inbuf) { - dev_err(&client->dev, "can not retrieve initial reports\n"); - return; - } - - /* - * The device must be powered on while we fetch initial reports - * from it. - */ - pm_runtime_get_sync(&client->dev); - - list_for_each_entry(report, - &hid->report_enum[HID_FEATURE_REPORT].report_list, list) - i2c_hid_init_report(report, inbuf, ihid->bufsize); - - pm_runtime_put(&client->dev); - - kfree(inbuf); -} - /* * Traverse the supplied list of reports and find the longest */ @@ -789,9 +729,6 @@ static int i2c_hid_start(struct hid_device *hid) return ret; } - if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS)) - i2c_hid_init_reports(hid); - return 0; } diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 961bc6fdd2d9..00e72c6ffc76 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -753,11 +753,9 @@ void usbhid_init_reports(struct hid_device *hid) struct hid_report_enum *report_enum; int err, ret; - if (!(hid->quirks & HID_QUIRK_NO_INIT_INPUT_REPORTS)) { - report_enum = &hid->report_enum[HID_INPUT_REPORT]; - list_for_each_entry(report, &report_enum->report_list, list) - usbhid_submit_report(hid, report, USB_DIR_IN); - } + report_enum = &hid->report_enum[HID_INPUT_REPORT]; + list_for_each_entry(report, &report_enum->report_list, list) + usbhid_submit_report(hid, report, USB_DIR_IN); report_enum = &hid->report_enum[HID_FEATURE_REPORT]; list_for_each_entry(report, &report_enum->report_list, list) @@ -1120,9 +1118,6 @@ static int usbhid_start(struct hid_device *hid) usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma; usbhid->urbctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS)) - usbhid_init_reports(hid); - set_bit(HID_STARTED, &usbhid->iofl); if (hid->quirks & HID_QUIRK_ALWAYS_POLL) { diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index d6847a664446..ec4fdba39722 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -158,7 +158,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103, HID_QUIRK_NO_INIT_REPORTS }, - { USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096, HID_QUIRK_NO_INIT_INPUT_REPORTS }, + { USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES, HID_QUIRK_MULTI_INPUT }, diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 700145b15088..667171829f65 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -54,6 +54,7 @@ struct hiddev { struct hid_device *hid; struct list_head list; spinlock_t list_lock; + bool initialized; }; struct hiddev_list { @@ -689,6 +690,7 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case HIDIOCINITREPORT: usbhid_init_reports(hid); + hiddev->initialized = true; r = 0; break; @@ -790,6 +792,10 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case HIDIOCGUSAGES: case HIDIOCSUSAGES: case HIDIOCGCOLLECTIONINDEX: + if (!hiddev->initialized) { + usbhid_init_reports(hid); + hiddev->initialized = true; + } r = hiddev_ioctl_usage(hiddev, cmd, user_arg); break; @@ -910,6 +916,13 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) kfree(hiddev); return -1; } + + /* + * If HID_QUIRK_NO_INIT_REPORTS is set, make sure we don't initialize + * the reports. + */ + hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS; + return 0; } -- cgit v1.2.3 From c846fe9ce90a5ab0329de7f08d7523fb58c1251e Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Tue, 14 Feb 2017 14:17:56 +0000 Subject: HID: Accutouch: Add driver for ELO Accutouch 2216 USB Touchscreens The Accutouch 2216 is reporting BTN_LEFT/BTN_MOUSE rather than BTM_TOUCH in it's capabilities, which is what user space expects a touchscreen device to report. This is causing udev to consider the device to be a "VMware's USB mouse" rather than as a touchscreen, which results in a mouse cursor being displayed in Weston. This patch adds a special driver for the device to correct the capabilities reported. Signed-off-by: Martyn Welch Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 12 +++++++++++ drivers/hid/Makefile | 1 + drivers/hid/hid-accutouch.c | 52 +++++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 1 + 5 files changed, 67 insertions(+) create mode 100644 drivers/hid/hid-accutouch.c (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1aeb80e52424..fb4cc0d28eea 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -98,6 +98,18 @@ config HID_A4TECH ---help--- Support for A4 tech X5 and WOP-35 / Trust 450L mice. +config HID_ACCUTOUCH + tristate "Accutouch touch device" + depends on USB_HID + ---help--- + This selects a driver for the Accutouch 2216 touch controller. + + The driver works around a problem in the reported device capabilities + which causes userspace to detect the device as a mouse rather than + a touchscreen. + + Say Y here if you have a Accutouch 2216 touch controller. + config HID_ACRUX tristate "ACRUX game controller support" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 4d111f23e801..48be3ed9be4e 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -21,6 +21,7 @@ hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o hid-wiimote-$(CONFIG_DEBUG_FS) += hid-wiimote-debug.o obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o +obj-$(CONFIG_HID_ACCUTOUCH) += hid-accutouch.o obj-$(CONFIG_HID_ALPS) += hid-alps.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o obj-$(CONFIG_HID_APPLE) += hid-apple.o diff --git a/drivers/hid/hid-accutouch.c b/drivers/hid/hid-accutouch.c new file mode 100644 index 000000000000..4e287160c50a --- /dev/null +++ b/drivers/hid/hid-accutouch.c @@ -0,0 +1,52 @@ +/* + * HID driver for Elo Accutouch touchscreens + * + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + * + * based on hid-penmount.c + * Copyright (c) 2014 Christian Gmeiner gmail.com> + */ + +/* + * 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 +#include +#include "hid-ids.h" + +static int accutouch_input_mapping(struct hid_device *hdev, + struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) { + hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); + return 1; + } + + return 0; +} + +static const struct hid_device_id accutouch_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_ACCUTOUCH_2216) }, + { } +}; +MODULE_DEVICE_TABLE(hid, accutouch_devices); + +static struct hid_driver accutouch_driver = { + .name = "hid-accutouch", + .id_table = accutouch_devices, + .input_mapping = accutouch_input_mapping, +}; + +module_hid_driver(accutouch_driver); + +MODULE_AUTHOR("Martyn Welch Date: Fri, 3 Mar 2017 17:54:00 +0900 Subject: HID: cp2112: use proper hidraw name with minor number The cp2112 driver is working on hidraw not hiddev. So we need to use proper hidraw name with hidraw's minor number. Reviewed-by: Benjamin Tissoires Signed-off-by: Jaejoong Kim Signed-off-by: Jiri Kosina --- drivers/hid/hid-cp2112.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index b22d0f83f8e3..078026f63b6f 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1297,7 +1298,8 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) dev->adap.algo_data = dev; dev->adap.dev.parent = &hdev->dev; snprintf(dev->adap.name, sizeof(dev->adap.name), - "CP2112 SMBus Bridge on hiddev%d", hdev->minor); + "CP2112 SMBus Bridge on hidraw%d", + ((struct hidraw *)hdev->hidraw)->minor); dev->hwversion = buf[2]; init_waitqueue_head(&dev->wait); -- cgit v1.2.3 From 733aca90300b76575b8a465dc49cbed7a991fd8b Mon Sep 17 00:00:00 2001 From: Jaejoong Kim Date: Fri, 3 Mar 2017 17:54:01 +0900 Subject: HID: hiddev: reallocate hiddev's minor number We need to store the minor number each drivers. In case of hidraw, the minor number is stored stores in struct hidraw. But hiddev's minor is located in struct hid_device. The hid-core driver announces a kernel message which driver is loaded when HID device connected, but hiddev's minor number is always zero. To proper display hiddev's minor number, we need to store the minor number asked from usb core and do some refactoring work (move from hiddev.c to hiddev.h) to access hiddev in hid-core. [jkosina@suse.cz: rebase on top of newer codebase] Reviewed-by: Benjamin Tissoires Signed-off-by: Jaejoong Kim Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 2 +- drivers/hid/usbhid/hiddev.c | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index e9e87d337446..1a0b91057484 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1695,7 +1695,7 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) len += sprintf(buf + len, "input"); if (hdev->claimed & HID_CLAIMED_HIDDEV) len += sprintf(buf + len, "%shiddev%d", len ? "," : "", - hdev->minor); + ((struct hiddev *)hdev->hiddev)->minor); if (hdev->claimed & HID_CLAIMED_HIDRAW) len += sprintf(buf + len, "%shidraw%d", len ? "," : "", ((struct hidraw *)hdev->hidraw)->minor); diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 667171829f65..a8baaf60e28a 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -46,17 +46,6 @@ #endif #define HIDDEV_BUFFER_SIZE 2048 -struct hiddev { - int exist; - int open; - struct mutex existancelock; - wait_queue_head_t wait; - struct hid_device *hid; - struct list_head list; - spinlock_t list_lock; - bool initialized; -}; - struct hiddev_list { struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE]; int head; @@ -923,6 +912,8 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) */ hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS; + hiddev->minor = usbhid->intf->minor; + return 0; } -- cgit v1.2.3 From ac34b970a9ec536c59faa8cdd5678daeae8b9dd3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Sun, 12 Mar 2017 19:56:08 +0100 Subject: HID: cp2112: select GPIOLIB_IRQCHIP instead of depending on it GPIOLIB_IRQCHIP is not visible to user, so we can't depend on it. Depend on GPIOLIB but select GPIOLIB_IRQCHIP. Signed-off-by: Bartosz Golaszewski Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1aeb80e52424..00e2809724bd 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -214,7 +214,8 @@ config HID_CMEDIA config HID_CP2112 tristate "Silicon Labs CP2112 HID USB-to-SMBus Bridge support" - depends on USB_HID && I2C && GPIOLIB && GPIOLIB_IRQCHIP + depends on USB_HID && I2C && GPIOLIB + select GPIOLIB_IRQCHIP ---help--- Support for Silicon Labs CP2112 HID USB to SMBus Master Bridge. This is a HID device driver which registers as an i2c adapter -- cgit v1.2.3 From 959d973e9890150342df76160d966ab1270208df Mon Sep 17 00:00:00 2001 From: Xiaolei Yu Date: Sat, 25 Mar 2017 14:04:58 +0800 Subject: HID: add two missing usages for digitizer They are part of HUTRR34 for multi-touch digitizers: 0x0E Device configuration CA 16.7 0x23 Device settings CL 16.7 Signed-off-by: Xiaolei Yu Signed-off-by: Jiri Kosina --- drivers/hid/hid-debug.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index acfb522a432a..5a0061c0ee87 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -140,9 +140,11 @@ static const struct hid_usage_entry hid_usage_table[] = { {0, 0x03, "LightPen"}, {0, 0x04, "TouchScreen"}, {0, 0x05, "TouchPad"}, + {0, 0x0e, "DeviceConfiguration"}, {0, 0x20, "Stylus"}, {0, 0x21, "Puck"}, {0, 0x22, "Finger"}, + {0, 0x23, "DeviceSettings"}, {0, 0x30, "TipPressure"}, {0, 0x31, "BarrelPressure"}, {0, 0x32, "InRange"}, -- cgit v1.2.3 From b79cbc55481ddc0b7cc68fb2072d93ee7eefbf8f Mon Sep 17 00:00:00 2001 From: Aaron Armstrong Skomra Date: Wed, 29 Mar 2017 13:07:36 -0700 Subject: HID: wacom: Bamboo One Medium does not have touch Commit 3b164a00a9fc ("HID: wacom: Cleanup unsupported device_type for BAMBOO_PT") cleaned up Bamboo devices which our driver falsely claimed had touch. Bamboo One Medium also does not have touch. Signed-off-by: Aaron Armstrong Skomra Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 68fb9e114a0b..f1360381c8f5 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -4158,7 +4158,7 @@ static const struct wacom_features wacom_features_0x300 = BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; static const struct wacom_features wacom_features_0x301 = { "Wacom Bamboo One M", 21648, 13530, 1023, 31, - BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; + BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; static const struct wacom_features wacom_features_0x302 = { "Wacom Intuos PT S", 15200, 9500, 1023, 31, INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16, -- cgit v1.2.3 From 8dba3026d5453aa6de00f8726fdcde45d5c57484 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:21 +0200 Subject: HID: logitech-dj: allow devices to request full pairing information Register 0xB5 should be handled specially no matter what function is used. This allows to retrieve the serial and the Quad ID from hid-logitech-hidpp directly. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-dj.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 5bc6d80d5be7..826fa1e1c8d9 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -692,8 +692,12 @@ static void logi_dj_ll_close(struct hid_device *hid) dbg_hid("%s:%s\n", __func__, hid->phys); } -static u8 unifying_name_query[] = {0x10, 0xff, 0x83, 0xb5, 0x40, 0x00, 0x00}; -static u8 unifying_name_answer[] = {0x11, 0xff, 0x83, 0xb5}; +/* + * Register 0xB5 is "pairing information". It is solely intended for the + * receiver, so do not overwrite the device index. + */ +static u8 unifying_pairing_query[] = {0x10, 0xff, 0x83, 0xb5}; +static u8 unifying_pairing_answer[] = {0x11, 0xff, 0x83, 0xb5}; static int logi_dj_ll_raw_request(struct hid_device *hid, unsigned char reportnum, __u8 *buf, @@ -712,9 +716,9 @@ static int logi_dj_ll_raw_request(struct hid_device *hid, /* special case where we should not overwrite * the device_index */ - if (count == 7 && !memcmp(buf, unifying_name_query, - sizeof(unifying_name_query))) - buf[4] |= djdev->device_index - 1; + if (count == 7 && !memcmp(buf, unifying_pairing_query, + sizeof(unifying_pairing_query))) + buf[4] = (buf[4] & 0xf0) | (djdev->device_index - 1); else buf[1] = djdev->device_index; return hid_hw_raw_request(djrcv_dev->hdev, reportnum, buf, @@ -911,9 +915,8 @@ static int logi_dj_hidpp_event(struct hid_device *hdev, /* special case were the device wants to know its unifying * name */ if (size == HIDPP_REPORT_LONG_LENGTH && - !memcmp(data, unifying_name_answer, - sizeof(unifying_name_answer)) && - ((data[4] & 0xF0) == 0x40)) + !memcmp(data, unifying_pairing_answer, + sizeof(unifying_pairing_answer))) device_index = (data[4] & 0x0F) + 1; else return false; -- cgit v1.2.3 From 3861e6ca305c5262f0493e9141f0c52ba066a447 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 27 Mar 2017 16:59:22 +0200 Subject: HID: logitech-hidpp: Add scope to battery Without a scope defined, UPower assumes that the battery provides power to the computer it's connected to, like a laptop battery or a UPS. Tested-by: Peter Hutterer Signed-off-by: Bastien Nocera Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4eeb550617bf..4aaf2378897f 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -761,6 +761,7 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, static enum power_supply_property hidpp_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_SCOPE, }; static int hidpp_battery_get_property(struct power_supply *psy, @@ -777,6 +778,9 @@ static int hidpp_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY: val->intval = hidpp->battery.level; break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; default: ret = -EINVAL; break; -- cgit v1.2.3 From 680de741e835d56e504997b613895058625291e9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:23 +0200 Subject: HID: logitech-hidpp: make sure we only register one battery per device Simple check to add, huge improvement :) Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4aaf2378897f..1cda29ec0efd 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -828,6 +828,9 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) { int ret; + if (hidpp->battery.ps) + return 0; + if (hidpp->protocol_major >= 2) { ret = hidpp20_initialize_battery(hidpp); if (ret == 0) -- cgit v1.2.3 From b4f8ce07b55fa30b5a8c0d2655f1c5cc4c0b9095 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:24 +0200 Subject: HID: logitech-hidpp: do not query the name through HID++ for 1.0 devices Unless they are connected through unifying, they don't support it, so remove one error in the logs. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1cda29ec0efd..4031405ac191 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2309,6 +2309,8 @@ static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) * Ask the receiver for its name. */ name = hidpp_get_unifying_name(hidpp); + else if (hidpp->protocol_major < 2) + return; else name = hidpp_get_device_name(hidpp); -- cgit v1.2.3 From 206d7c68e83e1add3aa67019ab95d98f87c6edd4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:25 +0200 Subject: HID: logitech-hidpp: create a capabilities bits field Do not pollute the quirks bits field which is public API with elements that are queried from the device. Move the 2 battery capabilities into the new field. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4031405ac191..4ee466c82502 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -62,11 +62,12 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) -#define HIDPP_QUIRK_HIDPP20_BATTERY BIT(25) -#define HIDPP_QUIRK_HIDPP10_BATTERY BIT(26) #define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT +#define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0) +#define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1) + /* * There are two hidpp protocols in use, the first version hidpp10 is known * as register access protocol or RAP, the second version hidpp20 is known as @@ -138,6 +139,7 @@ struct hidpp_device { struct input_dev *delayed_input; unsigned long quirks; + unsigned long capabilities; struct hidpp_battery battery; }; @@ -834,7 +836,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) if (hidpp->protocol_major >= 2) { ret = hidpp20_initialize_battery(hidpp); if (ret == 0) - hidpp->quirks |= HIDPP_QUIRK_HIDPP20_BATTERY; + hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY; } return ret; @@ -2283,7 +2285,7 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, if (ret != 0) return ret; - if (hidpp->quirks & HIDPP_QUIRK_HIDPP20_BATTERY) { + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { ret = hidpp20_battery_event(hidpp, data, size); if (ret != 0) return ret; -- cgit v1.2.3 From 843c624eef0c198d7052bd527613a5e7350593e4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:26 +0200 Subject: HID: logitech-hidpp: rework probe path for unifying devices Unifying devices are different from others because they can probed while not connected. So we need to talk to the receiver to get some extra information like the device name and the serial. Instead of having conditionals while attempting to read the device name from HID++ 2.0, have a special init path for them. Store the retrieved serial in hdev->uniq. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 86 +++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 19 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4ee466c82502..db15cfc622c4 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -62,6 +62,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) #define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) +#define HIDPP_QUIRK_UNIFYING BIT(25) #define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT @@ -394,14 +395,14 @@ static void hidpp_prefix_name(char **name, int name_length) #define HIDPP_GET_LONG_REGISTER 0x83 #define HIDPP_REG_PAIRING_INFORMATION 0xB5 -#define DEVICE_NAME 0x40 +#define HIDPP_EXTENDED_PAIRING 0x30 +#define HIDPP_DEVICE_NAME 0x40 -static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev) +static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev) { struct hidpp_report response; int ret; - /* hid-logitech-dj is in charge of setting the right device index */ - u8 params[1] = { DEVICE_NAME }; + u8 params[1] = { HIDPP_DEVICE_NAME }; char *name; int len; @@ -430,6 +431,54 @@ static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev) return name; } +static int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial) +{ + struct hidpp_report response; + int ret; + u8 params[1] = { HIDPP_EXTENDED_PAIRING }; + + ret = hidpp_send_rap_command_sync(hidpp, + REPORT_ID_HIDPP_SHORT, + HIDPP_GET_LONG_REGISTER, + HIDPP_REG_PAIRING_INFORMATION, + params, 1, &response); + if (ret) + return ret; + + /* + * We don't care about LE or BE, we will output it as a string + * with %4phD, so we need to keep the order. + */ + *serial = *((u32 *)&response.rap.params[1]); + return 0; +} + +static int hidpp_unifying_init(struct hidpp_device *hidpp) +{ + struct hid_device *hdev = hidpp->hid_dev; + const char *name; + u32 serial; + int ret; + + ret = hidpp_unifying_get_serial(hidpp, &serial); + if (ret) + return ret; + + snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD", + hdev->product, &serial); + dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq); + + name = hidpp_unifying_get_name(hidpp); + if (!name) + return -EIO; + + snprintf(hdev->name, sizeof(hdev->name), "%s", name); + dbg_hid("HID++ Unifying: Got name: %s\n", name); + + kfree(name); + return 0; +} + /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ @@ -2299,22 +2348,15 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; } -static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) +static void hidpp_overwrite_name(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); char *name; - if (use_unifying) - /* - * the device is connected through an Unifying receiver, and - * might not be already connected. - * Ask the receiver for its name. - */ - name = hidpp_get_unifying_name(hidpp); - else if (hidpp->protocol_major < 2) + if (hidpp->protocol_major < 2) return; - else - name = hidpp_get_device_name(hidpp); + + name = hidpp_get_device_name(hidpp); if (!name) { hid_err(hdev, "unable to retrieve the name of the device"); @@ -2456,6 +2498,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp->quirks = id->driver_data; + if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) + hidpp->quirks |= HIDPP_QUIRK_UNIFYING; + if (disable_raw_mode) { hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; @@ -2507,8 +2552,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Allow incoming packets */ hid_device_io_start(hdev); + if (hidpp->quirks & HIDPP_QUIRK_UNIFYING) + hidpp_unifying_init(hidpp); + connected = hidpp_is_connected(hidpp); - if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) { + atomic_set(&hidpp->connected, connected); + if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) { if (!connected) { ret = -ENODEV; hid_err(hdev, "Device not connected"); @@ -2517,10 +2566,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_info(hdev, "HID++ %u.%u device connected.\n", hidpp->protocol_major, hidpp->protocol_minor); - } - hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE); - atomic_set(&hidpp->connected, connected); + hidpp_overwrite_name(hdev); + } if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { ret = wtp_get_config(hidpp); -- cgit v1.2.3 From 187f2bba93816a300018ad7fb0d79175af0643d3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:27 +0200 Subject: HID: logitech-hidpp: retrieve the HID++ device name when available hidpp->name can't be null. Only HID++ 2.0 and above device supports the query. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index db15cfc622c4..b0d2fea7ec56 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2443,13 +2443,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp->protocol_major, hidpp->protocol_minor); } - hidpp_initialize_battery(hidpp); - - 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) { + if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) { name = hidpp_get_device_name(hidpp); if (!name) { hid_err(hdev, @@ -2465,6 +2459,12 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp->name = devm_name; } + hidpp_initialize_battery(hidpp); + + if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) + /* if HID created the input nodes for us, we can stop now */ + return; + input = hidpp_allocate_input(hdev); if (!input) { hid_err(hdev, "cannot allocate new input device: %d\n", ret); -- cgit v1.2.3 From 2936836f919af8d766a23fdf2bb945a0d74fa534 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:28 +0200 Subject: HID: logitech-hidpp: rework hidpp_connect_event() Looks like all users don't care about a disconnect. Simplify the various variant_connect() and put the connect state check at the beginning. For delayed input devices, make sure we go through all other connect values (protocol, battery) before bailing out. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index b0d2fea7ec56..81ebded3c8e8 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -1884,9 +1884,6 @@ static int wtp_connect(struct hid_device *hdev, bool connected) struct wtp_data *wd = hidpp->private_data; int ret; - if (!connected) - return 0; - if (!wd->x_size) { ret = wtp_get_config(hidpp); if (ret) { @@ -1954,9 +1951,6 @@ static int m560_send_config_command(struct hid_device *hdev, bool connected) hidpp_dev = hid_get_drvdata(hdev); - if (!connected) - return -ENODEV; - return hidpp_send_rap_command_sync( hidpp_dev, REPORT_ID_HIDPP_SHORT, @@ -2160,9 +2154,6 @@ 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; @@ -2414,6 +2405,9 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) struct input_dev *input; char *name, *devm_name; + if (!connected) + return; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_connect(hdev, connected); if (ret) @@ -2428,9 +2422,6 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } - if (!connected || hidpp->delayed_input) - return; - /* the device is already connected, we can ask for its name and * protocol */ if (!hidpp->protocol_major) { @@ -2461,8 +2452,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp_initialize_battery(hidpp); - if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) - /* if HID created the input nodes for us, we can stop now */ + if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input) + /* if the input nodes are already created, we can stop now */ return; input = hidpp_allocate_input(hdev); -- cgit v1.2.3 From eb626c573219233bfd330fef9066e7f9530b078e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:29 +0200 Subject: HID: logitech-hidpp: handle battery events in hidpp_raw_hidpp_event() Battery events are reported through HID++, so we need to be sure the report ID is the HID++ one. Without this, we might receive keyboard events that looks just like battery events with wrong data and which will confuse user space. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 81ebded3c8e8..3e2f716b446d 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2250,6 +2250,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, struct hidpp_report *question = hidpp->send_receive_buf; struct hidpp_report *answer = hidpp->send_receive_buf; struct hidpp_report *report = (struct hidpp_report *)data; + int ret; /* * If the mutex is locked then we have a pending answer from a @@ -2283,6 +2284,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return 1; } + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { + ret = hidpp20_battery_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -2325,12 +2332,6 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, if (ret != 0) return ret; - if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { - ret = hidpp20_battery_event(hidpp, data, size); - if (ret != 0) - return ret; - } - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) return wtp_raw_event(hdev, data, size); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) -- cgit v1.2.3 From 32043d0fdf9c5ef246e92f59c98624ed135b9e4e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:30 +0200 Subject: HID: logitech-hidpp: forward device info in power_supply Better forwarding the device name, manufacturer and serial to upower. Note that serial is still empty, it will be filled in a later patch in this series. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 3e2f716b446d..421c3740abcd 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -813,6 +813,9 @@ static enum power_supply_property hidpp_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, }; static int hidpp_battery_get_property(struct power_supply *psy, @@ -832,6 +835,18 @@ static int hidpp_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; + case POWER_SUPPLY_PROP_MODEL_NAME: + if (!strncmp(hidpp->name, "Logitech ", 9)) + val->strval = hidpp->name + 9; + else + val->strval = hidpp->name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Logitech"; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = hidpp->hid_dev->uniq; + break; default: ret = -EINVAL; break; -- cgit v1.2.3 From a52ec107fa81c8f799654b860e262f07bd14d63a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:31 +0200 Subject: HID: logitech-hidpp: create the battery for all types of HID++ devices The creation of the power_supply should not be in a HID++ 2.0 specific function. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 94 ++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 51 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 421c3740abcd..9a9771a31108 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -855,57 +855,6 @@ static int hidpp_battery_get_property(struct power_supply *psy, return ret; } -static int hidpp20_initialize_battery(struct hidpp_device *hidpp) -{ - static atomic_t battery_no = ATOMIC_INIT(0); - struct power_supply_config cfg = { .drv_data = hidpp }; - struct power_supply_desc *desc = &hidpp->battery.desc; - struct hidpp_battery *battery; - unsigned long n; - int ret; - - ret = hidpp20_query_battery_info(hidpp); - if (ret) - return ret; - - battery = &hidpp->battery; - - n = atomic_inc_return(&battery_no) - 1; - desc->properties = hidpp_battery_props; - desc->num_properties = ARRAY_SIZE(hidpp_battery_props); - desc->get_property = hidpp_battery_get_property; - sprintf(battery->name, "hidpp_battery_%ld", n); - desc->name = battery->name; - desc->type = POWER_SUPPLY_TYPE_BATTERY; - desc->use_for_apm = 0; - - battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev, - &battery->desc, - &cfg); - if (IS_ERR(battery->ps)) - return PTR_ERR(battery->ps); - - power_supply_powers(battery->ps, &hidpp->hid_dev->dev); - - return 0; -} - -static int hidpp_initialize_battery(struct hidpp_device *hidpp) -{ - int ret; - - if (hidpp->battery.ps) - return 0; - - if (hidpp->protocol_major >= 2) { - ret = hidpp20_initialize_battery(hidpp); - if (ret == 0) - hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY; - } - - return ret; -} - /* -------------------------------------------------------------------------- */ /* 0x6010: Touchpad FW items */ /* -------------------------------------------------------------------------- */ @@ -2355,6 +2304,49 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; } +static int hidpp_initialize_battery(struct hidpp_device *hidpp) +{ + static atomic_t battery_no = ATOMIC_INIT(0); + struct power_supply_config cfg = { .drv_data = hidpp }; + struct power_supply_desc *desc = &hidpp->battery.desc; + struct hidpp_battery *battery; + unsigned long n; + int ret; + + if (hidpp->battery.ps) + return 0; + + if (hidpp->protocol_major >= 2) { + ret = hidpp20_query_battery_info(hidpp); + if (ret) + return ret; + hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY; + } else { + return -ENOENT; + } + + battery = &hidpp->battery; + + n = atomic_inc_return(&battery_no) - 1; + desc->properties = hidpp_battery_props; + desc->num_properties = ARRAY_SIZE(hidpp_battery_props); + desc->get_property = hidpp_battery_get_property; + sprintf(battery->name, "hidpp_battery_%ld", n); + desc->name = battery->name; + desc->type = POWER_SUPPLY_TYPE_BATTERY; + desc->use_for_apm = 0; + + battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev, + &battery->desc, + &cfg); + if (IS_ERR(battery->ps)) + return PTR_ERR(battery->ps); + + power_supply_powers(battery->ps, &hidpp->hid_dev->dev); + + return ret; +} + static void hidpp_overwrite_name(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); -- cgit v1.2.3 From a9525b80feb1b6ae40244b16b0558cbdc64f28cd Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:32 +0200 Subject: HID: logitech-hidpp: return an error if the queried feature is not present Or the device just answers a valid feature '0'. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 9a9771a31108..22129ddac3ae 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -503,6 +503,9 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature, if (ret) return ret; + if (response.fap.params[0] == 0) + return -ENOENT; + *feature_index = response.fap.params[0]; *feature_type = response.fap.params[1]; -- cgit v1.2.3 From 9b9c519f1fe3ec9d2518a99c71c54f5c25eef345 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:33 +0200 Subject: HID: logitech-hidpp: notify battery on connect When a device reconnects, there is a high chance its power supply has been changed (for a battery replacement for instance). Just forward the battery state here. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 22129ddac3ae..0781d2bb00d6 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2463,6 +2463,13 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp_initialize_battery(hidpp); + /* forward current battery state */ + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { + hidpp20_query_battery_info(hidpp); + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input) /* if the input nodes are already created, we can stop now */ return; -- cgit v1.2.3 From 284f8d7592673a7a6dae96d082806d324378f212 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:34 +0200 Subject: HID: logitech-hidpp: battery: provide ONLINE property When ONLINE isn't set, upower should ignore the battery capacity, so there is no need to overload it with some random values. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 0781d2bb00d6..7e4445a3d7aa 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -120,6 +120,7 @@ struct hidpp_battery { char name[64]; int status; int level; + bool online; }; struct hidpp_device { @@ -686,7 +687,6 @@ static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, int *next_level) { int status; - int level_override; *level = data[0]; *next_level = data[1]; @@ -698,36 +698,28 @@ static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, switch (data[2]) { case 0: /* discharging (in use) */ status = POWER_SUPPLY_STATUS_DISCHARGING; - level_override = 0; break; case 1: /* recharging */ status = POWER_SUPPLY_STATUS_CHARGING; - level_override = 80; break; case 2: /* charge in final stage */ status = POWER_SUPPLY_STATUS_CHARGING; - level_override = 90; break; case 3: /* charge complete */ status = POWER_SUPPLY_STATUS_FULL; - level_override = 100; + *level = 100; break; case 4: /* recharging below optimal speed */ status = POWER_SUPPLY_STATUS_CHARGING; - level_override = 50; break; /* 5 = invalid battery type 6 = thermal error 7 = other charging error */ default: status = POWER_SUPPLY_STATUS_NOT_CHARGING; - level_override = 0; break; } - if (level_override != 0 && *level == 0) - *level = level_override; - return status; } @@ -781,6 +773,9 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) hidpp->battery.status = status; hidpp->battery.level = level; + /* the capacity is only available when discharging or full */ + hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || + status == POWER_SUPPLY_STATUS_FULL; return 0; } @@ -799,6 +794,10 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, status = hidpp20_batterylevel_map_status_level(report->fap.params, &level, &next_level); + /* the capacity is only available when discharging or full */ + hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || + status == POWER_SUPPLY_STATUS_FULL; + changed = level != hidpp->battery.level || status != hidpp->battery.status; @@ -813,6 +812,7 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, } static enum power_supply_property hidpp_battery_props[] = { + POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_SCOPE, @@ -838,6 +838,9 @@ static int hidpp_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = hidpp->battery.online; + break; case POWER_SUPPLY_PROP_MODEL_NAME: if (!strncmp(hidpp->name, "Logitech ", 9)) val->strval = hidpp->name + 9; @@ -2416,8 +2419,14 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) struct input_dev *input; char *name, *devm_name; - if (!connected) + if (!connected) { + if (hidpp->battery.ps) { + hidpp->battery.online = false; + hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN; + power_supply_changed(hidpp->battery.ps); + } return; + } if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_connect(hdev, connected); -- cgit v1.2.3 From 14f437a1d7b49a2e873f63436526f9aed3a781c3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:35 +0200 Subject: HID: logitech-hidpp: rename battery level into capacity The power_supply term for the percentage is capacity. Capacity level can be given when non accurate mileage is provided by the device, so better stick to the terms used in power_supply. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 55 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 7e4445a3d7aa..c59f7e5eedfa 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -119,7 +119,7 @@ struct hidpp_battery { struct power_supply *ps; char name[64]; int status; - int level; + int capacity; bool online; }; @@ -683,17 +683,16 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp) #define EVENT_BATTERY_LEVEL_STATUS_BROADCAST 0x00 -static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, - int *next_level) +static int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity, + int *next_capacity) { int status; - *level = data[0]; - *next_level = data[1]; + *capacity = data[0]; + *next_capacity = data[1]; - /* When discharging, we can rely on the device reported level. - * For all other states the device reports level 0 (unknown). Make up - * a number instead + /* When discharging, we can rely on the device reported capacity. + * For all other states the device reports 0 (unknown). */ switch (data[2]) { case 0: /* discharging (in use) */ @@ -707,7 +706,7 @@ static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, break; case 3: /* charge complete */ status = POWER_SUPPLY_STATUS_FULL; - *level = 100; + *capacity = 100; break; case 4: /* recharging below optimal speed */ status = POWER_SUPPLY_STATUS_CHARGING; @@ -723,11 +722,11 @@ static int hidpp20_batterylevel_map_status_level(u8 data[3], int *level, return status; } -static int hidpp20_batterylevel_get_battery_level(struct hidpp_device *hidpp, - u8 feature_index, - int *status, - int *level, - int *next_level) +static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp, + u8 feature_index, + int *status, + int *capacity, + int *next_capacity) { struct hidpp_report response; int ret; @@ -744,8 +743,8 @@ static int hidpp20_batterylevel_get_battery_level(struct hidpp_device *hidpp, if (ret) return ret; - *status = hidpp20_batterylevel_map_status_level(params, level, - next_level); + *status = hidpp20_batterylevel_map_status_capacity(params, capacity, + next_capacity); return 0; } @@ -754,7 +753,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) { u8 feature_type; int ret; - int status, level, next_level; + int status, capacity, next_capacity; if (hidpp->battery.feature_index == 0) { ret = hidpp_root_get_feature(hidpp, @@ -765,14 +764,15 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) return ret; } - ret = hidpp20_batterylevel_get_battery_level(hidpp, - hidpp->battery.feature_index, - &status, &level, &next_level); + ret = hidpp20_batterylevel_get_battery_capacity(hidpp, + hidpp->battery.feature_index, + &status, &capacity, + &next_capacity); if (ret) return ret; hidpp->battery.status = status; - hidpp->battery.level = level; + hidpp->battery.capacity = capacity; /* the capacity is only available when discharging or full */ hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || status == POWER_SUPPLY_STATUS_FULL; @@ -784,25 +784,26 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, u8 *data, int size) { struct hidpp_report *report = (struct hidpp_report *)data; - int status, level, next_level; + int status, capacity, next_capacity; bool changed; if (report->fap.feature_index != hidpp->battery.feature_index || report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST) return 0; - status = hidpp20_batterylevel_map_status_level(report->fap.params, - &level, &next_level); + status = hidpp20_batterylevel_map_status_capacity(report->fap.params, + &capacity, + &next_capacity); /* the capacity is only available when discharging or full */ hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || status == POWER_SUPPLY_STATUS_FULL; - changed = level != hidpp->battery.level || + changed = capacity != hidpp->battery.capacity || status != hidpp->battery.status; if (changed) { - hidpp->battery.level = level; + hidpp->battery.capacity = capacity; hidpp->battery.status = status; if (hidpp->battery.ps) power_supply_changed(hidpp->battery.ps); @@ -833,7 +834,7 @@ static int hidpp_battery_get_property(struct power_supply *psy, val->intval = hidpp->battery.status; break; case POWER_SUPPLY_PROP_CAPACITY: - val->intval = hidpp->battery.level; + val->intval = hidpp->battery.capacity; break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; -- cgit v1.2.3 From 5b036ea18e13e006e99cb197e9aceb09d897d20a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:36 +0200 Subject: HID: logitech-hidpp: battery: provide CAPACITY_LEVEL CAPACITY LEVEL allows to forward rough information on the battery mileage. HID++ 2.0 devices will either report percentage or levels, so better forwarding this information to the user space. The M325 supports only 2 levels: 'Full' and 'Critical'. With mileage, it will report either 90% or 5%, which might confuse users. With this change the battery will either report "Full" or "Critical". Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 104 +++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 10 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index c59f7e5eedfa..d7dc45801226 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -68,6 +68,8 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0) #define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1) +#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2) +#define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS BIT(3) /* * There are two hidpp protocols in use, the first version hidpp10 is known @@ -120,6 +122,7 @@ struct hidpp_battery { char name[64]; int status; int capacity; + int level; bool online; }; @@ -683,13 +686,30 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp) #define EVENT_BATTERY_LEVEL_STATUS_BROADCAST 0x00 +#define FLAG_BATTERY_LEVEL_DISABLE_OSD BIT(0) +#define FLAG_BATTERY_LEVEL_MILEAGE BIT(1) +#define FLAG_BATTERY_LEVEL_RECHARGEABLE BIT(2) + +static int hidpp_map_battery_level(int capacity) +{ + if (capacity < 11) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (capacity < 31) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (capacity < 81) + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; +} + static int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity, - int *next_capacity) + int *next_capacity, + int *level) { int status; *capacity = data[0]; *next_capacity = data[1]; + *level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; /* When discharging, we can rely on the device reported capacity. * For all other states the device reports 0 (unknown). @@ -697,6 +717,7 @@ static int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity, switch (data[2]) { case 0: /* discharging (in use) */ status = POWER_SUPPLY_STATUS_DISCHARGING; + *level = hidpp_map_battery_level(*capacity); break; case 1: /* recharging */ status = POWER_SUPPLY_STATUS_CHARGING; @@ -706,6 +727,7 @@ static int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity, break; case 3: /* charge complete */ status = POWER_SUPPLY_STATUS_FULL; + *level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; *capacity = 100; break; case 4: /* recharging below optimal speed */ @@ -726,7 +748,8 @@ static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp, u8 feature_index, int *status, int *capacity, - int *next_capacity) + int *next_capacity, + int *level) { struct hidpp_report response; int ret; @@ -744,7 +767,38 @@ static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp, return ret; *status = hidpp20_batterylevel_map_status_capacity(params, capacity, - next_capacity); + next_capacity, + level); + + return 0; +} + +static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp, + u8 feature_index) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + unsigned int level_count, flags; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY, + NULL, 0, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + level_count = params[0]; + flags = params[1]; + + if (level_count < 10 || !(flags & FLAG_BATTERY_LEVEL_MILEAGE)) + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS; + else + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE; return 0; } @@ -753,7 +807,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) { u8 feature_type; int ret; - int status, capacity, next_capacity; + int status, capacity, next_capacity, level; if (hidpp->battery.feature_index == 0) { ret = hidpp_root_get_feature(hidpp, @@ -767,12 +821,18 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) ret = hidpp20_batterylevel_get_battery_capacity(hidpp, hidpp->battery.feature_index, &status, &capacity, - &next_capacity); + &next_capacity, &level); + if (ret) + return ret; + + ret = hidpp20_batterylevel_get_battery_info(hidpp, + hidpp->battery.feature_index); if (ret) return ret; hidpp->battery.status = status; hidpp->battery.capacity = capacity; + hidpp->battery.level = level; /* the capacity is only available when discharging or full */ hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || status == POWER_SUPPLY_STATUS_FULL; @@ -784,7 +844,7 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, u8 *data, int size) { struct hidpp_report *report = (struct hidpp_report *)data; - int status, capacity, next_capacity; + int status, capacity, next_capacity, level; bool changed; if (report->fap.feature_index != hidpp->battery.feature_index || @@ -793,16 +853,19 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, status = hidpp20_batterylevel_map_status_capacity(report->fap.params, &capacity, - &next_capacity); + &next_capacity, + &level); /* the capacity is only available when discharging or full */ hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || status == POWER_SUPPLY_STATUS_FULL; changed = capacity != hidpp->battery.capacity || + level != hidpp->battery.level || status != hidpp->battery.status; if (changed) { + hidpp->battery.level = level; hidpp->battery.capacity = capacity; hidpp->battery.status = status; if (hidpp->battery.ps) @@ -815,11 +878,12 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, static enum power_supply_property hidpp_battery_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_SERIAL_NUMBER, + 0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */ + 0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */ }; static int hidpp_battery_get_property(struct power_supply *psy, @@ -836,6 +900,9 @@ static int hidpp_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY: val->intval = hidpp->battery.capacity; break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = hidpp->battery.level; + break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; @@ -2316,7 +2383,9 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) static atomic_t battery_no = ATOMIC_INIT(0); struct power_supply_config cfg = { .drv_data = hidpp }; struct power_supply_desc *desc = &hidpp->battery.desc; + enum power_supply_property *battery_props; struct hidpp_battery *battery; + unsigned int num_battery_props; unsigned long n; int ret; @@ -2332,11 +2401,25 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) return -ENOENT; } + battery_props = devm_kmemdup(&hidpp->hid_dev->dev, + hidpp_battery_props, + sizeof(hidpp_battery_props), + GFP_KERNEL); + num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 2; + + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE) + battery_props[num_battery_props++] = + POWER_SUPPLY_PROP_CAPACITY; + + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS) + battery_props[num_battery_props++] = + POWER_SUPPLY_PROP_CAPACITY_LEVEL; + battery = &hidpp->battery; n = atomic_inc_return(&battery_no) - 1; - desc->properties = hidpp_battery_props; - desc->num_properties = ARRAY_SIZE(hidpp_battery_props); + desc->properties = battery_props; + desc->num_properties = num_battery_props; desc->get_property = hidpp_battery_get_property; sprintf(battery->name, "hidpp_battery_%ld", n); desc->name = battery->name; @@ -2424,6 +2507,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) if (hidpp->battery.ps) { hidpp->battery.online = false; hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN; + hidpp->battery.level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; power_supply_changed(hidpp->battery.ps); } return; -- cgit v1.2.3 From 696ecef9b5874a312d74050525217f48d0f1b349 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:37 +0200 Subject: HID: logitech-hidpp: add support for battery status for the K750 The Solar Keyboard uses a different feature to report the battery level. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 115 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d7dc45801226..bb40e5d3a73e 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -56,6 +56,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_CLASS_M560 BIT(1) #define HIDPP_QUIRK_CLASS_K400 BIT(2) #define HIDPP_QUIRK_CLASS_G920 BIT(3) +#define HIDPP_QUIRK_CLASS_K750 BIT(4) /* bits 2..20 are reserved for classes */ /* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */ @@ -117,6 +118,7 @@ struct hidpp_report { struct hidpp_battery { u8 feature_index; + u8 solar_feature_index; struct power_supply_desc desc; struct power_supply *ps; char name[64]; @@ -809,7 +811,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) int ret; int status, capacity, next_capacity, level; - if (hidpp->battery.feature_index == 0) { + if (hidpp->battery.feature_index == 0xff) { ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_LEVEL_STATUS, &hidpp->battery.feature_index, @@ -929,6 +931,101 @@ static int hidpp_battery_get_property(struct power_supply *psy, return ret; } +/* -------------------------------------------------------------------------- */ +/* 0x4301: Solar Keyboard */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_SOLAR_KEYBOARD 0x4301 + +#define CMD_SOLAR_SET_LIGHT_MEASURE 0x00 + +#define EVENT_SOLAR_BATTERY_BROADCAST 0x00 +#define EVENT_SOLAR_BATTERY_LIGHT_MEASURE 0x10 +#define EVENT_SOLAR_CHECK_LIGHT_BUTTON 0x20 + +static int hidpp_solar_request_battery_event(struct hidpp_device *hidpp) +{ + struct hidpp_report response; + u8 params[2] = { 1, 1 }; + u8 feature_type; + int ret; + + if (hidpp->battery.feature_index == 0xff) { + ret = hidpp_root_get_feature(hidpp, + HIDPP_PAGE_SOLAR_KEYBOARD, + &hidpp->battery.solar_feature_index, + &feature_type); + if (ret) + return ret; + } + + ret = hidpp_send_fap_command_sync(hidpp, + hidpp->battery.solar_feature_index, + CMD_SOLAR_SET_LIGHT_MEASURE, + params, 2, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE; + + return 0; +} + +static int hidpp_solar_battery_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + int capacity, lux, status; + u8 function; + + function = report->fap.funcindex_clientid; + + + if (report->fap.feature_index != hidpp->battery.solar_feature_index || + !(function == EVENT_SOLAR_BATTERY_BROADCAST || + function == EVENT_SOLAR_BATTERY_LIGHT_MEASURE || + function == EVENT_SOLAR_CHECK_LIGHT_BUTTON)) + return 0; + + capacity = report->fap.params[0]; + + switch (function) { + case EVENT_SOLAR_BATTERY_LIGHT_MEASURE: + lux = (report->fap.params[1] << 8) | report->fap.params[2]; + if (lux > 200) + status = POWER_SUPPLY_STATUS_CHARGING; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case EVENT_SOLAR_CHECK_LIGHT_BUTTON: + default: + if (capacity < hidpp->battery.capacity) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + + } + + if (capacity == 100) + status = POWER_SUPPLY_STATUS_FULL; + + hidpp->battery.online = true; + if (capacity != hidpp->battery.capacity || + status != hidpp->battery.status) { + hidpp->battery.capacity = capacity; + hidpp->battery.status = status; + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + + return 0; +} + /* -------------------------------------------------------------------------- */ /* 0x6010: Touchpad FW items */ /* -------------------------------------------------------------------------- */ @@ -2326,6 +2423,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, ret = hidpp20_battery_event(hidpp, data, size); if (ret != 0) return ret; + ret = hidpp_solar_battery_event(hidpp, data, size); + if (ret != 0) + return ret; } return 0; @@ -2392,8 +2492,15 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) if (hidpp->battery.ps) return 0; + hidpp->battery.feature_index = 0xff; + hidpp->battery.solar_feature_index = 0xff; + if (hidpp->protocol_major >= 2) { - ret = hidpp20_query_battery_info(hidpp); + if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750) + ret = hidpp_solar_request_battery_event(hidpp); + else + ret = hidpp20_query_battery_info(hidpp); + if (ret) return ret; hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY; @@ -2751,6 +2858,10 @@ static const struct hid_device_id hidpp_devices[] = { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, 0x4024), .driver_data = HIDPP_QUIRK_CLASS_K400 }, + { /* Solar Keyboard Logitech K750 */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x4002), + .driver_data = HIDPP_QUIRK_CLASS_K750 }, { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, -- cgit v1.2.3 From 7f7ce2a258b47f9510ad613328c046a3ff9426b0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:38 +0200 Subject: HID: logitech-hidpp: enable HID++ 1.0 battery reporting Also enable battery reporting for HID++ 1.0 devices through 2 registers: 0x07: battery status -> reports only 4 levels (critical, low, good, full) 0x0D: battery mileage -> reports true pourcentage Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 234 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 4 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index bb40e5d3a73e..eb4339e91f23 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -400,6 +400,211 @@ static void hidpp_prefix_name(char **name, int name_length) #define HIDPP_SET_LONG_REGISTER 0x82 #define HIDPP_GET_LONG_REGISTER 0x83 +#define HIDPP_REG_GENERAL 0x00 + +static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev) +{ + struct hidpp_report response; + int ret; + u8 params[3] = { 0 }; + + ret = hidpp_send_rap_command_sync(hidpp_dev, + REPORT_ID_HIDPP_SHORT, + HIDPP_GET_REGISTER, + HIDPP_REG_GENERAL, + NULL, 0, &response); + if (ret) + return ret; + + memcpy(params, response.rap.params, 3); + + /* Set the battery bit */ + params[0] |= BIT(4); + + return hidpp_send_rap_command_sync(hidpp_dev, + REPORT_ID_HIDPP_SHORT, + HIDPP_SET_REGISTER, + HIDPP_REG_GENERAL, + params, 3, &response); +} + +#define HIDPP_REG_BATTERY_STATUS 0x07 + +static int hidpp10_battery_status_map_level(u8 param) +{ + int level; + + switch (param) { + case 1 ... 2: + level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + break; + case 3 ... 4: + level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + break; + case 5 ... 6: + level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + break; + case 7: + level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + break; + default: + level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + } + + return level; +} + +static int hidpp10_battery_status_map_status(u8 param) +{ + int status; + + switch (param) { + case 0x00: + /* discharging (in use) */ + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 0x21: /* (standard) charging */ + case 0x24: /* fast charging */ + case 0x25: /* slow charging */ + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x26: /* topping charge */ + case 0x22: /* charge complete */ + status = POWER_SUPPLY_STATUS_FULL; + break; + case 0x20: /* unknown */ + status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + /* + * 0x01...0x1F = reserved (not charging) + * 0x23 = charging error + * 0x27..0xff = reserved + */ + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + return status; +} + +static int hidpp10_query_battery_status(struct hidpp_device *hidpp) +{ + struct hidpp_report response; + int ret, status; + + ret = hidpp_send_rap_command_sync(hidpp, + REPORT_ID_HIDPP_SHORT, + HIDPP_GET_REGISTER, + HIDPP_REG_BATTERY_STATUS, + NULL, 0, &response); + if (ret) + return ret; + + hidpp->battery.level = + hidpp10_battery_status_map_level(response.rap.params[0]); + status = hidpp10_battery_status_map_status(response.rap.params[1]); + hidpp->battery.status = status; + /* the capacity is only available when discharging or full */ + hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || + status == POWER_SUPPLY_STATUS_FULL; + + return 0; +} + +#define HIDPP_REG_BATTERY_MILEAGE 0x0D + +static int hidpp10_battery_mileage_map_status(u8 param) +{ + int status; + + switch (param >> 6) { + case 0x00: + /* discharging (in use) */ + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 0x01: /* charging */ + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x02: /* charge complete */ + status = POWER_SUPPLY_STATUS_FULL; + break; + /* + * 0x03 = charging error + */ + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + return status; +} + +static int hidpp10_query_battery_mileage(struct hidpp_device *hidpp) +{ + struct hidpp_report response; + int ret, status; + + ret = hidpp_send_rap_command_sync(hidpp, + REPORT_ID_HIDPP_SHORT, + HIDPP_GET_REGISTER, + HIDPP_REG_BATTERY_MILEAGE, + NULL, 0, &response); + if (ret) + return ret; + + hidpp->battery.capacity = response.rap.params[0]; + status = hidpp10_battery_mileage_map_status(response.rap.params[2]); + hidpp->battery.status = status; + /* the capacity is only available when discharging or full */ + hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || + status == POWER_SUPPLY_STATUS_FULL; + + return 0; +} + +static int hidpp10_battery_event(struct hidpp_device *hidpp, u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + int status, capacity, level; + bool changed; + + if (report->report_id != REPORT_ID_HIDPP_SHORT) + return 0; + + switch (report->rap.sub_id) { + case HIDPP_REG_BATTERY_STATUS: + capacity = hidpp->battery.capacity; + level = hidpp10_battery_status_map_level(report->rawbytes[1]); + status = hidpp10_battery_status_map_status(report->rawbytes[2]); + break; + case HIDPP_REG_BATTERY_MILEAGE: + capacity = report->rap.params[0]; + level = hidpp->battery.level; + status = hidpp10_battery_mileage_map_status(report->rawbytes[3]); + break; + default: + return 0; + } + + changed = capacity != hidpp->battery.capacity || + level != hidpp->battery.level || + status != hidpp->battery.status; + + /* the capacity is only available when discharging or full */ + hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING || + status == POWER_SUPPLY_STATUS_FULL; + + if (changed) { + hidpp->battery.level = level; + hidpp->battery.status = status; + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + + return 0; +} + #define HIDPP_REG_PAIRING_INFORMATION 0xB5 #define HIDPP_EXTENDED_PAIRING 0x30 #define HIDPP_DEVICE_NAME 0x40 @@ -2428,6 +2633,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) { + ret = hidpp10_battery_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -2505,7 +2716,16 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) return ret; hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY; } else { - return -ENOENT; + ret = hidpp10_query_battery_status(hidpp); + if (ret) { + ret = hidpp10_query_battery_mileage(hidpp); + if (ret) + return -ENOENT; + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE; + } else { + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS; + } + hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_BATTERY; } battery_props = devm_kmemdup(&hidpp->hid_dev->dev, @@ -2665,11 +2885,17 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp_initialize_battery(hidpp); /* forward current battery state */ - if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) { + hidpp10_enable_battery_reporting(hidpp); + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE) + hidpp10_query_battery_mileage(hidpp); + else + hidpp10_query_battery_status(hidpp); + } else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { hidpp20_query_battery_info(hidpp); - if (hidpp->battery.ps) - power_supply_changed(hidpp->battery.ps); } + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input) /* if the input nodes are already created, we can stop now */ -- cgit v1.2.3 From a4bf6153b317754e058ad9c7f5f02367e0bfdcc8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 27 Mar 2017 16:59:39 +0200 Subject: HID: logitech-hidpp: add a sysfs file to tell we support power_supply This way, upower can add a simple udev rule to decide whether or not it should use the internal unifying support or just the generic kernel one. Signed-off-by: Benjamin Tissoires Tested-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index eb4339e91f23..41b39464ded8 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2916,6 +2916,17 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hidpp->delayed_input = input; } +static DEVICE_ATTR(builtin_power_supply, 0000, NULL, NULL); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_builtin_power_supply.attr, + NULL +}; + +static struct attribute_group ps_attribute_group = { + .attrs = sysfs_attrs +}; + static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hidpp_device *hidpp; @@ -2960,6 +2971,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) mutex_init(&hidpp->send_mutex); init_waitqueue_head(&hidpp->wait); + /* indicates we are handling the battery properties in the kernel */ + ret = sysfs_create_group(&hdev->dev.kobj, &ps_attribute_group); + if (ret) + hid_warn(hdev, "Cannot allocate sysfs group for %s\n", + hdev->name); + ret = hid_parse(hdev); if (ret) { hid_err(hdev, "%s:parse failed\n", __func__); @@ -3042,6 +3059,7 @@ hid_hw_open_failed: } hid_hw_start_fail: hid_parse_fail: + sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); allocate_fail: @@ -3053,6 +3071,8 @@ static void hidpp_remove(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { hidpp_ff_deinit(hdev); hid_hw_close(hdev); -- cgit v1.2.3 From ed1fa736839eb97b1d066e36150df28251095eef Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Tue, 4 Apr 2017 12:31:07 -0700 Subject: HID: wacom: generic: sync pad events only for actual packets Commits d793ff8 and 4082da8 introduced two pad usages which do not actually send pad input events. To make sure we do not post empty pad packets, pad_input_event_flag is introduced. Turn on the flag for real pad input events so we can synchronize them properly. Signed-off-by: Ping Cheng Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 7 ++++++- drivers/hid/wacom_wac.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index f1360381c8f5..037a9962d053 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1881,6 +1881,8 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field /* fall through*/ default: input_event(input, usage->type, usage->code, value); + if (value) + wacom_wac->hid_data.pad_input_event_flag = true; break; } } @@ -1921,9 +1923,12 @@ static void wacom_wac_pad_report(struct hid_device *hdev, bool active = wacom_wac->hid_data.inrange_state != 0; /* report prox for expresskey events */ - if (wacom_equivalent_usage(report->field[0]->physical) == HID_DG_TABLETFUNCTIONKEY) { + if ((wacom_equivalent_usage(report->field[0]->physical) == HID_DG_TABLETFUNCTIONKEY) && + wacom_wac->hid_data.pad_input_event_flag) { input_event(input, EV_ABS, ABS_MISC, active ? PAD_DEVICE_ID : 0); input_sync(input); + if (!active) + wacom_wac->hid_data.pad_input_event_flag = false; } } diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 839bd4b6388c..570d29582b82 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -301,6 +301,7 @@ struct hid_data { int bat_charging; int bat_connected; int ps_connected; + bool pad_input_event_flag; }; struct wacom_remote_data { -- cgit v1.2.3 From 149f6f6b8ff3288a88dc34755de7719637cc8cc4 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Fri, 31 Mar 2017 10:02:05 -0700 Subject: HID: wacom: Move wacom_remote_irq and wacom_remote_status_irq These two functions awkwardly break up the otherwise-contiguous chunk of related Intuos IRQ functions with a 500 line tangent about the operation of the EKR. Their presence makes it difficult to read/navigate through the the Intuos code. Since there is no dependency between these functions, it is possible to simply move them down somewhat. This commit moves them to be after the final Intuos IRQ function. Signed-off-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 250 ++++++++++++++++++++++++------------------------ 1 file changed, 125 insertions(+), 125 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 037a9962d053..6b8f6b816195 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -773,131 +773,6 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) return 0; } -static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) -{ - unsigned char *data = wacom_wac->data; - struct input_dev *input; - struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); - struct wacom_remote *remote = wacom->remote; - int bat_charging, bat_percent, touch_ring_mode; - __u32 serial; - int i, index = -1; - unsigned long flags; - - if (data[0] != WACOM_REPORT_REMOTE) { - hid_dbg(wacom->hdev, "%s: received unknown report #%d", - __func__, data[0]); - return 0; - } - - serial = data[3] + (data[4] << 8) + (data[5] << 16); - wacom_wac->id[0] = PAD_DEVICE_ID; - - spin_lock_irqsave(&remote->remote_lock, flags); - - for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (remote->remotes[i].serial == serial) { - index = i; - break; - } - } - - if (index < 0 || !remote->remotes[index].registered) - goto out; - - input = remote->remotes[index].input; - - input_report_key(input, BTN_0, (data[9] & 0x01)); - input_report_key(input, BTN_1, (data[9] & 0x02)); - input_report_key(input, BTN_2, (data[9] & 0x04)); - input_report_key(input, BTN_3, (data[9] & 0x08)); - input_report_key(input, BTN_4, (data[9] & 0x10)); - input_report_key(input, BTN_5, (data[9] & 0x20)); - input_report_key(input, BTN_6, (data[9] & 0x40)); - input_report_key(input, BTN_7, (data[9] & 0x80)); - - input_report_key(input, BTN_8, (data[10] & 0x01)); - input_report_key(input, BTN_9, (data[10] & 0x02)); - input_report_key(input, BTN_A, (data[10] & 0x04)); - input_report_key(input, BTN_B, (data[10] & 0x08)); - input_report_key(input, BTN_C, (data[10] & 0x10)); - input_report_key(input, BTN_X, (data[10] & 0x20)); - input_report_key(input, BTN_Y, (data[10] & 0x40)); - input_report_key(input, BTN_Z, (data[10] & 0x80)); - - input_report_key(input, BTN_BASE, (data[11] & 0x01)); - input_report_key(input, BTN_BASE2, (data[11] & 0x02)); - - if (data[12] & 0x80) - input_report_abs(input, ABS_WHEEL, (data[12] & 0x7f)); - else - input_report_abs(input, ABS_WHEEL, 0); - - bat_percent = data[7] & 0x7f; - bat_charging = !!(data[7] & 0x80); - - if (data[9] | data[10] | (data[11] & 0x03) | data[12]) - input_report_abs(input, ABS_MISC, PAD_DEVICE_ID); - else - input_report_abs(input, ABS_MISC, 0); - - input_event(input, EV_MSC, MSC_SERIAL, serial); - - input_sync(input); - - /*Which mode select (LED light) is currently on?*/ - touch_ring_mode = (data[11] & 0xC0) >> 6; - - for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (remote->remotes[i].serial == serial) - wacom->led.groups[i].select = touch_ring_mode; - } - - __wacom_notify_battery(&remote->remotes[index].battery, bat_percent, - bat_charging, 1, bat_charging); - -out: - spin_unlock_irqrestore(&remote->remote_lock, flags); - return 0; -} - -static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) -{ - struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); - unsigned char *data = wacom_wac->data; - struct wacom_remote *remote = wacom->remote; - struct wacom_remote_data remote_data; - unsigned long flags; - int i, ret; - - if (data[0] != WACOM_REPORT_DEVICE_LIST) - return; - - memset(&remote_data, 0, sizeof(struct wacom_remote_data)); - - for (i = 0; i < WACOM_MAX_REMOTES; i++) { - int j = i * 6; - int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4]; - bool connected = data[j+2]; - - remote_data.remote[i].serial = serial; - remote_data.remote[i].connected = connected; - } - - spin_lock_irqsave(&remote->remote_lock, flags); - - ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data)); - if (ret != sizeof(remote_data)) { - spin_unlock_irqrestore(&remote->remote_lock, flags); - hid_err(wacom->hdev, "Can't queue Remote status event.\n"); - return; - } - - spin_unlock_irqrestore(&remote->remote_lock, flags); - - wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); -} - static inline bool report_touch_events(struct wacom_wac *wacom) { return (touch_arbitration ? !wacom->shared->stylus_in_proximity : 1); @@ -1116,6 +991,131 @@ static int wacom_intuos_irq(struct wacom_wac *wacom) return 0; } +static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) +{ + unsigned char *data = wacom_wac->data; + struct input_dev *input; + struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); + struct wacom_remote *remote = wacom->remote; + int bat_charging, bat_percent, touch_ring_mode; + __u32 serial; + int i, index = -1; + unsigned long flags; + + if (data[0] != WACOM_REPORT_REMOTE) { + hid_dbg(wacom->hdev, "%s: received unknown report #%d", + __func__, data[0]); + return 0; + } + + serial = data[3] + (data[4] << 8) + (data[5] << 16); + wacom_wac->id[0] = PAD_DEVICE_ID; + + spin_lock_irqsave(&remote->remote_lock, flags); + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + if (remote->remotes[i].serial == serial) { + index = i; + break; + } + } + + if (index < 0 || !remote->remotes[index].registered) + goto out; + + input = remote->remotes[index].input; + + input_report_key(input, BTN_0, (data[9] & 0x01)); + input_report_key(input, BTN_1, (data[9] & 0x02)); + input_report_key(input, BTN_2, (data[9] & 0x04)); + input_report_key(input, BTN_3, (data[9] & 0x08)); + input_report_key(input, BTN_4, (data[9] & 0x10)); + input_report_key(input, BTN_5, (data[9] & 0x20)); + input_report_key(input, BTN_6, (data[9] & 0x40)); + input_report_key(input, BTN_7, (data[9] & 0x80)); + + input_report_key(input, BTN_8, (data[10] & 0x01)); + input_report_key(input, BTN_9, (data[10] & 0x02)); + input_report_key(input, BTN_A, (data[10] & 0x04)); + input_report_key(input, BTN_B, (data[10] & 0x08)); + input_report_key(input, BTN_C, (data[10] & 0x10)); + input_report_key(input, BTN_X, (data[10] & 0x20)); + input_report_key(input, BTN_Y, (data[10] & 0x40)); + input_report_key(input, BTN_Z, (data[10] & 0x80)); + + input_report_key(input, BTN_BASE, (data[11] & 0x01)); + input_report_key(input, BTN_BASE2, (data[11] & 0x02)); + + if (data[12] & 0x80) + input_report_abs(input, ABS_WHEEL, (data[12] & 0x7f)); + else + input_report_abs(input, ABS_WHEEL, 0); + + bat_percent = data[7] & 0x7f; + bat_charging = !!(data[7] & 0x80); + + if (data[9] | data[10] | (data[11] & 0x03) | data[12]) + input_report_abs(input, ABS_MISC, PAD_DEVICE_ID); + else + input_report_abs(input, ABS_MISC, 0); + + input_event(input, EV_MSC, MSC_SERIAL, serial); + + input_sync(input); + + /*Which mode select (LED light) is currently on?*/ + touch_ring_mode = (data[11] & 0xC0) >> 6; + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + if (remote->remotes[i].serial == serial) + wacom->led.groups[i].select = touch_ring_mode; + } + + __wacom_notify_battery(&remote->remotes[index].battery, bat_percent, + bat_charging, 1, bat_charging); + +out: + spin_unlock_irqrestore(&remote->remote_lock, flags); + return 0; +} + +static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) +{ + struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); + unsigned char *data = wacom_wac->data; + struct wacom_remote *remote = wacom->remote; + struct wacom_remote_data remote_data; + unsigned long flags; + int i, ret; + + if (data[0] != WACOM_REPORT_DEVICE_LIST) + return; + + memset(&remote_data, 0, sizeof(struct wacom_remote_data)); + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + int j = i * 6; + int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4]; + bool connected = data[j+2]; + + remote_data.remote[i].serial = serial; + remote_data.remote[i].connected = connected; + } + + spin_lock_irqsave(&remote->remote_lock, flags); + + ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data)); + if (ret != sizeof(remote_data)) { + spin_unlock_irqrestore(&remote->remote_lock, flags); + hid_err(wacom->hdev, "Can't queue Remote status event.\n"); + return; + } + + spin_unlock_irqrestore(&remote->remote_lock, flags); + + wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); +} + static int int_dist(int x1, int y1, int x2, int y2) { int x = x2 - x1; -- cgit v1.2.3