ia64/linux-2.6.18-xen.hg

view drivers/acpi/asus_acpi.c @ 897:329ea0ccb344

balloon: try harder to balloon up under memory pressure.

Currently if the balloon driver is unable to increase the guest's
reservation it assumes the failure was due to reaching its full
allocation, gives up on the ballooning operation and records the limit
it reached as the "hard limit". The driver will not try again until
the target is set again (even to the same value).

However it is possible that ballooning has in fact failed due to
memory pressure in the host and therefore it is desirable to keep
attempting to reach the target in case memory becomes available. The
most likely scenario is that some guests are ballooning down while
others are ballooning up and therefore there is temporary memory
pressure while things stabilise. You would not expect a well behaved
toolstack to ask a domain to balloon to more than its allocation nor
would you expect it to deliberately over-commit memory by setting
balloon targets which exceed the total host memory.

This patch drops the concept of a hard limit and causes the balloon
driver to retry increasing the reservation on a timer in the same
manner as when decreasing the reservation.

Also if we partially succeed in increasing the reservation
(i.e. receive less pages than we asked for) then we may as well keep
those pages rather than returning them to Xen.

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