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 * 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
18 *
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
22 *
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
30 *
31 * Dac.28, 2007 Harald Welte <laforge@gnumonks.org>
32 * -v0.91 merge with 2.6.24-rc6 ACPI changes
33 *
34 * Nov.04, 2006 Hiroshi Miura <miura@da-cha.org>
35 * -v0.9 remove warning about section reference.
36 * remove acpi_os_free
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
43 *
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.
49 *
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().
55 *
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
59 * Ryuta Nakanishi.
60 *
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.
64 *
65 * Aug.28, 2004 Hiroshi Miura <miura@da-cha.org>
66 * -v0.6.4 Fix a silly error with status checking
67 *
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()
74 *
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
79 *
80 * Aug.21, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
81 * -v0.6.1 Fix a silly error with status checking
82 *
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
87 *
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.
91 *
92 * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org>
93 * - v0.4 first post version
94 * add function to retrive SIFR
95 *
96 * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org>
97 * - v0.3 get proper status of hotkey
98 *
99 * Jul.22, 2004 Hiroshi Miura <miura@da-cha.org>
100 * - v0.2 add HotKey handler
101 *
102 * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org>
103 * - v0.1 start from toshiba_acpi driver written by John Belmonte
104 */
105
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>
118
119 #ifndef ACPI_HOTKEY_COMPONENT
120 #define ACPI_HOTKEY_COMPONENT 0x10000000
121 #endif
122
123 #define _COMPONENT ACPI_HOTKEY_COMPONENT
124
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");
128
129 #define LOGPREFIX "pcc_acpi: "
130
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
138
139 #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
140 #define ACPI_PCC_DEVICE_NAME "Hotkey"
141 #define ACPI_PCC_CLASS "pcc"
142
143 #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
144
145 /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
146 ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
147 */
148 enum SINF_BITS { SINF_NUM_BATTERIES = 0,
149 SINF_LCD_TYPE,
150 SINF_AC_MAX_BRIGHT,
151 SINF_AC_MIN_BRIGHT,
152 SINF_AC_CUR_BRIGHT,
153 SINF_DC_MAX_BRIGHT,
154 SINF_DC_MIN_BRIGHT,
155 SINF_DC_CUR_BRIGHT,
156 SINF_MUTE,
157 SINF_RESERVED,
158 SINF_ENV_STATE,
159 SINF_STICKY_KEY = 0x80,
160 };
161 /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
162
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);
166
167 static const struct acpi_device_id pcc_device_ids[] = {
168 { "MAT0012", 0},
169 { "MAT0013", 0},
170 { "MAT0018", 0},
171 { "MAT0019", 0},
172 { "", 0},
173 };
174 MODULE_DEVICE_TABLE(acpi, pcc_device_ids);
175
176 #ifdef CONFIG_PM_SLEEP
177 static int acpi_pcc_hotkey_resume(struct device *dev);
178 #endif
179 static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume);
180
181 static struct acpi_driver acpi_pcc_driver = {
182 .name = ACPI_PCC_DRIVER_NAME,
183 .class = ACPI_PCC_CLASS,
184 .ids = pcc_device_ids,
185 .ops = {
186 .add = acpi_pcc_hotkey_add,
187 .remove = acpi_pcc_hotkey_remove,
188 .notify = acpi_pcc_hotkey_notify,
189 },
190 .drv.pm = &acpi_pcc_hotkey_pm,
191 };
192
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 } },
205 { KE_END, 0 }
206 };
207
208 struct pcc_acpi {
209 acpi_handle handle;
210 unsigned long num_sifr;
211 int sticky_mode;
212 u32 *sinf;
213 struct acpi_device *device;
214 struct input_dev *input_dev;
215 struct backlight_device *backlight;
216 };
217
218 /* method access functions */
acpi_pcc_write_sset(struct pcc_acpi * pcc,int func,int val)219 static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val)
220 {
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, },
226 };
227 struct acpi_object_list params = {
228 .count = ARRAY_SIZE(in_objs),
229 .pointer = in_objs,
230 };
231 acpi_status status = AE_OK;
232
233 status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET,
234 ¶ms, NULL);
235
236 return (status == AE_OK) ? 0 : -EIO;
237 }
238
acpi_pcc_get_sqty(struct acpi_device * device)239 static inline int acpi_pcc_get_sqty(struct acpi_device *device)
240 {
241 unsigned long long s;
242 acpi_status status;
243
244 status = acpi_evaluate_integer(device->handle, METHOD_HKEY_SQTY,
245 NULL, &s);
246 if (ACPI_SUCCESS(status))
247 return s;
248 else {
249 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
250 "evaluation error HKEY.SQTY\n"));
251 return -EINVAL;
252 }
253 }
254
acpi_pcc_retrieve_biosdata(struct pcc_acpi * pcc)255 static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
256 {
257 acpi_status status;
258 struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
259 union acpi_object *hkey = NULL;
260 int i;
261
262 status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL,
263 &buffer);
264 if (ACPI_FAILURE(status)) {
265 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
266 "evaluation error HKEY.SINF\n"));
267 return 0;
268 }
269
270 hkey = buffer.pointer;
271 if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
272 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
273 status = AE_ERROR;
274 goto end;
275 }
276
277 if (pcc->num_sifr < hkey->package.count) {
278 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
279 "SQTY reports bad SINF length\n"));
280 status = AE_ERROR;
281 goto end;
282 }
283
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;
288 } else
289 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
290 "Invalid HKEY.SINF data\n"));
291 }
292 pcc->sinf[hkey->package.count] = -1;
293
294 end:
295 kfree(buffer.pointer);
296 return status == AE_OK;
297 }
298
299 /* backlight API interface functions */
300
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
304 * or removed */
305
bl_get(struct backlight_device * bd)306 static int bl_get(struct backlight_device *bd)
307 {
308 struct pcc_acpi *pcc = bl_get_data(bd);
309
310 if (!acpi_pcc_retrieve_biosdata(pcc))
311 return -EIO;
312
313 return pcc->sinf[SINF_AC_CUR_BRIGHT];
314 }
315
bl_set_status(struct backlight_device * bd)316 static int bl_set_status(struct backlight_device *bd)
317 {
318 struct pcc_acpi *pcc = bl_get_data(bd);
319 int bright = bd->props.brightness;
320 int rc;
321
322 if (!acpi_pcc_retrieve_biosdata(pcc))
323 return -EIO;
324
325 if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT])
326 bright = pcc->sinf[SINF_AC_MIN_BRIGHT];
327
328 if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT])
329 bright = pcc->sinf[SINF_DC_MIN_BRIGHT];
330
331 if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] ||
332 bright > pcc->sinf[SINF_AC_MAX_BRIGHT])
333 return -EINVAL;
334
335 rc = acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, bright);
336 if (rc < 0)
337 return rc;
338
339 return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright);
340 }
341
342 static const struct backlight_ops pcc_backlight_ops = {
343 .get_brightness = bl_get,
344 .update_status = bl_set_status,
345 };
346
347
348 /* sysfs user interface functions */
349
show_numbatt(struct device * dev,struct device_attribute * attr,char * buf)350 static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
351 char *buf)
352 {
353 struct acpi_device *acpi = to_acpi_device(dev);
354 struct pcc_acpi *pcc = acpi_driver_data(acpi);
355
356 if (!acpi_pcc_retrieve_biosdata(pcc))
357 return -EIO;
358
359 return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
360 }
361
show_lcdtype(struct device * dev,struct device_attribute * attr,char * buf)362 static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
363 char *buf)
364 {
365 struct acpi_device *acpi = to_acpi_device(dev);
366 struct pcc_acpi *pcc = acpi_driver_data(acpi);
367
368 if (!acpi_pcc_retrieve_biosdata(pcc))
369 return -EIO;
370
371 return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
372 }
373
show_mute(struct device * dev,struct device_attribute * attr,char * buf)374 static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
375 char *buf)
376 {
377 struct acpi_device *acpi = to_acpi_device(dev);
378 struct pcc_acpi *pcc = acpi_driver_data(acpi);
379
380 if (!acpi_pcc_retrieve_biosdata(pcc))
381 return -EIO;
382
383 return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]);
384 }
385
show_sticky(struct device * dev,struct device_attribute * attr,char * buf)386 static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
387 char *buf)
388 {
389 struct acpi_device *acpi = to_acpi_device(dev);
390 struct pcc_acpi *pcc = acpi_driver_data(acpi);
391
392 if (!acpi_pcc_retrieve_biosdata(pcc))
393 return -EIO;
394
395 return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
396 }
397
set_sticky(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)398 static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
399 const char *buf, size_t count)
400 {
401 struct acpi_device *acpi = to_acpi_device(dev);
402 struct pcc_acpi *pcc = acpi_driver_data(acpi);
403 int val;
404
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;
409 }
410
411 return count;
412 }
413
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);
418
419 static struct attribute *pcc_sysfs_entries[] = {
420 &dev_attr_numbatt.attr,
421 &dev_attr_lcdtype.attr,
422 &dev_attr_mute.attr,
423 &dev_attr_sticky_key.attr,
424 NULL,
425 };
426
427 static const struct attribute_group pcc_attr_group = {
428 .name = NULL, /* put in device directory */
429 .attrs = pcc_sysfs_entries,
430 };
431
432
433 /* hotkey input device driver */
434
435 static int sleep_keydown_seen;
acpi_pcc_generate_keyinput(struct pcc_acpi * pcc)436 static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
437 {
438 struct input_dev *hotk_input_dev = pcc->input_dev;
439 int rc;
440 unsigned long long result;
441
442 rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
443 NULL, &result);
444 if (ACPI_FAILURE(rc)) {
445 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
446 "error getting hotkey status\n"));
447 return;
448 }
449
450 /* hack: some firmware sends no key down for sleep / hibernate */
451 if ((result & 0xf) == 0x7 || (result & 0xf) == 0xa) {
452 if (result & 0x80)
453 sleep_keydown_seen = 1;
454 if (!sleep_keydown_seen)
455 sparse_keymap_report_event(hotk_input_dev,
456 result & 0xf, 0x80, false);
457 }
458
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));
463 }
464
acpi_pcc_hotkey_notify(struct acpi_device * device,u32 event)465 static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
466 {
467 struct pcc_acpi *pcc = acpi_driver_data(device);
468
469 switch (event) {
470 case HKEY_NOTIFY:
471 acpi_pcc_generate_keyinput(pcc);
472 break;
473 default:
474 /* nothing to do */
475 break;
476 }
477 }
478
acpi_pcc_init_input(struct pcc_acpi * pcc)479 static int acpi_pcc_init_input(struct pcc_acpi *pcc)
480 {
481 struct input_dev *input_dev;
482 int error;
483
484 input_dev = input_allocate_device();
485 if (!input_dev)
486 return -ENOMEM;
487
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;
494
495 error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL);
496 if (error) {
497 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
498 "Unable to setup input device keymap\n"));
499 goto err_free_dev;
500 }
501
502 error = input_register_device(input_dev);
503 if (error) {
504 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
505 "Unable to register input device\n"));
506 goto err_free_dev;
507 }
508
509 pcc->input_dev = input_dev;
510 return 0;
511
512 err_free_dev:
513 input_free_device(input_dev);
514 return error;
515 }
516
517 /* kernel module interface */
518
519 #ifdef CONFIG_PM_SLEEP
acpi_pcc_hotkey_resume(struct device * dev)520 static int acpi_pcc_hotkey_resume(struct device *dev)
521 {
522 struct pcc_acpi *pcc;
523
524 if (!dev)
525 return -EINVAL;
526
527 pcc = acpi_driver_data(to_acpi_device(dev));
528 if (!pcc)
529 return -EINVAL;
530
531 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
532 pcc->sticky_mode));
533
534 return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
535 }
536 #endif
537
acpi_pcc_hotkey_add(struct acpi_device * device)538 static int acpi_pcc_hotkey_add(struct acpi_device *device)
539 {
540 struct backlight_properties props;
541 struct pcc_acpi *pcc;
542 int num_sifr, result;
543
544 if (!device)
545 return -EINVAL;
546
547 num_sifr = acpi_pcc_get_sqty(device);
548
549 if (num_sifr < 0 || num_sifr > 255) {
550 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range"));
551 return -ENODEV;
552 }
553
554 pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
555 if (!pcc) {
556 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
557 "Couldn't allocate mem for pcc"));
558 return -ENOMEM;
559 }
560
561 pcc->sinf = kcalloc(num_sifr + 1, sizeof(u32), GFP_KERNEL);
562 if (!pcc->sinf) {
563 result = -ENOMEM;
564 goto out_hotkey;
565 }
566
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);
573
574 result = acpi_pcc_init_input(pcc);
575 if (result) {
576 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
577 "Error installing keyinput handler\n"));
578 goto out_sinf;
579 }
580
581 if (!acpi_pcc_retrieve_biosdata(pcc)) {
582 ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
583 "Couldn't retrieve BIOS data\n"));
584 result = -EIO;
585 goto out_input;
586 }
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);
595 goto out_input;
596 }
597
598 /* read the initial brightness setting from the hardware */
599 pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
600
601 /* read the initial sticky key mode from the hardware */
602 pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
603
604 /* add sysfs attributes */
605 result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
606 if (result)
607 goto out_backlight;
608
609 return 0;
610
611 out_backlight:
612 backlight_device_unregister(pcc->backlight);
613 out_input:
614 input_unregister_device(pcc->input_dev);
615 out_sinf:
616 kfree(pcc->sinf);
617 out_hotkey:
618 kfree(pcc);
619
620 return result;
621 }
622
acpi_pcc_hotkey_remove(struct acpi_device * device)623 static int acpi_pcc_hotkey_remove(struct acpi_device *device)
624 {
625 struct pcc_acpi *pcc = acpi_driver_data(device);
626
627 if (!device || !pcc)
628 return -EINVAL;
629
630 sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
631
632 backlight_device_unregister(pcc->backlight);
633
634 input_unregister_device(pcc->input_dev);
635
636 kfree(pcc->sinf);
637 kfree(pcc);
638
639 return 0;
640 }
641
642 module_acpi_driver(acpi_pcc_driver);
643