]>
Commit | Line | Data |
---|---|---|
b886d83c | 1 | // SPDX-License-Identifier: GPL-2.0-only |
3dda3b37 BT |
2 | /* |
3 | * Driver for the LID cover switch of the Surface 3 | |
4 | * | |
5 | * Copyright (c) 2016 Red Hat Inc. | |
6 | */ | |
7 | ||
3dda3b37 BT |
8 | |
9 | #include <linux/kernel.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/slab.h> | |
12 | ||
13 | #include <linux/acpi.h> | |
14 | #include <linux/dmi.h> | |
15 | #include <linux/input.h> | |
16 | #include <linux/mutex.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/spi/spi.h> | |
19 | ||
20 | MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); | |
21 | MODULE_DESCRIPTION("Surface 3 platform driver"); | |
22 | MODULE_LICENSE("GPL"); | |
23 | ||
24 | #define ACPI_BUTTON_HID_LID "PNP0C0D" | |
25 | #define SPI_CTL_OBJ_NAME "SPI" | |
26 | #define SPI_TS_OBJ_NAME "NTRG" | |
27 | ||
28 | #define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE" | |
29 | ||
30 | MODULE_ALIAS("wmi:" SURFACE3_LID_GUID); | |
31 | ||
32 | static const struct dmi_system_id surface3_dmi_table[] = { | |
33 | #if defined(CONFIG_X86) | |
34 | { | |
35 | .matches = { | |
36 | DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), | |
37 | DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), | |
38 | }, | |
39 | }, | |
40 | #endif | |
41 | { } | |
42 | }; | |
43 | ||
44 | struct surface3_wmi { | |
45 | struct acpi_device *touchscreen_adev; | |
46 | struct acpi_device *pnp0c0d_adev; | |
47 | struct acpi_hotplug_context hp; | |
48 | struct input_dev *input; | |
49 | }; | |
50 | ||
51 | static struct platform_device *s3_wmi_pdev; | |
52 | ||
53 | static struct surface3_wmi s3_wmi; | |
54 | ||
55 | static DEFINE_MUTEX(s3_wmi_lock); | |
56 | ||
57 | static int s3_wmi_query_block(const char *guid, int instance, int *ret) | |
58 | { | |
83da6b59 | 59 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
3dda3b37 BT |
60 | acpi_status status; |
61 | union acpi_object *obj; | |
83da6b59 | 62 | int error = 0; |
3dda3b37 BT |
63 | |
64 | mutex_lock(&s3_wmi_lock); | |
65 | status = wmi_query_block(guid, instance, &output); | |
66 | ||
67 | obj = output.pointer; | |
68 | ||
69 | if (!obj || obj->type != ACPI_TYPE_INTEGER) { | |
70 | if (obj) { | |
71 | pr_err("query block returned object type: %d - buffer length:%d\n", | |
72 | obj->type, | |
73 | obj->type == ACPI_TYPE_BUFFER ? | |
74 | obj->buffer.length : 0); | |
75 | } | |
83da6b59 AS |
76 | error = -EINVAL; |
77 | goto out_free_unlock; | |
3dda3b37 BT |
78 | } |
79 | *ret = obj->integer.value; | |
83da6b59 | 80 | out_free_unlock: |
3dda3b37 BT |
81 | kfree(obj); |
82 | mutex_unlock(&s3_wmi_lock); | |
83da6b59 | 83 | return error; |
3dda3b37 BT |
84 | } |
85 | ||
86 | static inline int s3_wmi_query_lid(int *ret) | |
87 | { | |
88 | return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret); | |
89 | } | |
90 | ||
91 | static int s3_wmi_send_lid_state(void) | |
92 | { | |
93 | int ret, lid_sw; | |
94 | ||
95 | ret = s3_wmi_query_lid(&lid_sw); | |
96 | if (ret) | |
97 | return ret; | |
98 | ||
99 | input_report_switch(s3_wmi.input, SW_LID, lid_sw); | |
100 | input_sync(s3_wmi.input); | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | static int s3_wmi_hp_notify(struct acpi_device *adev, u32 value) | |
106 | { | |
107 | return s3_wmi_send_lid_state(); | |
108 | } | |
109 | ||
110 | static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, | |
111 | u32 level, | |
112 | void *data, | |
113 | void **return_value) | |
114 | { | |
115 | struct acpi_device *adev, **ts_adev; | |
116 | ||
117 | if (acpi_bus_get_device(handle, &adev)) | |
118 | return AE_OK; | |
119 | ||
120 | ts_adev = data; | |
121 | ||
122 | if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, | |
123 | strlen(SPI_TS_OBJ_NAME))) | |
124 | return AE_OK; | |
125 | ||
126 | if (*ts_adev) { | |
127 | pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME); | |
128 | return AE_OK; | |
129 | } | |
130 | ||
131 | *ts_adev = adev; | |
132 | ||
133 | return AE_OK; | |
134 | } | |
135 | ||
136 | static int s3_wmi_check_platform_device(struct device *dev, void *data) | |
137 | { | |
e95ac457 | 138 | struct acpi_device *adev, *ts_adev = NULL; |
3dda3b37 BT |
139 | acpi_handle handle; |
140 | acpi_status status; | |
141 | ||
142 | /* ignore non ACPI devices */ | |
143 | handle = ACPI_HANDLE(dev); | |
144 | if (!handle || acpi_bus_get_device(handle, &adev)) | |
145 | return 0; | |
146 | ||
147 | /* check for LID ACPI switch */ | |
148 | if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) { | |
149 | s3_wmi.pnp0c0d_adev = adev; | |
150 | return 0; | |
151 | } | |
152 | ||
153 | /* ignore non SPI controllers */ | |
154 | if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME, | |
155 | strlen(SPI_CTL_OBJ_NAME))) | |
156 | return 0; | |
157 | ||
158 | status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, | |
159 | s3_wmi_attach_spi_device, NULL, | |
160 | &ts_adev, NULL); | |
161 | if (ACPI_FAILURE(status)) | |
162 | dev_warn(dev, "failed to enumerate SPI slaves\n"); | |
163 | ||
164 | if (!ts_adev) | |
165 | return 0; | |
166 | ||
167 | s3_wmi.touchscreen_adev = ts_adev; | |
168 | ||
169 | return 0; | |
170 | } | |
171 | ||
172 | static int s3_wmi_create_and_register_input(struct platform_device *pdev) | |
173 | { | |
174 | struct input_dev *input; | |
175 | int error; | |
176 | ||
177 | input = devm_input_allocate_device(&pdev->dev); | |
178 | if (!input) | |
179 | return -ENOMEM; | |
180 | ||
181 | input->name = "Lid Switch"; | |
182 | input->phys = "button/input0"; | |
183 | input->id.bustype = BUS_HOST; | |
184 | input->id.product = 0x0005; | |
185 | ||
186 | input_set_capability(input, EV_SW, SW_LID); | |
187 | ||
188 | error = input_register_device(input); | |
189 | if (error) | |
190 | goto out_err; | |
191 | ||
192 | s3_wmi.input = input; | |
193 | ||
194 | return 0; | |
195 | out_err: | |
196 | input_free_device(s3_wmi.input); | |
197 | return error; | |
198 | } | |
199 | ||
200 | static int __init s3_wmi_probe(struct platform_device *pdev) | |
201 | { | |
202 | int error; | |
203 | ||
204 | if (!dmi_check_system(surface3_dmi_table)) | |
205 | return -ENODEV; | |
206 | ||
207 | memset(&s3_wmi, 0, sizeof(s3_wmi)); | |
208 | ||
209 | bus_for_each_dev(&platform_bus_type, NULL, NULL, | |
210 | s3_wmi_check_platform_device); | |
211 | ||
212 | if (!s3_wmi.touchscreen_adev) | |
213 | return -ENODEV; | |
214 | ||
215 | acpi_bus_trim(s3_wmi.pnp0c0d_adev); | |
216 | ||
217 | error = s3_wmi_create_and_register_input(pdev); | |
218 | if (error) | |
219 | goto restore_acpi_lid; | |
220 | ||
221 | acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp, | |
222 | s3_wmi_hp_notify, NULL); | |
223 | ||
224 | s3_wmi_send_lid_state(); | |
225 | ||
226 | return 0; | |
227 | ||
228 | restore_acpi_lid: | |
229 | acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); | |
230 | return error; | |
231 | } | |
232 | ||
233 | static int s3_wmi_remove(struct platform_device *device) | |
234 | { | |
235 | /* remove the hotplug context from the acpi device */ | |
236 | s3_wmi.touchscreen_adev->hp = NULL; | |
237 | ||
238 | /* reinstall the actual PNPC0C0D LID default handle */ | |
239 | acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); | |
240 | return 0; | |
241 | } | |
242 | ||
44e68616 | 243 | static int __maybe_unused s3_wmi_resume(struct device *dev) |
3dda3b37 BT |
244 | { |
245 | s3_wmi_send_lid_state(); | |
246 | return 0; | |
247 | } | |
3dda3b37 BT |
248 | static SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume); |
249 | ||
250 | static struct platform_driver s3_wmi_driver = { | |
251 | .driver = { | |
252 | .name = "surface3-wmi", | |
253 | .pm = &s3_wmi_pm, | |
254 | }, | |
255 | .remove = s3_wmi_remove, | |
256 | }; | |
257 | ||
258 | static int __init s3_wmi_init(void) | |
259 | { | |
260 | int error; | |
261 | ||
262 | s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1); | |
263 | if (!s3_wmi_pdev) | |
264 | return -ENOMEM; | |
265 | ||
266 | error = platform_device_add(s3_wmi_pdev); | |
267 | if (error) | |
268 | goto err_device_put; | |
269 | ||
270 | error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe); | |
271 | if (error) | |
272 | goto err_device_del; | |
273 | ||
274 | pr_info("Surface 3 WMI Extras loaded\n"); | |
275 | return 0; | |
276 | ||
277 | err_device_del: | |
278 | platform_device_del(s3_wmi_pdev); | |
279 | err_device_put: | |
280 | platform_device_put(s3_wmi_pdev); | |
281 | return error; | |
282 | } | |
283 | ||
284 | static void __exit s3_wmi_exit(void) | |
285 | { | |
286 | platform_device_unregister(s3_wmi_pdev); | |
287 | platform_driver_unregister(&s3_wmi_driver); | |
288 | } | |
289 | ||
290 | module_init(s3_wmi_init); | |
291 | module_exit(s3_wmi_exit); |