Re: more backlight stuff, x61, kernel 2.6.26.1 [Thinkpad T61 Intel 965GM] "reverse engineering" from a Lenovo SLED preinstalled working laptop

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Yves-Alexis Perez wrote:

> Or maybe some hacks from SLED

"reverse engineering" from a Lenovo SLED preinstalled working laptop

I rebooted my laptop and went back to SLED10 (I still have it with dual
boot) to look for a clue of what makes the Fn keys work in it, or as you
said maybe a SLED special hack.

And I found something curius:

The acpi module 'video' does not load, here is the output when I tried to
load it (remember, we are in SLED):

# modprobe video
FATAL: Error inserting video
(/lib/modules/2.6.16.60-0.23-smp/kernel/drivers/acpi/video.ko): No such
device
Aug  9 03:56:10 xavier kernel: ACPI Video: Lenovo detected, use ibm_acpi
instead of video driver or use force parameter


Then I discover that the brightness Fn keys works only after
loading 'ibm-acpi' module.

I unloaded 'ibm-acpi' and the brightness Fn keys stop working, then I loaded
it again and the Fn keys worked again, so we need this module for this keys
to work (I mean that is not controled by the BIOS directly).

Here is the output when loading 'ibm-acpi':

# modprobe ibm-acpi
Aug  9 04:00:20 xavier kernel: Lenovo ThinkPad detected
Aug  9 03:58:36 xavier kernel: ibm_acpi: Found video device \_SB.PCI0.VID
Aug  9 03:58:36 xavier kernel: ibm_acpi: IBM ThinkPad ACPI Extras v0.12a
Aug  9 03:58:36 xavier kernel: ibm_acpi: http://ibm-acpi.sf.net/
Aug  9 03:58:36 xavier kernel: ibm_acpi: Found Lenovo ThinkPad with 15
brightness levels


Here is the output of modinfo for 'ibm-acpi' (looks like the lenovo people
do worked on the acpi modules code?):

# modinfo ibm-acpi
filename:       /lib/modules/2.6.16.60-0.23-smp/kernel/drivers/acpi/ibm_acpi.ko
license:        GPL
version:        0.12a
description:    IBM ThinkPad ACPI Extras
author:         Borislav Deianov
srcversion:     57622613BD62EE569520FAE
depends:        nvram
supported:      yes
vermagic:       2.6.16.60-0.23-smp SMP 586 REGPARM gcc-4.1
parm:           force_brightness:Control brightness, even ACPI brightness
functions are provided by BIOS
 - Default on SLES (int)
parm:           experimental:int


I also played with the parameters of the 'ibm-acpi' module: 
with 'force_brightness=0' => Brightness Fn keys do NOT work
with 'force_brightness=1' => Brightness Fn keys do work :)

If the 'ibm-acpi' module is loaded with no parameters, it automatically
force the 'force_brightness=1' parameter.

Additionaly here is the modinfo output for acpi 'video' module (lenovo
people was here too?):

# modinfo video
filename:       /lib/modules/2.6.16.60-0.23-smp/kernel/drivers/acpi/video.ko
license:        GPL
description:    ACPI Video Driver
author:         Bruno Ducrot
srcversion:     0AEFF7106D690120983D98F
depends:        
supported:      yes
vermagic:       2.6.16.60-0.23-smp SMP 586 REGPARM gcc-4.1
parm:           force:Control brightness even on Lenovo machines (int)



I went to the kernel sources (/usr/src/linux/...) to look for the sources of
the 'ibm-acpi', I only found a 'ibm-acpi.c' file, I have attached it. I
looked for the kernel-sources-xxx-src.rpm to look for a specific patch from
lenovo to this file but I couldn't find the sources for SLED (I think they
are on the SDK from Novell but the SKD is downloable only in very
large .iso images).

Well I hope that this information could be usefull to someone and maybe we
could resolve this problem making some kind of reverse engineering.

Thanks in advance.

Rgds., Xavier.-
/*
 *  ibm_acpi.c - IBM ThinkPad ACPI Extras
 *
 *
 *  Copyright (C) 2004-2005 Borislav Deianov <borislav@xxxxxxxxxxxx>
 *
 *  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"

/*
 *  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@xxxxxxxxxxxxxx>
 *			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@xxxxxxxxxx>
 *			fix parameter passing on module loading
 *			    thanks to Rusty Russell <rusty@xxxxxxxxxxxxxxx>
 *			    thanks to Jim Radford <radford@xxxxxxxxxxxxx>
 *  2004-11-08	0.8	fix init error case, don't return from a macro
 *			    thanks to Chris Wright <chrisw@xxxxxxxx>
 *  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
 *  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
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/dmi.h>
#include <linux/nvram.h>
#include <linux/acpi.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");

#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 force_brightness = 1;
module_param(force_brightness, int, 0);
MODULE_PARM_DESC(force_brightness, "Control brightness, even ACPI"
		 " brightness functions are provided by BIOS\n - Default on SLES");

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;			\
	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 DOCK_ENABLE
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 */
#endif
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 */
	   "\\_SB.PCI0.SATA.SCND.MSTR", /* T60 and similar */
    );				/* 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"

struct ibm_struct {
	char *name;
	char param[32];

	char *hid;
	struct acpi_driver *driver;

	int (*init) (void);
	int (*read) (char *);
	int (*write) (char *);
	void (*exit) (void);

	void (*notify) (struct ibm_struct *, u32);
	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;

	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 */
		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;

	status = acpi_evaluate_object(handle, method, &params, resultp);

	switch (res_type) {
	case 'd':		/* int */
		if (res)
			*(int *)res = out_obj.integer.value;
		success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
		break;
	case 'v':		/* void */
		success = status == AE_OK;
		break;
		/* add more types as needed */
	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 driver_init(void)
{
	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)
{
	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)
{
	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)
{
	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;
		}

	return 1;
}

