[ACPI] IBM ThinkPad ACPI Extras Driver v0.12
authorBorislav Deianov <borislav@users.sf.net>
Wed, 17 Aug 2005 04:00:00 +0000 (00:00 -0400)
committerLen Brown <len.brown@intel.com>
Thu, 25 Aug 2005 21:37:51 +0000 (17:37 -0400)
http://ibm-acpi.sf.net/

Signed-off-by: Borislav Deianov <borislav@users.sf.net>
Signed-off-by: Len Brown <len.brown@intel.com>
Documentation/ibm-acpi.txt
drivers/acpi/ibm_acpi.c

index c437b1a..8b3fd82 100644 (file)
@@ -1,16 +1,16 @@
                    IBM ThinkPad ACPI Extras Driver
 
-                            Version 0.8
-                          8 November 2004
+                            Version 0.12
+                           17 August 2005
 
                Borislav Deianov <borislav@users.sf.net>
                      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):
+
+These models do not have a button near the UltraBay device to request
+a hot eject but rather require the laptop to be put to sleep
+(suspend-to-ram) before the bay device is ejected or inserted).
+The sequence of steps to eject the device is as follows:
+
+       echo eject > /proc/acpi/ibm/bay
+       put the ThinkPad to sleep
+       remove the drive
+       resume from sleep
+       cat /proc/acpi/ibm/bay should show that the drive was removed
+
+On the A3x, both the UltraBay 2000 and UltraBay Plus devices are
+supported. Use "eject2" instead of "eject" for the second bay.
 
-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.
+Note: the UltraBay eject support on the 600e/x, A22p and A3x is
+EXPERIMENTAL and may not work as expected. USE WITH CAUTION!
 
-Experimental: CMOS control - /proc/acpi/ibm/cmos
-------------------------------------------------
+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 <led number> on >/proc/acpi/ibm/led
-       echo <led number> off >/proc/acpi/ibm/led
-       echo <led number> blink >/proc/acpi/ibm/led
+       echo '<led number> on' >/proc/acpi/ibm/led
+       echo '<led number> off' >/proc/acpi/ibm/led
+       echo '<led number> blink' >/proc/acpi/ibm/led
 
-The <led number> 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 <led number> 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 <number> >/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 <number> 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
+       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 <level>' >/proc/acpi/ibm/brightness
+
+The <level> 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 <level>' >/proc/acpi/ibm/volume
+
+The <level> 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 <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:
 
