Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux...
[firefly-linux-kernel-4.4.55.git] / drivers / hid / hid-lenovo.c
index 680bb11dbdeea848ff38cb4019001071d1b796c9..e4bc6cb6d7fa5d5481f650c87fde01a7348fdb6a 100644 (file)
@@ -37,6 +37,7 @@ struct lenovo_drvdata_tpkbd {
 };
 
 struct lenovo_drvdata_cptkbd {
+       u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
        bool fn_lock;
        int sensitivity;
 };
@@ -207,9 +208,12 @@ static void lenovo_features_set_cptkbd(struct hid_device *hdev)
        struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
 
        ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
-       ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity);
        if (ret)
                hid_err(hdev, "Fn-lock setting failed: %d\n", ret);
+
+       ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity);
+       if (ret)
+               hid_err(hdev, "Sensitivity setting failed: %d\n", ret);
 }
 
 static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
@@ -313,6 +317,53 @@ static int lenovo_raw_event(struct hid_device *hdev,
        return 0;
 }
 
+static int lenovo_event_cptkbd(struct hid_device *hdev,
+               struct hid_field *field, struct hid_usage *usage, __s32 value)
+{
+       struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+       /* "wheel" scroll events */
+       if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
+                       usage->code == REL_HWHEEL)) {
+               /* Scroll events disable middle-click event */
+               cptkbd_data->middlebutton_state = 2;
+               return 0;
+       }
+
+       /* Middle click events */
+       if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
+               if (value == 1) {
+                       cptkbd_data->middlebutton_state = 1;
+               } else if (value == 0) {
+                       if (cptkbd_data->middlebutton_state == 1) {
+                               /* No scrolling inbetween, send middle-click */
+                               input_event(field->hidinput->input,
+                                       EV_KEY, BTN_MIDDLE, 1);
+                               input_sync(field->hidinput->input);
+                               input_event(field->hidinput->input,
+                                       EV_KEY, BTN_MIDDLE, 0);
+                               input_sync(field->hidinput->input);
+                       }
+                       cptkbd_data->middlebutton_state = 0;
+               }
+               return 1;
+       }
+
+       return 0;
+}
+
+static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
+               struct hid_usage *usage, __s32 value)
+{
+       switch (hdev->product) {
+       case USB_DEVICE_ID_LENOVO_CUSBKBD:
+       case USB_DEVICE_ID_LENOVO_CBTKBD:
+               return lenovo_event_cptkbd(hdev, field, usage, value);
+       default:
+               return 0;
+       }
+}
+
 static int lenovo_features_set_tpkbd(struct hid_device *hdev)
 {
        struct hid_report *report;
@@ -705,6 +756,7 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev)
                hid_warn(hdev, "Failed to switch middle button: %d\n", ret);
 
        /* Set keyboard settings to known state */
+       cptkbd_data->middlebutton_state = 0;
        cptkbd_data->fn_lock = true;
        cptkbd_data->sensitivity = 0x05;
        lenovo_features_set_cptkbd(hdev);
@@ -832,6 +884,7 @@ static struct hid_driver lenovo_driver = {
        .probe = lenovo_probe,
        .remove = lenovo_remove,
        .raw_event = lenovo_raw_event,
+       .event = lenovo_event,
        .report_fixup = lenovo_report_fixup,
 };
 module_hid_driver(lenovo_driver);