static int hotkey_init(void)
{
	/* hotkey not supported on 570 */
	hotkey_supported = hkey_handle != NULL;

	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;
	}

	return 0;
}

static int hotkey_read(char *p)
{
	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))
		return -EIO;

	len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
	if (hotkey_mask_supported) {
		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)
{
	int status, mask;
	char *cmd;
	int do_cmd = 0;

	if (!hotkey_supported)
		return -ENODEV;

	if (!hotkey_get(&status, &mask))
		return -EIO;

	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;
		} 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))
		return -EIO;

	return 0;
}

static void hotkey_exit(void)
{
	if (hotkey_supported)
		hotkey_set(hotkey_orig_status, hotkey_orig_mask);
}

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)
{
	/* 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");

	return 0;
}

static int bluetooth_status(void)
{
	int status;

	if (!bluetooth_supported ||
	    !acpi_evalf(hkey_handle, &status, "GBDC", "d"))
		status = 0;

	return status;
}

static int bluetooth_read(char *p)
{
	int len = 0;
	int status = bluetooth_status();

	if (!bluetooth_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 bluetooth_write(char *buf)
{
	int status = bluetooth_status();
	char *cmd;
	int do_cmd = 0;

	if (!bluetooth_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, "SBDC", "vd", status))
		return -EIO;

	return 0;
}

#define IBM_VENDOR 1
#define LENOVO_VENDOR 2
static int vendor;

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)
{
	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;

	return 0;
}

static int video_status(void)
{
	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;
}

static int video_autosw(void)
{
	int autosw = 0;

	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");

	return autosw & 1;
}

static int video_read(char *p)
{
	int status = video_status();
	int autosw = video_autosw();
	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");
	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");
	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)
{
	char *cmd;
	int enable, disable, status;

	if (!video_supported)
		return -ENODEV;

	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) {
			enable |= 0x08;
		} else if (video_supported == VIDEO_NEW &&
			   strlencmp(cmd, "dvi_disable") == 0) {
			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())
				return -EIO;
		} else if (strlencmp(cmd, "expand_toggle") == 0) {
			if (!video_expand())
				return -EIO;
		} else
			return -EINVAL;
	}

	if (enable || disable) {
		status = (video_status() & 0x0f & ~disable) | enable;
		if (!video_switch2(status))
			return -EIO;
	}

	return 0;
}

static void video_exit(void)
{
	acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw);
}

static int light_supported;
static int light_status_supported;

static int light_init(void)
{
	/* 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");

	return 0;
}

static int light_read(char *p)
{
	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 {
		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");
	}

	return len;
}

static int light_write(char *buf)
{
	int cmos_cmd, lght_cmd;
	char *cmd;
	int success;

	if (!light_supported)
		return -ENODEV;

	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;

		success = cmos_handle ?
		    acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) :
		    acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd);
		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 DOCK_ENABLE
#define dock_docked() (_sta(dock_handle) & 1)

static int dock_read(char *p)
{
	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)
{
	char *cmd;

	if (!dock_docked())
		return -ENODEV;

	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))
				return -EIO;
		} else if (strlencmp(cmd, "dock") == 0) {
			if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
				return -EIO;
		} else
			return -EINVAL;
	}

	return 0;
}

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 */
	else if (event == 3 && !docked)
		acpi_bus_generate_event(ibm->device, event, 2);	/* undock */
	else if (event == 0 && docked)
		acpi_bus_generate_event(ibm->device, event, 3);	/* dock */
	else {
		printk(IBM_ERR "unknown dock event %d, status %d\n",
		       event, _sta(dock_handle));
		acpi_bus_generate_event(ibm->device, event, 0);	/* unknown */
	}
}
#endif
static int bay_status_supported;
static int bay_status2_supported;
static int bay_eject_supported;
static int bay_eject2_supported;

static int bay_init(void)
{
	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);

	return 0;
}

#define bay_occupied(b) (_sta(b##_handle) & 1)

static int bay_read(char *p)
{
	int len = 0;
	int occupied = bay_occupied(bay);
	int occupied2 = bay_occupied(bay2);
	int eject, eject2;

	len += sprintf(p + len, "status:\t\t%s\n", bay_status_supported ?
		       (occupied ? "occupied" : "unoccupied") :
		       "not supported");
	if (bay_status2_supported)
		len += sprintf(p + len, "status2:\t%s\n", occupied2 ?
			       "occupied" : "unoccupied");

	eject = bay_eject_supported && occupied;
	eject2 = bay_eject2_supported && occupied2;

	if (eject && eject2)
		len += sprintf(p + len, "commands:\teject, eject2\n");
	else if (eject)
		len += sprintf(p + len, "commands:\teject\n");
	else if (eject2)
		len += sprintf(p + len, "commands:\teject2\n");

	return len;
}

static int bay_write(char *buf)
{
	char *cmd;

	if (!bay_eject_supported && !bay_eject2_supported)
		return -ENODEV;

	while ((cmd = next_cmd(&buf))) {
		if (bay_eject_supported && strlencmp(cmd, "eject") == 0) {
			if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1))
				return -EIO;
		} else if (bay_eject2_supported &&
			   strlencmp(cmd, "eject2") == 0) {
			if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1))
				return -EIO;
		} else
			return -EINVAL;
	}

	return 0;
}

