From 86e590006c8f76bdbbbe1c09684d4862085f4c52 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Tue, 12 Dec 2006 18:34:20 -0300 Subject: [PATCH] Initial commit: publicly released versions of ibm-acpi from 2.6.18 and 2.6.19 Signed-off-by: Henrique de Moraes Holschuh --- .../2.6.18/0001-ACPI-generic-Hot-Key-support.txt | 36 + ...002-ACPI-Bind-PCI-devices-with-ACPI-devices.txt | 53 + ...-ACPI-IBM-ThinkPad-ACPI-Extras-Driver-v0.12.txt | 2735 ++++++++++++++++++++ ...pi-remove-dock-event-handling-from-ibm_acpi.txt | 92 + ...WAN-module-enable-disable-on-a-Thinkpad-X60.txt | 111 + .../2.6.19/0001-Fix-typos-in-Documentation-Q-R.txt | 31 + ...perimental-status-for-brightness-and-volume.txt | 36 + ...ate-documentation-for-brightness-and-volume.txt | 64 + ...ACPI-ibm_acpi-Documentation-the-wan-feature.txt | 53 + ...ACPI-ibm_acpi-delete-obsolete-documentation.txt | 63 + .../upstream/sunrise/Documentation/ibm-acpi.txt | 474 ++++ releases/upstream/sunrise/drivers/acpi/ibm_acpi.c | 1242 +++++++++ 12 files changed, 4990 insertions(+) create mode 100644 releases/upstream/2.6.18/0001-ACPI-generic-Hot-Key-support.txt create mode 100644 releases/upstream/2.6.18/0002-ACPI-Bind-PCI-devices-with-ACPI-devices.txt create mode 100644 releases/upstream/2.6.18/0003-ACPI-IBM-ThinkPad-ACPI-Extras-Driver-v0.12.txt create mode 100644 releases/upstream/2.6.18/0004-acpi-remove-dock-event-handling-from-ibm_acpi.txt create mode 100644 releases/upstream/2.6.18/0005-ACPI-Allow-a-WAN-module-enable-disable-on-a-Thinkpad-X60.txt create mode 100644 releases/upstream/2.6.19/0001-Fix-typos-in-Documentation-Q-R.txt create mode 100644 releases/upstream/2.6.19/0002-ACPI-ibm_acpi-Remove-experimental-status-for-brightness-and-volume.txt create mode 100644 releases/upstream/2.6.19/0003-ACPI-ibm_acpi-Update-documentation-for-brightness-and-volume.txt create mode 100644 releases/upstream/2.6.19/0004-ACPI-ibm_acpi-Documentation-the-wan-feature.txt create mode 100644 releases/upstream/2.6.19/0005-ACPI-ibm_acpi-delete-obsolete-documentation.txt create mode 100644 releases/upstream/sunrise/Documentation/ibm-acpi.txt create mode 100644 releases/upstream/sunrise/drivers/acpi/ibm_acpi.c diff --git a/releases/upstream/2.6.18/0001-ACPI-generic-Hot-Key-support.txt b/releases/upstream/2.6.18/0001-ACPI-generic-Hot-Key-support.txt new file mode 100644 index 00000000000..11eaa22c18f --- /dev/null +++ b/releases/upstream/2.6.18/0001-ACPI-generic-Hot-Key-support.txt @@ -0,0 +1,36 @@ +From fb9802fa59b196d7f90bb3c2e33c555c6bdc4c54 Mon Sep 17 00:00:00 2001 +From: Luming Yu +Date: Fri, 18 Mar 2005 18:03:45 -0500 +Subject: [PATCH 1/5] [ACPI] generic Hot Key support + +See Documentation/acpi-hotkey.txt + +Use cmdline "acpi_specific_hotkey" to enable +legacy platform specific drivers. + +http://bugzilla.kernel.org/show_bug.cgi?id=3887 + +Signed-off-by: Luming Yu +Signed-off-by: Len Brown +--- + drivers/acpi/ibm_acpi.c | 4 ++++ + 1 files changed, 4 insertions(+), 0 deletions(-) + +diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c +index 0fb731a..6c8291c 100644 +--- a/drivers/acpi/ibm_acpi.c ++++ b/drivers/acpi/ibm_acpi.c +@@ -1185,6 +1185,10 @@ static int __init acpi_ibm_init(void) + if (acpi_disabled) + return -ENODEV; + ++ if (!acpi_specific_hotkey_enabled){ ++ printk(IBM_ERR "Using generic hotkey driver\n"); ++ return -ENODEV; ++ } + /* these handles are required */ + if (IBM_HANDLE_INIT(ec, 1) < 0 || + IBM_HANDLE_INIT(hkey, 1) < 0 || +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.18/0002-ACPI-Bind-PCI-devices-with-ACPI-devices.txt b/releases/upstream/2.6.18/0002-ACPI-Bind-PCI-devices-with-ACPI-devices.txt new file mode 100644 index 00000000000..86096d8134f --- /dev/null +++ b/releases/upstream/2.6.18/0002-ACPI-Bind-PCI-devices-with-ACPI-devices.txt @@ -0,0 +1,53 @@ +From 4e10d12a3d88c88fba3258809aa42d14fd8cf1d1 Mon Sep 17 00:00:00 2001 +From: David Shaohua Li +Date: Fri, 18 Mar 2005 18:45:35 -0500 +Subject: [PATCH 2/5] [ACPI] Bind PCI devices with ACPI devices + +Implement the framework for binding physical devices +with ACPI devices. A physical bus like PCI bus +should create a 'acpi_bus_type', with: + +.find_device: + For device which has parent such as normal PCI devices. + +.find_bridge: + It's for special devices, such as PCI root bridge + or IDE controller. Such devices generally haven't a + parent or ->bus. We use the special method + to get an ACPI handle. + +Uses new field in struct device: firmware_data + +http://bugzilla.kernel.org/show_bug.cgi?id=4277 + +Signed-off-by: David Shaohua Li +Signed-off-by: Len Brown +--- + drivers/acpi/ibm_acpi.c | 4 ++-- + 1 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c +index 6c8291c..ad85e10 100644 +--- a/drivers/acpi/ibm_acpi.c ++++ b/drivers/acpi/ibm_acpi.c +@@ -1025,7 +1025,7 @@ static int setup_notify(struct ibm_struct *ibm) + return 0; + } + +-static int device_add(struct acpi_device *device) ++static int ibmacpi_device_add(struct acpi_device *device) + { + return 0; + } +@@ -1043,7 +1043,7 @@ static int register_driver(struct ibm_struct *ibm) + memset(ibm->driver, 0, sizeof(struct acpi_driver)); + sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name); + ibm->driver->ids = ibm->hid; +- ibm->driver->ops.add = &device_add; ++ ibm->driver->ops.add = &ibmacpi_device_add; + + ret = acpi_bus_register_driver(ibm->driver); + if (ret < 0) { +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.18/0003-ACPI-IBM-ThinkPad-ACPI-Extras-Driver-v0.12.txt b/releases/upstream/2.6.18/0003-ACPI-IBM-ThinkPad-ACPI-Extras-Driver-v0.12.txt new file mode 100644 index 00000000000..8ccc50e0a80 --- /dev/null +++ b/releases/upstream/2.6.18/0003-ACPI-IBM-ThinkPad-ACPI-Extras-Driver-v0.12.txt @@ -0,0 +1,2735 @@ +From 78f81cc4355c31c798564ff7efb253cc4cdce6c0 Mon Sep 17 00:00:00 2001 +From: Borislav Deianov +Date: Wed, 17 Aug 2005 00:00:00 -0400 +Subject: [PATCH 3/5] [ACPI] IBM ThinkPad ACPI Extras Driver v0.12 + +http://ibm-acpi.sf.net/ + +Signed-off-by: Borislav Deianov +Signed-off-by: Len Brown +--- + Documentation/ibm-acpi.txt | 382 ++++++++--- + drivers/acpi/ibm_acpi.c | 1598 ++++++++++++++++++++++++++++++++------------ + 2 files changed, 1445 insertions(+), 535 deletions(-) + +diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt +index c437b1a..8b3fd82 100644 +--- a/Documentation/ibm-acpi.txt ++++ b/Documentation/ibm-acpi.txt +@@ -1,16 +1,16 @@ + IBM ThinkPad ACPI Extras Driver + +- Version 0.8 +- 8 November 2004 ++ Version 0.12 ++ 17 August 2005 + + Borislav Deianov + http://ibm-acpi.sf.net/ + + +-This is a Linux ACPI driver for the IBM ThinkPad laptops. It aims to +-support various features of these laptops which are accessible through +-the ACPI framework but not otherwise supported by the generic Linux +-ACPI drivers. ++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. + + + Status +@@ -25,9 +25,14 @@ detailed description): + - ThinkLight on and off + - limited docking and undocking + - UltraBay eject +- - Experimental: CMOS control +- - Experimental: LED control +- - Experimental: ACPI sounds ++ - CMOS control ++ - LED control ++ - ACPI sounds ++ - temperature sensors ++ - Experimental: embedded controller register dump ++ - Experimental: LCD brightness control ++ - Experimental: volume control ++ - Experimental: fan speed, fan enable/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 +@@ -91,12 +96,12 @@ 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 +--------------------------------------- ++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 ++Hot keys -- /proc/acpi/ibm/hotkey + --------------------------------- + + Without this driver, only the Fn-F4 key (sleep button) generates an +@@ -188,7 +193,7 @@ 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 sumulates the behavior of Fn-F7). ++(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 +@@ -201,6 +206,12 @@ 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 + ------------------------------------------ + +@@ -211,7 +222,7 @@ models which do not make the status available will show it as + echo on > /proc/acpi/ibm/light + echo off > /proc/acpi/ibm/light + +-Docking / Undocking -- /proc/acpi/ibm/dock ++Docking / undocking -- /proc/acpi/ibm/dock + ------------------------------------------ + + Docking and undocking (e.g. with the X4 UltraBase) requires some +@@ -228,11 +239,15 @@ 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: "ibm_acpi: dock device not present". 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). ++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 +@@ -267,7 +282,7 @@ 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 ++UltraBay eject -- /proc/acpi/ibm/bay + ------------------------------------ + + Inserting or ejecting an UltraBay device requires some actions to be +@@ -284,8 +299,11 @@ 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: "ibm_acpi: bay +-device not present". No bay-related events are generated but the eject ++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. + +@@ -306,22 +324,33 @@ 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 Features +---------------------- ++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): + +-The following features are marked experimental because using them +-involves guessing the correct values of some parameters. Guessing +-incorrectly may have undesirable effects like crashing your +-ThinkPad. USE THESE WITH CAUTION! To activate them, you'll 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: + +-Experimental: CMOS control - /proc/acpi/ibm/cmos +------------------------------------------------- ++ 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 appears that it can also +-control LCD brightness, sounds volume and more, but only on some +-models. ++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: + +@@ -330,10 +359,9 @@ The commands are non-negative integer numbers: + echo 2 >/proc/acpi/ibm/cmos + ... + +-The range of numbers which are used internally by various models is 0 +-to 21, but it's possible that numbers outside this range have +-interesting behavior. Here is the behavior on the X40 (tpb is the +-ThinkPad Buttons utility): ++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" +@@ -346,26 +374,18 @@ ThinkPad Buttons utility): + 13 - ThinkLight off + 14 - no effect but tpb reports ThinkLight status change + +-If you try this feature, please send me a report similar to the +-above. On models which allow control of LCD brightness or sound +-volume, I'd like to provide this functionality in an user-friendly +-way, but first I need a way to identify the models which this is +-possible. +- +-Experimental: LED control - /proc/acpi/ibm/LED +----------------------------------------------- ++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 ++ echo ' on' >/proc/acpi/ibm/led ++ echo ' off' >/proc/acpi/ibm/led ++ echo ' blink' >/proc/acpi/ibm/led + +-The parameter is a non-negative integer. The range of LED +-numbers used internally by various models is 0 to 7 but it's possible +-that numbers outside this range are also valid. Here is the mapping on +-the X40: ++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) +@@ -376,49 +396,224 @@ the X40: + + All of the above can be turned on and off and can be made to blink. + +-If you try this feature, please send me a report similar to the +-above. I'd like to provide this functionality in an user-friendly way, +-but first I need to identify the which numbers correspond to which +-LEDs on various models. +- +-Experimental: ACPI sounds - /proc/acpi/ibm/beep +------------------------------------------------ ++ACPI sounds -- /proc/acpi/ibm/beep ++---------------------------------- + + The BEEP method is used internally by the ACPI firmware to provide +-audible alerts in various situtation. This feature allows the same ++audible alerts in various situations. This feature allows the same + sounds to be triggered manually. + + The commands are non-negative integer numbers: + +- echo 0 >/proc/acpi/ibm/beep +- echo 1 >/proc/acpi/ibm/beep +- echo 2 >/proc/acpi/ibm/beep +- ... ++ echo >/proc/acpi/ibm/beep + +-The range of numbers which are used internally by various models is 0 +-to 17, but it's possible that numbers outside this range are also +-valid. Here is the behavior on the X40: ++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: + +- 2 - two beeps, pause, third beep ++ 0 - stop a sound in progress (but use 17 to stop 16) ++ 2 - two beeps, pause, third beep ("low battery") + 3 - single beep +- 4 - "unable" ++ 4 - high, followed by low-pitched beep ("unable") + 5 - single beep +- 6 - "AC/DC" ++ 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 +- +-(I've only been able to identify a couple of them). +- +-If you try this feature, please send me a report similar to the +-above. I'd like to provide this functionality in an user-friendly way, +-but first I need to identify the which numbers correspond to which +-sounds on various models. +- +- +-Multiple Command, Module Parameters +------------------------------------ ++ 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. Some ++readings may not be valid, e.g. may show large negative values. For ++example, on the X40, a typical output may be: ++ ++temperatures: 42 42 45 41 36 -128 33 -128 ++ ++Thomas Gruber took his R51 apart and traced all six active sensors in ++his laptop (the location of sensors may vary on other models): ++ ++1: CPU ++2: Mini PCI Module ++3: HDD ++4: GPU ++5: Battery ++6: N/A ++7: Battery ++8: N/A ++ ++No commands can be written to this file. ++ ++EXPERIMENTAL: Embedded controller reigster 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.) ++ ++EXPERIMENTAL: LCD brightness control -- /proc/acpi/ibm/brightness ++----------------------------------------------------------------- ++ ++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 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. ++ ++EXPERIMENTAL: Volume control -- /proc/acpi/ibm/volume ++----------------------------------------------------- ++ ++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 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. 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. ++ ++The fan may be enabled or disabled with the following commands: ++ ++ echo enable >/proc/acpi/ibm/fan ++ echo disable >/proc/acpi/ibm/fan ++ ++WARNING WARNING WARNING: do not leave the fan disabled unless you are ++monitoring the temperature sensor readings and you are ready to enable ++it if necessary to avoid overheating. ++ ++The fan only runs if it's enabled *and* the various temperature ++sensors which control it read high enough. 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. ++ ++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. ++ ++On the 570, temperature readings are not available through this ++feature and the fan control works a little differently. The fan speed ++is reported in levels from 0 (off) to 7 (max) and can be controlled ++with the following command: ++ ++ echo 'level ' > /proc/acpi/ibm/thermal ++ ++ ++Multiple Commands, Module Parameters ++------------------------------------ + + Multiple commands can be written to the proc files in one shot by + separating them with commas, for example: +@@ -451,24 +646,19 @@ scripts (included with ibm-acpi for completeness): + /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 has written a SuSE powersave +-script for the X20, included in config/usr/sbin/ibm_hotkeys_X20 ++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, included in +-configs/etc/acpi/actions/blank-debian.sh +- +- +-TODO +----- +- +-I'd like to implement the following features but haven't yet found the +-time and/or I don't yet know how to implement them: +- +-- UltraBay floppy drive support +- ++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. +diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c +index ad85e10..5cc0903 100644 +--- a/drivers/acpi/ibm_acpi.c ++++ b/drivers/acpi/ibm_acpi.c +@@ -2,7 +2,7 @@ + * ibm_acpi.c - IBM ThinkPad ACPI Extras + * + * +- * Copyright (C) 2004 Borislav Deianov ++ * Copyright (C) 2004-2005 Borislav Deianov + * + * 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,38 +17,62 @@ + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +- * ++ */ ++ ++#define IBM_VERSION "0.12a" ++ ++/* + * Changelog: +- * +- * 2004-08-09 0.1 initial release, support for X series +- * 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-17 0.3 support for R40 +- * lcd off, brightness control +- * thinklight on/off +- * 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 ++ * ++ * 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-10-19 0.6 use acpi_bus_register_driver() to claim HKEY device +- * 2004-10-23 0.7 fix module loading on A21e, A22p, T20, T21, X20 +- * fix LED control on A21e +- * 2004-11-08 0.8 fix init error case, don't return from a macro +- * thanks to Chris Wright ++ * 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 + */ + +-#define IBM_VERSION "0.8" +- + #include + #include + #include +@@ -64,6 +88,11 @@ + #define IBM_FILE "ibm_acpi" + #define IBM_URL "http://ibm-acpi.sf.net/" + ++MODULE_AUTHOR("Borislav Deianov"); ++MODULE_DESCRIPTION(IBM_DESC); ++MODULE_VERSION(IBM_VERSION); ++MODULE_LICENSE("GPL"); ++ + #define IBM_DIR IBM_NAME + + #define IBM_LOG IBM_FILE ": " +@@ -84,54 +113,122 @@ 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.EC", /* A21e, A22p, T20, T21, X20 */ +- "\\_SB.PCI0.LPC.EC", /* all others */ +-); +- +-IBM_HANDLE(vid, root, +- "\\_SB.PCI0.VID", /* A21e, G40, X30, X40 */ +- "\\_SB.PCI0.AGP.VID", /* all others */ +-); +- +-IBM_HANDLE(cmos, root, +- "\\UCMS", /* R50, R50p, R51, T4x, X31, X40 */ +- "\\CMOS", /* A3x, G40, R32, T23, T30, X22, X24, X30 */ +- "\\CMS", /* R40, R40e */ +-); /* A21e, A22p, T20, T21, X20 */ +- +-IBM_HANDLE(dock, root, +- "\\_SB.GDCK", /* X30, X31, X40 */ +- "\\_SB.PCI0.DOCK", /* A22p, T20, T21, X20 */ +- "\\_SB.PCI0.PCI1.DOCK", /* all others */ +-); /* A21e, G40, R32, R40, R40e */ +- +-IBM_HANDLE(bay, root, +- "\\_SB.PCI0.IDE0.SCND.MSTR"); /* all except A21e */ +-IBM_HANDLE(bayej, root, +- "\\_SB.PCI0.IDE0.SCND.MSTR._EJ0"); /* all except A2x, A3x */ +- +-IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A22p, T20, T21, X20 */ +-IBM_HANDLE(hkey, ec, "HKEY"); /* all */ +-IBM_HANDLE(led, ec, "LED"); /* all except A21e, A22p, T20, T21, X20 */ +-IBM_HANDLE(sysl, ec, "SYSL"); /* A21e, A22p, T20, T21, X20 */ +-IBM_HANDLE(bled, ec, "BLED"); /* A22p, T20, T21, X20 */ +-IBM_HANDLE(beep, ec, "BEEP"); /* all models */ ++/* ++ * The following models are supported to various degrees: ++ * ++ * 570, 600e, 600x, 770e, 770x ++ * A20m, A21e, A21m, A21p, A22p, A30, A30p, A31, A31p ++ * G40, G41 ++ * R30, R31, R32, R40, R40e, R50, R50e, R50p, R51 ++ * T20, T21, T22, T23, T30, T40, T40p, T41, T41p, T42, T42p, T43 ++ * X20, X21, X22, X23, X24, X30, X31, X40 ++ * ++ * The following models have no supported features: ++ * ++ * 240, 240x, i1400 ++ * ++ * Still missing DSDTs for the following models: ++ * ++ * A20p, A22e, A22m ++ * R52 ++ * S31 ++ * T43p ++ */ ++ ++IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ ++ "\\_SB.PCI.ISA.EC", /* 570 */ ++ "\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */ ++ "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */ ++ "\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */ ++ "\\_SB.PCI0.ICH3.EC0", /* R31 */ ++ "\\_SB.PCI0.LPC.EC", /* all others */ ++ ); ++ ++IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ ++ "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */ ++ "\\_SB.PCI0.VID0", /* 770e */ ++ "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */ ++ "\\_SB.PCI0.AGP.VID", /* all others */ ++ ); /* R30, R31 */ ++ ++IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ ++ ++IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */ ++ "\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */ ++ "\\CMS", /* R40, R40e */ ++ ); /* all others */ ++ ++IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ ++ "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ ++ "\\_SB.PCI0.PCI1.DOCK", /* all others */ ++ "\\_SB.PCI.ISA.SLCE", /* 570 */ ++ ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ ++ ++IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */ ++ "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */ ++ "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */ ++ ); /* A21e, R30, R31 */ ++ ++IBM_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */ ++ "_EJ0", /* all others */ ++ ); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */ ++ ++IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */ ++ "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */ ++ ); /* all others */ ++ ++IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ ++ "_EJ0", /* 770x */ ++ ); /* all others */ ++ ++/* don't list other alternatives as we install a notify handler on the 570 */ ++IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ ++ ++IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ ++ "^HKEY", /* R30, R31 */ ++ "HKEY", /* all others */ ++ ); /* 570 */ ++ ++IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ ++IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ ++ ++IBM_HANDLE(led, ec, "SLED", /* 570 */ ++ "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ ++ "LED", /* all others */ ++ ); /* R30, R31 */ ++ ++IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ ++IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ ++IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ ++IBM_HANDLE(fans, ec, "FANS"); /* X31, X40 */ ++ ++IBM_HANDLE(gfan, ec, "GFAN", /* 570 */ ++ "\\FSPD", /* 600e/x, 770e, 770x */ ++ ); /* all others */ ++ ++IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ ++ "JFNS", /* 770x-JL */ ++ ); /* all others */ ++ ++#define IBM_HKEY_HID "IBM0068" ++#define IBM_PCI_HID "PNP0A03" + + struct ibm_struct { + char *name; ++ char param[32]; + + char *hid; + struct acpi_driver *driver; +- +- int (*init) (struct ibm_struct *); +- int (*read) (struct ibm_struct *, char *); +- int (*write) (struct ibm_struct *, char *); +- void (*exit) (struct ibm_struct *); + +- void (*notify) (struct ibm_struct *, u32); ++ 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; +@@ -141,17 +238,6 @@ struct ibm_struct { + int init_called; + int notify_installed; + +- int supported; +- union { +- struct { +- int status; +- int mask; +- } hotkey; +- struct { +- int autoswitch; +- } video; +- } state; +- + int experimental; + }; + +@@ -165,15 +251,15 @@ 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; +- union acpi_object out_obj; +- acpi_status status; +- va_list ap; +- char res_type; +- int success; +- int quiet; ++ 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"); +@@ -199,7 +285,7 @@ static int acpi_evalf(acpi_handle handle, + in_objs[params.count].integer.value = va_arg(ap, int); + in_objs[params.count++].type = ACPI_TYPE_INTEGER; + break; +- /* add more types as needed */ ++ /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", c); +@@ -208,21 +294,25 @@ static int acpi_evalf(acpi_handle handle, + } + va_end(ap); + +- result.length = sizeof(out_obj); +- result.pointer = &out_obj; ++ 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, &result); ++ status = acpi_evaluate_object(handle, method, ¶ms, resultp); + + switch (res_type) { +- case 'd': /* int */ ++ 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 */ ++ case 'v': /* void */ + success = status == AE_OK; + break; +- /* add more types as needed */ ++ /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", res_type); +@@ -262,7 +352,7 @@ static char *next_cmd(char **cmds) + return start; + } + +-static int driver_init(struct ibm_struct *ibm) ++static int driver_init(void) + { + printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); + printk(IBM_INFO "%s\n", IBM_URL); +@@ -270,7 +360,7 @@ static int driver_init(struct ibm_struct *ibm) + return 0; + } + +-static int driver_read(struct ibm_struct *ibm, char *p) ++static int driver_read(char *p) + { + int len = 0; + +@@ -280,67 +370,74 @@ static int driver_read(struct ibm_struct *ibm, char *p) + return len; + } + +-static int hotkey_get(struct ibm_struct *ibm, int *status, int *mask) ++static int hotkey_supported; ++static int hotkey_mask_supported; ++static int hotkey_orig_status; ++static int hotkey_orig_mask; ++ ++static int hotkey_get(int *status, int *mask) + { + if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) +- return -EIO; +- if (ibm->supported) { +- if (!acpi_evalf(hkey_handle, mask, "DHKN", "qd")) +- return -EIO; +- } else { +- *mask = ibm->state.hotkey.mask; +- } +- return 0; ++ return 0; ++ ++ if (hotkey_mask_supported) ++ if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) ++ return 0; ++ ++ return 1; + } + +-static int hotkey_set(struct ibm_struct *ibm, int status, int mask) ++static int hotkey_set(int status, int mask) + { + int i; + + if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) +- return -EIO; +- +- if (!ibm->supported) + return 0; + +- for (i=0; i<32; i++) { +- int bit = ((1 << i) & mask) != 0; +- if (!acpi_evalf(hkey_handle, NULL, "MHKM", "vdd", i+1, bit)) +- return -EIO; +- } ++ 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 0; ++ return 1; + } + +-static int hotkey_init(struct ibm_struct *ibm) ++static int hotkey_init(void) + { +- int ret; ++ /* hotkey not supported on 570 */ ++ hotkey_supported = hkey_handle != NULL; + +- ibm->supported = 1; +- ret = hotkey_get(ibm, +- &ibm->state.hotkey.status, +- &ibm->state.hotkey.mask); +- if (ret < 0) { +- /* mask not supported on A21e, A22p, T20, T21, X20, X22, X24 */ +- ibm->supported = 0; +- ret = hotkey_get(ibm, +- &ibm->state.hotkey.status, +- &ibm->state.hotkey.mask); ++ 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 ret; +-} ++ return 0; ++} + +-static int hotkey_read(struct ibm_struct *ibm, char *p) ++static int hotkey_read(char *p) + { + int status, mask; + int len = 0; + +- if (hotkey_get(ibm, &status, &mask) < 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 (ibm->supported) { ++ if (hotkey_mask_supported) { + len += sprintf(p + len, "mask:\t\t0x%04x\n", mask); + len += sprintf(p + len, + "commands:\tenable, disable, reset, \n"); +@@ -352,23 +449,26 @@ static int hotkey_read(struct ibm_struct *ibm, char *p) + return len; + } + +-static int hotkey_write(struct ibm_struct *ibm, char *buf) ++static int hotkey_write(char *buf) + { + int status, mask; + char *cmd; + int do_cmd = 0; + +- if (hotkey_get(ibm, &status, &mask) < 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 = ibm->state.hotkey.status; +- mask = ibm->state.hotkey.mask; ++ 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) { +@@ -378,15 +478,16 @@ static int hotkey_write(struct ibm_struct *ibm, char *buf) + do_cmd = 1; + } + +- if (do_cmd && hotkey_set(ibm, status, mask) < 0) ++ if (do_cmd && !hotkey_set(status, mask)) + return -EIO; + + return 0; +-} ++} + +-static void hotkey_exit(struct ibm_struct *ibm) ++static void hotkey_exit(void) + { +- hotkey_set(ibm, ibm->state.hotkey.status, ibm->state.hotkey.mask); ++ if (hotkey_supported) ++ hotkey_set(hotkey_orig_status, hotkey_orig_mask); + } + + static void hotkey_notify(struct ibm_struct *ibm, u32 event) +@@ -398,33 +499,38 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + else { + printk(IBM_ERR "unknown hotkey event %d\n", event); + acpi_bus_generate_event(ibm->device, event, 0); +- } ++ } + } + +-static int bluetooth_init(struct ibm_struct *ibm) ++static int bluetooth_supported; ++ ++static int bluetooth_init(void) + { +- /* bluetooth not supported on A21e, G40, T20, T21, X20 */ +- ibm->supported = acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); ++ /* 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(struct ibm_struct *ibm) ++static int bluetooth_status(void) + { + int status; + +- if (!ibm->supported || !acpi_evalf(hkey_handle, &status, "GBDC", "d")) ++ if (!bluetooth_supported || ++ !acpi_evalf(hkey_handle, &status, "GBDC", "d")) + status = 0; + + return status; + } + +-static int bluetooth_read(struct ibm_struct *ibm, char *p) ++static int bluetooth_read(char *p) + { + int len = 0; +- int status = bluetooth_status(ibm); ++ int status = bluetooth_status(); + +- if (!ibm->supported) ++ 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"); +@@ -436,14 +542,14 @@ static int bluetooth_read(struct ibm_struct *ibm, char *p) + return len; + } + +-static int bluetooth_write(struct ibm_struct *ibm, char *buf) ++static int bluetooth_write(char *buf) + { +- int status = bluetooth_status(ibm); ++ int status = bluetooth_status(); + char *cmd; + int do_cmd = 0; + +- if (!ibm->supported) +- return -EINVAL; ++ if (!bluetooth_supported) ++ return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { +@@ -456,64 +562,166 @@ static int bluetooth_write(struct ibm_struct *ibm, char *buf) + } + + if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) +- return -EIO; ++ return -EIO; + + return 0; + } + +-static int video_init(struct ibm_struct *ibm) ++static int video_supported; ++static int video_orig_autosw; ++ ++#define VIDEO_570 1 ++#define VIDEO_770 2 ++#define VIDEO_NEW 3 ++ ++static int video_init(void) + { +- if (!acpi_evalf(vid_handle, +- &ibm->state.video.autoswitch, "^VDEE", "d")) +- return -ENODEV; ++ int ivga; ++ ++ if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) ++ /* G41, assume IVGA doesn't change */ ++ vid_handle = vid2_handle; ++ ++ if (!vid_handle) ++ /* video switching not supported on R30, R31 */ ++ video_supported = 0; ++ else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) ++ /* 570 */ ++ video_supported = VIDEO_570; ++ else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) ++ /* 600e/x, 770e, 770x */ ++ video_supported = VIDEO_770; ++ else ++ /* all others */ ++ video_supported = VIDEO_NEW; + + return 0; + } + +-static int video_status(struct ibm_struct *ibm) ++static int video_status(void) + { + int status = 0; + int i; + +- acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); +- if (acpi_evalf(NULL, &i, "\\VCDC", "d")) +- status |= 0x02 * i; ++ if (video_supported == VIDEO_570) { ++ if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) ++ status = i & 3; ++ } else if (video_supported == VIDEO_770) { ++ if (acpi_evalf(NULL, &i, "\\VCDL", "d")) ++ status |= 0x01 * i; ++ if (acpi_evalf(NULL, &i, "\\VCDC", "d")) ++ status |= 0x02 * i; ++ } else if (video_supported == VIDEO_NEW) { ++ acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); ++ if (acpi_evalf(NULL, &i, "\\VCDC", "d")) ++ status |= 0x02 * i; ++ ++ acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0); ++ if (acpi_evalf(NULL, &i, "\\VCDL", "d")) ++ status |= 0x01 * i; ++ if (acpi_evalf(NULL, &i, "\\VCDD", "d")) ++ status |= 0x08 * i; ++ } ++ ++ return status; ++} + +- 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; ++static int video_autosw(void) ++{ ++ int autosw = 0; + +- if (acpi_evalf(vid_handle, &i, "^VDEE", "d")) +- status |= 0x10 * (i & 1); ++ if (video_supported == VIDEO_570) ++ acpi_evalf(vid_handle, &autosw, "SWIT", "d"); ++ else if (video_supported == VIDEO_770 || video_supported == VIDEO_NEW) ++ acpi_evalf(vid_handle, &autosw, "^VDEE", "d"); + +- return status; ++ return autosw & 1; + } + +-static int video_read(struct ibm_struct *ibm, char *p) ++static int video_read(char *p) + { +- int status = video_status(ibm); ++ 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)); +- len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); +- len += sprintf(p + len, "auto:\t\t%s\n", enabled(status, 4)); +- len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable, " +- "crt_enable, crt_disable\n"); +- len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable, " +- "auto_enable, auto_disable\n"); ++ if (video_supported == VIDEO_NEW) ++ len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); ++ len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0)); ++ len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n"); ++ len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n"); ++ if (video_supported == VIDEO_NEW) ++ len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n"); ++ len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n"); + len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); + + return len; + } + +-static int video_write(struct ibm_struct *ibm, char *buf) ++static int video_switch(void) ++{ ++ int autosw = video_autosw(); ++ int ret; ++ ++ if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) ++ return -EIO; ++ ret = video_supported == VIDEO_570 ? ++ acpi_evalf(ec_handle, NULL, "_Q16", "v") : ++ acpi_evalf(vid_handle, NULL, "VSWT", "v"); ++ acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); ++ ++ return ret; ++} ++ ++static int video_expand(void) ++{ ++ if (video_supported == VIDEO_570) ++ return acpi_evalf(ec_handle, NULL, "_Q17", "v"); ++ else if (video_supported == VIDEO_770) ++ return acpi_evalf(vid_handle, NULL, "VEXP", "v"); ++ else ++ return acpi_evalf(NULL, NULL, "\\VEXP", "v"); ++} ++ ++static int video_switch2(int status) ++{ ++ int ret; ++ ++ if (video_supported == VIDEO_570) { ++ ret = acpi_evalf(NULL, NULL, ++ "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); ++ } else if (video_supported == VIDEO_770) { ++ int autosw = video_autosw(); ++ if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) ++ return -EIO; ++ ++ ret = acpi_evalf(vid_handle, NULL, ++ "ASWT", "vdd", status * 0x100, 0); ++ ++ acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); ++ } else { ++ ret = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && ++ acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); ++ } ++ ++ return ret; ++} ++ ++static int video_write(char *buf) + { + char *cmd; + int enable, disable, status; + ++ if (!video_supported) ++ return -ENODEV; ++ + enable = disable = 0; + + while ((cmd = next_cmd(&buf))) { +@@ -525,9 +733,11 @@ static int video_write(struct ibm_struct *ibm, char *buf) + enable |= 0x02; + } else if (strlencmp(cmd, "crt_disable") == 0) { + disable |= 0x02; +- } else if (strlencmp(cmd, "dvi_enable") == 0) { ++ } else if (video_supported == VIDEO_NEW && ++ strlencmp(cmd, "dvi_enable") == 0) { + enable |= 0x08; +- } else if (strlencmp(cmd, "dvi_disable") == 0) { ++ } else if (video_supported == VIDEO_NEW && ++ strlencmp(cmd, "dvi_disable") == 0) { + disable |= 0x08; + } else if (strlencmp(cmd, "auto_enable") == 0) { + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) +@@ -536,71 +746,75 @@ static int video_write(struct ibm_struct *ibm, char *buf) + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0)) + return -EIO; + } else if (strlencmp(cmd, "video_switch") == 0) { +- int autoswitch; +- if (!acpi_evalf(vid_handle, &autoswitch, "^VDEE", "d")) +- return -EIO; +- if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) +- return -EIO; +- if (!acpi_evalf(vid_handle, NULL, "VSWT", "v")) +- return -EIO; +- if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", +- autoswitch)) ++ if (!video_switch()) + return -EIO; + } else if (strlencmp(cmd, "expand_toggle") == 0) { +- if (!acpi_evalf(NULL, NULL, "\\VEXP", "v")) ++ if (!video_expand()) + return -EIO; + } else + return -EINVAL; + } + + if (enable || disable) { +- status = (video_status(ibm) & 0x0f & ~disable) | enable; +- if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80)) +- return -EIO; +- if (!acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1)) ++ status = (video_status() & 0x0f & ~disable) | enable; ++ if (!video_switch2(status)) + return -EIO; + } + + return 0; + } + +-static void video_exit(struct ibm_struct *ibm) ++static void video_exit(void) + { +- acpi_evalf(vid_handle, NULL, "_DOS", "vd", +- ibm->state.video.autoswitch); ++ acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); + } + +-static int light_init(struct ibm_struct *ibm) ++static int light_supported; ++static int light_status_supported; ++ ++static int light_init(void) + { +- /* kblt not supported on G40, R32, X20 */ +- ibm->supported = acpi_evalf(ec_handle, NULL, "KBLT", "qv"); ++ /* 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(struct ibm_struct *ibm, char *p) ++static int light_read(char *p) + { + int len = 0; + int status = 0; + +- if (ibm->supported) { ++ 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)); +- } else +- len += sprintf(p + len, "status:\t\tunknown\n"); +- +- len += sprintf(p + len, "commands:\ton, off\n"); ++ len += sprintf(p + len, "commands:\ton, off\n"); ++ } + + return len; + } + +-static int light_write(struct ibm_struct *ibm, char *buf) ++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; +@@ -610,10 +824,10 @@ static int light_write(struct ibm_struct *ibm, char *buf) + 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); ++ acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : ++ acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); + if (!success) + return -EIO; + } +@@ -633,7 +847,7 @@ static int _sta(acpi_handle handle) + + #define dock_docked() (_sta(dock_handle) & 1) + +-static int dock_read(struct ibm_struct *ibm, char *p) ++static int dock_read(char *p) + { + int len = 0; + int docked = dock_docked(); +@@ -650,18 +864,17 @@ static int dock_read(struct ibm_struct *ibm, char *p) + return len; + } + +-static int dock_write(struct ibm_struct *ibm, char *buf) ++static int dock_write(char *buf) + { + char *cmd; + + if (!dock_docked()) +- return -EINVAL; ++ return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "undock") == 0) { +- if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0)) +- return -EIO; +- if (!acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1)) ++ 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)) +@@ -671,90 +884,131 @@ static int dock_write(struct ibm_struct *ibm, char *buf) + } + + return 0; +-} ++} + + static void dock_notify(struct ibm_struct *ibm, u32 event) + { + int docked = dock_docked(); +- +- if (event == 3 && docked) +- acpi_bus_generate_event(ibm->device, event, 1); /* button */ ++ 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 */ ++ acpi_bus_generate_event(ibm->device, event, 2); /* undock */ + else if (event == 0 && docked) +- acpi_bus_generate_event(ibm->device, event, 3); /* dock */ ++ 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 */ ++ acpi_bus_generate_event(ibm->device, event, 0); /* unknown */ + } + } + +-#define bay_occupied() (_sta(bay_handle) & 1) ++static int bay_status_supported; ++static int bay_status2_supported; ++static int bay_eject_supported; ++static int bay_eject2_supported; + +-static int bay_init(struct ibm_struct *ibm) ++static int bay_init(void) + { +- /* bay not supported on A21e, A22p, A31, A31p, G40, R32, R40e */ +- ibm->supported = bay_handle && bayej_handle && +- acpi_evalf(bay_handle, NULL, "_STA", "qv"); ++ 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 int bay_read(struct ibm_struct *ibm, char *p) ++#define bay_occupied(b) (_sta(b##_handle) & 1) ++ ++static int bay_read(char *p) + { + int len = 0; +- int occupied = bay_occupied(); +- +- if (!ibm->supported) +- len += sprintf(p + len, "status:\t\tnot supported\n"); +- else if (!occupied) +- len += sprintf(p + len, "status:\t\tunoccupied\n"); +- else { +- len += sprintf(p + len, "status:\t\toccupied\n"); ++ 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(struct ibm_struct *ibm, char *buf) ++static int bay_write(char *buf) + { + char *cmd; + ++ if (!bay_eject_supported && !bay_eject2_supported) ++ return -ENODEV; ++ + while ((cmd = next_cmd(&buf))) { +- if (strlencmp(cmd, "eject") == 0) { +- if (!ibm->supported || +- !acpi_evalf(bay_handle, NULL, "_EJ0", "vd", 1)) ++ if (bay_eject_supported && strlencmp(cmd, "eject") == 0) { ++ if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1)) ++ return -EIO; ++ } else if (bay_eject2_supported && ++ strlencmp(cmd, "eject2") == 0) { ++ if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +-} ++} + + static void bay_notify(struct ibm_struct *ibm, u32 event) + { + acpi_bus_generate_event(ibm->device, event, 0); + } + +-static int cmos_read(struct ibm_struct *ibm, char *p) ++static int cmos_read(char *p) + { + int len = 0; + +- /* cmos not supported on A21e, A22p, T20, T21, X20 */ ++ /* 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\n"); ++ len += sprintf(p + len, "commands:\t ( is 0-21)\n"); + } + + return len; + } + +-static int cmos_write(struct ibm_struct *ibm, char *buf) ++static int cmos_eval(int cmos_cmd) ++{ ++ if (cmos_handle) ++ return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd); ++ else ++ return 1; ++} ++ ++static int cmos_write(char *buf) + { + char *cmd; + int cmos_cmd; +@@ -763,183 +1017,644 @@ static int cmos_write(struct ibm_struct *ibm, char *buf) + return -EINVAL; + + while ((cmd = next_cmd(&buf))) { +- if (sscanf(cmd, "%u", &cmos_cmd) == 1) { ++ if (sscanf(cmd, "%u", &cmos_cmd) == 1 && ++ cmos_cmd >= 0 && cmos_cmd <= 21) { + /* cmos_cmd set */ + } else + return -EINVAL; + +- if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd)) ++ if (!cmos_eval(cmos_cmd)) + return -EIO; + } + + return 0; +-} +- +-static int led_read(struct ibm_struct *ibm, char *p) ++} ++ ++static int led_supported; ++ ++#define LED_570 1 ++#define LED_OLD 2 ++#define LED_NEW 3 ++ ++static int led_init(void) ++{ ++ if (!led_handle) ++ /* led not supported on R30, R31 */ ++ led_supported = 0; ++ else if (strlencmp(led_path, "SLED") == 0) ++ /* 570 */ ++ led_supported = LED_570; ++ else if (strlencmp(led_path, "SYSL") == 0) ++ /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ ++ led_supported = LED_OLD; ++ else ++ /* all others */ ++ led_supported = LED_NEW; ++ ++ return 0; ++} ++ ++#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking")) ++ ++static int led_read(char *p) + { + int len = 0; + ++ if (!led_supported) { ++ len += sprintf(p + len, "status:\t\tnot supported\n"); ++ return len; ++ } ++ len += sprintf(p + len, "status:\t\tsupported\n"); ++ ++ if (led_supported == LED_570) { ++ /* 570 */ ++ int i, status; ++ for (i = 0; i < 8; i++) { ++ if (!acpi_evalf(ec_handle, ++ &status, "GLED", "dd", 1 << i)) ++ return -EIO; ++ len += sprintf(p + len, "%d:\t\t%s\n", ++ i, led_status(status)); ++ } ++ } ++ + len += sprintf(p + len, "commands:\t" +- " on, off, blink\n"); ++ " on, off, blink ( is 0-7)\n"); + + return len; + } + +-static int led_write(struct ibm_struct *ibm, char *buf) ++/* off, on, blink */ ++static const int led_sled_arg1[] = { 0, 1, 3 }; ++static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ ++static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ ++static const int led_led_arg1[] = { 0, 0x80, 0xc0 }; ++ ++#define EC_HLCL 0x0c ++#define EC_HLBL 0x0d ++#define EC_HLMS 0x0e ++ ++static int led_write(char *buf) + { + char *cmd; +- unsigned int led; +- int led_cmd, sysl_cmd, bled_a, bled_b; ++ int led, ind, ret; ++ ++ if (!led_supported) ++ return -ENODEV; + + while ((cmd = next_cmd(&buf))) { +- if (sscanf(cmd, "%u", &led) != 1) ++ if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) + return -EINVAL; + +- if (strstr(cmd, "blink")) { +- led_cmd = 0xc0; +- sysl_cmd = 2; +- bled_a = 2; +- bled_b = 1; ++ if (strstr(cmd, "off")) { ++ ind = 0; + } else if (strstr(cmd, "on")) { +- led_cmd = 0x80; +- sysl_cmd = 1; +- bled_a = 2; +- bled_b = 0; +- } else if (strstr(cmd, "off")) { +- led_cmd = sysl_cmd = bled_a = bled_b = 0; ++ ind = 1; ++ } else if (strstr(cmd, "blink")) { ++ ind = 2; + } else + return -EINVAL; +- +- if (led_handle) { ++ ++ if (led_supported == LED_570) { ++ /* 570 */ ++ led = 1 << led; + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", +- led, led_cmd)) ++ led, led_sled_arg1[ind])) + return -EIO; +- } else if (led < 2) { +- if (acpi_evalf(sysl_handle, NULL, NULL, "vdd", +- led, sysl_cmd)) ++ } else if (led_supported == LED_OLD) { ++ /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ ++ led = 1 << led; ++ ret = ec_write(EC_HLMS, led); ++ if (ret >= 0) ++ ret = ++ ec_write(EC_HLBL, led * led_exp_hlbl[ind]); ++ if (ret >= 0) ++ ret = ++ ec_write(EC_HLCL, led * led_exp_hlcl[ind]); ++ if (ret < 0) ++ return ret; ++ } else { ++ /* all others */ ++ if (!acpi_evalf(led_handle, NULL, NULL, "vdd", ++ led, led_led_arg1[ind])) + return -EIO; +- } else if (led == 2 && bled_handle) { +- if (acpi_evalf(bled_handle, NULL, NULL, "vdd", +- bled_a, bled_b)) ++ } ++ } ++ ++ return 0; ++} ++ ++static int beep_read(char *p) ++{ ++ int len = 0; ++ ++ if (!beep_handle) ++ len += sprintf(p + len, "status:\t\tnot supported\n"); ++ else { ++ len += sprintf(p + len, "status:\t\tsupported\n"); ++ len += sprintf(p + len, "commands:\t ( is 0-17)\n"); ++ } ++ ++ return len; ++} ++ ++static int beep_write(char *buf) ++{ ++ char *cmd; ++ int beep_cmd; ++ ++ if (!beep_handle) ++ return -ENODEV; ++ ++ while ((cmd = next_cmd(&buf))) { ++ if (sscanf(cmd, "%u", &beep_cmd) == 1 && ++ beep_cmd >= 0 && beep_cmd <= 17) { ++ /* beep_cmd set */ ++ } else ++ return -EINVAL; ++ if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int acpi_ec_read(int i, u8 * p) ++{ ++ int v; ++ ++ if (ecrd_handle) { ++ if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i)) ++ return 0; ++ *p = v; ++ } else { ++ if (ec_read(i, p) < 0) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int acpi_ec_write(int i, u8 v) ++{ ++ if (ecwr_handle) { ++ if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v)) ++ return 0; ++ } else { ++ if (ec_write(i, v) < 0) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int thermal_tmp_supported; ++static int thermal_updt_supported; ++ ++static int thermal_init(void) ++{ ++ /* temperatures not supported on 570, G4x, R30, R31, R32 */ ++ thermal_tmp_supported = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); ++ ++ /* 600e/x, 770e, 770x */ ++ thermal_updt_supported = acpi_evalf(ec_handle, NULL, "UPDT", "qv"); ++ ++ return 0; ++} ++ ++static int thermal_read(char *p) ++{ ++ int len = 0; ++ ++ if (!thermal_tmp_supported) ++ len += sprintf(p + len, "temperatures:\tnot supported\n"); ++ else { ++ int i, t; ++ char tmpi[] = "TMPi"; ++ s8 tmp[8]; ++ ++ if (thermal_updt_supported) ++ if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) ++ return -EIO; ++ ++ for (i = 0; i < 8; i++) { ++ tmpi[3] = '0' + i; ++ if (!acpi_evalf(ec_handle, &t, tmpi, "d")) ++ return -EIO; ++ if (thermal_updt_supported) ++ tmp[i] = (t - 2732 + 5) / 10; ++ else ++ tmp[i] = t; ++ } ++ ++ len += sprintf(p + len, ++ "temperatures:\t%d %d %d %d %d %d %d %d\n", ++ tmp[0], tmp[1], tmp[2], tmp[3], ++ tmp[4], tmp[5], tmp[6], tmp[7]); ++ } ++ ++ return len; ++} ++ ++static u8 ecdump_regs[256]; ++ ++static int ecdump_read(char *p) ++{ ++ int len = 0; ++ int i, j; ++ u8 v; ++ ++ len += sprintf(p + len, "EC " ++ " +00 +01 +02 +03 +04 +05 +06 +07" ++ " +08 +09 +0a +0b +0c +0d +0e +0f\n"); ++ for (i = 0; i < 256; i += 16) { ++ len += sprintf(p + len, "EC 0x%02x:", i); ++ for (j = 0; j < 16; j++) { ++ if (!acpi_ec_read(i + j, &v)) ++ break; ++ if (v != ecdump_regs[i + j]) ++ len += sprintf(p + len, " *%02x", v); ++ else ++ len += sprintf(p + len, " %02x", v); ++ ecdump_regs[i + j] = v; ++ } ++ len += sprintf(p + len, "\n"); ++ if (j != 16) ++ break; ++ } ++ ++ /* These are way too dangerous to advertise openly... */ ++#if 0 ++ len += sprintf(p + len, "commands:\t0x 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; +-} +- +-static int beep_read(struct ibm_struct *ibm, char *p) ++} ++ ++static int brightness_offset = 0x31; ++ ++static int brightness_read(char *p) + { + int len = 0; ++ u8 level; + +- len += sprintf(p + len, "commands:\t\n"); ++ if (!acpi_ec_read(brightness_offset, &level)) { ++ 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 beep_write(struct ibm_struct *ibm, char *buf) ++#define BRIGHTNESS_UP 4 ++#define BRIGHTNESS_DOWN 5 ++ ++static int brightness_write(char *buf) + { ++ int cmos_cmd, inc, i; ++ u8 level; ++ int new_level; + char *cmd; +- int beep_cmd; + + while ((cmd = next_cmd(&buf))) { +- if (sscanf(cmd, "%u", &beep_cmd) == 1) { +- /* beep_cmd set */ ++ if (!acpi_ec_read(brightness_offset, &level)) ++ return -EIO; ++ 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; ++ ++ cmos_cmd = new_level > level ? BRIGHTNESS_UP : BRIGHTNESS_DOWN; ++ inc = new_level > level ? 1 : -1; ++ for (i = level; i != new_level; i += inc) { ++ if (!cmos_eval(cmos_cmd)) ++ return -EIO; ++ if (!acpi_ec_write(brightness_offset, i + inc)) ++ return -EIO; ++ } ++ } ++ ++ return 0; ++} ++ ++static int volume_offset = 0x30; ++ ++static int volume_read(char *p) ++{ ++ int len = 0; ++ u8 level; ++ ++ if (!acpi_ec_read(volume_offset, &level)) { ++ len += sprintf(p + len, "level:\t\tunreadable\n"); ++ } else { ++ len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); ++ len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); ++ len += sprintf(p + len, "commands:\tup, down, mute\n"); ++ len += sprintf(p + len, "commands:\tlevel " ++ " ( is 0-15)\n"); ++ } ++ ++ return len; ++} ++ ++#define VOLUME_DOWN 0 ++#define VOLUME_UP 1 ++#define VOLUME_MUTE 2 ++ ++static int volume_write(char *buf) ++{ ++ int cmos_cmd, inc, i; ++ u8 level, mute; ++ int new_level, new_mute; ++ char *cmd; ++ ++ while ((cmd = next_cmd(&buf))) { ++ if (!acpi_ec_read(volume_offset, &level)) ++ return -EIO; ++ new_mute = mute = level & 0x40; ++ new_level = level = level & 0xf; ++ ++ if (strlencmp(cmd, "up") == 0) { ++ if (mute) ++ new_mute = 0; ++ else ++ new_level = level == 15 ? 15 : level + 1; ++ } else if (strlencmp(cmd, "down") == 0) { ++ if (mute) ++ new_mute = 0; ++ else ++ new_level = level == 0 ? 0 : level - 1; ++ } else if (sscanf(cmd, "level %d", &new_level) == 1 && ++ new_level >= 0 && new_level <= 15) { ++ /* new_level set */ ++ } else if (strlencmp(cmd, "mute") == 0) { ++ new_mute = 0x40; + } else + return -EINVAL; + +- if (!acpi_evalf(beep_handle, NULL, NULL, "vd", beep_cmd)) ++ if (new_level != level) { /* mute doesn't change */ ++ cmos_cmd = new_level > level ? VOLUME_UP : VOLUME_DOWN; ++ inc = new_level > level ? 1 : -1; ++ ++ if (mute && (!cmos_eval(cmos_cmd) || ++ !acpi_ec_write(volume_offset, level))) ++ return -EIO; ++ ++ for (i = level; i != new_level; i += inc) ++ if (!cmos_eval(cmos_cmd) || ++ !acpi_ec_write(volume_offset, i + inc)) ++ return -EIO; ++ ++ if (mute && (!cmos_eval(VOLUME_MUTE) || ++ !acpi_ec_write(volume_offset, ++ new_level + mute))) ++ return -EIO; ++ } ++ ++ if (new_mute != mute) { /* level doesn't change */ ++ cmos_cmd = new_mute ? VOLUME_MUTE : VOLUME_UP; ++ ++ if (!cmos_eval(cmos_cmd) || ++ !acpi_ec_write(volume_offset, level + new_mute)) ++ return -EIO; ++ } ++ } ++ ++ return 0; ++} ++ ++static int fan_status_offset = 0x2f; ++static int fan_rpm_offset = 0x84; ++ ++static int fan_read(char *p) ++{ ++ int len = 0; ++ int s; ++ u8 lo, hi, status; ++ ++ if (gfan_handle) { ++ /* 570, 600e/x, 770e, 770x */ ++ if (!acpi_evalf(gfan_handle, &s, NULL, "d")) + return -EIO; ++ ++ len += sprintf(p + len, "level:\t\t%d\n", s); ++ } else { ++ /* all except 570, 600e/x, 770e, 770x */ ++ if (!acpi_ec_read(fan_status_offset, &status)) ++ len += sprintf(p + len, "status:\t\tunreadable\n"); ++ else ++ len += sprintf(p + len, "status:\t\t%s\n", ++ enabled(status, 7)); ++ ++ if (!acpi_ec_read(fan_rpm_offset, &lo) || ++ !acpi_ec_read(fan_rpm_offset + 1, &hi)) ++ len += sprintf(p + len, "speed:\t\tunreadable\n"); ++ else ++ len += sprintf(p + len, "speed:\t\t%d\n", ++ (hi << 8) + lo); ++ } ++ ++ if (sfan_handle) ++ /* 570, 770x-JL */ ++ len += sprintf(p + len, "commands:\tlevel " ++ " ( is 0-7)\n"); ++ if (!gfan_handle) ++ /* all except 570, 600e/x, 770e, 770x */ ++ len += sprintf(p + len, "commands:\tenable, disable\n"); ++ if (fans_handle) ++ /* X31, X40 */ ++ len += sprintf(p + len, "commands:\tspeed " ++ " ( is 0-65535)\n"); ++ ++ return len; ++} ++ ++static int fan_write(char *buf) ++{ ++ char *cmd; ++ int level, speed; ++ ++ while ((cmd = next_cmd(&buf))) { ++ if (sfan_handle && ++ sscanf(cmd, "level %d", &level) == 1 && ++ level >= 0 && level <= 7) { ++ /* 570, 770x-JL */ ++ if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) ++ return -EIO; ++ } else if (!gfan_handle && strlencmp(cmd, "enable") == 0) { ++ /* all except 570, 600e/x, 770e, 770x */ ++ if (!acpi_ec_write(fan_status_offset, 0x80)) ++ return -EIO; ++ } else if (!gfan_handle && strlencmp(cmd, "disable") == 0) { ++ /* all except 570, 600e/x, 770e, 770x */ ++ if (!acpi_ec_write(fan_status_offset, 0x00)) ++ return -EIO; ++ } else if (fans_handle && ++ sscanf(cmd, "speed %d", &speed) == 1 && ++ speed >= 0 && speed <= 65535) { ++ /* X31, X40 */ ++ if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", ++ speed, speed, speed)) ++ return -EIO; ++ } else ++ return -EINVAL; + } + + return 0; +-} +- ++} ++ + static struct ibm_struct ibms[] = { + { +- .name = "driver", +- .init = driver_init, +- .read = driver_read, +- }, ++ .name = "driver", ++ .init = driver_init, ++ .read = driver_read, ++ }, ++ { ++ .name = "hotkey", ++ .hid = IBM_HKEY_HID, ++ .init = hotkey_init, ++ .read = hotkey_read, ++ .write = hotkey_write, ++ .exit = hotkey_exit, ++ .notify = hotkey_notify, ++ .handle = &hkey_handle, ++ .type = ACPI_DEVICE_NOTIFY, ++ }, ++ { ++ .name = "bluetooth", ++ .init = bluetooth_init, ++ .read = bluetooth_read, ++ .write = bluetooth_write, ++ }, ++ { ++ .name = "video", ++ .init = video_init, ++ .read = video_read, ++ .write = video_write, ++ .exit = video_exit, ++ }, + { +- .name = "hotkey", +- .hid = "IBM0068", +- .init = hotkey_init, +- .read = hotkey_read, +- .write = hotkey_write, +- .exit = hotkey_exit, +- .notify = hotkey_notify, +- .handle = &hkey_handle, +- .type = ACPI_DEVICE_NOTIFY, +- }, ++ .name = "light", ++ .init = light_init, ++ .read = light_read, ++ .write = light_write, ++ }, + { +- .name = "bluetooth", +- .init = bluetooth_init, +- .read = bluetooth_read, +- .write = bluetooth_write, +- }, ++ .name = "dock", ++ .read = dock_read, ++ .write = dock_write, ++ .notify = dock_notify, ++ .handle = &dock_handle, ++ .type = ACPI_SYSTEM_NOTIFY, ++ }, + { +- .name = "video", +- .init = video_init, +- .read = video_read, +- .write = video_write, +- .exit = video_exit, +- }, ++ .name = "dock", ++ .hid = IBM_PCI_HID, ++ .notify = dock_notify, ++ .handle = &pci_handle, ++ .type = ACPI_SYSTEM_NOTIFY, ++ }, + { +- .name = "light", +- .init = light_init, +- .read = light_read, +- .write = light_write, +- }, ++ .name = "bay", ++ .init = bay_init, ++ .read = bay_read, ++ .write = bay_write, ++ .notify = bay_notify, ++ .handle = &bay_handle, ++ .type = ACPI_SYSTEM_NOTIFY, ++ }, + { +- .name = "dock", +- .read = dock_read, +- .write = dock_write, +- .notify = dock_notify, +- .handle = &dock_handle, +- .type = ACPI_SYSTEM_NOTIFY, +- }, ++ .name = "cmos", ++ .read = cmos_read, ++ .write = cmos_write, ++ }, + { +- .name = "bay", +- .init = bay_init, +- .read = bay_read, +- .write = bay_write, +- .notify = bay_notify, +- .handle = &bay_handle, +- .type = ACPI_SYSTEM_NOTIFY, +- }, ++ .name = "led", ++ .init = led_init, ++ .read = led_read, ++ .write = led_write, ++ }, + { +- .name = "cmos", +- .read = cmos_read, +- .write = cmos_write, +- .experimental = 1, +- }, ++ .name = "beep", ++ .read = beep_read, ++ .write = beep_write, ++ }, + { +- .name = "led", +- .read = led_read, +- .write = led_write, +- .experimental = 1, +- }, ++ .name = "thermal", ++ .init = thermal_init, ++ .read = thermal_read, ++ }, + { +- .name = "beep", +- .read = beep_read, +- .write = beep_write, +- .experimental = 1, +- }, ++ .name = "ecdump", ++ .read = ecdump_read, ++ .write = ecdump_write, ++ .experimental = 1, ++ }, ++ { ++ .name = "brightness", ++ .read = brightness_read, ++ .write = brightness_write, ++ .experimental = 1, ++ }, ++ { ++ .name = "volume", ++ .read = volume_read, ++ .write = volume_write, ++ .experimental = 1, ++ }, ++ { ++ .name = "fan", ++ .read = fan_read, ++ .write = fan_write, ++ .experimental = 1, ++ }, + }; +-#define NUM_IBMS (sizeof(ibms)/sizeof(ibms[0])) + + static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data) + { + struct ibm_struct *ibm = (struct ibm_struct *)data; + int len; +- ++ + if (!ibm || !ibm->read) + return -EINVAL; + +- len = ibm->read(ibm, page); ++ len = ibm->read(page); + if (len < 0) + return len; + +@@ -955,7 +1670,7 @@ 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, ++static int dispatch_write(struct file *file, const char __user * userbuf, + unsigned long count, void *data) + { + struct ibm_struct *ibm = (struct ibm_struct *)data; +@@ -969,20 +1684,20 @@ static int dispatch_write(struct file *file, const char __user *userbuf, + if (!kernbuf) + return -ENOMEM; + +- if (copy_from_user(kernbuf, userbuf, count)) { ++ if (copy_from_user(kernbuf, userbuf, count)) { + kfree(kernbuf); +- return -EFAULT; ++ return -EFAULT; + } + + kernbuf[count] = 0; + strcat(kernbuf, ","); +- ret = ibm->write(ibm, kernbuf); ++ ret = ibm->write(kernbuf); + if (ret == 0) + ret = count; + + kfree(kernbuf); + +- return ret; ++ return ret; + } + + static void dispatch_notify(acpi_handle handle, u32 event, void *data) +@@ -995,7 +1710,7 @@ static void dispatch_notify(acpi_handle handle, u32 event, void *data) + ibm->notify(ibm, event); + } + +-static int setup_notify(struct ibm_struct *ibm) ++static int __init setup_notify(struct ibm_struct *ibm) + { + acpi_status status; + int ret; +@@ -1020,17 +1735,15 @@ static int setup_notify(struct ibm_struct *ibm) + return -ENODEV; + } + +- ibm->notify_installed = 1; +- + return 0; + } + +-static int ibmacpi_device_add(struct acpi_device *device) ++static int __init ibm_device_add(struct acpi_device *device) + { + return 0; + } + +-static int register_driver(struct ibm_struct *ibm) ++static int __init register_driver(struct ibm_struct *ibm) + { + int ret; + +@@ -1043,7 +1756,7 @@ static int register_driver(struct ibm_struct *ibm) + memset(ibm->driver, 0, sizeof(struct acpi_driver)); + sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name); + ibm->driver->ids = ibm->hid; +- ibm->driver->ops.add = &ibmacpi_device_add; ++ ibm->driver->ops.add = &ibm_device_add; + + ret = acpi_bus_register_driver(ibm->driver); + if (ret < 0) { +@@ -1055,7 +1768,7 @@ static int register_driver(struct ibm_struct *ibm) + return ret; + } + +-static int ibm_init(struct ibm_struct *ibm) ++static int __init ibm_init(struct ibm_struct *ibm) + { + int ret; + struct proc_dir_entry *entry; +@@ -1071,31 +1784,34 @@ static int ibm_init(struct ibm_struct *ibm) + } + + if (ibm->init) { +- ret = ibm->init(ibm); ++ ret = ibm->init(); + if (ret != 0) + return ret; + ibm->init_called = 1; + } + +- 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; +- ibm->proc_created = 1; +- +- entry->data = ibm; +- if (ibm->read) ++ 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; ++ if (ibm->write) ++ entry->write_proc = &dispatch_write; ++ ibm->proc_created = 1; ++ } + + if (ibm->notify) { + ret = setup_notify(ibm); + if (ret < 0) + return ret; ++ ibm->notify_installed = 1; + } + + return 0; +@@ -1111,7 +1827,7 @@ static void ibm_exit(struct ibm_struct *ibm) + remove_proc_entry(ibm->name, proc_dir); + + if (ibm->init_called && ibm->exit) +- ibm->exit(ibm); ++ ibm->exit(); + + if (ibm->driver_registered) { + acpi_bus_unregister_driver(ibm->driver); +@@ -1119,60 +1835,66 @@ static void ibm_exit(struct ibm_struct *ibm) + } + } + +-static int ibm_handle_init(char *name, +- acpi_handle *handle, acpi_handle parent, +- char **paths, int num_paths, int required) ++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 30) +- return -ENOSPC; + +- strcpy(arg_with_comma, val); +- strcat(arg_with_comma, ","); ++ 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; ++ } + +- for (i=0; iname) == 0) +- return ibms[i].write(&ibms[i], arg_with_comma); +- BUG(); + return -EINVAL; + } + + #define IBM_PARAM(feature) \ + module_param_call(feature, set_ibm_param, NULL, NULL, 0) + ++IBM_PARAM(hotkey); ++IBM_PARAM(bluetooth); ++IBM_PARAM(video); ++IBM_PARAM(light); ++IBM_PARAM(dock); ++IBM_PARAM(bay); ++IBM_PARAM(cmos); ++IBM_PARAM(led); ++IBM_PARAM(beep); ++IBM_PARAM(ecdump); ++IBM_PARAM(brightness); ++IBM_PARAM(volume); ++IBM_PARAM(fan); ++ + static void acpi_ibm_exit(void) + { + int i; + +- for (i=NUM_IBMS-1; i>=0; i--) ++ for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) + ibm_exit(&ibms[i]); + + remove_proc_entry(IBM_DIR, acpi_root_dir); +@@ -1185,30 +1907,40 @@ static int __init acpi_ibm_init(void) + if (acpi_disabled) + return -ENODEV; + +- if (!acpi_specific_hotkey_enabled){ +- printk(IBM_ERR "Using generic hotkey driver\n"); +- return -ENODEV; +- } +- /* these handles are required */ +- if (IBM_HANDLE_INIT(ec, 1) < 0 || +- IBM_HANDLE_INIT(hkey, 1) < 0 || +- IBM_HANDLE_INIT(vid, 1) < 0 || +- IBM_HANDLE_INIT(beep, 1) < 0) ++ if (!acpi_specific_hotkey_enabled) { ++ printk(IBM_ERR "using generic hotkey driver\n"); + return -ENODEV; ++ } + +- /* these handles have alternatives */ +- IBM_HANDLE_INIT(lght, 0); +- if (IBM_HANDLE_INIT(cmos, !lght_handle) < 0) +- return -ENODEV; +- IBM_HANDLE_INIT(sysl, 0); +- if (IBM_HANDLE_INIT(led, !sysl_handle) < 0) ++ /* ec is required because many other handles are relative to it */ ++ IBM_HANDLE_INIT(ec); ++ if (!ec_handle) { ++ printk(IBM_ERR "ec object not found\n"); + return -ENODEV; ++ } + + /* these handles are not required */ +- IBM_HANDLE_INIT(dock, 0); +- IBM_HANDLE_INIT(bay, 0); +- IBM_HANDLE_INIT(bayej, 0); +- IBM_HANDLE_INIT(bled, 0); ++ 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); ++ IBM_HANDLE_INIT(dock); ++ IBM_HANDLE_INIT(pci); ++ IBM_HANDLE_INIT(bay); ++ if (bay_handle) ++ IBM_HANDLE_INIT(bay_ej); ++ IBM_HANDLE_INIT(bay2); ++ if (bay2_handle) ++ IBM_HANDLE_INIT(bay2_ej); ++ IBM_HANDLE_INIT(beep); ++ IBM_HANDLE_INIT(ecrd); ++ IBM_HANDLE_INIT(ecwr); ++ IBM_HANDLE_INIT(fans); ++ IBM_HANDLE_INIT(gfan); ++ IBM_HANDLE_INIT(sfan); + + proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); + if (!proc_dir) { +@@ -1216,9 +1948,11 @@ static int __init acpi_ibm_init(void) + return -ENODEV; + } + proc_dir->owner = THIS_MODULE; +- +- for (i=0; i= 0 && *ibms[i].param) ++ ret = ibms[i].write(ibms[i].param); + if (ret < 0) { + acpi_ibm_exit(); + return ret; +@@ -1230,17 +1964,3 @@ static int __init acpi_ibm_init(void) + + module_init(acpi_ibm_init); + module_exit(acpi_ibm_exit); +- +-MODULE_AUTHOR("Borislav Deianov"); +-MODULE_DESCRIPTION(IBM_DESC); +-MODULE_LICENSE("GPL"); +- +-IBM_PARAM(hotkey); +-IBM_PARAM(bluetooth); +-IBM_PARAM(video); +-IBM_PARAM(light); +-IBM_PARAM(dock); +-IBM_PARAM(bay); +-IBM_PARAM(cmos); +-IBM_PARAM(led); +-IBM_PARAM(beep); +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.18/0004-acpi-remove-dock-event-handling-from-ibm_acpi.txt b/releases/upstream/2.6.18/0004-acpi-remove-dock-event-handling-from-ibm_acpi.txt new file mode 100644 index 00000000000..0e0401f8402 --- /dev/null +++ b/releases/upstream/2.6.18/0004-acpi-remove-dock-event-handling-from-ibm_acpi.txt @@ -0,0 +1,92 @@ +From 63e5f248c4b748690b5180aa1b4b10eac51bb0e1 Mon Sep 17 00:00:00 2001 +From: Kristen Accardi +Date: Thu, 23 Feb 2006 17:56:06 -0800 +Subject: [PATCH 4/5] [PATCH] acpi: remove dock event handling from ibm_acpi + +Remove dock station support from ibm_acpi by default. This support has +been put into acpiphp instead. Allow ibm_acpi to continue to provide +docking station support via config option for laptops/docking stations +that are not supported by acpiphp. + +Signed-off-by: Kristen Carlson Accardi +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/ibm_acpi.c | 13 ++++++++++--- + 1 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c +index 5cc0903..262b1f4 100644 +--- a/drivers/acpi/ibm_acpi.c ++++ b/drivers/acpi/ibm_acpi.c +@@ -160,13 +160,13 @@ 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 + IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */ + "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */ + "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */ +@@ -844,7 +844,7 @@ static int _sta(acpi_handle handle) + + return status; + } +- ++#ifdef CONFIG_ACPI_IBM_DOCK + #define dock_docked() (_sta(dock_handle) & 1) + + static int dock_read(char *p) +@@ -907,6 +907,7 @@ static void dock_notify(struct ibm_struct *ibm, u32 event) + acpi_bus_generate_event(ibm->device, event, 0); /* unknown */ + } + } ++#endif + + static int bay_status_supported; + static int bay_status2_supported; +@@ -1574,6 +1575,7 @@ static struct ibm_struct ibms[] = { + .read = light_read, + .write = light_write, + }, ++#ifdef CONFIG_ACPI_IBM_DOCK + { + .name = "dock", + .read = dock_read, +@@ -1589,6 +1591,7 @@ static struct ibm_struct ibms[] = { + .handle = &pci_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, ++#endif + { + .name = "bay", + .init = bay_init, +@@ -1880,7 +1883,9 @@ IBM_PARAM(hotkey); + IBM_PARAM(bluetooth); + IBM_PARAM(video); + IBM_PARAM(light); ++#ifdef CONFIG_ACPI_IBM_DOCK + IBM_PARAM(dock); ++#endif + IBM_PARAM(bay); + IBM_PARAM(cmos); + IBM_PARAM(led); +@@ -1927,7 +1932,9 @@ static int __init acpi_ibm_init(void) + 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); + IBM_HANDLE_INIT(bay); + if (bay_handle) +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.18/0005-ACPI-Allow-a-WAN-module-enable-disable-on-a-Thinkpad-X60.txt b/releases/upstream/2.6.18/0005-ACPI-Allow-a-WAN-module-enable-disable-on-a-Thinkpad-X60.txt new file mode 100644 index 00000000000..9a90a1a76d8 --- /dev/null +++ b/releases/upstream/2.6.18/0005-ACPI-Allow-a-WAN-module-enable-disable-on-a-Thinkpad-X60.txt @@ -0,0 +1,111 @@ +From 42adb53cb36d19862a02d3087e2e3d9dab39e5fa Mon Sep 17 00:00:00 2001 +From: Jeremy Fitzhardinge +Date: Thu, 1 Jun 2006 17:41:00 -0400 +Subject: [PATCH 5/5] ACPI: Allow a WAN module enable/disable on a Thinkpad X60. + +The WAN (Sierra Wireless EV-DO) module is very similar to the +Bluetooth module. It appears on the USB bus when enabled. It can be +controlled via hot key, or directly via ACPI. This change enables +direct control via ACPI. + +I have tested it on my Lenovo Thinkpad X60; I guess it will probably +work on other Thinkpad models which come with this module installed. + +Signed-off-by: Jeremy Fitzhardinge +Ack'd by: Borislav Deianov +Signed-off-by: Len Brown +--- + drivers/acpi/ibm_acpi.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 70 insertions(+), 0 deletions(-) + +diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c +index 262b1f4..15fc124 100644 +--- a/drivers/acpi/ibm_acpi.c ++++ b/drivers/acpi/ibm_acpi.c +@@ -567,6 +567,69 @@ static int bluetooth_write(char *buf) + return 0; + } + ++static int wan_supported; ++ ++static int wan_init(void) ++{ ++ wan_supported = hkey_handle && ++ acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); ++ ++ return 0; ++} ++ ++static int wan_status(void) ++{ ++ int status; ++ ++ if (!wan_supported || ++ !acpi_evalf(hkey_handle, &status, "GWAN", "d")) ++ status = 0; ++ ++ return status; ++} ++ ++static int wan_read(char *p) ++{ ++ int len = 0; ++ int status = wan_status(); ++ ++ if (!wan_supported) ++ len += sprintf(p + len, "status:\t\tnot supported\n"); ++ else if (!(status & 1)) ++ len += sprintf(p + len, "status:\t\tnot installed\n"); ++ else { ++ len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); ++ len += sprintf(p + len, "commands:\tenable, disable\n"); ++ } ++ ++ return len; ++} ++ ++static int wan_write(char *buf) ++{ ++ int status = wan_status(); ++ char *cmd; ++ int do_cmd = 0; ++ ++ if (!wan_supported) ++ return -ENODEV; ++ ++ while ((cmd = next_cmd(&buf))) { ++ if (strlencmp(cmd, "enable") == 0) { ++ status |= 2; ++ } else if (strlencmp(cmd, "disable") == 0) { ++ status &= ~2; ++ } else ++ return -EINVAL; ++ do_cmd = 1; ++ } ++ ++ if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) ++ return -EIO; ++ ++ return 0; ++} ++ + static int video_supported; + static int video_orig_autosw; + +@@ -1563,6 +1626,13 @@ static struct ibm_struct ibms[] = { + .write = bluetooth_write, + }, + { ++ .name = "wan", ++ .init = wan_init, ++ .read = wan_read, ++ .write = wan_write, ++ .experimental = 1, ++ }, ++ { + .name = "video", + .init = video_init, + .read = video_read, +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.19/0001-Fix-typos-in-Documentation-Q-R.txt b/releases/upstream/2.6.19/0001-Fix-typos-in-Documentation-Q-R.txt new file mode 100644 index 00000000000..95621c17253 --- /dev/null +++ b/releases/upstream/2.6.19/0001-Fix-typos-in-Documentation-Q-R.txt @@ -0,0 +1,31 @@ +From d6bc8ac9e13e466e844313b590fbc49f7f1abdea Mon Sep 17 00:00:00 2001 +From: Matt LaPlante +Date: Tue, 3 Oct 2006 22:54:15 +0200 +Subject: [PATCH 1/5] Fix typos in Documentation/: 'Q'-'R' + +This patch fixes typos in various Documentation txts. The patch addresses +some words starting with the letters 'Q'-'R'. + +Signed-off-by: Matt LaPlante +Acked-by: Randy Dunlap +Signed-off-by: Adrian Bunk +--- + Documentation/ibm-acpi.txt | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt +index 8b3fd82..71aa403 100644 +--- a/Documentation/ibm-acpi.txt ++++ b/Documentation/ibm-acpi.txt +@@ -450,7 +450,7 @@ his laptop (the location of sensors may vary on other models): + + No commands can be written to this file. + +-EXPERIMENTAL: Embedded controller reigster dump -- /proc/acpi/ibm/ecdump ++EXPERIMENTAL: Embedded controller register dump -- /proc/acpi/ibm/ecdump + ------------------------------------------------------------------------ + + This feature is marked EXPERIMENTAL because the implementation +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.19/0002-ACPI-ibm_acpi-Remove-experimental-status-for-brightness-and-volume.txt b/releases/upstream/2.6.19/0002-ACPI-ibm_acpi-Remove-experimental-status-for-brightness-and-volume.txt new file mode 100644 index 00000000000..62da59a4ffc --- /dev/null +++ b/releases/upstream/2.6.19/0002-ACPI-ibm_acpi-Remove-experimental-status-for-brightness-and-volume.txt @@ -0,0 +1,36 @@ +From 4d6bd5ea4ec4991901a8cf5a586babef68e1fa3f Mon Sep 17 00:00:00 2001 +From: Stefan Schmidt +Date: Fri, 22 Sep 2006 12:19:14 +0200 +Subject: [PATCH 2/5] ACPI: ibm_acpi: Remove experimental status for brightness and volume. + +The brightness and volume features from ibm-acpi are stable. +The experimental flag is no longer needed. + +Signed-off-by: Stefan Schmidt +Acked-by: Borislav Deianov +Signed-off-by: Len Brown +--- + drivers/acpi/ibm_acpi.c | 2 -- + 1 files changed, 0 insertions(+), 2 deletions(-) + +diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c +index 15fc124..003a987 100644 +--- a/drivers/acpi/ibm_acpi.c ++++ b/drivers/acpi/ibm_acpi.c +@@ -1702,13 +1702,11 @@ static struct ibm_struct ibms[] = { + .name = "brightness", + .read = brightness_read, + .write = brightness_write, +- .experimental = 1, + }, + { + .name = "volume", + .read = volume_read, + .write = volume_write, +- .experimental = 1, + }, + { + .name = "fan", +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.19/0003-ACPI-ibm_acpi-Update-documentation-for-brightness-and-volume.txt b/releases/upstream/2.6.19/0003-ACPI-ibm_acpi-Update-documentation-for-brightness-and-volume.txt new file mode 100644 index 00000000000..15dedad53c8 --- /dev/null +++ b/releases/upstream/2.6.19/0003-ACPI-ibm_acpi-Update-documentation-for-brightness-and-volume.txt @@ -0,0 +1,64 @@ +From 24f7ff0af855ece60064a2532d8b316df02983c6 Mon Sep 17 00:00:00 2001 +From: Stefan Schmidt +Date: Fri, 22 Sep 2006 12:19:15 +0200 +Subject: [PATCH 3/5] ACPI: ibm_acpi: Update documentation for brightness and volume. + +Document the change of the experimental flag for brightness and volume. + +Signed-off-by: Stefan Schmidt +Acked-by: Borislav Deianov +Signed-off-by: Len Brown +--- + Documentation/ibm-acpi.txt | 22 ++++++---------------- + 1 files changed, 6 insertions(+), 16 deletions(-) + +diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt +index 71aa403..1672590 100644 +--- a/Documentation/ibm-acpi.txt ++++ b/Documentation/ibm-acpi.txt +@@ -30,8 +30,8 @@ detailed description): + - ACPI sounds + - temperature sensors + - Experimental: embedded controller register dump +- - Experimental: LCD brightness control +- - Experimental: volume control ++ - LCD brightness control ++ - Volume control + - Experimental: fan speed, fan enable/disable + + A compatibility table by model and feature is maintained on the web +@@ -523,13 +523,8 @@ 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.) + +-EXPERIMENTAL: LCD brightness control -- /proc/acpi/ibm/brightness +------------------------------------------------------------------ +- +-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. ++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 +@@ -542,13 +537,8 @@ commands are: + The number range is 0 to 7, although not all of them may be + distinct. The current brightness level is shown in the file. + +-EXPERIMENTAL: Volume control -- /proc/acpi/ibm/volume +------------------------------------------------------ +- +-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. ++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: +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.19/0004-ACPI-ibm_acpi-Documentation-the-wan-feature.txt b/releases/upstream/2.6.19/0004-ACPI-ibm_acpi-Documentation-the-wan-feature.txt new file mode 100644 index 00000000000..eec97f8de3e --- /dev/null +++ b/releases/upstream/2.6.19/0004-ACPI-ibm_acpi-Documentation-the-wan-feature.txt @@ -0,0 +1,53 @@ +From 28b779d127d3038ff83f42259d135a063b7cd848 Mon Sep 17 00:00:00 2001 +From: Stefan Schmidt +Date: Fri, 22 Sep 2006 12:19:16 +0200 +Subject: [PATCH 4/5] ACPI: ibm_acpi: Documentation the wan feature. + +Document the wan feature Jeremy Fitzhardinge added to ibm_acpi. + +Signed-off-by: Stefan Schmidt +Acked-by: Borislav Deianov +Signed-off-by: Len Brown +--- + Documentation/ibm-acpi.txt | 18 ++++++++++++++++++ + 1 files changed, 18 insertions(+), 0 deletions(-) + +diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt +index 1672590..00b8cf3 100644 +--- a/Documentation/ibm-acpi.txt ++++ b/Documentation/ibm-acpi.txt +@@ -33,6 +33,7 @@ detailed description): + - 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 +@@ -601,6 +602,23 @@ with the following command: + + echo 'level ' > /proc/acpi/ibm/thermal + ++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 + ------------------------------------ +-- +1.4.4.1 + diff --git a/releases/upstream/2.6.19/0005-ACPI-ibm_acpi-delete-obsolete-documentation.txt b/releases/upstream/2.6.19/0005-ACPI-ibm_acpi-delete-obsolete-documentation.txt new file mode 100644 index 00000000000..4ca9d661bd0 --- /dev/null +++ b/releases/upstream/2.6.19/0005-ACPI-ibm_acpi-delete-obsolete-documentation.txt @@ -0,0 +1,63 @@ +From 2fe6dffabb06bfa0591c8c490b092b458fba1f06 Mon Sep 17 00:00:00 2001 +From: Pavel Machek +Date: Thu, 31 Aug 2006 14:15:54 +0200 +Subject: [PATCH 5/5] ACPI: ibm_acpi: delete obsolete documentation + +As this module is now part of the kernel tree, there is no need +for instructions on how to download it and build an external module. + +Signed-off-by: Pavel Machek +Signed-off-by: Len Brown +--- + Documentation/ibm-acpi.txt | 35 +---------------------------------- + 1 files changed, 1 insertions(+), 34 deletions(-) + +diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt +index 00b8cf3..e50595b 100644 +--- a/Documentation/ibm-acpi.txt ++++ b/Documentation/ibm-acpi.txt +@@ -53,40 +53,7 @@ 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). The rest of this section describes +-how to install this driver when downloaded from the web site. +- +-First, you need to get a kernel with ACPI support up and running. +-Please refer to http://acpi.sourceforge.net/ for help with this +-step. How successful you will be depends a lot on you ThinkPad model, +-the kernel you are using and any additional patches applied. The +-kernel provided with your distribution may not be good enough. I +-needed to compile a 2.6.7 kernel with the 20040715 ACPI patch to get +-ACPI working reliably on my ThinkPad X40. Old ThinkPad models may not +-be supported at all. +- +-Assuming you have the basic ACPI support working (e.g. you can see the +-/proc/acpi directory), follow the following steps to install this +-driver: +- +- - unpack the archive: +- +- tar xzvf ibm-acpi-x.y.tar.gz; cd ibm-acpi-x.y +- +- - compile the driver: +- +- make +- +- - install the module in your kernel modules directory: +- +- make install +- +- - load the module: +- +- modprobe ibm_acpi +- +-After loading the module, check the "dmesg" output for any error messages. +- ++ACPI / IBM ThinkPad Laptop Extras). + + Features + -------- +-- +1.4.4.1 + diff --git a/releases/upstream/sunrise/Documentation/ibm-acpi.txt b/releases/upstream/sunrise/Documentation/ibm-acpi.txt new file mode 100644 index 00000000000..c437b1aeff5 --- /dev/null +++ b/releases/upstream/sunrise/Documentation/ibm-acpi.txt @@ -0,0 +1,474 @@ + IBM ThinkPad ACPI Extras Driver + + Version 0.8 + 8 November 2004 + + Borislav Deianov + http://ibm-acpi.sf.net/ + + +This is a Linux ACPI driver for the IBM ThinkPad laptops. It aims to +support various features of these laptops which are accessible through +the ACPI framework but not otherwise 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 + - Experimental: CMOS control + - Experimental: LED control + - Experimental: ACPI sounds + +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). The rest of this section describes +how to install this driver when downloaded from the web site. + +First, you need to get a kernel with ACPI support up and running. +Please refer to http://acpi.sourceforge.net/ for help with this +step. How successful you will be depends a lot on you ThinkPad model, +the kernel you are using and any additional patches applied. The +kernel provided with your distribution may not be good enough. I +needed to compile a 2.6.7 kernel with the 20040715 ACPI patch to get +ACPI working reliably on my ThinkPad X40. Old ThinkPad models may not +be supported at all. + +Assuming you have the basic ACPI support working (e.g. you can see the +/proc/acpi directory), follow the following steps to install this +driver: + + - unpack the archive: + + tar xzvf ibm-acpi-x.y.tar.gz; cd ibm-acpi-x.y + + - compile the driver: + + make + + - install the module in your kernel modules directory: + + make install + + - load the module: + + modprobe ibm_acpi + +After loading the module, check the "dmesg" output for any error messages. + + +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 sumulates 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. + +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: "ibm_acpi: dock device not present". 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: "ibm_acpi: bay +device not present". 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 Features +--------------------- + +The following features are marked experimental because using them +involves guessing the correct values of some parameters. Guessing +incorrectly may have undesirable effects like crashing your +ThinkPad. USE THESE WITH CAUTION! To activate them, you'll need to +supply the experimental=1 parameter when loading the module. + +Experimental: 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 appears that it can 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 numbers which are used internally by various models is 0 +to 21, but it's possible that numbers outside this range have +interesting behavior. 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 + +If you try this feature, please send me a report similar to the +above. On models which allow control of LCD brightness or sound +volume, I'd like to provide this functionality in an user-friendly +way, but first I need a way to identify the models which this is +possible. + +Experimental: 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 parameter is a non-negative integer. The range of LED +numbers used internally by various models is 0 to 7 but it's possible +that numbers outside this range are also valid. 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. + +If you try this feature, please send me a report similar to the +above. I'd like to provide this functionality in an user-friendly way, +but first I need to identify the which numbers correspond to which +LEDs on various models. + +Experimental: ACPI sounds - /proc/acpi/ibm/beep +----------------------------------------------- + +The BEEP method is used internally by the ACPI firmware to provide +audible alerts in various situtation. This feature allows the same +sounds to be triggered manually. + +The commands are non-negative integer numbers: + + echo 0 >/proc/acpi/ibm/beep + echo 1 >/proc/acpi/ibm/beep + echo 2 >/proc/acpi/ibm/beep + ... + +The range of numbers which are used internally by various models is 0 +to 17, but it's possible that numbers outside this range are also +valid. Here is the behavior on the X40: + + 2 - two beeps, pause, third beep + 3 - single beep + 4 - "unable" + 5 - single beep + 6 - "AC/DC" + 7 - high-pitched beep + 9 - three short beeps + 10 - very long beep + 12 - low-pitched beep + +(I've only been able to identify a couple of them). + +If you try this feature, please send me a report similar to the +above. I'd like to provide this functionality in an user-friendly way, +but first I need to identify the which numbers correspond to which +sounds on various models. + + +Multiple Command, 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 + + +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 + +Toan T Nguyen has written a SuSE powersave +script for the X20, included in config/usr/sbin/ibm_hotkeys_X20 + +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, included in +configs/etc/acpi/actions/blank-debian.sh + + +TODO +---- + +I'd like to implement the following features but haven't yet found the +time and/or I don't yet know how to implement them: + +- UltraBay floppy drive support + diff --git a/releases/upstream/sunrise/drivers/acpi/ibm_acpi.c b/releases/upstream/sunrise/drivers/acpi/ibm_acpi.c new file mode 100644 index 00000000000..0fb731a470d --- /dev/null +++ b/releases/upstream/sunrise/drivers/acpi/ibm_acpi.c @@ -0,0 +1,1242 @@ +/* + * ibm_acpi.c - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004 Borislav Deianov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Changelog: + * + * 2004-08-09 0.1 initial release, support for X series + * 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-17 0.3 support for R40 + * lcd off, brightness control + * thinklight on/off + * 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-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-10-19 0.6 use acpi_bus_register_driver() to claim HKEY device + * 2004-10-23 0.7 fix module loading on A21e, A22p, T20, T21, X20 + * fix LED control on A21e + * 2004-11-08 0.8 fix init error case, don't return from a macro + * thanks to Chris Wright + */ + +#define IBM_VERSION "0.8" + +#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/" + +#define IBM_DIR IBM_NAME + +#define IBM_LOG IBM_FILE ": " +#define IBM_ERR KERN_ERR IBM_LOG +#define IBM_NOTICE KERN_NOTICE IBM_LOG +#define IBM_INFO KERN_INFO IBM_LOG +#define IBM_DEBUG KERN_DEBUG IBM_LOG + +#define IBM_MAX_ACPI_ARGS 3 + +#define __unused __attribute__ ((unused)) + +static int experimental; +module_param(experimental, int, 0); + +static acpi_handle root_handle = NULL; + +#define IBM_HANDLE(object, parent, paths...) \ + static acpi_handle object##_handle; \ + static acpi_handle *object##_parent = &parent##_handle; \ + static char *object##_paths[] = { paths } + +IBM_HANDLE(ec, root, + "\\_SB.PCI0.ISA.EC", /* A21e, A22p, T20, T21, X20 */ + "\\_SB.PCI0.LPC.EC", /* all others */ +); + +IBM_HANDLE(vid, root, + "\\_SB.PCI0.VID", /* A21e, G40, X30, X40 */ + "\\_SB.PCI0.AGP.VID", /* all others */ +); + +IBM_HANDLE(cmos, root, + "\\UCMS", /* R50, R50p, R51, T4x, X31, X40 */ + "\\CMOS", /* A3x, G40, R32, T23, T30, X22, X24, X30 */ + "\\CMS", /* R40, R40e */ +); /* A21e, A22p, T20, T21, X20 */ + +IBM_HANDLE(dock, root, + "\\_SB.GDCK", /* X30, X31, X40 */ + "\\_SB.PCI0.DOCK", /* A22p, T20, T21, X20 */ + "\\_SB.PCI0.PCI1.DOCK", /* all others */ +); /* A21e, G40, R32, R40, R40e */ + +IBM_HANDLE(bay, root, + "\\_SB.PCI0.IDE0.SCND.MSTR"); /* all except A21e */ +IBM_HANDLE(bayej, root, + "\\_SB.PCI0.IDE0.SCND.MSTR._EJ0"); /* all except A2x, A3x */ + +IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A22p, T20, T21, X20 */ +IBM_HANDLE(hkey, ec, "HKEY"); /* all */ +IBM_HANDLE(led, ec, "LED"); /* all except A21e, A22p, T20, T21, X20 */ +IBM_HANDLE(sysl, ec, "SYSL"); /* A21e, A22p, T20, T21, X20 */ +IBM_HANDLE(bled, ec, "BLED"); /* A22p, T20, T21, X20 */ +IBM_HANDLE(beep, ec, "BEEP"); /* all models */ + +struct ibm_struct { + char *name; + + char *hid; + struct acpi_driver *driver; + + int (*init) (struct ibm_struct *); + int (*read) (struct ibm_struct *, char *); + int (*write) (struct ibm_struct *, char *); + void (*exit) (struct ibm_struct *); + + 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 supported; + union { + struct { + int status; + int mask; + } hotkey; + struct { + int autoswitch; + } video; + } state; + + int experimental; +}; + +static struct proc_dir_entry *proc_dir = NULL; + +#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off") +#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") +#define strlencmp(a,b) (strncmp((a), (b), strlen(b))) + +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...) +{ + char *fmt0 = fmt; + struct acpi_object_list params; + union acpi_object in_objs[IBM_MAX_ACPI_ARGS]; + struct acpi_buffer result; + 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); + + result.length = sizeof(out_obj); + result.pointer = &out_obj; + + status = acpi_evaluate_object(handle, method, ¶ms, &result); + + switch (res_type) { + case 'd': /* int */ + if (res) + *(int *)res = out_obj.integer.value; + success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; + break; + case 'v': /* void */ + success = status == AE_OK; + break; + /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", res_type); + return 0; + } + + if (!success && !quiet) + printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", + method, fmt0, status); + + return success; +} + +static void __unused acpi_print_int(acpi_handle handle, char *method) +{ + int i; + + if (acpi_evalf(handle, &i, method, "d")) + printk(IBM_INFO "%s = 0x%x\n", method, i); + else + printk(IBM_ERR "error calling %s\n", method); +} + +static char *next_cmd(char **cmds) +{ + char *start = *cmds; + char *end; + + while ((end = strchr(start, ',')) && end == start) + start = end + 1; + + if (!end) + return NULL; + + *end = 0; + *cmds = end + 1; + return start; +} + +static int driver_init(struct ibm_struct *ibm) +{ + printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); + printk(IBM_INFO "%s\n", IBM_URL); + + return 0; +} + +static int driver_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + + len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC); + len += sprintf(p + len, "version:\t%s\n", IBM_VERSION); + + return len; +} + +static int hotkey_get(struct ibm_struct *ibm, int *status, int *mask) +{ + if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) + return -EIO; + if (ibm->supported) { + if (!acpi_evalf(hkey_handle, mask, "DHKN", "qd")) + return -EIO; + } else { + *mask = ibm->state.hotkey.mask; + } + return 0; +} + +static int hotkey_set(struct ibm_struct *ibm, int status, int mask) +{ + int i; + + if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) + return -EIO; + + if (!ibm->supported) + return 0; + + for (i=0; i<32; i++) { + int bit = ((1 << i) & mask) != 0; + if (!acpi_evalf(hkey_handle, NULL, "MHKM", "vdd", i+1, bit)) + return -EIO; + } + + return 0; +} + +static int hotkey_init(struct ibm_struct *ibm) +{ + int ret; + + ibm->supported = 1; + ret = hotkey_get(ibm, + &ibm->state.hotkey.status, + &ibm->state.hotkey.mask); + if (ret < 0) { + /* mask not supported on A21e, A22p, T20, T21, X20, X22, X24 */ + ibm->supported = 0; + ret = hotkey_get(ibm, + &ibm->state.hotkey.status, + &ibm->state.hotkey.mask); + } + + return ret; +} + +static int hotkey_read(struct ibm_struct *ibm, char *p) +{ + int status, mask; + int len = 0; + + if (hotkey_get(ibm, &status, &mask) < 0) + return -EIO; + + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); + if (ibm->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(struct ibm_struct *ibm, char *buf) +{ + int status, mask; + char *cmd; + int do_cmd = 0; + + if (hotkey_get(ibm, &status, &mask) < 0) + return -ENODEV; + + 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 = ibm->state.hotkey.status; + mask = ibm->state.hotkey.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(ibm, status, mask) < 0) + return -EIO; + + return 0; +} + +static void hotkey_exit(struct ibm_struct *ibm) +{ + hotkey_set(ibm, ibm->state.hotkey.status, ibm->state.hotkey.mask); +} + +static void hotkey_notify(struct ibm_struct *ibm, u32 event) +{ + int hkey; + + if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) + acpi_bus_generate_event(ibm->device, event, hkey); + else { + printk(IBM_ERR "unknown hotkey event %d\n", event); + acpi_bus_generate_event(ibm->device, event, 0); + } +} + +static int bluetooth_init(struct ibm_struct *ibm) +{ + /* bluetooth not supported on A21e, G40, T20, T21, X20 */ + ibm->supported = acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); + + return 0; +} + +static int bluetooth_status(struct ibm_struct *ibm) +{ + int status; + + if (!ibm->supported || !acpi_evalf(hkey_handle, &status, "GBDC", "d")) + status = 0; + + return status; +} + +static int bluetooth_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + int status = bluetooth_status(ibm); + + if (!ibm->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(struct ibm_struct *ibm, char *buf) +{ + int status = bluetooth_status(ibm); + char *cmd; + int do_cmd = 0; + + if (!ibm->supported) + return -EINVAL; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status |= 2; + } else if (strlencmp(cmd, "disable") == 0) { + status &= ~2; + } else + return -EINVAL; + do_cmd = 1; + } + + if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) + return -EIO; + + return 0; +} + +static int video_init(struct ibm_struct *ibm) +{ + if (!acpi_evalf(vid_handle, + &ibm->state.video.autoswitch, "^VDEE", "d")) + return -ENODEV; + + return 0; +} + +static int video_status(struct ibm_struct *ibm) +{ + int status = 0; + int i; + + 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; + + if (acpi_evalf(vid_handle, &i, "^VDEE", "d")) + status |= 0x10 * (i & 1); + + return status; +} + +static int video_read(struct ibm_struct *ibm, char *p) +{ + int status = video_status(ibm); + int len = 0; + + len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); + len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); + len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); + len += sprintf(p + len, "auto:\t\t%s\n", enabled(status, 4)); + len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable, " + "crt_enable, crt_disable\n"); + len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable, " + "auto_enable, auto_disable\n"); + len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); + + return len; +} + +static int video_write(struct ibm_struct *ibm, char *buf) +{ + char *cmd; + int enable, disable, status; + + 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 (strlencmp(cmd, "dvi_enable") == 0) { + enable |= 0x08; + } else if (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) { + int autoswitch; + if (!acpi_evalf(vid_handle, &autoswitch, "^VDEE", "d")) + return -EIO; + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) + return -EIO; + if (!acpi_evalf(vid_handle, NULL, "VSWT", "v")) + return -EIO; + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", + autoswitch)) + return -EIO; + } else if (strlencmp(cmd, "expand_toggle") == 0) { + if (!acpi_evalf(NULL, NULL, "\\VEXP", "v")) + return -EIO; + } else + return -EINVAL; + } + + if (enable || disable) { + status = (video_status(ibm) & 0x0f & ~disable) | enable; + if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80)) + return -EIO; + if (!acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1)) + return -EIO; + } + + return 0; +} + +static void video_exit(struct ibm_struct *ibm) +{ + acpi_evalf(vid_handle, NULL, "_DOS", "vd", + ibm->state.video.autoswitch); +} + +static int light_init(struct ibm_struct *ibm) +{ + /* kblt not supported on G40, R32, X20 */ + ibm->supported = acpi_evalf(ec_handle, NULL, "KBLT", "qv"); + + return 0; +} + +static int light_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + int status = 0; + + if (ibm->supported) { + if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) + return -EIO; + len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); + } else + len += sprintf(p + len, "status:\t\tunknown\n"); + + len += sprintf(p + len, "commands:\ton, off\n"); + + return len; +} + +static int light_write(struct ibm_struct *ibm, char *buf) +{ + int cmos_cmd, lght_cmd; + char *cmd; + int success; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "on") == 0) { + cmos_cmd = 0x0c; + lght_cmd = 1; + } else if (strlencmp(cmd, "off") == 0) { + cmos_cmd = 0x0d; + lght_cmd = 0; + } else + return -EINVAL; + + success = cmos_handle ? + acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : + acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); + if (!success) + return -EIO; + } + + return 0; +} + +static int _sta(acpi_handle handle) +{ + int status; + + if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) + status = 0; + + return status; +} + +#define dock_docked() (_sta(dock_handle) & 1) + +static int dock_read(struct ibm_struct *ibm, 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(struct ibm_struct *ibm, char *buf) +{ + char *cmd; + + if (!dock_docked()) + return -EINVAL; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "undock") == 0) { + if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0)) + return -EIO; + if (!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(); + + 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 */ + } +} + +#define bay_occupied() (_sta(bay_handle) & 1) + +static int bay_init(struct ibm_struct *ibm) +{ + /* bay not supported on A21e, A22p, A31, A31p, G40, R32, R40e */ + ibm->supported = bay_handle && bayej_handle && + acpi_evalf(bay_handle, NULL, "_STA", "qv"); + + return 0; +} + +static int bay_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + int occupied = bay_occupied(); + + if (!ibm->supported) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!occupied) + len += sprintf(p + len, "status:\t\tunoccupied\n"); + else { + len += sprintf(p + len, "status:\t\toccupied\n"); + len += sprintf(p + len, "commands:\teject\n"); + } + + return len; +} + +static int bay_write(struct ibm_struct *ibm, char *buf) +{ + char *cmd; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "eject") == 0) { + if (!ibm->supported || + !acpi_evalf(bay_handle, NULL, "_EJ0", "vd", 1)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +static void bay_notify(struct ibm_struct *ibm, u32 event) +{ + acpi_bus_generate_event(ibm->device, event, 0); +} + +static int cmos_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + + /* cmos not supported on A21e, A22p, T20, T21, X20 */ + 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\n"); + } + + return len; +} + +static int cmos_write(struct ibm_struct *ibm, 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 set */ + } else + return -EINVAL; + + if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd)) + return -EIO; + } + + return 0; +} + +static int led_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + + len += sprintf(p + len, "commands:\t" + " on, off, blink\n"); + + return len; +} + +static int led_write(struct ibm_struct *ibm, char *buf) +{ + char *cmd; + unsigned int led; + int led_cmd, sysl_cmd, bled_a, bled_b; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%u", &led) != 1) + return -EINVAL; + + if (strstr(cmd, "blink")) { + led_cmd = 0xc0; + sysl_cmd = 2; + bled_a = 2; + bled_b = 1; + } else if (strstr(cmd, "on")) { + led_cmd = 0x80; + sysl_cmd = 1; + bled_a = 2; + bled_b = 0; + } else if (strstr(cmd, "off")) { + led_cmd = sysl_cmd = bled_a = bled_b = 0; + } else + return -EINVAL; + + if (led_handle) { + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + led, led_cmd)) + return -EIO; + } else if (led < 2) { + if (acpi_evalf(sysl_handle, NULL, NULL, "vdd", + led, sysl_cmd)) + return -EIO; + } else if (led == 2 && bled_handle) { + if (acpi_evalf(bled_handle, NULL, NULL, "vdd", + bled_a, bled_b)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +static int beep_read(struct ibm_struct *ibm, char *p) +{ + int len = 0; + + len += sprintf(p + len, "commands:\t\n"); + + return len; +} + +static int beep_write(struct ibm_struct *ibm, char *buf) +{ + char *cmd; + int beep_cmd; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%u", &beep_cmd) == 1) { + /* beep_cmd set */ + } else + return -EINVAL; + + if (!acpi_evalf(beep_handle, NULL, NULL, "vd", beep_cmd)) + return -EIO; + } + + return 0; +} + +static struct ibm_struct ibms[] = { + { + .name = "driver", + .init = driver_init, + .read = driver_read, + }, + { + .name = "hotkey", + .hid = "IBM0068", + .init = hotkey_init, + .read = hotkey_read, + .write = hotkey_write, + .exit = hotkey_exit, + .notify = hotkey_notify, + .handle = &hkey_handle, + .type = ACPI_DEVICE_NOTIFY, + }, + { + .name = "bluetooth", + .init = bluetooth_init, + .read = bluetooth_read, + .write = bluetooth_write, + }, + { + .name = "video", + .init = video_init, + .read = video_read, + .write = video_write, + .exit = video_exit, + }, + { + .name = "light", + .init = light_init, + .read = light_read, + .write = light_write, + }, + { + .name = "dock", + .read = dock_read, + .write = dock_write, + .notify = dock_notify, + .handle = &dock_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, + { + .name = "bay", + .init = bay_init, + .read = bay_read, + .write = bay_write, + .notify = bay_notify, + .handle = &bay_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, + { + .name = "cmos", + .read = cmos_read, + .write = cmos_write, + .experimental = 1, + }, + { + .name = "led", + .read = led_read, + .write = led_write, + .experimental = 1, + }, + { + .name = "beep", + .read = beep_read, + .write = beep_write, + .experimental = 1, + }, +}; +#define NUM_IBMS (sizeof(ibms)/sizeof(ibms[0])) + +static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct ibm_struct *ibm = (struct ibm_struct *)data; + int len; + + if (!ibm || !ibm->read) + return -EINVAL; + + len = ibm->read(ibm, page); + if (len < 0) + return len; + + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + + return len; +} + +static int dispatch_write(struct file *file, const char __user *userbuf, + unsigned long count, void *data) +{ + struct ibm_struct *ibm = (struct ibm_struct *)data; + char *kernbuf; + int ret; + + if (!ibm || !ibm->write) + return -EINVAL; + + kernbuf = kmalloc(count + 2, GFP_KERNEL); + if (!kernbuf) + return -ENOMEM; + + if (copy_from_user(kernbuf, userbuf, count)) { + kfree(kernbuf); + return -EFAULT; + } + + kernbuf[count] = 0; + strcat(kernbuf, ","); + ret = ibm->write(ibm, kernbuf); + if (ret == 0) + ret = count; + + kfree(kernbuf); + + return ret; +} + +static void dispatch_notify(acpi_handle handle, u32 event, void *data) +{ + struct ibm_struct *ibm = (struct ibm_struct *)data; + + if (!ibm || !ibm->notify) + return; + + ibm->notify(ibm, event); +} + +static int 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 0; + } + + acpi_driver_data(ibm->device) = ibm; + sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name); + + status = acpi_install_notify_handler(*ibm->handle, ibm->type, + dispatch_notify, ibm); + if (ACPI_FAILURE(status)) { + printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n", + ibm->name, status); + return -ENODEV; + } + + ibm->notify_installed = 1; + + return 0; +} + +static int device_add(struct acpi_device *device) +{ + return 0; +} + +static int register_driver(struct ibm_struct *ibm) +{ + int ret; + + ibm->driver = kmalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->driver) { + printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); + return -1; + } + + memset(ibm->driver, 0, sizeof(struct acpi_driver)); + sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name); + ibm->driver->ids = ibm->hid; + ibm->driver->ops.add = &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; +} + +static int ibm_init(struct ibm_struct *ibm) +{ + int ret; + struct proc_dir_entry *entry; + + if (ibm->experimental && !experimental) + return 0; + + if (ibm->hid) { + ret = register_driver(ibm); + if (ret < 0) + return ret; + ibm->driver_registered = 1; + } + + if (ibm->init) { + ret = ibm->init(ibm); + if (ret != 0) + return ret; + ibm->init_called = 1; + } + + 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; + ibm->proc_created = 1; + + entry->data = ibm; + if (ibm->read) + entry->read_proc = &dispatch_read; + if (ibm->write) + entry->write_proc = &dispatch_write; + + if (ibm->notify) { + ret = setup_notify(ibm); + 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(ibm); + + if (ibm->driver_registered) { + acpi_bus_unregister_driver(ibm->driver); + kfree(ibm->driver); + } +} + +static int ibm_handle_init(char *name, + acpi_handle *handle, acpi_handle parent, + char **paths, int num_paths, int required) +{ + int i; + acpi_status status; + + for (i=0; i 30) + return -ENOSPC; + + strcpy(arg_with_comma, val); + strcat(arg_with_comma, ","); + + for (i=0; iname) == 0) + return ibms[i].write(&ibms[i], arg_with_comma); + BUG(); + return -EINVAL; +} + +#define IBM_PARAM(feature) \ + module_param_call(feature, set_ibm_param, NULL, NULL, 0) + +static void acpi_ibm_exit(void) +{ + int i; + + for (i=NUM_IBMS-1; i>=0; i--) + ibm_exit(&ibms[i]); + + remove_proc_entry(IBM_DIR, acpi_root_dir); +} + +static int __init acpi_ibm_init(void) +{ + int ret, i; + + if (acpi_disabled) + return -ENODEV; + + /* these handles are required */ + if (IBM_HANDLE_INIT(ec, 1) < 0 || + IBM_HANDLE_INIT(hkey, 1) < 0 || + IBM_HANDLE_INIT(vid, 1) < 0 || + IBM_HANDLE_INIT(beep, 1) < 0) + return -ENODEV; + + /* these handles have alternatives */ + IBM_HANDLE_INIT(lght, 0); + if (IBM_HANDLE_INIT(cmos, !lght_handle) < 0) + return -ENODEV; + IBM_HANDLE_INIT(sysl, 0); + if (IBM_HANDLE_INIT(led, !sysl_handle) < 0) + return -ENODEV; + + /* these handles are not required */ + IBM_HANDLE_INIT(dock, 0); + IBM_HANDLE_INIT(bay, 0); + IBM_HANDLE_INIT(bayej, 0); + IBM_HANDLE_INIT(bled, 0); + + proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); + if (!proc_dir) { + printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); + return -ENODEV; + } + proc_dir->owner = THIS_MODULE; + + for (i=0; i