diff options
Diffstat (limited to 'drivers/hid/hid-logitech-hidpp.c')
| -rw-r--r-- | drivers/hid/hid-logitech-hidpp.c | 2191 |
1 files changed, 1720 insertions, 471 deletions
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 15ed6177a7a3..d5011a5d0890 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -1,16 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only /* - * HIDPP protocol for Logitech Unifying receivers + * HIDPP protocol for Logitech receivers * * Copyright (c) 2011 Logitech (c) * Copyright (c) 2012-2013 Google (c) * Copyright (c) 2013-2014 Red Hat Inc. */ -/* - * 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; version 2 of the License. - */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -27,31 +23,41 @@ #include <linux/workqueue.h> #include <linux/atomic.h> #include <linux/fixp-arith.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include "usbhid/usbhid.h" #include "hid-ids.h" +MODULE_DESCRIPTION("Support for Logitech devices relying on the HID++ specification"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>"); - -static bool disable_raw_mode; -module_param(disable_raw_mode, bool, 0644); -MODULE_PARM_DESC(disable_raw_mode, - "Disable Raw mode reporting for touchpads and keep firmware gestures."); +MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); static bool disable_tap_to_click; module_param(disable_tap_to_click, bool, 0644); MODULE_PARM_DESC(disable_tap_to_click, "Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently)."); +/* Define a non-zero software ID to identify our own requests */ +#define LINUX_KERNEL_SW_ID 0x01 + #define REPORT_ID_HIDPP_SHORT 0x10 #define REPORT_ID_HIDPP_LONG 0x11 #define REPORT_ID_HIDPP_VERY_LONG 0x12 #define HIDPP_REPORT_SHORT_LENGTH 7 #define HIDPP_REPORT_LONG_LENGTH 20 -#define HIDPP_REPORT_VERY_LONG_LENGTH 64 +#define HIDPP_REPORT_VERY_LONG_MAX_LENGTH 64 + +#define HIDPP_REPORT_SHORT_SUPPORTED BIT(0) +#define HIDPP_REPORT_LONG_SUPPORTED BIT(1) +#define HIDPP_REPORT_VERY_LONG_SUPPORTED BIT(2) + +#define HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS 0x03 +#define HIDPP_SUB_ID_ROLLER 0x05 +#define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS 0x06 +#define HIDPP_SUB_ID_USER_IFACE_EVENT 0x08 +#define HIDPP_USER_IFACE_EVENT_ENCRYPTION_KEY_LOST BIT(5) #define HIDPP_QUIRK_CLASS_WTP BIT(0) #define HIDPP_QUIRK_CLASS_M560 BIT(1) @@ -62,24 +68,37 @@ MODULE_PARM_DESC(disable_tap_to_click, /* bits 2..20 are reserved for classes */ /* #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_DELAYED_INIT BIT(23) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) -#define HIDPP_QUIRK_UNIFYING BIT(25) -#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(26) -#define HIDPP_QUIRK_HI_RES_SCROLL_X2120 BIT(27) -#define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28) +#define HIDPP_QUIRK_HIDPP_WHEELS BIT(25) +#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(26) +#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(27) +#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28) +#define HIDPP_QUIRK_WIRELESS_STATUS BIT(29) +#define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30) -/* Convenience constant to check for any high-res support. */ -#define HIDPP_QUIRK_HI_RES_SCROLL (HIDPP_QUIRK_HI_RES_SCROLL_1P0 | \ - HIDPP_QUIRK_HI_RES_SCROLL_X2120 | \ - HIDPP_QUIRK_HI_RES_SCROLL_X2121) +/* These are just aliases for now */ +#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS +#define HIDPP_QUIRK_KBD_ZOOM_WHEEL HIDPP_QUIRK_HIDPP_WHEELS -#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT +/* Convenience constant to check for any high-res support. */ +#define HIDPP_CAPABILITY_HI_RES_SCROLL (HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL | \ + HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \ + HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) #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) +#define HIDPP_CAPABILITY_BATTERY_VOLTAGE BIT(4) +#define HIDPP_CAPABILITY_BATTERY_PERCENTAGE BIT(5) +#define HIDPP_CAPABILITY_UNIFIED_BATTERY BIT(6) +#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL BIT(7) +#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL BIT(8) +#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL BIT(9) +#define HIDPP_CAPABILITY_ADC_MEASUREMENT BIT(10) + +#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) /* * There are two hidpp protocols in use, the first version hidpp10 is known @@ -106,13 +125,13 @@ MODULE_PARM_DESC(disable_tap_to_click, struct fap { u8 feature_index; u8 funcindex_clientid; - u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U]; + u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U]; }; struct rap { u8 sub_id; u8 reg_address; - u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U]; + u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U]; }; struct hidpp_report { @@ -128,13 +147,18 @@ struct hidpp_report { struct hidpp_battery { u8 feature_index; u8 solar_feature_index; + u8 voltage_feature_index; + u8 adc_measurement_feature_index; struct power_supply_desc desc; struct power_supply *ps; char name[64]; int status; int capacity; int level; + int voltage; + int charge_type; bool online; + u8 supported_levels_1004; }; /** @@ -149,7 +173,6 @@ struct hidpp_battery { * @last_time: last event time, used to reset remainder after inactivity */ struct hidpp_scroll_counter { - struct input_dev *dev; int wheel_multiplier; int remainder; int direction; @@ -158,10 +181,12 @@ struct hidpp_scroll_counter { struct hidpp_device { struct hid_device *hid_dev; + struct input_dev *input; struct mutex send_mutex; void *send_receive_buf; char *name; /* will never be NULL and should not be freed */ wait_queue_head_t wait; + int very_long_report_length; bool answer_available; u8 protocol_major; u8 protocol_minor; @@ -169,15 +194,20 @@ struct hidpp_device { void *private_data; struct work_struct work; + struct work_struct reset_hi_res_work; struct kfifo delayed_work_fifo; - atomic_t connected; struct input_dev *delayed_input; unsigned long quirks; unsigned long capabilities; + u8 supported_reports; struct hidpp_battery battery; struct hidpp_scroll_counter vertical_wheel_counter; + + u8 wireless_feature_index; + + bool connected_once; }; /* HID++ 1.0 error codes */ @@ -196,18 +226,24 @@ struct hidpp_device { #define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b #define HIDPP_ERROR_WRONG_PIN_CODE 0x0c /* HID++ 2.0 error codes */ +#define HIDPP20_ERROR_NO_ERROR 0x00 +#define HIDPP20_ERROR_UNKNOWN 0x01 +#define HIDPP20_ERROR_INVALID_ARGS 0x02 +#define HIDPP20_ERROR_OUT_OF_RANGE 0x03 +#define HIDPP20_ERROR_HW_ERROR 0x04 +#define HIDPP20_ERROR_NOT_ALLOWED 0x05 +#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06 +#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07 +#define HIDPP20_ERROR_BUSY 0x08 +#define HIDPP20_ERROR_UNSUPPORTED 0x09 #define HIDPP20_ERROR 0xff -static void hidpp_connect_event(struct hidpp_device *hidpp_dev); - static int __hidpp_send_report(struct hid_device *hdev, struct hidpp_report *hidpp_report) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); int fields_count, ret; - hidpp = hid_get_drvdata(hdev); - switch (hidpp_report->report_id) { case REPORT_ID_HIDPP_SHORT: fields_count = HIDPP_REPORT_SHORT_LENGTH; @@ -216,7 +252,7 @@ static int __hidpp_send_report(struct hid_device *hdev, fields_count = HIDPP_REPORT_LONG_LENGTH; break; case REPORT_ID_HIDPP_VERY_LONG: - fields_count = HIDPP_REPORT_VERY_LONG_LENGTH; + fields_count = hidpp->very_long_report_length; break; default: return -ENODEV; @@ -239,21 +275,23 @@ static int __hidpp_send_report(struct hid_device *hdev, return ret == fields_count ? 0 : -1; } -/** - * hidpp_send_message_sync() returns 0 in case of success, and something else - * in case of a failure. - * - If ' something else' is positive, that means that an error has been raised - * by the protocol itself. - * - If ' something else' is negative, that means that we had a classic error - * (-ENOMEM, -EPIPE, etc...) +/* + * Effectively send the message to the device, waiting for its answer. + * + * Must be called with hidpp->send_mutex locked + * + * Same return protocol than hidpp_send_message_sync(): + * - success on 0 + * - negative error means transport error + * - positive value means protocol error */ -static int hidpp_send_message_sync(struct hidpp_device *hidpp, +static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp, struct hidpp_report *message, struct hidpp_report *response) { int ret; - mutex_lock(&hidpp->send_mutex); + __must_hold(&hidpp->send_mutex); hidpp->send_receive_buf = response; hidpp->answer_available = false; @@ -265,41 +303,78 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, *response = *message; ret = __hidpp_send_report(hidpp->hid_dev, message); - if (ret) { dbg_hid("__hidpp_send_report returned err: %d\n", ret); memset(response, 0, sizeof(struct hidpp_report)); - goto exit; + return ret; } if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, 5*HZ)) { dbg_hid("%s:timeout waiting for response\n", __func__); memset(response, 0, sizeof(struct hidpp_report)); - ret = -ETIMEDOUT; + return -ETIMEDOUT; } if (response->report_id == REPORT_ID_HIDPP_SHORT && response->rap.sub_id == HIDPP_ERROR) { ret = response->rap.params[1]; dbg_hid("%s:got hidpp error %02X\n", __func__, ret); - goto exit; + return ret; } if ((response->report_id == REPORT_ID_HIDPP_LONG || - response->report_id == REPORT_ID_HIDPP_VERY_LONG) && - response->fap.feature_index == HIDPP20_ERROR) { + response->report_id == REPORT_ID_HIDPP_VERY_LONG) && + response->fap.feature_index == HIDPP20_ERROR) { ret = response->fap.params[1]; dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); - goto exit; + return ret; } -exit: + return 0; +} + +/* + * hidpp_send_message_sync() returns 0 in case of success, and something else + * in case of a failure. + * + * See __do_hidpp_send_message_sync() for a detailed explanation of the returned + * value. + */ +static int hidpp_send_message_sync(struct hidpp_device *hidpp, + struct hidpp_report *message, + struct hidpp_report *response) +{ + int ret; + int max_retries = 3; + + mutex_lock(&hidpp->send_mutex); + + do { + ret = __do_hidpp_send_message_sync(hidpp, message, response); + if (response->report_id == REPORT_ID_HIDPP_SHORT && + ret != HIDPP_ERROR_BUSY) + break; + if ((response->report_id == REPORT_ID_HIDPP_LONG || + response->report_id == REPORT_ID_HIDPP_VERY_LONG) && + ret != HIDPP20_ERROR_BUSY) + break; + + dbg_hid("%s:got busy hidpp error %02X, retrying\n", __func__, ret); + } while (--max_retries); + mutex_unlock(&hidpp->send_mutex); return ret; } +/* + * hidpp_send_fap_command_sync() returns 0 in case of success, and something else + * in case of a failure. + * + * See __do_hidpp_send_message_sync() for a detailed explanation of the returned + * value. + */ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, struct hidpp_report *response) @@ -307,8 +382,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, struct hidpp_report *message; int ret; - if (param_count > sizeof(message->fap.params)) + if (param_count > sizeof(message->fap.params)) { + hid_dbg(hidpp->hid_dev, + "Invalid number of parameters passed to command (%d != %llu)\n", + param_count, + (unsigned long long) sizeof(message->fap.params)); return -EINVAL; + } message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); if (!message) @@ -319,7 +399,7 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, else message->report_id = REPORT_ID_HIDPP_LONG; message->fap.feature_index = feat_index; - message->fap.funcindex_clientid = funcindex_clientid; + message->fap.funcindex_clientid = funcindex_clientid | LINUX_KERNEL_SW_ID; memcpy(&message->fap.params, params, param_count); ret = hidpp_send_message_sync(hidpp, message, response); @@ -327,6 +407,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, return ret; } +/* + * hidpp_send_rap_command_sync() returns 0 in case of success, and something else + * in case of a failure. + * + * See __do_hidpp_send_message_sync() for a detailed explanation of the returned + * value. + */ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count, struct hidpp_report *response) @@ -334,6 +421,11 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, struct hidpp_report *message; int ret, max_count; + /* Send as long report if short reports are not supported. */ + if (report_id == REPORT_ID_HIDPP_SHORT && + !(hidpp_dev->supported_reports & HIDPP_REPORT_SHORT_SUPPORTED)) + report_id = REPORT_ID_HIDPP_LONG; + switch (report_id) { case REPORT_ID_HIDPP_SHORT: max_count = HIDPP_REPORT_SHORT_LENGTH - 4; @@ -342,7 +434,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, max_count = HIDPP_REPORT_LONG_LENGTH - 4; break; case REPORT_ID_HIDPP_VERY_LONG: - max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; + max_count = hidpp_dev->very_long_report_length - 4; break; default: return -EINVAL; @@ -364,13 +456,6 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, return ret; } -static void delayed_work_cb(struct work_struct *work) -{ - struct hidpp_device *hidpp = container_of(work, struct hidpp_device, - work); - hidpp_connect_event(hidpp); -} - static inline bool hidpp_match_answer(struct hidpp_report *question, struct hidpp_report *answer) { @@ -387,13 +472,16 @@ static inline bool hidpp_match_error(struct hidpp_report *question, (answer->fap.params[0] == question->fap.funcindex_clientid); } -static inline bool hidpp_report_is_connect_event(struct hidpp_report *report) +static inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp, + struct hidpp_report *report) { - return (report->report_id == REPORT_ID_HIDPP_SHORT) && - (report->rap.sub_id == 0x41); + return (hidpp->wireless_feature_index && + (report->fap.feature_index == hidpp->wireless_feature_index)) || + ((report->report_id == REPORT_ID_HIDPP_SHORT) && + (report->rap.sub_id == 0x41)); } -/** +/* * hidpp_prefix_name() prefixes the current given name with "Logitech ". */ static void hidpp_prefix_name(char **name, int name_length) @@ -420,10 +508,31 @@ static void hidpp_prefix_name(char **name, int name_length) *name = new_name; } +/* + * Updates the USB wireless_status based on whether the headset + * is turned on and reachable. + */ +static void hidpp_update_usb_wireless_status(struct hidpp_device *hidpp) +{ + struct hid_device *hdev = hidpp->hid_dev; + struct usb_interface *intf; + + if (!(hidpp->quirks & HIDPP_QUIRK_WIRELESS_STATUS)) + return; + if (!hid_is_usb(hdev)) + return; + + intf = to_usb_interface(hdev->dev.parent); + usb_set_wireless_status(intf, hidpp->battery.online ? + USB_WIRELESS_STATUS_CONNECTED : + USB_WIRELESS_STATUS_DISCONNECTED); +} + /** * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll * events given a high-resolution wheel * movement. + * @input_dev: Pointer to the input device * @counter: a hid_scroll_counter struct describing the wheel. * @hi_res_value: the movement of the wheel, in the mouse's high-resolution * units. @@ -434,14 +543,15 @@ static void hidpp_prefix_name(char **name, int name_length) * emit low-resolution scroll events when appropriate for * backwards-compatibility with userspace input libraries. */ -static void hidpp_scroll_counter_handle_scroll(struct hidpp_scroll_counter *counter, +static void hidpp_scroll_counter_handle_scroll(struct input_dev *input_dev, + struct hidpp_scroll_counter *counter, int hi_res_value) { int low_res_value, remainder, direction; unsigned long long now, previous; hi_res_value = hi_res_value * 120/counter->wheel_multiplier; - input_report_rel(counter->dev, REL_WHEEL_HI_RES, hi_res_value); + input_report_rel(input_dev, REL_WHEEL_HI_RES, hi_res_value); remainder = counter->remainder; direction = hi_res_value > 0 ? 1 : -1; @@ -475,7 +585,7 @@ static void hidpp_scroll_counter_handle_scroll(struct hidpp_scroll_counter *coun low_res_value = remainder / 120; if (low_res_value == 0) low_res_value = (hi_res_value > 0 ? 1 : -1); - input_report_rel(counter->dev, REL_WHEEL, low_res_value); + input_report_rel(input_dev, REL_WHEEL, low_res_value); remainder -= low_res_value * 120; } counter->remainder = remainder; @@ -491,14 +601,16 @@ static void hidpp_scroll_counter_handle_scroll(struct hidpp_scroll_counter *coun #define HIDPP_GET_LONG_REGISTER 0x83 /** - * hidpp10_set_register_bit() - Sets a single bit in a HID++ 1.0 register. + * hidpp10_set_register - Modify a HID++ 1.0 register. * @hidpp_dev: the device to set the register on. * @register_address: the address of the register to modify. * @byte: the byte of the register to modify. Should be less than 3. + * @mask: mask of the bits to modify + * @value: new values for the bits in mask * Return: 0 if successful, otherwise a negative error code. */ -static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, - u8 register_address, u8 byte, u8 bit) +static int hidpp10_set_register(struct hidpp_device *hidpp_dev, + u8 register_address, u8 byte, u8 mask, u8 value) { struct hidpp_report response; int ret; @@ -514,7 +626,8 @@ static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, memcpy(params, response.rap.params, 3); - params[byte] |= BIT(bit); + params[byte] &= ~mask; + params[byte] |= value & mask; return hidpp_send_rap_command_sync(hidpp_dev, REPORT_ID_HIDPP_SHORT, @@ -523,20 +636,28 @@ static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, params, 3, &response); } - -#define HIDPP_REG_GENERAL 0x00 +#define HIDPP_REG_ENABLE_REPORTS 0x00 +#define HIDPP_ENABLE_CONSUMER_REPORT BIT(0) +#define HIDPP_ENABLE_WHEEL_REPORT BIT(2) +#define HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT BIT(3) +#define HIDPP_ENABLE_BAT_REPORT BIT(4) +#define HIDPP_ENABLE_HWHEEL_REPORT BIT(5) static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev) { - return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_GENERAL, 0, 4); + return hidpp10_set_register(hidpp_dev, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_BAT_REPORT, HIDPP_ENABLE_BAT_REPORT); } #define HIDPP_REG_FEATURES 0x01 +#define HIDPP_ENABLE_SPECIAL_BUTTON_FUNC BIT(1) +#define HIDPP_ENABLE_FAST_SCROLL BIT(6) /* On HID++ 1.0 devices, high-res scroll was called "scrolling acceleration". */ static int hidpp10_enable_scrolling_acceleration(struct hidpp_device *hidpp_dev) { - return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_FEATURES, 0, 6); + return hidpp10_set_register(hidpp_dev, HIDPP_REG_FEATURES, 0, + HIDPP_ENABLE_FAST_SCROLL, HIDPP_ENABLE_FAST_SCROLL); } #define HIDPP_REG_BATTERY_STATUS 0x07 @@ -741,6 +862,9 @@ static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev) if (2 + len > sizeof(response.rap.params)) return NULL; + if (len < 4) /* logitech devices are usually at least Xddd */ + return NULL; + name = kzalloc(len + 1, GFP_KERNEL); if (!name) return NULL; @@ -786,8 +910,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp) if (ret) return ret; - snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD", - hdev->product, &serial); + snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial); dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq); name = hidpp_unifying_get_name(hidpp); @@ -808,11 +931,11 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp) #define HIDPP_PAGE_ROOT 0x0000 #define HIDPP_PAGE_ROOT_IDX 0x00 -#define CMD_ROOT_GET_FEATURE 0x01 -#define CMD_ROOT_GET_PROTOCOL_VERSION 0x11 +#define CMD_ROOT_GET_FEATURE 0x00 +#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10 static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature, - u8 *feature_index, u8 *feature_type) + u8 *feature_index) { struct hidpp_report response; int ret; @@ -829,29 +952,32 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature, return -ENOENT; *feature_index = response.fap.params[0]; - *feature_type = response.fap.params[1]; return ret; } static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) { + const u8 ping_byte = 0x5a; + u8 ping_data[3] = { 0, 0, ping_byte }; struct hidpp_report response; int ret; - ret = hidpp_send_fap_command_sync(hidpp, + ret = hidpp_send_rap_command_sync(hidpp, + REPORT_ID_HIDPP_SHORT, HIDPP_PAGE_ROOT_IDX, - CMD_ROOT_GET_PROTOCOL_VERSION, - NULL, 0, &response); + CMD_ROOT_GET_PROTOCOL_VERSION | LINUX_KERNEL_SW_ID, + ping_data, sizeof(ping_data), &response); if (ret == HIDPP_ERROR_INVALID_SUBID) { hidpp->protocol_major = 1; hidpp->protocol_minor = 0; - return 0; + goto print_version; } /* the device might not be connected */ - if (ret == HIDPP_ERROR_RESOURCE_ERROR) + if (ret == HIDPP_ERROR_RESOURCE_ERROR || + ret == HIDPP_ERROR_UNKNOWN_DEVICE) return -EIO; if (ret > 0) { @@ -862,21 +988,70 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) if (ret) return ret; - hidpp->protocol_major = response.fap.params[0]; - hidpp->protocol_minor = response.fap.params[1]; + if (response.rap.params[2] != ping_byte) { + hid_err(hidpp->hid_dev, "%s: ping mismatch 0x%02x != 0x%02x\n", + __func__, response.rap.params[2], ping_byte); + return -EPROTO; + } - return ret; + hidpp->protocol_major = response.rap.params[0]; + hidpp->protocol_minor = response.rap.params[1]; + +print_version: + if (!hidpp->connected_once) { + hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + hidpp->connected_once = true; + } else + hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + return 0; } -static bool hidpp_is_connected(struct hidpp_device *hidpp) +/* -------------------------------------------------------------------------- */ +/* 0x0003: Device Information */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_DEVICE_INFORMATION 0x0003 + +#define CMD_GET_DEVICE_INFO 0x00 + +static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial) { + struct hidpp_report response; + u8 feature_index; int ret; - ret = hidpp_root_get_protocol_version(hidpp); - if (!ret) - hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); - return ret == 0; + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION, + &feature_index); + if (ret) + return ret; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_GET_DEVICE_INFO, + NULL, 0, &response); + if (ret) + return ret; + + /* See hidpp_unifying_get_serial() */ + *serial = *((u32 *)&response.rap.params[1]); + return 0; +} + +static int hidpp_serial_init(struct hidpp_device *hidpp) +{ + struct hid_device *hdev = hidpp->hid_dev; + u32 serial; + int ret; + + ret = hidpp_get_serial(hidpp, &serial); + if (ret) + return ret; + + snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial); + dbg_hid("HID++ DeviceInformation: Got serial: %s\n", hdev->uniq); + + return 0; } /* -------------------------------------------------------------------------- */ @@ -885,9 +1060,9 @@ static bool hidpp_is_connected(struct hidpp_device *hidpp) #define HIDPP_PAGE_GET_DEVICE_NAME_TYPE 0x0005 -#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT 0x01 -#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME 0x11 -#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE 0x21 +#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT 0x00 +#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME 0x10 +#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE 0x20 static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp, u8 feature_index, u8 *nameLength) @@ -932,7 +1107,7 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, switch (response.report_id) { case REPORT_ID_HIDPP_VERY_LONG: - count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; + count = hidpp->very_long_report_length - 4; break; case REPORT_ID_HIDPP_LONG: count = HIDPP_REPORT_LONG_LENGTH - 4; @@ -955,7 +1130,6 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, static char *hidpp_get_device_name(struct hidpp_device *hidpp) { - u8 feature_type; u8 feature_index; u8 __name_length; char *name; @@ -963,7 +1137,7 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp) int ret; ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE, - &feature_index, &feature_type); + &feature_index); if (ret) return NULL; @@ -1012,7 +1186,11 @@ static int hidpp_map_battery_level(int capacity) { if (capacity < 11) return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else if (capacity < 31) + /* + * The spec says this should be < 31 but some devices report 30 + * with brand new batteries and Windows reports 30 as "Good". + */ + else if (capacity < 30) return POWER_SUPPLY_CAPACITY_LEVEL_LOW; else if (capacity < 81) return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; @@ -1076,6 +1254,9 @@ static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp, ret = hidpp_send_fap_command_sync(hidpp, feature_index, CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS, NULL, 0, &response); + /* Ignore these intermittent errors */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; if (ret > 0) { hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", __func__, ret); @@ -1121,17 +1302,15 @@ static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp, return 0; } -static int hidpp20_query_battery_info(struct hidpp_device *hidpp) +static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp) { - u8 feature_type; int ret; int status, capacity, next_capacity, level; if (hidpp->battery.feature_index == 0xff) { ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_LEVEL_STATUS, - &hidpp->battery.feature_index, - &feature_type); + &hidpp->battery.feature_index); if (ret) return ret; } @@ -1158,7 +1337,7 @@ static int hidpp20_query_battery_info(struct hidpp_device *hidpp) return 0; } -static int hidpp20_battery_event(struct hidpp_device *hidpp, +static int hidpp20_battery_event_1000(struct hidpp_device *hidpp, u8 *data, int size) { struct hidpp_report *report = (struct hidpp_report *)data; @@ -1193,6 +1372,397 @@ static int hidpp20_battery_event(struct hidpp_device *hidpp, return 0; } +/* -------------------------------------------------------------------------- */ +/* 0x1001: Battery voltage */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_BATTERY_VOLTAGE 0x1001 + +#define CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE 0x00 + +#define EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST 0x00 + +static int hidpp20_battery_map_status_voltage(u8 data[3], int *voltage, + int *level, int *charge_type) +{ + int status; + + long flags = (long) data[2]; + *level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + + if (flags & 0x80) + switch (flags & 0x07) { + case 0: + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 1: + status = POWER_SUPPLY_STATUS_FULL; + *level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + break; + case 2: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + + *charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + if (test_bit(3, &flags)) { + *charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST; + } + if (test_bit(4, &flags)) { + *charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + } + if (test_bit(5, &flags)) { + *level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + } + + *voltage = get_unaligned_be16(data); + + return status; +} + +static int hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp, + u8 feature_index, + int *status, int *voltage, + int *level, int *charge_type) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE, + 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; + + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_VOLTAGE; + + *status = hidpp20_battery_map_status_voltage(params, voltage, + level, charge_type); + + return 0; +} + +static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage) +{ + /* NB: This voltage curve doesn't necessarily map perfectly to all + * devices that implement the BATTERY_VOLTAGE feature. This is because + * there are a few devices that use different battery technology. + */ + + static const int voltages[100] = { + 4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075, + 4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997, + 3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929, + 3922, 3916, 3909, 3902, 3896, 3890, 3883, 3877, 3870, 3865, + 3859, 3853, 3848, 3842, 3837, 3833, 3828, 3824, 3819, 3815, + 3811, 3808, 3804, 3800, 3797, 3793, 3790, 3787, 3784, 3781, + 3778, 3775, 3772, 3770, 3767, 3764, 3762, 3759, 3757, 3754, + 3751, 3748, 3744, 3741, 3737, 3734, 3730, 3726, 3724, 3720, + 3717, 3714, 3710, 3706, 3702, 3697, 3693, 3688, 3683, 3677, + 3671, 3666, 3662, 3658, 3654, 3646, 3633, 3612, 3579, 3537 + }; + + int i; + + if (unlikely(voltage < 3500 || voltage >= 5000)) + hid_warn_once(hid_dev, + "%s: possibly using the wrong voltage curve\n", + __func__); + + for (i = 0; i < ARRAY_SIZE(voltages); i++) { + if (voltage >= voltages[i]) + return ARRAY_SIZE(voltages) - i; + } + + return 0; +} + +static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp) +{ + int ret; + int status, voltage, level, charge_type; + + if (hidpp->battery.voltage_feature_index == 0xff) { + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE, + &hidpp->battery.voltage_feature_index); + if (ret) + return ret; + } + + ret = hidpp20_battery_get_battery_voltage(hidpp, + hidpp->battery.voltage_feature_index, + &status, &voltage, &level, &charge_type); + + if (ret) + return ret; + + hidpp->battery.status = status; + hidpp->battery.voltage = voltage; + hidpp->battery.capacity = hidpp20_map_battery_capacity(hidpp->hid_dev, + voltage); + hidpp->battery.level = level; + hidpp->battery.charge_type = charge_type; + hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING; + + return 0; +} + +static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + int status, voltage, level, charge_type; + + if (report->fap.feature_index != hidpp->battery.voltage_feature_index || + report->fap.funcindex_clientid != EVENT_BATTERY_VOLTAGE_STATUS_BROADCAST) + return 0; + + status = hidpp20_battery_map_status_voltage(report->fap.params, &voltage, + &level, &charge_type); + + hidpp->battery.online = status != POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) { + hidpp->battery.voltage = voltage; + hidpp->battery.capacity = hidpp20_map_battery_capacity(hidpp->hid_dev, + voltage); + hidpp->battery.status = status; + hidpp->battery.level = level; + hidpp->battery.charge_type = charge_type; + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* 0x1004: Unified battery */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_UNIFIED_BATTERY 0x1004 + +#define CMD_UNIFIED_BATTERY_GET_CAPABILITIES 0x00 +#define CMD_UNIFIED_BATTERY_GET_STATUS 0x10 + +#define EVENT_UNIFIED_BATTERY_STATUS_EVENT 0x00 + +#define FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL BIT(0) +#define FLAG_UNIFIED_BATTERY_LEVEL_LOW BIT(1) +#define FLAG_UNIFIED_BATTERY_LEVEL_GOOD BIT(2) +#define FLAG_UNIFIED_BATTERY_LEVEL_FULL BIT(3) + +#define FLAG_UNIFIED_BATTERY_FLAGS_RECHARGEABLE BIT(0) +#define FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE BIT(1) + +static int hidpp20_unifiedbattery_get_capabilities(struct hidpp_device *hidpp, + u8 feature_index) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS || + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) { + /* we have already set the device capabilities, so let's skip */ + return 0; + } + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_UNIFIED_BATTERY_GET_CAPABILITIES, + NULL, 0, &response); + /* Ignore these intermittent errors */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + /* + * If the device supports state of charge (battery percentage) we won't + * export the battery level information. there are 4 possible battery + * levels and they all are optional, this means that the device might + * not support any of them, we are just better off with the battery + * percentage. + */ + if (params[1] & FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE) { + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_PERCENTAGE; + hidpp->battery.supported_levels_1004 = 0; + } else { + hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS; + hidpp->battery.supported_levels_1004 = params[0]; + } + + return 0; +} + +static int hidpp20_unifiedbattery_map_status(struct hidpp_device *hidpp, + u8 charging_status, + u8 external_power_status) +{ + int status; + + switch (charging_status) { + case 0: /* discharging */ + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 1: /* charging */ + case 2: /* charging slow */ + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 3: /* complete */ + status = POWER_SUPPLY_STATUS_FULL; + break; + case 4: /* error */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + hid_info(hidpp->hid_dev, "%s: charging error", + hidpp->name); + break; + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + return status; +} + +static int hidpp20_unifiedbattery_map_level(struct hidpp_device *hidpp, + u8 battery_level) +{ + /* cler unsupported level bits */ + battery_level &= hidpp->battery.supported_levels_1004; + + if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_FULL) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_GOOD) + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_LOW) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (battery_level & FLAG_UNIFIED_BATTERY_LEVEL_CRITICAL) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; +} + +static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp, + u8 feature_index, + u8 *state_of_charge, + int *status, + int *level) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_UNIFIED_BATTERY_GET_STATUS, + NULL, 0, &response); + /* Ignore these intermittent errors */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } + if (ret) + return ret; + + *state_of_charge = params[0]; + *status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]); + *level = hidpp20_unifiedbattery_map_level(hidpp, params[1]); + + return 0; +} + +static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp) +{ + int ret; + u8 state_of_charge; + int status, level; + + if (hidpp->battery.feature_index == 0xff) { + ret = hidpp_root_get_feature(hidpp, + HIDPP_PAGE_UNIFIED_BATTERY, + &hidpp->battery.feature_index); + if (ret) + return ret; + } + + ret = hidpp20_unifiedbattery_get_capabilities(hidpp, + hidpp->battery.feature_index); + if (ret) + return ret; + + ret = hidpp20_unifiedbattery_get_status(hidpp, + hidpp->battery.feature_index, + &state_of_charge, + &status, + &level); + if (ret) + return ret; + + hidpp->capabilities |= HIDPP_CAPABILITY_UNIFIED_BATTERY; + hidpp->battery.capacity = state_of_charge; + hidpp->battery.status = status; + hidpp->battery.level = level; + hidpp->battery.online = true; + + return 0; +} + +static int hidpp20_battery_event_1004(struct hidpp_device *hidpp, + u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + u8 *params = (u8 *)report->fap.params; + int state_of_charge, status, level; + bool changed; + + if (report->fap.feature_index != hidpp->battery.feature_index || + report->fap.funcindex_clientid != EVENT_UNIFIED_BATTERY_STATUS_EVENT) + return 0; + + state_of_charge = params[0]; + status = hidpp20_unifiedbattery_map_status(hidpp, params[2], params[3]); + level = hidpp20_unifiedbattery_map_level(hidpp, params[1]); + + changed = status != hidpp->battery.status || + (state_of_charge != hidpp->battery.capacity && + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) || + (level != hidpp->battery.level && + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS); + + if (changed) { + hidpp->battery.capacity = state_of_charge; + hidpp->battery.status = status; + hidpp->battery.level = level; + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* Battery feature helpers */ +/* -------------------------------------------------------------------------- */ + static enum power_supply_property hidpp_battery_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, @@ -1202,6 +1772,7 @@ static enum power_supply_property hidpp_battery_props[] = { POWER_SUPPLY_PROP_SERIAL_NUMBER, 0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */ 0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */ + 0, /* placeholder for POWER_SUPPLY_PROP_VOLTAGE_NOW, */ }; static int hidpp_battery_get_property(struct power_supply *psy, @@ -1239,6 +1810,13 @@ static int hidpp_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_SERIAL_NUMBER: val->strval = hidpp->hid_dev->uniq; break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* hardware reports voltage in mV. sysfs expects uV */ + val->intval = hidpp->battery.voltage * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = hidpp->battery.charge_type; + break; default: ret = -EINVAL; break; @@ -1248,6 +1826,173 @@ static int hidpp_battery_get_property(struct power_supply *psy, } /* -------------------------------------------------------------------------- */ +/* 0x1d4b: Wireless device status */ +/* -------------------------------------------------------------------------- */ +#define HIDPP_PAGE_WIRELESS_DEVICE_STATUS 0x1d4b + +static int hidpp_get_wireless_feature_index(struct hidpp_device *hidpp, u8 *feature_index) +{ + return hidpp_root_get_feature(hidpp, + HIDPP_PAGE_WIRELESS_DEVICE_STATUS, + feature_index); +} + +/* -------------------------------------------------------------------------- */ +/* 0x1f20: ADC measurement */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20 + +#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00 + +#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00 + +static int hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage) +{ + /* NB: This voltage curve doesn't necessarily map perfectly to all + * devices that implement the ADC_MEASUREMENT feature. This is because + * there are a few devices that use different battery technology. + * + * Adapted from: + * https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52 + */ + static const int voltages[100] = { + 4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951, + 3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828, + 3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762, + 3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716, + 3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677, + 3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643, + 3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611, + 3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579, + 3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546, + 3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399, + }; + + int i; + + if (voltage == 0) + return 0; + + if (unlikely(voltage < 3400 || voltage >= 5000)) + hid_warn_once(hid_dev, + "%s: possibly using the wrong voltage curve\n", + __func__); + + for (i = 0; i < ARRAY_SIZE(voltages); i++) { + if (voltage >= voltages[i]) + return ARRAY_SIZE(voltages) - i; + } + + return 0; +} + +static int hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage) +{ + int status; + u8 flags; + + flags = data[2]; + + switch (flags) { + case 0x01: + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 0x03: + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x07: + status = POWER_SUPPLY_STATUS_FULL; + break; + case 0x0F: + default: + status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + *voltage = get_unaligned_be16(data); + + dbg_hid("Parsed 1f20 data as flag 0x%02x voltage %dmV\n", + flags, *voltage); + + return status; +} + +/* Return value is whether the device is online */ +static bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp, + u8 feature_index, + int *status, int *voltage) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + *status = POWER_SUPPLY_STATUS_UNKNOWN; + *voltage = 0; + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT, + NULL, 0, &response); + + if (ret > 0) { + hid_dbg(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return false; + } + + *status = hidpp20_map_adc_measurement_1f20(params, voltage); + return true; +} + +static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp) +{ + if (hidpp->battery.adc_measurement_feature_index == 0xff) { + int ret; + + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT, + &hidpp->battery.adc_measurement_feature_index); + if (ret) + return ret; + + hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT; + } + + hidpp->battery.online = hidpp20_get_adc_measurement_1f20(hidpp, + hidpp->battery.adc_measurement_feature_index, + &hidpp->battery.status, + &hidpp->battery.voltage); + hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, + hidpp->battery.voltage); + hidpp_update_usb_wireless_status(hidpp); + + return 0; +} + +static int hidpp20_adc_measurement_event_1f20(struct hidpp_device *hidpp, + u8 *data, int size) +{ + struct hidpp_report *report = (struct hidpp_report *)data; + int status, voltage; + + if (report->fap.feature_index != hidpp->battery.adc_measurement_feature_index || + report->fap.funcindex_clientid != EVENT_ADC_MEASUREMENT_STATUS_BROADCAST) + return 0; + + status = hidpp20_map_adc_measurement_1f20(report->fap.params, &voltage); + + hidpp->battery.online = status != POWER_SUPPLY_STATUS_UNKNOWN; + + if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) { + hidpp->battery.status = status; + hidpp->battery.voltage = voltage; + hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, voltage); + if (hidpp->battery.ps) + power_supply_changed(hidpp->battery.ps); + hidpp_update_usb_wireless_status(hidpp); + } + return 0; +} + +/* -------------------------------------------------------------------------- */ /* 0x2120: Hi-resolution scrolling */ /* -------------------------------------------------------------------------- */ @@ -1259,15 +2004,13 @@ static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp, bool enabled, u8 *multiplier) { u8 feature_index; - u8 feature_type; int ret; u8 params[1]; struct hidpp_report response; ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING, - &feature_index, - &feature_type); + &feature_index); if (ret) return ret; @@ -1294,12 +2037,11 @@ static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp, u8 *multiplier) { u8 feature_index; - u8 feature_type; int ret; struct hidpp_report response; ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL, - &feature_index, &feature_type); + &feature_index); if (ret) goto return_default; @@ -1321,13 +2063,12 @@ static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert, bool high_resolution, bool use_hidpp) { u8 feature_index; - u8 feature_type; int ret; u8 params[1]; struct hidpp_report response; ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL, - &feature_index, &feature_type); + &feature_index); if (ret) return ret; @@ -1356,14 +2097,12 @@ 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); + &hidpp->battery.solar_feature_index); if (ret) return ret; } @@ -1450,7 +2189,7 @@ struct hidpp_touchpad_fw_items { uint8_t persistent; }; -/** +/* * send a set state command to the device by reading the current items->state * field. items is then filled with the current state. */ @@ -1487,8 +2226,8 @@ static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp, #define HIDPP_PAGE_TOUCHPAD_RAW_XY 0x6100 -#define CMD_TOUCHPAD_GET_RAW_INFO 0x01 -#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE 0x21 +#define CMD_TOUCHPAD_GET_RAW_INFO 0x00 +#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE 0x20 #define EVENT_TOUCHPAD_RAW_XY 0x00 @@ -1643,6 +2382,7 @@ static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev, #define HIDPP_FF_EFFECTID_NONE -1 #define HIDPP_FF_EFFECTID_AUTOCENTER -2 +#define HIDPP_AUTOCENTER_PARAMS_LENGTH 18 #define HIDPP_FF_MAX_PARAMS 20 #define HIDPP_FF_RESERVED_SLOTS 1 @@ -1766,7 +2506,7 @@ static void hidpp_ff_work_handler(struct work_struct *w) /* regular effect destroyed */ data->effect_ids[wd->params[0]-1] = -1; else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) - /* autocenter spring destoyed */ + /* autocenter spring destroyed */ data->slot_autocenter = 0; break; case HIDPP_FF_SET_GLOBAL_GAINS: @@ -1801,11 +2541,10 @@ static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id wd->size = size; memcpy(wd->params, params, size); - atomic_inc(&data->workqueue_size); + s = atomic_inc_return(&data->workqueue_size); queue_work(data->wq, &wd->work); /* warn about excessive queue size */ - s = atomic_read(&data->workqueue_size); if (s >= 20 && s % 20 == 0) hid_warn(data->hidpp->hid_dev, "Force feedback command queue contains %d commands, causing substantial delays!", s); @@ -1983,7 +2722,7 @@ static int hidpp_ff_erase_effect(struct input_dev *dev, int effect_id) static void hidpp_ff_set_autocenter(struct input_dev *dev, u16 magnitude) { struct hidpp_ff_private_data *data = dev->ff->private; - u8 params[18]; + u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH]; dbg_hid("Setting autocenter to %d.\n", magnitude); @@ -2051,29 +2790,47 @@ static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp static void hidpp_ff_destroy(struct ff_device *ff) { struct hidpp_ff_private_data *data = ff->private; + struct hid_device *hid = data->hidpp->hid_dev; + hid_info(hid, "Unloading HID++ force feedback.\n"); + + device_remove_file(&hid->dev, &dev_attr_range); + destroy_workqueue(data->wq); kfree(data->effect_ids); } -static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index) +static int hidpp_ff_init(struct hidpp_device *hidpp, + struct hidpp_ff_private_data *data) { struct hid_device *hid = hidpp->hid_dev; - struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct input_dev *dev = hidinput->input; - const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor); - const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice); + struct hid_input *hidinput; + struct input_dev *dev; + struct usb_device_descriptor *udesc; + u16 bcdDevice; struct ff_device *ff; - struct hidpp_report response; - struct hidpp_ff_private_data *data; - int error, j, num_slots; + int error, j, num_slots = data->num_effects; u8 version; + if (!hid_is_usb(hid)) { + hid_err(hid, "device is not USB\n"); + return -ENODEV; + } + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + if (!dev) { hid_err(hid, "Struct input_dev not set!\n"); return -EINVAL; } /* Get firmware release */ + udesc = &(hid_to_usb_dev(hid)->descriptor); + bcdDevice = le16_to_cpu(udesc->bcdDevice); version = bcdDevice & 255; /* Set supported force feedback capabilities */ @@ -2083,27 +2840,17 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index) for (j = 0; hidpp_ff_effects_v2[j] >= 0; j++) set_bit(hidpp_ff_effects_v2[j], dev->ffbit); - /* Read number of slots available in device */ - error = hidpp_send_fap_command_sync(hidpp, feature_index, - HIDPP_FF_GET_INFO, NULL, 0, &response); - if (error) { - if (error < 0) - return error; - hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", - __func__, error); - return -EPROTO; - } - - num_slots = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS; - error = input_ff_create(dev, num_slots); if (error) { hid_err(dev, "Failed to create FF device!\n"); return error; } - - data = kzalloc(sizeof(*data), GFP_KERNEL); + /* + * Create a copy of passed data, so we can transfer memory + * ownership to FF core + */ + data = kmemdup(data, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL); @@ -2111,11 +2858,15 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index) kfree(data); return -ENOMEM; } + data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue"); + if (!data->wq) { + kfree(data->effect_ids); + kfree(data); + return -ENOMEM; + } + data->hidpp = hidpp; - data->feature_index = feature_index; data->version = version; - data->slot_autocenter = 0; - data->num_effects = num_slots; for (j = 0; j < num_slots; j++) data->effect_ids[j] = -1; @@ -2129,69 +2880,20 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index) ff->set_autocenter = hidpp_ff_set_autocenter; ff->destroy = hidpp_ff_destroy; - - /* reset all forces */ - error = hidpp_send_fap_command_sync(hidpp, feature_index, - HIDPP_FF_RESET_ALL, NULL, 0, &response); - - /* Read current Range */ - error = hidpp_send_fap_command_sync(hidpp, feature_index, - HIDPP_FF_GET_APERTURE, NULL, 0, &response); - if (error) - hid_warn(hidpp->hid_dev, "Failed to read range from device!\n"); - data->range = error ? 900 : get_unaligned_be16(&response.fap.params[0]); - /* Create sysfs interface */ error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range); if (error) hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error); - /* Read the current gain values */ - error = hidpp_send_fap_command_sync(hidpp, feature_index, - HIDPP_FF_GET_GLOBAL_GAINS, NULL, 0, &response); - if (error) - hid_warn(hidpp->hid_dev, "Failed to read gain values from device!\n"); - data->gain = error ? 0xffff : get_unaligned_be16(&response.fap.params[0]); - /* ignore boost value at response.fap.params[2] */ - /* init the hardware command queue */ - data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue"); atomic_set(&data->workqueue_size, 0); - /* initialize with zero autocenter to get wheel in usable state */ - hidpp_ff_set_autocenter(dev, 0); - hid_info(hid, "Force feedback support loaded (firmware release %d).\n", version); return 0; } -static int hidpp_ff_deinit(struct hid_device *hid) -{ - struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct input_dev *dev = hidinput->input; - struct hidpp_ff_private_data *data; - - if (!dev) { - hid_err(hid, "Struct input_dev not found!\n"); - return -EINVAL; - } - - hid_info(hid, "Unloading HID++ force feedback.\n"); - data = dev->ff->private; - if (!data) { - hid_err(hid, "Private data not found!\n"); - return -EINVAL; - } - - destroy_workqueue(data->wq); - device_remove_file(&hid->dev, &dev_attr_range); - - return 0; -} - - /* ************************************************************************** */ /* */ /* Device Support */ @@ -2205,7 +2907,6 @@ static int hidpp_ff_deinit(struct hid_device *hid) #define WTP_MANUAL_RESOLUTION 39 struct wtp_data { - struct input_dev *input; u16 x_size, y_size; u8 finger_count; u8 mt_feature_index; @@ -2223,7 +2924,7 @@ static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, } static void wtp_populate_input(struct hidpp_device *hidpp, - struct input_dev *input_dev, bool origin_is_hid_core) + struct input_dev *input_dev) { struct wtp_data *wd = hidpp->private_data; @@ -2249,31 +2950,30 @@ static void wtp_populate_input(struct hidpp_device *hidpp, input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED); - - wd->input = input_dev; } -static void wtp_touch_event(struct wtp_data *wd, +static void wtp_touch_event(struct hidpp_device *hidpp, struct hidpp_touchpad_raw_xy_finger *touch_report) { + struct wtp_data *wd = hidpp->private_data; int slot; if (!touch_report->finger_id || touch_report->contact_type) /* no actual data */ return; - slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id); + slot = input_mt_get_slot_by_key(hidpp->input, touch_report->finger_id); - input_mt_slot(wd->input, slot); - input_mt_report_slot_state(wd->input, MT_TOOL_FINGER, + input_mt_slot(hidpp->input, slot); + input_mt_report_slot_state(hidpp->input, MT_TOOL_FINGER, touch_report->contact_status); if (touch_report->contact_status) { - input_event(wd->input, EV_ABS, ABS_MT_POSITION_X, + input_event(hidpp->input, EV_ABS, ABS_MT_POSITION_X, touch_report->x); - input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y, + input_event(hidpp->input, EV_ABS, ABS_MT_POSITION_Y, wd->flip_y ? wd->y_size - touch_report->y : touch_report->y); - input_event(wd->input, EV_ABS, ABS_MT_PRESSURE, + input_event(hidpp->input, EV_ABS, ABS_MT_PRESSURE, touch_report->area); } } @@ -2281,19 +2981,18 @@ static void wtp_touch_event(struct wtp_data *wd, static void wtp_send_raw_xy_event(struct hidpp_device *hidpp, struct hidpp_touchpad_raw_xy *raw) { - struct wtp_data *wd = hidpp->private_data; int i; for (i = 0; i < 2; i++) - wtp_touch_event(wd, &(raw->fingers[i])); + wtp_touch_event(hidpp, &(raw->fingers[i])); if (raw->end_of_frame && !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)) - input_event(wd->input, EV_KEY, BTN_LEFT, raw->button); + input_event(hidpp->input, EV_KEY, BTN_LEFT, raw->button); if (raw->end_of_frame || raw->finger_count <= 2) { - input_mt_sync_frame(wd->input); - input_sync(wd->input); + input_mt_sync_frame(hidpp->input); + input_sync(hidpp->input); } } @@ -2343,7 +3042,7 @@ static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) struct hidpp_report *report = (struct hidpp_report *)data; struct hidpp_touchpad_raw_xy raw; - if (!wd || !wd->input) + if (!wd || !hidpp->input) return 1; switch (data[0]) { @@ -2354,11 +3053,11 @@ static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) return 1; } if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) { - input_event(wd->input, EV_KEY, BTN_LEFT, + input_event(hidpp->input, EV_KEY, BTN_LEFT, !!(data[1] & 0x01)); - input_event(wd->input, EV_KEY, BTN_RIGHT, + input_event(hidpp->input, EV_KEY, BTN_RIGHT, !!(data[1] & 0x02)); - input_sync(wd->input); + input_sync(hidpp->input); return 0; } else { if (size < 21) @@ -2383,11 +3082,10 @@ static int wtp_get_config(struct hidpp_device *hidpp) { struct wtp_data *wd = hidpp->private_data; struct hidpp_touchpad_raw_info raw_info = {0}; - u8 feature_type; int ret; ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY, - &wd->mt_feature_index, &feature_type); + &wd->mt_feature_index); if (ret) /* means that the device is not powered up */ return ret; @@ -2423,7 +3121,7 @@ static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id) return 0; }; -static int wtp_connect(struct hid_device *hdev, bool connected) +static int wtp_connect(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct wtp_data *wd = hidpp->private_data; @@ -2476,10 +3174,6 @@ static int wtp_connect(struct hid_device *hdev, bool connected) static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03}; -struct m560_private_data { - struct input_dev *input; -}; - /* how buttons are mapped in the report */ #define M560_MOUSE_BTN_LEFT 0x01 #define M560_MOUSE_BTN_RIGHT 0x02 @@ -2489,7 +3183,7 @@ struct m560_private_data { #define M560_SUB_ID 0x0a #define M560_BUTTON_MODE_REGISTER 0x35 -static int m560_send_config_command(struct hid_device *hdev, bool connected) +static int m560_send_config_command(struct hid_device *hdev) { struct hidpp_report response; struct hidpp_device *hidpp_dev; @@ -2507,28 +3201,12 @@ static int m560_send_config_command(struct hid_device *hdev, bool connected) ); } -static int m560_allocate(struct hid_device *hdev) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct m560_private_data *d; - - d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data), - GFP_KERNEL); - if (!d) - return -ENOMEM; - - hidpp->private_data = d; - - return 0; -}; - static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct m560_private_data *mydata = hidpp->private_data; /* sanity check */ - if (!mydata || !mydata->input) { + if (!hidpp->input) { hid_err(hdev, "error in parameter\n"); return -EINVAL; } @@ -2555,24 +3233,24 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) switch (data[5]) { case 0xaf: - input_report_key(mydata->input, BTN_MIDDLE, 1); + input_report_key(hidpp->input, BTN_MIDDLE, 1); break; case 0xb0: - input_report_key(mydata->input, BTN_FORWARD, 1); + input_report_key(hidpp->input, BTN_FORWARD, 1); break; case 0xae: - input_report_key(mydata->input, BTN_BACK, 1); + input_report_key(hidpp->input, BTN_BACK, 1); break; case 0x00: - input_report_key(mydata->input, BTN_BACK, 0); - input_report_key(mydata->input, BTN_FORWARD, 0); - input_report_key(mydata->input, BTN_MIDDLE, 0); + input_report_key(hidpp->input, BTN_BACK, 0); + input_report_key(hidpp->input, BTN_FORWARD, 0); + input_report_key(hidpp->input, BTN_MIDDLE, 0); break; default: hid_err(hdev, "error in report\n"); return 0; } - input_sync(mydata->input); + input_sync(hidpp->input); } else if (data[0] == 0x02) { /* @@ -2586,58 +3264,55 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) int v; - input_report_key(mydata->input, BTN_LEFT, + input_report_key(hidpp->input, BTN_LEFT, !!(data[1] & M560_MOUSE_BTN_LEFT)); - input_report_key(mydata->input, BTN_RIGHT, + input_report_key(hidpp->input, BTN_RIGHT, !!(data[1] & M560_MOUSE_BTN_RIGHT)); if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT) { - input_report_rel(mydata->input, REL_HWHEEL, -1); - input_report_rel(mydata->input, REL_HWHEEL_HI_RES, + input_report_rel(hidpp->input, REL_HWHEEL, -1); + input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, -120); } else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT) { - input_report_rel(mydata->input, REL_HWHEEL, 1); - input_report_rel(mydata->input, REL_HWHEEL_HI_RES, + input_report_rel(hidpp->input, REL_HWHEEL, 1); + input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, 120); } - v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12); - input_report_rel(mydata->input, REL_X, v); + v = sign_extend32(hid_field_extract(hdev, data + 3, 0, 12), 11); + input_report_rel(hidpp->input, REL_X, v); - v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12); - input_report_rel(mydata->input, REL_Y, v); + v = sign_extend32(hid_field_extract(hdev, data + 3, 12, 12), 11); + input_report_rel(hidpp->input, REL_Y, v); - v = hid_snto32(data[6], 8); - hidpp_scroll_counter_handle_scroll( - &hidpp->vertical_wheel_counter, v); + v = sign_extend32(data[6], 7); + if (v != 0) + hidpp_scroll_counter_handle_scroll(hidpp->input, + &hidpp->vertical_wheel_counter, v); - input_sync(mydata->input); + input_sync(hidpp->input); } return 1; } static void m560_populate_input(struct hidpp_device *hidpp, - struct input_dev *input_dev, bool origin_is_hid_core) + struct input_dev *input_dev) { - struct m560_private_data *mydata = hidpp->private_data; - - mydata->input = input_dev; - - __set_bit(EV_KEY, mydata->input->evbit); - __set_bit(BTN_MIDDLE, mydata->input->keybit); - __set_bit(BTN_RIGHT, mydata->input->keybit); - __set_bit(BTN_LEFT, mydata->input->keybit); - __set_bit(BTN_BACK, mydata->input->keybit); - __set_bit(BTN_FORWARD, mydata->input->keybit); - - __set_bit(EV_REL, mydata->input->evbit); - __set_bit(REL_X, mydata->input->relbit); - __set_bit(REL_Y, mydata->input->relbit); - __set_bit(REL_WHEEL, mydata->input->relbit); - __set_bit(REL_HWHEEL, mydata->input->relbit); - __set_bit(REL_WHEEL_HI_RES, mydata->input->relbit); - __set_bit(REL_HWHEEL_HI_RES, mydata->input->relbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_MIDDLE, input_dev->keybit); + __set_bit(BTN_RIGHT, input_dev->keybit); + __set_bit(BTN_LEFT, input_dev->keybit); + __set_bit(BTN_BACK, input_dev->keybit); + __set_bit(BTN_FORWARD, input_dev->keybit); + + __set_bit(EV_REL, input_dev->evbit); + __set_bit(REL_X, input_dev->relbit); + __set_bit(REL_Y, input_dev->relbit); + __set_bit(REL_WHEEL, input_dev->relbit); + __set_bit(REL_HWHEEL, input_dev->relbit); + __set_bit(REL_WHEEL_HI_RES, input_dev->relbit); + __set_bit(REL_HWHEEL_HI_RES, input_dev->relbit); } static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -2670,12 +3345,11 @@ static int k400_disable_tap_to_click(struct hidpp_device *hidpp) struct k400_private_data *k400 = hidpp->private_data; struct hidpp_touchpad_fw_items items = {}; int ret; - u8 feature_type; if (!k400->feature_index) { ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_FW_ITEMS, - &k400->feature_index, &feature_type); + &k400->feature_index); if (ret) /* means that the device is not powered up */ return ret; @@ -2703,7 +3377,7 @@ static int k400_allocate(struct hid_device *hdev) return 0; }; -static int k400_connect(struct hid_device *hdev, bool connected) +static int k400_connect(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); @@ -2719,24 +3393,281 @@ static int k400_connect(struct hid_device *hdev, bool connected) #define HIDPP_PAGE_G920_FORCE_FEEDBACK 0x8123 -static int g920_get_config(struct hidpp_device *hidpp) +static int g920_ff_set_autocenter(struct hidpp_device *hidpp, + struct hidpp_ff_private_data *data) { - u8 feature_type; - u8 feature_index; + struct hidpp_report response; + u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH] = { + [1] = HIDPP_FF_EFFECT_SPRING | HIDPP_FF_EFFECT_AUTOSTART, + }; int ret; + /* initialize with zero autocenter to get wheel in usable state */ + + dbg_hid("Setting autocenter to 0.\n"); + ret = hidpp_send_fap_command_sync(hidpp, data->feature_index, + HIDPP_FF_DOWNLOAD_EFFECT, + params, ARRAY_SIZE(params), + &response); + if (ret) + hid_warn(hidpp->hid_dev, "Failed to autocenter device!\n"); + else + data->slot_autocenter = response.fap.params[0]; + + return ret; +} + +static int g920_get_config(struct hidpp_device *hidpp, + struct hidpp_ff_private_data *data) +{ + struct hidpp_report response; + int ret; + + memset(data, 0, sizeof(*data)); + /* Find feature and store for later use */ ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK, - &feature_index, &feature_type); + &data->feature_index); if (ret) return ret; - ret = hidpp_ff_init(hidpp, feature_index); + /* Read number of slots available in device */ + ret = hidpp_send_fap_command_sync(hidpp, data->feature_index, + HIDPP_FF_GET_INFO, + NULL, 0, + &response); + if (ret) { + if (ret < 0) + return ret; + hid_err(hidpp->hid_dev, + "%s: received protocol error 0x%02x\n", __func__, ret); + return -EPROTO; + } + + data->num_effects = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS; + + /* reset all forces */ + ret = hidpp_send_fap_command_sync(hidpp, data->feature_index, + HIDPP_FF_RESET_ALL, + NULL, 0, + &response); if (ret) - hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n", - ret); + hid_warn(hidpp->hid_dev, "Failed to reset all forces!\n"); - return 0; + ret = hidpp_send_fap_command_sync(hidpp, data->feature_index, + HIDPP_FF_GET_APERTURE, + NULL, 0, + &response); + if (ret) { + hid_warn(hidpp->hid_dev, + "Failed to read range from device!\n"); + } + data->range = ret ? + 900 : get_unaligned_be16(&response.fap.params[0]); + + /* Read the current gain values */ + ret = hidpp_send_fap_command_sync(hidpp, data->feature_index, + HIDPP_FF_GET_GLOBAL_GAINS, + NULL, 0, + &response); + if (ret) + hid_warn(hidpp->hid_dev, + "Failed to read gain values from device!\n"); + data->gain = ret ? + 0xffff : get_unaligned_be16(&response.fap.params[0]); + + /* ignore boost value at response.fap.params[2] */ + + return g920_ff_set_autocenter(hidpp, data); +} + +/* -------------------------------------------------------------------------- */ +/* Logitech Dinovo Mini keyboard with builtin touchpad */ +/* -------------------------------------------------------------------------- */ +#define DINOVO_MINI_PRODUCT_ID 0xb30c + +static int lg_dinovo_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_LOGIVENDOR) + return 0; + + switch (usage->hid & HID_USAGE) { + case 0x00d: lg_map_key_clear(KEY_MEDIA); break; + default: + return 0; + } + return 1; +} + +/* -------------------------------------------------------------------------- */ +/* HID++1.0 devices which use HID++ reports for their wheels */ +/* -------------------------------------------------------------------------- */ +static int hidpp10_wheel_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_WHEEL_REPORT | HIDPP_ENABLE_HWHEEL_REPORT, + HIDPP_ENABLE_WHEEL_REPORT | HIDPP_ENABLE_HWHEEL_REPORT); +} + +static int hidpp10_wheel_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + s8 value, hvalue; + + if (!hidpp->input) + return -EINVAL; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || data[2] != HIDPP_SUB_ID_ROLLER) + return 0; + + value = data[3]; + hvalue = data[4]; + + input_report_rel(hidpp->input, REL_WHEEL, value); + input_report_rel(hidpp->input, REL_WHEEL_HI_RES, value * 120); + input_report_rel(hidpp->input, REL_HWHEEL, hvalue); + input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, hvalue * 120); + input_sync(hidpp->input); + + return 1; +} + +static void hidpp10_wheel_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev) +{ + __set_bit(EV_REL, input_dev->evbit); + __set_bit(REL_WHEEL, input_dev->relbit); + __set_bit(REL_WHEEL_HI_RES, input_dev->relbit); + __set_bit(REL_HWHEEL, input_dev->relbit); + __set_bit(REL_HWHEEL_HI_RES, input_dev->relbit); +} + +/* -------------------------------------------------------------------------- */ +/* HID++1.0 mice which use HID++ reports for extra mouse buttons */ +/* -------------------------------------------------------------------------- */ +static int hidpp10_extra_mouse_buttons_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT, + HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT); +} + +static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + int i; + + if (!hidpp->input) + return -EINVAL; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || + data[2] != HIDPP_SUB_ID_MOUSE_EXTRA_BTNS) + return 0; + + /* + * Buttons are either delivered through the regular mouse report *or* + * through the extra buttons report. At least for button 6 how it is + * delivered differs per receiver firmware version. Even receivers with + * the same usb-id show different behavior, so we handle both cases. + */ + for (i = 0; i < 8; i++) + input_report_key(hidpp->input, BTN_MOUSE + i, + (data[3] & (1 << i))); + + /* Some mice report events on button 9+, use BTN_MISC */ + for (i = 0; i < 8; i++) + input_report_key(hidpp->input, BTN_MISC + i, + (data[4] & (1 << i))); + + input_sync(hidpp->input); + return 1; +} + +static void hidpp10_extra_mouse_buttons_populate_input( + struct hidpp_device *hidpp, struct input_dev *input_dev) +{ + /* BTN_MOUSE - BTN_MOUSE+7 are set already by the descriptor */ + __set_bit(BTN_0, input_dev->keybit); + __set_bit(BTN_1, input_dev->keybit); + __set_bit(BTN_2, input_dev->keybit); + __set_bit(BTN_3, input_dev->keybit); + __set_bit(BTN_4, input_dev->keybit); + __set_bit(BTN_5, input_dev->keybit); + __set_bit(BTN_6, input_dev->keybit); + __set_bit(BTN_7, input_dev->keybit); +} + +/* -------------------------------------------------------------------------- */ +/* HID++1.0 kbds which only report 0x10xx consumer usages through sub-id 0x03 */ +/* -------------------------------------------------------------------------- */ + +/* Find the consumer-page input report desc and change Maximums to 0x107f */ +static u8 *hidpp10_consumer_keys_report_fixup(struct hidpp_device *hidpp, + u8 *_rdesc, unsigned int *rsize) +{ + /* Note 0 terminated so we can use strnstr to search for this. */ + static const char consumer_rdesc_start[] = { + 0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */ + 0x09, 0x01, /* USAGE (Consumer Control) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x03, /* REPORT_ID = 3 */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x15, 0x01, /* LOGICAL_MIN (1) */ + 0x26, 0x00 /* LOGICAL_MAX (... */ + }; + char *consumer_rdesc, *rdesc = (char *)_rdesc; + unsigned int size; + + consumer_rdesc = strnstr(rdesc, consumer_rdesc_start, *rsize); + size = *rsize - (consumer_rdesc - rdesc); + if (consumer_rdesc && size >= 25) { + consumer_rdesc[15] = 0x7f; + consumer_rdesc[16] = 0x10; + consumer_rdesc[20] = 0x7f; + consumer_rdesc[21] = 0x10; + } + return _rdesc; +} + +static int hidpp10_consumer_keys_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_CONSUMER_REPORT, + HIDPP_ENABLE_CONSUMER_REPORT); +} + +static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + u8 consumer_report[5]; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || + data[2] != HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS) + return 0; + + /* + * Build a normal consumer report (3) out of the data, this detour + * is necessary to get some keyboards to report their 0x10xx usages. + */ + consumer_report[0] = 0x03; + memcpy(&consumer_report[1], &data[3], 4); + /* We are called from atomic context */ + hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT, + consumer_report, 5, 1); + + return 1; } /* -------------------------------------------------------------------------- */ @@ -2748,25 +3679,67 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) int ret; u8 multiplier = 1; - if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2121) { + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) { ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, false); if (ret == 0) ret = hidpp_hrw_get_wheel_capability(hidpp, &multiplier); - } else if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2120) { + } else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL) { ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true, &multiplier); - } else /* if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) */ { + } else /* if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL) */ { ret = hidpp10_enable_scrolling_acceleration(hidpp); multiplier = 8; } - if (ret) + if (ret) { + hid_dbg(hidpp->hid_dev, + "Could not enable hi-res scrolling: %d\n", ret); return ret; + } - if (multiplier == 0) + if (multiplier == 0) { + hid_dbg(hidpp->hid_dev, + "Invalid multiplier 0 from device, setting it to 1\n"); multiplier = 1; + } hidpp->vertical_wheel_counter.wheel_multiplier = multiplier; - hid_info(hidpp->hid_dev, "multiplier = %d\n", multiplier); + hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier); + return 0; +} + +static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp) +{ + int ret; + unsigned long capabilities; + + capabilities = hidpp->capabilities; + + if (hidpp->protocol_major >= 2) { + u8 feature_index; + + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL, + &feature_index); + if (!ret) { + hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL; + hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n"); + return 0; + } + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING, + &feature_index); + if (!ret) { + hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL; + hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n"); + } + } else { + /* We cannot detect fast scrolling support on HID++ 1.0 devices */ + if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) { + hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL; + hid_dbg(hidpp->hid_dev, "Detected HID++ 1.0 fast scroll\n"); + } + } + + if (hidpp->capabilities == capabilities) + hid_dbg(hidpp->hid_dev, "Did not detect HID++ hi-res scrolling hardware support\n"); return 0; } @@ -2774,18 +3747,40 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ +static const u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc, + unsigned int *rsize) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (!hidpp) + return rdesc; + + /* For 27 MHz keyboards the quirk gets set after hid_parse. */ + if (hdev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE || + (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS)) + rdesc = hidpp10_consumer_keys_report_fixup(hidpp, rdesc, rsize); + + return rdesc; +} + static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + if (!hidpp) + return 0; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) return wtp_input_mapping(hdev, hi, field, usage, bit, max); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 && field->application != HID_GD_MOUSE) return m560_input_mapping(hdev, hi, field, usage, bit, max); + if (hdev->product == DINOVO_MINI_PRODUCT_ID) + return lg_dinovo_input_mapping(hdev, hi, field, usage, bit, max); + return 0; } @@ -2795,6 +3790,9 @@ static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi, { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + if (!hidpp) + return 0; + /* Ensure that Logitech G920 is not given a default fuzz/flat value */ if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { if (usage->type == EV_ABS && (usage->code == ABS_X || @@ -2809,15 +3807,20 @@ static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi, static void hidpp_populate_input(struct hidpp_device *hidpp, - struct input_dev *input, bool origin_is_hid_core) + struct input_dev *input) { + hidpp->input = input; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) - wtp_populate_input(hidpp, input, origin_is_hid_core); + wtp_populate_input(hidpp, input); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) - m560_populate_input(hidpp, input, origin_is_hid_core); + m560_populate_input(hidpp, input); + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) + hidpp10_wheel_populate_input(hidpp, input); - if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) - hidpp->vertical_wheel_counter.dev = input; + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) + hidpp10_extra_mouse_buttons_populate_input(hidpp, input); } static int hidpp_input_configured(struct hid_device *hdev, @@ -2826,7 +3829,10 @@ static int hidpp_input_configured(struct hid_device *hdev, struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct input_dev *input = hidinput->input; - hidpp_populate_input(hidpp, input, true); + if (!hidpp) + return 0; + + hidpp_populate_input(hidpp, input); return 0; } @@ -2838,6 +3844,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, struct hidpp_report *answer = hidpp->send_receive_buf; struct hidpp_report *report = (struct hidpp_report *)data; int ret; + int last_online; /* * If the mutex is locked then we have a pending answer from a @@ -2863,21 +3870,39 @@ 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 (unlikely(hidpp_report_is_connect_event(hidpp, report))) { if (schedule_work(&hidpp->work) == 0) dbg_hid("%s: connect event already queued\n", __func__); return 1; } + if (hidpp->hid_dev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && + data[0] == REPORT_ID_HIDPP_SHORT && + data[2] == HIDPP_SUB_ID_USER_IFACE_EVENT && + (data[3] & HIDPP_USER_IFACE_EVENT_ENCRYPTION_KEY_LOST)) { + dev_err_ratelimited(&hidpp->hid_dev->dev, + "Error the keyboard's wireless encryption key has been lost, your keyboard will not work unless you re-configure encryption.\n"); + dev_err_ratelimited(&hidpp->hid_dev->dev, + "See: https://gitlab.freedesktop.org/jwrdegoede/logitech-27mhz-keyboard-encryption-setup/\n"); + } + + last_online = hidpp->battery.online; if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { - ret = hidpp20_battery_event(hidpp, data, size); + ret = hidpp20_battery_event_1000(hidpp, data, size); + if (ret != 0) + return ret; + ret = hidpp20_battery_event_1004(hidpp, data, size); if (ret != 0) return ret; ret = hidpp_solar_battery_event(hidpp, data, size); if (ret != 0) return ret; + ret = hidpp20_battery_voltage_event(hidpp, data, size); + if (ret != 0) + return ret; + ret = hidpp20_adc_measurement_event_1f20(hidpp, data, size); + if (ret != 0) + return ret; } if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) { @@ -2886,6 +3911,29 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + if (hidpp->quirks & HIDPP_QUIRK_RESET_HI_RES_SCROLL) { + if (last_online == 0 && hidpp->battery.online == 1) + schedule_work(&hidpp->reset_hi_res_work); + } + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) { + ret = hidpp10_wheel_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) { + ret = hidpp10_extra_mouse_buttons_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { + ret = hidpp10_consumer_keys_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -2895,10 +3943,13 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, struct hidpp_device *hidpp = hid_get_drvdata(hdev); int ret = 0; + if (!hidpp) + return 0; + /* Generic HID++ processing. */ switch (data[0]) { case REPORT_ID_HIDPP_VERY_LONG: - if (size != HIDPP_REPORT_VERY_LONG_LENGTH) { + if (size != hidpp->very_long_report_length) { hid_err(hdev, "received hid++ report of bad size (%d)", size); return 1; @@ -2943,17 +3994,23 @@ static int hidpp_event(struct hid_device *hdev, struct hid_field *field, * restriction imposed in hidpp_usages. */ struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct hidpp_scroll_counter *counter = &hidpp->vertical_wheel_counter; + struct hidpp_scroll_counter *counter; + + if (!hidpp) + return 0; + + counter = &hidpp->vertical_wheel_counter; /* A scroll event may occur before the multiplier has been retrieved or * the input device set, or high-res scroll enabling may fail. In such * cases we must return early (falling back to default behaviour) to * avoid a crash in hidpp_scroll_counter_handle_scroll. */ - if (!(hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) || value == 0 - || counter->dev == NULL || counter->wheel_multiplier == 0) + if (!(hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL) + || value == 0 || hidpp->input == NULL + || counter->wheel_multiplier == 0) return 0; - hidpp_scroll_counter_handle_scroll(counter, value); + hidpp_scroll_counter_handle_scroll(hidpp->input, counter, value); return 1; } @@ -2973,12 +4030,24 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) hidpp->battery.feature_index = 0xff; hidpp->battery.solar_feature_index = 0xff; + hidpp->battery.voltage_feature_index = 0xff; + hidpp->battery.adc_measurement_feature_index = 0xff; if (hidpp->protocol_major >= 2) { if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750) ret = hidpp_solar_request_battery_event(hidpp); - else - ret = hidpp20_query_battery_info(hidpp); + else { + /* we only support one battery feature right now, so let's + first check the ones that support battery level first + and leave voltage for last */ + ret = hidpp20_query_battery_info_1000(hidpp); + if (ret) + ret = hidpp20_query_battery_info_1004(hidpp); + if (ret) + ret = hidpp20_query_battery_voltage_info(hidpp); + if (ret) + ret = hidpp20_query_adc_measurement_info_1f20(hidpp); + } if (ret) return ret; @@ -3003,9 +4072,12 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) if (!battery_props) return -ENOMEM; - num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 2; + num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3; - if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE) + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE || + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE || + hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE || + hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT) battery_props[num_battery_props++] = POWER_SUPPLY_PROP_CAPACITY; @@ -3013,6 +4085,11 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) battery_props[num_battery_props++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL; + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE || + hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT) + battery_props[num_battery_props++] = + POWER_SUPPLY_PROP_VOLTAGE_NOW; + battery = &hidpp->battery; n = atomic_inc_return(&battery_no) - 1; @@ -3035,24 +4112,22 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) return ret; } -static void hidpp_overwrite_name(struct hid_device *hdev) +/* Get name + serial for USB and Bluetooth HID++ devices */ +static void hidpp_non_unifying_init(struct hidpp_device *hidpp) { - struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct hid_device *hdev = hidpp->hid_dev; char *name; - if (hidpp->protocol_major < 2) - return; + /* Bluetooth devices already have their serialnr set */ + if (hid_is_usb(hdev)) + hidpp_serial_init(hidpp); name = hidpp_get_device_name(hidpp); - - if (!name) { - hid_err(hdev, "unable to retrieve the name of the device"); - } else { + if (name) { dbg_hid("HID++: Got name: %s\n", name); snprintf(hdev->name, sizeof(hdev->name), "%s", name); + kfree(name); } - - kfree(name); } static int hidpp_input_open(struct input_dev *dev) @@ -3093,15 +4168,18 @@ static struct input_dev *hidpp_allocate_input(struct hid_device *hdev) return input_dev; } -static void hidpp_connect_event(struct hidpp_device *hidpp) +static void hidpp_connect_event(struct work_struct *work) { + struct hidpp_device *hidpp = container_of(work, struct hidpp_device, work); struct hid_device *hdev = hidpp->hid_dev; - int ret = 0; - bool connected = atomic_read(&hidpp->connected); struct input_dev *input; char *name, *devm_name; + int ret; - if (!connected) { + /* Get device version to check if it is connected */ + ret = hidpp_root_get_protocol_version(hidpp); + if (ret) { + hid_dbg(hidpp->hid_dev, "Disconnected\n"); if (hidpp->battery.ps) { hidpp->battery.online = false; hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN; @@ -3112,48 +4190,60 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) } if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { - ret = wtp_connect(hdev, connected); + ret = wtp_connect(hdev); if (ret) return; } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { - ret = m560_send_config_command(hdev, connected); + ret = m560_send_config_command(hdev); if (ret) return; } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { - ret = k400_connect(hdev, connected); + ret = k400_connect(hdev); if (ret) return; } - /* the device is already connected, we can ask for its name and - * protocol */ - if (!hidpp->protocol_major) { - ret = !hidpp_is_connected(hidpp); - if (ret) { - hid_err(hdev, "Can not get the protocol version.\n"); + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) { + ret = hidpp10_wheel_connect(hidpp); + if (ret) return; - } - hid_info(hdev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); } - if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) { - name = hidpp_get_device_name(hidpp); - if (!name) { - hid_err(hdev, - "unable to retrieve the name of the device"); + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) { + ret = hidpp10_extra_mouse_buttons_connect(hidpp); + if (ret) return; - } + } - devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); - kfree(name); - if (!devm_name) + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { + ret = hidpp10_consumer_keys_connect(hidpp); + if (ret) return; + } - hidpp->name = devm_name; + if (hidpp->protocol_major >= 2) { + u8 feature_index; + + if (!hidpp_get_wireless_feature_index(hidpp, &feature_index)) + hidpp->wireless_feature_index = feature_index; + } + + if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) { + name = hidpp_get_device_name(hidpp); + if (name) { + devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "%s", name); + kfree(name); + if (!devm_name) + return; + + hidpp->name = devm_name; + } } hidpp_initialize_battery(hidpp); + if (!hid_is_usb(hidpp->hid_dev)) + hidpp_initialize_hires_scroll(hidpp); /* forward current battery state */ if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) { @@ -3163,15 +4253,22 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) else hidpp10_query_battery_status(hidpp); } else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { - hidpp20_query_battery_info(hidpp); + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE) + hidpp20_query_battery_voltage_info(hidpp); + else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY) + hidpp20_query_battery_info_1004(hidpp); + else if (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT) + hidpp20_query_adc_measurement_info_1f20(hidpp); + else + hidpp20_query_battery_info_1000(hidpp); } if (hidpp->battery.ps) power_supply_changed(hidpp->battery.ps); - if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) + if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL) hi_res_scroll_enable(hidpp); - if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input) + if (!(hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) || hidpp->delayed_input) /* if the input nodes are already created, we can stop now */ return; @@ -3181,15 +4278,24 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } - hidpp_populate_input(hidpp, input, false); + hidpp_populate_input(hidpp, input); ret = input_register_device(input); - if (ret) + if (ret) { input_free_device(input); + return; + } hidpp->delayed_input = input; } +static void hidpp_reset_hi_res_handler(struct work_struct *work) +{ + struct hidpp_device *hidpp = container_of(work, struct hidpp_device, reset_hi_res_work); + + hi_res_scroll_enable(hidpp); +} + static DEVICE_ATTR(builtin_power_supply, 0000, NULL, NULL); static struct attribute *sysfs_attrs[] = { @@ -3201,47 +4307,126 @@ static const struct attribute_group ps_attribute_group = { .attrs = sysfs_attrs }; +static int hidpp_get_report_length(struct hid_device *hdev, int id) +{ + struct hid_report_enum *re; + struct hid_report *report; + + re = &(hdev->report_enum[HID_OUTPUT_REPORT]); + report = re->report_id_hash[id]; + if (!report) + return 0; + + return report->field[0]->report_count + 1; +} + +static u8 hidpp_validate_device(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + int id, report_length; + u8 supported_reports = 0; + + id = REPORT_ID_HIDPP_SHORT; + report_length = hidpp_get_report_length(hdev, id); + if (report_length) { + if (report_length < HIDPP_REPORT_SHORT_LENGTH) + goto bad_device; + + supported_reports |= HIDPP_REPORT_SHORT_SUPPORTED; + } + + id = REPORT_ID_HIDPP_LONG; + report_length = hidpp_get_report_length(hdev, id); + if (report_length) { + if (report_length < HIDPP_REPORT_LONG_LENGTH) + goto bad_device; + + supported_reports |= HIDPP_REPORT_LONG_SUPPORTED; + } + + id = REPORT_ID_HIDPP_VERY_LONG; + report_length = hidpp_get_report_length(hdev, id); + if (report_length) { + if (report_length < HIDPP_REPORT_LONG_LENGTH || + report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH) + goto bad_device; + + supported_reports |= HIDPP_REPORT_VERY_LONG_SUPPORTED; + hidpp->very_long_report_length = report_length; + } + + return supported_reports; + +bad_device: + hid_warn(hdev, "not enough values in hidpp report %d\n", id); + return false; +} + +static bool hidpp_application_equals(struct hid_device *hdev, + unsigned int application) +{ + struct list_head *report_list; + struct hid_report *report; + + report_list = &hdev->report_enum[HID_INPUT_REPORT].report_list; + report = list_first_entry_or_null(report_list, struct hid_report, list); + return report && report->application == application; +} + static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hidpp_device *hidpp; int ret; - bool connected; unsigned int connect_mask = HID_CONNECT_DEFAULT; - hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), - GFP_KERNEL); + /* report_fixup needs drvdata to be set before we call hid_parse */ + hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL); if (!hidpp) return -ENOMEM; hidpp->hid_dev = hdev; hidpp->name = hdev->name; + hidpp->quirks = id->driver_data; hid_set_drvdata(hdev, hidpp); - hidpp->quirks = id->driver_data; + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "%s:parse failed\n", __func__); + return ret; + } - if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) - hidpp->quirks |= HIDPP_QUIRK_UNIFYING; + /* + * Make sure the device is HID++ capable, otherwise treat as generic HID + */ + hidpp->supported_reports = hidpp_validate_device(hdev); - if (disable_raw_mode) { - hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; - hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; + if (!hidpp->supported_reports) { + hid_set_drvdata(hdev, NULL); + devm_kfree(&hdev->dev, hidpp); + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); } + if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && + hidpp_application_equals(hdev, HID_GD_MOUSE)) + hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS | + HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS; + + if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && + hidpp_application_equals(hdev, HID_GD_KEYBOARD)) + hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_allocate(hdev, id); if (ret) - goto allocate_fail; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { - ret = m560_allocate(hdev); - if (ret) - goto allocate_fail; + return ret; } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { ret = k400_allocate(hdev); if (ret) - goto allocate_fail; + return ret; } - INIT_WORK(&hidpp->work, delayed_work_cb); + INIT_WORK(&hidpp->work, hidpp_connect_event); + INIT_WORK(&hidpp->reset_hi_res_work, hidpp_reset_hi_res_handler); mutex_init(&hidpp->send_mutex); init_waitqueue_head(&hidpp->wait); @@ -3251,93 +4436,78 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_warn(hdev, "Cannot allocate sysfs group for %s\n", hdev->name); - ret = hid_parse(hdev); + /* + * First call hid_hw_start(hdev, 0) to allow IO without connecting any + * hid subdrivers (hid-input, hidraw). This allows retrieving the dev's + * name and serial number and store these in hdev->name and hdev->uniq, + * before the hid-input and hidraw drivers expose these to userspace. + */ + ret = hid_hw_start(hdev, 0); if (ret) { - hid_err(hdev, "%s:parse failed\n", __func__); - goto hid_parse_fail; + hid_err(hdev, "hw start failed\n"); + goto hid_hw_start_fail; } - if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) - connect_mask &= ~HID_CONNECT_HIDINPUT; - - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - ret = hid_hw_start(hdev, connect_mask); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto hid_hw_start_fail; - } - ret = hid_hw_open(hdev); - if (ret < 0) { - dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", - __func__, ret); - hid_hw_stop(hdev); - goto hid_hw_start_fail; - } + ret = hid_hw_open(hdev); + if (ret < 0) { + dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", + __func__, ret); + goto hid_hw_open_fail; } - /* Allow incoming packets */ hid_device_io_start(hdev); - if (hidpp->quirks & HIDPP_QUIRK_UNIFYING) + /* Get name + serial, store in hdev->name + hdev->uniq */ + if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) hidpp_unifying_init(hidpp); + else + hidpp_non_unifying_init(hidpp); - connected = hidpp_is_connected(hidpp); - atomic_set(&hidpp->connected, connected); - if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) { - if (!connected) { - ret = -ENODEV; - hid_err(hdev, "Device not connected"); - goto hid_hw_open_failed; - } - - hid_info(hdev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); - - hidpp_overwrite_name(hdev); - } - - if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { - ret = wtp_get_config(hidpp); - if (ret) - goto hid_hw_open_failed; - } else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { - ret = g920_get_config(hidpp); - if (ret) - goto hid_hw_open_failed; - } + if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) + connect_mask &= ~HID_CONNECT_HIDINPUT; - /* Block incoming packets */ + /* Now export the actual inputs and hidraw nodes to the world */ hid_device_io_stop(hdev); - - if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { - ret = hid_hw_start(hdev, connect_mask); - if (ret) { - hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); - goto hid_hw_start_fail; - } + ret = hid_connect(hdev, connect_mask); + if (ret) { + hid_err(hdev, "%s:hid_connect returned error %d\n", __func__, ret); + goto hid_hw_init_fail; } - /* Allow incoming packets */ + /* Check for connected devices now that incoming packets will not be disabled again */ hid_device_io_start(hdev); + schedule_work(&hidpp->work); + flush_work(&hidpp->work); - hidpp_connect_event(hidpp); + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + struct hidpp_ff_private_data data; - return ret; + ret = g920_get_config(hidpp, &data); + if (!ret) + ret = hidpp_ff_init(hidpp, &data); -hid_hw_open_failed: - hid_device_io_stop(hdev); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - hid_hw_close(hdev); - hid_hw_stop(hdev); + if (ret) + hid_warn(hidpp->hid_dev, + "Unable to initialize force feedback support, errno %d\n", + ret); } + + /* + * This relies on logi_dj_ll_close() being a no-op so that DJ connection + * events will still be received. + */ + hid_hw_close(hdev); + return ret; + +hid_hw_init_fail: + hid_hw_close(hdev); +hid_hw_open_fail: + hid_hw_stop(hdev); 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: - hid_set_drvdata(hdev, NULL); return ret; } @@ -3345,14 +4515,14 @@ static void hidpp_remove(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + if (!hidpp) + return hid_hw_stop(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); - } hid_hw_stop(hdev); cancel_work_sync(&hidpp->work); + cancel_work_sync(&hidpp->reset_hi_res_work); mutex_destroy(&hidpp->send_mutex); } @@ -3360,6 +4530,10 @@ static void hidpp_remove(struct hid_device *hdev) HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \ USB_VENDOR_ID_LOGITECH, (product)) +#define L27MHZ_DEVICE(product) \ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_27MHZ_DEVICE, \ + USB_VENDOR_ID_LOGITECH, (product)) + static const struct hid_device_id hidpp_devices[] = { { /* wireless touchpad */ LDJ_DEVICE(0x4011), @@ -3371,38 +4545,14 @@ static const struct hid_device_id hidpp_devices[] = { { /* wireless touchpad T651 */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651), - .driver_data = HIDPP_QUIRK_CLASS_WTP }, + .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT }, { /* Mouse Logitech Anywhere MX */ LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, - { /* Mouse Logitech Cube */ - LDJ_DEVICE(0x4010), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 }, - { /* Mouse Logitech M335 */ - LDJ_DEVICE(0x4050), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { /* Mouse Logitech M515 */ - LDJ_DEVICE(0x4007), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 }, { /* Mouse logitech M560 */ LDJ_DEVICE(0x402d), - .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 - | HIDPP_QUIRK_HI_RES_SCROLL_X2120 }, + .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 }, { /* Mouse Logitech M705 (firmware RQM17) */ LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, - { /* Mouse Logitech M705 (firmware RQM67) */ - LDJ_DEVICE(0x406d), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { /* Mouse Logitech M720 */ - LDJ_DEVICE(0x405e), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { /* Mouse Logitech MX Anywhere 2 */ - LDJ_DEVICE(0x404a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { LDJ_DEVICE(0xb013), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { LDJ_DEVICE(0xb018), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { LDJ_DEVICE(0xb01f), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { /* Mouse Logitech MX Anywhere 2S */ - LDJ_DEVICE(0x406a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { /* Mouse Logitech MX Master */ - LDJ_DEVICE(0x4041), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { LDJ_DEVICE(0x4060), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { LDJ_DEVICE(0x4071), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, - { /* Mouse Logitech MX Master 2S */ - LDJ_DEVICE(0x4069), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, { /* Mouse Logitech Performance MX */ LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 }, { /* Keyboard logitech K400 */ @@ -3411,11 +4561,109 @@ static const struct hid_device_id hidpp_devices[] = { { /* Solar Keyboard Logitech K750 */ LDJ_DEVICE(0x4002), .driver_data = HIDPP_QUIRK_CLASS_K750 }, + { /* Keyboard MX5000 (Bluetooth-receiver in HID proxy mode) */ + LDJ_DEVICE(0xb305), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, + { /* Dinovo Edge (Bluetooth-receiver in HID proxy mode) */ + LDJ_DEVICE(0xb309), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, + { /* Keyboard MX5500 (Bluetooth-receiver in HID proxy mode) */ + LDJ_DEVICE(0xb30b), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, + { /* Logitech G502 Lightspeed Wireless Gaming Mouse */ + LDJ_DEVICE(0x407f), + .driver_data = HIDPP_QUIRK_RESET_HI_RES_SCROLL }, { LDJ_DEVICE(HID_ANY_ID) }, - { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), + { /* Keyboard LX501 (Y-RR53) */ + L27MHZ_DEVICE(0x0049), + .driver_data = HIDPP_QUIRK_KBD_ZOOM_WHEEL }, + { /* Keyboard MX3000 (Y-RAM74) */ + L27MHZ_DEVICE(0x0057), + .driver_data = HIDPP_QUIRK_KBD_SCROLL_WHEEL }, + { /* Keyboard MX3200 (Y-RAV80) */ + L27MHZ_DEVICE(0x005c), + .driver_data = HIDPP_QUIRK_KBD_ZOOM_WHEEL }, + { /* S510 Media Remote */ + L27MHZ_DEVICE(0x00fe), + .driver_data = HIDPP_QUIRK_KBD_SCROLL_WHEEL }, + + { L27MHZ_DEVICE(HID_ANY_ID) }, + + { /* Logitech G403 Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, + { /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, + { /* Logitech G703 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, + { /* Logitech G703 Hero Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) }, + { /* Logitech G900 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) }, + { /* Logitech G903 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC086) }, + { /* Logitech G Pro Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) }, + { /* MX Vertical over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) }, + { /* Logitech G703 Hero Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) }, + { /* Logitech G903 Hero Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) }, + { /* Logitech G915 TKL Keyboard over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC343) }, + { /* Logitech G920 Wheel over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, + { /* Logitech G923 Wheel (Xbox version) over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL), + .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS }, + { /* Logitech G Pro X Superlight Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) }, + { /* Logitech G Pro X Superlight 2 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC09b) }, + { /* Logitech G PRO 2 LIGHTSPEED Wireless Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xc09a) }, + + { /* G935 Gaming Headset */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87), + .driver_data = HIDPP_QUIRK_WIRELESS_STATUS }, + + { /* MX5000 keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, + { /* Dinovo Edge keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb309), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, + { /* MX5500 keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb30b), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, + { /* Logitech G915 TKL keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb35f) }, + { /* M-RCQ142 V470 Cordless Laser Mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb008) }, + { /* MX Master mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012) }, + { /* M720 Triathlon mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb015) }, + { /* MX Master 2S mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb019) }, + { /* MX Ergo trackball over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) }, + { /* MX Vertical mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) }, + { /* Signature M650 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) }, + { /* MX Master 3 mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, + { /* MX Anywhere 3 mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) }, + { /* MX Master 3S mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) }, + { /* MX Anywhere 3SB mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) }, {} }; @@ -3429,6 +4677,7 @@ static const struct hid_usage_id hidpp_usages[] = { static struct hid_driver hidpp_driver = { .name = "logitech-hidpp-device", .id_table = hidpp_devices, + .report_fixup = hidpp_report_fixup, .probe = hidpp_probe, .remove = hidpp_remove, .raw_event = hidpp_raw_event, |