static void bay_notify(struct ibm_struct *ibm, u32 event)
{
	acpi_bus_generate_event(ibm->device, event, 0);
}

static int cmos_read(char *p)
{
	int len = 0;

	/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
	   R30, R31, T20-22, X20-21 */
	if (!cmos_handle)
		len += sprintf(p + len, "status:\t\tnot supported\n");
	else {
		len += sprintf(p + len, "status:\t\tsupported\n");
		len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
	}

	return len;
}

static int cmos_eval(int cmos_cmd)
{
	if (cmos_handle)
		return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd);
	else
		return 1;
}

static int cmos_write(char *buf)
{
	char *cmd;
	int cmos_cmd;

	if (!cmos_handle)
		return -EINVAL;

	while ((cmd = next_cmd(&buf))) {
		if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
		    cmos_cmd >= 0 && cmos_cmd <= 21) {
			/* cmos_cmd set */
		} else
			return -EINVAL;

		if (!cmos_eval(cmos_cmd))
			return -EIO;
	}

	return 0;
}

static int led_supported;

#define LED_570 1
#define LED_OLD 2
#define LED_NEW 3

static int led_init(void)
{
	if (!led_handle)
		/* led not supported on R30, R31 */
		led_supported = 0;
	else if (strlencmp(led_path, "SLED") == 0)
		/* 570 */
		led_supported = LED_570;
	else if (strlencmp(led_path, "SYSL") == 0)
		/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
		led_supported = LED_OLD;
	else
		/* all others */
		led_supported = LED_NEW;

	return 0;
}

#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking"))

static int led_read(char *p)
{
	int len = 0;

	if (!led_supported) {
		len += sprintf(p + len, "status:\t\tnot supported\n");
		return len;
	}
	len += sprintf(p + len, "status:\t\tsupported\n");

	if (led_supported == LED_570) {
		/* 570 */
		int i, status;
		for (i = 0; i < 8; i++) {
			if (!acpi_evalf(ec_handle,
					&status, "GLED", "dd", 1 << i))
				return -EIO;
			len += sprintf(p + len, "%d:\t\t%s\n",
				       i, led_status(status));
		}
	}

	len += sprintf(p + len, "commands:\t"
		       "<led> on, <led> off, <led> blink (<led> is 0-7)\n");

	return len;
}

/* off, on, blink */
static const int led_sled_arg1[] = { 0, 1, 3 };
static const int led_exp_hlbl[] = { 0, 0, 1 };	/* led# * */
static const int led_exp_hlcl[] = { 0, 1, 1 };	/* led# * */
static const int led_led_arg1[] = { 0, 0x80, 0xc0 };

#define EC_HLCL 0x0c
#define EC_HLBL 0x0d
#define EC_HLMS 0x0e

static int led_write(char *buf)
{
	char *cmd;
	int led, ind, ret;

	if (!led_supported)
		return -ENODEV;

	while ((cmd = next_cmd(&buf))) {
		if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7)
			return -EINVAL;

		if (strstr(cmd, "off")) {
			ind = 0;
		} else if (strstr(cmd, "on")) {
			ind = 1;
		} else if (strstr(cmd, "blink")) {
			ind = 2;
		} else
			return -EINVAL;

		if (led_supported == LED_570) {
			/* 570 */
			led = 1 << led;
			if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
					led, led_sled_arg1[ind]))
				return -EIO;
		} else if (led_supported == LED_OLD) {
			/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
			led = 1 << led;
			ret = ec_write(EC_HLMS, led);
			if (ret >= 0)
				ret =
				    ec_write(EC_HLBL, led * led_exp_hlbl[ind]);
			if (ret >= 0)
				ret =
				    ec_write(EC_HLCL, led * led_exp_hlcl[ind]);
			if (ret < 0)
				return ret;
		} else {
			/* all others */
			if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
					led, led_led_arg1[ind]))
				return -EIO;
		}
	}

	return 0;
}

static int beep_read(char *p)
{
	int len = 0;

	if (!beep_handle)
		len += sprintf(p + len, "status:\t\tnot supported\n");
	else {
		len += sprintf(p + len, "status:\t\tsupported\n");
		len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
	}

	return len;
}

static int beep_write(char *buf)
{
	char *cmd;
	int beep_cmd;

	if (!beep_handle)
		return -ENODEV;

	while ((cmd = next_cmd(&buf))) {
		if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
		    beep_cmd >= 0 && beep_cmd <= 17) {
			/* beep_cmd set */
		} else
			return -EINVAL;
		if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
			return -EIO;
	}

	return 0;
}

static int acpi_ec_read(int i, u8 * p)
{
	int v;

	if (ecrd_handle) {
		if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
			return 0;
		*p = v;
	} else {
		if (ec_read(i, p) < 0)
			return 0;
	}

	return 1;
}

static int acpi_ec_write(int i, u8 v)
{
	if (ecwr_handle) {
		if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
			return 0;
	} else {
		if (ec_write(i, v) < 0)
			return 0;
	}

	return 1;
}

static int thermal_tmp_supported;
static int thermal_updt_supported;

static int thermal_init(void)
{
	/* temperatures not supported on 570, G4x, R30, R31, R32 */
	thermal_tmp_supported = acpi_evalf(ec_handle, NULL, "TMP7", "qv");

	/* 600e/x, 770e, 770x */
	thermal_updt_supported = acpi_evalf(ec_handle, NULL, "UPDT", "qv");

	return 0;
}

