diff options
Diffstat (limited to 'drivers/hid/hid-logitech-hidpp.c')
| -rw-r--r-- | drivers/hid/hid-logitech-hidpp.c | 842 |
1 files changed, 600 insertions, 242 deletions
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 61635e629469..d5011a5d0890 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -23,24 +23,24 @@ #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 @@ -68,26 +68,23 @@ 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(29) -#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(30) -#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(31) +#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) /* 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 /* 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) - -#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT +#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) @@ -96,6 +93,10 @@ MODULE_PARM_DESC(disable_tap_to_click, #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)) @@ -147,6 +148,7 @@ 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]; @@ -192,8 +194,8 @@ 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; @@ -204,6 +206,8 @@ struct hidpp_device { struct hidpp_scroll_counter vertical_wheel_counter; u8 wireless_feature_index; + + bool connected_once; }; /* HID++ 1.0 error codes */ @@ -222,10 +226,18 @@ 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) { @@ -264,20 +276,22 @@ static int __hidpp_send_report(struct hid_device *hdev, } /* - * 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; @@ -289,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) @@ -331,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) @@ -343,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); @@ -351,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) @@ -393,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) { @@ -452,6 +508,26 @@ 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 @@ -834,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); @@ -856,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; @@ -877,7 +952,6 @@ 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; } @@ -892,7 +966,7 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) ret = hidpp_send_rap_command_sync(hidpp, REPORT_ID_HIDPP_SHORT, HIDPP_PAGE_ROOT_IDX, - CMD_ROOT_GET_PROTOCOL_VERSION, + CMD_ROOT_GET_PROTOCOL_VERSION | LINUX_KERNEL_SW_ID, ping_data, sizeof(ping_data), &response); if (ret == HIDPP_ERROR_INVALID_SUBID) { @@ -902,7 +976,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) } /* 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) { @@ -923,8 +998,59 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) hidpp->protocol_minor = response.rap.params[1]; print_version: - hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); + 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; +} + +/* -------------------------------------------------------------------------- */ +/* 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_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; } @@ -934,9 +1060,9 @@ print_version: #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) @@ -1004,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; @@ -1012,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; @@ -1179,15 +1304,13 @@ static int hidpp20_batterylevel_get_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; } @@ -1331,16 +1454,49 @@ static int hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp, 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) { - u8 feature_type; 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, - &feature_type); + &hidpp->battery.voltage_feature_index); if (ret) return ret; } @@ -1354,6 +1510,8 @@ static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp) 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; @@ -1378,6 +1536,8 @@ static int hidpp20_battery_voltage_event(struct hidpp_device *hidpp, 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; @@ -1532,7 +1692,6 @@ static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp, static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp) { - u8 feature_type; int ret; u8 state_of_charge; int status, level; @@ -1540,8 +1699,7 @@ static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp) if (hidpp->battery.feature_index == 0xff) { ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_UNIFIED_BATTERY, - &hidpp->battery.feature_index, - &feature_type); + &hidpp->battery.feature_index); if (ret) return ret; } @@ -1653,7 +1811,7 @@ static int hidpp_battery_get_property(struct power_supply *psy, val->strval = hidpp->hid_dev->uniq; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - /* hardware reports voltage in in mV. sysfs expects uV */ + /* hardware reports voltage in mV. sysfs expects uV */ val->intval = hidpp->battery.voltage * 1000; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: @@ -1672,17 +1830,166 @@ static int hidpp_battery_get_property(struct power_supply *psy, /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_WIRELESS_DEVICE_STATUS 0x1d4b -static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp) +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) { - u8 feature_type; + struct hidpp_report response; int ret; + u8 *params = (u8 *)response.fap.params; - ret = hidpp_root_get_feature(hidpp, - HIDPP_PAGE_WIRELESS_DEVICE_STATUS, - &hidpp->wireless_feature_index, - &feature_type); + *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); - return ret; + 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; } /* -------------------------------------------------------------------------- */ @@ -1697,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; @@ -1732,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; @@ -1759,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; @@ -1794,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; } @@ -1925,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 @@ -2205,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: @@ -2240,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); @@ -2505,12 +2805,17 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, struct hid_device *hid = hidpp->hid_dev; struct hid_input *hidinput; struct input_dev *dev; - const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor); - const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice); + struct usb_device_descriptor *udesc; + u16 bcdDevice; struct ff_device *ff; 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; @@ -2524,6 +2829,8 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, } /* Get firmware release */ + udesc = &(hid_to_usb_dev(hid)->descriptor); + bcdDevice = le16_to_cpu(udesc->bcdDevice); version = bcdDevice & 255; /* Set supported force feedback capabilities */ @@ -2775,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; @@ -2815,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; @@ -2877,7 +3183,7 @@ static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03}; #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; @@ -2973,13 +3279,13 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) 120); } - v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12); + 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); + 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); + v = sign_extend32(data[6], 7); if (v != 0) hidpp_scroll_counter_handle_scroll(hidpp->input, &hidpp->vertical_wheel_counter, v); @@ -3039,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; @@ -3072,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); @@ -3116,14 +3421,13 @@ static int g920_get_config(struct hidpp_device *hidpp, struct hidpp_ff_private_data *data) { struct hidpp_report response; - u8 feature_type; 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, - &data->feature_index, &feature_type); + &data->feature_index); if (ret) return ret; @@ -3375,34 +3679,76 @@ 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_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; +} + /* -------------------------------------------------------------------------- */ /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ -static u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc, - unsigned int *rsize) +static const u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc, + unsigned int *rsize) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); @@ -3498,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 @@ -3524,8 +3871,6 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, } if (unlikely(hidpp_report_is_connect_event(hidpp, report))) { - atomic_set(&hidpp->connected, - !(report->rap.params[0] & (1 << 6))); if (schedule_work(&hidpp->work) == 0) dbg_hid("%s: connect event already queued\n", __func__); return 1; @@ -3541,6 +3886,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, "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_1000(hidpp, data, size); if (ret != 0) @@ -3554,6 +3900,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, 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) { @@ -3562,6 +3911,11 @@ 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) @@ -3651,8 +4005,9 @@ static int hidpp_event(struct hid_device *hdev, struct hid_field *field, * 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 - || hidpp->input == 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(hidpp->input, counter, value); @@ -3676,6 +4031,7 @@ 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) @@ -3689,6 +4045,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) 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) @@ -3717,7 +4075,9 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 3; if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE || - hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) + 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; @@ -3725,7 +4085,8 @@ 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) + if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE || + hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT) battery_props[num_battery_props++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; @@ -3751,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) @@ -3809,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; @@ -3828,15 +4190,15 @@ 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; } @@ -3859,14 +4221,11 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } - /* the device is already connected, we can ask for its name and - * protocol */ - if (!hidpp->protocol_major) { - ret = hidpp_root_get_protocol_version(hidpp); - if (ret) { - hid_err(hdev, "Can not get the protocol version.\n"); - return; - } + 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) { @@ -3883,6 +4242,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) } 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) { @@ -3896,16 +4257,18 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) 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; @@ -3918,12 +4281,21 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) 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[] = { @@ -4005,9 +4377,7 @@ 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; - struct hidpp_ff_private_data data; /* report_fixup needs drvdata to be set before we call hid_parse */ hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL); @@ -4036,9 +4406,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) return hid_hw_start(hdev, HID_CONNECT_DEFAULT); } - if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) - hidpp->quirks |= HIDPP_QUIRK_UNIFYING; - if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && hidpp_application_equals(hdev, HID_GD_MOUSE)) hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS | @@ -4048,11 +4415,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp_application_equals(hdev, HID_GD_KEYBOARD)) hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS; - if (disable_raw_mode) { - hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; - hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; - } - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_allocate(hdev, id); if (ret) @@ -4063,7 +4425,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) 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); @@ -4074,8 +4437,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->name); /* - * Plain USB connections need to actually call start and open - * on the transport driver to allow incoming data. + * 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) { @@ -4093,64 +4458,46 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Allow incoming packets */ hid_device_io_start(hdev); - if (hidpp->quirks & HIDPP_QUIRK_UNIFYING) + /* 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_root_get_protocol_version(hidpp) == 0; - atomic_set(&hidpp->connected, connected); - if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) { - if (!connected) { - ret = -ENODEV; - hid_err(hdev, "Device not connected"); - goto hid_hw_init_fail; - } - - hidpp_overwrite_name(hdev); - } - - if (connected && hidpp->protocol_major >= 2) { - ret = hidpp_set_wireless_feature_index(hidpp); - if (ret == -ENOENT) - hidpp->wireless_feature_index = 0; - else if (ret) - goto hid_hw_init_fail; - } - - if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { - ret = wtp_get_config(hidpp); - if (ret) - goto hid_hw_init_fail; - } else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { - ret = g920_get_config(hidpp, &data); - if (ret) - goto hid_hw_init_fail; - } - - hidpp_connect_event(hidpp); - - /* Reset the HID node state */ - hid_device_io_stop(hdev); - hid_hw_close(hdev); - hid_hw_stop(hdev); - - if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) + if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) connect_mask &= ~HID_CONNECT_HIDINPUT; /* Now export the actual inputs and hidraw nodes to the world */ - ret = hid_hw_start(hdev, connect_mask); + hid_device_io_stop(hdev); + ret = hid_connect(hdev, connect_mask); if (ret) { - hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); - goto hid_hw_start_fail; + hid_err(hdev, "%s:hid_connect returned error %d\n", __func__, ret); + goto hid_hw_init_fail; } + /* 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); + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - ret = hidpp_ff_init(hidpp, &data); + struct hidpp_ff_private_data data; + + ret = g920_get_config(hidpp, &data); + if (!ret) + ret = hidpp_ff_init(hidpp, &data); + 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: @@ -4175,6 +4522,7 @@ static void hidpp_remove(struct hid_device *hdev) hid_hw_stop(hdev); cancel_work_sync(&hidpp->work); + cancel_work_sync(&hidpp->reset_hi_res_work); mutex_destroy(&hidpp->send_mutex); } @@ -4197,41 +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(0x4072), .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 MX Master 3 */ - LDJ_DEVICE(0x4082), .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 */ @@ -4249,6 +4570,9 @@ static const struct hid_device_id hidpp_devices[] = { { /* 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) }, @@ -4269,6 +4593,8 @@ static const struct hid_device_id hidpp_devices[] = { { /* 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 */ @@ -4277,13 +4603,32 @@ static const struct hid_device_id hidpp_devices[] = { 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 G Pro Gaming Mouse over USB */ - HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) }, + { /* 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), @@ -4294,18 +4639,31 @@ static const struct hid_device_id hidpp_devices[] = { { /* 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), - .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + 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), - .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + { 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), - .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 }, + 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) }, {} }; |
