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