static int thermal_read(char *p)
{
	int len = 0;

	if (!thermal_tmp_supported)
		len += sprintf(p + len, "temperatures:\tnot supported\n");
	else {
		int i, t;
		char tmpi[] = "TMPi";
		s8 tmp[8];

		if (thermal_updt_supported)
			if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
				return -EIO;

		for (i = 0; i < 8; i++) {
			tmpi[3] = '0' + i;
			if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
				return -EIO;
			if (thermal_updt_supported)
				tmp[i] = (t - 2732 + 5) / 10;
			else
				tmp[i] = t;
		}

		len += sprintf(p + len,
			       "temperatures:\t%d %d %d %d %d %d %d %d\n",
			       tmp[0], tmp[1], tmp[2], tmp[3],
			       tmp[4], tmp[5], tmp[6], tmp[7]);
	}

	return len;
}

static u8 ecdump_regs[256];

static int ecdump_read(char *p)
{
	int len = 0;
	int i, j;
	u8 v;

	len += sprintf(p + len, "EC      "
		       " +00 +01 +02 +03 +04 +05 +06 +07"
		       " +08 +09 +0a +0b +0c +0d +0e +0f\n");
	for (i = 0; i < 256; i += 16) {
		len += sprintf(p + len, "EC 0x%02x:", i);
		for (j = 0; j < 16; j++) {
			if (!acpi_ec_read(i + j, &v))
				break;
			if (v != ecdump_regs[i + j])
				len += sprintf(p + len, " *%02x", v);
			else
				len += sprintf(p + len, "  %02x", v);
			ecdump_regs[i + j] = v;
		}
		len += sprintf(p + len, "\n");
		if (j != 16)
			break;
	}

	/* These are way too dangerous to advertise openly... */
#if 0
	len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
		       " (<offset> is 00-ff, <value> is 00-ff)\n");
	len += sprintf(p + len, "commands:\t0x<offset> <value>  "
		       " (<offset> is 00-ff, <value> is 0-255)\n");
#endif
	return len;
}

static int ecdump_write(char *buf)
{
	char *cmd;
	int i, v;

	while ((cmd = next_cmd(&buf))) {
		if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) {
			/* i and v set */
		} else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) {
			/* i and v set */
		} else
			return -EINVAL;
		if (i >= 0 && i < 256 && v >= 0 && v < 256) {
			if (!acpi_ec_write(i, v))
				return -EIO;
		} else
			return -EINVAL;
	}

	return 0;
}

/********************************************
Brightness
*/

static int brightness_offset = 0x31;

/* ThinkPad CMOS NVRAM constants */
#define TP_NVRAM_ADDR_BRIGHTNESS       0x5e
#define TP_NVRAM_MASK_LEVEL_BRIGHTNESS 0xF
#define TP_NVRAM_POS_LEVEL_BRIGHTNESS 0

static int max_brightness = 7;

static int query_bcl_levels(acpi_handle handle)
{
       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
       union acpi_object *obj;
       int rc;

       if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
               obj = (union acpi_object *)buffer.pointer;
               if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
                       printk(IBM_ERR "Invalid _BCL data\n");
                       rc = 0;
               } else {
                       if (obj->package.count >= 3)
                               rc = obj->package.count - 3;
                       else
                               rc = 0;
               }
       } else {
               return 0;
       }

       kfree(buffer.pointer);
       return rc;
}

#define ACPI_VIDEO_NOTIFY_CYCLE_BRIGHTNESS	0x85
#define ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS	0x86
#define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS	0x87
#define ACPI_VIDEO_NOTIFY_ZERO_BRIGHTNESS	0x88
#define ACPI_VIDEO_NOTIFY_DISPLAY_OFF		0x89

#define ACPI_VIDEO_NOTIFY_SWITCH 0x80	/* change in status (cycle output device) */
#define ACPI_VIDEO_NOTIFY_PROBE  0x81	/* change in status (output device status) */

#define BRIGHTNESS_UP	4
#define BRIGHTNESS_DOWN	5

static int brightness_get(void)
{
	u8 level, ret = 1;

	if (vendor == LENOVO_VENDOR) {
		level = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
			 & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
				 >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
		if (level > max_brightness)
			ret = 0;
	} else
		ret = acpi_ec_read(brightness_offset, &level);
	if (!ret){
		printk(IBM_ERR "Could not read current brightness level\n");
		return -ENODEV;
	}
	else
		return level;
}

static void brightness_notify(acpi_handle handle, u32 event, void *data)
{
	int new_level, level, cmos_cmd, inc, i;
	struct acpi_device *dev;

	if (!data)
		return;
	dev = (struct acpi_device*)data;

	switch (event) {
	case ACPI_VIDEO_NOTIFY_SWITCH:	/* change in status (cycle output device) */
	case ACPI_VIDEO_NOTIFY_PROBE:	/* change in status (output device status) */
		acpi_bus_generate_event(dev, event, 0);
		return;
	case ACPI_VIDEO_NOTIFY_CYCLE_BRIGHTNESS:	/* Cycle brightness */
	case ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS:	/* Increase brightness */
		level = brightness_get();
		new_level = level == (max_brightness)
			? max_brightness : level + 1;
		break;
	case ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS:	/* Decrease brightness */
		level = brightness_get();
		new_level = level == 0 ? 0 : level - 1;
		break;
	case ACPI_VIDEO_NOTIFY_DISPLAY_OFF:	/* display device off */
	case ACPI_VIDEO_NOTIFY_ZERO_BRIGHTNESS:	/* zero brightnesss */
		level = brightness_get();
		new_level = 0;
		break;
	default:
		printk(IBM_DEBUG "Unsupported event [0x%x]\n", event);
		return;
	}
	/* Export the display brightness event, graphics card drivers
	   may need it */
	acpi_bus_generate_event(dev, event, 0);
	cmos_cmd = new_level > level ? BRIGHTNESS_UP : BRIGHTNESS_DOWN;
	inc = new_level > level ? 1 : -1;
	for (i = level; i != new_level; i += inc) {
		if (!cmos_eval(cmos_cmd))
			return;
		if (vendor != LENOVO_VENDOR) {
			if (!acpi_ec_write(brightness_offset, i + inc))
				return;
		}
	}
}

