From 38e57f0693ad1b9a785b4f542ec019c5fdf91d57 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 13 Sep 2019 15:03:15 -0700 Subject: HID: google: whiskers: more robust tablet mode detection The USB interface may get detected before the platform/EC one, so let's note the state of the base (if we receive event) and use it to correctly initialize the tablet mode switch state. Also let's start the HID interface immediately when probing, this will ensure that we correctly process "base folded" events that may be sent as we initialize the base. Note that this requires us to add a remove() function where we stop and close the hardware and switch the LED registration away from devm interface as we need to make sure that we destroy the LED instance before we stop the hardware. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 71 ++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 84f8c127ebdc..4f64f93ddfcb 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,7 +346,26 @@ 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 @@ -376,8 +402,9 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field, usage->hid == WHISKERS_KBD_FOLDED) { spin_lock_irqsave(&cbas_ec_lock, flags); + cbas_ec.base_folded = value; hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__, - cbas_ec.base_present, value); + cbas_ec.base_present, cbas_ec.base_folded); /* * We should not get event if base is detached, but in case @@ -436,6 +463,14 @@ static int hammer_probe(struct hid_device *hdev, { int error; + error = hid_parse(hdev); + if (error) + return error; + + error = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (error) + return error; + /* * We always want to poll for, and handle tablet mode events from * Whiskers, even when nobody has opened the input device. This also @@ -443,16 +478,12 @@ static int hammer_probe(struct hid_device *hdev, * the device. */ if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) + hammer_is_keyboard_interface(hdev)) { hdev->quirks |= HID_QUIRK_ALWAYS_POLL; - - error = hid_parse(hdev); - if (error) - return error; - - error = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (error) - return error; + error = hid_hw_open(hdev); + if (error) + return error; + } if (hammer_has_backlight_control(hdev)) { error = hammer_register_leds(hdev); @@ -465,6 +496,15 @@ static int hammer_probe(struct hid_device *hdev, return 0; } +static void hammer_remove(struct hid_device *hdev) +{ + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && + hammer_is_keyboard_interface(hdev)) + hid_hw_close(hdev); + + hammer_unregister_leds(hdev); + hid_hw_stop(hdev); +} static const struct hid_device_id hammer_devices[] = { { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, @@ -483,6 +523,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, }; -- cgit From 79085c7dd24b7acd546f5b283d3f2319a9edbbd8 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 13 Sep 2019 15:03:16 -0700 Subject: HID: google: whiskers: signal tablet mode switch on disconnect Currently, the tablet mode switch that takes two signals as its input: base attached switch from the EC and some GMR signal from whiskers when it's folded over. This tablet mode switch is then sent to Chrome, which changes the UI. However, there are some units which have a lot of leakage on the ADC pins that the EC uses to determine whether or not a base is attached. This can result in the base being physically detached, but the EC thinking that it's still attached. The user would then be stuck in laptop mode and wouldn't be able to rotate their display. To work around this let's send "tablet mode" signal when we remove HID interface, which normally happens when we physically disconnect whiskers. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 4f64f93ddfcb..3dc6116c8f76 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -498,11 +498,33 @@ static int hammer_probe(struct hid_device *hdev, static void hammer_remove(struct hid_device *hdev) { + unsigned long flags; + if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) + hammer_is_keyboard_interface(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); } -- cgit From b543db46b47dc075535a5adf93460b611d669598 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 13 Sep 2019 15:03:17 -0700 Subject: HID: google: whiskers: signal tablet mode on connect When we receive "keyboard position" event from Whiskers we can conclude that the base is attached, even if we did not get EC event for that. We may miss EC event because there are some units which have a lot of leakage on the ADC pins that the EC uses to determine whether or not a base is attached. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 3dc6116c8f76..31e4a39946f5 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -402,16 +402,16 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field, usage->hid == WHISKERS_KBD_FOLDED) { spin_lock_irqsave(&cbas_ec_lock, flags); + /* + * If we are getting events from Whiskers that means that it + * is attached to the lid. + */ + 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); - /* - * 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 (cbas_ec.input && cbas_ec.base_present) { + if (cbas_ec.input) { input_report_switch(cbas_ec.input, SW_TABLET_MODE, value); input_sync(cbas_ec.input); -- cgit From 20c55f250618d4d110b27410a8ffd2c02a0e6911 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 3 Oct 2019 11:18:00 +0800 Subject: HID: google: Detect base folded usage instead of hard-coding whiskers Some other hammer-like device will emit a similar code, let's look for the folded event in HID usage table, instead of hard-coding whiskers in many places. Signed-off-by: Nicolas Boichat Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 53 +++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 28 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 31e4a39946f5..5d1544c1f691 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -370,7 +370,7 @@ static void hammer_unregister_leds(struct hid_device *hdev) #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 @@ -380,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 @@ -398,8 +397,7 @@ 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); /* @@ -424,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; } } @@ -458,6 +445,18 @@ 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) { @@ -473,12 +472,11 @@ static int hammer_probe(struct hid_device *hdev, /* * 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. + * 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 (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) { + if (hammer_has_folded_event(hdev)) { hdev->quirks |= HID_QUIRK_ALWAYS_POLL; error = hid_hw_open(hdev); if (error) @@ -500,8 +498,7 @@ static void hammer_remove(struct hid_device *hdev) { unsigned long flags; - if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS && - hammer_is_keyboard_interface(hdev)) { + if (hammer_has_folded_event(hdev)) { hid_hw_close(hdev); /* -- cgit