diff options
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/Kconfig | 85 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 3 | ||||
-rw-r--r-- | drivers/platform/x86/asus_acpi.c | 1460 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_acpi.c | 863 | ||||
-rw-r--r-- | drivers/platform/x86/wmi.c | 747 |
5 files changed, 3158 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0a9a5b9440af..31f3ce2ac01c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -287,4 +287,89 @@ config EEEPC_LAPTOP If you have an Eee PC laptop, say Y or M here. + +config ACPI_WMI + tristate "WMI (EXPERIMENTAL)" + depends on ACPI + depends on EXPERIMENTAL + help + This driver adds support for the ACPI-WMI (Windows Management + Instrumentation) mapper device (PNP0C14) found on some systems. + + ACPI-WMI is a proprietary extension to ACPI to expose parts of the + ACPI firmware to userspace - this is done through various vendor + defined methods and data blocks in a PNP0C14 device, which are then + made available for userspace to call. + + The implementation of this in Linux currently only exposes this to + other kernel space drivers. + + This driver is a required dependency to build the firmware specific + drivers needed on many machines, including Acer and HP laptops. + + It is safe to enable this driver even if your DSDT doesn't define + any ACPI-WMI devices. + +config ACPI_ASUS + tristate "ASUS/Medion Laptop Extras" + depends on ACPI + select BACKLIGHT_CLASS_DEVICE + ---help--- + This driver provides support for extra features of ACPI-compatible + ASUS laptops. As some of Medion laptops are made by ASUS, it may also + support some Medion laptops (such as 9675 for example). It makes all + the extra buttons generate standard ACPI events that go through + /proc/acpi/events, and (on some models) adds support for changing the + display brightness and output, switching the LCD backlight on and off, + and most importantly, allows you to blink those fancy LEDs intended + for reporting mail and wireless status. + + Note: display switching code is currently considered EXPERIMENTAL, + toying with these values may even lock your machine. + + All settings are changed via /proc/acpi/asus directory entries. Owner + and group for these entries can be set with asus_uid and asus_gid + parameters. + + More information and a userspace daemon for handling the extra buttons + at <http://sourceforge.net/projects/acpi4asus/>. + + If you have an ACPI-compatible ASUS laptop, say Y or M here. This + driver is still under development, so if your laptop is unsupported or + something works not quite as expected, please use the mailing list + available on the above page (acpi4asus-user@lists.sourceforge.net). + + NOTE: This driver is deprecated and will probably be removed soon, + use asus-laptop instead. + +config ACPI_TOSHIBA + tristate "Toshiba Laptop Extras" + depends on ACPI + depends on INPUT + select INPUT_POLLDEV + select NET + select RFKILL + select BACKLIGHT_CLASS_DEVICE + ---help--- + This driver adds support for access to certain system settings + on "legacy free" Toshiba laptops. These laptops can be recognized by + their lack of a BIOS setup menu and APM support. + + On these machines, all system configuration is handled through the + ACPI. This driver is required for access to controls not covered + by the general ACPI drivers, such as LCD brightness, video output, + etc. + + This driver differs from the non-ACPI Toshiba laptop driver (located + under "Processor type and features") in several aspects. + Configuration is accessed by reading and writing text files in the + /proc tree instead of by program interface to /dev. Furthermore, no + power management functions are exposed, as those are handled by the + general ACPI drivers. + + More information about this driver is available at + <http://memebeam.org/toys/ToshibaAcpiDriver>. + + If you have a legacy free Toshiba laptop (such as the Libretto L1 + series), say Y. endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 4d26b1bf22ae..1e9de2ae0de5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -14,3 +14,6 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o +obj-$(CONFIG_ACPI_WMI) += wmi.o +obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o +obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o diff --git a/drivers/platform/x86/asus_acpi.c b/drivers/platform/x86/asus_acpi.c new file mode 100644 index 000000000000..1e74988c7b2d --- /dev/null +++ b/drivers/platform/x86/asus_acpi.c @@ -0,0 +1,1460 @@ +/* + * asus_acpi.c - Asus Laptop ACPI Extras + * + * + * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 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: + * Pontus Fuchs - Helper functions, cleanup + * Johann Wiesner - Small compile fixes + * John Belmonte - ACPI code for Toshiba laptop was a good starting point. + * �ic Burghard - LED display support for W1N + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/backlight.h> +#include <acpi/acpi_drivers.h> +#include <acpi/acpi_bus.h> +#include <asm/uaccess.h> + +#define ASUS_ACPI_VERSION "0.30" + +#define PROC_ASUS "asus" /* The directory */ +#define PROC_MLED "mled" +#define PROC_WLED "wled" +#define PROC_TLED "tled" +#define PROC_BT "bluetooth" +#define PROC_LEDD "ledd" +#define PROC_INFO "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" + +/* + * Some events we use, same for all Asus + */ +#define BR_UP 0x10 +#define BR_DOWN 0x20 + +/* + * Flags for hotk status + */ +#define MLED_ON 0x01 /* Mail LED */ +#define WLED_ON 0x02 /* Wireless LED */ +#define TLED_ON 0x04 /* Touchpad LED */ +#define BT_ON 0x08 /* Internal Bluetooth */ + +MODULE_AUTHOR("Julien Lerouge, Karol Kozimor"); +MODULE_DESCRIPTION(ACPI_HOTK_NAME); +MODULE_LICENSE("GPL"); + +static uid_t asus_uid; +static gid_t asus_gid; +module_param(asus_uid, uint, 0); +MODULE_PARM_DESC(asus_uid, "UID for entries in /proc/acpi/asus"); +module_param(asus_gid, uint, 0); +MODULE_PARM_DESC(asus_gid, "GID for entries in /proc/acpi/asus"); + +/* For each model, all features implemented, + * those marked with R are relative to HOTK, A for absolute */ +struct model_data { + char *name; /* name of the laptop________________A */ + char *mt_mled; /* method to handle mled_____________R */ + char *mled_status; /* node to handle mled reading_______A */ + char *mt_wled; /* method to handle wled_____________R */ + char *wled_status; /* node to handle wled reading_______A */ + char *mt_tled; /* method to handle tled_____________R */ + char *tled_status; /* node to handle tled reading_______A */ + char *mt_ledd; /* method to handle LED display______R */ + char *mt_bt_switch; /* method to switch Bluetooth on/off_R */ + char *bt_status; /* no model currently supports this__? */ + char *mt_lcd_switch; /* method to turn LCD on/off_________A */ + char *lcd_status; /* node to read LCD panel state______A */ + char *brightness_up; /* method to set brightness up_______A */ + char *brightness_down; /* method to set brightness down ____A */ + char *brightness_set; /* method to set absolute brightness_R */ + char *brightness_get; /* method to get absolute brightness_R */ + char *brightness_status;/* node to get brightness____________A */ + char *display_set; /* method to set video output________R */ + char *display_get; /* method to get video output________R */ +}; + +/* + * 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 */ + u32 ledd_status; /* status of the LED display */ + struct model_data *methods; /* methods available on the laptop */ + u8 brightness; /* brightness level */ + enum { + A1x = 0, /* A1340D, A1300F */ + A2x, /* A2500H */ + A4G, /* A4700G */ + D1x, /* D1 */ + L2D, /* L2000D */ + L3C, /* L3800C */ + L3D, /* L3400D */ + L3H, /* L3H, L2000E, L5D */ + L4R, /* L4500R */ + L5x, /* L5800C */ + L8L, /* L8400L */ + M1A, /* M1300A */ + M2E, /* M2400E, L4400L */ + M6N, /* M6800N, W3400N */ + M6R, /* M6700R, A3000G */ + P30, /* Samsung P30 */ + S1x, /* S1300A, but also L1400B and M2400A (L84F) */ + S2x, /* S200 (J1 reported), Victor MP-XP7210 */ + W1N, /* W1000N */ + W5A, /* W5A */ + W3V, /* W3030V */ + xxN, /* M2400N, M3700N, M5200N, M6800N, + S1300N, S5200N*/ + A4S, /* Z81sp */ + F3Sa, /* (Centrino) */ + END_MODEL + } model; /* Models currently supported */ + u16 event_count[128]; /* Count for each event TODO make this better */ +}; + +/* Here we go */ +#define A1x_PREFIX "\\_SB.PCI0.ISA.EC0." +#define L3C_PREFIX "\\_SB.PCI0.PX40.ECD0." +#define M1A_PREFIX "\\_SB.PCI0.PX40.EC0." +#define P30_PREFIX "\\_SB.PCI0.LPCB.EC0." +#define S1x_PREFIX "\\_SB.PCI0.PX40." +#define S2x_PREFIX A1x_PREFIX +#define xxN_PREFIX "\\_SB.PCI0.SBRG.EC0." + +static struct model_data model_conf[END_MODEL] = { + /* + * 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 ? + */ + + { + .name = "A1x", + .mt_mled = "MLED", + .mled_status = "\\MAIL", + .mt_lcd_switch = A1x_PREFIX "_Q10", + .lcd_status = "\\BKLI", + .brightness_up = A1x_PREFIX "_Q0E", + .brightness_down = A1x_PREFIX "_Q0F"}, + + { + .name = "A2x", + .mt_mled = "MLED", + .mt_wled = "WLED", + .wled_status = "\\SG66", + .mt_lcd_switch = "\\Q10", + .lcd_status = "\\BAOF", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "A4G", + .mt_mled = "MLED", +/* WLED present, but not controlled by ACPI */ + .mt_lcd_switch = xxN_PREFIX "_Q10", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\ADVG"}, + + { + .name = "D1x", + .mt_mled = "MLED", + .mt_lcd_switch = "\\Q0D", + .lcd_status = "\\GP11", + .brightness_up = "\\Q0C", + .brightness_down = "\\Q0B", + .brightness_status = "\\BLVL", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "L2D", + .mt_mled = "MLED", + .mled_status = "\\SGP6", + .mt_wled = "WLED", + .wled_status = "\\RCP3", + .mt_lcd_switch = "\\Q10", + .lcd_status = "\\SGP0", + .brightness_up = "\\Q0E", + .brightness_down = "\\Q0F", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "L3C", + .mt_mled = "MLED", + .mt_wled = "WLED", + .mt_lcd_switch = L3C_PREFIX "_Q10", + .lcd_status = "\\GL32", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\_SB.PCI0.PCI1.VGAC.NMAP"}, + + { + .name = "L3D", + .mt_mled = "MLED", + .mled_status = "\\MALD", + .mt_wled = "WLED", + .mt_lcd_switch = "\\Q10", + .lcd_status = "\\BKLG", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "L3H", + .mt_mled = "MLED", + .mt_wled = "WLED", + .mt_lcd_switch = "EHK", + .lcd_status = "\\_SB.PCI0.PM.PBC", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "L4R", + .mt_mled = "MLED", + .mt_wled = "WLED", + .wled_status = "\\_SB.PCI0.SBRG.SG13", + .mt_lcd_switch = xxN_PREFIX "_Q10", + .lcd_status = "\\_SB.PCI0.SBSM.SEO4", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"}, + + { + .name = "L5x", + .mt_mled = "MLED", +/* WLED present, but not controlled by ACPI */ + .mt_tled = "TLED", + .mt_lcd_switch = "\\Q0D", + .lcd_status = "\\BAOF", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "L8L" +/* No features, but at least support the hotkeys */ + }, + + { + .name = "M1A", + .mt_mled = "MLED", + .mt_lcd_switch = M1A_PREFIX "Q10", + .lcd_status = "\\PNOF", + .brightness_up = M1A_PREFIX "Q0E", + .brightness_down = M1A_PREFIX "Q0F", + .brightness_status = "\\BRIT", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "M2E", + .mt_mled = "MLED", + .mt_wled = "WLED", + .mt_lcd_switch = "\\Q10", + .lcd_status = "\\GP06", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "M6N", + .mt_mled = "MLED", + .mt_wled = "WLED", + .wled_status = "\\_SB.PCI0.SBRG.SG13", + .mt_lcd_switch = xxN_PREFIX "_Q10", + .lcd_status = "\\_SB.BKLT", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\SSTE"}, + + { + .name = "M6R", + .mt_mled = "MLED", + .mt_wled = "WLED", + .mt_lcd_switch = xxN_PREFIX "_Q10", + .lcd_status = "\\_SB.PCI0.SBSM.SEO4", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"}, + + { + .name = "P30", + .mt_wled = "WLED", + .mt_lcd_switch = P30_PREFIX "_Q0E", + .lcd_status = "\\BKLT", + .brightness_up = P30_PREFIX "_Q68", + .brightness_down = P30_PREFIX "_Q69", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\DNXT"}, + + { + .name = "S1x", + .mt_mled = "MLED", + .mled_status = "\\EMLE", + .mt_wled = "WLED", + .mt_lcd_switch = S1x_PREFIX "Q10", + .lcd_status = "\\PNOF", + .brightness_set = "SPLV", + .brightness_get = "GPLV"}, + + { + .name = "S2x", + .mt_mled = "MLED", + .mled_status = "\\MAIL", + .mt_lcd_switch = S2x_PREFIX "_Q10", + .lcd_status = "\\BKLI", + .brightness_up = S2x_PREFIX "_Q0B", + .brightness_down = S2x_PREFIX "_Q0A"}, + + { + .name = "W1N", + .mt_mled = "MLED", + .mt_wled = "WLED", + .mt_ledd = "SLCM", + .mt_lcd_switch = xxN_PREFIX "_Q10", + .lcd_status = "\\BKLT", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\ADVG"}, + + { + .name = "W5A", + .mt_bt_switch = "BLED", + .mt_wled = "WLED", + .mt_lcd_switch = xxN_PREFIX "_Q10", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\ADVG"}, + + { + .name = "W3V", + .mt_mled = "MLED", + .mt_wled = "WLED", + .mt_lcd_switch = xxN_PREFIX "_Q10", + .lcd_status = "\\BKLT", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\INFB"}, + + { + .name = "xxN", + .mt_mled = "MLED", +/* WLED present, but not controlled by ACPI */ + .mt_lcd_switch = xxN_PREFIX "_Q10", + .lcd_status = "\\BKLT", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .display_set = "SDSP", + .display_get = "\\ADVG"}, + + { + .name = "A4S", + .brightness_set = "SPLV", + .brightness_get = "GPLV", + .mt_bt_switch = "BLED", + .mt_wled = "WLED" + }, + + { + .name = "F3Sa", + .mt_bt_switch = "BLED", + .mt_wled = "WLED", + .mt_mled = "MLED", + .brightness_get = "GPLV", + .brightness_set = "SPLV", + .mt_lcd_switch = "\\_SB.PCI0.SBRG.EC0._Q10", + .lcd_status = "\\_SB.PCI0.SBRG.EC0.RPIN", + .display_get = "\\ADVG", + .display_set = "SDSP", + }, + +}; + +/* procdir we use */ +static struct proc_dir_entry *asus_proc_dir; + +static struct backlight_device *asus_backlight_device; + +/* + * 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; + +/* The actual device the driver binds to */ +static struct asus_hotk *hotk; + +/* + * The hotkey driver and autoloading declaration + */ +static int asus_hotk_add(struct acpi_device *device); +static int asus_hotk_remove(struct acpi_device *device, int type); +static const struct acpi_device_id asus_device_ids[] = { + {"ATK0100", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, asus_device_ids); + +static struct acpi_driver asus_hotk_driver = { + .name = "asus_acpi", + .class = ACPI_HOTK_CLASS, + .ids = asus_device_ids, + .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 (int) */ + 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; + int temp; + 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); + /* + * The SFUN method probably allows the original driver to get the list + * of features supported by a given model. For now, 0x0100 or 0x0800 + * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. + * The significance of others is yet to be found. + */ + if (read_acpi_int(hotk->handle, "SFUN", &temp)) + len += + sprintf(page + len, "SFUN value : 0x%04x\n", temp); + /* + * Another value for userspace: the ASYM method returns 0x02 for + * battery low and 0x04 for battery critical, its readings tend to be + * more accurate than those provided by _BST. + * Note: since not all the laptops provide this method, errors are + * silently ignored. + */ + if (read_acpi_int(hotk->handle, "ASYM", &temp)) + len += + sprintf(page + len, "ASYM value : 0x%04x\n", temp); + if (asus_info) { + snprintf(buf, 16, "%d", asus_info->length); + len += sprintf(page + len, "DSDT length : %s\n", buf); + snprintf(buf, 16, "%d", asus_info->checksum); + len += sprintf(page + len, "DSDT checksum : %s\n", buf); + snprintf(buf, 16, "%d", asus_info->revision); + len += sprintf(page + len, "DSDT revision : %s\n", buf); + snprintf(buf, 7, "%s", asus_info->oem_id); + len += sprintf(page + len, "OEM id : %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 revision : 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 revision : 0x%s\n", buf); + } + + return len; +} + +/* + * /proc handlers + * 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 + */ + +/* Generic LED functions */ +static int read_led(const char *ledname, int ledmask) +{ + if (ledname) { + int led_status; + + if (read_acpi_int(NULL, ledname, &led_status)) + return led_status; + else + printk(KERN_WARNING "Asus ACPI: Error reading LED " + "status\n"); + } + return (hotk->status & ledmask) ? 1 : 0; +} + +static int parse_arg(const char __user *buf, unsigned long count, int *val) +{ + char s[32]; + if (!count) + return 0; + if (count > 31) + return -EINVAL; + if (copy_from_user(s, buf, count)) + return -EFAULT; + s[count] = 0; + if (sscanf(s, "%i", val) != 1) + return -EINVAL; + return count; +} + +/* FIXME: kill extraneous args so it can be called independently */ +static int +write_led(const char __user *buffer, unsigned long count, + char *ledname, int ledmask, int invert) +{ + int rv, value; + int led_out = 0; + + rv = parse_arg(buffer, count, &value); + if (rv > 0) + led_out = value ? 1 : 0; + + hotk->status = + (led_out) ? (hotk->status | ledmask) : (hotk->status & ~ledmask); + + if (invert) /* invert target value */ + led_out = !led_out; + + if (!write_acpi_int(hotk->handle, ledname, led_out, NULL)) + printk(KERN_WARNING "Asus ACPI: LED (%s) write failed\n", + ledname); + + return rv; +} + +/* + * Proc handlers for MLED + */ +static int +proc_read_mled(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + return sprintf(page, "%d\n", + read_led(hotk->methods->mled_status, MLED_ON)); +} + +static int +proc_write_mled(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + return write_led(buffer, count, hotk->methods->mt_mled, MLED_ON, 1); +} + +/* + * Proc handlers for LED display + */ +static int +proc_read_ledd(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + return sprintf(page, "0x%08x\n", hotk->ledd_status); +} + +static int +proc_write_ledd(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + int rv, value; + + rv = parse_arg(buffer, count, &value); + if (rv > 0) { + if (!write_acpi_int + (hotk->handle, hotk->methods->mt_ledd, value, NULL)) + printk(KERN_WARNING + "Asus ACPI: LED display write failed\n"); + else + hotk->ledd_status = (u32) value; + } + return rv; +} + +/* + * Proc handlers for WLED + */ +static int +proc_read_wled(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + return sprintf(page, "%d\n", + read_led(hotk->methods->wled_status, WLED_ON)); +} + +static int +proc_write_wled(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + return write_led(buffer, count, hotk->methods->mt_wled, WLED_ON, 0); +} + +/* + * Proc handlers for Bluetooth + */ +static int +proc_read_bluetooth(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + return sprintf(page, "%d\n", read_led(hotk->methods->bt_status, BT_ON)); +} + +static int +proc_write_bluetooth(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + /* Note: mt_bt_switch controls both internal Bluetooth adapter's + presence and its LED */ + return write_led(buffer, count, hotk->methods->mt_bt_switch, BT_ON, 0); +} + +/* + * Proc handlers for TLED + */ +static int +proc_read_tled(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + return sprintf(page, "%d\n", + read_led(hotk->methods->tled_status, TLED_ON)); +} + +static int +proc_write_tled(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + return write_led(buffer, count, hotk->methods->mt_tled, TLED_ON, 0); +} + +static int get_lcd_state(void) +{ + int lcd = 0; + + if (hotk->model == L3H) { + /* L3H and the like have to be handled differently */ + acpi_status status = 0; + struct acpi_object_list input; + union acpi_object mt_params[2]; + struct acpi_buffer output; + union acpi_object out_obj; + + input.count = 2; + input.pointer = mt_params; + /* Note: the following values are partly guessed up, but + otherwise they seem to work */ + mt_params[0].type = ACPI_TYPE_INTEGER; + mt_params[0].integer.value = 0x02; + mt_params[1].type = ACPI_TYPE_INTEGER; + mt_params[1].integer.value = 0x02; + + output.length = sizeof(out_obj); + output.pointer = &out_obj; + + status = + acpi_evaluate_object(NULL, hotk->methods->lcd_status, + &input, &output); + if (status != AE_OK) + return -1; + if (out_obj.type == ACPI_TYPE_INTEGER) + /* That's what the AML code does */ + lcd = out_obj.integer.value >> 8; + } else if (hotk->model == F3Sa) { + unsigned long long tmp; + union acpi_object param; + struct acpi_object_list input; + acpi_status status; + + /* Read pin 11 */ + param.type = ACPI_TYPE_INTEGER; + param.integer.value = 0x11; + input.count = 1; + input.pointer = ¶m; + + status = acpi_evaluate_integer(NULL, hotk->methods->lcd_status, + &input, &tmp); + if (status != AE_OK) + return -1; + + lcd = tmp; + } else { + /* We don't have to check anything if we are here */ + if (!read_acpi_int(NULL, hotk->methods->lcd_status, &lcd)) + printk(KERN_WARNING + "Asus ACPI: Error reading LCD status\n"); + + if (hotk->model == L2D) + lcd = ~lcd; + } + + return (lcd & 1); +} + +static int set_lcd_state(int value) +{ + int lcd = 0; + acpi_status status = 0; + + lcd = value ? 1 : 0; + if (lcd != get_lcd_state()) { + /* switch */ + if (hotk->model != L3H) { + status = + acpi_evaluate_object(NULL, + hotk->methods->mt_lcd_switch, + NULL, NULL); + } else { + /* L3H and the like must be handled differently */ + if (!write_acpi_int + (hotk->handle, hotk->methods->mt_lcd_switch, 0x07, + NULL)) + status = AE_ERROR; + /* L3H's AML executes EHK (0x07) upon Fn+F7 keypress, + the exact behaviour is simulated here */ + } + if (ACPI_FAILURE(status)) + printk(KERN_WARNING "Asus ACPI: Error switching LCD\n"); + } + return 0; + +} + +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()); +} + +static int +proc_write_lcd(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + int rv, value; + + rv = parse_arg(buffer, count, &value); + if (rv > 0) + set_lcd_state(value); + return rv; +} + +static int read_brightness(struct backlight_device *bd) +{ + int value; + + if (hotk->methods->brightness_get) { /* SPLV/GPLV laptop */ + if (!read_acpi_int(hotk->handle, hotk->methods->brightness_get, + &value)) + printk(KERN_WARNING + "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_WARNING + "Asus ACPI: Error reading brightness\n"); + } else /* No GPLV method */ + value = hotk->brightness; + return value; +} + +/* + * Change the brightness level + */ +static int set_brightness(int value) +{ + acpi_status status = 0; + int ret = 0; + + /* SPLV laptop */ + if (hotk->methods->brightness_set) { + if (!write_acpi_int(hotk->handle, hotk->methods->brightness_set, + value, NULL)) + printk(KERN_WARNING + "Asus ACPI: Error changing brightness\n"); + ret = -EIO; + goto out; + } + + /* No SPLV method if we are here, act as appropriate */ + value -= read_brightness(NULL); + 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_WARNING + "Asus ACPI: Error changing brightness\n"); + ret = -EIO; + } +out: + return ret; +} + +static int set_brightness_status(struct backlight_device *bd) +{ + return set_brightness(bd->props.brightness); +} + +static int +proc_read_brn(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + return sprintf(page, "%d\n", read_brightness(NULL)); +} + +static int +proc_write_brn(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + int rv, value; + + rv = parse_arg(buffer, count, &value); + if (rv > 0) { + value = (0 < value) ? ((15 < value) ? 15 : value) : 0; + /* 0 <= value <= 15 */ + set_brightness(value); + } + return rv; +} + +static void set_display(int value) +{ + /* no sanity check needed for now */ + if (!write_acpi_int(hotk->handle, hotk->methods->display_set, + value, NULL)) + printk(KERN_WARNING "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; + + if (!read_acpi_int(hotk->handle, hotk->methods->display_get, &value)) + printk(KERN_WARNING + "Asus ACPI: Error reading display status\n"); + value &= 0x07; /* needed for some models, shouldn't hurt others */ + return sprintf(page, "%d\n", value); +} + +/* + * Experimental support for display switching. As of now: 1 should activate + * the LCD output, 2 should do for CRT, and 4 for TV-Out. Any combination + * (bitwise) of these will suffice. I never actually tested 3 displays hooked + * up simultaneously, so be warned. See the acpi4asus README for more info. + */ +static int +proc_write_disp(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + int rv, value; + + rv = parse_arg(buffer, count, &value); + if (rv > 0) + set_display(value); + return rv; +} + +typedef int (proc_readfunc) (char *page, char **start, off_t off, int count, + int *eof, void *data); +typedef int (proc_writefunc) (struct file *file, const char __user *buffer, + unsigned long count, void *data); + +static int +asus_proc_add(char *name, proc_writefunc *writefunc, + proc_readfunc *readfunc, mode_t mode, + struct acpi_device *device) +{ + struct proc_dir_entry *proc = + create_proc_entry(name, mode, acpi_device_dir(device)); + if (!proc) { + printk(KERN_WARNING " Unable to create %s fs entry\n", name); + return -1; + } + proc->write_proc = writefunc; + proc->read_proc = readfunc; + proc->data = acpi_driver_data(device); + proc->owner = THIS_MODULE; + proc->uid = asus_uid; + proc->gid = asus_gid; + return 0; +} + +static int asus_hotk_add_fs(struct acpi_device *device) +{ + struct proc_dir_entry *proc; + 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; + printk(KERN_WARNING " asus_uid and asus_gid parameters are " + "deprecated, use chown and chmod instead!\n"); + } + + acpi_device_dir(device) = asus_proc_dir; + if (!acpi_device_dir(device)) + return -ENODEV; + + proc = create_proc_entry(PROC_INFO, 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_WARNING " Unable to create " PROC_INFO + " fs entry\n"); + } + + if (hotk->methods->mt_wled) { + asus_proc_add(PROC_WLED, &proc_write_wled, &proc_read_wled, + mode, device); + } + + if (hotk->methods->mt_ledd) { + asus_proc_add(PROC_LEDD, &proc_write_ledd, &proc_read_ledd, + mode, device); + } + + if (hotk->methods->mt_mled) { + asus_proc_add(PROC_MLED, &proc_write_mled, &proc_read_mled, + mode, device); + } + + if (hotk->methods->mt_tled) { + asus_proc_add(PROC_TLED, &proc_write_tled, &proc_read_tled, + mode, device); + } + + if (hotk->methods->mt_bt_switch) { + asus_proc_add(PROC_BT, &proc_write_bluetooth, + &proc_read_bluetooth, mode, device); + } + + /* + * We need both read node and write method as LCD switch is also + * accessible from the keyboard + */ + if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) { + asus_proc_add(PROC_LCD, &proc_write_lcd, &proc_read_lcd, mode, + device); + } + + if ((hotk->methods->brightness_up && hotk->methods->brightness_down) || + (hotk->methods->brightness_get && hotk->methods->brightness_set)) { + asus_proc_add(PROC_BRN, &proc_write_brn, &proc_read_brn, mode, + device); + } + + if (hotk->methods->display_set) { + asus_proc_add(PROC_DISP, &proc_write_disp, &proc_read_disp, + mode, device); + } + + return 0; +} + +static int asus_hotk_remove_fs(struct acpi_device *device) +{ + if (acpi_device_dir(device)) { + remove_proc_entry(PROC_INFO, acpi_device_dir(device)); + if (hotk->methods->mt_wled) + remove_proc_entry(PROC_WLED, acpi_device_dir(device)); + if (hotk->methods->mt_mled) + remove_proc_entry(PROC_MLED, acpi_device_dir(device)); + if (hotk->methods->mt_tled) + remove_proc_entry(PROC_TLED, acpi_device_dir(device)); + if (hotk->methods->mt_ledd) + remove_proc_entry(PROC_LEDD, acpi_device_dir(device)); + if (hotk->methods->mt_bt_switch) + remove_proc_entry(PROC_BT, acpi_device_dir(device)); + if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) + remove_proc_entry(PROC_LCD, acpi_device_dir(device)); + if ((hotk->methods->brightness_up + && hotk->methods->brightness_down) + || (hotk->methods->brightness_get + && hotk->methods->brightness_set)) + remove_proc_entry(PROC_BRN, acpi_device_dir(device)); + if (hotk->methods->display_set) + remove_proc_entry(PROC_DISP, acpi_device_dir(device)); + } + return 0; +} + +static void asus_hotk_notify(acpi_handle handle, u32 event, void *data) +{ + /* TODO Find a better way to handle events count. */ + 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_proc_event(hotk->device, event, + hotk->event_count[event % 128]++); + + return; +} + +/* + * Match the model string to the list of supported models. Return END_MODEL if + * no match or model is NULL. + */ +static int asus_model_match(char *model) +{ + if (model == NULL) + return END_MODEL; + + if (strncmp(model, "L3D", 3) == 0) + return L3D; + else if (strncmp(model, "L2E", 3) == 0 || + strncmp(model, "L3H", 3) == 0 || strncmp(model, "L5D", 3) == 0) + return L3H; + else if (strncmp(model, "L3", 2) == 0 || strncmp(model, "L2B", 3) == 0) + return L3C; + else if (strncmp(model, "L8L", 3) == 0) + return L8L; + else if (strncmp(model, "L4R", 3) == 0) + return L4R; + else if (strncmp(model, "M6N", 3) == 0 || strncmp(model, "W3N", 3) == 0) + return M6N; + else if (strncmp(model, "M6R", 3) == 0 || strncmp(model, "A3G", 3) == 0) + return M6R; + else if (strncmp(model, "M2N", 3) == 0 || + strncmp(model, "M3N", 3) == 0 || + strncmp(model, "M5N", 3) == 0 || + strncmp(model, "M6N", 3) == 0 || + strncmp(model, "S1N", 3) == 0 || + strncmp(model, "S5N", 3) == 0 || strncmp(model, "W1N", 3) == 0) + return xxN; + else if (strncmp(model, "M1", 2) == 0) + return M1A; + else if (strncmp(model, "M2", 2) == 0 || strncmp(model, "L4E", 3) == 0) + return M2E; + else if (strncmp(model, "L2", 2) == 0) + return L2D; + else if (strncmp(model, "L8", 2) == 0) + return S1x; + else if (strncmp(model, "D1", 2) == 0) + return D1x; + else if (strncmp(model, "A1", 2) == 0) + return A1x; + else if (strncmp(model, "A2", 2) == 0) + return A2x; + else if (strncmp(model, "J1", 2) == 0) + return S2x; + else if (strncmp(model, "L5", 2) == 0) + return L5x; + else if (strncmp(model, "A4G", 3) == 0) + return A4G; + else if (strncmp(model, "W1N", 3) == 0) + return W1N; + else if (strncmp(model, "W3V", 3) == 0) + return W3V; + else if (strncmp(model, "W5A", 3) == 0) + return W5A; + else if (strncmp(model, "A4S", 3) == 0) + return A4S; + else if (strncmp(model, "F3Sa", 4) == 0) + return F3Sa; + else + return END_MODEL; +} + +/* + * 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(void) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *model = NULL; + int bsts_result; + char *string = NULL; + acpi_status status; + + /* + * Get DSDT headers early enough to allow for differentiating between + * models, but late enough to allow acpi_bus_register_driver() to fail + * before doing anything ACPI-specific. Should we encounter a machine, + * which needs special handling (i.e. its hotkey device has a different + * HID), this bit will be moved. A global variable asus_info contains + * the DSDT header. + */ + status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus_info); + if (ACPI_FAILURE(status)) + printk(KERN_WARNING " Couldn't get the DSDT table header\n"); + + /* We have to write 0 on init this far for all ASUS models */ + if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) { + printk(KERN_ERR " Hotkey initialization failed\n"); + return -ENODEV; + } + + /* This needs to be called for some laptops to init properly */ + if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result)) + printk(KERN_WARNING " Error calling BSTS\n"); + else if (bsts_result) + printk(KERN_NOTICE " BSTS called, 0x%02x returned\n", + bsts_result); + + /* + * Try to match the object returned by INIT to the specific model. + * Handle every possible object (or the lack of thereof) the DSDT + * writers might throw at us. When in trouble, we pass NULL to + * asus_model_match() and try something completely different. + */ + if (buffer.pointer) { + model = buffer.pointer; + switch (model->type) { + case ACPI_TYPE_STRING: + string = model->string.pointer; + break; + case ACPI_TYPE_BUFFER: + string = model->buffer.pointer; + break; + default: + kfree(model); + model = NULL; + break; + } + } + hotk->model = asus_model_match(string); + if (hotk->model == END_MODEL) { /* match failed */ + if (asus_info && + strncmp(asus_info->oem_table_id, "ODEM", 4) == 0) { + hotk->model = P30; + printk(KERN_NOTICE + " Samsung P30 detected, supported\n"); + } else { + hotk->model = M2E; + printk(KERN_NOTICE " unsupported model %s, trying " + "default values\n", string); + printk(KERN_NOTICE + " send /proc/acpi/dsdt to the developers\n"); + kfree(model); + return -ENODEV; + } + hotk->methods = &model_conf[hotk->model]; + return AE_OK; + } + hotk->methods = &model_conf[hotk->model]; + printk(KERN_NOTICE " %s model detected, supported\n", string); + + /* Sort of per-model blacklist */ + if (strncmp(string, "L2B", 3) == 0) + hotk->methods->lcd_status = NULL; + /* L2B is similar enough to L3C to use its settings, with this only + exception */ + else if (strncmp(string, "A3G", 3) == 0) + hotk->methods->lcd_status = "\\BLFG"; + /* A3G is like M6R */ + else if (strncmp(string, "S5N", 3) == 0 || + strncmp(string, "M5N", 3) == 0 || + strncmp(string, "W3N", 3) == 0) + hotk->methods->mt_mled = NULL; + /* S5N, M5N and W3N have no MLED */ + else if (strncmp(string, "L5D", 3) == 0) + hotk->methods->mt_wled = NULL; + /* L5D's WLED is not controlled by ACPI */ + else if (strncmp(string, "M2N", 3) == 0 || + strncmp(string, "W3V", 3) == 0 || + strncmp(string, "S1N", 3) == 0) + hotk->methods->mt_wled = "WLED"; + /* M2N, S1N and W3V have a usable WLED */ + else if (asus_info) { + if (strncmp(asus_info->oem_table_id, "L1", 2) == 0) + hotk->methods->mled_status = NULL; + /* S1300A reports L84F, but L1400B too, account for that */ + } + + kfree(model); + + return AE_OK; +} + +static int asus_hotk_check(void) +{ + int result = 0; + + result = acpi_bus_get_status(hotk->device); + if (result) + return result; + + if (hotk->device->status.present) { + result = asus_hotk_get_info(); + } else { + printk(KERN_ERR " Hotkey device not present, aborting\n"); + return -EINVAL; + } + + return result; +} + +static int asus_hotk_found; + +static int asus_hotk_add(struct acpi_device *device) +{ + acpi_status status = AE_OK; + int result; + + if (!device) + return -EINVAL; + + printk(KERN_NOTICE "Asus Laptop ACPI Extras version %s\n", + ASUS_ACPI_VERSION); + + hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL); + if (!hotk) + return -ENOMEM; + + hotk->handle = device->handle; + strcpy(acpi_device_name(device), ACPI_HOTK_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_HOTK_CLASS); + device->driver_data = hotk; + hotk->device = device; + + result = asus_hotk_check(); + 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_ERR " Error installing notify handler\n"); + + /* For laptops without GPLV: 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_WARNING " Error changing brightness\n"); + else { + status = + acpi_evaluate_object(NULL, + hotk->methods->brightness_up, + NULL, NULL); + if (ACPI_FAILURE(status)) + printk(KERN_WARNING " Strange, error changing" + " brightness\n"); + } + } + + asus_hotk_found = 1; + + /* LED display is off by default */ + hotk->ledd_status = 0xFFF; + +end: + if (result) + kfree(hotk); + + return result; +} + +static int asus_hotk_remove(struct acpi_device *device, int type) +{ + acpi_status status = 0; + + if (!device || !acpi_driver_data(device)) + return -EINVAL; + + status = acpi_remove_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY, + asus_hotk_notify); + if (ACPI_FAILURE(status)) + printk(KERN_ERR "Asus ACPI: Error removing notify handler\n"); + + asus_hotk_remove_fs(device); + + kfree(hotk); + + return 0; +} + +static struct backlight_ops asus_backlight_data = { + .get_brightness = read_brightness, + .update_status = set_brightness_status, +}; + +static void asus_acpi_exit(void) +{ + if (asus_backlight_device) + backlight_device_unregister(asus_backlight_device); + + acpi_bus_unregister_driver(&asus_hotk_driver); + remove_proc_entry(PROC_ASUS, acpi_root_dir); + + return; +} + +static int __init asus_acpi_init(void) +{ + int result; + + if (acpi_disabled) + return -ENODEV; + + asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir); + if (!asus_proc_dir) { + printk(KERN_ERR "Asus ACPI: Unable to create /proc entry\n"); + return -ENODEV; + } + asus_proc_dir->owner = THIS_MODULE; + + result = acpi_bus_register_driver(&asus_hotk_driver); + if (result < 0) { + remove_proc_entry(PROC_ASUS, acpi_root_dir); + return result; + } + + /* + * This is a bit of a kludge. We only want this module loaded + * for ASUS systems, but there's currently no way to probe the + * ACPI namespace for ASUS HIDs. So we just return failure if + * we didn't find one, which will cause the module to be + * unloaded. + */ + if (!asus_hotk_found) { + acpi_bus_unregister_driver(&asus_hotk_driver); + remove_proc_entry(PROC_ASUS, acpi_root_dir); + return -ENODEV; + } + + asus_backlight_device = backlight_device_register("asus", NULL, NULL, + &asus_backlight_data); + if (IS_ERR(asus_backlight_device)) { + printk(KERN_ERR "Could not register asus backlight device\n"); + asus_backlight_device = NULL; + asus_acpi_exit(); + return -ENODEV; + } + asus_backlight_device->props.max_brightness = 15; + + return 0; +} + +module_init(asus_acpi_init); +module_exit(asus_acpi_exit); diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c new file mode 100644 index 000000000000..40e60fc2e596 --- /dev/null +++ b/drivers/platform/x86/toshiba_acpi.c @@ -0,0 +1,863 @@ +/* + * toshiba_acpi.c - Toshiba Laptop ACPI Extras + * + * + * Copyright (C) 2002-2004 John Belmonte + * Copyright (C) 2008 Philip Langdale + * + * 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 devolpment page for this driver is located at + * http://memebeam.org/toys/ToshibaAcpiDriver. + * + * Credits: + * Jonathan A. Buzzard - Toshiba HCI info, and critical tips on reverse + * engineering the Windows drivers + * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5 + * Rob Miller - TV out and hotkeys help + * + * + * TODO + * + */ + +#define TOSHIBA_ACPI_VERSION "0.19" +#define PROC_INTERFACE_VERSION 1 + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/backlight.h> +#include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/input-polldev.h> + +#include <asm/uaccess.h> + +#include <acpi/acpi_drivers.h> + +MODULE_AUTHOR("John Belmonte"); +MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); +MODULE_LICENSE("GPL"); + +#define MY_LOGPREFIX "toshiba_acpi: " +#define MY_ERR KERN_ERR MY_LOGPREFIX +#define MY_NOTICE KERN_NOTICE MY_LOGPREFIX +#define MY_INFO KERN_INFO MY_LOGPREFIX + +/* Toshiba ACPI method paths */ +#define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" +#define METHOD_HCI_1 "\\_SB_.VALD.GHCI" +#define METHOD_HCI_2 "\\_SB_.VALZ.GHCI" +#define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" + +/* Toshiba HCI interface definitions + * + * HCI is Toshiba's "Hardware Control Interface" which is supposed to + * be uniform across all their models. Ideally we would just call + * dedicated ACPI methods instead of using this primitive interface. + * However the ACPI methods seem to be incomplete in some areas (for + * example they allow setting, but not reading, the LCD brightness value), + * so this is still useful. + */ + +#define HCI_WORDS 6 + +/* operations */ +#define HCI_SET 0xff00 +#define HCI_GET 0xfe00 + +/* return codes */ +#define HCI_SUCCESS 0x0000 +#define HCI_FAILURE 0x1000 +#define HCI_NOT_SUPPORTED 0x8000 +#define HCI_EMPTY 0x8c00 + +/* registers */ +#define HCI_FAN 0x0004 +#define HCI_SYSTEM_EVENT 0x0016 +#define HCI_VIDEO_OUT 0x001c +#define HCI_HOTKEY_EVENT 0x001e +#define HCI_LCD_BRIGHTNESS 0x002a +#define HCI_WIRELESS 0x0056 + +/* field definitions */ +#define HCI_LCD_BRIGHTNESS_BITS 3 +#define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) +#define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) +#define HCI_VIDEO_OUT_LCD 0x1 +#define HCI_VIDEO_OUT_CRT 0x2 +#define HCI_VIDEO_OUT_TV 0x4 +#define HCI_WIRELESS_KILL_SWITCH 0x01 +#define HCI_WIRELESS_BT_PRESENT 0x0f +#define HCI_WIRELESS_BT_ATTACH 0x40 +#define HCI_WIRELESS_BT_POWER 0x80 + +static const struct acpi_device_id toshiba_device_ids[] = { + {"TOS6200", 0}, + {"TOS6208", 0}, + {"TOS1900", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); + +/* utility + */ + +static __inline__ void _set_bit(u32 * word, u32 mask, int value) +{ + *word = (*word & ~mask) | (mask * value); +} + +/* acpi interface wrappers + */ + +static int is_valid_acpi_path(const char *methodName) +{ + acpi_handle handle; + acpi_status status; + + status = acpi_get_handle(NULL, (char *)methodName, &handle); + return !ACPI_FAILURE(status); +} + +static int write_acpi_int(const char *methodName, int val) +{ + struct acpi_object_list params; + union acpi_object in_objs[1]; + acpi_status status; + + params.count = ARRAY_SIZE(in_objs); + params.pointer = in_objs; + in_objs[0].type = ACPI_TYPE_INTEGER; + in_objs[0].integer.value = val; + + status = acpi_evaluate_object(NULL, (char *)methodName, ¶ms, NULL); + return (status == AE_OK); +} + +#if 0 +static int read_acpi_int(const char *methodName, int *pVal) +{ + struct acpi_buffer results; + union acpi_object out_objs[1]; + acpi_status status; + + results.length = sizeof(out_objs); + results.pointer = out_objs; + + status = acpi_evaluate_object(0, (char *)methodName, 0, &results); + *pVal = out_objs[0].integer.value; + + return (status == AE_OK) && (out_objs[0].type == ACPI_TYPE_INTEGER); +} +#endif + +static const char *method_hci /*= 0*/ ; + +/* Perform a raw HCI call. Here we don't care about input or output buffer + * format. + */ +static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) +{ + struct acpi_object_list params; + union acpi_object in_objs[HCI_WORDS]; + struct acpi_buffer results; + union acpi_object out_objs[HCI_WORDS + 1]; + acpi_status status; + int i; + + params.count = HCI_WORDS; + params.pointer = in_objs; + for (i = 0; i < HCI_WORDS; ++i) { + in_objs[i].type = ACPI_TYPE_INTEGER; + in_objs[i].integer.value = in[i]; + } + + results.length = sizeof(out_objs); + results.pointer = out_objs; + + status = acpi_evaluate_object(NULL, (char *)method_hci, ¶ms, + &results); + if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) { + for (i = 0; i < out_objs->package.count; ++i) { + out[i] = out_objs->package.elements[i].integer.value; + } + } + + return status; +} + +/* common hci tasks (get or set one or two value) + * + * In addition to the ACPI status, the HCI system returns a result which + * may be useful (such as "not supported"). + */ + +static acpi_status hci_write1(u32 reg, u32 in1, u32 * result) +{ + u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(in, out); + *result = (status == AE_OK) ? out[0] : HCI_FAILURE; + return status; +} + +static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result) +{ + u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(in, out); + *out1 = out[2]; + *result = (status == AE_OK) ? out[0] : HCI_FAILURE; + return status; +} + +static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32 *result) +{ + u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(in, out); + *result = (status == AE_OK) ? out[0] : HCI_FAILURE; + return status; +} + +static acpi_status hci_read2(u32 reg, u32 *out1, u32 *out2, u32 *result) +{ + u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(in, out); + *out1 = out[2]; + *out2 = out[3]; + *result = (status == AE_OK) ? out[0] : HCI_FAILURE; + return status; +} + +struct toshiba_acpi_dev { + struct platform_device *p_dev; + struct rfkill *rfk_dev; + struct input_polled_dev *poll_dev; + + const char *bt_name; + const char *rfk_name; + + bool last_rfk_state; + + struct mutex mutex; +}; + +static struct toshiba_acpi_dev toshiba_acpi = { + .bt_name = "Toshiba Bluetooth", + .rfk_name = "Toshiba RFKill Switch", + .last_rfk_state = false, +}; + +/* Bluetooth rfkill handlers */ + +static u32 hci_get_bt_present(bool *present) +{ + u32 hci_result; + u32 value, value2; + + value = 0; + value2 = 0; + hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); + if (hci_result == HCI_SUCCESS) + *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false; + + return hci_result; +} + +static u32 hci_get_bt_on(bool *on) +{ + u32 hci_result; + u32 value, value2; + + value = 0; + value2 = 0x0001; + hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); + if (hci_result == HCI_SUCCESS) + *on = (value & HCI_WIRELESS_BT_POWER) && + (value & HCI_WIRELESS_BT_ATTACH); + + return hci_result; +} + +static u32 hci_get_radio_state(bool *radio_state) +{ + u32 hci_result; + u32 value, value2; + + value = 0; + value2 = 0x0001; + hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); + + *radio_state = value & HCI_WIRELESS_KILL_SWITCH; + return hci_result; +} + +static int bt_rfkill_toggle_radio(void *data, enum rfkill_state state) +{ + u32 result1, result2; + u32 value; + bool radio_state; + struct toshiba_acpi_dev *dev = data; + + value = (state == RFKILL_STATE_UNBLOCKED); + + if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) + return -EFAULT; + + switch (state) { + case RFKILL_STATE_UNBLOCKED: + if (!radio_state) + return -EPERM; + break; + case RFKILL_STATE_SOFT_BLOCKED: + break; + default: + return -EINVAL; + } + + mutex_lock(&dev->mutex); + hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1); + hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); + mutex_unlock(&dev->mutex); + + if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) + return -EFAULT; + + return 0; +} + +static void bt_poll_rfkill(struct input_polled_dev *poll_dev) +{ + bool state_changed; + bool new_rfk_state; + bool value; + u32 hci_result; + struct toshiba_acpi_dev *dev = poll_dev->private; + + hci_result = hci_get_radio_state(&value); + if (hci_result != HCI_SUCCESS) + return; /* Can't do anything useful */ + + new_rfk_state = value; + + mutex_lock(&dev->mutex); + state_changed = new_rfk_state != dev->last_rfk_state; + dev->last_rfk_state = new_rfk_state; + mutex_unlock(&dev->mutex); + + if (unlikely(state_changed)) { + rfkill_force_state(dev->rfk_dev, + new_rfk_state ? + RFKILL_STATE_SOFT_BLOCKED : + RFKILL_STATE_HARD_BLOCKED); + input_report_switch(poll_dev->input, SW_RFKILL_ALL, + new_rfk_state); + input_sync(poll_dev->input); + } +} + +static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; +static struct backlight_device *toshiba_backlight_device; +static int force_fan; +static int last_key_event; +static int key_event_valid; + +typedef struct _ProcItem { + const char *name; + char *(*read_func) (char *); + unsigned long (*write_func) (const char *, unsigned long); +} ProcItem; + +/* proc file handlers + */ + +static int +dispatch_read(char *page, char **start, off_t off, int count, int *eof, + ProcItem * item) +{ + char *p = page; + int len; + + if (off == 0) + p = item->read_func(p); + + /* ISSUE: I don't understand this code */ + len = (p - page); + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int +dispatch_write(struct file *file, const char __user * buffer, + unsigned long count, ProcItem * item) +{ + int result; + char *tmp_buffer; + + /* Arg buffer points to userspace memory, which can't be accessed + * directly. Since we're making a copy, zero-terminate the + * destination so that sscanf can be used on it safely. + */ + tmp_buffer = kmalloc(count + 1, GFP_KERNEL); + if (!tmp_buffer) + return -ENOMEM; + + if (copy_from_user(tmp_buffer, buffer, count)) { + result = -EFAULT; + } else { + tmp_buffer[count] = 0; + result = item->write_func(tmp_buffer, count); + } + kfree(tmp_buffer); + return result; +} + +static int get_lcd(struct backlight_device *bd) +{ + u32 hci_result; + u32 value; + + hci_read1(HCI_LCD_BRIGHTNESS, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + return (value >> HCI_LCD_BRIGHTNESS_SHIFT); + } else + return -EFAULT; +} + +static char *read_lcd(char *p) +{ + int value = get_lcd(NULL); + + if (value >= 0) { + p += sprintf(p, "brightness: %d\n", value); + p += sprintf(p, "brightness_levels: %d\n", + HCI_LCD_BRIGHTNESS_LEVELS); + } else { + printk(MY_ERR "Error reading LCD brightness\n"); + } + + return p; +} + +static int set_lcd(int value) +{ + u32 hci_result; + + value = value << HCI_LCD_BRIGHTNESS_SHIFT; + hci_write1(HCI_LCD_BRIGHTNESS, value, &hci_result); + if (hci_result != HCI_SUCCESS) + return -EFAULT; + + return 0; +} + +static int set_lcd_status(struct backlight_device *bd) +{ + return set_lcd(bd->props.brightness); +} + +static unsigned long write_lcd(const char *buffer, unsigned long count) +{ + int value; + int ret; + + if (sscanf(buffer, " brightness : %i", &value) == 1 && + value >= 0 && value < HCI_LCD_BRIGHTNESS_LEVELS) { + ret = set_lcd(value); + if (ret == 0) + ret = count; + } else { + ret = -EINVAL; + } + return ret; +} + +static char *read_video(char *p) +{ + u32 hci_result; + u32 value; + + hci_read1(HCI_VIDEO_OUT, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; + int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; + int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; + p += sprintf(p, "lcd_out: %d\n", is_lcd); + p += sprintf(p, "crt_out: %d\n", is_crt); + p += sprintf(p, "tv_out: %d\n", is_tv); + } else { + printk(MY_ERR "Error reading video out status\n"); + } + + return p; +} + +static unsigned long write_video(const char *buffer, unsigned long count) +{ + int value; + int remain = count; + int lcd_out = -1; + int crt_out = -1; + int tv_out = -1; + u32 hci_result; + u32 video_out; + + /* scan expression. Multiple expressions may be delimited with ; + * + * NOTE: to keep scanning simple, invalid fields are ignored + */ + while (remain) { + if (sscanf(buffer, " lcd_out : %i", &value) == 1) + lcd_out = value & 1; + else if (sscanf(buffer, " crt_out : %i", &value) == 1) + crt_out = value & 1; + else if (sscanf(buffer, " tv_out : %i", &value) == 1) + tv_out = value & 1; + /* advance to one character past the next ; */ + do { + ++buffer; + --remain; + } + while (remain && *(buffer - 1) != ';'); + } + + hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); + if (hci_result == HCI_SUCCESS) { + unsigned int new_video_out = video_out; + if (lcd_out != -1) + _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); + if (crt_out != -1) + _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out); + if (tv_out != -1) + _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); + /* To avoid unnecessary video disruption, only write the new + * video setting if something changed. */ + if (new_video_out != video_out) + write_acpi_int(METHOD_VIDEO_OUT, new_video_out); + } else { + return -EFAULT; + } + + return count; +} + +static char *read_fan(char *p) +{ + u32 hci_result; + u32 value; + + hci_read1(HCI_FAN, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + p += sprintf(p, "running: %d\n", (value > 0)); + p += sprintf(p, "force_on: %d\n", force_fan); + } else { + printk(MY_ERR "Error reading fan status\n"); + } + + return p; +} + +static unsigned long write_fan(const char *buffer, unsigned long count) +{ + int value; + u32 hci_result; + + if (sscanf(buffer, " force_on : %i", &value) == 1 && + value >= 0 && value <= 1) { + hci_write1(HCI_FAN, value, &hci_result); + if (hci_result != HCI_SUCCESS) + return -EFAULT; + else + force_fan = value; + } else { + return -EINVAL; + } + + return count; +} + +static char *read_keys(char *p) +{ + u32 hci_result; + u32 value; + + if (!key_event_valid) { + hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + key_event_valid = 1; + last_key_event = value; + } else if (hci_result == HCI_EMPTY) { + /* better luck next time */ + } else if (hci_result == HCI_NOT_SUPPORTED) { + /* This is a workaround for an unresolved issue on + * some machines where system events sporadically + * become disabled. */ + hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); + printk(MY_NOTICE "Re-enabled hotkeys\n"); + } else { + printk(MY_ERR "Error reading hotkey status\n"); + goto end; + } + } + + p += sprintf(p, "hotkey_ready: %d\n", key_event_valid); + p += sprintf(p, "hotkey: 0x%04x\n", last_key_event); + + end: + return p; +} + +static unsigned long write_keys(const char *buffer, unsigned long count) +{ + int value; + + if (sscanf(buffer, " hotkey_ready : %i", &value) == 1 && value == 0) { + key_event_valid = 0; + } else { + return -EINVAL; + } + + return count; +} + +static char *read_version(char *p) +{ + p += sprintf(p, "driver: %s\n", TOSHIBA_ACPI_VERSION); + p += sprintf(p, "proc_interface: %d\n", + PROC_INTERFACE_VERSION); + return p; +} + +/* proc and module init + */ + +#define PROC_TOSHIBA "toshiba" + +static ProcItem proc_items[] = { + {"lcd", read_lcd, write_lcd}, + {"video", read_video, write_video}, + {"fan", read_fan, write_fan}, + {"keys", read_keys, write_keys}, + {"version", read_version, NULL}, + {NULL} +}; + +static acpi_status __init add_device(void) +{ + struct proc_dir_entry *proc; + ProcItem *item; + + for (item = proc_items; item->name; ++item) { + proc = create_proc_read_entry(item->name, + S_IFREG | S_IRUGO | S_IWUSR, + toshiba_proc_dir, + (read_proc_t *) dispatch_read, + item); + if (proc) + proc->owner = THIS_MODULE; + if (proc && item->write_func) + proc->write_proc = (write_proc_t *) dispatch_write; + } + + return AE_OK; +} + +static acpi_status remove_device(void) +{ + ProcItem *item; + + for (item = proc_items; item->name; ++item) + remove_proc_entry(item->name, toshiba_proc_dir); + return AE_OK; +} + +static struct backlight_ops toshiba_backlight_data = { + .get_brightness = get_lcd, + .update_status = set_lcd_status, +}; + +static void toshiba_acpi_exit(void) +{ + if (toshiba_acpi.poll_dev) { + input_unregister_polled_device(toshiba_acpi.poll_dev); + input_free_polled_device(toshiba_acpi.poll_dev); + } + + if (toshiba_acpi.rfk_dev) + rfkill_unregister(toshiba_acpi.rfk_dev); + + if (toshiba_backlight_device) + backlight_device_unregister(toshiba_backlight_device); + + remove_device(); + + if (toshiba_proc_dir) + remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); + + platform_device_unregister(toshiba_acpi.p_dev); + + return; +} + +static int __init toshiba_acpi_init(void) +{ + acpi_status status = AE_OK; + u32 hci_result; + bool bt_present; + bool bt_on; + bool radio_on; + int ret = 0; + + if (acpi_disabled) + return -ENODEV; + + /* simple device detection: look for HCI method */ + if (is_valid_acpi_path(METHOD_HCI_1)) + method_hci = METHOD_HCI_1; + else if (is_valid_acpi_path(METHOD_HCI_2)) + method_hci = METHOD_HCI_2; + else + return -ENODEV; + + printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", + TOSHIBA_ACPI_VERSION); + printk(MY_INFO " HCI method: %s\n", method_hci); + + mutex_init(&toshiba_acpi.mutex); + + toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi", + -1, NULL, 0); + if (IS_ERR(toshiba_acpi.p_dev)) { + ret = PTR_ERR(toshiba_acpi.p_dev); + printk(MY_ERR "unable to register platform device\n"); + toshiba_acpi.p_dev = NULL; + toshiba_acpi_exit(); + return ret; + } + + force_fan = 0; + key_event_valid = 0; + + /* enable event fifo */ + hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); + + toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); + if (!toshiba_proc_dir) { + toshiba_acpi_exit(); + return -ENODEV; + } else { + toshiba_proc_dir->owner = THIS_MODULE; + status = add_device(); + if (ACPI_FAILURE(status)) { + toshiba_acpi_exit(); + return -ENODEV; + } + } + + toshiba_backlight_device = backlight_device_register("toshiba", + &toshiba_acpi.p_dev->dev, + NULL, + &toshiba_backlight_data); + if (IS_ERR(toshiba_backlight_device)) { + ret = PTR_ERR(toshiba_backlight_device); + + printk(KERN_ERR "Could not register toshiba backlight device\n"); + toshiba_backlight_device = NULL; + toshiba_acpi_exit(); + return ret; + } + toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; + + /* Register rfkill switch for Bluetooth */ + if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) { + toshiba_acpi.rfk_dev = rfkill_allocate(&toshiba_acpi.p_dev->dev, + RFKILL_TYPE_BLUETOOTH); + if (!toshiba_acpi.rfk_dev) { + printk(MY_ERR "unable to allocate rfkill device\n"); + toshiba_acpi_exit(); + return -ENOMEM; + } + + toshiba_acpi.rfk_dev->name = toshiba_acpi.bt_name; + toshiba_acpi.rfk_dev->toggle_radio = bt_rfkill_toggle_radio; + toshiba_acpi.rfk_dev->user_claim_unsupported = 1; + toshiba_acpi.rfk_dev->data = &toshiba_acpi; + + if (hci_get_bt_on(&bt_on) == HCI_SUCCESS && bt_on) { + toshiba_acpi.rfk_dev->state = RFKILL_STATE_UNBLOCKED; + } else if (hci_get_radio_state(&radio_on) == HCI_SUCCESS && + radio_on) { + toshiba_acpi.rfk_dev->state = RFKILL_STATE_SOFT_BLOCKED; + } else { + toshiba_acpi.rfk_dev->state = RFKILL_STATE_HARD_BLOCKED; + } + + ret = rfkill_register(toshiba_acpi.rfk_dev); + if (ret) { + printk(MY_ERR "unable to register rfkill device\n"); + toshiba_acpi_exit(); + return -ENOMEM; + } + + /* Register input device for kill switch */ + toshiba_acpi.poll_dev = input_allocate_polled_device(); + if (!toshiba_acpi.poll_dev) { + printk(MY_ERR + "unable to allocate kill-switch input device\n"); + toshiba_acpi_exit(); + return -ENOMEM; + } + toshiba_acpi.poll_dev->private = &toshiba_acpi; + toshiba_acpi.poll_dev->poll = bt_poll_rfkill; + toshiba_acpi.poll_dev->poll_interval = 1000; /* msecs */ + + toshiba_acpi.poll_dev->input->name = toshiba_acpi.rfk_name; + toshiba_acpi.poll_dev->input->id.bustype = BUS_HOST; + /* Toshiba USB ID */ + toshiba_acpi.poll_dev->input->id.vendor = 0x0930; + set_bit(EV_SW, toshiba_acpi.poll_dev->input->evbit); + set_bit(SW_RFKILL_ALL, toshiba_acpi.poll_dev->input->swbit); + input_report_switch(toshiba_acpi.poll_dev->input, + SW_RFKILL_ALL, TRUE); + input_sync(toshiba_acpi.poll_dev->input); + + ret = input_register_polled_device(toshiba_acpi.poll_dev); + if (ret) { + printk(MY_ERR + "unable to register kill-switch input device\n"); + toshiba_acpi_exit(); + return ret; + } + } + + return 0; +} + +module_init(toshiba_acpi_init); +module_exit(toshiba_acpi_exit); diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c new file mode 100644 index 000000000000..8a8b377712c9 --- /dev/null +++ b/drivers/platform/x86/wmi.c @@ -0,0 +1,747 @@ +/* + * ACPI-WMI mapping driver + * + * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> + * + * GUID parsing code from ldm.c is: + * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/acpi.h> +#include <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> + +ACPI_MODULE_NAME("wmi"); +MODULE_AUTHOR("Carlos Corbacho"); +MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); +MODULE_LICENSE("GPL"); + +#define ACPI_WMI_CLASS "wmi" + +#undef PREFIX +#define PREFIX "ACPI: WMI: " + +static DEFINE_MUTEX(wmi_data_lock); + +struct guid_block { + char guid[16]; + union { + char object_id[2]; + struct { + unsigned char notify_id; + unsigned char reserved; + }; + }; + u8 instance_count; + u8 flags; +}; + +struct wmi_block { + struct list_head list; + struct guid_block gblock; + acpi_handle handle; + wmi_notify_handler handler; + void *handler_data; +}; + +static struct wmi_block wmi_blocks; + +/* + * If the GUID data block is marked as expensive, we must enable and + * explicitily disable data collection. + */ +#define ACPI_WMI_EXPENSIVE 0x1 +#define ACPI_WMI_METHOD 0x2 /* GUID is a method */ +#define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ +#define ACPI_WMI_EVENT 0x8 /* GUID is an event */ + +static int acpi_wmi_remove(struct acpi_device *device, int type); +static int acpi_wmi_add(struct acpi_device *device); + +static const struct acpi_device_id wmi_device_ids[] = { + {"PNP0C14", 0}, + {"pnp0c14", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, wmi_device_ids); + +static struct acpi_driver acpi_wmi_driver = { + .name = "wmi", + .class = ACPI_WMI_CLASS, + .ids = wmi_device_ids, + .ops = { + .add = acpi_wmi_add, + .remove = acpi_wmi_remove, + }, +}; + +/* + * GUID parsing functions + */ + +/** + * wmi_parse_hexbyte - Convert a ASCII hex number to a byte + * @src: Pointer to at least 2 characters to convert. + * + * Convert a two character ASCII hex string to a number. + * + * Return: 0-255 Success, the byte was parsed correctly + * -1 Error, an invalid character was supplied + */ +static int wmi_parse_hexbyte(const u8 *src) +{ + unsigned int x; /* For correct wrapping */ + int h; + + /* high part */ + x = src[0]; + if (x - '0' <= '9' - '0') { + h = x - '0'; + } else if (x - 'a' <= 'f' - 'a') { + h = x - 'a' + 10; + } else if (x - 'A' <= 'F' - 'A') { + h = x - 'A' + 10; + } else { + return -1; + } + h <<= 4; + + /* low part */ + x = src[1]; + if (x - '0' <= '9' - '0') + return h | (x - '0'); + if (x - 'a' <= 'f' - 'a') + return h | (x - 'a' + 10); + if (x - 'A' <= 'F' - 'A') + return h | (x - 'A' + 10); + return -1; +} + +/** + * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary + * @src: Memory block holding binary GUID (16 bytes) + * @dest: Memory block to hold byte swapped binary GUID (16 bytes) + * + * Byte swap a binary GUID to match it's real GUID value + */ +static void wmi_swap_bytes(u8 *src, u8 *dest) +{ + int i; + + for (i = 0; i <= 3; i++) + memcpy(dest + i, src + (3 - i), 1); + + for (i = 0; i <= 1; i++) + memcpy(dest + 4 + i, src + (5 - i), 1); + + for (i = 0; i <= 1; i++) + memcpy(dest + 6 + i, src + (7 - i), 1); + + memcpy(dest + 8, src + 8, 8); +} + +/** + * wmi_parse_guid - Convert GUID from ASCII to binary + * @src: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @dest: Memory block to hold binary GUID (16 bytes) + * + * N.B. The GUID need not be NULL terminated. + * + * Return: 'true' @dest contains binary GUID + * 'false' @dest contents are undefined + */ +static bool wmi_parse_guid(const u8 *src, u8 *dest) +{ + static const int size[] = { 4, 2, 2, 2, 6 }; + int i, j, v; + + if (src[8] != '-' || src[13] != '-' || + src[18] != '-' || src[23] != '-') + return false; + + for (j = 0; j < 5; j++, src++) { + for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) { + v = wmi_parse_hexbyte(src); + if (v < 0) + return false; + } + } + + return true; +} + +static bool find_guid(const char *guid_string, struct wmi_block **out) +{ + char tmp[16], guid_input[16]; + struct wmi_block *wblock; + struct guid_block *block; + struct list_head *p; + + wmi_parse_guid(guid_string, tmp); + wmi_swap_bytes(tmp, guid_input); + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + block = &wblock->gblock; + + if (memcmp(block->guid, guid_input, 16) == 0) { + if (out) + *out = wblock; + return 1; + } + } + return 0; +} + +static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) +{ + struct guid_block *block = NULL; + char method[5]; + struct acpi_object_list input; + union acpi_object params[1]; + acpi_status status; + acpi_handle handle; + + block = &wblock->gblock; + handle = wblock->handle; + + if (!block) + return AE_NOT_EXIST; + + input.count = 1; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = enable; + + snprintf(method, 5, "WE%02X", block->notify_id); + status = acpi_evaluate_object(handle, method, &input, NULL); + + if (status != AE_OK && status != AE_NOT_FOUND) + return status; + else + return AE_OK; +} + +/* + * Exported WMI functions + */ +/** + * wmi_evaluate_method - Evaluate a WMI method + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * @method_id: Method ID to call + * &in: Buffer containing input for the method call + * &out: Empty buffer to return the method results + * + * Call an ACPI-WMI method + */ +acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, +u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) +{ + struct guid_block *block = NULL; + struct wmi_block *wblock = NULL; + acpi_handle handle; + acpi_status status; + struct acpi_object_list input; + union acpi_object params[3]; + char method[4] = "WM"; + + if (!find_guid(guid_string, &wblock)) + return AE_ERROR; + + block = &wblock->gblock; + handle = wblock->handle; + + if (!(block->flags & ACPI_WMI_METHOD)) + return AE_BAD_DATA; + + if (block->instance_count < instance) + return AE_BAD_PARAMETER; + + input.count = 2; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = instance; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = method_id; + + if (in) { + input.count = 3; + + if (block->flags & ACPI_WMI_STRING) { + params[2].type = ACPI_TYPE_STRING; + } else { + params[2].type = ACPI_TYPE_BUFFER; + } + params[2].buffer.length = in->length; + params[2].buffer.pointer = in->pointer; + } + + strncat(method, block->object_id, 2); + + status = acpi_evaluate_object(handle, method, &input, out); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_evaluate_method); + +/** + * wmi_query_block - Return contents of a WMI block + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * &out: Empty buffer to return the contents of the data block to + * + * Return the contents of an ACPI-WMI data block to a buffer + */ +acpi_status wmi_query_block(const char *guid_string, u8 instance, +struct acpi_buffer *out) +{ + struct guid_block *block = NULL; + struct wmi_block *wblock = NULL; + acpi_handle handle, wc_handle; + acpi_status status, wc_status = AE_ERROR; + struct acpi_object_list input, wc_input; + union acpi_object wc_params[1], wq_params[1]; + char method[4]; + char wc_method[4] = "WC"; + + if (!guid_string || !out) + return AE_BAD_PARAMETER; + + if (!find_guid(guid_string, &wblock)) + return AE_ERROR; + + block = &wblock->gblock; + handle = wblock->handle; + + if (block->instance_count < instance) + return AE_BAD_PARAMETER; + + /* Check GUID is a data block */ + if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) + return AE_ERROR; + + input.count = 1; + input.pointer = wq_params; + wq_params[0].type = ACPI_TYPE_INTEGER; + wq_params[0].integer.value = instance; + + /* + * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to + * enable collection. + */ + if (block->flags & ACPI_WMI_EXPENSIVE) { + wc_input.count = 1; + wc_input.pointer = wc_params; + wc_params[0].type = ACPI_TYPE_INTEGER; + wc_params[0].integer.value = 1; + + strncat(wc_method, block->object_id, 2); + + /* + * Some GUIDs break the specification by declaring themselves + * expensive, but have no corresponding WCxx method. So we + * should not fail if this happens. + */ + wc_status = acpi_get_handle(handle, wc_method, &wc_handle); + if (ACPI_SUCCESS(wc_status)) + wc_status = acpi_evaluate_object(handle, wc_method, + &wc_input, NULL); + } + + strcpy(method, "WQ"); + strncat(method, block->object_id, 2); + + status = acpi_evaluate_object(handle, method, &input, out); + + /* + * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if + * the WQxx method failed - we should disable collection anyway. + */ + if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { + wc_params[0].integer.value = 0; + status = acpi_evaluate_object(handle, + wc_method, &wc_input, NULL); + } + + return status; +} +EXPORT_SYMBOL_GPL(wmi_query_block); + +/** + * wmi_set_block - Write to a WMI block + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * &in: Buffer containing new values for the data block + * + * Write the contents of the input buffer to an ACPI-WMI data block + */ +acpi_status wmi_set_block(const char *guid_string, u8 instance, +const struct acpi_buffer *in) +{ + struct guid_block *block = NULL; + struct wmi_block *wblock = NULL; + acpi_handle handle; + struct acpi_object_list input; + union acpi_object params[2]; + char method[4] = "WS"; + + if (!guid_string || !in) + return AE_BAD_DATA; + + if (!find_guid(guid_string, &wblock)) + return AE_ERROR; + + block = &wblock->gblock; + handle = wblock->handle; + + if (block->instance_count < instance) + return AE_BAD_PARAMETER; + + /* Check GUID is a data block */ + if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) + return AE_ERROR; + + input.count = 2; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = instance; + + if (block->flags & ACPI_WMI_STRING) { + params[1].type = ACPI_TYPE_STRING; + } else { + params[1].type = ACPI_TYPE_BUFFER; + } + params[1].buffer.length = in->length; + params[1].buffer.pointer = in->pointer; + + strncat(method, block->object_id, 2); + + return acpi_evaluate_object(handle, method, &input, NULL); +} +EXPORT_SYMBOL_GPL(wmi_set_block); + +/** + * wmi_install_notify_handler - Register handler for WMI events + * @handler: Function to handle notifications + * @data: Data to be returned to handler when event is fired + * + * Register a handler for events sent to the ACPI-WMI mapper device. + */ +acpi_status wmi_install_notify_handler(const char *guid, +wmi_notify_handler handler, void *data) +{ + struct wmi_block *block; + acpi_status status; + + if (!guid || !handler) + return AE_BAD_PARAMETER; + + find_guid(guid, &block); + if (!block) + return AE_NOT_EXIST; + + if (block->handler) + return AE_ALREADY_ACQUIRED; + + block->handler = handler; + block->handler_data = data; + + status = wmi_method_enable(block, 1); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_install_notify_handler); + +/** + * wmi_uninstall_notify_handler - Unregister handler for WMI events + * + * Unregister handler for events sent to the ACPI-WMI mapper device. + */ +acpi_status wmi_remove_notify_handler(const char *guid) +{ + struct wmi_block *block; + acpi_status status; + + if (!guid) + return AE_BAD_PARAMETER; + + find_guid(guid, &block); + if (!block) + return AE_NOT_EXIST; + + if (!block->handler) + return AE_NULL_ENTRY; + + status = wmi_method_enable(block, 0); + + block->handler = NULL; + block->handler_data = NULL; + + return status; +} +EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); + +/** + * wmi_get_event_data - Get WMI data associated with an event + * + * @event - Event to find + * &out - Buffer to hold event data + * + * Returns extra data associated with an event in WMI. + */ +acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) +{ + struct acpi_object_list input; + union acpi_object params[1]; + struct guid_block *gblock; + struct wmi_block *wblock; + struct list_head *p; + + input.count = 1; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = event; + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + gblock = &wblock->gblock; + + if ((gblock->flags & ACPI_WMI_EVENT) && + (gblock->notify_id == event)) + return acpi_evaluate_object(wblock->handle, "_WED", + &input, out); + } + + return AE_NOT_FOUND; +} +EXPORT_SYMBOL_GPL(wmi_get_event_data); + +/** + * wmi_has_guid - Check if a GUID is available + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Check if a given GUID is defined by _WDG + */ +bool wmi_has_guid(const char *guid_string) +{ + return find_guid(guid_string, NULL); +} +EXPORT_SYMBOL_GPL(wmi_has_guid); + +/* + * Parse the _WDG method for the GUID data blocks + */ +static __init acpi_status parse_wdg(acpi_handle handle) +{ + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + struct guid_block *gblock; + struct wmi_block *wblock; + acpi_status status; + u32 i, total; + + status = acpi_evaluate_object(handle, "_WDG", NULL, &out); + + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) out.pointer; + + if (obj->type != ACPI_TYPE_BUFFER) + return AE_ERROR; + + total = obj->buffer.length / sizeof(struct guid_block); + + gblock = kzalloc(obj->buffer.length, GFP_KERNEL); + if (!gblock) + return AE_NO_MEMORY; + + memcpy(gblock, obj->buffer.pointer, obj->buffer.length); + + for (i = 0; i < total; i++) { + wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); + if (!wblock) + return AE_NO_MEMORY; + + wblock->gblock = gblock[i]; + wblock->handle = handle; + list_add_tail(&wblock->list, &wmi_blocks.list); + } + + kfree(out.pointer); + kfree(gblock); + + return status; +} + +/* + * WMI can have EmbeddedControl access regions. In which case, we just want to + * hand these off to the EC driver. + */ +static acpi_status +acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, + u32 bits, acpi_integer * value, + void *handler_context, void *region_context) +{ + int result = 0, i = 0; + u8 temp = 0; + + if ((address > 0xFF) || !value) + return AE_BAD_PARAMETER; + + if (function != ACPI_READ && function != ACPI_WRITE) + return AE_BAD_PARAMETER; + + if (bits != 8) + return AE_BAD_PARAMETER; + + if (function == ACPI_READ) { + result = ec_read(address, &temp); + (*value) |= ((acpi_integer)temp) << i; + } else { + temp = 0xff & ((*value) >> i); + result = ec_write(address, temp); + } + + switch (result) { + case -EINVAL: + return AE_BAD_PARAMETER; + break; + case -ENODEV: + return AE_NOT_FOUND; + break; + case -ETIME: + return AE_TIME; + break; + default: + return AE_OK; + } +} + +static void acpi_wmi_notify(acpi_handle handle, u32 event, void *data) +{ + struct guid_block *block; + struct wmi_block *wblock; + struct list_head *p; + struct acpi_device *device = data; + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + block = &wblock->gblock; + + if ((block->flags & ACPI_WMI_EVENT) && + (block->notify_id == event)) { + if (wblock->handler) + wblock->handler(event, wblock->handler_data); + + acpi_bus_generate_netlink_event( + device->pnp.device_class, dev_name(&device->dev), + event, 0); + break; + } + } +} + +static int acpi_wmi_remove(struct acpi_device *device, int type) +{ + acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify); + + acpi_remove_address_space_handler(device->handle, + ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); + + return 0; +} + +static int __init acpi_wmi_add(struct acpi_device *device) +{ + acpi_status status; + int result = 0; + + status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify, device); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX "Error installing notify handler\n"); + return -ENODEV; + } + + status = acpi_install_address_space_handler(device->handle, + ACPI_ADR_SPACE_EC, + &acpi_wmi_ec_space_handler, + NULL, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + status = parse_wdg(device->handle); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX "Error installing EC region handler\n"); + return -ENODEV; + } + + return result; +} + +static int __init acpi_wmi_init(void) +{ + acpi_status result; + + INIT_LIST_HEAD(&wmi_blocks.list); + + if (acpi_disabled) + return -ENODEV; + + result = acpi_bus_register_driver(&acpi_wmi_driver); + + if (result < 0) { + printk(KERN_INFO PREFIX "Error loading mapper\n"); + } else { + printk(KERN_INFO PREFIX "Mapper loaded\n"); + } + + return result; +} + +static void __exit acpi_wmi_exit(void) +{ + struct list_head *p, *tmp; + struct wmi_block *wblock; + + acpi_bus_unregister_driver(&acpi_wmi_driver); + + list_for_each_safe(p, tmp, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + + list_del(p); + kfree(wblock); + } + + printk(KERN_INFO PREFIX "Mapper unloaded\n"); +} + +subsys_initcall(acpi_wmi_init); +module_exit(acpi_wmi_exit); |