static int brightness_notify_installed;
static acpi_handle lcd_handle;
static int brightness_unsupported;

static int brightness_init(void)
{
	int status, ret;
	struct acpi_device *lcd_device;

	/* We check for _BCL for the brightness levels on Lenovos.
	   Video and brightness are the same device therefore the vid handle...
	   If anything goes wrong we still return success and try to handle
	   brightness in the old way
	*/
	if (vendor == LENOVO_VENDOR) {
		/* Video module is going to support brightness */
		if (!force_brightness &&
		    (acpi_video_support & ACPI_VIDEO_BRIGHTNESS)){
			printk (IBM_INFO "Brightness supported by video driver\n");
			brightness_unsupported = 1;
			return 0;
		}

		status = acpi_get_handle(vid_handle, "LCD0._BCL", &lcd_handle);
		if (!ACPI_SUCCESS(status)){
			printk (IBM_INFO "Lenovo without BCL "
				"support found\n");
			return 0;
		}
		/* Invoke _BCL -> if this is done we *must* catch
		   brighntess up/down events and set the brightness ourself
		*/
		ret = query_bcl_levels(lcd_handle);
		printk(IBM_INFO "Found Lenovo ThinkPad with %d"
		       " brightness levels\n", ret);
		if (!ret)
			return 0;
		max_brightness = ret;
		status = acpi_get_handle(vid_handle, "LCD0", &lcd_handle);
		if (status) {
			printk(IBM_ERR "Could not get brightness handle\n");
			return 0;
		}
		status = acpi_bus_get_device(lcd_handle, &lcd_device);
		if (status) {
			printk(IBM_ERR "Could not get brightness device\n");
			return 0;
		}
		brightness_notify_installed = 1;
		status = acpi_install_notify_handler(lcd_handle, ACPI_DEVICE_NOTIFY,
						     brightness_notify, lcd_device);
		if (status) {
			printk(IBM_ERR "Could not register video brightness notify handler\n");
			return 0;
		}
	}
	return 0;
}

static void brightness_exit(void)
{
	int status;
	if (brightness_notify_installed) {
		status = acpi_remove_notify_handler(lcd_handle,
					   ACPI_DEVICE_NOTIFY,
					   brightness_notify);
	}
}

static int brightness_read(char *p)
{
	int len = 0;
	int level;
	level = brightness_get();

	if (brightness_unsupported)
		len += sprintf(p + len, "level:\t\tunsupported\n");
	else if (level < 0) {
		len += sprintf(p + len, "level:\t\tunreadable\n");
	} else {
		len += sprintf(p + len, "level:\t\t%d\n", level);
		len += sprintf(p + len, "commands:\tup, down\n");
		len += sprintf(p + len, "commands:\tlevel <level>"
			       " (<level> is 0-%d)\n",
			       max_brightness);
	}

	return len;
}

static int brightness_write(char *buf)
{
	int cmos_cmd, inc, i;
	int level;
	int new_level;
	char *cmd;

	if (brightness_unsupported)
		return -EINVAL;

	while ((cmd = next_cmd(&buf))) {
		level = brightness_get();
		if (level < 0)
			return -EIO;

		if (strlencmp(cmd, "up") == 0) {
			new_level = level == (max_brightness)
				? max_brightness : level + 1;
		} else if (strlencmp(cmd, "down") == 0) {
			new_level = level == 0 ? 0 : level - 1;
		} else if (sscanf(cmd, "level %d", &new_level) == 1 &&
			   new_level >= 0 && new_level <= max_brightness) {
			/* new_level set */
		} else
			return -EINVAL;

		cmos_cmd = new_level > level ? BRIGHTNESS_UP : BRIGHTNESS_DOWN;
		inc = new_level > level ? 1 : -1;
		for (i = level; i != new_level; i += inc) {
			if (!cmos_eval(cmos_cmd))
				return -EIO;
			/* Writing to EC might break s2ram on Lenovos */
			if (vendor != LENOVO_VENDOR) {
				if (!acpi_ec_write(brightness_offset, i + inc))
					return -EIO;
			}
		}
	}

	return 0;
}

static int volume_offset = 0x30;

static int volume_read(char *p)
{
	int len = 0;
	u8 level;

	if (!acpi_ec_read(volume_offset, &level)) {
		len += sprintf(p + len, "level:\t\tunreadable\n");
	} else {
		len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
		len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
		len += sprintf(p + len, "commands:\tup, down, mute\n");
		len += sprintf(p + len, "commands:\tlevel <level>"
			       " (<level> is 0-15)\n");
	}

	return len;
}

#define VOLUME_DOWN	0
#define VOLUME_UP	1
#define VOLUME_MUTE	2

