aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2008-09-23 11:46:57 -0400
committerLen Brown <len.brown@intel.com>2008-09-24 04:09:00 -0400
commit709ee531c153038d81b30649b9eeed3c44a4d5cc (patch)
treef1202abe42a374543d19a159a741d14b77d51ce7
parent72d31053f62c4bc464c2783974926969614a8649 (diff)
panasonic-laptop: add Panasonic Let's Note laptop extras driver v0.94
This is a driver for ACPI extras such as hotkeys and backlight brightness control on various Panasonic "Let's Note" series laptop computers. It exports the backlight via the backlight class device API, and the hotkeys as input event device. Some more esoteric items like number of installed batteries are exported via sysfs device attributes. Hotkey events also generate old-style ACPI enents through /proc/acpi/event to interoperate with current versions of acpid. Signed-off-by: Harald Welte <laforge@gnumonks.org> Acked-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br> Acked-by: Matthew Garrett <mjg@redhat.com> Signed-off-by: Len Brown <len.brown@intel.com>
-rw-r--r--MAINTAINERS5
-rw-r--r--drivers/misc/Kconfig11
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/panasonic-laptop.c767
4 files changed, 784 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index cad81a24e832..fa3579382ed2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3148,6 +3148,11 @@ M: olof@lixom.net
3148L: i2c@lm-sensors.org 3148L: i2c@lm-sensors.org
3149S: Maintained 3149S: Maintained
3150 3150
3151PANASONIC LAPTOP ACPI EXTRAS DRIVER
3152P: Harald Welte
3153M: laforge@gnumonks.org
3154S: Maintained
3155
3151PARALLEL PORT SUPPORT 3156PARALLEL PORT SUPPORT
3152L: linux-parport@lists.infradead.org (subscribers-only) 3157L: linux-parport@lists.infradead.org (subscribers-only)
3153S: Orphan 3158S: Orphan
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index a726f3b01a6b..4ed83575393c 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -245,6 +245,17 @@ config MSI_LAPTOP
245 245
246 If you have an MSI S270 laptop, say Y or M here. 246 If you have an MSI S270 laptop, say Y or M here.
247 247
248config PANASONIC_LAPTOP
249 tristate "Panasonic Laptop Extras"
250 depends on X86 && INPUT
251 depends on BACKLIGHT_CLASS_DEVICE
252 ---help---
253 This driver adds support for access to backlight control and hotkeys
254 on Panasonic Let's Note laptops.
255
256 If you have a Panasonic Let's note laptop (such as the R1(N variant),
257 R2, R3, R5, T2, W2 and Y2 series), say Y.
258
248config COMPAL_LAPTOP 259config COMPAL_LAPTOP
249 tristate "Compal Laptop Extras" 260 tristate "Compal Laptop Extras"
250 depends on X86 261 depends on X86
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c6c13f60b452..909e2468cdc9 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_SGI_IOC4) += ioc4.o
23obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o 23obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
24obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o 24obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
25obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o 25obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
26obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o
26obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o 27obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
27obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o 28obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
28obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o 29obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
diff --git a/drivers/misc/panasonic-laptop.c b/drivers/misc/panasonic-laptop.c
new file mode 100644
index 000000000000..9aecda5a399c
--- /dev/null
+++ b/drivers/misc/panasonic-laptop.c
@@ -0,0 +1,767 @@
1/*
2 * Panasonic HotKey and LCD brightness control driver
3 * (C) 2004 Hiroshi Miura <miura@da-cha.org>
4 * (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/
5 * (C) YOKOTA Hiroshi <yokota (at) netlab. is. tsukuba. ac. jp>
6 * (C) 2004 David Bronaugh <dbronaugh>
7 * (C) 2006-2008 Harald Welte <laforge@gnumonks.org>
8 *
9 * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * publicshed by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 *---------------------------------------------------------------------------
25 *
26 * ChangeLog:
27 * Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
28 * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
29 * drivers/misc/panasonic-laptop.c
30 *
31 * Jul.04, 2008 Harald Welte <laforge@gnumonks.org>
32 * -v0.94 replace /proc interface with device attributes
33 * support {set,get}keycode on th input device
34 *
35 * Jun.27, 2008 Harald Welte <laforge@gnumonks.org>
36 * -v0.92 merge with 2.6.26-rc6 input API changes
37 * remove broken <= 2.6.15 kernel support
38 * resolve all compiler warnings
39 * various coding style fixes (checkpatch.pl)
40 * add support for backlight api
41 * major code restructuring
42 *
43 * Dac.28, 2007 Harald Welte <laforge@gnumonks.org>
44 * -v0.91 merge with 2.6.24-rc6 ACPI changes
45 *
46 * Nov.04, 2006 Hiroshi Miura <miura@da-cha.org>
47 * -v0.9 remove warning about section reference.
48 * remove acpi_os_free
49 * add /proc/acpi/pcc/brightness interface for HAL access
50 * merge dbronaugh's enhancement
51 * Aug.17, 2004 David Bronaugh (dbronaugh)
52 * - Added screen brightness setting interface
53 * Thanks to FreeBSD crew (acpi_panasonic.c)
54 * for the ideas I needed to accomplish it
55 *
56 * May.29, 2006 Hiroshi Miura <miura@da-cha.org>
57 * -v0.8.4 follow to change keyinput structure
58 * thanks Fabian Yamaguchi <fabs@cs.tu-berlin.de>,
59 * Jacob Bower <jacob.bower@ic.ac.uk> and
60 * Hiroshi Yokota for providing solutions.
61 *
62 * Oct.02, 2004 Hiroshi Miura <miura@da-cha.org>
63 * -v0.8.2 merge code of YOKOTA Hiroshi
64 * <yokota@netlab.is.tsukuba.ac.jp>.
65 * Add sticky key mode interface.
66 * Refactoring acpi_pcc_generate_keyinput().
67 *
68 * Sep.15, 2004 Hiroshi Miura <miura@da-cha.org>
69 * -v0.8 Generate key input event on input subsystem.
70 * This is based on yet another driver written by
71 * Ryuta Nakanishi.
72 *
73 * Sep.10, 2004 Hiroshi Miura <miura@da-cha.org>
74 * -v0.7 Change proc interface functions using seq_file
75 * facility as same as other ACPI drivers.
76 *
77 * Aug.28, 2004 Hiroshi Miura <miura@da-cha.org>
78 * -v0.6.4 Fix a silly error with status checking
79 *
80 * Aug.25, 2004 Hiroshi Miura <miura@da-cha.org>
81 * -v0.6.3 replace read_acpi_int by standard function
82 * acpi_evaluate_integer
83 * some clean up and make smart copyright notice.
84 * fix return value of pcc_acpi_get_key()
85 * fix checking return value of acpi_bus_register_driver()
86 *
87 * Aug.22, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
88 * -v0.6.2 Add check on ACPI data (num_sifr)
89 * Coding style cleanups, better error messages/handling
90 * Fixed an off-by-one error in memory allocation
91 *
92 * Aug.21, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
93 * -v0.6.1 Fix a silly error with status checking
94 *
95 * Aug.20, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
96 * - v0.6 Correct brightness controls to reflect reality
97 * based on information gleaned by Hiroshi Miura
98 * and discussions with Hiroshi Miura
99 *
100 * Aug.10, 2004 Hiroshi Miura <miura@da-cha.org>
101 * - v0.5 support LCD brightness control
102 * based on the disclosed information by MEI.
103 *
104 * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org>
105 * - v0.4 first post version
106 * add function to retrive SIFR
107 *
108 * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org>
109 * - v0.3 get proper status of hotkey
110 *
111 * Jul.22, 2004 Hiroshi Miura <miura@da-cha.org>
112 * - v0.2 add HotKey handler
113 *
114 * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org>
115 * - v0.1 start from toshiba_acpi driver written by John Belmonte
116 *
117 */
118
119#include <linux/version.h>
120#include <linux/kernel.h>
121#include <linux/module.h>
122#include <linux/init.h>
123#include <linux/types.h>
124#include <linux/backlight.h>
125#include <linux/ctype.h>
126#include <linux/seq_file.h>
127#include <linux/uaccess.h>
128#include <acpi/acpi_bus.h>
129#include <acpi/acpi_drivers.h>
130#include <linux/input.h>
131
132
133#ifndef ACPI_HOTKEY_COMPONENT
134#define ACPI_HOTKEY_COMPONENT 0x10000000
135#endif
136
137#define _COMPONENT ACPI_HOTKEY_COMPONENT
138
139MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
140MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
141MODULE_LICENSE("GPL");
142
143#define LOGPREFIX "pcc_acpi: "
144
145/* Define ACPI PATHs */
146/* Lets note hotkeys */
147#define METHOD_HKEY_QUERY "HINF"
148#define METHOD_HKEY_SQTY "SQTY"
149#define METHOD_HKEY_SINF "SINF"
150#define METHOD_HKEY_SSET "SSET"
151#define HKEY_NOTIFY 0x80
152
153#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
154#define ACPI_PCC_DEVICE_NAME "Hotkey"
155#define ACPI_PCC_CLASS "pcc"
156
157#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
158
159/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
160 ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
161*/
162enum SINF_BITS { SINF_NUM_BATTERIES = 0,
163 SINF_LCD_TYPE,
164 SINF_AC_MAX_BRIGHT,
165 SINF_AC_MIN_BRIGHT,
166 SINF_AC_CUR_BRIGHT,
167 SINF_DC_MAX_BRIGHT,
168 SINF_DC_MIN_BRIGHT,
169 SINF_DC_CUR_BRIGHT,
170 SINF_MUTE,
171 SINF_RESERVED,
172 SINF_ENV_STATE,
173 SINF_STICKY_KEY = 0x80,
174 };
175/* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
176
177static int acpi_pcc_hotkey_add(struct acpi_device *device);
178static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type);
179static int acpi_pcc_hotkey_resume(struct acpi_device *device);
180
181static const struct acpi_device_id pcc_device_ids[] = {
182 { "MAT0012", 0},
183 { "MAT0013", 0},
184 { "MAT0018", 0},
185 { "MAT0019", 0},
186 { "", 0},
187};
188
189static struct acpi_driver acpi_pcc_driver = {
190 .name = ACPI_PCC_DRIVER_NAME,
191 .class = ACPI_PCC_CLASS,
192 .ids = pcc_device_ids,
193 .ops = {
194 .add = acpi_pcc_hotkey_add,
195 .remove = acpi_pcc_hotkey_remove,
196 .resume = acpi_pcc_hotkey_resume,
197 },
198};
199
200#define KEYMAP_SIZE 11
201static const int initial_keymap[KEYMAP_SIZE] = {
202 /* 0 */ KEY_RESERVED,
203 /* 1 */ KEY_BRIGHTNESSDOWN,
204 /* 2 */ KEY_BRIGHTNESSUP,
205 /* 3 */ KEY_DISPLAYTOGGLE,
206 /* 4 */ KEY_MUTE,
207 /* 5 */ KEY_VOLUMEDOWN,
208 /* 6 */ KEY_VOLUMEUP,
209 /* 7 */ KEY_SLEEP,
210 /* 8 */ KEY_PROG1, /* Change CPU boost */
211 /* 9 */ KEY_BATTERY,
212 /* 10 */ KEY_SUSPEND,
213};
214
215struct pcc_acpi {
216 acpi_handle handle;
217 unsigned long num_sifr;
218 int sticky_mode;
219 u32 *sinf;
220 struct acpi_device *device;
221 struct input_dev *input_dev;
222 struct backlight_device *backlight;
223 int keymap[KEYMAP_SIZE];
224};
225
226struct pcc_keyinput {
227 struct acpi_hotkey *hotkey;
228};
229
230/* method access functions */
231static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val)
232{
233 union acpi_object in_objs[] = {
234 { .integer.type = ACPI_TYPE_INTEGER,
235 .integer.value = func, },
236 { .integer.type = ACPI_TYPE_INTEGER,
237 .integer.value = val, },
238 };
239 struct acpi_object_list params = {
240 .count = ARRAY_SIZE(in_objs),
241 .pointer = in_objs,
242 };
243 acpi_status status = AE_OK;
244
245 ACPI_FUNCTION_TRACE("acpi_pcc_write_sset");
246
247 status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET,
248 &params, NULL);
249
250 return status == AE_OK;
251}
252
253static inline int acpi_pcc_get_sqty(struct acpi_device *device)
254{
255 unsigned long s;
256 acpi_status status;
257
258 ACPI_FUNCTION_TRACE("acpi_pcc_get_sqty");
259
260 status = acpi_evaluate_integer(device->handle, METHOD_HKEY_SQTY,
261 NULL, &s);
262 if (ACPI_SUCCESS(status))
263 return s;
264 else {
265 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
266 "evaluation error HKEY.SQTY\n"));
267 return -EINVAL;
268 }
269}
270
271static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc, u32 *sinf)
272{
273 acpi_status status;
274 struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
275 union acpi_object *hkey = NULL;
276 int i;
277
278 ACPI_FUNCTION_TRACE("acpi_pcc_retrieve_biosdata");
279
280 status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, 0,
281 &buffer);
282 if (ACPI_FAILURE(status)) {
283 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
284 "evaluation error HKEY.SINF\n"));
285 return 0;
286 }
287
288 hkey = buffer.pointer;
289 if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
290 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
291 goto end;
292 }
293
294 if (pcc->num_sifr < hkey->package.count) {
295 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
296 "SQTY reports bad SINF length\n"));
297 status = AE_ERROR;
298 goto end;
299 }
300
301 for (i = 0; i < hkey->package.count; i++) {
302 union acpi_object *element = &(hkey->package.elements[i]);
303 if (likely(element->type == ACPI_TYPE_INTEGER)) {
304 sinf[i] = element->integer.value;
305 } else
306 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
307 "Invalid HKEY.SINF data\n"));
308 }
309 sinf[hkey->package.count] = -1;
310
311end:
312 kfree(buffer.pointer);
313 return status == AE_OK;
314}
315
316/* backlight API interface functions */
317
318/* This driver currently treats AC and DC brightness identical,
319 * since we don't need to invent an interface to the core ACPI
320 * logic to receive events in case a power supply is plugged in
321 * or removed */
322
323static int bl_get(struct backlight_device *bd)
324{
325 struct pcc_acpi *pcc = bl_get_data(bd);
326
327 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
328 return -EIO;
329
330 return pcc->sinf[SINF_AC_CUR_BRIGHT];
331}
332
333static int bl_set_status(struct backlight_device *bd)
334{
335 struct pcc_acpi *pcc = bl_get_data(bd);
336 int bright = bd->props.brightness;
337 int rc;
338
339 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
340 return -EIO;
341
342 if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT])
343 bright = pcc->sinf[SINF_AC_MIN_BRIGHT];
344
345 if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT])
346 bright = pcc->sinf[SINF_DC_MIN_BRIGHT];
347
348 if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] ||
349 bright > pcc->sinf[SINF_AC_MAX_BRIGHT])
350 return -EINVAL;
351
352 rc = acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, bright);
353 if (rc < 0)
354 return rc;
355
356 return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright);
357}
358
359static struct backlight_ops pcc_backlight_ops = {
360 .get_brightness = bl_get,
361 .update_status = bl_set_status,
362};
363
364
365/* sysfs user interface functions */
366
367static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
368 char *buf)
369{
370 struct acpi_device *acpi = to_acpi_device(dev);
371 struct pcc_acpi *pcc = acpi_driver_data(acpi);
372
373 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
374 return -EIO;
375
376 return sprintf(buf, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
377}
378
379static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
380 char *buf)
381{
382 struct acpi_device *acpi = to_acpi_device(dev);
383 struct pcc_acpi *pcc = acpi_driver_data(acpi);
384
385 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
386 return -EIO;
387
388 return sprintf(buf, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
389}
390
391static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
392 char *buf)
393{
394 struct acpi_device *acpi = to_acpi_device(dev);
395 struct pcc_acpi *pcc = acpi_driver_data(acpi);
396
397 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
398 return -EIO;
399
400 return sprintf(buf, "%u\n", pcc->sinf[SINF_MUTE]);
401}
402
403static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
404 char *buf)
405{
406 struct acpi_device *acpi = to_acpi_device(dev);
407 struct pcc_acpi *pcc = acpi_driver_data(acpi);
408
409 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
410 return -EIO;
411
412 return sprintf(buf, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
413}
414
415static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
416 const char *buf, size_t count)
417{
418 struct acpi_device *acpi = to_acpi_device(dev);
419 struct pcc_acpi *pcc = acpi_driver_data(acpi);
420 int val;
421
422 if (count && sscanf(buf, "%i", &val) == 1 &&
423 (val == 0 || val == 1)) {
424 acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val);
425 pcc->sticky_mode = val;
426 }
427
428 return count;
429}
430
431static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
432static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
433static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
434static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
435
436static struct attribute *pcc_sysfs_entries[] = {
437 &dev_attr_numbatt.attr,
438 &dev_attr_lcdtype.attr,
439 &dev_attr_mute.attr,
440 &dev_attr_sticky_key.attr,
441 NULL,
442};
443
444static struct attribute_group pcc_attr_group = {
445 .name = NULL, /* put in device directory */
446 .attrs = pcc_sysfs_entries,
447};
448
449
450/* hotkey input device driver */
451
452static int pcc_getkeycode(struct input_dev *dev, int scancode, int *keycode)
453{
454 struct pcc_acpi *pcc = input_get_drvdata(dev);
455
456 if (scancode >= ARRAY_SIZE(pcc->keymap))
457 return -EINVAL;
458
459 *keycode = pcc->keymap[scancode];
460
461 return 0;
462}
463
464static int keymap_get_by_keycode(struct pcc_acpi *pcc, int keycode)
465{
466 int i;
467
468 for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++) {
469 if (pcc->keymap[i] == keycode)
470 return i+1;
471 }
472
473 return 0;
474}
475
476static int pcc_setkeycode(struct input_dev *dev, int scancode, int keycode)
477{
478 struct pcc_acpi *pcc = input_get_drvdata(dev);
479 int oldkeycode;
480
481 if (scancode >= ARRAY_SIZE(pcc->keymap))
482 return -EINVAL;
483
484 if (keycode < 0 || keycode > KEY_MAX)
485 return -EINVAL;
486
487 oldkeycode = pcc->keymap[scancode];
488 pcc->keymap[scancode] = keycode;
489
490 set_bit(keycode, dev->keybit);
491
492 if (!keymap_get_by_keycode(pcc, oldkeycode))
493 clear_bit(oldkeycode, dev->keybit);
494
495 return 0;
496}
497
498static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
499{
500 struct input_dev *hotk_input_dev = pcc->input_dev;
501 int rc;
502 int key_code, hkey_num;
503 unsigned long result;
504
505 ACPI_FUNCTION_TRACE("acpi_pcc_generate_keyinput");
506
507 rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
508 NULL, &result);
509 if (!ACPI_SUCCESS(rc)) {
510 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
511 "error getting hotkey status\n"));
512 return;
513 }
514
515 acpi_bus_generate_proc_event(pcc->device, HKEY_NOTIFY, result);
516
517 hkey_num = result & 0xf;
518
519 if (hkey_num < 0 || hkey_num > ARRAY_SIZE(pcc->keymap)) {
520 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
521 "hotkey number out of range: %d\n",
522 hkey_num));
523 return;
524 }
525
526 key_code = pcc->keymap[hkey_num];
527
528 if (key_code != KEY_RESERVED) {
529 int pushed = (result & 0x80) ? TRUE : FALSE;
530
531 input_report_key(hotk_input_dev, key_code, pushed);
532 input_sync(hotk_input_dev);
533 }
534
535 return;
536}
537
538static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data)
539{
540 struct pcc_acpi *pcc = (struct pcc_acpi *) data;
541
542 ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_notify");
543
544 switch (event) {
545 case HKEY_NOTIFY:
546 acpi_pcc_generate_keyinput(pcc);
547 break;
548 default:
549 /* nothing to do */
550 break;
551 }
552}
553
554static int acpi_pcc_init_input(struct pcc_acpi *pcc)
555{
556 int i, rc;
557
558 ACPI_FUNCTION_TRACE("acpi_pcc_init_input");
559
560 pcc->input_dev = input_allocate_device();
561 if (!pcc->input_dev) {
562 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
563 "Couldn't allocate input device for hotkey"));
564 return -ENOMEM;
565 }
566
567 pcc->input_dev->evbit[0] = BIT(EV_KEY);
568
569 pcc->input_dev->name = ACPI_PCC_DRIVER_NAME;
570 pcc->input_dev->phys = ACPI_PCC_INPUT_PHYS;
571 pcc->input_dev->id.bustype = BUS_HOST;
572 pcc->input_dev->id.vendor = 0x0001;
573 pcc->input_dev->id.product = 0x0001;
574 pcc->input_dev->id.version = 0x0100;
575 pcc->input_dev->getkeycode = pcc_getkeycode;
576 pcc->input_dev->setkeycode = pcc_setkeycode;
577
578 /* load initial keymap */
579 memcpy(pcc->keymap, initial_keymap, sizeof(pcc->keymap));
580
581 for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++)
582 __set_bit(pcc->keymap[i], pcc->input_dev->keybit);
583 __clear_bit(KEY_RESERVED, pcc->input_dev->keybit);
584
585 input_set_drvdata(pcc->input_dev, pcc);
586
587 rc = input_register_device(pcc->input_dev);
588 if (rc < 0)
589 input_free_device(pcc->input_dev);
590
591 return rc;
592}
593
594/* kernel module interface */
595
596static int acpi_pcc_hotkey_resume(struct acpi_device *device)
597{
598 struct pcc_acpi *pcc = acpi_driver_data(device);
599 acpi_status status = AE_OK;
600
601 ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_resume");
602
603 if (device == NULL || pcc == NULL)
604 return -EINVAL;
605
606 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
607 pcc->sticky_mode));
608
609 status = acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
610
611 return status == AE_OK ? 0 : -EINVAL;
612}
613
614static int acpi_pcc_hotkey_add(struct acpi_device *device)
615{
616 acpi_status status;
617 struct pcc_acpi *pcc;
618 int num_sifr, result;
619
620 ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_add");
621
622 if (!device)
623 return -EINVAL;
624
625 num_sifr = acpi_pcc_get_sqty(device);
626
627 if (num_sifr > 255) {
628 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr too large"));
629 return -ENODEV;
630 }
631
632 pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
633 if (!pcc) {
634 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
635 "Couldn't allocate mem for pcc"));
636 return -ENOMEM;
637 }
638
639 pcc->sinf = kzalloc(sizeof(u32) * (num_sifr + 1), GFP_KERNEL);
640 if (!pcc->sinf) {
641 result = -ENOMEM;
642 goto out_hotkey;
643 }
644
645 pcc->device = device;
646 pcc->handle = device->handle;
647 pcc->num_sifr = num_sifr;
648 acpi_driver_data(device) = pcc;
649 strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
650 strcpy(acpi_device_class(device), ACPI_PCC_CLASS);
651
652 result = acpi_pcc_init_input(pcc);
653 if (result) {
654 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
655 "Error installing keyinput handler\n"));
656 goto out_sinf;
657 }
658
659 /* initialize hotkey input device */
660 status = acpi_install_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY,
661 acpi_pcc_hotkey_notify, pcc);
662
663 if (ACPI_FAILURE(status)) {
664 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
665 "Error installing notify handler\n"));
666 result = -ENODEV;
667 goto out_input;
668 }
669
670 /* initialize backlight */
671 pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
672 &pcc_backlight_ops);
673 if (IS_ERR(pcc->backlight))
674 goto out_notify;
675
676 if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) {
677 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
678 "Couldn't retrieve BIOS data\n"));
679 goto out_backlight;
680 }
681
682 /* read the initial brightness setting from the hardware */
683 pcc->backlight->props.max_brightness =
684 pcc->sinf[SINF_AC_MAX_BRIGHT];
685 pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
686
687 /* read the initial sticky key mode from the hardware */
688 pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
689
690 /* add sysfs attributes */
691 result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
692 if (result)
693 goto out_backlight;
694
695 return 0;
696
697out_backlight:
698 backlight_device_unregister(pcc->backlight);
699out_notify:
700 acpi_remove_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY,
701 acpi_pcc_hotkey_notify);
702out_input:
703 input_unregister_device(pcc->input_dev);
704 /* no need to input_free_device() since core input API refcount and
705 * free()s the device */
706out_sinf:
707 kfree(pcc->sinf);
708out_hotkey:
709 kfree(pcc);
710
711 return result;
712}
713
714static int __init acpi_pcc_init(void)
715{
716 int result = 0;
717
718 ACPI_FUNCTION_TRACE("acpi_pcc_init");
719
720 if (acpi_disabled)
721 return -ENODEV;
722
723 result = acpi_bus_register_driver(&acpi_pcc_driver);
724 if (result < 0) {
725 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
726 "Error registering hotkey driver\n"));
727 return -ENODEV;
728 }
729
730 return 0;
731}
732
733static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type)
734{
735 struct pcc_acpi *pcc = acpi_driver_data(device);
736
737 ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_remove");
738
739 if (!device || !pcc)
740 return -EINVAL;
741
742 sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
743
744 backlight_device_unregister(pcc->backlight);
745
746 acpi_remove_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY,
747 acpi_pcc_hotkey_notify);
748
749 input_unregister_device(pcc->input_dev);
750 /* no need to input_free_device() since core input API refcount and
751 * free()s the device */
752
753 kfree(pcc->sinf);
754 kfree(pcc);
755
756 return 0;
757}
758
759static void __exit acpi_pcc_exit(void)
760{
761 ACPI_FUNCTION_TRACE("acpi_pcc_exit");
762
763 acpi_bus_unregister_driver(&acpi_pcc_driver);
764}
765
766module_init(acpi_pcc_init);
767module_exit(acpi_pcc_exit);