These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / hid / hid-lenovo.c
index c4c3f09..8979f1f 100644 (file)
@@ -37,12 +37,42 @@ struct lenovo_drvdata_tpkbd {
 };
 
 struct lenovo_drvdata_cptkbd {
+       u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
        bool fn_lock;
        int sensitivity;
 };
 
 #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
 
+static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
+       0x05, 0x88,             /* Usage Page (Vendor Usage Page 0x88)  */
+       0x09, 0x01,             /* Usage (Vendor Usage 0x01)            */
+       0xa1, 0x01,             /* Collection (Application)             */
+       0x85, 0x04,             /*  Report ID (4)                       */
+       0x19, 0x00,             /*  Usage Minimum (0)                   */
+       0x2a, 0xff, 0xff,       /*  Usage Maximum (65535)               */
+};
+
+static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+               unsigned int *rsize)
+{
+       switch (hdev->product) {
+       case USB_DEVICE_ID_LENOVO_TPPRODOCK:
+               /* the fixups that need to be done:
+                *   - get a reasonable usage max for the vendor collection
+                *     0x8801 from the report ID 4
+                */
+               if (*rsize >= 153 &&
+                   memcmp(&rdesc[140], lenovo_pro_dock_need_fixup_collection,
+                         sizeof(lenovo_pro_dock_need_fixup_collection)) == 0) {
+                       rdesc[151] = 0x01;
+                       rdesc[152] = 0x00;
+               }
+               break;
+       }
+       return rdesc;
+}
+
 static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
                struct hid_input *hi, struct hid_field *field,
                struct hid_usage *usage, unsigned long **bit, int *max)
@@ -117,10 +147,10 @@ static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
 
                switch (usage->hid & HID_USAGE) {
                case 0x0000:
-                       hid_map_usage(hi, usage, bit, max, EV_REL, 0x06);
+                       hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
                        return 1;
                case 0x0001:
-                       hid_map_usage(hi, usage, bit, max, EV_REL, 0x08);
+                       hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL);
                        return 1;
                default:
                        return -1;
@@ -178,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,
@@ -284,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;
@@ -599,7 +679,8 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
                                    GFP_KERNEL);
        if (data_pointer == NULL) {
                hid_err(hdev, "Could not allocate memory for driver data\n");
-               return -ENOMEM;
+               ret = -ENOMEM;
+               goto err;
        }
 
        // set same default values as windows driver
@@ -610,7 +691,8 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
        name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
        if (name_mute == NULL || name_micmute == NULL) {
                hid_err(hdev, "Could not allocate memory for led data\n");
-               return -ENOMEM;
+               ret = -ENOMEM;
+               goto err;
        }
        snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
        snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
@@ -634,6 +716,9 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
        lenovo_features_set_tpkbd(hdev);
 
        return 0;
+err:
+       sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
+       return ret;
 }
 
 static int lenovo_probe_cptkbd(struct hid_device *hdev)
@@ -671,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);
@@ -762,10 +848,31 @@ static void lenovo_remove(struct hid_device *hdev)
        hid_hw_stop(hdev);
 }
 
+static int lenovo_input_configured(struct hid_device *hdev,
+               struct hid_input *hi)
+{
+       switch (hdev->product) {
+               case USB_DEVICE_ID_LENOVO_TPKBD:
+               case USB_DEVICE_ID_LENOVO_CUSBKBD:
+               case USB_DEVICE_ID_LENOVO_CBTKBD:
+                       if (test_bit(EV_REL, hi->input->evbit)) {
+                               /* set only for trackpoint device */
+                               __set_bit(INPUT_PROP_POINTER, hi->input->propbit);
+                               __set_bit(INPUT_PROP_POINTING_STICK,
+                                               hi->input->propbit);
+                       }
+                       break;
+       }
+
+       return 0;
+}
+
+
 static const struct hid_device_id lenovo_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) },
        { }
 };
 
@@ -774,10 +881,13 @@ MODULE_DEVICE_TABLE(hid, lenovo_devices);
 static struct hid_driver lenovo_driver = {
        .name = "lenovo",
        .id_table = lenovo_devices,
+       .input_configured = lenovo_input_configured,
        .input_mapping = lenovo_input_mapping,
        .probe = lenovo_probe,
        .remove = lenovo_remove,
        .raw_event = lenovo_raw_event,
+       .event = lenovo_event,
+       .report_fixup = lenovo_report_fixup,
 };
 module_hid_driver(lenovo_driver);