-(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.
+       echo 'level <level>' > /proc/acpi/ibm/thermal
 
 
-Multiple Command, Module Parameters
------------------------------------
+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 <ntt@control.uchicago.edu> has written a SuSE powersave
-script for the X20, included in config/usr/sbin/ibm_hotkeys_X20
+Toan T Nguyen <ntt@physics.ucla.edu> 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 <brix@gentoo.org> 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 <dws@ee.eth.ch> 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.
index ad85e10..5cc0903 100644 (file)
@@ -2,7 +2,7 @@
  *  ibm_acpi.c - IBM ThinkPad ACPI Extras
  *
  *
- *  Copyright (C) 2004 Borislav Deianov
+ *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
  *
  *  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
  *  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 <lentinj@dial.pipex.com>
+ *                     support for 770e, G41
+ *                     G40 and G41 don't have a thinklight
+ *                     temperatures no longer experimental
+ *                     experimental brightness control
+ *                     experimental volume control
+ *                     experimental fan enable/disable
+ *  2005-01-16 0.10    fix module loading on R30, R31 
+ *  2005-01-16 0.9     support for 570, R30, R31
+ *                     ultrabay support on A22p, A3x
+ *                     limit arg for cmos, led, beep, drop experimental status
+ *                     more capable led control on A21e, A22p, T20-22, X20
+ *                     experimental temperatures and fan speed
+ *                     experimental embedded controller register dump
+ *                     mark more functions as __init, drop incorrect __exit
+ *                     use MODULE_VERSION
+ *                         thanks to Henrik Brix Andersen <brix@gentoo.org>
+ *                     fix parameter passing on module loading
+ *                         thanks to Rusty Russell <rusty@rustcorp.com.au>
+ *                         thanks to Jim Radford <radford@blackbean.org>
+ *  2004-11-08 0.8     fix init error case, don't return from a macro
+ *                         thanks to Chris Wright <chrisw@osdl.org>
+ *  2004-10-23 0.7     fix module loading on A21e, A22p, T20, T21, X20
+ *                     fix led control on A21e
+ *  2004-10-19 0.6     use acpi_bus_register_driver() to claim HKEY device
  *  2004-10-18 0.5     thinklight support on A21e, G40, R32, T20, T21, X20
  *                     proc file format changed
  *                     video_switch command
  *                     experimental cmos control
  *                     experimental led control
  *                     experimental acpi sounds
- *  2004-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 <chrisw@osdl.org>
+ *  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 <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #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, &params, &result);
+       status = acpi_evaluate_object(handle, method, &params, 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, <mask>\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<int>\n");
+               len += sprintf(p + len, "commands:\t<cmd> (<cmd> 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"
-                      "<int> on, <int> off, <int> blink\n");
+                      "<led> on, <led> off, <led> blink (<led> 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<cmd> (<cmd> is 0-17)\n");
+       }
+
+       return len;
+}
+
+static int beep_write(char *buf)
+{
+       char *cmd;
+       int beep_cmd;
+
+       if (!beep_handle)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
+                   beep_cmd >= 0 && beep_cmd <= 17) {
+                       /* beep_cmd set */
+               } else
+                       return -EINVAL;
+               if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static int acpi_ec_read(int i, u8 * p)
+{
+       int v;
+
+       if (ecrd_handle) {
+               if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
+                       return 0;
+               *p = v;
+       } else {
+               if (ec_read(i, p) < 0)
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int acpi_ec_write(int i, u8 v)
+{
+       if (ecwr_handle) {
+               if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
+                       return 0;
+       } else {
+               if (ec_write(i, v) < 0)
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int thermal_tmp_supported;
+static int thermal_updt_supported;
+
+static int thermal_init(void)
+{
+       /* temperatures not supported on 570, G4x, R30, R31, R32 */
+       thermal_tmp_supported = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
+
+       /* 600e/x, 770e, 770x */
+       thermal_updt_supported = acpi_evalf(ec_handle, NULL, "UPDT", "qv");
+
+       return 0;
+}
+
+static int thermal_read(char *p)
+{
+       int len = 0;
+
+       if (!thermal_tmp_supported)
+               len += sprintf(p + len, "temperatures:\tnot supported\n");
+       else {
+               int i, t;
+               char tmpi[] = "TMPi";
+               s8 tmp[8];
+
+               if (thermal_updt_supported)
+                       if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+                               return -EIO;
+
+               for (i = 0; i < 8; i++) {
+                       tmpi[3] = '0' + i;
+                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+                               return -EIO;
+                       if (thermal_updt_supported)
+                               tmp[i] = (t - 2732 + 5) / 10;
+                       else
+                               tmp[i] = t;
+               }
+
+               len += sprintf(p + len,
+                              "temperatures:\t%d %d %d %d %d %d %d %d\n",
+                              tmp[0], tmp[1], tmp[2], tmp[3],
+                              tmp[4], tmp[5], tmp[6], tmp[7]);
+       }
+
+       return len;
+}
+
+static u8 ecdump_regs[256];
+
+static int ecdump_read(char *p)
+{
+       int len = 0;
+       int i, j;
+       u8 v;
+
+       len += sprintf(p + len, "EC      "
+                      " +00 +01 +02 +03 +04 +05 +06 +07"
+                      " +08 +09 +0a +0b +0c +0d +0e +0f\n");
+       for (i = 0; i < 256; i += 16) {
+               len += sprintf(p + len, "EC 0x%02x:", i);
+               for (j = 0; j < 16; j++) {
+                       if (!acpi_ec_read(i + j, &v))
+                               break;
+                       if (v != ecdump_regs[i + j])
+                               len += sprintf(p + len, " *%02x", v);
+                       else
+                               len += sprintf(p + len, "  %02x", v);
+                       ecdump_regs[i + j] = v;
+               }
+               len += sprintf(p + len, "\n");
+               if (j != 16)
+                       break;
+       }
+
+       /* These are way too dangerous to advertise openly... */
+#if 0
+       len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
+                      " (<offset> is 00-ff, <value> is 00-ff)\n");
+       len += sprintf(p + len, "commands:\t0x<offset> <value>  "
+                      " (<offset> is 00-ff, <value> is 0-255)\n");
+#endif
+       return len;
+}
+
+static int ecdump_write(char *buf)
+{
+       char *cmd;
+       int i, v;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) {
+                       /* i and v set */
+               } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) {
+                       /* i and v set */
+               } else
+                       return -EINVAL;
+               if (i >= 0 && i < 256 && v >= 0 && v < 256) {
+                       if (!acpi_ec_write(i, v))
                                return -EIO;
                } else
                        return -EINVAL;
        }
 
        return 0;
-}      
-               
-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<int>\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 <level>"
+                              " (<level> 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 <level>"
+                              " (<level> is 0-15)\n");
+       }
+
+       return len;
+}
+
+#define VOLUME_DOWN    0
+#define VOLUME_UP      1
+#define VOLUME_MUTE    2
+
+static int volume_write(char *buf)
+{
+       int cmos_cmd, inc, i;
+       u8 level, mute;
+       int new_level, new_mute;
+       char *cmd;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (!acpi_ec_read(volume_offset, &level))
+                       return -EIO;
+               new_mute = mute = level & 0x40;
+               new_level = level = level & 0xf;
+
+               if (strlencmp(cmd, "up") == 0) {
+                       if (mute)
+                               new_mute = 0;
+                       else
+                               new_level = level == 15 ? 15 : level + 1;
+               } else if (strlencmp(cmd, "down") == 0) {
+                       if (mute)
+                               new_mute = 0;
+                       else
+                               new_level = level == 0 ? 0 : level - 1;
+               } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
+                          new_level >= 0 && new_level <= 15) {
+                       /* new_level set */
+               } else if (strlencmp(cmd, "mute") == 0) {
+                       new_mute = 0x40;
                } else
                        return -EINVAL;
 
