Commit | Line | Data |
---|---|---|
00f49ee7 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
332e0812 AK |
2 | /* |
3 | * Intel Virtual Button driver for Windows 8.1+ | |
4 | * | |
5 | * Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com> | |
6 | * Copyright (C) 2016 Alex Hung <alex.hung@canonical.com> | |
332e0812 AK |
7 | */ |
8 | ||
bd1b27e2 AS |
9 | #include <linux/acpi.h> |
10 | #include <linux/input.h> | |
11 | #include <linux/input/sparse-keymap.h> | |
332e0812 AK |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> | |
332e0812 | 14 | #include <linux/platform_device.h> |
91f9e850 | 15 | #include <linux/suspend.h> |
332e0812 | 16 | |
30323fb6 MM |
17 | /* When NOT in tablet mode, VGBS returns with the flag 0x40 */ |
18 | #define TABLET_MODE_FLAG 0x40 | |
19 | ||
332e0812 AK |
20 | MODULE_LICENSE("GPL"); |
21 | MODULE_AUTHOR("AceLan Kao"); | |
22 | ||
23 | static const struct acpi_device_id intel_vbtn_ids[] = { | |
24 | {"INT33D6", 0}, | |
25 | {"", 0}, | |
26 | }; | |
27 | ||
28 | /* In theory, these are HID usages. */ | |
29 | static const struct key_entry intel_vbtn_keymap[] = { | |
c801603e ML |
30 | { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */ |
31 | { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */ | |
9678d0ef SB |
32 | { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */ |
33 | { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */ | |
8d9e2997 MM |
34 | { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */ |
35 | { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */ | |
36 | { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */ | |
37 | { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ | |
3d5d95d3 SB |
38 | { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ |
39 | { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ | |
1c828496 SB |
40 | { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ |
41 | { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ | |
332e0812 AK |
42 | { KE_END }, |
43 | }; | |
44 | ||
45 | struct intel_vbtn_priv { | |
46 | struct input_dev *input_dev; | |
91f9e850 | 47 | bool wakeup_mode; |
332e0812 AK |
48 | }; |
49 | ||
50 | static int intel_vbtn_input_setup(struct platform_device *device) | |
51 | { | |
52 | struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); | |
53 | int ret; | |
54 | ||
bb9ad484 | 55 | priv->input_dev = devm_input_allocate_device(&device->dev); |
332e0812 AK |
56 | if (!priv->input_dev) |
57 | return -ENOMEM; | |
58 | ||
59 | ret = sparse_keymap_setup(priv->input_dev, intel_vbtn_keymap, NULL); | |
60 | if (ret) | |
bb9ad484 | 61 | return ret; |
332e0812 AK |
62 | |
63 | priv->input_dev->dev.parent = &device->dev; | |
64 | priv->input_dev->name = "Intel Virtual Button driver"; | |
65 | priv->input_dev->id.bustype = BUS_HOST; | |
66 | ||
bb9ad484 | 67 | return input_register_device(priv->input_dev); |
332e0812 AK |
68 | } |
69 | ||
70 | static void notify_handler(acpi_handle handle, u32 event, void *context) | |
71 | { | |
72 | struct platform_device *device = context; | |
73 | struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); | |
1c3fdf12 | 74 | unsigned int val = !(event & 1); /* Even=press, Odd=release */ |
95f38fd4 SB |
75 | const struct key_entry *ke_rel; |
76 | bool autorelease; | |
332e0812 | 77 | |
91f9e850 RW |
78 | if (priv->wakeup_mode) { |
79 | if (sparse_keymap_entry_from_scancode(priv->input_dev, event)) { | |
80 | pm_wakeup_hard_event(&device->dev); | |
81 | return; | |
82 | } | |
1c3fdf12 DHV |
83 | goto out_unknown; |
84 | } | |
95f38fd4 | 85 | |
1c3fdf12 DHV |
86 | /* |
87 | * Even press events are autorelease if there is no corresponding odd | |
88 | * release event, or if the odd event is KE_IGNORE. | |
89 | */ | |
90 | ke_rel = sparse_keymap_entry_from_scancode(priv->input_dev, event | 1); | |
91 | autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); | |
95f38fd4 | 92 | |
1c3fdf12 DHV |
93 | if (sparse_keymap_report_event(priv->input_dev, event, val, autorelease)) |
94 | return; | |
95 | ||
96 | out_unknown: | |
a9c37b74 | 97 | dev_dbg(&device->dev, "unknown event index 0x%x\n", event); |
332e0812 AK |
98 | } |
99 | ||
100 | static int intel_vbtn_probe(struct platform_device *device) | |
101 | { | |
30323fb6 | 102 | struct acpi_buffer vgbs_output = { ACPI_ALLOCATE_BUFFER, NULL }; |
332e0812 AK |
103 | acpi_handle handle = ACPI_HANDLE(&device->dev); |
104 | struct intel_vbtn_priv *priv; | |
105 | acpi_status status; | |
106 | int err; | |
107 | ||
108 | status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); | |
3526ecad | 109 | if (ACPI_FAILURE(status)) { |
332e0812 AK |
110 | dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n"); |
111 | return -ENODEV; | |
112 | } | |
113 | ||
114 | priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); | |
115 | if (!priv) | |
116 | return -ENOMEM; | |
117 | dev_set_drvdata(&device->dev, priv); | |
118 | ||
119 | err = intel_vbtn_input_setup(device); | |
120 | if (err) { | |
121 | pr_err("Failed to setup Intel Virtual Button\n"); | |
122 | return err; | |
123 | } | |
124 | ||
30323fb6 MM |
125 | /* |
126 | * VGBS being present and returning something means we have | |
127 | * a tablet mode switch. | |
128 | */ | |
129 | status = acpi_evaluate_object(handle, "VGBS", NULL, &vgbs_output); | |
130 | if (ACPI_SUCCESS(status)) { | |
131 | union acpi_object *obj = vgbs_output.pointer; | |
132 | ||
133 | if (obj && obj->type == ACPI_TYPE_INTEGER) { | |
134 | int m = !(obj->integer.value & TABLET_MODE_FLAG); | |
135 | ||
136 | input_report_switch(priv->input_dev, SW_TABLET_MODE, m); | |
137 | } | |
138 | } | |
139 | ||
140 | kfree(vgbs_output.pointer); | |
141 | ||
332e0812 AK |
142 | status = acpi_install_notify_handler(handle, |
143 | ACPI_DEVICE_NOTIFY, | |
144 | notify_handler, | |
145 | device); | |
bb9ad484 AL |
146 | if (ACPI_FAILURE(status)) |
147 | return -EBUSY; | |
332e0812 | 148 | |
91f9e850 | 149 | device_init_wakeup(&device->dev, true); |
332e0812 | 150 | return 0; |
332e0812 AK |
151 | } |
152 | ||
153 | static int intel_vbtn_remove(struct platform_device *device) | |
154 | { | |
155 | acpi_handle handle = ACPI_HANDLE(&device->dev); | |
156 | ||
332e0812 AK |
157 | acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); |
158 | ||
159 | /* | |
160 | * Even if we failed to shut off the event stream, we can still | |
161 | * safely detach from the device. | |
162 | */ | |
163 | return 0; | |
164 | } | |
165 | ||
91f9e850 RW |
166 | static int intel_vbtn_pm_prepare(struct device *dev) |
167 | { | |
168 | struct intel_vbtn_priv *priv = dev_get_drvdata(dev); | |
169 | ||
170 | priv->wakeup_mode = true; | |
171 | return 0; | |
172 | } | |
173 | ||
174 | static int intel_vbtn_pm_resume(struct device *dev) | |
175 | { | |
176 | struct intel_vbtn_priv *priv = dev_get_drvdata(dev); | |
177 | ||
178 | priv->wakeup_mode = false; | |
179 | return 0; | |
180 | } | |
181 | ||
182 | static const struct dev_pm_ops intel_vbtn_pm_ops = { | |
183 | .prepare = intel_vbtn_pm_prepare, | |
184 | .resume = intel_vbtn_pm_resume, | |
185 | .restore = intel_vbtn_pm_resume, | |
186 | .thaw = intel_vbtn_pm_resume, | |
187 | }; | |
188 | ||
332e0812 AK |
189 | static struct platform_driver intel_vbtn_pl_driver = { |
190 | .driver = { | |
191 | .name = "intel-vbtn", | |
192 | .acpi_match_table = intel_vbtn_ids, | |
91f9e850 | 193 | .pm = &intel_vbtn_pm_ops, |
332e0812 AK |
194 | }, |
195 | .probe = intel_vbtn_probe, | |
196 | .remove = intel_vbtn_remove, | |
197 | }; | |
198 | MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids); | |
199 | ||
200 | static acpi_status __init | |
201 | check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) | |
202 | { | |
203 | const struct acpi_device_id *ids = context; | |
204 | struct acpi_device *dev; | |
205 | ||
206 | if (acpi_bus_get_device(handle, &dev) != 0) | |
207 | return AE_OK; | |
208 | ||
209 | if (acpi_match_device_ids(dev, ids) == 0) | |
1571875b | 210 | if (acpi_create_platform_device(dev, NULL)) |
332e0812 AK |
211 | dev_info(&dev->dev, |
212 | "intel-vbtn: created platform device\n"); | |
213 | ||
214 | return AE_OK; | |
215 | } | |
216 | ||
217 | static int __init intel_vbtn_init(void) | |
218 | { | |
219 | acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, | |
220 | ACPI_UINT32_MAX, check_acpi_dev, NULL, | |
221 | (void *)intel_vbtn_ids, NULL); | |
222 | ||
223 | return platform_driver_register(&intel_vbtn_pl_driver); | |
224 | } | |
225 | module_init(intel_vbtn_init); | |
226 | ||
227 | static void __exit intel_vbtn_exit(void) | |
228 | { | |
229 | platform_driver_unregister(&intel_vbtn_pl_driver); | |
230 | } | |
231 | module_exit(intel_vbtn_exit); |