/* * asus_acpi.c - Asus Laptop ACPI Extras * * * Copyright (C) 2002, 2003 Julien Lerouge, Karol Kozimor * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * The development page for this driver is located at * http://sourceforge.net/projects/acpi4asus/ * * Credits: * Johann Wiesner - Small compile fixes * John Belmonte - ACPI code for Toshiba laptop was a good starting point. * * TODO * add Fn key status * Add mode selection on module loading (parameter) -> still necessary ? * Complete display switching -- may require dirty hacks? * */ #include #include #include #include #include #include #include #define ASUS_ACPI_VERSION "0.24a" #define PROC_ASUS "asus" //the directory #define PROC_MLED "mled" #define PROC_WLED "wled" #define PROC_INFOS "info" #define PROC_LCD "lcd" #define PROC_BRN "brn" #define PROC_DISP "disp" #define ACPI_HOTK_NAME "Asus Laptop ACPI Extras Driver" #define ACPI_HOTK_CLASS "hotkey" #define ACPI_HOTK_DEVICE_NAME "Hotkey" #define ACPI_HOTK_HID "ATK0100" /* * Some events we use, same for all Asus */ #define BR_UP 0x10 #define BR_DOWN 0x20 /* * Flags for hotk status */ #define MLED_ON 0x01 //is MLED ON ? #define WLED_ON 0x02 MODULE_AUTHOR("Julien Lerouge, Karol Kozimor"); MODULE_DESCRIPTION(ACPI_HOTK_NAME); MODULE_LICENSE("GPL"); EXPORT_NO_SYMBOLS; static uid_t asus_uid = 0; static gid_t asus_gid = 0; MODULE_PARM(asus_uid, "i"); MODULE_PARM_DESC(uid, "UID for entries in /proc/acpi/asus.\n"); MODULE_PARM(asus_gid, "i"); MODULE_PARM_DESC(gid, "GID for entries in /proc/acpi/asus.\n"); /* For each model, all features implemented */ struct model_data { char *name; //name of the laptop char *mt_mled; //method to handle mled char *mled_status; //node to handle mled reading char *mt_wled; //method to handle wled char *wled_status; //node to handle wled reading char *mt_lcd_switch; //method to turn LCD ON/OFF char *lcd_status; //node to read LCD panel state char *brightness_up; //method to set brightness up char *brightness_down; //guess what ? char *brightness_set; //method to set absolute brightness char *brightness_get; //method to get absolute brightness char *brightness_status;//node to get brightness char *display_set; //method to set video output char *display_get; //method to get video output }; /* * This is the main structure, we can use it to store anything interesting * about the hotk device */ struct asus_hotk { struct acpi_device *device; //the device we are in acpi_handle handle; //the handle of the hotk device char status; //status of the hotk, for LEDs, ... struct model_data *methods; //methods available on the laptop u8 brightness; //brighness level enum { L2X = 0, //L200D -> TODO check Q11 (Fn+F8) // Calling this method simply hang the // computer, ISMI method hangs the laptop. L3X, //L3C L3D, //L3400D M2X, //M2400E S1X, //S1300A -> TODO special keys do not work ? D1X, //D1 L1X, //L1400B A1X, //A1340D, A1300F J1X, //S200 (J1) //TODO A1370D does not seems to have a ATK device // L8400 model doesn't have ATK END_MODEL, } model; //Models currently supported u16 event_count[128]; //count for each event TODO make this better }; /* Here we go */ #define L3X_PREFIX "\\_SB.PCI0.PX40.ECD0." #define S1X_PREFIX "\\_SB.PCI0.PX40." #define L1X_PREFIX S1X_PREFIX #define A1X_PREFIX "\\_SB.PCI0.ISA.EC0." #define J1X_PREFIX A1X_PREFIX static struct model_data model_conf[END_MODEL] = { /* * name| mled |mled read| wled |wled read| lcd sw |lcd read | * br up|br down | br set | br read | br status|set disp | get disp * * br set and read shall be in hotk device ! * same for set disp * * TODO I have seen a SWBX and AIBX method on some models, like L1400B, * it seems to be a kind of switch, but what for ? * */ {"L2X", "MLED", "\\SGP6", "WLED", "\\RCP3", "\\Q10", "\\SGP0", "\\Q0E", "\\Q0F", NULL, NULL, NULL, "SDSP", "\\INFB"}, {"L3X", "MLED", NULL, "WLED", NULL, L3X_PREFIX "_Q10", "\\GL32", L3X_PREFIX "_Q0F", L3X_PREFIX "_Q0E", "SPLV", "GPLV", "\\BLVL", "SDSP", "\\_SB.PCI0.PCI1.VGAC.NMAP"}, {"L3D", "MLED", "\\MALD", "WLED", NULL, "\\Q10", "\\BKLG", "\\Q0E", "\\Q0F", "SPLV", "GPLV", "\\BLVL", "SDSP", "\\INFB"}, {"M2X", "MLED", NULL, "WLED", NULL, "\\Q10", "\\GP06", "\\Q0E","\\Q0F", "SPLV", "GPLV", NULL, "SDSP", "\\INFB"}, {"S1X", "MLED", "\\EMLE", "WLED", NULL, S1X_PREFIX "Q10", "\\PNOF", S1X_PREFIX "Q0F", S1X_PREFIX "Q0E", "SPLV", "GPLV", "\\BRIT", NULL, NULL}, {"D1X", "MLED", NULL, NULL, NULL, "\\Q0D", "\\GP11", "\\Q0C", "\\Q0B", NULL, NULL, "\\BLVL", "SDSP","\\INFB"}, {"L1X", "MLED", NULL, "WLED", NULL, L1X_PREFIX "Q10", "\\PNOF", L1X_PREFIX "Q0F", L1X_PREFIX "Q0E", "SPLV", "GPLV", "\\BRIT", NULL, NULL}, {"A1X", "MLED", "\\MAIL", NULL, NULL, A1X_PREFIX "_Q10", "\\BKLI", A1X_PREFIX "_Q0E", A1X_PREFIX "_Q0F", NULL, NULL, NULL, NULL, NULL}, {"J1X", "MLED", "\\MAIL", NULL, NULL, J1X_PREFIX "_Q10", "\\BKLI", J1X_PREFIX "_Q0B", J1X_PREFIX "_Q0A", NULL, NULL, NULL, NULL, NULL} }; /* procdir we use */ static struct proc_dir_entry *asus_proc_dir = NULL; /* * This header is made available to allow proper configuration given model, * revision number , ... this info cannot go in struct asus_hotk because it is * available before the hotk */ static struct acpi_table_header *asus_info = NULL; /* * The hotkey driver declaration */ static int asus_hotk_add(struct acpi_device *device); static int asus_hotk_remove(struct acpi_device *device, int type); static struct acpi_driver asus_hotk_driver = { .name = ACPI_HOTK_NAME, .class = ACPI_HOTK_CLASS, .ids = ACPI_HOTK_HID, .ops = { .add = asus_hotk_add, .remove = asus_hotk_remove, }, }; /* * This function evaluates an ACPI method, given an int as parameter, the * method is searched within the scope of the handle, can be NULL. The output * of the method is written is output, which can also be NULL * * returns 1 if write is successful, 0 else. */ static int write_acpi_int(acpi_handle handle, const char *method, int val, struct acpi_buffer *output) { struct acpi_object_list params; //list of input parameters (an int here) union acpi_object in_obj; //the only param we use acpi_status status; params.count = 1; params.pointer = &in_obj; in_obj.type = ACPI_TYPE_INTEGER; in_obj.integer.value = val; status = acpi_evaluate_object(handle, (char *) method, ¶ms, output); return (status == AE_OK); } static int read_acpi_int(acpi_handle handle, const char *method, int *val) { struct acpi_buffer output; union acpi_object out_obj; acpi_status status; output.length = sizeof(out_obj); output.pointer = &out_obj; status = acpi_evaluate_object(handle, (char*) method, NULL, &output); *val = out_obj.integer.value; return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); } /* * We write our info in page, we begin at offset off and cannot write more * than count bytes. We set eof to 1 if we handle those 2 values. We return the * number of bytes written in page */ static int proc_read_info(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; char buf[16]; //enough for all info /* * We use the easy way, we don't care of off and count, so we don't set eof * to 1 */ len += sprintf(page, ACPI_HOTK_NAME " " ASUS_ACPI_VERSION "\n"); len += sprintf(page + len, "Model reference : %s\n", hotk->methods->name); if (asus_info) { snprintf(buf, 5, "%s", asus_info->signature); len += sprintf(page + len, "ACPI signature : %s\n", buf); snprintf(buf, 16, "%d", asus_info->length); len += sprintf(page + len, "Table length : %s\n", buf); snprintf(buf, 16, "%d", asus_info->revision); len += sprintf(page + len, "ACPI minor version : %s\n", buf); snprintf(buf, 16, "%d", asus_info->checksum); len += sprintf(page + len, "Checksum : %s\n", buf); snprintf(buf, 7, "%s", asus_info->oem_id); len += sprintf(page + len, "OEM identification : %s\n", buf); snprintf(buf, 9, "%s", asus_info->oem_table_id); len += sprintf(page + len, "OEM table id : %s\n", buf); snprintf(buf, 16, "%x", asus_info->oem_revision); len += sprintf(page + len, "OEM rev number : 0x%s\n", buf); snprintf(buf, 5, "%s", asus_info->asl_compiler_id); len += sprintf(page + len, "ASL comp vendor ID : %s\n", buf); snprintf(buf, 16, "%x", asus_info->asl_compiler_revision); len += sprintf(page + len, "ASL comp rev number: 0x%s\n", buf); } return len; } /* * proc file handlers */ static int proc_read_mled(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; int led_status = 0; /* * We use the easy way, we don't care of off and count, so we don't set eof * to 1 */ if (hotk->methods->mled_status) { if (read_acpi_int(NULL, hotk->methods->mled_status, &led_status)) len = sprintf(page, "%d\n", led_status); else printk(KERN_NOTICE "Asus ACPI: Error reading MLED " "status\n"); } else { len = sprintf(page, "%d\n", (hotk->status & MLED_ON) ? 1 : 0); } return len; } static int proc_write_mled(struct file *file, const char *buffer, unsigned long count, void *data) { int value; int led_out = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; /* scan expression. Multiple expressions may be delimited with ; */ if (sscanf(buffer, "%i", &value) == 1) led_out = ~value & 1; hotk->status = (value) ? (hotk->status | MLED_ON) : (hotk->status & ~MLED_ON); /* We don't have to check mt_mled exists if we are here :) */ if (!write_acpi_int(hotk->handle, hotk->methods->mt_mled, led_out, NULL)) printk(KERN_NOTICE "Asus ACPI: MLED write failed\n"); return count; } /* * We write our info in page, we begin at offset off and cannot write more * than count bytes. We set eof to 1 if we handle those 2 values. We return the * number of bytes written in page */ static int proc_read_wled(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; int led_status; if (hotk->methods->wled_status) { if (read_acpi_int(NULL, hotk->methods->mled_status, &led_status)) len = sprintf(page, "%d\n", led_status); else printk(KERN_NOTICE "Asus ACPI: Error reading WLED " "status\n"); } else { len = sprintf(page, "%d\n", (hotk->status & WLED_ON) ? 1 : 0); } return len; } static int proc_write_wled(struct file *file, const char *buffer, unsigned long count, void *data) { int value; int led_out = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; /* scan expression. Multiple expressions may be delimited with ; */ if (sscanf(buffer, "%i", &value) == 1) led_out = value & 1; hotk->status = (value) ? (hotk->status | WLED_ON) : (hotk->status & ~WLED_ON); /* We don't have to check if mt_wled exists if we are here :) */ if (!write_acpi_int(hotk->handle, hotk->methods->mt_wled, led_out, NULL)) printk(KERN_NOTICE "Asus ACPI: WLED write failed\n"); return count; } static int get_lcd_state(struct asus_hotk *hotk) { int lcd = 0; /* We don't have to check anything, if we are here */ if (!read_acpi_int(NULL, hotk->methods->lcd_status, &lcd)) printk(KERN_NOTICE "Asus ACPI: Error reading LCD status\n"); if (hotk->model == L2X) lcd = ~lcd; return (lcd & 1); } static int proc_read_lcd(char *page, char **start, off_t off, int count, int *eof, void *data) { return sprintf(page, "%d\n", get_lcd_state((struct asus_hotk *) data)); } static int proc_write_lcd(struct file *file, const char *buffer, unsigned long count, void *data) { int value; int lcd = 0; acpi_status status = 0; int lcd_status = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; /* scan expression. Multiple expressions may be delimited with ; */ if (sscanf(buffer, "%i", &value) == 1) lcd = value & 1; lcd_status = get_lcd_state(hotk); if (lcd_status != lcd) { /* switch */ status = acpi_evaluate_object(NULL, hotk->methods->mt_lcd_switch, NULL, NULL); if (ACPI_FAILURE(status)) printk(KERN_NOTICE "Asus ACPI: Error switching LCD\n"); } return count; } /* * Change the brightness level */ static void set_brightness(int value, struct asus_hotk *hotk) { acpi_status status = 0; /* ATKD laptop */ if(hotk->methods->brightness_set) { if (!write_acpi_int(hotk->handle, hotk->methods->brightness_set, value, NULL)) printk(KERN_NOTICE "Asus ACPI: Error changing brightness\n"); return; } /* HOTK laptop if we are here, act as appropriate */ value -= hotk->brightness; while (value != 0) { status = acpi_evaluate_object(NULL, (value > 0) ? hotk->methods->brightness_up : hotk->methods->brightness_down, NULL, NULL); (value > 0) ? value-- : value++; if (ACPI_FAILURE(status)) printk(KERN_NOTICE "Asus ACPI: Error changing brightness\n"); } return; } static int read_brightness(struct asus_hotk *hotk) { int value; if(hotk->methods->brightness_get) { /* ATKD laptop */ if (!read_acpi_int(hotk->handle, hotk->methods->brightness_get, &value)) printk(KERN_NOTICE "Asus ACPI: Error reading brightness\n"); } else if (hotk->methods->brightness_status) { /* For D1 for example */ if (!read_acpi_int(NULL, hotk->methods->brightness_status, &value)) printk(KERN_NOTICE "Asus ACPI: Error reading brightness\n"); } else /* HOTK laptop */ value = hotk->brightness; return value; } static int proc_read_brn(char *page, char **start, off_t off, int count, int *eof, void *data) { struct asus_hotk *hotk = (struct asus_hotk *) data; return sprintf(page, "%d\n", read_brightness(hotk)); } static int proc_write_brn(struct file *file, const char *buffer, unsigned long count, void *data) { int value; struct asus_hotk *hotk = (struct asus_hotk *) data; /* scan expression. Multiple expressions may be delimited with ; */ if (sscanf(buffer, "%d", &value) == 1) { value = (0 < value) ? ((15 < value) ? 15 : value) : 0; /* 0 <= value <= 15 */ set_brightness(value, hotk); } else { printk(KERN_NOTICE "Asus ACPI: Error reading user input\n"); } return count; } static void set_display(int value, struct asus_hotk *hotk) { /* no sanity check needed for now */ if (!write_acpi_int(hotk->handle, hotk->methods->display_set, value, NULL)) printk(KERN_NOTICE "Asus ACPI: Error setting display\n"); return; } /* * Now, *this* one could be more user-friendly, but so far, no-one has * complained. The significance of bits is the same as in proc_write_disp() */ static int proc_read_disp(char *page, char **start, off_t off, int count, int *eof, void *data) { int value = 0; struct asus_hotk *hotk = (struct asus_hotk *) data; if (!read_acpi_int(hotk->handle, hotk->methods->display_get, &value)) printk(KERN_NOTICE "Asus ACPI: Error reading display status\n"); return sprintf(page, "%d\n", value); } /* * Preliminary support for display switching. As of now: 0x01 should activate * the LCD output, 0x02 should do for CRT, and 0x04 for TV-Out. Any combination * (bitwise) of these will suffice. I never actually tested 3 displays hooked up * simultaneously, so be warned. */ static int proc_write_disp(struct file *file, const char *buffer, unsigned long count, void *data) { int value; struct asus_hotk *hotk = (struct asus_hotk *) data; /* scan expression. Multiple expressions may be delimited with ; */ if (sscanf(buffer, "%d", &value) == 1) set_display(value, hotk); else { printk(KERN_NOTICE "Asus ACPI: Error reading user input\n"); } return count; } static int asus_hotk_add_fs(struct acpi_device *device) { struct proc_dir_entry *proc; struct asus_hotk *hotk = acpi_driver_data(device); mode_t mode; /* * If parameter uid or gid is not changed, keep the default setting for * our proc entries (-rw-rw-rw-) else, it means we care about security, * and then set to -rw-rw---- */ if ((asus_uid == 0) && (asus_gid == 0)){ mode = S_IFREG | S_IRUGO | S_IWUGO; }else{ mode = S_IFREG | S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP; } acpi_device_dir(device) = asus_proc_dir; if (!acpi_device_dir(device)) return(-ENODEV); proc = create_proc_entry(PROC_INFOS, mode, acpi_device_dir(device)); if (proc) { proc->read_proc = proc_read_info; proc->data = acpi_driver_data(device); proc->owner = THIS_MODULE; proc->uid = asus_uid; proc->gid = asus_gid;; } else { printk(KERN_NOTICE " Unable to create " PROC_INFOS " fs entry\n"); } if (hotk->methods->mt_wled) { proc = create_proc_entry(PROC_WLED, mode, acpi_device_dir(device)); if (proc) { proc->write_proc = proc_write_wled; proc->read_proc = proc_read_wled; proc->data = acpi_driver_data(device); proc->owner = THIS_MODULE; proc->uid = asus_uid; proc->gid = asus_gid;; } else { printk(KERN_NOTICE " Unable to create " PROC_WLED " fs entry\n"); } } if (hotk->methods->mt_mled) { proc = create_proc_entry(PROC_MLED, mode, acpi_device_dir(device)); if (proc) { proc->write_proc = proc_write_mled; proc->read_proc = proc_read_mled; proc->data = acpi_driver_data(device); proc->owner = THIS_MODULE; proc->uid = asus_uid; proc->gid = asus_gid;; } else { printk(KERN_NOTICE " Unable to create " PROC_MLED " fs entry\n"); } } /* * We need both read node and write method as LCD switch is also accessible * from keyboard */ if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) { proc = create_proc_entry(PROC_LCD, mode, acpi_device_dir(device)); if (proc) { proc->write_proc = proc_write_lcd; proc->read_proc = proc_read_lcd; proc->data = acpi_driver_data(device); proc->owner = THIS_MODULE; proc->uid = asus_uid; proc->gid = asus_gid;; } else { printk(KERN_NOTICE " Unable to create " PROC_LCD " fs entry\n"); } } if ((hotk->methods->brightness_up && hotk->methods->brightness_down) || (hotk->methods->brightness_get && hotk->methods->brightness_get)) { proc = create_proc_entry(PROC_BRN, mode, acpi_device_dir(device)); if (proc) { proc->write_proc = proc_write_brn; proc->read_proc = proc_read_brn; proc->data = acpi_driver_data(device); proc->owner = THIS_MODULE; proc->uid = asus_uid; proc->gid = asus_gid;; } else { printk(KERN_NOTICE " Unable to create " PROC_BRN " fs entry\n"); } } if (hotk->methods->display_set) { proc = create_proc_entry(PROC_DISP, mode, acpi_device_dir(device)); if (proc) { proc->write_proc = proc_write_disp; proc->read_proc = proc_read_disp; proc->data = acpi_driver_data(device); proc->owner = THIS_MODULE; proc->uid = asus_uid; proc->gid = asus_gid;; } else { printk(KERN_NOTICE " Unable to create " PROC_DISP " fs entry\n"); } } return (AE_OK); } static void asus_hotk_notify(acpi_handle handle, u32 event, void *data) { /* TODO Find a better way to handle events count. Here, in data, we receive * the hotk, so we can make anything !! */ struct asus_hotk *hotk = (struct asus_hotk *) data; if (!hotk) return; if ((event & ~((u32) BR_UP)) < 16) { hotk->brightness = (event & ~((u32) BR_UP)); } else if ((event & ~((u32) BR_DOWN)) < 16 ) { hotk->brightness = (event & ~((u32) BR_DOWN)); } acpi_bus_generate_event(hotk->device, event, hotk->event_count[event % 128]++); return; } /* * This function is used to initialize the hotk with right values. In this * method, we can make all the detection we want, and modify the hotk struct */ static int asus_hotk_get_info(struct asus_hotk *hotk) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *model = NULL; /* * We have to write 0 on init this far for all ASUS models */ if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) { printk(KERN_NOTICE " Hotkey initialization failed\n"); return -ENODEV; } /* * Here, we also use asus_info to make decision. For example, on INIT * method, S1X and L1X models both reports to be L84F, but they don't * have the same methods (L1X has WLED, S1X don't) */ model = (union acpi_object *) buffer.pointer; if (model->type == ACPI_TYPE_STRING) { printk(KERN_NOTICE " %s model detected, ", model->string.pointer); } hotk->model = END_MODEL; if (strncmp(model->string.pointer, "L3D", 3) == 0) hotk->model = L3D; /* * L2B has same settings that L3X, except for GL32, but as * there is no node to get the LCD status, and as GL32 is never * used anywhere else, I assume it's safe, even if lcd get is * broken for this model (TODO fix it ?) */ else if (strncmp(model->string.pointer, "L3", 2) == 0 || strncmp(model->string.pointer, "L2B", 3) == 0) hotk->model = L3X; else if (strncmp(model->string.pointer, "M2", 2) == 0) hotk->model = M2X; else if (strncmp(model->string.pointer, "L2", 2) == 0) hotk->model = L2X; else if (strncmp(model->string.pointer, "L8", 2) == 0) /* S1300A reports L84F, but L1400B too */ if (strncmp(asus_info->oem_table_id, "L1", 2) == 0) hotk->model = L1X; else hotk->model = S1X; else if (strncmp(model->string.pointer, "D1", 2) == 0) hotk->model = D1X; else if (strncmp(model->string.pointer, "A1", 2) == 0) hotk->model = A1X; else if (strncmp(model->string.pointer, "J1", 2) == 0) hotk->model = J1X; if (hotk->model == END_MODEL) { /* By default use the same values, as I don't know others */ printk("unsupported, trying default values, contact the " "developers\n"); hotk->model = L2X; } else { printk("supported\n"); } hotk->methods = &model_conf[hotk->model]; acpi_os_free(model); return AE_OK; } static int asus_hotk_check(struct asus_hotk *hotk) { int result = 0; if (!hotk) return(-EINVAL); result = acpi_bus_get_status(hotk->device); if (result) return(result); if (hotk->device->status.present) { result = asus_hotk_get_info(hotk); } else { printk(KERN_NOTICE " Hotkey device not present, aborting\n"); return(-EINVAL); } return(result); } static int asus_hotk_add(struct acpi_device *device) { struct asus_hotk *hotk = NULL; acpi_status status = AE_OK; int result; if (!device) return(-EINVAL); hotk = (struct asus_hotk *) kmalloc(sizeof(struct asus_hotk), GFP_KERNEL); if (!hotk) return(-ENOMEM); memset(hotk, 0, sizeof(struct asus_hotk)); hotk->handle = device->handle; sprintf(acpi_device_name(device), "%s", ACPI_HOTK_DEVICE_NAME); sprintf(acpi_device_class(device), "%s", ACPI_HOTK_CLASS); acpi_driver_data(device) = hotk; hotk->device = device; result = asus_hotk_check(hotk); if (result) goto end; result = asus_hotk_add_fs(device); if (result) goto end; /* * We install the handler, it will receive the hotk in parameter, so, we * could add other data to the hotk struct */ status = acpi_install_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY, asus_hotk_notify, hotk); if (ACPI_FAILURE(status)) { printk(KERN_NOTICE " Error installing notify handler\n"); } else { printk(KERN_DEBUG " Notify Handler installed successfully\n"); } /* For HOTK laptops: init the hotk->brightness value */ if ((!hotk->methods->brightness_get) && (!hotk->methods->brightness_status) && (hotk->methods->brightness_up && hotk->methods->brightness_down)) { status = acpi_evaluate_object(NULL, hotk->methods->brightness_down, NULL, NULL); if (ACPI_FAILURE(status)) printk(KERN_NOTICE " Error changing brightness\n"); status = acpi_evaluate_object(NULL, hotk->methods->brightness_up, NULL, NULL); if (ACPI_FAILURE(status)) printk(KERN_NOTICE " Error changing brightness\n"); } end: if (result) { kfree(hotk); } return(result); } static int asus_hotk_remove(struct acpi_device *device, int type) { acpi_status status = 0; struct asus_hotk *hotk = NULL; if (!device || !acpi_driver_data(device)) return(-EINVAL); hotk = (struct asus_hotk *) acpi_driver_data(device); status = acpi_remove_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY, asus_hotk_notify); if (ACPI_FAILURE(status)) printk(KERN_NOTICE "Error removing notify handler\n"); kfree(hotk); return(0); } static int __init asus_acpi_init(void) { int result = 0; acpi_status status = 0; struct acpi_buffer dsdt = { ACPI_ALLOCATE_BUFFER, NULL }; printk(KERN_NOTICE "Asus Laptop ACPI Extras version %s\n", ASUS_ACPI_VERSION); /* * Here is the code to know the model we are running on. We need to * know this before calling the acpi_bus_register_driver function, in * case the HID for the laptop we are running on is different from * ACPI_HOTK_HID, which I have never seen yet :) * * This information is then available in the global var asus_info */ status = acpi_get_table(ACPI_TABLE_DSDT, 1, &dsdt); if (ACPI_FAILURE(status)) { printk(KERN_NOTICE " Couldn't get the DSDT table header\n"); } else { asus_info = (struct acpi_table_header *) dsdt.pointer; } asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir); if (!asus_proc_dir) return(-ENODEV); asus_proc_dir->owner = THIS_MODULE; result = acpi_bus_register_driver(&asus_hotk_driver); if (result < 0) { printk(KERN_NOTICE " Error registering " ACPI_HOTK_NAME " \n"); remove_proc_entry(PROC_ASUS, acpi_root_dir); return(-ENODEV); } return(0); } static void __exit asus_acpi_exit(void) { acpi_bus_unregister_driver(&asus_hotk_driver); remove_proc_entry(PROC_ASUS, acpi_root_dir); acpi_os_free(asus_info); return; } module_init(asus_acpi_init); module_exit(asus_acpi_exit);