OpenCloudOS-Kernel/drivers/misc/thinkpad_acpi.c

3077 lines
72 KiB
C
Raw Normal View History

/*
* thinkpad_acpi.c - ThinkPad ACPI Extras
*
*
* Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
* Copyright (C) 2006-2007 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#define IBM_VERSION "0.14"
/*
* Changelog:
* 2007-03-27 0.14 renamed to thinkpad_acpi and moved to
* drivers/misc.
*
* 2006-11-22 0.13 new maintainer
* changelog now lives in git commit history, and will
* not be updated further in-file.
*
* 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
* 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 "thinkpad_acpi.h"
MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
MODULE_DESCRIPTION(IBM_DESC);
MODULE_VERSION(IBM_VERSION);
MODULE_LICENSE("GPL");
/* Please remove this in year 2009 */
MODULE_ALIAS("ibm_acpi");
#define __unused __attribute__ ((unused))
/****************************************************************************
****************************************************************************
*
* ACPI Helpers and device model
*
****************************************************************************
****************************************************************************/
/*************************************************************************
* ACPI basic handles
*/
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 }
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(ecrd, ec, "ECRD"); /* 570 */
IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */
/*************************************************************************
* Misc ACPI handles
*/
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 */
IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */
"^HKEY", /* R30, R31 */
"HKEY", /* all others */
); /* 570 */
/*************************************************************************
* ACPI helpers
*/
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 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 _sta(acpi_handle handle)
{
int status;
if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
status = 0;
return status;
}
/*************************************************************************
* ACPI device model
*/
static void ibm_handle_init(char *name,
acpi_handle *handle, acpi_handle parent,
char **paths, int num_paths, char **path)
{
int i;
acpi_status status;
for (i = 0; i < num_paths; i++) {
status = acpi_get_handle(parent, paths[i], handle);
if (ACPI_SUCCESS(status)) {
*path = paths[i];
return;
}
}
*handle = NULL;
}
static void dispatch_notify(acpi_handle handle, u32 event, void *data)
{
struct ibm_struct *ibm = 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 0;
dbg_printk(TPACPI_DBG_INIT,
"setting up ACPI notify for %s\n", ibm->name);
ret = acpi_bus_get_device(*ibm->handle, &ibm->device);
if (ret < 0) {
printk(IBM_ERR "%s device not present\n", ibm->name);
return -ENODEV;
}
acpi_driver_data(ibm->device) = ibm;
sprintf(acpi_device_class(ibm->device), "%s/%s",
IBM_ACPI_EVENT_PREFIX,
ibm->name);
status = acpi_install_notify_handler(*ibm->handle, ibm->type,
dispatch_notify, ibm);
if (ACPI_FAILURE(status)) {
if (status == AE_ALREADY_EXISTS) {
printk(IBM_NOTICE "another device driver is already handling %s events\n",
ibm->name);
} else {
printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n",
ibm->name, status);
}
return -ENODEV;
}
ibm->notify_installed = 1;
return 0;
}
static int __init ibm_device_add(struct acpi_device *device)
{
return 0;
}
static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
{
int ret;
dbg_printk(TPACPI_DBG_INIT,
"registering %s as an ACPI driver\n", ibm->name);
ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
if (!ibm->driver) {
printk(IBM_ERR "kzalloc(ibm->driver) failed\n");
return -ENOMEM;
}
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(ibm->driver);
if (ret < 0) {
printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n",
ibm->hid, ret);
kfree(ibm->driver);
ibm->driver = NULL;
} else if (!ret)
ibm->driver_registered = 1;
return ret;
}
/****************************************************************************
****************************************************************************
*
* Procfs Helpers
*
****************************************************************************
****************************************************************************/
static int dispatch_read(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
struct ibm_struct *ibm = 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 = 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 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;
}
/****************************************************************************
****************************************************************************
*
* Subdrivers
*
****************************************************************************
****************************************************************************/
/*************************************************************************
* thinkpad-acpi init subdriver
*/
static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
{
printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
printk(IBM_INFO "%s\n", IBM_URL);
if (ibm_thinkpad_ec_found)
printk(IBM_INFO "ThinkPad EC firmware %s\n",
ibm_thinkpad_ec_found);
return 0;
}
static int thinkpad_acpi_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 struct ibm_struct thinkpad_acpi_driver_data = {
.name = "driver",
.read = thinkpad_acpi_driver_read,
};
/*************************************************************************
* Hotkey subdriver
*/
static int hotkey_supported;
static int hotkey_mask_supported;
static int hotkey_orig_status;
static int hotkey_orig_mask;
static int __init hotkey_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n");
IBM_HANDLE_INIT(hkey);
/* hotkey not supported on 570 */
hotkey_supported = hkey_handle != NULL;
vdbg_printk(TPACPI_DBG_INIT, "hotkeys are %s\n",
str_supported(hotkey_supported));
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");
vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n",
str_supported(hotkey_mask_supported));
if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask))
return -ENODEV;
}
return (hotkey_supported)? 0 : 1;
}
static void hotkey_exit(void)
{
if (hotkey_supported) {
dbg_printk(TPACPI_DBG_EXIT, "restoring original hotkey mask\n");
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 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_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 struct ibm_struct hotkey_driver_data = {
.name = "hotkey",
.hid = IBM_HKEY_HID,
.read = hotkey_read,
.write = hotkey_write,
.exit = hotkey_exit,
.notify = hotkey_notify,
.handle = &hkey_handle,
.type = ACPI_DEVICE_NOTIFY,
};
/*************************************************************************
* Bluetooth subdriver
*/
static int bluetooth_supported;
static int __init bluetooth_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n");
IBM_HANDLE_INIT(hkey);
/* 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");
vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s\n",
str_supported(bluetooth_supported));
return (bluetooth_supported)? 0 : 1;
}
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;
}
static struct ibm_struct bluetooth_driver_data = {
.name = "bluetooth",
.read = bluetooth_read,
.write = bluetooth_write,
};
/*************************************************************************
* Wan subdriver
*/
static int wan_supported;
static int __init wan_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n");
IBM_HANDLE_INIT(hkey);
wan_supported = hkey_handle &&
acpi_evalf(hkey_handle, NULL, "GWAN", "qv");
vdbg_printk(TPACPI_DBG_INIT, "wan is %s\n",
str_supported(wan_supported));
return (wan_supported)? 0 : 1;
}
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 struct ibm_struct wan_driver_data = {
.name = "wan",
.read = wan_read,
.write = wan_write,
.experimental = 1,
};
/*************************************************************************
* Video subdriver
*/
static enum video_access_mode video_supported;
static int video_orig_autosw;
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 */
static int __init video_init(struct ibm_init_struct *iibm)
{
int ivga;
vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
IBM_HANDLE_INIT(vid);
IBM_HANDLE_INIT(vid2);
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 = TPACPI_VIDEO_NONE;
else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
/* 570 */
video_supported = TPACPI_VIDEO_570;
else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
/* 600e/x, 770e, 770x */
video_supported = TPACPI_VIDEO_770;
else
/* all others */
video_supported = TPACPI_VIDEO_NEW;
vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
str_supported(video_supported != TPACPI_VIDEO_NONE),
video_supported);
return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
}
static void video_exit(void)
{
dbg_printk(TPACPI_DBG_EXIT, "restoring original video autoswitch mode\n");
acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw);
}
static int video_status(void)
{
int status = 0;
int i;
if (video_supported == TPACPI_VIDEO_570) {
if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87))
status = i & 3;
} else if (video_supported == TPACPI_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 == TPACPI_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 == TPACPI_VIDEO_570)
acpi_evalf(vid_handle, &autosw, "SWIT", "d");
else if (video_supported == TPACPI_VIDEO_770 ||
video_supported == TPACPI_VIDEO_NEW)
acpi_evalf(vid_handle, &autosw, "^VDEE", "d");
return autosw & 1;
}
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 == TPACPI_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 == TPACPI_VIDEO_570)
return acpi_evalf(ec_handle, NULL, "_Q17", "v");
else if (video_supported == TPACPI_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 == TPACPI_VIDEO_570) {
ret = acpi_evalf(NULL, NULL,
"\\_SB.PHS2", "vdd", 0x8b, status | 0x80);
} else if (video_supported == TPACPI_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_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 == TPACPI_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 == TPACPI_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_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 == TPACPI_VIDEO_NEW &&
strlencmp(cmd, "dvi_enable") == 0) {
enable |= 0x08;
} else if (video_supported == TPACPI_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 struct ibm_struct video_driver_data = {
.name = "video",
.read = video_read,
.write = video_write,
.exit = video_exit,
};
/*************************************************************************
* Light (thinklight) subdriver
*/
static int light_supported;
static int light_status_supported;
IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */
IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */
static int __init light_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
IBM_HANDLE_INIT(ledb);
IBM_HANDLE_INIT(lght);
IBM_HANDLE_INIT(cmos);
/* 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");
vdbg_printk(TPACPI_DBG_INIT, "light is %s\n",
str_supported(light_supported));
return (light_supported)? 0 : 1;
}
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 struct ibm_struct light_driver_data = {
.name = "light",
.read = light_read,
.write = light_write,
};
/*************************************************************************
* Dock subdriver
*/
#ifdef CONFIG_THINKPAD_ACPI_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 */
/* don't list other alternatives as we install a notify handler on the 570 */
IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */
#define dock_docked() (_sta(dock_handle) & 1)
static int __init dock_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n");
IBM_HANDLE_INIT(dock);
IBM_HANDLE_INIT(pci);
vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n",
str_supported(dock_handle != NULL));
return (dock_handle)? 0 : 1;
}
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 */
}
}
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 struct ibm_struct dock_driver_data[2] = {
{
.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 /* CONFIG_THINKPAD_ACPI_DOCK */
/*************************************************************************
* Bay subdriver
*/
#ifdef CONFIG_THINKPAD_ACPI_BAY
static int bay_status_supported;
static int bay_status2_supported;
static int bay_eject_supported;
static int bay_eject2_supported;
IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */
"\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */
"\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */
"\\_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 */
static int __init bay_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n");
IBM_HANDLE_INIT(bay);
if (bay_handle)
IBM_HANDLE_INIT(bay_ej);
IBM_HANDLE_INIT(bay2);
if (bay2_handle)
IBM_HANDLE_INIT(bay2_ej);
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);
vdbg_printk(TPACPI_DBG_INIT,
"bay 1: status %s, eject %s; bay 2: status %s, eject %s\n",
str_supported(bay_status_supported),
str_supported(bay_eject_supported),
str_supported(bay_status2_supported),
str_supported(bay_eject2_supported));
return (bay_status_supported || bay_eject_supported ||
bay_status2_supported || bay_eject2_supported)? 0 : 1;
}
static void bay_notify(struct ibm_struct *ibm, u32 event)
{
acpi_bus_generate_event(ibm->device, event, 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 struct ibm_struct bay_driver_data = {
.name = "bay",
.read = bay_read,
.write = bay_write,
.notify = bay_notify,
.handle = &bay_handle,
.type = ACPI_SYSTEM_NOTIFY,
};
#endif /* CONFIG_THINKPAD_ACPI_BAY */
/*************************************************************************
* CMOS subdriver
*/
static int __init cmos_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT,
"initializing cmos commands subdriver\n");
IBM_HANDLE_INIT(cmos);
vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
str_supported(cmos_handle != NULL));
return (cmos_handle)? 0 : 1;
}
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_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_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 struct ibm_struct cmos_driver_data = {
.name = "cmos",
.read = cmos_read,
.write = cmos_write,
};
/*************************************************************************
* LED subdriver
*/
static enum led_access_mode led_supported;
IBM_HANDLE(led, ec, "SLED", /* 570 */
"SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
"LED", /* all others */
); /* R30, R31 */
static int __init led_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
IBM_HANDLE_INIT(led);
if (!led_handle)
/* led not supported on R30, R31 */
led_supported = TPACPI_LED_NONE;
else if (strlencmp(led_path, "SLED") == 0)
/* 570 */
led_supported = TPACPI_LED_570;
else if (strlencmp(led_path, "SYSL") == 0)
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
led_supported = TPACPI_LED_OLD;
else
/* all others */
led_supported = TPACPI_LED_NEW;
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
str_supported(led_supported), led_supported);
return (led_supported != TPACPI_LED_NONE)? 0 : 1;
}
#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 == TPACPI_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 };
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 == TPACPI_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 == TPACPI_LED_OLD) {
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
led = 1 << led;
ret = ec_write(TPACPI_LED_EC_HLMS, led);
if (ret >= 0)
ret =
ec_write(TPACPI_LED_EC_HLBL,
led * led_exp_hlbl[ind]);
if (ret >= 0)
ret =
ec_write(TPACPI_LED_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 struct ibm_struct led_driver_data = {
.name = "led",
.read = led_read,
.write = led_write,
};
/*************************************************************************
* Beep subdriver
*/
IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */
static int __init beep_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
IBM_HANDLE_INIT(beep);
vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
str_supported(beep_handle != NULL));
return (beep_handle)? 0 : 1;
}
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 struct ibm_struct beep_driver_data = {
.name = "beep",
.read = beep_read,
.write = beep_write,
};
/*************************************************************************
* Thermal subdriver
*/
static enum thermal_access_mode thermal_read_mode;
static int __init thermal_init(struct ibm_init_struct *iibm)
{
2006-11-24 21:47:08 +08:00
u8 t, ta1, ta2;
int i;
int acpi_tmp7;
vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
2006-11-24 21:47:08 +08:00
if (ibm_thinkpad_ec_found && experimental) {
/*
* Direct EC access mode: sensors at registers
* 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for
* non-implemented, thermal sensors return 0x80 when
* not available
*/
2006-11-24 21:47:08 +08:00
ta1 = ta2 = 0;
for (i = 0; i < 8; i++) {
if (likely(acpi_ec_read(0x78 + i, &t))) {
ta1 |= t;
} else {
ta1 = 0;
break;
}
if (likely(acpi_ec_read(0xC0 + i, &t))) {
ta2 |= t;
} else {
ta1 = 0;
break;
}
}
if (ta1 == 0) {
/* This is sheer paranoia, but we handle it anyway */
if (acpi_tmp7) {
printk(IBM_ERR
"ThinkPad ACPI EC access misbehaving, "
"falling back to ACPI TMPx access mode\n");
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
2006-11-24 21:47:08 +08:00
} else {
printk(IBM_ERR
"ThinkPad ACPI EC access misbehaving, "
"disabling thermal sensors access\n");
thermal_read_mode = TPACPI_THERMAL_NONE;
2006-11-24 21:47:08 +08:00
}
} else {
thermal_read_mode =
(ta2 != 0) ?
TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
2006-11-24 21:47:08 +08:00
}
} else if (acpi_tmp7) {
if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
/* 600e/x, 770e, 770x */
thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
} else {
/* Standard ACPI TMPx access, max 8 sensors */
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
}
} else {
/* temperatures not supported on 570, G4x, R30, R31, R32 */
thermal_read_mode = TPACPI_THERMAL_NONE;
}
vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
thermal_read_mode);
return (thermal_read_mode != TPACPI_THERMAL_NONE)? 0 : 1;
}
static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
{
int i, t;
2006-11-24 21:47:08 +08:00
s8 tmp;
char tmpi[] = "TMPi";
if (!s)
return -EINVAL;
switch (thermal_read_mode) {
#if TPACPI_MAX_THERMAL_SENSORS >= 16
case TPACPI_THERMAL_TPEC_16:
2006-11-24 21:47:08 +08:00
for (i = 0; i < 8; i++) {
if (!acpi_ec_read(0xC0 + i, &tmp))
return -EIO;
s->temp[i + 8] = tmp * 1000;
}
/* fallthrough */
#endif
case TPACPI_THERMAL_TPEC_8:
2006-11-24 21:47:08 +08:00
for (i = 0; i < 8; i++) {
if (!acpi_ec_read(0x78 + i, &tmp))
return -EIO;
2006-11-24 21:47:08 +08:00
s->temp[i] = tmp * 1000;
}
return (thermal_read_mode == TPACPI_THERMAL_TPEC_16) ? 16 : 8;
case TPACPI_THERMAL_ACPI_UPDT:
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;
s->temp[i] = (t - 2732) * 100;
}
return 8;
case TPACPI_THERMAL_ACPI_TMP07:
for (i = 0; i < 8; i++) {
tmpi[3] = '0' + i;
if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
return -EIO;
s->temp[i] = t * 1000;
}
return 8;
case TPACPI_THERMAL_NONE:
default:
return 0;
}
}
static int thermal_read(char *p)
{
int len = 0;
int n, i;
struct ibm_thermal_sensors_struct t;
n = thermal_get_sensors(&t);
if (unlikely(n < 0))
return n;
len += sprintf(p + len, "temperatures:\t");
if (n > 0) {
for (i = 0; i < (n - 1); i++)
len += sprintf(p + len, "%d ", t.temp[i] / 1000);
len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
} else
len += sprintf(p + len, "not supported\n");
return len;
}
static struct ibm_struct thermal_driver_data = {
.name = "thermal",
.read = thermal_read,
};
/*************************************************************************
* EC Dump subdriver
*/
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;
}
static struct ibm_struct ecdump_driver_data = {
.name = "ecdump",
.read = ecdump_read,
.write = ecdump_write,
.experimental = 1,
};
/*************************************************************************
* Backlight/brightness subdriver
*/
static struct backlight_device *ibm_backlight_device = NULL;
static struct backlight_ops ibm_backlight_data = {
.get_brightness = brightness_get,
.update_status = brightness_update_status,
};
static int __init brightness_init(struct ibm_init_struct *iibm)
{
int b;
vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
b = brightness_get(NULL);
if (b < 0)
return b;
ibm_backlight_device = backlight_device_register("ibm", NULL, NULL,
&ibm_backlight_data);
if (IS_ERR(ibm_backlight_device)) {
printk(IBM_ERR "Could not register backlight device\n");
return PTR_ERR(ibm_backlight_device);
}
vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n");
ibm_backlight_device->props.max_brightness = 7;
ibm_backlight_device->props.brightness = b;
backlight_update_status(ibm_backlight_device);
return 0;
}
static void brightness_exit(void)
{
if (ibm_backlight_device) {
vdbg_printk(TPACPI_DBG_EXIT,
"calling backlight_device_unregister()\n");
backlight_device_unregister(ibm_backlight_device);
ibm_backlight_device = NULL;
}
}
static int brightness_update_status(struct backlight_device *bd)
{
return brightness_set(
(bd->props.fb_blank == FB_BLANK_UNBLANK &&
bd->props.power == FB_BLANK_UNBLANK) ?
bd->props.brightness : 0);
}
static int brightness_get(struct backlight_device *bd)
{
u8 level;
if (!acpi_ec_read(brightness_offset, &level))
return -EIO;
level &= 0x7;
return level;
}
static int brightness_set(int value)
{
int cmos_cmd, inc, i;
int current_value = brightness_get(NULL);
value &= 7;
cmos_cmd = value > current_value ? TP_CMOS_BRIGHTNESS_UP : TP_CMOS_BRIGHTNESS_DOWN;
inc = value > current_value ? 1 : -1;
for (i = current_value; i != value; i += inc) {
if (!cmos_eval(cmos_cmd))
return -EIO;
if (!acpi_ec_write(brightness_offset, i + inc))
return -EIO;
}
return 0;
}
static int brightness_read(char *p)
{
int len = 0;
int level;
if ((level = brightness_get(NULL)) < 0) {
len += sprintf(p + len, "level:\t\tunreadable\n");
} else {
len += sprintf(p + len, "level:\t\t%d\n", level & 0x7);
len += sprintf(p + len, "commands:\tup, down\n");
len += sprintf(p + len, "commands:\tlevel <level>"
" (<level> is 0-7)\n");
}
return len;
}
static int brightness_write(char *buf)
{
int level;
int new_level;
char *cmd;
while ((cmd = next_cmd(&buf))) {
if ((level = brightness_get(NULL)) < 0)
return level;
level &= 7;
if (strlencmp(cmd, "up") == 0) {
new_level = level == 7 ? 7 : 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 <= 7) {
/* new_level set */
} else
return -EINVAL;
brightness_set(new_level);
}
return 0;
}
static struct ibm_struct brightness_driver_data = {
.name = "brightness",
.read = brightness_read,
.write = brightness_write,
.exit = brightness_exit,
};
/*************************************************************************
* Volume subdriver
*/
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;
}
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 ? TP_CMOS_VOLUME_UP : TP_CMOS_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(TP_CMOS_VOLUME_MUTE) ||
!acpi_ec_write(volume_offset,
new_level + mute)))
return -EIO;
}
if (new_mute != mute) { /* level doesn't change */
cmos_cmd = new_mute ? TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
if (!cmos_eval(cmos_cmd) ||
!acpi_ec_write(volume_offset, level + new_mute))
return -EIO;
}
}
return 0;
}
static struct ibm_struct volume_driver_data = {
.name = "volume",
.read = volume_read,
.write = volume_write,
};
/*************************************************************************
* Fan subdriver
*/
/*
* FAN ACCESS MODES
*
* TPACPI_FAN_RD_ACPI_GFAN:
* ACPI GFAN method: returns fan level
*
* see TPACPI_FAN_WR_ACPI_SFAN
* EC 0x2f (HFSP) not available if GFAN exists
*
* TPACPI_FAN_WR_ACPI_SFAN:
* ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
*
* EC 0x2f (HFSP) might be available *for reading*, but do not use
* it for writing.
*
* TPACPI_FAN_WR_TPEC:
* ThinkPad EC register 0x2f (HFSP): fan control loop mode
* Supported on almost all ThinkPads
*
* Fan speed changes of any sort (including those caused by the
* disengaged mode) are usually done slowly by the firmware as the
* maximum ammount of fan duty cycle change per second seems to be
* limited.
*
* Reading is not available if GFAN exists.
* Writing is not available if SFAN exists.
*
* Bits
* 7 automatic mode engaged;
* (default operation mode of the ThinkPad)
* fan level is ignored in this mode.
* 6 full speed mode (takes precedence over bit 7);
* not available on all thinkpads. May disable
* the tachometer while the fan controller ramps up
* the speed (which can take up to a few *minutes*).
* Speeds up fan to 100% duty-cycle, which is far above
* the standard RPM levels. It is not impossible that
* it could cause hardware damage.
* 5-3 unused in some models. Extra bits for fan level
* in others, but still useless as all values above
* 7 map to the same speed as level 7 in these models.
* 2-0 fan level (0..7 usually)
* 0x00 = stop
* 0x07 = max (set when temperatures critical)
* Some ThinkPads may have other levels, see
* TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
*
* FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
* boot. Apparently the EC does not intialize it, so unless ACPI DSDT
* does so, its initial value is meaningless (0x07).
*
* For firmware bugs, refer to:
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
*
* ----
*
* ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
* Main fan tachometer reading (in RPM)
*
* This register is present on all ThinkPads with a new-style EC, and
* it is known not to be present on the A21m/e, and T22, as there is
* something else in offset 0x84 according to the ACPI DSDT. Other
* ThinkPads from this same time period (and earlier) probably lack the
* tachometer as well.
*
* Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare
* was never fixed by IBM to report the EC firmware version string
* probably support the tachometer (like the early X models), so
* detecting it is quite hard. We need more data to know for sure.
*
* FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
* might result.
*
* FIRMWARE BUG: may go stale while the EC is switching to full speed
* mode.
*
* For firmware bugs, refer to:
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
*
* TPACPI_FAN_WR_ACPI_FANS:
* ThinkPad X31, X40, X41. Not available in the X60.
*
* FANS ACPI handle: takes three arguments: low speed, medium speed,
* high speed. ACPI DSDT seems to map these three speeds to levels
* as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
* (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
*
* The speeds are stored on handles
* (FANA:FAN9), (FANC:FANB), (FANE:FAND).
*
* There are three default speed sets, acessible as handles:
* FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
*
* ACPI DSDT switches which set is in use depending on various
* factors.
*
* TPACPI_FAN_WR_TPEC is also available and should be used to
* command the fan. The X31/X40/X41 seems to have 8 fan levels,
* but the ACPI tables just mention level 7.
*/
static enum fan_status_access_mode fan_status_access_mode;
static enum fan_control_access_mode fan_control_access_mode;
static enum fan_control_commands fan_control_commands;
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
static int fan_control_status_known;
static u8 fan_control_initial_status;
static void fan_watchdog_fire(struct work_struct *ignored);
static int fan_watchdog_maxinterval;
static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */
IBM_HANDLE(gfan, ec, "GFAN", /* 570 */
"\\FSPD", /* 600e/x, 770e, 770x */
); /* all others */
IBM_HANDLE(sfan, ec, "SFAN", /* 570 */
"JFNS", /* 770x-JL */
); /* all others */
static int __init fan_init(struct ibm_init_struct *iibm)
{
vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
fan_status_access_mode = TPACPI_FAN_NONE;
fan_control_access_mode = TPACPI_FAN_WR_NONE;
fan_control_commands = 0;
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
fan_control_status_known = 1;
fan_watchdog_maxinterval = 0;
IBM_HANDLE_INIT(fans);
IBM_HANDLE_INIT(gfan);
IBM_HANDLE_INIT(sfan);
if (gfan_handle) {
/* 570, 600e/x, 770e, 770x */
fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
} else {
/* all other ThinkPads: note that even old-style
* ThinkPad ECs supports the fan control register */
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
if (likely(acpi_ec_read(fan_status_offset,
&fan_control_initial_status))) {
fan_status_access_mode = TPACPI_FAN_RD_TPEC;
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
/* In some ThinkPads, neither the EC nor the ACPI
* DSDT initialize the fan status, and it ends up
* being set to 0x07 when it *could* be either
* 0x07 or 0x80.
*
* Enable for TP-1Y (T43), TP-78 (R51e),
* TP-76 (R52), TP-70 (T43, R52), which are known
* to be buggy. */
if (fan_control_initial_status == 0x07 &&
ibm_thinkpad_ec_found &&
((ibm_thinkpad_ec_found[0] == '1' &&
ibm_thinkpad_ec_found[1] == 'Y') ||
(ibm_thinkpad_ec_found[0] == '7' &&
(ibm_thinkpad_ec_found[1] == '6' ||
ibm_thinkpad_ec_found[1] == '8' ||
ibm_thinkpad_ec_found[1] == '0'))
)) {
printk(IBM_NOTICE
"fan_init: initial fan status is "
"unknown, assuming it is in auto "
"mode\n");
fan_control_status_known = 0;
}
} else {
printk(IBM_ERR
"ThinkPad ACPI EC access misbehaving, "
"fan status and control unavailable\n");
return 1;
}
}
if (sfan_handle) {
/* 570, 770x-JL */
fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
fan_control_commands |=
TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
} else {
if (!gfan_handle) {
/* gfan without sfan means no fan control */
/* all other models implement TP EC 0x2f control */
if (fans_handle) {
/* X31, X40, X41 */
fan_control_access_mode =
TPACPI_FAN_WR_ACPI_FANS;
fan_control_commands |=
TPACPI_FAN_CMD_SPEED |
TPACPI_FAN_CMD_LEVEL |
TPACPI_FAN_CMD_ENABLE;
} else {
fan_control_access_mode = TPACPI_FAN_WR_TPEC;
fan_control_commands |=
TPACPI_FAN_CMD_LEVEL |
TPACPI_FAN_CMD_ENABLE;
}
}
}
vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
fan_control_access_mode != TPACPI_FAN_WR_NONE),
fan_status_access_mode, fan_control_access_mode);
return (fan_status_access_mode != TPACPI_FAN_NONE ||
fan_control_access_mode != TPACPI_FAN_WR_NONE)?
0 : 1;
}
static int fan_get_status(u8 *status)
{
u8 s;
/* TODO:
* Add TPACPI_FAN_RD_ACPI_FANS ? */
switch (fan_status_access_mode) {
case TPACPI_FAN_RD_ACPI_GFAN:
/* 570, 600e/x, 770e, 770x */
if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
return -EIO;
if (likely(status))
*status = s & 0x07;
break;
case TPACPI_FAN_RD_TPEC:
/* all except 570, 600e/x, 770e, 770x */
if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
return -EIO;
if (likely(status))
*status = s;
break;
default:
return -ENXIO;
}
return 0;
}
static void fan_exit(void)
{
vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending watchdogs\n");
cancel_delayed_work(&fan_watchdog_task);
flush_scheduled_work();
}
static int fan_get_speed(unsigned int *speed)
{
u8 hi, lo;
switch (fan_status_access_mode) {
case TPACPI_FAN_RD_TPEC:
/* all except 570, 600e/x, 770e, 770x */
if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
!acpi_ec_read(fan_rpm_offset + 1, &hi)))
return -EIO;
if (likely(speed))
*speed = (hi << 8) | lo;
break;
default:
return -ENXIO;
}
return 0;
}
static void fan_watchdog_fire(struct work_struct *ignored)
{
printk(IBM_NOTICE "fan watchdog: enabling fan\n");
if (fan_set_enable()) {
printk(IBM_ERR "fan watchdog: error while enabling fan\n");
/* reschedule for later */
fan_watchdog_reset();
}
}
static void fan_watchdog_reset(void)
{
static int fan_watchdog_active = 0;
if (fan_watchdog_active)
cancel_delayed_work(&fan_watchdog_task);
if (fan_watchdog_maxinterval > 0) {
fan_watchdog_active = 1;
if (!schedule_delayed_work(&fan_watchdog_task,
msecs_to_jiffies(fan_watchdog_maxinterval
* 1000))) {
printk(IBM_ERR "failed to schedule the fan watchdog, "
"watchdog will not trigger\n");
}
} else
fan_watchdog_active = 0;
}
static int fan_set_level(int level)
{
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_SFAN:
if (level >= 0 && level <= 7) {
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
return -EIO;
} else
return -EINVAL;
break;
case TPACPI_FAN_WR_ACPI_FANS:
case TPACPI_FAN_WR_TPEC:
if ((level != TP_EC_FAN_AUTO) &&
(level != TP_EC_FAN_FULLSPEED) &&
((level < 0) || (level > 7)))
return -EINVAL;
if (!acpi_ec_write(fan_status_offset, level))
return -EIO;
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
else
fan_control_status_known = 1;
break;
default:
return -ENXIO;
}
return 0;
}
static int fan_set_enable(void)
{
u8 s;
int rc;
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_FANS:
case TPACPI_FAN_WR_TPEC:
if ((rc = fan_get_status(&s)) < 0)
return rc;
/* Don't go out of emergency fan mode */
if (s != 7)
s = TP_EC_FAN_AUTO;
if (!acpi_ec_write(fan_status_offset, s))
return -EIO;
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
else
fan_control_status_known = 1;
break;
case TPACPI_FAN_WR_ACPI_SFAN:
if ((rc = fan_get_status(&s)) < 0)
return rc;
s &= 0x07;
/* Set fan to at least level 4 */
if (s < 4)
s = 4;
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
return -EIO;
break;
default:
return -ENXIO;
}
return 0;
}
static int fan_set_disable(void)
{
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_FANS:
case TPACPI_FAN_WR_TPEC:
if (!acpi_ec_write(fan_status_offset, 0x00))
return -EIO;
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug A few ThinkPads fail to initialize EC register 0x2f both in the EC firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not initialize it, then the initial status of that register does not correspond to reality. On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon cold boot, when the EC is actually in mode 0x80 (auto mode). Since returning a text string ("unknown") would break a number of userspace programs, instead we correct the reading for the most probably correct answer, and return it is in auto mode. The workaround flags the status and level as unknown on module load/kernel boot, until we are certain at least one fan control command was issued, either by us, or by something else. We don't work around the bug by doing a "fan enable" at module load/startup (which would initialize the EC register) because it is not known if these ThinkPad ACPI DSDT might have set the fan to level 7 instead of "auto" (we don't know if they can do this or not) due to a thermal condition, and we don't want to override that, should they be capable of it. We should be setting the workaround flag to "status known" upon resume, as both reports and a exaustive search on the DSDT tables at acpi.sf.net show that the DSDTs always enable the fan on resume, thus working around the bug. But since we don't have suspend/resume handlers in ibm-acpi yet and the "EC register 0x2f was modified" logic is likely to catch the change anyway, we don't. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
else
fan_control_status_known = 1;
break;
case TPACPI_FAN_WR_ACPI_SFAN:
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
return -EIO;
break;
default:
return -ENXIO;
}
return 0;
}
static int fan_set_speed(int speed)
{
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_FANS:
if (speed >= 0 && speed <= 65535) {
if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
speed, speed, speed))
return -EIO;
} else
return -EINVAL;
break;
default:
return -ENXIO;
}
return 0;
}
static int fan_read(char *p)
{
int len = 0;
int rc;
u8 status;
unsigned int speed = 0;
switch (fan_status_access_mode) {
case TPACPI_FAN_RD_ACPI_GFAN:
/* 570, 600e/x, 770e, 770x */
if ((rc = fan_get_status(&status)) < 0)
return rc;
len += sprintf(p + len, "status:\t\t%s\n"
"level:\t\t%d\n",
(status != 0) ? "enabled" : "disabled", status);
break;
case TPACPI_FAN_RD_TPEC:
/* all except 570, 600e/x, 770e, 770x */
if ((rc = fan_get_status(&status)) < 0)
return rc;
if (unlikely(!fan_control_status_known)) {
if (status != fan_control_initial_status)
fan_control_status_known = 1;
else
/* Return most likely status. In fact, it
* might be the only possible status */
status = TP_EC_FAN_AUTO;
}
len += sprintf(p + len, "status:\t\t%s\n",
(status != 0) ? "enabled" : "disabled");
if ((rc = fan_get_speed(&speed)) < 0)
return rc;
len += sprintf(p + len, "speed:\t\t%d\n", speed);
if (status & TP_EC_FAN_FULLSPEED)
/* Disengaged mode takes precedence */
len += sprintf(p + len, "level:\t\tdisengaged\n");
else if (status & TP_EC_FAN_AUTO)
len += sprintf(p + len, "level:\t\tauto\n");
else
len += sprintf(p + len, "level:\t\t%d\n", status);
break;
case TPACPI_FAN_NONE:
default:
len += sprintf(p + len, "status:\t\tnot supported\n");
}
if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
len += sprintf(p + len, "commands:\tlevel <level>");
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_SFAN:
len += sprintf(p + len, " (<level> is 0-7)\n");
break;
default:
len += sprintf(p + len, " (<level> is 0-7, "
"auto, disengaged)\n");
break;
}
}
if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
len += sprintf(p + len, "commands:\tenable, disable\n"
"commands:\twatchdog <timeout> (<timeout> is 0 (off), "
"1-120 (seconds))\n");
if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
len += sprintf(p + len, "commands:\tspeed <speed>"
" (<speed> is 0-65535)\n");
return len;
}
static int fan_write_cmd_level(const char *cmd, int *rc)
{
int level;
if (strlencmp(cmd, "level auto") == 0)
level = TP_EC_FAN_AUTO;
else if (strlencmp(cmd, "level disengaged") == 0)
level = TP_EC_FAN_FULLSPEED;
else if (sscanf(cmd, "level %d", &level) != 1)
return 0;
if ((*rc = fan_set_level(level)) == -ENXIO)
printk(IBM_ERR "level command accepted for unsupported "
"access mode %d", fan_control_access_mode);
return 1;
}
static int fan_write_cmd_enable(const char *cmd, int *rc)
{
if (strlencmp(cmd, "enable") != 0)
return 0;
if ((*rc = fan_set_enable()) == -ENXIO)
printk(IBM_ERR "enable command accepted for unsupported "
"access mode %d", fan_control_access_mode);
return 1;
}
static int fan_write_cmd_disable(const char *cmd, int *rc)
{
if (strlencmp(cmd, "disable") != 0)
return 0;
if ((*rc = fan_set_disable()) == -ENXIO)
printk(IBM_ERR "disable command accepted for unsupported "
"access mode %d", fan_control_access_mode);
return 1;
}
static int fan_write_cmd_speed(const char *cmd, int *rc)
{
int speed;
/* TODO:
* Support speed <low> <medium> <high> ? */
if (sscanf(cmd, "speed %d", &speed) != 1)
return 0;
if ((*rc = fan_set_speed(speed)) == -ENXIO)
printk(IBM_ERR "speed command accepted for unsupported "
"access mode %d", fan_control_access_mode);
return 1;
}
static int fan_write_cmd_watchdog(const char *cmd, int *rc)
{
int interval;
if (sscanf(cmd, "watchdog %d", &interval) != 1)
return 0;
if (interval < 0 || interval > 120)
*rc = -EINVAL;
else
fan_watchdog_maxinterval = interval;
return 1;
}
static int fan_write(char *buf)
{
char *cmd;
int rc = 0;
while (!rc && (cmd = next_cmd(&buf))) {
if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) &&
fan_write_cmd_level(cmd, &rc)) &&
!((fan_control_commands & TPACPI_FAN_CMD_ENABLE) &&
(fan_write_cmd_enable(cmd, &rc) ||
fan_write_cmd_disable(cmd, &rc) ||
fan_write_cmd_watchdog(cmd, &rc))) &&
!((fan_control_commands & TPACPI_FAN_CMD_SPEED) &&
fan_write_cmd_speed(cmd, &rc))
)
rc = -EINVAL;
else if (!rc)
fan_watchdog_reset();
}
return rc;
}
static struct ibm_struct fan_driver_data = {
.name = "fan",
.read = fan_read,
.write = fan_write,
.exit = fan_exit,
.experimental = 1,
};
/****************************************************************************
****************************************************************************
*
* Infrastructure
*
****************************************************************************
****************************************************************************/
/* /proc support */
static struct proc_dir_entry *proc_dir = NULL;
/* Subdriver registry */
static LIST_HEAD(tpacpi_all_drivers);
/*
* Module and infrastructure proble, init and exit handling
*/
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
static const char * __init str_supported(int is_supported)
{
static char text_unsupported[] __initdata = "not supported";
return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
}
#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
static int __init ibm_init(struct ibm_init_struct *iibm)
{
int ret;
struct ibm_struct *ibm = iibm->data;
struct proc_dir_entry *entry;
BUG_ON(ibm == NULL);
INIT_LIST_HEAD(&ibm->all_drivers);
if (ibm->experimental && !experimental)
return 0;
dbg_printk(TPACPI_DBG_INIT,
"probing for %s\n", ibm->name);
if (iibm->init) {
ret = iibm->init(iibm);
if (ret > 0)
return 0; /* probe failed */
if (ret)
return ret;
ibm->init_called = 1;
}
if (ibm->hid) {
ret = register_tpacpi_subdriver(ibm);
if (ret)
goto err_out;
}
if (ibm->notify) {
ret = setup_notify(ibm);
if (ret == -ENODEV) {
printk(IBM_NOTICE "disabling subdriver %s\n",
ibm->name);
ret = 0;
goto err_out;
}
if (ret < 0)
goto err_out;
}
dbg_printk(TPACPI_DBG_INIT,
"%s installed\n", ibm->name);
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);
ret = -ENODEV;
goto err_out;
}
entry->owner = THIS_MODULE;
entry->data = ibm;
entry->read_proc = &dispatch_read;
if (ibm->write)
entry->write_proc = &dispatch_write;
ibm->proc_created = 1;
}
list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers);
return 0;
err_out:
dbg_printk(TPACPI_DBG_INIT,
"%s: at error exit path with result %d\n",
ibm->name, ret);
ibm_exit(ibm);
return (ret < 0)? ret : 0;
}
static void ibm_exit(struct ibm_struct *ibm)
{
dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
list_del_init(&ibm->all_drivers);
if (ibm->notify_installed) {
dbg_printk(TPACPI_DBG_EXIT,
"%s: acpi_remove_notify_handler\n", ibm->name);
acpi_remove_notify_handler(*ibm->handle, ibm->type,
dispatch_notify);
ibm->notify_installed = 0;
}
if (ibm->proc_created) {
dbg_printk(TPACPI_DBG_EXIT,
"%s: remove_proc_entry\n", ibm->name);
remove_proc_entry(ibm->name, proc_dir);
ibm->proc_created = 0;
}
if (ibm->driver_registered) {
dbg_printk(TPACPI_DBG_EXIT,
"%s: acpi_bus_unregister_driver\n", ibm->name);
acpi_bus_unregister_driver(ibm->driver);
kfree(ibm->driver);
ibm->driver = NULL;
ibm->driver_registered = 0;
}
if (ibm->init_called && ibm->exit) {
ibm->exit();
ibm->init_called = 0;
}
dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
}
/* Probing */
static char *ibm_thinkpad_ec_found = NULL;
static char* __init check_dmi_for_ec(void)
{
struct dmi_device *dev = NULL;
char ec_fw_string[18];
/*
* ThinkPad T23 or newer, A31 or newer, R50e or newer,
* X32 or newer, all Z series; Some models must have an
* up-to-date BIOS or they will not be detected.
*
* See http://thinkwiki.org/wiki/List_of_DMI_IDs
*/
while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
if (sscanf(dev->name,
"IBM ThinkPad Embedded Controller -[%17c",
ec_fw_string) == 1) {
ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
return kstrdup(ec_fw_string, GFP_KERNEL);
}
}
return NULL;
}
static int __init probe_for_thinkpad(void)
{
int is_thinkpad;
if (acpi_disabled)
return -ENODEV;
/*
* Non-ancient models have better DMI tagging, but very old models
* don't.
*/
is_thinkpad = dmi_name_in_vendors("ThinkPad");
/* ec is required because many other handles are relative to it */
IBM_HANDLE_INIT(ec);
if (!ec_handle) {
if (is_thinkpad)
printk(IBM_ERR
"Not yet supported ThinkPad detected!\n");
return -ENODEV;
}
/*
* Risks a regression on very old machines, but reduces potential
* false positives a damn great deal
*/
if (!is_thinkpad)
is_thinkpad = dmi_name_in_vendors("IBM");
if (!is_thinkpad && !force_load)
return -ENODEV;
return 0;
}
/* Module init, exit, parameters */
static struct ibm_init_struct ibms_init[] __initdata = {
{
.init = thinkpad_acpi_driver_init,
.data = &thinkpad_acpi_driver_data,
},
{
.init = hotkey_init,
.data = &hotkey_driver_data,
},
{
.init = bluetooth_init,
.data = &bluetooth_driver_data,
},
{
.init = wan_init,
.data = &wan_driver_data,
},
{
.init = video_init,
.data = &video_driver_data,
},
{
.init = light_init,
.data = &light_driver_data,
},
#ifdef CONFIG_THINKPAD_ACPI_DOCK
{
.init = dock_init,
.data = &dock_driver_data[0],
},
{
.data = &dock_driver_data[1],
},
#endif
#ifdef CONFIG_THINKPAD_ACPI_BAY
{
.init = bay_init,
.data = &bay_driver_data,
},
#endif
{
.init = cmos_init,
.data = &cmos_driver_data,
},
{
.init = led_init,
.data = &led_driver_data,
},
{
.init = beep_init,
.data = &beep_driver_data,
},
{
.init = thermal_init,
.data = &thermal_driver_data,
},
{
.data = &ecdump_driver_data,
},
{
.init = brightness_init,
.data = &brightness_driver_data,
},
{
.data = &volume_driver_data,
},
{
.init = fan_init,
.data = &fan_driver_data,
},
};
static int __init set_ibm_param(const char *val, struct kernel_param *kp)
{
unsigned int i;
struct ibm_struct *ibm;
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
ibm = ibms_init[i].data;
BUG_ON(ibm == NULL);
if (strcmp(ibm->name, kp->name) == 0 && ibm->write) {
if (strlen(val) > sizeof(ibms_init[i].param) - 2)
return -ENOSPC;
strcpy(ibms_init[i].param, val);
strcat(ibms_init[i].param, ",");
return 0;
}
}
return -EINVAL;
}
static int experimental;
module_param(experimental, int, 0);
static u32 dbg_level;
module_param_named(debug, dbg_level, uint, 0);
static int force_load;
module_param(force_load, int, 0);
#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 CONFIG_THINKPAD_ACPI_DOCK
IBM_PARAM(dock);
#endif
#ifdef CONFIG_THINKPAD_ACPI_BAY
IBM_PARAM(bay);
#endif /* CONFIG_THINKPAD_ACPI_BAY */
IBM_PARAM(cmos);
IBM_PARAM(led);
IBM_PARAM(beep);
IBM_PARAM(ecdump);
IBM_PARAM(brightness);
IBM_PARAM(volume);
IBM_PARAM(fan);
static int __init thinkpad_acpi_module_init(void)
{
int ret, i;
ret = probe_for_thinkpad();
if (ret)
return ret;
2006-11-24 21:47:08 +08:00
ibm_thinkpad_ec_found = check_dmi_for_ec();
IBM_HANDLE_INIT(ecrd);
IBM_HANDLE_INIT(ecwr);
proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir);
if (!proc_dir) {
printk(IBM_ERR "unable to create proc dir %s", IBM_DIR);
thinkpad_acpi_module_exit();
return -ENODEV;
}
proc_dir->owner = THIS_MODULE;
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
ret = ibm_init(&ibms_init[i]);
if (ret >= 0 && *ibms_init[i].param)
ret = ibms_init[i].data->write(ibms_init[i].param);
if (ret < 0) {
thinkpad_acpi_module_exit();
return ret;
}
}
return 0;
}
static void thinkpad_acpi_module_exit(void)
{
struct ibm_struct *ibm, *itmp;
list_for_each_entry_safe_reverse(ibm, itmp,
&tpacpi_all_drivers,
all_drivers) {
ibm_exit(ibm);
}
dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
if (proc_dir)
remove_proc_entry(IBM_DIR, acpi_root_dir);
if (ibm_thinkpad_ec_found)
kfree(ibm_thinkpad_ec_found);
}
module_init(thinkpad_acpi_module_init);
module_exit(thinkpad_acpi_module_exit);