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 <eliadev...@gmail.com>
---
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

Reply via email to