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 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 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 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 From 765a1077c85e5f2efcc43582f80caf43a052e903 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Wed, 8 Feb 2017 13:58:43 -0500 Subject: HID: sony: Use LED_CORE_SUSPENDRESUME The LED subsystem provides the LED_CORE_SUSPENDRESUME flag to handle automatically turning off and restoring the state of device LEDs during suspend/resume. Use this flag instead of saving and restoring the state locally. Signed-off-by: Frank Praznik Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 45 +++++++++++++++------------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index f405b07d0381..7e2bae309671 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1095,7 +1095,6 @@ struct sony_sc { u8 battery_charging; u8 battery_capacity; u8 led_state[MAX_LEDS]; - u8 resume_led_state[MAX_LEDS]; u8 led_delay_on[MAX_LEDS]; u8 led_delay_off[MAX_LEDS]; u8 led_count; @@ -1964,6 +1963,7 @@ static int sony_leds_init(struct sony_sc *sc) led->name = name; led->brightness = sc->led_state[n]; led->max_brightness = max_brightness[n]; + led->flags = LED_CORE_SUSPENDRESUME; led->brightness_get = sony_led_get_brightness; led->brightness_set = sony_led_set_brightness; @@ -2734,47 +2734,32 @@ static void sony_remove(struct hid_device *hdev) static int sony_suspend(struct hid_device *hdev, pm_message_t message) { - /* - * On suspend save the current LED state, - * stop running force-feedback and blank the LEDS. - */ - if (SONY_LED_SUPPORT || SONY_FF_SUPPORT) { - struct sony_sc *sc = hid_get_drvdata(hdev); - #ifdef CONFIG_SONY_FF - sc->left = sc->right = 0; -#endif - memcpy(sc->resume_led_state, sc->led_state, - sizeof(sc->resume_led_state)); - memset(sc->led_state, 0, sizeof(sc->led_state)); + /* On suspend stop any running force-feedback events */ + if (SONY_FF_SUPPORT) { + struct sony_sc *sc = hid_get_drvdata(hdev); + sc->left = sc->right = 0; sony_send_output_report(sc); } +#endif return 0; } static int sony_resume(struct hid_device *hdev) { - /* Restore the state of controller LEDs on resume */ - if (SONY_LED_SUPPORT) { - struct sony_sc *sc = hid_get_drvdata(hdev); - - memcpy(sc->led_state, sc->resume_led_state, - sizeof(sc->led_state)); - - /* - * The Sixaxis and navigation controllers on USB need to be - * reinitialized on resume or they won't behave properly. - */ - if ((sc->quirks & SIXAXIS_CONTROLLER_USB) || - (sc->quirks & NAVIGATION_CONTROLLER_USB)) { - sixaxis_set_operational_usb(sc->hdev); - sc->defer_initialization = 1; - } + struct sony_sc *sc = hid_get_drvdata(hdev); - sony_set_leds(sc); + /* + * The Sixaxis and navigation controllers on USB need to be + * reinitialized on resume or they won't behave properly. + */ + if ((sc->quirks & SIXAXIS_CONTROLLER_USB) || + (sc->quirks & NAVIGATION_CONTROLLER_USB)) { + sixaxis_set_operational_usb(sc->hdev); + sc->defer_initialization = 1; } return 0; -- cgit 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 From 3f3752705dbd50b66b66ad7b4d54fe33d2f746ed Mon Sep 17 00:00:00 2001 From: Valtteri Heikkilä 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 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 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 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 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 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 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 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 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 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 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 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: Tue, 7 Mar 2017 15:45:00 -0800 Subject: HID: sony: Report DS4 motion sensors through a separate device The DS4 motion sensors are currently mapped by the hid-core driver to non-existing axes in between ABS_MISC and ABS_MT_SLOT, because the device already exhausted ABS_X-ABS_RZ. For a part the mapping by hid-core is accomplished by a fixup in hid-sony as the motion axes actually use vendor specific usage pages. This patch makes the DS4 use a separate input device for the motion sensors and reports acceleration data through ABS_X-ABS_Z and gyroscope data through ABS_RX-ABS_RZ. In addition it extends the event spec to allow gyroscope data through ABS_RX-ABS_RZ when INPUT_PROP_ACCELEROMETER is set. This change was suggested by Peter Hutterer during a discussion on linux-input. [jkosina@suse.cz: rebase onto slightly newer codebase] Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 397 +++++++++++++------------------------------------ 1 file changed, 105 insertions(+), 292 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 7e2bae309671..17df165133ab 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -344,265 +344,6 @@ static u8 navigation_rdesc[] = { 0xC0 /* End Collection */ }; -/* - * The default descriptor doesn't provide mapping for the accelerometers - * or orientation sensors. This fixed descriptor maps the accelerometers - * to usage values 0x40, 0x41 and 0x42 and maps the orientation sensors - * to usage values 0x43, 0x44 and 0x45. - */ -static u8 dualshock4_usb_rdesc[] = { - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x05, /* Usage (Gamepad), */ - 0xA1, 0x01, /* Collection (Application), */ - 0x85, 0x01, /* Report ID (1), */ - 0x09, 0x30, /* Usage (X), */ - 0x09, 0x31, /* Usage (Y), */ - 0x09, 0x32, /* Usage (Z), */ - 0x09, 0x35, /* Usage (Rz), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x04, /* Report Count (4), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x39, /* Usage (Hat Switch), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x07, /* Logical Maximum (7), */ - 0x35, 0x00, /* Physical Minimum (0), */ - 0x46, 0x3B, 0x01, /* Physical Maximum (315), */ - 0x65, 0x14, /* Unit (Degrees), */ - 0x75, 0x04, /* Report Size (4), */ - 0x95, 0x01, /* Report Count (1), */ - 0x81, 0x42, /* Input (Variable, Null State), */ - 0x65, 0x00, /* Unit, */ - 0x05, 0x09, /* Usage Page (Button), */ - 0x19, 0x01, /* Usage Minimum (01h), */ - 0x29, 0x0D, /* Usage Maximum (0Dh), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x01, /* Logical Maximum (1), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x0E, /* Report Count (14), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x09, 0x20, /* Usage (20h), */ - 0x75, 0x06, /* Report Size (6), */ - 0x95, 0x01, /* Report Count (1), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x3F, /* Logical Maximum (63), */ - 0x81, 0x02, /* Input (Variable), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x33, /* Usage (Rx), */ - 0x09, 0x34, /* Usage (Ry), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x02, /* Report Count (2), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x09, 0x21, /* Usage (21h), */ - 0x95, 0x03, /* Report Count (3), */ - 0x81, 0x02, /* Input (Variable), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x19, 0x40, /* Usage Minimum (40h), */ - 0x29, 0x42, /* Usage Maximum (42h), */ - 0x16, 0x00, 0x80, /* Logical Minimum (-32768), */ - 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ - 0x75, 0x10, /* Report Size (16), */ - 0x95, 0x03, /* Report Count (3), */ - 0x81, 0x02, /* Input (Variable), */ - 0x19, 0x43, /* Usage Minimum (43h), */ - 0x29, 0x45, /* Usage Maximum (45h), */ - 0x16, 0x00, 0x80, /* Logical Minimum (-32768), */ - 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ - 0x95, 0x03, /* Report Count (3), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x09, 0x21, /* Usage (21h), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x27, /* Report Count (39), */ - 0x81, 0x02, /* Input (Variable), */ - 0x85, 0x05, /* Report ID (5), */ - 0x09, 0x22, /* Usage (22h), */ - 0x95, 0x1F, /* Report Count (31), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x04, /* Report ID (4), */ - 0x09, 0x23, /* Usage (23h), */ - 0x95, 0x24, /* Report Count (36), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x02, /* Report ID (2), */ - 0x09, 0x24, /* Usage (24h), */ - 0x95, 0x24, /* Report Count (36), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x08, /* Report ID (8), */ - 0x09, 0x25, /* Usage (25h), */ - 0x95, 0x03, /* Report Count (3), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x10, /* Report ID (16), */ - 0x09, 0x26, /* Usage (26h), */ - 0x95, 0x04, /* Report Count (4), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x11, /* Report ID (17), */ - 0x09, 0x27, /* Usage (27h), */ - 0x95, 0x02, /* Report Count (2), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x12, /* Report ID (18), */ - 0x06, 0x02, 0xFF, /* Usage Page (FF02h), */ - 0x09, 0x21, /* Usage (21h), */ - 0x95, 0x0F, /* Report Count (15), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x13, /* Report ID (19), */ - 0x09, 0x22, /* Usage (22h), */ - 0x95, 0x16, /* Report Count (22), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x14, /* Report ID (20), */ - 0x06, 0x05, 0xFF, /* Usage Page (FF05h), */ - 0x09, 0x20, /* Usage (20h), */ - 0x95, 0x10, /* Report Count (16), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x15, /* Report ID (21), */ - 0x09, 0x21, /* Usage (21h), */ - 0x95, 0x2C, /* Report Count (44), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x06, 0x80, 0xFF, /* Usage Page (FF80h), */ - 0x85, 0x80, /* Report ID (128), */ - 0x09, 0x20, /* Usage (20h), */ - 0x95, 0x06, /* Report Count (6), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x81, /* Report ID (129), */ - 0x09, 0x21, /* Usage (21h), */ - 0x95, 0x06, /* Report Count (6), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x82, /* Report ID (130), */ - 0x09, 0x22, /* Usage (22h), */ - 0x95, 0x05, /* Report Count (5), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x83, /* Report ID (131), */ - 0x09, 0x23, /* Usage (23h), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x84, /* Report ID (132), */ - 0x09, 0x24, /* Usage (24h), */ - 0x95, 0x04, /* Report Count (4), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x85, /* Report ID (133), */ - 0x09, 0x25, /* Usage (25h), */ - 0x95, 0x06, /* Report Count (6), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x86, /* Report ID (134), */ - 0x09, 0x26, /* Usage (26h), */ - 0x95, 0x06, /* Report Count (6), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x87, /* Report ID (135), */ - 0x09, 0x27, /* Usage (27h), */ - 0x95, 0x23, /* Report Count (35), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x88, /* Report ID (136), */ - 0x09, 0x28, /* Usage (28h), */ - 0x95, 0x22, /* Report Count (34), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x89, /* Report ID (137), */ - 0x09, 0x29, /* Usage (29h), */ - 0x95, 0x02, /* Report Count (2), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x90, /* Report ID (144), */ - 0x09, 0x30, /* Usage (30h), */ - 0x95, 0x05, /* Report Count (5), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x91, /* Report ID (145), */ - 0x09, 0x31, /* Usage (31h), */ - 0x95, 0x03, /* Report Count (3), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x92, /* Report ID (146), */ - 0x09, 0x32, /* Usage (32h), */ - 0x95, 0x03, /* Report Count (3), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x93, /* Report ID (147), */ - 0x09, 0x33, /* Usage (33h), */ - 0x95, 0x0C, /* Report Count (12), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA0, /* Report ID (160), */ - 0x09, 0x40, /* Usage (40h), */ - 0x95, 0x06, /* Report Count (6), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA1, /* Report ID (161), */ - 0x09, 0x41, /* Usage (41h), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA2, /* Report ID (162), */ - 0x09, 0x42, /* Usage (42h), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA3, /* Report ID (163), */ - 0x09, 0x43, /* Usage (43h), */ - 0x95, 0x30, /* Report Count (48), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA4, /* Report ID (164), */ - 0x09, 0x44, /* Usage (44h), */ - 0x95, 0x0D, /* Report Count (13), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA5, /* Report ID (165), */ - 0x09, 0x45, /* Usage (45h), */ - 0x95, 0x15, /* Report Count (21), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA6, /* Report ID (166), */ - 0x09, 0x46, /* Usage (46h), */ - 0x95, 0x15, /* Report Count (21), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xF0, /* Report ID (240), */ - 0x09, 0x47, /* Usage (47h), */ - 0x95, 0x3F, /* Report Count (63), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xF1, /* Report ID (241), */ - 0x09, 0x48, /* Usage (48h), */ - 0x95, 0x3F, /* Report Count (63), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xF2, /* Report ID (242), */ - 0x09, 0x49, /* Usage (49h), */ - 0x95, 0x0F, /* Report Count (15), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA7, /* Report ID (167), */ - 0x09, 0x4A, /* Usage (4Ah), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA8, /* Report ID (168), */ - 0x09, 0x4B, /* Usage (4Bh), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA9, /* Report ID (169), */ - 0x09, 0x4C, /* Usage (4Ch), */ - 0x95, 0x08, /* Report Count (8), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xAA, /* Report ID (170), */ - 0x09, 0x4E, /* Usage (4Eh), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xAB, /* Report ID (171), */ - 0x09, 0x4F, /* Usage (4Fh), */ - 0x95, 0x39, /* Report Count (57), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xAC, /* Report ID (172), */ - 0x09, 0x50, /* Usage (50h), */ - 0x95, 0x39, /* Report Count (57), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xAD, /* Report ID (173), */ - 0x09, 0x51, /* Usage (51h), */ - 0x95, 0x0B, /* Report Count (11), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xAE, /* Report ID (174), */ - 0x09, 0x52, /* Usage (52h), */ - 0x95, 0x01, /* Report Count (1), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xAF, /* Report ID (175), */ - 0x09, 0x53, /* Usage (53h), */ - 0x95, 0x02, /* Report Count (2), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xB0, /* Report ID (176), */ - 0x09, 0x54, /* Usage (54h), */ - 0x95, 0x3F, /* Report Count (63), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0 /* End Collection */ -}; /* * The default behavior of the Dualshock 4 is to send reports using report @@ -706,31 +447,10 @@ static u8 dualshock4_bt_rdesc[] = { 0x75, 0x08, /* Report Size (8), */ 0x95, 0x02, /* Report Count (2), */ 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x09, 0x20, /* Usage (20h), */ - 0x95, 0x03, /* Report Count (3), */ - 0x81, 0x02, /* Input (Variable), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x19, 0x40, /* Usage Minimum (40h), */ - 0x29, 0x42, /* Usage Maximum (42h), */ - 0x16, 0x00, 0x80, /* Logical Minimum (-32768), */ - 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ - 0x75, 0x10, /* Report Size (16), */ - 0x95, 0x03, /* Report Count (3), */ - 0x81, 0x02, /* Input (Variable), */ - 0x19, 0x43, /* Usage Minimum (43h), */ - 0x29, 0x45, /* Usage Maximum (45h), */ - 0x16, 0x00, 0x80, /* Logical Minimum (-32768), */ - 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ - 0x95, 0x03, /* Report Count (3), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x09, 0x20, /* Usage (20h), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x31, /* Report Count (51), */ - 0x81, 0x02, /* Input (Variable), */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h) */ + 0x09, 0x21, /* Usage (0x21) */ + 0x95, 0x42, /* Report Count (66) */ + 0x81, 0x02, /* Input (Variable) */ 0x09, 0x21, /* Usage (21h), */ 0x75, 0x08, /* Report Size (8), */ 0x95, 0x4D, /* Report Count (77), */ @@ -1060,9 +780,11 @@ struct motion_output_report_02 { * additional +2. */ #define DS4_INPUT_REPORT_BUTTON_OFFSET 5 +#define DS4_INPUT_REPORT_GYRO_X_OFFSET 13 #define DS4_INPUT_REPORT_BATTERY_OFFSET 30 #define DS4_INPUT_REPORT_TOUCHPAD_OFFSET 33 +#define DS4_SENSOR_SUFFIX " Motion Sensors" #define DS4_TOUCHPAD_SUFFIX " Touchpad" static DEFINE_SPINLOCK(sony_dev_list_lock); @@ -1074,6 +796,7 @@ struct sony_sc { struct list_head list_node; struct hid_device *hdev; struct input_dev *touchpad; + struct input_dev *sensor_dev; struct led_classdev *leds[MAX_LEDS]; unsigned long quirks; struct work_struct state_worker; @@ -1227,15 +950,11 @@ static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc, } /* - * The default Dualshock 4 USB descriptor doesn't assign - * the gyroscope values to corresponding axes so we need a - * modified one. + * The default Dualshock 4 BT descriptor doesn't describe report ID 17 + * which is most often used for input data. Add this mapping, so we + * use the generic hid code for parsing the buttons and axes. */ - if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { - hid_info(hdev, "Using modified Dualshock 4 report descriptor with gyroscope axes\n"); - rdesc = dualshock4_usb_rdesc; - *rsize = sizeof(dualshock4_usb_rdesc); - } else if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { + if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { hid_info(hdev, "Using modified Dualshock 4 Bluetooth report descriptor\n"); rdesc = dualshock4_bt_rdesc; *rsize = sizeof(dualshock4_bt_rdesc); @@ -1295,6 +1014,9 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) int n, m, offset, num_touch_data, max_touch_data; u8 cable_state, battery_capacity, battery_charging; + /* Order of hw axes is gyro first, then accelerometer. */ + int axes[6] = {ABS_RX, ABS_RY, ABS_RZ, ABS_X, ABS_Y, ABS_Z}; + /* When using Bluetooth the header is 2 bytes longer, so skip these. */ int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_USB) ? 0 : 2; @@ -1302,6 +1024,14 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) offset = data_offset + DS4_INPUT_REPORT_BUTTON_OFFSET; input_report_key(sc->touchpad, BTN_LEFT, rd[offset+2] & 0x2); + offset = data_offset + DS4_INPUT_REPORT_GYRO_X_OFFSET; + for (n = 0; n < 6; n++, offset += 2) { + short value = get_unaligned_le16(&rd[offset]); + + input_report_abs(sc->sensor_dev, axes[n], value); + } + input_sync(sc->sensor_dev); + /* * The lower 4 bits of byte 30 (or 32 for BT) contain the battery level * and the 5th bit contains the USB cable state. @@ -1580,6 +1310,76 @@ static void sony_unregister_touchpad(struct sony_sc *sc) sc->touchpad = NULL; } +static int sony_register_sensors(struct sony_sc *sc) +{ + size_t name_sz; + char *name; + int ret; + + sc->sensor_dev = input_allocate_device(); + if (!sc->sensor_dev) + return -ENOMEM; + + input_set_drvdata(sc->sensor_dev, sc); + sc->sensor_dev->dev.parent = &sc->hdev->dev; + sc->sensor_dev->phys = sc->hdev->phys; + sc->sensor_dev->uniq = sc->hdev->uniq; + sc->sensor_dev->id.bustype = sc->hdev->bus; + sc->sensor_dev->id.vendor = sc->hdev->vendor; + sc->sensor_dev->id.product = sc->hdev->product; + sc->sensor_dev->id.version = sc->hdev->version; + + /* Append a suffix to the controller name as there are various + * DS4 compatible non-Sony devices with different names. + */ + name_sz = strlen(sc->hdev->name) + sizeof(DS4_SENSOR_SUFFIX); + name = kzalloc(name_sz, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto err; + } + snprintf(name, name_sz, "%s" DS4_SENSOR_SUFFIX, sc->hdev->name); + sc->sensor_dev->name = name; + + input_set_abs_params(sc->sensor_dev, ABS_X, -32768, 32767, 0, 0); + input_set_abs_params(sc->sensor_dev, ABS_Y, -32768, 32767, 0, 0); + input_set_abs_params(sc->sensor_dev, ABS_Z, -32768, 32767, 0, 0); + + input_set_abs_params(sc->sensor_dev, ABS_RX, -32768, 32767, 0, 0); + input_set_abs_params(sc->sensor_dev, ABS_RY, -32768, 32767, 0, 0); + input_set_abs_params(sc->sensor_dev, ABS_RZ, -32768, 32767, 0, 0); + + __set_bit(INPUT_PROP_ACCELEROMETER, sc->sensor_dev->propbit); + + ret = input_register_device(sc->sensor_dev); + if (ret < 0) + goto err; + + return 0; + +err: + kfree(sc->sensor_dev->name); + sc->sensor_dev->name = NULL; + + input_free_device(sc->sensor_dev); + sc->sensor_dev = NULL; + + return ret; +} + +static void sony_unregister_sensors(struct sony_sc *sc) +{ + if (!sc->sensor_dev) + return; + + kfree(sc->sensor_dev->name); + sc->sensor_dev->name = NULL; + + input_unregister_device(sc->sensor_dev); + sc->sensor_dev = NULL; +} + + /* * Sending HID_REQ_GET_REPORT changes the operation mode of the ps3 controller * to "operational". Without this, the ps3 controller will not report any @@ -2585,6 +2385,13 @@ static int sony_input_configured(struct hid_device *hdev, goto err_stop; } + ret = sony_register_sensors(sc); + if (ret) { + hid_err(sc->hdev, + "Unable to initialize motion sensors: %d\n", ret); + goto err_stop; + } + sony_init_output_report(sc, dualshock4_send_output_report); } else if (sc->quirks & MOTION_CONTROLLER) { sony_init_output_report(sc, motion_send_output_report); @@ -2719,6 +2526,12 @@ static void sony_remove(struct hid_device *hdev) if (sc->touchpad) sony_unregister_touchpad(sc); + if (sc->sensor_dev) + sony_unregister_sensors(sc); + + if (sc->sensor_dev) + sony_unregister_sensors(sc); + sony_cancel_work_sync(sc); kfree(sc->output_report_dmabuf); -- cgit From 55a07d62db0feda2e37f6962a1b2799e2a8b42ba Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:01 -0800 Subject: HID: sony: Calibrate DS4 motion sensors The DS4 motion sensors require calibration for accurate operation. This patch adds calibration for both the accelerometer and the gyroscope. Calibration requires reading device specific scaling factors and offsets. For precision reasons we store these values as a numerator and denominator and apply the values when processing the data. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 212 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 184 insertions(+), 28 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 17df165133ab..fedaebe0363f 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -768,6 +768,7 @@ struct motion_output_report_02 { }; #define DS4_FEATURE_REPORT_0x02_SIZE 37 +#define DS4_FEATURE_REPORT_0x05_SIZE 41 #define DS4_FEATURE_REPORT_0x81_SIZE 7 #define DS4_INPUT_REPORT_0x11_SIZE 78 #define DS4_OUTPUT_REPORT_0x05_SIZE 32 @@ -787,10 +788,25 @@ struct motion_output_report_02 { #define DS4_SENSOR_SUFFIX " Motion Sensors" #define DS4_TOUCHPAD_SUFFIX " Touchpad" +#define DS4_GYRO_RES_PER_DEG_S 1024 +#define DS4_ACC_RES_PER_G 8192 + static DEFINE_SPINLOCK(sony_dev_list_lock); static LIST_HEAD(sony_device_list); static DEFINE_IDA(sony_device_id_allocator); +/* Used for calibration of DS4 accelerometer and gyro. */ +struct ds4_calibration_data { + int abs_code; + short bias; + /* Calibration requires scaling against a sensitivity value, which is a + * float. Store sensitivity as a fraction to limit floating point + * calculations until final calibration. + */ + int sens_numer; + int sens_denom; +}; + struct sony_sc { spinlock_t lock; struct list_head list_node; @@ -822,6 +838,8 @@ struct sony_sc { u8 led_delay_off[MAX_LEDS]; u8 led_count; bool ds4_dongle_connected; + /* DS4 calibration data */ + struct ds4_calibration_data ds4_calib_data[6]; }; static void sony_set_leds(struct sony_sc *sc); @@ -1014,9 +1032,6 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) int n, m, offset, num_touch_data, max_touch_data; u8 cable_state, battery_capacity, battery_charging; - /* Order of hw axes is gyro first, then accelerometer. */ - int axes[6] = {ABS_RX, ABS_RY, ABS_RZ, ABS_X, ABS_Y, ABS_Z}; - /* When using Bluetooth the header is 2 bytes longer, so skip these. */ int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_USB) ? 0 : 2; @@ -1025,10 +1040,22 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) input_report_key(sc->touchpad, BTN_LEFT, rd[offset+2] & 0x2); offset = data_offset + DS4_INPUT_REPORT_GYRO_X_OFFSET; - for (n = 0; n < 6; n++, offset += 2) { - short value = get_unaligned_le16(&rd[offset]); + for (n = 0; n < 6; n++) { + /* Store data in int for more precision during mult_frac. */ + int raw_data = (short)((rd[offset+1] << 8) | rd[offset]); + struct ds4_calibration_data *calib = &sc->ds4_calib_data[n]; + + /* High precision is needed during calibration, but the + * calibrated values are within 32-bit. + * Note: we swap numerator 'x' and 'numer' in mult_frac for + * precision reasons so we don't need 64-bit. + */ + int calib_data = mult_frac(calib->sens_numer, + raw_data - calib->bias, + calib->sens_denom); - input_report_abs(sc->sensor_dev, axes[n], value); + input_report_abs(sc->sensor_dev, calib->abs_code, calib_data); + offset += 2; } input_sync(sc->sensor_dev); @@ -1315,6 +1342,7 @@ static int sony_register_sensors(struct sony_sc *sc) size_t name_sz; char *name; int ret; + int range; sc->sensor_dev = input_allocate_device(); if (!sc->sensor_dev) @@ -1341,13 +1369,21 @@ static int sony_register_sensors(struct sony_sc *sc) snprintf(name, name_sz, "%s" DS4_SENSOR_SUFFIX, sc->hdev->name); sc->sensor_dev->name = name; - input_set_abs_params(sc->sensor_dev, ABS_X, -32768, 32767, 0, 0); - input_set_abs_params(sc->sensor_dev, ABS_Y, -32768, 32767, 0, 0); - input_set_abs_params(sc->sensor_dev, ABS_Z, -32768, 32767, 0, 0); - - input_set_abs_params(sc->sensor_dev, ABS_RX, -32768, 32767, 0, 0); - input_set_abs_params(sc->sensor_dev, ABS_RY, -32768, 32767, 0, 0); - input_set_abs_params(sc->sensor_dev, ABS_RZ, -32768, 32767, 0, 0); + range = DS4_ACC_RES_PER_G*4; + input_set_abs_params(sc->sensor_dev, ABS_X, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_Y, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_Z, -range, range, 16, 0); + input_abs_set_res(sc->sensor_dev, ABS_X, DS4_ACC_RES_PER_G); + input_abs_set_res(sc->sensor_dev, ABS_Y, DS4_ACC_RES_PER_G); + input_abs_set_res(sc->sensor_dev, ABS_Z, DS4_ACC_RES_PER_G); + + range = DS4_GYRO_RES_PER_DEG_S*2048; + input_set_abs_params(sc->sensor_dev, ABS_RX, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_RY, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_RZ, -range, range, 16, 0); + input_abs_set_res(sc->sensor_dev, ABS_RX, DS4_GYRO_RES_PER_DEG_S); + input_abs_set_res(sc->sensor_dev, ABS_RY, DS4_GYRO_RES_PER_DEG_S); + input_abs_set_res(sc->sensor_dev, ABS_RZ, DS4_GYRO_RES_PER_DEG_S); __set_bit(INPUT_PROP_ACCELEROMETER, sc->sensor_dev->propbit); @@ -1445,23 +1481,145 @@ static int sixaxis_set_operational_bt(struct hid_device *hdev) } /* - * Requesting feature report 0x02 in Bluetooth mode changes the state of the - * controller so that it sends full input reports of type 0x11. + * Request DS4 calibration data for the motion sensors. + * For Bluetooth this also affects the operating mode (see below). */ -static int dualshock4_set_operational_bt(struct hid_device *hdev) +static int dualshock4_get_calibration_data(struct sony_sc *sc) { u8 *buf; int ret; + short gyro_pitch_bias, gyro_pitch_plus, gyro_pitch_minus; + short gyro_yaw_bias, gyro_yaw_plus, gyro_yaw_minus; + short gyro_roll_bias, gyro_roll_plus, gyro_roll_minus; + short gyro_speed_plus, gyro_speed_minus; + short acc_x_plus, acc_x_minus; + short acc_y_plus, acc_y_minus; + short acc_z_plus, acc_z_minus; + int speed_2x; + int range_2g; + + /* For Bluetooth we use a different request, which supports CRC. + * Note: in Bluetooth mode feature report 0x02 also changes the state + * of the controller, so that it sends input reports of type 0x11. + */ + if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { + buf = kmalloc(DS4_FEATURE_REPORT_0x02_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - buf = kmalloc(DS4_FEATURE_REPORT_0x02_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; + ret = hid_hw_raw_request(sc->hdev, 0x02, buf, + DS4_FEATURE_REPORT_0x02_SIZE, + HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + if (ret < 0) + goto err_stop; + } else { + u8 bthdr = 0xA3; + u32 crc; + u32 report_crc; + int retries; - ret = hid_hw_raw_request(hdev, 0x02, buf, DS4_FEATURE_REPORT_0x02_SIZE, - HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + buf = kmalloc(DS4_FEATURE_REPORT_0x05_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - kfree(buf); + for (retries = 0; retries < 3; retries++) { + ret = hid_hw_raw_request(sc->hdev, 0x05, buf, + DS4_FEATURE_REPORT_0x05_SIZE, + HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + if (ret < 0) + goto err_stop; + /* CRC check */ + crc = crc32_le(0xFFFFFFFF, &bthdr, 1); + crc = ~crc32_le(crc, buf, DS4_FEATURE_REPORT_0x05_SIZE-4); + report_crc = get_unaligned_le32(&buf[DS4_FEATURE_REPORT_0x05_SIZE-4]); + if (crc != report_crc) { + hid_warn(sc->hdev, "DualShock 4 calibration report's CRC check failed, received crc 0x%0x != 0x%0x\n", + report_crc, crc); + if (retries < 2) { + hid_warn(sc->hdev, "Retrying DualShock 4 get calibration report request\n"); + continue; + } else { + ret = -EILSEQ; + goto err_stop; + } + } else { + break; + } + } + } + + gyro_pitch_bias = get_unaligned_le16(&buf[1]); + gyro_yaw_bias = get_unaligned_le16(&buf[3]); + gyro_roll_bias = get_unaligned_le16(&buf[5]); + if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { + gyro_pitch_plus = get_unaligned_le16(&buf[7]); + gyro_pitch_minus = get_unaligned_le16(&buf[9]); + gyro_yaw_plus = get_unaligned_le16(&buf[11]); + gyro_yaw_minus = get_unaligned_le16(&buf[13]); + gyro_roll_plus = get_unaligned_le16(&buf[15]); + gyro_roll_minus = get_unaligned_le16(&buf[17]); + } else { + gyro_pitch_plus = get_unaligned_le16(&buf[7]); + gyro_yaw_plus = get_unaligned_le16(&buf[9]); + gyro_roll_plus = get_unaligned_le16(&buf[11]); + gyro_pitch_minus = get_unaligned_le16(&buf[13]); + gyro_yaw_minus = get_unaligned_le16(&buf[15]); + gyro_roll_minus = get_unaligned_le16(&buf[17]); + } + gyro_speed_plus = get_unaligned_le16(&buf[19]); + gyro_speed_minus = get_unaligned_le16(&buf[21]); + acc_x_plus = get_unaligned_le16(&buf[23]); + acc_x_minus = get_unaligned_le16(&buf[25]); + acc_y_plus = get_unaligned_le16(&buf[27]); + acc_y_minus = get_unaligned_le16(&buf[29]); + acc_z_plus = get_unaligned_le16(&buf[31]); + acc_z_minus = get_unaligned_le16(&buf[33]); + + /* Set gyroscope calibration and normalization parameters. + * Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s. + */ + speed_2x = (gyro_speed_plus + gyro_speed_minus); + sc->ds4_calib_data[0].abs_code = ABS_RX; + sc->ds4_calib_data[0].bias = gyro_pitch_bias; + sc->ds4_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + sc->ds4_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus; + + sc->ds4_calib_data[1].abs_code = ABS_RY; + sc->ds4_calib_data[1].bias = gyro_yaw_bias; + sc->ds4_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + sc->ds4_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus; + + sc->ds4_calib_data[2].abs_code = ABS_RZ; + sc->ds4_calib_data[2].bias = gyro_roll_bias; + sc->ds4_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + sc->ds4_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus; + + /* Set accelerometer calibration and normalization parameters. + * Data values will be normalized to 1/DS4_ACC_RES_PER_G G. + */ + range_2g = acc_x_plus - acc_x_minus; + sc->ds4_calib_data[3].abs_code = ABS_X; + sc->ds4_calib_data[3].bias = acc_x_plus - range_2g / 2; + sc->ds4_calib_data[3].sens_numer = 2*DS4_ACC_RES_PER_G; + sc->ds4_calib_data[3].sens_denom = range_2g; + + range_2g = acc_y_plus - acc_y_minus; + sc->ds4_calib_data[4].abs_code = ABS_Y; + sc->ds4_calib_data[4].bias = acc_y_plus - range_2g / 2; + sc->ds4_calib_data[4].sens_numer = 2*DS4_ACC_RES_PER_G; + sc->ds4_calib_data[4].sens_denom = range_2g; + + range_2g = acc_z_plus - acc_z_minus; + sc->ds4_calib_data[5].abs_code = ABS_Z; + sc->ds4_calib_data[5].bias = acc_z_plus - range_2g / 2; + sc->ds4_calib_data[5].sens_numer = 2*DS4_ACC_RES_PER_G; + sc->ds4_calib_data[5].sens_denom = range_2g; + +err_stop: + kfree(buf); return ret; } @@ -2365,12 +2523,10 @@ static int sony_input_configured(struct hid_device *hdev, ret = sixaxis_set_operational_bt(hdev); sony_init_output_report(sc, sixaxis_send_output_report); } else if (sc->quirks & DUALSHOCK4_CONTROLLER) { - if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { - ret = dualshock4_set_operational_bt(hdev); - if (ret < 0) { - hid_err(hdev, "failed to set the Dualshock 4 operational mode\n"); - goto err_stop; - } + ret = dualshock4_get_calibration_data(sc); + if (ret < 0) { + hid_err(hdev, "Failed to get calibration data from Dualshock 4\n"); + goto err_stop; } /* -- cgit From 80786eb9abebce64ec471de12d8ab3072834b333 Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:02 -0800 Subject: HID: sony: Report hardware timestamp for DS4 sensor values Report the hardware timestamp inside each HID report through MSC_TIMESTAMP for motion sensor values. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index fedaebe0363f..dab4aefd841e 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -781,6 +781,7 @@ struct motion_output_report_02 { * additional +2. */ #define DS4_INPUT_REPORT_BUTTON_OFFSET 5 +#define DS4_INPUT_REPORT_TIMESTAMP_OFFSET 10 #define DS4_INPUT_REPORT_GYRO_X_OFFSET 13 #define DS4_INPUT_REPORT_BATTERY_OFFSET 30 #define DS4_INPUT_REPORT_TOUCHPAD_OFFSET 33 @@ -837,6 +838,11 @@ struct sony_sc { u8 led_delay_on[MAX_LEDS]; u8 led_delay_off[MAX_LEDS]; u8 led_count; + + bool timestamp_initialized; + u16 prev_timestamp; + unsigned int timestamp_us; + bool ds4_dongle_connected; /* DS4 calibration data */ struct ds4_calibration_data ds4_calib_data[6]; @@ -1031,6 +1037,7 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) unsigned long flags; int n, m, offset, num_touch_data, max_touch_data; u8 cable_state, battery_capacity, battery_charging; + u16 timestamp; /* When using Bluetooth the header is 2 bytes longer, so skip these. */ int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_USB) ? 0 : 2; @@ -1039,6 +1046,24 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) offset = data_offset + DS4_INPUT_REPORT_BUTTON_OFFSET; input_report_key(sc->touchpad, BTN_LEFT, rd[offset+2] & 0x2); + /* Convert timestamp (in 5.33us unit) to timestamp_us */ + offset = data_offset + DS4_INPUT_REPORT_TIMESTAMP_OFFSET; + timestamp = get_unaligned_le16(&rd[offset]); + if (!sc->timestamp_initialized) { + sc->timestamp_us = ((unsigned int)timestamp * 16) / 3; + sc->timestamp_initialized = true; + } else { + u16 delta; + + if (sc->prev_timestamp > timestamp) + delta = (U16_MAX - sc->prev_timestamp + timestamp + 1); + else + delta = timestamp - sc->prev_timestamp; + sc->timestamp_us += (delta * 16) / 3; + } + sc->prev_timestamp = timestamp; + input_event(sc->sensor_dev, EV_MSC, MSC_TIMESTAMP, sc->timestamp_us); + offset = data_offset + DS4_INPUT_REPORT_GYRO_X_OFFSET; for (n = 0; n < 6; n++) { /* Store data in int for more precision during mult_frac. */ @@ -1385,6 +1410,8 @@ static int sony_register_sensors(struct sony_sc *sc) input_abs_set_res(sc->sensor_dev, ABS_RY, DS4_GYRO_RES_PER_DEG_S); input_abs_set_res(sc->sensor_dev, ABS_RZ, DS4_GYRO_RES_PER_DEG_S); + __set_bit(EV_MSC, sc->sensor_dev->evbit); + __set_bit(MSC_TIMESTAMP, sc->sensor_dev->mscbit); __set_bit(INPUT_PROP_ACCELEROMETER, sc->sensor_dev->propbit); ret = input_register_device(sc->sensor_dev); -- cgit From d03ae2e1080026951376d787b96109169926a15c Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:03 -0800 Subject: HID: sony: Remove report descriptor fixup for DS4 The DS4 in BT mode sends initial input reports through report 1, which is described in the HID report descriptors. When activated after sending a certain feature report, the device uses report 17. Currently the hid-sony driver fixes up the BT HID report descriptors, so the HID layer can manage input reports for report 17. We think it is best to eliminate this fixup and do the handling ourselves, which is what this patch does. The main motivation is that there are various users of DS4 through hidraw, including various cross-platform applications/games, which have their own HID parsing across Linux/Win/OSX. Due to the fixup the descriptors differ, which is causing pain for many developers including major game publishers (who reached out privately). Without the fixup, the Windows titles also have a fighting chance for working on Wine, which provides HID support now. Overall it felt best because of these reasons to remove the fixup. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 261 +++++++++++-------------------------------------- 1 file changed, 59 insertions(+), 202 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index dab4aefd841e..f9ff10bbc1ac 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -344,197 +344,6 @@ static u8 navigation_rdesc[] = { 0xC0 /* End Collection */ }; - -/* - * The default behavior of the Dualshock 4 is to send reports using report - * type 1 when running over Bluetooth. However, when feature report 2 is - * requested during the controller initialization it starts sending input - * reports in report 17. Since report 17 is undefined in the default HID - * descriptor the button and axis definitions must be moved to report 17 or - * the HID layer won't process the received input. - */ -static u8 dualshock4_bt_rdesc[] = { - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x05, /* Usage (Gamepad), */ - 0xA1, 0x01, /* Collection (Application), */ - 0x85, 0x01, /* Report ID (1), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x0A, /* Report Count (9), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x04, 0xFF, /* Usage Page (FF04h), */ - 0x85, 0x02, /* Report ID (2), */ - 0x09, 0x24, /* Usage (24h), */ - 0x95, 0x24, /* Report Count (36), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA3, /* Report ID (163), */ - 0x09, 0x25, /* Usage (25h), */ - 0x95, 0x30, /* Report Count (48), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x05, /* Report ID (5), */ - 0x09, 0x26, /* Usage (26h), */ - 0x95, 0x28, /* Report Count (40), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x06, /* Report ID (6), */ - 0x09, 0x27, /* Usage (27h), */ - 0x95, 0x34, /* Report Count (52), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x07, /* Report ID (7), */ - 0x09, 0x28, /* Usage (28h), */ - 0x95, 0x30, /* Report Count (48), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x08, /* Report ID (8), */ - 0x09, 0x29, /* Usage (29h), */ - 0x95, 0x2F, /* Report Count (47), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x06, 0x03, 0xFF, /* Usage Page (FF03h), */ - 0x85, 0x03, /* Report ID (3), */ - 0x09, 0x21, /* Usage (21h), */ - 0x95, 0x26, /* Report Count (38), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x04, /* Report ID (4), */ - 0x09, 0x22, /* Usage (22h), */ - 0x95, 0x2E, /* Report Count (46), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xF0, /* Report ID (240), */ - 0x09, 0x47, /* Usage (47h), */ - 0x95, 0x3F, /* Report Count (63), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xF1, /* Report ID (241), */ - 0x09, 0x48, /* Usage (48h), */ - 0x95, 0x3F, /* Report Count (63), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xF2, /* Report ID (242), */ - 0x09, 0x49, /* Usage (49h), */ - 0x95, 0x0F, /* Report Count (15), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x11, /* Report ID (17), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x09, 0x20, /* Usage (20h), */ - 0x95, 0x02, /* Report Count (2), */ - 0x81, 0x02, /* Input (Variable), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x30, /* Usage (X), */ - 0x09, 0x31, /* Usage (Y), */ - 0x09, 0x32, /* Usage (Z), */ - 0x09, 0x35, /* Usage (Rz), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x04, /* Report Count (4), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x39, /* Usage (Hat Switch), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x07, /* Logical Maximum (7), */ - 0x75, 0x04, /* Report Size (4), */ - 0x95, 0x01, /* Report Count (1), */ - 0x81, 0x42, /* Input (Variable, Null State), */ - 0x05, 0x09, /* Usage Page (Button), */ - 0x19, 0x01, /* Usage Minimum (01h), */ - 0x29, 0x0D, /* Usage Maximum (0Dh), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x01, /* Logical Maximum (1), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x0E, /* Report Count (14), */ - 0x81, 0x02, /* Input (Variable), */ - 0x75, 0x06, /* Report Size (6), */ - 0x95, 0x01, /* Report Count (1), */ - 0x81, 0x01, /* Input (Constant), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x33, /* Usage (Rx), */ - 0x09, 0x34, /* Usage (Ry), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x02, /* Report Count (2), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h) */ - 0x09, 0x21, /* Usage (0x21) */ - 0x95, 0x42, /* Report Count (66) */ - 0x81, 0x02, /* Input (Variable) */ - 0x09, 0x21, /* Usage (21h), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x4D, /* Report Count (77), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x12, /* Report ID (18), */ - 0x09, 0x22, /* Usage (22h), */ - 0x95, 0x8D, /* Report Count (141), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x23, /* Usage (23h), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x13, /* Report ID (19), */ - 0x09, 0x24, /* Usage (24h), */ - 0x95, 0xCD, /* Report Count (205), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x25, /* Usage (25h), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x14, /* Report ID (20), */ - 0x09, 0x26, /* Usage (26h), */ - 0x96, 0x0D, 0x01, /* Report Count (269), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x27, /* Usage (27h), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x15, /* Report ID (21), */ - 0x09, 0x28, /* Usage (28h), */ - 0x96, 0x4D, 0x01, /* Report Count (333), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x29, /* Usage (29h), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x16, /* Report ID (22), */ - 0x09, 0x2A, /* Usage (2Ah), */ - 0x96, 0x8D, 0x01, /* Report Count (397), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x2B, /* Usage (2Bh), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x17, /* Report ID (23), */ - 0x09, 0x2C, /* Usage (2Ch), */ - 0x96, 0xCD, 0x01, /* Report Count (461), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x2D, /* Usage (2Dh), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x18, /* Report ID (24), */ - 0x09, 0x2E, /* Usage (2Eh), */ - 0x96, 0x0D, 0x02, /* Report Count (525), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x2F, /* Usage (2Fh), */ - 0x91, 0x02, /* Output (Variable), */ - 0x85, 0x19, /* Report ID (25), */ - 0x09, 0x30, /* Usage (30h), */ - 0x96, 0x22, 0x02, /* Report Count (546), */ - 0x81, 0x02, /* Input (Variable), */ - 0x09, 0x31, /* Usage (31h), */ - 0x91, 0x02, /* Output (Variable), */ - 0x06, 0x80, 0xFF, /* Usage Page (FF80h), */ - 0x85, 0x82, /* Report ID (130), */ - 0x09, 0x22, /* Usage (22h), */ - 0x95, 0x3F, /* Report Count (63), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x83, /* Report ID (131), */ - 0x09, 0x23, /* Usage (23h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x84, /* Report ID (132), */ - 0x09, 0x24, /* Usage (24h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x90, /* Report ID (144), */ - 0x09, 0x30, /* Usage (30h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x91, /* Report ID (145), */ - 0x09, 0x31, /* Usage (31h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x92, /* Report ID (146), */ - 0x09, 0x32, /* Usage (32h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0x93, /* Report ID (147), */ - 0x09, 0x33, /* Usage (33h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA0, /* Report ID (160), */ - 0x09, 0x40, /* Usage (40h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0x85, 0xA4, /* Report ID (164), */ - 0x09, 0x44, /* Usage (44h), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0 /* End Collection */ -}; - static u8 ps3remote_rdesc[] = { 0x05, 0x01, /* GUsagePage Generic Desktop */ 0x09, 0x05, /* LUsage 0x05 [Game Pad] */ @@ -722,6 +531,10 @@ static const unsigned int ds4_keymap[] = { [0xd] = BTN_MODE, /* PS */ }; +static const struct {int x; int y; } ds4_hat_mapping[] = { + {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, + {0, 0} +}; static enum power_supply_property sony_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, @@ -780,6 +593,7 @@ struct motion_output_report_02 { /* Offsets relative to USB input report (0x1). Bluetooth (0x11) requires an * additional +2. */ +#define DS4_INPUT_REPORT_AXIS_OFFSET 1 #define DS4_INPUT_REPORT_BUTTON_OFFSET 5 #define DS4_INPUT_REPORT_TIMESTAMP_OFFSET 10 #define DS4_INPUT_REPORT_GYRO_X_OFFSET 13 @@ -973,17 +787,6 @@ static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc, rdesc[55] = 0x06; } - /* - * The default Dualshock 4 BT descriptor doesn't describe report ID 17 - * which is most often used for input data. Add this mapping, so we - * use the generic hid code for parsing the buttons and axes. - */ - if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { - hid_info(hdev, "Using modified Dualshock 4 Bluetooth report descriptor\n"); - rdesc = dualshock4_bt_rdesc; - *rsize = sizeof(dualshock4_bt_rdesc); - } - if (sc->quirks & SIXAXIS_CONTROLLER) return sixaxis_fixup(hdev, rdesc, rsize); @@ -1034,6 +837,9 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size) static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) { + struct hid_input *hidinput = list_entry(sc->hdev->inputs.next, + struct hid_input, list); + struct input_dev *input_dev = hidinput->input; unsigned long flags; int n, m, offset, num_touch_data, max_touch_data; u8 cable_state, battery_capacity, battery_charging; @@ -1046,6 +852,57 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) offset = data_offset + DS4_INPUT_REPORT_BUTTON_OFFSET; input_report_key(sc->touchpad, BTN_LEFT, rd[offset+2] & 0x2); + /* + * The default behavior of the Dualshock 4 is to send reports using + * report type 1 when running over Bluetooth. However, when feature + * report 2 is requested during the controller initialization it starts + * sending input reports in report 17. Since report 17 is undefined + * in the default HID descriptor, the HID layer won't generate events. + * While it is possible (and this was done before) to fixup the HID + * descriptor to add this mapping, it was better to do this manually. + * The reason is there were various pieces software both open and closed + * source, relying on the descriptors to be the same across various + * operating systems. If the descriptors wouldn't match some + * applications e.g. games on Wine would not be able to function due + * to different descriptors, which such applications are not parsing. + */ + if (rd[0] == 17) { + int value; + + offset = data_offset + DS4_INPUT_REPORT_AXIS_OFFSET; + input_report_abs(input_dev, ABS_X, rd[offset]); + input_report_abs(input_dev, ABS_Y, rd[offset+1]); + input_report_abs(input_dev, ABS_RX, rd[offset+2]); + input_report_abs(input_dev, ABS_RY, rd[offset+3]); + + value = rd[offset+4] & 0xf; + if (value > 7) + value = 8; /* Center 0, 0 */ + input_report_abs(input_dev, ABS_HAT0X, ds4_hat_mapping[value].x); + input_report_abs(input_dev, ABS_HAT0Y, ds4_hat_mapping[value].y); + + input_report_key(input_dev, BTN_WEST, rd[offset+4] & 0x10); + input_report_key(input_dev, BTN_SOUTH, rd[offset+4] & 0x20); + input_report_key(input_dev, BTN_EAST, rd[offset+4] & 0x40); + input_report_key(input_dev, BTN_NORTH, rd[offset+4] & 0x80); + + input_report_key(input_dev, BTN_TL, rd[offset+5] & 0x1); + input_report_key(input_dev, BTN_TR, rd[offset+5] & 0x2); + input_report_key(input_dev, BTN_TL2, rd[offset+5] & 0x4); + input_report_key(input_dev, BTN_TR2, rd[offset+5] & 0x8); + input_report_key(input_dev, BTN_SELECT, rd[offset+5] & 0x10); + input_report_key(input_dev, BTN_START, rd[offset+5] & 0x20); + input_report_key(input_dev, BTN_THUMBL, rd[offset+5] & 0x40); + input_report_key(input_dev, BTN_THUMBR, rd[offset+5] & 0x80); + + input_report_key(input_dev, BTN_MODE, rd[offset+6] & 0x1); + + input_report_abs(input_dev, ABS_Z, rd[offset+7]); + input_report_abs(input_dev, ABS_RZ, rd[offset+8]); + + input_sync(input_dev); + } + /* Convert timestamp (in 5.33us unit) to timestamp_us */ offset = data_offset + DS4_INPUT_REPORT_TIMESTAMP_OFFSET; timestamp = get_unaligned_le16(&rd[offset]); -- cgit From 35f436c31ea81d240ed53fe3467946e2a53032bb Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:04 -0800 Subject: HID: sony: Treat the ds4 dongle as a separate device This patch adds a new quirk, which allows us to differentiate between the DualShock 4 USB and the dongle. So far they have been treated the same, but handling of calibration data differs as the dongle behaves like Bluetooth, for other requests it behaves like USB. In addition this patches changes usb/dongle/bt handling in sony_raw_event, which makes the code cleaner to read. In addition another patch in this series will add more dongle logic, so this change paves the road for that. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 99 ++++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 48 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index f9ff10bbc1ac..98b1b7550745 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -48,19 +48,21 @@ #define PS3REMOTE BIT(4) #define DUALSHOCK4_CONTROLLER_USB BIT(5) #define DUALSHOCK4_CONTROLLER_BT BIT(6) -#define MOTION_CONTROLLER_USB BIT(7) -#define MOTION_CONTROLLER_BT BIT(8) -#define NAVIGATION_CONTROLLER_USB BIT(9) -#define NAVIGATION_CONTROLLER_BT BIT(10) -#define SINO_LITE_CONTROLLER BIT(11) -#define FUTUREMAX_DANCE_MAT BIT(12) +#define DUALSHOCK4_DONGLE BIT(7) +#define MOTION_CONTROLLER_USB BIT(8) +#define MOTION_CONTROLLER_BT BIT(9) +#define NAVIGATION_CONTROLLER_USB BIT(10) +#define NAVIGATION_CONTROLLER_BT BIT(11) +#define SINO_LITE_CONTROLLER BIT(12) +#define FUTUREMAX_DANCE_MAT BIT(13) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) #define NAVIGATION_CONTROLLER (NAVIGATION_CONTROLLER_USB |\ NAVIGATION_CONTROLLER_BT) #define DUALSHOCK4_CONTROLLER (DUALSHOCK4_CONTROLLER_USB |\ - DUALSHOCK4_CONTROLLER_BT) + DUALSHOCK4_CONTROLLER_BT | \ + DUALSHOCK4_DONGLE) #define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\ DUALSHOCK4_CONTROLLER | MOTION_CONTROLLER |\ NAVIGATION_CONTROLLER) @@ -846,7 +848,7 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) u16 timestamp; /* When using Bluetooth the header is 2 bytes longer, so skip these. */ - int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_USB) ? 0 : 2; + int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_BT) ? 2 : 0; /* Second bit of third button byte is for the touchpad button. */ offset = data_offset + DS4_INPUT_REPORT_BUTTON_OFFSET; @@ -979,7 +981,7 @@ static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) * Trackpad data starts 2 bytes later (e.g. 35 for USB). */ offset = data_offset + DS4_INPUT_REPORT_TOUCHPAD_OFFSET; - max_touch_data = (sc->quirks & DUALSHOCK4_CONTROLLER_USB) ? 3 : 4; + max_touch_data = (sc->quirks & DUALSHOCK4_CONTROLLER_BT) ? 4 : 3; if (rd[offset] > 0 && rd[offset] <= max_touch_data) num_touch_data = rd[offset]; else @@ -1053,47 +1055,47 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, } else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && size == 49) { sixaxis_parse_report(sc, rd, size); - } else if (((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 && - size == 64) || ((sc->quirks & DUALSHOCK4_CONTROLLER_BT) - && rd[0] == 0x11 && size == 78)) { - if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { - /* CRC check */ - u8 bthdr = 0xA1; - u32 crc; - u32 report_crc; + } else if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 && + size == 64) { + dualshock4_parse_report(sc, rd, size); + } else if (((sc->quirks & DUALSHOCK4_CONTROLLER_BT) && rd[0] == 0x11 && + size == 78)) { + /* CRC check */ + u8 bthdr = 0xA1; + u32 crc; + u32 report_crc; - crc = crc32_le(0xFFFFFFFF, &bthdr, 1); - crc = ~crc32_le(crc, rd, DS4_INPUT_REPORT_0x11_SIZE-4); - report_crc = get_unaligned_le32(&rd[DS4_INPUT_REPORT_0x11_SIZE-4]); - if (crc != report_crc) { - hid_dbg(sc->hdev, "DualShock 4 input report's CRC check failed, received crc 0x%0x != 0x%0x\n", - report_crc, crc); - return -EILSEQ; - } + crc = crc32_le(0xFFFFFFFF, &bthdr, 1); + crc = ~crc32_le(crc, rd, DS4_INPUT_REPORT_0x11_SIZE-4); + report_crc = get_unaligned_le32(&rd[DS4_INPUT_REPORT_0x11_SIZE-4]); + if (crc != report_crc) { + hid_dbg(sc->hdev, "DualShock 4 input report's CRC check failed, received crc 0x%0x != 0x%0x\n", + report_crc, crc); + return -EILSEQ; } + dualshock4_parse_report(sc, rd, size); + } else if ((sc->quirks & DUALSHOCK4_DONGLE) && rd[0] == 0x01 && + size == 64) { /* * In the case of a DS4 USB dongle, bit[2] of byte 31 indicates * if a DS4 is actually connected (indicated by '0'). * For non-dongle, this bit is always 0 (connected). */ - if (sc->hdev->vendor == USB_VENDOR_ID_SONY && - sc->hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) { - bool connected = (rd[31] & 0x04) ? false : true; - - if (!sc->ds4_dongle_connected && connected) { - hid_info(sc->hdev, "DualShock 4 USB dongle: controller connected\n"); - sony_set_leds(sc); - sc->ds4_dongle_connected = true; - } else if (sc->ds4_dongle_connected && !connected) { - hid_info(sc->hdev, "DualShock 4 USB dongle: controller disconnected\n"); - sc->ds4_dongle_connected = false; - /* Return 0, so hidraw can get the report. */ - return 0; - } else if (!sc->ds4_dongle_connected) { - /* Return 0, so hidraw can get the report. */ - return 0; - } + bool connected = (rd[31] & 0x04) ? false : true; + + if (!sc->ds4_dongle_connected && connected) { + hid_info(sc->hdev, "DualShock 4 USB dongle: controller connected\n"); + sony_set_leds(sc); + sc->ds4_dongle_connected = true; + } else if (sc->ds4_dongle_connected && !connected) { + hid_info(sc->hdev, "DualShock 4 USB dongle: controller disconnected\n"); + sc->ds4_dongle_connected = false; + /* Return 0, so hidraw can get the report. */ + return 0; + } else if (!sc->ds4_dongle_connected) { + /* Return 0, so hidraw can get the report. */ + return 0; } dualshock4_parse_report(sc, rd, size); @@ -1386,7 +1388,7 @@ static int dualshock4_get_calibration_data(struct sony_sc *sc) * Note: in Bluetooth mode feature report 0x02 also changes the state * of the controller, so that it sends input reports of type 0x11. */ - if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { + if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) { buf = kmalloc(DS4_FEATURE_REPORT_0x02_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -1446,6 +1448,7 @@ static int dualshock4_get_calibration_data(struct sony_sc *sc) gyro_roll_plus = get_unaligned_le16(&buf[15]); gyro_roll_minus = get_unaligned_le16(&buf[17]); } else { + /* BT + Dongle */ gyro_pitch_plus = get_unaligned_le16(&buf[7]); gyro_yaw_plus = get_unaligned_le16(&buf[9]); gyro_roll_plus = get_unaligned_le16(&buf[11]); @@ -1904,7 +1907,7 @@ static void dualshock4_send_output_report(struct sony_sc *sc) * 0xB0 - 20hz * 0xD0 - 66hz */ - if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { + if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) { memset(buf, 0, DS4_OUTPUT_REPORT_0x05_SIZE); buf[0] = 0x05; buf[1] = 0xFF; @@ -1937,7 +1940,7 @@ static void dualshock4_send_output_report(struct sony_sc *sc) buf[offset++] = sc->led_delay_on[3]; buf[offset++] = sc->led_delay_off[3]; - if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) + if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) hid_hw_output_report(hdev, buf, DS4_OUTPUT_REPORT_0x05_SIZE); else { /* CRC generation */ @@ -1994,7 +1997,7 @@ static int sony_allocate_output_report(struct sony_sc *sc) else if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) sc->output_report_dmabuf = kmalloc(DS4_OUTPUT_REPORT_0x11_SIZE, GFP_KERNEL); - else if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) + else if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) sc->output_report_dmabuf = kmalloc(DS4_OUTPUT_REPORT_0x05_SIZE, GFP_KERNEL); else if (sc->quirks & MOTION_CONTROLLER) @@ -2239,7 +2242,7 @@ static int sony_check_add(struct sony_sc *sc) hid_warn(sc->hdev, "UNIQ does not contain a MAC address; duplicate check skipped\n"); return 0; } - } else if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { + } else if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) { buf = kmalloc(DS4_FEATURE_REPORT_0x81_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -2664,7 +2667,7 @@ static const struct hid_device_id sony_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2), .driver_data = DUALSHOCK4_CONTROLLER_BT }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE), - .driver_data = DUALSHOCK4_CONTROLLER_USB }, + .driver_data = DUALSHOCK4_DONGLE }, /* Nyko Core Controller for PS3 */ { HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER), .driver_data = SIXAXIS_CONTROLLER_USB | SINO_LITE_CONTROLLER }, -- cgit From b53227360dc2e30fb8809815dfdc39339a34b8dd Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:05 -0800 Subject: HID: sony: Make work handling more generic The driver currently uses sony_schedule_work to submit output reports for the different devices for LEDs or rumble. This patch adds a new parameter to sony_schedule_work to allow scheduling for other types of work. The next patch in this series will utilize this functionality. Considering the driver structure and all error handling it felt best to reuse sony_schedule_work and sony_cancel_work. The idea was inspired by the wacom driver which does something similar. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 98b1b7550745..f82ef68206cf 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -624,6 +624,10 @@ struct ds4_calibration_data { int sens_denom; }; +enum sony_worker { + SONY_WORKER_STATE +}; + struct sony_sc { spinlock_t lock; struct list_head list_node; @@ -645,7 +649,7 @@ struct sony_sc { #endif u8 mac_address[6]; - u8 worker_initialized; + u8 state_worker_initialized; u8 defer_initialization; u8 cable_state; u8 battery_charging; @@ -666,10 +670,14 @@ struct sony_sc { static void sony_set_leds(struct sony_sc *sc); -static inline void sony_schedule_work(struct sony_sc *sc) +static inline void sony_schedule_work(struct sony_sc *sc, + enum sony_worker which) { - if (!sc->defer_initialization) - schedule_work(&sc->state_worker); + switch (which) { + case SONY_WORKER_STATE: + if (!sc->defer_initialization) + schedule_work(&sc->state_worker); + } } static u8 *sixaxis_fixup(struct hid_device *hdev, u8 *rdesc, @@ -1103,7 +1111,7 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, if (sc->defer_initialization) { sc->defer_initialization = 0; - sony_schedule_work(sc); + sony_schedule_work(sc, SONY_WORKER_STATE); } return 0; @@ -1584,7 +1592,7 @@ static void buzz_set_leds(struct sony_sc *sc) static void sony_set_leds(struct sony_sc *sc) { if (!(sc->quirks & BUZZ_CONTROLLER)) - sony_schedule_work(sc); + sony_schedule_work(sc, SONY_WORKER_STATE); else buzz_set_leds(sc); } @@ -1695,7 +1703,7 @@ static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on, new_off != drv_data->led_delay_off[n]) { drv_data->led_delay_on[n] = new_on; drv_data->led_delay_off[n] = new_off; - sony_schedule_work(drv_data); + sony_schedule_work(drv_data, SONY_WORKER_STATE); } return 0; @@ -2025,7 +2033,7 @@ static int sony_play_effect(struct input_dev *dev, void *data, sc->left = effect->u.rumble.strong_magnitude / 256; sc->right = effect->u.rumble.weak_magnitude / 256; - sony_schedule_work(sc); + sony_schedule_work(sc, SONY_WORKER_STATE); return 0; } @@ -2346,15 +2354,15 @@ static inline void sony_init_output_report(struct sony_sc *sc, { sc->send_output_report = send_output_report; - if (!sc->worker_initialized) + if (!sc->state_worker_initialized) INIT_WORK(&sc->state_worker, sony_state_worker); - sc->worker_initialized = 1; + sc->state_worker_initialized = 1; } static inline void sony_cancel_work_sync(struct sony_sc *sc) { - if (sc->worker_initialized) + if (sc->state_worker_initialized) cancel_work_sync(&sc->state_worker); } -- cgit From f2f47c385d2babf44cbd2ec2be4f2b29b88041bc Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:06 -0800 Subject: HID: sony: Support motion sensor calibration on dongle The DualShock 4 dongle isn't connected to a real DualShock 4 at time of driver loading. When a DualShock 4 is plugged in, we need to obtain calibration data (the dongle would have zeros). This patch adds calibration logic, which we schedule on a hotplug from sony_raw_event. In addition this patch adds dongle state handling. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 7 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index f82ef68206cf..dabac09fd934 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -624,8 +624,16 @@ struct ds4_calibration_data { int sens_denom; }; +enum ds4_dongle_state { + DONGLE_DISCONNECTED, + DONGLE_CALIBRATING, + DONGLE_CONNECTED, + DONGLE_DISABLED +}; + enum sony_worker { - SONY_WORKER_STATE + SONY_WORKER_STATE, + SONY_WORKER_HOTPLUG }; struct sony_sc { @@ -636,6 +644,7 @@ struct sony_sc { struct input_dev *sensor_dev; struct led_classdev *leds[MAX_LEDS]; unsigned long quirks; + struct work_struct hotplug_worker; struct work_struct state_worker; void (*send_output_report)(struct sony_sc *); struct power_supply *battery; @@ -649,6 +658,7 @@ struct sony_sc { #endif u8 mac_address[6]; + u8 hotplug_worker_initialized; u8 state_worker_initialized; u8 defer_initialization; u8 cable_state; @@ -663,7 +673,7 @@ struct sony_sc { u16 prev_timestamp; unsigned int timestamp_us; - bool ds4_dongle_connected; + enum ds4_dongle_state ds4_dongle_state; /* DS4 calibration data */ struct ds4_calibration_data ds4_calib_data[6]; }; @@ -677,6 +687,11 @@ static inline void sony_schedule_work(struct sony_sc *sc, case SONY_WORKER_STATE: if (!sc->defer_initialization) schedule_work(&sc->state_worker); + break; + case SONY_WORKER_HOTPLUG: + if (sc->hotplug_worker_initialized) + schedule_work(&sc->hotplug_worker); + break; } } @@ -1085,6 +1100,9 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, dualshock4_parse_report(sc, rd, size); } else if ((sc->quirks & DUALSHOCK4_DONGLE) && rd[0] == 0x01 && size == 64) { + unsigned long flags; + enum ds4_dongle_state dongle_state; + /* * In the case of a DS4 USB dongle, bit[2] of byte 31 indicates * if a DS4 is actually connected (indicated by '0'). @@ -1092,16 +1110,45 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, */ bool connected = (rd[31] & 0x04) ? false : true; - if (!sc->ds4_dongle_connected && connected) { + spin_lock_irqsave(&sc->lock, flags); + dongle_state = sc->ds4_dongle_state; + spin_unlock_irqrestore(&sc->lock, flags); + + /* + * The dongle always sends input reports even when no + * DS4 is attached. When a DS4 is connected, we need to + * obtain calibration data before we can use it. + * The code below tracks dongle state and kicks of + * calibration when needed and only allows us to process + * input if a DS4 is actually connected. + */ + if (dongle_state == DONGLE_DISCONNECTED && connected) { hid_info(sc->hdev, "DualShock 4 USB dongle: controller connected\n"); sony_set_leds(sc); - sc->ds4_dongle_connected = true; - } else if (sc->ds4_dongle_connected && !connected) { + + spin_lock_irqsave(&sc->lock, flags); + sc->ds4_dongle_state = DONGLE_CALIBRATING; + spin_unlock_irqrestore(&sc->lock, flags); + + sony_schedule_work(sc, SONY_WORKER_HOTPLUG); + + /* Don't process the report since we don't have + * calibration data, but let hidraw have it anyway. + */ + return 0; + } else if ((dongle_state == DONGLE_CONNECTED || + dongle_state == DONGLE_DISABLED) && !connected) { hid_info(sc->hdev, "DualShock 4 USB dongle: controller disconnected\n"); - sc->ds4_dongle_connected = false; + + spin_lock_irqsave(&sc->lock, flags); + sc->ds4_dongle_state = DONGLE_DISCONNECTED; + spin_unlock_irqrestore(&sc->lock, flags); + /* Return 0, so hidraw can get the report. */ return 0; - } else if (!sc->ds4_dongle_connected) { + } else if (dongle_state == DONGLE_CALIBRATING || + dongle_state == DONGLE_DISABLED || + dongle_state == DONGLE_DISCONNECTED) { /* Return 0, so hidraw can get the report. */ return 0; } @@ -1518,6 +1565,33 @@ err_stop: return ret; } +static void dualshock4_calibration_work(struct work_struct *work) +{ + struct sony_sc *sc = container_of(work, struct sony_sc, hotplug_worker); + unsigned long flags; + enum ds4_dongle_state dongle_state; + int ret; + + ret = dualshock4_get_calibration_data(sc); + if (ret < 0) { + /* This call is very unlikely to fail for the dongle. When it + * fails we are probably in a very bad state, so mark the + * dongle as disabled. We will re-enable the dongle if a new + * DS4 hotplug is detect from sony_raw_event as any issues + * are likely resolved then (the dongle is quite stupid). + */ + hid_err(sc->hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n"); + dongle_state = DONGLE_DISABLED; + } else { + hid_info(sc->hdev, "DualShock 4 USB dongle: calibration completed\n"); + dongle_state = DONGLE_CONNECTED; + } + + spin_lock_irqsave(&sc->lock, flags); + sc->ds4_dongle_state = dongle_state; + spin_unlock_irqrestore(&sc->lock, flags); +} + static void sixaxis_set_leds_from_id(struct sony_sc *sc) { static const u8 sixaxis_leds[10][4] = { @@ -2362,6 +2436,8 @@ static inline void sony_init_output_report(struct sony_sc *sc, static inline void sony_cancel_work_sync(struct sony_sc *sc) { + if (sc->hotplug_worker_initialized) + cancel_work_sync(&sc->hotplug_worker); if (sc->state_worker_initialized) cancel_work_sync(&sc->state_worker); } @@ -2443,6 +2519,12 @@ static int sony_input_configured(struct hid_device *hdev, goto err_stop; } + if (sc->quirks & DUALSHOCK4_DONGLE) { + INIT_WORK(&sc->hotplug_worker, dualshock4_calibration_work); + sc->hotplug_worker_initialized = 1; + sc->ds4_dongle_state = DONGLE_DISCONNECTED; + } + sony_init_output_report(sc, dualshock4_send_output_report); } else if (sc->quirks & MOTION_CONTROLLER) { sony_init_output_report(sc, motion_send_output_report); -- cgit From b9f7d245e34b1f42b5389b015962a2f022d4ece2 Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:07 -0800 Subject: HID: sony: Mark DS4 touchpad device as a pointer Currently the DS4 touchpad device is neither classified as a direct input device nor as a pointer device. It makes most sense to mark it as a pointer device. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index dabac09fd934..dedaefa3e1f9 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1236,7 +1236,7 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count, snprintf(name, name_sz, "%s" DS4_TOUCHPAD_SUFFIX, sc->hdev->name); sc->touchpad->name = name; - ret = input_mt_init_slots(sc->touchpad, touch_count, 0); + ret = input_mt_init_slots(sc->touchpad, touch_count, INPUT_MT_POINTER); if (ret < 0) goto err; -- cgit From e19a267b9987135c00155a51e683e434b9abb56b Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:08 -0800 Subject: HID: sony: DS3 comply to Linux gamepad spec The axis and button mapping for the DS3 is strange. This is mostly due to the device reporting many axes as for every digital button it also has an analog button. Due to amount of analog values it is even leaking well into the MT axes range. We felt it is best to remove the many analog buttons and just report digital and comply to the Linux gamepad spec. The analog buttons are rarely used on the official platform, let alone on Linux. This patch does remove motion sensor support (added back in another patch). Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 176 +++++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 95 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index dedaefa3e1f9..b1a21ba4263a 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -75,89 +75,6 @@ #define MAX_LEDS 4 -/* - * The Sixaxis reports both digital and analog values for each button on the - * controller except for Start, Select and the PS button. The controller ends - * up reporting 27 axes which causes them to spill over into the multi-touch - * axis values. Additionally, the controller only has 20 actual, physical axes - * so there are several unused axes in between the used ones. - */ -static u8 sixaxis_rdesc[] = { - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x04, /* Usage (Joystick), */ - 0xA1, 0x01, /* Collection (Application), */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0x01, /* Report ID (1), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x01, /* Report Count (1), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x81, 0x03, /* Input (Constant, Variable), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x13, /* Report Count (19), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x01, /* Logical Maximum (1), */ - 0x35, 0x00, /* Physical Minimum (0), */ - 0x45, 0x01, /* Physical Maximum (1), */ - 0x05, 0x09, /* Usage Page (Button), */ - 0x19, 0x01, /* Usage Minimum (01h), */ - 0x29, 0x13, /* Usage Maximum (13h), */ - 0x81, 0x02, /* Input (Variable), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x0D, /* Report Count (13), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x81, 0x03, /* Input (Constant, Variable), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xA1, 0x00, /* Collection (Physical), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x04, /* Report Count (4), */ - 0x35, 0x00, /* Physical Minimum (0), */ - 0x46, 0xFF, 0x00, /* Physical Maximum (255), */ - 0x09, 0x30, /* Usage (X), */ - 0x09, 0x31, /* Usage (Y), */ - 0x09, 0x32, /* Usage (Z), */ - 0x09, 0x35, /* Usage (Rz), */ - 0x81, 0x02, /* Input (Variable), */ - 0xC0, /* End Collection, */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x95, 0x13, /* Report Count (19), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0x81, 0x02, /* Input (Variable), */ - 0x95, 0x0C, /* Report Count (12), */ - 0x81, 0x01, /* Input (Constant), */ - 0x75, 0x10, /* Report Size (16), */ - 0x95, 0x04, /* Report Count (4), */ - 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ - 0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0x81, 0x02, /* Input (Variable), */ - 0xC0, /* End Collection, */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0x02, /* Report ID (2), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0xEE, /* Report ID (238), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0xEF, /* Report ID (239), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xC0 /* End Collection */ -}; /* PS/3 Motion controller */ static u8 motion_rdesc[] = { @@ -508,6 +425,34 @@ static const unsigned int buzz_keymap[] = { [20] = BTN_TRIGGER_HAPPY20, }; +static const unsigned int sixaxis_absmap[] = { + [0x30] = ABS_X, + [0x31] = ABS_Y, + [0x32] = ABS_RX, /* right stick X */ + [0x35] = ABS_RY, /* right stick Y */ +}; + +static const unsigned int sixaxis_keymap[] = { + [0x01] = BTN_SELECT, /* Select */ + [0x02] = BTN_THUMBL, /* L3 */ + [0x03] = BTN_THUMBR, /* R3 */ + [0x04] = BTN_START, /* Start */ + [0x05] = BTN_DPAD_UP, /* Up */ + [0x06] = BTN_DPAD_RIGHT, /* Right */ + [0x07] = BTN_DPAD_DOWN, /* Down */ + [0x08] = BTN_DPAD_LEFT, /* Left */ + [0x09] = BTN_TL2, /* L2 */ + [0x0a] = BTN_TR2, /* R2 */ + [0x0b] = BTN_TL, /* L1 */ + [0x0c] = BTN_TR, /* R1 */ + [0x0d] = BTN_NORTH, /* Triangle */ + [0x0e] = BTN_EAST, /* Circle */ + [0x0f] = BTN_SOUTH, /* Cross */ + [0x10] = BTN_WEST, /* Square */ + [0x11] = BTN_MODE, /* PS */ +}; + + static const unsigned int ds4_absmap[] = { [0x30] = ABS_X, [0x31] = ABS_Y, @@ -695,13 +640,6 @@ static inline void sony_schedule_work(struct sony_sc *sc, } } -static u8 *sixaxis_fixup(struct hid_device *hdev, u8 *rdesc, - unsigned int *rsize) -{ - *rsize = sizeof(sixaxis_rdesc); - return sixaxis_rdesc; -} - static u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) { @@ -757,6 +695,54 @@ static int ps3remote_mapping(struct hid_device *hdev, struct hid_input *hi, return 1; } +static int sixaxis_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) { + unsigned int key = usage->hid & HID_USAGE; + + if (key >= ARRAY_SIZE(sixaxis_keymap)) + return -1; + + key = sixaxis_keymap[key]; + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); + return 1; + } else if (usage->hid == HID_GD_POINTER) { + /* The DS3 provides analog values for most buttons and even + * for HAT axes through GD Pointer. L2 and R2 are reported + * among these as well instead of as GD Z / RZ. Remap L2 + * and R2 and ignore other analog 'button axes' as there is + * no good way for reporting them. + */ + switch (usage->usage_index) { + case 8: /* L2 */ + usage->hid = HID_GD_Z; + break; + case 9: /* R2 */ + usage->hid = HID_GD_RZ; + break; + default: + return -1; + } + + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, usage->hid & 0xf); + return 1; + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + unsigned int abs = usage->hid & HID_USAGE; + + if (abs >= ARRAY_SIZE(sixaxis_absmap)) + return -1; + + abs = sixaxis_absmap[abs]; + + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs); + return 1; + } + + return -1; +} + static int ds4_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -812,9 +798,6 @@ static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc, rdesc[55] = 0x06; } - if (sc->quirks & SIXAXIS_CONTROLLER) - return sixaxis_fixup(hdev, rdesc, rsize); - if (sc->quirks & MOTION_CONTROLLER) return motion_fixup(hdev, rdesc, rsize); @@ -1196,10 +1179,13 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, if (sc->quirks & PS3REMOTE) return ps3remote_mapping(hdev, hi, field, usage, bit, max); + if (sc->quirks & SIXAXIS_CONTROLLER) + return sixaxis_mapping(hdev, hi, field, usage, bit, max); if (sc->quirks & DUALSHOCK4_CONTROLLER) return ds4_mapping(hdev, hi, field, usage, bit, max); + /* Let hid-core decide for the others */ return 0; } @@ -2613,13 +2599,13 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) else if (sc->quirks & SIXAXIS_CONTROLLER) connect_mask |= HID_CONNECT_HIDDEV_FORCE; - /* Patch the hw version on DS4 compatible devices, so applications can + /* Patch the hw version on DS3/4 compatible devices, so applications can * distinguish between the default HID mappings and the mappings defined * by the Linux game controller spec. This is important for the SDL2 * library, which has a game controller database, which uses device ids * in combination with version as a key. */ - if (sc->quirks & DUALSHOCK4_CONTROLLER) + if (sc->quirks & (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)) hdev->version |= 0x8000; ret = hid_hw_start(hdev, connect_mask); -- cgit From 80ecc48c0ade5e99cc9d84febd17c44a4f011c13 Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:09 -0800 Subject: HID: sony: Print error on failure to active DS3 / Navigation controllers This patch adds printing when we failed to activate DS3 / Nagivation controllers and checks the return value for these failures earlier in sony_input_configured. This paves the way for other configuration logic for these devices, which we don't want to call if for example the activation failed. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index b1a21ba4263a..fa2ea085fec8 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2468,7 +2468,13 @@ static int sony_input_configured(struct hid_device *hdev, hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID; sc->defer_initialization = 1; + ret = sixaxis_set_operational_usb(hdev); + if (ret < 0) { + hid_err(hdev, "Failed to set controller into operational mode\n"); + goto err_stop; + } + sony_init_output_report(sc, sixaxis_send_output_report); } else if ((sc->quirks & SIXAXIS_CONTROLLER_BT) || (sc->quirks & NAVIGATION_CONTROLLER_BT)) { @@ -2477,7 +2483,13 @@ static int sony_input_configured(struct hid_device *hdev, * when connected via Bluetooth. */ hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; + ret = sixaxis_set_operational_bt(hdev); + if (ret < 0) { + hid_err(hdev, "Failed to set controller into operational mode\n"); + goto err_stop; + } + sony_init_output_report(sc, sixaxis_send_output_report); } else if (sc->quirks & DUALSHOCK4_CONTROLLER) { ret = dualshock4_get_calibration_data(sc); -- cgit From 510c8b7c168bcad75e2864f73e5125d7218ee3cf Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:10 -0800 Subject: HID: sony: Expose DS3 motion sensors through separate device This patch adds a separate evdev node for the DS3 its motion sensors. We only expose the accelerometers as the gyroscope is extremely difficult to manage and behavior varies a lot between hardware revisions. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 130 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 25 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index fa2ea085fec8..d8a13081701f 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -547,12 +547,15 @@ struct motion_output_report_02 { #define DS4_INPUT_REPORT_BATTERY_OFFSET 30 #define DS4_INPUT_REPORT_TOUCHPAD_OFFSET 33 -#define DS4_SENSOR_SUFFIX " Motion Sensors" +#define SENSOR_SUFFIX " Motion Sensors" #define DS4_TOUCHPAD_SUFFIX " Touchpad" #define DS4_GYRO_RES_PER_DEG_S 1024 #define DS4_ACC_RES_PER_G 8192 +#define SIXAXIS_INPUT_REPORT_ACC_X_OFFSET 41 +#define SIXAXIS_ACC_RES_PER_G 113 + static DEFINE_SPINLOCK(sony_dev_list_lock); static LIST_HEAD(sony_device_list); static DEFINE_IDA(sony_device_id_allocator); @@ -841,6 +844,23 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size) sc->battery_capacity = battery_capacity; sc->battery_charging = battery_charging; spin_unlock_irqrestore(&sc->lock, flags); + + if (sc->quirks & SIXAXIS_CONTROLLER) { + int val; + + offset = SIXAXIS_INPUT_REPORT_ACC_X_OFFSET; + val = ((rd[offset+1] << 8) | rd[offset]) - 511; + input_report_abs(sc->sensor_dev, ABS_X, val); + + /* Y and Z are swapped and inversed */ + val = 511 - ((rd[offset+5] << 8) | rd[offset+4]); + input_report_abs(sc->sensor_dev, ABS_Y, val); + + val = 511 - ((rd[offset+3] << 8) | rd[offset+2]); + input_report_abs(sc->sensor_dev, ABS_Z, val); + + input_sync(sc->sensor_dev); + } } static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size) @@ -1285,33 +1305,49 @@ static int sony_register_sensors(struct sony_sc *sc) /* Append a suffix to the controller name as there are various * DS4 compatible non-Sony devices with different names. */ - name_sz = strlen(sc->hdev->name) + sizeof(DS4_SENSOR_SUFFIX); + name_sz = strlen(sc->hdev->name) + sizeof(SENSOR_SUFFIX); name = kzalloc(name_sz, GFP_KERNEL); if (!name) { ret = -ENOMEM; goto err; } - snprintf(name, name_sz, "%s" DS4_SENSOR_SUFFIX, sc->hdev->name); + snprintf(name, name_sz, "%s" SENSOR_SUFFIX, sc->hdev->name); sc->sensor_dev->name = name; - range = DS4_ACC_RES_PER_G*4; - input_set_abs_params(sc->sensor_dev, ABS_X, -range, range, 16, 0); - input_set_abs_params(sc->sensor_dev, ABS_Y, -range, range, 16, 0); - input_set_abs_params(sc->sensor_dev, ABS_Z, -range, range, 16, 0); - input_abs_set_res(sc->sensor_dev, ABS_X, DS4_ACC_RES_PER_G); - input_abs_set_res(sc->sensor_dev, ABS_Y, DS4_ACC_RES_PER_G); - input_abs_set_res(sc->sensor_dev, ABS_Z, DS4_ACC_RES_PER_G); - - range = DS4_GYRO_RES_PER_DEG_S*2048; - input_set_abs_params(sc->sensor_dev, ABS_RX, -range, range, 16, 0); - input_set_abs_params(sc->sensor_dev, ABS_RY, -range, range, 16, 0); - input_set_abs_params(sc->sensor_dev, ABS_RZ, -range, range, 16, 0); - input_abs_set_res(sc->sensor_dev, ABS_RX, DS4_GYRO_RES_PER_DEG_S); - input_abs_set_res(sc->sensor_dev, ABS_RY, DS4_GYRO_RES_PER_DEG_S); - input_abs_set_res(sc->sensor_dev, ABS_RZ, DS4_GYRO_RES_PER_DEG_S); - - __set_bit(EV_MSC, sc->sensor_dev->evbit); - __set_bit(MSC_TIMESTAMP, sc->sensor_dev->mscbit); + if (sc->quirks & SIXAXIS_CONTROLLER) { + /* For the DS3 we only support the accelerometer, which works + * quite well even without calibration. The device also has + * a 1-axis gyro, but it is very difficult to manage from within + * the driver even to get data, the sensor is inaccurate and + * the behavior is very different between hardware revisions. + */ + input_set_abs_params(sc->sensor_dev, ABS_X, -512, 511, 4, 0); + input_set_abs_params(sc->sensor_dev, ABS_Y, -512, 511, 4, 0); + input_set_abs_params(sc->sensor_dev, ABS_Z, -512, 511, 4, 0); + input_abs_set_res(sc->sensor_dev, ABS_X, SIXAXIS_ACC_RES_PER_G); + input_abs_set_res(sc->sensor_dev, ABS_Y, SIXAXIS_ACC_RES_PER_G); + input_abs_set_res(sc->sensor_dev, ABS_Z, SIXAXIS_ACC_RES_PER_G); + } else if (sc->quirks & DUALSHOCK4_CONTROLLER) { + range = DS4_ACC_RES_PER_G*4; + input_set_abs_params(sc->sensor_dev, ABS_X, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_Y, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_Z, -range, range, 16, 0); + input_abs_set_res(sc->sensor_dev, ABS_X, DS4_ACC_RES_PER_G); + input_abs_set_res(sc->sensor_dev, ABS_Y, DS4_ACC_RES_PER_G); + input_abs_set_res(sc->sensor_dev, ABS_Z, DS4_ACC_RES_PER_G); + + range = DS4_GYRO_RES_PER_DEG_S*2048; + input_set_abs_params(sc->sensor_dev, ABS_RX, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_RY, -range, range, 16, 0); + input_set_abs_params(sc->sensor_dev, ABS_RZ, -range, range, 16, 0); + input_abs_set_res(sc->sensor_dev, ABS_RX, DS4_GYRO_RES_PER_DEG_S); + input_abs_set_res(sc->sensor_dev, ABS_RY, DS4_GYRO_RES_PER_DEG_S); + input_abs_set_res(sc->sensor_dev, ABS_RZ, DS4_GYRO_RES_PER_DEG_S); + + __set_bit(EV_MSC, sc->sensor_dev->evbit); + __set_bit(MSC_TIMESTAMP, sc->sensor_dev->mscbit); + } + __set_bit(INPUT_PROP_ACCELEROMETER, sc->sensor_dev->propbit); ret = input_register_device(sc->sensor_dev); @@ -2447,8 +2483,7 @@ static int sony_input_configured(struct hid_device *hdev, goto err_stop; } - if ((sc->quirks & SIXAXIS_CONTROLLER_USB) || - (sc->quirks & NAVIGATION_CONTROLLER_USB)) { + if (sc->quirks & NAVIGATION_CONTROLLER_USB) { /* * The Sony Sixaxis does not handle HID Output Reports on the * Interrupt EP like it could, so we need to force HID Output @@ -2476,8 +2511,46 @@ static int sony_input_configured(struct hid_device *hdev, } sony_init_output_report(sc, sixaxis_send_output_report); - } else if ((sc->quirks & SIXAXIS_CONTROLLER_BT) || - (sc->quirks & NAVIGATION_CONTROLLER_BT)) { + } else if (sc->quirks & NAVIGATION_CONTROLLER_BT) { + /* + * The Navigation controller wants output reports sent on the ctrl + * endpoint when connected via Bluetooth. + */ + hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; + + ret = sixaxis_set_operational_bt(hdev); + if (ret < 0) { + hid_err(hdev, "Failed to set controller into operational mode\n"); + goto err_stop; + } + + sony_init_output_report(sc, sixaxis_send_output_report); + } else if (sc->quirks & SIXAXIS_CONTROLLER_USB) { + /* + * The Sony Sixaxis does not handle HID Output Reports on the + * Interrupt EP and the device only becomes active when the + * PS button is pressed. See comment for Navigation controller + * above for more details. + */ + hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; + hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID; + sc->defer_initialization = 1; + + ret = sixaxis_set_operational_usb(hdev); + if (ret < 0) { + hid_err(hdev, "Failed to set controller into operational mode\n"); + goto err_stop; + } + + ret = sony_register_sensors(sc); + if (ret) { + hid_err(sc->hdev, + "Unable to initialize motion sensors: %d\n", ret); + goto err_stop; + } + + sony_init_output_report(sc, sixaxis_send_output_report); + } else if (sc->quirks & SIXAXIS_CONTROLLER_BT) { /* * The Sixaxis wants output reports sent on the ctrl endpoint * when connected via Bluetooth. @@ -2490,6 +2563,13 @@ static int sony_input_configured(struct hid_device *hdev, goto err_stop; } + ret = sony_register_sensors(sc); + if (ret) { + hid_err(sc->hdev, + "Unable to initialize motion sensors: %d\n", ret); + goto err_stop; + } + sony_init_output_report(sc, sixaxis_send_output_report); } else if (sc->quirks & DUALSHOCK4_CONTROLLER) { ret = dualshock4_get_calibration_data(sc); -- cgit From df848bc05db1c49756a2befbe03ab03ce34a6bcc Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Tue, 7 Mar 2017 15:45:11 -0800 Subject: HID: sony: Perform duplicate device check earlier on Game controllers can be connected twice through USB and BT. Only one connection is allowed. Currently we perform a check for duplicate controllers halfway through device initialization. To prevent 'transient' devices, we should do this check as early as we can. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index d8a13081701f..5eb5ac922c09 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2477,6 +2477,10 @@ static int sony_input_configured(struct hid_device *hdev, goto err_stop; } + ret = append_dev_id = sony_check_add(sc); + if (ret < 0) + goto err_stop; + ret = sony_allocate_output_report(sc); if (ret < 0) { hid_err(hdev, "failed to allocate the output report buffer\n"); @@ -2610,10 +2614,6 @@ static int sony_input_configured(struct hid_device *hdev, ret = 0; } - if (ret < 0) - goto err_stop; - - ret = append_dev_id = sony_check_add(sc); if (ret < 0) goto err_stop; -- cgit From 42cb6b35b9e6b226c9b762627beec353642ff521 Mon Sep 17 00:00:00 2001 From: Jaejoong Kim 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 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 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 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 From 7877474905460e084bdfa65a82b9280767cfbfd6 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Wed, 1 Mar 2017 15:48:50 -0600 Subject: HID: asus: drop dependency on I2C_HID There is nothing transport-specific in this driver, and we will now be adding support for some Asus USB devices too. Signed-off-by: Daniel Drake Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 8c54cb8f5d6d..b22bcefd5e85 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -136,7 +136,6 @@ config HID_APPLEIR config HID_ASUS tristate "Asus" - depends on I2C_HID ---help--- Support for Asus notebook built-in keyboard and touchpad via i2c. -- cgit From 1caccc2565a83b79dd6e4af15bea35995487a379 Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Wed, 1 Mar 2017 15:48:51 -0600 Subject: HID: asus: support Republic of Gamers special keys Add support for the special keys found on the internal keyboard of the Asus Republic of Gamers (ROG) laptop models GL553VD, GL553VE, GL753VD and GL753VE. Signed-off-by: Chris Chiu Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 5 ++++- drivers/hid/hid-asus.c | 38 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 2 ++ drivers/hid/hid-ids.h | 2 ++ 4 files changed, 46 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index b22bcefd5e85..de4ed3752a4e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -137,11 +137,14 @@ config HID_APPLEIR config HID_ASUS tristate "Asus" ---help--- - Support for Asus notebook built-in keyboard and touchpad via i2c. + Support for Asus notebook built-in keyboard and touchpad via i2c, and + the Asus Republic of Gamers laptop keyboard special keys. Supported devices: - EeeBook X205TA - VivoBook E200HA + - GL553V series + - GL753V series config HID_AUREAL tristate "Aureal" diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 70b12f89a193..5bae6ffcae94 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -199,6 +199,8 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) return 0; } +#define rog_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, \ + max, EV_KEY, (c)) static int asus_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, @@ -213,6 +215,38 @@ static int asus_input_mapping(struct hid_device *hdev, return -1; } + /* ASUS Republic of Gamers laptop keyboard hotkeys */ + if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) { + set_bit(EV_REP, hi->input->evbit); + switch (usage->hid & HID_USAGE) { + case 0x10: rog_map_key_clear(KEY_BRIGHTNESSDOWN); break; + case 0x20: rog_map_key_clear(KEY_BRIGHTNESSUP); break; + case 0x35: rog_map_key_clear(KEY_DISPLAY_OFF); break; + case 0x6c: rog_map_key_clear(KEY_SLEEP); break; + case 0x82: rog_map_key_clear(KEY_CAMERA); break; + case 0x88: rog_map_key_clear(KEY_WLAN); break; + case 0xb5: rog_map_key_clear(KEY_CALC); break; + case 0xc4: rog_map_key_clear(KEY_KBDILLUMUP); break; + case 0xc5: rog_map_key_clear(KEY_KBDILLUMDOWN); break; + + /* ASUS touchpad toggle */ + case 0x6b: rog_map_key_clear(KEY_F21); break; + + /* ROG key */ + case 0x38: rog_map_key_clear(KEY_PROG1); break; + + /* Fn+C ASUS Splendid */ + case 0xba: rog_map_key_clear(KEY_PROG2); break; + + /* Fn+Space Power4Gear Hybrid */ + case 0x5c: rog_map_key_clear(KEY_PROG3); break; + + default: + return 0; + } + return 1; + } + return 0; } @@ -323,6 +357,10 @@ static const struct hid_device_id asus_devices[] = { USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD), KEYBOARD_QUIRKS}, { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_TOUCHPAD), TOUCHPAD_QUIRKS }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2) }, { } }; MODULE_DEVICE_TABLE(hid, asus_devices); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3ceb4a2af381..5408bce3428b 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1854,6 +1854,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD) }, { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_TOUCHPAD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) }, { HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) }, { HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185BFM, 0x2208) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 0e2e7c571d22..ae4e90f166d3 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -175,6 +175,8 @@ #define USB_DEVICE_ID_ASUSTEK_LCM2 0x175b #define USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD 0x8585 #define USB_DEVICE_ID_ASUSTEK_TOUCHPAD 0x0101 +#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854 +#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837 #define USB_VENDOR_ID_ATEN 0x0557 #define USB_DEVICE_ID_ATEN_UC100KM 0x2004 -- cgit From a93913e1496d6103ec0b76d8397fc04ece55f570 Mon Sep 17 00:00:00 2001 From: Matjaz Hegedic Date: Thu, 9 Mar 2017 00:31:13 +0100 Subject: HID: asus: fix and generalize ambiguous preprocessor macros Before commits a1cbda7a65a7a ("HID: asus: drop dependency on I2C_HID") and 64a403c6555fd ("HID: asus: support Republic Of Gamers special keys") hid-asus only pertained to a single I2C keyboard model found in ASUS X205TA, F205TA, & X200HA. The aforementioned commits expanded this support to other ASUS laptop keyboard models. In order to clarify that existing keyboard and touchpad quirks only apply to the I2C devices, and not ASUS keyboards in general, I2C HID IDs and their corresponding quirk sets have been renamed. In addition, the latter commit introduced special key handling, which also applies to the I2C keyboard, not just Republic of Gamers series. Therefore, the rog_map_key_clear() macro is renamed to asus_map_key_clear() for the sake of generality. Signed-off-by: Matjaz Hegedic Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-asus.c | 38 +++++++++++++++++++------------------- drivers/hid/hid-core.c | 4 ++-- drivers/hid/hid-ids.h | 4 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 5bae6ffcae94..20fe97fb9bbc 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -64,9 +64,9 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_SKIP_INPUT_MAPPING BIT(2) #define QUIRK_IS_MULTITOUCH BIT(3) -#define KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ +#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS) -#define TOUCHPAD_QUIRKS (QUIRK_NO_INIT_REPORTS | \ +#define I2C_TOUCHPAD_QUIRKS (QUIRK_NO_INIT_REPORTS | \ QUIRK_SKIP_INPUT_MAPPING | \ QUIRK_IS_MULTITOUCH) @@ -199,7 +199,7 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) return 0; } -#define rog_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, \ +#define asus_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, \ max, EV_KEY, (c)) static int asus_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, @@ -215,31 +215,31 @@ static int asus_input_mapping(struct hid_device *hdev, return -1; } - /* ASUS Republic of Gamers laptop keyboard hotkeys */ + /* ASUS-specific keyboard hotkeys */ if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) { set_bit(EV_REP, hi->input->evbit); switch (usage->hid & HID_USAGE) { - case 0x10: rog_map_key_clear(KEY_BRIGHTNESSDOWN); break; - case 0x20: rog_map_key_clear(KEY_BRIGHTNESSUP); break; - case 0x35: rog_map_key_clear(KEY_DISPLAY_OFF); break; - case 0x6c: rog_map_key_clear(KEY_SLEEP); break; - case 0x82: rog_map_key_clear(KEY_CAMERA); break; - case 0x88: rog_map_key_clear(KEY_WLAN); break; - case 0xb5: rog_map_key_clear(KEY_CALC); break; - case 0xc4: rog_map_key_clear(KEY_KBDILLUMUP); break; - case 0xc5: rog_map_key_clear(KEY_KBDILLUMDOWN); break; + case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN); break; + case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP); break; + case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF); break; + case 0x6c: asus_map_key_clear(KEY_SLEEP); break; + case 0x82: asus_map_key_clear(KEY_CAMERA); break; + case 0x88: asus_map_key_clear(KEY_WLAN); break; + case 0xb5: asus_map_key_clear(KEY_CALC); break; + case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break; + case 0xc5: asus_map_key_clear(KEY_KBDILLUMDOWN); break; /* ASUS touchpad toggle */ - case 0x6b: rog_map_key_clear(KEY_F21); break; + case 0x6b: asus_map_key_clear(KEY_F21); break; /* ROG key */ - case 0x38: rog_map_key_clear(KEY_PROG1); break; + case 0x38: asus_map_key_clear(KEY_PROG1); break; /* Fn+C ASUS Splendid */ - case 0xba: rog_map_key_clear(KEY_PROG2); break; + case 0xba: asus_map_key_clear(KEY_PROG2); break; /* Fn+Space Power4Gear Hybrid */ - case 0x5c: rog_map_key_clear(KEY_PROG3); break; + case 0x5c: asus_map_key_clear(KEY_PROG3); break; default: return 0; @@ -354,9 +354,9 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, static const struct hid_device_id asus_devices[] = { { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, - USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD), KEYBOARD_QUIRKS}, + USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS}, { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, - USB_DEVICE_ID_ASUSTEK_TOUCHPAD), TOUCHPAD_QUIRKS }, + USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 5408bce3428b..d4910ef4191e 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1852,8 +1852,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, - { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD) }, - { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_TOUCHPAD) }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD) }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index ae4e90f166d3..d106213f69a4 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -173,8 +173,8 @@ #define USB_VENDOR_ID_ASUSTEK 0x0b05 #define USB_DEVICE_ID_ASUSTEK_LCM 0x1726 #define USB_DEVICE_ID_ASUSTEK_LCM2 0x175b -#define USB_DEVICE_ID_ASUSTEK_NOTEBOOK_KEYBOARD 0x8585 -#define USB_DEVICE_ID_ASUSTEK_TOUCHPAD 0x0101 +#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585 +#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837 -- cgit From 0485b1ec280cb9b2b6ed6e44e999294b4c698182 Mon Sep 17 00:00:00 2001 From: Matjaz Hegedic Date: Thu, 9 Mar 2017 00:31:14 +0100 Subject: HID: asus: ignore declared dummy usages Keyboards handled by hid-asus declare special key functions using a vendor-specific page, however, alongside legitimate key functions, dummy usages with seemingly arbitrary values are also declared and can lead to keyboards being detected as pointer devices by some software (such as X.org). In addition, for the I2C keyboard volume controls are separately declared in a Consumer Usage page, with the same dummy usage problem. The fix in 1989dada7ce0 ("HID: input: ignore System Control application usages if not System Controls") does not mitigate the problem described above, therefore dummy usages need to be ignored in the driver itself. This fix properly ignores dummy usages and introduces a quirk for custom handling of the Consumer Usages on the I2C keyboard. Signed-off-by: Matjaz Hegedic Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-asus.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 20fe97fb9bbc..e0de54b4f499 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -63,9 +63,11 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_NO_INIT_REPORTS BIT(1) #define QUIRK_SKIP_INPUT_MAPPING BIT(2) #define QUIRK_IS_MULTITOUCH BIT(3) +#define QUIRK_NO_CONSUMER_USAGES BIT(4) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ - QUIRK_NO_INIT_REPORTS) + QUIRK_NO_INIT_REPORTS | \ + QUIRK_NO_CONSUMER_USAGES) #define I2C_TOUCHPAD_QUIRKS (QUIRK_NO_INIT_REPORTS | \ QUIRK_SKIP_INPUT_MAPPING | \ QUIRK_IS_MULTITOUCH) @@ -242,11 +244,28 @@ static int asus_input_mapping(struct hid_device *hdev, case 0x5c: asus_map_key_clear(KEY_PROG3); break; default: - return 0; + /* ASUS lazily declares 256 usages, ignore the rest, + * as some make the keyboard appear as a pointer device. */ + return -1; } return 1; } + if (drvdata->quirks & QUIRK_NO_CONSUMER_USAGES && + (usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) { + switch (usage->hid & HID_USAGE) { + case 0xe2: /* Mute */ + case 0xe9: /* Volume up */ + case 0xea: /* Volume down */ + return 0; + default: + /* Ignore dummy Consumer usages which make the + * keyboard incorrectly appear as a pointer device. + */ + return -1; + } + } + return 0; } -- cgit From 802b24b475e459e985681d6e0815ae6cb52e5560 Mon Sep 17 00:00:00 2001 From: Matjaz Hegedic Date: Thu, 9 Mar 2017 00:31:15 +0100 Subject: HID: asus: change mapping from KEY_WLAN to KEY_RFKILL The input mapping code incorrectly maps the Airplane Mode button to KEY_WLAN, which stands for WiFi toggle, but doesn't affect Bluetooth (and other active radios) which is expected behavior for Airplane Mode. The fix replaces KEY_WLAN with the more appropriate KEY_RFKILL. The declared usage code 0x88 corresponds to Airplane Mode button on all keyboards handled by hid-asus (I2C netbook keyboards and USB RoG series keyboards), so the fix doesn't introduce any inconsistencies across different models. Signed-off-by: Matjaz Hegedic Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-asus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index e0de54b4f499..bacba97668bf 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -226,7 +226,7 @@ static int asus_input_mapping(struct hid_device *hdev, case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF); break; case 0x6c: asus_map_key_clear(KEY_SLEEP); break; case 0x82: asus_map_key_clear(KEY_CAMERA); break; - case 0x88: asus_map_key_clear(KEY_WLAN); break; + case 0x88: asus_map_key_clear(KEY_RFKILL); break; case 0xb5: asus_map_key_clear(KEY_CALC); break; case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break; case 0xc5: asus_map_key_clear(KEY_KBDILLUMDOWN); break; -- cgit 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 From 5a144be39c3a32b3072529ccee79e4ec9eb9b275 Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Fri, 24 Mar 2017 15:17:45 -0700 Subject: HID: sony: Use DS3 MAC address as unique identifier on USB The DS3 MAC address is reported as a unique identified when using Bluetooth. For USB there is no unique identifier reported yet, so use the MAC address. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 5eb5ac922c09..38674f51be8e 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2400,6 +2400,12 @@ static int sony_check_add(struct sony_sc *sc) */ for (n = 0; n < 6; n++) sc->mac_address[5-n] = buf[4+n]; + + snprintf(sc->hdev->uniq, sizeof(sc->hdev->uniq), + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + sc->mac_address[5], sc->mac_address[4], + sc->mac_address[3], sc->mac_address[2], + sc->mac_address[1], sc->mac_address[0]); } else { return 0; } -- cgit From b8f0970d2c5a03f5a431d51af74dd1a0ec62fe91 Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Fri, 24 Mar 2017 15:17:46 -0700 Subject: HID: sony: Improve navigation controller axis/button mapping The navigation controller is a DS3 (sixaxis) with fewer physical axes and buttons. It utilizes the same HID report as the DS3 and thus reports axes/buttons which aren't physically present. Currently many non-existing buttons and axes are reported, which we are now removing. For the axes/buttons which do exist, we make the axis/button mapping similar to the DS3. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 186 ++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 101 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 38674f51be8e..85d24be74db3 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -173,96 +173,6 @@ static u8 motion_rdesc[] = { 0xC0 /* End Collection */ }; -/* PS/3 Navigation controller */ -static u8 navigation_rdesc[] = { - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x04, /* Usage (Joystick), */ - 0xA1, 0x01, /* Collection (Application), */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0x01, /* Report ID (1), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x01, /* Report Count (1), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x81, 0x03, /* Input (Constant, Variable), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x13, /* Report Count (19), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x25, 0x01, /* Logical Maximum (1), */ - 0x35, 0x00, /* Physical Minimum (0), */ - 0x45, 0x01, /* Physical Maximum (1), */ - 0x05, 0x09, /* Usage Page (Button), */ - 0x19, 0x01, /* Usage Minimum (01h), */ - 0x29, 0x13, /* Usage Maximum (13h), */ - 0x81, 0x02, /* Input (Variable), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x0D, /* Report Count (13), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x81, 0x03, /* Input (Constant, Variable), */ - 0x15, 0x00, /* Logical Minimum (0), */ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xA1, 0x00, /* Collection (Physical), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x02, /* Report Count (2), */ - 0x35, 0x00, /* Physical Minimum (0), */ - 0x46, 0xFF, 0x00, /* Physical Maximum (255), */ - 0x09, 0x30, /* Usage (X), */ - 0x09, 0x31, /* Usage (Y), */ - 0x81, 0x02, /* Input (Variable), */ - 0xC0, /* End Collection, */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x95, 0x06, /* Report Count (6), */ - 0x81, 0x03, /* Input (Constant, Variable), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x05, /* Report Count (5), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x95, 0x01, /* Report Count (1), */ - 0x81, 0x02, /* Input (Variable), */ - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x95, 0x01, /* Report Count (1), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0x81, 0x02, /* Input (Variable), */ - 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ - 0x95, 0x1E, /* Report Count (24), */ - 0x81, 0x02, /* Input (Variable), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0x91, 0x02, /* Output (Variable), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0x02, /* Report ID (2), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0xEE, /* Report ID (238), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xA1, 0x02, /* Collection (Logical), */ - 0x85, 0xEF, /* Report ID (239), */ - 0x75, 0x08, /* Report Size (8), */ - 0x95, 0x30, /* Report Count (48), */ - 0x09, 0x01, /* Usage (Pointer), */ - 0xB1, 0x02, /* Feature (Variable), */ - 0xC0, /* End Collection, */ - 0xC0 /* End Collection */ -}; - static u8 ps3remote_rdesc[] = { 0x05, 0x01, /* GUsagePage Generic Desktop */ 0x09, 0x05, /* LUsage 0x05 [Game Pad] */ @@ -425,6 +335,40 @@ static const unsigned int buzz_keymap[] = { [20] = BTN_TRIGGER_HAPPY20, }; +/* The Navigation controller is a partial DS3 and uses the same HID report + * and hence the same keymap indices, however not not all axes/buttons + * are physically present. We use the same axis and button mapping as + * the DS3, which uses the Linux gamepad spec. + */ +static const unsigned int navigation_absmap[] = { + [0x30] = ABS_X, + [0x31] = ABS_Y, + [0x33] = ABS_Z, /* L2 */ +}; + +/* Buttons not physically available on the device, but still available + * in the reports are explicitly set to 0 for documentation purposes. + */ +static const unsigned int navigation_keymap[] = { + [0x01] = 0, /* Select */ + [0x02] = BTN_THUMBL, /* L3 */ + [0x03] = 0, /* R3 */ + [0x04] = 0, /* Start */ + [0x05] = BTN_DPAD_UP, /* Up */ + [0x06] = BTN_DPAD_RIGHT, /* Right */ + [0x07] = BTN_DPAD_DOWN, /* Down */ + [0x08] = BTN_DPAD_LEFT, /* Left */ + [0x09] = BTN_TL2, /* L2 */ + [0x0a] = 0, /* R2 */ + [0x0b] = BTN_TL, /* L1 */ + [0x0c] = 0, /* R1 */ + [0x0d] = BTN_NORTH, /* Triangle */ + [0x0e] = BTN_EAST, /* Circle */ + [0x0f] = BTN_SOUTH, /* Cross */ + [0x10] = BTN_WEST, /* Square */ + [0x11] = BTN_MODE, /* PS */ +}; + static const unsigned int sixaxis_absmap[] = { [0x30] = ABS_X, [0x31] = ABS_Y, @@ -452,7 +396,6 @@ static const unsigned int sixaxis_keymap[] = { [0x11] = BTN_MODE, /* PS */ }; - static const unsigned int ds4_absmap[] = { [0x30] = ABS_X, [0x31] = ABS_Y, @@ -650,13 +593,6 @@ static u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc, return motion_rdesc; } -static u8 *navigation_fixup(struct hid_device *hdev, u8 *rdesc, - unsigned int *rsize) -{ - *rsize = sizeof(navigation_rdesc); - return navigation_rdesc; -} - static u8 *ps3remote_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) { @@ -698,6 +634,54 @@ static int ps3remote_mapping(struct hid_device *hdev, struct hid_input *hi, return 1; } +static int navigation_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) { + unsigned int key = usage->hid & HID_USAGE; + + if (key >= ARRAY_SIZE(sixaxis_keymap)) + return -1; + + key = navigation_keymap[key]; + if (!key) + return -1; + + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); + return 1; + } else if (usage->hid == HID_GD_POINTER) { + /* See comment in sixaxis_mapping, basically the L2 (and R2) + * triggers are reported through GD Pointer. + * In addition we ignore any analog button 'axes' and only + * support digital buttons. + */ + switch (usage->usage_index) { + case 8: /* L2 */ + usage->hid = HID_GD_Z; + break; + default: + return -1; + } + + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, usage->hid & 0xf); + return 1; + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + unsigned int abs = usage->hid & HID_USAGE; + + if (abs >= ARRAY_SIZE(navigation_absmap)) + return -1; + + abs = navigation_absmap[abs]; + + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs); + return 1; + } + + return -1; +} + + static int sixaxis_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -804,9 +788,6 @@ static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc, if (sc->quirks & MOTION_CONTROLLER) return motion_fixup(hdev, rdesc, rsize); - if (sc->quirks & NAVIGATION_CONTROLLER) - return navigation_fixup(hdev, rdesc, rsize); - if (sc->quirks & PS3REMOTE) return ps3remote_fixup(hdev, rdesc, rsize); @@ -1199,6 +1180,9 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, if (sc->quirks & PS3REMOTE) return ps3remote_mapping(hdev, hi, field, usage, bit, max); + if (sc->quirks & NAVIGATION_CONTROLLER) + return navigation_mapping(hdev, hi, field, usage, bit, max); + if (sc->quirks & SIXAXIS_CONTROLLER) return sixaxis_mapping(hdev, hi, field, usage, bit, max); -- cgit From 39254a13d64bc69b83f4097dacc4117d7b865118 Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Fri, 24 Mar 2017 15:17:47 -0700 Subject: HID: sony: DS4 use brighter LED colors These colors are more the default colors normally used on the DS4. The previous ones were faint and not so noticeable. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 85d24be74db3..9863adf768ac 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1628,10 +1628,10 @@ static void dualshock4_set_leds_from_id(struct sony_sc *sc) { /* The first 4 color/index entries match what the PS4 assigns */ static const u8 color_code[7][3] = { - /* Blue */ { 0x00, 0x00, 0x01 }, - /* Red */ { 0x01, 0x00, 0x00 }, - /* Green */ { 0x00, 0x01, 0x00 }, - /* Pink */ { 0x02, 0x00, 0x01 }, + /* Blue */ { 0x00, 0x00, 0x40 }, + /* Red */ { 0x40, 0x00, 0x00 }, + /* Green */ { 0x00, 0x40, 0x00 }, + /* Pink */ { 0x20, 0x00, 0x20 }, /* Orange */ { 0x02, 0x01, 0x00 }, /* Teal */ { 0x00, 0x01, 0x01 }, /* White */ { 0x01, 0x01, 0x01 } -- cgit From 5caceb0695d0498b8c931cbc3cdafd99bd37b8ae Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Fri, 24 Mar 2017 15:17:48 -0700 Subject: HID: sony: Set proper bit flags on DS4 output report Only set bit flags for the portions of the DS4 output report for which we have data. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 9863adf768ac..107aae9627bc 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1998,13 +1998,13 @@ static void dualshock4_send_output_report(struct sony_sc *sc) if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) { memset(buf, 0, DS4_OUTPUT_REPORT_0x05_SIZE); buf[0] = 0x05; - buf[1] = 0xFF; + buf[1] = 0x07; /* blink + LEDs + motor */ offset = 4; } else { memset(buf, 0, DS4_OUTPUT_REPORT_0x11_SIZE); buf[0] = 0x11; buf[1] = 0xC0; /* HID + CRC */ - buf[3] = 0x0F; + buf[3] = 0x07; /* blink + LEDs + motor */ offset = 6; } -- cgit From 77b499e739ed5561e5026fa7140ae53f6c4d1d8e Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Fri, 24 Mar 2017 15:17:49 -0700 Subject: HID: sony: Make DS4 bt poll interval adjustable By default when using bluetooth the DS4 reports data at about 1kHz, which is quite fast especially on weak devices. We now make the device use the USB poll interval, which is a fixed 4ms. In addition we make the value adjustable through sysfs. The error handling in sony_input_configured is a little tricky. It is not easy to add other goto's as not all codepaths have logic for adding this attribute. Luckily we are setting the value for the attribute to a default value, so we can use that to detect if we need to remove the file. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 79 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 10 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 107aae9627bc..444a3f04f047 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -493,6 +493,9 @@ struct motion_output_report_02 { #define SENSOR_SUFFIX " Motion Sensors" #define DS4_TOUCHPAD_SUFFIX " Touchpad" +/* Default to 4ms poll interval, which is same as USB (not adjustable). */ +#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 4 +#define DS4_BT_MAX_POLL_INTERVAL_MS 62 #define DS4_GYRO_RES_PER_DEG_S 1024 #define DS4_ACC_RES_PER_G 8192 @@ -564,6 +567,7 @@ struct sony_sc { u16 prev_timestamp; unsigned int timestamp_us; + u8 ds4_bt_poll_interval; enum ds4_dongle_state ds4_dongle_state; /* DS4 calibration data */ struct ds4_calibration_data ds4_calib_data[6]; @@ -586,6 +590,44 @@ static inline void sony_schedule_work(struct sony_sc *sc, } } +static ssize_t ds4_show_poll_interval(struct device *dev, + struct device_attribute + *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct sony_sc *sc = hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%i\n", sc->ds4_bt_poll_interval); +} + +static ssize_t ds4_store_poll_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct sony_sc *sc = hid_get_drvdata(hdev); + unsigned long flags; + u8 interval; + + if (kstrtou8(buf, 0, &interval)) + return -EINVAL; + + if (interval > DS4_BT_MAX_POLL_INTERVAL_MS) + return -EINVAL; + + spin_lock_irqsave(&sc->lock, flags); + sc->ds4_bt_poll_interval = interval; + spin_unlock_irqrestore(&sc->lock, flags); + + sony_schedule_work(sc, SONY_WORKER_STATE); + + return count; +} + +static DEVICE_ATTR(bt_poll_interval, 0644, ds4_show_poll_interval, + ds4_store_poll_interval); + + static u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) { @@ -1985,15 +2027,13 @@ static void dualshock4_send_output_report(struct sony_sc *sc) int offset; /* - * NOTE: The buf[1] field of the Bluetooth report controls - * the Dualshock 4 reporting rate. - * - * Known values include: - * - * 0x80 - 1000hz (full speed) - * 0xA0 - 31hz - * 0xB0 - 20hz - * 0xD0 - 66hz + * NOTE: The lower 6 bits of buf[1] field of the Bluetooth report + * control the interval at which Dualshock 4 reports data: + * 0x00 - 1ms + * 0x01 - 1ms + * 0x02 - 2ms + * 0x3E - 62ms + * 0x3F - disabled */ if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) { memset(buf, 0, DS4_OUTPUT_REPORT_0x05_SIZE); @@ -2003,7 +2043,7 @@ static void dualshock4_send_output_report(struct sony_sc *sc) } else { memset(buf, 0, DS4_OUTPUT_REPORT_0x11_SIZE); buf[0] = 0x11; - buf[1] = 0xC0; /* HID + CRC */ + buf[1] = 0xC0 /* HID + CRC */ | sc->ds4_bt_poll_interval; buf[3] = 0x07; /* blink + LEDs + motor */ offset = 6; } @@ -2454,6 +2494,7 @@ static inline void sony_cancel_work_sync(struct sony_sc *sc) cancel_work_sync(&sc->state_worker); } + static int sony_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { @@ -2591,6 +2632,15 @@ static int sony_input_configured(struct hid_device *hdev, goto err_stop; } + if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) { + sc->ds4_bt_poll_interval = DS4_BT_DEFAULT_POLL_INTERVAL_MS; + ret = device_create_file(&sc->hdev->dev, &dev_attr_bt_poll_interval); + if (ret) + hid_warn(sc->hdev, + "can't create sysfs bt_poll_interval attribute err: %d\n", + ret); + } + if (sc->quirks & DUALSHOCK4_DONGLE) { INIT_WORK(&sc->hotplug_worker, dualshock4_calibration_work); sc->hotplug_worker_initialized = 1; @@ -2636,6 +2686,12 @@ static int sony_input_configured(struct hid_device *hdev, err_close: hid_hw_close(hdev); err_stop: + /* Piggy back on the default ds4_bt_ poll_interval to determine + * if we need to remove the file as we don't know for sure if we + * executed that logic. + */ + if (sc->ds4_bt_poll_interval) + device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval); if (sc->quirks & SONY_LED_SUPPORT) sony_leds_remove(sc); if (sc->quirks & SONY_BATTERY_SUPPORT) @@ -2733,6 +2789,9 @@ static void sony_remove(struct hid_device *hdev) if (sc->sensor_dev) sony_unregister_sensors(sc); + if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) + device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval); + sony_cancel_work_sync(sc); kfree(sc->output_report_dmabuf); -- cgit From a676bdc422241822130364443a6a65b6520440ba Mon Sep 17 00:00:00 2001 From: Roderick Colenbrander Date: Fri, 24 Mar 2017 15:17:50 -0700 Subject: HID: sony: Make sure to unregister sensors on failure Make sure we sure register any sensor when sony_input_configured failes. Somehow this line got lost during resolving of merge conflicts in the motion sensor patch series and a redudant remove was added as well later on. Signed-off-by: Roderick Colenbrander Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 444a3f04f047..e90ce63897df 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2696,6 +2696,8 @@ err_stop: sony_leds_remove(sc); if (sc->quirks & SONY_BATTERY_SUPPORT) sony_battery_remove(sc); + if (sc->sensor_dev) + sony_unregister_sensors(sc); sony_cancel_work_sync(sc); kfree(sc->output_report_dmabuf); sony_remove_dev_list(sc); @@ -2786,9 +2788,6 @@ static void sony_remove(struct hid_device *hdev) if (sc->sensor_dev) sony_unregister_sensors(sc); - if (sc->sensor_dev) - sony_unregister_sensors(sc); - if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval); -- cgit From 040fc001765d374776353cb4f8b03ea7fa41e3cd Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 27 Mar 2017 18:51:18 +0100 Subject: HID: sony: remove redundant check for -ve err err is being checked for failure each time it is being updated so this err check is totally redundant and can be removed Detected with CoverityScan, CID#1420665 ("Logically dead code") Signed-off-by: Colin Ian King Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index e90ce63897df..43213d6ff2d1 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2654,9 +2654,6 @@ static int sony_input_configured(struct hid_device *hdev, ret = 0; } - if (ret < 0) - goto err_stop; - if (sc->quirks & SONY_LED_SUPPORT) { ret = sony_leds_init(sc); if (ret < 0) -- cgit 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 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 From af22a610bc38508d5ea760507d31be6b6983dfa8 Mon Sep 17 00:00:00 2001 From: Carlo Caione Date: Thu, 6 Apr 2017 12:18:17 +0200 Subject: HID: asus: support backlight on USB keyboards The latest USB keyboards shipped on several ASUS laptop models (including ROG laptop models such as GL702VMK) have the keyboards backlight controlled by the keyboard firmware. The firmware implements at least 3 different commands: - Init command (to use when the system starts) - Configuration command (to get keyboard status/information) - Backlight level control (to change the level of the keyboard light) With this patch we create the usual 'asus::kbd_backlight' led class entry to control the keyboard backlight. [jkosina@suse.cz: remove pointless cancel_work_sync() call while handling an error in asus_kbd_register_leds(), as spotted by Benjamin] Signed-off-by: Carlo Caione Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-asus.c | 183 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 183 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index de4ed3752a4e..502444fff869 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -136,6 +136,7 @@ config HID_APPLEIR config HID_ASUS tristate "Asus" + depends on LEDS_CLASS ---help--- Support for Asus notebook built-in keyboard and touchpad via i2c, and the Asus Republic of Gamers laptop keyboard special keys. diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index bacba97668bf..16df6cc90235 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -40,8 +40,12 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define FEATURE_REPORT_ID 0x0d #define INPUT_REPORT_ID 0x5d +#define FEATURE_KBD_REPORT_ID 0x5a #define INPUT_REPORT_SIZE 28 +#define FEATURE_KBD_REPORT_SIZE 16 + +#define SUPPORT_KBD_BACKLIGHT BIT(0) #define MAX_CONTACTS 5 @@ -64,6 +68,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_SKIP_INPUT_MAPPING BIT(2) #define QUIRK_IS_MULTITOUCH BIT(3) #define QUIRK_NO_CONSUMER_USAGES BIT(4) +#define QUIRK_USE_KBD_BACKLIGHT BIT(5) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ @@ -74,9 +79,19 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define TRKID_SGN ((TRKID_MAX + 1) >> 1) +struct asus_kbd_leds { + struct led_classdev cdev; + struct hid_device *hdev; + struct work_struct work; + unsigned int brightness; + bool removed; +}; + struct asus_drvdata { unsigned long quirks; struct input_dev *input; + struct asus_kbd_leds *kbd_backlight; + bool enable_backlight; }; static void asus_report_contact_down(struct input_dev *input, @@ -171,6 +186,148 @@ static int asus_raw_event(struct hid_device *hdev, return 0; } +static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size) +{ + unsigned char *dmabuf; + int ret; + + dmabuf = kmemdup(buf, buf_size, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf, + buf_size, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + kfree(dmabuf); + + return ret; +} + +static int asus_kbd_init(struct hid_device *hdev) +{ + u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, + 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; + int ret; + + ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(hdev, "Asus failed to send init command: %d\n", ret); + + return ret; +} + +static int asus_kbd_get_functions(struct hid_device *hdev, + unsigned char *kbd_func) +{ + u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 }; + u8 *readbuf; + int ret; + + ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); + if (ret < 0) { + hid_err(hdev, "Asus failed to send configuration command: %d\n", ret); + return ret; + } + + readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL); + if (!readbuf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf, + FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + if (ret < 0) { + hid_err(hdev, "Asus failed to request functions: %d\n", ret); + kfree(readbuf); + return ret; + } + + *kbd_func = readbuf[6]; + + kfree(readbuf); + return ret; +} + +static void asus_kbd_backlight_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, + cdev); + if (led->brightness == brightness) + return; + + led->brightness = brightness; + schedule_work(&led->work); +} + +static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev) +{ + struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, + cdev); + + return led->brightness; +} + +static void asus_kbd_backlight_work(struct work_struct *work) +{ + struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); + u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 }; + int ret; + + if (led->removed) + return; + + buf[4] = led->brightness; + + ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); +} + +static int asus_kbd_register_leds(struct hid_device *hdev) +{ + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + unsigned char kbd_func; + int ret; + + /* Initialize keyboard */ + ret = asus_kbd_init(hdev); + if (ret < 0) + return ret; + + /* Get keyboard functions */ + ret = asus_kbd_get_functions(hdev, &kbd_func); + if (ret < 0) + return ret; + + /* Check for backlight support */ + if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) + return -ENODEV; + + drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, + sizeof(struct asus_kbd_leds), + GFP_KERNEL); + if (!drvdata->kbd_backlight) + return -ENOMEM; + + drvdata->kbd_backlight->removed = false; + drvdata->kbd_backlight->brightness = 0; + drvdata->kbd_backlight->hdev = hdev; + drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight"; + drvdata->kbd_backlight->cdev.max_brightness = 3; + drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set; + drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get; + INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); + + ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev); + if (ret < 0) { + /* No need to have this still around */ + devm_kfree(&hdev->dev, drvdata->kbd_backlight); + } + + return ret; +} + static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct input_dev *input = hi->input; @@ -198,6 +355,9 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) drvdata->input = input; + if (drvdata->enable_backlight && asus_kbd_register_leds(hdev)) + hid_warn(hdev, "Failed to initialize backlight.\n"); + return 0; } @@ -248,6 +408,16 @@ static int asus_input_mapping(struct hid_device *hdev, * as some make the keyboard appear as a pointer device. */ return -1; } + + /* + * Check and enable backlight only on devices with UsagePage == + * 0xff31 to avoid initializing the keyboard firmware multiple + * times on devices with multiple HID descriptors but same + * PID/VID. + */ + if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) + drvdata->enable_backlight = true; + return 1; } @@ -358,6 +528,16 @@ err_stop_hw: return ret; } +static void asus_remove(struct hid_device *hdev) +{ + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + + if (drvdata->kbd_backlight) { + drvdata->kbd_backlight->removed = true; + cancel_work_sync(&drvdata->kbd_backlight->work); + } +} + static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { @@ -379,7 +559,7 @@ static const struct hid_device_id asus_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, - USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2) }, + USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT }, { } }; MODULE_DEVICE_TABLE(hid, asus_devices); @@ -389,6 +569,7 @@ static struct hid_driver asus_driver = { .id_table = asus_devices, .report_fixup = asus_report_fixup, .probe = asus_probe, + .remove = asus_remove, .input_mapping = asus_input_mapping, .input_configured = asus_input_configured, #ifdef CONFIG_PM -- cgit From d529a4ad91efcf68b65440c6555895fd7ad5a08e Mon Sep 17 00:00:00 2001 From: Vasilis Liaskovitis Date: Tue, 25 Apr 2017 10:26:44 +0200 Subject: HID: usbhid: Add HID_QUIRK_NOGET for Aten CS-1758 KVM switch Like other switches, the Aten CS-1758 KVM switch needs a quirk to avoid spewing errors: [12599018.071059] usb 5-2: input irq status -75 received [12599018.079053] usb 5-2: input irq status -75 received Signed-off-by: Vasilis Liaskovitis Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/usbhid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b26c030926c1..cac06b514455 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -184,6 +184,7 @@ #define USB_DEVICE_ID_ATEN_4PORTKVMC 0x2208 #define USB_DEVICE_ID_ATEN_CS682 0x2213 #define USB_DEVICE_ID_ATEN_CS692 0x8021 +#define USB_DEVICE_ID_ATEN_CS1758 0x2220 #define USB_VENDOR_ID_ATMEL 0x03eb #define USB_DEVICE_ID_ATMEL_MULTITOUCH 0x211c diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index a69a3c88ab29..8284781782cd 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -65,6 +65,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC, HID_QUIRK_NOGET }, { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682, HID_QUIRK_NOGET }, { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS1758, HID_QUIRK_NOGET }, { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FIGHTERSTICK, HID_QUIRK_NOGET }, { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_COMBATSTICK, HID_QUIRK_NOGET }, { USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE, HID_QUIRK_NOGET }, -- cgit