-               if (!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 <level>"
+                              " (<level> is 0-7)\n");
+       if (!gfan_handle)
+               /* all except 570, 600e/x, 770e, 770x */
+               len += sprintf(p + len, "commands:\tenable, disable\n");
+       if (fans_handle)
+               /* X31, X40 */
+               len += sprintf(p + len, "commands:\tspeed <speed>"
+                              " (<speed> is 0-65535)\n");
+
+       return len;
+}
+
+static int fan_write(char *buf)
+{
+       char *cmd;
+       int level, speed;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sfan_handle &&
+                   sscanf(cmd, "level %d", &level) == 1 &&
+                   level >= 0 && level <= 7) {
+                       /* 570, 770x-JL */
+                       if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
+                               return -EIO;
+               } else if (!gfan_handle && strlencmp(cmd, "enable") == 0) {
+                       /* all except 570, 600e/x, 770e, 770x */
+                       if (!acpi_ec_write(fan_status_offset, 0x80))
+                               return -EIO;
+               } else if (!gfan_handle && strlencmp(cmd, "disable") == 0) {
+                       /* all except 570, 600e/x, 770e, 770x */
+                       if (!acpi_ec_write(fan_status_offset, 0x00))
+                               return -EIO;
+               } else if (fans_handle &&
+                          sscanf(cmd, "speed %d", &speed) == 1 &&
+                          speed >= 0 && speed <= 65535) {
+                       /* X31, X40 */
+                       if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
+                                       speed, speed, speed))
+                               return -EIO;
+               } else
+                       return -EINVAL;
        }
 
        return 0;
-}      
-               
+}
+
 static struct ibm_struct ibms[] = {
        {
-               .name   = "driver",
-               .init   = driver_init,
-               .read   = driver_read,
-       },
+        .name = "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<num_paths; i++) {
+       for (i = 0; i < num_paths; i++) {
                status = acpi_get_handle(parent, paths[i], handle);
-               if (ACPI_SUCCESS(status))
-                       return 0;
-       }
-       
-       *handle = NULL;
-
-       if (required) {
-               printk(IBM_ERR "%s object not found\n", name);
-               return -1;
+               if (ACPI_SUCCESS(status)) {
+                       *path = paths[i];
+                       return;
+               }
        }
 
-       return 0;
+       *handle = NULL;
 }
 
-#define IBM_HANDLE_INIT(object, required)                              \
+#define IBM_HANDLE_INIT(object)                                                \
        ibm_handle_init(#object, &object##_handle, *object##_parent,    \
-               object##_paths, sizeof(object##_paths)/sizeof(char*), required)
-
+               object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
 
 static int set_ibm_param(const char *val, struct kernel_param *kp)
 {
        unsigned int i;
-       char arg_with_comma[32];
-
-       if (strlen(val) > 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; i<NUM_IBMS; i++)
-               if (strcmp(ibms[i].name, kp->name) == 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<NUM_IBMS; i++) {
+
+       for (i = 0; i < ARRAY_SIZE(ibms); i++) {
                ret = ibm_init(&ibms[i]);
+               if (ret >= 0 && *ibms[i].param)
+                       ret = ibms[i].write(ibms[i].param);
                if (ret < 0) {
                        acpi_ibm_exit();
                        return ret;
@@ -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);