static int volume_write(char *buf)
{
	int cmos_cmd, inc, i;
	u8 level, mute;
	int new_level, new_mute;
	char *cmd;

	while ((cmd = next_cmd(&buf))) {
		if (!acpi_ec_read(volume_offset, &level))
			return -EIO;
		new_mute = mute = level & 0x40;
		new_level = level = level & 0xf;

		if (strlencmp(cmd, "up") == 0) {
			if (mute)
				new_mute = 0;
			else
				new_level = level == 15 ? 15 : level + 1;
		} else if (strlencmp(cmd, "down") == 0) {
			if (mute)
				new_mute = 0;
			else
				new_level = level == 0 ? 0 : level - 1;
		} else if (sscanf(cmd, "level %d", &new_level) == 1 &&
			   new_level >= 0 && new_level <= 15) {
			/* new_level set */
		} else if (strlencmp(cmd, "mute") == 0) {
			new_mute = 0x40;
		} else
			return -EINVAL;

		if (new_level != level) {	/* mute doesn't change */
			cmos_cmd = new_level > level ? VOLUME_UP : VOLUME_DOWN;
			inc = new_level > level ? 1 : -1;

			if (mute && (!cmos_eval(cmos_cmd) ||
				     !acpi_ec_write(volume_offset, level)))
				return -EIO;

			for (i = level; i != new_level; i += inc)
				if (!cmos_eval(cmos_cmd) ||
				    !acpi_ec_write(volume_offset, i + inc))
					return -EIO;

			if (mute && (!cmos_eval(VOLUME_MUTE) ||
				     !acpi_ec_write(volume_offset,
						    new_level + mute)))
				return -EIO;
		}

		if (new_mute != mute) {	/* level doesn't change */
			cmos_cmd = new_mute ? VOLUME_MUTE : VOLUME_UP;

			if (!cmos_eval(cmos_cmd) ||
			    !acpi_ec_write(volume_offset, level + new_mute))
				return -EIO;
		}
	}

	return 0;
}

static int fan_status_offset = 0x2f;
static int fan_rpm_offset = 0x84;

static int fan_read(char *p)
{
	int len = 0;
	int s;
	u8 lo, hi, status;

	if (gfan_handle) {
		/* 570, 600e/x, 770e, 770x */
		if (!acpi_evalf(gfan_handle, &s, NULL, "d"))
			return -EIO;

		len += sprintf(p + len, "level:\t\t%d\n", s);
	} else {
		/* all except 570, 600e/x, 770e, 770x */
		if (!acpi_ec_read(fan_status_offset, &status))
			len += sprintf(p + len, "status:\t\tunreadable\n");
		else
			len += sprintf(p + len, "status:\t\t%s\n",
				       enabled(status, 7));

		if (!acpi_ec_read(fan_rpm_offset, &lo) ||
		    !acpi_ec_read(fan_rpm_offset + 1, &hi))
			len += sprintf(p + len, "speed:\t\tunreadable\n");
		else
			len += sprintf(p + len, "speed:\t\t%d\n",
				       (hi << 8) + lo);
	}

	if (sfan_handle)
		/* 570, 770x-JL */
		len += sprintf(p + len, "commands:\tlevel <level>"
			       " (<level> is 0-7)\n");
	if (!gfan_handle)
		/* all except 570, 600e/x, 770e, 770x */
		len += sprintf(p + len, "commands:\tenable, disable\n");
	if (fans_handle)
		/* X31, X40 */
		len += sprintf(p + len, "commands:\tspeed <speed>"
			       " (<speed> is 0-65535)\n");

	return len;
}

static int fan_write(char *buf)
{
	char *cmd;
	int level, speed;

	while ((cmd = next_cmd(&buf))) {
		if (sfan_handle &&
		    sscanf(cmd, "level %d", &level) == 1 &&
		    level >= 0 && level <= 7) {
			/* 570, 770x-JL */
			if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
				return -EIO;
		} else if (!gfan_handle && strlencmp(cmd, "enable") == 0) {
			/* all except 570, 600e/x, 770e, 770x */
			if (!acpi_ec_write(fan_status_offset, 0x80))
				return -EIO;
		} else if (!gfan_handle && strlencmp(cmd, "disable") == 0) {
			/* all except 570, 600e/x, 770e, 770x */
			if (!acpi_ec_write(fan_status_offset, 0x00))
				return -EIO;
		} else if (fans_handle &&
			   sscanf(cmd, "speed %d", &speed) == 1 &&
			   speed >= 0 && speed <= 65535) {
			/* X31, X40 */
			if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
					speed, speed, speed))
				return -EIO;
		} else
			return -EINVAL;
	}

	return 0;
}

