Skip to content
Snippets Groups Projects
ibm_acpi.c 45.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *  ibm_acpi.c - IBM ThinkPad ACPI Extras
     *
     *
    
     *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     *  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
    
     */
    
    #define IBM_VERSION "0.12a"
    
    /*
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *  Changelog:
    
     *  
     *  2005-08-17  0.12	fix compilation on 2.6.13-rc kernels
     *  2005-03-17	0.11	support for 600e, 770x
     *			    thanks to Jamie Lentin <lentinj@dial.pipex.com>
     *			support for 770e, G41
     *			G40 and G41 don't have a thinklight
     *			temperatures no longer experimental
     *			experimental brightness control
     *			experimental volume control
     *			experimental fan enable/disable
     *  2005-01-16	0.10	fix module loading on R30, R31 
     *  2005-01-16	0.9	support for 570, R30, R31
     *			ultrabay support on A22p, A3x
     *			limit arg for cmos, led, beep, drop experimental status
     *			more capable led control on A21e, A22p, T20-22, X20
     *			experimental temperatures and fan speed
     *			experimental embedded controller register dump
     *			mark more functions as __init, drop incorrect __exit
     *			use MODULE_VERSION
     *			    thanks to Henrik Brix Andersen <brix@gentoo.org>
     *			fix parameter passing on module loading
     *			    thanks to Rusty Russell <rusty@rustcorp.com.au>
     *			    thanks to Jim Radford <radford@blackbean.org>
     *  2004-11-08	0.8	fix init error case, don't return from a macro
     *			    thanks to Chris Wright <chrisw@osdl.org>
     *  2004-10-23	0.7	fix module loading on A21e, A22p, T20, T21, X20
     *			fix led control on A21e
     *  2004-10-19	0.6	use acpi_bus_register_driver() to claim HKEY device
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *  2004-10-18	0.5	thinklight support on A21e, G40, R32, T20, T21, X20
     *			proc file format changed
     *			video_switch command
     *			experimental cmos control
     *			experimental led control
     *			experimental acpi sounds
    
     *  2004-09-16	0.4	support for module parameters
     *			hotkey mask can be prefixed by 0x
     *			video output switching
     *			video expansion control
     *			ultrabay eject support
     *			removed lcd brightness/on/off control, didn't work
     *  2004-08-17	0.3	support for R40
     *			lcd off, brightness control
     *			thinklight on/off
     *  2004-08-14	0.2	support for T series, X20
     *			bluetooth enable/disable
     *			hotkey events disabled by default
     *			removed fan control, currently useless
     *  2004-08-09	0.1	initial release, support for X series
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/types.h>
    #include <linux/proc_fs.h>
    #include <asm/uaccess.h>
    
    #include <acpi/acpi_drivers.h>
    #include <acpi/acnamesp.h>
    
    #define IBM_NAME "ibm"
    #define IBM_DESC "IBM ThinkPad ACPI Extras"
    #define IBM_FILE "ibm_acpi"
    #define IBM_URL "http://ibm-acpi.sf.net/"
    
    
    MODULE_AUTHOR("Borislav Deianov");
    MODULE_DESCRIPTION(IBM_DESC);
    MODULE_VERSION(IBM_VERSION);
    MODULE_LICENSE("GPL");
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #define IBM_DIR IBM_NAME
    
    #define IBM_LOG IBM_FILE ": "
    #define IBM_ERR	   KERN_ERR    IBM_LOG
    #define IBM_NOTICE KERN_NOTICE IBM_LOG
    #define IBM_INFO   KERN_INFO   IBM_LOG
    #define IBM_DEBUG  KERN_DEBUG  IBM_LOG
    
    #define IBM_MAX_ACPI_ARGS 3
    
    #define __unused __attribute__ ((unused))
    
    static int experimental;
    module_param(experimental, int, 0);
    
    static acpi_handle root_handle = NULL;
    
    #define IBM_HANDLE(object, parent, paths...)			\
    	static acpi_handle  object##_handle;			\
    	static acpi_handle *object##_parent = &parent##_handle;	\
    
    	static char        *object##_path;			\
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	static char        *object##_paths[] = { paths }
    
    
    /*
     * The following models are supported to various degrees:
     *
     * 570, 600e, 600x, 770e, 770x
     * A20m, A21e, A21m, A21p, A22p, A30, A30p, A31, A31p
     * G40, G41
     * R30, R31, R32, R40, R40e, R50, R50e, R50p, R51
     * T20, T21, T22, T23, T30, T40, T40p, T41, T41p, T42, T42p, T43
     * X20, X21, X22, X23, X24, X30, X31, X40
     *
     * The following models have no supported features:
     *
     * 240, 240x, i1400
     *
     * Still missing DSDTs for the following models:
     *
     * A20p, A22e, A22m
     * R52
     * S31
     * T43p
     */
    
    IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0",	/* 240, 240x */
    	   "\\_SB.PCI.ISA.EC",	/* 570 */
    	   "\\_SB.PCI0.ISA0.EC0",	/* 600e/x, 770e, 770x */
    	   "\\_SB.PCI0.ISA.EC",	/* A21e, A2xm/p, T20-22, X20-21 */
    	   "\\_SB.PCI0.AD4S.EC0",	/* i1400, R30 */
    	   "\\_SB.PCI0.ICH3.EC0",	/* R31 */
    	   "\\_SB.PCI0.LPC.EC",	/* all others */
        );
    
    IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA",	/* 570 */
    	   "\\_SB.PCI0.AGP0.VID0",	/* 600e/x, 770x */
    	   "\\_SB.PCI0.VID0",	/* 770e */
    	   "\\_SB.PCI0.VID",	/* A21e, G4x, R50e, X30, X40 */
    	   "\\_SB.PCI0.AGP.VID",	/* all others */
        );				/* R30, R31 */
    
    IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID");	/* G41 */
    
    IBM_HANDLE(cmos, root, "\\UCMS",	/* R50, R50e, R50p, R51, T4x, X31, X40 */
    	   "\\CMOS",		/* A3x, G4x, R32, T23, T30, X22-24, X30 */
    	   "\\CMS",		/* R40, R40e */
        );				/* all others */
    
    #ifdef CONFIG_ACPI_IBM_DOCK
    
    IBM_HANDLE(dock, root, "\\_SB.GDCK",	/* X30, X31, X40 */
    	   "\\_SB.PCI0.DOCK",	/* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */
    	   "\\_SB.PCI0.PCI1.DOCK",	/* all others */
    	   "\\_SB.PCI.ISA.SLCE",	/* 570 */
        );				/* A21e,G4x,R30,R31,R32,R40,R40e,R50e */
    
    IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST",	/* 570 */
    	   "\\_SB.PCI0.IDE0.IDES.IDSM",	/* 600e/x, 770e, 770x */
    	   "\\_SB.PCI0.IDE0.SCND.MSTR",	/* all others */
        );				/* A21e, R30, R31 */
    
    IBM_HANDLE(bay_ej, bay, "_EJ3",	/* 600e/x, A2xm/p, A3x */
    	   "_EJ0",		/* all others */
        );				/* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
    
    IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV",	/* A3x, R32 */
    	   "\\_SB.PCI0.IDE0.IDEP.IDPS",	/* 600e/x, 770e, 770x */
        );				/* all others */
    
    IBM_HANDLE(bay2_ej, bay2, "_EJ3",	/* 600e/x, 770e, A3x */
    	   "_EJ0",		/* 770x */
        );				/* all others */
    
    /* don't list other alternatives as we install a notify handler on the 570 */
    IBM_HANDLE(pci, root, "\\_SB.PCI");	/* 570 */
    
    IBM_HANDLE(hkey, ec, "\\_SB.HKEY",	/* 600e/x, 770e, 770x */
    	   "^HKEY",		/* R30, R31 */
    	   "HKEY",		/* all others */
        );				/* 570 */
    
    IBM_HANDLE(lght, root, "\\LGHT");	/* A21e, A2xm/p, T20-22, X20-21 */
    IBM_HANDLE(ledb, ec, "LEDB");	/* G4x */
    
    IBM_HANDLE(led, ec, "SLED",	/* 570 */
    	   "SYSL",		/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
    	   "LED",		/* all others */
        );				/* R30, R31 */
    
    IBM_HANDLE(beep, ec, "BEEP");	/* all except R30, R31 */
    IBM_HANDLE(ecrd, ec, "ECRD");	/* 570 */
    IBM_HANDLE(ecwr, ec, "ECWR");	/* 570 */
    IBM_HANDLE(fans, ec, "FANS");	/* X31, X40 */
    
    IBM_HANDLE(gfan, ec, "GFAN",	/* 570 */
    	   "\\FSPD",		/* 600e/x, 770e, 770x */
        );				/* all others */
    
    IBM_HANDLE(sfan, ec, "SFAN",	/* 570 */
    	   "JFNS",		/* 770x-JL */
        );				/* all others */
    
    #define IBM_HKEY_HID	"IBM0068"
    #define IBM_PCI_HID	"PNP0A03"
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    struct ibm_struct {
    	char *name;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	char *hid;
    	struct acpi_driver *driver;
    
    
    	int (*init) (void);
    	int (*read) (char *);
    	int (*write) (char *);
    	void (*exit) (void);
    
    	void (*notify) (struct ibm_struct *, u32);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	acpi_handle *handle;
    	int type;
    	struct acpi_device *device;
    
    	int driver_registered;
    	int proc_created;
    	int init_called;
    	int notify_installed;
    
    	int experimental;
    };
    
    static struct proc_dir_entry *proc_dir = NULL;
    
    #define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
    #define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
    #define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
    
    static int acpi_evalf(acpi_handle handle,
    		      void *res, char *method, char *fmt, ...)
    {
    	char *fmt0 = fmt;
    
    	struct acpi_object_list params;
    	union acpi_object in_objs[IBM_MAX_ACPI_ARGS];
    	struct acpi_buffer result, *resultp;
    	union acpi_object out_obj;
    	acpi_status status;
    	va_list ap;
    	char res_type;
    	int success;
    	int quiet;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!*fmt) {
    		printk(IBM_ERR "acpi_evalf() called with empty format\n");
    		return 0;
    	}
    
    	if (*fmt == 'q') {
    		quiet = 1;
    		fmt++;
    	} else
    		quiet = 0;
    
    	res_type = *(fmt++);
    
    	params.count = 0;
    	params.pointer = &in_objs[0];
    
    	va_start(ap, fmt);
    	while (*fmt) {
    		char c = *(fmt++);
    		switch (c) {
    		case 'd':	/* int */
    			in_objs[params.count].integer.value = va_arg(ap, int);
    			in_objs[params.count++].type = ACPI_TYPE_INTEGER;
    			break;
    
    			/* add more types as needed */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		default:
    			printk(IBM_ERR "acpi_evalf() called "
    			       "with invalid format character '%c'\n", c);
    			return 0;
    		}
    	}
    	va_end(ap);
    
    
    	if (res_type != 'v') {
    		result.length = sizeof(out_obj);
    		result.pointer = &out_obj;
    		resultp = &result;
    	} else
    		resultp = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	status = acpi_evaluate_object(handle, method, &params, resultp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	switch (res_type) {
    
    	case 'd':		/* int */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (res)
    			*(int *)res = out_obj.integer.value;
    		success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
    		break;
    
    	case 'v':		/* void */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		success = status == AE_OK;
    		break;
    
    		/* add more types as needed */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	default:
    		printk(IBM_ERR "acpi_evalf() called "
    		       "with invalid format character '%c'\n", res_type);
    		return 0;
    	}
    
    	if (!success && !quiet)
    		printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
    		       method, fmt0, status);
    
    	return success;
    }
    
    static void __unused acpi_print_int(acpi_handle handle, char *method)
    {
    	int i;
    
    	if (acpi_evalf(handle, &i, method, "d"))
    		printk(IBM_INFO "%s = 0x%x\n", method, i);
    	else
    		printk(IBM_ERR "error calling %s\n", method);
    }
    
    static char *next_cmd(char **cmds)
    {
    	char *start = *cmds;
    	char *end;
    
    	while ((end = strchr(start, ',')) && end == start)
    		start = end + 1;
    
    	if (!end)
    		return NULL;
    
    	*end = 0;
    	*cmds = end + 1;
    	return start;
    }
    
    
    static int ibm_acpi_driver_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
    	printk(IBM_INFO "%s\n", IBM_URL);
    
    	return 0;
    }
    
    
    static int driver_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int len = 0;
    
    	len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC);
    	len += sprintf(p + len, "version:\t%s\n", IBM_VERSION);
    
    	return len;
    }
    
    
    static int hotkey_supported;
    static int hotkey_mask_supported;
    static int hotkey_orig_status;
    static int hotkey_orig_mask;
    
    static int hotkey_get(int *status, int *mask)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
    
    		return 0;
    
    	if (hotkey_mask_supported)
    		if (!acpi_evalf(hkey_handle, mask, "DHKN", "d"))
    			return 0;
    
    	return 1;
    
    static int hotkey_set(int status, int mask)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int i;
    
    	if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
    		return 0;
    
    
    	if (hotkey_mask_supported)
    		for (i = 0; i < 32; i++) {
    			int bit = ((1 << i) & mask) != 0;
    			if (!acpi_evalf(hkey_handle,
    					NULL, "MHKM", "vdd", i + 1, bit))
    				return 0;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int hotkey_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	/* hotkey not supported on 570 */
    	hotkey_supported = hkey_handle != NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (hotkey_supported) {
    		/* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
    		   A30, R30, R31, T20-22, X20-21, X22-24 */
    		hotkey_mask_supported =
    		    acpi_evalf(hkey_handle, NULL, "DHKN", "qv");
    
    		if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask))
    			return -ENODEV;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int hotkey_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int status, mask;
    	int len = 0;
    
    
    	if (!hotkey_supported) {
    		len += sprintf(p + len, "status:\t\tnot supported\n");
    		return len;
    	}
    
    	if (!hotkey_get(&status, &mask))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EIO;
    
    	len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
    
    	if (hotkey_mask_supported) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		len += sprintf(p + len, "mask:\t\t0x%04x\n", mask);
    		len += sprintf(p + len,
    			       "commands:\tenable, disable, reset, <mask>\n");
    	} else {
    		len += sprintf(p + len, "mask:\t\tnot supported\n");
    		len += sprintf(p + len, "commands:\tenable, disable, reset\n");
    	}
    
    	return len;
    }
    
    
    static int hotkey_write(char *buf)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int status, mask;
    	char *cmd;
    	int do_cmd = 0;
    
    
    	if (!hotkey_supported)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -ENODEV;
    
    
    	if (!hotkey_get(&status, &mask))
    		return -EIO;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	while ((cmd = next_cmd(&buf))) {
    		if (strlencmp(cmd, "enable") == 0) {
    			status = 1;
    		} else if (strlencmp(cmd, "disable") == 0) {
    			status = 0;
    		} else if (strlencmp(cmd, "reset") == 0) {
    
    			status = hotkey_orig_status;
    			mask = hotkey_orig_mask;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		} else if (sscanf(cmd, "0x%x", &mask) == 1) {
    			/* mask set */
    		} else if (sscanf(cmd, "%x", &mask) == 1) {
    			/* mask set */
    		} else
    			return -EINVAL;
    		do_cmd = 1;
    	}
    
    
    	if (do_cmd && !hotkey_set(status, mask))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EIO;
    
    	return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void hotkey_exit(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (hotkey_supported)
    		hotkey_set(hotkey_orig_status, hotkey_orig_mask);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void hotkey_notify(struct ibm_struct *ibm, u32 event)
    {
    	int hkey;
    
    	if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d"))
    		acpi_bus_generate_event(ibm->device, event, hkey);
    	else {
    		printk(IBM_ERR "unknown hotkey event %d\n", event);
    		acpi_bus_generate_event(ibm->device, event, 0);
    
    static int bluetooth_supported;
    
    static int bluetooth_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	/* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
    	   G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
    	bluetooth_supported = hkey_handle &&
    	    acpi_evalf(hkey_handle, NULL, "GBDC", "qv");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    }
    
    
    static int bluetooth_status(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int status;
    
    
    	if (!bluetooth_supported ||
    	    !acpi_evalf(hkey_handle, &status, "GBDC", "d"))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		status = 0;
    
    	return status;
    }
    
    
    static int bluetooth_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int len = 0;
    
    	int status = bluetooth_status();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (!bluetooth_supported)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		len += sprintf(p + len, "status:\t\tnot supported\n");
    	else if (!(status & 1))
    		len += sprintf(p + len, "status:\t\tnot installed\n");
    	else {
    		len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1));
    		len += sprintf(p + len, "commands:\tenable, disable\n");
    	}
    
    	return len;
    }
    
    
    static int bluetooth_write(char *buf)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	int status = bluetooth_status();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	char *cmd;
    	int do_cmd = 0;
    
    
    	if (!bluetooth_supported)
    		return -ENODEV;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	while ((cmd = next_cmd(&buf))) {
    		if (strlencmp(cmd, "enable") == 0) {
    			status |= 2;
    		} else if (strlencmp(cmd, "disable") == 0) {
    			status &= ~2;
    		} else
    			return -EINVAL;
    		do_cmd = 1;
    	}
    
    	if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    }
    
    
    static int wan_supported;
    
    static int wan_init(void)
    {
    	wan_supported = hkey_handle &&
    	    acpi_evalf(hkey_handle, NULL, "GWAN", "qv");
    
    	return 0;
    }
    
    static int wan_status(void)
    {
    	int status;
    
    	if (!wan_supported ||
    	    !acpi_evalf(hkey_handle, &status, "GWAN", "d"))
    		status = 0;
    
    	return status;
    }
    
    static int wan_read(char *p)
    {
    	int len = 0;
    	int status = wan_status();
    
    	if (!wan_supported)
    		len += sprintf(p + len, "status:\t\tnot supported\n");
    	else if (!(status & 1))
    		len += sprintf(p + len, "status:\t\tnot installed\n");
    	else {
    		len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1));
    		len += sprintf(p + len, "commands:\tenable, disable\n");
    	}
    
    	return len;
    }
    
    static int wan_write(char *buf)
    {
    	int status = wan_status();
    	char *cmd;
    	int do_cmd = 0;
    
    	if (!wan_supported)
    		return -ENODEV;
    
    	while ((cmd = next_cmd(&buf))) {
    		if (strlencmp(cmd, "enable") == 0) {
    			status |= 2;
    		} else if (strlencmp(cmd, "disable") == 0) {
    			status &= ~2;
    		} else
    			return -EINVAL;
    		do_cmd = 1;
    	}
    
    	if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
    		return -EIO;
    
    	return 0;
    }
    
    
    static int video_supported;
    static int video_orig_autosw;
    
    #define VIDEO_570 1
    #define VIDEO_770 2
    #define VIDEO_NEW 3
    
    static int video_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	int ivga;
    
    	if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
    		/* G41, assume IVGA doesn't change */
    		vid_handle = vid2_handle;
    
    	if (!vid_handle)
    		/* video switching not supported on R30, R31 */
    		video_supported = 0;
    	else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
    		/* 570 */
    		video_supported = VIDEO_570;
    	else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
    		/* 600e/x, 770e, 770x */
    		video_supported = VIDEO_770;
    	else
    		/* all others */
    		video_supported = VIDEO_NEW;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    }
    
    
    static int video_status(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int status = 0;
    	int i;
    
    
    	if (video_supported == VIDEO_570) {
    		if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87))
    			status = i & 3;
    	} else if (video_supported == VIDEO_770) {
    		if (acpi_evalf(NULL, &i, "\\VCDL", "d"))
    			status |= 0x01 * i;
    		if (acpi_evalf(NULL, &i, "\\VCDC", "d"))
    			status |= 0x02 * i;
    	} else if (video_supported == VIDEO_NEW) {
    		acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1);
    		if (acpi_evalf(NULL, &i, "\\VCDC", "d"))
    			status |= 0x02 * i;
    
    		acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0);
    		if (acpi_evalf(NULL, &i, "\\VCDL", "d"))
    			status |= 0x01 * i;
    		if (acpi_evalf(NULL, &i, "\\VCDD", "d"))
    			status |= 0x08 * i;
    	}
    
    	return status;
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int video_autosw(void)
    {
    	int autosw = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (video_supported == VIDEO_570)
    		acpi_evalf(vid_handle, &autosw, "SWIT", "d");
    	else if (video_supported == VIDEO_770 || video_supported == VIDEO_NEW)
    		acpi_evalf(vid_handle, &autosw, "^VDEE", "d");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	return autosw & 1;
    
    static int video_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	int status = video_status();
    	int autosw = video_autosw();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int len = 0;
    
    
    	if (!video_supported) {
    		len += sprintf(p + len, "status:\t\tnot supported\n");
    		return len;
    	}
    
    	len += sprintf(p + len, "status:\t\tsupported\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
    	len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
    
    	if (video_supported == VIDEO_NEW)
    		len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
    	len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
    	len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
    	len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
    	if (video_supported == VIDEO_NEW)
    		len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
    	len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
    
    	return len;
    }
    
    
    static int video_switch(void)
    {
    	int autosw = video_autosw();
    	int ret;
    
    	if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1))
    		return -EIO;
    	ret = video_supported == VIDEO_570 ?
    	    acpi_evalf(ec_handle, NULL, "_Q16", "v") :
    	    acpi_evalf(vid_handle, NULL, "VSWT", "v");
    	acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw);
    
    	return ret;
    }
    
    static int video_expand(void)
    {
    	if (video_supported == VIDEO_570)
    		return acpi_evalf(ec_handle, NULL, "_Q17", "v");
    	else if (video_supported == VIDEO_770)
    		return acpi_evalf(vid_handle, NULL, "VEXP", "v");
    	else
    		return acpi_evalf(NULL, NULL, "\\VEXP", "v");
    }
    
    static int video_switch2(int status)
    {
    	int ret;
    
    	if (video_supported == VIDEO_570) {
    		ret = acpi_evalf(NULL, NULL,
    				 "\\_SB.PHS2", "vdd", 0x8b, status | 0x80);
    	} else if (video_supported == VIDEO_770) {
    		int autosw = video_autosw();
    		if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1))
    			return -EIO;
    
    		ret = acpi_evalf(vid_handle, NULL,
    				 "ASWT", "vdd", status * 0x100, 0);
    
    		acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw);
    	} else {
    		ret = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
    		    acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
    	}
    
    	return ret;
    }
    
    static int video_write(char *buf)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	char *cmd;
    	int enable, disable, status;
    
    
    	if (!video_supported)
    		return -ENODEV;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	enable = disable = 0;
    
    	while ((cmd = next_cmd(&buf))) {
    		if (strlencmp(cmd, "lcd_enable") == 0) {
    			enable |= 0x01;
    		} else if (strlencmp(cmd, "lcd_disable") == 0) {
    			disable |= 0x01;
    		} else if (strlencmp(cmd, "crt_enable") == 0) {
    			enable |= 0x02;
    		} else if (strlencmp(cmd, "crt_disable") == 0) {
    			disable |= 0x02;
    
    		} else if (video_supported == VIDEO_NEW &&
    			   strlencmp(cmd, "dvi_enable") == 0) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			enable |= 0x08;
    
    		} else if (video_supported == VIDEO_NEW &&
    			   strlencmp(cmd, "dvi_disable") == 0) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			disable |= 0x08;
    		} else if (strlencmp(cmd, "auto_enable") == 0) {
    			if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1))
    				return -EIO;
    		} else if (strlencmp(cmd, "auto_disable") == 0) {
    			if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0))
    				return -EIO;
    		} else if (strlencmp(cmd, "video_switch") == 0) {
    
    			if (!video_switch())
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return -EIO;
    		} else if (strlencmp(cmd, "expand_toggle") == 0) {
    
    			if (!video_expand())
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return -EIO;
    		} else
    			return -EINVAL;
    	}
    
    	if (enable || disable) {
    
    		status = (video_status() & 0x0f & ~disable) | enable;
    		if (!video_switch2(status))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return -EIO;
    	}
    
    	return 0;
    }
    
    
    static void video_exit(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw);
    
    static int light_supported;
    static int light_status_supported;
    
    static int light_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	/* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
    	light_supported = (cmos_handle || lght_handle) && !ledb_handle;
    
    	if (light_supported)
    		/* light status not supported on
    		   570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
    		light_status_supported = acpi_evalf(ec_handle, NULL,
    						    "KBLT", "qv");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    }
    
    
    static int light_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int len = 0;
    	int status = 0;
    
    
    	if (!light_supported) {
    		len += sprintf(p + len, "status:\t\tnot supported\n");
    	} else if (!light_status_supported) {
    		len += sprintf(p + len, "status:\t\tunknown\n");
    		len += sprintf(p + len, "commands:\ton, off\n");
    	} else {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
    			return -EIO;
    		len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
    
    		len += sprintf(p + len, "commands:\ton, off\n");
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return len;
    }
    
    
    static int light_write(char *buf)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int cmos_cmd, lght_cmd;
    	char *cmd;
    	int success;
    
    
    	if (!light_supported)
    		return -ENODEV;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	while ((cmd = next_cmd(&buf))) {
    		if (strlencmp(cmd, "on") == 0) {
    			cmos_cmd = 0x0c;
    			lght_cmd = 1;
    		} else if (strlencmp(cmd, "off") == 0) {
    			cmos_cmd = 0x0d;
    			lght_cmd = 0;
    		} else
    			return -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		success = cmos_handle ?
    
    		    acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) :
    		    acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!success)
    			return -EIO;
    	}
    
    	return 0;
    }
    
    static int _sta(acpi_handle handle)
    {
    	int status;
    
    	if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
    		status = 0;
    
    	return status;
    }
    
    #ifdef CONFIG_ACPI_IBM_DOCK
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #define dock_docked() (_sta(dock_handle) & 1)
    
    
    static int dock_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int len = 0;
    	int docked = dock_docked();
    
    	if (!dock_handle)
    		len += sprintf(p + len, "status:\t\tnot supported\n");
    	else if (!docked)
    		len += sprintf(p + len, "status:\t\tundocked\n");
    	else {
    		len += sprintf(p + len, "status:\t\tdocked\n");
    		len += sprintf(p + len, "commands:\tdock, undock\n");
    	}
    
    	return len;
    }
    
    
    static int dock_write(char *buf)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	char *cmd;
    
    	if (!dock_docked())
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	while ((cmd = next_cmd(&buf))) {
    		if (strlencmp(cmd, "undock") == 0) {
    
    			if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) ||
    			    !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return -EIO;
    		} else if (strlencmp(cmd, "dock") == 0) {
    			if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
    				return -EIO;
    		} else
    			return -EINVAL;
    	}
    
    	return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static void dock_notify(struct ibm_struct *ibm, u32 event)
    {
    	int docked = dock_docked();
    
    	int pci = ibm->hid && strstr(ibm->hid, IBM_PCI_HID);
    
    	if (event == 1 && !pci)	/* 570 */
    		acpi_bus_generate_event(ibm->device, event, 1);	/* button */
    	else if (event == 1 && pci)	/* 570 */
    		acpi_bus_generate_event(ibm->device, event, 3);	/* dock */
    	else if (event == 3 && docked)
    		acpi_bus_generate_event(ibm->device, event, 1);	/* button */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else if (event == 3 && !docked)
    
    		acpi_bus_generate_event(ibm->device, event, 2);	/* undock */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else if (event == 0 && docked)
    
    		acpi_bus_generate_event(ibm->device, event, 3);	/* dock */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else {
    		printk(IBM_ERR "unknown dock event %d, status %d\n",
    		       event, _sta(dock_handle));
    
    		acpi_bus_generate_event(ibm->device, event, 0);	/* unknown */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int bay_status_supported;
    static int bay_status2_supported;
    static int bay_eject_supported;
    static int bay_eject2_supported;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int bay_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	bay_status_supported = bay_handle &&
    	    acpi_evalf(bay_handle, NULL, "_STA", "qv");
    	bay_status2_supported = bay2_handle &&
    	    acpi_evalf(bay2_handle, NULL, "_STA", "qv");
    
    	bay_eject_supported = bay_handle && bay_ej_handle &&
    	    (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental);
    	bay_eject2_supported = bay2_handle && bay2_ej_handle &&
    	    (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    }
    
    
    #define bay_occupied(b) (_sta(b##_handle) & 1)
    
    static int bay_read(char *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int len = 0;
    
    	int occupied = bay_occupied(bay);