1 // SPDX-License-Identifier: GPL-2.0-only
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>
10 * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte
12 *---------------------------------------------------------------------------
15 * Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
16 * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
17 * drivers/misc/panasonic-laptop.c
19 * Jul.04, 2008 Harald Welte <laforge@gnumonks.org>
20 * -v0.94 replace /proc interface with device attributes
21 * support {set,get}keycode on th input device
23 * Jun.27, 2008 Harald Welte <laforge@gnumonks.org>
24 * -v0.92 merge with 2.6.26-rc6 input API changes
25 * remove broken <= 2.6.15 kernel support
26 * resolve all compiler warnings
27 * various coding style fixes (checkpatch.pl)
28 * add support for backlight api
29 * major code restructuring
31 * Dac.28, 2007 Harald Welte <laforge@gnumonks.org>
32 * -v0.91 merge with 2.6.24-rc6 ACPI changes
34 * Nov.04, 2006 Hiroshi Miura <miura@da-cha.org>
35 * -v0.9 remove warning about section reference.
37 * add /proc/acpi/pcc/brightness interface for HAL access
38 * merge dbronaugh's enhancement
39 * Aug.17, 2004 David Bronaugh (dbronaugh)
40 * - Added screen brightness setting interface
41 * Thanks to FreeBSD crew (acpi_panasonic.c)
42 * for the ideas I needed to accomplish it
44 * May.29, 2006 Hiroshi Miura <miura@da-cha.org>
45 * -v0.8.4 follow to change keyinput structure
46 * thanks Fabian Yamaguchi <fabs@cs.tu-berlin.de>,
47 * Jacob Bower <jacob.bower@ic.ac.uk> and
48 * Hiroshi Yokota for providing solutions.
50 * Oct.02, 2004 Hiroshi Miura <miura@da-cha.org>
51 * -v0.8.2 merge code of YOKOTA Hiroshi
52 * <yokota@netlab.is.tsukuba.ac.jp>.
53 * Add sticky key mode interface.
54 * Refactoring acpi_pcc_generate_keyinput().
56 * Sep.15, 2004 Hiroshi Miura <miura@da-cha.org>
57 * -v0.8 Generate key input event on input subsystem.
58 * This is based on yet another driver written by
61 * Sep.10, 2004 Hiroshi Miura <miura@da-cha.org>
62 * -v0.7 Change proc interface functions using seq_file
63 * facility as same as other ACPI drivers.
65 * Aug.28, 2004 Hiroshi Miura <miura@da-cha.org>
66 * -v0.6.4 Fix a silly error with status checking
68 * Aug.25, 2004 Hiroshi Miura <miura@da-cha.org>
69 * -v0.6.3 replace read_acpi_int by standard function
70 * acpi_evaluate_integer
71 * some clean up and make smart copyright notice.
72 * fix return value of pcc_acpi_get_key()
73 * fix checking return value of acpi_bus_register_driver()
75 * Aug.22, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
76 * -v0.6.2 Add check on ACPI data (num_sifr)
77 * Coding style cleanups, better error messages/handling
78 * Fixed an off-by-one error in memory allocation
80 * Aug.21, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
81 * -v0.6.1 Fix a silly error with status checking
83 * Aug.20, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
84 * - v0.6 Correct brightness controls to reflect reality
85 * based on information gleaned by Hiroshi Miura
86 * and discussions with Hiroshi Miura
88 * Aug.10, 2004 Hiroshi Miura <miura@da-cha.org>
89 * - v0.5 support LCD brightness control
90 * based on the disclosed information by MEI.
92 * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org>
93 * - v0.4 first post version
94 * add function to retrive SIFR
96 * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org>
97 * - v0.3 get proper status of hotkey
99 * Jul.22, 2004 Hiroshi Miura <miura@da-cha.org>
100 * - v0.2 add HotKey handler
102 * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org>
103 * - v0.1 start from toshiba_acpi driver written by John Belmonte
106 #include <linux/kernel.h>
107 #include <linux/module.h>
108 #include <linux/init.h>
109 #include <linux/types.h>
110 #include <linux/backlight.h>
111 #include <linux/ctype.h>
112 #include <linux/seq_file.h>
113 #include <linux/uaccess.h>
114 #include <linux/slab.h>
115 #include <linux/acpi.h>
116 #include <linux/input.h>
117 #include <linux/input/sparse-keymap.h>
119 #ifndef ACPI_HOTKEY_COMPONENT
120 #define ACPI_HOTKEY_COMPONENT 0x10000000
123 #define _COMPONENT ACPI_HOTKEY_COMPONENT
125 MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
126 MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
127 MODULE_LICENSE("GPL");
129 #define LOGPREFIX "pcc_acpi: "
131 /* Define ACPI PATHs */
132 /* Lets note hotkeys */
133 #define METHOD_HKEY_QUERY "HINF"
134 #define METHOD_HKEY_SQTY "SQTY"
135 #define METHOD_HKEY_SINF "SINF"
136 #define METHOD_HKEY_SSET "SSET"
137 #define HKEY_NOTIFY 0x80
139 #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
140 #define ACPI_PCC_DEVICE_NAME "Hotkey"
141 #define ACPI_PCC_CLASS "pcc"
143 #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
145 /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
146 ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
148 enum SINF_BITS
{ SINF_NUM_BATTERIES
= 0,
159 SINF_STICKY_KEY
= 0x80,
161 /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
163 static int acpi_pcc_hotkey_add(struct acpi_device
*device
);
164 static int acpi_pcc_hotkey_remove(struct acpi_device
*device
);
165 static void acpi_pcc_hotkey_notify(struct acpi_device
*device
, u32 event
);
167 static const struct acpi_device_id pcc_device_ids
[] = {
174 MODULE_DEVICE_TABLE(acpi
, pcc_device_ids
);
176 #ifdef CONFIG_PM_SLEEP
177 static int acpi_pcc_hotkey_resume(struct device
*dev
);
179 static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm
, NULL
, acpi_pcc_hotkey_resume
);
181 static struct acpi_driver acpi_pcc_driver
= {
182 .name
= ACPI_PCC_DRIVER_NAME
,
183 .class = ACPI_PCC_CLASS
,
184 .ids
= pcc_device_ids
,
186 .add
= acpi_pcc_hotkey_add
,
187 .remove
= acpi_pcc_hotkey_remove
,
188 .notify
= acpi_pcc_hotkey_notify
,
190 .drv
.pm
= &acpi_pcc_hotkey_pm
,
193 static const struct key_entry panasonic_keymap
[] = {
194 { KE_KEY
, 0, { KEY_RESERVED
} },
195 { KE_KEY
, 1, { KEY_BRIGHTNESSDOWN
} },
196 { KE_KEY
, 2, { KEY_BRIGHTNESSUP
} },
197 { KE_KEY
, 3, { KEY_DISPLAYTOGGLE
} },
198 { KE_KEY
, 4, { KEY_MUTE
} },
199 { KE_KEY
, 5, { KEY_VOLUMEDOWN
} },
200 { KE_KEY
, 6, { KEY_VOLUMEUP
} },
201 { KE_KEY
, 7, { KEY_SLEEP
} },
202 { KE_KEY
, 8, { KEY_PROG1
} }, /* Change CPU boost */
203 { KE_KEY
, 9, { KEY_BATTERY
} },
204 { KE_KEY
, 10, { KEY_SUSPEND
} },
210 unsigned long num_sifr
;
213 struct acpi_device
*device
;
214 struct input_dev
*input_dev
;
215 struct backlight_device
*backlight
;
218 /* method access functions */
219 static int acpi_pcc_write_sset(struct pcc_acpi
*pcc
, int func
, int val
)
221 union acpi_object in_objs
[] = {
222 { .integer
.type
= ACPI_TYPE_INTEGER
,
223 .integer
.value
= func
, },
224 { .integer
.type
= ACPI_TYPE_INTEGER
,
225 .integer
.value
= val
, },
227 struct acpi_object_list params
= {
228 .count
= ARRAY_SIZE(in_objs
),
231 acpi_status status
= AE_OK
;
233 status
= acpi_evaluate_object(pcc
->handle
, METHOD_HKEY_SSET
,
236 return (status
== AE_OK
) ? 0 : -EIO
;
239 static inline int acpi_pcc_get_sqty(struct acpi_device
*device
)
241 unsigned long long s
;
244 status
= acpi_evaluate_integer(device
->handle
, METHOD_HKEY_SQTY
,
246 if (ACPI_SUCCESS(status
))
249 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
250 "evaluation error HKEY.SQTY\n"));
255 static int acpi_pcc_retrieve_biosdata(struct pcc_acpi
*pcc
)
258 struct acpi_buffer buffer
= {ACPI_ALLOCATE_BUFFER
, NULL
};
259 union acpi_object
*hkey
= NULL
;
262 status
= acpi_evaluate_object(pcc
->handle
, METHOD_HKEY_SINF
, NULL
,
264 if (ACPI_FAILURE(status
)) {
265 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
266 "evaluation error HKEY.SINF\n"));
270 hkey
= buffer
.pointer
;
271 if (!hkey
|| (hkey
->type
!= ACPI_TYPE_PACKAGE
)) {
272 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
, "Invalid HKEY.SINF\n"));
277 if (pcc
->num_sifr
< hkey
->package
.count
) {
278 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
279 "SQTY reports bad SINF length\n"));
284 for (i
= 0; i
< hkey
->package
.count
; i
++) {
285 union acpi_object
*element
= &(hkey
->package
.elements
[i
]);
286 if (likely(element
->type
== ACPI_TYPE_INTEGER
)) {
287 pcc
->sinf
[i
] = element
->integer
.value
;
289 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
290 "Invalid HKEY.SINF data\n"));
292 pcc
->sinf
[hkey
->package
.count
] = -1;
295 kfree(buffer
.pointer
);
296 return status
== AE_OK
;
299 /* backlight API interface functions */
301 /* This driver currently treats AC and DC brightness identical,
302 * since we don't need to invent an interface to the core ACPI
303 * logic to receive events in case a power supply is plugged in
306 static int bl_get(struct backlight_device
*bd
)
308 struct pcc_acpi
*pcc
= bl_get_data(bd
);
310 if (!acpi_pcc_retrieve_biosdata(pcc
))
313 return pcc
->sinf
[SINF_AC_CUR_BRIGHT
];
316 static int bl_set_status(struct backlight_device
*bd
)
318 struct pcc_acpi
*pcc
= bl_get_data(bd
);
319 int bright
= bd
->props
.brightness
;
322 if (!acpi_pcc_retrieve_biosdata(pcc
))
325 if (bright
< pcc
->sinf
[SINF_AC_MIN_BRIGHT
])
326 bright
= pcc
->sinf
[SINF_AC_MIN_BRIGHT
];
328 if (bright
< pcc
->sinf
[SINF_DC_MIN_BRIGHT
])
329 bright
= pcc
->sinf
[SINF_DC_MIN_BRIGHT
];
331 if (bright
< pcc
->sinf
[SINF_AC_MIN_BRIGHT
] ||
332 bright
> pcc
->sinf
[SINF_AC_MAX_BRIGHT
])
335 rc
= acpi_pcc_write_sset(pcc
, SINF_AC_CUR_BRIGHT
, bright
);
339 return acpi_pcc_write_sset(pcc
, SINF_DC_CUR_BRIGHT
, bright
);
342 static const struct backlight_ops pcc_backlight_ops
= {
343 .get_brightness
= bl_get
,
344 .update_status
= bl_set_status
,
348 /* sysfs user interface functions */
350 static ssize_t
show_numbatt(struct device
*dev
, struct device_attribute
*attr
,
353 struct acpi_device
*acpi
= to_acpi_device(dev
);
354 struct pcc_acpi
*pcc
= acpi_driver_data(acpi
);
356 if (!acpi_pcc_retrieve_biosdata(pcc
))
359 return snprintf(buf
, PAGE_SIZE
, "%u\n", pcc
->sinf
[SINF_NUM_BATTERIES
]);
362 static ssize_t
show_lcdtype(struct device
*dev
, struct device_attribute
*attr
,
365 struct acpi_device
*acpi
= to_acpi_device(dev
);
366 struct pcc_acpi
*pcc
= acpi_driver_data(acpi
);
368 if (!acpi_pcc_retrieve_biosdata(pcc
))
371 return snprintf(buf
, PAGE_SIZE
, "%u\n", pcc
->sinf
[SINF_LCD_TYPE
]);
374 static ssize_t
show_mute(struct device
*dev
, struct device_attribute
*attr
,
377 struct acpi_device
*acpi
= to_acpi_device(dev
);
378 struct pcc_acpi
*pcc
= acpi_driver_data(acpi
);
380 if (!acpi_pcc_retrieve_biosdata(pcc
))
383 return snprintf(buf
, PAGE_SIZE
, "%u\n", pcc
->sinf
[SINF_MUTE
]);
386 static ssize_t
show_sticky(struct device
*dev
, struct device_attribute
*attr
,
389 struct acpi_device
*acpi
= to_acpi_device(dev
);
390 struct pcc_acpi
*pcc
= acpi_driver_data(acpi
);
392 if (!acpi_pcc_retrieve_biosdata(pcc
))
395 return snprintf(buf
, PAGE_SIZE
, "%u\n", pcc
->sinf
[SINF_STICKY_KEY
]);
398 static ssize_t
set_sticky(struct device
*dev
, struct device_attribute
*attr
,
399 const char *buf
, size_t count
)
401 struct acpi_device
*acpi
= to_acpi_device(dev
);
402 struct pcc_acpi
*pcc
= acpi_driver_data(acpi
);
405 if (count
&& sscanf(buf
, "%i", &val
) == 1 &&
406 (val
== 0 || val
== 1)) {
407 acpi_pcc_write_sset(pcc
, SINF_STICKY_KEY
, val
);
408 pcc
->sticky_mode
= val
;
414 static DEVICE_ATTR(numbatt
, S_IRUGO
, show_numbatt
, NULL
);
415 static DEVICE_ATTR(lcdtype
, S_IRUGO
, show_lcdtype
, NULL
);
416 static DEVICE_ATTR(mute
, S_IRUGO
, show_mute
, NULL
);
417 static DEVICE_ATTR(sticky_key
, S_IRUGO
| S_IWUSR
, show_sticky
, set_sticky
);
419 static struct attribute
*pcc_sysfs_entries
[] = {
420 &dev_attr_numbatt
.attr
,
421 &dev_attr_lcdtype
.attr
,
423 &dev_attr_sticky_key
.attr
,
427 static const struct attribute_group pcc_attr_group
= {
428 .name
= NULL
, /* put in device directory */
429 .attrs
= pcc_sysfs_entries
,
433 /* hotkey input device driver */
435 static int sleep_keydown_seen
;
436 static void acpi_pcc_generate_keyinput(struct pcc_acpi
*pcc
)
438 struct input_dev
*hotk_input_dev
= pcc
->input_dev
;
440 unsigned long long result
;
442 rc
= acpi_evaluate_integer(pcc
->handle
, METHOD_HKEY_QUERY
,
444 if (ACPI_FAILURE(rc
)) {
445 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
446 "error getting hotkey status\n"));
450 /* hack: some firmware sends no key down for sleep / hibernate */
451 if ((result
& 0xf) == 0x7 || (result
& 0xf) == 0xa) {
453 sleep_keydown_seen
= 1;
454 if (!sleep_keydown_seen
)
455 sparse_keymap_report_event(hotk_input_dev
,
456 result
& 0xf, 0x80, false);
459 if (!sparse_keymap_report_event(hotk_input_dev
,
460 result
& 0xf, result
& 0x80, false))
461 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
462 "Unknown hotkey event: %d\n", result
));
465 static void acpi_pcc_hotkey_notify(struct acpi_device
*device
, u32 event
)
467 struct pcc_acpi
*pcc
= acpi_driver_data(device
);
471 acpi_pcc_generate_keyinput(pcc
);
479 static int acpi_pcc_init_input(struct pcc_acpi
*pcc
)
481 struct input_dev
*input_dev
;
484 input_dev
= input_allocate_device();
488 input_dev
->name
= ACPI_PCC_DRIVER_NAME
;
489 input_dev
->phys
= ACPI_PCC_INPUT_PHYS
;
490 input_dev
->id
.bustype
= BUS_HOST
;
491 input_dev
->id
.vendor
= 0x0001;
492 input_dev
->id
.product
= 0x0001;
493 input_dev
->id
.version
= 0x0100;
495 error
= sparse_keymap_setup(input_dev
, panasonic_keymap
, NULL
);
497 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
498 "Unable to setup input device keymap\n"));
502 error
= input_register_device(input_dev
);
504 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
505 "Unable to register input device\n"));
509 pcc
->input_dev
= input_dev
;
513 input_free_device(input_dev
);
517 /* kernel module interface */
519 #ifdef CONFIG_PM_SLEEP
520 static int acpi_pcc_hotkey_resume(struct device
*dev
)
522 struct pcc_acpi
*pcc
;
527 pcc
= acpi_driver_data(to_acpi_device(dev
));
531 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
, "Sticky mode restore: %d\n",
534 return acpi_pcc_write_sset(pcc
, SINF_STICKY_KEY
, pcc
->sticky_mode
);
538 static int acpi_pcc_hotkey_add(struct acpi_device
*device
)
540 struct backlight_properties props
;
541 struct pcc_acpi
*pcc
;
542 int num_sifr
, result
;
547 num_sifr
= acpi_pcc_get_sqty(device
);
549 if (num_sifr
< 0 || num_sifr
> 255) {
550 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
, "num_sifr out of range"));
554 pcc
= kzalloc(sizeof(struct pcc_acpi
), GFP_KERNEL
);
556 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
557 "Couldn't allocate mem for pcc"));
561 pcc
->sinf
= kcalloc(num_sifr
+ 1, sizeof(u32
), GFP_KERNEL
);
567 pcc
->device
= device
;
568 pcc
->handle
= device
->handle
;
569 pcc
->num_sifr
= num_sifr
;
570 device
->driver_data
= pcc
;
571 strcpy(acpi_device_name(device
), ACPI_PCC_DEVICE_NAME
);
572 strcpy(acpi_device_class(device
), ACPI_PCC_CLASS
);
574 result
= acpi_pcc_init_input(pcc
);
576 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
577 "Error installing keyinput handler\n"));
581 if (!acpi_pcc_retrieve_biosdata(pcc
)) {
582 ACPI_DEBUG_PRINT((ACPI_DB_ERROR
,
583 "Couldn't retrieve BIOS data\n"));
587 /* initialize backlight */
588 memset(&props
, 0, sizeof(struct backlight_properties
));
589 props
.type
= BACKLIGHT_PLATFORM
;
590 props
.max_brightness
= pcc
->sinf
[SINF_AC_MAX_BRIGHT
];
591 pcc
->backlight
= backlight_device_register("panasonic", NULL
, pcc
,
592 &pcc_backlight_ops
, &props
);
593 if (IS_ERR(pcc
->backlight
)) {
594 result
= PTR_ERR(pcc
->backlight
);
598 /* read the initial brightness setting from the hardware */
599 pcc
->backlight
->props
.brightness
= pcc
->sinf
[SINF_AC_CUR_BRIGHT
];
601 /* read the initial sticky key mode from the hardware */
602 pcc
->sticky_mode
= pcc
->sinf
[SINF_STICKY_KEY
];
604 /* add sysfs attributes */
605 result
= sysfs_create_group(&device
->dev
.kobj
, &pcc_attr_group
);
612 backlight_device_unregister(pcc
->backlight
);
614 input_unregister_device(pcc
->input_dev
);
623 static int acpi_pcc_hotkey_remove(struct acpi_device
*device
)
625 struct pcc_acpi
*pcc
= acpi_driver_data(device
);
630 sysfs_remove_group(&device
->dev
.kobj
, &pcc_attr_group
);
632 backlight_device_unregister(pcc
->backlight
);
634 input_unregister_device(pcc
->input_dev
);
642 module_acpi_driver(acpi_pcc_driver
);