static struct ibm_struct ibms[] = {
	{
	 .name = "driver",
	 .init = driver_init,
	 .read = driver_read,
	 },
	{
	 .name = "hotkey",
	 .hid = IBM_HKEY_HID,
	 .init = hotkey_init,
	 .read = hotkey_read,
	 .write = hotkey_write,
	 .exit = hotkey_exit,
	 .notify = hotkey_notify,
	 .handle = &hkey_handle,
	 .type = ACPI_DEVICE_NOTIFY,
	 },
	{
	 .name = "bluetooth",
	 .init = bluetooth_init,
	 .read = bluetooth_read,
	 .write = bluetooth_write,
	 },
	{
	 .name = "video",
	 .init = video_init,
	 .read = video_read,
	 .write = video_write,
	 .exit = video_exit,
	 },
	{
	 .name = "light",
	 .init = light_init,
	 .read = light_read,
	 .write = light_write,
	 },
#ifdef DOCK_ENABLE
	{
	 .name = "dock",
	 .read = dock_read,
	 .write = dock_write,
	 .notify = dock_notify,
	 .handle = &dock_handle,
	 .type = ACPI_SYSTEM_NOTIFY,
	 },
	{
	 .name = "dock",
	 .hid = IBM_PCI_HID,
	 .notify = dock_notify,
	 .handle = &pci_handle,
	 .type = ACPI_SYSTEM_NOTIFY,
	 },
#endif
	{
	 .name = "bay",
	 .init = bay_init,
	 .read = bay_read,
	 .write = bay_write,
	 .notify = bay_notify,
	 .handle = &bay_handle,
	 .type = ACPI_SYSTEM_NOTIFY,
	 },
	{
	 .name = "cmos",
	 .read = cmos_read,
	 .write = cmos_write,
	 },
	{
	 .name = "led",
	 .init = led_init,
	 .read = led_read,
	 .write = led_write,
	 },
	{
	 .name = "beep",
	 .read = beep_read,
	 .write = beep_write,
	 },
	{
	 .name = "thermal",
	 .init = thermal_init,
	 .read = thermal_read,
	 },
	{
	 .name = "ecdump",
	 .read = ecdump_read,
	 .write = ecdump_write,
	 .experimental = 1,
	 },
	{
	 .name = "brightness",
	 .init = brightness_init,
	 .exit = brightness_exit,
	 .read = brightness_read,
	 .write = brightness_write,
	 .experimental = 1,
	 },
	{
	 .name = "volume",
	 .read = volume_read,
	 .write = volume_write,
	 .experimental = 1,
	 },
	{
	 .name = "fan",
	 .read = fan_read,
	 .write = fan_write,
	 .experimental = 1,
	 },
};

static int dispatch_read(char *page, char **start, off_t off, int count,
			 int *eof, void *data)
{
	struct ibm_struct *ibm = (struct ibm_struct *)data;
	int len;

	if (!ibm || !ibm->read)
		return -EINVAL;

	len = ibm->read(page);
	if (len < 0)
		return len;

	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 * userbuf,
			  unsigned long count, void *data)
{
	struct ibm_struct *ibm = (struct ibm_struct *)data;
	char *kernbuf;
	int ret;

	if (!ibm || !ibm->write)
		return -EINVAL;

	kernbuf = kmalloc(count + 2, GFP_KERNEL);
	if (!kernbuf)
		return -ENOMEM;

	if (copy_from_user(kernbuf, userbuf, count)) {
		kfree(kernbuf);
		return -EFAULT;
	}

	kernbuf[count] = 0;
	strcat(kernbuf, ",");
	ret = ibm->write(kernbuf);
	if (ret == 0)
		ret = count;

	kfree(kernbuf);

	return ret;
}

static void dispatch_notify(acpi_handle handle, u32 event, void *data)
{
	struct ibm_struct *ibm = (struct ibm_struct *)data;

	if (!ibm || !ibm->notify)
		return;

	ibm->notify(ibm, event);
}

static int __init setup_notify(struct ibm_struct *ibm)
{
	acpi_status status;
	int ret;

	if (!*ibm->handle)
		return 1;

	ret = acpi_bus_get_device(*ibm->handle, &ibm->device);
	if (ret < 0) {
		printk(IBM_ERR "%s device not present\n", ibm->name);
		return 1;
	}

	acpi_driver_data(ibm->device) = ibm;
	sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name);

	status = acpi_install_notify_handler(*ibm->handle, ibm->type,
					     dispatch_notify, ibm);
	if (ACPI_FAILURE(status)) {
		printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n",
		       ibm->name, status);
		return -ENODEV;
	}

	return 0;
}

static int __init ibm_device_add(struct acpi_device *device)
{
	return 0;
}

static int __init register_driver(struct ibm_struct *ibm)
{
	int ret;

	ibm->driver = kmalloc(sizeof(struct acpi_driver), GFP_KERNEL);
	if (!ibm->driver) {
		printk(IBM_ERR "kmalloc(ibm->driver) failed\n");
		return -1;
	}

	memset(ibm->driver, 0, sizeof(struct acpi_driver));
	sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name);
	ibm->driver->ids = ibm->hid;
	ibm->driver->ops.add = &ibm_device_add;

	ret = acpi_bus_register_driver_hotplug(ibm->driver);
	if (ret < 0) {
		printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n",
		       ibm->hid, ret);
		kfree(ibm->driver);
	}

	return ret;
}

static int __init ibm_init(struct ibm_struct *ibm)
{
	int ret;
	struct proc_dir_entry *entry;

	if (ibm->experimental && !experimental)
		return 0;

	if (ibm->hid) {
		ret = register_driver(ibm);
		if (ret < 0)
			return ret;
		ibm->driver_registered = 1;
	}

	if (ibm->init) {
		ret = ibm->init();
		if (ret != 0)
			return ret;
		ibm->init_called = 1;
	}

	if (ibm->read) {
		entry = create_proc_entry(ibm->name,
					  S_IFREG | S_IRUGO | S_IWUSR,
					  proc_dir);
		if (!entry) {
			printk(IBM_ERR "unable to create proc entry %s\n",
			       ibm->name);
			return -ENODEV;
		}
		entry->owner = THIS_MODULE;
		entry->data = ibm;
		entry->read_proc = &dispatch_read;
		if (ibm->write)
			entry->write_proc = &dispatch_write;
		ibm->proc_created = 1;
	}

	if (ibm->notify) {
		ret = setup_notify(ibm);
		if (ret < 0)
			return ret;
		if (ret > 0)
			return 0;
		ibm->notify_installed = 1;
	}

	return 0;
}

