[PATCH 1/3] intel-hid: add support for SW_TABLET_MODE

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add support for SW_TABLET_MODE for convertibles notebook.

Exactly as intel-vbtn driver, the event code 0xcc is emitted by
convertibles when entering tablet mode and 0xcd when return to
laptop mode.

Signed-off-by: Elia Devito <eliadevito@xxxxxxxxx>
---
more info: https://bugzilla.kernel.org/show_bug.cgi?id=207433
 
 drivers/platform/x86/intel-hid.c | 84 ++++++++++++++++++++++++++++++--
 1 file changed, 80 insertions(+), 4 deletions(-)

diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c
index 86261970bd8f..5093c57102cf 100644
--- a/drivers/platform/x86/intel-hid.c
+++ b/drivers/platform/x86/intel-hid.c
@@ -15,6 +15,9 @@
 #include <linux/platform_device.h>
 #include <linux/suspend.h>
 
+/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
+#define TABLET_MODE_FLAG 0x40
+
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Alex Hung");
 
@@ -61,7 +64,11 @@ static const struct key_entry intel_array_keymap[] = {
 	{ KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } },      /* Release */
 	{ KE_KEY,    0xCE, { KEY_POWER } },                   /* Press */
 	{ KE_IGNORE, 0xCF, { KEY_POWER } },                   /* Release */
-	{ KE_END },
+};
+
+static const struct key_entry intel_array_switches[] = {
+	{ KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } },  /* Tablet */
+	{ KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } },  /* Laptop */
 };
 
 static const struct dmi_system_id button_array_table[] = {
@@ -89,9 +96,23 @@ static const struct dmi_system_id button_array_table[] = {
 	{ }
 };
 
+static const struct dmi_system_id button_array_switches_table[] = {
+	{
+		.matches = {
+			DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
+		},
+	},
+	{ }
+};
+
+#define KEYMAP_LEN \
+	(ARRAY_SIZE(intel_array_keymap) + ARRAY_SIZE(intel_array_switches) + 1)
+
 struct intel_hid_priv {
+	struct key_entry keymap[KEYMAP_LEN];
 	struct input_dev *input_dev;
 	struct input_dev *array;
+	bool has_switches;
 	bool wakeup_mode;
 };
 
@@ -327,23 +348,54 @@ static int intel_hid_input_setup(struct platform_device *device)
 	return input_register_device(priv->input_dev);
 }
 
+static void detect_tablet_mode(struct platform_device *device)
+{
+	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+	acpi_handle handle = ACPI_HANDLE(&device->dev);
+	unsigned long long vgbs;
+	int m;
+
+	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs))
+		return;
+
+	m = !(vgbs & TABLET_MODE_FLAG);
+	input_report_switch(priv->array, SW_TABLET_MODE, m);
+}
+
 static int intel_button_array_input_setup(struct platform_device *device)
 {
 	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
-	int ret;
+	int ret, keymap_len = 0;
 
 	/* Setup input device for 5 button array */
 	priv->array = devm_input_allocate_device(&device->dev);
 	if (!priv->array)
 		return -ENOMEM;
 
-	ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL);
+	memcpy(&priv->keymap[keymap_len], intel_array_keymap,
+		       ARRAY_SIZE(intel_array_keymap) *
+		       sizeof(struct key_entry));
+	keymap_len += ARRAY_SIZE(intel_array_keymap);
+
+	if (priv->has_switches) {
+		memcpy(&priv->keymap[keymap_len], intel_array_switches,
+		       ARRAY_SIZE(intel_array_switches) *
+		       sizeof(struct key_entry));
+		keymap_len += ARRAY_SIZE(intel_array_switches);
+	}
+
+	priv->keymap[keymap_len].type = KE_END;
+
+	ret = sparse_keymap_setup(priv->array, priv->keymap, NULL);
 	if (ret)
 		return ret;
 
 	priv->array->name = "Intel HID 5 button array";
 	priv->array->id.bustype = BUS_HOST;
 
+	if (priv->has_switches)
+		detect_tablet_mode(device);
+
 	return input_register_device(priv->array);
 }
 
@@ -352,7 +404,10 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
 	struct platform_device *device = context;
 	struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
 	unsigned long long ev_index;
+	unsigned int val = !(event & 1); /* Even=press, Odd=release */
+	const struct key_entry *ke;
 
+	dev_info(&device->dev, "event 0x%x\n", event);
 	if (priv->wakeup_mode) {
 		/*
 		 * Needed for wakeup from suspend-to-idle to work on some
@@ -367,13 +422,19 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
 		if (event == 0xc0 || !priv->array)
 			return;
 
-		if (!sparse_keymap_entry_from_scancode(priv->array, event)) {
+		ke = sparse_keymap_entry_from_scancode(priv->array, event);
+		if (!ke) {
 			dev_info(&device->dev, "unknown event 0x%x\n", event);
 			return;
 		}
 
 wakeup:
 		pm_wakeup_hard_event(&device->dev);
+
+		/* report the new switch position to the input subsystem. */
+		if (ke && ke->type == KE_SW)
+			sparse_keymap_report_event(priv->array, event, val, 0);
+
 		return;
 	}
 
@@ -441,6 +502,20 @@ static bool button_array_present(struct platform_device *device)
 	return false;
 }
 
+static bool intel_button_array_has_switches(struct platform_device *device)
+{
+	acpi_handle handle = ACPI_HANDLE(&device->dev);
+	unsigned long long vgbs;
+
+	if (!dmi_check_system(button_array_switches_table))
+		return false;
+
+	if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs))
+		return false;
+
+	return true;
+}
+
 static int intel_hid_probe(struct platform_device *device)
 {
 	acpi_handle handle = ACPI_HANDLE(&device->dev);
@@ -479,6 +554,7 @@ static int intel_hid_probe(struct platform_device *device)
 
 	/* Setup 5 button array */
 	if (button_array_present(device)) {
+		priv->has_switches = intel_button_array_has_switches(device);
 		dev_info(&device->dev, "platform supports 5 button array\n");
 		err = intel_button_array_input_setup(device);
 		if (err)
-- 
2.28.0




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux