Commit d8d04708 authored by Jiri Kosina's avatar Jiri Kosina
Browse files

Merge branch 'for-5.5/whiskers' into for-linus

- robustification of tablet mode support in google-whiskers
  driver (Dmitry Torokhov)
parents a820e450 20c55f25
Loading
Loading
Loading
Loading
+103 −43
Original line number Diff line number Diff line
@@ -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)
{
	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)
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_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,
};