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