HID: logitech-hidpp: add support for HID++ 1.0 wheel reports
authorHans de Goede <hdegoede@redhat.com>
Sat, 20 Apr 2019 11:22:15 +0000 (13:22 +0200)
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>
Tue, 23 Apr 2019 16:03:05 +0000 (18:03 +0200)
Add a quirk for switching wheel event reporting to using the HID++
report for this.

This has 2 advantages:
1) Without this tilting the scrollwheel left / right will send a
   scroll-lock + cursor-left/-right + scroll-lock key-sequence instead of
   hwheel events

2) The HID++ reports contain the device index instead of using the generic
   HID implementation, so this will make scroll-wheel events from the wheel
   on some keyboards be emitted by the right event node.

2. also fixes keyboard scroll-wheel events getting lost in the (mostly
theoretical) case of there not being a mouse paired with the receiver.

This commit enables this quirk for all 27Mhz mice, it cannot hurt to have
it enabled and this avoids the need to keep adding more and more quirks for
this. This has been tested in 5 different 27MHz mice, 3 of which have a
wheel which can tilt.

This commit also adds explicit quirks for 3 keyboards with a zoom-/scroll-
wheel. The MX3000 keyboard scroll-wheel can also tilt. I've defined aliases
to the new HIDPP_QUIRK_HIDPP_WHEELS for this, so that it is clear why the
keyboard has the quirk and in case we want to handle the keyboard wheels
and especially the keyboard zoom-wheels differently in the future.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
drivers/hid/hid-logitech-hidpp.c

index 2b76976..7f6beba 100644 (file)
@@ -53,6 +53,9 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_REPORT_LONG_LENGTH               20
 #define HIDPP_REPORT_VERY_LONG_MAX_LENGTH      64
 
+#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)
 #define HIDPP_QUIRK_CLASS_K400                 BIT(2)
@@ -68,6 +71,11 @@ 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)
+
+/* 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 | \
@@ -2739,6 +2747,52 @@ 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);
+}
+
+/* -------------------------------------------------------------------------- */
 /* High-resolution scroll wheels                                              */
 /* -------------------------------------------------------------------------- */
 
@@ -2822,6 +2876,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
                wtp_populate_input(hidpp, input);
        else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
                m560_populate_input(hidpp, input);
+
+       if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS)
+               hidpp10_wheel_populate_input(hidpp, input);
 }
 
 static int hidpp_input_configured(struct hid_device *hdev,
@@ -2893,6 +2950,12 @@ 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;
+       }
+
        return 0;
 }
 
@@ -3140,6 +3203,12 @@ 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;
+       }
+
        /* the device is already connected, we can ask for its name and
         * protocol */
        if (!hidpp->protocol_major) {
@@ -3254,6 +3323,17 @@ static bool hidpp_validate_device(struct hid_device *hdev)
                                     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;
@@ -3292,6 +3372,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        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;
+
        if (disable_raw_mode) {
                hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
                hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
@@ -3472,6 +3556,16 @@ static const struct hid_device_id hidpp_devices[] = {
 
        { LDJ_DEVICE(HID_ANY_ID) },
 
+       { /* 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 */