Merge branch 'for-5.5/whiskers' into for-linus
authorJiri Kosina <jkosina@suse.cz>
Fri, 29 Nov 2019 19:39:21 +0000 (20:39 +0100)
committerJiri Kosina <jkosina@suse.cz>
Fri, 29 Nov 2019 19:39:21 +0000 (20:39 +0100)
- robustification of tablet mode support in google-whiskers
  driver (Dmitry Torokhov)

drivers/hid/hid-google-hammer.c

index d86a918..2aa4ed1 100644 (file)
@@ -35,6 +35,7 @@ struct cbas_ec {
        struct device *dev;     /* The platform device (EC) */
        struct input_dev *input;
        bool base_present;
+       bool base_folded;
        struct notifier_block notifier;
 };
 
@@ -208,7 +209,14 @@ static int __cbas_ec_probe(struct platform_device *pdev)
                return error;
        }
 
-       input_report_switch(input, SW_TABLET_MODE, !cbas_ec.base_present);
+       if (!cbas_ec.base_present)
+               cbas_ec.base_folded = false;
+
+       dev_dbg(&pdev->dev, "%s: base: %d, folded: %d\n", __func__,
+               cbas_ec.base_present, cbas_ec.base_folded);
+
+       input_report_switch(input, SW_TABLET_MODE,
+                           !cbas_ec.base_present || cbas_ec.base_folded);
 
        cbas_ec_set_input(input);
 
@@ -322,10 +330,9 @@ static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev,
 static int hammer_register_leds(struct hid_device *hdev)
 {
        struct hammer_kbd_leds *kbd_backlight;
+       int error;
 
-       kbd_backlight = devm_kzalloc(&hdev->dev,
-                                    sizeof(*kbd_backlight),
-                                    GFP_KERNEL);
+       kbd_backlight = kzalloc(sizeof(*kbd_backlight), GFP_KERNEL);
        if (!kbd_backlight)
                return -ENOMEM;
 
@@ -339,12 +346,31 @@ static int hammer_register_leds(struct hid_device *hdev)
        /* Set backlight to 0% initially. */
        hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0);
 
-       return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
+       error = led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
+       if (error)
+               goto err_free_mem;
+
+       hid_set_drvdata(hdev, kbd_backlight);
+       return 0;
+
+err_free_mem:
+       kfree(kbd_backlight);
+       return error;
+}
+
+static void hammer_unregister_leds(struct hid_device *hdev)
+{
+       struct hammer_kbd_leds *kbd_backlight = hid_get_drvdata(hdev);
+
+       if (kbd_backlight) {
+               led_classdev_unregister(&kbd_backlight->cdev);
+               kfree(kbd_backlight);
+       }
 }
 
 #define HID_UP_GOOGLEVENDOR    0xffd10000
 #define HID_VD_KBD_FOLDED      0x00000019
-#define WHISKERS_KBD_FOLDED    (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
+#define HID_USAGE_KBD_FOLDED   (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
 
 /* HID usage for keyboard backlight (Alphanumeric display brightness) */
 #define HID_AD_BRIGHTNESS      0x00140046
