aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid/hid-logitech-hidpp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/hid-logitech-hidpp.c')
-rw-r--r--drivers/hid/hid-logitech-hidpp.c736
1 files changed, 537 insertions, 199 deletions
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 199cc256e9d9..72fc9c0566db 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -51,7 +51,11 @@ MODULE_PARM_DESC(disable_tap_to_click,
#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_SUB_ID_CONSUMER_VENDOR_KEYS 0x03
+#define HIDPP_SUB_ID_ROLLER 0x05
+#define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS 0x06
#define HIDPP_QUIRK_CLASS_WTP BIT(0)
#define HIDPP_QUIRK_CLASS_M560 BIT(1)
@@ -68,6 +72,13 @@ MODULE_PARM_DESC(disable_tap_to_click,
#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)
+
+/* 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 | \
@@ -106,13 +117,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 {
@@ -149,7 +160,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 +168,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;
@@ -206,8 +218,6 @@ static int __hidpp_send_report(struct hid_device *hdev,
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 +226,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;
@@ -342,7 +352,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;
@@ -434,14 +444,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 +486,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 +502,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 +527,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 +537,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 +763,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;
@@ -836,18 +861,21 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
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);
+ 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 */
@@ -862,21 +890,19 @@ 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];
-static bool hidpp_is_connected(struct hidpp_device *hidpp)
-{
- 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;
+print_version:
+ hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
+ hidpp->protocol_major, hidpp->protocol_minor);
+ return 0;
}
/* -------------------------------------------------------------------------- */
@@ -932,7 +958,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;
@@ -1012,7 +1038,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;
@@ -2211,7 +2241,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;
@@ -2229,7 +2258,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;
@@ -2255,31 +2284,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);
}
}
@@ -2287,19 +2315,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);
}
}
@@ -2349,7 +2376,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]) {
@@ -2360,11 +2387,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)
@@ -2482,10 +2509,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
@@ -2513,28 +2536,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;
}
@@ -2561,24 +2568,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) {
/*
@@ -2592,59 +2599,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);
+ 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);
+ input_report_rel(hidpp->input, REL_Y, v);
v = hid_snto32(data[6], 8);
if (v != 0)
- hidpp_scroll_counter_handle_scroll(
+ 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_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, 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_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,
@@ -2747,6 +2750,175 @@ static int g920_get_config(struct hidpp_device *hidpp)
}
/* -------------------------------------------------------------------------- */
+/* 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. */
+ 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;
+}
+
+/* -------------------------------------------------------------------------- */
/* High-resolution scroll wheels */
/* -------------------------------------------------------------------------- */
@@ -2781,12 +2953,31 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp)
/* Generic HID++ devices */
/* -------------------------------------------------------------------------- */
+static 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 &&
@@ -2802,6 +2993,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 ||
@@ -2816,15 +3010,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_HI_RES_SCROLL)
- hidpp->vertical_wheel_counter.dev = input;
+ if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS)
+ hidpp10_wheel_populate_input(hidpp, 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,
@@ -2833,7 +3032,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;
}
@@ -2893,6 +3095,24 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
return ret;
}
+ 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;
}
@@ -2902,10 +3122,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;
@@ -2950,17 +3173,22 @@ 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)
+ || 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;
}
@@ -3132,32 +3360,45 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
return;
}
+ if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
+ ret = hidpp10_wheel_connect(hidpp);
+ if (ret)
+ return;
+ }
+
+ if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) {
+ ret = hidpp10_extra_mouse_buttons_connect(hidpp);
+ if (ret)
+ return;
+ }
+
+ if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
+ ret = hidpp10_consumer_keys_connect(hidpp);
+ 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);
+ ret = hidpp_root_get_protocol_version(hidpp);
if (ret) {
hid_err(hdev, "Can not get the protocol version.\n");
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");
- return;
- }
-
- devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
- kfree(name);
- if (!devm_name)
- return;
+ if (name) {
+ devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "%s", name);
+ kfree(name);
+ if (!devm_name)
+ return;
- hidpp->name = devm_name;
+ hidpp->name = devm_name;
+ }
}
hidpp_initialize_battery(hidpp);
@@ -3188,7 +3429,7 @@ 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)
@@ -3208,6 +3449,60 @@ 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 bool hidpp_validate_report(struct hid_device *hdev, int id,
+ int expected_length, bool optional)
+{
+ int report_length;
+
+ if (id >= HID_MAX_IDS || id < 0) {
+ hid_err(hdev, "invalid HID report id %u\n", id);
+ return false;
+ }
+
+ report_length = hidpp_get_report_length(hdev, id);
+ if (!report_length)
+ return optional;
+
+ if (report_length < expected_length) {
+ hid_warn(hdev, "not enough values in hidpp report %d\n", id);
+ return false;
+ }
+
+ return true;
+}
+
+static bool hidpp_validate_device(struct hid_device *hdev)
+{
+ return hidpp_validate_report(hdev, REPORT_ID_HIDPP_SHORT,
+ HIDPP_REPORT_SHORT_LENGTH, false) &&
+ hidpp_validate_report(hdev, REPORT_ID_HIDPP_LONG,
+ HIDPP_REPORT_LONG_LENGTH, true);
+}
+
+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;
@@ -3215,20 +3510,48 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
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;
+ }
+
+ /*
+ * Make sure the device is HID++ capable, otherwise treat as generic HID
+ */
+ if (!hidpp_validate_device(hdev)) {
+ hid_set_drvdata(hdev, NULL);
+ devm_kfree(&hdev->dev, hidpp);
+ return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ }
+
+ hidpp->very_long_report_length =
+ hidpp_get_report_length(hdev, REPORT_ID_HIDPP_VERY_LONG);
+ if (hidpp->very_long_report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH)
+ hidpp->very_long_report_length = HIDPP_REPORT_VERY_LONG_MAX_LENGTH;
if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
hidpp->quirks |= HIDPP_QUIRK_UNIFYING;
+ 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 (disable_raw_mode) {
hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
@@ -3237,15 +3560,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
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);
@@ -3258,93 +3577,79 @@ 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);
+ /*
+ * Plain USB connections need to actually call start and open
+ * on the transport driver to allow incoming data.
+ */
+ 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);
+ hid_hw_stop(hdev);
+ goto hid_hw_open_fail;
}
-
/* Allow incoming packets */
hid_device_io_start(hdev);
if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
hidpp_unifying_init(hidpp);
- connected = hidpp_is_connected(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_open_failed;
+ goto hid_hw_init_fail;
}
- 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;
+ goto hid_hw_init_fail;
} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
ret = g920_get_config(hidpp);
if (ret)
- goto hid_hw_open_failed;
+ goto hid_hw_init_fail;
}
- /* Block incoming packets */
- hid_device_io_stop(hdev);
+ hidpp_connect_event(hidpp);
- 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;
- }
- }
+ /* Reset the HID node state */
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
- /* Allow incoming packets */
- hid_device_io_start(hdev);
+ if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
+ connect_mask &= ~HID_CONNECT_HIDINPUT;
- hidpp_connect_event(hidpp);
+ /* Now export the actual inputs and hidraw nodes to the world */
+ 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;
+ }
return ret;
-hid_hw_open_failed:
- hid_device_io_stop(hdev);
- if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
- hid_hw_close(hdev);
- hid_hw_stop(hdev);
- }
+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;
}
@@ -3352,12 +3657,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) {
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)
hidpp_ff_deinit(hdev);
- hid_hw_close(hdev);
- }
+
hid_hw_stop(hdev);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
@@ -3367,6 +3674,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),
@@ -3418,11 +3729,37 @@ 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 },
{ 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 },
+
+ { L27MHZ_DEVICE(HID_ANY_ID) },
+
+ { /* Logitech G403 Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) },
+ { /* Logitech G700 Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC06B) },
+ { /* Logitech G900 Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) },
+ { /* 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},
+
+ { /* MX5000 keyboard over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305),
+ .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
{}
};
@@ -3436,6 +3773,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,