From 837ca6ddb440c186eaa8e01b69486581d3457f2c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:33:54 -0300 Subject: ACPI: ibm-acpi: kill trailing whitespace I shall protect the ibm-acpi city against the invasion of the barbarian blanks! To the unforgiving jaws of sed s/[[:blank:]]\+$// they go! Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/ibm-acpi.txt | 6 +++--- drivers/acpi/ibm_acpi.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt index 0132d36..cdcef01 100644 --- a/Documentation/ibm-acpi.txt +++ b/Documentation/ibm-acpi.txt @@ -21,7 +21,7 @@ detailed description): - Fn key combinations - Bluetooth enable and disable - - video output switching, expansion control + - video output switching, expansion control - ThinkLight on and off - limited docking and undocking - UltraBay eject @@ -472,7 +472,7 @@ This feature dumps the values of 256 embedded controller registers. Values which have changed since the last time the registers were dumped are marked with a star: -[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump +[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 @@ -503,7 +503,7 @@ vary. The second ensures that the fan-related values do vary, since the fan speed fluctuates a bit. The third will (hopefully) mark the fan register with a star: -[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump +[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c index dc10966..60aaed9 100644 --- a/drivers/acpi/ibm_acpi.c +++ b/drivers/acpi/ibm_acpi.c @@ -28,7 +28,7 @@ * 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 @@ -38,7 +38,7 @@ * 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.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 @@ -161,7 +161,7 @@ IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ #ifdef CONFIG_ACPI_IBM_BAY 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.SATA.SCND.MSTR", /* T60, X60, Z60 */ "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */ ); /* A21e, R30, R31 */ -- cgit v1.1 From e062e0343871a41e8ec408f1c1e8ac3b0310da9d Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:33:55 -0300 Subject: ACPI: ibm-acpi: rename some identifiers Rename some identifiers so that they are more in tune with the rest of the driver code, or less generic. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/acpi/ibm_acpi.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c index 60aaed9..41c46b0 100644 --- a/drivers/acpi/ibm_acpi.c +++ b/drivers/acpi/ibm_acpi.c @@ -506,7 +506,7 @@ static int ibm_acpi_driver_init(void) return 0; } -static int driver_read(char *p) +static int ibm_acpi_driver_read(char *p) { int len = 0; @@ -1310,9 +1310,9 @@ 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 +#define IBMACPI_LED_EC_HLCL 0x0c +#define IBMACPI_LED_EC_HLBL 0x0d +#define IBMACPI_LED_EC_HLMS 0x0e static int led_write(char *buf) { @@ -1344,13 +1344,15 @@ static int led_write(char *buf) } else if (led_supported == IBMACPI_LED_OLD) { /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ led = 1 << led; - ret = ec_write(EC_HLMS, led); + ret = ec_write(IBMACPI_LED_EC_HLMS, led); if (ret >= 0) ret = - ec_write(EC_HLBL, led * led_exp_hlbl[ind]); + ec_write(IBMACPI_LED_EC_HLBL, + led * led_exp_hlbl[ind]); if (ret >= 0) ret = - ec_write(EC_HLCL, led * led_exp_hlcl[ind]); + ec_write(IBMACPI_LED_EC_HLCL, + led * led_exp_hlcl[ind]); if (ret < 0) return ret; } else { @@ -1657,8 +1659,8 @@ static int brightness_read(char *p) return len; } -#define BRIGHTNESS_UP 4 -#define BRIGHTNESS_DOWN 5 +#define TP_CMOS_BRIGHTNESS_UP 4 +#define TP_CMOS_BRIGHTNESS_DOWN 5 static int brightness_set(int value) { @@ -1667,7 +1669,8 @@ static int brightness_set(int value) value &= 7; - cmos_cmd = value > current_value ? BRIGHTNESS_UP : BRIGHTNESS_DOWN; + 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)) @@ -1769,9 +1772,9 @@ static int volume_read(char *p) return len; } -#define VOLUME_DOWN 0 -#define VOLUME_UP 1 -#define VOLUME_MUTE 2 +#define TP_CMOS_VOLUME_DOWN 0 +#define TP_CMOS_VOLUME_UP 1 +#define TP_CMOS_VOLUME_MUTE 2 static int volume_write(char *buf) { @@ -1805,7 +1808,8 @@ static int volume_write(char *buf) return -EINVAL; if (new_level != level) { /* mute doesn't change */ - cmos_cmd = new_level > level ? VOLUME_UP : VOLUME_DOWN; + 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) || @@ -1817,14 +1821,15 @@ static int volume_write(char *buf) !acpi_ec_write(volume_offset, i + inc)) return -EIO; - if (mute && (!cmos_eval(VOLUME_MUTE) || + 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 ? VOLUME_MUTE : VOLUME_UP; + 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)) @@ -2315,7 +2320,7 @@ static struct ibm_struct ibms[] = { { .name = "driver", .init = ibm_acpi_driver_init, - .read = driver_read, + .read = ibm_acpi_driver_read, }, { .name = "hotkey", @@ -2534,7 +2539,7 @@ static int __init ibm_device_add(struct acpi_device *device) return 0; } -static int __init register_driver(struct ibm_struct *ibm) +static int __init register_ibmacpi_subdriver(struct ibm_struct *ibm) { int ret; @@ -2569,7 +2574,7 @@ static int __init ibm_init(struct ibm_struct *ibm) return 0; if (ibm->hid) { - ret = register_driver(ibm); + ret = register_ibmacpi_subdriver(ibm); if (ret < 0) return ret; ibm->driver_registered = 1; -- cgit v1.1 From 1406cdd1760743106278c1f02a0f445159c8f400 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:33:56 -0300 Subject: ACPI: ibm-acpi: add header file Add a (private) header file for ibm-acpi, and move type definitions and ThinkPad driver constants to the new header file. This patch has no functional changes. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/acpi/ibm_acpi.c | 139 +-------------- drivers/acpi/ibm_acpi.h | 437 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 438 insertions(+), 138 deletions(-) create mode 100644 drivers/acpi/ibm_acpi.h diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c index 41c46b0..e2da954 100644 --- a/drivers/acpi/ibm_acpi.c +++ b/drivers/acpi/ibm_acpi.c @@ -78,44 +78,13 @@ * 2004-08-09 0.1 initial release, support for X series */ -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#define IBM_NAME "ibm" -#define IBM_DESC "IBM ThinkPad ACPI Extras" -#define IBM_FILE "ibm_acpi" -#define IBM_URL "http://ibm-acpi.sf.net/" +#include "ibm_acpi.h" MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); 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 experimental; @@ -207,22 +176,6 @@ IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ "JFNS", /* 770x-JL */ ); /* all others */ -#define IBM_HKEY_HID "IBM0068" -#define IBM_PCI_HID "PNP0A03" - -enum thermal_access_mode { - IBMACPI_THERMAL_NONE = 0, /* No thermal support */ - IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ -}; - -#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -struct ibm_thermal_sensors_struct { - s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; -}; - /* * FAN ACCESS MODES * @@ -323,72 +276,12 @@ struct ibm_thermal_sensors_struct { * but the ACPI tables just mention level 7. */ -enum fan_status_access_mode { - IBMACPI_FAN_NONE = 0, /* No fan status or control */ - IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ -}; - -enum fan_control_access_mode { - IBMACPI_FAN_WR_NONE = 0, /* No fan control */ - IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ -}; - -enum fan_control_commands { - IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, - * and also watchdog cmd */ -}; - -enum { /* Fan control constants */ - fan_status_offset = 0x2f, /* EC register 0x2f */ - fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) - * 0x84 must be read before 0x85 */ - - IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer - * disengaged */ - IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan - * control */ -}; - static char *ibm_thinkpad_ec_found = NULL; -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; static struct backlight_device *ibm_backlight_device = 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, ...) { @@ -775,13 +668,6 @@ static int wan_write(char *buf) return 0; } -enum video_access_mode { - IBMACPI_VIDEO_NONE = 0, - IBMACPI_VIDEO_570, /* 570 */ - IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - IBMACPI_VIDEO_NEW, /* all others */ -}; - static enum video_access_mode video_supported; static int video_orig_autosw; @@ -1248,12 +1134,6 @@ static int cmos_write(char *buf) return 0; } -enum led_access_mode { - IBMACPI_LED_NONE = 0, - IBMACPI_LED_570, /* 570 */ - IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - IBMACPI_LED_NEW, /* all others */ -}; static enum led_access_mode led_supported; static int led_init(void) @@ -1310,10 +1190,6 @@ 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 IBMACPI_LED_EC_HLCL 0x0c -#define IBMACPI_LED_EC_HLBL 0x0d -#define IBMACPI_LED_EC_HLMS 0x0e - static int led_write(char *buf) { char *cmd; @@ -1629,8 +1505,6 @@ static int ecdump_write(char *buf) return 0; } -static int brightness_offset = 0x31; - static int brightness_get(struct backlight_device *bd) { u8 level; @@ -1659,9 +1533,6 @@ static int brightness_read(char *p) return len; } -#define TP_CMOS_BRIGHTNESS_UP 4 -#define TP_CMOS_BRIGHTNESS_DOWN 5 - static int brightness_set(int value) { int cmos_cmd, inc, i; @@ -1752,8 +1623,6 @@ static void brightness_exit(void) } } -static int volume_offset = 0x30; - static int volume_read(char *p) { int len = 0; @@ -1772,10 +1641,6 @@ static int volume_read(char *p) return len; } -#define TP_CMOS_VOLUME_DOWN 0 -#define TP_CMOS_VOLUME_UP 1 -#define TP_CMOS_VOLUME_MUTE 2 - static int volume_write(char *buf) { int cmos_cmd, inc, i; @@ -2563,8 +2428,6 @@ static int __init register_ibmacpi_subdriver(struct ibm_struct *ibm) return ret; } -static void ibm_exit(struct ibm_struct *ibm); - static int __init ibm_init(struct ibm_struct *ibm) { int ret; diff --git a/drivers/acpi/ibm_acpi.h b/drivers/acpi/ibm_acpi.h new file mode 100644 index 0000000..7ebaaa4 --- /dev/null +++ b/drivers/acpi/ibm_acpi.h @@ -0,0 +1,437 @@ +/* + * ibm_acpi.h - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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. + */ + +#ifndef __IBM_ACPI_H__ +#define __IBM_ACPI_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +/**************************************************************************** + * Main driver + */ + +#define IBM_NAME "ibm" +#define IBM_DESC "IBM ThinkPad ACPI Extras" +#define IBM_FILE "ibm_acpi" +#define IBM_URL "http://ibm-acpi.sf.net/" + +#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 + +/* ThinkPad CMOS commands */ +#define TP_CMOS_VOLUME_DOWN 0 +#define TP_CMOS_VOLUME_UP 1 +#define TP_CMOS_VOLUME_MUTE 2 +#define TP_CMOS_BRIGHTNESS_UP 4 +#define TP_CMOS_BRIGHTNESS_DOWN 5 + +#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))) + +/* ACPI HIDs */ +#define IBM_HKEY_HID "IBM0068" +#define IBM_PCI_HID "PNP0A03" + +/* ACPI helpers */ +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...); +static int acpi_ec_read(int i, u8 * p); +static int acpi_ec_write(int i, u8 v); +static int _sta(acpi_handle handle); + +/* ACPI handles */ +static acpi_handle root_handle; /* root namespace */ +static acpi_handle ec_handle; /* EC */ +static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ +static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ + +static void ibm_handle_init(char *name, + acpi_handle * handle, acpi_handle parent, + char **paths, int num_paths, char **path); +#define IBM_HANDLE_INIT(object) \ + ibm_handle_init(#object, &object##_handle, *object##_parent, \ + object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + +/* procfs support */ +static struct proc_dir_entry *proc_dir; +static int ibm_acpi_driver_init(void); +static int ibm_acpi_driver_read(char *p); + +/* procfs helpers */ +static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data); +static int dispatch_write(struct file *file, const char __user * userbuf, + unsigned long count, void *data); +static char *next_cmd(char **cmds); + +/* Module */ +static int experimental; +static char *ibm_thinkpad_ec_found; + +static char* check_dmi_for_ec(void); +static int acpi_ibm_init(void); +static void acpi_ibm_exit(void); + + +/**************************************************************************** + * Subdrivers + */ + +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 ibm_struct ibms[]; +static int set_ibm_param(const char *val, struct kernel_param *kp); +static int ibm_init(struct ibm_struct *ibm); +static void ibm_exit(struct ibm_struct *ibm); + +/* ACPI devices */ +static void dispatch_notify(acpi_handle handle, u32 event, void *data); +static int setup_notify(struct ibm_struct *ibm); +static int ibm_device_add(struct acpi_device *device); +static int register_ibmacpi_subdriver(struct ibm_struct *ibm); + + +/* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_BAY +static int bay_status_supported, bay_eject_supported; +static int bay_status2_supported, bay_eject2_supported; + +static acpi_handle bay_handle, bay_ej_handle; +static acpi_handle bay2_handle, bay2_ej_handle; + +static int bay_init(void); +static void bay_notify(struct ibm_struct *ibm, u32 event); +static int bay_read(char *p); +static int bay_write(char *buf); +#endif /* CONFIG_ACPI_IBM_BAY */ + + +/* + * Beep subdriver + */ + +static acpi_handle beep_handle; + +static int beep_read(char *p); +static int beep_write(char *buf); + + +/* + * Bluetooth subdriver + */ + +static int bluetooth_supported; + +static int bluetooth_init(void); +static int bluetooth_status(void); +static int bluetooth_read(char *p); +static int bluetooth_write(char *buf); + + +/* + * Brightness (backlight) subdriver + */ + +static struct backlight_device *ibm_backlight_device; +static int brightness_offset = 0x31; + +static int brightness_init(void); +static void brightness_exit(void); +static int brightness_get(struct backlight_device *bd); +static int brightness_set(int value); +static int brightness_update_status(struct backlight_device *bd); +static int brightness_read(char *p); +static int brightness_write(char *buf); + + +/* + * CMOS subdriver + */ + +static int cmos_eval(int cmos_cmd); +static int cmos_read(char *p); +static int cmos_write(char *buf); + + +/* + * Dock subdriver + */ + +static acpi_handle pci_handle; +#ifdef CONFIG_ACPI_IBM_DOCK +static acpi_handle dock_handle; + +static void dock_notify(struct ibm_struct *ibm, u32 event); +static int dock_read(char *p); +static int dock_write(char *buf); +#endif /* CONFIG_ACPI_IBM_DOCK */ + + +/* + * EC dump subdriver + */ + +static int ecdump_read(char *p) ; +static int ecdump_write(char *buf); + + +/* + * Fan subdriver + */ + +enum { /* Fan control constants */ + fan_status_offset = 0x2f, /* EC register 0x2f */ + fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) + * 0x84 must be read before 0x85 */ + + IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer + * disengaged */ + IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan + * control */ +}; + +enum fan_status_access_mode { + IBMACPI_FAN_NONE = 0, /* No fan status or control */ + IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ +}; + +enum fan_control_access_mode { + IBMACPI_FAN_WR_NONE = 0, /* No fan control */ + IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ +}; + +enum fan_control_commands { + IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + * and also watchdog cmd */ +}; + +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; +static int fan_control_status_known; +static u8 fan_control_initial_status; +static int fan_watchdog_maxinterval; + +static acpi_handle fans_handle, gfan_handle, sfan_handle; + +static int fan_init(void); +static void fan_exit(void); +static int fan_get_status(u8 *status); +static int fan_get_speed(unsigned int *speed); +static void fan_watchdog_fire(struct work_struct *ignored); +static void fan_watchdog_reset(void); +static int fan_set_level(int level); +static int fan_set_enable(void); +static int fan_set_disable(void); +static int fan_set_speed(int speed); +static int fan_read(char *p); +static int fan_write(char *buf); +static int fan_write_cmd_level(const char *cmd, int *rc); +static int fan_write_cmd_enable(const char *cmd, int *rc); +static int fan_write_cmd_disable(const char *cmd, int *rc); +static int fan_write_cmd_speed(const char *cmd, int *rc); +static int fan_write_cmd_watchdog(const char *cmd, int *rc); + + +/* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static int hotkey_init(void); +static void hotkey_exit(void); +static int hotkey_get(int *status, int *mask); +static int hotkey_set(int status, int mask); +static void hotkey_notify(struct ibm_struct *ibm, u32 event); +static int hotkey_read(char *p); +static int hotkey_write(char *buf); + + +/* + * LED subdriver + */ + +enum led_access_mode { + IBMACPI_LED_NONE = 0, + IBMACPI_LED_570, /* 570 */ + IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + IBMACPI_LED_NEW, /* all others */ +}; + +enum { /* For IBMACPI_LED_OLD */ + IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +}; + +static enum led_access_mode led_supported; +static acpi_handle led_handle; + +static int led_init(void); +static int led_read(char *p); +static int led_write(char *buf); + +/* + * Light (thinklight) subdriver + */ + +static int light_supported; +static int light_status_supported; +static acpi_handle lght_handle, ledb_handle; + +static int light_init(void); +static int light_read(char *p); +static int light_write(char *buf); + + +/* + * Thermal subdriver + */ + +enum thermal_access_mode { + IBMACPI_THERMAL_NONE = 0, /* No thermal support */ + IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ +}; + +#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +struct ibm_thermal_sensors_struct { + s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; +}; + +static int thermal_init(void); +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); +static int thermal_read(char *p); + + +/* + * Video subdriver + */ + +enum video_access_mode { + IBMACPI_VIDEO_NONE = 0, + IBMACPI_VIDEO_570, /* 570 */ + IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + IBMACPI_VIDEO_NEW, /* all others */ +}; + +static enum video_access_mode video_supported; +static int video_orig_autosw; +static acpi_handle vid_handle, vid2_handle; + +static int video_init(void); +static void video_exit(void); +static int video_status(void); +static int video_autosw(void); +static int video_switch(void); +static int video_switch2(int status); +static int video_expand(void); +static int video_read(char *p); +static int video_write(char *buf); + + +/* + * Volume subdriver + */ + +static int volume_offset = 0x30; + +static int volume_read(char *p); +static int volume_write(char *buf); + + +/* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void); +static int wan_status(void); +static int wan_read(char *p); +static int wan_write(char *buf); + + +#endif /* __IBM_ACPI_H */ -- cgit v1.1 From 56b6aeb05890f219895197f5166637b3d7a6f679 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:33:57 -0300 Subject: ACPI: ibm-acpi: organize code Shuffle code around to better organize the driver code inside the ibm-acpi.c file. This patch adds no functional changes. It is pure fluff that will make me a bit more productive. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/acpi/ibm_acpi.c | 1476 +++++++++++++++++++++++++---------------------- 1 file changed, 796 insertions(+), 680 deletions(-) diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c index e2da954..984ec81 100644 --- a/drivers/acpi/ibm_acpi.c +++ b/drivers/acpi/ibm_acpi.c @@ -87,8 +87,17 @@ MODULE_LICENSE("GPL"); #define __unused __attribute__ ((unused)) -static int experimental; -module_param(experimental, int, 0); +/**************************************************************************** + **************************************************************************** + * + * ACPI Helpers and device model + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ACPI basic handles + */ static acpi_handle root_handle = NULL; @@ -105,183 +114,31 @@ IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ "\\_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(ecrd, ec, "ECRD"); /* 570 */ +IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ -IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ + +/************************************************************************* + * 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 */ -#ifdef CONFIG_ACPI_IBM_DOCK -IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ - "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ - "\\_SB.PCI0.PCI1.DOCK", /* all others */ - "\\_SB.PCI.ISA.SLCE", /* 570 */ - ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ -#endif -#ifdef CONFIG_ACPI_IBM_BAY -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 */ -#endif /* CONFIG_ACPI_IBM_BAY */ - -/* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ + ); /* all others */ IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ "^HKEY", /* R30, R31 */ "HKEY", /* all others */ - ); /* 570 */ + ); /* 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, 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 */ - -/* - * FAN ACCESS MODES - * - * IBMACPI_FAN_RD_ACPI_GFAN: - * ACPI GFAN method: returns fan level - * - * see IBMACPI_FAN_WR_ACPI_SFAN - * EC 0x2f not available if GFAN exists - * - * IBMACPI_FAN_WR_ACPI_SFAN: - * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) - * - * EC 0x2f might be available *for reading*, but never for writing. - * - * IBMACPI_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 disengage mode (takes precedence over bit 7); - * not available on all thinkpads. May disable - * the tachometer, and speeds up fan to 100% duty-cycle, - * which speeds it up 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 - * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this - * register is not invalidated in ThinkPads that disable tachometer - * readings. Thus, the tachometer readings go stale. - * - * For firmware bugs, refer to: - * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues - * - * IBMACPI_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. - * - * IBMACPI_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. +/************************************************************************* + * ACPI helpers */ -static char *ibm_thinkpad_ec_found = NULL; - -static struct proc_dir_entry *proc_dir = NULL; - -static struct backlight_device *ibm_backlight_device = NULL; - static int acpi_evalf(acpi_handle handle, void *res, char *method, char *fmt, ...) { @@ -354,21 +211,218 @@ static int acpi_evalf(acpi_handle handle, return 0; } - if (!success && !quiet) - printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", - method, fmt0, status); + 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 __init 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; + + 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_NAME, 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_ibmacpi_subdriver(struct ibm_struct *ibm) +{ + int ret; + + ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->driver) { + printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); + return -1; + } + + 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); + } + + 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; - return success; -} + if (copy_from_user(kernbuf, userbuf, count)) { + kfree(kernbuf); + return -EFAULT; + } -static void __unused acpi_print_int(acpi_handle handle, char *method) -{ - int i; + kernbuf[count] = 0; + strcat(kernbuf, ","); + ret = ibm->write(kernbuf); + if (ret == 0) + ret = count; - 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); + kfree(kernbuf); + + return ret; } static char *next_cmd(char **cmds) @@ -387,6 +441,19 @@ static char *next_cmd(char **cmds) return start; } + +/**************************************************************************** + **************************************************************************** + * + * Subdrivers + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ibm-acpi init subdriver + */ + static int ibm_acpi_driver_init(void) { printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); @@ -409,11 +476,51 @@ static int ibm_acpi_driver_read(char *p) return len; } +/************************************************************************* + * Hotkey subdriver + */ + static int hotkey_supported; static int hotkey_mask_supported; static int hotkey_orig_status; static int hotkey_orig_mask; +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 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 hotkey_get(int *status, int *mask) { if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) @@ -444,24 +551,6 @@ static int hotkey_set(int status, int mask) 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; @@ -523,23 +612,9 @@ static int hotkey_write(char *buf) 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); - } -} +/************************************************************************* + * Bluetooth subdriver + */ static int bluetooth_supported; @@ -606,6 +681,10 @@ static int bluetooth_write(char *buf) return 0; } +/************************************************************************* + * Wan subdriver + */ + static int wan_supported; static int wan_init(void) @@ -668,9 +747,22 @@ static int wan_write(char *buf) return 0; } +/************************************************************************* + * 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 video_init(void) { int ivga; @@ -695,6 +787,11 @@ static int video_init(void) return 0; } +static void video_exit(void) +{ + acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); +} + static int video_status(void) { int status = 0; @@ -736,33 +833,6 @@ static int video_autosw(void) 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 == IBMACPI_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 == IBMACPI_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(); @@ -812,6 +882,33 @@ static int video_switch2(int status) 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 == IBMACPI_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 == IBMACPI_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; @@ -862,14 +959,16 @@ static int video_write(char *buf) return 0; } -static void video_exit(void) -{ - acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); -} +/************************************************************************* + * 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 light_init(void) { /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ @@ -933,21 +1032,45 @@ static int light_write(char *buf) return 0; } -#if defined(CONFIG_ACPI_IBM_DOCK) || defined(CONFIG_ACPI_IBM_BAY) -static int _sta(acpi_handle handle) -{ - int status; - - if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) - status = 0; +/************************************************************************* + * Dock subdriver + */ - return status; -} -#endif +/* don't list other alternatives as we install a notify handler on the 570 */ +IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ #ifdef CONFIG_ACPI_IBM_DOCK + +IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ + "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ + "\\_SB.PCI0.PCI1.DOCK", /* all others */ + "\\_SB.PCI.ISA.SLCE", /* 570 */ + ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ + #define dock_docked() (_sta(dock_handle) & 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; @@ -976,39 +1099,22 @@ static int dock_write(char *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 */ + return -EIO; + } else if (strlencmp(cmd, "dock") == 0) { + if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1)) + return -EIO; + } else + return -EINVAL; } + + return 0; } -#endif + +#endif /* CONFIG_ACPI_IBM_DOCK */ + +/************************************************************************* + * Bay subdriver + */ #ifdef CONFIG_ACPI_IBM_BAY static int bay_status_supported; @@ -1016,6 +1122,21 @@ 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 bay_init(void) { bay_status_supported = bay_handle && @@ -1031,6 +1152,11 @@ static int bay_init(void) return 0; } +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) @@ -1081,12 +1207,19 @@ static int bay_write(char *buf) return 0; } +#endif /* CONFIG_ACPI_IBM_BAY */ -static void bay_notify(struct ibm_struct *ibm, u32 event) +/************************************************************************* + * CMOS subdriver + */ + +static int cmos_eval(int cmos_cmd) { - acpi_bus_generate_event(ibm->device, event, 0); + if (cmos_handle) + return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd); + else + return 1; } -#endif /* CONFIG_ACPI_IBM_BAY */ static int cmos_read(char *p) { @@ -1104,14 +1237,6 @@ static int cmos_read(char *p) 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; @@ -1134,8 +1259,18 @@ static int cmos_write(char *buf) return 0; } + +/************************************************************************* + * 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 led_init(void) { if (!led_handle) @@ -1242,6 +1377,12 @@ static int led_write(char *buf) return 0; } +/************************************************************************* + * Beep subdriver + */ + +IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ + static int beep_read(char *p) { int len = 0; @@ -1277,34 +1418,9 @@ static int beep_write(char *buf) 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; -} +/************************************************************************* + * Thermal subdriver + */ static enum thermal_access_mode thermal_read_mode; @@ -1446,6 +1562,10 @@ static int thermal_read(char *p) return len; } +/************************************************************************* + * EC Dump subdriver + */ + static u8 ecdump_regs[256]; static int ecdump_read(char *p) @@ -1505,6 +1625,55 @@ static int ecdump_write(char *buf) return 0; } +/************************************************************************* + * 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 brightness_init(void) +{ + int b; + + 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); + } + + 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) { + 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; @@ -1516,23 +1685,6 @@ static int brightness_get(struct backlight_device *bd) return level; } -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 " - " ( is 0-7)\n"); - } - - return len; -} - static int brightness_set(int value) { int cmos_cmd, inc, i; @@ -1540,8 +1692,7 @@ static int brightness_set(int value) value &= 7; - cmos_cmd = value > current_value ? - TP_CMOS_BRIGHTNESS_UP : TP_CMOS_BRIGHTNESS_DOWN; + 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)) @@ -1553,6 +1704,23 @@ static int brightness_set(int value) 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 " + " ( is 0-7)\n"); + } + + return len; +} + static int brightness_write(char *buf) { int level; @@ -1580,48 +1748,9 @@ static int brightness_write(char *buf) return 0; } -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 struct backlight_ops ibm_backlight_data = { - .get_brightness = brightness_get, - .update_status = brightness_update_status, -}; - -static int brightness_init(void) -{ - int b; - - 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); - } - - 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) { - backlight_device_unregister(ibm_backlight_device); - ibm_backlight_device = NULL; - } -} +/************************************************************************* + * Volume subdriver + */ static int volume_read(char *p) { @@ -1673,8 +1802,7 @@ static int volume_write(char *buf) return -EINVAL; if (new_level != level) { /* mute doesn't change */ - cmos_cmd = new_level > level ? - TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; + 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) || @@ -1693,8 +1821,7 @@ static int volume_write(char *buf) } if (new_mute != mute) { /* level doesn't change */ - cmos_cmd = new_mute ? - TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; + 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)) @@ -1705,6 +1832,111 @@ static int volume_write(char *buf) return 0; } + +/************************************************************************* + * Fan subdriver + */ + +/* + * FAN ACCESS MODES + * + * IBMACPI_FAN_RD_ACPI_GFAN: + * ACPI GFAN method: returns fan level + * + * see IBMACPI_FAN_WR_ACPI_SFAN + * EC 0x2f not available if GFAN exists + * + * IBMACPI_FAN_WR_ACPI_SFAN: + * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) + * + * EC 0x2f might be available *for reading*, but never for writing. + * + * IBMACPI_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 disengage mode (takes precedence over bit 7); + * not available on all thinkpads. May disable + * the tachometer, and speeds up fan to 100% duty-cycle, + * which speeds it up 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 + * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this + * register is not invalidated in ThinkPads that disable tachometer + * readings. Thus, the tachometer readings go stale. + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * IBMACPI_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. + * + * IBMACPI_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; @@ -1716,6 +1948,14 @@ 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 fan_init(void) { fan_status_access_mode = IBMACPI_FAN_NONE; @@ -1831,6 +2071,12 @@ static int fan_get_status(u8 *status) return 0; } +static void fan_exit(void) +{ + cancel_delayed_work(&fan_watchdog_task); + flush_scheduled_work(); +} + static int fan_get_speed(unsigned int *speed) { u8 hi, lo; @@ -1854,10 +2100,14 @@ static int fan_get_speed(unsigned int *speed) return 0; } -static void fan_exit(void) +static void fan_watchdog_fire(struct work_struct *ignored) { - cancel_delayed_work(&fan_watchdog_task); - flush_scheduled_work(); + 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) @@ -1879,90 +2129,6 @@ static void fan_watchdog_reset(void) fan_watchdog_active = 0; } -static int fan_read(char *p) -{ - int len = 0; - int rc; - u8 status; - unsigned int speed = 0; - - switch (fan_status_access_mode) { - case IBMACPI_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 IBMACPI_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 = IBMACPI_FAN_EC_AUTO; - } - - len += sprintf(p + len, "status:\t\t%s\n", - (status != 0) ? "enabled" : "disabled"); - - /* No ThinkPad boots on disengaged mode, we can safely - * assume the tachometer is online if fan control status - * was unknown */ - if ((rc = fan_get_speed(&speed)) < 0) - return rc; - - len += sprintf(p + len, "speed:\t\t%d\n", speed); - - if (status & IBMACPI_FAN_EC_DISENGAGED) - /* Disengaged mode takes precedence */ - len += sprintf(p + len, "level:\t\tdisengaged\n"); - else if (status & IBMACPI_FAN_EC_AUTO) - len += sprintf(p + len, "level:\t\tauto\n"); - else - len += sprintf(p + len, "level:\t\t%d\n", status); - break; - - case IBMACPI_FAN_NONE: - default: - len += sprintf(p + len, "status:\t\tnot supported\n"); - } - - if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { - len += sprintf(p + len, "commands:\tlevel "); - - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: - len += sprintf(p + len, " ( is 0-7)\n"); - break; - - default: - len += sprintf(p + len, " ( is 0-7, " - "auto, disengaged)\n"); - break; - } - } - - if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) - len += sprintf(p + len, "commands:\tenable, disable\n" - "commands:\twatchdog ( is 0 (off), " - "1-120 (seconds))\n"); - - if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed " - " ( is 0-65535)\n"); - - return len; -} - static int fan_set_level(int level) { switch (fan_control_access_mode) { @@ -2053,25 +2219,109 @@ static int fan_set_disable(void) default: return -ENXIO; } - return 0; -} + return 0; +} + +static int fan_set_speed(int speed) +{ + switch (fan_control_access_mode) { + case IBMACPI_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 IBMACPI_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 IBMACPI_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 = IBMACPI_FAN_EC_AUTO; + } + + len += sprintf(p + len, "status:\t\t%s\n", + (status != 0) ? "enabled" : "disabled"); + + /* No ThinkPad boots on disengaged mode, we can safely + * assume the tachometer is online if fan control status + * was unknown */ + if ((rc = fan_get_speed(&speed)) < 0) + return rc; + + len += sprintf(p + len, "speed:\t\t%d\n", speed); + + if (status & IBMACPI_FAN_EC_DISENGAGED) + /* Disengaged mode takes precedence */ + len += sprintf(p + len, "level:\t\tdisengaged\n"); + else if (status & IBMACPI_FAN_EC_AUTO) + len += sprintf(p + len, "level:\t\tauto\n"); + else + len += sprintf(p + len, "level:\t\t%d\n", status); + break; + + case IBMACPI_FAN_NONE: + default: + len += sprintf(p + len, "status:\t\tnot supported\n"); + } + + if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { + len += sprintf(p + len, "commands:\tlevel "); + + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_SFAN: + len += sprintf(p + len, " ( is 0-7)\n"); + break; + + default: + len += sprintf(p + len, " ( is 0-7, " + "auto, disengaged)\n"); + break; + } + } -static int fan_set_speed(int speed) -{ - switch (fan_control_access_mode) { - case IBMACPI_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; + if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) + len += sprintf(p + len, "commands:\tenable, disable\n" + "commands:\twatchdog ( is 0 (off), " + "1-120 (seconds))\n"); - default: - return -ENXIO; - } - return 0; + if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) + len += sprintf(p + len, "commands:\tspeed " + " ( is 0-65535)\n"); + + return len; } static int fan_write_cmd_level(const char *cmd, int *rc) @@ -2171,16 +2421,18 @@ static int fan_write(char *buf) return rc; } -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(); - } -} +/**************************************************************************** + **************************************************************************** + * + * Infrastructure + * + **************************************************************************** + ****************************************************************************/ + +/* /proc support */ +static struct proc_dir_entry *proc_dir = NULL; +/* Subdriver registry */ static struct ibm_struct ibms[] = { { .name = "driver", @@ -2301,132 +2553,9 @@ static struct ibm_struct ibms[] = { }, }; -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 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; - - 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_NAME, 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_ibmacpi_subdriver(struct ibm_struct *ibm) -{ - int ret; - - ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); - if (!ibm->driver) { - printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); - return -1; - } - - 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); - } - - return ret; -} +/* + * Module and infrastructure proble, init and exit handling + */ static int __init ibm_init(struct ibm_struct *ibm) { @@ -2500,27 +2629,35 @@ static void ibm_exit(struct ibm_struct *ibm) } } -static void __init ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, - char **paths, int num_paths, char **path) +/* Probing */ + +static char *ibm_thinkpad_ec_found = NULL; + +static char* __init check_dmi_for_ec(void) { - int i; - acpi_status status; + struct dmi_device *dev = NULL; + char ec_fw_string[18]; - for (i = 0; i < num_paths; i++) { - status = acpi_get_handle(parent, paths[i], handle); - if (ACPI_SUCCESS(status)) { - *path = paths[i]; - return; + /* + * 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); } } - - *handle = NULL; + return NULL; } -#define IBM_HANDLE_INIT(object) \ - ibm_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) +/* Module init, exit, parameters */ static int __init set_ibm_param(const char *val, struct kernel_param *kp) { @@ -2538,6 +2675,9 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp) return -EINVAL; } +static int experimental; +module_param(experimental, int, 0); + #define IBM_PARAM(feature) \ module_param_call(feature, set_ibm_param, NULL, NULL, 0) @@ -2559,44 +2699,6 @@ 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]); - - if (proc_dir) - remove_proc_entry(IBM_DIR, acpi_root_dir); - - if (ibm_thinkpad_ec_found) - kfree(ibm_thinkpad_ec_found); -} - -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 acpi_ibm_init(void) { int ret, i; @@ -2662,5 +2764,19 @@ static int __init acpi_ibm_init(void) return 0; } +static void acpi_ibm_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) + ibm_exit(&ibms[i]); + + if (proc_dir) + remove_proc_entry(IBM_DIR, acpi_root_dir); + + if (ibm_thinkpad_ec_found) + kfree(ibm_thinkpad_ec_found); +} + module_init(acpi_ibm_init); module_exit(acpi_ibm_exit); -- cgit v1.1 From a62bc916cf48caaf9efa2fed20440fd617647c6c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:33:58 -0300 Subject: ACPI: ibm-acpi: update copyright notice Update copyright and license info on the source code comments. No functional changes. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/acpi/ibm_acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c index 984ec81..ae03b8f 100644 --- a/drivers/acpi/ibm_acpi.c +++ b/drivers/acpi/ibm_acpi.c @@ -3,7 +3,7 @@ * * * Copyright (C) 2004-2005 Borislav Deianov - * Copyright (C) 2006 Henrique de Moraes Holschuh + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh * * 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 @@ -17,7 +17,8 @@ * * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. */ #define IBM_VERSION "0.13" -- cgit v1.1 From 38f996ed21089fa4ae40526a5f428e3c792ea561 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:33:59 -0300 Subject: ACPI: ibm-acpi: update documentation Update documentation header, and relocate a hunk of text that was missplaced. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/ibm-acpi.txt | 85 ++++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 60 deletions(-) diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt index cdcef01..f409f4b 100644 --- a/Documentation/ibm-acpi.txt +++ b/Documentation/ibm-acpi.txt @@ -1,16 +1,17 @@ IBM ThinkPad ACPI Extras Driver - Version 0.12 - 17 August 2005 + Version 0.13 + 31 December 2006 Borislav Deianov + Henrique de Moraes Holschuh http://ibm-acpi.sf.net/ This is a Linux ACPI driver for the IBM ThinkPad laptops. It supports various features of these laptops which are accessible through the -ACPI framework but not otherwise supported by the generic Linux ACPI -drivers. +ACPI framework but not otherwise fully supported by the generic Linux +ACPI drivers. Status @@ -638,6 +639,26 @@ The ThinkPad's ACPI DSDT code will reprogram the fan on its own when certain conditions are met. It will override any fan programming done through ibm-acpi. +The ibm-acpi kernel driver can be programmed to revert the fan level +to a safe setting if userspace does not issue one of the fan commands: +"enable", "disable", "level" or "watchdog" within a configurable +ammount of time. To do this, use the "watchdog" command. + + echo 'watchdog ' > /proc/acpi/ibm/fan + +Interval is the ammount of time in seconds to wait for one of the +above mentioned fan commands before reseting the fan level to a safe +one. If set to zero, the watchdog is disabled (default). When the +watchdog timer runs out, it does the exact equivalent of the "enable" +fan command. + +Note that the watchdog timer stops after it enables the fan. It will +be rearmed again automatically (using the same interval) when one of +the above mentioned fan commands is received. The fan watchdog is, +therefore, not suitable to protect against fan mode changes made +through means other than the "enable", "disable", and "level" fan +commands. + EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan --------------------------------------- @@ -670,59 +691,3 @@ example: modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable -The ibm-acpi kernel driver can be programmed to revert the fan level -to a safe setting if userspace does not issue one of the fan commands: -"enable", "disable", "level" or "watchdog" within a configurable -ammount of time. To do this, use the "watchdog" command. - - echo 'watchdog ' > /proc/acpi/ibm/fan - -Interval is the ammount of time in seconds to wait for one of the -above mentioned fan commands before reseting the fan level to a safe -one. If set to zero, the watchdog is disabled (default). When the -watchdog timer runs out, it does the exact equivalent of the "enable" -fan command. - -Note that the watchdog timer stops after it enables the fan. It will -be rearmed again automatically (using the same interval) when one of -the above mentioned fan commands is received. The fan watchdog is, -therefore, not suitable to protect against fan mode changes made -through means other than the "enable", "disable", and "level" fan -commands. - - -Example Configuration ---------------------- - -The ACPI support in the kernel is intended to be used in conjunction -with a user-space daemon, acpid. The configuration files for this -daemon control what actions are taken in response to various ACPI -events. An example set of configuration files are included in the -config/ directory of the tarball package available on the web -site. Note that these are provided for illustration purposes only and -may need to be adapted to your particular setup. - -The following utility scripts are used by the example action -scripts (included with ibm-acpi for completeness): - - /usr/local/sbin/idectl -- from the hdparm source distribution, - see http://www.ibiblio.org/pub/Linux/system/hardware - /usr/local/sbin/laptop_mode -- from the Linux kernel source - distribution, see Documentation/laptop-mode.txt - /sbin/service -- comes with Redhat/Fedora distributions - /usr/sbin/hibernate -- from the Software Suspend 2 distribution, - see http://softwaresuspend.berlios.de/ - -Toan T Nguyen notes that Suse uses the -powersave program to suspend ('powersave --suspend-to-ram') or -hibernate ('powersave --suspend-to-disk'). This means that the -hibernate script is not needed on that distribution. - -Henrik Brix Andersen has written a Gentoo ACPI event -handler script for the X31. You can get the latest version from -http://dev.gentoo.org/~brix/files/x31.sh - -David Schweikert has written an alternative blank.sh -script which works on Debian systems. This scripts has now been -extended to also work on Fedora systems and included as the default -blank.sh in the distribution. -- cgit v1.1 From 3ede41c718c7845905231019e42d05a3ed329515 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 23 Mar 2007 17:34:00 -0300 Subject: ACPI: ibm-acpi: move driver to drivers/misc hierarchy ibm-acpi is not an ACPICA driver, so move it to drivers/misc as per Len Brown's request. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/acpi/Kconfig | 37 - drivers/acpi/Makefile | 1 - drivers/acpi/ibm_acpi.c | 2783 ----------------------------------------------- drivers/acpi/ibm_acpi.h | 437 -------- drivers/misc/Kconfig | 37 + drivers/misc/Makefile | 1 + drivers/misc/ibm_acpi.c | 2783 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/ibm_acpi.h | 437 ++++++++ 8 files changed, 3258 insertions(+), 3258 deletions(-) delete mode 100644 drivers/acpi/ibm_acpi.c delete mode 100644 drivers/acpi/ibm_acpi.h create mode 100644 drivers/misc/ibm_acpi.c create mode 100644 drivers/misc/ibm_acpi.h diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index e2ce4a9..45c4315 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -218,43 +218,6 @@ config ACPI_ASUS NOTE: This driver is deprecated and will probably be removed soon, use asus-laptop instead. -config ACPI_IBM - tristate "IBM ThinkPad Laptop Extras" - depends on X86 - select BACKLIGHT_CLASS_DEVICE - ---help--- - This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds - support for Fn-Fx key combinations, Bluetooth control, video - output switching, ThinkLight control, UltraBay eject and more. - For more information about this driver see - and . - - If you have an IBM ThinkPad laptop, say Y or M here. - -config ACPI_IBM_DOCK - bool "Legacy Docking Station Support" - depends on ACPI_IBM - depends on ACPI_DOCK=n - default n - ---help--- - Allows the ibm_acpi driver to handle docking station events. - This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI. It will - allow locking and removing the laptop from the docking station, - but will not properly connect PCI devices. - - If you are not sure, say N here. - -config ACPI_IBM_BAY - bool "Legacy Removable Bay Support" - depends on ACPI_IBM - default y - ---help--- - Allows the ibm_acpi driver to handle removable bays. It will allow - disabling the device in the bay, and also generate notifications when - the bay lever is ejected or inserted. - - If you are not sure, say Y here. - config ACPI_TOSHIBA tristate "Toshiba Laptop Extras" depends on X86 diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 5956e9f..92642ab 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -55,7 +55,6 @@ obj-$(CONFIG_ACPI_SYSTEM) += system.o event.o obj-$(CONFIG_ACPI_DEBUG) += debug.o obj-$(CONFIG_ACPI_NUMA) += numa.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o -obj-$(CONFIG_ACPI_IBM) += ibm_acpi.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o obj-y += cm_sbs.o diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c deleted file mode 100644 index ae03b8f..0000000 --- a/drivers/acpi/ibm_acpi.c +++ /dev/null @@ -1,2783 +0,0 @@ -/* - * ibm_acpi.c - IBM ThinkPad ACPI Extras - * - * - * Copyright (C) 2004-2005 Borislav Deianov - * Copyright (C) 2006-2007 Henrique de Moraes Holschuh - * - * 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.13" - -/* - * Changelog: - * - * 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 - * 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 - * fix parameter passing on module loading - * thanks to Rusty Russell - * thanks to Jim Radford - * 2004-11-08 0.8 fix init error case, don't return from a macro - * thanks to Chris Wright - * 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 "ibm_acpi.h" - -MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); -MODULE_DESCRIPTION(IBM_DESC); -MODULE_VERSION(IBM_VERSION); -MODULE_LICENSE("GPL"); - -#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, ¶ms, 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 __init 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; - - 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_NAME, 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_ibmacpi_subdriver(struct ibm_struct *ibm) -{ - int ret; - - ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); - if (!ibm->driver) { - printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); - return -1; - } - - 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); - } - - 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 - * - **************************************************************************** - ****************************************************************************/ - -/************************************************************************* - * ibm-acpi init subdriver - */ - -static int ibm_acpi_driver_init(void) -{ - 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 ibm_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; -} - -/************************************************************************* - * Hotkey subdriver - */ - -static int hotkey_supported; -static int hotkey_mask_supported; -static int hotkey_orig_status; -static int hotkey_orig_mask; - -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 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 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, \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; -} - -/************************************************************************* - * Bluetooth subdriver - */ - -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; -} - -/************************************************************************* - * Wan subdriver - */ - -static int wan_supported; - -static int wan_init(void) -{ - wan_supported = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); - - return 0; -} - -static int wan_status(void) -{ - int status; - - if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) - status = 0; - - return status; -} - -static int wan_read(char *p) -{ - int len = 0; - int status = wan_status(); - - if (!wan_supported) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!(status & 1)) - len += sprintf(p + len, "status:\t\tnot installed\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; -} - -static int wan_write(char *buf) -{ - int status = wan_status(); - char *cmd; - int do_cmd = 0; - - if (!wan_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status |= 2; - } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) - return -EIO; - - return 0; -} - -/************************************************************************* - * 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 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 = IBMACPI_VIDEO_NONE; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) - /* 570 */ - video_supported = IBMACPI_VIDEO_570; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) - /* 600e/x, 770e, 770x */ - video_supported = IBMACPI_VIDEO_770; - else - /* all others */ - video_supported = IBMACPI_VIDEO_NEW; - - return 0; -} - -static void video_exit(void) -{ - acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); -} - -static int video_status(void) -{ - int status = 0; - int i; - - if (video_supported == IBMACPI_VIDEO_570) { - if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) - status = i & 3; - } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) - acpi_evalf(vid_handle, &autosw, "SWIT", "d"); - else if (video_supported == IBMACPI_VIDEO_770 || - video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) - return acpi_evalf(ec_handle, NULL, "_Q17", "v"); - else if (video_supported == IBMACPI_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 == IBMACPI_VIDEO_570) { - ret = acpi_evalf(NULL, NULL, - "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); - } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_NEW && - strlencmp(cmd, "dvi_enable") == 0) { - enable |= 0x08; - } else if (video_supported == IBMACPI_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; -} - -/************************************************************************* - * 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 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; -} - -/************************************************************************* - * Dock subdriver - */ - -/* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ - -#ifdef CONFIG_ACPI_IBM_DOCK - -IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ - "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ - "\\_SB.PCI0.PCI1.DOCK", /* all others */ - "\\_SB.PCI.ISA.SLCE", /* 570 */ - ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ - -#define dock_docked() (_sta(dock_handle) & 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; -} - -#endif /* CONFIG_ACPI_IBM_DOCK */ - -/************************************************************************* - * Bay subdriver - */ - -#ifdef CONFIG_ACPI_IBM_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 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; -} - -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; -} -#endif /* CONFIG_ACPI_IBM_BAY */ - -/************************************************************************* - * CMOS subdriver - */ - -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 ( 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; -} - - -/************************************************************************* - * 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 led_init(void) -{ - if (!led_handle) - /* led not supported on R30, R31 */ - led_supported = IBMACPI_LED_NONE; - else if (strlencmp(led_path, "SLED") == 0) - /* 570 */ - led_supported = IBMACPI_LED_570; - else if (strlencmp(led_path, "SYSL") == 0) - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - led_supported = IBMACPI_LED_OLD; - else - /* all others */ - led_supported = IBMACPI_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 == IBMACPI_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" - " on, off, blink ( 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 == IBMACPI_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 == IBMACPI_LED_OLD) { - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ - led = 1 << led; - ret = ec_write(IBMACPI_LED_EC_HLMS, led); - if (ret >= 0) - ret = - ec_write(IBMACPI_LED_EC_HLBL, - led * led_exp_hlbl[ind]); - if (ret >= 0) - ret = - ec_write(IBMACPI_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; -} - -/************************************************************************* - * Beep subdriver - */ - -IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ - -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 ( 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; -} - -/************************************************************************* - * Thermal subdriver - */ - -static enum thermal_access_mode thermal_read_mode; - -static int thermal_init(void) -{ - u8 t, ta1, ta2; - int i; - int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); - - 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 - */ - - 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 = IBMACPI_THERMAL_ACPI_TMP07; - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "disabling thermal sensors access\n"); - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - } else { - thermal_read_mode = - (ta2 != 0) ? - IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; - } - } else if (acpi_tmp7) { - if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { - /* 600e/x, 770e, 770x */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; - } else { - /* Standard ACPI TMPx access, max 8 sensors */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; - } - } else { - /* temperatures not supported on 570, G4x, R30, R31, R32 */ - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - - return 0; -} - -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) -{ - int i, t; - s8 tmp; - char tmpi[] = "TMPi"; - - if (!s) - return -EINVAL; - - switch (thermal_read_mode) { -#if IBMACPI_MAX_THERMAL_SENSORS >= 16 - case IBMACPI_THERMAL_TPEC_16: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0xC0 + i, &tmp)) - return -EIO; - s->temp[i + 8] = tmp * 1000; - } - /* fallthrough */ -#endif - case IBMACPI_THERMAL_TPEC_8: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0x78 + i, &tmp)) - return -EIO; - s->temp[i] = tmp * 1000; - } - return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; - - case IBMACPI_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 IBMACPI_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 IBMACPI_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; -} - -/************************************************************************* - * 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 0x" - " ( is 00-ff, is 00-ff)\n"); - len += sprintf(p + len, "commands:\t0x " - " ( is 00-ff, 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; -} - -/************************************************************************* - * 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 brightness_init(void) -{ - int b; - - 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); - } - - 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) { - 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 " - " ( 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; -} - -/************************************************************************* - * 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 " - " ( 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; -} - - -/************************************************************************* - * Fan subdriver - */ - -/* - * FAN ACCESS MODES - * - * IBMACPI_FAN_RD_ACPI_GFAN: - * ACPI GFAN method: returns fan level - * - * see IBMACPI_FAN_WR_ACPI_SFAN - * EC 0x2f not available if GFAN exists - * - * IBMACPI_FAN_WR_ACPI_SFAN: - * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) - * - * EC 0x2f might be available *for reading*, but never for writing. - * - * IBMACPI_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 disengage mode (takes precedence over bit 7); - * not available on all thinkpads. May disable - * the tachometer, and speeds up fan to 100% duty-cycle, - * which speeds it up 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 - * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this - * register is not invalidated in ThinkPads that disable tachometer - * readings. Thus, the tachometer readings go stale. - * - * For firmware bugs, refer to: - * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues - * - * IBMACPI_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. - * - * IBMACPI_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; - -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 fan_init(void) -{ - fan_status_access_mode = IBMACPI_FAN_NONE; - fan_control_access_mode = IBMACPI_FAN_WR_NONE; - fan_control_commands = 0; - fan_control_status_known = 1; - fan_watchdog_maxinterval = 0; - - if (gfan_handle) { - /* 570, 600e/x, 770e, 770x */ - fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; - } else { - /* all other ThinkPads: note that even old-style - * ThinkPad ECs supports the fan control register */ - if (likely(acpi_ec_read(fan_status_offset, - &fan_control_initial_status))) { - fan_status_access_mode = IBMACPI_FAN_RD_TPEC; - - /* 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 0; - } - } - - if (sfan_handle) { - /* 570, 770x-JL */ - fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | IBMACPI_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 = - IBMACPI_FAN_WR_ACPI_FANS; - fan_control_commands |= - IBMACPI_FAN_CMD_SPEED | - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } else { - fan_control_access_mode = IBMACPI_FAN_WR_TPEC; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } - } - } - - return 0; -} - -static int fan_get_status(u8 *status) -{ - u8 s; - - /* TODO: - * Add IBMACPI_FAN_RD_ACPI_FANS ? */ - - switch (fan_status_access_mode) { - case IBMACPI_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 IBMACPI_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) -{ - 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 IBMACPI_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 IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((level != IBMACPI_FAN_EC_AUTO) && - (level != IBMACPI_FAN_EC_DISENGAGED) && - ((level < 0) || (level > 7))) - return -EINVAL; - - if (!acpi_ec_write(fan_status_offset, level)) - return -EIO; - 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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((rc = fan_get_status(&s)) < 0) - return rc; - - /* Don't go out of emergency fan mode */ - if (s != 7) - s = IBMACPI_FAN_EC_AUTO; - - if (!acpi_ec_write(fan_status_offset, s)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if (!acpi_ec_write(fan_status_offset, 0x00)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_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 IBMACPI_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 IBMACPI_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 IBMACPI_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 = IBMACPI_FAN_EC_AUTO; - } - - len += sprintf(p + len, "status:\t\t%s\n", - (status != 0) ? "enabled" : "disabled"); - - /* No ThinkPad boots on disengaged mode, we can safely - * assume the tachometer is online if fan control status - * was unknown */ - if ((rc = fan_get_speed(&speed)) < 0) - return rc; - - len += sprintf(p + len, "speed:\t\t%d\n", speed); - - if (status & IBMACPI_FAN_EC_DISENGAGED) - /* Disengaged mode takes precedence */ - len += sprintf(p + len, "level:\t\tdisengaged\n"); - else if (status & IBMACPI_FAN_EC_AUTO) - len += sprintf(p + len, "level:\t\tauto\n"); - else - len += sprintf(p + len, "level:\t\t%d\n", status); - break; - - case IBMACPI_FAN_NONE: - default: - len += sprintf(p + len, "status:\t\tnot supported\n"); - } - - if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { - len += sprintf(p + len, "commands:\tlevel "); - - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: - len += sprintf(p + len, " ( is 0-7)\n"); - break; - - default: - len += sprintf(p + len, " ( is 0-7, " - "auto, disengaged)\n"); - break; - } - } - - if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) - len += sprintf(p + len, "commands:\tenable, disable\n" - "commands:\twatchdog ( is 0 (off), " - "1-120 (seconds))\n"); - - if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed " - " ( 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 = IBMACPI_FAN_EC_AUTO; - else if (strlencmp(cmd, "level disengaged") == 0) - level = IBMACPI_FAN_EC_DISENGAGED; - 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 ? */ - - 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 & IBMACPI_FAN_CMD_LEVEL) && - fan_write_cmd_level(cmd, &rc)) && - !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && - (fan_write_cmd_enable(cmd, &rc) || - fan_write_cmd_disable(cmd, &rc) || - fan_write_cmd_watchdog(cmd, &rc))) && - !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && - fan_write_cmd_speed(cmd, &rc)) - ) - rc = -EINVAL; - else if (!rc) - fan_watchdog_reset(); - } - - return rc; -} - -/**************************************************************************** - **************************************************************************** - * - * Infrastructure - * - **************************************************************************** - ****************************************************************************/ - -/* /proc support */ -static struct proc_dir_entry *proc_dir = NULL; - -/* Subdriver registry */ -static struct ibm_struct ibms[] = { - { - .name = "driver", - .init = ibm_acpi_driver_init, - .read = ibm_acpi_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 = "wan", - .init = wan_init, - .read = wan_read, - .write = wan_write, - .experimental = 1, - }, - { - .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 CONFIG_ACPI_IBM_DOCK - { - .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 -#ifdef CONFIG_ACPI_IBM_BAY - { - .name = "bay", - .init = bay_init, - .read = bay_read, - .write = bay_write, - .notify = bay_notify, - .handle = &bay_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif /* CONFIG_ACPI_IBM_BAY */ - { - .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", - .read = brightness_read, - .write = brightness_write, - .init = brightness_init, - .exit = brightness_exit, - }, - { - .name = "volume", - .read = volume_read, - .write = volume_write, - }, - { - .name = "fan", - .read = fan_read, - .write = fan_write, - .init = fan_init, - .exit = fan_exit, - .experimental = 1, - }, -}; - -/* - * Module and infrastructure proble, init and exit handling - */ - -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_ibmacpi_subdriver(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 == -ENODEV) { - printk(IBM_NOTICE "disabling subdriver %s\n", - ibm->name); - ibm_exit(ibm); - return 0; - } - if (ret < 0) - return ret; - } - - 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); - } -} - -/* 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; -} - -/* Module init, exit, parameters */ - -static int __init 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; -} - -static int experimental; -module_param(experimental, 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_ACPI_IBM_DOCK -IBM_PARAM(dock); -#endif -#ifdef CONFIG_ACPI_IBM_BAY -IBM_PARAM(bay); -#endif /* CONFIG_ACPI_IBM_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 acpi_ibm_init(void) -{ - int ret, i; - - if (acpi_disabled) - 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; - } - - /* Models with newer firmware report the EC in DMI */ - ibm_thinkpad_ec_found = check_dmi_for_ec(); - - /* 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 CONFIG_ACPI_IBM_DOCK - IBM_HANDLE_INIT(dock); -#endif - IBM_HANDLE_INIT(pci); -#ifdef CONFIG_ACPI_IBM_BAY - IBM_HANDLE_INIT(bay); - if (bay_handle) - IBM_HANDLE_INIT(bay_ej); - IBM_HANDLE_INIT(bay2); - if (bay2_handle) - IBM_HANDLE_INIT(bay2_ej); -#endif /* CONFIG_ACPI_IBM_BAY */ - 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); - acpi_ibm_exit(); - 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; -} - -static void acpi_ibm_exit(void) -{ - int i; - - for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) - ibm_exit(&ibms[i]); - - if (proc_dir) - remove_proc_entry(IBM_DIR, acpi_root_dir); - - if (ibm_thinkpad_ec_found) - kfree(ibm_thinkpad_ec_found); -} - -module_init(acpi_ibm_init); -module_exit(acpi_ibm_exit); diff --git a/drivers/acpi/ibm_acpi.h b/drivers/acpi/ibm_acpi.h deleted file mode 100644 index 7ebaaa4..0000000 --- a/drivers/acpi/ibm_acpi.h +++ /dev/null @@ -1,437 +0,0 @@ -/* - * ibm_acpi.h - IBM ThinkPad ACPI Extras - * - * - * Copyright (C) 2004-2005 Borislav Deianov - * Copyright (C) 2006-2007 Henrique de Moraes Holschuh - * - * 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. - */ - -#ifndef __IBM_ACPI_H__ -#define __IBM_ACPI_H__ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - - -/**************************************************************************** - * Main driver - */ - -#define IBM_NAME "ibm" -#define IBM_DESC "IBM ThinkPad ACPI Extras" -#define IBM_FILE "ibm_acpi" -#define IBM_URL "http://ibm-acpi.sf.net/" - -#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 - -/* ThinkPad CMOS commands */ -#define TP_CMOS_VOLUME_DOWN 0 -#define TP_CMOS_VOLUME_UP 1 -#define TP_CMOS_VOLUME_MUTE 2 -#define TP_CMOS_BRIGHTNESS_UP 4 -#define TP_CMOS_BRIGHTNESS_DOWN 5 - -#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))) - -/* ACPI HIDs */ -#define IBM_HKEY_HID "IBM0068" -#define IBM_PCI_HID "PNP0A03" - -/* ACPI helpers */ -static int acpi_evalf(acpi_handle handle, - void *res, char *method, char *fmt, ...); -static int acpi_ec_read(int i, u8 * p); -static int acpi_ec_write(int i, u8 v); -static int _sta(acpi_handle handle); - -/* ACPI handles */ -static acpi_handle root_handle; /* root namespace */ -static acpi_handle ec_handle; /* EC */ -static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ -static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ - -static void ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, - char **paths, int num_paths, char **path); -#define IBM_HANDLE_INIT(object) \ - ibm_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) - -/* procfs support */ -static struct proc_dir_entry *proc_dir; -static int ibm_acpi_driver_init(void); -static int ibm_acpi_driver_read(char *p); - -/* procfs helpers */ -static int dispatch_read(char *page, char **start, off_t off, int count, - int *eof, void *data); -static int dispatch_write(struct file *file, const char __user * userbuf, - unsigned long count, void *data); -static char *next_cmd(char **cmds); - -/* Module */ -static int experimental; -static char *ibm_thinkpad_ec_found; - -static char* check_dmi_for_ec(void); -static int acpi_ibm_init(void); -static void acpi_ibm_exit(void); - - -/**************************************************************************** - * Subdrivers - */ - -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 ibm_struct ibms[]; -static int set_ibm_param(const char *val, struct kernel_param *kp); -static int ibm_init(struct ibm_struct *ibm); -static void ibm_exit(struct ibm_struct *ibm); - -/* ACPI devices */ -static void dispatch_notify(acpi_handle handle, u32 event, void *data); -static int setup_notify(struct ibm_struct *ibm); -static int ibm_device_add(struct acpi_device *device); -static int register_ibmacpi_subdriver(struct ibm_struct *ibm); - - -/* - * Bay subdriver - */ - -#ifdef CONFIG_ACPI_IBM_BAY -static int bay_status_supported, bay_eject_supported; -static int bay_status2_supported, bay_eject2_supported; - -static acpi_handle bay_handle, bay_ej_handle; -static acpi_handle bay2_handle, bay2_ej_handle; - -static int bay_init(void); -static void bay_notify(struct ibm_struct *ibm, u32 event); -static int bay_read(char *p); -static int bay_write(char *buf); -#endif /* CONFIG_ACPI_IBM_BAY */ - - -/* - * Beep subdriver - */ - -static acpi_handle beep_handle; - -static int beep_read(char *p); -static int beep_write(char *buf); - - -/* - * Bluetooth subdriver - */ - -static int bluetooth_supported; - -static int bluetooth_init(void); -static int bluetooth_status(void); -static int bluetooth_read(char *p); -static int bluetooth_write(char *buf); - - -/* - * Brightness (backlight) subdriver - */ - -static struct backlight_device *ibm_backlight_device; -static int brightness_offset = 0x31; - -static int brightness_init(void); -static void brightness_exit(void); -static int brightness_get(struct backlight_device *bd); -static int brightness_set(int value); -static int brightness_update_status(struct backlight_device *bd); -static int brightness_read(char *p); -static int brightness_write(char *buf); - - -/* - * CMOS subdriver - */ - -static int cmos_eval(int cmos_cmd); -static int cmos_read(char *p); -static int cmos_write(char *buf); - - -/* - * Dock subdriver - */ - -static acpi_handle pci_handle; -#ifdef CONFIG_ACPI_IBM_DOCK -static acpi_handle dock_handle; - -static void dock_notify(struct ibm_struct *ibm, u32 event); -static int dock_read(char *p); -static int dock_write(char *buf); -#endif /* CONFIG_ACPI_IBM_DOCK */ - - -/* - * EC dump subdriver - */ - -static int ecdump_read(char *p) ; -static int ecdump_write(char *buf); - - -/* - * Fan subdriver - */ - -enum { /* Fan control constants */ - fan_status_offset = 0x2f, /* EC register 0x2f */ - fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) - * 0x84 must be read before 0x85 */ - - IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer - * disengaged */ - IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan - * control */ -}; - -enum fan_status_access_mode { - IBMACPI_FAN_NONE = 0, /* No fan status or control */ - IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ -}; - -enum fan_control_access_mode { - IBMACPI_FAN_WR_NONE = 0, /* No fan control */ - IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ -}; - -enum fan_control_commands { - IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, - * and also watchdog cmd */ -}; - -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; -static int fan_control_status_known; -static u8 fan_control_initial_status; -static int fan_watchdog_maxinterval; - -static acpi_handle fans_handle, gfan_handle, sfan_handle; - -static int fan_init(void); -static void fan_exit(void); -static int fan_get_status(u8 *status); -static int fan_get_speed(unsigned int *speed); -static void fan_watchdog_fire(struct work_struct *ignored); -static void fan_watchdog_reset(void); -static int fan_set_level(int level); -static int fan_set_enable(void); -static int fan_set_disable(void); -static int fan_set_speed(int speed); -static int fan_read(char *p); -static int fan_write(char *buf); -static int fan_write_cmd_level(const char *cmd, int *rc); -static int fan_write_cmd_enable(const char *cmd, int *rc); -static int fan_write_cmd_disable(const char *cmd, int *rc); -static int fan_write_cmd_speed(const char *cmd, int *rc); -static int fan_write_cmd_watchdog(const char *cmd, int *rc); - - -/* - * Hotkey subdriver - */ - -static int hotkey_supported; -static int hotkey_mask_supported; -static int hotkey_orig_status; -static int hotkey_orig_mask; - -static int hotkey_init(void); -static void hotkey_exit(void); -static int hotkey_get(int *status, int *mask); -static int hotkey_set(int status, int mask); -static void hotkey_notify(struct ibm_struct *ibm, u32 event); -static int hotkey_read(char *p); -static int hotkey_write(char *buf); - - -/* - * LED subdriver - */ - -enum led_access_mode { - IBMACPI_LED_NONE = 0, - IBMACPI_LED_570, /* 570 */ - IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - IBMACPI_LED_NEW, /* all others */ -}; - -enum { /* For IBMACPI_LED_OLD */ - IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ - IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ - IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ -}; - -static enum led_access_mode led_supported; -static acpi_handle led_handle; - -static int led_init(void); -static int led_read(char *p); -static int led_write(char *buf); - -/* - * Light (thinklight) subdriver - */ - -static int light_supported; -static int light_status_supported; -static acpi_handle lght_handle, ledb_handle; - -static int light_init(void); -static int light_read(char *p); -static int light_write(char *buf); - - -/* - * Thermal subdriver - */ - -enum thermal_access_mode { - IBMACPI_THERMAL_NONE = 0, /* No thermal support */ - IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ -}; - -#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -struct ibm_thermal_sensors_struct { - s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; -}; - -static int thermal_init(void); -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); -static int thermal_read(char *p); - - -/* - * Video subdriver - */ - -enum video_access_mode { - IBMACPI_VIDEO_NONE = 0, - IBMACPI_VIDEO_570, /* 570 */ - IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - IBMACPI_VIDEO_NEW, /* all others */ -}; - -static enum video_access_mode video_supported; -static int video_orig_autosw; -static acpi_handle vid_handle, vid2_handle; - -static int video_init(void); -static void video_exit(void); -static int video_status(void); -static int video_autosw(void); -static int video_switch(void); -static int video_switch2(int status); -static int video_expand(void); -static int video_read(char *p); -static int video_write(char *buf); - - -/* - * Volume subdriver - */ - -static int volume_offset = 0x30; - -static int volume_read(char *p); -static int volume_write(char *buf); - - -/* - * Wan subdriver - */ - -static int wan_supported; - -static int wan_init(void); -static int wan_status(void); -static int wan_read(char *p); -static int wan_write(char *buf); - - -#endif /* __IBM_ACPI_H */ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 80b199f..5d2bcbf 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -122,4 +122,41 @@ config SONY_LAPTOP Read for more information. +config ACPI_IBM + tristate "IBM ThinkPad Laptop Extras" + depends on X86 && ACPI + select BACKLIGHT_CLASS_DEVICE + ---help--- + This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds + support for Fn-Fx key combinations, Bluetooth control, video + output switching, ThinkLight control, UltraBay eject and more. + For more information about this driver see + and . + + If you have an IBM ThinkPad laptop, say Y or M here. + +config ACPI_IBM_DOCK + bool "Legacy Docking Station Support" + depends on ACPI_IBM + depends on ACPI_DOCK=n + default n + ---help--- + Allows the ibm_acpi driver to handle docking station events. + This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI. It will + allow locking and removing the laptop from the docking station, + but will not properly connect PCI devices. + + If you are not sure, say N here. + +config ACPI_IBM_BAY + bool "Legacy Removable Bay Support" + depends on ACPI_IBM + default y + ---help--- + Allows the ibm_acpi driver to handle removable bays. It will allow + disabling the device in the bay, and also generate notifications when + the bay lever is ejected or inserted. + + If you are not sure, say Y here. + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7793ccd..848b398 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o +obj-$(CONFIG_ACPI_IBM) += ibm_acpi.o diff --git a/drivers/misc/ibm_acpi.c b/drivers/misc/ibm_acpi.c new file mode 100644 index 0000000..ae03b8f --- /dev/null +++ b/drivers/misc/ibm_acpi.c @@ -0,0 +1,2783 @@ +/* + * ibm_acpi.c - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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.13" + +/* + * Changelog: + * + * 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 + * 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 + * fix parameter passing on module loading + * thanks to Rusty Russell + * thanks to Jim Radford + * 2004-11-08 0.8 fix init error case, don't return from a macro + * thanks to Chris Wright + * 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 "ibm_acpi.h" + +MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); +MODULE_DESCRIPTION(IBM_DESC); +MODULE_VERSION(IBM_VERSION); +MODULE_LICENSE("GPL"); + +#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, ¶ms, 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 __init 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; + + 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_NAME, 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_ibmacpi_subdriver(struct ibm_struct *ibm) +{ + int ret; + + ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->driver) { + printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); + return -1; + } + + 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); + } + + 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 + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ibm-acpi init subdriver + */ + +static int ibm_acpi_driver_init(void) +{ + 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 ibm_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; +} + +/************************************************************************* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +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 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 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, \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; +} + +/************************************************************************* + * Bluetooth subdriver + */ + +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; +} + +/************************************************************************* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void) +{ + wan_supported = hkey_handle && + acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); + + return 0; +} + +static int wan_status(void) +{ + int status; + + if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) + status = 0; + + return status; +} + +static int wan_read(char *p) +{ + int len = 0; + int status = wan_status(); + + if (!wan_supported) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!(status & 1)) + len += sprintf(p + len, "status:\t\tnot installed\n"); + else { + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int wan_write(char *buf) +{ + int status = wan_status(); + char *cmd; + int do_cmd = 0; + + if (!wan_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status |= 2; + } else if (strlencmp(cmd, "disable") == 0) { + status &= ~2; + } else + return -EINVAL; + do_cmd = 1; + } + + if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) + return -EIO; + + return 0; +} + +/************************************************************************* + * 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 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 = IBMACPI_VIDEO_NONE; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) + /* 570 */ + video_supported = IBMACPI_VIDEO_570; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) + /* 600e/x, 770e, 770x */ + video_supported = IBMACPI_VIDEO_770; + else + /* all others */ + video_supported = IBMACPI_VIDEO_NEW; + + return 0; +} + +static void video_exit(void) +{ + acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); +} + +static int video_status(void) +{ + int status = 0; + int i; + + if (video_supported == IBMACPI_VIDEO_570) { + if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) + status = i & 3; + } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) + acpi_evalf(vid_handle, &autosw, "SWIT", "d"); + else if (video_supported == IBMACPI_VIDEO_770 || + video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) + return acpi_evalf(ec_handle, NULL, "_Q17", "v"); + else if (video_supported == IBMACPI_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 == IBMACPI_VIDEO_570) { + ret = acpi_evalf(NULL, NULL, + "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); + } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_NEW && + strlencmp(cmd, "dvi_enable") == 0) { + enable |= 0x08; + } else if (video_supported == IBMACPI_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; +} + +/************************************************************************* + * 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 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; +} + +/************************************************************************* + * Dock subdriver + */ + +/* don't list other alternatives as we install a notify handler on the 570 */ +IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ + +#ifdef CONFIG_ACPI_IBM_DOCK + +IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ + "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ + "\\_SB.PCI0.PCI1.DOCK", /* all others */ + "\\_SB.PCI.ISA.SLCE", /* 570 */ + ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ + +#define dock_docked() (_sta(dock_handle) & 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; +} + +#endif /* CONFIG_ACPI_IBM_DOCK */ + +/************************************************************************* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_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 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; +} + +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; +} +#endif /* CONFIG_ACPI_IBM_BAY */ + +/************************************************************************* + * CMOS subdriver + */ + +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 ( 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; +} + + +/************************************************************************* + * 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 led_init(void) +{ + if (!led_handle) + /* led not supported on R30, R31 */ + led_supported = IBMACPI_LED_NONE; + else if (strlencmp(led_path, "SLED") == 0) + /* 570 */ + led_supported = IBMACPI_LED_570; + else if (strlencmp(led_path, "SYSL") == 0) + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + led_supported = IBMACPI_LED_OLD; + else + /* all others */ + led_supported = IBMACPI_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 == IBMACPI_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" + " on, off, blink ( 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 == IBMACPI_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 == IBMACPI_LED_OLD) { + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ + led = 1 << led; + ret = ec_write(IBMACPI_LED_EC_HLMS, led); + if (ret >= 0) + ret = + ec_write(IBMACPI_LED_EC_HLBL, + led * led_exp_hlbl[ind]); + if (ret >= 0) + ret = + ec_write(IBMACPI_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; +} + +/************************************************************************* + * Beep subdriver + */ + +IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ + +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 ( 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; +} + +/************************************************************************* + * Thermal subdriver + */ + +static enum thermal_access_mode thermal_read_mode; + +static int thermal_init(void) +{ + u8 t, ta1, ta2; + int i; + int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); + + 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 + */ + + 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 = IBMACPI_THERMAL_ACPI_TMP07; + } else { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "disabling thermal sensors access\n"); + thermal_read_mode = IBMACPI_THERMAL_NONE; + } + } else { + thermal_read_mode = + (ta2 != 0) ? + IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; + } + } else if (acpi_tmp7) { + if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { + /* 600e/x, 770e, 770x */ + thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; + } else { + /* Standard ACPI TMPx access, max 8 sensors */ + thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; + } + } else { + /* temperatures not supported on 570, G4x, R30, R31, R32 */ + thermal_read_mode = IBMACPI_THERMAL_NONE; + } + + return 0; +} + +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +{ + int i, t; + s8 tmp; + char tmpi[] = "TMPi"; + + if (!s) + return -EINVAL; + + switch (thermal_read_mode) { +#if IBMACPI_MAX_THERMAL_SENSORS >= 16 + case IBMACPI_THERMAL_TPEC_16: + for (i = 0; i < 8; i++) { + if (!acpi_ec_read(0xC0 + i, &tmp)) + return -EIO; + s->temp[i + 8] = tmp * 1000; + } + /* fallthrough */ +#endif + case IBMACPI_THERMAL_TPEC_8: + for (i = 0; i < 8; i++) { + if (!acpi_ec_read(0x78 + i, &tmp)) + return -EIO; + s->temp[i] = tmp * 1000; + } + return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; + + case IBMACPI_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 IBMACPI_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 IBMACPI_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; +} + +/************************************************************************* + * 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 0x" + " ( is 00-ff, is 00-ff)\n"); + len += sprintf(p + len, "commands:\t0x " + " ( is 00-ff, 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; +} + +/************************************************************************* + * 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 brightness_init(void) +{ + int b; + + 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); + } + + 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) { + 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 " + " ( 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; +} + +/************************************************************************* + * 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 " + " ( 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; +} + + +/************************************************************************* + * Fan subdriver + */ + +/* + * FAN ACCESS MODES + * + * IBMACPI_FAN_RD_ACPI_GFAN: + * ACPI GFAN method: returns fan level + * + * see IBMACPI_FAN_WR_ACPI_SFAN + * EC 0x2f not available if GFAN exists + * + * IBMACPI_FAN_WR_ACPI_SFAN: + * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) + * + * EC 0x2f might be available *for reading*, but never for writing. + * + * IBMACPI_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 disengage mode (takes precedence over bit 7); + * not available on all thinkpads. May disable + * the tachometer, and speeds up fan to 100% duty-cycle, + * which speeds it up 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 + * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this + * register is not invalidated in ThinkPads that disable tachometer + * readings. Thus, the tachometer readings go stale. + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * IBMACPI_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. + * + * IBMACPI_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; + +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 fan_init(void) +{ + fan_status_access_mode = IBMACPI_FAN_NONE; + fan_control_access_mode = IBMACPI_FAN_WR_NONE; + fan_control_commands = 0; + fan_control_status_known = 1; + fan_watchdog_maxinterval = 0; + + if (gfan_handle) { + /* 570, 600e/x, 770e, 770x */ + fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; + } else { + /* all other ThinkPads: note that even old-style + * ThinkPad ECs supports the fan control register */ + if (likely(acpi_ec_read(fan_status_offset, + &fan_control_initial_status))) { + fan_status_access_mode = IBMACPI_FAN_RD_TPEC; + + /* 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 0; + } + } + + if (sfan_handle) { + /* 570, 770x-JL */ + fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; + fan_control_commands |= + IBMACPI_FAN_CMD_LEVEL | IBMACPI_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 = + IBMACPI_FAN_WR_ACPI_FANS; + fan_control_commands |= + IBMACPI_FAN_CMD_SPEED | + IBMACPI_FAN_CMD_LEVEL | + IBMACPI_FAN_CMD_ENABLE; + } else { + fan_control_access_mode = IBMACPI_FAN_WR_TPEC; + fan_control_commands |= + IBMACPI_FAN_CMD_LEVEL | + IBMACPI_FAN_CMD_ENABLE; + } + } + } + + return 0; +} + +static int fan_get_status(u8 *status) +{ + u8 s; + + /* TODO: + * Add IBMACPI_FAN_RD_ACPI_FANS ? */ + + switch (fan_status_access_mode) { + case IBMACPI_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 IBMACPI_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) +{ + 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 IBMACPI_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 IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if ((level != IBMACPI_FAN_EC_AUTO) && + (level != IBMACPI_FAN_EC_DISENGAGED) && + ((level < 0) || (level > 7))) + return -EINVAL; + + if (!acpi_ec_write(fan_status_offset, level)) + return -EIO; + 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 IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if ((rc = fan_get_status(&s)) < 0) + return rc; + + /* Don't go out of emergency fan mode */ + if (s != 7) + s = IBMACPI_FAN_EC_AUTO; + + if (!acpi_ec_write(fan_status_offset, s)) + return -EIO; + else + fan_control_status_known = 1; + break; + + case IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if (!acpi_ec_write(fan_status_offset, 0x00)) + return -EIO; + else + fan_control_status_known = 1; + break; + + case IBMACPI_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 IBMACPI_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 IBMACPI_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 IBMACPI_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 = IBMACPI_FAN_EC_AUTO; + } + + len += sprintf(p + len, "status:\t\t%s\n", + (status != 0) ? "enabled" : "disabled"); + + /* No ThinkPad boots on disengaged mode, we can safely + * assume the tachometer is online if fan control status + * was unknown */ + if ((rc = fan_get_speed(&speed)) < 0) + return rc; + + len += sprintf(p + len, "speed:\t\t%d\n", speed); + + if (status & IBMACPI_FAN_EC_DISENGAGED) + /* Disengaged mode takes precedence */ + len += sprintf(p + len, "level:\t\tdisengaged\n"); + else if (status & IBMACPI_FAN_EC_AUTO) + len += sprintf(p + len, "level:\t\tauto\n"); + else + len += sprintf(p + len, "level:\t\t%d\n", status); + break; + + case IBMACPI_FAN_NONE: + default: + len += sprintf(p + len, "status:\t\tnot supported\n"); + } + + if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { + len += sprintf(p + len, "commands:\tlevel "); + + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_SFAN: + len += sprintf(p + len, " ( is 0-7)\n"); + break; + + default: + len += sprintf(p + len, " ( is 0-7, " + "auto, disengaged)\n"); + break; + } + } + + if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) + len += sprintf(p + len, "commands:\tenable, disable\n" + "commands:\twatchdog ( is 0 (off), " + "1-120 (seconds))\n"); + + if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) + len += sprintf(p + len, "commands:\tspeed " + " ( 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 = IBMACPI_FAN_EC_AUTO; + else if (strlencmp(cmd, "level disengaged") == 0) + level = IBMACPI_FAN_EC_DISENGAGED; + 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 ? */ + + 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 & IBMACPI_FAN_CMD_LEVEL) && + fan_write_cmd_level(cmd, &rc)) && + !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && + (fan_write_cmd_enable(cmd, &rc) || + fan_write_cmd_disable(cmd, &rc) || + fan_write_cmd_watchdog(cmd, &rc))) && + !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && + fan_write_cmd_speed(cmd, &rc)) + ) + rc = -EINVAL; + else if (!rc) + fan_watchdog_reset(); + } + + return rc; +} + +/**************************************************************************** + **************************************************************************** + * + * Infrastructure + * + **************************************************************************** + ****************************************************************************/ + +/* /proc support */ +static struct proc_dir_entry *proc_dir = NULL; + +/* Subdriver registry */ +static struct ibm_struct ibms[] = { + { + .name = "driver", + .init = ibm_acpi_driver_init, + .read = ibm_acpi_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 = "wan", + .init = wan_init, + .read = wan_read, + .write = wan_write, + .experimental = 1, + }, + { + .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 CONFIG_ACPI_IBM_DOCK + { + .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 +#ifdef CONFIG_ACPI_IBM_BAY + { + .name = "bay", + .init = bay_init, + .read = bay_read, + .write = bay_write, + .notify = bay_notify, + .handle = &bay_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, +#endif /* CONFIG_ACPI_IBM_BAY */ + { + .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", + .read = brightness_read, + .write = brightness_write, + .init = brightness_init, + .exit = brightness_exit, + }, + { + .name = "volume", + .read = volume_read, + .write = volume_write, + }, + { + .name = "fan", + .read = fan_read, + .write = fan_write, + .init = fan_init, + .exit = fan_exit, + .experimental = 1, + }, +}; + +/* + * Module and infrastructure proble, init and exit handling + */ + +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_ibmacpi_subdriver(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 == -ENODEV) { + printk(IBM_NOTICE "disabling subdriver %s\n", + ibm->name); + ibm_exit(ibm); + return 0; + } + if (ret < 0) + return ret; + } + + 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); + } +} + +/* 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; +} + +/* Module init, exit, parameters */ + +static int __init 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; +} + +static int experimental; +module_param(experimental, 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_ACPI_IBM_DOCK +IBM_PARAM(dock); +#endif +#ifdef CONFIG_ACPI_IBM_BAY +IBM_PARAM(bay); +#endif /* CONFIG_ACPI_IBM_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 acpi_ibm_init(void) +{ + int ret, i; + + if (acpi_disabled) + 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; + } + + /* Models with newer firmware report the EC in DMI */ + ibm_thinkpad_ec_found = check_dmi_for_ec(); + + /* 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 CONFIG_ACPI_IBM_DOCK + IBM_HANDLE_INIT(dock); +#endif + IBM_HANDLE_INIT(pci); +#ifdef CONFIG_ACPI_IBM_BAY + IBM_HANDLE_INIT(bay); + if (bay_handle) + IBM_HANDLE_INIT(bay_ej); + IBM_HANDLE_INIT(bay2); + if (bay2_handle) + IBM_HANDLE_INIT(bay2_ej); +#endif /* CONFIG_ACPI_IBM_BAY */ + 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); + acpi_ibm_exit(); + 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; +} + +static void acpi_ibm_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) + ibm_exit(&ibms[i]); + + if (proc_dir) + remove_proc_entry(IBM_DIR, acpi_root_dir); + + if (ibm_thinkpad_ec_found) + kfree(ibm_thinkpad_ec_found); +} + +module_init(acpi_ibm_init); +module_exit(acpi_ibm_exit); diff --git a/drivers/misc/ibm_acpi.h b/drivers/misc/ibm_acpi.h new file mode 100644 index 0000000..7ebaaa4 --- /dev/null +++ b/drivers/misc/ibm_acpi.h @@ -0,0 +1,437 @@ +/* + * ibm_acpi.h - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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. + */ + +#ifndef __IBM_ACPI_H__ +#define __IBM_ACPI_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +/**************************************************************************** + * Main driver + */ + +#define IBM_NAME "ibm" +#define IBM_DESC "IBM ThinkPad ACPI Extras" +#define IBM_FILE "ibm_acpi" +#define IBM_URL "http://ibm-acpi.sf.net/" + +#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 + +/* ThinkPad CMOS commands */ +#define TP_CMOS_VOLUME_DOWN 0 +#define TP_CMOS_VOLUME_UP 1 +#define TP_CMOS_VOLUME_MUTE 2 +#define TP_CMOS_BRIGHTNESS_UP 4 +#define TP_CMOS_BRIGHTNESS_DOWN 5 + +#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))) + +/* ACPI HIDs */ +#define IBM_HKEY_HID "IBM0068" +#define IBM_PCI_HID "PNP0A03" + +/* ACPI helpers */ +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...); +static int acpi_ec_read(int i, u8 * p); +static int acpi_ec_write(int i, u8 v); +static int _sta(acpi_handle handle); + +/* ACPI handles */ +static acpi_handle root_handle; /* root namespace */ +static acpi_handle ec_handle; /* EC */ +static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ +static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ + +static void ibm_handle_init(char *name, + acpi_handle * handle, acpi_handle parent, + char **paths, int num_paths, char **path); +#define IBM_HANDLE_INIT(object) \ + ibm_handle_init(#object, &object##_handle, *object##_parent, \ + object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + +/* procfs support */ +static struct proc_dir_entry *proc_dir; +static int ibm_acpi_driver_init(void); +static int ibm_acpi_driver_read(char *p); + +/* procfs helpers */ +static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data); +static int dispatch_write(struct file *file, const char __user * userbuf, + unsigned long count, void *data); +static char *next_cmd(char **cmds); + +/* Module */ +static int experimental; +static char *ibm_thinkpad_ec_found; + +static char* check_dmi_for_ec(void); +static int acpi_ibm_init(void); +static void acpi_ibm_exit(void); + + +/**************************************************************************** + * Subdrivers + */ + +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 ibm_struct ibms[]; +static int set_ibm_param(const char *val, struct kernel_param *kp); +static int ibm_init(struct ibm_struct *ibm); +static void ibm_exit(struct ibm_struct *ibm); + +/* ACPI devices */ +static void dispatch_notify(acpi_handle handle, u32 event, void *data); +static int setup_notify(struct ibm_struct *ibm); +static int ibm_device_add(struct acpi_device *device); +static int register_ibmacpi_subdriver(struct ibm_struct *ibm); + + +/* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_BAY +static int bay_status_supported, bay_eject_supported; +static int bay_status2_supported, bay_eject2_supported; + +static acpi_handle bay_handle, bay_ej_handle; +static acpi_handle bay2_handle, bay2_ej_handle; + +static int bay_init(void); +static void bay_notify(struct ibm_struct *ibm, u32 event); +static int bay_read(char *p); +static int bay_write(char *buf); +#endif /* CONFIG_ACPI_IBM_BAY */ + + +/* + * Beep subdriver + */ + +static acpi_handle beep_handle; + +static int beep_read(char *p); +static int beep_write(char *buf); + + +/* + * Bluetooth subdriver + */ + +static int bluetooth_supported; + +static int bluetooth_init(void); +static int bluetooth_status(void); +static int bluetooth_read(char *p); +static int bluetooth_write(char *buf); + + +/* + * Brightness (backlight) subdriver + */ + +static struct backlight_device *ibm_backlight_device; +static int brightness_offset = 0x31; + +static int brightness_init(void); +static void brightness_exit(void); +static int brightness_get(struct backlight_device *bd); +static int brightness_set(int value); +static int brightness_update_status(struct backlight_device *bd); +static int brightness_read(char *p); +static int brightness_write(char *buf); + + +/* + * CMOS subdriver + */ + +static int cmos_eval(int cmos_cmd); +static int cmos_read(char *p); +static int cmos_write(char *buf); + + +/* + * Dock subdriver + */ + +static acpi_handle pci_handle; +#ifdef CONFIG_ACPI_IBM_DOCK +static acpi_handle dock_handle; + +static void dock_notify(struct ibm_struct *ibm, u32 event); +static int dock_read(char *p); +static int dock_write(char *buf); +#endif /* CONFIG_ACPI_IBM_DOCK */ + + +/* + * EC dump subdriver + */ + +static int ecdump_read(char *p) ; +static int ecdump_write(char *buf); + + +/* + * Fan subdriver + */ + +enum { /* Fan control constants */ + fan_status_offset = 0x2f, /* EC register 0x2f */ + fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) + * 0x84 must be read before 0x85 */ + + IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer + * disengaged */ + IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan + * control */ +}; + +enum fan_status_access_mode { + IBMACPI_FAN_NONE = 0, /* No fan status or control */ + IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ +}; + +enum fan_control_access_mode { + IBMACPI_FAN_WR_NONE = 0, /* No fan control */ + IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ +}; + +enum fan_control_commands { + IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + * and also watchdog cmd */ +}; + +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; +static int fan_control_status_known; +static u8 fan_control_initial_status; +static int fan_watchdog_maxinterval; + +static acpi_handle fans_handle, gfan_handle, sfan_handle; + +static int fan_init(void); +static void fan_exit(void); +static int fan_get_status(u8 *status); +static int fan_get_speed(unsigned int *speed); +static void fan_watchdog_fire(struct work_struct *ignored); +static void fan_watchdog_reset(void); +static int fan_set_level(int level); +static int fan_set_enable(void); +static int fan_set_disable(void); +static int fan_set_speed(int speed); +static int fan_read(char *p); +static int fan_write(char *buf); +static int fan_write_cmd_level(const char *cmd, int *rc); +static int fan_write_cmd_enable(const char *cmd, int *rc); +static int fan_write_cmd_disable(const char *cmd, int *rc); +static int fan_write_cmd_speed(const char *cmd, int *rc); +static int fan_write_cmd_watchdog(const char *cmd, int *rc); + + +/* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static int hotkey_init(void); +static void hotkey_exit(void); +static int hotkey_get(int *status, int *mask); +static int hotkey_set(int status, int mask); +static void hotkey_notify(struct ibm_struct *ibm, u32 event); +static int hotkey_read(char *p); +static int hotkey_write(char *buf); + + +/* + * LED subdriver + */ + +enum led_access_mode { + IBMACPI_LED_NONE = 0, + IBMACPI_LED_570, /* 570 */ + IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + IBMACPI_LED_NEW, /* all others */ +}; + +enum { /* For IBMACPI_LED_OLD */ + IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +}; + +static enum led_access_mode led_supported; +static acpi_handle led_handle; + +static int led_init(void); +static int led_read(char *p); +static int led_write(char *buf); + +/* + * Light (thinklight) subdriver + */ + +static int light_supported; +static int light_status_supported; +static acpi_handle lght_handle, ledb_handle; + +static int light_init(void); +static int light_read(char *p); +static int light_write(char *buf); + + +/* + * Thermal subdriver + */ + +enum thermal_access_mode { + IBMACPI_THERMAL_NONE = 0, /* No thermal support */ + IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ +}; + +#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +struct ibm_thermal_sensors_struct { + s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; +}; + +static int thermal_init(void); +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); +static int thermal_read(char *p); + + +/* + * Video subdriver + */ + +enum video_access_mode { + IBMACPI_VIDEO_NONE = 0, + IBMACPI_VIDEO_570, /* 570 */ + IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + IBMACPI_VIDEO_NEW, /* all others */ +}; + +static enum video_access_mode video_supported; +static int video_orig_autosw; +static acpi_handle vid_handle, vid2_handle; + +static int video_init(void); +static void video_exit(void); +static int video_status(void); +static int video_autosw(void); +static int video_switch(void); +static int video_switch2(int status); +static int video_expand(void); +static int video_read(char *p); +static int video_write(char *buf); + + +/* + * Volume subdriver + */ + +static int volume_offset = 0x30; + +static int volume_read(char *p); +static int volume_write(char *buf); + + +/* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void); +static int wan_status(void); +static int wan_read(char *p); +static int wan_write(char *buf); + + +#endif /* __IBM_ACPI_H */ -- cgit v1.1 From f21f85de4b3b9ad4a671fb19a889c16db2ea38b2 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 29 Mar 2007 01:58:40 -0300 Subject: ACPI: ibm-acpi: rename driver to thinkpad-acpi Rename the ibm-acpi driver to thinkpad-acpi. ThinkPads are not even made by IBM anymore, so it is high time to rename the driver... The name thinkpad-acpi was used sometime ago by a thinkpad-specific hotkey driver by Erik Rigtorp, around the 2.6.8-2.6.10 time frame. The driver apparently never got merged into mainline (it did make some trips through -mm). ibm-acpi was merged soon after, making its debut in 2.6.10. The reuse of the thinkpad-acpi name shouldn't be a problem as far as user confusion goes, as Erik's thinkpad-acpi apparently didn't get widespread use in the Linux ThinkPad community and most hits for thinkpad-acpi in google point to ibm-acpi anyway. Erik, if you read this, please consider the reuse of the thinkpad-acpi name as a compliment to your effort to make ThinkPads more useful to all of us. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/ibm-acpi.txt | 693 ---------- Documentation/thinkpad-acpi.txt | 693 ++++++++++ drivers/misc/Makefile | 2 +- drivers/misc/ibm_acpi.c | 2783 --------------------------------------- drivers/misc/ibm_acpi.h | 437 ------ drivers/misc/thinkpad_acpi.c | 2783 +++++++++++++++++++++++++++++++++++++++ drivers/misc/thinkpad_acpi.h | 437 ++++++ 7 files changed, 3914 insertions(+), 3914 deletions(-) delete mode 100644 Documentation/ibm-acpi.txt create mode 100644 Documentation/thinkpad-acpi.txt delete mode 100644 drivers/misc/ibm_acpi.c delete mode 100644 drivers/misc/ibm_acpi.h create mode 100644 drivers/misc/thinkpad_acpi.c create mode 100644 drivers/misc/thinkpad_acpi.h diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt deleted file mode 100644 index f409f4b..0000000 --- a/Documentation/ibm-acpi.txt +++ /dev/null @@ -1,693 +0,0 @@ - IBM ThinkPad ACPI Extras Driver - - Version 0.13 - 31 December 2006 - - Borislav Deianov - Henrique de Moraes Holschuh - http://ibm-acpi.sf.net/ - - -This is a Linux ACPI driver for the IBM ThinkPad laptops. It supports -various features of these laptops which are accessible through the -ACPI framework but not otherwise fully supported by the generic Linux -ACPI drivers. - - -Status ------- - -The features currently supported are the following (see below for -detailed description): - - - Fn key combinations - - Bluetooth enable and disable - - video output switching, expansion control - - ThinkLight on and off - - limited docking and undocking - - UltraBay eject - - CMOS control - - LED control - - ACPI sounds - - temperature sensors - - Experimental: embedded controller register dump - - LCD brightness control - - Volume control - - Experimental: fan speed, fan enable/disable - - Experimental: WAN enable and disable - -A compatibility table by model and feature is maintained on the web -site, http://ibm-acpi.sf.net/. I appreciate any success or failure -reports, especially if they add to or correct the compatibility table. -Please include the following information in your report: - - - ThinkPad model name - - a copy of your DSDT, from /proc/acpi/dsdt - - which driver features work and which don't - - the observed behavior of non-working features - -Any other comments or patches are also more than welcome. - - -Installation ------------- - -If you are compiling this driver as included in the Linux kernel -sources, simply enable the CONFIG_ACPI_IBM option (Power Management / -ACPI / IBM ThinkPad Laptop Extras). - -Features --------- - -The driver creates the /proc/acpi/ibm directory. There is a file under -that directory for each feature described below. Note that while the -driver is still in the alpha stage, the exact proc file format and -commands supported by the various features is guaranteed to change -frequently. - -Driver version -- /proc/acpi/ibm/driver ---------------------------------------- - -The driver name and version. No commands can be written to this file. - -Hot keys -- /proc/acpi/ibm/hotkey ---------------------------------- - -Without this driver, only the Fn-F4 key (sleep button) generates an -ACPI event. With the driver loaded, the hotkey feature enabled and the -mask set (see below), the various hot keys generate ACPI events in the -following format: - - ibm/hotkey HKEY 00000080 0000xxxx - -The last four digits vary depending on the key combination pressed. -All labeled Fn-Fx key combinations generate distinct events. In -addition, the lid microswitch and some docking station buttons may -also generate such events. - -The following commands can be written to this file: - - echo enable > /proc/acpi/ibm/hotkey -- enable the hot keys feature - echo disable > /proc/acpi/ibm/hotkey -- disable the hot keys feature - echo 0xffff > /proc/acpi/ibm/hotkey -- enable all possible hot keys - echo 0x0000 > /proc/acpi/ibm/hotkey -- disable all possible hot keys - ... any other 4-hex-digit mask ... - echo reset > /proc/acpi/ibm/hotkey -- restore the original mask - -The bit mask allows some control over which hot keys generate ACPI -events. Not all bits in the mask can be modified. Not all bits that -can be modified do anything. Not all hot keys can be individually -controlled by the mask. Most recent ThinkPad models honor the -following bits (assuming the hot keys feature has been enabled): - - key bit behavior when set behavior when unset - - Fn-F3 always generates ACPI event - Fn-F4 always generates ACPI event - Fn-F5 0010 generate ACPI event enable/disable Bluetooth - Fn-F7 0040 generate ACPI event switch LCD and external display - Fn-F8 0080 generate ACPI event expand screen or none - Fn-F9 0100 generate ACPI event none - Fn-F12 always generates ACPI event - -Some models do not support all of the above. For example, the T30 does -not support Fn-F5 and Fn-F9. Other models do not support the mask at -all. On those models, hot keys cannot be controlled individually. - -Note that enabling ACPI events for some keys prevents their default -behavior. For example, if events for Fn-F5 are enabled, that key will -no longer enable/disable Bluetooth by itself. This can still be done -from an acpid handler for the ibm/hotkey event. - -Note also that not all Fn key combinations are supported through -ACPI. For example, on the X40, the brightness, volume and "Access IBM" -buttons do not generate ACPI events even with this driver. They *can* -be used through the "ThinkPad Buttons" utility, see -http://www.nongnu.org/tpb/ - -Bluetooth -- /proc/acpi/ibm/bluetooth -------------------------------------- - -This feature shows the presence and current state of a Bluetooth -device. If Bluetooth is installed, the following commands can be used: - - echo enable > /proc/acpi/ibm/bluetooth - echo disable > /proc/acpi/ibm/bluetooth - -Video output control -- /proc/acpi/ibm/video --------------------------------------------- - -This feature allows control over the devices used for video output - -LCD, CRT or DVI (if available). The following commands are available: - - echo lcd_enable > /proc/acpi/ibm/video - echo lcd_disable > /proc/acpi/ibm/video - echo crt_enable > /proc/acpi/ibm/video - echo crt_disable > /proc/acpi/ibm/video - echo dvi_enable > /proc/acpi/ibm/video - echo dvi_disable > /proc/acpi/ibm/video - echo auto_enable > /proc/acpi/ibm/video - echo auto_disable > /proc/acpi/ibm/video - echo expand_toggle > /proc/acpi/ibm/video - echo video_switch > /proc/acpi/ibm/video - -Each video output device can be enabled or disabled individually. -Reading /proc/acpi/ibm/video shows the status of each device. - -Automatic video switching can be enabled or disabled. When automatic -video switching is enabled, certain events (e.g. opening the lid, -docking or undocking) cause the video output device to change -automatically. While this can be useful, it also causes flickering -and, on the X40, video corruption. By disabling automatic switching, -the flickering or video corruption can be avoided. - -The video_switch command cycles through the available video outputs -(it simulates the behavior of Fn-F7). - -Video expansion can be toggled through this feature. This controls -whether the display is expanded to fill the entire LCD screen when a -mode with less than full resolution is used. Note that the current -video expansion status cannot be determined through this feature. - -Note that on many models (particularly those using Radeon graphics -chips) the X driver configures the video card in a way which prevents -Fn-F7 from working. This also disables the video output switching -features of this driver, as it uses the same ACPI methods as -Fn-F7. Video switching on the console should still work. - -UPDATE: There's now a patch for the X.org Radeon driver which -addresses this issue. Some people are reporting success with the patch -while others are still having problems. For more information: - -https://bugs.freedesktop.org/show_bug.cgi?id=2000 - -ThinkLight control -- /proc/acpi/ibm/light ------------------------------------------- - -The current status of the ThinkLight can be found in this file. A few -models which do not make the status available will show it as -"unknown". The available commands are: - - echo on > /proc/acpi/ibm/light - echo off > /proc/acpi/ibm/light - -Docking / undocking -- /proc/acpi/ibm/dock ------------------------------------------- - -Docking and undocking (e.g. with the X4 UltraBase) requires some -actions to be taken by the operating system to safely make or break -the electrical connections with the dock. - -The docking feature of this driver generates the following ACPI events: - - ibm/dock GDCK 00000003 00000001 -- eject request - ibm/dock GDCK 00000003 00000002 -- undocked - ibm/dock GDCK 00000000 00000003 -- docked - -NOTE: These events will only be generated if the laptop was docked -when originally booted. This is due to the current lack of support for -hot plugging of devices in the Linux ACPI framework. If the laptop was -booted while not in the dock, the following message is shown in the -logs: - - Mar 17 01:42:34 aero kernel: ibm_acpi: dock device not present - -In this case, no dock-related events are generated but the dock and -undock commands described below still work. They can be executed -manually or triggered by Fn key combinations (see the example acpid -configuration files included in the driver tarball package available -on the web site). - -When the eject request button on the dock is pressed, the first event -above is generated. The handler for this event should issue the -following command: - - echo undock > /proc/acpi/ibm/dock - -After the LED on the dock goes off, it is safe to eject the laptop. -Note: if you pressed this key by mistake, go ahead and eject the -laptop, then dock it back in. Otherwise, the dock may not function as -expected. - -When the laptop is docked, the third event above is generated. The -handler for this event should issue the following command to fully -enable the dock: - - echo dock > /proc/acpi/ibm/dock - -The contents of the /proc/acpi/ibm/dock file shows the current status -of the dock, as provided by the ACPI framework. - -The docking support in this driver does not take care of enabling or -disabling any other devices you may have attached to the dock. For -example, a CD drive plugged into the UltraBase needs to be disabled or -enabled separately. See the provided example acpid configuration files -for how this can be accomplished. - -There is no support yet for PCI devices that may be attached to a -docking station, e.g. in the ThinkPad Dock II. The driver currently -does not recognize, enable or disable such devices. This means that -the only docking stations currently supported are the X-series -UltraBase docks and "dumb" port replicators like the Mini Dock (the -latter don't need any ACPI support, actually). - -UltraBay eject -- /proc/acpi/ibm/bay ------------------------------------- - -Inserting or ejecting an UltraBay device requires some actions to be -taken by the operating system to safely make or break the electrical -connections with the device. - -This feature generates the following ACPI events: - - ibm/bay MSTR 00000003 00000000 -- eject request - ibm/bay MSTR 00000001 00000000 -- eject lever inserted - -NOTE: These events will only be generated if the UltraBay was present -when the laptop was originally booted (on the X series, the UltraBay -is in the dock, so it may not be present if the laptop was undocked). -This is due to the current lack of support for hot plugging of devices -in the Linux ACPI framework. If the laptop was booted without the -UltraBay, the following message is shown in the logs: - - Mar 17 01:42:34 aero kernel: ibm_acpi: bay device not present - -In this case, no bay-related events are generated but the eject -command described below still works. It can be executed manually or -triggered by a hot key combination. - -Sliding the eject lever generates the first event shown above. The -handler for this event should take whatever actions are necessary to -shut down the device in the UltraBay (e.g. call idectl), then issue -the following command: - - echo eject > /proc/acpi/ibm/bay - -After the LED on the UltraBay goes off, it is safe to pull out the -device. - -When the eject lever is inserted, the second event above is -generated. The handler for this event should take whatever actions are -necessary to enable the UltraBay device (e.g. call idectl). - -The contents of the /proc/acpi/ibm/bay file shows the current status -of the UltraBay, as provided by the ACPI framework. - -EXPERIMENTAL warm eject support on the 600e/x, A22p and A3x (To use -this feature, you need to supply the experimental=1 parameter when -loading the module): - -These models do not have a button near the UltraBay device to request -a hot eject but rather require the laptop to be put to sleep -(suspend-to-ram) before the bay device is ejected or inserted). -The sequence of steps to eject the device is as follows: - - echo eject > /proc/acpi/ibm/bay - put the ThinkPad to sleep - remove the drive - resume from sleep - cat /proc/acpi/ibm/bay should show that the drive was removed - -On the A3x, both the UltraBay 2000 and UltraBay Plus devices are -supported. Use "eject2" instead of "eject" for the second bay. - -Note: the UltraBay eject support on the 600e/x, A22p and A3x is -EXPERIMENTAL and may not work as expected. USE WITH CAUTION! - -CMOS control -- /proc/acpi/ibm/cmos ------------------------------------ - -This feature is used internally by the ACPI firmware to control the -ThinkLight on most newer ThinkPad models. It may also control LCD -brightness, sounds volume and more, but only on some models. - -The commands are non-negative integer numbers: - - echo 0 >/proc/acpi/ibm/cmos - echo 1 >/proc/acpi/ibm/cmos - echo 2 >/proc/acpi/ibm/cmos - ... - -The range of valid numbers is 0 to 21, but not all have an effect and -the behavior varies from model to model. Here is the behavior on the -X40 (tpb is the ThinkPad Buttons utility): - - 0 - no effect but tpb reports "Volume down" - 1 - no effect but tpb reports "Volume up" - 2 - no effect but tpb reports "Mute on" - 3 - simulate pressing the "Access IBM" button - 4 - LCD brightness up - 5 - LCD brightness down - 11 - toggle screen expansion - 12 - ThinkLight on - 13 - ThinkLight off - 14 - no effect but tpb reports ThinkLight status change - -LED control -- /proc/acpi/ibm/led ---------------------------------- - -Some of the LED indicators can be controlled through this feature. The -available commands are: - - echo ' on' >/proc/acpi/ibm/led - echo ' off' >/proc/acpi/ibm/led - echo ' blink' >/proc/acpi/ibm/led - -The range is 0 to 7. The set of LEDs that can be -controlled varies from model to model. Here is the mapping on the X40: - - 0 - power - 1 - battery (orange) - 2 - battery (green) - 3 - UltraBase - 4 - UltraBay - 7 - standby - -All of the above can be turned on and off and can be made to blink. - -ACPI sounds -- /proc/acpi/ibm/beep ----------------------------------- - -The BEEP method is used internally by the ACPI firmware to provide -audible alerts in various situations. This feature allows the same -sounds to be triggered manually. - -The commands are non-negative integer numbers: - - echo >/proc/acpi/ibm/beep - -The valid range is 0 to 17. Not all numbers trigger sounds -and the sounds vary from model to model. Here is the behavior on the -X40: - - 0 - stop a sound in progress (but use 17 to stop 16) - 2 - two beeps, pause, third beep ("low battery") - 3 - single beep - 4 - high, followed by low-pitched beep ("unable") - 5 - single beep - 6 - very high, followed by high-pitched beep ("AC/DC") - 7 - high-pitched beep - 9 - three short beeps - 10 - very long beep - 12 - low-pitched beep - 15 - three high-pitched beeps repeating constantly, stop with 0 - 16 - one medium-pitched beep repeating constantly, stop with 17 - 17 - stop 16 - -Temperature sensors -- /proc/acpi/ibm/thermal ---------------------------------------------- - -Most ThinkPads include six or more separate temperature sensors but -only expose the CPU temperature through the standard ACPI methods. -This feature shows readings from up to eight different sensors on older -ThinkPads, and it has experimental support for up to sixteen different -sensors on newer ThinkPads. Readings from sensors that are not available -return -128. - -No commands can be written to this file. - -EXPERIMENTAL: The 16-sensors feature is marked EXPERIMENTAL because the -implementation directly accesses hardware registers and may not work as -expected. USE WITH CAUTION! To use this feature, you need to supply the -experimental=1 parameter when loading the module. When EXPERIMENTAL -mode is enabled, reading the first 8 sensors on newer ThinkPads will -also use an new experimental thermal sensor access mode. - -For example, on the X40, a typical output may be: -temperatures: 42 42 45 41 36 -128 33 -128 - -EXPERIMENTAL: On the T43/p, a typical output may be: -temperatures: 48 48 36 52 38 -128 31 -128 48 52 48 -128 -128 -128 -128 -128 - -The mapping of thermal sensors to physical locations varies depending on -system-board model (and thus, on ThinkPad model). - -http://thinkwiki.org/wiki/Thermal_Sensors is a public wiki page that -tries to track down these locations for various models. - -Most (newer?) models seem to follow this pattern: - -1: CPU -2: (depends on model) -3: (depends on model) -4: GPU -5: Main battery: main sensor -6: Bay battery: main sensor -7: Main battery: secondary sensor -8: Bay battery: secondary sensor -9-15: (depends on model) - -For the R51 (source: Thomas Gruber): -2: Mini-PCI -3: Internal HDD - -For the T43, T43/p (source: Shmidoax/Thinkwiki.org) -http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_T43.2C_T43p -2: System board, left side (near PCMCIA slot), reported as HDAPS temp -3: PCMCIA slot -9: MCH (northbridge) to DRAM Bus -10: ICH (southbridge), under Mini-PCI card, under touchpad -11: Power regulator, underside of system board, below F2 key - -The A31 has a very atypical layout for the thermal sensors -(source: Milos Popovic, http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_A31) -1: CPU -2: Main Battery: main sensor -3: Power Converter -4: Bay Battery: main sensor -5: MCH (northbridge) -6: PCMCIA/ambient -7: Main Battery: secondary sensor -8: Bay Battery: secondary sensor - - -EXPERIMENTAL: Embedded controller register dump -- /proc/acpi/ibm/ecdump ------------------------------------------------------------------------- - -This feature is marked EXPERIMENTAL because the implementation -directly accesses hardware registers and may not work as expected. USE -WITH CAUTION! To use this feature, you need to supply the -experimental=1 parameter when loading the module. - -This feature dumps the values of 256 embedded controller -registers. Values which have changed since the last time the registers -were dumped are marked with a star: - -[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump -EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f -EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 -EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 -EC 0x20: 00 00 00 00 00 00 00 00 00 00 00 03 43 00 00 80 -EC 0x30: 01 07 1a 00 30 04 00 00 *85 00 00 10 00 50 00 00 -EC 0x40: 00 00 00 00 00 00 14 01 00 04 00 00 00 00 00 00 -EC 0x50: 00 c0 02 0d 00 01 01 02 02 03 03 03 03 *bc *02 *bc -EC 0x60: *02 *bc *02 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0x70: 00 00 00 00 00 12 30 40 *24 *26 *2c *27 *20 80 *1f 80 -EC 0x80: 00 00 00 06 *37 *0e 03 00 00 00 0e 07 00 00 00 00 -EC 0x90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xa0: *ff 09 ff 09 ff ff *64 00 *00 *00 *a2 41 *ff *ff *e0 00 -EC 0xb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xd0: 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xe0: 00 00 00 00 00 00 00 00 11 20 49 04 24 06 55 03 -EC 0xf0: 31 55 48 54 35 38 57 57 08 2f 45 73 07 65 6c 1a - -This feature can be used to determine the register holding the fan -speed on some models. To do that, do the following: - - - make sure the battery is fully charged - - make sure the fan is running - - run 'cat /proc/acpi/ibm/ecdump' several times, once per second or so - -The first step makes sure various charging-related values don't -vary. The second ensures that the fan-related values do vary, since -the fan speed fluctuates a bit. The third will (hopefully) mark the -fan register with a star: - -[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump -EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f -EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 -EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 -EC 0x20: 00 00 00 00 00 00 00 00 00 00 00 03 43 00 00 80 -EC 0x30: 01 07 1a 00 30 04 00 00 85 00 00 10 00 50 00 00 -EC 0x40: 00 00 00 00 00 00 14 01 00 04 00 00 00 00 00 00 -EC 0x50: 00 c0 02 0d 00 01 01 02 02 03 03 03 03 bc 02 bc -EC 0x60: 02 bc 02 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0x70: 00 00 00 00 00 12 30 40 24 27 2c 27 21 80 1f 80 -EC 0x80: 00 00 00 06 *be 0d 03 00 00 00 0e 07 00 00 00 00 -EC 0x90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xa0: ff 09 ff 09 ff ff 64 00 00 00 a2 41 ff ff e0 00 -EC 0xb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xd0: 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -EC 0xe0: 00 00 00 00 00 00 00 00 11 20 49 04 24 06 55 03 -EC 0xf0: 31 55 48 54 35 38 57 57 08 2f 45 73 07 65 6c 1a - -Another set of values that varies often is the temperature -readings. Since temperatures don't change vary fast, you can take -several quick dumps to eliminate them. - -You can use a similar method to figure out the meaning of other -embedded controller registers - e.g. make sure nothing else changes -except the charging or discharging battery to determine which -registers contain the current battery capacity, etc. If you experiment -with this, do send me your results (including some complete dumps with -a description of the conditions when they were taken.) - -LCD brightness control -- /proc/acpi/ibm/brightness ---------------------------------------------------- - -This feature allows software control of the LCD brightness on ThinkPad -models which don't have a hardware brightness slider. The available -commands are: - - echo up >/proc/acpi/ibm/brightness - echo down >/proc/acpi/ibm/brightness - echo 'level ' >/proc/acpi/ibm/brightness - -The number range is 0 to 7, although not all of them may be -distinct. The current brightness level is shown in the file. - -Volume control -- /proc/acpi/ibm/volume ---------------------------------------- - -This feature allows volume control on ThinkPad models which don't have -a hardware volume knob. The available commands are: - - echo up >/proc/acpi/ibm/volume - echo down >/proc/acpi/ibm/volume - echo mute >/proc/acpi/ibm/volume - echo 'level ' >/proc/acpi/ibm/volume - -The number range is 0 to 15 although not all of them may be -distinct. The unmute the volume after the mute command, use either the -up or down command (the level command will not unmute the volume). -The current volume level and mute state is shown in the file. - -EXPERIMENTAL: fan speed, fan enable/disable -- /proc/acpi/ibm/fan ------------------------------------------------------------------ - -This feature is marked EXPERIMENTAL because the implementation -directly accesses hardware registers and may not work as expected. USE -WITH CAUTION! To use this feature, you need to supply the -experimental=1 parameter when loading the module. - -This feature attempts to show the current fan speed, control mode and -other fan data that might be available. The speed is read directly -from the hardware registers of the embedded controller. This is known -to work on later R, T and X series ThinkPads but may show a bogus -value on other models. - -Most ThinkPad fans work in "levels". Level 0 stops the fan. The higher -the level, the higher the fan speed, although adjacent levels often map -to the same fan speed. 7 is the highest level, where the fan reaches -the maximum recommended speed. Level "auto" means the EC changes the -fan level according to some internal algorithm, usually based on -readings from the thermal sensors. Level "disengaged" means the EC -disables the speed-locked closed-loop fan control, and drives the fan as -fast as it can go, which might exceed hardware limits, so use this level -with caution. - -The fan usually ramps up or down slowly from one speed to another, -and it is normal for the EC to take several seconds to react to fan -commands. - -The fan may be enabled or disabled with the following commands: - - echo enable >/proc/acpi/ibm/fan - echo disable >/proc/acpi/ibm/fan - -Placing a fan on level 0 is the same as disabling it. Enabling a fan -will try to place it in a safe level if it is too slow or disabled. - -WARNING WARNING WARNING: do not leave the fan disabled unless you are -monitoring all of the temperature sensor readings and you are ready to -enable it if necessary to avoid overheating. - -An enabled fan in level "auto" may stop spinning if the EC decides the -ThinkPad is cool enough and doesn't need the extra airflow. This is -normal, and the EC will spin the fan up if the varios thermal readings -rise too much. - -On the X40, this seems to depend on the CPU and HDD temperatures. -Specifically, the fan is turned on when either the CPU temperature -climbs to 56 degrees or the HDD temperature climbs to 46 degrees. The -fan is turned off when the CPU temperature drops to 49 degrees and the -HDD temperature drops to 41 degrees. These thresholds cannot -currently be controlled. - -The fan level can be controlled with the command: - - echo 'level ' > /proc/acpi/ibm/thermal - -Where is an integer from 0 to 7, or one of the words "auto" -or "disengaged" (without the quotes). Not all ThinkPads support the -"auto" and "disengaged" levels. - -On the X31 and X40 (and ONLY on those models), the fan speed can be -controlled to a certain degree. Once the fan is running, it can be -forced to run faster or slower with the following command: - - echo 'speed ' > /proc/acpi/ibm/thermal - -The sustainable range of fan speeds on the X40 appears to be from -about 3700 to about 7350. Values outside this range either do not have -any effect or the fan speed eventually settles somewhere in that -range. The fan cannot be stopped or started with this command. - -The ThinkPad's ACPI DSDT code will reprogram the fan on its own when -certain conditions are met. It will override any fan programming done -through ibm-acpi. - -The ibm-acpi kernel driver can be programmed to revert the fan level -to a safe setting if userspace does not issue one of the fan commands: -"enable", "disable", "level" or "watchdog" within a configurable -ammount of time. To do this, use the "watchdog" command. - - echo 'watchdog ' > /proc/acpi/ibm/fan - -Interval is the ammount of time in seconds to wait for one of the -above mentioned fan commands before reseting the fan level to a safe -one. If set to zero, the watchdog is disabled (default). When the -watchdog timer runs out, it does the exact equivalent of the "enable" -fan command. - -Note that the watchdog timer stops after it enables the fan. It will -be rearmed again automatically (using the same interval) when one of -the above mentioned fan commands is received. The fan watchdog is, -therefore, not suitable to protect against fan mode changes made -through means other than the "enable", "disable", and "level" fan -commands. - -EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan ---------------------------------------- - -This feature is marked EXPERIMENTAL because the implementation -directly accesses hardware registers and may not work as expected. USE -WITH CAUTION! To use this feature, you need to supply the -experimental=1 parameter when loading the module. - -This feature shows the presence and current state of a WAN (Sierra -Wireless EV-DO) device. If WAN is installed, the following commands can -be used: - - echo enable > /proc/acpi/ibm/wan - echo disable > /proc/acpi/ibm/wan - -It was tested on a Lenovo Thinkpad X60. It should probably work on other -Thinkpad models which come with this module installed. - -Multiple Commands, Module Parameters ------------------------------------- - -Multiple commands can be written to the proc files in one shot by -separating them with commas, for example: - - echo enable,0xffff > /proc/acpi/ibm/hotkey - echo lcd_disable,crt_enable > /proc/acpi/ibm/video - -Commands can also be specified when loading the ibm_acpi module, for -example: - - modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable - diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt new file mode 100644 index 0000000..f409f4b --- /dev/null +++ b/Documentation/thinkpad-acpi.txt @@ -0,0 +1,693 @@ + IBM ThinkPad ACPI Extras Driver + + Version 0.13 + 31 December 2006 + + Borislav Deianov + Henrique de Moraes Holschuh + http://ibm-acpi.sf.net/ + + +This is a Linux ACPI driver for the IBM ThinkPad laptops. It supports +various features of these laptops which are accessible through the +ACPI framework but not otherwise fully supported by the generic Linux +ACPI drivers. + + +Status +------ + +The features currently supported are the following (see below for +detailed description): + + - Fn key combinations + - Bluetooth enable and disable + - video output switching, expansion control + - ThinkLight on and off + - limited docking and undocking + - UltraBay eject + - CMOS control + - LED control + - ACPI sounds + - temperature sensors + - Experimental: embedded controller register dump + - LCD brightness control + - Volume control + - Experimental: fan speed, fan enable/disable + - Experimental: WAN enable and disable + +A compatibility table by model and feature is maintained on the web +site, http://ibm-acpi.sf.net/. I appreciate any success or failure +reports, especially if they add to or correct the compatibility table. +Please include the following information in your report: + + - ThinkPad model name + - a copy of your DSDT, from /proc/acpi/dsdt + - which driver features work and which don't + - the observed behavior of non-working features + +Any other comments or patches are also more than welcome. + + +Installation +------------ + +If you are compiling this driver as included in the Linux kernel +sources, simply enable the CONFIG_ACPI_IBM option (Power Management / +ACPI / IBM ThinkPad Laptop Extras). + +Features +-------- + +The driver creates the /proc/acpi/ibm directory. There is a file under +that directory for each feature described below. Note that while the +driver is still in the alpha stage, the exact proc file format and +commands supported by the various features is guaranteed to change +frequently. + +Driver version -- /proc/acpi/ibm/driver +--------------------------------------- + +The driver name and version. No commands can be written to this file. + +Hot keys -- /proc/acpi/ibm/hotkey +--------------------------------- + +Without this driver, only the Fn-F4 key (sleep button) generates an +ACPI event. With the driver loaded, the hotkey feature enabled and the +mask set (see below), the various hot keys generate ACPI events in the +following format: + + ibm/hotkey HKEY 00000080 0000xxxx + +The last four digits vary depending on the key combination pressed. +All labeled Fn-Fx key combinations generate distinct events. In +addition, the lid microswitch and some docking station buttons may +also generate such events. + +The following commands can be written to this file: + + echo enable > /proc/acpi/ibm/hotkey -- enable the hot keys feature + echo disable > /proc/acpi/ibm/hotkey -- disable the hot keys feature + echo 0xffff > /proc/acpi/ibm/hotkey -- enable all possible hot keys + echo 0x0000 > /proc/acpi/ibm/hotkey -- disable all possible hot keys + ... any other 4-hex-digit mask ... + echo reset > /proc/acpi/ibm/hotkey -- restore the original mask + +The bit mask allows some control over which hot keys generate ACPI +events. Not all bits in the mask can be modified. Not all bits that +can be modified do anything. Not all hot keys can be individually +controlled by the mask. Most recent ThinkPad models honor the +following bits (assuming the hot keys feature has been enabled): + + key bit behavior when set behavior when unset + + Fn-F3 always generates ACPI event + Fn-F4 always generates ACPI event + Fn-F5 0010 generate ACPI event enable/disable Bluetooth + Fn-F7 0040 generate ACPI event switch LCD and external display + Fn-F8 0080 generate ACPI event expand screen or none + Fn-F9 0100 generate ACPI event none + Fn-F12 always generates ACPI event + +Some models do not support all of the above. For example, the T30 does +not support Fn-F5 and Fn-F9. Other models do not support the mask at +all. On those models, hot keys cannot be controlled individually. + +Note that enabling ACPI events for some keys prevents their default +behavior. For example, if events for Fn-F5 are enabled, that key will +no longer enable/disable Bluetooth by itself. This can still be done +from an acpid handler for the ibm/hotkey event. + +Note also that not all Fn key combinations are supported through +ACPI. For example, on the X40, the brightness, volume and "Access IBM" +buttons do not generate ACPI events even with this driver. They *can* +be used through the "ThinkPad Buttons" utility, see +http://www.nongnu.org/tpb/ + +Bluetooth -- /proc/acpi/ibm/bluetooth +------------------------------------- + +This feature shows the presence and current state of a Bluetooth +device. If Bluetooth is installed, the following commands can be used: + + echo enable > /proc/acpi/ibm/bluetooth + echo disable > /proc/acpi/ibm/bluetooth + +Video output control -- /proc/acpi/ibm/video +-------------------------------------------- + +This feature allows control over the devices used for video output - +LCD, CRT or DVI (if available). The following commands are available: + + echo lcd_enable > /proc/acpi/ibm/video + echo lcd_disable > /proc/acpi/ibm/video + echo crt_enable > /proc/acpi/ibm/video + echo crt_disable > /proc/acpi/ibm/video + echo dvi_enable > /proc/acpi/ibm/video + echo dvi_disable > /proc/acpi/ibm/video + echo auto_enable > /proc/acpi/ibm/video + echo auto_disable > /proc/acpi/ibm/video + echo expand_toggle > /proc/acpi/ibm/video + echo video_switch > /proc/acpi/ibm/video + +Each video output device can be enabled or disabled individually. +Reading /proc/acpi/ibm/video shows the status of each device. + +Automatic video switching can be enabled or disabled. When automatic +video switching is enabled, certain events (e.g. opening the lid, +docking or undocking) cause the video output device to change +automatically. While this can be useful, it also causes flickering +and, on the X40, video corruption. By disabling automatic switching, +the flickering or video corruption can be avoided. + +The video_switch command cycles through the available video outputs +(it simulates the behavior of Fn-F7). + +Video expansion can be toggled through this feature. This controls +whether the display is expanded to fill the entire LCD screen when a +mode with less than full resolution is used. Note that the current +video expansion status cannot be determined through this feature. + +Note that on many models (particularly those using Radeon graphics +chips) the X driver configures the video card in a way which prevents +Fn-F7 from working. This also disables the video output switching +features of this driver, as it uses the same ACPI methods as +Fn-F7. Video switching on the console should still work. + +UPDATE: There's now a patch for the X.org Radeon driver which +addresses this issue. Some people are reporting success with the patch +while others are still having problems. For more information: + +https://bugs.freedesktop.org/show_bug.cgi?id=2000 + +ThinkLight control -- /proc/acpi/ibm/light +------------------------------------------ + +The current status of the ThinkLight can be found in this file. A few +models which do not make the status available will show it as +"unknown". The available commands are: + + echo on > /proc/acpi/ibm/light + echo off > /proc/acpi/ibm/light + +Docking / undocking -- /proc/acpi/ibm/dock +------------------------------------------ + +Docking and undocking (e.g. with the X4 UltraBase) requires some +actions to be taken by the operating system to safely make or break +the electrical connections with the dock. + +The docking feature of this driver generates the following ACPI events: + + ibm/dock GDCK 00000003 00000001 -- eject request + ibm/dock GDCK 00000003 00000002 -- undocked + ibm/dock GDCK 00000000 00000003 -- docked + +NOTE: These events will only be generated if the laptop was docked +when originally booted. This is due to the current lack of support for +hot plugging of devices in the Linux ACPI framework. If the laptop was +booted while not in the dock, the following message is shown in the +logs: + + Mar 17 01:42:34 aero kernel: ibm_acpi: dock device not present + +In this case, no dock-related events are generated but the dock and +undock commands described below still work. They can be executed +manually or triggered by Fn key combinations (see the example acpid +configuration files included in the driver tarball package available +on the web site). + +When the eject request button on the dock is pressed, the first event +above is generated. The handler for this event should issue the +following command: + + echo undock > /proc/acpi/ibm/dock + +After the LED on the dock goes off, it is safe to eject the laptop. +Note: if you pressed this key by mistake, go ahead and eject the +laptop, then dock it back in. Otherwise, the dock may not function as +expected. + +When the laptop is docked, the third event above is generated. The +handler for this event should issue the following command to fully +enable the dock: + + echo dock > /proc/acpi/ibm/dock + +The contents of the /proc/acpi/ibm/dock file shows the current status +of the dock, as provided by the ACPI framework. + +The docking support in this driver does not take care of enabling or +disabling any other devices you may have attached to the dock. For +example, a CD drive plugged into the UltraBase needs to be disabled or +enabled separately. See the provided example acpid configuration files +for how this can be accomplished. + +There is no support yet for PCI devices that may be attached to a +docking station, e.g. in the ThinkPad Dock II. The driver currently +does not recognize, enable or disable such devices. This means that +the only docking stations currently supported are the X-series +UltraBase docks and "dumb" port replicators like the Mini Dock (the +latter don't need any ACPI support, actually). + +UltraBay eject -- /proc/acpi/ibm/bay +------------------------------------ + +Inserting or ejecting an UltraBay device requires some actions to be +taken by the operating system to safely make or break the electrical +connections with the device. + +This feature generates the following ACPI events: + + ibm/bay MSTR 00000003 00000000 -- eject request + ibm/bay MSTR 00000001 00000000 -- eject lever inserted + +NOTE: These events will only be generated if the UltraBay was present +when the laptop was originally booted (on the X series, the UltraBay +is in the dock, so it may not be present if the laptop was undocked). +This is due to the current lack of support for hot plugging of devices +in the Linux ACPI framework. If the laptop was booted without the +UltraBay, the following message is shown in the logs: + + Mar 17 01:42:34 aero kernel: ibm_acpi: bay device not present + +In this case, no bay-related events are generated but the eject +command described below still works. It can be executed manually or +triggered by a hot key combination. + +Sliding the eject lever generates the first event shown above. The +handler for this event should take whatever actions are necessary to +shut down the device in the UltraBay (e.g. call idectl), then issue +the following command: + + echo eject > /proc/acpi/ibm/bay + +After the LED on the UltraBay goes off, it is safe to pull out the +device. + +When the eject lever is inserted, the second event above is +generated. The handler for this event should take whatever actions are +necessary to enable the UltraBay device (e.g. call idectl). + +The contents of the /proc/acpi/ibm/bay file shows the current status +of the UltraBay, as provided by the ACPI framework. + +EXPERIMENTAL warm eject support on the 600e/x, A22p and A3x (To use +this feature, you need to supply the experimental=1 parameter when +loading the module): + +These models do not have a button near the UltraBay device to request +a hot eject but rather require the laptop to be put to sleep +(suspend-to-ram) before the bay device is ejected or inserted). +The sequence of steps to eject the device is as follows: + + echo eject > /proc/acpi/ibm/bay + put the ThinkPad to sleep + remove the drive + resume from sleep + cat /proc/acpi/ibm/bay should show that the drive was removed + +On the A3x, both the UltraBay 2000 and UltraBay Plus devices are +supported. Use "eject2" instead of "eject" for the second bay. + +Note: the UltraBay eject support on the 600e/x, A22p and A3x is +EXPERIMENTAL and may not work as expected. USE WITH CAUTION! + +CMOS control -- /proc/acpi/ibm/cmos +----------------------------------- + +This feature is used internally by the ACPI firmware to control the +ThinkLight on most newer ThinkPad models. It may also control LCD +brightness, sounds volume and more, but only on some models. + +The commands are non-negative integer numbers: + + echo 0 >/proc/acpi/ibm/cmos + echo 1 >/proc/acpi/ibm/cmos + echo 2 >/proc/acpi/ibm/cmos + ... + +The range of valid numbers is 0 to 21, but not all have an effect and +the behavior varies from model to model. Here is the behavior on the +X40 (tpb is the ThinkPad Buttons utility): + + 0 - no effect but tpb reports "Volume down" + 1 - no effect but tpb reports "Volume up" + 2 - no effect but tpb reports "Mute on" + 3 - simulate pressing the "Access IBM" button + 4 - LCD brightness up + 5 - LCD brightness down + 11 - toggle screen expansion + 12 - ThinkLight on + 13 - ThinkLight off + 14 - no effect but tpb reports ThinkLight status change + +LED control -- /proc/acpi/ibm/led +--------------------------------- + +Some of the LED indicators can be controlled through this feature. The +available commands are: + + echo ' on' >/proc/acpi/ibm/led + echo ' off' >/proc/acpi/ibm/led + echo ' blink' >/proc/acpi/ibm/led + +The range is 0 to 7. The set of LEDs that can be +controlled varies from model to model. Here is the mapping on the X40: + + 0 - power + 1 - battery (orange) + 2 - battery (green) + 3 - UltraBase + 4 - UltraBay + 7 - standby + +All of the above can be turned on and off and can be made to blink. + +ACPI sounds -- /proc/acpi/ibm/beep +---------------------------------- + +The BEEP method is used internally by the ACPI firmware to provide +audible alerts in various situations. This feature allows the same +sounds to be triggered manually. + +The commands are non-negative integer numbers: + + echo >/proc/acpi/ibm/beep + +The valid range is 0 to 17. Not all numbers trigger sounds +and the sounds vary from model to model. Here is the behavior on the +X40: + + 0 - stop a sound in progress (but use 17 to stop 16) + 2 - two beeps, pause, third beep ("low battery") + 3 - single beep + 4 - high, followed by low-pitched beep ("unable") + 5 - single beep + 6 - very high, followed by high-pitched beep ("AC/DC") + 7 - high-pitched beep + 9 - three short beeps + 10 - very long beep + 12 - low-pitched beep + 15 - three high-pitched beeps repeating constantly, stop with 0 + 16 - one medium-pitched beep repeating constantly, stop with 17 + 17 - stop 16 + +Temperature sensors -- /proc/acpi/ibm/thermal +--------------------------------------------- + +Most ThinkPads include six or more separate temperature sensors but +only expose the CPU temperature through the standard ACPI methods. +This feature shows readings from up to eight different sensors on older +ThinkPads, and it has experimental support for up to sixteen different +sensors on newer ThinkPads. Readings from sensors that are not available +return -128. + +No commands can be written to this file. + +EXPERIMENTAL: The 16-sensors feature is marked EXPERIMENTAL because the +implementation directly accesses hardware registers and may not work as +expected. USE WITH CAUTION! To use this feature, you need to supply the +experimental=1 parameter when loading the module. When EXPERIMENTAL +mode is enabled, reading the first 8 sensors on newer ThinkPads will +also use an new experimental thermal sensor access mode. + +For example, on the X40, a typical output may be: +temperatures: 42 42 45 41 36 -128 33 -128 + +EXPERIMENTAL: On the T43/p, a typical output may be: +temperatures: 48 48 36 52 38 -128 31 -128 48 52 48 -128 -128 -128 -128 -128 + +The mapping of thermal sensors to physical locations varies depending on +system-board model (and thus, on ThinkPad model). + +http://thinkwiki.org/wiki/Thermal_Sensors is a public wiki page that +tries to track down these locations for various models. + +Most (newer?) models seem to follow this pattern: + +1: CPU +2: (depends on model) +3: (depends on model) +4: GPU +5: Main battery: main sensor +6: Bay battery: main sensor +7: Main battery: secondary sensor +8: Bay battery: secondary sensor +9-15: (depends on model) + +For the R51 (source: Thomas Gruber): +2: Mini-PCI +3: Internal HDD + +For the T43, T43/p (source: Shmidoax/Thinkwiki.org) +http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_T43.2C_T43p +2: System board, left side (near PCMCIA slot), reported as HDAPS temp +3: PCMCIA slot +9: MCH (northbridge) to DRAM Bus +10: ICH (southbridge), under Mini-PCI card, under touchpad +11: Power regulator, underside of system board, below F2 key + +The A31 has a very atypical layout for the thermal sensors +(source: Milos Popovic, http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_A31) +1: CPU +2: Main Battery: main sensor +3: Power Converter +4: Bay Battery: main sensor +5: MCH (northbridge) +6: PCMCIA/ambient +7: Main Battery: secondary sensor +8: Bay Battery: secondary sensor + + +EXPERIMENTAL: Embedded controller register dump -- /proc/acpi/ibm/ecdump +------------------------------------------------------------------------ + +This feature is marked EXPERIMENTAL because the implementation +directly accesses hardware registers and may not work as expected. USE +WITH CAUTION! To use this feature, you need to supply the +experimental=1 parameter when loading the module. + +This feature dumps the values of 256 embedded controller +registers. Values which have changed since the last time the registers +were dumped are marked with a star: + +[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump +EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f +EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 +EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 +EC 0x20: 00 00 00 00 00 00 00 00 00 00 00 03 43 00 00 80 +EC 0x30: 01 07 1a 00 30 04 00 00 *85 00 00 10 00 50 00 00 +EC 0x40: 00 00 00 00 00 00 14 01 00 04 00 00 00 00 00 00 +EC 0x50: 00 c0 02 0d 00 01 01 02 02 03 03 03 03 *bc *02 *bc +EC 0x60: *02 *bc *02 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0x70: 00 00 00 00 00 12 30 40 *24 *26 *2c *27 *20 80 *1f 80 +EC 0x80: 00 00 00 06 *37 *0e 03 00 00 00 0e 07 00 00 00 00 +EC 0x90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xa0: *ff 09 ff 09 ff ff *64 00 *00 *00 *a2 41 *ff *ff *e0 00 +EC 0xb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xd0: 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xe0: 00 00 00 00 00 00 00 00 11 20 49 04 24 06 55 03 +EC 0xf0: 31 55 48 54 35 38 57 57 08 2f 45 73 07 65 6c 1a + +This feature can be used to determine the register holding the fan +speed on some models. To do that, do the following: + + - make sure the battery is fully charged + - make sure the fan is running + - run 'cat /proc/acpi/ibm/ecdump' several times, once per second or so + +The first step makes sure various charging-related values don't +vary. The second ensures that the fan-related values do vary, since +the fan speed fluctuates a bit. The third will (hopefully) mark the +fan register with a star: + +[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump +EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f +EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 +EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 +EC 0x20: 00 00 00 00 00 00 00 00 00 00 00 03 43 00 00 80 +EC 0x30: 01 07 1a 00 30 04 00 00 85 00 00 10 00 50 00 00 +EC 0x40: 00 00 00 00 00 00 14 01 00 04 00 00 00 00 00 00 +EC 0x50: 00 c0 02 0d 00 01 01 02 02 03 03 03 03 bc 02 bc +EC 0x60: 02 bc 02 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0x70: 00 00 00 00 00 12 30 40 24 27 2c 27 21 80 1f 80 +EC 0x80: 00 00 00 06 *be 0d 03 00 00 00 0e 07 00 00 00 00 +EC 0x90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xa0: ff 09 ff 09 ff ff 64 00 00 00 a2 41 ff ff e0 00 +EC 0xb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xd0: 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +EC 0xe0: 00 00 00 00 00 00 00 00 11 20 49 04 24 06 55 03 +EC 0xf0: 31 55 48 54 35 38 57 57 08 2f 45 73 07 65 6c 1a + +Another set of values that varies often is the temperature +readings. Since temperatures don't change vary fast, you can take +several quick dumps to eliminate them. + +You can use a similar method to figure out the meaning of other +embedded controller registers - e.g. make sure nothing else changes +except the charging or discharging battery to determine which +registers contain the current battery capacity, etc. If you experiment +with this, do send me your results (including some complete dumps with +a description of the conditions when they were taken.) + +LCD brightness control -- /proc/acpi/ibm/brightness +--------------------------------------------------- + +This feature allows software control of the LCD brightness on ThinkPad +models which don't have a hardware brightness slider. The available +commands are: + + echo up >/proc/acpi/ibm/brightness + echo down >/proc/acpi/ibm/brightness + echo 'level ' >/proc/acpi/ibm/brightness + +The number range is 0 to 7, although not all of them may be +distinct. The current brightness level is shown in the file. + +Volume control -- /proc/acpi/ibm/volume +--------------------------------------- + +This feature allows volume control on ThinkPad models which don't have +a hardware volume knob. The available commands are: + + echo up >/proc/acpi/ibm/volume + echo down >/proc/acpi/ibm/volume + echo mute >/proc/acpi/ibm/volume + echo 'level ' >/proc/acpi/ibm/volume + +The number range is 0 to 15 although not all of them may be +distinct. The unmute the volume after the mute command, use either the +up or down command (the level command will not unmute the volume). +The current volume level and mute state is shown in the file. + +EXPERIMENTAL: fan speed, fan enable/disable -- /proc/acpi/ibm/fan +----------------------------------------------------------------- + +This feature is marked EXPERIMENTAL because the implementation +directly accesses hardware registers and may not work as expected. USE +WITH CAUTION! To use this feature, you need to supply the +experimental=1 parameter when loading the module. + +This feature attempts to show the current fan speed, control mode and +other fan data that might be available. The speed is read directly +from the hardware registers of the embedded controller. This is known +to work on later R, T and X series ThinkPads but may show a bogus +value on other models. + +Most ThinkPad fans work in "levels". Level 0 stops the fan. The higher +the level, the higher the fan speed, although adjacent levels often map +to the same fan speed. 7 is the highest level, where the fan reaches +the maximum recommended speed. Level "auto" means the EC changes the +fan level according to some internal algorithm, usually based on +readings from the thermal sensors. Level "disengaged" means the EC +disables the speed-locked closed-loop fan control, and drives the fan as +fast as it can go, which might exceed hardware limits, so use this level +with caution. + +The fan usually ramps up or down slowly from one speed to another, +and it is normal for the EC to take several seconds to react to fan +commands. + +The fan may be enabled or disabled with the following commands: + + echo enable >/proc/acpi/ibm/fan + echo disable >/proc/acpi/ibm/fan + +Placing a fan on level 0 is the same as disabling it. Enabling a fan +will try to place it in a safe level if it is too slow or disabled. + +WARNING WARNING WARNING: do not leave the fan disabled unless you are +monitoring all of the temperature sensor readings and you are ready to +enable it if necessary to avoid overheating. + +An enabled fan in level "auto" may stop spinning if the EC decides the +ThinkPad is cool enough and doesn't need the extra airflow. This is +normal, and the EC will spin the fan up if the varios thermal readings +rise too much. + +On the X40, this seems to depend on the CPU and HDD temperatures. +Specifically, the fan is turned on when either the CPU temperature +climbs to 56 degrees or the HDD temperature climbs to 46 degrees. The +fan is turned off when the CPU temperature drops to 49 degrees and the +HDD temperature drops to 41 degrees. These thresholds cannot +currently be controlled. + +The fan level can be controlled with the command: + + echo 'level ' > /proc/acpi/ibm/thermal + +Where is an integer from 0 to 7, or one of the words "auto" +or "disengaged" (without the quotes). Not all ThinkPads support the +"auto" and "disengaged" levels. + +On the X31 and X40 (and ONLY on those models), the fan speed can be +controlled to a certain degree. Once the fan is running, it can be +forced to run faster or slower with the following command: + + echo 'speed ' > /proc/acpi/ibm/thermal + +The sustainable range of fan speeds on the X40 appears to be from +about 3700 to about 7350. Values outside this range either do not have +any effect or the fan speed eventually settles somewhere in that +range. The fan cannot be stopped or started with this command. + +The ThinkPad's ACPI DSDT code will reprogram the fan on its own when +certain conditions are met. It will override any fan programming done +through ibm-acpi. + +The ibm-acpi kernel driver can be programmed to revert the fan level +to a safe setting if userspace does not issue one of the fan commands: +"enable", "disable", "level" or "watchdog" within a configurable +ammount of time. To do this, use the "watchdog" command. + + echo 'watchdog ' > /proc/acpi/ibm/fan + +Interval is the ammount of time in seconds to wait for one of the +above mentioned fan commands before reseting the fan level to a safe +one. If set to zero, the watchdog is disabled (default). When the +watchdog timer runs out, it does the exact equivalent of the "enable" +fan command. + +Note that the watchdog timer stops after it enables the fan. It will +be rearmed again automatically (using the same interval) when one of +the above mentioned fan commands is received. The fan watchdog is, +therefore, not suitable to protect against fan mode changes made +through means other than the "enable", "disable", and "level" fan +commands. + +EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan +--------------------------------------- + +This feature is marked EXPERIMENTAL because the implementation +directly accesses hardware registers and may not work as expected. USE +WITH CAUTION! To use this feature, you need to supply the +experimental=1 parameter when loading the module. + +This feature shows the presence and current state of a WAN (Sierra +Wireless EV-DO) device. If WAN is installed, the following commands can +be used: + + echo enable > /proc/acpi/ibm/wan + echo disable > /proc/acpi/ibm/wan + +It was tested on a Lenovo Thinkpad X60. It should probably work on other +Thinkpad models which come with this module installed. + +Multiple Commands, Module Parameters +------------------------------------ + +Multiple commands can be written to the proc files in one shot by +separating them with commas, for example: + + echo enable,0xffff > /proc/acpi/ibm/hotkey + echo lcd_disable,crt_enable > /proc/acpi/ibm/video + +Commands can also be specified when loading the ibm_acpi module, for +example: + + modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable + diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 848b398..ebf4ff2 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -12,4 +12,4 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o -obj-$(CONFIG_ACPI_IBM) += ibm_acpi.o +obj-$(CONFIG_ACPI_IBM) += thinkpad_acpi.o diff --git a/drivers/misc/ibm_acpi.c b/drivers/misc/ibm_acpi.c deleted file mode 100644 index ae03b8f..0000000 --- a/drivers/misc/ibm_acpi.c +++ /dev/null @@ -1,2783 +0,0 @@ -/* - * ibm_acpi.c - IBM ThinkPad ACPI Extras - * - * - * Copyright (C) 2004-2005 Borislav Deianov - * Copyright (C) 2006-2007 Henrique de Moraes Holschuh - * - * 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.13" - -/* - * Changelog: - * - * 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 - * 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 - * fix parameter passing on module loading - * thanks to Rusty Russell - * thanks to Jim Radford - * 2004-11-08 0.8 fix init error case, don't return from a macro - * thanks to Chris Wright - * 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 "ibm_acpi.h" - -MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); -MODULE_DESCRIPTION(IBM_DESC); -MODULE_VERSION(IBM_VERSION); -MODULE_LICENSE("GPL"); - -#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, ¶ms, 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 __init 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; - - 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_NAME, 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_ibmacpi_subdriver(struct ibm_struct *ibm) -{ - int ret; - - ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); - if (!ibm->driver) { - printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); - return -1; - } - - 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); - } - - 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 - * - **************************************************************************** - ****************************************************************************/ - -/************************************************************************* - * ibm-acpi init subdriver - */ - -static int ibm_acpi_driver_init(void) -{ - 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 ibm_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; -} - -/************************************************************************* - * Hotkey subdriver - */ - -static int hotkey_supported; -static int hotkey_mask_supported; -static int hotkey_orig_status; -static int hotkey_orig_mask; - -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 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 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, \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; -} - -/************************************************************************* - * Bluetooth subdriver - */ - -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; -} - -/************************************************************************* - * Wan subdriver - */ - -static int wan_supported; - -static int wan_init(void) -{ - wan_supported = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); - - return 0; -} - -static int wan_status(void) -{ - int status; - - if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) - status = 0; - - return status; -} - -static int wan_read(char *p) -{ - int len = 0; - int status = wan_status(); - - if (!wan_supported) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!(status & 1)) - len += sprintf(p + len, "status:\t\tnot installed\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; -} - -static int wan_write(char *buf) -{ - int status = wan_status(); - char *cmd; - int do_cmd = 0; - - if (!wan_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status |= 2; - } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) - return -EIO; - - return 0; -} - -/************************************************************************* - * 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 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 = IBMACPI_VIDEO_NONE; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) - /* 570 */ - video_supported = IBMACPI_VIDEO_570; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) - /* 600e/x, 770e, 770x */ - video_supported = IBMACPI_VIDEO_770; - else - /* all others */ - video_supported = IBMACPI_VIDEO_NEW; - - return 0; -} - -static void video_exit(void) -{ - acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); -} - -static int video_status(void) -{ - int status = 0; - int i; - - if (video_supported == IBMACPI_VIDEO_570) { - if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) - status = i & 3; - } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) - acpi_evalf(vid_handle, &autosw, "SWIT", "d"); - else if (video_supported == IBMACPI_VIDEO_770 || - video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) - return acpi_evalf(ec_handle, NULL, "_Q17", "v"); - else if (video_supported == IBMACPI_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 == IBMACPI_VIDEO_570) { - ret = acpi_evalf(NULL, NULL, - "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); - } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_NEW && - strlencmp(cmd, "dvi_enable") == 0) { - enable |= 0x08; - } else if (video_supported == IBMACPI_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; -} - -/************************************************************************* - * 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 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; -} - -/************************************************************************* - * Dock subdriver - */ - -/* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ - -#ifdef CONFIG_ACPI_IBM_DOCK - -IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ - "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ - "\\_SB.PCI0.PCI1.DOCK", /* all others */ - "\\_SB.PCI.ISA.SLCE", /* 570 */ - ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ - -#define dock_docked() (_sta(dock_handle) & 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; -} - -#endif /* CONFIG_ACPI_IBM_DOCK */ - -/************************************************************************* - * Bay subdriver - */ - -#ifdef CONFIG_ACPI_IBM_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 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; -} - -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; -} -#endif /* CONFIG_ACPI_IBM_BAY */ - -/************************************************************************* - * CMOS subdriver - */ - -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 ( 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; -} - - -/************************************************************************* - * 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 led_init(void) -{ - if (!led_handle) - /* led not supported on R30, R31 */ - led_supported = IBMACPI_LED_NONE; - else if (strlencmp(led_path, "SLED") == 0) - /* 570 */ - led_supported = IBMACPI_LED_570; - else if (strlencmp(led_path, "SYSL") == 0) - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - led_supported = IBMACPI_LED_OLD; - else - /* all others */ - led_supported = IBMACPI_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 == IBMACPI_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" - " on, off, blink ( 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 == IBMACPI_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 == IBMACPI_LED_OLD) { - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ - led = 1 << led; - ret = ec_write(IBMACPI_LED_EC_HLMS, led); - if (ret >= 0) - ret = - ec_write(IBMACPI_LED_EC_HLBL, - led * led_exp_hlbl[ind]); - if (ret >= 0) - ret = - ec_write(IBMACPI_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; -} - -/************************************************************************* - * Beep subdriver - */ - -IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ - -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 ( 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; -} - -/************************************************************************* - * Thermal subdriver - */ - -static enum thermal_access_mode thermal_read_mode; - -static int thermal_init(void) -{ - u8 t, ta1, ta2; - int i; - int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); - - 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 - */ - - 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 = IBMACPI_THERMAL_ACPI_TMP07; - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "disabling thermal sensors access\n"); - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - } else { - thermal_read_mode = - (ta2 != 0) ? - IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; - } - } else if (acpi_tmp7) { - if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { - /* 600e/x, 770e, 770x */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; - } else { - /* Standard ACPI TMPx access, max 8 sensors */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; - } - } else { - /* temperatures not supported on 570, G4x, R30, R31, R32 */ - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - - return 0; -} - -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) -{ - int i, t; - s8 tmp; - char tmpi[] = "TMPi"; - - if (!s) - return -EINVAL; - - switch (thermal_read_mode) { -#if IBMACPI_MAX_THERMAL_SENSORS >= 16 - case IBMACPI_THERMAL_TPEC_16: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0xC0 + i, &tmp)) - return -EIO; - s->temp[i + 8] = tmp * 1000; - } - /* fallthrough */ -#endif - case IBMACPI_THERMAL_TPEC_8: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0x78 + i, &tmp)) - return -EIO; - s->temp[i] = tmp * 1000; - } - return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; - - case IBMACPI_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 IBMACPI_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 IBMACPI_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; -} - -/************************************************************************* - * 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 0x" - " ( is 00-ff, is 00-ff)\n"); - len += sprintf(p + len, "commands:\t0x " - " ( is 00-ff, 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; -} - -/************************************************************************* - * 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 brightness_init(void) -{ - int b; - - 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); - } - - 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) { - 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 " - " ( 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; -} - -/************************************************************************* - * 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 " - " ( 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; -} - - -/************************************************************************* - * Fan subdriver - */ - -/* - * FAN ACCESS MODES - * - * IBMACPI_FAN_RD_ACPI_GFAN: - * ACPI GFAN method: returns fan level - * - * see IBMACPI_FAN_WR_ACPI_SFAN - * EC 0x2f not available if GFAN exists - * - * IBMACPI_FAN_WR_ACPI_SFAN: - * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) - * - * EC 0x2f might be available *for reading*, but never for writing. - * - * IBMACPI_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 disengage mode (takes precedence over bit 7); - * not available on all thinkpads. May disable - * the tachometer, and speeds up fan to 100% duty-cycle, - * which speeds it up 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 - * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this - * register is not invalidated in ThinkPads that disable tachometer - * readings. Thus, the tachometer readings go stale. - * - * For firmware bugs, refer to: - * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues - * - * IBMACPI_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. - * - * IBMACPI_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; - -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 fan_init(void) -{ - fan_status_access_mode = IBMACPI_FAN_NONE; - fan_control_access_mode = IBMACPI_FAN_WR_NONE; - fan_control_commands = 0; - fan_control_status_known = 1; - fan_watchdog_maxinterval = 0; - - if (gfan_handle) { - /* 570, 600e/x, 770e, 770x */ - fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; - } else { - /* all other ThinkPads: note that even old-style - * ThinkPad ECs supports the fan control register */ - if (likely(acpi_ec_read(fan_status_offset, - &fan_control_initial_status))) { - fan_status_access_mode = IBMACPI_FAN_RD_TPEC; - - /* 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 0; - } - } - - if (sfan_handle) { - /* 570, 770x-JL */ - fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | IBMACPI_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 = - IBMACPI_FAN_WR_ACPI_FANS; - fan_control_commands |= - IBMACPI_FAN_CMD_SPEED | - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } else { - fan_control_access_mode = IBMACPI_FAN_WR_TPEC; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } - } - } - - return 0; -} - -static int fan_get_status(u8 *status) -{ - u8 s; - - /* TODO: - * Add IBMACPI_FAN_RD_ACPI_FANS ? */ - - switch (fan_status_access_mode) { - case IBMACPI_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 IBMACPI_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) -{ - 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 IBMACPI_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 IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((level != IBMACPI_FAN_EC_AUTO) && - (level != IBMACPI_FAN_EC_DISENGAGED) && - ((level < 0) || (level > 7))) - return -EINVAL; - - if (!acpi_ec_write(fan_status_offset, level)) - return -EIO; - 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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((rc = fan_get_status(&s)) < 0) - return rc; - - /* Don't go out of emergency fan mode */ - if (s != 7) - s = IBMACPI_FAN_EC_AUTO; - - if (!acpi_ec_write(fan_status_offset, s)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if (!acpi_ec_write(fan_status_offset, 0x00)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_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 IBMACPI_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 IBMACPI_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 IBMACPI_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 = IBMACPI_FAN_EC_AUTO; - } - - len += sprintf(p + len, "status:\t\t%s\n", - (status != 0) ? "enabled" : "disabled"); - - /* No ThinkPad boots on disengaged mode, we can safely - * assume the tachometer is online if fan control status - * was unknown */ - if ((rc = fan_get_speed(&speed)) < 0) - return rc; - - len += sprintf(p + len, "speed:\t\t%d\n", speed); - - if (status & IBMACPI_FAN_EC_DISENGAGED) - /* Disengaged mode takes precedence */ - len += sprintf(p + len, "level:\t\tdisengaged\n"); - else if (status & IBMACPI_FAN_EC_AUTO) - len += sprintf(p + len, "level:\t\tauto\n"); - else - len += sprintf(p + len, "level:\t\t%d\n", status); - break; - - case IBMACPI_FAN_NONE: - default: - len += sprintf(p + len, "status:\t\tnot supported\n"); - } - - if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { - len += sprintf(p + len, "commands:\tlevel "); - - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: - len += sprintf(p + len, " ( is 0-7)\n"); - break; - - default: - len += sprintf(p + len, " ( is 0-7, " - "auto, disengaged)\n"); - break; - } - } - - if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) - len += sprintf(p + len, "commands:\tenable, disable\n" - "commands:\twatchdog ( is 0 (off), " - "1-120 (seconds))\n"); - - if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed " - " ( 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 = IBMACPI_FAN_EC_AUTO; - else if (strlencmp(cmd, "level disengaged") == 0) - level = IBMACPI_FAN_EC_DISENGAGED; - 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 ? */ - - 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 & IBMACPI_FAN_CMD_LEVEL) && - fan_write_cmd_level(cmd, &rc)) && - !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && - (fan_write_cmd_enable(cmd, &rc) || - fan_write_cmd_disable(cmd, &rc) || - fan_write_cmd_watchdog(cmd, &rc))) && - !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && - fan_write_cmd_speed(cmd, &rc)) - ) - rc = -EINVAL; - else if (!rc) - fan_watchdog_reset(); - } - - return rc; -} - -/**************************************************************************** - **************************************************************************** - * - * Infrastructure - * - **************************************************************************** - ****************************************************************************/ - -/* /proc support */ -static struct proc_dir_entry *proc_dir = NULL; - -/* Subdriver registry */ -static struct ibm_struct ibms[] = { - { - .name = "driver", - .init = ibm_acpi_driver_init, - .read = ibm_acpi_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 = "wan", - .init = wan_init, - .read = wan_read, - .write = wan_write, - .experimental = 1, - }, - { - .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 CONFIG_ACPI_IBM_DOCK - { - .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 -#ifdef CONFIG_ACPI_IBM_BAY - { - .name = "bay", - .init = bay_init, - .read = bay_read, - .write = bay_write, - .notify = bay_notify, - .handle = &bay_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif /* CONFIG_ACPI_IBM_BAY */ - { - .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", - .read = brightness_read, - .write = brightness_write, - .init = brightness_init, - .exit = brightness_exit, - }, - { - .name = "volume", - .read = volume_read, - .write = volume_write, - }, - { - .name = "fan", - .read = fan_read, - .write = fan_write, - .init = fan_init, - .exit = fan_exit, - .experimental = 1, - }, -}; - -/* - * Module and infrastructure proble, init and exit handling - */ - -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_ibmacpi_subdriver(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 == -ENODEV) { - printk(IBM_NOTICE "disabling subdriver %s\n", - ibm->name); - ibm_exit(ibm); - return 0; - } - if (ret < 0) - return ret; - } - - 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); - } -} - -/* 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; -} - -/* Module init, exit, parameters */ - -static int __init 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; -} - -static int experimental; -module_param(experimental, 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_ACPI_IBM_DOCK -IBM_PARAM(dock); -#endif -#ifdef CONFIG_ACPI_IBM_BAY -IBM_PARAM(bay); -#endif /* CONFIG_ACPI_IBM_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 acpi_ibm_init(void) -{ - int ret, i; - - if (acpi_disabled) - 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; - } - - /* Models with newer firmware report the EC in DMI */ - ibm_thinkpad_ec_found = check_dmi_for_ec(); - - /* 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 CONFIG_ACPI_IBM_DOCK - IBM_HANDLE_INIT(dock); -#endif - IBM_HANDLE_INIT(pci); -#ifdef CONFIG_ACPI_IBM_BAY - IBM_HANDLE_INIT(bay); - if (bay_handle) - IBM_HANDLE_INIT(bay_ej); - IBM_HANDLE_INIT(bay2); - if (bay2_handle) - IBM_HANDLE_INIT(bay2_ej); -#endif /* CONFIG_ACPI_IBM_BAY */ - 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); - acpi_ibm_exit(); - 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; -} - -static void acpi_ibm_exit(void) -{ - int i; - - for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) - ibm_exit(&ibms[i]); - - if (proc_dir) - remove_proc_entry(IBM_DIR, acpi_root_dir); - - if (ibm_thinkpad_ec_found) - kfree(ibm_thinkpad_ec_found); -} - -module_init(acpi_ibm_init); -module_exit(acpi_ibm_exit); diff --git a/drivers/misc/ibm_acpi.h b/drivers/misc/ibm_acpi.h deleted file mode 100644 index 7ebaaa4..0000000 --- a/drivers/misc/ibm_acpi.h +++ /dev/null @@ -1,437 +0,0 @@ -/* - * ibm_acpi.h - IBM ThinkPad ACPI Extras - * - * - * Copyright (C) 2004-2005 Borislav Deianov - * Copyright (C) 2006-2007 Henrique de Moraes Holschuh - * - * 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. - */ - -#ifndef __IBM_ACPI_H__ -#define __IBM_ACPI_H__ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - - -/**************************************************************************** - * Main driver - */ - -#define IBM_NAME "ibm" -#define IBM_DESC "IBM ThinkPad ACPI Extras" -#define IBM_FILE "ibm_acpi" -#define IBM_URL "http://ibm-acpi.sf.net/" - -#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 - -/* ThinkPad CMOS commands */ -#define TP_CMOS_VOLUME_DOWN 0 -#define TP_CMOS_VOLUME_UP 1 -#define TP_CMOS_VOLUME_MUTE 2 -#define TP_CMOS_BRIGHTNESS_UP 4 -#define TP_CMOS_BRIGHTNESS_DOWN 5 - -#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))) - -/* ACPI HIDs */ -#define IBM_HKEY_HID "IBM0068" -#define IBM_PCI_HID "PNP0A03" - -/* ACPI helpers */ -static int acpi_evalf(acpi_handle handle, - void *res, char *method, char *fmt, ...); -static int acpi_ec_read(int i, u8 * p); -static int acpi_ec_write(int i, u8 v); -static int _sta(acpi_handle handle); - -/* ACPI handles */ -static acpi_handle root_handle; /* root namespace */ -static acpi_handle ec_handle; /* EC */ -static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ -static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ - -static void ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, - char **paths, int num_paths, char **path); -#define IBM_HANDLE_INIT(object) \ - ibm_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) - -/* procfs support */ -static struct proc_dir_entry *proc_dir; -static int ibm_acpi_driver_init(void); -static int ibm_acpi_driver_read(char *p); - -/* procfs helpers */ -static int dispatch_read(char *page, char **start, off_t off, int count, - int *eof, void *data); -static int dispatch_write(struct file *file, const char __user * userbuf, - unsigned long count, void *data); -static char *next_cmd(char **cmds); - -/* Module */ -static int experimental; -static char *ibm_thinkpad_ec_found; - -static char* check_dmi_for_ec(void); -static int acpi_ibm_init(void); -static void acpi_ibm_exit(void); - - -/**************************************************************************** - * Subdrivers - */ - -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 ibm_struct ibms[]; -static int set_ibm_param(const char *val, struct kernel_param *kp); -static int ibm_init(struct ibm_struct *ibm); -static void ibm_exit(struct ibm_struct *ibm); - -/* ACPI devices */ -static void dispatch_notify(acpi_handle handle, u32 event, void *data); -static int setup_notify(struct ibm_struct *ibm); -static int ibm_device_add(struct acpi_device *device); -static int register_ibmacpi_subdriver(struct ibm_struct *ibm); - - -/* - * Bay subdriver - */ - -#ifdef CONFIG_ACPI_IBM_BAY -static int bay_status_supported, bay_eject_supported; -static int bay_status2_supported, bay_eject2_supported; - -static acpi_handle bay_handle, bay_ej_handle; -static acpi_handle bay2_handle, bay2_ej_handle; - -static int bay_init(void); -static void bay_notify(struct ibm_struct *ibm, u32 event); -static int bay_read(char *p); -static int bay_write(char *buf); -#endif /* CONFIG_ACPI_IBM_BAY */ - - -/* - * Beep subdriver - */ - -static acpi_handle beep_handle; - -static int beep_read(char *p); -static int beep_write(char *buf); - - -/* - * Bluetooth subdriver - */ - -static int bluetooth_supported; - -static int bluetooth_init(void); -static int bluetooth_status(void); -static int bluetooth_read(char *p); -static int bluetooth_write(char *buf); - - -/* - * Brightness (backlight) subdriver - */ - -static struct backlight_device *ibm_backlight_device; -static int brightness_offset = 0x31; - -static int brightness_init(void); -static void brightness_exit(void); -static int brightness_get(struct backlight_device *bd); -static int brightness_set(int value); -static int brightness_update_status(struct backlight_device *bd); -static int brightness_read(char *p); -static int brightness_write(char *buf); - - -/* - * CMOS subdriver - */ - -static int cmos_eval(int cmos_cmd); -static int cmos_read(char *p); -static int cmos_write(char *buf); - - -/* - * Dock subdriver - */ - -static acpi_handle pci_handle; -#ifdef CONFIG_ACPI_IBM_DOCK -static acpi_handle dock_handle; - -static void dock_notify(struct ibm_struct *ibm, u32 event); -static int dock_read(char *p); -static int dock_write(char *buf); -#endif /* CONFIG_ACPI_IBM_DOCK */ - - -/* - * EC dump subdriver - */ - -static int ecdump_read(char *p) ; -static int ecdump_write(char *buf); - - -/* - * Fan subdriver - */ - -enum { /* Fan control constants */ - fan_status_offset = 0x2f, /* EC register 0x2f */ - fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) - * 0x84 must be read before 0x85 */ - - IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer - * disengaged */ - IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan - * control */ -}; - -enum fan_status_access_mode { - IBMACPI_FAN_NONE = 0, /* No fan status or control */ - IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ -}; - -enum fan_control_access_mode { - IBMACPI_FAN_WR_NONE = 0, /* No fan control */ - IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ -}; - -enum fan_control_commands { - IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, - * and also watchdog cmd */ -}; - -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; -static int fan_control_status_known; -static u8 fan_control_initial_status; -static int fan_watchdog_maxinterval; - -static acpi_handle fans_handle, gfan_handle, sfan_handle; - -static int fan_init(void); -static void fan_exit(void); -static int fan_get_status(u8 *status); -static int fan_get_speed(unsigned int *speed); -static void fan_watchdog_fire(struct work_struct *ignored); -static void fan_watchdog_reset(void); -static int fan_set_level(int level); -static int fan_set_enable(void); -static int fan_set_disable(void); -static int fan_set_speed(int speed); -static int fan_read(char *p); -static int fan_write(char *buf); -static int fan_write_cmd_level(const char *cmd, int *rc); -static int fan_write_cmd_enable(const char *cmd, int *rc); -static int fan_write_cmd_disable(const char *cmd, int *rc); -static int fan_write_cmd_speed(const char *cmd, int *rc); -static int fan_write_cmd_watchdog(const char *cmd, int *rc); - - -/* - * Hotkey subdriver - */ - -static int hotkey_supported; -static int hotkey_mask_supported; -static int hotkey_orig_status; -static int hotkey_orig_mask; - -static int hotkey_init(void); -static void hotkey_exit(void); -static int hotkey_get(int *status, int *mask); -static int hotkey_set(int status, int mask); -static void hotkey_notify(struct ibm_struct *ibm, u32 event); -static int hotkey_read(char *p); -static int hotkey_write(char *buf); - - -/* - * LED subdriver - */ - -enum led_access_mode { - IBMACPI_LED_NONE = 0, - IBMACPI_LED_570, /* 570 */ - IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - IBMACPI_LED_NEW, /* all others */ -}; - -enum { /* For IBMACPI_LED_OLD */ - IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ - IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ - IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ -}; - -static enum led_access_mode led_supported; -static acpi_handle led_handle; - -static int led_init(void); -static int led_read(char *p); -static int led_write(char *buf); - -/* - * Light (thinklight) subdriver - */ - -static int light_supported; -static int light_status_supported; -static acpi_handle lght_handle, ledb_handle; - -static int light_init(void); -static int light_read(char *p); -static int light_write(char *buf); - - -/* - * Thermal subdriver - */ - -enum thermal_access_mode { - IBMACPI_THERMAL_NONE = 0, /* No thermal support */ - IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ -}; - -#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -struct ibm_thermal_sensors_struct { - s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; -}; - -static int thermal_init(void); -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); -static int thermal_read(char *p); - - -/* - * Video subdriver - */ - -enum video_access_mode { - IBMACPI_VIDEO_NONE = 0, - IBMACPI_VIDEO_570, /* 570 */ - IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - IBMACPI_VIDEO_NEW, /* all others */ -}; - -static enum video_access_mode video_supported; -static int video_orig_autosw; -static acpi_handle vid_handle, vid2_handle; - -static int video_init(void); -static void video_exit(void); -static int video_status(void); -static int video_autosw(void); -static int video_switch(void); -static int video_switch2(int status); -static int video_expand(void); -static int video_read(char *p); -static int video_write(char *buf); - - -/* - * Volume subdriver - */ - -static int volume_offset = 0x30; - -static int volume_read(char *p); -static int volume_write(char *buf); - - -/* - * Wan subdriver - */ - -static int wan_supported; - -static int wan_init(void); -static int wan_status(void); -static int wan_read(char *p); -static int wan_write(char *buf); - - -#endif /* __IBM_ACPI_H */ diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c new file mode 100644 index 0000000..2836516 --- /dev/null +++ b/drivers/misc/thinkpad_acpi.c @@ -0,0 +1,2783 @@ +/* + * ibm_acpi.c - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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.13" + +/* + * Changelog: + * + * 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 + * 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 + * fix parameter passing on module loading + * thanks to Rusty Russell + * thanks to Jim Radford + * 2004-11-08 0.8 fix init error case, don't return from a macro + * thanks to Chris Wright + * 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"); + +#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, ¶ms, 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 __init 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; + + 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_NAME, 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_ibmacpi_subdriver(struct ibm_struct *ibm) +{ + int ret; + + ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->driver) { + printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); + return -1; + } + + 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); + } + + 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 + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ibm-acpi init subdriver + */ + +static int ibm_acpi_driver_init(void) +{ + 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 ibm_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; +} + +/************************************************************************* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +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 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 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, \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; +} + +/************************************************************************* + * Bluetooth subdriver + */ + +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; +} + +/************************************************************************* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void) +{ + wan_supported = hkey_handle && + acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); + + return 0; +} + +static int wan_status(void) +{ + int status; + + if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) + status = 0; + + return status; +} + +static int wan_read(char *p) +{ + int len = 0; + int status = wan_status(); + + if (!wan_supported) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!(status & 1)) + len += sprintf(p + len, "status:\t\tnot installed\n"); + else { + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int wan_write(char *buf) +{ + int status = wan_status(); + char *cmd; + int do_cmd = 0; + + if (!wan_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status |= 2; + } else if (strlencmp(cmd, "disable") == 0) { + status &= ~2; + } else + return -EINVAL; + do_cmd = 1; + } + + if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) + return -EIO; + + return 0; +} + +/************************************************************************* + * 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 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 = IBMACPI_VIDEO_NONE; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) + /* 570 */ + video_supported = IBMACPI_VIDEO_570; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) + /* 600e/x, 770e, 770x */ + video_supported = IBMACPI_VIDEO_770; + else + /* all others */ + video_supported = IBMACPI_VIDEO_NEW; + + return 0; +} + +static void video_exit(void) +{ + acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); +} + +static int video_status(void) +{ + int status = 0; + int i; + + if (video_supported == IBMACPI_VIDEO_570) { + if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) + status = i & 3; + } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) + acpi_evalf(vid_handle, &autosw, "SWIT", "d"); + else if (video_supported == IBMACPI_VIDEO_770 || + video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_570) + return acpi_evalf(ec_handle, NULL, "_Q17", "v"); + else if (video_supported == IBMACPI_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 == IBMACPI_VIDEO_570) { + ret = acpi_evalf(NULL, NULL, + "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); + } else if (video_supported == IBMACPI_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 == IBMACPI_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 == IBMACPI_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 == IBMACPI_VIDEO_NEW && + strlencmp(cmd, "dvi_enable") == 0) { + enable |= 0x08; + } else if (video_supported == IBMACPI_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; +} + +/************************************************************************* + * 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 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; +} + +/************************************************************************* + * Dock subdriver + */ + +/* don't list other alternatives as we install a notify handler on the 570 */ +IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ + +#ifdef CONFIG_ACPI_IBM_DOCK + +IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ + "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ + "\\_SB.PCI0.PCI1.DOCK", /* all others */ + "\\_SB.PCI.ISA.SLCE", /* 570 */ + ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ + +#define dock_docked() (_sta(dock_handle) & 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; +} + +#endif /* CONFIG_ACPI_IBM_DOCK */ + +/************************************************************************* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_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 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; +} + +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; +} +#endif /* CONFIG_ACPI_IBM_BAY */ + +/************************************************************************* + * CMOS subdriver + */ + +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 ( 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; +} + + +/************************************************************************* + * 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 led_init(void) +{ + if (!led_handle) + /* led not supported on R30, R31 */ + led_supported = IBMACPI_LED_NONE; + else if (strlencmp(led_path, "SLED") == 0) + /* 570 */ + led_supported = IBMACPI_LED_570; + else if (strlencmp(led_path, "SYSL") == 0) + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + led_supported = IBMACPI_LED_OLD; + else + /* all others */ + led_supported = IBMACPI_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 == IBMACPI_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" + " on, off, blink ( 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 == IBMACPI_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 == IBMACPI_LED_OLD) { + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ + led = 1 << led; + ret = ec_write(IBMACPI_LED_EC_HLMS, led); + if (ret >= 0) + ret = + ec_write(IBMACPI_LED_EC_HLBL, + led * led_exp_hlbl[ind]); + if (ret >= 0) + ret = + ec_write(IBMACPI_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; +} + +/************************************************************************* + * Beep subdriver + */ + +IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ + +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 ( 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; +} + +/************************************************************************* + * Thermal subdriver + */ + +static enum thermal_access_mode thermal_read_mode; + +static int thermal_init(void) +{ + u8 t, ta1, ta2; + int i; + int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); + + 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 + */ + + 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 = IBMACPI_THERMAL_ACPI_TMP07; + } else { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "disabling thermal sensors access\n"); + thermal_read_mode = IBMACPI_THERMAL_NONE; + } + } else { + thermal_read_mode = + (ta2 != 0) ? + IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; + } + } else if (acpi_tmp7) { + if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { + /* 600e/x, 770e, 770x */ + thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; + } else { + /* Standard ACPI TMPx access, max 8 sensors */ + thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; + } + } else { + /* temperatures not supported on 570, G4x, R30, R31, R32 */ + thermal_read_mode = IBMACPI_THERMAL_NONE; + } + + return 0; +} + +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +{ + int i, t; + s8 tmp; + char tmpi[] = "TMPi"; + + if (!s) + return -EINVAL; + + switch (thermal_read_mode) { +#if IBMACPI_MAX_THERMAL_SENSORS >= 16 + case IBMACPI_THERMAL_TPEC_16: + for (i = 0; i < 8; i++) { + if (!acpi_ec_read(0xC0 + i, &tmp)) + return -EIO; + s->temp[i + 8] = tmp * 1000; + } + /* fallthrough */ +#endif + case IBMACPI_THERMAL_TPEC_8: + for (i = 0; i < 8; i++) { + if (!acpi_ec_read(0x78 + i, &tmp)) + return -EIO; + s->temp[i] = tmp * 1000; + } + return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; + + case IBMACPI_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 IBMACPI_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 IBMACPI_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; +} + +/************************************************************************* + * 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 0x" + " ( is 00-ff, is 00-ff)\n"); + len += sprintf(p + len, "commands:\t0x " + " ( is 00-ff, 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; +} + +/************************************************************************* + * 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 brightness_init(void) +{ + int b; + + 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); + } + + 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) { + 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 " + " ( 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; +} + +/************************************************************************* + * 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 " + " ( 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; +} + + +/************************************************************************* + * Fan subdriver + */ + +/* + * FAN ACCESS MODES + * + * IBMACPI_FAN_RD_ACPI_GFAN: + * ACPI GFAN method: returns fan level + * + * see IBMACPI_FAN_WR_ACPI_SFAN + * EC 0x2f not available if GFAN exists + * + * IBMACPI_FAN_WR_ACPI_SFAN: + * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) + * + * EC 0x2f might be available *for reading*, but never for writing. + * + * IBMACPI_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 disengage mode (takes precedence over bit 7); + * not available on all thinkpads. May disable + * the tachometer, and speeds up fan to 100% duty-cycle, + * which speeds it up 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 + * IBMACPI_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: when EC 0x2f bit 6 is set (disengaged mode), this + * register is not invalidated in ThinkPads that disable tachometer + * readings. Thus, the tachometer readings go stale. + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * IBMACPI_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. + * + * IBMACPI_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; + +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 fan_init(void) +{ + fan_status_access_mode = IBMACPI_FAN_NONE; + fan_control_access_mode = IBMACPI_FAN_WR_NONE; + fan_control_commands = 0; + fan_control_status_known = 1; + fan_watchdog_maxinterval = 0; + + if (gfan_handle) { + /* 570, 600e/x, 770e, 770x */ + fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; + } else { + /* all other ThinkPads: note that even old-style + * ThinkPad ECs supports the fan control register */ + if (likely(acpi_ec_read(fan_status_offset, + &fan_control_initial_status))) { + fan_status_access_mode = IBMACPI_FAN_RD_TPEC; + + /* 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 0; + } + } + + if (sfan_handle) { + /* 570, 770x-JL */ + fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; + fan_control_commands |= + IBMACPI_FAN_CMD_LEVEL | IBMACPI_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 = + IBMACPI_FAN_WR_ACPI_FANS; + fan_control_commands |= + IBMACPI_FAN_CMD_SPEED | + IBMACPI_FAN_CMD_LEVEL | + IBMACPI_FAN_CMD_ENABLE; + } else { + fan_control_access_mode = IBMACPI_FAN_WR_TPEC; + fan_control_commands |= + IBMACPI_FAN_CMD_LEVEL | + IBMACPI_FAN_CMD_ENABLE; + } + } + } + + return 0; +} + +static int fan_get_status(u8 *status) +{ + u8 s; + + /* TODO: + * Add IBMACPI_FAN_RD_ACPI_FANS ? */ + + switch (fan_status_access_mode) { + case IBMACPI_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 IBMACPI_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) +{ + 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 IBMACPI_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 IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if ((level != IBMACPI_FAN_EC_AUTO) && + (level != IBMACPI_FAN_EC_DISENGAGED) && + ((level < 0) || (level > 7))) + return -EINVAL; + + if (!acpi_ec_write(fan_status_offset, level)) + return -EIO; + 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 IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if ((rc = fan_get_status(&s)) < 0) + return rc; + + /* Don't go out of emergency fan mode */ + if (s != 7) + s = IBMACPI_FAN_EC_AUTO; + + if (!acpi_ec_write(fan_status_offset, s)) + return -EIO; + else + fan_control_status_known = 1; + break; + + case IBMACPI_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 IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if (!acpi_ec_write(fan_status_offset, 0x00)) + return -EIO; + else + fan_control_status_known = 1; + break; + + case IBMACPI_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 IBMACPI_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 IBMACPI_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 IBMACPI_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 = IBMACPI_FAN_EC_AUTO; + } + + len += sprintf(p + len, "status:\t\t%s\n", + (status != 0) ? "enabled" : "disabled"); + + /* No ThinkPad boots on disengaged mode, we can safely + * assume the tachometer is online if fan control status + * was unknown */ + if ((rc = fan_get_speed(&speed)) < 0) + return rc; + + len += sprintf(p + len, "speed:\t\t%d\n", speed); + + if (status & IBMACPI_FAN_EC_DISENGAGED) + /* Disengaged mode takes precedence */ + len += sprintf(p + len, "level:\t\tdisengaged\n"); + else if (status & IBMACPI_FAN_EC_AUTO) + len += sprintf(p + len, "level:\t\tauto\n"); + else + len += sprintf(p + len, "level:\t\t%d\n", status); + break; + + case IBMACPI_FAN_NONE: + default: + len += sprintf(p + len, "status:\t\tnot supported\n"); + } + + if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { + len += sprintf(p + len, "commands:\tlevel "); + + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_SFAN: + len += sprintf(p + len, " ( is 0-7)\n"); + break; + + default: + len += sprintf(p + len, " ( is 0-7, " + "auto, disengaged)\n"); + break; + } + } + + if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) + len += sprintf(p + len, "commands:\tenable, disable\n" + "commands:\twatchdog ( is 0 (off), " + "1-120 (seconds))\n"); + + if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) + len += sprintf(p + len, "commands:\tspeed " + " ( 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 = IBMACPI_FAN_EC_AUTO; + else if (strlencmp(cmd, "level disengaged") == 0) + level = IBMACPI_FAN_EC_DISENGAGED; + 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 ? */ + + 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 & IBMACPI_FAN_CMD_LEVEL) && + fan_write_cmd_level(cmd, &rc)) && + !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && + (fan_write_cmd_enable(cmd, &rc) || + fan_write_cmd_disable(cmd, &rc) || + fan_write_cmd_watchdog(cmd, &rc))) && + !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && + fan_write_cmd_speed(cmd, &rc)) + ) + rc = -EINVAL; + else if (!rc) + fan_watchdog_reset(); + } + + return rc; +} + +/**************************************************************************** + **************************************************************************** + * + * Infrastructure + * + **************************************************************************** + ****************************************************************************/ + +/* /proc support */ +static struct proc_dir_entry *proc_dir = NULL; + +/* Subdriver registry */ +static struct ibm_struct ibms[] = { + { + .name = "driver", + .init = ibm_acpi_driver_init, + .read = ibm_acpi_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 = "wan", + .init = wan_init, + .read = wan_read, + .write = wan_write, + .experimental = 1, + }, + { + .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 CONFIG_ACPI_IBM_DOCK + { + .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 +#ifdef CONFIG_ACPI_IBM_BAY + { + .name = "bay", + .init = bay_init, + .read = bay_read, + .write = bay_write, + .notify = bay_notify, + .handle = &bay_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, +#endif /* CONFIG_ACPI_IBM_BAY */ + { + .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", + .read = brightness_read, + .write = brightness_write, + .init = brightness_init, + .exit = brightness_exit, + }, + { + .name = "volume", + .read = volume_read, + .write = volume_write, + }, + { + .name = "fan", + .read = fan_read, + .write = fan_write, + .init = fan_init, + .exit = fan_exit, + .experimental = 1, + }, +}; + +/* + * Module and infrastructure proble, init and exit handling + */ + +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_ibmacpi_subdriver(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 == -ENODEV) { + printk(IBM_NOTICE "disabling subdriver %s\n", + ibm->name); + ibm_exit(ibm); + return 0; + } + if (ret < 0) + return ret; + } + + 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); + } +} + +/* 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; +} + +/* Module init, exit, parameters */ + +static int __init 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; +} + +static int experimental; +module_param(experimental, 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_ACPI_IBM_DOCK +IBM_PARAM(dock); +#endif +#ifdef CONFIG_ACPI_IBM_BAY +IBM_PARAM(bay); +#endif /* CONFIG_ACPI_IBM_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 acpi_ibm_init(void) +{ + int ret, i; + + if (acpi_disabled) + 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; + } + + /* Models with newer firmware report the EC in DMI */ + ibm_thinkpad_ec_found = check_dmi_for_ec(); + + /* 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 CONFIG_ACPI_IBM_DOCK + IBM_HANDLE_INIT(dock); +#endif + IBM_HANDLE_INIT(pci); +#ifdef CONFIG_ACPI_IBM_BAY + IBM_HANDLE_INIT(bay); + if (bay_handle) + IBM_HANDLE_INIT(bay_ej); + IBM_HANDLE_INIT(bay2); + if (bay2_handle) + IBM_HANDLE_INIT(bay2_ej); +#endif /* CONFIG_ACPI_IBM_BAY */ + 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); + acpi_ibm_exit(); + 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; +} + +static void acpi_ibm_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) + ibm_exit(&ibms[i]); + + if (proc_dir) + remove_proc_entry(IBM_DIR, acpi_root_dir); + + if (ibm_thinkpad_ec_found) + kfree(ibm_thinkpad_ec_found); +} + +module_init(acpi_ibm_init); +module_exit(acpi_ibm_exit); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h new file mode 100644 index 0000000..7ebaaa4 --- /dev/null +++ b/drivers/misc/thinkpad_acpi.h @@ -0,0 +1,437 @@ +/* + * ibm_acpi.h - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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. + */ + +#ifndef __IBM_ACPI_H__ +#define __IBM_ACPI_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +/**************************************************************************** + * Main driver + */ + +#define IBM_NAME "ibm" +#define IBM_DESC "IBM ThinkPad ACPI Extras" +#define IBM_FILE "ibm_acpi" +#define IBM_URL "http://ibm-acpi.sf.net/" + +#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 + +/* ThinkPad CMOS commands */ +#define TP_CMOS_VOLUME_DOWN 0 +#define TP_CMOS_VOLUME_UP 1 +#define TP_CMOS_VOLUME_MUTE 2 +#define TP_CMOS_BRIGHTNESS_UP 4 +#define TP_CMOS_BRIGHTNESS_DOWN 5 + +#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))) + +/* ACPI HIDs */ +#define IBM_HKEY_HID "IBM0068" +#define IBM_PCI_HID "PNP0A03" + +/* ACPI helpers */ +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...); +static int acpi_ec_read(int i, u8 * p); +static int acpi_ec_write(int i, u8 v); +static int _sta(acpi_handle handle); + +/* ACPI handles */ +static acpi_handle root_handle; /* root namespace */ +static acpi_handle ec_handle; /* EC */ +static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ +static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ + +static void ibm_handle_init(char *name, + acpi_handle * handle, acpi_handle parent, + char **paths, int num_paths, char **path); +#define IBM_HANDLE_INIT(object) \ + ibm_handle_init(#object, &object##_handle, *object##_parent, \ + object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + +/* procfs support */ +static struct proc_dir_entry *proc_dir; +static int ibm_acpi_driver_init(void); +static int ibm_acpi_driver_read(char *p); + +/* procfs helpers */ +static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data); +static int dispatch_write(struct file *file, const char __user * userbuf, + unsigned long count, void *data); +static char *next_cmd(char **cmds); + +/* Module */ +static int experimental; +static char *ibm_thinkpad_ec_found; + +static char* check_dmi_for_ec(void); +static int acpi_ibm_init(void); +static void acpi_ibm_exit(void); + + +/**************************************************************************** + * Subdrivers + */ + +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 ibm_struct ibms[]; +static int set_ibm_param(const char *val, struct kernel_param *kp); +static int ibm_init(struct ibm_struct *ibm); +static void ibm_exit(struct ibm_struct *ibm); + +/* ACPI devices */ +static void dispatch_notify(acpi_handle handle, u32 event, void *data); +static int setup_notify(struct ibm_struct *ibm); +static int ibm_device_add(struct acpi_device *device); +static int register_ibmacpi_subdriver(struct ibm_struct *ibm); + + +/* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_BAY +static int bay_status_supported, bay_eject_supported; +static int bay_status2_supported, bay_eject2_supported; + +static acpi_handle bay_handle, bay_ej_handle; +static acpi_handle bay2_handle, bay2_ej_handle; + +static int bay_init(void); +static void bay_notify(struct ibm_struct *ibm, u32 event); +static int bay_read(char *p); +static int bay_write(char *buf); +#endif /* CONFIG_ACPI_IBM_BAY */ + + +/* + * Beep subdriver + */ + +static acpi_handle beep_handle; + +static int beep_read(char *p); +static int beep_write(char *buf); + + +/* + * Bluetooth subdriver + */ + +static int bluetooth_supported; + +static int bluetooth_init(void); +static int bluetooth_status(void); +static int bluetooth_read(char *p); +static int bluetooth_write(char *buf); + + +/* + * Brightness (backlight) subdriver + */ + +static struct backlight_device *ibm_backlight_device; +static int brightness_offset = 0x31; + +static int brightness_init(void); +static void brightness_exit(void); +static int brightness_get(struct backlight_device *bd); +static int brightness_set(int value); +static int brightness_update_status(struct backlight_device *bd); +static int brightness_read(char *p); +static int brightness_write(char *buf); + + +/* + * CMOS subdriver + */ + +static int cmos_eval(int cmos_cmd); +static int cmos_read(char *p); +static int cmos_write(char *buf); + + +/* + * Dock subdriver + */ + +static acpi_handle pci_handle; +#ifdef CONFIG_ACPI_IBM_DOCK +static acpi_handle dock_handle; + +static void dock_notify(struct ibm_struct *ibm, u32 event); +static int dock_read(char *p); +static int dock_write(char *buf); +#endif /* CONFIG_ACPI_IBM_DOCK */ + + +/* + * EC dump subdriver + */ + +static int ecdump_read(char *p) ; +static int ecdump_write(char *buf); + + +/* + * Fan subdriver + */ + +enum { /* Fan control constants */ + fan_status_offset = 0x2f, /* EC register 0x2f */ + fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) + * 0x84 must be read before 0x85 */ + + IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer + * disengaged */ + IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan + * control */ +}; + +enum fan_status_access_mode { + IBMACPI_FAN_NONE = 0, /* No fan status or control */ + IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ +}; + +enum fan_control_access_mode { + IBMACPI_FAN_WR_NONE = 0, /* No fan control */ + IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ +}; + +enum fan_control_commands { + IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + * and also watchdog cmd */ +}; + +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; +static int fan_control_status_known; +static u8 fan_control_initial_status; +static int fan_watchdog_maxinterval; + +static acpi_handle fans_handle, gfan_handle, sfan_handle; + +static int fan_init(void); +static void fan_exit(void); +static int fan_get_status(u8 *status); +static int fan_get_speed(unsigned int *speed); +static void fan_watchdog_fire(struct work_struct *ignored); +static void fan_watchdog_reset(void); +static int fan_set_level(int level); +static int fan_set_enable(void); +static int fan_set_disable(void); +static int fan_set_speed(int speed); +static int fan_read(char *p); +static int fan_write(char *buf); +static int fan_write_cmd_level(const char *cmd, int *rc); +static int fan_write_cmd_enable(const char *cmd, int *rc); +static int fan_write_cmd_disable(const char *cmd, int *rc); +static int fan_write_cmd_speed(const char *cmd, int *rc); +static int fan_write_cmd_watchdog(const char *cmd, int *rc); + + +/* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static int hotkey_init(void); +static void hotkey_exit(void); +static int hotkey_get(int *status, int *mask); +static int hotkey_set(int status, int mask); +static void hotkey_notify(struct ibm_struct *ibm, u32 event); +static int hotkey_read(char *p); +static int hotkey_write(char *buf); + + +/* + * LED subdriver + */ + +enum led_access_mode { + IBMACPI_LED_NONE = 0, + IBMACPI_LED_570, /* 570 */ + IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + IBMACPI_LED_NEW, /* all others */ +}; + +enum { /* For IBMACPI_LED_OLD */ + IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +}; + +static enum led_access_mode led_supported; +static acpi_handle led_handle; + +static int led_init(void); +static int led_read(char *p); +static int led_write(char *buf); + +/* + * Light (thinklight) subdriver + */ + +static int light_supported; +static int light_status_supported; +static acpi_handle lght_handle, ledb_handle; + +static int light_init(void); +static int light_read(char *p); +static int light_write(char *buf); + + +/* + * Thermal subdriver + */ + +enum thermal_access_mode { + IBMACPI_THERMAL_NONE = 0, /* No thermal support */ + IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ +}; + +#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +struct ibm_thermal_sensors_struct { + s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; +}; + +static int thermal_init(void); +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); +static int thermal_read(char *p); + + +/* + * Video subdriver + */ + +enum video_access_mode { + IBMACPI_VIDEO_NONE = 0, + IBMACPI_VIDEO_570, /* 570 */ + IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + IBMACPI_VIDEO_NEW, /* all others */ +}; + +static enum video_access_mode video_supported; +static int video_orig_autosw; +static acpi_handle vid_handle, vid2_handle; + +static int video_init(void); +static void video_exit(void); +static int video_status(void); +static int video_autosw(void); +static int video_switch(void); +static int video_switch2(int status); +static int video_expand(void); +static int video_read(char *p); +static int video_write(char *buf); + + +/* + * Volume subdriver + */ + +static int volume_offset = 0x30; + +static int volume_read(char *p); +static int volume_write(char *buf); + + +/* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void); +static int wan_status(void); +static int wan_read(char *p); +static int wan_write(char *buf); + + +#endif /* __IBM_ACPI_H */ -- cgit v1.1 From 85998248b2e8c6ae7d3ad1fa7b059aed22205ec4 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 29 Mar 2007 01:58:41 -0300 Subject: ACPI: thinkpad-acpi: cleanup Kconfig for thinkpad-acpi Since ibm-acpi was renamed to thinkpad-acpi, rename and update its Kconfig entries and Kconfig-related symbols accordingly. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/Kconfig | 37 ++++++++++++++++++++----------------- drivers/misc/Makefile | 2 +- drivers/misc/thinkpad_acpi.c | 26 +++++++++++++------------- drivers/misc/thinkpad_acpi.h | 8 ++++---- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 5d2bcbf..2cd96a3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -122,40 +122,43 @@ config SONY_LAPTOP Read for more information. -config ACPI_IBM - tristate "IBM ThinkPad Laptop Extras" +config THINKPAD_ACPI + tristate "ThinkPad ACPI Laptop Extras" depends on X86 && ACPI select BACKLIGHT_CLASS_DEVICE ---help--- - This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds + This is a driver for the IBM and Lenovo ThinkPad laptops. It adds support for Fn-Fx key combinations, Bluetooth control, video output switching, ThinkLight control, UltraBay eject and more. - For more information about this driver see - and . + For more information about this driver see + and . - If you have an IBM ThinkPad laptop, say Y or M here. + This driver was formely known as ibm-acpi. -config ACPI_IBM_DOCK + If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. + +config THINKPAD_ACPI_DOCK bool "Legacy Docking Station Support" - depends on ACPI_IBM + depends on THINKPAD_ACPI depends on ACPI_DOCK=n default n ---help--- - Allows the ibm_acpi driver to handle docking station events. - This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI. It will - allow locking and removing the laptop from the docking station, - but will not properly connect PCI devices. + Allows the thinkpad_acpi driver to handle docking station events. + This support was made obsolete by the generic ACPI docking station + support (CONFIG_ACPI_DOCK). It will allow locking and removing the + laptop from the docking station, but will not properly connect PCI + devices. If you are not sure, say N here. -config ACPI_IBM_BAY +config THINKPAD_ACPI_BAY bool "Legacy Removable Bay Support" - depends on ACPI_IBM + depends on THINKPAD_ACPI default y ---help--- - Allows the ibm_acpi driver to handle removable bays. It will allow - disabling the device in the bay, and also generate notifications when - the bay lever is ejected or inserted. + Allows the thinkpad_acpi driver to handle removable bays. It will + eletrically disable the device in the bay, and also generate + notifications when the bay lever is ejected or inserted. If you are not sure, say Y here. diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index ebf4ff2..e325164 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -12,4 +12,4 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o -obj-$(CONFIG_ACPI_IBM) += thinkpad_acpi.o +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 2836516..bb789db 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1040,7 +1040,7 @@ static int light_write(char *buf) /* don't list other alternatives as we install a notify handler on the 570 */ IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ -#ifdef CONFIG_ACPI_IBM_DOCK +#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 */ @@ -1111,13 +1111,13 @@ static int dock_write(char *buf) return 0; } -#endif /* CONFIG_ACPI_IBM_DOCK */ +#endif /* CONFIG_THINKPAD_ACPI_DOCK */ /************************************************************************* * Bay subdriver */ -#ifdef CONFIG_ACPI_IBM_BAY +#ifdef CONFIG_THINKPAD_ACPI_BAY static int bay_status_supported; static int bay_status2_supported; static int bay_eject_supported; @@ -1208,7 +1208,7 @@ static int bay_write(char *buf) return 0; } -#endif /* CONFIG_ACPI_IBM_BAY */ +#endif /* CONFIG_THINKPAD_ACPI_BAY */ /************************************************************************* * CMOS subdriver @@ -2477,7 +2477,7 @@ static struct ibm_struct ibms[] = { .read = light_read, .write = light_write, }, -#ifdef CONFIG_ACPI_IBM_DOCK +#ifdef CONFIG_THINKPAD_ACPI_DOCK { .name = "dock", .read = dock_read, @@ -2494,7 +2494,7 @@ static struct ibm_struct ibms[] = { .type = ACPI_SYSTEM_NOTIFY, }, #endif -#ifdef CONFIG_ACPI_IBM_BAY +#ifdef CONFIG_THINKPAD_ACPI_BAY { .name = "bay", .init = bay_init, @@ -2504,7 +2504,7 @@ static struct ibm_struct ibms[] = { .handle = &bay_handle, .type = ACPI_SYSTEM_NOTIFY, }, -#endif /* CONFIG_ACPI_IBM_BAY */ +#endif /* CONFIG_THINKPAD_ACPI_BAY */ { .name = "cmos", .read = cmos_read, @@ -2686,12 +2686,12 @@ IBM_PARAM(hotkey); IBM_PARAM(bluetooth); IBM_PARAM(video); IBM_PARAM(light); -#ifdef CONFIG_ACPI_IBM_DOCK +#ifdef CONFIG_THINKPAD_ACPI_DOCK IBM_PARAM(dock); #endif -#ifdef CONFIG_ACPI_IBM_BAY +#ifdef CONFIG_THINKPAD_ACPI_BAY IBM_PARAM(bay); -#endif /* CONFIG_ACPI_IBM_BAY */ +#endif /* CONFIG_THINKPAD_ACPI_BAY */ IBM_PARAM(cmos); IBM_PARAM(led); IBM_PARAM(beep); @@ -2725,18 +2725,18 @@ static int __init acpi_ibm_init(void) IBM_HANDLE_INIT(hkey); IBM_HANDLE_INIT(lght); IBM_HANDLE_INIT(cmos); -#ifdef CONFIG_ACPI_IBM_DOCK +#ifdef CONFIG_THINKPAD_ACPI_DOCK IBM_HANDLE_INIT(dock); #endif IBM_HANDLE_INIT(pci); -#ifdef CONFIG_ACPI_IBM_BAY +#ifdef CONFIG_THINKPAD_ACPI_BAY IBM_HANDLE_INIT(bay); if (bay_handle) IBM_HANDLE_INIT(bay_ej); IBM_HANDLE_INIT(bay2); if (bay2_handle) IBM_HANDLE_INIT(bay2_ej); -#endif /* CONFIG_ACPI_IBM_BAY */ +#endif /* CONFIG_THINKPAD_ACPI_BAY */ IBM_HANDLE_INIT(beep); IBM_HANDLE_INIT(ecrd); IBM_HANDLE_INIT(ecwr); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 7ebaaa4..ee1b93a 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -163,7 +163,7 @@ static int register_ibmacpi_subdriver(struct ibm_struct *ibm); * Bay subdriver */ -#ifdef CONFIG_ACPI_IBM_BAY +#ifdef CONFIG_THINKPAD_ACPI_BAY static int bay_status_supported, bay_eject_supported; static int bay_status2_supported, bay_eject2_supported; @@ -174,7 +174,7 @@ static int bay_init(void); static void bay_notify(struct ibm_struct *ibm, u32 event); static int bay_read(char *p); static int bay_write(char *buf); -#endif /* CONFIG_ACPI_IBM_BAY */ +#endif /* CONFIG_THINKPAD_ACPI_BAY */ /* @@ -229,13 +229,13 @@ static int cmos_write(char *buf); */ static acpi_handle pci_handle; -#ifdef CONFIG_ACPI_IBM_DOCK +#ifdef CONFIG_THINKPAD_ACPI_DOCK static acpi_handle dock_handle; static void dock_notify(struct ibm_struct *ibm, u32 event); static int dock_read(char *p); static int dock_write(char *buf); -#endif /* CONFIG_ACPI_IBM_DOCK */ +#endif /* CONFIG_THINKPAD_ACPI_DOCK */ /* -- cgit v1.1 From d903ac5455102b13d0e28d6a39f640175fb4cd4d Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 29 Mar 2007 01:58:42 -0300 Subject: ACPI: thinkpad-acpi: add compatibility MODULE_ALIAS entry Add a ibm_acpi module alias for userpace, so that modprobe ibm_acpi will still load the correct driver. This alias can be removed in the future, probably two years from now if nothing warrants removing it sooner. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index bb789db..90ffc46 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -86,6 +86,9 @@ 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)) /**************************************************************************** -- cgit v1.1 From 643f12dbb660e139fbaea268f3e3ce4d7d594b8f Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 29 Mar 2007 01:58:43 -0300 Subject: ACPI: thinkpad-acpi: cleanup after rename Cleanup documentation, driver strings and other misc stuff, now that the driver is named "thinkpad-acpi". Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 46 ++++++++++++++++++++++++----------------- drivers/misc/thinkpad_acpi.c | 18 +++++++++------- drivers/misc/thinkpad_acpi.h | 21 ++++++++++--------- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index f409f4b..af18d29 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -1,17 +1,22 @@ - IBM ThinkPad ACPI Extras Driver + ThinkPad ACPI Extras Driver - Version 0.13 - 31 December 2006 + Version 0.14 + March 26th, 2007 Borislav Deianov Henrique de Moraes Holschuh http://ibm-acpi.sf.net/ -This is a Linux ACPI driver for the IBM ThinkPad laptops. It supports -various features of these laptops which are accessible through the -ACPI framework but not otherwise fully supported by the generic Linux -ACPI drivers. +This is a Linux driver for the IBM and Lenovo ThinkPad laptops. It +supports various features of these laptops which are accessible +through the ACPI and ACPI EC framework, but not otherwise fully +supported by the generic Linux ACPI drivers. + +This driver used to be named ibm-acpi until kernel 2.6.21 and release +0.13-20070314. It used to be in the drivers/acpi tree, but it was +moved to the drivers/misc tree and renamed to thinkpad-acpi for kernel +2.6.22, and release 0.14. Status @@ -43,6 +48,8 @@ Please include the following information in your report: - ThinkPad model name - a copy of your DSDT, from /proc/acpi/dsdt + - a copy of the output of dmidecode, with serial numbers + and UUIDs masked off - which driver features work and which don't - the observed behavior of non-working features @@ -53,8 +60,9 @@ Installation ------------ If you are compiling this driver as included in the Linux kernel -sources, simply enable the CONFIG_ACPI_IBM option (Power Management / -ACPI / IBM ThinkPad Laptop Extras). +sources, simply enable the CONFIG_THINKPAD_ACPI option, and optionally +enable the CONFIG_THINKPAD_ACPI_BAY option if you want the +thinkpad-specific bay functionality. Features -------- @@ -210,7 +218,7 @@ hot plugging of devices in the Linux ACPI framework. If the laptop was booted while not in the dock, the following message is shown in the logs: - Mar 17 01:42:34 aero kernel: ibm_acpi: dock device not present + Mar 17 01:42:34 aero kernel: thinkpad_acpi: dock device not present In this case, no dock-related events are generated but the dock and undock commands described below still work. They can be executed @@ -270,7 +278,7 @@ This is due to the current lack of support for hot plugging of devices in the Linux ACPI framework. If the laptop was booted without the UltraBay, the following message is shown in the logs: - Mar 17 01:42:34 aero kernel: ibm_acpi: bay device not present + Mar 17 01:42:34 aero kernel: thinkpad_acpi: bay device not present In this case, no bay-related events are generated but the eject command described below still works. It can be executed manually or @@ -637,12 +645,12 @@ range. The fan cannot be stopped or started with this command. The ThinkPad's ACPI DSDT code will reprogram the fan on its own when certain conditions are met. It will override any fan programming done -through ibm-acpi. +through thinkpad-acpi. -The ibm-acpi kernel driver can be programmed to revert the fan level -to a safe setting if userspace does not issue one of the fan commands: -"enable", "disable", "level" or "watchdog" within a configurable -ammount of time. To do this, use the "watchdog" command. +The thinkpad-acpi kernel driver can be programmed to revert the fan +level to a safe setting if userspace does not issue one of the fan +commands: "enable", "disable", "level" or "watchdog" within a +configurable ammount of time. To do this, use the "watchdog" command. echo 'watchdog ' > /proc/acpi/ibm/fan @@ -686,8 +694,8 @@ separating them with commas, for example: echo enable,0xffff > /proc/acpi/ibm/hotkey echo lcd_disable,crt_enable > /proc/acpi/ibm/video -Commands can also be specified when loading the ibm_acpi module, for -example: +Commands can also be specified when loading the thinkpad-acpi module, +for example: - modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable + modprobe thinkpad_acpi hotkey=enable,0xffff video=auto_disable diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 90ffc46..ddaedf8 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1,5 +1,5 @@ /* - * ibm_acpi.c - IBM ThinkPad ACPI Extras + * thinkpad_acpi.c - ThinkPad ACPI Extras * * * Copyright (C) 2004-2005 Borislav Deianov @@ -21,10 +21,12 @@ * 02110-1301, USA. */ -#define IBM_VERSION "0.13" +#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 @@ -318,7 +320,9 @@ static int __init setup_notify(struct ibm_struct *ibm) } acpi_driver_data(ibm->device) = ibm; - sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name); + 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); @@ -458,7 +462,7 @@ static char *next_cmd(char **cmds) * ibm-acpi init subdriver */ -static int ibm_acpi_driver_init(void) +static int thinkpad_acpi_driver_init(void) { printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); printk(IBM_INFO "%s\n", IBM_URL); @@ -470,7 +474,7 @@ static int ibm_acpi_driver_init(void) return 0; } -static int ibm_acpi_driver_read(char *p) +static int thinkpad_acpi_driver_read(char *p) { int len = 0; @@ -2440,8 +2444,8 @@ static struct proc_dir_entry *proc_dir = NULL; static struct ibm_struct ibms[] = { { .name = "driver", - .init = ibm_acpi_driver_init, - .read = ibm_acpi_driver_read, + .init = thinkpad_acpi_driver_init, + .read = thinkpad_acpi_driver_read, }, { .name = "hotkey", diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index ee1b93a..015c02b 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -1,5 +1,5 @@ /* - * ibm_acpi.h - IBM ThinkPad ACPI Extras + * thinkpad_acpi.h - ThinkPad ACPI Extras * * * Copyright (C) 2004-2005 Borislav Deianov @@ -21,8 +21,8 @@ * 02110-1301, USA. */ -#ifndef __IBM_ACPI_H__ -#define __IBM_ACPI_H__ +#ifndef __THINKPAD_ACPI_H__ +#define __THINKPAD_ACPI_H__ #include #include @@ -47,12 +47,13 @@ * Main driver */ -#define IBM_NAME "ibm" -#define IBM_DESC "IBM ThinkPad ACPI Extras" -#define IBM_FILE "ibm_acpi" +#define IBM_NAME "thinkpad" +#define IBM_DESC "ThinkPad ACPI Extras" +#define IBM_FILE "thinkpad_acpi" #define IBM_URL "http://ibm-acpi.sf.net/" -#define IBM_DIR IBM_NAME +#define IBM_DIR "ibm" +#define IBM_ACPI_EVENT_PREFIX "ibm" #define IBM_LOG IBM_FILE ": " #define IBM_ERR KERN_ERR IBM_LOG @@ -99,8 +100,8 @@ static void ibm_handle_init(char *name, /* procfs support */ static struct proc_dir_entry *proc_dir; -static int ibm_acpi_driver_init(void); -static int ibm_acpi_driver_read(char *p); +static int thinkpad_acpi_driver_init(void); +static int thinkpad_acpi_driver_read(char *p); /* procfs helpers */ static int dispatch_read(char *page, char **start, off_t off, int count, @@ -434,4 +435,4 @@ static int wan_read(char *p); static int wan_write(char *buf); -#endif /* __IBM_ACPI_H */ +#endif /* __THINKPAD_ACPI_H */ -- cgit v1.1 From 756970ad4bb93027a60da2de9b43d094b7f387a2 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Thu, 29 Mar 2007 01:58:44 -0300 Subject: ACPI: thinkpad-acpi: update MAINTAINERS Update MAINTAINERS file for the ibm-acpi -> thinkpad-acpi renaming. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- MAINTAINERS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index dd6978b..7d42183 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1618,15 +1618,6 @@ W: http://www.ia64-linux.org/ T: git kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git S: Maintained -IBM ACPI EXTRAS DRIVER -P: Henrique de Moraes Holschuh -M: ibm-acpi@hmh.eng.br -L: ibm-acpi-devel@lists.sourceforge.net -W: http://ibm-acpi.sourceforge.net -W: http://thinkwiki.org/wiki/Ibm-acpi -T: git repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git -S: Maintained - SN-IA64 (Itanium) SUB-PLATFORM P: Jes Sorensen M: jes@sgi.com @@ -3118,6 +3109,15 @@ P: Chris Zankel M: chris@zankel.net S: Maintained +THINKPAD ACPI EXTRAS DRIVER +P: Henrique de Moraes Holschuh +M: ibm-acpi@hmh.eng.br +L: ibm-acpi-devel@lists.sourceforge.net +W: http://ibm-acpi.sourceforge.net +W: http://thinkwiki.org/wiki/Ibm-acpi +T: git repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git +S: Maintained + UltraSPARC (sparc64): P: David S. Miller M: davem@davemloft.net -- cgit v1.1 From 6700121b535fa16fe1c8aaac03559b2f12909726 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:25 -0300 Subject: ACPI: thinkpad-acpi: rename register_ibmacpi_subdriver Rename register_ibmacpi_subdriver to register_tpacpi_subdriver, as we are not called ibmacpi anymore. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 4 ++-- drivers/misc/thinkpad_acpi.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index ddaedf8..cac73e5 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -345,7 +345,7 @@ static int __init ibm_device_add(struct acpi_device *device) return 0; } -static int __init register_ibmacpi_subdriver(struct ibm_struct *ibm) +static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) { int ret; @@ -2574,7 +2574,7 @@ static int __init ibm_init(struct ibm_struct *ibm) return 0; if (ibm->hid) { - ret = register_ibmacpi_subdriver(ibm); + ret = register_tpacpi_subdriver(ibm); if (ret < 0) return ret; ibm->driver_registered = 1; diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 015c02b..b86e574 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -157,7 +157,7 @@ static void ibm_exit(struct ibm_struct *ibm); static void dispatch_notify(acpi_handle handle, u32 event, void *data); static int setup_notify(struct ibm_struct *ibm); static int ibm_device_add(struct acpi_device *device); -static int register_ibmacpi_subdriver(struct ibm_struct *ibm); +static int register_tpacpi_subdriver(struct ibm_struct *ibm); /* -- cgit v1.1 From 142cfc90f026b0b8fd1a14ba11ae29eb7b1b6ca1 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:26 -0300 Subject: ACPI: thinkpad-acpi: rename one stray use of ibm-acpi in a comment Rename a stray use of ibm-acpi on a comment, no functional changes. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index cac73e5..4b43486 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -459,7 +459,7 @@ static char *next_cmd(char **cmds) ****************************************************************************/ /************************************************************************* - * ibm-acpi init subdriver + * thinkpad-acpi init subdriver */ static int thinkpad_acpi_driver_init(void) -- cgit v1.1 From 1def7115f0277ce9d2a54efd0ae187aa88d5c7fa Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:27 -0300 Subject: ACPI: thinkpad-acpi: rename module glue Rename module init and exit functions, now that we are not called ibm-acpi anymore. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 12 ++++++------ drivers/misc/thinkpad_acpi.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 4b43486..80ef195 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2707,7 +2707,7 @@ IBM_PARAM(brightness); IBM_PARAM(volume); IBM_PARAM(fan); -static int __init acpi_ibm_init(void) +static int __init thinkpad_acpi_module_init(void) { int ret, i; @@ -2754,7 +2754,7 @@ static int __init acpi_ibm_init(void) proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); if (!proc_dir) { printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); - acpi_ibm_exit(); + thinkpad_acpi_module_exit(); return -ENODEV; } proc_dir->owner = THIS_MODULE; @@ -2764,7 +2764,7 @@ static int __init acpi_ibm_init(void) if (ret >= 0 && *ibms[i].param) ret = ibms[i].write(ibms[i].param); if (ret < 0) { - acpi_ibm_exit(); + thinkpad_acpi_module_exit(); return ret; } } @@ -2772,7 +2772,7 @@ static int __init acpi_ibm_init(void) return 0; } -static void acpi_ibm_exit(void) +static void thinkpad_acpi_module_exit(void) { int i; @@ -2786,5 +2786,5 @@ static void acpi_ibm_exit(void) kfree(ibm_thinkpad_ec_found); } -module_init(acpi_ibm_init); -module_exit(acpi_ibm_exit); +module_init(thinkpad_acpi_module_init); +module_exit(thinkpad_acpi_module_exit); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index b86e574..8b2fd1a 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -115,8 +115,8 @@ static int experimental; static char *ibm_thinkpad_ec_found; static char* check_dmi_for_ec(void); -static int acpi_ibm_init(void); -static void acpi_ibm_exit(void); +static int thinkpad_acpi_module_init(void); +static void thinkpad_acpi_module_exit(void); /**************************************************************************** -- cgit v1.1 From efa27145df34eacf2569bd45f68dbe00003d3616 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:28 -0300 Subject: ACPI: thinkpad-acpi: rename thinkpad constants Rename all IBMACPI_ constants, now that we are not called ibm-acpi anymore. Driver-specific constants are now prefixed TPACPI_, ThinkPad firmware specific ones are now prefixed TP_CMOS_, TP_ACPI_, or TP_EC_. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 188 +++++++++++++++++++++---------------------- drivers/misc/thinkpad_acpi.h | 64 +++++++-------- 2 files changed, 125 insertions(+), 127 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 80ef195..1683bfe 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -781,16 +781,16 @@ static int video_init(void) if (!vid_handle) /* video switching not supported on R30, R31 */ - video_supported = IBMACPI_VIDEO_NONE; + video_supported = TPACPI_VIDEO_NONE; else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) /* 570 */ - video_supported = IBMACPI_VIDEO_570; + video_supported = TPACPI_VIDEO_570; else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) /* 600e/x, 770e, 770x */ - video_supported = IBMACPI_VIDEO_770; + video_supported = TPACPI_VIDEO_770; else /* all others */ - video_supported = IBMACPI_VIDEO_NEW; + video_supported = TPACPI_VIDEO_NEW; return 0; } @@ -805,15 +805,15 @@ static int video_status(void) int status = 0; int i; - if (video_supported == IBMACPI_VIDEO_570) { + if (video_supported == TPACPI_VIDEO_570) { if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) status = i & 3; - } else if (video_supported == IBMACPI_VIDEO_770) { + } 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 == IBMACPI_VIDEO_NEW) { + } else if (video_supported == TPACPI_VIDEO_NEW) { acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); if (acpi_evalf(NULL, &i, "\\VCDC", "d")) status |= 0x02 * i; @@ -832,10 +832,10 @@ static int video_autosw(void) { int autosw = 0; - if (video_supported == IBMACPI_VIDEO_570) + if (video_supported == TPACPI_VIDEO_570) acpi_evalf(vid_handle, &autosw, "SWIT", "d"); - else if (video_supported == IBMACPI_VIDEO_770 || - video_supported == IBMACPI_VIDEO_NEW) + else if (video_supported == TPACPI_VIDEO_770 || + video_supported == TPACPI_VIDEO_NEW) acpi_evalf(vid_handle, &autosw, "^VDEE", "d"); return autosw & 1; @@ -848,7 +848,7 @@ static int video_switch(void) if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) return -EIO; - ret = video_supported == IBMACPI_VIDEO_570 ? + 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); @@ -858,9 +858,9 @@ static int video_switch(void) static int video_expand(void) { - if (video_supported == IBMACPI_VIDEO_570) + if (video_supported == TPACPI_VIDEO_570) return acpi_evalf(ec_handle, NULL, "_Q17", "v"); - else if (video_supported == IBMACPI_VIDEO_770) + else if (video_supported == TPACPI_VIDEO_770) return acpi_evalf(vid_handle, NULL, "VEXP", "v"); else return acpi_evalf(NULL, NULL, "\\VEXP", "v"); @@ -870,10 +870,10 @@ static int video_switch2(int status) { int ret; - if (video_supported == IBMACPI_VIDEO_570) { + if (video_supported == TPACPI_VIDEO_570) { ret = acpi_evalf(NULL, NULL, "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); - } else if (video_supported == IBMACPI_VIDEO_770) { + } else if (video_supported == TPACPI_VIDEO_770) { int autosw = video_autosw(); if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) return -EIO; @@ -904,12 +904,12 @@ static int video_read(char *p) 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 == IBMACPI_VIDEO_NEW) + 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 == IBMACPI_VIDEO_NEW) + 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"); @@ -936,10 +936,10 @@ static int video_write(char *buf) enable |= 0x02; } else if (strlencmp(cmd, "crt_disable") == 0) { disable |= 0x02; - } else if (video_supported == IBMACPI_VIDEO_NEW && + } else if (video_supported == TPACPI_VIDEO_NEW && strlencmp(cmd, "dvi_enable") == 0) { enable |= 0x08; - } else if (video_supported == IBMACPI_VIDEO_NEW && + } else if (video_supported == TPACPI_VIDEO_NEW && strlencmp(cmd, "dvi_disable") == 0) { disable |= 0x08; } else if (strlencmp(cmd, "auto_enable") == 0) { @@ -1283,16 +1283,16 @@ static int led_init(void) { if (!led_handle) /* led not supported on R30, R31 */ - led_supported = IBMACPI_LED_NONE; + led_supported = TPACPI_LED_NONE; else if (strlencmp(led_path, "SLED") == 0) /* 570 */ - led_supported = IBMACPI_LED_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 = IBMACPI_LED_OLD; + led_supported = TPACPI_LED_OLD; else /* all others */ - led_supported = IBMACPI_LED_NEW; + led_supported = TPACPI_LED_NEW; return 0; } @@ -1309,7 +1309,7 @@ static int led_read(char *p) } len += sprintf(p + len, "status:\t\tsupported\n"); - if (led_supported == IBMACPI_LED_570) { + if (led_supported == TPACPI_LED_570) { /* 570 */ int i, status; for (i = 0; i < 8; i++) { @@ -1354,23 +1354,23 @@ static int led_write(char *buf) } else return -EINVAL; - if (led_supported == IBMACPI_LED_570) { + 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 == IBMACPI_LED_OLD) { + } else if (led_supported == TPACPI_LED_OLD) { /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ led = 1 << led; - ret = ec_write(IBMACPI_LED_EC_HLMS, led); + ret = ec_write(TPACPI_LED_EC_HLMS, led); if (ret >= 0) ret = - ec_write(IBMACPI_LED_EC_HLBL, + ec_write(TPACPI_LED_EC_HLBL, led * led_exp_hlbl[ind]); if (ret >= 0) ret = - ec_write(IBMACPI_LED_EC_HLCL, + ec_write(TPACPI_LED_EC_HLCL, led * led_exp_hlcl[ind]); if (ret < 0) return ret; @@ -1467,29 +1467,29 @@ static int thermal_init(void) printk(IBM_ERR "ThinkPad ACPI EC access misbehaving, " "falling back to ACPI TMPx access mode\n"); - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; + thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; } else { printk(IBM_ERR "ThinkPad ACPI EC access misbehaving, " "disabling thermal sensors access\n"); - thermal_read_mode = IBMACPI_THERMAL_NONE; + thermal_read_mode = TPACPI_THERMAL_NONE; } } else { thermal_read_mode = (ta2 != 0) ? - IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; + TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8; } } else if (acpi_tmp7) { if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { /* 600e/x, 770e, 770x */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; + thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT; } else { /* Standard ACPI TMPx access, max 8 sensors */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; + thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; } } else { /* temperatures not supported on 570, G4x, R30, R31, R32 */ - thermal_read_mode = IBMACPI_THERMAL_NONE; + thermal_read_mode = TPACPI_THERMAL_NONE; } return 0; @@ -1505,8 +1505,8 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) return -EINVAL; switch (thermal_read_mode) { -#if IBMACPI_MAX_THERMAL_SENSORS >= 16 - case IBMACPI_THERMAL_TPEC_16: +#if TPACPI_MAX_THERMAL_SENSORS >= 16 + case TPACPI_THERMAL_TPEC_16: for (i = 0; i < 8; i++) { if (!acpi_ec_read(0xC0 + i, &tmp)) return -EIO; @@ -1514,15 +1514,15 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) } /* fallthrough */ #endif - case IBMACPI_THERMAL_TPEC_8: + case TPACPI_THERMAL_TPEC_8: for (i = 0; i < 8; i++) { if (!acpi_ec_read(0x78 + i, &tmp)) return -EIO; s->temp[i] = tmp * 1000; } - return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; + return (thermal_read_mode == TPACPI_THERMAL_TPEC_16) ? 16 : 8; - case IBMACPI_THERMAL_ACPI_UPDT: + case TPACPI_THERMAL_ACPI_UPDT: if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) return -EIO; for (i = 0; i < 8; i++) { @@ -1533,7 +1533,7 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) } return 8; - case IBMACPI_THERMAL_ACPI_TMP07: + case TPACPI_THERMAL_ACPI_TMP07: for (i = 0; i < 8; i++) { tmpi[3] = '0' + i; if (!acpi_evalf(ec_handle, &t, tmpi, "d")) @@ -1542,7 +1542,7 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) } return 8; - case IBMACPI_THERMAL_NONE: + case TPACPI_THERMAL_NONE: default: return 0; } @@ -1848,18 +1848,18 @@ static int volume_write(char *buf) /* * FAN ACCESS MODES * - * IBMACPI_FAN_RD_ACPI_GFAN: + * TPACPI_FAN_RD_ACPI_GFAN: * ACPI GFAN method: returns fan level * - * see IBMACPI_FAN_WR_ACPI_SFAN + * see TPACPI_FAN_WR_ACPI_SFAN * EC 0x2f not available if GFAN exists * - * IBMACPI_FAN_WR_ACPI_SFAN: + * TPACPI_FAN_WR_ACPI_SFAN: * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) * * EC 0x2f might be available *for reading*, but never for writing. * - * IBMACPI_FAN_WR_TPEC: + * TPACPI_FAN_WR_TPEC: * ThinkPad EC register 0x2f (HFSP): fan control loop mode Supported * on almost all ThinkPads * @@ -1888,7 +1888,7 @@ static int volume_write(char *buf) * 0x00 = stop * 0x07 = max (set when temperatures critical) * Some ThinkPads may have other levels, see - * IBMACPI_FAN_WR_ACPI_FANS (X31/X40/X41) + * 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 @@ -1923,7 +1923,7 @@ static int volume_write(char *buf) * For firmware bugs, refer to: * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues * - * IBMACPI_FAN_WR_ACPI_FANS: + * TPACPI_FAN_WR_ACPI_FANS: * ThinkPad X31, X40, X41. Not available in the X60. * * FANS ACPI handle: takes three arguments: low speed, medium speed, @@ -1940,7 +1940,7 @@ static int volume_write(char *buf) * ACPI DSDT switches which set is in use depending on various * factors. * - * IBMACPI_FAN_WR_TPEC is also available and should be used to + * 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. */ @@ -1966,21 +1966,21 @@ IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ static int fan_init(void) { - fan_status_access_mode = IBMACPI_FAN_NONE; - fan_control_access_mode = IBMACPI_FAN_WR_NONE; + fan_status_access_mode = TPACPI_FAN_NONE; + fan_control_access_mode = TPACPI_FAN_WR_NONE; fan_control_commands = 0; fan_control_status_known = 1; fan_watchdog_maxinterval = 0; if (gfan_handle) { /* 570, 600e/x, 770e, 770x */ - fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; + 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 */ if (likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) { - fan_status_access_mode = IBMACPI_FAN_RD_TPEC; + fan_status_access_mode = TPACPI_FAN_RD_TPEC; /* In some ThinkPads, neither the EC nor the ACPI * DSDT initialize the fan status, and it ends up @@ -2015,9 +2015,9 @@ static int fan_init(void) if (sfan_handle) { /* 570, 770x-JL */ - fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; + fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN; fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | IBMACPI_FAN_CMD_ENABLE; + TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE; } else { if (!gfan_handle) { /* gfan without sfan means no fan control */ @@ -2026,16 +2026,16 @@ static int fan_init(void) if (fans_handle) { /* X31, X40, X41 */ fan_control_access_mode = - IBMACPI_FAN_WR_ACPI_FANS; + TPACPI_FAN_WR_ACPI_FANS; fan_control_commands |= - IBMACPI_FAN_CMD_SPEED | - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; + TPACPI_FAN_CMD_SPEED | + TPACPI_FAN_CMD_LEVEL | + TPACPI_FAN_CMD_ENABLE; } else { - fan_control_access_mode = IBMACPI_FAN_WR_TPEC; + fan_control_access_mode = TPACPI_FAN_WR_TPEC; fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; + TPACPI_FAN_CMD_LEVEL | + TPACPI_FAN_CMD_ENABLE; } } } @@ -2048,10 +2048,10 @@ static int fan_get_status(u8 *status) u8 s; /* TODO: - * Add IBMACPI_FAN_RD_ACPI_FANS ? */ + * Add TPACPI_FAN_RD_ACPI_FANS ? */ switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_ACPI_GFAN: + case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) @@ -2062,7 +2062,7 @@ static int fan_get_status(u8 *status) break; - case IBMACPI_FAN_RD_TPEC: + case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ if (unlikely(!acpi_ec_read(fan_status_offset, &s))) return -EIO; @@ -2090,7 +2090,7 @@ static int fan_get_speed(unsigned int *speed) u8 hi, lo; switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_TPEC: + 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))) @@ -2140,7 +2140,7 @@ static void fan_watchdog_reset(void) static int fan_set_level(int level) { switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: + case TPACPI_FAN_WR_ACPI_SFAN: if (level >= 0 && level <= 7) { if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) return -EIO; @@ -2148,10 +2148,10 @@ static int fan_set_level(int level) return -EINVAL; break; - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((level != IBMACPI_FAN_EC_AUTO) && - (level != IBMACPI_FAN_EC_DISENGAGED) && + 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; @@ -2173,14 +2173,14 @@ static int fan_set_enable(void) int rc; switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: + 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 = IBMACPI_FAN_EC_AUTO; + s = TP_EC_FAN_AUTO; if (!acpi_ec_write(fan_status_offset, s)) return -EIO; @@ -2188,7 +2188,7 @@ static int fan_set_enable(void) fan_control_status_known = 1; break; - case IBMACPI_FAN_WR_ACPI_SFAN: + case TPACPI_FAN_WR_ACPI_SFAN: if ((rc = fan_get_status(&s)) < 0) return rc; @@ -2211,15 +2211,15 @@ static int fan_set_enable(void) static int fan_set_disable(void) { switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: + case TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: if (!acpi_ec_write(fan_status_offset, 0x00)) return -EIO; else fan_control_status_known = 1; break; - case IBMACPI_FAN_WR_ACPI_SFAN: + case TPACPI_FAN_WR_ACPI_SFAN: if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) return -EIO; break; @@ -2233,7 +2233,7 @@ static int fan_set_disable(void) static int fan_set_speed(int speed) { switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_ACPI_FANS: if (speed >= 0 && speed <= 65535) { if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", speed, speed, speed)) @@ -2256,7 +2256,7 @@ static int fan_read(char *p) unsigned int speed = 0; switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_ACPI_GFAN: + case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ if ((rc = fan_get_status(&status)) < 0) return rc; @@ -2266,7 +2266,7 @@ static int fan_read(char *p) (status != 0) ? "enabled" : "disabled", status); break; - case IBMACPI_FAN_RD_TPEC: + case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ if ((rc = fan_get_status(&status)) < 0) return rc; @@ -2277,7 +2277,7 @@ static int fan_read(char *p) else /* Return most likely status. In fact, it * might be the only possible status */ - status = IBMACPI_FAN_EC_AUTO; + status = TP_EC_FAN_AUTO; } len += sprintf(p + len, "status:\t\t%s\n", @@ -2291,25 +2291,25 @@ static int fan_read(char *p) len += sprintf(p + len, "speed:\t\t%d\n", speed); - if (status & IBMACPI_FAN_EC_DISENGAGED) + if (status & TP_EC_FAN_FULLSPEED) /* Disengaged mode takes precedence */ len += sprintf(p + len, "level:\t\tdisengaged\n"); - else if (status & IBMACPI_FAN_EC_AUTO) + 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 IBMACPI_FAN_NONE: + case TPACPI_FAN_NONE: default: len += sprintf(p + len, "status:\t\tnot supported\n"); } - if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { + if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) { len += sprintf(p + len, "commands:\tlevel "); switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: + case TPACPI_FAN_WR_ACPI_SFAN: len += sprintf(p + len, " ( is 0-7)\n"); break; @@ -2320,12 +2320,12 @@ static int fan_read(char *p) } } - if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) + if (fan_control_commands & TPACPI_FAN_CMD_ENABLE) len += sprintf(p + len, "commands:\tenable, disable\n" "commands:\twatchdog ( is 0 (off), " "1-120 (seconds))\n"); - if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) + if (fan_control_commands & TPACPI_FAN_CMD_SPEED) len += sprintf(p + len, "commands:\tspeed " " ( is 0-65535)\n"); @@ -2337,9 +2337,9 @@ static int fan_write_cmd_level(const char *cmd, int *rc) int level; if (strlencmp(cmd, "level auto") == 0) - level = IBMACPI_FAN_EC_AUTO; + level = TP_EC_FAN_AUTO; else if (strlencmp(cmd, "level disengaged") == 0) - level = IBMACPI_FAN_EC_DISENGAGED; + level = TP_EC_FAN_FULLSPEED; else if (sscanf(cmd, "level %d", &level) != 1) return 0; @@ -2412,13 +2412,13 @@ static int fan_write(char *buf) int rc = 0; while (!rc && (cmd = next_cmd(&buf))) { - if (!((fan_control_commands & IBMACPI_FAN_CMD_LEVEL) && + if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) && fan_write_cmd_level(cmd, &rc)) && - !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && + !((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 & IBMACPI_FAN_CMD_SPEED) && + !((fan_control_commands & TPACPI_FAN_CMD_SPEED) && fan_write_cmd_speed(cmd, &rc)) ) rc = -EINVAL; diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 8b2fd1a..02a297e 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -256,29 +256,27 @@ enum { /* Fan control constants */ fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) * 0x84 must be read before 0x85 */ - IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer - * disengaged */ - IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan - * control */ + TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ + TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ }; enum fan_status_access_mode { - IBMACPI_FAN_NONE = 0, /* No fan status or control */ - IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ + TPACPI_FAN_NONE = 0, /* No fan status or control */ + TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ }; enum fan_control_access_mode { - IBMACPI_FAN_WR_NONE = 0, /* No fan control */ - IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ + TPACPI_FAN_WR_NONE = 0, /* No fan control */ + TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ }; enum fan_control_commands { - IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, * and also watchdog cmd */ }; @@ -333,16 +331,16 @@ static int hotkey_write(char *buf); */ enum led_access_mode { - IBMACPI_LED_NONE = 0, - IBMACPI_LED_570, /* 570 */ - IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - IBMACPI_LED_NEW, /* all others */ + TPACPI_LED_NONE = 0, + TPACPI_LED_570, /* 570 */ + TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + TPACPI_LED_NEW, /* all others */ }; -enum { /* For IBMACPI_LED_OLD */ - IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ - IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ - IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +enum { /* For TPACPI_LED_OLD */ + TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ }; static enum led_access_mode led_supported; @@ -370,16 +368,16 @@ static int light_write(char *buf); */ enum thermal_access_mode { - IBMACPI_THERMAL_NONE = 0, /* No thermal support */ - IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ + TPACPI_THERMAL_NONE = 0, /* No thermal support */ + TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ }; -#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ struct ibm_thermal_sensors_struct { - s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; + s32 temp[TPACPI_MAX_THERMAL_SENSORS]; }; static int thermal_init(void); @@ -392,10 +390,10 @@ static int thermal_read(char *p); */ enum video_access_mode { - IBMACPI_VIDEO_NONE = 0, - IBMACPI_VIDEO_570, /* 570 */ - IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - IBMACPI_VIDEO_NEW, /* all others */ + TPACPI_VIDEO_NONE = 0, + TPACPI_VIDEO_570, /* 570 */ + TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + TPACPI_VIDEO_NEW, /* all others */ }; static enum video_access_mode video_supported; -- cgit v1.1 From f51d1a39840ae5e8678d702ab57377c611fc3826 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:29 -0300 Subject: ACPI: thinkpad-acpi: update fan firmware documentation Update some stuff in the in-code text describing the ThinkPad fan firmware. This patch has no code changes. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 1683bfe..4131a78 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1852,16 +1852,17 @@ static int volume_write(char *buf) * ACPI GFAN method: returns fan level * * see TPACPI_FAN_WR_ACPI_SFAN - * EC 0x2f not available if GFAN exists + * 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 might be available *for reading*, but never for writing. + * 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 + * 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 @@ -1875,12 +1876,13 @@ static int volume_write(char *buf) * 7 automatic mode engaged; * (default operation mode of the ThinkPad) * fan level is ignored in this mode. - * 6 disengage mode (takes precedence over bit 7); + * 6 full speed mode (takes precedence over bit 7); * not available on all thinkpads. May disable - * the tachometer, and speeds up fan to 100% duty-cycle, - * which speeds it up far above the standard RPM - * levels. It is not impossible that it could cause - * hardware damage. + * 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. @@ -1916,9 +1918,8 @@ static int volume_write(char *buf) * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings * might result. * - * FIRMWARE BUG: when EC 0x2f bit 6 is set (disengaged mode), this - * register is not invalidated in ThinkPads that disable tachometer - * readings. Thus, the tachometer readings go stale. + * 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 @@ -2283,9 +2284,6 @@ static int fan_read(char *p) len += sprintf(p + len, "status:\t\t%s\n", (status != 0) ? "enabled" : "disabled"); - /* No ThinkPad boots on disengaged mode, we can safely - * assume the tachometer is online if fan control status - * was unknown */ if ((rc = fan_get_speed(&speed)) < 0) return rc; -- cgit v1.1 From 132ce09123755ec5e3d3a8ae22f4f753c3baac97 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:30 -0300 Subject: ACPI: thinkpad-acpi: add debug mode Add a debug mode parameter and verbose debug mode Kconfig option. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 13 +++++++++++++ drivers/misc/Kconfig | 10 ++++++++++ drivers/misc/thinkpad_acpi.c | 3 +++ drivers/misc/thinkpad_acpi.h | 13 +++++++++++++ 4 files changed, 39 insertions(+) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index af18d29..82fd822 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -699,3 +699,16 @@ for example: modprobe thinkpad_acpi hotkey=enable,0xffff video=auto_disable +Enabling debugging output +------------------------- + +The module takes a debug paramater which can be used to selectively +enable various classes of debugging output, for example: + + modprobe ibm_acpi debug=0xffff + +will enable all debugging output classes. It takes a bitmask, so +to enable more than one output class, just add their values. + +There is also a kernel build option to enable more debugging +information, which may be necessary to debug driver problems. diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 2cd96a3..44e4c8f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -137,6 +137,16 @@ config THINKPAD_ACPI If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. +config THINKPAD_ACPI_DEBUG + bool "Verbose debug mode" + depends on THINKPAD_ACPI + default n + ---help--- + Enables extra debugging information, at the expense of a slightly + increase in driver size. + + If you are not sure, say N here. + config THINKPAD_ACPI_DOCK bool "Legacy Docking Station Support" depends on THINKPAD_ACPI diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 4131a78..7fa906f 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2684,6 +2684,9 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp) static int experimental; module_param(experimental, int, 0); +static u32 dbg_level; +module_param_named(debug, dbg_level, uint, 0); + #define IBM_PARAM(feature) \ module_param_call(feature, set_ibm_param, NULL, NULL, 0) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 02a297e..b2348d7 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -74,6 +74,18 @@ #define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") #define strlencmp(a,b) (strncmp((a), (b), strlen(b))) +/* Debugging */ +#define TPACPI_DBG_ALL 0xffff +#define dbg_printk(a_dbg_level, format, arg...) \ + do { if (dbg_level & a_dbg_level) \ + printk(IBM_DEBUG "%s: " format, __func__ , ## arg); } while (0) +#ifdef CONFIG_THINKPAD_ACPI_DEBUG +#define vdbg_printk(a_dbg_level, format, arg...) \ + dbg_printk(a_dbg_level, format, ## arg) +#else +#define vdbg_printk(a_dbg_level, format, arg...) +#endif + /* ACPI HIDs */ #define IBM_HKEY_HID "IBM0068" #define IBM_PCI_HID "PNP0A03" @@ -112,6 +124,7 @@ static char *next_cmd(char **cmds); /* Module */ static int experimental; +static u32 dbg_level; static char *ibm_thinkpad_ec_found; static char* check_dmi_for_ec(void); -- cgit v1.1 From 5fba344cfdbaa79e6320da26c3db34dfb219a845 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:31 -0300 Subject: ACPI: thinkpad-acpi: clean up probing and move init to subdrivers Move most of the probing code to its own function, and most of the subdriver-specific init code into subdriver init functions. This allows us to not define pci_handle unless the dock subdriver is enabled, as well. This patch causes a minor userland interface change: if a subdriver doesn't detect a capability, /proc entries for it are not created anymore (as opposed to a /proc entry that just returned "unsupported"). Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 231 ++++++++++++++++++++++++++++--------------- drivers/misc/thinkpad_acpi.h | 4 +- 2 files changed, 151 insertions(+), 84 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 7fa906f..eeab394 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -277,9 +277,9 @@ static int _sta(acpi_handle handle) * ACPI device model */ -static void __init ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, - char **paths, int num_paths, char **path) +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; @@ -351,8 +351,8 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); if (!ibm->driver) { - printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); - return -1; + printk(IBM_ERR "kzalloc(ibm->driver) failed\n"); + return -ENOMEM; } sprintf(ibm->driver->name, "%s_%s", IBM_NAME, ibm->name); @@ -364,7 +364,9 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) 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; } @@ -495,6 +497,8 @@ static int hotkey_orig_mask; static int hotkey_init(void) { + IBM_HANDLE_INIT(hkey); + /* hotkey not supported on 570 */ hotkey_supported = hkey_handle != NULL; @@ -508,13 +512,14 @@ static int hotkey_init(void) return -ENODEV; } - return 0; + return (hotkey_supported)? 0 : 1; } static void hotkey_exit(void) { - if (hotkey_supported) + if (hotkey_supported) { hotkey_set(hotkey_orig_status, hotkey_orig_mask); + } } static void hotkey_notify(struct ibm_struct *ibm, u32 event) @@ -628,12 +633,14 @@ static int bluetooth_supported; static int bluetooth_init(void) { + 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"); - return 0; + return (bluetooth_supported)? 0 : 1; } static int bluetooth_status(void) @@ -697,10 +704,12 @@ static int wan_supported; static int wan_init(void) { + IBM_HANDLE_INIT(hkey); + wan_supported = hkey_handle && acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); - return 0; + return (wan_supported)? 0 : 1; } static int wan_status(void) @@ -775,6 +784,9 @@ static int video_init(void) { int ivga; + 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; @@ -792,7 +804,7 @@ static int video_init(void) /* all others */ video_supported = TPACPI_VIDEO_NEW; - return 0; + return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1; } static void video_exit(void) @@ -979,6 +991,10 @@ IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ static int light_init(void) { + 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; @@ -988,7 +1004,7 @@ static int light_init(void) light_status_supported = acpi_evalf(ec_handle, NULL, "KBLT", "qv"); - return 0; + return (light_supported)? 0 : 1; } static int light_read(char *p) @@ -1044,9 +1060,6 @@ static int light_write(char *buf) * Dock subdriver */ -/* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ - #ifdef CONFIG_THINKPAD_ACPI_DOCK IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ @@ -1055,8 +1068,19 @@ IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ "\\_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 dock_init(void) +{ + IBM_HANDLE_INIT(dock); + IBM_HANDLE_INIT(pci); + + return (dock_handle)? 0 : 1; +} + static void dock_notify(struct ibm_struct *ibm, u32 event) { int docked = dock_docked(); @@ -1147,6 +1171,13 @@ IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ static int bay_init(void) { + 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 && @@ -1157,7 +1188,8 @@ static int bay_init(void) bay_eject2_supported = bay2_handle && bay2_ej_handle && (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental); - return 0; + 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) @@ -1221,6 +1253,13 @@ static int bay_write(char *buf) * CMOS subdriver */ +static int cmos_init(void) +{ + IBM_HANDLE_INIT(cmos); + + return (cmos_handle)? 0 : 1; +} + static int cmos_eval(int cmos_cmd) { if (cmos_handle) @@ -1281,6 +1320,8 @@ IBM_HANDLE(led, ec, "SLED", /* 570 */ static int led_init(void) { + IBM_HANDLE_INIT(led); + if (!led_handle) /* led not supported on R30, R31 */ led_supported = TPACPI_LED_NONE; @@ -1294,7 +1335,7 @@ static int led_init(void) /* all others */ led_supported = TPACPI_LED_NEW; - return 0; + return (led_supported != TPACPI_LED_NONE)? 0 : 1; } #define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking")) @@ -1391,6 +1432,13 @@ static int led_write(char *buf) IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ +static int beep_init(void) +{ + IBM_HANDLE_INIT(beep); + + return (beep_handle)? 0 : 1; +} + static int beep_read(char *p) { int len = 0; @@ -1436,7 +1484,9 @@ static int thermal_init(void) { u8 t, ta1, ta2; int i; - int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); + int acpi_tmp7; + + acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); if (ibm_thinkpad_ec_found && experimental) { /* @@ -1492,7 +1542,7 @@ static int thermal_init(void) thermal_read_mode = TPACPI_THERMAL_NONE; } - return 0; + return (thermal_read_mode != TPACPI_THERMAL_NONE)? 0 : 1; } static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) @@ -1973,6 +2023,10 @@ static int fan_init(void) 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; @@ -2010,7 +2064,7 @@ static int fan_init(void) printk(IBM_ERR "ThinkPad ACPI EC access misbehaving, " "fan status and control unavailable\n"); - return 0; + return 1; } } @@ -2041,7 +2095,9 @@ static int fan_init(void) } } - return 0; + 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) @@ -2485,6 +2541,7 @@ static struct ibm_struct ibms[] = { #ifdef CONFIG_THINKPAD_ACPI_DOCK { .name = "dock", + .init = dock_init, .read = dock_read, .write = dock_write, .notify = dock_notify, @@ -2512,6 +2569,7 @@ static struct ibm_struct ibms[] = { #endif /* CONFIG_THINKPAD_ACPI_BAY */ { .name = "cmos", + .init = cmos_init, .read = cmos_read, .write = cmos_write, }, @@ -2523,6 +2581,7 @@ static struct ibm_struct ibms[] = { }, { .name = "beep", + .init = beep_init, .read = beep_read, .write = beep_write, }, @@ -2571,20 +2630,33 @@ static int __init ibm_init(struct ibm_struct *ibm) if (ibm->experimental && !experimental) return 0; - if (ibm->hid) { - ret = register_tpacpi_subdriver(ibm); - if (ret < 0) - return ret; - ibm->driver_registered = 1; - } - if (ibm->init) { ret = ibm->init(); - if (ret != 0) + 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; + } + if (ibm->read) { entry = create_proc_entry(ibm->name, S_IFREG | S_IRUGO | S_IWUSR, @@ -2592,7 +2664,8 @@ static int __init ibm_init(struct ibm_struct *ibm) if (!entry) { printk(IBM_ERR "unable to create proc entry %s\n", ibm->name); - return -ENODEV; + ret = -ENODEV; + goto err_out; } entry->owner = THIS_MODULE; entry->data = ibm; @@ -2602,36 +2675,36 @@ static int __init ibm_init(struct ibm_struct *ibm) ibm->proc_created = 1; } - if (ibm->notify) { - ret = setup_notify(ibm); - if (ret == -ENODEV) { - printk(IBM_NOTICE "disabling subdriver %s\n", - ibm->name); - ibm_exit(ibm); - return 0; - } - if (ret < 0) - return ret; - } - return 0; + +err_out: + ibm_exit(ibm); + return (ret < 0)? ret : 0; } static void ibm_exit(struct ibm_struct *ibm) { - if (ibm->notify_installed) + if (ibm->notify_installed) { acpi_remove_notify_handler(*ibm->handle, ibm->type, dispatch_notify); + ibm->notify_installed = 0; + } - if (ibm->proc_created) + if (ibm->proc_created) { remove_proc_entry(ibm->name, proc_dir); - - if (ibm->init_called && ibm->exit) - ibm->exit(); + ibm->proc_created = 0; + } if (ibm->driver_registered) { 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; } } @@ -2663,6 +2736,32 @@ static char* __init check_dmi_for_ec(void) 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; + } + + return 0; +} + + /* Module init, exit, parameters */ static int __init set_ibm_param(const char *val, struct kernel_param *kp) @@ -2712,45 +2811,13 @@ static int __init thinkpad_acpi_module_init(void) { int ret, i; - if (acpi_disabled) - return -ENODEV; + ret = probe_for_thinkpad(); + if (ret) + return ret; - /* 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; - } - - /* Models with newer firmware report the EC in DMI */ ibm_thinkpad_ec_found = check_dmi_for_ec(); - - /* 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 CONFIG_THINKPAD_ACPI_DOCK - IBM_HANDLE_INIT(dock); -#endif - IBM_HANDLE_INIT(pci); -#ifdef CONFIG_THINKPAD_ACPI_BAY - IBM_HANDLE_INIT(bay); - if (bay_handle) - IBM_HANDLE_INIT(bay_ej); - IBM_HANDLE_INIT(bay2); - if (bay2_handle) - IBM_HANDLE_INIT(bay2_ej); -#endif /* CONFIG_THINKPAD_ACPI_BAY */ - 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) { diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index b2348d7..06d4c38 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -104,7 +104,7 @@ static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ static void ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, + acpi_handle *handle, acpi_handle parent, char **paths, int num_paths, char **path); #define IBM_HANDLE_INIT(object) \ ibm_handle_init(#object, &object##_handle, *object##_parent, \ @@ -242,8 +242,8 @@ static int cmos_write(char *buf); * Dock subdriver */ -static acpi_handle pci_handle; #ifdef CONFIG_THINKPAD_ACPI_DOCK +static acpi_handle pci_handle; static acpi_handle dock_handle; static void dock_notify(struct ibm_struct *ibm, u32 event); -- cgit v1.1 From fe08bc4b4fd1371fad111675a564e4d2ebbf39ea Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:32 -0300 Subject: ACPI: thinkpad-acpi: add subdriver debug statements Add debug messages to the subdriver initialization and exit code. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 4 ++ drivers/misc/thinkpad_acpi.c | 111 ++++++++++++++++++++++++++++++++++++++++ drivers/misc/thinkpad_acpi.h | 4 ++ 3 files changed, 119 insertions(+) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 82fd822..20d5ec3 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -710,5 +710,9 @@ enable various classes of debugging output, for example: will enable all debugging output classes. It takes a bitmask, so to enable more than one output class, just add their values. + Debug bitmask Description + 0x0001 Initialization and probing + 0x0002 Removal + There is also a kernel build option to enable more debugging information, which may be necessary to debug driver problems. diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index eeab394..e8fc8da 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -313,6 +313,9 @@ static int __init setup_notify(struct ibm_struct *ibm) 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); @@ -349,6 +352,9 @@ 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"); @@ -497,17 +503,25 @@ static int hotkey_orig_mask; static int hotkey_init(void) { + 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; } @@ -518,6 +532,7 @@ static int hotkey_init(void) 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); } } @@ -633,6 +648,8 @@ static int bluetooth_supported; static int bluetooth_init(void) { + 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, @@ -640,6 +657,9 @@ static int bluetooth_init(void) 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; } @@ -704,11 +724,16 @@ static int wan_supported; static int wan_init(void) { + 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; } @@ -784,6 +809,8 @@ static int video_init(void) { int ivga; + vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n"); + IBM_HANDLE_INIT(vid); IBM_HANDLE_INIT(vid2); @@ -804,11 +831,16 @@ static int video_init(void) /* 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); } @@ -991,6 +1023,8 @@ IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ static int light_init(void) { + vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); + IBM_HANDLE_INIT(ledb); IBM_HANDLE_INIT(lght); IBM_HANDLE_INIT(cmos); @@ -1004,6 +1038,9 @@ static int light_init(void) 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; } @@ -1075,9 +1112,14 @@ IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ static int dock_init(void) { + 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; } @@ -1171,6 +1213,8 @@ IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ static int bay_init(void) { + vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n"); + IBM_HANDLE_INIT(bay); if (bay_handle) IBM_HANDLE_INIT(bay_ej); @@ -1188,6 +1232,13 @@ static int bay_init(void) 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; } @@ -1255,8 +1306,13 @@ static int bay_write(char *buf) static int cmos_init(void) { + 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; } @@ -1320,6 +1376,8 @@ IBM_HANDLE(led, ec, "SLED", /* 570 */ static int led_init(void) { + vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); + IBM_HANDLE_INIT(led); if (!led_handle) @@ -1335,6 +1393,9 @@ static int led_init(void) /* 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; } @@ -1434,8 +1495,13 @@ IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ static int beep_init(void) { + 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; } @@ -1486,6 +1552,8 @@ static int thermal_init(void) int i; int acpi_tmp7; + vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n"); + acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); if (ibm_thinkpad_ec_found && experimental) { @@ -1542,6 +1610,10 @@ static int thermal_init(void) 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; } @@ -1698,6 +1770,8 @@ static int brightness_init(void) { int b; + vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n"); + b = brightness_get(NULL); if (b < 0) return b; @@ -1708,6 +1782,7 @@ static int brightness_init(void) 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; @@ -1719,6 +1794,8 @@ static int brightness_init(void) 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; } @@ -2017,6 +2094,8 @@ IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ static int fan_init(void) { + 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; @@ -2095,6 +2174,11 @@ static int fan_init(void) } } + 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; @@ -2138,6 +2222,7 @@ static int fan_get_status(u8 *status) static void fan_exit(void) { + vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending watchdogs\n"); cancel_delayed_work(&fan_watchdog_task); flush_scheduled_work(); } @@ -2622,6 +2707,15 @@ static struct ibm_struct ibms[] = { * Module and infrastructure proble, init and exit handling */ +#ifdef CONFIG_THINKPAD_ACPI_DEBUG +static const char * str_supported(int is_supported) +{ + static const char * const text_unsupported = "not supported"; + + return (is_supported)? text_unsupported + 4 : text_unsupported; +} +#endif /* CONFIG_THINKPAD_ACPI_DEBUG */ + static int __init ibm_init(struct ibm_struct *ibm) { int ret; @@ -2630,6 +2724,9 @@ static int __init ibm_init(struct ibm_struct *ibm) if (ibm->experimental && !experimental) return 0; + dbg_printk(TPACPI_DBG_INIT, + "probing for %s\n", ibm->name); + if (ibm->init) { ret = ibm->init(); if (ret > 0) @@ -2657,6 +2754,9 @@ static int __init ibm_init(struct ibm_struct *ibm) 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, @@ -2678,24 +2778,35 @@ static int __init ibm_init(struct ibm_struct *ibm) 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); 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; diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 06d4c38..beb1447 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -76,12 +76,16 @@ /* Debugging */ #define TPACPI_DBG_ALL 0xffff +#define TPACPI_DBG_ALL 0xffff +#define TPACPI_DBG_INIT 0x0001 +#define TPACPI_DBG_EXIT 0x0002 #define dbg_printk(a_dbg_level, format, arg...) \ do { if (dbg_level & a_dbg_level) \ printk(IBM_DEBUG "%s: " format, __func__ , ## arg); } while (0) #ifdef CONFIG_THINKPAD_ACPI_DEBUG #define vdbg_printk(a_dbg_level, format, arg...) \ dbg_printk(a_dbg_level, format, ## arg) +static const char *str_supported(int is_supported); #else #define vdbg_printk(a_dbg_level, format, arg...) #endif -- cgit v1.1 From a5763f2223ce3fdbc75923f8c948fc7b59ed2f96 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:33 -0300 Subject: ACPI: thinkpad-acpi: uncouple subdriver init from ibms struct Move the .init method from ibms struct to another struct, and use a list head to control which subdrivers have been activated. This allows us to have the subdriver init methods marked __init, saving quite a lot of .text size, and even a bit of .data size as some data can now be made __initdata. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 402 ++++++++++++++++++++++++++----------------- drivers/misc/thinkpad_acpi.h | 51 +++--- 2 files changed, 278 insertions(+), 175 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index e8fc8da..5611268 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -470,7 +470,7 @@ static char *next_cmd(char **cmds) * thinkpad-acpi init subdriver */ -static int thinkpad_acpi_driver_init(void) +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); @@ -492,6 +492,11 @@ static int thinkpad_acpi_driver_read(char *p) return len; } +static struct ibm_struct thinkpad_acpi_driver_data = { + .name = "driver", + .read = thinkpad_acpi_driver_read, +}; + /************************************************************************* * Hotkey subdriver */ @@ -501,7 +506,7 @@ static int hotkey_mask_supported; static int hotkey_orig_status; static int hotkey_orig_mask; -static int hotkey_init(void) +static int __init hotkey_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n"); @@ -640,13 +645,24 @@ static int hotkey_write(char *buf) 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 bluetooth_init(void) +static int __init bluetooth_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n"); @@ -716,13 +732,19 @@ static int bluetooth_write(char *buf) 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 wan_init(void) +static int __init wan_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n"); @@ -789,6 +811,13 @@ static int wan_write(char *buf) return 0; } +static struct ibm_struct wan_driver_data = { + .name = "wan", + .read = wan_read, + .write = wan_write, + .experimental = 1, +}; + /************************************************************************* * Video subdriver */ @@ -805,7 +834,7 @@ IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ -static int video_init(void) +static int __init video_init(struct ibm_init_struct *iibm) { int ivga; @@ -1011,6 +1040,13 @@ static int video_write(char *buf) return 0; } +static struct ibm_struct video_driver_data = { + .name = "video", + .read = video_read, + .write = video_write, + .exit = video_exit, +}; + /************************************************************************* * Light (thinklight) subdriver */ @@ -1021,7 +1057,7 @@ 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 light_init(void) +static int __init light_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); @@ -1093,6 +1129,12 @@ static int light_write(char *buf) return 0; } +static struct ibm_struct light_driver_data = { + .name = "light", + .read = light_read, + .write = light_write, +}; + /************************************************************************* * Dock subdriver */ @@ -1110,7 +1152,7 @@ IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ #define dock_docked() (_sta(dock_handle) & 1) -static int dock_init(void) +static int __init dock_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n"); @@ -1184,6 +1226,24 @@ static int dock_write(char *buf) 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 */ /************************************************************************* @@ -1211,7 +1271,7 @@ IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ "_EJ0", /* 770x */ ); /* all others */ -static int bay_init(void) +static int __init bay_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n"); @@ -1298,13 +1358,23 @@ static int bay_write(char *buf) 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 cmos_init(void) +static int __init cmos_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing cmos commands subdriver\n"); @@ -1362,6 +1432,11 @@ static int cmos_write(char *buf) return 0; } +static struct ibm_struct cmos_driver_data = { + .name = "cmos", + .read = cmos_read, + .write = cmos_write, +}; /************************************************************************* * LED subdriver @@ -1374,7 +1449,7 @@ IBM_HANDLE(led, ec, "SLED", /* 570 */ "LED", /* all others */ ); /* R30, R31 */ -static int led_init(void) +static int __init led_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); @@ -1487,13 +1562,19 @@ static int led_write(char *buf) 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 beep_init(void) +static int __init beep_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n"); @@ -1540,13 +1621,19 @@ static int beep_write(char *buf) 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 thermal_init(void) +static int __init thermal_init(struct ibm_init_struct *iibm) { u8 t, ta1, ta2; int i; @@ -1692,6 +1779,11 @@ static int thermal_read(char *p) return len; } +static struct ibm_struct thermal_driver_data = { + .name = "thermal", + .read = thermal_read, +}; + /************************************************************************* * EC Dump subdriver */ @@ -1755,6 +1847,13 @@ static int ecdump_write(char *buf) return 0; } +static struct ibm_struct ecdump_driver_data = { + .name = "ecdump", + .read = ecdump_read, + .write = ecdump_write, + .experimental = 1, +}; + /************************************************************************* * Backlight/brightness subdriver */ @@ -1766,7 +1865,7 @@ static struct backlight_ops ibm_backlight_data = { .update_status = brightness_update_status, }; -static int brightness_init(void) +static int __init brightness_init(struct ibm_init_struct *iibm) { int b; @@ -1883,6 +1982,13 @@ static int brightness_write(char *buf) return 0; } +static struct ibm_struct brightness_driver_data = { + .name = "brightness", + .read = brightness_read, + .write = brightness_write, + .exit = brightness_exit, +}; + /************************************************************************* * Volume subdriver */ @@ -1967,6 +2073,11 @@ static int volume_write(char *buf) return 0; } +static struct ibm_struct volume_driver_data = { + .name = "volume", + .read = volume_read, + .write = volume_write, +}; /************************************************************************* * Fan subdriver @@ -2092,7 +2203,7 @@ IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ "JFNS", /* 770x-JL */ ); /* all others */ -static int fan_init(void) +static int __init fan_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); @@ -2568,6 +2679,14 @@ static int fan_write(char *buf) return rc; } +static struct ibm_struct fan_driver_data = { + .name = "fan", + .read = fan_read, + .write = fan_write, + .exit = fan_exit, + .experimental = 1, +}; + /**************************************************************************** **************************************************************************** * @@ -2580,159 +2699,45 @@ static int fan_write(char *buf) static struct proc_dir_entry *proc_dir = NULL; /* Subdriver registry */ -static struct ibm_struct ibms[] = { - { - .name = "driver", - .init = thinkpad_acpi_driver_init, - .read = thinkpad_acpi_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 = "wan", - .init = wan_init, - .read = wan_read, - .write = wan_write, - .experimental = 1, - }, - { - .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 CONFIG_THINKPAD_ACPI_DOCK - { - .name = "dock", - .init = dock_init, - .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 -#ifdef CONFIG_THINKPAD_ACPI_BAY - { - .name = "bay", - .init = bay_init, - .read = bay_read, - .write = bay_write, - .notify = bay_notify, - .handle = &bay_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif /* CONFIG_THINKPAD_ACPI_BAY */ - { - .name = "cmos", - .init = cmos_init, - .read = cmos_read, - .write = cmos_write, - }, - { - .name = "led", - .init = led_init, - .read = led_read, - .write = led_write, - }, - { - .name = "beep", - .init = beep_init, - .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", - .read = brightness_read, - .write = brightness_write, - .init = brightness_init, - .exit = brightness_exit, - }, - { - .name = "volume", - .read = volume_read, - .write = volume_write, - }, - { - .name = "fan", - .read = fan_read, - .write = fan_write, - .init = fan_init, - .exit = fan_exit, - .experimental = 1, - }, -}; +static LIST_HEAD(tpacpi_all_drivers); + /* * Module and infrastructure proble, init and exit handling */ #ifdef CONFIG_THINKPAD_ACPI_DEBUG -static const char * str_supported(int is_supported) +static const char * __init str_supported(int is_supported) { - static const char * const text_unsupported = "not supported"; + static char text_unsupported[] __initdata = "not supported"; - return (is_supported)? text_unsupported + 4 : text_unsupported; + return (is_supported)? &text_unsupported[4] : &text_unsupported[0]; } #endif /* CONFIG_THINKPAD_ACPI_DEBUG */ -static int __init ibm_init(struct ibm_struct *ibm) +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 (ibm->init) { - ret = ibm->init(); + if (iibm->init) { + ret = iibm->init(iibm); if (ret > 0) return 0; /* probe failed */ if (ret) return ret; + ibm->init_called = 1; } @@ -2775,6 +2780,8 @@ static int __init ibm_init(struct ibm_struct *ibm) ibm->proc_created = 1; } + list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers); + return 0; err_out: @@ -2789,6 +2796,9 @@ err_out: 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); @@ -2817,6 +2827,8 @@ static void ibm_exit(struct ibm_struct *ibm) ibm->exit(); ibm->init_called = 0; } + + dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name); } /* Probing */ @@ -2875,18 +2887,95 @@ static int __init probe_for_thinkpad(void) /* 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); i++) - if (strcmp(ibms[i].name, kp->name) == 0 && ibms[i].write) { - if (strlen(val) > sizeof(ibms[i].param) - 2) + 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[i].param, val); - strcat(ibms[i].param, ","); + strcpy(ibms_init[i].param, val); + strcat(ibms_init[i].param, ","); return 0; } + } return -EINVAL; } @@ -2938,10 +3027,10 @@ static int __init thinkpad_acpi_module_init(void) } 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); + 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; @@ -2953,10 +3042,15 @@ static int __init thinkpad_acpi_module_init(void) static void thinkpad_acpi_module_exit(void) { - int i; + struct ibm_struct *ibm, *itmp; + + list_for_each_entry_safe_reverse(ibm, itmp, + &tpacpi_all_drivers, + all_drivers) { + ibm_exit(ibm); + } - for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) - ibm_exit(&ibms[i]); + dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n"); if (proc_dir) remove_proc_entry(IBM_DIR, acpi_root_dir); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index beb1447..97467b7 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -116,8 +117,6 @@ static void ibm_handle_init(char *name, /* procfs support */ static struct proc_dir_entry *proc_dir; -static int thinkpad_acpi_driver_init(void); -static int thinkpad_acpi_driver_read(char *p); /* procfs helpers */ static int dispatch_read(char *page, char **start, off_t off, int count, @@ -142,12 +141,10 @@ static void thinkpad_acpi_module_exit(void); 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); @@ -157,6 +154,8 @@ struct ibm_struct { int type; struct acpi_device *device; + struct list_head all_drivers; + int driver_registered; int proc_created; int init_called; @@ -165,16 +164,26 @@ struct ibm_struct { int experimental; }; -static struct ibm_struct ibms[]; +struct ibm_init_struct { + char param[32]; + + int (*init) (struct ibm_init_struct *); + struct ibm_struct *data; +}; + +static struct list_head tpacpi_all_drivers; + +static struct ibm_init_struct ibms_init[]; static int set_ibm_param(const char *val, struct kernel_param *kp); -static int ibm_init(struct ibm_struct *ibm); +static int ibm_init(struct ibm_init_struct *iibm); static void ibm_exit(struct ibm_struct *ibm); -/* ACPI devices */ -static void dispatch_notify(acpi_handle handle, u32 event, void *data); -static int setup_notify(struct ibm_struct *ibm); -static int ibm_device_add(struct acpi_device *device); -static int register_tpacpi_subdriver(struct ibm_struct *ibm); + +/* + * procfs master subdriver + */ +static int thinkpad_acpi_driver_init(struct ibm_init_struct *iibm); +static int thinkpad_acpi_driver_read(char *p); /* @@ -188,7 +197,7 @@ static int bay_status2_supported, bay_eject2_supported; static acpi_handle bay_handle, bay_ej_handle; static acpi_handle bay2_handle, bay2_ej_handle; -static int bay_init(void); +static int bay_init(struct ibm_init_struct *iibm); static void bay_notify(struct ibm_struct *ibm, u32 event); static int bay_read(char *p); static int bay_write(char *buf); @@ -211,7 +220,7 @@ static int beep_write(char *buf); static int bluetooth_supported; -static int bluetooth_init(void); +static int bluetooth_init(struct ibm_init_struct *iibm); static int bluetooth_status(void); static int bluetooth_read(char *p); static int bluetooth_write(char *buf); @@ -224,7 +233,7 @@ static int bluetooth_write(char *buf); static struct backlight_device *ibm_backlight_device; static int brightness_offset = 0x31; -static int brightness_init(void); +static int brightness_init(struct ibm_init_struct *iibm); static void brightness_exit(void); static int brightness_get(struct backlight_device *bd); static int brightness_set(int value); @@ -306,7 +315,7 @@ static int fan_watchdog_maxinterval; static acpi_handle fans_handle, gfan_handle, sfan_handle; -static int fan_init(void); +static int fan_init(struct ibm_init_struct *iibm); static void fan_exit(void); static int fan_get_status(u8 *status); static int fan_get_speed(unsigned int *speed); @@ -334,7 +343,7 @@ static int hotkey_mask_supported; static int hotkey_orig_status; static int hotkey_orig_mask; -static int hotkey_init(void); +static int hotkey_init(struct ibm_init_struct *iibm); static void hotkey_exit(void); static int hotkey_get(int *status, int *mask); static int hotkey_set(int status, int mask); @@ -363,7 +372,7 @@ enum { /* For TPACPI_LED_OLD */ static enum led_access_mode led_supported; static acpi_handle led_handle; -static int led_init(void); +static int led_init(struct ibm_init_struct *iibm); static int led_read(char *p); static int led_write(char *buf); @@ -375,7 +384,7 @@ static int light_supported; static int light_status_supported; static acpi_handle lght_handle, ledb_handle; -static int light_init(void); +static int light_init(struct ibm_init_struct *iibm); static int light_read(char *p); static int light_write(char *buf); @@ -397,7 +406,7 @@ struct ibm_thermal_sensors_struct { s32 temp[TPACPI_MAX_THERMAL_SENSORS]; }; -static int thermal_init(void); +static int thermal_init(struct ibm_init_struct *iibm); static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); static int thermal_read(char *p); @@ -417,7 +426,7 @@ static enum video_access_mode video_supported; static int video_orig_autosw; static acpi_handle vid_handle, vid2_handle; -static int video_init(void); +static int video_init(struct ibm_init_struct *iibm); static void video_exit(void); static int video_status(void); static int video_autosw(void); @@ -444,7 +453,7 @@ static int volume_write(char *buf); static int wan_supported; -static int wan_init(void); +static int wan_init(struct ibm_init_struct *iibm); static int wan_status(void); static int wan_read(char *p); static int wan_write(char *buf); -- cgit v1.1 From 0dcef77c5b889338811d35e786b42046259fe433 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:34 -0300 Subject: ACPI: thinkpad-acpi: improve thinkpad detection Improve the detection of ThinkPads, so as to reduce the chances of false positives. Since this could potentially add false negatives on the very old models, add a module parameter to force the detection of a thinkpad. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 7 +++++++ drivers/misc/thinkpad_acpi.c | 13 +++++++++++++ drivers/misc/thinkpad_acpi.h | 1 + 3 files changed, 21 insertions(+) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 20d5ec3..1a42b77 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -716,3 +716,10 @@ to enable more than one output class, just add their values. There is also a kernel build option to enable more debugging information, which may be necessary to debug driver problems. + +Force loading of module +----------------------- + +If thinkpad-acpi refuses to detect your ThinkPad, you can try to specify +the module parameter force_load=1. Regardless of whether this works or +not, please contact ibm-acpi-devel@lists.sourceforge.net with a report. diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 5611268..cddf81b 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2881,6 +2881,16 @@ static int __init probe_for_thinkpad(void) 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; } @@ -2986,6 +2996,9 @@ 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) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 97467b7..2020398 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -128,6 +128,7 @@ static char *next_cmd(char **cmds); /* Module */ static int experimental; static u32 dbg_level; +static int force_load; static char *ibm_thinkpad_ec_found; static char* check_dmi_for_ec(void); -- cgit v1.1 From 926411779287ad4f7013c9d80aa44fd131b70cd9 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:35 -0300 Subject: ACPI: thinkpad-acpi: use bitfields to hold subdriver flags Save some memory by using bitfields to hold boolean flags for the subdrivers. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 32 ++++++++++++++++---------------- drivers/misc/thinkpad_acpi.h | 13 +++++++------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index cddf81b..a5efd06 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -339,7 +339,7 @@ static int __init setup_notify(struct ibm_struct *ibm) } return -ENODEV; } - ibm->notify_installed = 1; + ibm->flags.notify_installed = 1; return 0; } @@ -372,7 +372,7 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) kfree(ibm->driver); ibm->driver = NULL; } else if (!ret) - ibm->driver_registered = 1; + ibm->flags.driver_registered = 1; return ret; } @@ -815,7 +815,7 @@ static struct ibm_struct wan_driver_data = { .name = "wan", .read = wan_read, .write = wan_write, - .experimental = 1, + .flags.experimental = 1, }; /************************************************************************* @@ -1851,7 +1851,7 @@ static struct ibm_struct ecdump_driver_data = { .name = "ecdump", .read = ecdump_read, .write = ecdump_write, - .experimental = 1, + .flags.experimental = 1, }; /************************************************************************* @@ -2684,7 +2684,7 @@ static struct ibm_struct fan_driver_data = { .read = fan_read, .write = fan_write, .exit = fan_exit, - .experimental = 1, + .flags.experimental = 1, }; /**************************************************************************** @@ -2725,7 +2725,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm) INIT_LIST_HEAD(&ibm->all_drivers); - if (ibm->experimental && !experimental) + if (ibm->flags.experimental && !experimental) return 0; dbg_printk(TPACPI_DBG_INIT, @@ -2738,7 +2738,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm) if (ret) return ret; - ibm->init_called = 1; + ibm->flags.init_called = 1; } if (ibm->hid) { @@ -2777,7 +2777,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm) entry->read_proc = &dispatch_read; if (ibm->write) entry->write_proc = &dispatch_write; - ibm->proc_created = 1; + ibm->flags.proc_created = 1; } list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers); @@ -2799,33 +2799,33 @@ static void ibm_exit(struct ibm_struct *ibm) list_del_init(&ibm->all_drivers); - if (ibm->notify_installed) { + if (ibm->flags.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; + ibm->flags.notify_installed = 0; } - if (ibm->proc_created) { + if (ibm->flags.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; + ibm->flags.proc_created = 0; } - if (ibm->driver_registered) { + if (ibm->flags.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; + ibm->flags.driver_registered = 0; } - if (ibm->init_called && ibm->exit) { + if (ibm->flags.init_called && ibm->exit) { ibm->exit(); - ibm->init_called = 0; + ibm->flags.init_called = 0; } dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 2020398..8b72061 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -157,12 +157,13 @@ struct ibm_struct { struct list_head all_drivers; - int driver_registered; - int proc_created; - int init_called; - int notify_installed; - - int experimental; + struct { + u8 driver_registered:1; + u8 proc_created:1; + u8 init_called:1; + u8 notify_installed:1; + u8 experimental:1; + } flags; }; struct ibm_init_struct { -- cgit v1.1 From d8fd94d9f08237ffda7e44e6825b057bf20a90e3 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:36 -0300 Subject: ACPI: thinkpad-acpi: use bitfields for module flags Use a bitfield to hold boolean module-wide flags, to conserve some memory. It is easy and it is clean, so we do it just for the heck of it even if it saves very little space. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 147 ++++++++++++++++++++----------------------- drivers/misc/thinkpad_acpi.h | 28 +++++---- 2 files changed, 83 insertions(+), 92 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index a5efd06..e2a1b63 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -501,8 +501,6 @@ static struct ibm_struct thinkpad_acpi_driver_data = { * Hotkey subdriver */ -static int hotkey_supported; -static int hotkey_mask_supported; static int hotkey_orig_status; static int hotkey_orig_mask; @@ -513,30 +511,30 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) IBM_HANDLE_INIT(hkey); /* hotkey not supported on 570 */ - hotkey_supported = hkey_handle != NULL; + tp_features.hotkey = hkey_handle != NULL; vdbg_printk(TPACPI_DBG_INIT, "hotkeys are %s\n", - str_supported(hotkey_supported)); + str_supported(tp_features.hotkey)); - if (hotkey_supported) { + if (tp_features.hotkey) { /* 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"); + tp_features.hotkey_mask = + acpi_evalf(hkey_handle, NULL, "DHKN", "qv"); vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n", - str_supported(hotkey_mask_supported)); + str_supported(tp_features.hotkey_mask)); if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask)) return -ENODEV; } - return (hotkey_supported)? 0 : 1; + return (tp_features.hotkey)? 0 : 1; } static void hotkey_exit(void) { - if (hotkey_supported) { + if (tp_features.hotkey) { dbg_printk(TPACPI_DBG_EXIT, "restoring original hotkey mask\n"); hotkey_set(hotkey_orig_status, hotkey_orig_mask); } @@ -559,7 +557,7 @@ static int hotkey_get(int *status, int *mask) if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) return 0; - if (hotkey_mask_supported) + if (tp_features.hotkey_mask) if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) return 0; @@ -573,7 +571,7 @@ static int hotkey_set(int status, int mask) if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) return 0; - if (hotkey_mask_supported) + if (tp_features.hotkey_mask) for (i = 0; i < 32; i++) { int bit = ((1 << i) & mask) != 0; if (!acpi_evalf(hkey_handle, @@ -589,7 +587,7 @@ static int hotkey_read(char *p) int status, mask; int len = 0; - if (!hotkey_supported) { + if (!tp_features.hotkey) { len += sprintf(p + len, "status:\t\tnot supported\n"); return len; } @@ -598,7 +596,7 @@ static int hotkey_read(char *p) return -EIO; len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); - if (hotkey_mask_supported) { + if (tp_features.hotkey_mask) { len += sprintf(p + len, "mask:\t\t0x%04x\n", mask); len += sprintf(p + len, "commands:\tenable, disable, reset, \n"); @@ -616,7 +614,7 @@ static int hotkey_write(char *buf) char *cmd; int do_cmd = 0; - if (!hotkey_supported) + if (!tp_features.hotkey) return -ENODEV; if (!hotkey_get(&status, &mask)) @@ -660,8 +658,6 @@ static struct ibm_struct hotkey_driver_data = { * 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"); @@ -670,20 +666,20 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) /* 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 && + tp_features.bluetooth = hkey_handle && acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s\n", - str_supported(bluetooth_supported)); + str_supported(tp_features.bluetooth)); - return (bluetooth_supported)? 0 : 1; + return (tp_features.bluetooth)? 0 : 1; } static int bluetooth_status(void) { int status; - if (!bluetooth_supported || + if (!tp_features.bluetooth || !acpi_evalf(hkey_handle, &status, "GBDC", "d")) status = 0; @@ -695,7 +691,7 @@ static int bluetooth_read(char *p) int len = 0; int status = bluetooth_status(); - if (!bluetooth_supported) + if (!tp_features.bluetooth) len += sprintf(p + len, "status:\t\tnot supported\n"); else if (!(status & 1)) len += sprintf(p + len, "status:\t\tnot installed\n"); @@ -713,7 +709,7 @@ static int bluetooth_write(char *buf) char *cmd; int do_cmd = 0; - if (!bluetooth_supported) + if (!tp_features.bluetooth) return -ENODEV; while ((cmd = next_cmd(&buf))) { @@ -742,28 +738,27 @@ static struct ibm_struct bluetooth_driver_data = { * 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"); + tp_features.wan = hkey_handle && + acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); vdbg_printk(TPACPI_DBG_INIT, "wan is %s\n", - str_supported(wan_supported)); + str_supported(tp_features.wan)); - return (wan_supported)? 0 : 1; + return (tp_features.wan)? 0 : 1; } static int wan_status(void) { int status; - if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) + if (!tp_features.wan || + !acpi_evalf(hkey_handle, &status, "GWAN", "d")) status = 0; return status; @@ -774,7 +769,7 @@ static int wan_read(char *p) int len = 0; int status = wan_status(); - if (!wan_supported) + if (!tp_features.wan) len += sprintf(p + len, "status:\t\tnot supported\n"); else if (!(status & 1)) len += sprintf(p + len, "status:\t\tnot installed\n"); @@ -792,7 +787,7 @@ static int wan_write(char *buf) char *cmd; int do_cmd = 0; - if (!wan_supported) + if (!tp_features.wan) return -ENODEV; while ((cmd = next_cmd(&buf))) { @@ -1051,9 +1046,6 @@ static struct ibm_struct video_driver_data = { * 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 */ @@ -1066,18 +1058,18 @@ static int __init light_init(struct ibm_init_struct *iibm) IBM_HANDLE_INIT(cmos); /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ - light_supported = (cmos_handle || lght_handle) && !ledb_handle; + tp_features.light = (cmos_handle || lght_handle) && !ledb_handle; - if (light_supported) + if (tp_features.light) /* 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"); + tp_features.light_status = + acpi_evalf(ec_handle, NULL, "KBLT", "qv"); vdbg_printk(TPACPI_DBG_INIT, "light is %s\n", - str_supported(light_supported)); + str_supported(tp_features.light)); - return (light_supported)? 0 : 1; + return (tp_features.light)? 0 : 1; } static int light_read(char *p) @@ -1085,9 +1077,9 @@ static int light_read(char *p) int len = 0; int status = 0; - if (!light_supported) { + if (!tp_features.light) { len += sprintf(p + len, "status:\t\tnot supported\n"); - } else if (!light_status_supported) { + } else if (!tp_features.light_status) { len += sprintf(p + len, "status:\t\tunknown\n"); len += sprintf(p + len, "commands:\ton, off\n"); } else { @@ -1106,7 +1098,7 @@ static int light_write(char *buf) char *cmd; int success; - if (!light_supported) + if (!tp_features.light) return -ENODEV; while ((cmd = next_cmd(&buf))) { @@ -1251,11 +1243,6 @@ static struct ibm_struct dock_driver_data[2] = { */ #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 */ @@ -1282,25 +1269,25 @@ static int __init bay_init(struct ibm_init_struct *iibm) 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"); + tp_features.bay_status = bay_handle && + acpi_evalf(bay_handle, NULL, "_STA", "qv"); + tp_features.bay_status2 = 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); + tp_features.bay_eject = bay_handle && bay_ej_handle && + (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental); + tp_features.bay_eject2 = 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)); + str_supported(tp_features.bay_status), + str_supported(tp_features.bay_eject), + str_supported(tp_features.bay_status2), + str_supported(tp_features.bay_eject2)); - return (bay_status_supported || bay_eject_supported || - bay_status2_supported || bay_eject2_supported)? 0 : 1; + return (tp_features.bay_status || tp_features.bay_eject || + tp_features.bay_status2 || tp_features.bay_eject2)? 0 : 1; } static void bay_notify(struct ibm_struct *ibm, u32 event) @@ -1317,15 +1304,16 @@ static int bay_read(char *p) 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, "status:\t\t%s\n", + tp_features.bay_status ? + (occupied ? "occupied" : "unoccupied") : + "not supported"); + if (tp_features.bay_status2) len += sprintf(p + len, "status2:\t%s\n", occupied2 ? "occupied" : "unoccupied"); - eject = bay_eject_supported && occupied; - eject2 = bay_eject2_supported && occupied2; + eject = tp_features.bay_eject && occupied; + eject2 = tp_features.bay_eject2 && occupied2; if (eject && eject2) len += sprintf(p + len, "commands:\teject, eject2\n"); @@ -1341,14 +1329,14 @@ static int bay_write(char *buf) { char *cmd; - if (!bay_eject_supported && !bay_eject2_supported) + if (!tp_features.bay_eject && !tp_features.bay_eject2) return -ENODEV; while ((cmd = next_cmd(&buf))) { - if (bay_eject_supported && strlencmp(cmd, "eject") == 0) { + if (tp_features.bay_eject && strlencmp(cmd, "eject") == 0) { if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1)) return -EIO; - } else if (bay_eject2_supported && + } else if (tp_features.bay_eject2 && strlencmp(cmd, "eject2") == 0) { if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1)) return -EIO; @@ -2188,7 +2176,6 @@ 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; -static int fan_control_status_known; static u8 fan_control_initial_status; static void fan_watchdog_fire(struct work_struct *ignored); @@ -2210,8 +2197,8 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_status_access_mode = TPACPI_FAN_NONE; fan_control_access_mode = TPACPI_FAN_WR_NONE; fan_control_commands = 0; - fan_control_status_known = 1; fan_watchdog_maxinterval = 0; + tp_features.fan_ctrl_status_undef = 0; IBM_HANDLE_INIT(fans); IBM_HANDLE_INIT(gfan); @@ -2248,7 +2235,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) "fan_init: initial fan status is " "unknown, assuming it is in auto " "mode\n"); - fan_control_status_known = 0; + tp_features.fan_ctrl_status_undef = 1; } } else { printk(IBM_ERR @@ -2411,7 +2398,7 @@ static int fan_set_level(int level) if (!acpi_ec_write(fan_status_offset, level)) return -EIO; else - fan_control_status_known = 1; + tp_features.fan_ctrl_status_undef = 0; break; default: @@ -2438,7 +2425,7 @@ static int fan_set_enable(void) if (!acpi_ec_write(fan_status_offset, s)) return -EIO; else - fan_control_status_known = 1; + tp_features.fan_ctrl_status_undef = 0; break; case TPACPI_FAN_WR_ACPI_SFAN: @@ -2469,7 +2456,7 @@ static int fan_set_disable(void) if (!acpi_ec_write(fan_status_offset, 0x00)) return -EIO; else - fan_control_status_known = 1; + tp_features.fan_ctrl_status_undef = 0; break; case TPACPI_FAN_WR_ACPI_SFAN: @@ -2524,9 +2511,9 @@ static int fan_read(char *p) if ((rc = fan_get_status(&status)) < 0) return rc; - if (unlikely(!fan_control_status_known)) { + if (unlikely(tp_features.fan_ctrl_status_undef)) { if (status != fan_control_initial_status) - fan_control_status_known = 1; + tp_features.fan_ctrl_status_undef = 0; else /* Return most likely status. In fact, it * might be the only possible status */ diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 8b72061..4d3ab40 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -173,6 +173,22 @@ struct ibm_init_struct { struct ibm_struct *data; }; +static struct { +#ifdef CONFIG_THINKPAD_ACPI_BAY + u16 bay_status:1; + u16 bay_eject:1; + u16 bay_status2:1; + u16 bay_eject2:1; +#endif + u16 bluetooth:1; + u16 hotkey:1; + u16 hotkey_mask:1; + u16 light:1; + u16 light_status:1; + u16 wan:1; + u16 fan_ctrl_status_undef:1; +} tp_features; + static struct list_head tpacpi_all_drivers; static struct ibm_init_struct ibms_init[]; @@ -193,9 +209,6 @@ static int thinkpad_acpi_driver_read(char *p); */ #ifdef CONFIG_THINKPAD_ACPI_BAY -static int bay_status_supported, bay_eject_supported; -static int bay_status2_supported, bay_eject2_supported; - static acpi_handle bay_handle, bay_ej_handle; static acpi_handle bay2_handle, bay2_ej_handle; @@ -220,8 +233,6 @@ static int beep_write(char *buf); * Bluetooth subdriver */ -static int bluetooth_supported; - static int bluetooth_init(struct ibm_init_struct *iibm); static int bluetooth_status(void); static int bluetooth_read(char *p); @@ -311,7 +322,6 @@ enum fan_control_commands { 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; -static int fan_control_status_known; static u8 fan_control_initial_status; static int fan_watchdog_maxinterval; @@ -340,8 +350,6 @@ static int fan_write_cmd_watchdog(const char *cmd, int *rc); * Hotkey subdriver */ -static int hotkey_supported; -static int hotkey_mask_supported; static int hotkey_orig_status; static int hotkey_orig_mask; @@ -382,8 +390,6 @@ static int led_write(char *buf); * Light (thinklight) subdriver */ -static int light_supported; -static int light_status_supported; static acpi_handle lght_handle, ledb_handle; static int light_init(struct ibm_init_struct *iibm); @@ -453,8 +459,6 @@ static int volume_write(char *buf); * Wan subdriver */ -static int wan_supported; - static int wan_init(struct ibm_init_struct *iibm); static int wan_status(void); static int wan_read(char *p); -- cgit v1.1 From 8d376cd6543d57ef10799be02ba5f19aa6678032 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:37 -0300 Subject: ACPI: thinkpad-acpi: prepare for device model conversion Prepare the thinkpad-acpi driver for the conversion to the device model, by renaming variables and doing other glue work that shall make the later patches much cleaner. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 231 ++++++++++++++++++++++++------------------- drivers/misc/thinkpad_acpi.h | 40 +++++--- 2 files changed, 153 insertions(+), 118 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index e2a1b63..809ec84 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -277,7 +277,7 @@ static int _sta(acpi_handle handle) * ACPI device model */ -static void ibm_handle_init(char *name, +static void drv_acpi_handle_init(char *name, acpi_handle *handle, acpi_handle parent, char **paths, int num_paths, char **path) { @@ -295,40 +295,42 @@ static void ibm_handle_init(char *name, *handle = NULL; } -static void dispatch_notify(acpi_handle handle, u32 event, void *data) +static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) { struct ibm_struct *ibm = data; - if (!ibm || !ibm->notify) + if (!ibm || !ibm->acpi || !ibm->acpi->notify) return; - ibm->notify(ibm, event); + ibm->acpi->notify(ibm, event); } -static int __init setup_notify(struct ibm_struct *ibm) +static int __init setup_acpi_notify(struct ibm_struct *ibm) { acpi_status status; int ret; - if (!*ibm->handle) + BUG_ON(!ibm->acpi); + + if (!*ibm->acpi->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); + ret = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->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", + acpi_driver_data(ibm->acpi->device) = ibm; + sprintf(acpi_device_class(ibm->acpi->device), "%s/%s", IBM_ACPI_EVENT_PREFIX, ibm->name); - status = acpi_install_notify_handler(*ibm->handle, ibm->type, - dispatch_notify, ibm); + status = acpi_install_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, dispatch_acpi_notify, ibm); if (ACPI_FAILURE(status)) { if (status == AE_ALREADY_EXISTS) { printk(IBM_NOTICE "another device driver is already handling %s events\n", @@ -339,11 +341,11 @@ static int __init setup_notify(struct ibm_struct *ibm) } return -ENODEV; } - ibm->flags.notify_installed = 1; + ibm->flags.acpi_notify_installed = 1; return 0; } -static int __init ibm_device_add(struct acpi_device *device) +static int __init tpacpi_device_add(struct acpi_device *device) { return 0; } @@ -355,24 +357,26 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) 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) { + BUG_ON(!ibm->acpi); + + ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->acpi->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; + sprintf(ibm->acpi->driver->name, "%s_%s", IBM_NAME, ibm->name); + ibm->acpi->driver->ids = ibm->acpi->hid; + ibm->acpi->driver->ops.add = &tpacpi_device_add; - ret = acpi_bus_register_driver(ibm->driver); + ret = acpi_bus_register_driver(ibm->acpi->driver); if (ret < 0) { printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", - ibm->hid, ret); - kfree(ibm->driver); - ibm->driver = NULL; + ibm->acpi->hid, ret); + kfree(ibm->acpi->driver); + ibm->acpi->driver = NULL; } else if (!ret) - ibm->flags.driver_registered = 1; + ibm->flags.acpi_driver_registered = 1; return ret; } @@ -386,8 +390,8 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) **************************************************************************** ****************************************************************************/ -static int dispatch_read(char *page, char **start, off_t off, int count, - int *eof, void *data) +static int dispatch_procfs_read(char *page, char **start, off_t off, + int count, int *eof, void *data) { struct ibm_struct *ibm = data; int len; @@ -411,8 +415,9 @@ static int dispatch_read(char *page, char **start, off_t off, int count, return len; } -static int dispatch_write(struct file *file, const char __user * userbuf, - unsigned long count, void *data) +static int dispatch_procfs_write(struct file *file, + const char __user * userbuf, + unsigned long count, void *data) { struct ibm_struct *ibm = data; char *kernbuf; @@ -508,7 +513,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n"); - IBM_HANDLE_INIT(hkey); + IBM_ACPIHANDLE_INIT(hkey); /* hotkey not supported on 570 */ tp_features.hotkey = hkey_handle != NULL; @@ -545,10 +550,10 @@ 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); + acpi_bus_generate_event(ibm->acpi->device, event, hkey); else { printk(IBM_ERR "unknown hotkey event %d\n", event); - acpi_bus_generate_event(ibm->device, event, 0); + acpi_bus_generate_event(ibm->acpi->device, event, 0); } } @@ -643,15 +648,19 @@ static int hotkey_write(char *buf) return 0; } +static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = { + .hid = IBM_HKEY_HID, + .notify = hotkey_notify, + .handle = &hkey_handle, + .type = ACPI_DEVICE_NOTIFY, +}; + 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, + .acpi = &ibm_hotkey_acpidriver, }; /************************************************************************* @@ -662,7 +671,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n"); - IBM_HANDLE_INIT(hkey); + IBM_ACPIHANDLE_INIT(hkey); /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ @@ -742,7 +751,7 @@ static int __init wan_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n"); - IBM_HANDLE_INIT(hkey); + IBM_ACPIHANDLE_INIT(hkey); tp_features.wan = hkey_handle && acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); @@ -835,8 +844,8 @@ static int __init video_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n"); - IBM_HANDLE_INIT(vid); - IBM_HANDLE_INIT(vid2); + IBM_ACPIHANDLE_INIT(vid); + IBM_ACPIHANDLE_INIT(vid2); if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) /* G41, assume IVGA doesn't change */ @@ -1053,9 +1062,9 @@ 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); + IBM_ACPIHANDLE_INIT(ledb); + IBM_ACPIHANDLE_INIT(lght); + IBM_ACPIHANDLE_INIT(cmos); /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ tp_features.light = (cmos_handle || lght_handle) && !ledb_handle; @@ -1148,8 +1157,8 @@ 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); + IBM_ACPIHANDLE_INIT(dock); + IBM_ACPIHANDLE_INIT(pci); vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n", str_supported(dock_handle != NULL)); @@ -1160,22 +1169,22 @@ static int __init dock_init(struct ibm_init_struct *iibm) static void dock_notify(struct ibm_struct *ibm, u32 event) { int docked = dock_docked(); - int pci = ibm->hid && strstr(ibm->hid, IBM_PCI_HID); + int pci = ibm->acpi->hid && strstr(ibm->acpi->hid, IBM_PCI_HID); if (event == 1 && !pci) /* 570 */ - acpi_bus_generate_event(ibm->device, event, 1); /* button */ + acpi_bus_generate_event(ibm->acpi->device, event, 1); /* button */ else if (event == 1 && pci) /* 570 */ - acpi_bus_generate_event(ibm->device, event, 3); /* dock */ + acpi_bus_generate_event(ibm->acpi->device, event, 3); /* dock */ else if (event == 3 && docked) - acpi_bus_generate_event(ibm->device, event, 1); /* button */ + acpi_bus_generate_event(ibm->acpi->device, event, 1); /* button */ else if (event == 3 && !docked) - acpi_bus_generate_event(ibm->device, event, 2); /* undock */ + acpi_bus_generate_event(ibm->acpi->device, event, 2); /* undock */ else if (event == 0 && docked) - acpi_bus_generate_event(ibm->device, event, 3); /* dock */ + acpi_bus_generate_event(ibm->acpi->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 */ + acpi_bus_generate_event(ibm->acpi->device, event, 0); /* unknown */ } } @@ -1218,17 +1227,13 @@ static int dock_write(char *buf) return 0; } -static struct ibm_struct dock_driver_data[2] = { +static struct tp_acpi_drv_struct ibm_dock_acpidriver[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, @@ -1236,6 +1241,19 @@ static struct ibm_struct dock_driver_data[2] = { }, }; +static struct ibm_struct dock_driver_data[2] = { + { + .name = "dock", + .read = dock_read, + .write = dock_write, + .acpi = &ibm_dock_acpidriver[0], + }, + { + .name = "dock", + .acpi = &ibm_dock_acpidriver[1], + }, +}; + #endif /* CONFIG_THINKPAD_ACPI_DOCK */ /************************************************************************* @@ -1262,12 +1280,12 @@ static int __init bay_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n"); - IBM_HANDLE_INIT(bay); + IBM_ACPIHANDLE_INIT(bay); if (bay_handle) - IBM_HANDLE_INIT(bay_ej); - IBM_HANDLE_INIT(bay2); + IBM_ACPIHANDLE_INIT(bay_ej); + IBM_ACPIHANDLE_INIT(bay2); if (bay2_handle) - IBM_HANDLE_INIT(bay2_ej); + IBM_ACPIHANDLE_INIT(bay2_ej); tp_features.bay_status = bay_handle && acpi_evalf(bay_handle, NULL, "_STA", "qv"); @@ -1292,7 +1310,7 @@ static int __init bay_init(struct ibm_init_struct *iibm) static void bay_notify(struct ibm_struct *ibm, u32 event) { - acpi_bus_generate_event(ibm->device, event, 0); + acpi_bus_generate_event(ibm->acpi->device, event, 0); } #define bay_occupied(b) (_sta(b##_handle) & 1) @@ -1347,13 +1365,17 @@ static int bay_write(char *buf) return 0; } +static struct tp_acpi_drv_struct ibm_bay_acpidriver = { + .notify = bay_notify, + .handle = &bay_handle, + .type = ACPI_SYSTEM_NOTIFY, +}; + 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, + .acpi = &ibm_bay_acpidriver, }; #endif /* CONFIG_THINKPAD_ACPI_BAY */ @@ -1367,7 +1389,7 @@ static int __init cmos_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing cmos commands subdriver\n"); - IBM_HANDLE_INIT(cmos); + IBM_ACPIHANDLE_INIT(cmos); vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n", str_supported(cmos_handle != NULL)); @@ -1441,7 +1463,7 @@ static int __init led_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); - IBM_HANDLE_INIT(led); + IBM_ACPIHANDLE_INIT(led); if (!led_handle) /* led not supported on R30, R31 */ @@ -1566,7 +1588,7 @@ static int __init beep_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n"); - IBM_HANDLE_INIT(beep); + IBM_ACPIHANDLE_INIT(beep); vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", str_supported(beep_handle != NULL)); @@ -2200,9 +2222,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_watchdog_maxinterval = 0; tp_features.fan_ctrl_status_undef = 0; - IBM_HANDLE_INIT(fans); - IBM_HANDLE_INIT(gfan); - IBM_HANDLE_INIT(sfan); + IBM_ACPIHANDLE_INIT(fans); + IBM_ACPIHANDLE_INIT(gfan); + IBM_ACPIHANDLE_INIT(sfan); if (gfan_handle) { /* 570, 600e/x, 770e, 770x */ @@ -2728,22 +2750,24 @@ static int __init ibm_init(struct ibm_init_struct *iibm) ibm->flags.init_called = 1; } - if (ibm->hid) { - ret = register_tpacpi_subdriver(ibm); - if (ret) - goto err_out; - } + if (ibm->acpi) { + if (ibm->acpi->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 (ibm->acpi->notify) { + ret = setup_acpi_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; } - if (ret < 0) - goto err_out; } dbg_printk(TPACPI_DBG_INIT, @@ -2761,9 +2785,9 @@ static int __init ibm_init(struct ibm_init_struct *iibm) } entry->owner = THIS_MODULE; entry->data = ibm; - entry->read_proc = &dispatch_read; + entry->read_proc = &dispatch_procfs_read; if (ibm->write) - entry->write_proc = &dispatch_write; + entry->write_proc = &dispatch_procfs_write; ibm->flags.proc_created = 1; } @@ -2786,12 +2810,15 @@ static void ibm_exit(struct ibm_struct *ibm) list_del_init(&ibm->all_drivers); - if (ibm->flags.notify_installed) { + if (ibm->flags.acpi_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->flags.notify_installed = 0; + BUG_ON(!ibm->acpi); + acpi_remove_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, + dispatch_acpi_notify); + ibm->flags.acpi_notify_installed = 0; + ibm->flags.acpi_notify_installed = 0; } if (ibm->flags.proc_created) { @@ -2801,13 +2828,14 @@ static void ibm_exit(struct ibm_struct *ibm) ibm->flags.proc_created = 0; } - if (ibm->flags.driver_registered) { + if (ibm->flags.acpi_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->flags.driver_registered = 0; + BUG_ON(!ibm->acpi); + acpi_bus_unregister_driver(ibm->acpi->driver); + kfree(ibm->acpi->driver); + ibm->acpi->driver = NULL; + ibm->flags.acpi_driver_registered = 0; } if (ibm->flags.init_called && ibm->exit) { @@ -2860,7 +2888,7 @@ static int __init probe_for_thinkpad(void) is_thinkpad = dmi_name_in_vendors("ThinkPad"); /* ec is required because many other handles are relative to it */ - IBM_HANDLE_INIT(ec); + IBM_ACPIHANDLE_INIT(ec); if (!ec_handle) { if (is_thinkpad) printk(IBM_ERR @@ -3016,12 +3044,12 @@ static int __init thinkpad_acpi_module_init(void) return ret; ibm_thinkpad_ec_found = check_dmi_for_ec(); - IBM_HANDLE_INIT(ecrd); - IBM_HANDLE_INIT(ecwr); + IBM_ACPIHANDLE_INIT(ecrd); + IBM_ACPIHANDLE_INIT(ecwr); - proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); + proc_dir = proc_mkdir(IBM_PROC_DIR, acpi_root_dir); if (!proc_dir) { - printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); + printk(IBM_ERR "unable to create proc dir " IBM_PROC_DIR); thinkpad_acpi_module_exit(); return -ENODEV; } @@ -3053,10 +3081,9 @@ static void thinkpad_acpi_module_exit(void) dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n"); if (proc_dir) - remove_proc_entry(IBM_DIR, acpi_root_dir); + remove_proc_entry(IBM_PROC_DIR, acpi_root_dir); - if (ibm_thinkpad_ec_found) - kfree(ibm_thinkpad_ec_found); + kfree(ibm_thinkpad_ec_found); } module_init(thinkpad_acpi_module_init); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 4d3ab40..529528c 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -52,8 +52,9 @@ #define IBM_DESC "ThinkPad ACPI Extras" #define IBM_FILE "thinkpad_acpi" #define IBM_URL "http://ibm-acpi.sf.net/" +#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net" -#define IBM_DIR "ibm" +#define IBM_PROC_DIR "ibm" #define IBM_ACPI_EVENT_PREFIX "ibm" #define IBM_LOG IBM_FILE ": " @@ -108,20 +109,21 @@ static acpi_handle ec_handle; /* EC */ static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ -static void ibm_handle_init(char *name, +static void drv_acpi_handle_init(char *name, acpi_handle *handle, acpi_handle parent, char **paths, int num_paths, char **path); -#define IBM_HANDLE_INIT(object) \ - ibm_handle_init(#object, &object##_handle, *object##_parent, \ +#define IBM_ACPIHANDLE_INIT(object) \ + drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \ object##_paths, ARRAY_SIZE(object##_paths), &object##_path) /* procfs support */ static struct proc_dir_entry *proc_dir; /* procfs helpers */ -static int dispatch_read(char *page, char **start, off_t off, int count, - int *eof, void *data); -static int dispatch_write(struct file *file, const char __user * userbuf, +static int dispatch_procfs_read(char *page, char **start, off_t off, + int count, int *eof, void *data); +static int dispatch_procfs_write(struct file *file, + const char __user * userbuf, unsigned long count, void *data); static char *next_cmd(char **cmds); @@ -140,28 +142,34 @@ static void thinkpad_acpi_module_exit(void); * Subdrivers */ -struct ibm_struct { - char *name; +struct ibm_struct; +struct tp_acpi_drv_struct { char *hid; struct acpi_driver *driver; - int (*read) (char *); - int (*write) (char *); - void (*exit) (void); - void (*notify) (struct ibm_struct *, u32); acpi_handle *handle; - int type; + u32 type; struct acpi_device *device; +}; + +struct ibm_struct { + char *name; + + int (*read) (char *); + int (*write) (char *); + void (*exit) (void); struct list_head all_drivers; + struct tp_acpi_drv_struct *acpi; + struct { - u8 driver_registered:1; + u8 acpi_driver_registered:1; + u8 acpi_notify_installed:1; u8 proc_created:1; u8 init_called:1; - u8 notify_installed:1; u8 experimental:1; } flags; }; -- cgit v1.1 From d01320e606d334a0cd35d781a58f9f3c254829ab Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:38 -0300 Subject: ACPI: thinkpad-acpi: mark acpi helper functions __must_check Mark acpi_evalf and friends __must_check. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 529528c..1b4cd16 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -97,11 +97,11 @@ static const char *str_supported(int is_supported); #define IBM_PCI_HID "PNP0A03" /* ACPI helpers */ -static int acpi_evalf(acpi_handle handle, +static int __must_check acpi_evalf(acpi_handle handle, void *res, char *method, char *fmt, ...); -static int acpi_ec_read(int i, u8 * p); -static int acpi_ec_write(int i, u8 v); -static int _sta(acpi_handle handle); +static int __must_check acpi_ec_read(int i, u8 * p); +static int __must_check acpi_ec_write(int i, u8 v); +static int __must_check _sta(acpi_handle handle); /* ACPI handles */ static acpi_handle root_handle; /* root namespace */ -- cgit v1.1 From b86c4722de62f336b82dff3c47ef59ba2a587ec1 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:39 -0300 Subject: ACPI: thinkpad-acpi: clean up hotkey subdriver Cleanup hotkey subdriver code. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 46 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 809ec84..344eb55 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -511,6 +511,8 @@ static int hotkey_orig_mask; static int __init hotkey_init(struct ibm_init_struct *iibm) { + int res; + vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n"); IBM_ACPIHANDLE_INIT(hkey); @@ -530,8 +532,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n", str_supported(tp_features.hotkey_mask)); - if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask)) - return -ENODEV; + res = hotkey_get(&hotkey_orig_status, &hotkey_orig_mask); + if (res) + return res; } return (tp_features.hotkey)? 0 : 1; @@ -539,9 +542,13 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) static void hotkey_exit(void) { + int res; + if (tp_features.hotkey) { dbg_printk(TPACPI_DBG_EXIT, "restoring original hotkey mask\n"); - hotkey_set(hotkey_orig_status, hotkey_orig_mask); + res = hotkey_set(hotkey_orig_status, hotkey_orig_mask); + if (res) + printk(IBM_ERR "failed to restore hotkey to BIOS defaults\n"); } } @@ -560,13 +567,13 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) static int hotkey_get(int *status, int *mask) { if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) - return 0; + return -EIO; if (tp_features.hotkey_mask) if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) - return 0; + return -EIO; - return 1; + return 0; } static int hotkey_set(int status, int mask) @@ -574,22 +581,22 @@ static int hotkey_set(int status, int mask) int i; if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) - return 0; + return -EIO; if (tp_features.hotkey_mask) 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 -EIO; } - return 1; + return 0; } static int hotkey_read(char *p) { - int status, mask; + int res, status, mask; int len = 0; if (!tp_features.hotkey) { @@ -597,8 +604,9 @@ static int hotkey_read(char *p) return len; } - if (!hotkey_get(&status, &mask)) - return -EIO; + res = hotkey_get(&status, &mask); + if (res) + return res; len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); if (tp_features.hotkey_mask) { @@ -615,15 +623,16 @@ static int hotkey_read(char *p) static int hotkey_write(char *buf) { - int status, mask; + int res, status, mask; char *cmd; int do_cmd = 0; if (!tp_features.hotkey) return -ENODEV; - if (!hotkey_get(&status, &mask)) - return -EIO; + res = hotkey_get(&status, &mask); + if (res) + return res; while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "enable") == 0) { @@ -642,8 +651,11 @@ static int hotkey_write(char *buf) do_cmd = 1; } - if (do_cmd && !hotkey_set(status, mask)) - return -EIO; + if (do_cmd) { + res = hotkey_set(status, mask); + if (res) + return res; + } return 0; } -- cgit v1.1 From d6fdd1e91a8a4cd852dc1d945165e3a69ac9e257 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:40 -0300 Subject: ACPI: thinkpad-acpi: cleanup bluetooth and wan for sysfs conversion Prepare bluetooth and wan driver code to be more easily hooked into sysfs helpers, by separating the procfs logic from the device attribute handling. These changes also remove the entries from procfs on notebooks without the bluetooth/wan hardware installed. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 130 ++++++++++++++++++++++++++++++------------- drivers/misc/thinkpad_acpi.h | 20 ++++++- 2 files changed, 108 insertions(+), 42 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 344eb55..a77368f 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -681,6 +681,8 @@ static struct ibm_struct hotkey_driver_data = { static int __init bluetooth_init(struct ibm_init_struct *iibm) { + int status = 0; + vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n"); IBM_ACPIHANDLE_INIT(hkey); @@ -688,36 +690,65 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ tp_features.bluetooth = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); + acpi_evalf(hkey_handle, &status, "GBDC", "qd"); - vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s\n", - str_supported(tp_features.bluetooth)); + vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s, status 0x%02x\n", + str_supported(tp_features.bluetooth), + status); + + if (tp_features.bluetooth && + !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) { + /* no bluetooth hardware present in system */ + tp_features.bluetooth = 0; + dbg_printk(TPACPI_DBG_INIT, + "bluetooth hardware not installed\n"); + } return (tp_features.bluetooth)? 0 : 1; } -static int bluetooth_status(void) +static int bluetooth_get_radiosw(void) { int status; - if (!tp_features.bluetooth || - !acpi_evalf(hkey_handle, &status, "GBDC", "d")) - status = 0; + if (!tp_features.bluetooth) + return -ENODEV; - return status; + if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) + return -EIO; + + return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0); +} + +static int bluetooth_set_radiosw(int radio_on) +{ + int status; + + if (!tp_features.bluetooth) + return -ENODEV; + + if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) + return -EIO; + if (radio_on) + status |= TP_ACPI_BLUETOOTH_RADIOSSW; + else + status &= ~TP_ACPI_BLUETOOTH_RADIOSSW; + if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) + return -EIO; + + return 0; } static int bluetooth_read(char *p) { int len = 0; - int status = bluetooth_status(); + int status = bluetooth_get_radiosw(); if (!tp_features.bluetooth) 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, "status:\t\t%s\n", + (status)? "enabled" : "disabled"); len += sprintf(p + len, "commands:\tenable, disable\n"); } @@ -726,26 +757,20 @@ static int bluetooth_read(char *p) static int bluetooth_write(char *buf) { - int status = bluetooth_status(); char *cmd; - int do_cmd = 0; if (!tp_features.bluetooth) return -ENODEV; while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "enable") == 0) { - status |= 2; + bluetooth_set_radiosw(1); } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; + bluetooth_set_radiosw(0); } else return -EINVAL; - do_cmd = 1; } - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) - return -EIO; - return 0; } @@ -761,41 +786,72 @@ static struct ibm_struct bluetooth_driver_data = { static int __init wan_init(struct ibm_init_struct *iibm) { + int status = 0; + vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n"); IBM_ACPIHANDLE_INIT(hkey); tp_features.wan = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); + acpi_evalf(hkey_handle, &status, "GWAN", "qd"); - vdbg_printk(TPACPI_DBG_INIT, "wan is %s\n", - str_supported(tp_features.wan)); + vdbg_printk(TPACPI_DBG_INIT, "wan is %s, status 0x%02x\n", + str_supported(tp_features.wan), + status); + + if (tp_features.wan && + !(status & TP_ACPI_WANCARD_HWPRESENT)) { + /* no wan hardware present in system */ + tp_features.wan = 0; + dbg_printk(TPACPI_DBG_INIT, + "wan hardware not installed\n"); + } return (tp_features.wan)? 0 : 1; } -static int wan_status(void) +static int wan_get_radiosw(void) { int status; - if (!tp_features.wan || - !acpi_evalf(hkey_handle, &status, "GWAN", "d")) - status = 0; + if (!tp_features.wan) + return -ENODEV; - return status; + if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) + return -EIO; + + return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0); +} + +static int wan_set_radiosw(int radio_on) +{ + int status; + + if (!tp_features.wan) + return -ENODEV; + + if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) + return -EIO; + if (radio_on) + status |= TP_ACPI_WANCARD_RADIOSSW; + else + status &= ~TP_ACPI_WANCARD_RADIOSSW; + if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) + return -EIO; + + return 0; } static int wan_read(char *p) { int len = 0; - int status = wan_status(); + int status = wan_get_radiosw(); if (!tp_features.wan) 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, "status:\t\t%s\n", + (status)? "enabled" : "disabled"); len += sprintf(p + len, "commands:\tenable, disable\n"); } @@ -804,26 +860,20 @@ static int wan_read(char *p) static int wan_write(char *buf) { - int status = wan_status(); char *cmd; - int do_cmd = 0; if (!tp_features.wan) return -ENODEV; while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "enable") == 0) { - status |= 2; + wan_set_radiosw(1); } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; + wan_set_radiosw(0); } else return -EINVAL; - do_cmd = 1; } - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) - return -EIO; - return 0; } diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 1b4cd16..e06bad5 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -241,8 +241,16 @@ static int beep_write(char *buf); * Bluetooth subdriver */ +enum { + /* ACPI GBDC/SBDC bits */ + TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ + TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */ + TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */ +}; + static int bluetooth_init(struct ibm_init_struct *iibm); -static int bluetooth_status(void); +static int bluetooth_get_radiosw(void); +static int bluetooth_set_radiosw(int radio_on); static int bluetooth_read(char *p); static int bluetooth_write(char *buf); @@ -467,8 +475,16 @@ static int volume_write(char *buf); * Wan subdriver */ +enum { + /* ACPI GWAN/SWAN bits */ + TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ + TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */ + TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */ +}; + static int wan_init(struct ibm_init_struct *iibm); -static int wan_status(void); +static int wan_get_radiosw(void); +static int wan_set_radiosw(int radio_on); static int wan_read(char *p); static int wan_write(char *buf); -- cgit v1.1 From 83f34724643a3b0ec9322490b9ad9f1b60170a6c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:41 -0300 Subject: ACPI: thinkpad-acpi: cleanup video subdriver Cleanup video subdriver for sysfs conversion, and properly check result status of acpi_evalf. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 262 +++++++++++++++++++++++++++++-------------- drivers/misc/thinkpad_acpi.h | 25 ++++- 2 files changed, 197 insertions(+), 90 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index a77368f..19c14bb 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -935,111 +935,194 @@ static int __init video_init(struct ibm_init_struct *iibm) 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); + dbg_printk(TPACPI_DBG_EXIT, + "restoring original video autoswitch mode\n"); + if (video_autosw_set(video_orig_autosw)) + printk(IBM_ERR "error while trying to restore original " + "video autoswitch mode\n"); } -static int video_status(void) +static int video_outputsw_get(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; + switch (video_supported) { + case TPACPI_VIDEO_570: + if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", + TP_ACPI_VIDEO_570_PHSCMD)) + return -EIO; + status = i & TP_ACPI_VIDEO_570_PHSMASK; + break; + case TPACPI_VIDEO_770: + if (!acpi_evalf(NULL, &i, "\\VCDL", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_LCD; + if (!acpi_evalf(NULL, &i, "\\VCDC", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_CRT; + break; + case TPACPI_VIDEO_NEW: + if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) || + !acpi_evalf(NULL, &i, "\\VCDC", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_CRT; + + if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) || + !acpi_evalf(NULL, &i, "\\VCDL", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_LCD; + if (!acpi_evalf(NULL, &i, "\\VCDD", "d")) + return -EIO; + if (i) + status |= TP_ACPI_VIDEO_S_DVI; + break; + default: + return -ENOSYS; } return status; } -static int video_autosw(void) +static int video_outputsw_set(int status) { - int autosw = 0; + int autosw; + int res = 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"); + switch (video_supported) { + case TPACPI_VIDEO_570: + res = acpi_evalf(NULL, NULL, + "\\_SB.PHS2", "vdd", + TP_ACPI_VIDEO_570_PHS2CMD, + status | TP_ACPI_VIDEO_570_PHS2SET); + break; + case TPACPI_VIDEO_770: + autosw = video_autosw_get(); + if (autosw < 0) + return autosw; - return autosw & 1; + res = video_autosw_set(1); + if (res) + return res; + res = acpi_evalf(vid_handle, NULL, + "ASWT", "vdd", status * 0x100, 0); + if (!autosw && video_autosw_set(autosw)) { + printk(IBM_ERR "video auto-switch left enabled due to error\n"); + return -EIO; + } + break; + case TPACPI_VIDEO_NEW: + res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && + acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); + break; + default: + return -ENOSYS; + } + + return (res)? 0 : -EIO; } -static int video_switch(void) +static int video_autosw_get(void) { - int autosw = video_autosw(); - int ret; + int autosw = 0; - 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); + switch (video_supported) { + case TPACPI_VIDEO_570: + if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d")) + return -EIO; + break; + case TPACPI_VIDEO_770: + case TPACPI_VIDEO_NEW: + if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d")) + return -EIO; + break; + default: + return -ENOSYS; + } - return ret; + return autosw & 1; } -static int video_expand(void) +static int video_autosw_set(int enable) { - 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"); + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0)) + return -EIO; + return 0; } -static int video_switch2(int status) +static int video_outputsw_cycle(void) { - 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; + int autosw = video_autosw_get(); + int res; - ret = acpi_evalf(vid_handle, NULL, - "ASWT", "vdd", status * 0x100, 0); + if (autosw < 0) + return autosw; - 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); + switch (video_supported) { + case TPACPI_VIDEO_570: + res = video_autosw_set(1); + if (res) + return res; + res = acpi_evalf(ec_handle, NULL, "_Q16", "v"); + break; + case TPACPI_VIDEO_770: + case TPACPI_VIDEO_NEW: + res = video_autosw_set(1); + if (res) + return res; + res = acpi_evalf(vid_handle, NULL, "VSWT", "v"); + break; + default: + return -ENOSYS; + } + if (!autosw && video_autosw_set(autosw)) { + printk(IBM_ERR "video auto-switch left enabled due to error\n"); + return -EIO; } - return ret; + return (res)? 0 : -EIO; +} + +static int video_expand_toggle(void) +{ + switch (video_supported) { + case TPACPI_VIDEO_570: + return acpi_evalf(ec_handle, NULL, "_Q17", "v")? + 0 : -EIO; + case TPACPI_VIDEO_770: + return acpi_evalf(vid_handle, NULL, "VEXP", "v")? + 0 : -EIO; + case TPACPI_VIDEO_NEW: + return acpi_evalf(NULL, NULL, "\\VEXP", "v")? + 0 : -EIO; + default: + return -ENOSYS; + } + /* not reached */ } static int video_read(char *p) { - int status = video_status(); - int autosw = video_autosw(); + int status, autosw; int len = 0; - if (!video_supported) { + if (video_supported == TPACPI_VIDEO_NONE) { len += sprintf(p + len, "status:\t\tnot supported\n"); return len; } + status = video_outputsw_get(); + if (status < 0) + return status; + + autosw = video_autosw_get(); + if (autosw < 0) + return autosw; + 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)); @@ -1060,47 +1143,56 @@ static int video_write(char *buf) { char *cmd; int enable, disable, status; + int res; - if (!video_supported) + if (video_supported == TPACPI_VIDEO_NONE) return -ENODEV; - enable = disable = 0; + enable = 0; + disable = 0; while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "lcd_enable") == 0) { - enable |= 0x01; + enable |= TP_ACPI_VIDEO_S_LCD; } else if (strlencmp(cmd, "lcd_disable") == 0) { - disable |= 0x01; + disable |= TP_ACPI_VIDEO_S_LCD; } else if (strlencmp(cmd, "crt_enable") == 0) { - enable |= 0x02; + enable |= TP_ACPI_VIDEO_S_CRT; } else if (strlencmp(cmd, "crt_disable") == 0) { - disable |= 0x02; + disable |= TP_ACPI_VIDEO_S_CRT; } else if (video_supported == TPACPI_VIDEO_NEW && strlencmp(cmd, "dvi_enable") == 0) { - enable |= 0x08; + enable |= TP_ACPI_VIDEO_S_DVI; } else if (video_supported == TPACPI_VIDEO_NEW && strlencmp(cmd, "dvi_disable") == 0) { - disable |= 0x08; + disable |= TP_ACPI_VIDEO_S_DVI; } else if (strlencmp(cmd, "auto_enable") == 0) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; + res = video_autosw_set(1); + if (res) + return res; } else if (strlencmp(cmd, "auto_disable") == 0) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0)) - return -EIO; + res = video_autosw_set(0); + if (res) + return res; } else if (strlencmp(cmd, "video_switch") == 0) { - if (!video_switch()) - return -EIO; + res = video_outputsw_cycle(); + if (res) + return res; } else if (strlencmp(cmd, "expand_toggle") == 0) { - if (!video_expand()) - return -EIO; + res = video_expand_toggle(); + if (res) + return res; } else return -EINVAL; } if (enable || disable) { - status = (video_status() & 0x0f & ~disable) | enable; - if (!video_switch2(status)) - return -EIO; + status = video_outputsw_get(); + if (status < 0) + return status; + res = video_outputsw_set((status & ~disable) | enable); + if (res) + return res; } return 0; diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index e06bad5..3a8718a 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -446,17 +446,32 @@ enum video_access_mode { TPACPI_VIDEO_NEW, /* all others */ }; +enum { /* video status flags, based on VIDEO_570 */ + TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */ + TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */ + TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */ +}; + +enum { /* TPACPI_VIDEO_570 constants */ + TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */ + TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to + * video_status_flags */ + TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */ + TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */ +}; + static enum video_access_mode video_supported; static int video_orig_autosw; static acpi_handle vid_handle, vid2_handle; static int video_init(struct ibm_init_struct *iibm); static void video_exit(void); -static int video_status(void); -static int video_autosw(void); -static int video_switch(void); -static int video_switch2(int status); -static int video_expand(void); +static int video_outputsw_get(void); +static int video_outputsw_set(int status); +static int video_autosw_get(void); +static int video_autosw_set(int enable); +static int video_outputsw_cycle(void); +static int video_expand_toggle(void); static int video_read(char *p); static int video_write(char *buf); -- cgit v1.1 From c9bea99c1a712548db3437cbca52b0da8f30069c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:42 -0300 Subject: ACPI: thinkpad-acpi: clean up CMOS commands subdriver Some ThinkPad CMOS commands subdriver cleanups, and also rename/promote cmos_eval to a ACPI helper function, as it is used by many other subdrivers. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 39 ++++++++++++++++++++------------------- drivers/misc/thinkpad_acpi.h | 4 +++- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 19c14bb..8829d3c 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -273,6 +273,17 @@ static int _sta(acpi_handle handle) return status; } +static int issue_thinkpad_cmos_command(int cmos_cmd) +{ + if (!cmos_handle) + return -ENXIO; + + if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd)) + return -EIO; + + return 0; +} + /************************************************************************* * ACPI device model */ @@ -1550,14 +1561,6 @@ static int __init cmos_init(struct ibm_init_struct *iibm) 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; @@ -1577,10 +1580,7 @@ static int cmos_read(char *p) static int cmos_write(char *buf) { char *cmd; - int cmos_cmd; - - if (!cmos_handle) - return -EINVAL; + int cmos_cmd, res; while ((cmd = next_cmd(&buf))) { if (sscanf(cmd, "%u", &cmos_cmd) == 1 && @@ -1589,8 +1589,9 @@ static int cmos_write(char *buf) } else return -EINVAL; - if (!cmos_eval(cmos_cmd)) - return -EIO; + res = issue_thinkpad_cmos_command(cmos_cmd); + if (res) + return res; } return 0; @@ -2093,7 +2094,7 @@ static int brightness_set(int value) 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)) + if (issue_thinkpad_cmos_command(cmos_cmd)) return -EIO; if (!acpi_ec_write(brightness_offset, i + inc)) return -EIO; @@ -2210,16 +2211,16 @@ static int volume_write(char *buf) 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) || + if (mute && (issue_thinkpad_cmos_command(cmos_cmd) || !acpi_ec_write(volume_offset, level))) return -EIO; for (i = level; i != new_level; i += inc) - if (!cmos_eval(cmos_cmd) || + if (issue_thinkpad_cmos_command(cmos_cmd) || !acpi_ec_write(volume_offset, i + inc)) return -EIO; - if (mute && (!cmos_eval(TP_CMOS_VOLUME_MUTE) || + if (mute && (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) || !acpi_ec_write(volume_offset, new_level + mute))) return -EIO; @@ -2228,7 +2229,7 @@ static int volume_write(char *buf) 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) || + if (issue_thinkpad_cmos_command(cmos_cmd) || !acpi_ec_write(volume_offset, level + new_mute)) return -EIO; } diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 3a8718a..fb0abb0 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -116,6 +116,9 @@ static void drv_acpi_handle_init(char *name, drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \ object##_paths, ARRAY_SIZE(object##_paths), &object##_path) +/* ThinkPad ACPI helpers */ +static int issue_thinkpad_cmos_command(int cmos_cmd); + /* procfs support */ static struct proc_dir_entry *proc_dir; @@ -275,7 +278,6 @@ static int brightness_write(char *buf); * CMOS subdriver */ -static int cmos_eval(int cmos_cmd); static int cmos_read(char *p); static int cmos_write(char *buf); -- cgit v1.1 From 04cc862c1893a055ab1117fa6f3aa0886c0ba032 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:43 -0300 Subject: ACPI: thinkpad-acpi: cleanup thermal subdriver for sysfs conversion Clean-up the thermal subdriver for sysfs conversion. Make thermal_get_* reentrancy-safe while at it, and add the missing thermal_read_mode variable to the header file. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 79 +++++++++++++++++++++++++++++--------------- drivers/misc/thinkpad_acpi.h | 8 +++++ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 8829d3c..e9aec87 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1818,13 +1818,13 @@ static int __init thermal_init(struct ibm_init_struct *iibm) ta1 = ta2 = 0; for (i = 0; i < 8; i++) { - if (likely(acpi_ec_read(0x78 + i, &t))) { + if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) { ta1 |= t; } else { ta1 = 0; break; } - if (likely(acpi_ec_read(0xC0 + i, &t))) { + if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) { ta2 |= t; } else { ta1 = 0; @@ -1869,57 +1869,84 @@ static int __init thermal_init(struct ibm_init_struct *iibm) return (thermal_read_mode != TPACPI_THERMAL_NONE)? 0 : 1; } -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +/* idx is zero-based */ +static int thermal_get_sensor(int idx, s32 *value) { - int i, t; + int t; s8 tmp; - char tmpi[] = "TMPi"; + char tmpi[5]; - if (!s) - return -EINVAL; + t = TP_EC_THERMAL_TMP0; switch (thermal_read_mode) { #if TPACPI_MAX_THERMAL_SENSORS >= 16 case TPACPI_THERMAL_TPEC_16: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0xC0 + i, &tmp)) - return -EIO; - s->temp[i + 8] = tmp * 1000; + if (idx >= 8 && idx <= 15) { + t = TP_EC_THERMAL_TMP8; + idx -= 8; } /* fallthrough */ #endif case TPACPI_THERMAL_TPEC_8: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0x78 + i, &tmp)) + if (idx <= 7) { + if (!acpi_ec_read(t + idx, &tmp)) return -EIO; - s->temp[i] = tmp * 1000; + *value = tmp * 1000; + return 0; } - return (thermal_read_mode == TPACPI_THERMAL_TPEC_16) ? 16 : 8; + break; 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 (idx <= 7) { + snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); + if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) + return -EIO; if (!acpi_evalf(ec_handle, &t, tmpi, "d")) return -EIO; - s->temp[i] = (t - 2732) * 100; + *value = (t - 2732) * 100; + return 0; } - return 8; + break; case TPACPI_THERMAL_ACPI_TMP07: - for (i = 0; i < 8; i++) { - tmpi[3] = '0' + i; + if (idx <= 7) { + snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); if (!acpi_evalf(ec_handle, &t, tmpi, "d")) return -EIO; - s->temp[i] = t * 1000; + *value = t * 1000; + return 0; } - return 8; + break; case TPACPI_THERMAL_NONE: default: - return 0; + return -ENOSYS; } + + return -EINVAL; +} + +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +{ + int res, i; + int n; + + n = 8; + i = 0; + + if (!s) + return -EINVAL; + + if (thermal_read_mode == TPACPI_THERMAL_TPEC_16) + n = 16; + + for(i = 0 ; i < n; i++) { + res = thermal_get_sensor(i, &s->temp[i]); + if (res) + return res; + } + + return n; } static int thermal_read(char *p) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index fb0abb0..6432b28 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -427,12 +427,20 @@ enum thermal_access_mode { TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ }; +enum { /* TPACPI_THERMAL_TPEC_* */ + TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ + TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ +}; + #define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ struct ibm_thermal_sensors_struct { s32 temp[TPACPI_MAX_THERMAL_SENSORS]; }; +static enum thermal_access_mode thermal_read_mode; + static int thermal_init(struct ibm_init_struct *iibm); +static int thermal_get_sensor(int idx, s32 *value); static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); static int thermal_read(char *p); -- cgit v1.1 From 99fba3f8177956170f3d86f83c2cf2f70747105f Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Sat, 21 Apr 2007 11:08:44 -0300 Subject: ACPI: thinkpad-acpi: improve fan watchdog messages Improve some of the fan watchdog error messages to be a little more helpful. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index e9aec87..9b4eea4 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2524,7 +2524,7 @@ static int fan_get_status(u8 *status) static void fan_exit(void) { - vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending watchdogs\n"); + vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n"); cancel_delayed_work(&fan_watchdog_task); flush_scheduled_work(); } @@ -2554,9 +2554,13 @@ static int fan_get_speed(unsigned int *speed) static void fan_watchdog_fire(struct work_struct *ignored) { + int rc; + printk(IBM_NOTICE "fan watchdog: enabling fan\n"); - if (fan_set_enable()) { - printk(IBM_ERR "fan watchdog: error while enabling fan\n"); + rc = fan_set_enable(); + if (rc < 0) { + printk(IBM_ERR "fan watchdog: error %d while enabling fan, " + "will try again later...\n", -rc); /* reschedule for later */ fan_watchdog_reset(); } -- cgit v1.1 From 54ae15014c306b3d7ad32c996fea9a5ac8560b60 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:12 -0300 Subject: ACPI: thinkpad-acpi: register with the device model Register thinkpad-acpi platform driver and platform device for the device model. Also register the platform device with the hwmon class. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 40 +++++++++++++++++++++++++----- drivers/misc/Kconfig | 1 + drivers/misc/thinkpad_acpi.c | 54 +++++++++++++++++++++++++++++++++++++++++ drivers/misc/thinkpad_acpi.h | 8 ++++++ 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 1a42b77..0e4e053 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -1,7 +1,7 @@ ThinkPad ACPI Extras Driver Version 0.14 - March 26th, 2007 + April 21st, 2007 Borislav Deianov Henrique de Moraes Holschuh @@ -67,11 +67,39 @@ thinkpad-specific bay functionality. Features -------- -The driver creates the /proc/acpi/ibm directory. There is a file under -that directory for each feature described below. Note that while the -driver is still in the alpha stage, the exact proc file format and -commands supported by the various features is guaranteed to change -frequently. +The driver exports two different interfaces to userspace, which can be +used to access the features it provides. One is a legacy procfs-based +interface, which will be removed at some time in the distant future. +The other is a new sysfs-based interface which is not complete yet. + +The procfs interface creates the /proc/acpi/ibm directory. There is a +file under that directory for each feature it supports. The procfs +interface is mostly frozen, and will change very little if at all: it +will not be extended to add any new functionality in the driver, instead +all new functionality will be implemented on the sysfs interface. + +The sysfs interface tries to blend in the generic Linux sysfs subsystems +and classes as much as possible. Since some of these subsystems are not +yet ready or stabilized, it is expected that this interface will change, +and any and all userspace programs must deal with it. + + +Notes about the sysfs interface: + +Unlike what was done with the procfs interface, correctness when talking +to the sysfs interfaces will be enforced, as will correctness in the +thinkpad-acpi's implementation of sysfs interfaces. + +Also, any bugs in the thinkpad-acpi sysfs driver code or in the +thinkpad-acpi's implementation of the sysfs interfaces will be fixed for +maximum correctness, even if that means changing an interface in +non-compatible ways. As these interfaces mature both in the kernel and +in thinkpad-acpi, such changes should become quite rare. + +Applications interfacing to the thinkpad-acpi sysfs interfaces must +follow all sysfs guidelines and correctly process all errors (the sysfs +interface makes extensive use of errors). File descriptors and open / +close operations to the sysfs inodes must also be properly implemented. Driver version -- /proc/acpi/ibm/driver --------------------------------------- diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 44e4c8f..445c4b1 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -126,6 +126,7 @@ config THINKPAD_ACPI tristate "ThinkPad ACPI Laptop Extras" depends on X86 && ACPI select BACKLIGHT_CLASS_DEVICE + select HWMON ---help--- This is a driver for the IBM and Lenovo ThinkPad laptops. It adds support for Fn-Fx key combinations, Bluetooth control, video diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 9b4eea4..e47eaf7 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -477,6 +477,25 @@ static char *next_cmd(char **cmds) /**************************************************************************** **************************************************************************** * + * Device model: hwmon and platform + * + **************************************************************************** + ****************************************************************************/ + +static struct platform_device *tpacpi_pdev = NULL; +static struct class_device *tpacpi_hwmon = NULL; + +static struct platform_driver tpacpi_pdriver = { + .driver = { + .name = IBM_DRVR_NAME, + .owner = THIS_MODULE, + }, +}; + + +/**************************************************************************** + **************************************************************************** + * * Subdrivers * **************************************************************************** @@ -3225,10 +3244,12 @@ static int __init thinkpad_acpi_module_init(void) { int ret, i; + /* Driver-level probe */ ret = probe_for_thinkpad(); if (ret) return ret; + /* Driver initialization */ ibm_thinkpad_ec_found = check_dmi_for_ec(); IBM_ACPIHANDLE_INIT(ecrd); IBM_ACPIHANDLE_INIT(ecwr); @@ -3241,6 +3262,31 @@ static int __init thinkpad_acpi_module_init(void) } proc_dir->owner = THIS_MODULE; + ret = platform_driver_register(&tpacpi_pdriver); + if (ret) { + printk(IBM_ERR "unable to register platform driver\n"); + thinkpad_acpi_module_exit(); + return ret; + } + + /* Device initialization */ + tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1, + NULL, 0); + if (IS_ERR(tpacpi_pdev)) { + ret = PTR_ERR(tpacpi_pdev); + tpacpi_pdev = NULL; + printk(IBM_ERR "unable to register platform device\n"); + thinkpad_acpi_module_exit(); + return ret; + } + tpacpi_hwmon = hwmon_device_register(&tpacpi_pdev->dev); + if (IS_ERR(tpacpi_hwmon)) { + ret = PTR_ERR(tpacpi_hwmon); + tpacpi_hwmon = NULL; + printk(IBM_ERR "unable to register hwmon device\n"); + thinkpad_acpi_module_exit(); + return ret; + } for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { ret = ibm_init(&ibms_init[i]); if (ret >= 0 && *ibms_init[i].param) @@ -3266,6 +3312,14 @@ static void thinkpad_acpi_module_exit(void) dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n"); + if (tpacpi_hwmon) + hwmon_device_unregister(tpacpi_hwmon); + + if (tpacpi_pdev) + platform_device_unregister(tpacpi_pdev); + + platform_driver_unregister(&tpacpi_pdriver); + if (proc_dir) remove_proc_entry(IBM_PROC_DIR, acpi_root_dir); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 6432b28..fea58099 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include @@ -56,6 +58,7 @@ #define IBM_PROC_DIR "ibm" #define IBM_ACPI_EVENT_PREFIX "ibm" +#define IBM_DRVR_NAME IBM_FILE #define IBM_LOG IBM_FILE ": " #define IBM_ERR KERN_ERR IBM_LOG @@ -130,6 +133,11 @@ static int dispatch_procfs_write(struct file *file, unsigned long count, void *data); static char *next_cmd(char **cmds); +/* Device model */ +static struct platform_device *tpacpi_pdev; +static struct class_device *tpacpi_hwmon; +static struct platform_driver tpacpi_pdriver; + /* Module */ static int experimental; static u32 dbg_level; -- cgit v1.1 From 176750d68801bfa4a88d1cf54174aa0347d7e5d8 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:13 -0300 Subject: ACPI: thinkpad-acpi: driver sysfs conversion Add the sysfs attributes for the platform driver. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 42 ++++++++++++++++++- drivers/misc/thinkpad_acpi.c | 90 +++++++++++++++++++++++++++++++++++++++++ drivers/misc/thinkpad_acpi.h | 3 ++ 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 0e4e053..cc079af 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -101,11 +101,39 @@ follow all sysfs guidelines and correctly process all errors (the sysfs interface makes extensive use of errors). File descriptors and open / close operations to the sysfs inodes must also be properly implemented. -Driver version -- /proc/acpi/ibm/driver ---------------------------------------- +The version of thinkpad-acpi's sysfs interface is exported by the driver +as a driver attribute (see below). + +Sysfs driver attributes are on the driver's sysfs attribute space, +for 2.6.20 this is /sys/bus/platform/drivers/thinkpad-acpi/. + +Sysfs device attributes are on the driver's sysfs attribute space, +for 2.6.20 this is /sys/devices/platform/thinkpad-acpi/. + +Driver version +-------------- + +procfs: /proc/acpi/ibm/driver +sysfs driver attribute: version The driver name and version. No commands can be written to this file. +Sysfs interface version +----------------------- + +sysfs driver attribute: interface_version + +Version of the thinkpad-acpi sysfs interface, as an unsigned long +(output in hex format: 0xAAAABBCC), where: + AAAA - major revision + BB - minor revision + CC - bugfix revision + +The sysfs interface version changelog for the driver can be found at the +end of this document. Changes to the sysfs interface done by the kernel +subsystems are not documented here, nor are they tracked by this +attribute. + Hot keys -- /proc/acpi/ibm/hotkey --------------------------------- @@ -745,9 +773,19 @@ to enable more than one output class, just add their values. There is also a kernel build option to enable more debugging information, which may be necessary to debug driver problems. +The level of debugging information output by the driver can be changed +at runtime through sysfs, using the driver attribute debug_level. The +attribute takes the same bitmask as the debug module parameter above. + Force loading of module ----------------------- If thinkpad-acpi refuses to detect your ThinkPad, you can try to specify the module parameter force_load=1. Regardless of whether this works or not, please contact ibm-acpi-devel@lists.sourceforge.net with a report. + + +Sysfs interface changelog: + +0x000100: Initial sysfs support, as a single platform driver and + device. diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index e47eaf7..a31d00d 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -22,6 +22,7 @@ */ #define IBM_VERSION "0.14" +#define TPACPI_SYSFS_VERSION 0x000100 /* * Changelog: @@ -493,6 +494,87 @@ static struct platform_driver tpacpi_pdriver = { }; +/************************************************************************* + * thinkpad-acpi driver attributes + */ + +/* interface_version --------------------------------------------------- */ +static ssize_t tpacpi_driver_interface_version_show( + struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION); +} + +static DRIVER_ATTR(interface_version, S_IRUGO, + tpacpi_driver_interface_version_show, NULL); + +/* debug_level --------------------------------------------------------- */ +static ssize_t tpacpi_driver_debug_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level); +} + +static ssize_t tpacpi_driver_debug_store(struct device_driver *drv, + const char *buf, size_t count) +{ + unsigned long t; + char *endp; + + t = simple_strtoul(buf, &endp, 0); + while (*endp && isspace(*endp)) + endp++; + if (*endp) + return -EINVAL; + + dbg_level = t; + + return count; +} + +static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, + tpacpi_driver_debug_show, tpacpi_driver_debug_store); + +/* version ------------------------------------------------------------- */ +static ssize_t tpacpi_driver_version_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION); +} + +static DRIVER_ATTR(version, S_IRUGO, + tpacpi_driver_version_show, NULL); + +/* --------------------------------------------------------------------- */ + +static struct driver_attribute* tpacpi_driver_attributes[] = { + &driver_attr_debug_level, &driver_attr_version, + &driver_attr_interface_version, +}; + +static int __init tpacpi_create_driver_attributes(struct device_driver *drv) +{ + int i, res; + + i = 0; + res = 0; + while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) { + res = driver_create_file(drv, tpacpi_driver_attributes[i]); + i++; + } + + return res; +} + +static void tpacpi_remove_driver_attributes(struct device_driver *drv) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++) + driver_remove_file(drv, tpacpi_driver_attributes[i]); +} + /**************************************************************************** **************************************************************************** * @@ -3268,6 +3350,13 @@ static int __init thinkpad_acpi_module_init(void) thinkpad_acpi_module_exit(); return ret; } + ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver); + if (ret) { + printk(IBM_ERR "unable to create sysfs driver attributes\n"); + thinkpad_acpi_module_exit(); + return ret; + } + /* Device initialization */ tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1, @@ -3318,6 +3407,7 @@ static void thinkpad_acpi_module_exit(void) if (tpacpi_pdev) platform_device_unregister(tpacpi_pdev); + tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver); platform_driver_unregister(&tpacpi_pdriver); if (proc_dir) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index fea58099..3786058 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -137,6 +138,8 @@ static char *next_cmd(char **cmds); static struct platform_device *tpacpi_pdev; static struct class_device *tpacpi_hwmon; static struct platform_driver tpacpi_pdriver; +static int tpacpi_create_driver_attributes(struct device_driver *drv); +static void tpacpi_remove_driver_attributes(struct device_driver *drv); /* Module */ static int experimental; -- cgit v1.1 From 7252374a39d794879f5e47bcfa0a16e7599b27b5 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:14 -0300 Subject: ACPI: thinkpad-acpi: add infrastructure for the sysfs device attributes Add infrastructure to deal with sysfs attributes and grouping, and helpers for common sysfs parsing. Switch driver attributes to use them. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 86 +++++++++++++++++++++++++++++++++++++++++--- drivers/misc/thinkpad_acpi.h | 21 +++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index a31d00d..ca6d15c 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -520,12 +520,8 @@ static ssize_t tpacpi_driver_debug_store(struct device_driver *drv, const char *buf, size_t count) { unsigned long t; - char *endp; - t = simple_strtoul(buf, &endp, 0); - while (*endp && isspace(*endp)) - endp++; - if (*endp) + if (parse_strtoul(buf, 0xffff, &t)) return -EINVAL; dbg_level = t; @@ -575,6 +571,86 @@ static void tpacpi_remove_driver_attributes(struct device_driver *drv) driver_remove_file(drv, tpacpi_driver_attributes[i]); } +/************************************************************************* + * sysfs support helpers + */ + +struct attribute_set_obj { + struct attribute_set s; + struct attribute *a; +} __attribute__((packed)); + +static struct attribute_set *create_attr_set(unsigned int max_members, + const char* name) +{ + struct attribute_set_obj *sobj; + + if (max_members == 0) + return NULL; + + /* Allocates space for implicit NULL at the end too */ + sobj = kzalloc(sizeof(struct attribute_set_obj) + + max_members * sizeof(struct attribute *), + GFP_KERNEL); + if (!sobj) + return NULL; + sobj->s.max_members = max_members; + sobj->s.group.attrs = &sobj->a; + sobj->s.group.name = name; + + return &sobj->s; +} + +/* not multi-threaded safe, use it in a single thread per set */ +static int add_to_attr_set(struct attribute_set* s, struct attribute *attr) +{ + if (!s || !attr) + return -EINVAL; + + if (s->members >= s->max_members) + return -ENOMEM; + + s->group.attrs[s->members] = attr; + s->members++; + + return 0; +} + +static int add_many_to_attr_set(struct attribute_set* s, + struct attribute **attr, + unsigned int count) +{ + int i, res; + + for (i = 0; i < count; i++) { + res = add_to_attr_set(s, attr[i]); + if (res) + return res; + } + + return 0; +} + +static void delete_attr_set(struct attribute_set* s, struct kobject *kobj) +{ + sysfs_remove_group(kobj, &s->group); + destroy_attr_set(s); +} + +static int parse_strtoul(const char *buf, + unsigned long max, unsigned long *value) +{ + char *endp; + + *value = simple_strtoul(buf, &endp, 0); + while (*endp && isspace(*endp)) + endp++; + if (*endp || *value > max) + return -EINVAL; + + return 0; +} + /**************************************************************************** **************************************************************************** * diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 3786058..84fdefe 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -134,6 +134,27 @@ static int dispatch_procfs_write(struct file *file, unsigned long count, void *data); static char *next_cmd(char **cmds); +/* sysfs support */ +struct attribute_set { + unsigned int members, max_members; + struct attribute_group group; +}; + +static struct attribute_set *create_attr_set(unsigned int max_members, + const char* name); +#define destroy_attr_set(_set) \ + kfree(_set); +static int add_to_attr_set(struct attribute_set* s, struct attribute *attr); +static int add_many_to_attr_set(struct attribute_set* s, + struct attribute **attr, + unsigned int count); +#define register_attr_set_with_sysfs(_attr_set, _kobj) \ + sysfs_create_group(_kobj, &_attr_set->group) +static void delete_attr_set(struct attribute_set* s, struct kobject *kobj); + +static int parse_strtoul(const char *buf, unsigned long max, + unsigned long *value); + /* Device model */ static struct platform_device *tpacpi_pdev; static struct class_device *tpacpi_hwmon; -- cgit v1.1 From 40ca9fdf8aa7d929e2b8939be1e6380d107381e1 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:15 -0300 Subject: ACPI: thinkpad-acpi: protect fan and hotkey data structures Add proper mutex locking to some data structures access subject to races due to concurrent access of driver functions on the hotkey and fan subdrivers. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 114 +++++++++++++++++++++++++++++++++---------- drivers/misc/thinkpad_acpi.h | 5 ++ 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index ca6d15c..aa69ff0 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -704,6 +704,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n"); IBM_ACPIHANDLE_INIT(hkey); + mutex_init(&hotkey_mutex); /* hotkey not supported on 570 */ tp_features.hotkey = hkey_handle != NULL; @@ -752,6 +753,9 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) } } +/* + * Call with hotkey_mutex held + */ static int hotkey_get(int *status, int *mask) { if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) @@ -764,6 +768,9 @@ static int hotkey_get(int *status, int *mask) return 0; } +/* + * Call with hotkey_mutex held + */ static int hotkey_set(int status, int mask) { int i; @@ -792,7 +799,11 @@ static int hotkey_read(char *p) return len; } + res = mutex_lock_interruptible(&hotkey_mutex); + if (res < 0) + return res; res = hotkey_get(&status, &mask); + mutex_unlock(&hotkey_mutex); if (res) return res; @@ -818,10 +829,15 @@ static int hotkey_write(char *buf) if (!tp_features.hotkey) return -ENODEV; + res = mutex_lock_interruptible(&hotkey_mutex); + if (res < 0) + return res; + res = hotkey_get(&status, &mask); if (res) - return res; + goto errexit; + res = 0; while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "enable") == 0) { status = 1; @@ -834,18 +850,19 @@ static int hotkey_write(char *buf) /* mask set */ } else if (sscanf(cmd, "%x", &mask) == 1) { /* mask set */ - } else - return -EINVAL; + } else { + res = -EINVAL; + goto errexit; + } do_cmd = 1; } - if (do_cmd) { + if (do_cmd) res = hotkey_set(status, mask); - if (res) - return res; - } - return 0; +errexit: + mutex_unlock(&hotkey_mutex); + return res; } static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = { @@ -2575,6 +2592,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); + mutex_init(&fan_mutex); fan_status_access_mode = TPACPI_FAN_NONE; fan_control_access_mode = TPACPI_FAN_WR_NONE; fan_control_commands = 0; @@ -2764,10 +2782,17 @@ static void fan_watchdog_reset(void) static int fan_set_level(int level) { + int res; + 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)) + res = mutex_lock_interruptible(&fan_mutex); + if (res < 0) + return res; + res = acpi_evalf(sfan_handle, NULL, NULL, "vd", level); + mutex_unlock(&fan_mutex); + if (!res) return -EIO; } else return -EINVAL; @@ -2780,7 +2805,12 @@ static int fan_set_level(int level) ((level < 0) || (level > 7))) return -EINVAL; - if (!acpi_ec_write(fan_status_offset, level)) + res = mutex_lock_interruptible(&fan_mutex); + if (res < 0) + return res; + res = acpi_ec_write(fan_status_offset, level); + mutex_unlock(&fan_mutex); + if (!res) return -EIO; else tp_features.fan_ctrl_status_undef = 0; @@ -2797,25 +2827,33 @@ static int fan_set_enable(void) u8 s; int rc; + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return 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; + rc = fan_get_status(&s); + if (rc < 0) + break; /* 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; - else + rc = -EIO; + else { tp_features.fan_ctrl_status_undef = 0; + rc = 0; + } break; case TPACPI_FAN_WR_ACPI_SFAN: - if ((rc = fan_get_status(&s)) < 0) - return rc; + rc = fan_get_status(&s); + if (rc < 0) + break; s &= 0x07; @@ -2824,53 +2862,75 @@ static int fan_set_enable(void) s = 4; if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) - return -EIO; + rc= -EIO; + else + rc = 0; break; default: - return -ENXIO; + rc = -ENXIO; } - return 0; + + mutex_unlock(&fan_mutex); + return rc; } static int fan_set_disable(void) { + int rc; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = 0; 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; + rc = -EIO; else tp_features.fan_ctrl_status_undef = 0; break; case TPACPI_FAN_WR_ACPI_SFAN: if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) - return -EIO; + rc = -EIO; break; default: - return -ENXIO; + rc = -ENXIO; } - return 0; + + mutex_unlock(&fan_mutex); + return rc; } static int fan_set_speed(int speed) { + int rc; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = 0; 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; + rc = -EIO; } else - return -EINVAL; + rc = -EINVAL; break; default: - return -ENXIO; + rc = -ENXIO; } - return 0; + + mutex_unlock(&fan_mutex); + return rc; } static int fan_read(char *p) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 84fdefe..a9feb53 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -375,6 +376,8 @@ static enum fan_control_commands fan_control_commands; static u8 fan_control_initial_status; static int fan_watchdog_maxinterval; +struct mutex fan_mutex; + static acpi_handle fans_handle, gfan_handle, sfan_handle; static int fan_init(struct ibm_init_struct *iibm); @@ -403,6 +406,8 @@ static int fan_write_cmd_watchdog(const char *cmd, int *rc); static int hotkey_orig_status; static int hotkey_orig_mask; +static struct mutex hotkey_mutex; + static int hotkey_init(struct ibm_init_struct *iibm); static void hotkey_exit(void); static int hotkey_get(int *status, int *mask); -- cgit v1.1 From 2c37aa4e22dd55070c608290c5031f2ee93e69ce Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:16 -0300 Subject: ACPI: thinkpad-acpi: add sysfs support to the thermal subdriver Export thinkpad thermal sensors to sysfs, following the hwmon specification for thermal monitoring sensors. ThinkPad thermal monitoring is done by the EC. Sensors can show up or disappear at runtime when they are inside hotswappable hardware, such as batteries. Sensors that are not available return -ENXIO when accessed. Up to 16 thermal sensors are supported on new firmware (but nobody has reported a ThinkPad with more than 12 sensors so far), and 8 sensors are supported on older firmware. Thermal sensor mapping is model-specific. Precision varies, it is 1 degree Celcius on new ThinkPads, but higher on some older models. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 26 +++++++-- drivers/misc/thinkpad_acpi.c | 122 +++++++++++++++++++++++++++++++++++++++- drivers/misc/thinkpad_acpi.h | 2 + 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index cc079af..80c0bf2 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -458,17 +458,17 @@ X40: 16 - one medium-pitched beep repeating constantly, stop with 17 17 - stop 16 -Temperature sensors -- /proc/acpi/ibm/thermal ---------------------------------------------- +Temperature sensors +------------------- + +procfs: /proc/acpi/ibm/thermal +sysfs device attributes: (hwmon) temp*_input Most ThinkPads include six or more separate temperature sensors but only expose the CPU temperature through the standard ACPI methods. This feature shows readings from up to eight different sensors on older ThinkPads, and it has experimental support for up to sixteen different -sensors on newer ThinkPads. Readings from sensors that are not available -return -128. - -No commands can be written to this file. +sensors on newer ThinkPads. EXPERIMENTAL: The 16-sensors feature is marked EXPERIMENTAL because the implementation directly accesses hardware registers and may not work as @@ -525,6 +525,20 @@ The A31 has a very atypical layout for the thermal sensors 8: Bay Battery: secondary sensor +Procfs notes: + Readings from sensors that are not available return -128. + No commands can be written to this file. + +Sysfs notes: + Sensors that are not available return the ENXIO error. This + status may change at runtime, as there are hotplug thermal + sensors, like those inside the batteries and docks. + + thinkpad-acpi thermal sensors are reported through the hwmon + subsystem, and follow all of the hwmon guidelines at + Documentation/hwmon. + + EXPERIMENTAL: Embedded controller register dump -- /proc/acpi/ibm/ecdump ------------------------------------------------------------------------ diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index aa69ff0..d5526e8 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1992,11 +1992,91 @@ static struct ibm_struct beep_driver_data = { static enum thermal_access_mode thermal_read_mode; +/* sysfs temp##_input -------------------------------------------------- */ + +static ssize_t thermal_temp_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = + to_sensor_dev_attr(attr); + int idx = sensor_attr->index; + s32 value; + int res; + + res = thermal_get_sensor(idx, &value); + if (res) + return res; + if (value == TP_EC_THERMAL_TMP_NA * 1000) + return -ENXIO; + + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \ + SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, thermal_temp_input_show, NULL, _idxB) + +static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = { + THERMAL_SENSOR_ATTR_TEMP(1, 0), + THERMAL_SENSOR_ATTR_TEMP(2, 1), + THERMAL_SENSOR_ATTR_TEMP(3, 2), + THERMAL_SENSOR_ATTR_TEMP(4, 3), + THERMAL_SENSOR_ATTR_TEMP(5, 4), + THERMAL_SENSOR_ATTR_TEMP(6, 5), + THERMAL_SENSOR_ATTR_TEMP(7, 6), + THERMAL_SENSOR_ATTR_TEMP(8, 7), + THERMAL_SENSOR_ATTR_TEMP(9, 8), + THERMAL_SENSOR_ATTR_TEMP(10, 9), + THERMAL_SENSOR_ATTR_TEMP(11, 10), + THERMAL_SENSOR_ATTR_TEMP(12, 11), + THERMAL_SENSOR_ATTR_TEMP(13, 12), + THERMAL_SENSOR_ATTR_TEMP(14, 13), + THERMAL_SENSOR_ATTR_TEMP(15, 14), + THERMAL_SENSOR_ATTR_TEMP(16, 15), +}; + +#define THERMAL_ATTRS(X) \ + &sensor_dev_attr_thermal_temp_input[X].dev_attr.attr + +static struct attribute *thermal_temp_input_attr[] = { + THERMAL_ATTRS(8), + THERMAL_ATTRS(9), + THERMAL_ATTRS(10), + THERMAL_ATTRS(11), + THERMAL_ATTRS(12), + THERMAL_ATTRS(13), + THERMAL_ATTRS(14), + THERMAL_ATTRS(15), + THERMAL_ATTRS(0), + THERMAL_ATTRS(1), + THERMAL_ATTRS(2), + THERMAL_ATTRS(3), + THERMAL_ATTRS(4), + THERMAL_ATTRS(5), + THERMAL_ATTRS(6), + THERMAL_ATTRS(7), + NULL +}; + +static const struct attribute_group thermal_temp_input16_group = { + .attrs = thermal_temp_input_attr +}; + +static const struct attribute_group thermal_temp_input8_group = { + .attrs = &thermal_temp_input_attr[8] +}; + +#undef THERMAL_SENSOR_ATTR_TEMP +#undef THERMAL_ATTRS + +/* --------------------------------------------------------------------- */ + static int __init thermal_init(struct ibm_init_struct *iibm) { u8 t, ta1, ta2; int i; int acpi_tmp7; + int res; vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n"); @@ -2060,7 +2140,46 @@ static int __init thermal_init(struct ibm_init_struct *iibm) str_supported(thermal_read_mode != TPACPI_THERMAL_NONE), thermal_read_mode); - return (thermal_read_mode != TPACPI_THERMAL_NONE)? 0 : 1; + switch(thermal_read_mode) { + case TPACPI_THERMAL_TPEC_16: + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input16_group); + if (res) + return res; + break; + case TPACPI_THERMAL_TPEC_8: + case TPACPI_THERMAL_ACPI_TMP07: + case TPACPI_THERMAL_ACPI_UPDT: + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input8_group); + if (res) + return res; + break; + case TPACPI_THERMAL_NONE: + default: + return 1; + } + + return 0; +} + +static void thermal_exit(void) +{ + switch(thermal_read_mode) { + case TPACPI_THERMAL_TPEC_16: + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input16_group); + break; + case TPACPI_THERMAL_TPEC_8: + case TPACPI_THERMAL_ACPI_TMP07: + case TPACPI_THERMAL_ACPI_UPDT: + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &thermal_temp_input16_group); + break; + case TPACPI_THERMAL_NONE: + default: + break; + } } /* idx is zero-based */ @@ -2168,6 +2287,7 @@ static int thermal_read(char *p) static struct ibm_struct thermal_driver_data = { .name = "thermal", .read = thermal_read, + .exit = thermal_exit, }; /************************************************************************* diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index a9feb53..e833ff3 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -467,6 +468,7 @@ enum thermal_access_mode { enum { /* TPACPI_THERMAL_TPEC_* */ TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ + TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ }; #define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -- cgit v1.1 From fe98a52ce7540fb3a19d57488a08864110cf4d5c Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:17 -0300 Subject: ACPI: thinkpad-acpi: add sysfs support to fan subdriver Export sysfs attributes to monitor and control the internal thinkpad fan (some thinkpads have more than one fan, but thinkpad-acpi doesn't support the second fan yet). The sysfs interface follows the hwmon design guide for fan devices. Also, fix some stray "thermal" files in the fan procfs description that have been there forever, and officially support "full-speed" as the name for the PWM-disabled state of the fan controller to keep it in line with the hwmon interface. It is much better a name for that mode than the unobvious "disengaged" anyway. Change the procfs interface to also accept full-speed as a fan level, but still report it as disengaged for backwards compatibility. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 157 ++++++++++++------- drivers/misc/thinkpad_acpi.c | 326 +++++++++++++++++++++++++++++++++++++--- drivers/misc/thinkpad_acpi.h | 6 + 3 files changed, 415 insertions(+), 74 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 80c0bf2..339ce21 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -642,8 +642,11 @@ distinct. The unmute the volume after the mute command, use either the up or down command (the level command will not unmute the volume). The current volume level and mute state is shown in the file. -EXPERIMENTAL: fan speed, fan enable/disable -- /proc/acpi/ibm/fan ------------------------------------------------------------------ +EXPERIMENTAL: fan speed, fan enable/disable +------------------------------------------- + +procfs: /proc/acpi/ibm/fan +sysfs device attributes: (hwmon) fan_input, pwm1, pwm1_enable This feature is marked EXPERIMENTAL because the implementation directly accesses hardware registers and may not work as expected. USE @@ -656,27 +659,26 @@ from the hardware registers of the embedded controller. This is known to work on later R, T and X series ThinkPads but may show a bogus value on other models. -Most ThinkPad fans work in "levels". Level 0 stops the fan. The higher -the level, the higher the fan speed, although adjacent levels often map -to the same fan speed. 7 is the highest level, where the fan reaches -the maximum recommended speed. Level "auto" means the EC changes the -fan level according to some internal algorithm, usually based on -readings from the thermal sensors. Level "disengaged" means the EC -disables the speed-locked closed-loop fan control, and drives the fan as -fast as it can go, which might exceed hardware limits, so use this level -with caution. +Fan levels: -The fan usually ramps up or down slowly from one speed to another, -and it is normal for the EC to take several seconds to react to fan -commands. +Most ThinkPad fans work in "levels" at the firmware interface. Level 0 +stops the fan. The higher the level, the higher the fan speed, although +adjacent levels often map to the same fan speed. 7 is the highest +level, where the fan reaches the maximum recommended speed. -The fan may be enabled or disabled with the following commands: +Level "auto" means the EC changes the fan level according to some +internal algorithm, usually based on readings from the thermal sensors. - echo enable >/proc/acpi/ibm/fan - echo disable >/proc/acpi/ibm/fan +There is also a "full-speed" level, also known as "disengaged" level. +In this level, the EC disables the speed-locked closed-loop fan control, +and drives the fan as fast as it can go, which might exceed hardware +limits, so use this level with caution. -Placing a fan on level 0 is the same as disabling it. Enabling a fan -will try to place it in a safe level if it is too slow or disabled. +The fan usually ramps up or down slowly from one speed to another, and +it is normal for the EC to take several seconds to react to fan +commands. The full-speed level may take up to two minutes to ramp up to +maximum speed, and in some ThinkPads, the tachometer readings go stale +while the EC is transitioning to the full-speed level. WARNING WARNING WARNING: do not leave the fan disabled unless you are monitoring all of the temperature sensor readings and you are ready to @@ -694,48 +696,101 @@ fan is turned off when the CPU temperature drops to 49 degrees and the HDD temperature drops to 41 degrees. These thresholds cannot currently be controlled. +The ThinkPad's ACPI DSDT code will reprogram the fan on its own when +certain conditions are met. It will override any fan programming done +through thinkpad-acpi. + +The thinkpad-acpi kernel driver can be programmed to revert the fan +level to a safe setting if userspace does not issue one of the procfs +fan commands: "enable", "disable", "level" or "watchdog", or if there +are no writes to pwm1_enable (or to pwm1 *if and only if* pwm1_enable is +set to 1, manual mode) within a configurable amount of time of up to +120 seconds. This functionality is called fan safety watchdog. + +Note that the watchdog timer stops after it enables the fan. It will be +rearmed again automatically (using the same interval) when one of the +above mentioned fan commands is received. The fan watchdog is, +therefore, not suitable to protect against fan mode changes made through +means other than the "enable", "disable", and "level" procfs fan +commands, or the hwmon fan control sysfs interface. + +Procfs notes: + +The fan may be enabled or disabled with the following commands: + + echo enable >/proc/acpi/ibm/fan + echo disable >/proc/acpi/ibm/fan + +Placing a fan on level 0 is the same as disabling it. Enabling a fan +will try to place it in a safe level if it is too slow or disabled. + The fan level can be controlled with the command: - echo 'level ' > /proc/acpi/ibm/thermal + echo 'level ' > /proc/acpi/ibm/fan -Where is an integer from 0 to 7, or one of the words "auto" -or "disengaged" (without the quotes). Not all ThinkPads support the -"auto" and "disengaged" levels. +Where is an integer from 0 to 7, or one of the words "auto" or +"full-speed" (without the quotes). Not all ThinkPads support the "auto" +and "full-speed" levels. The driver accepts "disengaged" as an alias for +"full-speed", and reports it as "disengaged" for backwards +compatibility. On the X31 and X40 (and ONLY on those models), the fan speed can be -controlled to a certain degree. Once the fan is running, it can be +controlled to a certain degree. Once the fan is running, it can be forced to run faster or slower with the following command: - echo 'speed ' > /proc/acpi/ibm/thermal + echo 'speed ' > /proc/acpi/ibm/fan -The sustainable range of fan speeds on the X40 appears to be from -about 3700 to about 7350. Values outside this range either do not have -any effect or the fan speed eventually settles somewhere in that -range. The fan cannot be stopped or started with this command. +The sustainable range of fan speeds on the X40 appears to be from about +3700 to about 7350. Values outside this range either do not have any +effect or the fan speed eventually settles somewhere in that range. The +fan cannot be stopped or started with this command. This functionality +is incomplete, and not available through the sysfs interface. -The ThinkPad's ACPI DSDT code will reprogram the fan on its own when -certain conditions are met. It will override any fan programming done -through thinkpad-acpi. +To program the safety watchdog, use the "watchdog" command. + + echo 'watchdog ' > /proc/acpi/ibm/fan + +If you want to disable the watchdog, use 0 as the interval. + +Sysfs notes: + +The sysfs interface follows the hwmon subsystem guidelines for the most +part, and the exception is the fan safety watchdog. + +hwmon device attribute pwm1_enable: + 0: PWM offline (fan is set to full-speed mode) + 1: Manual PWM control (use pwm1 to set fan level) + 2: Hardware PWM control (EC "auto" mode) + 3: reserved (Software PWM control, not implemented yet) + + Modes 0 and 2 are not supported by all ThinkPads, and the driver + is not always able to detect this. If it does know a mode is + unsupported, it will return -EINVAL. + +hwmon device attribute pwm1: + Fan level, scaled from the firmware values of 0-7 to the hwmon + scale of 0-255. 0 means fan stopped, 255 means highest normal + speed (level 7). + + This attribute only commands the fan if pmw1_enable is set to 1 + (manual PWM control). + +hwmon device attribute fan1_input: + Fan tachometer reading, in RPM. May go stale on certain + ThinkPads while the EC transitions the PWM to offline mode, + which can take up to two minutes. May return rubbish on older + ThinkPads. + +driver attribute fan_watchdog: + Fan safety watchdog timer interval, in seconds. Minimum is + 1 second, maximum is 120 seconds. 0 disables the watchdog. + +To stop the fan: set pwm1 to zero, and pwm1_enable to 1. + +To start the fan in a safe mode: set pwm1_enable to 2. If that fails +with ENOTSUP, set it to 1 and set pwm1 to at least 128 (255 would be the +safest choice, though). -The thinkpad-acpi kernel driver can be programmed to revert the fan -level to a safe setting if userspace does not issue one of the fan -commands: "enable", "disable", "level" or "watchdog" within a -configurable ammount of time. To do this, use the "watchdog" command. - - echo 'watchdog ' > /proc/acpi/ibm/fan - -Interval is the ammount of time in seconds to wait for one of the -above mentioned fan commands before reseting the fan level to a safe -one. If set to zero, the watchdog is disabled (default). When the -watchdog timer runs out, it does the exact equivalent of the "enable" -fan command. - -Note that the watchdog timer stops after it enables the fan. It will -be rearmed again automatically (using the same interval) when one of -the above mentioned fan commands is received. The fan watchdog is, -therefore, not suitable to protect against fan mode changes made -through means other than the "enable", "disable", and "level" fan -commands. EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan --------------------------------------- diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index d5526e8..a4d7ee4 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2695,6 +2695,7 @@ static enum fan_control_access_mode fan_control_access_mode; static enum fan_control_commands fan_control_commands; static u8 fan_control_initial_status; +static u8 fan_control_desired_level; static void fan_watchdog_fire(struct work_struct *ignored); static int fan_watchdog_maxinterval; @@ -2708,8 +2709,222 @@ IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ "JFNS", /* 770x-JL */ ); /* all others */ +/* + * SYSFS fan layout: hwmon compatible (device) + * + * pwm*_enable: + * 0: "disengaged" mode + * 1: manual mode + * 2: native EC "auto" mode (recommended, hardware default) + * + * pwm*: set speed in manual mode, ignored otherwise. + * 0 is level 0; 255 is level 7. Intermediate points done with linear + * interpolation. + * + * fan*_input: tachometer reading, RPM + * + * + * SYSFS fan layout: extensions + * + * fan_watchdog (driver): + * fan watchdog interval in seconds, 0 disables (default), max 120 + */ + +/* sysfs fan pwm1_enable ----------------------------------------------- */ +static ssize_t fan_pwm1_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, mode; + u8 status; + + res = fan_get_status_safe(&status); + if (res) + return res; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + /* Return most likely status. In fact, it + * might be the only possible status */ + status = TP_EC_FAN_AUTO; + } + } + + if (status & TP_EC_FAN_FULLSPEED) { + mode = 0; + } else if (status & TP_EC_FAN_AUTO) { + mode = 2; + } else + mode = 1; + + return snprintf(buf, PAGE_SIZE, "%d\n", mode); +} + +static ssize_t fan_pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, level; + + if (parse_strtoul(buf, 2, &t)) + return -EINVAL; + + switch (t) { + case 0: + level = TP_EC_FAN_FULLSPEED; + break; + case 1: + level = TPACPI_FAN_LAST_LEVEL; + break; + case 2: + level = TP_EC_FAN_AUTO; + break; + case 3: + /* reserved for software-controlled auto mode */ + return -ENOSYS; + default: + return -EINVAL; + } + + res = fan_set_level_safe(level); + if (res < 0) + return res; + + fan_watchdog_reset(); + + return count; +} + +static struct device_attribute dev_attr_fan_pwm1_enable = + __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + fan_pwm1_enable_show, fan_pwm1_enable_store); + +/* sysfs fan pwm1 ------------------------------------------------------ */ +static ssize_t fan_pwm1_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + u8 status; + + res = fan_get_status_safe(&status); + if (res) + return res; + + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + status = TP_EC_FAN_AUTO; + } + } + + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0) + status = fan_control_desired_level; + + if (status > 7) + status = 7; + + return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7); +} + +static ssize_t fan_pwm1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long s; + int rc; + u8 status, newlevel; + + if (parse_strtoul(buf, 255, &s)) + return -EINVAL; + + /* scale down from 0-255 to 0-7 */ + newlevel = (s >> 5) & 0x07; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + rc = fan_get_status(&status); + if (!rc && (status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + rc = fan_set_level(newlevel); + if (!rc) + fan_update_desired_level(newlevel); + fan_watchdog_reset(); + } + + mutex_unlock(&fan_mutex); + return (rc)? rc : count; +} + +static struct device_attribute dev_attr_fan_pwm1 = + __ATTR(pwm1, S_IWUSR | S_IRUGO, + fan_pwm1_show, fan_pwm1_store); + +/* sysfs fan fan1_input ------------------------------------------------ */ +static ssize_t fan_fan1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + unsigned int speed; + + res = fan_get_speed(&speed); + if (res < 0) + return res; + + return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} + +static struct device_attribute dev_attr_fan_fan1_input = + __ATTR(fan1_input, S_IRUGO, + fan_fan1_input_show, NULL); + +/* sysfs fan fan_watchdog (driver) ------------------------------------- */ +static ssize_t fan_fan_watchdog_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval); +} + +static ssize_t fan_fan_watchdog_store(struct device_driver *drv, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 120, &t)) + return -EINVAL; + + fan_watchdog_maxinterval = t; + fan_watchdog_reset(); + + return count; +} + +static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, + fan_fan_watchdog_show, fan_fan_watchdog_store); + +/* --------------------------------------------------------------------- */ +static struct attribute *fan_attributes[] = { + &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, + &dev_attr_fan_fan1_input.attr, + NULL +}; + +static const struct attribute_group fan_attr_group = { + .attrs = fan_attributes, +}; + static int __init fan_init(struct ibm_init_struct *iibm) { + int rc; + vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); mutex_init(&fan_mutex); @@ -2718,6 +2933,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_control_commands = 0; fan_watchdog_maxinterval = 0; tp_features.fan_ctrl_status_undef = 0; + fan_control_desired_level = 7; IBM_ACPIHANDLE_INIT(fans); IBM_ACPIHANDLE_INIT(gfan); @@ -2796,9 +3012,36 @@ static int __init fan_init(struct ibm_init_struct *iibm) 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; + /* update fan_control_desired_level */ + if (fan_status_access_mode != TPACPI_FAN_NONE) + fan_get_status_safe(NULL); + + if (fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE) { + rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &fan_attr_group); + if (!(rc < 0)) + rc = driver_create_file(&tpacpi_pdriver.driver, + &driver_attr_fan_watchdog); + if (rc < 0) + return rc; + return 0; + } else + return 1; +} + +/* + * Call with fan_mutex held + */ +static void fan_update_desired_level(u8 status) +{ + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + if (status > 7) + fan_control_desired_level = 7; + else + fan_control_desired_level = status; + } } static int fan_get_status(u8 *status) @@ -2837,9 +3080,33 @@ static int fan_get_status(u8 *status) return 0; } +static int fan_get_status_safe(u8 *status) +{ + int rc; + u8 s; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + rc = fan_get_status(&s); + if (!rc) + fan_update_desired_level(s); + mutex_unlock(&fan_mutex); + + if (status) + *status = s; + + return rc; +} + static void fan_exit(void) { vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n"); + + /* FIXME: can we really do this unconditionally? */ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &fan_attr_group); + driver_remove_file(&tpacpi_pdriver.driver, &driver_attr_fan_watchdog); + cancel_delayed_work(&fan_watchdog_task); flush_scheduled_work(); } @@ -2902,17 +3169,10 @@ static void fan_watchdog_reset(void) static int fan_set_level(int level) { - int res; - switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: if (level >= 0 && level <= 7) { - res = mutex_lock_interruptible(&fan_mutex); - if (res < 0) - return res; - res = acpi_evalf(sfan_handle, NULL, NULL, "vd", level); - mutex_unlock(&fan_mutex); - if (!res) + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) return -EIO; } else return -EINVAL; @@ -2925,12 +3185,7 @@ static int fan_set_level(int level) ((level < 0) || (level > 7))) return -EINVAL; - res = mutex_lock_interruptible(&fan_mutex); - if (res < 0) - return res; - res = acpi_ec_write(fan_status_offset, level); - mutex_unlock(&fan_mutex); - if (!res) + if (!acpi_ec_write(fan_status_offset, level)) return -EIO; else tp_features.fan_ctrl_status_undef = 0; @@ -2942,6 +3197,25 @@ static int fan_set_level(int level) return 0; } +static int fan_set_level_safe(int level) +{ + int rc; + + rc = mutex_lock_interruptible(&fan_mutex); + if (rc < 0) + return rc; + + if (level == TPACPI_FAN_LAST_LEVEL) + level = fan_control_desired_level; + + rc = fan_set_level(level); + if (!rc) + fan_update_desired_level(level); + + mutex_unlock(&fan_mutex); + return rc; +} + static int fan_set_enable(void) { u8 s; @@ -3009,19 +3283,24 @@ static int fan_set_disable(void) case TPACPI_FAN_WR_TPEC: if (!acpi_ec_write(fan_status_offset, 0x00)) rc = -EIO; - else + else { + fan_control_desired_level = 0; tp_features.fan_ctrl_status_undef = 0; + } break; case TPACPI_FAN_WR_ACPI_SFAN: if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) rc = -EIO; + else + fan_control_desired_level = 0; break; default: rc = -ENXIO; } + mutex_unlock(&fan_mutex); return rc; } @@ -3063,7 +3342,7 @@ static int fan_read(char *p) switch (fan_status_access_mode) { case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) + if ((rc = fan_get_status_safe(&status)) < 0) return rc; len += sprintf(p + len, "status:\t\t%s\n" @@ -3073,7 +3352,7 @@ static int fan_read(char *p) case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) + if ((rc = fan_get_status_safe(&status)) < 0) return rc; if (unlikely(tp_features.fan_ctrl_status_undef)) { @@ -3117,7 +3396,7 @@ static int fan_read(char *p) default: len += sprintf(p + len, " ( is 0-7, " - "auto, disengaged)\n"); + "auto, disengaged, full-speed)\n"); break; } } @@ -3140,12 +3419,13 @@ static int fan_write_cmd_level(const char *cmd, int *rc) if (strlencmp(cmd, "level auto") == 0) level = TP_EC_FAN_AUTO; - else if (strlencmp(cmd, "level disengaged") == 0) + else if ((strlencmp(cmd, "level disengaged") == 0) | + (strlencmp(cmd, "level full-speed") == 0)) level = TP_EC_FAN_FULLSPEED; else if (sscanf(cmd, "level %d", &level) != 1) return 0; - if ((*rc = fan_set_level(level)) == -ENXIO) + if ((*rc = fan_set_level_safe(level)) == -ENXIO) printk(IBM_ERR "level command accepted for unsupported " "access mode %d", fan_control_access_mode); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index e833ff3..2fe4d61 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -349,6 +349,8 @@ enum { /* Fan control constants */ TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ + + TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */ }; enum fan_status_access_mode { @@ -375,6 +377,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; static u8 fan_control_initial_status; +static u8 fan_control_desired_level; static int fan_watchdog_maxinterval; struct mutex fan_mutex; @@ -384,10 +387,13 @@ static acpi_handle fans_handle, gfan_handle, sfan_handle; static int fan_init(struct ibm_init_struct *iibm); static void fan_exit(void); static int fan_get_status(u8 *status); +static int fan_get_status_safe(u8 *status); static int fan_get_speed(unsigned int *speed); +static void fan_update_desired_level(u8 status); static void fan_watchdog_fire(struct work_struct *ignored); static void fan_watchdog_reset(void); static int fan_set_level(int level); +static int fan_set_level_safe(int level); static int fan_set_enable(void); static int fan_set_disable(void); static int fan_set_speed(int speed); -- cgit v1.1 From eaa7571b2d1a08873e4bdd8e6db3431df61cd9ad Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:18 -0300 Subject: ACPI: thinkpad-acpi: add a safety net for TPEC fan control mode The Linux ThinkPad community is not positive that all ThinkPads that do HFSP EC fan control do implement full-speed and auto modes, some of the earlier ones supporting HFSP might not. If the EC ignores the AUTO or FULL-SPEED bits, it will pay attention to the lower three bits that set the fan level. And as thinkpad-acpi was leaving these set to zero, it would stop(!) the fan, which is Not A Good Thing. So, as a safety net, we now make sure to also set the fan level part of the HFSP register to speed 7 for full-speed, and a minimum of speed 4 for auto mode. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index a4d7ee4..79abc68 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -3185,6 +3185,13 @@ static int fan_set_level(int level) ((level < 0) || (level > 7))) return -EINVAL; + /* safety net should the EC not support AUTO + * or FULLSPEED mode bits and just ignore them */ + if (level & TP_EC_FAN_FULLSPEED) + level |= 7; /* safety min speed 7 */ + else if (level & TP_EC_FAN_FULLSPEED) + level |= 4; /* safety min speed 4 */ + if (!acpi_ec_write(fan_status_offset, level)) return -EIO; else @@ -3233,8 +3240,10 @@ static int fan_set_enable(void) break; /* Don't go out of emergency fan mode */ - if (s != 7) - s = TP_EC_FAN_AUTO; + if (s != 7) { + s &= 0x07; + s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */ + } if (!acpi_ec_write(fan_status_offset, s)) rc = -EIO; @@ -3252,8 +3261,7 @@ static int fan_set_enable(void) s &= 0x07; /* Set fan to at least level 4 */ - if (s < 4) - s = 4; + s |= 4; if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) rc= -EIO; -- cgit v1.1 From b616004c70dd7f60a1477c3e9d6fddd00ee1fa37 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:19 -0300 Subject: ACPI: thinkpad-acpi: add sysfs support to the cmos command subdriver Add sysfs attributes to send ThinkPad CMOS commands. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 23 +++++++++++------------ drivers/misc/thinkpad_acpi.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 339ce21..352e8ae 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -378,23 +378,19 @@ supported. Use "eject2" instead of "eject" for the second bay. Note: the UltraBay eject support on the 600e/x, A22p and A3x is EXPERIMENTAL and may not work as expected. USE WITH CAUTION! -CMOS control -- /proc/acpi/ibm/cmos ------------------------------------ +CMOS control +------------ + +procfs: /proc/acpi/ibm/cmos +sysfs device attribute: cmos_command This feature is used internally by the ACPI firmware to control the ThinkLight on most newer ThinkPad models. It may also control LCD brightness, sounds volume and more, but only on some models. -The commands are non-negative integer numbers: - - echo 0 >/proc/acpi/ibm/cmos - echo 1 >/proc/acpi/ibm/cmos - echo 2 >/proc/acpi/ibm/cmos - ... - -The range of valid numbers is 0 to 21, but not all have an effect and -the behavior varies from model to model. Here is the behavior on the -X40 (tpb is the ThinkPad Buttons utility): +The range of valid cmos command numbers is 0 to 21, but not all have an +effect and the behavior varies from model to model. Here is the behavior +on the X40 (tpb is the ThinkPad Buttons utility): 0 - no effect but tpb reports "Volume down" 1 - no effect but tpb reports "Volume up" @@ -407,6 +403,9 @@ X40 (tpb is the ThinkPad Buttons utility): 13 - ThinkLight off 14 - no effect but tpb reports ThinkLight status change +The cmos command interface is prone to firmware split-brain problems, as +in newer ThinkPads it is just a compatibility layer. + LED control -- /proc/acpi/ibm/led --------------------------------- diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 79abc68..ba749df 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1743,8 +1743,30 @@ static struct ibm_struct bay_driver_data = { * CMOS subdriver */ +/* sysfs cmos_command -------------------------------------------------- */ +static ssize_t cmos_command_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long cmos_cmd; + int res; + + if (parse_strtoul(buf, 21, &cmos_cmd)) + return -EINVAL; + + res = issue_thinkpad_cmos_command(cmos_cmd); + return (res)? res : count; +} + +static struct device_attribute dev_attr_cmos_command = + __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store); + +/* --------------------------------------------------------------------- */ + static int __init cmos_init(struct ibm_init_struct *iibm) { + int res; + vdbg_printk(TPACPI_DBG_INIT, "initializing cmos commands subdriver\n"); @@ -1752,9 +1774,19 @@ static int __init cmos_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n", str_supported(cmos_handle != NULL)); + + res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command); + if (res) + return res; + return (cmos_handle)? 0 : 1; } +static void cmos_exit(void) +{ + device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command); +} + static int cmos_read(char *p) { int len = 0; @@ -1795,6 +1827,7 @@ static struct ibm_struct cmos_driver_data = { .name = "cmos", .read = cmos_read, .write = cmos_write, + .exit = cmos_exit, }; /************************************************************************* -- cgit v1.1 From 7d5a015eece8be9186d3613d595643a520555e33 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 24 Apr 2007 11:48:20 -0300 Subject: ACPI: thinkpad-acpi: update brightness sysfs interface support Update the brightness sysfs interface (done through the backlight class) to be in line with the rest of the thinkpad-acpi driver. This renames the incorrect, un-obvious, and clash-prone name of "ibm" for the backlight device to a much more fitting and descriptive "thinkpad_screen". This is something I wanted to do for quite a while... Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 52 ++++++++++++++++++++++++++++++++++++----- drivers/misc/thinkpad_acpi.c | 5 ++-- drivers/misc/thinkpad_acpi.h | 2 ++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 352e8ae..eab4997 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -611,19 +611,59 @@ registers contain the current battery capacity, etc. If you experiment with this, do send me your results (including some complete dumps with a description of the conditions when they were taken.) -LCD brightness control -- /proc/acpi/ibm/brightness ---------------------------------------------------- +LCD brightness control +---------------------- + +procfs: /proc/acpi/ibm/brightness +sysfs backlight device "thinkpad_screen" This feature allows software control of the LCD brightness on ThinkPad -models which don't have a hardware brightness slider. The available -commands are: +models which don't have a hardware brightness slider. + +It has some limitations: the LCD backlight cannot be actually turned on or off +by this interface, and in many ThinkPad models, the "dim while on battery" +functionality will be enabled by the BIOS when this interface is used, and +cannot be controlled. + +The backlight control has eight levels, ranging from 0 to 7. Some of the +levels may not be distinct. + +Procfs notes: + + The available commands are: echo up >/proc/acpi/ibm/brightness echo down >/proc/acpi/ibm/brightness echo 'level ' >/proc/acpi/ibm/brightness -The number range is 0 to 7, although not all of them may be -distinct. The current brightness level is shown in the file. +Sysfs notes: + +The interface is implemented through the backlight sysfs class, which is poorly +documented at this time. + +Locate the thinkpad_screen device under /sys/class/backlight, and inside it +there will be the following attributes: + + max_brightness: + Reads the maximum brightness the hardware can be set to. + The minimum is always zero. + + actual_brightness: + Reads what brightness the screen is set to at this instant. + + brightness: + Writes request the driver to change brightness to the given + value. Reads will tell you what brightness the driver is trying + to set the display to when "power" is set to zero and the display + has not been dimmed by a kernel power management event. + + power: + power management mode, where 0 is "display on", and 1 to 3 will + dim the display backlight to brightness level 0 because + thinkpad-acpi cannot really turn the backlight off. Kernel + power management events can temporarily increase the current + power management level, i.e. they can dim the display. + Volume control -- /proc/acpi/ibm/volume --------------------------------------- diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index ba749df..c0a023c 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2414,8 +2414,9 @@ static int __init brightness_init(struct ibm_init_struct *iibm) if (b < 0) return b; - ibm_backlight_device = backlight_device_register("ibm", NULL, NULL, - &ibm_backlight_data); + ibm_backlight_device = backlight_device_register( + TPACPI_BACKLIGHT_DEV_NAME, 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); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 2fe4d61..8348fc6 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -296,6 +296,8 @@ static int bluetooth_write(char *buf); * Brightness (backlight) subdriver */ +#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen" + static struct backlight_device *ibm_backlight_device; static int brightness_offset = 0x31; -- cgit v1.1 From ecf2a80a97b3d38ae008fa8a3cb98cd540ac1eae Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:09 -0300 Subject: ACPI: thinkpad-acpi: add a fan-control feature master toggle Len Brown considers that an active by default fan control interface in laptops may be too close to giving users enough rope. There is a good chance he is quite correct on this, especially if someone decides to use that interface in applets and users are not aware of its risks. This patch adds a master switch to thinkpad-acpi that enables or disables the entire fan-control feature as a module parameter: "fan_control". It defaults to disabled. Set it to non-zero to enable fan control. Also, the patch removes the expermiental status from fan control, since it is stable enough to not be called experimental, and the master switch makes it safe enough to do so. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 15 +++++++-------- drivers/misc/thinkpad_acpi.c | 30 +++++++++++++++++++++++++++++- drivers/misc/thinkpad_acpi.h | 2 ++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index eab4997..bca50d7 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -38,7 +38,7 @@ detailed description): - Experimental: embedded controller register dump - LCD brightness control - Volume control - - Experimental: fan speed, fan enable/disable + - Fan control and monitoring: fan speed, fan enable/disable - Experimental: WAN enable and disable A compatibility table by model and feature is maintained on the web @@ -681,21 +681,20 @@ distinct. The unmute the volume after the mute command, use either the up or down command (the level command will not unmute the volume). The current volume level and mute state is shown in the file. -EXPERIMENTAL: fan speed, fan enable/disable -------------------------------------------- +Fan control and monitoring: fan speed, fan enable/disable +--------------------------------------------------------- procfs: /proc/acpi/ibm/fan sysfs device attributes: (hwmon) fan_input, pwm1, pwm1_enable -This feature is marked EXPERIMENTAL because the implementation -directly accesses hardware registers and may not work as expected. USE -WITH CAUTION! To use this feature, you need to supply the -experimental=1 parameter when loading the module. +NOTE NOTE NOTE: fan control operations are disabled by default for +safety reasons. To enable them, the module parameter "fan_control=1" +must be given to thinkpad-acpi. This feature attempts to show the current fan speed, control mode and other fan data that might be available. The speed is read directly from the hardware registers of the embedded controller. This is known -to work on later R, T and X series ThinkPads but may show a bogus +to work on later R, T, X and Z series ThinkPads but may show a bogus value on other models. Fan levels: diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index c0a023c..7dc3a22 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2935,6 +2935,9 @@ static ssize_t fan_fan_watchdog_store(struct device_driver *drv, if (parse_strtoul(buf, 120, &t)) return -EINVAL; + if (!fan_control_allowed) + return -EPERM; + fan_watchdog_maxinterval = t; fan_watchdog_reset(); @@ -3046,6 +3049,14 @@ static int __init fan_init(struct ibm_init_struct *iibm) fan_control_access_mode != TPACPI_FAN_WR_NONE), fan_status_access_mode, fan_control_access_mode); + /* fan control master switch */ + if (!fan_control_allowed) { + fan_control_access_mode = TPACPI_FAN_WR_NONE; + fan_control_commands = 0; + dbg_printk(TPACPI_DBG_INIT, + "fan control features disabled by parameter\n"); + } + /* update fan_control_desired_level */ if (fan_status_access_mode != TPACPI_FAN_NONE) fan_get_status_safe(NULL); @@ -3203,6 +3214,9 @@ static void fan_watchdog_reset(void) static int fan_set_level(int level) { + if (!fan_control_allowed) + return -EPERM; + switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: if (level >= 0 && level <= 7) { @@ -3242,6 +3256,9 @@ static int fan_set_level_safe(int level) { int rc; + if (!fan_control_allowed) + return -EPERM; + rc = mutex_lock_interruptible(&fan_mutex); if (rc < 0) return rc; @@ -3262,6 +3279,9 @@ static int fan_set_enable(void) u8 s; int rc; + if (!fan_control_allowed) + return -EPERM; + rc = mutex_lock_interruptible(&fan_mutex); if (rc < 0) return rc; @@ -3315,6 +3335,9 @@ static int fan_set_disable(void) { int rc; + if (!fan_control_allowed) + return -EPERM; + rc = mutex_lock_interruptible(&fan_mutex); if (rc < 0) return rc; @@ -3351,6 +3374,9 @@ static int fan_set_speed(int speed) { int rc; + if (!fan_control_allowed) + return -EPERM; + rc = mutex_lock_interruptible(&fan_mutex); if (rc < 0) return rc; @@ -3558,7 +3584,6 @@ static struct ibm_struct fan_driver_data = { .read = fan_read, .write = fan_write, .exit = fan_exit, - .flags.experimental = 1, }; /**************************************************************************** @@ -3879,6 +3904,9 @@ module_param_named(debug, dbg_level, uint, 0); static int force_load; module_param(force_load, int, 0); +static int fan_control_allowed; +module_param_named(fan_control, fan_control_allowed, int, 0); + #define IBM_PARAM(feature) \ module_param_call(feature, set_ibm_param, NULL, NULL, 0) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 8348fc6..a9e7093 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -375,6 +375,8 @@ enum fan_control_commands { * and also watchdog cmd */ }; +static int fan_control_allowed; + 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; -- cgit v1.1 From 4985cd0a63b0713b6469ef01aae6a0e63ea72f83 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:10 -0300 Subject: ACPI: thinkpad-acpi: do not arm fan watchdog if it would not work Do not enable/rearm the fan control safety watchdog if we would not be able to do anything to the fan anyway. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 7dc3a22..f824259 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -3197,6 +3197,9 @@ static void fan_watchdog_reset(void) { static int fan_watchdog_active = 0; + if (fan_control_access_mode == TPACPI_FAN_WR_NONE) + return; + if (fan_watchdog_active) cancel_delayed_work(&fan_watchdog_task); -- cgit v1.1 From ca4ac2f48a4502bbbfcb47b86312273c28194f53 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:11 -0300 Subject: ACPI: thinkpad-acpi: fix a fan watchdog invocation The fan control watchdog was being called in one place even when the fan control operation had failed. Fix it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index f824259..b85f096 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2888,9 +2888,10 @@ static ssize_t fan_pwm1_store(struct device *dev, if (!rc && (status & (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { rc = fan_set_level(newlevel); - if (!rc) + if (!rc) { fan_update_desired_level(newlevel); - fan_watchdog_reset(); + fan_watchdog_reset(); + } } mutex_unlock(&fan_mutex); -- cgit v1.1 From c573ddb998456a89a5ccb83a922d2c8ba18484a6 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:12 -0300 Subject: ACPI: thinkpad-acpi: map ENXIO to EINVAL for fan sysfs Currently, all fan control operations return ENXIO if unsupported operations are requested, but return EINVAL if invalid fan modes are requested on a given ThinkPad. This is not strictly correct for sysfs, so map ENXIO to EINVAL in the sysfs attribute store handlers, as we do benefit from the ENXIO in other parts of the driver code. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index b85f096..7aed118 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -2824,7 +2824,9 @@ static ssize_t fan_pwm1_enable_store(struct device *dev, } res = fan_set_level_safe(level); - if (res < 0) + if (res == -ENXIO) + return -EINVAL; + else if (res < 0) return res; fan_watchdog_reset(); @@ -2888,7 +2890,9 @@ static ssize_t fan_pwm1_store(struct device *dev, if (!rc && (status & (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { rc = fan_set_level(newlevel); - if (!rc) { + if (rc == -ENXIO) + rc = -EINVAL; + else if (!rc) { fan_update_desired_level(newlevel); fan_watchdog_reset(); } -- cgit v1.1 From b39fe582eb9252dca9a62f7135bcad2e486083e5 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:13 -0300 Subject: ACPI: thinkpad-acpi: improve fan control documentation Improve fan control documentation and fix one mistake. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index bca50d7..e3ad7a4 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -795,15 +795,23 @@ Sysfs notes: The sysfs interface follows the hwmon subsystem guidelines for the most part, and the exception is the fan safety watchdog. +Writes to any of the sysfs attributes may return the EINVAL error if +that operation is not supported in a given ThinkPad or if the parameter +is out-of-bounds, and EPERM if it is forbidden. They may also return +EINTR (interrupted system call), and EIO (I/O error while trying to talk +to the firmware). + +Features not yet implemented by the driver return ENOSYS. + hwmon device attribute pwm1_enable: 0: PWM offline (fan is set to full-speed mode) 1: Manual PWM control (use pwm1 to set fan level) 2: Hardware PWM control (EC "auto" mode) 3: reserved (Software PWM control, not implemented yet) - Modes 0 and 2 are not supported by all ThinkPads, and the driver - is not always able to detect this. If it does know a mode is - unsupported, it will return -EINVAL. + Modes 0 and 2 are not supported by all ThinkPads, and the + driver is not always able to detect this. If it does know a + mode is unsupported, it will return -EINVAL. hwmon device attribute pwm1: Fan level, scaled from the firmware values of 0-7 to the hwmon @@ -826,8 +834,8 @@ driver attribute fan_watchdog: To stop the fan: set pwm1 to zero, and pwm1_enable to 1. To start the fan in a safe mode: set pwm1_enable to 2. If that fails -with ENOTSUP, set it to 1 and set pwm1 to at least 128 (255 would be the -safest choice, though). +with EINVAL, try to set pwm1_enable to 1 and pwm1 to at least 128 (255 +would be the safest choice, though). EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan -- cgit v1.1 From 5ae930e685018e2dc6d4139362213e4b283e5700 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:14 -0300 Subject: ACPI: thinkpad-acpi: improve debugging for acpi helpers Some issues with the dock subdriver proved that a slightly improved debugging setup for ACPI notifiers and handler helpers would be useful. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 7aed118..68f1cc0 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -296,14 +296,22 @@ static void drv_acpi_handle_init(char *name, int i; acpi_status status; + vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n", + name); + for (i = 0; i < num_paths; i++) { status = acpi_get_handle(parent, paths[i], handle); if (ACPI_SUCCESS(status)) { *path = paths[i]; + dbg_printk(TPACPI_DBG_INIT, + "Found ACPI handle %s for %s\n", + *path, name); return; } } + vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n", + name); *handle = NULL; } @@ -320,19 +328,20 @@ static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) static int __init setup_acpi_notify(struct ibm_struct *ibm) { acpi_status status; - int ret; + int rc; BUG_ON(!ibm->acpi); if (!*ibm->acpi->handle) return 0; - dbg_printk(TPACPI_DBG_INIT, + vdbg_printk(TPACPI_DBG_INIT, "setting up ACPI notify for %s\n", ibm->name); - ret = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); - if (ret < 0) { - printk(IBM_ERR "%s device not present\n", ibm->name); + rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); + if (rc < 0) { + printk(IBM_ERR "acpi_bus_get_device(%s) failed: %d\n", + ibm->name, rc); return -ENODEV; } @@ -364,7 +373,7 @@ static int __init tpacpi_device_add(struct acpi_device *device) static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) { - int ret; + int rc; dbg_printk(TPACPI_DBG_INIT, "registering %s as an ACPI driver\n", ibm->name); @@ -381,16 +390,16 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) ibm->acpi->driver->ids = ibm->acpi->hid; ibm->acpi->driver->ops.add = &tpacpi_device_add; - ret = acpi_bus_register_driver(ibm->acpi->driver); - if (ret < 0) { + rc = acpi_bus_register_driver(ibm->acpi->driver); + if (rc < 0) { printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", - ibm->acpi->hid, ret); + ibm->acpi->hid, rc); kfree(ibm->acpi->driver); ibm->acpi->driver = NULL; - } else if (!ret) + } else if (!rc) ibm->flags.acpi_driver_registered = 1; - return ret; + return rc; } -- cgit v1.1 From d94a7f16cad7700f8d2b142cc13cfba5387af3db Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:15 -0300 Subject: ACPI: thinkpad-acpi: improve dock subdriver initialization The dock sub-driver has split-personality (two subdrivers), and it was doing some unoptimal things on init because of that. Fix it so that the second half of it will only init when necessary, and only if the first half initialized sucessfully in the first place. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.c | 78 ++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 68f1cc0..a565265 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1519,6 +1519,33 @@ IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ /* don't list other alternatives as we install a notify handler on the 570 */ IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ +static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = { + { + .notify = dock_notify, + .handle = &dock_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, + { + .hid = IBM_PCI_HID, + .notify = dock_notify, + .handle = &pci_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, +}; + +static struct ibm_struct dock_driver_data[2] = { + { + .name = "dock", + .read = dock_read, + .write = dock_write, + .acpi = &ibm_dock_acpidriver[0], + }, + { + .name = "dock", + .acpi = &ibm_dock_acpidriver[1], + }, +}; + #define dock_docked() (_sta(dock_handle) & 1) static int __init dock_init(struct ibm_init_struct *iibm) @@ -1526,7 +1553,6 @@ static int __init dock_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n"); IBM_ACPIHANDLE_INIT(dock); - IBM_ACPIHANDLE_INIT(pci); vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n", str_supported(dock_handle != NULL)); @@ -1534,6 +1560,28 @@ static int __init dock_init(struct ibm_init_struct *iibm) return (dock_handle)? 0 : 1; } +static int __init dock_init2(struct ibm_init_struct *iibm) +{ + int dock2_needed; + + vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n"); + + if (dock_driver_data[0].flags.acpi_driver_registered && + dock_driver_data[0].flags.acpi_notify_installed) { + IBM_ACPIHANDLE_INIT(pci); + dock2_needed = (pci_handle != NULL); + vdbg_printk(TPACPI_DBG_INIT, + "dock PCI handler for the TP 570 is %s\n", + str_supported(dock2_needed)); + } else { + vdbg_printk(TPACPI_DBG_INIT, + "dock subdriver part 2 not required\n"); + dock2_needed = 0; + } + + return (dock2_needed)? 0 : 1; +} + static void dock_notify(struct ibm_struct *ibm, u32 event) { int docked = dock_docked(); @@ -1595,33 +1643,6 @@ static int dock_write(char *buf) return 0; } -static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = { - { - .notify = dock_notify, - .handle = &dock_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, - { - .hid = IBM_PCI_HID, - .notify = dock_notify, - .handle = &pci_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -}; - -static struct ibm_struct dock_driver_data[2] = { - { - .name = "dock", - .read = dock_read, - .write = dock_write, - .acpi = &ibm_dock_acpidriver[0], - }, - { - .name = "dock", - .acpi = &ibm_dock_acpidriver[1], - }, -}; - #endif /* CONFIG_THINKPAD_ACPI_DOCK */ /************************************************************************* @@ -3850,6 +3871,7 @@ static struct ibm_init_struct ibms_init[] __initdata = { .data = &dock_driver_data[0], }, { + .init = dock_init2, .data = &dock_driver_data[1], }, #endif -- cgit v1.1 From a0416420e2c6244792d6f308183ad57c40532078 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:16 -0300 Subject: ACPI: thinkpad-acpi: add sysfs support to hotkey subdriver Add the hotkey sysfs support. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 58 ++++++++++++++---- drivers/misc/thinkpad_acpi.c | 127 ++++++++++++++++++++++++++++++++++++++++ drivers/misc/thinkpad_acpi.h | 2 + 3 files changed, 176 insertions(+), 11 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index e3ad7a4..ebeed58 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -134,8 +134,11 @@ end of this document. Changes to the sysfs interface done by the kernel subsystems are not documented here, nor are they tracked by this attribute. -Hot keys -- /proc/acpi/ibm/hotkey ---------------------------------- +Hot keys +-------- + +procfs: /proc/acpi/ibm/hotkey +sysfs device attribute: hotkey/* Without this driver, only the Fn-F4 key (sleep button) generates an ACPI event. With the driver loaded, the hotkey feature enabled and the @@ -149,15 +152,6 @@ All labeled Fn-Fx key combinations generate distinct events. In addition, the lid microswitch and some docking station buttons may also generate such events. -The following commands can be written to this file: - - echo enable > /proc/acpi/ibm/hotkey -- enable the hot keys feature - echo disable > /proc/acpi/ibm/hotkey -- disable the hot keys feature - echo 0xffff > /proc/acpi/ibm/hotkey -- enable all possible hot keys - echo 0x0000 > /proc/acpi/ibm/hotkey -- disable all possible hot keys - ... any other 4-hex-digit mask ... - echo reset > /proc/acpi/ibm/hotkey -- restore the original mask - The bit mask allows some control over which hot keys generate ACPI events. Not all bits in the mask can be modified. Not all bits that can be modified do anything. Not all hot keys can be individually @@ -189,6 +183,48 @@ buttons do not generate ACPI events even with this driver. They *can* be used through the "ThinkPad Buttons" utility, see http://www.nongnu.org/tpb/ +procfs notes: + +The following commands can be written to the /proc/acpi/ibm/hotkey file: + + echo enable > /proc/acpi/ibm/hotkey -- enable the hot keys feature + echo disable > /proc/acpi/ibm/hotkey -- disable the hot keys feature + echo 0xffff > /proc/acpi/ibm/hotkey -- enable all possible hot keys + echo 0x0000 > /proc/acpi/ibm/hotkey -- disable all possible hot keys + ... any other 4-hex-digit mask ... + echo reset > /proc/acpi/ibm/hotkey -- restore the original mask + +sysfs notes: + + The hot keys attributes are in a hotkey/ subdirectory off the + thinkpad device. + + bios_enabled: + Returns the status of the hot keys feature when + thinkpad-acpi was loaded. Upon module unload, the hot + key feature status will be restored to this value. + + 0: hot keys were disabled + 1: hot keys were enabled + + bios_mask: + Returns the hot keys mask when thinkpad-acpi was loaded. + Upon module unload, the hot keys mask will be restored + to this value. + + enable: + Enables/disables the hot keys feature, and reports + current status of the hot keys feature. + + 0: disables the hot keys feature / feature disabled + 1: enables the hot keys feature / feature enabled + + mask: + bit mask to enable ACPI event generation for each hot + key (see above). Returns the current status of the hot + keys mask, and allows one to modify it. + + Bluetooth -- /proc/acpi/ibm/bluetooth ------------------------------------- diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index a565265..83a8d98 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -706,6 +706,108 @@ static struct ibm_struct thinkpad_acpi_driver_data = { static int hotkey_orig_status; static int hotkey_orig_mask; +static struct attribute_set *hotkey_dev_attributes = NULL; + +/* sysfs hotkey enable ------------------------------------------------- */ +static ssize_t hotkey_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, status, mask; + + res = hotkey_get(&status, &mask); + if (res) + return res; + + return snprintf(buf, PAGE_SIZE, "%d\n", status); +} + +static ssize_t hotkey_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, status, mask; + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + res = hotkey_get(&status, &mask); + if (!res) + res = hotkey_set(t, mask); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_hotkey_enable = + __ATTR(enable, S_IWUSR | S_IRUGO, + hotkey_enable_show, hotkey_enable_store); + +/* sysfs hotkey mask --------------------------------------------------- */ +static ssize_t hotkey_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, status, mask; + + res = hotkey_get(&status, &mask); + if (res) + return res; + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", mask); +} + +static ssize_t hotkey_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res, status, mask; + + if (parse_strtoul(buf, 0xffff, &t)) + return -EINVAL; + + res = hotkey_get(&status, &mask); + if (!res) + hotkey_set(status, t); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_hotkey_mask = + __ATTR(mask, S_IWUSR | S_IRUGO, + hotkey_mask_show, hotkey_mask_store); + +/* sysfs hotkey bios_enabled ------------------------------------------- */ +static ssize_t hotkey_bios_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_orig_status); +} + +static struct device_attribute dev_attr_hotkey_bios_enabled = + __ATTR(bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL); + +/* sysfs hotkey bios_mask ---------------------------------------------- */ +static ssize_t hotkey_bios_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%04x\n", hotkey_orig_mask); +} + +static struct device_attribute dev_attr_hotkey_bios_mask = + __ATTR(bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL); + +/* --------------------------------------------------------------------- */ + +static struct attribute *hotkey_mask_attributes[] = { + &dev_attr_hotkey_mask.attr, + &dev_attr_hotkey_bios_enabled.attr, + &dev_attr_hotkey_bios_mask.attr, +}; + static int __init hotkey_init(struct ibm_init_struct *iibm) { int res; @@ -722,6 +824,15 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) str_supported(tp_features.hotkey)); if (tp_features.hotkey) { + hotkey_dev_attributes = create_attr_set(4, + TPACPI_HOTKEY_SYSFS_GROUP); + if (!hotkey_dev_attributes) + return -ENOMEM; + res = add_to_attr_set(hotkey_dev_attributes, + &dev_attr_hotkey_enable.attr); + if (res) + return res; + /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, A30, R30, R31, T20-22, X20-21, X22-24 */ tp_features.hotkey_mask = @@ -731,6 +842,16 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) str_supported(tp_features.hotkey_mask)); res = hotkey_get(&hotkey_orig_status, &hotkey_orig_mask); + if (!res && tp_features.hotkey_mask) { + res = add_many_to_attr_set(hotkey_dev_attributes, + hotkey_mask_attributes, + ARRAY_SIZE(hotkey_mask_attributes)); + } + if (!res) + res = register_attr_set_with_sysfs( + hotkey_dev_attributes, + &tpacpi_pdev->dev.kobj); + if (res) return res; } @@ -748,6 +869,11 @@ static void hotkey_exit(void) if (res) printk(IBM_ERR "failed to restore hotkey to BIOS defaults\n"); } + + if (hotkey_dev_attributes) { + delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj); + hotkey_dev_attributes = NULL; + } } static void hotkey_notify(struct ibm_struct *ibm, u32 event) @@ -798,6 +924,7 @@ static int hotkey_set(int status, int mask) return 0; } +/* procfs -------------------------------------------------------------- */ static int hotkey_read(char *p) { int res, status, mask; diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index a9e7093..7615adb 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -414,6 +414,8 @@ static int fan_write_cmd_watchdog(const char *cmd, int *rc); * Hotkey subdriver */ +#define TPACPI_HOTKEY_SYSFS_GROUP "hotkey" + static int hotkey_orig_status; static int hotkey_orig_mask; -- cgit v1.1 From d3a6ade4f84416d774c3e5db5faae1840d55bd97 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 27 Apr 2007 22:00:17 -0300 Subject: ACPI: thinkpad-acpi: add sysfs support to wan and bluetooth subdrivers Add support to sysfs to the wan and bluetooth subdrivers. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- Documentation/thinkpad-acpi.txt | 61 ++++++++++++++--- drivers/misc/thinkpad_acpi.c | 144 ++++++++++++++++++++++++++++++++++++---- drivers/misc/thinkpad_acpi.h | 4 ++ 3 files changed, 186 insertions(+), 23 deletions(-) diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index ebeed58..2d48033 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -225,15 +225,35 @@ sysfs notes: keys mask, and allows one to modify it. -Bluetooth -- /proc/acpi/ibm/bluetooth -------------------------------------- +Bluetooth +--------- -This feature shows the presence and current state of a Bluetooth -device. If Bluetooth is installed, the following commands can be used: +procfs: /proc/acpi/ibm/bluetooth +sysfs device attribute: bluetooth/enable + +This feature shows the presence and current state of a ThinkPad +Bluetooth device in the internal ThinkPad CDC slot. + +Procfs notes: + +If Bluetooth is installed, the following commands can be used: echo enable > /proc/acpi/ibm/bluetooth echo disable > /proc/acpi/ibm/bluetooth +Sysfs notes: + + If the Bluetooth CDC card is installed, it can be enabled / + disabled through the "bluetooth/enable" thinkpad-acpi device + attribute, and its current status can also be queried. + + enable: + 0: disables Bluetooth / Bluetooth is disabled + 1: enables Bluetooth / Bluetooth is enabled. + + Note: this interface will be probably be superseeded by the + generic rfkill class. + Video output control -- /proc/acpi/ibm/video -------------------------------------------- @@ -874,23 +894,42 @@ with EINVAL, try to set pwm1_enable to 1 and pwm1 to at least 128 (255 would be the safest choice, though). -EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan ---------------------------------------- +EXPERIMENTAL: WAN +----------------- + +procfs: /proc/acpi/ibm/wan +sysfs device attribute: wwan/enable This feature is marked EXPERIMENTAL because the implementation directly accesses hardware registers and may not work as expected. USE WITH CAUTION! To use this feature, you need to supply the experimental=1 parameter when loading the module. -This feature shows the presence and current state of a WAN (Sierra -Wireless EV-DO) device. If WAN is installed, the following commands can -be used: +This feature shows the presence and current state of a W-WAN (Sierra +Wireless EV-DO) device. + +It was tested on a Lenovo Thinkpad X60. It should probably work on other +Thinkpad models which come with this module installed. + +Procfs notes: + +If the W-WAN card is installed, the following commands can be used: echo enable > /proc/acpi/ibm/wan echo disable > /proc/acpi/ibm/wan -It was tested on a Lenovo Thinkpad X60. It should probably work on other -Thinkpad models which come with this module installed. +Sysfs notes: + + If the W-WAN card is installed, it can be enabled / + disabled through the "wwan/enable" thinkpad-acpi device + attribute, and its current status can also be queried. + + enable: + 0: disables WWAN card / WWAN card is disabled + 1: enables WWAN card / WWAN card is enabled. + + Note: this interface will be probably be superseeded by the + generic rfkill class. Multiple Commands, Module Parameters ------------------------------------ diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 83a8d98..6c36a55 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -1020,8 +1020,54 @@ static struct ibm_struct hotkey_driver_data = { * Bluetooth subdriver */ +/* sysfs bluetooth enable ---------------------------------------------- */ +static ssize_t bluetooth_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int status; + + status = bluetooth_get_radiosw(); + if (status < 0) + return status; + + return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); +} + +static ssize_t bluetooth_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + res = bluetooth_set_radiosw(t); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_bluetooth_enable = + __ATTR(enable, S_IWUSR | S_IRUGO, + bluetooth_enable_show, bluetooth_enable_store); + +/* --------------------------------------------------------------------- */ + +static struct attribute *bluetooth_attributes[] = { + &dev_attr_bluetooth_enable.attr, + NULL +}; + +static const struct attribute_group bluetooth_attr_group = { + .name = TPACPI_BLUETH_SYSFS_GROUP, + .attrs = bluetooth_attributes, +}; + static int __init bluetooth_init(struct ibm_init_struct *iibm) { + int res; int status = 0; vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n"); @@ -1037,17 +1083,29 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) str_supported(tp_features.bluetooth), status); - if (tp_features.bluetooth && - !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) { - /* no bluetooth hardware present in system */ - tp_features.bluetooth = 0; - dbg_printk(TPACPI_DBG_INIT, - "bluetooth hardware not installed\n"); + if (tp_features.bluetooth) { + if (!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) { + /* no bluetooth hardware present in system */ + tp_features.bluetooth = 0; + dbg_printk(TPACPI_DBG_INIT, + "bluetooth hardware not installed\n"); + } else { + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &bluetooth_attr_group); + if (res) + return res; + } } return (tp_features.bluetooth)? 0 : 1; } +static void bluetooth_exit(void) +{ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &bluetooth_attr_group); +} + static int bluetooth_get_radiosw(void) { int status; @@ -1080,6 +1138,7 @@ static int bluetooth_set_radiosw(int radio_on) return 0; } +/* procfs -------------------------------------------------------------- */ static int bluetooth_read(char *p) { int len = 0; @@ -1119,14 +1178,61 @@ static struct ibm_struct bluetooth_driver_data = { .name = "bluetooth", .read = bluetooth_read, .write = bluetooth_write, + .exit = bluetooth_exit, }; /************************************************************************* * Wan subdriver */ +/* sysfs wan enable ---------------------------------------------------- */ +static ssize_t wan_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int status; + + status = wan_get_radiosw(); + if (status < 0) + return status; + + return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); +} + +static ssize_t wan_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + int res; + + if (parse_strtoul(buf, 1, &t)) + return -EINVAL; + + res = wan_set_radiosw(t); + + return (res) ? res : count; +} + +static struct device_attribute dev_attr_wan_enable = + __ATTR(enable, S_IWUSR | S_IRUGO, + wan_enable_show, wan_enable_store); + +/* --------------------------------------------------------------------- */ + +static struct attribute *wan_attributes[] = { + &dev_attr_wan_enable.attr, + NULL +}; + +static const struct attribute_group wan_attr_group = { + .name = TPACPI_WAN_SYSFS_GROUP, + .attrs = wan_attributes, +}; + static int __init wan_init(struct ibm_init_struct *iibm) { + int res; int status = 0; vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n"); @@ -1140,17 +1246,29 @@ static int __init wan_init(struct ibm_init_struct *iibm) str_supported(tp_features.wan), status); - if (tp_features.wan && - !(status & TP_ACPI_WANCARD_HWPRESENT)) { - /* no wan hardware present in system */ - tp_features.wan = 0; - dbg_printk(TPACPI_DBG_INIT, - "wan hardware not installed\n"); + if (tp_features.wan) { + if (!(status & TP_ACPI_WANCARD_HWPRESENT)) { + /* no wan hardware present in system */ + tp_features.wan = 0; + dbg_printk(TPACPI_DBG_INIT, + "wan hardware not installed\n"); + } else { + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &wan_attr_group); + if (res) + return res; + } } return (tp_features.wan)? 0 : 1; } +static void wan_exit(void) +{ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &wan_attr_group); +} + static int wan_get_radiosw(void) { int status; @@ -1183,6 +1301,7 @@ static int wan_set_radiosw(int radio_on) return 0; } +/* procfs -------------------------------------------------------------- */ static int wan_read(char *p) { int len = 0; @@ -1222,6 +1341,7 @@ static struct ibm_struct wan_driver_data = { .name = "wan", .read = wan_read, .write = wan_write, + .exit = wan_exit, .flags.experimental = 1, }; diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index 7615adb..a6c2855 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -278,6 +278,8 @@ static int beep_write(char *buf); * Bluetooth subdriver */ +#define TPACPI_BLUETH_SYSFS_GROUP "bluetooth" + enum { /* ACPI GBDC/SBDC bits */ TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ @@ -551,6 +553,8 @@ static int volume_write(char *buf); * Wan subdriver */ +#define TPACPI_WAN_SYSFS_GROUP "wwan" + enum { /* ACPI GWAN/SWAN bits */ TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ -- cgit v1.1 From 836a53f42f3b5d5cb3a0751587ea33801e4b120d Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Sat, 28 Apr 2007 21:19:38 +0200 Subject: thinkpad-acpi: make drivers/misc/thinkpad_acpi:fan_mutex static This patch makes the needlessly global fan_mutex static. Signed-off-by: Adrian Bunk Acked-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/thinkpad_acpi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h index a6c2855..440145a 100644 --- a/drivers/misc/thinkpad_acpi.h +++ b/drivers/misc/thinkpad_acpi.h @@ -386,7 +386,7 @@ static u8 fan_control_initial_status; static u8 fan_control_desired_level; static int fan_watchdog_maxinterval; -struct mutex fan_mutex; +static struct mutex fan_mutex; static acpi_handle fans_handle, gfan_handle, sfan_handle; -- cgit v1.1