1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2009 Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com> |
4 | */ |
5 | |
6 | |
7 | #include <linux/init.h> |
8 | #include <linux/module.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/workqueue.h> |
11 | #include <linux/acpi.h> |
12 | #include <linux/backlight.h> |
13 | #include <linux/input.h> |
14 | #include <linux/rfkill.h> |
15 | |
16 | MODULE_LICENSE("GPL" ); |
17 | |
18 | struct cmpc_accel { |
19 | int sensitivity; |
20 | int g_select; |
21 | int inputdev_state; |
22 | }; |
23 | |
24 | #define CMPC_ACCEL_DEV_STATE_CLOSED 0 |
25 | #define CMPC_ACCEL_DEV_STATE_OPEN 1 |
26 | |
27 | #define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 |
28 | #define CMPC_ACCEL_G_SELECT_DEFAULT 0 |
29 | |
30 | #define CMPC_ACCEL_HID "ACCE0000" |
31 | #define CMPC_ACCEL_HID_V4 "ACCE0001" |
32 | #define CMPC_TABLET_HID "TBLT0000" |
33 | #define CMPC_IPML_HID "IPML200" |
34 | #define CMPC_KEYS_HID "FNBT0000" |
35 | |
36 | /* |
37 | * Generic input device code. |
38 | */ |
39 | |
40 | typedef void (*input_device_init)(struct input_dev *dev); |
41 | |
42 | static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, |
43 | input_device_init idev_init) |
44 | { |
45 | struct input_dev *inputdev; |
46 | int error; |
47 | |
48 | inputdev = input_allocate_device(); |
49 | if (!inputdev) |
50 | return -ENOMEM; |
51 | inputdev->name = name; |
52 | inputdev->dev.parent = &acpi->dev; |
53 | idev_init(inputdev); |
54 | error = input_register_device(inputdev); |
55 | if (error) { |
56 | input_free_device(dev: inputdev); |
57 | return error; |
58 | } |
59 | dev_set_drvdata(dev: &acpi->dev, data: inputdev); |
60 | return 0; |
61 | } |
62 | |
63 | static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) |
64 | { |
65 | struct input_dev *inputdev = dev_get_drvdata(dev: &acpi->dev); |
66 | input_unregister_device(inputdev); |
67 | return 0; |
68 | } |
69 | |
70 | /* |
71 | * Accelerometer code for Classmate V4 |
72 | */ |
73 | static acpi_status cmpc_start_accel_v4(acpi_handle handle) |
74 | { |
75 | union acpi_object param[4]; |
76 | struct acpi_object_list input; |
77 | acpi_status status; |
78 | |
79 | param[0].type = ACPI_TYPE_INTEGER; |
80 | param[0].integer.value = 0x3; |
81 | param[1].type = ACPI_TYPE_INTEGER; |
82 | param[1].integer.value = 0; |
83 | param[2].type = ACPI_TYPE_INTEGER; |
84 | param[2].integer.value = 0; |
85 | param[3].type = ACPI_TYPE_INTEGER; |
86 | param[3].integer.value = 0; |
87 | input.count = 4; |
88 | input.pointer = param; |
89 | status = acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
90 | return status; |
91 | } |
92 | |
93 | static acpi_status cmpc_stop_accel_v4(acpi_handle handle) |
94 | { |
95 | union acpi_object param[4]; |
96 | struct acpi_object_list input; |
97 | acpi_status status; |
98 | |
99 | param[0].type = ACPI_TYPE_INTEGER; |
100 | param[0].integer.value = 0x4; |
101 | param[1].type = ACPI_TYPE_INTEGER; |
102 | param[1].integer.value = 0; |
103 | param[2].type = ACPI_TYPE_INTEGER; |
104 | param[2].integer.value = 0; |
105 | param[3].type = ACPI_TYPE_INTEGER; |
106 | param[3].integer.value = 0; |
107 | input.count = 4; |
108 | input.pointer = param; |
109 | status = acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
110 | return status; |
111 | } |
112 | |
113 | static acpi_status cmpc_accel_set_sensitivity_v4(acpi_handle handle, int val) |
114 | { |
115 | union acpi_object param[4]; |
116 | struct acpi_object_list input; |
117 | |
118 | param[0].type = ACPI_TYPE_INTEGER; |
119 | param[0].integer.value = 0x02; |
120 | param[1].type = ACPI_TYPE_INTEGER; |
121 | param[1].integer.value = val; |
122 | param[2].type = ACPI_TYPE_INTEGER; |
123 | param[2].integer.value = 0; |
124 | param[3].type = ACPI_TYPE_INTEGER; |
125 | param[3].integer.value = 0; |
126 | input.count = 4; |
127 | input.pointer = param; |
128 | return acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
129 | } |
130 | |
131 | static acpi_status cmpc_accel_set_g_select_v4(acpi_handle handle, int val) |
132 | { |
133 | union acpi_object param[4]; |
134 | struct acpi_object_list input; |
135 | |
136 | param[0].type = ACPI_TYPE_INTEGER; |
137 | param[0].integer.value = 0x05; |
138 | param[1].type = ACPI_TYPE_INTEGER; |
139 | param[1].integer.value = val; |
140 | param[2].type = ACPI_TYPE_INTEGER; |
141 | param[2].integer.value = 0; |
142 | param[3].type = ACPI_TYPE_INTEGER; |
143 | param[3].integer.value = 0; |
144 | input.count = 4; |
145 | input.pointer = param; |
146 | return acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
147 | } |
148 | |
149 | static acpi_status cmpc_get_accel_v4(acpi_handle handle, |
150 | int16_t *x, |
151 | int16_t *y, |
152 | int16_t *z) |
153 | { |
154 | union acpi_object param[4]; |
155 | struct acpi_object_list input; |
156 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
157 | int16_t *locs; |
158 | acpi_status status; |
159 | |
160 | param[0].type = ACPI_TYPE_INTEGER; |
161 | param[0].integer.value = 0x01; |
162 | param[1].type = ACPI_TYPE_INTEGER; |
163 | param[1].integer.value = 0; |
164 | param[2].type = ACPI_TYPE_INTEGER; |
165 | param[2].integer.value = 0; |
166 | param[3].type = ACPI_TYPE_INTEGER; |
167 | param[3].integer.value = 0; |
168 | input.count = 4; |
169 | input.pointer = param; |
170 | status = acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, return_object_buffer: &output); |
171 | if (ACPI_SUCCESS(status)) { |
172 | union acpi_object *obj; |
173 | obj = output.pointer; |
174 | locs = (int16_t *) obj->buffer.pointer; |
175 | *x = locs[0]; |
176 | *y = locs[1]; |
177 | *z = locs[2]; |
178 | kfree(objp: output.pointer); |
179 | } |
180 | return status; |
181 | } |
182 | |
183 | static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event) |
184 | { |
185 | if (event == 0x81) { |
186 | int16_t x, y, z; |
187 | acpi_status status; |
188 | |
189 | status = cmpc_get_accel_v4(handle: dev->handle, x: &x, y: &y, z: &z); |
190 | if (ACPI_SUCCESS(status)) { |
191 | struct input_dev *inputdev = dev_get_drvdata(dev: &dev->dev); |
192 | |
193 | input_report_abs(dev: inputdev, ABS_X, value: x); |
194 | input_report_abs(dev: inputdev, ABS_Y, value: y); |
195 | input_report_abs(dev: inputdev, ABS_Z, value: z); |
196 | input_sync(dev: inputdev); |
197 | } |
198 | } |
199 | } |
200 | |
201 | static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev, |
202 | struct device_attribute *attr, |
203 | char *buf) |
204 | { |
205 | struct acpi_device *acpi; |
206 | struct input_dev *inputdev; |
207 | struct cmpc_accel *accel; |
208 | |
209 | acpi = to_acpi_device(dev); |
210 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
211 | accel = dev_get_drvdata(dev: &inputdev->dev); |
212 | |
213 | return sprintf(buf, fmt: "%d\n" , accel->sensitivity); |
214 | } |
215 | |
216 | static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev, |
217 | struct device_attribute *attr, |
218 | const char *buf, size_t count) |
219 | { |
220 | struct acpi_device *acpi; |
221 | struct input_dev *inputdev; |
222 | struct cmpc_accel *accel; |
223 | unsigned long sensitivity; |
224 | int r; |
225 | |
226 | acpi = to_acpi_device(dev); |
227 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
228 | accel = dev_get_drvdata(dev: &inputdev->dev); |
229 | |
230 | r = kstrtoul(s: buf, base: 0, res: &sensitivity); |
231 | if (r) |
232 | return r; |
233 | |
234 | /* sensitivity must be between 1 and 127 */ |
235 | if (sensitivity < 1 || sensitivity > 127) |
236 | return -EINVAL; |
237 | |
238 | accel->sensitivity = sensitivity; |
239 | cmpc_accel_set_sensitivity_v4(handle: acpi->handle, val: sensitivity); |
240 | |
241 | return strnlen(p: buf, maxlen: count); |
242 | } |
243 | |
244 | static struct device_attribute cmpc_accel_sensitivity_attr_v4 = { |
245 | .attr = { .name = "sensitivity" , .mode = 0660 }, |
246 | .show = cmpc_accel_sensitivity_show_v4, |
247 | .store = cmpc_accel_sensitivity_store_v4 |
248 | }; |
249 | |
250 | static ssize_t cmpc_accel_g_select_show_v4(struct device *dev, |
251 | struct device_attribute *attr, |
252 | char *buf) |
253 | { |
254 | struct acpi_device *acpi; |
255 | struct input_dev *inputdev; |
256 | struct cmpc_accel *accel; |
257 | |
258 | acpi = to_acpi_device(dev); |
259 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
260 | accel = dev_get_drvdata(dev: &inputdev->dev); |
261 | |
262 | return sprintf(buf, fmt: "%d\n" , accel->g_select); |
263 | } |
264 | |
265 | static ssize_t cmpc_accel_g_select_store_v4(struct device *dev, |
266 | struct device_attribute *attr, |
267 | const char *buf, size_t count) |
268 | { |
269 | struct acpi_device *acpi; |
270 | struct input_dev *inputdev; |
271 | struct cmpc_accel *accel; |
272 | unsigned long g_select; |
273 | int r; |
274 | |
275 | acpi = to_acpi_device(dev); |
276 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
277 | accel = dev_get_drvdata(dev: &inputdev->dev); |
278 | |
279 | r = kstrtoul(s: buf, base: 0, res: &g_select); |
280 | if (r) |
281 | return r; |
282 | |
283 | /* 0 means 1.5g, 1 means 6g, everything else is wrong */ |
284 | if (g_select != 0 && g_select != 1) |
285 | return -EINVAL; |
286 | |
287 | accel->g_select = g_select; |
288 | cmpc_accel_set_g_select_v4(handle: acpi->handle, val: g_select); |
289 | |
290 | return strnlen(p: buf, maxlen: count); |
291 | } |
292 | |
293 | static struct device_attribute cmpc_accel_g_select_attr_v4 = { |
294 | .attr = { .name = "g_select" , .mode = 0660 }, |
295 | .show = cmpc_accel_g_select_show_v4, |
296 | .store = cmpc_accel_g_select_store_v4 |
297 | }; |
298 | |
299 | static int cmpc_accel_open_v4(struct input_dev *input) |
300 | { |
301 | struct acpi_device *acpi; |
302 | struct cmpc_accel *accel; |
303 | |
304 | acpi = to_acpi_device(input->dev.parent); |
305 | accel = dev_get_drvdata(dev: &input->dev); |
306 | |
307 | cmpc_accel_set_sensitivity_v4(handle: acpi->handle, val: accel->sensitivity); |
308 | cmpc_accel_set_g_select_v4(handle: acpi->handle, val: accel->g_select); |
309 | |
310 | if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) { |
311 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN; |
312 | return 0; |
313 | } |
314 | return -EIO; |
315 | } |
316 | |
317 | static void cmpc_accel_close_v4(struct input_dev *input) |
318 | { |
319 | struct acpi_device *acpi; |
320 | struct cmpc_accel *accel; |
321 | |
322 | acpi = to_acpi_device(input->dev.parent); |
323 | accel = dev_get_drvdata(dev: &input->dev); |
324 | |
325 | cmpc_stop_accel_v4(handle: acpi->handle); |
326 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; |
327 | } |
328 | |
329 | static void cmpc_accel_idev_init_v4(struct input_dev *inputdev) |
330 | { |
331 | set_bit(EV_ABS, addr: inputdev->evbit); |
332 | input_set_abs_params(dev: inputdev, ABS_X, min: -255, max: 255, fuzz: 16, flat: 0); |
333 | input_set_abs_params(dev: inputdev, ABS_Y, min: -255, max: 255, fuzz: 16, flat: 0); |
334 | input_set_abs_params(dev: inputdev, ABS_Z, min: -255, max: 255, fuzz: 16, flat: 0); |
335 | inputdev->open = cmpc_accel_open_v4; |
336 | inputdev->close = cmpc_accel_close_v4; |
337 | } |
338 | |
339 | #ifdef CONFIG_PM_SLEEP |
340 | static int cmpc_accel_suspend_v4(struct device *dev) |
341 | { |
342 | struct input_dev *inputdev; |
343 | struct cmpc_accel *accel; |
344 | |
345 | inputdev = dev_get_drvdata(dev); |
346 | accel = dev_get_drvdata(dev: &inputdev->dev); |
347 | |
348 | if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) |
349 | return cmpc_stop_accel_v4(to_acpi_device(dev)->handle); |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | static int cmpc_accel_resume_v4(struct device *dev) |
355 | { |
356 | struct input_dev *inputdev; |
357 | struct cmpc_accel *accel; |
358 | |
359 | inputdev = dev_get_drvdata(dev); |
360 | accel = dev_get_drvdata(dev: &inputdev->dev); |
361 | |
362 | if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) { |
363 | cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle, |
364 | val: accel->sensitivity); |
365 | cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle, |
366 | val: accel->g_select); |
367 | |
368 | if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle))) |
369 | return -EIO; |
370 | } |
371 | |
372 | return 0; |
373 | } |
374 | #endif |
375 | |
376 | static int cmpc_accel_add_v4(struct acpi_device *acpi) |
377 | { |
378 | int error; |
379 | struct input_dev *inputdev; |
380 | struct cmpc_accel *accel; |
381 | |
382 | accel = kmalloc(size: sizeof(*accel), GFP_KERNEL); |
383 | if (!accel) |
384 | return -ENOMEM; |
385 | |
386 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; |
387 | |
388 | accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; |
389 | cmpc_accel_set_sensitivity_v4(handle: acpi->handle, val: accel->sensitivity); |
390 | |
391 | error = device_create_file(device: &acpi->dev, entry: &cmpc_accel_sensitivity_attr_v4); |
392 | if (error) |
393 | goto failed_sensitivity; |
394 | |
395 | accel->g_select = CMPC_ACCEL_G_SELECT_DEFAULT; |
396 | cmpc_accel_set_g_select_v4(handle: acpi->handle, val: accel->g_select); |
397 | |
398 | error = device_create_file(device: &acpi->dev, entry: &cmpc_accel_g_select_attr_v4); |
399 | if (error) |
400 | goto failed_g_select; |
401 | |
402 | error = cmpc_add_acpi_notify_device(acpi, name: "cmpc_accel_v4" , |
403 | idev_init: cmpc_accel_idev_init_v4); |
404 | if (error) |
405 | goto failed_input; |
406 | |
407 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
408 | dev_set_drvdata(dev: &inputdev->dev, data: accel); |
409 | |
410 | return 0; |
411 | |
412 | failed_input: |
413 | device_remove_file(dev: &acpi->dev, attr: &cmpc_accel_g_select_attr_v4); |
414 | failed_g_select: |
415 | device_remove_file(dev: &acpi->dev, attr: &cmpc_accel_sensitivity_attr_v4); |
416 | failed_sensitivity: |
417 | kfree(objp: accel); |
418 | return error; |
419 | } |
420 | |
421 | static void cmpc_accel_remove_v4(struct acpi_device *acpi) |
422 | { |
423 | device_remove_file(dev: &acpi->dev, attr: &cmpc_accel_sensitivity_attr_v4); |
424 | device_remove_file(dev: &acpi->dev, attr: &cmpc_accel_g_select_attr_v4); |
425 | cmpc_remove_acpi_notify_device(acpi); |
426 | } |
427 | |
428 | static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4, |
429 | cmpc_accel_resume_v4); |
430 | |
431 | static const struct acpi_device_id cmpc_accel_device_ids_v4[] = { |
432 | {CMPC_ACCEL_HID_V4, 0}, |
433 | {"" , 0} |
434 | }; |
435 | |
436 | static struct acpi_driver cmpc_accel_acpi_driver_v4 = { |
437 | .owner = THIS_MODULE, |
438 | .name = "cmpc_accel_v4" , |
439 | .class = "cmpc_accel_v4" , |
440 | .ids = cmpc_accel_device_ids_v4, |
441 | .ops = { |
442 | .add = cmpc_accel_add_v4, |
443 | .remove = cmpc_accel_remove_v4, |
444 | .notify = cmpc_accel_handler_v4, |
445 | }, |
446 | .drv.pm = &cmpc_accel_pm, |
447 | }; |
448 | |
449 | |
450 | /* |
451 | * Accelerometer code for Classmate versions prior to V4 |
452 | */ |
453 | static acpi_status cmpc_start_accel(acpi_handle handle) |
454 | { |
455 | union acpi_object param[2]; |
456 | struct acpi_object_list input; |
457 | acpi_status status; |
458 | |
459 | param[0].type = ACPI_TYPE_INTEGER; |
460 | param[0].integer.value = 0x3; |
461 | param[1].type = ACPI_TYPE_INTEGER; |
462 | input.count = 2; |
463 | input.pointer = param; |
464 | status = acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
465 | return status; |
466 | } |
467 | |
468 | static acpi_status cmpc_stop_accel(acpi_handle handle) |
469 | { |
470 | union acpi_object param[2]; |
471 | struct acpi_object_list input; |
472 | acpi_status status; |
473 | |
474 | param[0].type = ACPI_TYPE_INTEGER; |
475 | param[0].integer.value = 0x4; |
476 | param[1].type = ACPI_TYPE_INTEGER; |
477 | input.count = 2; |
478 | input.pointer = param; |
479 | status = acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
480 | return status; |
481 | } |
482 | |
483 | static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val) |
484 | { |
485 | union acpi_object param[2]; |
486 | struct acpi_object_list input; |
487 | |
488 | param[0].type = ACPI_TYPE_INTEGER; |
489 | param[0].integer.value = 0x02; |
490 | param[1].type = ACPI_TYPE_INTEGER; |
491 | param[1].integer.value = val; |
492 | input.count = 2; |
493 | input.pointer = param; |
494 | return acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, NULL); |
495 | } |
496 | |
497 | static acpi_status cmpc_get_accel(acpi_handle handle, |
498 | unsigned char *x, |
499 | unsigned char *y, |
500 | unsigned char *z) |
501 | { |
502 | union acpi_object param[2]; |
503 | struct acpi_object_list input; |
504 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
505 | unsigned char *locs; |
506 | acpi_status status; |
507 | |
508 | param[0].type = ACPI_TYPE_INTEGER; |
509 | param[0].integer.value = 0x01; |
510 | param[1].type = ACPI_TYPE_INTEGER; |
511 | input.count = 2; |
512 | input.pointer = param; |
513 | status = acpi_evaluate_object(object: handle, pathname: "ACMD" , parameter_objects: &input, return_object_buffer: &output); |
514 | if (ACPI_SUCCESS(status)) { |
515 | union acpi_object *obj; |
516 | obj = output.pointer; |
517 | locs = obj->buffer.pointer; |
518 | *x = locs[0]; |
519 | *y = locs[1]; |
520 | *z = locs[2]; |
521 | kfree(objp: output.pointer); |
522 | } |
523 | return status; |
524 | } |
525 | |
526 | static void cmpc_accel_handler(struct acpi_device *dev, u32 event) |
527 | { |
528 | if (event == 0x81) { |
529 | unsigned char x, y, z; |
530 | acpi_status status; |
531 | |
532 | status = cmpc_get_accel(handle: dev->handle, x: &x, y: &y, z: &z); |
533 | if (ACPI_SUCCESS(status)) { |
534 | struct input_dev *inputdev = dev_get_drvdata(dev: &dev->dev); |
535 | |
536 | input_report_abs(dev: inputdev, ABS_X, value: x); |
537 | input_report_abs(dev: inputdev, ABS_Y, value: y); |
538 | input_report_abs(dev: inputdev, ABS_Z, value: z); |
539 | input_sync(dev: inputdev); |
540 | } |
541 | } |
542 | } |
543 | |
544 | static ssize_t cmpc_accel_sensitivity_show(struct device *dev, |
545 | struct device_attribute *attr, |
546 | char *buf) |
547 | { |
548 | struct acpi_device *acpi; |
549 | struct input_dev *inputdev; |
550 | struct cmpc_accel *accel; |
551 | |
552 | acpi = to_acpi_device(dev); |
553 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
554 | accel = dev_get_drvdata(dev: &inputdev->dev); |
555 | |
556 | return sprintf(buf, fmt: "%d\n" , accel->sensitivity); |
557 | } |
558 | |
559 | static ssize_t cmpc_accel_sensitivity_store(struct device *dev, |
560 | struct device_attribute *attr, |
561 | const char *buf, size_t count) |
562 | { |
563 | struct acpi_device *acpi; |
564 | struct input_dev *inputdev; |
565 | struct cmpc_accel *accel; |
566 | unsigned long sensitivity; |
567 | int r; |
568 | |
569 | acpi = to_acpi_device(dev); |
570 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
571 | accel = dev_get_drvdata(dev: &inputdev->dev); |
572 | |
573 | r = kstrtoul(s: buf, base: 0, res: &sensitivity); |
574 | if (r) |
575 | return r; |
576 | |
577 | accel->sensitivity = sensitivity; |
578 | cmpc_accel_set_sensitivity(handle: acpi->handle, val: sensitivity); |
579 | |
580 | return strnlen(p: buf, maxlen: count); |
581 | } |
582 | |
583 | static struct device_attribute cmpc_accel_sensitivity_attr = { |
584 | .attr = { .name = "sensitivity" , .mode = 0660 }, |
585 | .show = cmpc_accel_sensitivity_show, |
586 | .store = cmpc_accel_sensitivity_store |
587 | }; |
588 | |
589 | static int cmpc_accel_open(struct input_dev *input) |
590 | { |
591 | struct acpi_device *acpi; |
592 | |
593 | acpi = to_acpi_device(input->dev.parent); |
594 | if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle))) |
595 | return 0; |
596 | return -EIO; |
597 | } |
598 | |
599 | static void cmpc_accel_close(struct input_dev *input) |
600 | { |
601 | struct acpi_device *acpi; |
602 | |
603 | acpi = to_acpi_device(input->dev.parent); |
604 | cmpc_stop_accel(handle: acpi->handle); |
605 | } |
606 | |
607 | static void cmpc_accel_idev_init(struct input_dev *inputdev) |
608 | { |
609 | set_bit(EV_ABS, addr: inputdev->evbit); |
610 | input_set_abs_params(dev: inputdev, ABS_X, min: 0, max: 255, fuzz: 8, flat: 0); |
611 | input_set_abs_params(dev: inputdev, ABS_Y, min: 0, max: 255, fuzz: 8, flat: 0); |
612 | input_set_abs_params(dev: inputdev, ABS_Z, min: 0, max: 255, fuzz: 8, flat: 0); |
613 | inputdev->open = cmpc_accel_open; |
614 | inputdev->close = cmpc_accel_close; |
615 | } |
616 | |
617 | static int cmpc_accel_add(struct acpi_device *acpi) |
618 | { |
619 | int error; |
620 | struct input_dev *inputdev; |
621 | struct cmpc_accel *accel; |
622 | |
623 | accel = kmalloc(size: sizeof(*accel), GFP_KERNEL); |
624 | if (!accel) |
625 | return -ENOMEM; |
626 | |
627 | accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; |
628 | cmpc_accel_set_sensitivity(handle: acpi->handle, val: accel->sensitivity); |
629 | |
630 | error = device_create_file(device: &acpi->dev, entry: &cmpc_accel_sensitivity_attr); |
631 | if (error) |
632 | goto failed_file; |
633 | |
634 | error = cmpc_add_acpi_notify_device(acpi, name: "cmpc_accel" , |
635 | idev_init: cmpc_accel_idev_init); |
636 | if (error) |
637 | goto failed_input; |
638 | |
639 | inputdev = dev_get_drvdata(dev: &acpi->dev); |
640 | dev_set_drvdata(dev: &inputdev->dev, data: accel); |
641 | |
642 | return 0; |
643 | |
644 | failed_input: |
645 | device_remove_file(dev: &acpi->dev, attr: &cmpc_accel_sensitivity_attr); |
646 | failed_file: |
647 | kfree(objp: accel); |
648 | return error; |
649 | } |
650 | |
651 | static void cmpc_accel_remove(struct acpi_device *acpi) |
652 | { |
653 | device_remove_file(dev: &acpi->dev, attr: &cmpc_accel_sensitivity_attr); |
654 | cmpc_remove_acpi_notify_device(acpi); |
655 | } |
656 | |
657 | static const struct acpi_device_id cmpc_accel_device_ids[] = { |
658 | {CMPC_ACCEL_HID, 0}, |
659 | {"" , 0} |
660 | }; |
661 | |
662 | static struct acpi_driver cmpc_accel_acpi_driver = { |
663 | .owner = THIS_MODULE, |
664 | .name = "cmpc_accel" , |
665 | .class = "cmpc_accel" , |
666 | .ids = cmpc_accel_device_ids, |
667 | .ops = { |
668 | .add = cmpc_accel_add, |
669 | .remove = cmpc_accel_remove, |
670 | .notify = cmpc_accel_handler, |
671 | } |
672 | }; |
673 | |
674 | |
675 | /* |
676 | * Tablet mode code. |
677 | */ |
678 | static acpi_status cmpc_get_tablet(acpi_handle handle, |
679 | unsigned long long *value) |
680 | { |
681 | union acpi_object param; |
682 | struct acpi_object_list input; |
683 | unsigned long long output; |
684 | acpi_status status; |
685 | |
686 | param.type = ACPI_TYPE_INTEGER; |
687 | param.integer.value = 0x01; |
688 | input.count = 1; |
689 | input.pointer = ¶m; |
690 | status = acpi_evaluate_integer(handle, pathname: "TCMD" , arguments: &input, data: &output); |
691 | if (ACPI_SUCCESS(status)) |
692 | *value = output; |
693 | return status; |
694 | } |
695 | |
696 | static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) |
697 | { |
698 | unsigned long long val = 0; |
699 | struct input_dev *inputdev = dev_get_drvdata(dev: &dev->dev); |
700 | |
701 | if (event == 0x81) { |
702 | if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) { |
703 | input_report_switch(dev: inputdev, SW_TABLET_MODE, value: !val); |
704 | input_sync(dev: inputdev); |
705 | } |
706 | } |
707 | } |
708 | |
709 | static void cmpc_tablet_idev_init(struct input_dev *inputdev) |
710 | { |
711 | unsigned long long val = 0; |
712 | struct acpi_device *acpi; |
713 | |
714 | set_bit(EV_SW, addr: inputdev->evbit); |
715 | set_bit(SW_TABLET_MODE, addr: inputdev->swbit); |
716 | |
717 | acpi = to_acpi_device(inputdev->dev.parent); |
718 | if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) { |
719 | input_report_switch(dev: inputdev, SW_TABLET_MODE, value: !val); |
720 | input_sync(dev: inputdev); |
721 | } |
722 | } |
723 | |
724 | static int cmpc_tablet_add(struct acpi_device *acpi) |
725 | { |
726 | return cmpc_add_acpi_notify_device(acpi, name: "cmpc_tablet" , |
727 | idev_init: cmpc_tablet_idev_init); |
728 | } |
729 | |
730 | static void cmpc_tablet_remove(struct acpi_device *acpi) |
731 | { |
732 | cmpc_remove_acpi_notify_device(acpi); |
733 | } |
734 | |
735 | #ifdef CONFIG_PM_SLEEP |
736 | static int cmpc_tablet_resume(struct device *dev) |
737 | { |
738 | struct input_dev *inputdev = dev_get_drvdata(dev); |
739 | |
740 | unsigned long long val = 0; |
741 | if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) { |
742 | input_report_switch(dev: inputdev, SW_TABLET_MODE, value: !val); |
743 | input_sync(dev: inputdev); |
744 | } |
745 | return 0; |
746 | } |
747 | #endif |
748 | |
749 | static SIMPLE_DEV_PM_OPS(cmpc_tablet_pm, NULL, cmpc_tablet_resume); |
750 | |
751 | static const struct acpi_device_id cmpc_tablet_device_ids[] = { |
752 | {CMPC_TABLET_HID, 0}, |
753 | {"" , 0} |
754 | }; |
755 | |
756 | static struct acpi_driver cmpc_tablet_acpi_driver = { |
757 | .owner = THIS_MODULE, |
758 | .name = "cmpc_tablet" , |
759 | .class = "cmpc_tablet" , |
760 | .ids = cmpc_tablet_device_ids, |
761 | .ops = { |
762 | .add = cmpc_tablet_add, |
763 | .remove = cmpc_tablet_remove, |
764 | .notify = cmpc_tablet_handler, |
765 | }, |
766 | .drv.pm = &cmpc_tablet_pm, |
767 | }; |
768 | |
769 | |
770 | /* |
771 | * Backlight code. |
772 | */ |
773 | |
774 | static acpi_status cmpc_get_brightness(acpi_handle handle, |
775 | unsigned long long *value) |
776 | { |
777 | union acpi_object param; |
778 | struct acpi_object_list input; |
779 | unsigned long long output; |
780 | acpi_status status; |
781 | |
782 | param.type = ACPI_TYPE_INTEGER; |
783 | param.integer.value = 0xC0; |
784 | input.count = 1; |
785 | input.pointer = ¶m; |
786 | status = acpi_evaluate_integer(handle, pathname: "GRDI" , arguments: &input, data: &output); |
787 | if (ACPI_SUCCESS(status)) |
788 | *value = output; |
789 | return status; |
790 | } |
791 | |
792 | static acpi_status cmpc_set_brightness(acpi_handle handle, |
793 | unsigned long long value) |
794 | { |
795 | union acpi_object param[2]; |
796 | struct acpi_object_list input; |
797 | acpi_status status; |
798 | unsigned long long output; |
799 | |
800 | param[0].type = ACPI_TYPE_INTEGER; |
801 | param[0].integer.value = 0xC0; |
802 | param[1].type = ACPI_TYPE_INTEGER; |
803 | param[1].integer.value = value; |
804 | input.count = 2; |
805 | input.pointer = param; |
806 | status = acpi_evaluate_integer(handle, pathname: "GWRI" , arguments: &input, data: &output); |
807 | return status; |
808 | } |
809 | |
810 | static int cmpc_bl_get_brightness(struct backlight_device *bd) |
811 | { |
812 | acpi_status status; |
813 | acpi_handle handle; |
814 | unsigned long long brightness; |
815 | |
816 | handle = bl_get_data(bl_dev: bd); |
817 | status = cmpc_get_brightness(handle, value: &brightness); |
818 | if (ACPI_SUCCESS(status)) |
819 | return brightness; |
820 | else |
821 | return -1; |
822 | } |
823 | |
824 | static int cmpc_bl_update_status(struct backlight_device *bd) |
825 | { |
826 | acpi_status status; |
827 | acpi_handle handle; |
828 | |
829 | handle = bl_get_data(bl_dev: bd); |
830 | status = cmpc_set_brightness(handle, value: bd->props.brightness); |
831 | if (ACPI_SUCCESS(status)) |
832 | return 0; |
833 | else |
834 | return -1; |
835 | } |
836 | |
837 | static const struct backlight_ops cmpc_bl_ops = { |
838 | .get_brightness = cmpc_bl_get_brightness, |
839 | .update_status = cmpc_bl_update_status |
840 | }; |
841 | |
842 | /* |
843 | * RFKILL code. |
844 | */ |
845 | |
846 | static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle, |
847 | unsigned long long *value) |
848 | { |
849 | union acpi_object param; |
850 | struct acpi_object_list input; |
851 | unsigned long long output; |
852 | acpi_status status; |
853 | |
854 | param.type = ACPI_TYPE_INTEGER; |
855 | param.integer.value = 0xC1; |
856 | input.count = 1; |
857 | input.pointer = ¶m; |
858 | status = acpi_evaluate_integer(handle, pathname: "GRDI" , arguments: &input, data: &output); |
859 | if (ACPI_SUCCESS(status)) |
860 | *value = output; |
861 | return status; |
862 | } |
863 | |
864 | static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle, |
865 | unsigned long long value) |
866 | { |
867 | union acpi_object param[2]; |
868 | struct acpi_object_list input; |
869 | acpi_status status; |
870 | unsigned long long output; |
871 | |
872 | param[0].type = ACPI_TYPE_INTEGER; |
873 | param[0].integer.value = 0xC1; |
874 | param[1].type = ACPI_TYPE_INTEGER; |
875 | param[1].integer.value = value; |
876 | input.count = 2; |
877 | input.pointer = param; |
878 | status = acpi_evaluate_integer(handle, pathname: "GWRI" , arguments: &input, data: &output); |
879 | return status; |
880 | } |
881 | |
882 | static void cmpc_rfkill_query(struct rfkill *rfkill, void *data) |
883 | { |
884 | acpi_status status; |
885 | acpi_handle handle; |
886 | unsigned long long state; |
887 | bool blocked; |
888 | |
889 | handle = data; |
890 | status = cmpc_get_rfkill_wlan(handle, value: &state); |
891 | if (ACPI_SUCCESS(status)) { |
892 | blocked = state & 1 ? false : true; |
893 | rfkill_set_sw_state(rfkill, blocked); |
894 | } |
895 | } |
896 | |
897 | static int cmpc_rfkill_block(void *data, bool blocked) |
898 | { |
899 | acpi_status status; |
900 | acpi_handle handle; |
901 | unsigned long long state; |
902 | bool is_blocked; |
903 | |
904 | handle = data; |
905 | status = cmpc_get_rfkill_wlan(handle, value: &state); |
906 | if (ACPI_FAILURE(status)) |
907 | return -ENODEV; |
908 | /* Check if we really need to call cmpc_set_rfkill_wlan */ |
909 | is_blocked = state & 1 ? false : true; |
910 | if (is_blocked != blocked) { |
911 | state = blocked ? 0 : 1; |
912 | status = cmpc_set_rfkill_wlan(handle, value: state); |
913 | if (ACPI_FAILURE(status)) |
914 | return -ENODEV; |
915 | } |
916 | return 0; |
917 | } |
918 | |
919 | static const struct rfkill_ops cmpc_rfkill_ops = { |
920 | .query = cmpc_rfkill_query, |
921 | .set_block = cmpc_rfkill_block, |
922 | }; |
923 | |
924 | /* |
925 | * Common backlight and rfkill code. |
926 | */ |
927 | |
928 | struct ipml200_dev { |
929 | struct backlight_device *bd; |
930 | struct rfkill *rf; |
931 | }; |
932 | |
933 | static int cmpc_ipml_add(struct acpi_device *acpi) |
934 | { |
935 | int retval; |
936 | struct ipml200_dev *ipml; |
937 | struct backlight_properties props; |
938 | |
939 | ipml = kmalloc(size: sizeof(*ipml), GFP_KERNEL); |
940 | if (ipml == NULL) |
941 | return -ENOMEM; |
942 | |
943 | memset(&props, 0, sizeof(struct backlight_properties)); |
944 | props.type = BACKLIGHT_PLATFORM; |
945 | props.max_brightness = 7; |
946 | ipml->bd = backlight_device_register(name: "cmpc_bl" , dev: &acpi->dev, |
947 | devdata: acpi->handle, ops: &cmpc_bl_ops, |
948 | props: &props); |
949 | if (IS_ERR(ptr: ipml->bd)) { |
950 | retval = PTR_ERR(ptr: ipml->bd); |
951 | goto out_bd; |
952 | } |
953 | |
954 | ipml->rf = rfkill_alloc(name: "cmpc_rfkill" , parent: &acpi->dev, type: RFKILL_TYPE_WLAN, |
955 | ops: &cmpc_rfkill_ops, ops_data: acpi->handle); |
956 | /* |
957 | * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV). |
958 | * This is OK, however, since all other uses of the device will not |
959 | * dereference it. |
960 | */ |
961 | if (ipml->rf) { |
962 | retval = rfkill_register(rfkill: ipml->rf); |
963 | if (retval) { |
964 | rfkill_destroy(rfkill: ipml->rf); |
965 | ipml->rf = NULL; |
966 | } |
967 | } |
968 | |
969 | dev_set_drvdata(dev: &acpi->dev, data: ipml); |
970 | return 0; |
971 | |
972 | out_bd: |
973 | kfree(objp: ipml); |
974 | return retval; |
975 | } |
976 | |
977 | static void cmpc_ipml_remove(struct acpi_device *acpi) |
978 | { |
979 | struct ipml200_dev *ipml; |
980 | |
981 | ipml = dev_get_drvdata(dev: &acpi->dev); |
982 | |
983 | backlight_device_unregister(bd: ipml->bd); |
984 | |
985 | if (ipml->rf) { |
986 | rfkill_unregister(rfkill: ipml->rf); |
987 | rfkill_destroy(rfkill: ipml->rf); |
988 | } |
989 | |
990 | kfree(objp: ipml); |
991 | } |
992 | |
993 | static const struct acpi_device_id cmpc_ipml_device_ids[] = { |
994 | {CMPC_IPML_HID, 0}, |
995 | {"" , 0} |
996 | }; |
997 | |
998 | static struct acpi_driver cmpc_ipml_acpi_driver = { |
999 | .owner = THIS_MODULE, |
1000 | .name = "cmpc" , |
1001 | .class = "cmpc" , |
1002 | .ids = cmpc_ipml_device_ids, |
1003 | .ops = { |
1004 | .add = cmpc_ipml_add, |
1005 | .remove = cmpc_ipml_remove |
1006 | } |
1007 | }; |
1008 | |
1009 | |
1010 | /* |
1011 | * Extra keys code. |
1012 | */ |
1013 | static int cmpc_keys_codes[] = { |
1014 | KEY_UNKNOWN, |
1015 | KEY_WLAN, |
1016 | KEY_SWITCHVIDEOMODE, |
1017 | KEY_BRIGHTNESSDOWN, |
1018 | KEY_BRIGHTNESSUP, |
1019 | KEY_VENDOR, |
1020 | KEY_UNKNOWN, |
1021 | KEY_CAMERA, |
1022 | KEY_BACK, |
1023 | KEY_FORWARD, |
1024 | KEY_UNKNOWN, |
1025 | KEY_WLAN, /* NL3: 0x8b (press), 0x9b (release) */ |
1026 | KEY_MAX |
1027 | }; |
1028 | |
1029 | static void cmpc_keys_handler(struct acpi_device *dev, u32 event) |
1030 | { |
1031 | struct input_dev *inputdev; |
1032 | int code = KEY_MAX; |
1033 | |
1034 | if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) |
1035 | code = cmpc_keys_codes[event & 0x0F]; |
1036 | inputdev = dev_get_drvdata(dev: &dev->dev); |
1037 | input_report_key(dev: inputdev, code, value: !(event & 0x10)); |
1038 | input_sync(dev: inputdev); |
1039 | } |
1040 | |
1041 | static void cmpc_keys_idev_init(struct input_dev *inputdev) |
1042 | { |
1043 | int i; |
1044 | |
1045 | set_bit(EV_KEY, addr: inputdev->evbit); |
1046 | for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++) |
1047 | set_bit(nr: cmpc_keys_codes[i], addr: inputdev->keybit); |
1048 | } |
1049 | |
1050 | static int cmpc_keys_add(struct acpi_device *acpi) |
1051 | { |
1052 | return cmpc_add_acpi_notify_device(acpi, name: "cmpc_keys" , |
1053 | idev_init: cmpc_keys_idev_init); |
1054 | } |
1055 | |
1056 | static void cmpc_keys_remove(struct acpi_device *acpi) |
1057 | { |
1058 | cmpc_remove_acpi_notify_device(acpi); |
1059 | } |
1060 | |
1061 | static const struct acpi_device_id cmpc_keys_device_ids[] = { |
1062 | {CMPC_KEYS_HID, 0}, |
1063 | {"" , 0} |
1064 | }; |
1065 | |
1066 | static struct acpi_driver cmpc_keys_acpi_driver = { |
1067 | .owner = THIS_MODULE, |
1068 | .name = "cmpc_keys" , |
1069 | .class = "cmpc_keys" , |
1070 | .ids = cmpc_keys_device_ids, |
1071 | .ops = { |
1072 | .add = cmpc_keys_add, |
1073 | .remove = cmpc_keys_remove, |
1074 | .notify = cmpc_keys_handler, |
1075 | } |
1076 | }; |
1077 | |
1078 | |
1079 | /* |
1080 | * General init/exit code. |
1081 | */ |
1082 | |
1083 | static int cmpc_init(void) |
1084 | { |
1085 | int r; |
1086 | |
1087 | r = acpi_bus_register_driver(driver: &cmpc_keys_acpi_driver); |
1088 | if (r) |
1089 | goto failed_keys; |
1090 | |
1091 | r = acpi_bus_register_driver(driver: &cmpc_ipml_acpi_driver); |
1092 | if (r) |
1093 | goto failed_bl; |
1094 | |
1095 | r = acpi_bus_register_driver(driver: &cmpc_tablet_acpi_driver); |
1096 | if (r) |
1097 | goto failed_tablet; |
1098 | |
1099 | r = acpi_bus_register_driver(driver: &cmpc_accel_acpi_driver); |
1100 | if (r) |
1101 | goto failed_accel; |
1102 | |
1103 | r = acpi_bus_register_driver(driver: &cmpc_accel_acpi_driver_v4); |
1104 | if (r) |
1105 | goto failed_accel_v4; |
1106 | |
1107 | return r; |
1108 | |
1109 | failed_accel_v4: |
1110 | acpi_bus_unregister_driver(driver: &cmpc_accel_acpi_driver); |
1111 | |
1112 | failed_accel: |
1113 | acpi_bus_unregister_driver(driver: &cmpc_tablet_acpi_driver); |
1114 | |
1115 | failed_tablet: |
1116 | acpi_bus_unregister_driver(driver: &cmpc_ipml_acpi_driver); |
1117 | |
1118 | failed_bl: |
1119 | acpi_bus_unregister_driver(driver: &cmpc_keys_acpi_driver); |
1120 | |
1121 | failed_keys: |
1122 | return r; |
1123 | } |
1124 | |
1125 | static void cmpc_exit(void) |
1126 | { |
1127 | acpi_bus_unregister_driver(driver: &cmpc_accel_acpi_driver_v4); |
1128 | acpi_bus_unregister_driver(driver: &cmpc_accel_acpi_driver); |
1129 | acpi_bus_unregister_driver(driver: &cmpc_tablet_acpi_driver); |
1130 | acpi_bus_unregister_driver(driver: &cmpc_ipml_acpi_driver); |
1131 | acpi_bus_unregister_driver(driver: &cmpc_keys_acpi_driver); |
1132 | } |
1133 | |
1134 | module_init(cmpc_init); |
1135 | module_exit(cmpc_exit); |
1136 | |
1137 | static const struct acpi_device_id cmpc_device_ids[] __maybe_unused = { |
1138 | {CMPC_ACCEL_HID, 0}, |
1139 | {CMPC_ACCEL_HID_V4, 0}, |
1140 | {CMPC_TABLET_HID, 0}, |
1141 | {CMPC_IPML_HID, 0}, |
1142 | {CMPC_KEYS_HID, 0}, |
1143 | {"" , 0} |
1144 | }; |
1145 | |
1146 | MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); |
1147 | |