ACPI: asus_acpi: LED display support
[pandora-kernel.git] / drivers / acpi / asus_acpi.c
1 /*
2  *  asus_acpi.c - Asus Laptop ACPI Extras
3  *
4  *
5  *  Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  *
22  *  The development page for this driver is located at
23  *  http://sourceforge.net/projects/acpi4asus/
24  *
25  *  Credits:
26  *  Pontus Fuchs   - Helper functions, cleanup
27  *  Johann Wiesner - Small compile fixes
28  *  John Belmonte  - ACPI code for Toshiba laptop was a good starting point.
29  *  Éric Burghard  - LED display support for W1N
30  *
31  */
32
33 #include <linux/kernel.h>
34 #include <linux/module.h>
35 #include <linux/init.h>
36 #include <linux/types.h>
37 #include <linux/proc_fs.h>
38 #include <acpi/acpi_drivers.h>
39 #include <acpi/acpi_bus.h>
40 #include <asm/uaccess.h>
41
42 #define ASUS_ACPI_VERSION "0.30"
43
44 #define PROC_ASUS       "asus"  //the directory
45 #define PROC_MLED       "mled"
46 #define PROC_WLED       "wled"
47 #define PROC_TLED       "tled"
48 #define PROC_LEDD       "ledd"
49 #define PROC_INFO       "info"
50 #define PROC_LCD        "lcd"
51 #define PROC_BRN        "brn"
52 #define PROC_DISP       "disp"
53
54 #define ACPI_HOTK_NAME          "Asus Laptop ACPI Extras Driver"
55 #define ACPI_HOTK_CLASS         "hotkey"
56 #define ACPI_HOTK_DEVICE_NAME   "Hotkey"
57 #define ACPI_HOTK_HID           "ATK0100"
58
59 /*
60  * Some events we use, same for all Asus
61  */
62 #define BR_UP       0x10
63 #define BR_DOWN     0x20
64
65 /*
66  * Flags for hotk status
67  */
68 #define MLED_ON     0x01        //is MLED ON ?
69 #define WLED_ON     0x02
70 #define TLED_ON     0x04
71
72 MODULE_AUTHOR("Julien Lerouge, Karol Kozimor");
73 MODULE_DESCRIPTION(ACPI_HOTK_NAME);
74 MODULE_LICENSE("GPL");
75
76 static uid_t asus_uid;
77 static gid_t asus_gid;
78 module_param(asus_uid, uint, 0);
79 MODULE_PARM_DESC(asus_uid, "UID for entries in /proc/acpi/asus.\n");
80 module_param(asus_gid, uint, 0);
81 MODULE_PARM_DESC(asus_gid, "GID for entries in /proc/acpi/asus.\n");
82
83 /* For each model, all features implemented, 
84  * those marked with R are relative to HOTK, A for absolute */
85 struct model_data {
86         char *name;             //name of the laptop________________A
87         char *mt_mled;          //method to handle mled_____________R
88         char *mled_status;      //node to handle mled reading_______A
89         char *mt_wled;          //method to handle wled_____________R
90         char *wled_status;      //node to handle wled reading_______A
91         char *mt_tled;          //method to handle tled_____________R
92         char *tled_status;      //node to handle tled reading_______A
93         char *mt_ledd;          //method to handle LED display______R
94         char *mt_lcd_switch;    //method to turn LCD ON/OFF_________A
95         char *lcd_status;       //node to read LCD panel state______A
96         char *brightness_up;    //method to set brightness up_______A
97         char *brightness_down;  //guess what ?______________________A
98         char *brightness_set;   //method to set absolute brightness_R
99         char *brightness_get;   //method to get absolute brightness_R
100         char *brightness_status;        //node to get brightness____________A
101         char *display_set;      //method to set video output________R
102         char *display_get;      //method to get video output________R
103 };
104
105 /*
106  * This is the main structure, we can use it to store anything interesting
107  * about the hotk device
108  */
109 struct asus_hotk {
110         struct acpi_device *device;     //the device we are in
111         acpi_handle handle;     //the handle of the hotk device
112         char status;            //status of the hotk, for LEDs, ...
113         u32 ledd_status;        //status of the LED display
114         struct model_data *methods;     //methods available on the laptop
115         u8 brightness;          //brightness level
116         enum {
117                 A1x = 0,        //A1340D, A1300F
118                 A2x,            //A2500H
119                 D1x,            //D1
120                 L2D,            //L2000D
121                 L3C,            //L3800C
122                 L3D,            //L3400D
123                 L3H,            //L3H, but also L2000E
124                 L4R,            //L4500R
125                 L5x,            //L5800C 
126                 L8L,            //L8400L
127                 M1A,            //M1300A
128                 M2E,            //M2400E, L4400L
129                 M6N,            //M6800N
130                 M6R,            //M6700R, A3000G
131                 P30,            //Samsung P30
132                 S1x,            //S1300A, but also L1400B and M2400A (L84F)
133                 S2x,            //S200 (J1 reported), Victor MP-XP7210
134                 W1N,            //W1000N
135                 xxN,            //M2400N, M3700N, M5200N, M6800N, S1300N, S5200N
136                 //(Centrino)
137                 END_MODEL
138         } model;                //Models currently supported
139         u16 event_count[128];   //count for each event TODO make this better
140 };
141
142 /* Here we go */
143 #define A1x_PREFIX "\\_SB.PCI0.ISA.EC0."
144 #define L3C_PREFIX "\\_SB.PCI0.PX40.ECD0."
145 #define M1A_PREFIX "\\_SB.PCI0.PX40.EC0."
146 #define P30_PREFIX "\\_SB.PCI0.LPCB.EC0."
147 #define S1x_PREFIX "\\_SB.PCI0.PX40."
148 #define S2x_PREFIX A1x_PREFIX
149 #define xxN_PREFIX "\\_SB.PCI0.SBRG.EC0."
150
151 static struct model_data model_conf[END_MODEL] = {
152         /*
153          * TODO I have seen a SWBX and AIBX method on some models, like L1400B,
154          * it seems to be a kind of switch, but what for ?
155          */
156
157         {
158          .name = "A1x",
159          .mt_mled = "MLED",
160          .mled_status = "\\MAIL",
161          .mt_lcd_switch = A1x_PREFIX "_Q10",
162          .lcd_status = "\\BKLI",
163          .brightness_up = A1x_PREFIX "_Q0E",
164          .brightness_down = A1x_PREFIX "_Q0F"},
165
166         {
167          .name = "A2x",
168          .mt_mled = "MLED",
169          .mt_wled = "WLED",
170          .wled_status = "\\SG66",
171          .mt_lcd_switch = "\\Q10",
172          .lcd_status = "\\BAOF",
173          .brightness_set = "SPLV",
174          .brightness_get = "GPLV",
175          .display_set = "SDSP",
176          .display_get = "\\INFB"},
177
178         {
179          .name = "D1x",
180          .mt_mled = "MLED",
181          .mt_lcd_switch = "\\Q0D",
182          .lcd_status = "\\GP11",
183          .brightness_up = "\\Q0C",
184          .brightness_down = "\\Q0B",
185          .brightness_status = "\\BLVL",
186          .display_set = "SDSP",
187          .display_get = "\\INFB"},
188
189         {
190          .name = "L2D",
191          .mt_mled = "MLED",
192          .mled_status = "\\SGP6",
193          .mt_wled = "WLED",
194          .wled_status = "\\RCP3",
195          .mt_lcd_switch = "\\Q10",
196          .lcd_status = "\\SGP0",
197          .brightness_up = "\\Q0E",
198          .brightness_down = "\\Q0F",
199          .display_set = "SDSP",
200          .display_get = "\\INFB"},
201
202         {
203          .name = "L3C",
204          .mt_mled = "MLED",
205          .mt_wled = "WLED",
206          .mt_lcd_switch = L3C_PREFIX "_Q10",
207          .lcd_status = "\\GL32",
208          .brightness_set = "SPLV",
209          .brightness_get = "GPLV",
210          .display_set = "SDSP",
211          .display_get = "\\_SB.PCI0.PCI1.VGAC.NMAP"},
212
213         {
214          .name = "L3D",
215          .mt_mled = "MLED",
216          .mled_status = "\\MALD",
217          .mt_wled = "WLED",
218          .mt_lcd_switch = "\\Q10",
219          .lcd_status = "\\BKLG",
220          .brightness_set = "SPLV",
221          .brightness_get = "GPLV",
222          .display_set = "SDSP",
223          .display_get = "\\INFB"},
224
225         {
226          .name = "L3H",
227          .mt_mled = "MLED",
228          .mt_wled = "WLED",
229          .mt_lcd_switch = "EHK",
230          .lcd_status = "\\_SB.PCI0.PM.PBC",
231          .brightness_set = "SPLV",
232          .brightness_get = "GPLV",
233          .display_set = "SDSP",
234          .display_get = "\\INFB"},
235
236         {
237          .name = "L4R",
238          .mt_mled = "MLED",
239          .mt_wled = "WLED",
240          .wled_status = "\\_SB.PCI0.SBRG.SG13",
241          .mt_lcd_switch = xxN_PREFIX "_Q10",
242          .lcd_status = "\\_SB.PCI0.SBSM.SEO4",
243          .brightness_set = "SPLV",
244          .brightness_get = "GPLV",
245          .display_set = "SDSP",
246          .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"},
247
248         {
249          .name = "L5x",
250          .mt_mled = "MLED",
251 /* WLED present, but not controlled by ACPI */
252          .mt_tled = "TLED",
253          .mt_lcd_switch = "\\Q0D",
254          .lcd_status = "\\BAOF",
255          .brightness_set = "SPLV",
256          .brightness_get = "GPLV",
257          .display_set = "SDSP",
258          .display_get = "\\INFB"},
259
260         {
261          .name = "L8L"
262 /* No features, but at least support the hotkeys */
263          },
264
265         {
266          .name = "M1A",
267          .mt_mled = "MLED",
268          .mt_lcd_switch = M1A_PREFIX "Q10",
269          .lcd_status = "\\PNOF",
270          .brightness_up = M1A_PREFIX "Q0E",
271          .brightness_down = M1A_PREFIX "Q0F",
272          .brightness_status = "\\BRIT",
273          .display_set = "SDSP",
274          .display_get = "\\INFB"},
275
276         {
277          .name = "M2E",
278          .mt_mled = "MLED",
279          .mt_wled = "WLED",
280          .mt_lcd_switch = "\\Q10",
281          .lcd_status = "\\GP06",
282          .brightness_set = "SPLV",
283          .brightness_get = "GPLV",
284          .display_set = "SDSP",
285          .display_get = "\\INFB"},
286
287         {
288          .name = "M6N",
289          .mt_mled = "MLED",
290          .mt_wled = "WLED",
291          .wled_status = "\\_SB.PCI0.SBRG.SG13",
292          .mt_lcd_switch = xxN_PREFIX "_Q10",
293          .lcd_status = "\\_SB.BKLT",
294          .brightness_set = "SPLV",
295          .brightness_get = "GPLV",
296          .display_set = "SDSP",
297          .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"},
298         {
299          .name = "M6R",
300          .mt_mled = "MLED",
301          .mt_wled = "WLED",
302          .mt_lcd_switch = xxN_PREFIX "_Q10",
303          .lcd_status = "\\_SB.PCI0.SBSM.SEO4",
304          .brightness_set = "SPLV",
305          .brightness_get = "GPLV",
306          .display_set = "SDSP",
307          .display_get = "\\SSTE"},
308
309         {
310          .name = "P30",
311          .mt_wled = "WLED",
312          .mt_lcd_switch = P30_PREFIX "_Q0E",
313          .lcd_status = "\\BKLT",
314          .brightness_up = P30_PREFIX "_Q68",
315          .brightness_down = P30_PREFIX "_Q69",
316          .brightness_get = "GPLV",
317          .display_set = "SDSP",
318          .display_get = "\\DNXT"},
319
320         {
321          .name = "S1x",
322          .mt_mled = "MLED",
323          .mled_status = "\\EMLE",
324          .mt_wled = "WLED",
325          .mt_lcd_switch = S1x_PREFIX "Q10",
326          .lcd_status = "\\PNOF",
327          .brightness_set = "SPLV",
328          .brightness_get = "GPLV"},
329
330         {
331          .name = "S2x",
332          .mt_mled = "MLED",
333          .mled_status = "\\MAIL",
334          .mt_lcd_switch = S2x_PREFIX "_Q10",
335          .lcd_status = "\\BKLI",
336          .brightness_up = S2x_PREFIX "_Q0B",
337          .brightness_down = S2x_PREFIX "_Q0A"},
338
339         {
340          .name = "W1N",
341          .mt_mled = "MLED",
342          .mt_wled = "WLED",
343          .mt_ledd = "SLCM",
344          .mt_lcd_switch = xxN_PREFIX "_Q10",
345          .lcd_status = "\\BKLT",
346          .brightness_set = "SPLV",
347          .brightness_get = "GPLV",
348          .display_set = "SDSP",
349          .display_get = "\\ADVG"},
350
351         {
352          .name = "xxN",
353          .mt_mled = "MLED",
354 /* WLED present, but not controlled by ACPI */
355          .mt_lcd_switch = xxN_PREFIX "_Q10",
356          .lcd_status = "\\BKLT",
357          .brightness_set = "SPLV",
358          .brightness_get = "GPLV",
359          .display_set = "SDSP",
360          .display_get = "\\ADVG"}
361 };
362
363 /* procdir we use */
364 static struct proc_dir_entry *asus_proc_dir;
365
366 /*
367  * This header is made available to allow proper configuration given model,
368  * revision number , ... this info cannot go in struct asus_hotk because it is
369  * available before the hotk
370  */
371 static struct acpi_table_header *asus_info;
372
373 /* The actual device the driver binds to */
374 static struct asus_hotk *hotk;
375
376 /*
377  * The hotkey driver declaration
378  */
379 static int asus_hotk_add(struct acpi_device *device);
380 static int asus_hotk_remove(struct acpi_device *device, int type);
381 static struct acpi_driver asus_hotk_driver = {
382         .name = ACPI_HOTK_NAME,
383         .class = ACPI_HOTK_CLASS,
384         .ids = ACPI_HOTK_HID,
385         .ops = {
386                 .add = asus_hotk_add,
387                 .remove = asus_hotk_remove,
388                 },
389 };
390
391 /* 
392  * This function evaluates an ACPI method, given an int as parameter, the
393  * method is searched within the scope of the handle, can be NULL. The output
394  * of the method is written is output, which can also be NULL
395  *
396  * returns 1 if write is successful, 0 else. 
397  */
398 static int write_acpi_int(acpi_handle handle, const char *method, int val,
399                           struct acpi_buffer *output)
400 {
401         struct acpi_object_list params; //list of input parameters (an int here)
402         union acpi_object in_obj;       //the only param we use
403         acpi_status status;
404
405         params.count = 1;
406         params.pointer = &in_obj;
407         in_obj.type = ACPI_TYPE_INTEGER;
408         in_obj.integer.value = val;
409
410         status = acpi_evaluate_object(handle, (char *)method, &params, output);
411         return (status == AE_OK);
412 }
413
414 static int read_acpi_int(acpi_handle handle, const char *method, int *val)
415 {
416         struct acpi_buffer output;
417         union acpi_object out_obj;
418         acpi_status status;
419
420         output.length = sizeof(out_obj);
421         output.pointer = &out_obj;
422
423         status = acpi_evaluate_object(handle, (char *)method, NULL, &output);
424         *val = out_obj.integer.value;
425         return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER);
426 }
427
428 /*
429  * We write our info in page, we begin at offset off and cannot write more
430  * than count bytes. We set eof to 1 if we handle those 2 values. We return the
431  * number of bytes written in page
432  */
433 static int
434 proc_read_info(char *page, char **start, off_t off, int count, int *eof,
435                void *data)
436 {
437         int len = 0;
438         int temp;
439         char buf[16];           //enough for all info
440         /*
441          * We use the easy way, we don't care of off and count, so we don't set eof
442          * to 1
443          */
444
445         len += sprintf(page, ACPI_HOTK_NAME " " ASUS_ACPI_VERSION "\n");
446         len += sprintf(page + len, "Model reference    : %s\n",
447                        hotk->methods->name);
448         /* 
449          * The SFUN method probably allows the original driver to get the list 
450          * of features supported by a given model. For now, 0x0100 or 0x0800 
451          * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
452          * The significance of others is yet to be found.
453          */
454         if (read_acpi_int(hotk->handle, "SFUN", &temp))
455                 len +=
456                     sprintf(page + len, "SFUN value         : 0x%04x\n", temp);
457         /*
458          * Another value for userspace: the ASYM method returns 0x02 for
459          * battery low and 0x04 for battery critical, its readings tend to be
460          * more accurate than those provided by _BST. 
461          * Note: since not all the laptops provide this method, errors are
462          * silently ignored.
463          */
464         if (read_acpi_int(hotk->handle, "ASYM", &temp))
465                 len +=
466                     sprintf(page + len, "ASYM value         : 0x%04x\n", temp);
467         if (asus_info) {
468                 snprintf(buf, 16, "%d", asus_info->length);
469                 len += sprintf(page + len, "DSDT length        : %s\n", buf);
470                 snprintf(buf, 16, "%d", asus_info->checksum);
471                 len += sprintf(page + len, "DSDT checksum      : %s\n", buf);
472                 snprintf(buf, 16, "%d", asus_info->revision);
473                 len += sprintf(page + len, "DSDT revision      : %s\n", buf);
474                 snprintf(buf, 7, "%s", asus_info->oem_id);
475                 len += sprintf(page + len, "OEM id             : %s\n", buf);
476                 snprintf(buf, 9, "%s", asus_info->oem_table_id);
477                 len += sprintf(page + len, "OEM table id       : %s\n", buf);
478                 snprintf(buf, 16, "%x", asus_info->oem_revision);
479                 len += sprintf(page + len, "OEM revision       : 0x%s\n", buf);
480                 snprintf(buf, 5, "%s", asus_info->asl_compiler_id);
481                 len += sprintf(page + len, "ASL comp vendor id : %s\n", buf);
482                 snprintf(buf, 16, "%x", asus_info->asl_compiler_revision);
483                 len += sprintf(page + len, "ASL comp revision  : 0x%s\n", buf);
484         }
485
486         return len;
487 }
488
489 /*
490  * /proc handlers
491  * We write our info in page, we begin at offset off and cannot write more
492  * than count bytes. We set eof to 1 if we handle those 2 values. We return the
493  * number of bytes written in page
494  */
495
496 /* Generic LED functions */
497 static int read_led(const char *ledname, int ledmask)
498 {
499         if (ledname) {
500                 int led_status;
501
502                 if (read_acpi_int(NULL, ledname, &led_status))
503                         return led_status;
504                 else
505                         printk(KERN_WARNING "Asus ACPI: Error reading LED "
506                                "status\n");
507         }
508         return (hotk->status & ledmask) ? 1 : 0;
509 }
510
511 static int parse_arg(const char __user * buf, unsigned long count, int *val)
512 {
513         char s[32];
514         if (!count)
515                 return 0;
516         if (count > 31)
517                 return -EINVAL;
518         if (copy_from_user(s, buf, count))
519                 return -EFAULT;
520         s[count] = 0;
521         if (sscanf(s, "%i", val) != 1)
522                 return -EINVAL;
523         return count;
524 }
525
526 /* FIXME: kill extraneous args so it can be called independently */
527 static int
528 write_led(const char __user * buffer, unsigned long count,
529           char *ledname, int ledmask, int invert)
530 {
531         int value;
532         int led_out = 0;
533
534         count = parse_arg(buffer, count, &value);
535         if (count > 0)
536                 led_out = value ? 1 : 0;
537
538         hotk->status =
539             (led_out) ? (hotk->status | ledmask) : (hotk->status & ~ledmask);
540
541         if (invert)             /* invert target value */
542                 led_out = !led_out & 0x1;
543
544         if (!write_acpi_int(hotk->handle, ledname, led_out, NULL))
545                 printk(KERN_WARNING "Asus ACPI: LED (%s) write failed\n",
546                        ledname);
547
548         return count;
549 }
550
551 /*
552  * Proc handlers for MLED
553  */
554 static int
555 proc_read_mled(char *page, char **start, off_t off, int count, int *eof,
556                void *data)
557 {
558         return sprintf(page, "%d\n",
559                        read_led(hotk->methods->mled_status, MLED_ON));
560 }
561
562 static int
563 proc_write_mled(struct file *file, const char __user * buffer,
564                 unsigned long count, void *data)
565 {
566         return write_led(buffer, count, hotk->methods->mt_mled, MLED_ON, 1);
567 }
568
569 /*
570  * Proc handlers for LED display
571  */
572 static int
573 proc_read_ledd(char *page, char **start, off_t off, int count, int *eof,
574                void *data)
575 {
576         return sprintf(page, "0x%08x\n", hotk->ledd_status);
577 }
578
579 static int
580 proc_write_ledd(struct file *file, const char __user * buffer,
581                 unsigned long count, void *data)
582 {
583         int value;
584
585         count = parse_arg(buffer, count, &value);
586         if (count > 0) {
587                 if (!write_acpi_int
588                     (hotk->handle, hotk->methods->mt_ledd, value, NULL))
589                         printk(KERN_WARNING
590                                "Asus ACPI: LED display write failed\n");
591                 else
592                         hotk->ledd_status = (u32) value;
593         } else if (count < 0)
594                 printk(KERN_WARNING "Asus ACPI: Error reading user input\n");
595
596         return count;
597 }
598
599 /*
600  * Proc handlers for WLED
601  */
602 static int
603 proc_read_wled(char *page, char **start, off_t off, int count, int *eof,
604                void *data)
605 {
606         return sprintf(page, "%d\n",
607                        read_led(hotk->methods->wled_status, WLED_ON));
608 }
609
610 static int
611 proc_write_wled(struct file *file, const char __user * buffer,
612                 unsigned long count, void *data)
613 {
614         return write_led(buffer, count, hotk->methods->mt_wled, WLED_ON, 0);
615 }
616
617 /*
618  * Proc handlers for TLED
619  */
620 static int
621 proc_read_tled(char *page, char **start, off_t off, int count, int *eof,
622                void *data)
623 {
624         return sprintf(page, "%d\n",
625                        read_led(hotk->methods->tled_status, TLED_ON));
626 }
627
628 static int
629 proc_write_tled(struct file *file, const char __user * buffer,
630                 unsigned long count, void *data)
631 {
632         return write_led(buffer, count, hotk->methods->mt_tled, TLED_ON, 0);
633 }
634
635 static int get_lcd_state(void)
636 {
637         int lcd = 0;
638
639         if (hotk->model != L3H) {
640                 /* We don't have to check anything if we are here */
641                 if (!read_acpi_int(NULL, hotk->methods->lcd_status, &lcd))
642                         printk(KERN_WARNING
643                                "Asus ACPI: Error reading LCD status\n");
644
645                 if (hotk->model == L2D)
646                         lcd = ~lcd;
647         } else {                /* L3H and the like have to be handled differently */
648                 acpi_status status = 0;
649                 struct acpi_object_list input;
650                 union acpi_object mt_params[2];
651                 struct acpi_buffer output;
652                 union acpi_object out_obj;
653
654                 input.count = 2;
655                 input.pointer = mt_params;
656                 /* Note: the following values are partly guessed up, but 
657                    otherwise they seem to work */
658                 mt_params[0].type = ACPI_TYPE_INTEGER;
659                 mt_params[0].integer.value = 0x02;
660                 mt_params[1].type = ACPI_TYPE_INTEGER;
661                 mt_params[1].integer.value = 0x02;
662
663                 output.length = sizeof(out_obj);
664                 output.pointer = &out_obj;
665
666                 status =
667                     acpi_evaluate_object(NULL, hotk->methods->lcd_status,
668                                          &input, &output);
669                 if (status != AE_OK)
670                         return -1;
671                 if (out_obj.type == ACPI_TYPE_INTEGER)
672                         /* That's what the AML code does */
673                         lcd = out_obj.integer.value >> 8;
674         }
675
676         return (lcd & 1);
677 }
678
679 static int set_lcd_state(int value)
680 {
681         int lcd = 0;
682         acpi_status status = 0;
683
684         lcd = value ? 1 : 0;
685         if (lcd != get_lcd_state()) {
686                 /* switch */
687                 if (hotk->model != L3H) {
688                         status =
689                             acpi_evaluate_object(NULL,
690                                                  hotk->methods->mt_lcd_switch,
691                                                  NULL, NULL);
692                 } else {        /* L3H and the like have to be handled differently */
693                         if (!write_acpi_int
694                             (hotk->handle, hotk->methods->mt_lcd_switch, 0x07,
695                              NULL))
696                                 status = AE_ERROR;
697                         /* L3H's AML executes EHK (0x07) upon Fn+F7 keypress, 
698                            the exact behaviour is simulated here */
699                 }
700                 if (ACPI_FAILURE(status))
701                         printk(KERN_WARNING "Asus ACPI: Error switching LCD\n");
702         }
703         return 0;
704
705 }
706
707 static int
708 proc_read_lcd(char *page, char **start, off_t off, int count, int *eof,
709               void *data)
710 {
711         return sprintf(page, "%d\n", get_lcd_state());
712 }
713
714 static int
715 proc_write_lcd(struct file *file, const char __user * buffer,
716                unsigned long count, void *data)
717 {
718         int value;
719
720         count = parse_arg(buffer, count, &value);
721         if (count > 0)
722                 set_lcd_state(value);
723         return count;
724 }
725
726 static int read_brightness(void)
727 {
728         int value;
729
730         if (hotk->methods->brightness_get) {    /* SPLV/GPLV laptop */
731                 if (!read_acpi_int(hotk->handle, hotk->methods->brightness_get,
732                                    &value))
733                         printk(KERN_WARNING
734                                "Asus ACPI: Error reading brightness\n");
735         } else if (hotk->methods->brightness_status) {  /* For D1 for example */
736                 if (!read_acpi_int(NULL, hotk->methods->brightness_status,
737                                    &value))
738                         printk(KERN_WARNING
739                                "Asus ACPI: Error reading brightness\n");
740         } else                  /* No GPLV method */
741                 value = hotk->brightness;
742         return value;
743 }
744
745 /*
746  * Change the brightness level
747  */
748 static void set_brightness(int value)
749 {
750         acpi_status status = 0;
751
752         /* SPLV laptop */
753         if (hotk->methods->brightness_set) {
754                 if (!write_acpi_int(hotk->handle, hotk->methods->brightness_set,
755                                     value, NULL))
756                         printk(KERN_WARNING
757                                "Asus ACPI: Error changing brightness\n");
758                 return;
759         }
760
761         /* No SPLV method if we are here, act as appropriate */
762         value -= read_brightness();
763         while (value != 0) {
764                 status = acpi_evaluate_object(NULL, (value > 0) ?
765                                               hotk->methods->brightness_up :
766                                               hotk->methods->brightness_down,
767                                               NULL, NULL);
768                 (value > 0) ? value-- : value++;
769                 if (ACPI_FAILURE(status))
770                         printk(KERN_WARNING
771                                "Asus ACPI: Error changing brightness\n");
772         }
773         return;
774 }
775
776 static int
777 proc_read_brn(char *page, char **start, off_t off, int count, int *eof,
778               void *data)
779 {
780         return sprintf(page, "%d\n", read_brightness());
781 }
782
783 static int
784 proc_write_brn(struct file *file, const char __user * buffer,
785                unsigned long count, void *data)
786 {
787         int value;
788
789         count = parse_arg(buffer, count, &value);
790         if (count > 0) {
791                 value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
792                 /* 0 <= value <= 15 */
793                 set_brightness(value);
794         } else if (count < 0) {
795                 printk(KERN_WARNING "Asus ACPI: Error reading user input\n");
796         }
797
798         return count;
799 }
800
801 static void set_display(int value)
802 {
803         /* no sanity check needed for now */
804         if (!write_acpi_int(hotk->handle, hotk->methods->display_set,
805                             value, NULL))
806                 printk(KERN_WARNING "Asus ACPI: Error setting display\n");
807         return;
808 }
809
810 /*
811  * Now, *this* one could be more user-friendly, but so far, no-one has 
812  * complained. The significance of bits is the same as in proc_write_disp()
813  */
814 static int
815 proc_read_disp(char *page, char **start, off_t off, int count, int *eof,
816                void *data)
817 {
818         int value = 0;
819
820         if (!read_acpi_int(hotk->handle, hotk->methods->display_get, &value))
821                 printk(KERN_WARNING
822                        "Asus ACPI: Error reading display status\n");
823         value &= 0x07;          /* needed for some models, shouldn't hurt others */
824         return sprintf(page, "%d\n", value);
825 }
826
827 /*
828  * Experimental support for display switching. As of now: 1 should activate 
829  * the LCD output, 2 should do for CRT, and 4 for TV-Out. Any combination 
830  * (bitwise) of these will suffice. I never actually tested 3 displays hooked up 
831  * simultaneously, so be warned. See the acpi4asus README for more info.
832  */
833 static int
834 proc_write_disp(struct file *file, const char __user * buffer,
835                 unsigned long count, void *data)
836 {
837         int value;
838
839         count = parse_arg(buffer, count, &value);
840         if (count > 0)
841                 set_display(value);
842         else if (count < 0)
843                 printk(KERN_WARNING "Asus ACPI: Error reading user input\n");
844
845         return count;
846 }
847
848 typedef int (proc_readfunc) (char *page, char **start, off_t off, int count,
849                              int *eof, void *data);
850 typedef int (proc_writefunc) (struct file * file, const char __user * buffer,
851                               unsigned long count, void *data);
852
853 static int
854 asus_proc_add(char *name, proc_writefunc * writefunc,
855                      proc_readfunc * readfunc, mode_t mode,
856                      struct acpi_device *device)
857 {
858         struct proc_dir_entry *proc =
859             create_proc_entry(name, mode, acpi_device_dir(device));
860         if (!proc) {
861                 printk(KERN_WARNING "  Unable to create %s fs entry\n", name);
862                 return -1;
863         }
864         proc->write_proc = writefunc;
865         proc->read_proc = readfunc;
866         proc->data = acpi_driver_data(device);
867         proc->owner = THIS_MODULE;
868         proc->uid = asus_uid;
869         proc->gid = asus_gid;
870         return 0;
871 }
872
873 static int asus_hotk_add_fs(struct acpi_device *device)
874 {
875         struct proc_dir_entry *proc;
876         mode_t mode;
877
878         /*
879          * If parameter uid or gid is not changed, keep the default setting for
880          * our proc entries (-rw-rw-rw-) else, it means we care about security,
881          * and then set to -rw-rw----
882          */
883
884         if ((asus_uid == 0) && (asus_gid == 0)) {
885                 mode = S_IFREG | S_IRUGO | S_IWUGO;
886         } else {
887                 mode = S_IFREG | S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP;
888                 printk(KERN_WARNING "  asus_uid and asus_gid parameters are "
889                        "deprecated, use chown and chmod instead!\n");
890         }
891
892         acpi_device_dir(device) = asus_proc_dir;
893         if (!acpi_device_dir(device))
894                 return -ENODEV;
895
896         proc = create_proc_entry(PROC_INFO, mode, acpi_device_dir(device));
897         if (proc) {
898                 proc->read_proc = proc_read_info;
899                 proc->data = acpi_driver_data(device);
900                 proc->owner = THIS_MODULE;
901                 proc->uid = asus_uid;
902                 proc->gid = asus_gid;
903         } else {
904                 printk(KERN_WARNING "  Unable to create " PROC_INFO
905                        " fs entry\n");
906         }
907
908         if (hotk->methods->mt_wled) {
909                 asus_proc_add(PROC_WLED, &proc_write_wled, &proc_read_wled,
910                               mode, device);
911         }
912
913         if (hotk->methods->mt_ledd) {
914                 asus_proc_add(PROC_LEDD, &proc_write_ledd, &proc_read_ledd,
915                               mode, device);
916         }
917
918         if (hotk->methods->mt_mled) {
919                 asus_proc_add(PROC_MLED, &proc_write_mled, &proc_read_mled,
920                               mode, device);
921         }
922
923         if (hotk->methods->mt_tled) {
924                 asus_proc_add(PROC_TLED, &proc_write_tled, &proc_read_tled,
925                               mode, device);
926         }
927
928         /* 
929          * We need both read node and write method as LCD switch is also accessible
930          * from keyboard 
931          */
932         if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) {
933                 asus_proc_add(PROC_LCD, &proc_write_lcd, &proc_read_lcd, mode,
934                               device);
935         }
936
937         if ((hotk->methods->brightness_up && hotk->methods->brightness_down) ||
938             (hotk->methods->brightness_get && hotk->methods->brightness_set)) {
939                 asus_proc_add(PROC_BRN, &proc_write_brn, &proc_read_brn, mode,
940                               device);
941         }
942
943         if (hotk->methods->display_set) {
944                 asus_proc_add(PROC_DISP, &proc_write_disp, &proc_read_disp,
945                               mode, device);
946         }
947
948         return 0;
949 }
950
951 static int asus_hotk_remove_fs(struct acpi_device *device)
952 {
953         if (acpi_device_dir(device)) {
954                 remove_proc_entry(PROC_INFO, acpi_device_dir(device));
955                 if (hotk->methods->mt_wled)
956                         remove_proc_entry(PROC_WLED, acpi_device_dir(device));
957                 if (hotk->methods->mt_mled)
958                         remove_proc_entry(PROC_MLED, acpi_device_dir(device));
959                 if (hotk->methods->mt_tled)
960                         remove_proc_entry(PROC_TLED, acpi_device_dir(device));
961                 if (hotk->methods->mt_ledd)
962                         remove_proc_entry(PROC_LEDD, acpi_device_dir(device));
963                 if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status)
964                         remove_proc_entry(PROC_LCD, acpi_device_dir(device));
965                 if ((hotk->methods->brightness_up
966                      && hotk->methods->brightness_down)
967                     || (hotk->methods->brightness_get
968                         && hotk->methods->brightness_set))
969                         remove_proc_entry(PROC_BRN, acpi_device_dir(device));
970                 if (hotk->methods->display_set)
971                         remove_proc_entry(PROC_DISP, acpi_device_dir(device));
972         }
973         return 0;
974 }
975
976 static void asus_hotk_notify(acpi_handle handle, u32 event, void *data)
977 {
978         /* TODO Find a better way to handle events count. */
979         if (!hotk)
980                 return;
981
982         if ((event & ~((u32) BR_UP)) < 16) {
983                 hotk->brightness = (event & ~((u32) BR_UP));
984         } else if ((event & ~((u32) BR_DOWN)) < 16) {
985                 hotk->brightness = (event & ~((u32) BR_DOWN));
986         }
987
988         acpi_bus_generate_event(hotk->device, event,
989                                 hotk->event_count[event % 128]++);
990
991         return;
992 }
993
994 /*
995  * This function is used to initialize the hotk with right values. In this
996  * method, we can make all the detection we want, and modify the hotk struct
997  */
998 static int asus_hotk_get_info(void)
999 {
1000         struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
1001         struct acpi_buffer dsdt = { ACPI_ALLOCATE_BUFFER, NULL };
1002         union acpi_object *model = NULL;
1003         int bsts_result;
1004         acpi_status status;
1005
1006         /*
1007          * Get DSDT headers early enough to allow for differentiating between 
1008          * models, but late enough to allow acpi_bus_register_driver() to fail 
1009          * before doing anything ACPI-specific. Should we encounter a machine,
1010          * which needs special handling (i.e. its hotkey device has a different
1011          * HID), this bit will be moved. A global variable asus_info contains
1012          * the DSDT header.
1013          */
1014         status = acpi_get_table(ACPI_TABLE_ID_DSDT, 1, &dsdt);
1015         if (ACPI_FAILURE(status))
1016                 printk(KERN_WARNING "  Couldn't get the DSDT table header\n");
1017         else
1018                 asus_info = (struct acpi_table_header *)dsdt.pointer;
1019
1020         /* We have to write 0 on init this far for all ASUS models */
1021         if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) {
1022                 printk(KERN_ERR "  Hotkey initialization failed\n");
1023                 return -ENODEV;
1024         }
1025
1026         /* This needs to be called for some laptops to init properly */
1027         if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result))
1028                 printk(KERN_WARNING "  Error calling BSTS\n");
1029         else if (bsts_result)
1030                 printk(KERN_NOTICE "  BSTS called, 0x%02x returned\n",
1031                        bsts_result);
1032
1033         /* This is unlikely with implicit return */
1034         if (buffer.pointer == NULL)
1035                 return -EINVAL;
1036
1037         model = (union acpi_object *)buffer.pointer;
1038         /*
1039          * Samsung P30 has a device with a valid _HID whose INIT does not 
1040          * return anything. It used to be possible to catch this exception,
1041          * but the implicit return code will now happily confuse the 
1042          * driver. We assume that every ACPI_TYPE_STRING is a valid model
1043          * identifier but it's still possible to get completely bogus data.
1044          */
1045         if (model->type == ACPI_TYPE_STRING) {
1046                 printk(KERN_NOTICE "  %s model detected, ",
1047                        model->string.pointer);
1048         } else {
1049                 if (asus_info &&        /* Samsung P30 */
1050                     strncmp(asus_info->oem_table_id, "ODEM", 4) == 0) {
1051                         hotk->model = P30;
1052                         printk(KERN_NOTICE
1053                                "  Samsung P30 detected, supported\n");
1054                 } else {
1055                         hotk->model = M2E;
1056                         printk(KERN_WARNING "  no string returned by INIT\n");
1057                         printk(KERN_WARNING "  trying default values, supply "
1058                                "the developers with your DSDT\n");
1059                 }
1060                 hotk->methods = &model_conf[hotk->model];
1061
1062                 kfree(model);
1063
1064                 return AE_OK;
1065         }
1066
1067         hotk->model = END_MODEL;
1068         if (strncmp(model->string.pointer, "L3D", 3) == 0)
1069                 hotk->model = L3D;
1070         else if (strncmp(model->string.pointer, "L3H", 3) == 0 ||
1071                  strncmp(model->string.pointer, "L2E", 3) == 0)
1072                 hotk->model = L3H;
1073         else if (strncmp(model->string.pointer, "L3", 2) == 0 ||
1074                  strncmp(model->string.pointer, "L2B", 3) == 0)
1075                 hotk->model = L3C;
1076         else if (strncmp(model->string.pointer, "L8L", 3) == 0)
1077                 hotk->model = L8L;
1078         else if (strncmp(model->string.pointer, "L4R", 3) == 0)
1079                 hotk->model = L4R;
1080         else if (strncmp(model->string.pointer, "M6N", 3) == 0)
1081                 hotk->model = M6N;
1082         else if (strncmp(model->string.pointer, "M6R", 3) == 0 ||
1083                  strncmp(model->string.pointer, "A3G", 3) == 0)
1084                 hotk->model = M6R;
1085         else if (strncmp(model->string.pointer, "M2N", 3) == 0 ||
1086                  strncmp(model->string.pointer, "M3N", 3) == 0 ||
1087                  strncmp(model->string.pointer, "M5N", 3) == 0 ||
1088                  strncmp(model->string.pointer, "M6N", 3) == 0 ||
1089                  strncmp(model->string.pointer, "S1N", 3) == 0 ||
1090                  strncmp(model->string.pointer, "S5N", 3) == 0)
1091                 hotk->model = xxN;
1092         else if (strncmp(model->string.pointer, "M1", 2) == 0)
1093                 hotk->model = M1A;
1094         else if (strncmp(model->string.pointer, "M2", 2) == 0 ||
1095                  strncmp(model->string.pointer, "L4E", 3) == 0)
1096                 hotk->model = M2E;
1097         else if (strncmp(model->string.pointer, "L2", 2) == 0)
1098                 hotk->model = L2D;
1099         else if (strncmp(model->string.pointer, "L8", 2) == 0)
1100                 hotk->model = S1x;
1101         else if (strncmp(model->string.pointer, "D1", 2) == 0)
1102                 hotk->model = D1x;
1103         else if (strncmp(model->string.pointer, "A1", 2) == 0)
1104                 hotk->model = A1x;
1105         else if (strncmp(model->string.pointer, "A2", 2) == 0)
1106                 hotk->model = A2x;
1107         else if (strncmp(model->string.pointer, "J1", 2) == 0)
1108                 hotk->model = S2x;
1109         else if (strncmp(model->string.pointer, "L5", 2) == 0)
1110                 hotk->model = L5x;
1111         else if (strncmp(model->string.pointer, "W1N", 3) == 0)
1112                 hotk->model = W1N;
1113
1114         if (hotk->model == END_MODEL) {
1115                 printk("unsupported, trying default values, supply the "
1116                        "developers with your DSDT\n");
1117                 hotk->model = M2E;
1118         } else {
1119                 printk("supported\n");
1120         }
1121
1122         hotk->methods = &model_conf[hotk->model];
1123
1124         /* Sort of per-model blacklist */
1125         if (strncmp(model->string.pointer, "L2B", 3) == 0)
1126                 hotk->methods->lcd_status = NULL;
1127         /* L2B is similar enough to L3C to use its settings, with this only 
1128            exception */
1129         else if (strncmp(model->string.pointer, "A3G", 3) == 0)
1130                 hotk->methods->lcd_status = "\\BLFG";
1131         /* A3G is like M6R */
1132         else if (strncmp(model->string.pointer, "S5N", 3) == 0 ||
1133                  strncmp(model->string.pointer, "M5N", 3) == 0)
1134                 hotk->methods->mt_mled = NULL;
1135         /* S5N and M5N have no MLED */
1136         else if (strncmp(model->string.pointer, "M2N", 3) == 0)
1137                 hotk->methods->mt_wled = "WLED";
1138         /* M2N has a usable WLED */
1139         else if (asus_info) {
1140                 if (strncmp(asus_info->oem_table_id, "L1", 2) == 0)
1141                         hotk->methods->mled_status = NULL;
1142                 /* S1300A reports L84F, but L1400B too, account for that */
1143         }
1144
1145         kfree(model);
1146
1147         return AE_OK;
1148 }
1149
1150 static int asus_hotk_check(void)
1151 {
1152         int result = 0;
1153
1154         result = acpi_bus_get_status(hotk->device);
1155         if (result)
1156                 return result;
1157
1158         if (hotk->device->status.present) {
1159                 result = asus_hotk_get_info();
1160         } else {
1161                 printk(KERN_ERR "  Hotkey device not present, aborting\n");
1162                 return -EINVAL;
1163         }
1164
1165         return result;
1166 }
1167
1168 static int asus_hotk_found;
1169
1170 static int asus_hotk_add(struct acpi_device *device)
1171 {
1172         acpi_status status = AE_OK;
1173         int result;
1174
1175         if (!device)
1176                 return -EINVAL;
1177
1178         printk(KERN_NOTICE "Asus Laptop ACPI Extras version %s\n",
1179                ASUS_ACPI_VERSION);
1180
1181         hotk =
1182             (struct asus_hotk *)kmalloc(sizeof(struct asus_hotk), GFP_KERNEL);
1183         if (!hotk)
1184                 return -ENOMEM;
1185         memset(hotk, 0, sizeof(struct asus_hotk));
1186
1187         hotk->handle = device->handle;
1188         strcpy(acpi_device_name(device), ACPI_HOTK_DEVICE_NAME);
1189         strcpy(acpi_device_class(device), ACPI_HOTK_CLASS);
1190         acpi_driver_data(device) = hotk;
1191         hotk->device = device;
1192
1193         result = asus_hotk_check();
1194         if (result)
1195                 goto end;
1196
1197         result = asus_hotk_add_fs(device);
1198         if (result)
1199                 goto end;
1200
1201         /*
1202          * We install the handler, it will receive the hotk in parameter, so, we
1203          * could add other data to the hotk struct
1204          */
1205         status = acpi_install_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY,
1206                                              asus_hotk_notify, hotk);
1207         if (ACPI_FAILURE(status))
1208                 printk(KERN_ERR "  Error installing notify handler\n");
1209
1210         /* For laptops without GPLV: init the hotk->brightness value */
1211         if ((!hotk->methods->brightness_get)
1212             && (!hotk->methods->brightness_status)
1213             && (hotk->methods->brightness_up && hotk->methods->brightness_down)) {
1214                 status =
1215                     acpi_evaluate_object(NULL, hotk->methods->brightness_down,
1216                                          NULL, NULL);
1217                 if (ACPI_FAILURE(status))
1218                         printk(KERN_WARNING "  Error changing brightness\n");
1219                 else {
1220                         status =
1221                             acpi_evaluate_object(NULL,
1222                                                  hotk->methods->brightness_up,
1223                                                  NULL, NULL);
1224                         if (ACPI_FAILURE(status))
1225                                 printk(KERN_WARNING "  Strange, error changing"
1226                                        " brightness\n");
1227                 }
1228         }
1229
1230         asus_hotk_found = 1;
1231
1232         /* LED display is off by default */
1233         hotk->ledd_status = 0xFFF;
1234
1235       end:
1236         if (result) {
1237                 kfree(hotk);
1238         }
1239
1240         return result;
1241 }
1242
1243 static int asus_hotk_remove(struct acpi_device *device, int type)
1244 {
1245         acpi_status status = 0;
1246
1247         if (!device || !acpi_driver_data(device))
1248                 return -EINVAL;
1249
1250         status = acpi_remove_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY,
1251                                             asus_hotk_notify);
1252         if (ACPI_FAILURE(status))
1253                 printk(KERN_ERR "Asus ACPI: Error removing notify handler\n");
1254
1255         asus_hotk_remove_fs(device);
1256
1257         kfree(hotk);
1258
1259         return 0;
1260 }
1261
1262 static int __init asus_acpi_init(void)
1263 {
1264         int result;
1265
1266         if (acpi_disabled)
1267                 return -ENODEV;
1268
1269         if (!acpi_specific_hotkey_enabled) {
1270                 printk(KERN_ERR "Using generic hotkey driver\n");
1271                 return -ENODEV;
1272         }
1273         asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir);
1274         if (!asus_proc_dir) {
1275                 printk(KERN_ERR "Asus ACPI: Unable to create /proc entry\n");
1276                 return -ENODEV;
1277         }
1278         asus_proc_dir->owner = THIS_MODULE;
1279
1280         result = acpi_bus_register_driver(&asus_hotk_driver);
1281         if (result < 0) {
1282                 remove_proc_entry(PROC_ASUS, acpi_root_dir);
1283                 return result;
1284         }
1285
1286         /*
1287          * This is a bit of a kludge.  We only want this module loaded
1288          * for ASUS systems, but there's currently no way to probe the
1289          * ACPI namespace for ASUS HIDs.  So we just return failure if
1290          * we didn't find one, which will cause the module to be
1291          * unloaded.
1292          */
1293         if (!asus_hotk_found) {
1294                 acpi_bus_unregister_driver(&asus_hotk_driver);
1295                 remove_proc_entry(PROC_ASUS, acpi_root_dir);
1296                 return result;
1297         }
1298
1299         return 0;
1300 }
1301
1302 static void __exit asus_acpi_exit(void)
1303 {
1304         acpi_bus_unregister_driver(&asus_hotk_driver);
1305         remove_proc_entry(PROC_ASUS, acpi_root_dir);
1306
1307         kfree(asus_info);
1308
1309         return;
1310 }
1311
1312 module_init(asus_acpi_init);
1313 module_exit(asus_acpi_exit);