162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Intel Virtual Button driver for Windows 8.1+ 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com> 662306a36Sopenharmony_ci * Copyright (C) 2016 Alex Hung <alex.hung@canonical.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/acpi.h> 1062306a36Sopenharmony_ci#include <linux/dmi.h> 1162306a36Sopenharmony_ci#include <linux/input.h> 1262306a36Sopenharmony_ci#include <linux/input/sparse-keymap.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/suspend.h> 1762306a36Sopenharmony_ci#include "../dual_accel_detect.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Returned when NOT in tablet mode on some HP Stream x360 11 models */ 2062306a36Sopenharmony_ci#define VGBS_TABLET_MODE_FLAG_ALT 0x10 2162306a36Sopenharmony_ci/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ 2262306a36Sopenharmony_ci#define VGBS_TABLET_MODE_FLAG 0x40 2362306a36Sopenharmony_ci#define VGBS_DOCK_MODE_FLAG 0x80 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2862306a36Sopenharmony_ciMODULE_AUTHOR("AceLan Kao"); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic const struct acpi_device_id intel_vbtn_ids[] = { 3162306a36Sopenharmony_ci {"INT33D6", 0}, 3262306a36Sopenharmony_ci {"", 0}, 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, intel_vbtn_ids); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* In theory, these are HID usages. */ 3762306a36Sopenharmony_cistatic const struct key_entry intel_vbtn_keymap[] = { 3862306a36Sopenharmony_ci { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */ 3962306a36Sopenharmony_ci { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */ 4062306a36Sopenharmony_ci { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */ 4162306a36Sopenharmony_ci { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */ 4262306a36Sopenharmony_ci { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */ 4362306a36Sopenharmony_ci { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */ 4462306a36Sopenharmony_ci { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */ 4562306a36Sopenharmony_ci { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ 4662306a36Sopenharmony_ci { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ 4762306a36Sopenharmony_ci { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ 4862306a36Sopenharmony_ci { KE_END } 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic const struct key_entry intel_vbtn_switchmap[] = { 5262306a36Sopenharmony_ci /* 5362306a36Sopenharmony_ci * SW_DOCK should only be reported for docking stations, but DSDTs using the 5462306a36Sopenharmony_ci * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set 5562306a36Sopenharmony_ci * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0). 5662306a36Sopenharmony_ci * This causes userspace to think the laptop is docked to a port-replicator 5762306a36Sopenharmony_ci * and to disable suspend-on-lid-close, which is undesirable. 5862306a36Sopenharmony_ci * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting. 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ci { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */ 6162306a36Sopenharmony_ci { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ 6262306a36Sopenharmony_ci { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ 6362306a36Sopenharmony_ci { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ 6462306a36Sopenharmony_ci { KE_END } 6562306a36Sopenharmony_ci}; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistruct intel_vbtn_priv { 6862306a36Sopenharmony_ci struct input_dev *buttons_dev; 6962306a36Sopenharmony_ci struct input_dev *switches_dev; 7062306a36Sopenharmony_ci bool dual_accel; 7162306a36Sopenharmony_ci bool has_buttons; 7262306a36Sopenharmony_ci bool has_switches; 7362306a36Sopenharmony_ci bool wakeup_mode; 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic void detect_tablet_mode(struct device *dev) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct intel_vbtn_priv *priv = dev_get_drvdata(dev); 7962306a36Sopenharmony_ci acpi_handle handle = ACPI_HANDLE(dev); 8062306a36Sopenharmony_ci unsigned long long vgbs; 8162306a36Sopenharmony_ci acpi_status status; 8262306a36Sopenharmony_ci int m; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs); 8562306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 8662306a36Sopenharmony_ci return; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci m = !(vgbs & VGBS_TABLET_MODE_FLAGS); 8962306a36Sopenharmony_ci input_report_switch(priv->switches_dev, SW_TABLET_MODE, m); 9062306a36Sopenharmony_ci m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0; 9162306a36Sopenharmony_ci input_report_switch(priv->switches_dev, SW_DOCK, m); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci input_sync(priv->switches_dev); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci/* 9762306a36Sopenharmony_ci * Note this unconditionally creates the 2 input_dev-s and sets up 9862306a36Sopenharmony_ci * the sparse-keymaps. Only the registration is conditional on 9962306a36Sopenharmony_ci * have_buttons / have_switches. This is done so that the notify 10062306a36Sopenharmony_ci * handler can always call sparse_keymap_entry_from_scancode() 10162306a36Sopenharmony_ci * on the input_dev-s do determine the event type. 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_cistatic int intel_vbtn_input_setup(struct platform_device *device) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); 10662306a36Sopenharmony_ci int ret; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci priv->buttons_dev = devm_input_allocate_device(&device->dev); 10962306a36Sopenharmony_ci if (!priv->buttons_dev) 11062306a36Sopenharmony_ci return -ENOMEM; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL); 11362306a36Sopenharmony_ci if (ret) 11462306a36Sopenharmony_ci return ret; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci priv->buttons_dev->dev.parent = &device->dev; 11762306a36Sopenharmony_ci priv->buttons_dev->name = "Intel Virtual Buttons"; 11862306a36Sopenharmony_ci priv->buttons_dev->id.bustype = BUS_HOST; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (priv->has_buttons) { 12162306a36Sopenharmony_ci ret = input_register_device(priv->buttons_dev); 12262306a36Sopenharmony_ci if (ret) 12362306a36Sopenharmony_ci return ret; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci priv->switches_dev = devm_input_allocate_device(&device->dev); 12762306a36Sopenharmony_ci if (!priv->switches_dev) 12862306a36Sopenharmony_ci return -ENOMEM; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL); 13162306a36Sopenharmony_ci if (ret) 13262306a36Sopenharmony_ci return ret; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci priv->switches_dev->dev.parent = &device->dev; 13562306a36Sopenharmony_ci priv->switches_dev->name = "Intel Virtual Switches"; 13662306a36Sopenharmony_ci priv->switches_dev->id.bustype = BUS_HOST; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (priv->has_switches) { 13962306a36Sopenharmony_ci detect_tablet_mode(&device->dev); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci ret = input_register_device(priv->switches_dev); 14262306a36Sopenharmony_ci if (ret) 14362306a36Sopenharmony_ci return ret; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci return 0; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic void notify_handler(acpi_handle handle, u32 event, void *context) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci struct platform_device *device = context; 15262306a36Sopenharmony_ci struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); 15362306a36Sopenharmony_ci unsigned int val = !(event & 1); /* Even=press, Odd=release */ 15462306a36Sopenharmony_ci const struct key_entry *ke, *ke_rel; 15562306a36Sopenharmony_ci struct input_dev *input_dev; 15662306a36Sopenharmony_ci bool autorelease; 15762306a36Sopenharmony_ci int ret; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) { 16062306a36Sopenharmony_ci if (!priv->has_buttons) { 16162306a36Sopenharmony_ci dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n"); 16262306a36Sopenharmony_ci return; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci input_dev = priv->buttons_dev; 16562306a36Sopenharmony_ci } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) { 16662306a36Sopenharmony_ci if (!priv->has_switches) { 16762306a36Sopenharmony_ci /* See dual_accel_detect.h for more info */ 16862306a36Sopenharmony_ci if (priv->dual_accel) 16962306a36Sopenharmony_ci return; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n"); 17262306a36Sopenharmony_ci ret = input_register_device(priv->switches_dev); 17362306a36Sopenharmony_ci if (ret) 17462306a36Sopenharmony_ci return; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci priv->has_switches = true; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci input_dev = priv->switches_dev; 17962306a36Sopenharmony_ci } else { 18062306a36Sopenharmony_ci dev_dbg(&device->dev, "unknown event index 0x%x\n", event); 18162306a36Sopenharmony_ci return; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (priv->wakeup_mode) { 18562306a36Sopenharmony_ci pm_wakeup_hard_event(&device->dev); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci /* 18862306a36Sopenharmony_ci * Skip reporting an evdev event for button wake events, 18962306a36Sopenharmony_ci * mirroring how the drivers/acpi/button.c code skips this too. 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_ci if (ke->type == KE_KEY) 19262306a36Sopenharmony_ci return; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* 19662306a36Sopenharmony_ci * Even press events are autorelease if there is no corresponding odd 19762306a36Sopenharmony_ci * release event, or if the odd event is KE_IGNORE. 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_ci ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1); 20062306a36Sopenharmony_ci autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci sparse_keymap_report_event(input_dev, event, val, autorelease); 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci/* 20662306a36Sopenharmony_ci * There are several laptops (non 2-in-1) models out there which support VGBS, 20762306a36Sopenharmony_ci * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in 20862306a36Sopenharmony_ci * turn causes userspace (libinput) to suppress events from the builtin 20962306a36Sopenharmony_ci * keyboard and touchpad, making the laptop essentially unusable. 21062306a36Sopenharmony_ci * 21162306a36Sopenharmony_ci * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination 21262306a36Sopenharmony_ci * with libinput, leads to a non-usable system. Where as OTOH many people will 21362306a36Sopenharmony_ci * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow 21462306a36Sopenharmony_ci * list is used here. This list mainly matches on the chassis-type of 2-in-1s. 21562306a36Sopenharmony_ci * 21662306a36Sopenharmony_ci * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report 21762306a36Sopenharmony_ci * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"), 21862306a36Sopenharmony_ci * these are matched on a per model basis, since many normal laptops with a 21962306a36Sopenharmony_ci * possible broken VGBS ACPI-method also use these chassis-types. 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_cistatic const struct dmi_system_id dmi_switches_allow_list[] = { 22262306a36Sopenharmony_ci { 22362306a36Sopenharmony_ci .matches = { 22462306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */), 22562306a36Sopenharmony_ci }, 22662306a36Sopenharmony_ci }, 22762306a36Sopenharmony_ci { 22862306a36Sopenharmony_ci .matches = { 22962306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */), 23062306a36Sopenharmony_ci }, 23162306a36Sopenharmony_ci }, 23262306a36Sopenharmony_ci { 23362306a36Sopenharmony_ci .matches = { 23462306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 23562306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"), 23662306a36Sopenharmony_ci }, 23762306a36Sopenharmony_ci }, 23862306a36Sopenharmony_ci { 23962306a36Sopenharmony_ci .matches = { 24062306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), 24162306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"), 24262306a36Sopenharmony_ci }, 24362306a36Sopenharmony_ci }, 24462306a36Sopenharmony_ci { 24562306a36Sopenharmony_ci .matches = { 24662306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Acer"), 24762306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"), 24862306a36Sopenharmony_ci }, 24962306a36Sopenharmony_ci }, 25062306a36Sopenharmony_ci { 25162306a36Sopenharmony_ci .matches = { 25262306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 25362306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"), 25462306a36Sopenharmony_ci }, 25562306a36Sopenharmony_ci }, 25662306a36Sopenharmony_ci {} /* Array terminator */ 25762306a36Sopenharmony_ci}; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci unsigned long long vgbs; 26262306a36Sopenharmony_ci acpi_status status; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci /* See dual_accel_detect.h for more info */ 26562306a36Sopenharmony_ci if (dual_accel) 26662306a36Sopenharmony_ci return false; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (!dmi_check_system(dmi_switches_allow_list)) 26962306a36Sopenharmony_ci return false; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs); 27262306a36Sopenharmony_ci return ACPI_SUCCESS(status); 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cistatic int intel_vbtn_probe(struct platform_device *device) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci acpi_handle handle = ACPI_HANDLE(&device->dev); 27862306a36Sopenharmony_ci bool dual_accel, has_buttons, has_switches; 27962306a36Sopenharmony_ci struct intel_vbtn_priv *priv; 28062306a36Sopenharmony_ci acpi_status status; 28162306a36Sopenharmony_ci int err; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci dual_accel = dual_accel_detect(); 28462306a36Sopenharmony_ci has_buttons = acpi_has_method(handle, "VBDL"); 28562306a36Sopenharmony_ci has_switches = intel_vbtn_has_switches(handle, dual_accel); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci if (!has_buttons && !has_switches) { 28862306a36Sopenharmony_ci dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n"); 28962306a36Sopenharmony_ci return -ENODEV; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); 29362306a36Sopenharmony_ci if (!priv) 29462306a36Sopenharmony_ci return -ENOMEM; 29562306a36Sopenharmony_ci dev_set_drvdata(&device->dev, priv); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci priv->dual_accel = dual_accel; 29862306a36Sopenharmony_ci priv->has_buttons = has_buttons; 29962306a36Sopenharmony_ci priv->has_switches = has_switches; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci err = intel_vbtn_input_setup(device); 30262306a36Sopenharmony_ci if (err) { 30362306a36Sopenharmony_ci pr_err("Failed to setup Intel Virtual Button\n"); 30462306a36Sopenharmony_ci return err; 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci status = acpi_install_notify_handler(handle, 30862306a36Sopenharmony_ci ACPI_DEVICE_NOTIFY, 30962306a36Sopenharmony_ci notify_handler, 31062306a36Sopenharmony_ci device); 31162306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 31262306a36Sopenharmony_ci return -EBUSY; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (has_buttons) { 31562306a36Sopenharmony_ci status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); 31662306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 31762306a36Sopenharmony_ci dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status); 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci device_init_wakeup(&device->dev, true); 32162306a36Sopenharmony_ci /* 32262306a36Sopenharmony_ci * In order for system wakeup to work, the EC GPE has to be marked as 32362306a36Sopenharmony_ci * a wakeup one, so do that here (this setting will persist, but it has 32462306a36Sopenharmony_ci * no effect until the wakeup mask is set for the EC GPE). 32562306a36Sopenharmony_ci */ 32662306a36Sopenharmony_ci acpi_ec_mark_gpe_for_wake(); 32762306a36Sopenharmony_ci return 0; 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic void intel_vbtn_remove(struct platform_device *device) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci acpi_handle handle = ACPI_HANDLE(&device->dev); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci device_init_wakeup(&device->dev, false); 33562306a36Sopenharmony_ci acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); 33662306a36Sopenharmony_ci} 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_cistatic int intel_vbtn_pm_prepare(struct device *dev) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci if (device_may_wakeup(dev)) { 34162306a36Sopenharmony_ci struct intel_vbtn_priv *priv = dev_get_drvdata(dev); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci priv->wakeup_mode = true; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci return 0; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic void intel_vbtn_pm_complete(struct device *dev) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci struct intel_vbtn_priv *priv = dev_get_drvdata(dev); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci priv->wakeup_mode = false; 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_cistatic int intel_vbtn_pm_resume(struct device *dev) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci struct intel_vbtn_priv *priv = dev_get_drvdata(dev); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci intel_vbtn_pm_complete(dev); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci if (priv->has_switches) 36262306a36Sopenharmony_ci detect_tablet_mode(dev); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci return 0; 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic const struct dev_pm_ops intel_vbtn_pm_ops = { 36862306a36Sopenharmony_ci .prepare = intel_vbtn_pm_prepare, 36962306a36Sopenharmony_ci .complete = intel_vbtn_pm_complete, 37062306a36Sopenharmony_ci .resume = intel_vbtn_pm_resume, 37162306a36Sopenharmony_ci .restore = intel_vbtn_pm_resume, 37262306a36Sopenharmony_ci .thaw = intel_vbtn_pm_resume, 37362306a36Sopenharmony_ci}; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_cistatic struct platform_driver intel_vbtn_pl_driver = { 37662306a36Sopenharmony_ci .driver = { 37762306a36Sopenharmony_ci .name = "intel-vbtn", 37862306a36Sopenharmony_ci .acpi_match_table = intel_vbtn_ids, 37962306a36Sopenharmony_ci .pm = &intel_vbtn_pm_ops, 38062306a36Sopenharmony_ci }, 38162306a36Sopenharmony_ci .probe = intel_vbtn_probe, 38262306a36Sopenharmony_ci .remove_new = intel_vbtn_remove, 38362306a36Sopenharmony_ci}; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic acpi_status __init 38662306a36Sopenharmony_cicheck_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci const struct acpi_device_id *ids = context; 38962306a36Sopenharmony_ci struct acpi_device *dev = acpi_fetch_acpi_dev(handle); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (dev && acpi_match_device_ids(dev, ids) == 0) 39262306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) 39362306a36Sopenharmony_ci dev_info(&dev->dev, 39462306a36Sopenharmony_ci "intel-vbtn: created platform device\n"); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci return AE_OK; 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_cistatic int __init intel_vbtn_init(void) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 40262306a36Sopenharmony_ci ACPI_UINT32_MAX, check_acpi_dev, NULL, 40362306a36Sopenharmony_ci (void *)intel_vbtn_ids, NULL); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci return platform_driver_register(&intel_vbtn_pl_driver); 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_cimodule_init(intel_vbtn_init); 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_cistatic void __exit intel_vbtn_exit(void) 41062306a36Sopenharmony_ci{ 41162306a36Sopenharmony_ci platform_driver_unregister(&intel_vbtn_pl_driver); 41262306a36Sopenharmony_ci} 41362306a36Sopenharmony_cimodule_exit(intel_vbtn_exit); 414