@@ -354,8 +380,7 @@ static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                                struct hid_usage *usage,
                                unsigned long **bit, int *max)
 {
-       if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
-           usage->hid == WHISKERS_KBD_FOLDED) {
+       if (usage->hid == HID_USAGE_KBD_FOLDED) {
                /*
                 * We do not want to have this usage mapped as it will get
                 * mixed in with "base attached" signal and delivered over
@@ -372,19 +397,19 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field,
 {
        unsigned long flags;
 
-       if (hid->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
-           usage->hid == WHISKERS_KBD_FOLDED) {
+       if (usage->hid == HID_USAGE_KBD_FOLDED) {
                spin_lock_irqsave(&cbas_ec_lock, flags);
 
-               hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
-                       cbas_ec.base_present, value);
-
                /*
-                * We should not get event if base is detached, but in case
-                * we happen to service HID and EC notifications out of order
-                * let's still check the "base present" flag.
+                * If we are getting events from Whiskers that means that it
+                * is attached to the lid.
                 */
-               if (cbas_ec.input && cbas_ec.base_present) {
+               cbas_ec.base_present = true;
+               cbas_ec.base_folded = value;
+               hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
+                       cbas_ec.base_present, cbas_ec.base_folded);
+
+               if (cbas_ec.input) {
                        input_report_switch(cbas_ec.input,
                                            SW_TABLET_MODE, value);
                        input_sync(cbas_ec.input);
@@ -397,33 +422,22 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field,
        return 0;
 }
 
-static bool hammer_is_keyboard_interface(struct hid_device *hdev)
+static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type,
+                       unsigned application, unsigned usage)
 {
-       struct hid_report_enum *re = &hdev->report_enum[HID_INPUT_REPORT];
-       struct hid_report *report;
-
-       list_for_each_entry(report, &re->report_list, list)
-               if (report->application == HID_GD_KEYBOARD)
-                       return true;
-
-       return false;
-}
-
-static bool hammer_has_backlight_control(struct hid_device *hdev)
-{
-       struct hid_report_enum *re = &hdev->report_enum[HID_OUTPUT_REPORT];
+       struct hid_report_enum *re = &hdev->report_enum[report_type];
        struct hid_report *report;
        int i, j;
 
        list_for_each_entry(report, &re->report_list, list) {
-               if (report->application != HID_GD_KEYBOARD)
+               if (report->application != application)
                        continue;
 
                for (i = 0; i < report->maxfield; i++) {
                        struct hid_field *field = report->field[i];
 
                        for (j = 0; j < field->maxusage; j++)
-                               if (field->usage[j].hid == HID_AD_BRIGHTNESS)
+                               if (field->usage[j].hid == usage)
                                        return true;
                }
        }
@@ -431,21 +445,23 @@ static bool hammer_has_backlight_control(struct hid_device *hdev)
        return false;
 }
 
+static bool hammer_has_folded_event(struct hid_device *hdev)
+{
+       return hammer_has_usage(hdev, HID_INPUT_REPORT,
+                               HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED);
+}
+
+static bool hammer_has_backlight_control(struct hid_device *hdev)
+{
+       return hammer_has_usage(hdev, HID_OUTPUT_REPORT,
+                               HID_GD_KEYBOARD, HID_AD_BRIGHTNESS);
+}
+
 static int hammer_probe(struct hid_device *hdev,
                        const struct hid_device_id *id)
 {
        int error;
 
-       /*
-        * We always want to poll for, and handle tablet mode events from
-        * Whiskers, even when nobody has opened the input device. This also
-        * prevents the hid core from dropping early tablet mode events from
-        * the device.
-        */
-       if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
-                       hammer_is_keyboard_interface(hdev))
-               hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
-
        error = hid_parse(hdev);
        if (error)
                return error;
@@ -454,6 +470,19 @@ static int hammer_probe(struct hid_device *hdev,
        if (error)
                return error;
 
+       /*
+        * We always want to poll for, and handle tablet mode events from
+        * devices that have folded usage, even when nobody has opened the input
+        * device. This also prevents the hid core from dropping early tablet
+        * mode events from the device.
+        */
+       if (hammer_has_folded_event(hdev)) {
+               hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
+               error = hid_hw_open(hdev);
+               if (error)
+                       return error;
+       }
+
        if (hammer_has_backlight_control(hdev)) {
                error = hammer_register_leds(hdev);
                if (error)
@@ -465,6 +494,36 @@ static int hammer_probe(struct hid_device *hdev,
        return 0;
 }
 
+static void hammer_remove(struct hid_device *hdev)
+{
+       unsigned long flags;
+
+       if (hammer_has_folded_event(hdev)) {
+               hid_hw_close(hdev);
+
+               /*
+                * If we are disconnecting then most likely Whiskers is
+                * being removed. Even if it is not removed, without proper
+                * keyboard we should not stay in clamshell mode.
+                *
+                * The reason for doing it here and not waiting for signal
+                * from EC, is that on some devices there are high leakage
+                * on Whiskers pins and we do not detect disconnect reliably,
+                * resulting in devices being stuck in clamshell mode.
+                */
+               spin_lock_irqsave(&cbas_ec_lock, flags);
+               if (cbas_ec.input && cbas_ec.base_present) {
+                       input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1);
+                       input_sync(cbas_ec.input);
+               }
+               cbas_ec.base_present = false;
+               spin_unlock_irqrestore(&cbas_ec_lock, flags);
+       }
+
+       hammer_unregister_leds(hdev);
+
+       hid_hw_stop(hdev);
+}
 
 static const struct hid_device_id hammer_devices[] = {
        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
@@ -487,6 +546,7 @@ static struct hid_driver hammer_driver = {
        .name = "hammer",
        .id_table = hammer_devices,
        .probe = hammer_probe,
+       .remove = hammer_remove,
        .input_mapping = hammer_input_mapping,
        .event = hammer_event,
 };