static void ibm_exit(struct ibm_struct *ibm)
{
	if (ibm->notify_installed)
		acpi_remove_notify_handler(*ibm->handle, ibm->type,
					   dispatch_notify);

	if (ibm->proc_created)
		remove_proc_entry(ibm->name, proc_dir);

	if (ibm->init_called && ibm->exit)
		ibm->exit();

	if (ibm->driver_registered) {
		acpi_bus_unregister_driver(ibm->driver);
		kfree(ibm->driver);
	}
}

extern struct acpi_device *acpi_root;

static void __init ibm_handle_init(char *name,
				   acpi_handle * handle, acpi_handle parent,
				   char **paths, int num_paths, char **path)
{
	int i;
	acpi_status status;
	int vid_handle = 0;
	struct device *dev;


	if (!strcmp("vid", name))
		vid_handle = 1;

	for (i = 0; i < num_paths; i++) {
		status = acpi_get_handle(parent, paths[i], handle);
		if (ACPI_SUCCESS(status)) {
			if (vid_handle){
				dev = acpi_get_physical_device(*handle);
				if (!dev) {
					printk (IBM_DEBUG "Video device %s"
						" not physically connected, "
						"ignoring\n", paths[i]);
					continue;
				} else
					printk(IBM_INFO "Found video device %s\n",
					       paths[i]);
			}
			*path = paths[i];
			return;
		}
	}

	*handle = NULL;
}

#define IBM_HANDLE_INIT(object)						\
	ibm_handle_init(#object, &object##_handle, *object##_parent,	\
		object##_paths, ARRAY_SIZE(object##_paths), &object##_path)

static int set_ibm_param(const char *val, struct kernel_param *kp)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(ibms); i++)
		if (strcmp(ibms[i].name, kp->name) == 0 && ibms[i].write) {
			if (strlen(val) > sizeof(ibms[i].param) - 2)
				return -ENOSPC;
			strcpy(ibms[i].param, val);
			strcat(ibms[i].param, ",");
			return 0;
		}

	return -EINVAL;
}

#define IBM_PARAM(feature) \
	module_param_call(feature, set_ibm_param, NULL, NULL, 0)

IBM_PARAM(hotkey);
IBM_PARAM(bluetooth);
IBM_PARAM(video);
IBM_PARAM(light);
#ifdef DOCK_ENABLE
IBM_PARAM(dock);
#endif
IBM_PARAM(bay);
IBM_PARAM(cmos);
IBM_PARAM(led);
IBM_PARAM(beep);
IBM_PARAM(ecdump);
IBM_PARAM(brightness);
IBM_PARAM(volume);
IBM_PARAM(fan);

static void acpi_ibm_exit(void)
{
	int i;

	for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--)
		ibm_exit(&ibms[i]);

	remove_proc_entry(IBM_DIR, acpi_root_dir);
}

static int __init acpi_ibm_init(void)
{
	int ret, i;

	if (acpi_disabled)
		return -ENODEV;

	if (dmi_name_in_vendors("LENOVO")){
		printk("Lenovo ThinkPad detected\n");
		vendor = LENOVO_VENDOR;
	}
	else
		vendor = IBM_VENDOR;

	if (!acpi_specific_hotkey_enabled) {
		printk(IBM_ERR "using generic hotkey driver\n");
		return -ENODEV;
	}

	/* ec is required because many other handles are relative to it */
	IBM_HANDLE_INIT(ec);
	if (!ec_handle) {
		printk(IBM_ERR "ec object not found\n");
		return -ENODEV;
	}

	/* these handles are not required */
	IBM_HANDLE_INIT(vid);
	IBM_HANDLE_INIT(vid2);
	IBM_HANDLE_INIT(ledb);
	IBM_HANDLE_INIT(led);
	IBM_HANDLE_INIT(hkey);
	IBM_HANDLE_INIT(lght);
	IBM_HANDLE_INIT(cmos);
#ifdef DOCK_ENABLE
	IBM_HANDLE_INIT(dock);
#endif
	IBM_HANDLE_INIT(pci);
	IBM_HANDLE_INIT(bay);
	if (bay_handle)
		IBM_HANDLE_INIT(bay_ej);
	IBM_HANDLE_INIT(bay2);
	if (bay2_handle)
		IBM_HANDLE_INIT(bay2_ej);
	IBM_HANDLE_INIT(beep);
	IBM_HANDLE_INIT(ecrd);
	IBM_HANDLE_INIT(ecwr);
	IBM_HANDLE_INIT(fans);
	IBM_HANDLE_INIT(gfan);
	IBM_HANDLE_INIT(sfan);

	proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir);
	if (!proc_dir) {
		printk(IBM_ERR "unable to create proc dir %s", IBM_DIR);
		return -ENODEV;
	}
	proc_dir->owner = THIS_MODULE;

	for (i = 0; i < ARRAY_SIZE(ibms); i++) {
		ret = ibm_init(&ibms[i]);
		if (ret >= 0 && *ibms[i].param)
			ret = ibms[i].write(ibms[i].param);
		if (ret < 0) {
			acpi_ibm_exit();
			return ret;
		}
	}

	return 0;
}

module_init(acpi_ibm_init);
module_exit(acpi_ibm_exit);

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
ibm-acpi-devel mailing list
ibm-acpi-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.sourceforge.net/lists/listinfo/ibm-acpi-devel

[Index of Archives]     [Linux ACPI]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Photo]     [Yosemite Photos]     [Yosemite Advice]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Device Mapper]

  Powered by Linux