1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Panasonic HotKey and LCD brightness control driver |
4 | * (C) 2004 Hiroshi Miura <miura@da-cha.org> |
5 | * (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/ |
6 | * (C) YOKOTA Hiroshi <yokota (at) netlab. is. tsukuba. ac. jp> |
7 | * (C) 2004 David Bronaugh <dbronaugh> |
8 | * (C) 2006-2008 Harald Welte <laforge@gnumonks.org> |
9 | * |
10 | * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte |
11 | * |
12 | *--------------------------------------------------------------------------- |
13 | * |
14 | * ChangeLog: |
15 | * Aug.18, 2020 Kenneth Chan <kenneth.t.chan@gmail.com> |
16 | * -v0.98 add platform devices for firmware brightness registers |
17 | * add support for battery charging threshold (eco mode) |
18 | * resolve hotkey double trigger |
19 | * add write support to mute |
20 | * fix sticky_key init bug |
21 | * fix naming of platform files for consistency with other |
22 | * modules |
23 | * split MODULE_AUTHOR() by one author per macro call |
24 | * replace ACPI prints with pr_*() macros |
25 | * -v0.97 add support for cdpower hardware switch |
26 | * -v0.96 merge Lucina's enhancement |
27 | * Jan.13, 2009 Martin Lucina <mato@kotelna.sk> |
28 | * - add support for optical driver power in |
29 | * Y and W series |
30 | * |
31 | * Sep.23, 2008 Harald Welte <laforge@gnumonks.org> |
32 | * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to |
33 | * drivers/misc/panasonic-laptop.c |
34 | * |
35 | * Jul.04, 2008 Harald Welte <laforge@gnumonks.org> |
36 | * -v0.94 replace /proc interface with device attributes |
37 | * support {set,get}keycode on th input device |
38 | * |
39 | * Jun.27, 2008 Harald Welte <laforge@gnumonks.org> |
40 | * -v0.92 merge with 2.6.26-rc6 input API changes |
41 | * remove broken <= 2.6.15 kernel support |
42 | * resolve all compiler warnings |
43 | * various coding style fixes (checkpatch.pl) |
44 | * add support for backlight api |
45 | * major code restructuring |
46 | * |
47 | * Dac.28, 2007 Harald Welte <laforge@gnumonks.org> |
48 | * -v0.91 merge with 2.6.24-rc6 ACPI changes |
49 | * |
50 | * Nov.04, 2006 Hiroshi Miura <miura@da-cha.org> |
51 | * -v0.9 remove warning about section reference. |
52 | * remove acpi_os_free |
53 | * add /proc/acpi/pcc/brightness interface for HAL access |
54 | * merge dbronaugh's enhancement |
55 | * Aug.17, 2004 David Bronaugh (dbronaugh) |
56 | * - Added screen brightness setting interface |
57 | * Thanks to FreeBSD crew (acpi_panasonic.c) |
58 | * for the ideas I needed to accomplish it |
59 | * |
60 | * May.29, 2006 Hiroshi Miura <miura@da-cha.org> |
61 | * -v0.8.4 follow to change keyinput structure |
62 | * thanks Fabian Yamaguchi <fabs@cs.tu-berlin.de>, |
63 | * Jacob Bower <jacob.bower@ic.ac.uk> and |
64 | * Hiroshi Yokota for providing solutions. |
65 | * |
66 | * Oct.02, 2004 Hiroshi Miura <miura@da-cha.org> |
67 | * -v0.8.2 merge code of YOKOTA Hiroshi |
68 | * <yokota@netlab.is.tsukuba.ac.jp>. |
69 | * Add sticky key mode interface. |
70 | * Refactoring acpi_pcc_generate_keyinput(). |
71 | * |
72 | * Sep.15, 2004 Hiroshi Miura <miura@da-cha.org> |
73 | * -v0.8 Generate key input event on input subsystem. |
74 | * This is based on yet another driver written by |
75 | * Ryuta Nakanishi. |
76 | * |
77 | * Sep.10, 2004 Hiroshi Miura <miura@da-cha.org> |
78 | * -v0.7 Change proc interface functions using seq_file |
79 | * facility as same as other ACPI drivers. |
80 | * |
81 | * Aug.28, 2004 Hiroshi Miura <miura@da-cha.org> |
82 | * -v0.6.4 Fix a silly error with status checking |
83 | * |
84 | * Aug.25, 2004 Hiroshi Miura <miura@da-cha.org> |
85 | * -v0.6.3 replace read_acpi_int by standard function |
86 | * acpi_evaluate_integer |
87 | * some clean up and make smart copyright notice. |
88 | * fix return value of pcc_acpi_get_key() |
89 | * fix checking return value of acpi_bus_register_driver() |
90 | * |
91 | * Aug.22, 2004 David Bronaugh <dbronaugh@linuxboxen.org> |
92 | * -v0.6.2 Add check on ACPI data (num_sifr) |
93 | * Coding style cleanups, better error messages/handling |
94 | * Fixed an off-by-one error in memory allocation |
95 | * |
96 | * Aug.21, 2004 David Bronaugh <dbronaugh@linuxboxen.org> |
97 | * -v0.6.1 Fix a silly error with status checking |
98 | * |
99 | * Aug.20, 2004 David Bronaugh <dbronaugh@linuxboxen.org> |
100 | * - v0.6 Correct brightness controls to reflect reality |
101 | * based on information gleaned by Hiroshi Miura |
102 | * and discussions with Hiroshi Miura |
103 | * |
104 | * Aug.10, 2004 Hiroshi Miura <miura@da-cha.org> |
105 | * - v0.5 support LCD brightness control |
106 | * based on the disclosed information by MEI. |
107 | * |
108 | * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org> |
109 | * - v0.4 first post version |
110 | * add function to retrive SIFR |
111 | * |
112 | * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org> |
113 | * - v0.3 get proper status of hotkey |
114 | * |
115 | * Jul.22, 2004 Hiroshi Miura <miura@da-cha.org> |
116 | * - v0.2 add HotKey handler |
117 | * |
118 | * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org> |
119 | * - v0.1 start from toshiba_acpi driver written by John Belmonte |
120 | */ |
121 | |
122 | #include <linux/acpi.h> |
123 | #include <linux/backlight.h> |
124 | #include <linux/ctype.h> |
125 | #include <linux/i8042.h> |
126 | #include <linux/init.h> |
127 | #include <linux/input.h> |
128 | #include <linux/input/sparse-keymap.h> |
129 | #include <linux/kernel.h> |
130 | #include <linux/module.h> |
131 | #include <linux/platform_device.h> |
132 | #include <linux/seq_file.h> |
133 | #include <linux/serio.h> |
134 | #include <linux/slab.h> |
135 | #include <linux/types.h> |
136 | #include <linux/uaccess.h> |
137 | #include <acpi/video.h> |
138 | |
139 | MODULE_AUTHOR("Hiroshi Miura <miura@da-cha.org>" ); |
140 | MODULE_AUTHOR("David Bronaugh <dbronaugh@linuxboxen.org>" ); |
141 | MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>" ); |
142 | MODULE_AUTHOR("Martin Lucina <mato@kotelna.sk>" ); |
143 | MODULE_AUTHOR("Kenneth Chan <kenneth.t.chan@gmail.com>" ); |
144 | MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops" ); |
145 | MODULE_LICENSE("GPL" ); |
146 | |
147 | #define LOGPREFIX "pcc_acpi: " |
148 | |
149 | /* Define ACPI PATHs */ |
150 | /* Lets note hotkeys */ |
151 | #define METHOD_HKEY_QUERY "HINF" |
152 | #define METHOD_HKEY_SQTY "SQTY" |
153 | #define METHOD_HKEY_SINF "SINF" |
154 | #define METHOD_HKEY_SSET "SSET" |
155 | #define METHOD_ECWR "\\_SB.ECWR" |
156 | #define HKEY_NOTIFY 0x80 |
157 | #define ECO_MODE_OFF 0x00 |
158 | #define ECO_MODE_ON 0x80 |
159 | |
160 | #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" |
161 | #define ACPI_PCC_DEVICE_NAME "Hotkey" |
162 | #define ACPI_PCC_CLASS "pcc" |
163 | |
164 | #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" |
165 | |
166 | /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent |
167 | ECO_MODEs: 0x03 = off, 0x83 = on |
168 | */ |
169 | enum SINF_BITS { SINF_NUM_BATTERIES = 0, |
170 | SINF_LCD_TYPE, |
171 | SINF_AC_MAX_BRIGHT, |
172 | SINF_AC_MIN_BRIGHT, |
173 | SINF_AC_CUR_BRIGHT, |
174 | SINF_DC_MAX_BRIGHT, |
175 | SINF_DC_MIN_BRIGHT, |
176 | SINF_DC_CUR_BRIGHT, |
177 | SINF_MUTE, |
178 | SINF_RESERVED, |
179 | SINF_ECO_MODE = 0x0A, |
180 | SINF_CUR_BRIGHT = 0x0D, |
181 | SINF_STICKY_KEY = 0x80, |
182 | }; |
183 | /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */ |
184 | |
185 | static int acpi_pcc_hotkey_add(struct acpi_device *device); |
186 | static void acpi_pcc_hotkey_remove(struct acpi_device *device); |
187 | static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event); |
188 | |
189 | static const struct acpi_device_id pcc_device_ids[] = { |
190 | { "MAT0012" , 0}, |
191 | { "MAT0013" , 0}, |
192 | { "MAT0018" , 0}, |
193 | { "MAT0019" , 0}, |
194 | { "" , 0}, |
195 | }; |
196 | MODULE_DEVICE_TABLE(acpi, pcc_device_ids); |
197 | |
198 | #ifdef CONFIG_PM_SLEEP |
199 | static int acpi_pcc_hotkey_resume(struct device *dev); |
200 | #endif |
201 | static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume); |
202 | |
203 | static struct acpi_driver acpi_pcc_driver = { |
204 | .name = ACPI_PCC_DRIVER_NAME, |
205 | .class = ACPI_PCC_CLASS, |
206 | .ids = pcc_device_ids, |
207 | .ops = { |
208 | .add = acpi_pcc_hotkey_add, |
209 | .remove = acpi_pcc_hotkey_remove, |
210 | .notify = acpi_pcc_hotkey_notify, |
211 | }, |
212 | .drv.pm = &acpi_pcc_hotkey_pm, |
213 | }; |
214 | |
215 | static const struct key_entry panasonic_keymap[] = { |
216 | { KE_KEY, 0, { KEY_RESERVED } }, |
217 | { KE_KEY, 1, { KEY_BRIGHTNESSDOWN } }, |
218 | { KE_KEY, 2, { KEY_BRIGHTNESSUP } }, |
219 | { KE_KEY, 3, { KEY_DISPLAYTOGGLE } }, |
220 | { KE_KEY, 4, { KEY_MUTE } }, |
221 | { KE_KEY, 5, { KEY_VOLUMEDOWN } }, |
222 | { KE_KEY, 6, { KEY_VOLUMEUP } }, |
223 | { KE_KEY, 7, { KEY_SLEEP } }, |
224 | { KE_KEY, 8, { KEY_PROG1 } }, /* Change CPU boost */ |
225 | { KE_KEY, 9, { KEY_BATTERY } }, |
226 | { KE_KEY, 10, { KEY_SUSPEND } }, |
227 | { KE_END, 0 } |
228 | }; |
229 | |
230 | struct pcc_acpi { |
231 | acpi_handle handle; |
232 | unsigned long num_sifr; |
233 | int sticky_key; |
234 | int eco_mode; |
235 | int mute; |
236 | int ac_brightness; |
237 | int dc_brightness; |
238 | int current_brightness; |
239 | u32 *sinf; |
240 | struct acpi_device *device; |
241 | struct input_dev *input_dev; |
242 | struct backlight_device *backlight; |
243 | struct platform_device *platform; |
244 | }; |
245 | |
246 | /* |
247 | * On some Panasonic models the volume up / down / mute keys send duplicate |
248 | * keypress events over the PS/2 kbd interface, filter these out. |
249 | */ |
250 | static bool panasonic_i8042_filter(unsigned char data, unsigned char str, |
251 | struct serio *port) |
252 | { |
253 | static bool extended; |
254 | |
255 | if (str & I8042_STR_AUXDATA) |
256 | return false; |
257 | |
258 | if (data == 0xe0) { |
259 | extended = true; |
260 | return true; |
261 | } else if (extended) { |
262 | extended = false; |
263 | |
264 | switch (data & 0x7f) { |
265 | case 0x20: /* e0 20 / e0 a0, Volume Mute press / release */ |
266 | case 0x2e: /* e0 2e / e0 ae, Volume Down press / release */ |
267 | case 0x30: /* e0 30 / e0 b0, Volume Up press / release */ |
268 | return true; |
269 | default: |
270 | /* |
271 | * Report the previously filtered e0 before continuing |
272 | * with the next non-filtered byte. |
273 | */ |
274 | serio_interrupt(serio: port, data: 0xe0, flags: 0); |
275 | return false; |
276 | } |
277 | } |
278 | |
279 | return false; |
280 | } |
281 | |
282 | /* method access functions */ |
283 | static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val) |
284 | { |
285 | union acpi_object in_objs[] = { |
286 | { .integer.type = ACPI_TYPE_INTEGER, |
287 | .integer.value = func, }, |
288 | { .integer.type = ACPI_TYPE_INTEGER, |
289 | .integer.value = val, }, |
290 | }; |
291 | struct acpi_object_list params = { |
292 | .count = ARRAY_SIZE(in_objs), |
293 | .pointer = in_objs, |
294 | }; |
295 | acpi_status status = AE_OK; |
296 | |
297 | status = acpi_evaluate_object(object: pcc->handle, METHOD_HKEY_SSET, |
298 | parameter_objects: ¶ms, NULL); |
299 | |
300 | return (status == AE_OK) ? 0 : -EIO; |
301 | } |
302 | |
303 | static inline int acpi_pcc_get_sqty(struct acpi_device *device) |
304 | { |
305 | unsigned long long s; |
306 | acpi_status status; |
307 | |
308 | status = acpi_evaluate_integer(handle: device->handle, METHOD_HKEY_SQTY, |
309 | NULL, data: &s); |
310 | if (ACPI_SUCCESS(status)) |
311 | return s; |
312 | else { |
313 | pr_err("evaluation error HKEY.SQTY\n" ); |
314 | return -EINVAL; |
315 | } |
316 | } |
317 | |
318 | static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) |
319 | { |
320 | acpi_status status; |
321 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; |
322 | union acpi_object *hkey = NULL; |
323 | int i; |
324 | |
325 | status = acpi_evaluate_object(object: pcc->handle, METHOD_HKEY_SINF, NULL, |
326 | return_object_buffer: &buffer); |
327 | if (ACPI_FAILURE(status)) { |
328 | pr_err("evaluation error HKEY.SINF\n" ); |
329 | return 0; |
330 | } |
331 | |
332 | hkey = buffer.pointer; |
333 | if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) { |
334 | pr_err("Invalid HKEY.SINF\n" ); |
335 | status = AE_ERROR; |
336 | goto end; |
337 | } |
338 | |
339 | if (pcc->num_sifr < hkey->package.count) { |
340 | pr_err("SQTY reports bad SINF length\n" ); |
341 | status = AE_ERROR; |
342 | goto end; |
343 | } |
344 | |
345 | for (i = 0; i < hkey->package.count; i++) { |
346 | union acpi_object *element = &(hkey->package.elements[i]); |
347 | if (likely(element->type == ACPI_TYPE_INTEGER)) { |
348 | pcc->sinf[i] = element->integer.value; |
349 | } else |
350 | pr_err("Invalid HKEY.SINF data\n" ); |
351 | } |
352 | pcc->sinf[hkey->package.count] = -1; |
353 | |
354 | end: |
355 | kfree(objp: buffer.pointer); |
356 | return status == AE_OK; |
357 | } |
358 | |
359 | /* backlight API interface functions */ |
360 | |
361 | /* This driver currently treats AC and DC brightness identical, |
362 | * since we don't need to invent an interface to the core ACPI |
363 | * logic to receive events in case a power supply is plugged in |
364 | * or removed */ |
365 | |
366 | static int bl_get(struct backlight_device *bd) |
367 | { |
368 | struct pcc_acpi *pcc = bl_get_data(bl_dev: bd); |
369 | |
370 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
371 | return -EIO; |
372 | |
373 | return pcc->sinf[SINF_AC_CUR_BRIGHT]; |
374 | } |
375 | |
376 | static int bl_set_status(struct backlight_device *bd) |
377 | { |
378 | struct pcc_acpi *pcc = bl_get_data(bl_dev: bd); |
379 | int bright = bd->props.brightness; |
380 | int rc; |
381 | |
382 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
383 | return -EIO; |
384 | |
385 | if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT]) |
386 | bright = pcc->sinf[SINF_AC_MIN_BRIGHT]; |
387 | |
388 | if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT]) |
389 | bright = pcc->sinf[SINF_DC_MIN_BRIGHT]; |
390 | |
391 | if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] || |
392 | bright > pcc->sinf[SINF_AC_MAX_BRIGHT]) |
393 | return -EINVAL; |
394 | |
395 | rc = acpi_pcc_write_sset(pcc, func: SINF_AC_CUR_BRIGHT, val: bright); |
396 | if (rc < 0) |
397 | return rc; |
398 | |
399 | return acpi_pcc_write_sset(pcc, func: SINF_DC_CUR_BRIGHT, val: bright); |
400 | } |
401 | |
402 | static const struct backlight_ops pcc_backlight_ops = { |
403 | .get_brightness = bl_get, |
404 | .update_status = bl_set_status, |
405 | }; |
406 | |
407 | |
408 | /* returns ACPI_SUCCESS if methods to control optical drive are present */ |
409 | |
410 | static acpi_status check_optd_present(void) |
411 | { |
412 | acpi_status status = AE_OK; |
413 | acpi_handle handle; |
414 | |
415 | status = acpi_get_handle(NULL, pathname: "\\_SB.STAT" , ret_handle: &handle); |
416 | if (ACPI_FAILURE(status)) |
417 | goto out; |
418 | status = acpi_get_handle(NULL, pathname: "\\_SB.FBAY" , ret_handle: &handle); |
419 | if (ACPI_FAILURE(status)) |
420 | goto out; |
421 | status = acpi_get_handle(NULL, pathname: "\\_SB.CDDI" , ret_handle: &handle); |
422 | if (ACPI_FAILURE(status)) |
423 | goto out; |
424 | |
425 | out: |
426 | return status; |
427 | } |
428 | |
429 | /* get optical driver power state */ |
430 | |
431 | static int get_optd_power_state(void) |
432 | { |
433 | acpi_status status; |
434 | unsigned long long state; |
435 | int result; |
436 | |
437 | status = acpi_evaluate_integer(NULL, pathname: "\\_SB.STAT" , NULL, data: &state); |
438 | if (ACPI_FAILURE(status)) { |
439 | pr_err("evaluation error _SB.STAT\n" ); |
440 | result = -EIO; |
441 | goto out; |
442 | } |
443 | switch (state) { |
444 | case 0: /* power off */ |
445 | result = 0; |
446 | break; |
447 | case 0x0f: /* power on */ |
448 | result = 1; |
449 | break; |
450 | default: |
451 | result = -EIO; |
452 | break; |
453 | } |
454 | |
455 | out: |
456 | return result; |
457 | } |
458 | |
459 | /* set optical drive power state */ |
460 | |
461 | static int set_optd_power_state(int new_state) |
462 | { |
463 | int result; |
464 | acpi_status status; |
465 | |
466 | result = get_optd_power_state(); |
467 | if (result < 0) |
468 | goto out; |
469 | if (new_state == result) |
470 | goto out; |
471 | |
472 | switch (new_state) { |
473 | case 0: /* power off */ |
474 | /* Call CDDR instead, since they both call the same method |
475 | * while CDDI takes 1 arg and we are not quite sure what it is. |
476 | */ |
477 | status = acpi_evaluate_object(NULL, pathname: "\\_SB.CDDR" , NULL, NULL); |
478 | if (ACPI_FAILURE(status)) { |
479 | pr_err("evaluation error _SB.CDDR\n" ); |
480 | result = -EIO; |
481 | } |
482 | break; |
483 | case 1: /* power on */ |
484 | status = acpi_evaluate_object(NULL, pathname: "\\_SB.FBAY" , NULL, NULL); |
485 | if (ACPI_FAILURE(status)) { |
486 | pr_err("evaluation error _SB.FBAY\n" ); |
487 | result = -EIO; |
488 | } |
489 | break; |
490 | default: |
491 | result = -EINVAL; |
492 | break; |
493 | } |
494 | |
495 | out: |
496 | return result; |
497 | } |
498 | |
499 | |
500 | /* sysfs user interface functions */ |
501 | |
502 | static ssize_t numbatt_show(struct device *dev, struct device_attribute *attr, |
503 | char *buf) |
504 | { |
505 | struct acpi_device *acpi = to_acpi_device(dev); |
506 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
507 | |
508 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
509 | return -EIO; |
510 | |
511 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sinf[SINF_NUM_BATTERIES]); |
512 | } |
513 | |
514 | static ssize_t lcdtype_show(struct device *dev, struct device_attribute *attr, |
515 | char *buf) |
516 | { |
517 | struct acpi_device *acpi = to_acpi_device(dev); |
518 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
519 | |
520 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
521 | return -EIO; |
522 | |
523 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sinf[SINF_LCD_TYPE]); |
524 | } |
525 | |
526 | static ssize_t mute_show(struct device *dev, struct device_attribute *attr, |
527 | char *buf) |
528 | { |
529 | struct acpi_device *acpi = to_acpi_device(dev); |
530 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
531 | |
532 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
533 | return -EIO; |
534 | |
535 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sinf[SINF_MUTE]); |
536 | } |
537 | |
538 | static ssize_t mute_store(struct device *dev, struct device_attribute *attr, |
539 | const char *buf, size_t count) |
540 | { |
541 | struct acpi_device *acpi = to_acpi_device(dev); |
542 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
543 | int err, val; |
544 | |
545 | err = kstrtoint(s: buf, base: 0, res: &val); |
546 | if (err) |
547 | return err; |
548 | if (val == 0 || val == 1) { |
549 | acpi_pcc_write_sset(pcc, func: SINF_MUTE, val); |
550 | pcc->mute = val; |
551 | } |
552 | |
553 | return count; |
554 | } |
555 | |
556 | static ssize_t sticky_key_show(struct device *dev, struct device_attribute *attr, |
557 | char *buf) |
558 | { |
559 | struct acpi_device *acpi = to_acpi_device(dev); |
560 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
561 | |
562 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
563 | return -EIO; |
564 | |
565 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sticky_key); |
566 | } |
567 | |
568 | static ssize_t sticky_key_store(struct device *dev, struct device_attribute *attr, |
569 | const char *buf, size_t count) |
570 | { |
571 | struct acpi_device *acpi = to_acpi_device(dev); |
572 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
573 | int err, val; |
574 | |
575 | err = kstrtoint(s: buf, base: 0, res: &val); |
576 | if (err) |
577 | return err; |
578 | if (val == 0 || val == 1) { |
579 | acpi_pcc_write_sset(pcc, func: SINF_STICKY_KEY, val); |
580 | pcc->sticky_key = val; |
581 | } |
582 | |
583 | return count; |
584 | } |
585 | |
586 | static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr, |
587 | char *buf) |
588 | { |
589 | struct acpi_device *acpi = to_acpi_device(dev); |
590 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
591 | int result; |
592 | |
593 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
594 | return -EIO; |
595 | |
596 | switch (pcc->sinf[SINF_ECO_MODE]) { |
597 | case (ECO_MODE_OFF + 3): |
598 | result = 0; |
599 | break; |
600 | case (ECO_MODE_ON + 3): |
601 | result = 1; |
602 | break; |
603 | default: |
604 | result = -EIO; |
605 | break; |
606 | } |
607 | return sysfs_emit(buf, fmt: "%u\n" , result); |
608 | } |
609 | |
610 | static ssize_t eco_mode_store(struct device *dev, struct device_attribute *attr, |
611 | const char *buf, size_t count) |
612 | { |
613 | struct acpi_device *acpi = to_acpi_device(dev); |
614 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
615 | int err, state; |
616 | |
617 | union acpi_object param[2]; |
618 | struct acpi_object_list input; |
619 | acpi_status status; |
620 | |
621 | param[0].type = ACPI_TYPE_INTEGER; |
622 | param[0].integer.value = 0x15; |
623 | param[1].type = ACPI_TYPE_INTEGER; |
624 | input.count = 2; |
625 | input.pointer = param; |
626 | |
627 | err = kstrtoint(s: buf, base: 0, res: &state); |
628 | if (err) |
629 | return err; |
630 | |
631 | switch (state) { |
632 | case 0: |
633 | param[1].integer.value = ECO_MODE_OFF; |
634 | pcc->sinf[SINF_ECO_MODE] = 0; |
635 | pcc->eco_mode = 0; |
636 | break; |
637 | case 1: |
638 | param[1].integer.value = ECO_MODE_ON; |
639 | pcc->sinf[SINF_ECO_MODE] = 1; |
640 | pcc->eco_mode = 1; |
641 | break; |
642 | default: |
643 | /* nothing to do */ |
644 | return count; |
645 | } |
646 | |
647 | status = acpi_evaluate_object(NULL, METHOD_ECWR, |
648 | parameter_objects: &input, NULL); |
649 | if (ACPI_FAILURE(status)) { |
650 | pr_err("%s evaluation failed\n" , METHOD_ECWR); |
651 | return -EINVAL; |
652 | } |
653 | |
654 | return count; |
655 | } |
656 | |
657 | static ssize_t ac_brightness_show(struct device *dev, struct device_attribute *attr, |
658 | char *buf) |
659 | { |
660 | struct acpi_device *acpi = to_acpi_device(dev); |
661 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
662 | |
663 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
664 | return -EIO; |
665 | |
666 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sinf[SINF_AC_CUR_BRIGHT]); |
667 | } |
668 | |
669 | static ssize_t ac_brightness_store(struct device *dev, struct device_attribute *attr, |
670 | const char *buf, size_t count) |
671 | { |
672 | struct acpi_device *acpi = to_acpi_device(dev); |
673 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
674 | int err, val; |
675 | |
676 | err = kstrtoint(s: buf, base: 0, res: &val); |
677 | if (err) |
678 | return err; |
679 | if (val >= 0 && val <= 255) { |
680 | acpi_pcc_write_sset(pcc, func: SINF_AC_CUR_BRIGHT, val); |
681 | pcc->ac_brightness = val; |
682 | } |
683 | |
684 | return count; |
685 | } |
686 | |
687 | static ssize_t dc_brightness_show(struct device *dev, struct device_attribute *attr, |
688 | char *buf) |
689 | { |
690 | struct acpi_device *acpi = to_acpi_device(dev); |
691 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
692 | |
693 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
694 | return -EIO; |
695 | |
696 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sinf[SINF_DC_CUR_BRIGHT]); |
697 | } |
698 | |
699 | static ssize_t dc_brightness_store(struct device *dev, struct device_attribute *attr, |
700 | const char *buf, size_t count) |
701 | { |
702 | struct acpi_device *acpi = to_acpi_device(dev); |
703 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
704 | int err, val; |
705 | |
706 | err = kstrtoint(s: buf, base: 0, res: &val); |
707 | if (err) |
708 | return err; |
709 | if (val >= 0 && val <= 255) { |
710 | acpi_pcc_write_sset(pcc, func: SINF_DC_CUR_BRIGHT, val); |
711 | pcc->dc_brightness = val; |
712 | } |
713 | |
714 | return count; |
715 | } |
716 | |
717 | static ssize_t current_brightness_show(struct device *dev, struct device_attribute *attr, |
718 | char *buf) |
719 | { |
720 | struct acpi_device *acpi = to_acpi_device(dev); |
721 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
722 | |
723 | if (!acpi_pcc_retrieve_biosdata(pcc)) |
724 | return -EIO; |
725 | |
726 | return sysfs_emit(buf, fmt: "%u\n" , pcc->sinf[SINF_CUR_BRIGHT]); |
727 | } |
728 | |
729 | static ssize_t current_brightness_store(struct device *dev, struct device_attribute *attr, |
730 | const char *buf, size_t count) |
731 | { |
732 | struct acpi_device *acpi = to_acpi_device(dev); |
733 | struct pcc_acpi *pcc = acpi_driver_data(d: acpi); |
734 | int err, val; |
735 | |
736 | err = kstrtoint(s: buf, base: 0, res: &val); |
737 | if (err) |
738 | return err; |
739 | |
740 | if (val >= 0 && val <= 255) { |
741 | err = acpi_pcc_write_sset(pcc, func: SINF_CUR_BRIGHT, val); |
742 | pcc->current_brightness = val; |
743 | } |
744 | |
745 | return count; |
746 | } |
747 | |
748 | static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr, |
749 | char *buf) |
750 | { |
751 | return sysfs_emit(buf, fmt: "%d\n" , get_optd_power_state()); |
752 | } |
753 | |
754 | static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr, |
755 | const char *buf, size_t count) |
756 | { |
757 | int err, val; |
758 | |
759 | err = kstrtoint(s: buf, base: 10, res: &val); |
760 | if (err) |
761 | return err; |
762 | set_optd_power_state(val); |
763 | return count; |
764 | } |
765 | |
766 | static DEVICE_ATTR_RO(numbatt); |
767 | static DEVICE_ATTR_RO(lcdtype); |
768 | static DEVICE_ATTR_RW(mute); |
769 | static DEVICE_ATTR_RW(sticky_key); |
770 | static DEVICE_ATTR_RW(eco_mode); |
771 | static DEVICE_ATTR_RW(ac_brightness); |
772 | static DEVICE_ATTR_RW(dc_brightness); |
773 | static DEVICE_ATTR_RW(current_brightness); |
774 | static DEVICE_ATTR_RW(cdpower); |
775 | |
776 | static struct attribute *pcc_sysfs_entries[] = { |
777 | &dev_attr_numbatt.attr, |
778 | &dev_attr_lcdtype.attr, |
779 | &dev_attr_mute.attr, |
780 | &dev_attr_sticky_key.attr, |
781 | &dev_attr_eco_mode.attr, |
782 | &dev_attr_ac_brightness.attr, |
783 | &dev_attr_dc_brightness.attr, |
784 | &dev_attr_current_brightness.attr, |
785 | &dev_attr_cdpower.attr, |
786 | NULL, |
787 | }; |
788 | |
789 | static const struct attribute_group pcc_attr_group = { |
790 | .name = NULL, /* put in device directory */ |
791 | .attrs = pcc_sysfs_entries, |
792 | }; |
793 | |
794 | |
795 | /* hotkey input device driver */ |
796 | |
797 | static int sleep_keydown_seen; |
798 | static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) |
799 | { |
800 | struct input_dev *hotk_input_dev = pcc->input_dev; |
801 | int rc; |
802 | unsigned long long result; |
803 | unsigned int key; |
804 | unsigned int updown; |
805 | |
806 | rc = acpi_evaluate_integer(handle: pcc->handle, METHOD_HKEY_QUERY, |
807 | NULL, data: &result); |
808 | if (ACPI_FAILURE(rc)) { |
809 | pr_err("error getting hotkey status\n" ); |
810 | return; |
811 | } |
812 | |
813 | key = result & 0xf; |
814 | updown = result & 0x80; /* 0x80 == key down; 0x00 = key up */ |
815 | |
816 | /* hack: some firmware sends no key down for sleep / hibernate */ |
817 | if (key == 7 || key == 10) { |
818 | if (updown) |
819 | sleep_keydown_seen = 1; |
820 | if (!sleep_keydown_seen) |
821 | sparse_keymap_report_event(dev: hotk_input_dev, |
822 | code: key, value: 0x80, autorelease: false); |
823 | } |
824 | |
825 | /* |
826 | * Don't report brightness key-presses if they are also reported |
827 | * by the ACPI video bus. |
828 | */ |
829 | if ((key == 1 || key == 2) && acpi_video_handles_brightness_key_presses()) |
830 | return; |
831 | |
832 | if (!sparse_keymap_report_event(dev: hotk_input_dev, code: key, value: updown, autorelease: false)) |
833 | pr_err("Unknown hotkey event: 0x%04llx\n" , result); |
834 | } |
835 | |
836 | static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) |
837 | { |
838 | struct pcc_acpi *pcc = acpi_driver_data(d: device); |
839 | |
840 | switch (event) { |
841 | case HKEY_NOTIFY: |
842 | acpi_pcc_generate_keyinput(pcc); |
843 | break; |
844 | default: |
845 | /* nothing to do */ |
846 | break; |
847 | } |
848 | } |
849 | |
850 | static void pcc_optd_notify(acpi_handle handle, u32 event, void *data) |
851 | { |
852 | if (event != ACPI_NOTIFY_EJECT_REQUEST) |
853 | return; |
854 | |
855 | set_optd_power_state(0); |
856 | } |
857 | |
858 | static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node) |
859 | { |
860 | acpi_status status; |
861 | acpi_handle handle; |
862 | |
863 | status = acpi_get_handle(NULL, pathname: node, ret_handle: &handle); |
864 | |
865 | if (ACPI_SUCCESS(status)) { |
866 | status = acpi_install_notify_handler(device: handle, |
867 | ACPI_SYSTEM_NOTIFY, |
868 | handler: pcc_optd_notify, context: pcc); |
869 | if (ACPI_FAILURE(status)) |
870 | pr_err("Failed to register notify on %s\n" , node); |
871 | } else |
872 | return -ENODEV; |
873 | |
874 | return 0; |
875 | } |
876 | |
877 | static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node) |
878 | { |
879 | acpi_status status = AE_OK; |
880 | acpi_handle handle; |
881 | |
882 | status = acpi_get_handle(NULL, pathname: node, ret_handle: &handle); |
883 | |
884 | if (ACPI_SUCCESS(status)) { |
885 | status = acpi_remove_notify_handler(device: handle, |
886 | ACPI_SYSTEM_NOTIFY, |
887 | handler: pcc_optd_notify); |
888 | if (ACPI_FAILURE(status)) |
889 | pr_err("Error removing optd notify handler %s\n" , |
890 | node); |
891 | } |
892 | } |
893 | |
894 | static int acpi_pcc_init_input(struct pcc_acpi *pcc) |
895 | { |
896 | struct input_dev *input_dev; |
897 | int error; |
898 | |
899 | input_dev = input_allocate_device(); |
900 | if (!input_dev) |
901 | return -ENOMEM; |
902 | |
903 | input_dev->name = ACPI_PCC_DRIVER_NAME; |
904 | input_dev->phys = ACPI_PCC_INPUT_PHYS; |
905 | input_dev->id.bustype = BUS_HOST; |
906 | input_dev->id.vendor = 0x0001; |
907 | input_dev->id.product = 0x0001; |
908 | input_dev->id.version = 0x0100; |
909 | |
910 | error = sparse_keymap_setup(dev: input_dev, keymap: panasonic_keymap, NULL); |
911 | if (error) { |
912 | pr_err("Unable to setup input device keymap\n" ); |
913 | goto err_free_dev; |
914 | } |
915 | |
916 | error = input_register_device(input_dev); |
917 | if (error) { |
918 | pr_err("Unable to register input device\n" ); |
919 | goto err_free_dev; |
920 | } |
921 | |
922 | pcc->input_dev = input_dev; |
923 | return 0; |
924 | |
925 | err_free_dev: |
926 | input_free_device(dev: input_dev); |
927 | return error; |
928 | } |
929 | |
930 | /* kernel module interface */ |
931 | |
932 | #ifdef CONFIG_PM_SLEEP |
933 | static int acpi_pcc_hotkey_resume(struct device *dev) |
934 | { |
935 | struct pcc_acpi *pcc; |
936 | |
937 | if (!dev) |
938 | return -EINVAL; |
939 | |
940 | pcc = acpi_driver_data(to_acpi_device(dev)); |
941 | if (!pcc) |
942 | return -EINVAL; |
943 | |
944 | acpi_pcc_write_sset(pcc, func: SINF_MUTE, val: pcc->mute); |
945 | acpi_pcc_write_sset(pcc, func: SINF_ECO_MODE, val: pcc->eco_mode); |
946 | acpi_pcc_write_sset(pcc, func: SINF_STICKY_KEY, val: pcc->sticky_key); |
947 | acpi_pcc_write_sset(pcc, func: SINF_AC_CUR_BRIGHT, val: pcc->ac_brightness); |
948 | acpi_pcc_write_sset(pcc, func: SINF_DC_CUR_BRIGHT, val: pcc->dc_brightness); |
949 | acpi_pcc_write_sset(pcc, func: SINF_CUR_BRIGHT, val: pcc->current_brightness); |
950 | |
951 | return 0; |
952 | } |
953 | #endif |
954 | |
955 | static int acpi_pcc_hotkey_add(struct acpi_device *device) |
956 | { |
957 | struct backlight_properties props; |
958 | struct pcc_acpi *pcc; |
959 | int num_sifr, result; |
960 | |
961 | if (!device) |
962 | return -EINVAL; |
963 | |
964 | num_sifr = acpi_pcc_get_sqty(device); |
965 | |
966 | if (num_sifr < 0 || num_sifr > 255) { |
967 | pr_err("num_sifr out of range" ); |
968 | return -ENODEV; |
969 | } |
970 | |
971 | pcc = kzalloc(size: sizeof(struct pcc_acpi), GFP_KERNEL); |
972 | if (!pcc) { |
973 | pr_err("Couldn't allocate mem for pcc" ); |
974 | return -ENOMEM; |
975 | } |
976 | |
977 | pcc->sinf = kcalloc(n: num_sifr + 1, size: sizeof(u32), GFP_KERNEL); |
978 | if (!pcc->sinf) { |
979 | result = -ENOMEM; |
980 | goto out_hotkey; |
981 | } |
982 | |
983 | pcc->device = device; |
984 | pcc->handle = device->handle; |
985 | pcc->num_sifr = num_sifr; |
986 | device->driver_data = pcc; |
987 | strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); |
988 | strcpy(acpi_device_class(device), ACPI_PCC_CLASS); |
989 | |
990 | result = acpi_pcc_init_input(pcc); |
991 | if (result) { |
992 | pr_err("Error installing keyinput handler\n" ); |
993 | goto out_sinf; |
994 | } |
995 | |
996 | if (!acpi_pcc_retrieve_biosdata(pcc)) { |
997 | result = -EIO; |
998 | pr_err("Couldn't retrieve BIOS data\n" ); |
999 | goto out_input; |
1000 | } |
1001 | |
1002 | if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { |
1003 | /* initialize backlight */ |
1004 | memset(&props, 0, sizeof(struct backlight_properties)); |
1005 | props.type = BACKLIGHT_PLATFORM; |
1006 | props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT]; |
1007 | |
1008 | pcc->backlight = backlight_device_register(name: "panasonic" , NULL, devdata: pcc, |
1009 | ops: &pcc_backlight_ops, props: &props); |
1010 | if (IS_ERR(ptr: pcc->backlight)) { |
1011 | result = PTR_ERR(ptr: pcc->backlight); |
1012 | goto out_input; |
1013 | } |
1014 | |
1015 | /* read the initial brightness setting from the hardware */ |
1016 | pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; |
1017 | } |
1018 | |
1019 | /* Reset initial sticky key mode since the hardware register state is not consistent */ |
1020 | acpi_pcc_write_sset(pcc, func: SINF_STICKY_KEY, val: 0); |
1021 | pcc->sticky_key = 0; |
1022 | |
1023 | pcc->eco_mode = pcc->sinf[SINF_ECO_MODE]; |
1024 | pcc->mute = pcc->sinf[SINF_MUTE]; |
1025 | pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; |
1026 | pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT]; |
1027 | pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT]; |
1028 | |
1029 | /* add sysfs attributes */ |
1030 | result = sysfs_create_group(kobj: &device->dev.kobj, grp: &pcc_attr_group); |
1031 | if (result) |
1032 | goto out_backlight; |
1033 | |
1034 | /* optical drive initialization */ |
1035 | if (ACPI_SUCCESS(check_optd_present())) { |
1036 | pcc->platform = platform_device_register_simple(name: "panasonic" , |
1037 | PLATFORM_DEVID_NONE, NULL, num: 0); |
1038 | if (IS_ERR(ptr: pcc->platform)) { |
1039 | result = PTR_ERR(ptr: pcc->platform); |
1040 | goto out_backlight; |
1041 | } |
1042 | result = device_create_file(device: &pcc->platform->dev, |
1043 | entry: &dev_attr_cdpower); |
1044 | pcc_register_optd_notifier(pcc, node: "\\_SB.PCI0.EHCI.ERHB.OPTD" ); |
1045 | if (result) |
1046 | goto out_platform; |
1047 | } else { |
1048 | pcc->platform = NULL; |
1049 | } |
1050 | |
1051 | i8042_install_filter(filter: panasonic_i8042_filter); |
1052 | return 0; |
1053 | |
1054 | out_platform: |
1055 | platform_device_unregister(pcc->platform); |
1056 | out_backlight: |
1057 | backlight_device_unregister(bd: pcc->backlight); |
1058 | out_input: |
1059 | input_unregister_device(pcc->input_dev); |
1060 | out_sinf: |
1061 | kfree(objp: pcc->sinf); |
1062 | out_hotkey: |
1063 | kfree(objp: pcc); |
1064 | |
1065 | return result; |
1066 | } |
1067 | |
1068 | static void acpi_pcc_hotkey_remove(struct acpi_device *device) |
1069 | { |
1070 | struct pcc_acpi *pcc = acpi_driver_data(d: device); |
1071 | |
1072 | if (!device || !pcc) |
1073 | return; |
1074 | |
1075 | i8042_remove_filter(filter: panasonic_i8042_filter); |
1076 | |
1077 | if (pcc->platform) { |
1078 | device_remove_file(dev: &pcc->platform->dev, attr: &dev_attr_cdpower); |
1079 | platform_device_unregister(pcc->platform); |
1080 | } |
1081 | pcc_unregister_optd_notifier(pcc, node: "\\_SB.PCI0.EHCI.ERHB.OPTD" ); |
1082 | |
1083 | sysfs_remove_group(kobj: &device->dev.kobj, grp: &pcc_attr_group); |
1084 | |
1085 | backlight_device_unregister(bd: pcc->backlight); |
1086 | |
1087 | input_unregister_device(pcc->input_dev); |
1088 | |
1089 | kfree(objp: pcc->sinf); |
1090 | kfree(objp: pcc); |
1091 | } |
1092 | |
1093 | module_acpi_driver(acpi_pcc_driver); |
1094 | |