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