diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index d86a9189e88f..2aa4ed157aec 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -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, };