Pull misc-for-upstream into release branch
authorLen Brown <len.brown@intel.com>
Sun, 29 Apr 2007 03:12:03 +0000 (23:12 -0400)
committerLen Brown <len.brown@intel.com>
Sun, 29 Apr 2007 03:12:03 +0000 (23:12 -0400)
25 files changed:
Documentation/ibm-acpi.txt [deleted file]
Documentation/sony-laptop.txt
Documentation/thinkpad-acpi.txt [new file with mode: 0644]
Documentation/video4linux/meye.txt
MAINTAINERS
Makefile
block/cfq-iosched.c
drivers/acpi/Kconfig
drivers/acpi/Makefile
drivers/acpi/ibm_acpi.c [deleted file]
drivers/block/pktcdvd.c
drivers/char/sonypi.c
drivers/media/video/Kconfig
drivers/media/video/meye.c
drivers/media/video/meye.h
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/asus-laptop.c
drivers/misc/sony-laptop.c
drivers/misc/thinkpad_acpi.c [new file with mode: 0644]
drivers/misc/thinkpad_acpi.h [new file with mode: 0644]
drivers/parport/parport_sunbpp.c
drivers/sbus/char/openprom.c
include/linux/sony-laptop.h [new file with mode: 0644]
net/ipv4/fib_frontend.c

diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt
deleted file mode 100644 (file)
index 0132d36..0000000
+++ /dev/null
@@ -1,728 +0,0 @@
-                   IBM ThinkPad ACPI Extras Driver
-
-                            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 supports
-various features of these laptops which are accessible through the
-ACPI framework but not otherwise supported by the generic Linux ACPI
-drivers.
-
-
-Status
-------
-
-The features currently supported are the following (see below for
-detailed description):
-
-       - Fn key combinations
-       - Bluetooth enable and disable
-       - video output switching, expansion control     
-       - ThinkLight on and off
-       - limited docking and undocking
-       - UltraBay eject
-       - CMOS control
-       - LED control
-       - ACPI sounds
-       - temperature sensors
-       - Experimental: embedded controller register dump
-       - LCD brightness control
-       - Volume control
-       - Experimental: fan speed, fan enable/disable
-       - Experimental: WAN enable and disable
-
-A compatibility table by model and feature is maintained on the web
-site, http://ibm-acpi.sf.net/. I appreciate any success or failure
-reports, especially if they add to or correct the compatibility table.
-Please include the following information in your report:
-
-       - ThinkPad model name
-       - a copy of your DSDT, from /proc/acpi/dsdt
-       - which driver features work and which don't
-       - the observed behavior of non-working features
-
-Any other comments or patches are also more than welcome.
-
-
-Installation
-------------
-
-If you are compiling this driver as included in the Linux kernel
-sources, simply enable the CONFIG_ACPI_IBM option (Power Management /
-ACPI / IBM ThinkPad Laptop Extras).
-
-Features
---------
-
-The driver creates the /proc/acpi/ibm directory. There is a file under
-that directory for each feature described below. Note that while the
-driver is still in the alpha stage, the exact proc file format and
-commands supported by the various features is guaranteed to change
-frequently.
-
-Driver version -- /proc/acpi/ibm/driver
----------------------------------------
-
-The driver name and version. No commands can be written to this file.
-
-Hot keys -- /proc/acpi/ibm/hotkey
----------------------------------
-
-Without this driver, only the Fn-F4 key (sleep button) generates an
-ACPI event. With the driver loaded, the hotkey feature enabled and the
-mask set (see below), the various hot keys generate ACPI events in the
-following format:
-
-       ibm/hotkey HKEY 00000080 0000xxxx
-
-The last four digits vary depending on the key combination pressed.
-All labeled Fn-Fx key combinations generate distinct events. In
-addition, the lid microswitch and some docking station buttons may
-also generate such events.
-
-The following commands can be written to this file:
-
-       echo enable > /proc/acpi/ibm/hotkey -- enable the hot keys feature
-       echo disable > /proc/acpi/ibm/hotkey -- disable the hot keys feature
-       echo 0xffff > /proc/acpi/ibm/hotkey -- enable all possible hot keys
-       echo 0x0000 > /proc/acpi/ibm/hotkey -- disable all possible hot keys
-       ... any other 4-hex-digit mask ...
-       echo reset > /proc/acpi/ibm/hotkey -- restore the original mask
-
-The bit mask allows some control over which hot keys generate ACPI
-events. Not all bits in the mask can be modified. Not all bits that
-can be modified do anything. Not all hot keys can be individually
-controlled by the mask. Most recent ThinkPad models honor the
-following bits (assuming the hot keys feature has been enabled):
-
-       key     bit     behavior when set       behavior when unset
-
-       Fn-F3                   always generates ACPI event
-       Fn-F4                   always generates ACPI event
-       Fn-F5   0010    generate ACPI event     enable/disable Bluetooth
-       Fn-F7   0040    generate ACPI event     switch LCD and external display
-       Fn-F8   0080    generate ACPI event     expand screen or none
-       Fn-F9   0100    generate ACPI event     none
-       Fn-F12                  always generates ACPI event
-
-Some models do not support all of the above. For example, the T30 does
-not support Fn-F5 and Fn-F9. Other models do not support the mask at
-all. On those models, hot keys cannot be controlled individually.
-
-Note that enabling ACPI events for some keys prevents their default
-behavior. For example, if events for Fn-F5 are enabled, that key will
-no longer enable/disable Bluetooth by itself. This can still be done
-from an acpid handler for the ibm/hotkey event.
-
-Note also that not all Fn key combinations are supported through
-ACPI. For example, on the X40, the brightness, volume and "Access IBM"
-buttons do not generate ACPI events even with this driver. They *can*
-be used through the "ThinkPad Buttons" utility, see
-http://www.nongnu.org/tpb/
-
-Bluetooth -- /proc/acpi/ibm/bluetooth
--------------------------------------
-
-This feature shows the presence and current state of a Bluetooth
-device. If Bluetooth is installed, the following commands can be used:
-
-       echo enable > /proc/acpi/ibm/bluetooth
-       echo disable > /proc/acpi/ibm/bluetooth
-
-Video output control -- /proc/acpi/ibm/video
---------------------------------------------
-
-This feature allows control over the devices used for video output -
-LCD, CRT or DVI (if available). The following commands are available:
-
-       echo lcd_enable > /proc/acpi/ibm/video
-       echo lcd_disable > /proc/acpi/ibm/video
-       echo crt_enable > /proc/acpi/ibm/video
-       echo crt_disable > /proc/acpi/ibm/video
-       echo dvi_enable > /proc/acpi/ibm/video
-       echo dvi_disable > /proc/acpi/ibm/video
-       echo auto_enable > /proc/acpi/ibm/video
-       echo auto_disable > /proc/acpi/ibm/video
-       echo expand_toggle > /proc/acpi/ibm/video
-       echo video_switch > /proc/acpi/ibm/video
-
-Each video output device can be enabled or disabled individually.
-Reading /proc/acpi/ibm/video shows the status of each device.
-
-Automatic video switching can be enabled or disabled.  When automatic
-video switching is enabled, certain events (e.g. opening the lid,
-docking or undocking) cause the video output device to change
-automatically. While this can be useful, it also causes flickering
-and, on the X40, video corruption. By disabling automatic switching,
-the flickering or video corruption can be avoided.
-
-The video_switch command cycles through the available video outputs
-(it simulates the behavior of Fn-F7).
-
-Video expansion can be toggled through this feature. This controls
-whether the display is expanded to fill the entire LCD screen when a
-mode with less than full resolution is used. Note that the current
-video expansion status cannot be determined through this feature.
-
-Note that on many models (particularly those using Radeon graphics
-chips) the X driver configures the video card in a way which prevents
-Fn-F7 from working. This also disables the video output switching
-features of this driver, as it uses the same ACPI methods as
-Fn-F7. Video switching on the console should still work.
-
-UPDATE: There's now a patch for the X.org Radeon driver which
-addresses this issue. Some people are reporting success with the patch
-while others are still having problems. For more information:
-
-https://bugs.freedesktop.org/show_bug.cgi?id=2000
-
-ThinkLight control -- /proc/acpi/ibm/light
-------------------------------------------
-
-The current status of the ThinkLight can be found in this file. A few
-models which do not make the status available will show it as
-"unknown". The available commands are:
-
-       echo on  > /proc/acpi/ibm/light
-       echo off > /proc/acpi/ibm/light
-
-Docking / undocking -- /proc/acpi/ibm/dock
-------------------------------------------
-
-Docking and undocking (e.g. with the X4 UltraBase) requires some
-actions to be taken by the operating system to safely make or break
-the electrical connections with the dock.
-
-The docking feature of this driver generates the following ACPI events:
-
-       ibm/dock GDCK 00000003 00000001 -- eject request
-       ibm/dock GDCK 00000003 00000002 -- undocked
-       ibm/dock GDCK 00000000 00000003 -- docked
-
-NOTE: These events will only be generated if the laptop was docked
-when originally booted. This is due to the current lack of support for
-hot plugging of devices in the Linux ACPI framework. If the laptop was
-booted while not in the dock, the following message is shown in the
-logs:
-
-       Mar 17 01:42:34 aero kernel: ibm_acpi: dock device not present
-
-In this case, no dock-related events are generated but the dock and
-undock commands described below still work. They can be executed
-manually or triggered by Fn key combinations (see the example acpid
-configuration files included in the driver tarball package available
-on the web site).
-
-When the eject request button on the dock is pressed, the first event
-above is generated. The handler for this event should issue the
-following command:
-
-       echo undock > /proc/acpi/ibm/dock
-
-After the LED on the dock goes off, it is safe to eject the laptop.
-Note: if you pressed this key by mistake, go ahead and eject the
-laptop, then dock it back in. Otherwise, the dock may not function as
-expected.
-
-When the laptop is docked, the third event above is generated. The
-handler for this event should issue the following command to fully
-enable the dock:
-
-       echo dock > /proc/acpi/ibm/dock
-
-The contents of the /proc/acpi/ibm/dock file shows the current status
-of the dock, as provided by the ACPI framework.
-
-The docking support in this driver does not take care of enabling or
-disabling any other devices you may have attached to the dock. For
-example, a CD drive plugged into the UltraBase needs to be disabled or
-enabled separately. See the provided example acpid configuration files
-for how this can be accomplished.
-
-There is no support yet for PCI devices that may be attached to a
-docking station, e.g. in the ThinkPad Dock II. The driver currently
-does not recognize, enable or disable such devices. This means that
-the only docking stations currently supported are the X-series
-UltraBase docks and "dumb" port replicators like the Mini Dock (the
-latter don't need any ACPI support, actually).
-
-UltraBay eject -- /proc/acpi/ibm/bay
-------------------------------------
-
-Inserting or ejecting an UltraBay device requires some actions to be
-taken by the operating system to safely make or break the electrical
-connections with the device.
-
-This feature generates the following ACPI events:
-
-       ibm/bay MSTR 00000003 00000000 -- eject request
-       ibm/bay MSTR 00000001 00000000 -- eject lever inserted
-
-NOTE: These events will only be generated if the UltraBay was present
-when the laptop was originally booted (on the X series, the UltraBay
-is in the dock, so it may not be present if the laptop was undocked).
-This is due to the current lack of support for hot plugging of devices
-in the Linux ACPI framework. If the laptop was booted without the
-UltraBay, the following message is shown in the logs:
-
-       Mar 17 01:42:34 aero kernel: ibm_acpi: bay device not present
-
-In this case, no bay-related events are generated but the eject
-command described below still works. It can be executed manually or
-triggered by a hot key combination.
-
-Sliding the eject lever generates the first event shown above. The
-handler for this event should take whatever actions are necessary to
-shut down the device in the UltraBay (e.g. call idectl), then issue
-the following command:
-
-       echo eject > /proc/acpi/ibm/bay
-
-After the LED on the UltraBay goes off, it is safe to pull out the
-device.
-
-When the eject lever is inserted, the second event above is
-generated. The handler for this event should take whatever actions are
-necessary to enable the UltraBay device (e.g. call idectl).
-
-The contents of the /proc/acpi/ibm/bay file shows the current status
-of the UltraBay, as provided by the ACPI framework.
-
-EXPERIMENTAL warm eject support on the 600e/x, A22p and A3x (To use
-this feature, you need to supply the experimental=1 parameter when
-loading the module):
-
-These models do not have a button near the UltraBay device to request
-a hot eject but rather require the laptop to be put to sleep
-(suspend-to-ram) before the bay device is ejected or inserted).
-The sequence of steps to eject the device is as follows:
-
-       echo eject > /proc/acpi/ibm/bay
-       put the ThinkPad to sleep
-       remove the drive
-       resume from sleep
-       cat /proc/acpi/ibm/bay should show that the drive was removed
-
-On the A3x, both the UltraBay 2000 and UltraBay Plus devices are
-supported. Use "eject2" instead of "eject" for the second bay.
-
-Note: the UltraBay eject support on the 600e/x, A22p and A3x is
-EXPERIMENTAL and may not work as expected. USE WITH CAUTION!
-
-CMOS control -- /proc/acpi/ibm/cmos
------------------------------------
-
-This feature is used internally by the ACPI firmware to control the
-ThinkLight on most newer ThinkPad models. It may also control LCD
-brightness, sounds volume and more, but only on some models.
-
-The commands are non-negative integer numbers:
-
-       echo 0 >/proc/acpi/ibm/cmos
-       echo 1 >/proc/acpi/ibm/cmos
-       echo 2 >/proc/acpi/ibm/cmos
-       ...
-
-The range of valid numbers is 0 to 21, but not all have an effect and
-the behavior varies from model to model. Here is the behavior on the
-X40 (tpb is the ThinkPad Buttons utility):
-
-       0 - no effect but tpb reports "Volume down"
-       1 - no effect but tpb reports "Volume up"
-       2 - no effect but tpb reports "Mute on"
-       3 - simulate pressing the "Access IBM" button
-       4 - LCD brightness up
-       5 - LCD brightness down
-       11 - toggle screen expansion
-       12 - ThinkLight on
-       13 - ThinkLight off
-       14 - no effect but tpb reports ThinkLight status change
-
-LED control -- /proc/acpi/ibm/led
----------------------------------
-
-Some of the LED indicators can be controlled through this feature. The
-available commands are:
-
-       echo '<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> range is 0 to 7. The set of LEDs that can be
-controlled varies from model to model. Here is the mapping on the X40:
-
-       0 - power
-       1 - battery (orange)
-       2 - battery (green)
-       3 - UltraBase
-       4 - UltraBay
-       7 - standby
-
-All of the above can be turned on and off and can be made to blink.
-
-ACPI sounds -- /proc/acpi/ibm/beep
-----------------------------------
-
-The BEEP method is used internally by the ACPI firmware to provide
-audible alerts in various situations. This feature allows the same
-sounds to be triggered manually.
-
-The commands are non-negative integer numbers:
-
-       echo <number> >/proc/acpi/ibm/beep
-
-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:
-
-       0 - stop a sound in progress (but use 17 to stop 16)
-       2 - two beeps, pause, third beep ("low battery")
-       3 - single beep
-       4 - high, followed by low-pitched beep ("unable")
-       5 - single beep
-       6 - very high, followed by high-pitched beep ("AC/DC")
-       7 - high-pitched beep
-       9 - three short beeps
-       10 - very long beep
-       12 - low-pitched beep
-       15 - three high-pitched beeps repeating constantly, stop with 0
-       16 - one medium-pitched beep repeating constantly, stop with 17
-       17 - stop 16
-
-Temperature sensors -- /proc/acpi/ibm/thermal
----------------------------------------------
-
-Most ThinkPads include six or more separate temperature sensors but
-only expose the CPU temperature through the standard ACPI methods.
-This feature shows readings from up to eight different sensors on older
-ThinkPads, and it has experimental support for up to sixteen different
-sensors on newer ThinkPads.  Readings from sensors that are not available
-return -128.
-
-No commands can be written to this file.
-
-EXPERIMENTAL: The 16-sensors feature is marked EXPERIMENTAL because the
-implementation directly accesses hardware registers and may not work as
-expected. USE WITH CAUTION! To use this feature, you need to supply the
-experimental=1 parameter when loading the module.  When EXPERIMENTAL
-mode is enabled, reading the first 8 sensors on newer ThinkPads will
-also use an new experimental thermal sensor access mode.
-
-For example, on the X40, a typical output may be:
-temperatures:   42 42 45 41 36 -128 33 -128
-
-EXPERIMENTAL: On the T43/p, a typical output may be:
-temperatures:   48 48 36 52 38 -128 31 -128 48 52 48 -128 -128 -128 -128 -128
-
-The mapping of thermal sensors to physical locations varies depending on
-system-board model (and thus, on ThinkPad model).
-
-http://thinkwiki.org/wiki/Thermal_Sensors is a public wiki page that
-tries to track down these locations for various models.
-
-Most (newer?) models seem to follow this pattern:
-
-1:  CPU
-2:  (depends on model)
-3:  (depends on model)
-4:  GPU
-5:  Main battery: main sensor
-6:  Bay battery: main sensor
-7:  Main battery: secondary sensor
-8:  Bay battery: secondary sensor
-9-15: (depends on model)
-
-For the R51 (source: Thomas Gruber):
-2:  Mini-PCI
-3:  Internal HDD
-
-For the T43, T43/p (source: Shmidoax/Thinkwiki.org)
-http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_T43.2C_T43p
-2:  System board, left side (near PCMCIA slot), reported as HDAPS temp
-3:  PCMCIA slot
-9:  MCH (northbridge) to DRAM Bus
-10: ICH (southbridge), under Mini-PCI card, under touchpad
-11: Power regulator, underside of system board, below F2 key
-
-The A31 has a very atypical layout for the thermal sensors
-(source: Milos Popovic, http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_A31)
-1:  CPU
-2:  Main Battery: main sensor
-3:  Power Converter
-4:  Bay Battery: main sensor
-5:  MCH (northbridge)
-6:  PCMCIA/ambient
-7:  Main Battery: secondary sensor
-8:  Bay Battery: secondary sensor
-
-
-EXPERIMENTAL: Embedded controller register dump -- /proc/acpi/ibm/ecdump
-------------------------------------------------------------------------
-
-This feature is marked EXPERIMENTAL because the implementation
-directly accesses hardware registers and may not work as expected. USE
-WITH CAUTION! To use this feature, you need to supply the
-experimental=1 parameter when loading the module.
-
-This feature dumps the values of 256 embedded controller
-registers. Values which have changed since the last time the registers
-were dumped are marked with a star:
-
-[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump 
-EC       +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f
-EC 0x00:  a7  47  87  01  fe  96  00  08  01  00  cb  00  00  00  40  00
-EC 0x10:  00  00  ff  ff  f4  3c  87  09  01  ff  42  01  ff  ff  0d  00
-EC 0x20:  00  00  00  00  00  00  00  00  00  00  00  03  43  00  00  80
-EC 0x30:  01  07  1a  00  30  04  00  00 *85  00  00  10  00  50  00  00
-EC 0x40:  00  00  00  00  00  00  14  01  00  04  00  00  00  00  00  00
-EC 0x50:  00  c0  02  0d  00  01  01  02  02  03  03  03  03 *bc *02 *bc
-EC 0x60: *02 *bc *02  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0x70:  00  00  00  00  00  12  30  40 *24 *26 *2c *27 *20  80 *1f  80
-EC 0x80:  00  00  00  06 *37 *0e  03  00  00  00  0e  07  00  00  00  00
-EC 0x90:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xa0: *ff  09  ff  09  ff  ff *64  00 *00 *00 *a2  41 *ff *ff *e0  00
-EC 0xb0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xc0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xd0:  03  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xe0:  00  00  00  00  00  00  00  00  11  20  49  04  24  06  55  03
-EC 0xf0:  31  55  48  54  35  38  57  57  08  2f  45  73  07  65  6c  1a
-
-This feature can be used to determine the register holding the fan
-speed on some models. To do that, do the following:
-
-       - make sure the battery is fully charged
-       - make sure the fan is running
-       - run 'cat /proc/acpi/ibm/ecdump' several times, once per second or so
-
-The first step makes sure various charging-related values don't
-vary. The second ensures that the fan-related values do vary, since
-the fan speed fluctuates a bit. The third will (hopefully) mark the
-fan register with a star:
-
-[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump 
-EC       +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f
-EC 0x00:  a7  47  87  01  fe  96  00  08  01  00  cb  00  00  00  40  00
-EC 0x10:  00  00  ff  ff  f4  3c  87  09  01  ff  42  01  ff  ff  0d  00
-EC 0x20:  00  00  00  00  00  00  00  00  00  00  00  03  43  00  00  80
-EC 0x30:  01  07  1a  00  30  04  00  00  85  00  00  10  00  50  00  00
-EC 0x40:  00  00  00  00  00  00  14  01  00  04  00  00  00  00  00  00
-EC 0x50:  00  c0  02  0d  00  01  01  02  02  03  03  03  03  bc  02  bc
-EC 0x60:  02  bc  02  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0x70:  00  00  00  00  00  12  30  40  24  27  2c  27  21  80  1f  80
-EC 0x80:  00  00  00  06 *be  0d  03  00  00  00  0e  07  00  00  00  00
-EC 0x90:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xa0:  ff  09  ff  09  ff  ff  64  00  00  00  a2  41  ff  ff  e0  00
-EC 0xb0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xc0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xd0:  03  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
-EC 0xe0:  00  00  00  00  00  00  00  00  11  20  49  04  24  06  55  03
-EC 0xf0:  31  55  48  54  35  38  57  57  08  2f  45  73  07  65  6c  1a
-
-Another set of values that varies often is the temperature
-readings. Since temperatures don't change vary fast, you can take
-several quick dumps to eliminate them.
-
-You can use a similar method to figure out the meaning of other
-embedded controller registers - e.g. make sure nothing else changes
-except the charging or discharging battery to determine which
-registers contain the current battery capacity, etc. If you experiment
-with this, do send me your results (including some complete dumps with
-a description of the conditions when they were taken.)
-
-LCD brightness control -- /proc/acpi/ibm/brightness
----------------------------------------------------
-
-This feature allows software control of the LCD brightness on ThinkPad
-models which don't have a hardware brightness slider. The available
-commands are:
-
-       echo up   >/proc/acpi/ibm/brightness
-       echo down >/proc/acpi/ibm/brightness
-       echo 'level <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.
-
-Volume control -- /proc/acpi/ibm/volume
----------------------------------------
-
-This feature allows volume control on ThinkPad models which don't have
-a hardware volume knob. The available commands are:
-
-       echo up   >/proc/acpi/ibm/volume
-       echo down >/proc/acpi/ibm/volume
-       echo mute >/proc/acpi/ibm/volume
-       echo 'level <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, control mode and
-other fan data that might be available.  The speed is read directly
-from the hardware registers of the embedded controller.  This is known
-to work on later R, T and X series ThinkPads but may show a bogus
-value on other models.
-
-Most ThinkPad fans work in "levels".  Level 0 stops the fan.  The higher
-the level, the higher the fan speed, although adjacent levels often map
-to the same fan speed.  7 is the highest level, where the fan reaches
-the maximum recommended speed.  Level "auto" means the EC changes the
-fan level according to some internal algorithm, usually based on
-readings from the thermal sensors.  Level "disengaged" means the EC
-disables the speed-locked closed-loop fan control, and drives the fan as
-fast as it can go, which might exceed hardware limits, so use this level
-with caution.
-
-The fan usually ramps up or down slowly from one speed to another,
-and it is normal for the EC to take several seconds to react to fan
-commands.
-
-The fan may be enabled or disabled with the following commands:
-
-       echo enable  >/proc/acpi/ibm/fan
-       echo disable >/proc/acpi/ibm/fan
-
-Placing a fan on level 0 is the same as disabling it.  Enabling a fan
-will try to place it in a safe level if it is too slow or disabled.
-
-WARNING WARNING WARNING: do not leave the fan disabled unless you are
-monitoring all of the temperature sensor readings and you are ready to
-enable it if necessary to avoid overheating.
-
-An enabled fan in level "auto" may stop spinning if the EC decides the
-ThinkPad is cool enough and doesn't need the extra airflow.  This is
-normal, and the EC will spin the fan up if the varios thermal readings
-rise too much.
-
-On the X40, this seems to depend on the CPU and HDD temperatures.
-Specifically, the fan is turned on when either the CPU temperature
-climbs to 56 degrees or the HDD temperature climbs to 46 degrees.  The
-fan is turned off when the CPU temperature drops to 49 degrees and the
-HDD temperature drops to 41 degrees.  These thresholds cannot
-currently be controlled.
-
-The fan level can be controlled with the command:
-
-       echo 'level <level>' > /proc/acpi/ibm/thermal
-
-Where <level> is an integer from 0 to 7, or one of the words "auto"
-or "disengaged" (without the quotes).  Not all ThinkPads support the
-"auto" and "disengaged" levels.
-
-On the X31 and X40 (and ONLY on those models), the fan speed can be
-controlled to a certain degree. Once the fan is running, it can be
-forced to run faster or slower with the following command:
-
-       echo 'speed <speed>' > /proc/acpi/ibm/thermal
-
-The sustainable range of fan speeds on the X40 appears to be from
-about 3700 to about 7350. Values outside this range either do not have
-any effect or the fan speed eventually settles somewhere in that
-range. The fan cannot be stopped or started with this command.
-
-The ThinkPad's ACPI DSDT code will reprogram the fan on its own when
-certain conditions are met.  It will override any fan programming done
-through ibm-acpi.
-
-EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan
----------------------------------------
-
-This feature is marked EXPERIMENTAL because the implementation
-directly accesses hardware registers and may not work as expected. USE
-WITH CAUTION! To use this feature, you need to supply the
-experimental=1 parameter when loading the module.
-
-This feature shows the presence and current state of a WAN (Sierra
-Wireless EV-DO) device. If WAN is installed, the following commands can
-be used:
-
-       echo enable > /proc/acpi/ibm/wan
-       echo disable > /proc/acpi/ibm/wan
-
-It was tested on a Lenovo Thinkpad X60. It should probably work on other
-Thinkpad models which come with this module installed.
-
-Multiple Commands, Module Parameters
-------------------------------------
-
-Multiple commands can be written to the proc files in one shot by
-separating them with commas, for example:
-
-       echo enable,0xffff > /proc/acpi/ibm/hotkey
-       echo lcd_disable,crt_enable > /proc/acpi/ibm/video
-
-Commands can also be specified when loading the ibm_acpi module, for
-example:
-
-       modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable
-
-The ibm-acpi kernel driver can be programmed to revert the fan level
-to a safe setting if userspace does not issue one of the fan commands:
-"enable", "disable", "level" or "watchdog" within a configurable
-ammount of time.  To do this, use the "watchdog" command.
-
-       echo 'watchdog <interval>' > /proc/acpi/ibm/fan
-
-Interval is the ammount of time in seconds to wait for one of the
-above mentioned fan commands before reseting the fan level to a safe
-one.  If set to zero, the watchdog is disabled (default).  When the
-watchdog timer runs out, it does the exact equivalent of the "enable"
-fan command.
-
-Note that the watchdog timer stops after it enables the fan.  It will
-be rearmed again automatically (using the same interval) when one of
-the above mentioned fan commands is received.  The fan watchdog is,
-therefore, not suitable to protect against fan mode changes made
-through means other than the "enable", "disable", and "level" fan
-commands.
-
-
-Example Configuration
----------------------
-
-The ACPI support in the kernel is intended to be used in conjunction
-with a user-space daemon, acpid. The configuration files for this
-daemon control what actions are taken in response to various ACPI
-events. An example set of configuration files are included in the
-config/ directory of the tarball package available on the web
-site. Note that these are provided for illustration purposes only and
-may need to be adapted to your particular setup.
-
-The following utility scripts are used by the example action
-scripts (included with ibm-acpi for completeness):
-
-       /usr/local/sbin/idectl -- from the hdparm source distribution,
-               see http://www.ibiblio.org/pub/Linux/system/hardware
-       /usr/local/sbin/laptop_mode -- from the Linux kernel source
-               distribution, see Documentation/laptop-mode.txt
-       /sbin/service -- comes with Redhat/Fedora distributions
-       /usr/sbin/hibernate -- from the Software Suspend 2 distribution,
-               see http://softwaresuspend.berlios.de/
-
-Toan T Nguyen <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. This scripts has now been
-extended to also work on Fedora systems and included as the default
-blank.sh in the distribution.
index dfd26df056f4039fcf35c87548424ee301c21850..7a5c1a81905c66940fe2c3a2d516b237f957b970 100644 (file)
@@ -3,12 +3,18 @@ Sony Notebook Control Driver (SNC) Readme
        Copyright (C) 2004- 2005 Stelian Pop <stelian@popies.net>
        Copyright (C) 2007 Mattia Dongili <malattia@linux.it>
 
-This mini-driver drives the SNC device present in the ACPI BIOS of
-the Sony Vaio laptops.
+This mini-driver drives the SNC and SPIC device present in the ACPI BIOS of the
+Sony Vaio laptops. This driver mixes both devices functions under the same
+(hopefully consistent) interface. This also means that the sonypi driver is
+obsoleted by sony-laptop now.
 
-It gives access to some extra laptop functionalities. In its current
-form, this driver let the user set or query the screen brightness
-through the backlight subsystem and remove/apply power to some devices.
+Fn keys (hotkeys):
+------------------
+Some models report hotkeys through the SNC or SPIC devices, such events are
+reported both through the ACPI subsystem as acpi events and through the INPUT
+subsystem. See the logs of acpid or /proc/acpi/event and
+/proc/bus/input/devices to find out what those events are and which input
+devices are created by the driver.
 
 Backlight control:
 ------------------
@@ -39,6 +45,8 @@ The files are:
        audiopower              power on/off the internal sound card
        lanpower                power on/off the internal ethernet card
                                (only in debug mode)
+       bluetoothpower          power on/off the internal bluetooth device
+       fanspeed                get/set the fan speed
 
 Note that some files may be missing if they are not supported
 by your particular laptop model.
@@ -76,9 +84,9 @@ The sony-laptop driver creates, for some of those methods (the most
 current ones found on several Vaio models), an entry under
 /sys/devices/platform/sony-laptop, just like the 'cdpower' one.
 You can create other entries corresponding to your own laptop methods by
-further editing the source (see the 'sony_acpi_values' table, and add a new
+further editing the source (see the 'sony_nc_values' table, and add a new
 entry to this table with your get/set method names using the
-HANDLE_NAMES macro).
+SNC_HANDLE_NAMES macro).
 
 Your mission, should you accept it, is to try finding out what
 those entries are for, by reading/writing random values from/to those
@@ -87,6 +95,9 @@ files and find out what is the impact on your laptop.
 Should you find anything interesting, please report it back to me,
 I will not disavow all knowledge of your actions :)
 
+See also http://www.linux.it/~malattia/wiki/index.php/Sony_drivers for other
+useful info.
+
 Bugs/Limitations:
 -----------------
 
diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt
new file mode 100644 (file)
index 0000000..2d48033
--- /dev/null
@@ -0,0 +1,981 @@
+                    ThinkPad ACPI Extras Driver
+
+                            Version 0.14
+                          April 21st, 2007
+
+               Borislav Deianov <borislav@users.sf.net>
+            Henrique de Moraes Holschuh <hmh@hmh.eng.br>
+                     http://ibm-acpi.sf.net/
+
+
+This is a Linux driver for the IBM and Lenovo ThinkPad laptops. It
+supports various features of these laptops which are accessible
+through the ACPI and ACPI EC framework, but not otherwise fully
+supported by the generic Linux ACPI drivers.
+
+This driver used to be named ibm-acpi until kernel 2.6.21 and release
+0.13-20070314.  It used to be in the drivers/acpi tree, but it was
+moved to the drivers/misc tree and renamed to thinkpad-acpi for kernel
+2.6.22, and release 0.14.
+
+
+Status
+------
+
+The features currently supported are the following (see below for
+detailed description):
+
+       - Fn key combinations
+       - Bluetooth enable and disable
+       - video output switching, expansion control
+       - ThinkLight on and off
+       - limited docking and undocking
+       - UltraBay eject
+       - CMOS control
+       - LED control
+       - ACPI sounds
+       - temperature sensors
+       - Experimental: embedded controller register dump
+       - LCD brightness control
+       - Volume control
+       - Fan control and monitoring: fan speed, fan enable/disable
+       - Experimental: WAN enable and disable
+
+A compatibility table by model and feature is maintained on the web
+site, http://ibm-acpi.sf.net/. I appreciate any success or failure
+reports, especially if they add to or correct the compatibility table.
+Please include the following information in your report:
+
+       - ThinkPad model name
+       - a copy of your DSDT, from /proc/acpi/dsdt
+       - a copy of the output of dmidecode, with serial numbers
+         and UUIDs masked off
+       - which driver features work and which don't
+       - the observed behavior of non-working features
+
+Any other comments or patches are also more than welcome.
+
+
+Installation
+------------
+
+If you are compiling this driver as included in the Linux kernel
+sources, simply enable the CONFIG_THINKPAD_ACPI option, and optionally
+enable the CONFIG_THINKPAD_ACPI_BAY option if you want the
+thinkpad-specific bay functionality.
+
+Features
+--------
+
+The driver exports two different interfaces to userspace, which can be
+used to access the features it provides.  One is a legacy procfs-based
+interface, which will be removed at some time in the distant future.
+The other is a new sysfs-based interface which is not complete yet.
+
+The procfs interface creates the /proc/acpi/ibm directory.  There is a
+file under that directory for each feature it supports.  The procfs
+interface is mostly frozen, and will change very little if at all: it
+will not be extended to add any new functionality in the driver, instead
+all new functionality will be implemented on the sysfs interface.
+
+The sysfs interface tries to blend in the generic Linux sysfs subsystems
+and classes as much as possible.  Since some of these subsystems are not
+yet ready or stabilized, it is expected that this interface will change,
+and any and all userspace programs must deal with it.
+
+
+Notes about the sysfs interface:
+
+Unlike what was done with the procfs interface, correctness when talking
+to the sysfs interfaces will be enforced, as will correctness in the
+thinkpad-acpi's implementation of sysfs interfaces.
+
+Also, any bugs in the thinkpad-acpi sysfs driver code or in the
+thinkpad-acpi's implementation of the sysfs interfaces will be fixed for
+maximum correctness, even if that means changing an interface in
+non-compatible ways.  As these interfaces mature both in the kernel and
+in thinkpad-acpi, such changes should become quite rare.
+
+Applications interfacing to the thinkpad-acpi sysfs interfaces must
+follow all sysfs guidelines and correctly process all errors (the sysfs
+interface makes extensive use of errors).  File descriptors and open /
+close operations to the sysfs inodes must also be properly implemented.
+
+The version of thinkpad-acpi's sysfs interface is exported by the driver
+as a driver attribute (see below).
+
+Sysfs driver attributes are on the driver's sysfs attribute space,
+for 2.6.20 this is /sys/bus/platform/drivers/thinkpad-acpi/.
+
+Sysfs device attributes are on the driver's sysfs attribute space,
+for 2.6.20 this is /sys/devices/platform/thinkpad-acpi/.
+
+Driver version
+--------------
+
+procfs: /proc/acpi/ibm/driver
+sysfs driver attribute: version
+
+The driver name and version. No commands can be written to this file.
+
+Sysfs interface version
+-----------------------
+
+sysfs driver attribute: interface_version
+
+Version of the thinkpad-acpi sysfs interface, as an unsigned long
+(output in hex format: 0xAAAABBCC), where:
+       AAAA - major revision
+       BB - minor revision
+       CC - bugfix revision
+
+The sysfs interface version changelog for the driver can be found at the
+end of this document.  Changes to the sysfs interface done by the kernel
+subsystems are not documented here, nor are they tracked by this
+attribute.
+
+Hot keys
+--------
+
+procfs: /proc/acpi/ibm/hotkey
+sysfs device attribute: hotkey/*
+
+Without this driver, only the Fn-F4 key (sleep button) generates an
+ACPI event. With the driver loaded, the hotkey feature enabled and the
+mask set (see below), the various hot keys generate ACPI events in the
+following format:
+
+       ibm/hotkey HKEY 00000080 0000xxxx
+
+The last four digits vary depending on the key combination pressed.
+All labeled Fn-Fx key combinations generate distinct events. In
+addition, the lid microswitch and some docking station buttons may
+also generate such events.
+
+The bit mask allows some control over which hot keys generate ACPI
+events. Not all bits in the mask can be modified. Not all bits that
+can be modified do anything. Not all hot keys can be individually
+controlled by the mask. Most recent ThinkPad models honor the
+following bits (assuming the hot keys feature has been enabled):
+
+       key     bit     behavior when set       behavior when unset
+
+       Fn-F3                   always generates ACPI event
+       Fn-F4                   always generates ACPI event
+       Fn-F5   0010    generate ACPI event     enable/disable Bluetooth
+       Fn-F7   0040    generate ACPI event     switch LCD and external display
+       Fn-F8   0080    generate ACPI event     expand screen or none
+       Fn-F9   0100    generate ACPI event     none
+       Fn-F12                  always generates ACPI event
+
+Some models do not support all of the above. For example, the T30 does
+not support Fn-F5 and Fn-F9. Other models do not support the mask at
+all. On those models, hot keys cannot be controlled individually.
+
+Note that enabling ACPI events for some keys prevents their default
+behavior. For example, if events for Fn-F5 are enabled, that key will
+no longer enable/disable Bluetooth by itself. This can still be done
+from an acpid handler for the ibm/hotkey event.
+
+Note also that not all Fn key combinations are supported through
+ACPI. For example, on the X40, the brightness, volume and "Access IBM"
+buttons do not generate ACPI events even with this driver. They *can*
+be used through the "ThinkPad Buttons" utility, see
+http://www.nongnu.org/tpb/
+
+procfs notes:
+
+The following commands can be written to the /proc/acpi/ibm/hotkey file:
+
+       echo enable > /proc/acpi/ibm/hotkey -- enable the hot keys feature
+       echo disable > /proc/acpi/ibm/hotkey -- disable the hot keys feature
+       echo 0xffff > /proc/acpi/ibm/hotkey -- enable all possible hot keys
+       echo 0x0000 > /proc/acpi/ibm/hotkey -- disable all possible hot keys
+       ... any other 4-hex-digit mask ...
+       echo reset > /proc/acpi/ibm/hotkey -- restore the original mask
+
+sysfs notes:
+
+       The hot keys attributes are in a hotkey/ subdirectory off the
+       thinkpad device.
+
+       bios_enabled:
+               Returns the status of the hot keys feature when
+               thinkpad-acpi was loaded.  Upon module unload, the hot
+               key feature status will be restored to this value.
+
+               0: hot keys were disabled
+               1: hot keys were enabled
+
+       bios_mask:
+               Returns the hot keys mask when thinkpad-acpi was loaded.
+               Upon module unload, the hot keys mask will be restored
+               to this value.
+
+       enable:
+               Enables/disables the hot keys feature, and reports
+               current status of the hot keys feature.
+
+               0: disables the hot keys feature / feature disabled
+               1: enables the hot keys feature / feature enabled
+
+       mask:
+               bit mask to enable ACPI event generation for each hot
+               key (see above).  Returns the current status of the hot
+               keys mask, and allows one to modify it.
+
+
+Bluetooth
+---------
+
+procfs: /proc/acpi/ibm/bluetooth
+sysfs device attribute: bluetooth/enable
+
+This feature shows the presence and current state of a ThinkPad
+Bluetooth device in the internal ThinkPad CDC slot.
+
+Procfs notes:
+
+If Bluetooth is installed, the following commands can be used:
+
+       echo enable > /proc/acpi/ibm/bluetooth
+       echo disable > /proc/acpi/ibm/bluetooth
+
+Sysfs notes:
+
+       If the Bluetooth CDC card is installed, it can be enabled /
+       disabled through the "bluetooth/enable" thinkpad-acpi device
+       attribute, and its current status can also be queried.
+
+       enable:
+               0: disables Bluetooth / Bluetooth is disabled
+               1: enables Bluetooth / Bluetooth is enabled.
+
+       Note: this interface will be probably be superseeded by the
+       generic rfkill class.
+
+Video output control -- /proc/acpi/ibm/video
+--------------------------------------------
+
+This feature allows control over the devices used for video output -
+LCD, CRT or DVI (if available). The following commands are available:
+
+       echo lcd_enable > /proc/acpi/ibm/video
+       echo lcd_disable > /proc/acpi/ibm/video
+       echo crt_enable > /proc/acpi/ibm/video
+       echo crt_disable > /proc/acpi/ibm/video
+       echo dvi_enable > /proc/acpi/ibm/video
+       echo dvi_disable > /proc/acpi/ibm/video
+       echo auto_enable > /proc/acpi/ibm/video
+       echo auto_disable > /proc/acpi/ibm/video
+       echo expand_toggle > /proc/acpi/ibm/video
+       echo video_switch > /proc/acpi/ibm/video
+
+Each video output device can be enabled or disabled individually.
+Reading /proc/acpi/ibm/video shows the status of each device.
+
+Automatic video switching can be enabled or disabled.  When automatic
+video switching is enabled, certain events (e.g. opening the lid,
+docking or undocking) cause the video output device to change
+automatically. While this can be useful, it also causes flickering
+and, on the X40, video corruption. By disabling automatic switching,
+the flickering or video corruption can be avoided.
+
+The video_switch command cycles through the available video outputs
+(it simulates the behavior of Fn-F7).
+
+Video expansion can be toggled through this feature. This controls
+whether the display is expanded to fill the entire LCD screen when a
+mode with less than full resolution is used. Note that the current
+video expansion status cannot be determined through this feature.
+
+Note that on many models (particularly those using Radeon graphics
+chips) the X driver configures the video card in a way which prevents
+Fn-F7 from working. This also disables the video output switching
+features of this driver, as it uses the same ACPI methods as
+Fn-F7. Video switching on the console should still work.
+
+UPDATE: There's now a patch for the X.org Radeon driver which
+addresses this issue. Some people are reporting success with the patch
+while others are still having problems. For more information:
+
+https://bugs.freedesktop.org/show_bug.cgi?id=2000
+
+ThinkLight control -- /proc/acpi/ibm/light
+------------------------------------------
+
+The current status of the ThinkLight can be found in this file. A few
+models which do not make the status available will show it as
+"unknown". The available commands are:
+
+       echo on  > /proc/acpi/ibm/light
+       echo off > /proc/acpi/ibm/light
+
+Docking / undocking -- /proc/acpi/ibm/dock
+------------------------------------------
+
+Docking and undocking (e.g. with the X4 UltraBase) requires some
+actions to be taken by the operating system to safely make or break
+the electrical connections with the dock.
+
+The docking feature of this driver generates the following ACPI events:
+
+       ibm/dock GDCK 00000003 00000001 -- eject request
+       ibm/dock GDCK 00000003 00000002 -- undocked
+       ibm/dock GDCK 00000000 00000003 -- docked
+
+NOTE: These events will only be generated if the laptop was docked
+when originally booted. This is due to the current lack of support for
+hot plugging of devices in the Linux ACPI framework. If the laptop was
+booted while not in the dock, the following message is shown in the
+logs:
+
+       Mar 17 01:42:34 aero kernel: thinkpad_acpi: dock device not present
+
+In this case, no dock-related events are generated but the dock and
+undock commands described below still work. They can be executed
+manually or triggered by Fn key combinations (see the example acpid
+configuration files included in the driver tarball package available
+on the web site).
+
+When the eject request button on the dock is pressed, the first event
+above is generated. The handler for this event should issue the
+following command:
+
+       echo undock > /proc/acpi/ibm/dock
+
+After the LED on the dock goes off, it is safe to eject the laptop.
+Note: if you pressed this key by mistake, go ahead and eject the
+laptop, then dock it back in. Otherwise, the dock may not function as
+expected.
+
+When the laptop is docked, the third event above is generated. The
+handler for this event should issue the following command to fully
+enable the dock:
+
+       echo dock > /proc/acpi/ibm/dock
+
+The contents of the /proc/acpi/ibm/dock file shows the current status
+of the dock, as provided by the ACPI framework.
+
+The docking support in this driver does not take care of enabling or
+disabling any other devices you may have attached to the dock. For
+example, a CD drive plugged into the UltraBase needs to be disabled or
+enabled separately. See the provided example acpid configuration files
+for how this can be accomplished.
+
+There is no support yet for PCI devices that may be attached to a
+docking station, e.g. in the ThinkPad Dock II. The driver currently
+does not recognize, enable or disable such devices. This means that
+the only docking stations currently supported are the X-series
+UltraBase docks and "dumb" port replicators like the Mini Dock (the
+latter don't need any ACPI support, actually).
+
+UltraBay eject -- /proc/acpi/ibm/bay
+------------------------------------
+
+Inserting or ejecting an UltraBay device requires some actions to be
+taken by the operating system to safely make or break the electrical
+connections with the device.
+
+This feature generates the following ACPI events:
+
+       ibm/bay MSTR 00000003 00000000 -- eject request
+       ibm/bay MSTR 00000001 00000000 -- eject lever inserted
+
+NOTE: These events will only be generated if the UltraBay was present
+when the laptop was originally booted (on the X series, the UltraBay
+is in the dock, so it may not be present if the laptop was undocked).
+This is due to the current lack of support for hot plugging of devices
+in the Linux ACPI framework. If the laptop was booted without the
+UltraBay, the following message is shown in the logs:
+
+       Mar 17 01:42:34 aero kernel: thinkpad_acpi: bay device not present
+
+In this case, no bay-related events are generated but the eject
+command described below still works. It can be executed manually or
+triggered by a hot key combination.
+
+Sliding the eject lever generates the first event shown above. The
+handler for this event should take whatever actions are necessary to
+shut down the device in the UltraBay (e.g. call idectl), then issue
+the following command:
+
+       echo eject > /proc/acpi/ibm/bay
+
+After the LED on the UltraBay goes off, it is safe to pull out the
+device.
+
+When the eject lever is inserted, the second event above is
+generated. The handler for this event should take whatever actions are
+necessary to enable the UltraBay device (e.g. call idectl).
+
+The contents of the /proc/acpi/ibm/bay file shows the current status
+of the UltraBay, as provided by the ACPI framework.
+
+EXPERIMENTAL warm eject support on the 600e/x, A22p and A3x (To use
+this feature, you need to supply the experimental=1 parameter when
+loading the module):
+
+These models do not have a button near the UltraBay device to request
+a hot eject but rather require the laptop to be put to sleep
+(suspend-to-ram) before the bay device is ejected or inserted).
+The sequence of steps to eject the device is as follows:
+
+       echo eject > /proc/acpi/ibm/bay
+       put the ThinkPad to sleep
+       remove the drive
+       resume from sleep
+       cat /proc/acpi/ibm/bay should show that the drive was removed
+
+On the A3x, both the UltraBay 2000 and UltraBay Plus devices are
+supported. Use "eject2" instead of "eject" for the second bay.
+
+Note: the UltraBay eject support on the 600e/x, A22p and A3x is
+EXPERIMENTAL and may not work as expected. USE WITH CAUTION!
+
+CMOS control
+------------
+
+procfs: /proc/acpi/ibm/cmos
+sysfs device attribute: cmos_command
+
+This feature is used internally by the ACPI firmware to control the
+ThinkLight on most newer ThinkPad models. It may also control LCD
+brightness, sounds volume and more, but only on some models.
+
+The range of valid cmos command numbers is 0 to 21, but not all have an
+effect and the behavior varies from model to model.  Here is the behavior
+on the X40 (tpb is the ThinkPad Buttons utility):
+
+       0 - no effect but tpb reports "Volume down"
+       1 - no effect but tpb reports "Volume up"
+       2 - no effect but tpb reports "Mute on"
+       3 - simulate pressing the "Access IBM" button
+       4 - LCD brightness up
+       5 - LCD brightness down
+       11 - toggle screen expansion
+       12 - ThinkLight on
+       13 - ThinkLight off
+       14 - no effect but tpb reports ThinkLight status change
+
+The cmos command interface is prone to firmware split-brain problems, as
+in newer ThinkPads it is just a compatibility layer.
+
+LED control -- /proc/acpi/ibm/led
+---------------------------------
+
+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
+
+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)
+       2 - battery (green)
+       3 - UltraBase
+       4 - UltraBay
+       7 - standby
+
+All of the above can be turned on and off and can be made to blink.
+
+ACPI sounds -- /proc/acpi/ibm/beep
+----------------------------------
+
+The BEEP method is used internally by the ACPI firmware to provide
+audible alerts in various situations. This feature allows the same
+sounds to be triggered manually.
+
+The commands are non-negative integer numbers:
+
+       echo <number> >/proc/acpi/ibm/beep
+
+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:
+
+       0 - stop a sound in progress (but use 17 to stop 16)
+       2 - two beeps, pause, third beep ("low battery")
+       3 - single beep
+       4 - high, followed by low-pitched beep ("unable")
+       5 - single beep
+       6 - very high, followed by high-pitched beep ("AC/DC")
+       7 - high-pitched beep
+       9 - three short beeps
+       10 - very long beep
+       12 - low-pitched beep
+       15 - three high-pitched beeps repeating constantly, stop with 0
+       16 - one medium-pitched beep repeating constantly, stop with 17
+       17 - stop 16
+
+Temperature sensors
+-------------------
+
+procfs: /proc/acpi/ibm/thermal
+sysfs device attributes: (hwmon) temp*_input
+
+Most ThinkPads include six or more separate temperature sensors but
+only expose the CPU temperature through the standard ACPI methods.
+This feature shows readings from up to eight different sensors on older
+ThinkPads, and it has experimental support for up to sixteen different
+sensors on newer ThinkPads.
+
+EXPERIMENTAL: The 16-sensors feature is marked EXPERIMENTAL because the
+implementation directly accesses hardware registers and may not work as
+expected. USE WITH CAUTION! To use this feature, you need to supply the
+experimental=1 parameter when loading the module.  When EXPERIMENTAL
+mode is enabled, reading the first 8 sensors on newer ThinkPads will
+also use an new experimental thermal sensor access mode.
+
+For example, on the X40, a typical output may be:
+temperatures:   42 42 45 41 36 -128 33 -128
+
+EXPERIMENTAL: On the T43/p, a typical output may be:
+temperatures:   48 48 36 52 38 -128 31 -128 48 52 48 -128 -128 -128 -128 -128
+
+The mapping of thermal sensors to physical locations varies depending on
+system-board model (and thus, on ThinkPad model).
+
+http://thinkwiki.org/wiki/Thermal_Sensors is a public wiki page that
+tries to track down these locations for various models.
+
+Most (newer?) models seem to follow this pattern:
+
+1:  CPU
+2:  (depends on model)
+3:  (depends on model)
+4:  GPU
+5:  Main battery: main sensor
+6:  Bay battery: main sensor
+7:  Main battery: secondary sensor
+8:  Bay battery: secondary sensor
+9-15: (depends on model)
+
+For the R51 (source: Thomas Gruber):
+2:  Mini-PCI
+3:  Internal HDD
+
+For the T43, T43/p (source: Shmidoax/Thinkwiki.org)
+http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_T43.2C_T43p
+2:  System board, left side (near PCMCIA slot), reported as HDAPS temp
+3:  PCMCIA slot
+9:  MCH (northbridge) to DRAM Bus
+10: ICH (southbridge), under Mini-PCI card, under touchpad
+11: Power regulator, underside of system board, below F2 key
+
+The A31 has a very atypical layout for the thermal sensors
+(source: Milos Popovic, http://thinkwiki.org/wiki/Thermal_Sensors#ThinkPad_A31)
+1:  CPU
+2:  Main Battery: main sensor
+3:  Power Converter
+4:  Bay Battery: main sensor
+5:  MCH (northbridge)
+6:  PCMCIA/ambient
+7:  Main Battery: secondary sensor
+8:  Bay Battery: secondary sensor
+
+
+Procfs notes:
+       Readings from sensors that are not available return -128.
+       No commands can be written to this file.
+
+Sysfs notes:
+       Sensors that are not available return the ENXIO error.  This
+       status may change at runtime, as there are hotplug thermal
+       sensors, like those inside the batteries and docks.
+
+       thinkpad-acpi thermal sensors are reported through the hwmon
+       subsystem, and follow all of the hwmon guidelines at
+       Documentation/hwmon.
+
+
+EXPERIMENTAL: Embedded controller register dump -- /proc/acpi/ibm/ecdump
+------------------------------------------------------------------------
+
+This feature is marked EXPERIMENTAL because the implementation
+directly accesses hardware registers and may not work as expected. USE
+WITH CAUTION! To use this feature, you need to supply the
+experimental=1 parameter when loading the module.
+
+This feature dumps the values of 256 embedded controller
+registers. Values which have changed since the last time the registers
+were dumped are marked with a star:
+
+[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump
+EC       +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f
+EC 0x00:  a7  47  87  01  fe  96  00  08  01  00  cb  00  00  00  40  00
+EC 0x10:  00  00  ff  ff  f4  3c  87  09  01  ff  42  01  ff  ff  0d  00
+EC 0x20:  00  00  00  00  00  00  00  00  00  00  00  03  43  00  00  80
+EC 0x30:  01  07  1a  00  30  04  00  00 *85  00  00  10  00  50  00  00
+EC 0x40:  00  00  00  00  00  00  14  01  00  04  00  00  00  00  00  00
+EC 0x50:  00  c0  02  0d  00  01  01  02  02  03  03  03  03 *bc *02 *bc
+EC 0x60: *02 *bc *02  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0x70:  00  00  00  00  00  12  30  40 *24 *26 *2c *27 *20  80 *1f  80
+EC 0x80:  00  00  00  06 *37 *0e  03  00  00  00  0e  07  00  00  00  00
+EC 0x90:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xa0: *ff  09  ff  09  ff  ff *64  00 *00 *00 *a2  41 *ff *ff *e0  00
+EC 0xb0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xc0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xd0:  03  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xe0:  00  00  00  00  00  00  00  00  11  20  49  04  24  06  55  03
+EC 0xf0:  31  55  48  54  35  38  57  57  08  2f  45  73  07  65  6c  1a
+
+This feature can be used to determine the register holding the fan
+speed on some models. To do that, do the following:
+
+       - make sure the battery is fully charged
+       - make sure the fan is running
+       - run 'cat /proc/acpi/ibm/ecdump' several times, once per second or so
+
+The first step makes sure various charging-related values don't
+vary. The second ensures that the fan-related values do vary, since
+the fan speed fluctuates a bit. The third will (hopefully) mark the
+fan register with a star:
+
+[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump
+EC       +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f
+EC 0x00:  a7  47  87  01  fe  96  00  08  01  00  cb  00  00  00  40  00
+EC 0x10:  00  00  ff  ff  f4  3c  87  09  01  ff  42  01  ff  ff  0d  00
+EC 0x20:  00  00  00  00  00  00  00  00  00  00  00  03  43  00  00  80
+EC 0x30:  01  07  1a  00  30  04  00  00  85  00  00  10  00  50  00  00
+EC 0x40:  00  00  00  00  00  00  14  01  00  04  00  00  00  00  00  00
+EC 0x50:  00  c0  02  0d  00  01  01  02  02  03  03  03  03  bc  02  bc
+EC 0x60:  02  bc  02  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0x70:  00  00  00  00  00  12  30  40  24  27  2c  27  21  80  1f  80
+EC 0x80:  00  00  00  06 *be  0d  03  00  00  00  0e  07  00  00  00  00
+EC 0x90:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xa0:  ff  09  ff  09  ff  ff  64  00  00  00  a2  41  ff  ff  e0  00
+EC 0xb0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xc0:  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xd0:  03  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
+EC 0xe0:  00  00  00  00  00  00  00  00  11  20  49  04  24  06  55  03
+EC 0xf0:  31  55  48  54  35  38  57  57  08  2f  45  73  07  65  6c  1a
+
+Another set of values that varies often is the temperature
+readings. Since temperatures don't change vary fast, you can take
+several quick dumps to eliminate them.
+
+You can use a similar method to figure out the meaning of other
+embedded controller registers - e.g. make sure nothing else changes
+except the charging or discharging battery to determine which
+registers contain the current battery capacity, etc. If you experiment
+with this, do send me your results (including some complete dumps with
+a description of the conditions when they were taken.)
+
+LCD brightness control
+----------------------
+
+procfs: /proc/acpi/ibm/brightness
+sysfs backlight device "thinkpad_screen"
+
+This feature allows software control of the LCD brightness on ThinkPad
+models which don't have a hardware brightness slider.
+
+It has some limitations: the LCD backlight cannot be actually turned on or off
+by this interface, and in many ThinkPad models, the "dim while on battery"
+functionality will be enabled by the BIOS when this interface is used, and
+cannot be controlled.
+
+The backlight control has eight levels, ranging from 0 to 7.  Some of the
+levels may not be distinct.
+
+Procfs notes:
+
+       The available commands are:
+
+       echo up   >/proc/acpi/ibm/brightness
+       echo down >/proc/acpi/ibm/brightness
+       echo 'level <level>' >/proc/acpi/ibm/brightness
+
+Sysfs notes:
+
+The interface is implemented through the backlight sysfs class, which is poorly
+documented at this time.
+
+Locate the thinkpad_screen device under /sys/class/backlight, and inside it
+there will be the following attributes:
+
+       max_brightness:
+               Reads the maximum brightness the hardware can be set to.
+               The minimum is always zero.
+
+       actual_brightness:
+               Reads what brightness the screen is set to at this instant.
+
+       brightness:
+               Writes request the driver to change brightness to the given
+               value.  Reads will tell you what brightness the driver is trying
+               to set the display to when "power" is set to zero and the display
+               has not been dimmed by a kernel power management event.
+
+       power:
+               power management mode, where 0 is "display on", and 1 to 3 will
+               dim the display backlight to brightness level 0 because
+               thinkpad-acpi cannot really turn the backlight off.  Kernel
+               power management events can temporarily increase the current
+               power management level, i.e. they can dim the display.
+
+
+Volume control -- /proc/acpi/ibm/volume
+---------------------------------------
+
+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.
+
+Fan control and monitoring: fan speed, fan enable/disable
+---------------------------------------------------------
+
+procfs: /proc/acpi/ibm/fan
+sysfs device attributes: (hwmon) fan_input, pwm1, pwm1_enable
+
+NOTE NOTE NOTE: fan control operations are disabled by default for
+safety reasons.  To enable them, the module parameter "fan_control=1"
+must be given to thinkpad-acpi.
+
+This feature attempts to show the current fan speed, control mode and
+other fan data that might be available.  The speed is read directly
+from the hardware registers of the embedded controller.  This is known
+to work on later R, T, X and Z series ThinkPads but may show a bogus
+value on other models.
+
+Fan levels:
+
+Most ThinkPad fans work in "levels" at the firmware interface.  Level 0
+stops the fan.  The higher the level, the higher the fan speed, although
+adjacent levels often map to the same fan speed.  7 is the highest
+level, where the fan reaches the maximum recommended speed.
+
+Level "auto" means the EC changes the fan level according to some
+internal algorithm, usually based on readings from the thermal sensors.
+
+There is also a "full-speed" level, also known as "disengaged" level.
+In this level, the EC disables the speed-locked closed-loop fan control,
+and drives the fan as fast as it can go, which might exceed hardware
+limits, so use this level with caution.
+
+The fan usually ramps up or down slowly from one speed to another, and
+it is normal for the EC to take several seconds to react to fan
+commands.  The full-speed level may take up to two minutes to ramp up to
+maximum speed, and in some ThinkPads, the tachometer readings go stale
+while the EC is transitioning to the full-speed level.
+
+WARNING WARNING WARNING: do not leave the fan disabled unless you are
+monitoring all of the temperature sensor readings and you are ready to
+enable it if necessary to avoid overheating.
+
+An enabled fan in level "auto" may stop spinning if the EC decides the
+ThinkPad is cool enough and doesn't need the extra airflow.  This is
+normal, and the EC will spin the fan up if the varios thermal readings
+rise too much.
+
+On the X40, this seems to depend on the CPU and HDD temperatures.
+Specifically, the fan is turned on when either the CPU temperature
+climbs to 56 degrees or the HDD temperature climbs to 46 degrees.  The
+fan is turned off when the CPU temperature drops to 49 degrees and the
+HDD temperature drops to 41 degrees.  These thresholds cannot
+currently be controlled.
+
+The ThinkPad's ACPI DSDT code will reprogram the fan on its own when
+certain conditions are met.  It will override any fan programming done
+through thinkpad-acpi.
+
+The thinkpad-acpi kernel driver can be programmed to revert the fan
+level to a safe setting if userspace does not issue one of the procfs
+fan commands: "enable", "disable", "level" or "watchdog", or if there
+are no writes to pwm1_enable (or to pwm1 *if and only if* pwm1_enable is
+set to 1, manual mode) within a configurable amount of time of up to
+120 seconds.  This functionality is called fan safety watchdog.
+
+Note that the watchdog timer stops after it enables the fan.  It will be
+rearmed again automatically (using the same interval) when one of the
+above mentioned fan commands is received.  The fan watchdog is,
+therefore, not suitable to protect against fan mode changes made through
+means other than the "enable", "disable", and "level" procfs fan
+commands, or the hwmon fan control sysfs interface.
+
+Procfs notes:
+
+The fan may be enabled or disabled with the following commands:
+
+       echo enable  >/proc/acpi/ibm/fan
+       echo disable >/proc/acpi/ibm/fan
+
+Placing a fan on level 0 is the same as disabling it.  Enabling a fan
+will try to place it in a safe level if it is too slow or disabled.
+
+The fan level can be controlled with the command:
+
+       echo 'level <level>' > /proc/acpi/ibm/fan
+
+Where <level> is an integer from 0 to 7, or one of the words "auto" or
+"full-speed" (without the quotes).  Not all ThinkPads support the "auto"
+and "full-speed" levels.  The driver accepts "disengaged" as an alias for
+"full-speed", and reports it as "disengaged" for backwards
+compatibility.
+
+On the X31 and X40 (and ONLY on those models), the fan speed can be
+controlled to a certain degree.  Once the fan is running, it can be
+forced to run faster or slower with the following command:
+
+       echo 'speed <speed>' > /proc/acpi/ibm/fan
+
+The sustainable range of fan speeds on the X40 appears to be from about
+3700 to about 7350. Values outside this range either do not have any
+effect or the fan speed eventually settles somewhere in that range.  The
+fan cannot be stopped or started with this command.  This functionality
+is incomplete, and not available through the sysfs interface.
+
+To program the safety watchdog, use the "watchdog" command.
+
+       echo 'watchdog <interval in seconds>' > /proc/acpi/ibm/fan
+
+If you want to disable the watchdog, use 0 as the interval.
+
+Sysfs notes:
+
+The sysfs interface follows the hwmon subsystem guidelines for the most
+part, and the exception is the fan safety watchdog.
+
+Writes to any of the sysfs attributes may return the EINVAL error if
+that operation is not supported in a given ThinkPad or if the parameter
+is out-of-bounds, and EPERM if it is forbidden.  They may also return
+EINTR (interrupted system call), and EIO (I/O error while trying to talk
+to the firmware).
+
+Features not yet implemented by the driver return ENOSYS.
+
+hwmon device attribute pwm1_enable:
+       0: PWM offline (fan is set to full-speed mode)
+       1: Manual PWM control (use pwm1 to set fan level)
+       2: Hardware PWM control (EC "auto" mode)
+       3: reserved (Software PWM control, not implemented yet)
+
+       Modes 0 and 2 are not supported by all ThinkPads, and the
+       driver is not always able to detect this.  If it does know a
+       mode is unsupported, it will return -EINVAL.
+
+hwmon device attribute pwm1:
+       Fan level, scaled from the firmware values of 0-7 to the hwmon
+       scale of 0-255.  0 means fan stopped, 255 means highest normal
+       speed (level 7).
+
+       This attribute only commands the fan if pmw1_enable is set to 1
+       (manual PWM control).
+
+hwmon device attribute fan1_input:
+       Fan tachometer reading, in RPM.  May go stale on certain
+       ThinkPads while the EC transitions the PWM to offline mode,
+       which can take up to two minutes.  May return rubbish on older
+       ThinkPads.
+
+driver attribute fan_watchdog:
+       Fan safety watchdog timer interval, in seconds.  Minimum is
+       1 second, maximum is 120 seconds.  0 disables the watchdog.
+
+To stop the fan: set pwm1 to zero, and pwm1_enable to 1.
+
+To start the fan in a safe mode: set pwm1_enable to 2.  If that fails
+with EINVAL, try to set pwm1_enable to 1 and pwm1 to at least 128 (255
+would be the safest choice, though).
+
+
+EXPERIMENTAL: WAN
+-----------------
+
+procfs: /proc/acpi/ibm/wan
+sysfs device attribute: wwan/enable
+
+This feature is marked EXPERIMENTAL because the implementation
+directly accesses hardware registers and may not work as expected. USE
+WITH CAUTION! To use this feature, you need to supply the
+experimental=1 parameter when loading the module.
+
+This feature shows the presence and current state of a W-WAN (Sierra
+Wireless EV-DO) device.
+
+It was tested on a Lenovo Thinkpad X60. It should probably work on other
+Thinkpad models which come with this module installed.
+
+Procfs notes:
+
+If the W-WAN card is installed, the following commands can be used:
+
+       echo enable > /proc/acpi/ibm/wan
+       echo disable > /proc/acpi/ibm/wan
+
+Sysfs notes:
+
+       If the W-WAN card is installed, it can be enabled /
+       disabled through the "wwan/enable" thinkpad-acpi device
+       attribute, and its current status can also be queried.
+
+       enable:
+               0: disables WWAN card / WWAN card is disabled
+               1: enables WWAN card / WWAN card is enabled.
+
+       Note: this interface will be probably be superseeded by the
+       generic rfkill class.
+
+Multiple Commands, Module Parameters
+------------------------------------
+
+Multiple commands can be written to the proc files in one shot by
+separating them with commas, for example:
+
+       echo enable,0xffff > /proc/acpi/ibm/hotkey
+       echo lcd_disable,crt_enable > /proc/acpi/ibm/video
+
+Commands can also be specified when loading the thinkpad-acpi module,
+for example:
+
+       modprobe thinkpad_acpi hotkey=enable,0xffff video=auto_disable
+
+Enabling debugging output
+-------------------------
+
+The module takes a debug paramater which can be used to selectively
+enable various classes of debugging output, for example:
+
+        modprobe ibm_acpi debug=0xffff
+
+will enable all debugging output classes.  It takes a bitmask, so
+to enable more than one output class, just add their values.
+
+       Debug bitmask           Description
+       0x0001                  Initialization and probing
+       0x0002                  Removal
+
+There is also a kernel build option to enable more debugging
+information, which may be necessary to debug driver problems.
+
+The level of debugging information output by the driver can be changed
+at runtime through sysfs, using the driver attribute debug_level.  The
+attribute takes the same bitmask as the debug module parameter above.
+
+Force loading of module
+-----------------------
+
+If thinkpad-acpi refuses to detect your ThinkPad, you can try to specify
+the module parameter force_load=1.  Regardless of whether this works or
+not, please contact ibm-acpi-devel@lists.sourceforge.net with a report.
+
+
+Sysfs interface changelog:
+
+0x000100:      Initial sysfs support, as a single platform driver and
+               device.
index ecb34160e61d5ca2fafad4bba259b782199cc66e..5e51c59bf2b024e2915d747e76f63d00e11d53f9 100644 (file)
@@ -5,10 +5,9 @@ Vaio Picturebook Motion Eye Camera Driver Readme
        Copyright (C) 2000 Andrew Tridgell <tridge@samba.org>
 
 This driver enable the use of video4linux compatible applications with the
-Motion Eye camera. This driver requires the "Sony Vaio Programmable I/O
-Control Device" driver (which can be found in the "Character drivers"
-section of the kernel configuration utility) to be compiled and installed
-(using its "camera=1" parameter).
+Motion Eye camera. This driver requires the "Sony Laptop Extras" driver (which
+can be found in the "Misc devices" section of the kernel configuration utility)
+to be compiled and installed (using its "camera=1" parameter).
 
 It can do at maximum 30 fps @ 320x240 or 15 fps @ 640x480.
 
index 277877a34ef620f4a43614018e4cd2f89b74d921..d0243d07da20babba967cafdafd22eb8d18e78bd 100644 (file)
@@ -1632,15 +1632,6 @@ W:       http://www.ia64-linux.org/
 T:     git kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
 S:     Maintained
 
-IBM ACPI EXTRAS DRIVER
-P:     Henrique de Moraes Holschuh
-M:     ibm-acpi@hmh.eng.br
-L:     ibm-acpi-devel@lists.sourceforge.net
-W:     http://ibm-acpi.sourceforge.net
-W:     http://thinkwiki.org/wiki/Ibm-acpi
-T:     git repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git
-S:     Maintained
-
 SN-IA64 (Itanium) SUB-PLATFORM
 P:     Jes Sorensen
 M:     jes@sgi.com
@@ -3121,6 +3112,15 @@ P:       Chris Zankel
 M:     chris@zankel.net
 S:     Maintained
 
+THINKPAD ACPI EXTRAS DRIVER
+P:     Henrique de Moraes Holschuh
+M:     ibm-acpi@hmh.eng.br
+L:     ibm-acpi-devel@lists.sourceforge.net
+W:     http://ibm-acpi.sourceforge.net
+W:     http://thinkwiki.org/wiki/Ibm-acpi
+T:     git repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git
+S:     Maintained
+
 UltraSPARC (sparc64):
 P:     David S. Miller
 M:     davem@davemloft.net
index 234bae68cb98ec1eb6d6ef7d4d5b5a1e912cb7bf..d970cb16545ac5adfffbe2b9446afec556803825 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
 SUBLEVEL = 21
-EXTRAVERSION = -rc7
+EXTRAVERSION =
 NAME = Nocturnal Monster Puppy
 
 # *DOCUMENTATION*
index 9e3797167c8183f67e0de413fc307b9177c02a92..f92ba2a869b4fe32348002cf00260ce6ac943542 100644 (file)
@@ -532,6 +532,12 @@ static void cfq_add_rq_rb(struct request *rq)
 
        if (!cfq_cfqq_on_rr(cfqq))
                cfq_add_cfqq_rr(cfqd, cfqq);
+
+       /*
+        * check if this request is a better next-serve candidate
+        */
+       cfqq->next_rq = cfq_choose_req(cfqd, cfqq->next_rq, rq);
+       BUG_ON(!cfqq->next_rq);
 }
 
 static inline void
@@ -1638,12 +1644,6 @@ cfq_rq_enqueued(struct cfq_data *cfqd, struct cfq_queue *cfqq,
        if (rq_is_meta(rq))
                cfqq->meta_pending++;
 
-       /*
-        * check if this request is a better next-serve candidate)) {
-        */
-       cfqq->next_rq = cfq_choose_req(cfqd, cfqq->next_rq, rq);
-       BUG_ON(!cfqq->next_rq);
-
        /*
         * we never wait for an async request and we don't allow preemption
         * of an async request. so just return early
index 2a18d4cd85b15233fdabce6ceb835a28d966eb86..e905c950fd7b9e487f3eeea798c55f5b7ff3374b 100644 (file)
@@ -218,43 +218,6 @@ config ACPI_ASUS
          NOTE: This driver is deprecated and will probably be removed soon,
          use asus-laptop instead.
 
-config ACPI_IBM
-       tristate "IBM ThinkPad Laptop Extras"
-       depends on X86
-       select BACKLIGHT_CLASS_DEVICE
-       ---help---
-         This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds
-         support for Fn-Fx key combinations, Bluetooth control, video
-         output switching, ThinkLight control, UltraBay eject and more.
-         For more information about this driver see <file:Documentation/ibm-acpi.txt>
-         and <http://ibm-acpi.sf.net/> .
-
-         If you have an IBM ThinkPad laptop, say Y or M here.
-
-config ACPI_IBM_DOCK
-       bool "Legacy Docking Station Support"
-       depends on ACPI_IBM
-       depends on ACPI_DOCK=n
-       default n
-       ---help---
-         Allows the ibm_acpi driver to handle docking station events.
-         This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI.  It will
-         allow locking and removing the laptop from the docking station,
-         but will not properly connect PCI devices.
-
-         If you are not sure, say N here.
-
-config ACPI_IBM_BAY
-       bool "Legacy Removable Bay Support"
-       depends on ACPI_IBM
-       default y
-       ---help---
-         Allows the ibm_acpi driver to handle removable bays.  It will allow
-         disabling the device in the bay, and also generate notifications when
-         the bay lever is ejected or inserted.
-
-         If you are not sure, say Y here.
-
 config ACPI_TOSHIBA
        tristate "Toshiba Laptop Extras"
        depends on X86
index 5956e9f64a8bfd682754999267f99f80ba9a7558..92642ab1545166e70c68451d8da54424b2769184 100644 (file)
@@ -55,7 +55,6 @@ obj-$(CONFIG_ACPI_SYSTEM)     += system.o event.o
 obj-$(CONFIG_ACPI_DEBUG)       += debug.o
 obj-$(CONFIG_ACPI_NUMA)                += numa.o
 obj-$(CONFIG_ACPI_ASUS)                += asus_acpi.o
-obj-$(CONFIG_ACPI_IBM)         += ibm_acpi.o
 obj-$(CONFIG_ACPI_TOSHIBA)     += toshiba_acpi.o
 obj-$(CONFIG_ACPI_HOTPLUG_MEMORY)      += acpi_memhotplug.o
 obj-y                          += cm_sbs.o
diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c
deleted file mode 100644 (file)
index dc10966..0000000
+++ /dev/null
@@ -1,2798 +0,0 @@
-/*
- *  ibm_acpi.c - IBM ThinkPad ACPI Extras
- *
- *
- *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
- *  Copyright (C) 2006 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#define IBM_VERSION "0.13"
-
-/*
- *  Changelog:
- *
- *  2006-11-22 0.13    new maintainer
- *                     changelog now lives in git commit history, and will
- *                     not be updated further in-file.
- *  
- *  2005-08-17  0.12   fix compilation on 2.6.13-rc kernels
- *  2005-03-17 0.11    support for 600e, 770x
- *                         thanks to Jamie Lentin <lentinj@dial.pipex.com>
- *                     support for 770e, G41
- *                     G40 and G41 don't have a thinklight
- *                     temperatures no longer experimental
- *                     experimental brightness control
- *                     experimental volume control
- *                     experimental fan enable/disable
- *  2005-01-16 0.10    fix module loading on R30, R31 
- *  2005-01-16 0.9     support for 570, R30, R31
- *                     ultrabay support on A22p, A3x
- *                     limit arg for cmos, led, beep, drop experimental status
- *                     more capable led control on A21e, A22p, T20-22, X20
- *                     experimental temperatures and fan speed
- *                     experimental embedded controller register dump
- *                     mark more functions as __init, drop incorrect __exit
- *                     use MODULE_VERSION
- *                         thanks to Henrik Brix Andersen <brix@gentoo.org>
- *                     fix parameter passing on module loading
- *                         thanks to Rusty Russell <rusty@rustcorp.com.au>
- *                         thanks to Jim Radford <radford@blackbean.org>
- *  2004-11-08 0.8     fix init error case, don't return from a macro
- *                         thanks to Chris Wright <chrisw@osdl.org>
- *  2004-10-23 0.7     fix module loading on A21e, A22p, T20, T21, X20
- *                     fix led control on A21e
- *  2004-10-19 0.6     use acpi_bus_register_driver() to claim HKEY device
- *  2004-10-18 0.5     thinklight support on A21e, G40, R32, T20, T21, X20
- *                     proc file format changed
- *                     video_switch command
- *                     experimental cmos control
- *                     experimental led control
- *                     experimental acpi sounds
- *  2004-09-16 0.4     support for module parameters
- *                     hotkey mask can be prefixed by 0x
- *                     video output switching
- *                     video expansion control
- *                     ultrabay eject support
- *                     removed lcd brightness/on/off control, didn't work
- *  2004-08-17 0.3     support for R40
- *                     lcd off, brightness control
- *                     thinklight on/off
- *  2004-08-14 0.2     support for T series, X20
- *                     bluetooth enable/disable
- *                     hotkey events disabled by default
- *                     removed fan control, currently useless
- *  2004-08-09 0.1     initial release, support for X series
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/types.h>
-#include <linux/string.h>
-
-#include <linux/proc_fs.h>
-#include <linux/backlight.h>
-#include <linux/fb.h>
-#include <asm/uaccess.h>
-
-#include <linux/dmi.h>
-#include <linux/jiffies.h>
-#include <linux/workqueue.h>
-
-#include <acpi/acpi_drivers.h>
-#include <acpi/acnamesp.h>
-
-#define IBM_NAME "ibm"
-#define IBM_DESC "IBM ThinkPad ACPI Extras"
-#define IBM_FILE "ibm_acpi"
-#define IBM_URL "http://ibm-acpi.sf.net/"
-
-MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
-MODULE_DESCRIPTION(IBM_DESC);
-MODULE_VERSION(IBM_VERSION);
-MODULE_LICENSE("GPL");
-
-#define IBM_DIR IBM_NAME
-
-#define IBM_LOG IBM_FILE ": "
-#define IBM_ERR           KERN_ERR    IBM_LOG
-#define IBM_NOTICE KERN_NOTICE IBM_LOG
-#define IBM_INFO   KERN_INFO   IBM_LOG
-#define IBM_DEBUG  KERN_DEBUG  IBM_LOG
-
-#define IBM_MAX_ACPI_ARGS 3
-
-#define __unused __attribute__ ((unused))
-
-static int experimental;
-module_param(experimental, int, 0);
-
-static acpi_handle root_handle = NULL;
-
-#define IBM_HANDLE(object, parent, paths...)                   \
-       static acpi_handle  object##_handle;                    \
-       static acpi_handle *object##_parent = &parent##_handle; \
-       static char        *object##_path;                      \
-       static char        *object##_paths[] = { paths }
-
-IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0",     /* 240, 240x */
-          "\\_SB.PCI.ISA.EC",  /* 570 */
-          "\\_SB.PCI0.ISA0.EC0",       /* 600e/x, 770e, 770x */
-          "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */
-          "\\_SB.PCI0.AD4S.EC0",       /* i1400, R30 */
-          "\\_SB.PCI0.ICH3.EC0",       /* R31 */
-          "\\_SB.PCI0.LPC.EC", /* all others */
-    );
-
-IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA",     /* 570 */
-          "\\_SB.PCI0.AGP0.VID0",      /* 600e/x, 770x */
-          "\\_SB.PCI0.VID0",   /* 770e */
-          "\\_SB.PCI0.VID",    /* A21e, G4x, R50e, X30, X40 */
-          "\\_SB.PCI0.AGP.VID",        /* all others */
-    );                         /* R30, R31 */
-
-IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */
-
-IBM_HANDLE(cmos, root, "\\UCMS",       /* R50, R50e, R50p, R51, T4x, X31, X40 */
-          "\\CMOS",            /* A3x, G4x, R32, T23, T30, X22-24, X30 */
-          "\\CMS",             /* R40, R40e */
-    );                         /* all others */
-#ifdef CONFIG_ACPI_IBM_DOCK
-IBM_HANDLE(dock, root, "\\_SB.GDCK",   /* X30, X31, X40 */
-          "\\_SB.PCI0.DOCK",   /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */
-          "\\_SB.PCI0.PCI1.DOCK",      /* all others */
-          "\\_SB.PCI.ISA.SLCE",        /* 570 */
-    );                         /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */
-#endif
-#ifdef CONFIG_ACPI_IBM_BAY
-IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST",       /* 570 */
-          "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */
-          "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */ 
-          "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */
-    );                         /* A21e, R30, R31 */
-
-IBM_HANDLE(bay_ej, bay, "_EJ3",        /* 600e/x, A2xm/p, A3x */
-          "_EJ0",              /* all others */
-    );                         /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
-
-IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV",    /* A3x, R32 */
-          "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */
-    );                         /* all others */
-
-IBM_HANDLE(bay2_ej, bay2, "_EJ3",      /* 600e/x, 770e, A3x */
-          "_EJ0",              /* 770x */
-    );                         /* all others */
-#endif /* CONFIG_ACPI_IBM_BAY */
-
-/* don't list other alternatives as we install a notify handler on the 570 */
-IBM_HANDLE(pci, root, "\\_SB.PCI");    /* 570 */
-
-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, X41 */
-
-IBM_HANDLE(gfan, ec, "GFAN",   /* 570 */
-          "\\FSPD",            /* 600e/x, 770e, 770x */
-    );                         /* all others */
-
-IBM_HANDLE(sfan, ec, "SFAN",   /* 570 */
-          "JFNS",              /* 770x-JL */
-    );                         /* all others */
-
-#define IBM_HKEY_HID   "IBM0068"
-#define IBM_PCI_HID    "PNP0A03"
-
-enum thermal_access_mode {
-       IBMACPI_THERMAL_NONE = 0,       /* No thermal support */
-       IBMACPI_THERMAL_ACPI_TMP07,     /* Use ACPI TMP0-7 */
-       IBMACPI_THERMAL_ACPI_UPDT,      /* Use ACPI TMP0-7 with UPDT */
-       IBMACPI_THERMAL_TPEC_8,         /* Use ACPI EC regs, 8 sensors */
-       IBMACPI_THERMAL_TPEC_16,        /* Use ACPI EC regs, 16 sensors */
-};
-
-#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
-struct ibm_thermal_sensors_struct {
-       s32 temp[IBMACPI_MAX_THERMAL_SENSORS];
-};
-
-/*
- * FAN ACCESS MODES
- *
- * IBMACPI_FAN_RD_ACPI_GFAN:
- *     ACPI GFAN method: returns fan level
- *
- *     see IBMACPI_FAN_WR_ACPI_SFAN
- *     EC 0x2f not available if GFAN exists
- *
- * IBMACPI_FAN_WR_ACPI_SFAN:
- *     ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
- *
- *     EC 0x2f might be available *for reading*, but never for writing.
- *
- * IBMACPI_FAN_WR_TPEC:
- *     ThinkPad EC register 0x2f (HFSP): fan control loop mode Supported
- *     on almost all ThinkPads
- *
- *     Fan speed changes of any sort (including those caused by the
- *     disengaged mode) are usually done slowly by the firmware as the
- *     maximum ammount of fan duty cycle change per second seems to be
- *     limited.
- *
- *     Reading is not available if GFAN exists.
- *     Writing is not available if SFAN exists.
- *
- *     Bits
- *      7      automatic mode engaged;
- *             (default operation mode of the ThinkPad)
- *             fan level is ignored in this mode.
- *      6      disengage mode (takes precedence over bit 7);
- *             not available on all thinkpads.  May disable
- *             the tachometer, and speeds up fan to 100% duty-cycle,
- *             which speeds it up far above the standard RPM
- *             levels.  It is not impossible that it could cause
- *             hardware damage.
- *     5-3     unused in some models.  Extra bits for fan level
- *             in others, but still useless as all values above
- *             7 map to the same speed as level 7 in these models.
- *     2-0     fan level (0..7 usually)
- *                     0x00 = stop
- *                     0x07 = max (set when temperatures critical)
- *             Some ThinkPads may have other levels, see
- *             IBMACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
- *
- *     FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
- *     boot. Apparently the EC does not intialize it, so unless ACPI DSDT
- *     does so, its initial value is meaningless (0x07).
- *
- *     For firmware bugs, refer to:
- *     http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
- *
- *     ----
- *
- *     ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
- *     Main fan tachometer reading (in RPM)
- *
- *     This register is present on all ThinkPads with a new-style EC, and
- *     it is known not to be present on the A21m/e, and T22, as there is
- *     something else in offset 0x84 according to the ACPI DSDT.  Other
- *     ThinkPads from this same time period (and earlier) probably lack the
- *     tachometer as well.
- *
- *     Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare
- *     was never fixed by IBM to report the EC firmware version string
- *     probably support the tachometer (like the early X models), so
- *     detecting it is quite hard.  We need more data to know for sure.
- *
- *     FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
- *     might result.
- *
- *     FIRMWARE BUG: when EC 0x2f bit 6 is set (disengaged mode), this
- *     register is not invalidated in ThinkPads that disable tachometer
- *     readings.  Thus, the tachometer readings go stale.
- *
- *     For firmware bugs, refer to:
- *     http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
- *
- * IBMACPI_FAN_WR_ACPI_FANS:
- *     ThinkPad X31, X40, X41.  Not available in the X60.
- *
- *     FANS ACPI handle: takes three arguments: low speed, medium speed,
- *     high speed.  ACPI DSDT seems to map these three speeds to levels
- *     as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
- *     (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
- *
- *     The speeds are stored on handles
- *     (FANA:FAN9), (FANC:FANB), (FANE:FAND).
- *
- *     There are three default speed sets, acessible as handles:
- *     FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
- *
- *     ACPI DSDT switches which set is in use depending on various
- *     factors.
- *
- *     IBMACPI_FAN_WR_TPEC is also available and should be used to
- *     command the fan.  The X31/X40/X41 seems to have 8 fan levels,
- *     but the ACPI tables just mention level 7.
- */
-
-enum fan_status_access_mode {
-       IBMACPI_FAN_NONE = 0,           /* No fan status or control */
-       IBMACPI_FAN_RD_ACPI_GFAN,       /* Use ACPI GFAN */
-       IBMACPI_FAN_RD_TPEC,            /* Use ACPI EC regs 0x2f, 0x84-0x85 */
-};
-
-enum fan_control_access_mode {
-       IBMACPI_FAN_WR_NONE = 0,        /* No fan control */
-       IBMACPI_FAN_WR_ACPI_SFAN,       /* Use ACPI SFAN */
-       IBMACPI_FAN_WR_TPEC,            /* Use ACPI EC reg 0x2f */
-       IBMACPI_FAN_WR_ACPI_FANS,       /* Use ACPI FANS and EC reg 0x2f */
-};
-
-enum fan_control_commands {
-       IBMACPI_FAN_CMD_SPEED   = 0x0001,       /* speed command */
-       IBMACPI_FAN_CMD_LEVEL   = 0x0002,       /* level command  */
-       IBMACPI_FAN_CMD_ENABLE  = 0x0004,       /* enable/disable cmd,
-                                                * and also watchdog cmd */
-};
-
-enum {                                 /* Fan control constants */
-       fan_status_offset = 0x2f,       /* EC register 0x2f */
-       fan_rpm_offset = 0x84,          /* EC register 0x84: LSB, 0x85 MSB (RPM)
-                                        * 0x84 must be read before 0x85 */
-
-       IBMACPI_FAN_EC_DISENGAGED       = 0x40, /* EC mode: tachometer
-                                                * disengaged */
-       IBMACPI_FAN_EC_AUTO             = 0x80, /* EC mode: auto fan
-                                                * control */
-};
-
-static char *ibm_thinkpad_ec_found = NULL;
-
-struct ibm_struct {
-       char *name;
-       char param[32];
-
-       char *hid;
-       struct acpi_driver *driver;
-
-       int (*init) (void);
-       int (*read) (char *);
-       int (*write) (char *);
-       void (*exit) (void);
-
-       void (*notify) (struct ibm_struct *, u32);
-       acpi_handle *handle;
-       int type;
-       struct acpi_device *device;
-
-       int driver_registered;
-       int proc_created;
-       int init_called;
-       int notify_installed;
-
-       int experimental;
-};
-
-static struct proc_dir_entry *proc_dir = NULL;
-
-static struct backlight_device *ibm_backlight_device = NULL;
-
-#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
-#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
-#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
-
-static int acpi_evalf(acpi_handle handle,
-                     void *res, char *method, char *fmt, ...)
-{
-       char *fmt0 = fmt;
-       struct acpi_object_list params;
-       union acpi_object in_objs[IBM_MAX_ACPI_ARGS];
-       struct acpi_buffer result, *resultp;
-       union acpi_object out_obj;
-       acpi_status status;
-       va_list ap;
-       char res_type;
-       int success;
-       int quiet;
-
-       if (!*fmt) {
-               printk(IBM_ERR "acpi_evalf() called with empty format\n");
-               return 0;
-       }
-
-       if (*fmt == 'q') {
-               quiet = 1;
-               fmt++;
-       } else
-               quiet = 0;
-
-       res_type = *(fmt++);
-
-       params.count = 0;
-       params.pointer = &in_objs[0];
-
-       va_start(ap, fmt);
-       while (*fmt) {
-               char c = *(fmt++);
-               switch (c) {
-               case 'd':       /* int */
-                       in_objs[params.count].integer.value = va_arg(ap, int);
-                       in_objs[params.count++].type = ACPI_TYPE_INTEGER;
-                       break;
-                       /* add more types as needed */
-               default:
-                       printk(IBM_ERR "acpi_evalf() called "
-                              "with invalid format character '%c'\n", c);
-                       return 0;
-               }
-       }
-       va_end(ap);
-
-       if (res_type != 'v') {
-               result.length = sizeof(out_obj);
-               result.pointer = &out_obj;
-               resultp = &result;
-       } else
-               resultp = NULL;
-
-       status = acpi_evaluate_object(handle, method, &params, resultp);
-
-       switch (res_type) {
-       case 'd':               /* int */
-               if (res)
-                       *(int *)res = out_obj.integer.value;
-               success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
-               break;
-       case 'v':               /* void */
-               success = status == AE_OK;
-               break;
-               /* add more types as needed */
-       default:
-               printk(IBM_ERR "acpi_evalf() called "
-                      "with invalid format character '%c'\n", res_type);
-               return 0;
-       }
-
-       if (!success && !quiet)
-               printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
-                      method, fmt0, status);
-
-       return success;
-}
-
-static void __unused acpi_print_int(acpi_handle handle, char *method)
-{
-       int i;
-
-       if (acpi_evalf(handle, &i, method, "d"))
-               printk(IBM_INFO "%s = 0x%x\n", method, i);
-       else
-               printk(IBM_ERR "error calling %s\n", method);
-}
-
-static char *next_cmd(char **cmds)
-{
-       char *start = *cmds;
-       char *end;
-
-       while ((end = strchr(start, ',')) && end == start)
-               start = end + 1;
-
-       if (!end)
-               return NULL;
-
-       *end = 0;
-       *cmds = end + 1;
-       return start;
-}
-
-static int ibm_acpi_driver_init(void)
-{
-       printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
-       printk(IBM_INFO "%s\n", IBM_URL);
-
-       if (ibm_thinkpad_ec_found)
-               printk(IBM_INFO "ThinkPad EC firmware %s\n",
-                      ibm_thinkpad_ec_found);
-
-       return 0;
-}
-
-static int driver_read(char *p)
-{
-       int len = 0;
-
-       len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC);
-       len += sprintf(p + len, "version:\t%s\n", IBM_VERSION);
-
-       return len;
-}
-
-static int hotkey_supported;
-static int hotkey_mask_supported;
-static int hotkey_orig_status;
-static int hotkey_orig_mask;
-
-static int hotkey_get(int *status, int *mask)
-{
-       if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
-               return 0;
-
-       if (hotkey_mask_supported)
-               if (!acpi_evalf(hkey_handle, mask, "DHKN", "d"))
-                       return 0;
-
-       return 1;
-}
-
-static int hotkey_set(int status, int mask)
-{
-       int i;
-
-       if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
-               return 0;
-
-       if (hotkey_mask_supported)
-               for (i = 0; i < 32; i++) {
-                       int bit = ((1 << i) & mask) != 0;
-                       if (!acpi_evalf(hkey_handle,
-                                       NULL, "MHKM", "vdd", i + 1, bit))
-                               return 0;
-               }
-
-       return 1;
-}
-
-static int hotkey_init(void)
-{
-       /* hotkey not supported on 570 */
-       hotkey_supported = hkey_handle != NULL;
-
-       if (hotkey_supported) {
-               /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
-                  A30, R30, R31, T20-22, X20-21, X22-24 */
-               hotkey_mask_supported =
-                   acpi_evalf(hkey_handle, NULL, "DHKN", "qv");
-
-               if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask))
-                       return -ENODEV;
-       }
-
-       return 0;
-}
-
-static int hotkey_read(char *p)
-{
-       int status, mask;
-       int len = 0;
-
-       if (!hotkey_supported) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-               return len;
-       }
-
-       if (!hotkey_get(&status, &mask))
-               return -EIO;
-
-       len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
-       if (hotkey_mask_supported) {
-               len += sprintf(p + len, "mask:\t\t0x%04x\n", mask);
-               len += sprintf(p + len,
-                              "commands:\tenable, disable, reset, <mask>\n");
-       } else {
-               len += sprintf(p + len, "mask:\t\tnot supported\n");
-               len += sprintf(p + len, "commands:\tenable, disable, reset\n");
-       }
-
-       return len;
-}
-
-static int hotkey_write(char *buf)
-{
-       int status, mask;
-       char *cmd;
-       int do_cmd = 0;
-
-       if (!hotkey_supported)
-               return -ENODEV;
-
-       if (!hotkey_get(&status, &mask))
-               return -EIO;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "enable") == 0) {
-                       status = 1;
-               } else if (strlencmp(cmd, "disable") == 0) {
-                       status = 0;
-               } else if (strlencmp(cmd, "reset") == 0) {
-                       status = hotkey_orig_status;
-                       mask = hotkey_orig_mask;
-               } else if (sscanf(cmd, "0x%x", &mask) == 1) {
-                       /* mask set */
-               } else if (sscanf(cmd, "%x", &mask) == 1) {
-                       /* mask set */
-               } else
-                       return -EINVAL;
-               do_cmd = 1;
-       }
-
-       if (do_cmd && !hotkey_set(status, mask))
-               return -EIO;
-
-       return 0;
-}
-
-static void hotkey_exit(void)
-{
-       if (hotkey_supported)
-               hotkey_set(hotkey_orig_status, hotkey_orig_mask);
-}
-
-static void hotkey_notify(struct ibm_struct *ibm, u32 event)
-{
-       int hkey;
-
-       if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d"))
-               acpi_bus_generate_event(ibm->device, event, hkey);
-       else {
-               printk(IBM_ERR "unknown hotkey event %d\n", event);
-               acpi_bus_generate_event(ibm->device, event, 0);
-       }
-}
-
-static int bluetooth_supported;
-
-static int bluetooth_init(void)
-{
-       /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
-          G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
-       bluetooth_supported = hkey_handle &&
-           acpi_evalf(hkey_handle, NULL, "GBDC", "qv");
-
-       return 0;
-}
-
-static int bluetooth_status(void)
-{
-       int status;
-
-       if (!bluetooth_supported ||
-           !acpi_evalf(hkey_handle, &status, "GBDC", "d"))
-               status = 0;
-
-       return status;
-}
-
-static int bluetooth_read(char *p)
-{
-       int len = 0;
-       int status = bluetooth_status();
-
-       if (!bluetooth_supported)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       else if (!(status & 1))
-               len += sprintf(p + len, "status:\t\tnot installed\n");
-       else {
-               len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1));
-               len += sprintf(p + len, "commands:\tenable, disable\n");
-       }
-
-       return len;
-}
-
-static int bluetooth_write(char *buf)
-{
-       int status = bluetooth_status();
-       char *cmd;
-       int do_cmd = 0;
-
-       if (!bluetooth_supported)
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "enable") == 0) {
-                       status |= 2;
-               } else if (strlencmp(cmd, "disable") == 0) {
-                       status &= ~2;
-               } else
-                       return -EINVAL;
-               do_cmd = 1;
-       }
-
-       if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
-               return -EIO;
-
-       return 0;
-}
-
-static int wan_supported;
-
-static int wan_init(void)
-{
-       wan_supported = hkey_handle &&
-           acpi_evalf(hkey_handle, NULL, "GWAN", "qv");
-
-       return 0;
-}
-
-static int wan_status(void)
-{
-       int status;
-
-       if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d"))
-               status = 0;
-
-       return status;
-}
-
-static int wan_read(char *p)
-{
-       int len = 0;
-       int status = wan_status();
-
-       if (!wan_supported)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       else if (!(status & 1))
-               len += sprintf(p + len, "status:\t\tnot installed\n");
-       else {
-               len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1));
-               len += sprintf(p + len, "commands:\tenable, disable\n");
-       }
-
-       return len;
-}
-
-static int wan_write(char *buf)
-{
-       int status = wan_status();
-       char *cmd;
-       int do_cmd = 0;
-
-       if (!wan_supported)
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "enable") == 0) {
-                       status |= 2;
-               } else if (strlencmp(cmd, "disable") == 0) {
-                       status &= ~2;
-               } else
-                       return -EINVAL;
-               do_cmd = 1;
-       }
-
-       if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
-               return -EIO;
-
-       return 0;
-}
-
-enum video_access_mode {
-       IBMACPI_VIDEO_NONE = 0,
-       IBMACPI_VIDEO_570,      /* 570 */
-       IBMACPI_VIDEO_770,      /* 600e/x, 770e, 770x */
-       IBMACPI_VIDEO_NEW,      /* all others */
-};
-
-static enum video_access_mode video_supported;
-static int video_orig_autosw;
-
-static int video_init(void)
-{
-       int ivga;
-
-       if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
-               /* G41, assume IVGA doesn't change */
-               vid_handle = vid2_handle;
-
-       if (!vid_handle)
-               /* video switching not supported on R30, R31 */
-               video_supported = IBMACPI_VIDEO_NONE;
-       else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
-               /* 570 */
-               video_supported = IBMACPI_VIDEO_570;
-       else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
-               /* 600e/x, 770e, 770x */
-               video_supported = IBMACPI_VIDEO_770;
-       else
-               /* all others */
-               video_supported = IBMACPI_VIDEO_NEW;
-
-       return 0;
-}
-
-static int video_status(void)
-{
-       int status = 0;
-       int i;
-
-       if (video_supported == IBMACPI_VIDEO_570) {
-               if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87))
-                       status = i & 3;
-       } else if (video_supported == IBMACPI_VIDEO_770) {
-               if (acpi_evalf(NULL, &i, "\\VCDL", "d"))
-                       status |= 0x01 * i;
-               if (acpi_evalf(NULL, &i, "\\VCDC", "d"))
-                       status |= 0x02 * i;
-       } else if (video_supported == IBMACPI_VIDEO_NEW) {
-               acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1);
-               if (acpi_evalf(NULL, &i, "\\VCDC", "d"))
-                       status |= 0x02 * i;
-
-               acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0);
-               if (acpi_evalf(NULL, &i, "\\VCDL", "d"))
-                       status |= 0x01 * i;
-               if (acpi_evalf(NULL, &i, "\\VCDD", "d"))
-                       status |= 0x08 * i;
-       }
-
-       return status;
-}
-
-static int video_autosw(void)
-{
-       int autosw = 0;
-
-       if (video_supported == IBMACPI_VIDEO_570)
-               acpi_evalf(vid_handle, &autosw, "SWIT", "d");
-       else if (video_supported == IBMACPI_VIDEO_770 ||
-                video_supported == IBMACPI_VIDEO_NEW)
-               acpi_evalf(vid_handle, &autosw, "^VDEE", "d");
-
-       return autosw & 1;
-}
-
-static int video_read(char *p)
-{
-       int status = video_status();
-       int autosw = video_autosw();
-       int len = 0;
-
-       if (!video_supported) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-               return len;
-       }
-
-       len += sprintf(p + len, "status:\t\tsupported\n");
-       len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
-       len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
-       if (video_supported == IBMACPI_VIDEO_NEW)
-               len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
-       len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
-       len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
-       len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
-       if (video_supported == IBMACPI_VIDEO_NEW)
-               len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
-       len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
-       len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
-
-       return len;
-}
-
-static int video_switch(void)
-{
-       int autosw = video_autosw();
-       int ret;
-
-       if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1))
-               return -EIO;
-       ret = video_supported == IBMACPI_VIDEO_570 ?
-           acpi_evalf(ec_handle, NULL, "_Q16", "v") :
-           acpi_evalf(vid_handle, NULL, "VSWT", "v");
-       acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw);
-
-       return ret;
-}
-
-static int video_expand(void)
-{
-       if (video_supported == IBMACPI_VIDEO_570)
-               return acpi_evalf(ec_handle, NULL, "_Q17", "v");
-       else if (video_supported == IBMACPI_VIDEO_770)
-               return acpi_evalf(vid_handle, NULL, "VEXP", "v");
-       else
-               return acpi_evalf(NULL, NULL, "\\VEXP", "v");
-}
-
-static int video_switch2(int status)
-{
-       int ret;
-
-       if (video_supported == IBMACPI_VIDEO_570) {
-               ret = acpi_evalf(NULL, NULL,
-                                "\\_SB.PHS2", "vdd", 0x8b, status | 0x80);
-       } else if (video_supported == IBMACPI_VIDEO_770) {
-               int autosw = video_autosw();
-               if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1))
-                       return -EIO;
-
-               ret = acpi_evalf(vid_handle, NULL,
-                                "ASWT", "vdd", status * 0x100, 0);
-
-               acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw);
-       } else {
-               ret = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
-                   acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
-       }
-
-       return ret;
-}
-
-static int video_write(char *buf)
-{
-       char *cmd;
-       int enable, disable, status;
-
-       if (!video_supported)
-               return -ENODEV;
-
-       enable = disable = 0;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "lcd_enable") == 0) {
-                       enable |= 0x01;
-               } else if (strlencmp(cmd, "lcd_disable") == 0) {
-                       disable |= 0x01;
-               } else if (strlencmp(cmd, "crt_enable") == 0) {
-                       enable |= 0x02;
-               } else if (strlencmp(cmd, "crt_disable") == 0) {
-                       disable |= 0x02;
-               } else if (video_supported == IBMACPI_VIDEO_NEW &&
-                          strlencmp(cmd, "dvi_enable") == 0) {
-                       enable |= 0x08;
-               } else if (video_supported == IBMACPI_VIDEO_NEW &&
-                          strlencmp(cmd, "dvi_disable") == 0) {
-                       disable |= 0x08;
-               } else if (strlencmp(cmd, "auto_enable") == 0) {
-                       if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1))
-                               return -EIO;
-               } else if (strlencmp(cmd, "auto_disable") == 0) {
-                       if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0))
-                               return -EIO;
-               } else if (strlencmp(cmd, "video_switch") == 0) {
-                       if (!video_switch())
-                               return -EIO;
-               } else if (strlencmp(cmd, "expand_toggle") == 0) {
-                       if (!video_expand())
-                               return -EIO;
-               } else
-                       return -EINVAL;
-       }
-
-       if (enable || disable) {
-               status = (video_status() & 0x0f & ~disable) | enable;
-               if (!video_switch2(status))
-                       return -EIO;
-       }
-
-       return 0;
-}
-
-static void video_exit(void)
-{
-       acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw);
-}
-
-static int light_supported;
-static int light_status_supported;
-
-static int light_init(void)
-{
-       /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
-       light_supported = (cmos_handle || lght_handle) && !ledb_handle;
-
-       if (light_supported)
-               /* light status not supported on
-                  570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
-               light_status_supported = acpi_evalf(ec_handle, NULL,
-                                                   "KBLT", "qv");
-
-       return 0;
-}
-
-static int light_read(char *p)
-{
-       int len = 0;
-       int status = 0;
-
-       if (!light_supported) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       } else if (!light_status_supported) {
-               len += sprintf(p + len, "status:\t\tunknown\n");
-               len += sprintf(p + len, "commands:\ton, off\n");
-       } else {
-               if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
-                       return -EIO;
-               len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
-               len += sprintf(p + len, "commands:\ton, off\n");
-       }
-
-       return len;
-}
-
-static int light_write(char *buf)
-{
-       int cmos_cmd, lght_cmd;
-       char *cmd;
-       int success;
-
-       if (!light_supported)
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "on") == 0) {
-                       cmos_cmd = 0x0c;
-                       lght_cmd = 1;
-               } else if (strlencmp(cmd, "off") == 0) {
-                       cmos_cmd = 0x0d;
-                       lght_cmd = 0;
-               } else
-                       return -EINVAL;
-
-               success = cmos_handle ?
-                   acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) :
-                   acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd);
-               if (!success)
-                       return -EIO;
-       }
-
-       return 0;
-}
-
-#if defined(CONFIG_ACPI_IBM_DOCK) || defined(CONFIG_ACPI_IBM_BAY)
-static int _sta(acpi_handle handle)
-{
-       int status;
-
-       if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
-               status = 0;
-
-       return status;
-}
-#endif
-
-#ifdef CONFIG_ACPI_IBM_DOCK
-#define dock_docked() (_sta(dock_handle) & 1)
-
-static int dock_read(char *p)
-{
-       int len = 0;
-       int docked = dock_docked();
-
-       if (!dock_handle)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       else if (!docked)
-               len += sprintf(p + len, "status:\t\tundocked\n");
-       else {
-               len += sprintf(p + len, "status:\t\tdocked\n");
-               len += sprintf(p + len, "commands:\tdock, undock\n");
-       }
-
-       return len;
-}
-
-static int dock_write(char *buf)
-{
-       char *cmd;
-
-       if (!dock_docked())
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "undock") == 0) {
-                       if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) ||
-                           !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1))
-                               return -EIO;
-               } else if (strlencmp(cmd, "dock") == 0) {
-                       if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
-                               return -EIO;
-               } else
-                       return -EINVAL;
-       }
-
-       return 0;
-}
-
-static void dock_notify(struct ibm_struct *ibm, u32 event)
-{
-       int docked = dock_docked();
-       int pci = ibm->hid && strstr(ibm->hid, IBM_PCI_HID);
-
-       if (event == 1 && !pci) /* 570 */
-               acpi_bus_generate_event(ibm->device, event, 1); /* button */
-       else if (event == 1 && pci)     /* 570 */
-               acpi_bus_generate_event(ibm->device, event, 3); /* dock */
-       else if (event == 3 && docked)
-               acpi_bus_generate_event(ibm->device, event, 1); /* button */
-       else if (event == 3 && !docked)
-               acpi_bus_generate_event(ibm->device, event, 2); /* undock */
-       else if (event == 0 && docked)
-               acpi_bus_generate_event(ibm->device, event, 3); /* dock */
-       else {
-               printk(IBM_ERR "unknown dock event %d, status %d\n",
-                      event, _sta(dock_handle));
-               acpi_bus_generate_event(ibm->device, event, 0); /* unknown */
-       }
-}
-#endif
-
-#ifdef CONFIG_ACPI_IBM_BAY
-static int bay_status_supported;
-static int bay_status2_supported;
-static int bay_eject_supported;
-static int bay_eject2_supported;
-
-static int bay_init(void)
-{
-       bay_status_supported = bay_handle &&
-           acpi_evalf(bay_handle, NULL, "_STA", "qv");
-       bay_status2_supported = bay2_handle &&
-           acpi_evalf(bay2_handle, NULL, "_STA", "qv");
-
-       bay_eject_supported = bay_handle && bay_ej_handle &&
-           (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental);
-       bay_eject2_supported = bay2_handle && bay2_ej_handle &&
-           (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental);
-
-       return 0;
-}
-
-#define bay_occupied(b) (_sta(b##_handle) & 1)
-
-static int bay_read(char *p)
-{
-       int len = 0;
-       int occupied = bay_occupied(bay);
-       int occupied2 = bay_occupied(bay2);
-       int eject, eject2;
-
-       len += sprintf(p + len, "status:\t\t%s\n", bay_status_supported ?
-                      (occupied ? "occupied" : "unoccupied") :
-                      "not supported");
-       if (bay_status2_supported)
-               len += sprintf(p + len, "status2:\t%s\n", occupied2 ?
-                              "occupied" : "unoccupied");
-
-       eject = bay_eject_supported && occupied;
-       eject2 = bay_eject2_supported && occupied2;
-
-       if (eject && eject2)
-               len += sprintf(p + len, "commands:\teject, eject2\n");
-       else if (eject)
-               len += sprintf(p + len, "commands:\teject\n");
-       else if (eject2)
-               len += sprintf(p + len, "commands:\teject2\n");
-
-       return len;
-}
-
-static int bay_write(char *buf)
-{
-       char *cmd;
-
-       if (!bay_eject_supported && !bay_eject2_supported)
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (bay_eject_supported && strlencmp(cmd, "eject") == 0) {
-                       if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1))
-                               return -EIO;
-               } else if (bay_eject2_supported &&
-                          strlencmp(cmd, "eject2") == 0) {
-                       if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1))
-                               return -EIO;
-               } else
-                       return -EINVAL;
-       }
-
-       return 0;
-}
-
-static void bay_notify(struct ibm_struct *ibm, u32 event)
-{
-       acpi_bus_generate_event(ibm->device, event, 0);
-}
-#endif /* CONFIG_ACPI_IBM_BAY */
-
-static int cmos_read(char *p)
-{
-       int len = 0;
-
-       /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
-          R30, R31, T20-22, X20-21 */
-       if (!cmos_handle)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       else {
-               len += sprintf(p + len, "status:\t\tsupported\n");
-               len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
-       }
-
-       return len;
-}
-
-static int cmos_eval(int cmos_cmd)
-{
-       if (cmos_handle)
-               return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd);
-       else
-               return 1;
-}
-
-static int cmos_write(char *buf)
-{
-       char *cmd;
-       int cmos_cmd;
-
-       if (!cmos_handle)
-               return -EINVAL;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
-                   cmos_cmd >= 0 && cmos_cmd <= 21) {
-                       /* cmos_cmd set */
-               } else
-                       return -EINVAL;
-
-               if (!cmos_eval(cmos_cmd))
-                       return -EIO;
-       }
-
-       return 0;
-}
-
-enum led_access_mode {
-       IBMACPI_LED_NONE = 0,
-       IBMACPI_LED_570,        /* 570 */
-       IBMACPI_LED_OLD,        /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
-       IBMACPI_LED_NEW,        /* all others */
-};
-static enum led_access_mode led_supported;
-
-static int led_init(void)
-{
-       if (!led_handle)
-               /* led not supported on R30, R31 */
-               led_supported = IBMACPI_LED_NONE;
-       else if (strlencmp(led_path, "SLED") == 0)
-               /* 570 */
-               led_supported = IBMACPI_LED_570;
-       else if (strlencmp(led_path, "SYSL") == 0)
-               /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
-               led_supported = IBMACPI_LED_OLD;
-       else
-               /* all others */
-               led_supported = IBMACPI_LED_NEW;
-
-       return 0;
-}
-
-#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking"))
-
-static int led_read(char *p)
-{
-       int len = 0;
-
-       if (!led_supported) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-               return len;
-       }
-       len += sprintf(p + len, "status:\t\tsupported\n");
-
-       if (led_supported == IBMACPI_LED_570) {
-               /* 570 */
-               int i, status;
-               for (i = 0; i < 8; i++) {
-                       if (!acpi_evalf(ec_handle,
-                                       &status, "GLED", "dd", 1 << i))
-                               return -EIO;
-                       len += sprintf(p + len, "%d:\t\t%s\n",
-                                      i, led_status(status));
-               }
-       }
-
-       len += sprintf(p + len, "commands:\t"
-                      "<led> on, <led> off, <led> blink (<led> is 0-7)\n");
-
-       return len;
-}
-
-/* off, on, blink */
-static const int led_sled_arg1[] = { 0, 1, 3 };
-static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */
-static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */
-static const int led_led_arg1[] = { 0, 0x80, 0xc0 };
-
-#define EC_HLCL 0x0c
-#define EC_HLBL 0x0d
-#define EC_HLMS 0x0e
-
-static int led_write(char *buf)
-{
-       char *cmd;
-       int led, ind, ret;
-
-       if (!led_supported)
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7)
-                       return -EINVAL;
-
-               if (strstr(cmd, "off")) {
-                       ind = 0;
-               } else if (strstr(cmd, "on")) {
-                       ind = 1;
-               } else if (strstr(cmd, "blink")) {
-                       ind = 2;
-               } else
-                       return -EINVAL;
-
-               if (led_supported == IBMACPI_LED_570) {
-                       /* 570 */
-                       led = 1 << led;
-                       if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
-                                       led, led_sled_arg1[ind]))
-                               return -EIO;
-               } else if (led_supported == IBMACPI_LED_OLD) {
-                       /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
-                       led = 1 << led;
-                       ret = ec_write(EC_HLMS, led);
-                       if (ret >= 0)
-                               ret =
-                                   ec_write(EC_HLBL, led * led_exp_hlbl[ind]);
-                       if (ret >= 0)
-                               ret =
-                                   ec_write(EC_HLCL, led * led_exp_hlcl[ind]);
-                       if (ret < 0)
-                               return ret;
-               } else {
-                       /* all others */
-                       if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
-                                       led, led_led_arg1[ind]))
-                               return -EIO;
-               }
-       }
-
-       return 0;
-}
-
-static int beep_read(char *p)
-{
-       int len = 0;
-
-       if (!beep_handle)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       else {
-               len += sprintf(p + len, "status:\t\tsupported\n");
-               len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
-       }
-
-       return len;
-}
-
-static int beep_write(char *buf)
-{
-       char *cmd;
-       int beep_cmd;
-
-       if (!beep_handle)
-               return -ENODEV;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
-                   beep_cmd >= 0 && beep_cmd <= 17) {
-                       /* beep_cmd set */
-               } else
-                       return -EINVAL;
-               if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
-                       return -EIO;
-       }
-
-       return 0;
-}
-
-static int acpi_ec_read(int i, u8 * p)
-{
-       int v;
-
-       if (ecrd_handle) {
-               if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
-                       return 0;
-               *p = v;
-       } else {
-               if (ec_read(i, p) < 0)
-                       return 0;
-       }
-
-       return 1;
-}
-
-static int acpi_ec_write(int i, u8 v)
-{
-       if (ecwr_handle) {
-               if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
-                       return 0;
-       } else {
-               if (ec_write(i, v) < 0)
-                       return 0;
-       }
-
-       return 1;
-}
-
-static enum thermal_access_mode thermal_read_mode;
-
-static int thermal_init(void)
-{
-       u8 t, ta1, ta2;
-       int i;
-       int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
-
-       if (ibm_thinkpad_ec_found && experimental) {
-               /*
-                * Direct EC access mode: sensors at registers
-                * 0x78-0x7F, 0xC0-0xC7.  Registers return 0x00 for
-                * non-implemented, thermal sensors return 0x80 when
-                * not available
-                */
-
-               ta1 = ta2 = 0;
-               for (i = 0; i < 8; i++) {
-                       if (likely(acpi_ec_read(0x78 + i, &t))) {
-                               ta1 |= t;
-                       } else {
-                               ta1 = 0;
-                               break;
-                       }
-                       if (likely(acpi_ec_read(0xC0 + i, &t))) {
-                               ta2 |= t;
-                       } else {
-                               ta1 = 0;
-                               break;
-                       }
-               }
-               if (ta1 == 0) {
-                       /* This is sheer paranoia, but we handle it anyway */
-                       if (acpi_tmp7) {
-                               printk(IBM_ERR
-                                      "ThinkPad ACPI EC access misbehaving, "
-                                      "falling back to ACPI TMPx access mode\n");
-                               thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07;
-                       } else {
-                               printk(IBM_ERR
-                                      "ThinkPad ACPI EC access misbehaving, "
-                                      "disabling thermal sensors access\n");
-                               thermal_read_mode = IBMACPI_THERMAL_NONE;
-                       }
-               } else {
-                       thermal_read_mode =
-                           (ta2 != 0) ?
-                           IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8;
-               }
-       } else if (acpi_tmp7) {
-               if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
-                       /* 600e/x, 770e, 770x */
-                       thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT;
-               } else {
-                       /* Standard ACPI TMPx access, max 8 sensors */
-                       thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07;
-               }
-       } else {
-               /* temperatures not supported on 570, G4x, R30, R31, R32 */
-               thermal_read_mode = IBMACPI_THERMAL_NONE;
-       }
-
-       return 0;
-}
-
-static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
-{
-       int i, t;
-       s8 tmp;
-       char tmpi[] = "TMPi";
-
-       if (!s)
-               return -EINVAL;
-
-       switch (thermal_read_mode) {
-#if IBMACPI_MAX_THERMAL_SENSORS >= 16
-       case IBMACPI_THERMAL_TPEC_16:
-               for (i = 0; i < 8; i++) {
-                       if (!acpi_ec_read(0xC0 + i, &tmp))
-                               return -EIO;
-                       s->temp[i + 8] = tmp * 1000;
-               }
-               /* fallthrough */
-#endif
-       case IBMACPI_THERMAL_TPEC_8:
-               for (i = 0; i < 8; i++) {
-                       if (!acpi_ec_read(0x78 + i, &tmp))
-                               return -EIO;
-                       s->temp[i] = tmp * 1000;
-               }
-               return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8;
-
-       case IBMACPI_THERMAL_ACPI_UPDT:
-               if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
-                       return -EIO;
-               for (i = 0; i < 8; i++) {
-                       tmpi[3] = '0' + i;
-                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
-                               return -EIO;
-                       s->temp[i] = (t - 2732) * 100;
-               }
-               return 8;
-
-       case IBMACPI_THERMAL_ACPI_TMP07:
-               for (i = 0; i < 8; i++) {
-                       tmpi[3] = '0' + i;
-                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
-                               return -EIO;
-                       s->temp[i] = t * 1000;
-               }
-               return 8;
-
-       case IBMACPI_THERMAL_NONE:
-       default:
-               return 0;
-       }
-}
-
-static int thermal_read(char *p)
-{
-       int len = 0;
-       int n, i;
-       struct ibm_thermal_sensors_struct t;
-
-       n = thermal_get_sensors(&t);
-       if (unlikely(n < 0))
-               return n;
-
-       len += sprintf(p + len, "temperatures:\t");
-
-       if (n > 0) {
-               for (i = 0; i < (n - 1); i++)
-                       len += sprintf(p + len, "%d ", t.temp[i] / 1000);
-               len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
-       } else
-               len += sprintf(p + len, "not supported\n");
-
-       return len;
-}
-
-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 brightness_offset = 0x31;
-
-static int brightness_get(struct backlight_device *bd)
-{
-       u8 level;
-       if (!acpi_ec_read(brightness_offset, &level))
-               return -EIO;
-
-       level &= 0x7;
-
-       return level;
-}
-
-static int brightness_read(char *p)
-{
-       int len = 0;
-       int level;
-
-       if ((level = brightness_get(NULL)) < 0) {
-               len += sprintf(p + len, "level:\t\tunreadable\n");
-       } else {
-               len += sprintf(p + len, "level:\t\t%d\n", level & 0x7);
-               len += sprintf(p + len, "commands:\tup, down\n");
-               len += sprintf(p + len, "commands:\tlevel <level>"
-                              " (<level> is 0-7)\n");
-       }
-
-       return len;
-}
-
-#define BRIGHTNESS_UP  4
-#define BRIGHTNESS_DOWN        5
-
-static int brightness_set(int value)
-{
-       int cmos_cmd, inc, i;
-       int current_value = brightness_get(NULL);
-
-       value &= 7;
-
-       cmos_cmd = value > current_value ? BRIGHTNESS_UP : BRIGHTNESS_DOWN;
-       inc = value > current_value ? 1 : -1;
-       for (i = current_value; i != value; i += inc) {
-               if (!cmos_eval(cmos_cmd))
-                       return -EIO;
-               if (!acpi_ec_write(brightness_offset, i + inc))
-                       return -EIO;
-       }
-
-       return 0;
-}
-
-static int brightness_write(char *buf)
-{
-       int level;
-       int new_level;
-       char *cmd;
-
-       while ((cmd = next_cmd(&buf))) {
-               if ((level = brightness_get(NULL)) < 0)
-                       return level;
-               level &= 7;
-
-               if (strlencmp(cmd, "up") == 0) {
-                       new_level = level == 7 ? 7 : level + 1;
-               } else if (strlencmp(cmd, "down") == 0) {
-                       new_level = level == 0 ? 0 : level - 1;
-               } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
-                          new_level >= 0 && new_level <= 7) {
-                       /* new_level set */
-               } else
-                       return -EINVAL;
-
-               brightness_set(new_level);
-       }
-
-       return 0;
-}
-
-static int brightness_update_status(struct backlight_device *bd)
-{
-       return brightness_set(
-               (bd->props.fb_blank == FB_BLANK_UNBLANK &&
-                bd->props.power == FB_BLANK_UNBLANK) ?
-                               bd->props.brightness : 0);
-}
-
-static struct backlight_ops ibm_backlight_data = {
-        .get_brightness = brightness_get,
-        .update_status  = brightness_update_status,
-};
-
-static int brightness_init(void)
-{
-       int b;
-
-       b = brightness_get(NULL);
-       if (b < 0)
-               return b;
-
-       ibm_backlight_device = backlight_device_register("ibm", NULL, NULL,
-                                                        &ibm_backlight_data);
-       if (IS_ERR(ibm_backlight_device)) {
-               printk(IBM_ERR "Could not register backlight device\n");
-               return PTR_ERR(ibm_backlight_device);
-       }
-
-       ibm_backlight_device->props.max_brightness = 7;
-       ibm_backlight_device->props.brightness = b;
-       backlight_update_status(ibm_backlight_device);
-
-       return 0;
-}
-
-static void brightness_exit(void)
-{
-       if (ibm_backlight_device) {
-               backlight_device_unregister(ibm_backlight_device);
-               ibm_backlight_device = NULL;
-       }
-}
-
-static int volume_offset = 0x30;
-
-static int volume_read(char *p)
-{
-       int len = 0;
-       u8 level;
-
-       if (!acpi_ec_read(volume_offset, &level)) {
-               len += sprintf(p + len, "level:\t\tunreadable\n");
-       } else {
-               len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
-               len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
-               len += sprintf(p + len, "commands:\tup, down, mute\n");
-               len += sprintf(p + len, "commands:\tlevel <level>"
-                              " (<level> is 0-15)\n");
-       }
-
-       return len;
-}
-
-#define VOLUME_DOWN    0
-#define VOLUME_UP      1
-#define VOLUME_MUTE    2
-
-static int volume_write(char *buf)
-{
-       int cmos_cmd, inc, i;
-       u8 level, mute;
-       int new_level, new_mute;
-       char *cmd;
-
-       while ((cmd = next_cmd(&buf))) {
-               if (!acpi_ec_read(volume_offset, &level))
-                       return -EIO;
-               new_mute = mute = level & 0x40;
-               new_level = level = level & 0xf;
-
-               if (strlencmp(cmd, "up") == 0) {
-                       if (mute)
-                               new_mute = 0;
-                       else
-                               new_level = level == 15 ? 15 : level + 1;
-               } else if (strlencmp(cmd, "down") == 0) {
-                       if (mute)
-                               new_mute = 0;
-                       else
-                               new_level = level == 0 ? 0 : level - 1;
-               } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
-                          new_level >= 0 && new_level <= 15) {
-                       /* new_level set */
-               } else if (strlencmp(cmd, "mute") == 0) {
-                       new_mute = 0x40;
-               } else
-                       return -EINVAL;
-
-               if (new_level != level) {       /* mute doesn't change */
-                       cmos_cmd = new_level > level ? VOLUME_UP : VOLUME_DOWN;
-                       inc = new_level > level ? 1 : -1;
-
-                       if (mute && (!cmos_eval(cmos_cmd) ||
-                                    !acpi_ec_write(volume_offset, level)))
-                               return -EIO;
-
-                       for (i = level; i != new_level; i += inc)
-                               if (!cmos_eval(cmos_cmd) ||
-                                   !acpi_ec_write(volume_offset, i + inc))
-                                       return -EIO;
-
-                       if (mute && (!cmos_eval(VOLUME_MUTE) ||
-                                    !acpi_ec_write(volume_offset,
-                                                   new_level + mute)))
-                               return -EIO;
-               }
-
-               if (new_mute != mute) { /* level doesn't change */
-                       cmos_cmd = new_mute ? VOLUME_MUTE : VOLUME_UP;
-
-                       if (!cmos_eval(cmos_cmd) ||
-                           !acpi_ec_write(volume_offset, level + new_mute))
-                               return -EIO;
-               }
-       }
-
-       return 0;
-}
-
-static enum fan_status_access_mode fan_status_access_mode;
-static enum fan_control_access_mode fan_control_access_mode;
-static enum fan_control_commands fan_control_commands;
-
-static int fan_control_status_known;
-static u8 fan_control_initial_status;
-
-static void fan_watchdog_fire(struct work_struct *ignored);
-static int fan_watchdog_maxinterval;
-static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
-
-static int fan_init(void)
-{
-       fan_status_access_mode = IBMACPI_FAN_NONE;
-       fan_control_access_mode = IBMACPI_FAN_WR_NONE;
-       fan_control_commands = 0;
-       fan_control_status_known = 1;
-       fan_watchdog_maxinterval = 0;
-
-       if (gfan_handle) {
-               /* 570, 600e/x, 770e, 770x */
-               fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN;
-       } else {
-               /* all other ThinkPads: note that even old-style
-                * ThinkPad ECs supports the fan control register */
-               if (likely(acpi_ec_read(fan_status_offset,
-                                       &fan_control_initial_status))) {
-                       fan_status_access_mode = IBMACPI_FAN_RD_TPEC;
-
-                       /* In some ThinkPads, neither the EC nor the ACPI
-                        * DSDT initialize the fan status, and it ends up
-                        * being set to 0x07 when it *could* be either
-                        * 0x07 or 0x80.
-                        *
-                        * Enable for TP-1Y (T43), TP-78 (R51e),
-                        * TP-76 (R52), TP-70 (T43, R52), which are known
-                        * to be buggy. */
-                       if (fan_control_initial_status == 0x07 &&
-                           ibm_thinkpad_ec_found &&
-                           ((ibm_thinkpad_ec_found[0] == '1' &&
-                             ibm_thinkpad_ec_found[1] == 'Y') ||
-                            (ibm_thinkpad_ec_found[0] == '7' &&
-                             (ibm_thinkpad_ec_found[1] == '6' ||
-                              ibm_thinkpad_ec_found[1] == '8' ||
-                              ibm_thinkpad_ec_found[1] == '0'))
-                           )) {
-                               printk(IBM_NOTICE
-                                      "fan_init: initial fan status is "
-                                      "unknown, assuming it is in auto "
-                                      "mode\n");
-                               fan_control_status_known = 0;
-                       }
-               } else {
-                       printk(IBM_ERR
-                              "ThinkPad ACPI EC access misbehaving, "
-                              "fan status and control unavailable\n");
-                       return 0;
-               }
-       }
-
-       if (sfan_handle) {
-               /* 570, 770x-JL */
-               fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN;
-               fan_control_commands |=
-                   IBMACPI_FAN_CMD_LEVEL | IBMACPI_FAN_CMD_ENABLE;
-       } else {
-               if (!gfan_handle) {
-                       /* gfan without sfan means no fan control */
-                       /* all other models implement TP EC 0x2f control */
-
-                       if (fans_handle) {
-                               /* X31, X40, X41 */
-                               fan_control_access_mode =
-                                   IBMACPI_FAN_WR_ACPI_FANS;
-                               fan_control_commands |=
-                                   IBMACPI_FAN_CMD_SPEED |
-                                   IBMACPI_FAN_CMD_LEVEL |
-                                   IBMACPI_FAN_CMD_ENABLE;
-                       } else {
-                               fan_control_access_mode = IBMACPI_FAN_WR_TPEC;
-                               fan_control_commands |=
-                                   IBMACPI_FAN_CMD_LEVEL |
-                                   IBMACPI_FAN_CMD_ENABLE;
-                       }
-               }
-       }
-
-       return 0;
-}
-
-static int fan_get_status(u8 *status)
-{
-       u8 s;
-
-       /* TODO:
-        * Add IBMACPI_FAN_RD_ACPI_FANS ? */
-
-       switch (fan_status_access_mode) {
-       case IBMACPI_FAN_RD_ACPI_GFAN:
-               /* 570, 600e/x, 770e, 770x */
-
-               if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
-                       return -EIO;
-
-               if (likely(status))
-                       *status = s & 0x07;
-
-               break;
-
-       case IBMACPI_FAN_RD_TPEC:
-               /* all except 570, 600e/x, 770e, 770x */
-               if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
-                       return -EIO;
-
-               if (likely(status))
-                       *status = s;
-
-               break;
-
-       default:
-               return -ENXIO;
-       }
-
-       return 0;
-}
-
-static int fan_get_speed(unsigned int *speed)
-{
-       u8 hi, lo;
-
-       switch (fan_status_access_mode) {
-       case IBMACPI_FAN_RD_TPEC:
-               /* all except 570, 600e/x, 770e, 770x */
-               if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
-                            !acpi_ec_read(fan_rpm_offset + 1, &hi)))
-                       return -EIO;
-
-               if (likely(speed))
-                       *speed = (hi << 8) | lo;
-
-               break;
-
-       default:
-               return -ENXIO;
-       }
-
-       return 0;
-}
-
-static void fan_exit(void)
-{
-       cancel_delayed_work(&fan_watchdog_task);
-       flush_scheduled_work();
-}
-
-static void fan_watchdog_reset(void)
-{
-       static int fan_watchdog_active = 0;
-
-       if (fan_watchdog_active)
-               cancel_delayed_work(&fan_watchdog_task);
-
-       if (fan_watchdog_maxinterval > 0) {
-               fan_watchdog_active = 1;
-               if (!schedule_delayed_work(&fan_watchdog_task,
-                               msecs_to_jiffies(fan_watchdog_maxinterval
-                                                * 1000))) {
-                       printk(IBM_ERR "failed to schedule the fan watchdog, "
-                              "watchdog will not trigger\n");
-               }
-       } else
-               fan_watchdog_active = 0;
-}
-
-static int fan_read(char *p)
-{
-       int len = 0;
-       int rc;
-       u8 status;
-       unsigned int speed = 0;
-
-       switch (fan_status_access_mode) {
-       case IBMACPI_FAN_RD_ACPI_GFAN:
-               /* 570, 600e/x, 770e, 770x */
-               if ((rc = fan_get_status(&status)) < 0)
-                       return rc;
-
-               len += sprintf(p + len, "status:\t\t%s\n"
-                              "level:\t\t%d\n",
-                              (status != 0) ? "enabled" : "disabled", status);
-               break;
-
-       case IBMACPI_FAN_RD_TPEC:
-               /* all except 570, 600e/x, 770e, 770x */
-               if ((rc = fan_get_status(&status)) < 0)
-                       return rc;
-
-               if (unlikely(!fan_control_status_known)) {
-                       if (status != fan_control_initial_status)
-                               fan_control_status_known = 1;
-                       else
-                               /* Return most likely status. In fact, it
-                                * might be the only possible status */
-                               status = IBMACPI_FAN_EC_AUTO;
-               }
-
-               len += sprintf(p + len, "status:\t\t%s\n",
-                              (status != 0) ? "enabled" : "disabled");
-
-               /* No ThinkPad boots on disengaged mode, we can safely
-                * assume the tachometer is online if fan control status
-                * was unknown */
-               if ((rc = fan_get_speed(&speed)) < 0)
-                       return rc;
-
-               len += sprintf(p + len, "speed:\t\t%d\n", speed);
-
-               if (status & IBMACPI_FAN_EC_DISENGAGED)
-                       /* Disengaged mode takes precedence */
-                       len += sprintf(p + len, "level:\t\tdisengaged\n");
-               else if (status & IBMACPI_FAN_EC_AUTO)
-                       len += sprintf(p + len, "level:\t\tauto\n");
-               else
-                       len += sprintf(p + len, "level:\t\t%d\n", status);
-               break;
-
-       case IBMACPI_FAN_NONE:
-       default:
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-       }
-
-       if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) {
-               len += sprintf(p + len, "commands:\tlevel <level>");
-
-               switch (fan_control_access_mode) {
-               case IBMACPI_FAN_WR_ACPI_SFAN:
-                       len += sprintf(p + len, " (<level> is 0-7)\n");
-                       break;
-
-               default:
-                       len += sprintf(p + len, " (<level> is 0-7, "
-                                      "auto, disengaged)\n");
-                       break;
-               }
-       }
-
-       if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE)
-               len += sprintf(p + len, "commands:\tenable, disable\n"
-                              "commands:\twatchdog <timeout> (<timeout> is 0 (off), "
-                              "1-120 (seconds))\n");
-
-       if (fan_control_commands & IBMACPI_FAN_CMD_SPEED)
-               len += sprintf(p + len, "commands:\tspeed <speed>"
-                              " (<speed> is 0-65535)\n");
-
-       return len;
-}
-
-static int fan_set_level(int level)
-{
-       switch (fan_control_access_mode) {
-       case IBMACPI_FAN_WR_ACPI_SFAN:
-               if (level >= 0 && level <= 7) {
-                       if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
-                               return -EIO;
-               } else
-                       return -EINVAL;
-               break;
-
-       case IBMACPI_FAN_WR_ACPI_FANS:
-       case IBMACPI_FAN_WR_TPEC:
-               if ((level != IBMACPI_FAN_EC_AUTO) &&
-                   (level != IBMACPI_FAN_EC_DISENGAGED) &&
-                   ((level < 0) || (level > 7)))
-                       return -EINVAL;
-
-               if (!acpi_ec_write(fan_status_offset, level))
-                       return -EIO;
-               else
-                       fan_control_status_known = 1;
-               break;
-
-       default:
-               return -ENXIO;
-       }
-       return 0;
-}
-
-static int fan_set_enable(void)
-{
-       u8 s;
-       int rc;
-
-       switch (fan_control_access_mode) {
-       case IBMACPI_FAN_WR_ACPI_FANS:
-       case IBMACPI_FAN_WR_TPEC:
-               if ((rc = fan_get_status(&s)) < 0)
-                       return rc;
-
-               /* Don't go out of emergency fan mode */
-               if (s != 7)
-                       s = IBMACPI_FAN_EC_AUTO;
-
-               if (!acpi_ec_write(fan_status_offset, s))
-                       return -EIO;
-               else
-                       fan_control_status_known = 1;
-               break;
-
-       case IBMACPI_FAN_WR_ACPI_SFAN:
-               if ((rc = fan_get_status(&s)) < 0)
-                       return rc;
-
-               s &= 0x07;
-
-               /* Set fan to at least level 4 */
-               if (s < 4)
-                       s = 4;
-
-               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
-                       return -EIO;
-               break;
-
-       default:
-               return -ENXIO;
-       }
-       return 0;
-}
-
-static int fan_set_disable(void)
-{
-       switch (fan_control_access_mode) {
-       case IBMACPI_FAN_WR_ACPI_FANS:
-       case IBMACPI_FAN_WR_TPEC:
-               if (!acpi_ec_write(fan_status_offset, 0x00))
-                       return -EIO;
-               else
-                       fan_control_status_known = 1;
-               break;
-
-       case IBMACPI_FAN_WR_ACPI_SFAN:
-               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
-                       return -EIO;
-               break;
-
-       default:
-               return -ENXIO;
-       }
-       return 0;
-}
-
-static int fan_set_speed(int speed)
-{
-       switch (fan_control_access_mode) {
-       case IBMACPI_FAN_WR_ACPI_FANS:
-               if (speed >= 0 && speed <= 65535) {
-                       if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
-                                       speed, speed, speed))
-                               return -EIO;
-               } else
-                       return -EINVAL;
-               break;
-
-       default:
-               return -ENXIO;
-       }
-       return 0;
-}
-
-static int fan_write_cmd_level(const char *cmd, int *rc)
-{
-       int level;
-
-       if (strlencmp(cmd, "level auto") == 0)
-               level = IBMACPI_FAN_EC_AUTO;
-       else if (strlencmp(cmd, "level disengaged") == 0)
-               level = IBMACPI_FAN_EC_DISENGAGED;
-       else if (sscanf(cmd, "level %d", &level) != 1)
-               return 0;
-
-       if ((*rc = fan_set_level(level)) == -ENXIO)
-               printk(IBM_ERR "level command accepted for unsupported "
-                      "access mode %d", fan_control_access_mode);
-
-       return 1;
-}
-
-static int fan_write_cmd_enable(const char *cmd, int *rc)
-{
-       if (strlencmp(cmd, "enable") != 0)
-               return 0;
-
-       if ((*rc = fan_set_enable()) == -ENXIO)
-               printk(IBM_ERR "enable command accepted for unsupported "
-                      "access mode %d", fan_control_access_mode);
-
-       return 1;
-}
-
-static int fan_write_cmd_disable(const char *cmd, int *rc)
-{
-       if (strlencmp(cmd, "disable") != 0)
-               return 0;
-
-       if ((*rc = fan_set_disable()) == -ENXIO)
-               printk(IBM_ERR "disable command accepted for unsupported "
-                      "access mode %d", fan_control_access_mode);
-
-       return 1;
-}
-
-static int fan_write_cmd_speed(const char *cmd, int *rc)
-{
-       int speed;
-
-       /* TODO:
-        * Support speed <low> <medium> <high> ? */
-
-       if (sscanf(cmd, "speed %d", &speed) != 1)
-               return 0;
-
-       if ((*rc = fan_set_speed(speed)) == -ENXIO)
-               printk(IBM_ERR "speed command accepted for unsupported "
-                      "access mode %d", fan_control_access_mode);
-
-       return 1;
-}
-
-static int fan_write_cmd_watchdog(const char *cmd, int *rc)
-{
-       int interval;
-
-       if (sscanf(cmd, "watchdog %d", &interval) != 1)
-               return 0;
-
-       if (interval < 0 || interval > 120)
-               *rc = -EINVAL;
-       else
-               fan_watchdog_maxinterval = interval;
-
-       return 1;
-}
-
-static int fan_write(char *buf)
-{
-       char *cmd;
-       int rc = 0;
-
-       while (!rc && (cmd = next_cmd(&buf))) {
-               if (!((fan_control_commands & IBMACPI_FAN_CMD_LEVEL) &&
-                     fan_write_cmd_level(cmd, &rc)) &&
-                   !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) &&
-                     (fan_write_cmd_enable(cmd, &rc) ||
-                      fan_write_cmd_disable(cmd, &rc) ||
-                      fan_write_cmd_watchdog(cmd, &rc))) &&
-                   !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) &&
-                     fan_write_cmd_speed(cmd, &rc))
-                   )
-                       rc = -EINVAL;
-               else if (!rc)
-                       fan_watchdog_reset();
-       }
-
-       return rc;
-}
-
-static void fan_watchdog_fire(struct work_struct *ignored)
-{
-       printk(IBM_NOTICE "fan watchdog: enabling fan\n");
-       if (fan_set_enable()) {
-               printk(IBM_ERR "fan watchdog: error while enabling fan\n");
-               /* reschedule for later */
-               fan_watchdog_reset();
-       }
-}
-
-static struct ibm_struct ibms[] = {
-       {
-        .name = "driver",
-        .init = ibm_acpi_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 = "wan",
-        .init = wan_init,
-        .read = wan_read,
-        .write = wan_write,
-        .experimental = 1,
-        },
-       {
-        .name = "video",
-        .init = video_init,
-        .read = video_read,
-        .write = video_write,
-        .exit = video_exit,
-        },
-       {
-        .name = "light",
-        .init = light_init,
-        .read = light_read,
-        .write = light_write,
-        },
-#ifdef CONFIG_ACPI_IBM_DOCK
-       {
-        .name = "dock",
-        .read = dock_read,
-        .write = dock_write,
-        .notify = dock_notify,
-        .handle = &dock_handle,
-        .type = ACPI_SYSTEM_NOTIFY,
-        },
-       {
-        .name = "dock",
-        .hid = IBM_PCI_HID,
-        .notify = dock_notify,
-        .handle = &pci_handle,
-        .type = ACPI_SYSTEM_NOTIFY,
-        },
-#endif
-#ifdef CONFIG_ACPI_IBM_BAY
-       {
-        .name = "bay",
-        .init = bay_init,
-        .read = bay_read,
-        .write = bay_write,
-        .notify = bay_notify,
-        .handle = &bay_handle,
-        .type = ACPI_SYSTEM_NOTIFY,
-        },
-#endif /* CONFIG_ACPI_IBM_BAY */
-       {
-        .name = "cmos",
-        .read = cmos_read,
-        .write = cmos_write,
-        },
-       {
-        .name = "led",
-        .init = led_init,
-        .read = led_read,
-        .write = led_write,
-        },
-       {
-        .name = "beep",
-        .read = beep_read,
-        .write = beep_write,
-        },
-       {
-        .name = "thermal",
-        .init = thermal_init,
-        .read = thermal_read,
-        },
-       {
-        .name = "ecdump",
-        .read = ecdump_read,
-        .write = ecdump_write,
-        .experimental = 1,
-        },
-       {
-        .name = "brightness",
-        .read = brightness_read,
-        .write = brightness_write,
-        .init = brightness_init,
-        .exit = brightness_exit,
-        },
-       {
-        .name = "volume",
-        .read = volume_read,
-        .write = volume_write,
-        },
-       {
-        .name = "fan",
-        .read = fan_read,
-        .write = fan_write,
-        .init = fan_init,
-        .exit = fan_exit,
-        .experimental = 1,
-        },
-};
-
-static int dispatch_read(char *page, char **start, off_t off, int count,
-                        int *eof, void *data)
-{
-       struct ibm_struct *ibm = data;
-       int len;
-
-       if (!ibm || !ibm->read)
-               return -EINVAL;
-
-       len = ibm->read(page);
-       if (len < 0)
-               return len;
-
-       if (len <= off + count)
-               *eof = 1;
-       *start = page + off;
-       len -= off;
-       if (len > count)
-               len = count;
-       if (len < 0)
-               len = 0;
-
-       return len;
-}
-
-static int dispatch_write(struct file *file, const char __user * userbuf,
-                         unsigned long count, void *data)
-{
-       struct ibm_struct *ibm = data;
-       char *kernbuf;
-       int ret;
-
-       if (!ibm || !ibm->write)
-               return -EINVAL;
-
-       kernbuf = kmalloc(count + 2, GFP_KERNEL);
-       if (!kernbuf)
-               return -ENOMEM;
-
-       if (copy_from_user(kernbuf, userbuf, count)) {
-               kfree(kernbuf);
-               return -EFAULT;
-       }
-
-       kernbuf[count] = 0;
-       strcat(kernbuf, ",");
-       ret = ibm->write(kernbuf);
-       if (ret == 0)
-               ret = count;
-
-       kfree(kernbuf);
-
-       return ret;
-}
-
-static void dispatch_notify(acpi_handle handle, u32 event, void *data)
-{
-       struct ibm_struct *ibm = data;
-
-       if (!ibm || !ibm->notify)
-               return;
-
-       ibm->notify(ibm, event);
-}
-
-static int __init setup_notify(struct ibm_struct *ibm)
-{
-       acpi_status status;
-       int ret;
-
-       if (!*ibm->handle)
-               return 0;
-
-       ret = acpi_bus_get_device(*ibm->handle, &ibm->device);
-       if (ret < 0) {
-               printk(IBM_ERR "%s device not present\n", ibm->name);
-               return -ENODEV;
-       }
-
-       acpi_driver_data(ibm->device) = ibm;
-       sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name);
-
-       status = acpi_install_notify_handler(*ibm->handle, ibm->type,
-                                            dispatch_notify, ibm);
-       if (ACPI_FAILURE(status)) {
-               if (status == AE_ALREADY_EXISTS) {
-                       printk(IBM_NOTICE "another device driver is already handling %s events\n",
-                               ibm->name);
-               } else {
-                       printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n",
-                               ibm->name, status);
-               }
-               return -ENODEV;
-       }
-       ibm->notify_installed = 1;
-       return 0;
-}
-
-static int __init ibm_device_add(struct acpi_device *device)
-{
-       return 0;
-}
-
-static int __init register_driver(struct ibm_struct *ibm)
-{
-       int ret;
-
-       ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
-       if (!ibm->driver) {
-               printk(IBM_ERR "kmalloc(ibm->driver) failed\n");
-               return -1;
-       }
-
-       sprintf(ibm->driver->name, "%s_%s", IBM_NAME, ibm->name);
-       ibm->driver->ids = ibm->hid;
-       ibm->driver->ops.add = &ibm_device_add;
-
-       ret = acpi_bus_register_driver(ibm->driver);
-       if (ret < 0) {
-               printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n",
-                      ibm->hid, ret);
-               kfree(ibm->driver);
-       }
-
-       return ret;
-}
-
-static void ibm_exit(struct ibm_struct *ibm);
-
-static int __init ibm_init(struct ibm_struct *ibm)
-{
-       int ret;
-       struct proc_dir_entry *entry;
-
-       if (ibm->experimental && !experimental)
-               return 0;
-
-       if (ibm->hid) {
-               ret = register_driver(ibm);
-               if (ret < 0)
-                       return ret;
-               ibm->driver_registered = 1;
-       }
-
-       if (ibm->init) {
-               ret = ibm->init();
-               if (ret != 0)
-                       return ret;
-               ibm->init_called = 1;
-       }
-
-       if (ibm->read) {
-               entry = create_proc_entry(ibm->name,
-                                         S_IFREG | S_IRUGO | S_IWUSR,
-                                         proc_dir);
-               if (!entry) {
-                       printk(IBM_ERR "unable to create proc entry %s\n",
-                              ibm->name);
-                       return -ENODEV;
-               }
-               entry->owner = THIS_MODULE;
-               entry->data = ibm;
-               entry->read_proc = &dispatch_read;
-               if (ibm->write)
-                       entry->write_proc = &dispatch_write;
-               ibm->proc_created = 1;
-       }
-
-       if (ibm->notify) {
-               ret = setup_notify(ibm);
-               if (ret == -ENODEV) {
-                       printk(IBM_NOTICE "disabling subdriver %s\n",
-                               ibm->name);
-                       ibm_exit(ibm);
-                       return 0;
-               }
-               if (ret < 0)
-                       return ret;
-       }
-
-       return 0;
-}
-
-static void ibm_exit(struct ibm_struct *ibm)
-{
-       if (ibm->notify_installed)
-               acpi_remove_notify_handler(*ibm->handle, ibm->type,
-                                          dispatch_notify);
-
-       if (ibm->proc_created)
-               remove_proc_entry(ibm->name, proc_dir);
-
-       if (ibm->init_called && ibm->exit)
-               ibm->exit();
-
-       if (ibm->driver_registered) {
-               acpi_bus_unregister_driver(ibm->driver);
-               kfree(ibm->driver);
-       }
-}
-
-static void __init ibm_handle_init(char *name,
-                                  acpi_handle * handle, acpi_handle parent,
-                                  char **paths, int num_paths, char **path)
-{
-       int i;
-       acpi_status status;
-
-       for (i = 0; i < num_paths; i++) {
-               status = acpi_get_handle(parent, paths[i], handle);
-               if (ACPI_SUCCESS(status)) {
-                       *path = paths[i];
-                       return;
-               }
-       }
-
-       *handle = NULL;
-}
-
-#define IBM_HANDLE_INIT(object)                                                \
-       ibm_handle_init(#object, &object##_handle, *object##_parent,    \
-               object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
-
-static int __init set_ibm_param(const char *val, struct kernel_param *kp)
-{
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(ibms); i++)
-               if (strcmp(ibms[i].name, kp->name) == 0 && ibms[i].write) {
-                       if (strlen(val) > sizeof(ibms[i].param) - 2)
-                               return -ENOSPC;
-                       strcpy(ibms[i].param, val);
-                       strcat(ibms[i].param, ",");
-                       return 0;
-               }
-
-       return -EINVAL;
-}
-
-#define IBM_PARAM(feature) \
-       module_param_call(feature, set_ibm_param, NULL, NULL, 0)
-
-IBM_PARAM(hotkey);
-IBM_PARAM(bluetooth);
-IBM_PARAM(video);
-IBM_PARAM(light);
-#ifdef CONFIG_ACPI_IBM_DOCK
-IBM_PARAM(dock);
-#endif
-#ifdef CONFIG_ACPI_IBM_BAY
-IBM_PARAM(bay);
-#endif /* CONFIG_ACPI_IBM_BAY */
-IBM_PARAM(cmos);
-IBM_PARAM(led);
-IBM_PARAM(beep);
-IBM_PARAM(ecdump);
-IBM_PARAM(brightness);
-IBM_PARAM(volume);
-IBM_PARAM(fan);
-
-static void acpi_ibm_exit(void)
-{
-       int i;
-
-       for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--)
-               ibm_exit(&ibms[i]);
-
-       if (proc_dir)
-               remove_proc_entry(IBM_DIR, acpi_root_dir);
-
-       if (ibm_thinkpad_ec_found)
-               kfree(ibm_thinkpad_ec_found);
-}
-
-static char* __init check_dmi_for_ec(void)
-{
-       struct dmi_device *dev = NULL;
-       char ec_fw_string[18];
-
-       /*
-        * ThinkPad T23 or newer, A31 or newer, R50e or newer,
-        * X32 or newer, all Z series;  Some models must have an
-        * up-to-date BIOS or they will not be detected.
-        *
-        * See http://thinkwiki.org/wiki/List_of_DMI_IDs
-        */
-       while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
-               if (sscanf(dev->name,
-                          "IBM ThinkPad Embedded Controller -[%17c",
-                          ec_fw_string) == 1) {
-                       ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
-                       ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
-                       return kstrdup(ec_fw_string, GFP_KERNEL);
-               }
-       }
-       return NULL;
-}
-
-static int __init acpi_ibm_init(void)
-{
-       int ret, i;
-
-       if (acpi_disabled)
-               return -ENODEV;
-
-       /* ec is required because many other handles are relative to it */
-       IBM_HANDLE_INIT(ec);
-       if (!ec_handle) {
-               printk(IBM_ERR "ec object not found\n");
-               return -ENODEV;
-       }
-
-       /* Models with newer firmware report the EC in DMI */
-       ibm_thinkpad_ec_found = check_dmi_for_ec();
-
-       /* these handles are not required */
-       IBM_HANDLE_INIT(vid);
-       IBM_HANDLE_INIT(vid2);
-       IBM_HANDLE_INIT(ledb);
-       IBM_HANDLE_INIT(led);
-       IBM_HANDLE_INIT(hkey);
-       IBM_HANDLE_INIT(lght);
-       IBM_HANDLE_INIT(cmos);
-#ifdef CONFIG_ACPI_IBM_DOCK
-       IBM_HANDLE_INIT(dock);
-#endif
-       IBM_HANDLE_INIT(pci);
-#ifdef CONFIG_ACPI_IBM_BAY
-       IBM_HANDLE_INIT(bay);
-       if (bay_handle)
-               IBM_HANDLE_INIT(bay_ej);
-       IBM_HANDLE_INIT(bay2);
-       if (bay2_handle)
-               IBM_HANDLE_INIT(bay2_ej);
-#endif /* CONFIG_ACPI_IBM_BAY */
-       IBM_HANDLE_INIT(beep);
-       IBM_HANDLE_INIT(ecrd);
-       IBM_HANDLE_INIT(ecwr);
-       IBM_HANDLE_INIT(fans);
-       IBM_HANDLE_INIT(gfan);
-       IBM_HANDLE_INIT(sfan);
-
-       proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir);
-       if (!proc_dir) {
-               printk(IBM_ERR "unable to create proc dir %s", IBM_DIR);
-               acpi_ibm_exit();
-               return -ENODEV;
-       }
-       proc_dir->owner = THIS_MODULE;
-
-       for (i = 0; i < ARRAY_SIZE(ibms); i++) {
-               ret = ibm_init(&ibms[i]);
-               if (ret >= 0 && *ibms[i].param)
-                       ret = ibms[i].write(ibms[i].param);
-               if (ret < 0) {
-                       acpi_ibm_exit();
-                       return ret;
-               }
-       }
-
-       return 0;
-}
-
-module_init(acpi_ibm_init);
-module_exit(acpi_ibm_exit);
index a4fb7038318817d02b6d39f83c5ccf8e004408dd..f1b9dd7d47d62c00868acdba0927d68a0c8777e0 100644 (file)
@@ -777,7 +777,8 @@ static int pkt_generic_packet(struct pktcdvd_device *pd, struct packet_command *
                rq->cmd_flags |= REQ_QUIET;
 
        blk_execute_rq(rq->q, pd->bdev->bd_disk, rq, 0);
-       ret = rq->errors;
+       if (rq->errors)
+               ret = -EIO;
 out:
        blk_put_request(rq);
        return ret;
index 78237577b05ac57790ac34aee0f601b8aae23aa7..3ef593a9015f1d298348cc44040000182ef7b63e 100644 (file)
@@ -1,6 +1,8 @@
 /*
  * Sony Programmable I/O Control Device driver for VAIO
  *
+ * Copyright (C) 2007 Mattia Dongili <malattia@linux.it>
+ *
  * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net>
  *
  * Copyright (C) 2005 Narayanan R S <nars@kadamba.org>
@@ -95,6 +97,11 @@ module_param(useinput, int, 0444);
 MODULE_PARM_DESC(useinput,
                 "set this if you would like sonypi to feed events to the input subsystem");
 
+static int check_ioport = 1;
+module_param(check_ioport, int, 0444);
+MODULE_PARM_DESC(check_ioport,
+                "set this to 0 if you think the automatic ioport check for sony-laptop is wrong");
+
 #define SONYPI_DEVICE_MODEL_TYPE1      1
 #define SONYPI_DEVICE_MODEL_TYPE2      2
 #define SONYPI_DEVICE_MODEL_TYPE3      3
@@ -477,7 +484,7 @@ static struct sonypi_device {
        u16 evtype_offset;
        int camera_power;
        int bluetooth_power;
-       struct semaphore lock;
+       struct mutex lock;
        struct kfifo *fifo;
        spinlock_t fifo_lock;
        wait_queue_head_t fifo_proc_list;
@@ -884,7 +891,7 @@ int sonypi_camera_command(int command, u8 value)
        if (!camera)
                return -EIO;
 
-       down(&sonypi_device.lock);
+       mutex_lock(&sonypi_device.lock);
 
        switch (command) {
        case SONYPI_COMMAND_SETCAMERA:
@@ -919,7 +926,7 @@ int sonypi_camera_command(int command, u8 value)
                       command);
                break;
        }
-       up(&sonypi_device.lock);
+       mutex_unlock(&sonypi_device.lock);
        return 0;
 }
 
@@ -938,20 +945,20 @@ static int sonypi_misc_fasync(int fd, struct file *filp, int on)
 static int sonypi_misc_release(struct inode *inode, struct file *file)
 {
        sonypi_misc_fasync(-1, file, 0);
-       down(&sonypi_device.lock);
+       mutex_lock(&sonypi_device.lock);
        sonypi_device.open_count--;
-       up(&sonypi_device.lock);
+       mutex_unlock(&sonypi_device.lock);
        return 0;
 }
 
 static int sonypi_misc_open(struct inode *inode, struct file *file)
 {
-       down(&sonypi_device.lock);
+       mutex_lock(&sonypi_device.lock);
        /* Flush input queue on first open */
        if (!sonypi_device.open_count)
                kfifo_reset(sonypi_device.fifo);
        sonypi_device.open_count++;
-       up(&sonypi_device.lock);
+       mutex_unlock(&sonypi_device.lock);
        return 0;
 }
 
@@ -1001,7 +1008,7 @@ static int sonypi_misc_ioctl(struct inode *ip, struct file *fp,
        u8 val8;
        u16 val16;
 
-       down(&sonypi_device.lock);
+       mutex_lock(&sonypi_device.lock);
        switch (cmd) {
        case SONYPI_IOCGBRT:
                if (sonypi_ec_read(SONYPI_LCD_LIGHT, &val8)) {
@@ -1101,7 +1108,7 @@ static int sonypi_misc_ioctl(struct inode *ip, struct file *fp,
        default:
                ret = -EINVAL;
        }
-       up(&sonypi_device.lock);
+       mutex_unlock(&sonypi_device.lock);
        return ret;
 }
 
@@ -1260,6 +1267,28 @@ static int __devinit sonypi_create_input_devices(void)
 static int __devinit sonypi_setup_ioports(struct sonypi_device *dev,
                                const struct sonypi_ioport_list *ioport_list)
 {
+       /* try to detect if sony-laptop is being used and thus
+        * has already requested one of the known ioports.
+        * As in the deprecated check_region this is racy has we have
+        * multiple ioports available and one of them can be requested
+        * between this check and the subsequent request. Anyway, as an
+        * attempt to be some more user-friendly as we currently are,
+        * this is enough.
+        */
+       const struct sonypi_ioport_list *check = ioport_list;
+       while (check_ioport && check->port1) {
+               if (!request_region(check->port1,
+                                  sonypi_device.region_size,
+                                  "Sony Programable I/O Device Check")) {
+                       printk(KERN_ERR "sonypi: ioport 0x%.4x busy, using sony-laptop? "
+                                       "if not use check_ioport=0\n",
+                                       check->port1);
+                       return -EBUSY;
+               }
+               release_region(check->port1, sonypi_device.region_size);
+               check++;
+       }
+
        while (ioport_list->port1) {
 
                if (request_region(ioport_list->port1,
@@ -1321,6 +1350,10 @@ static int __devinit sonypi_probe(struct platform_device *dev)
        struct pci_dev *pcidev;
        int error;
 
+       printk(KERN_WARNING "sonypi: please try the sony-laptop module instead "
+                       "and report failures, see also "
+                       "http://www.linux.it/~malattia/wiki/index.php/Sony_drivers\n");
+
        spin_lock_init(&sonypi_device.fifo_lock);
        sonypi_device.fifo = kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL,
                                         &sonypi_device.fifo_lock);
@@ -1330,7 +1363,7 @@ static int __devinit sonypi_probe(struct platform_device *dev)
        }
 
        init_waitqueue_head(&sonypi_device.fifo_proc_list);
-       init_MUTEX(&sonypi_device.lock);
+       mutex_init(&sonypi_device.lock);
        sonypi_device.bluetooth_power = -1;
 
        if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
index 7a6105153f23231ef971e46bd603c1d1b5b1f76a..ff6aefde10df258f0955b75199006ab6106ca9da 100644 (file)
@@ -577,14 +577,14 @@ config VIDEO_ZORAN_AVS6EYES
 
 config VIDEO_MEYE
        tristate "Sony Vaio Picturebook Motion Eye Video For Linux"
-       depends on PCI && SONYPI && VIDEO_V4L1
+       depends on PCI && SONY_LAPTOP && VIDEO_V4L1
        ---help---
          This is the video4linux driver for the Motion Eye camera found
          in the Vaio Picturebook laptops. Please read the material in
          <file:Documentation/video4linux/meye.txt> for more information.
 
-         If you say Y or M here, you need to say Y or M to "Sony Programmable
-         I/O Control Device" in the character device section.
+         If you say Y or M here, you need to say Y or M to "Sony Laptop
+         Extras" in the misc device section.
 
          To compile this driver as a module, choose M here: the
          module will be called meye.
index 98681da5e3b91fbbd72d9c665c277eaf22e48cea..664aba8b4d85231895954ac48df4447212796d48 100644 (file)
@@ -925,13 +925,13 @@ static int meye_do_ioctl(struct inode *inode, struct file *file,
                if (p->palette != VIDEO_PALETTE_YUV422 && p->palette != VIDEO_PALETTE_YUYV)
                        return -EINVAL;
                mutex_lock(&meye.lock);
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERABRIGHTNESS,
                                      p->brightness >> 10);
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAHUE,
                                      p->hue >> 10);
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACOLOR,
                                      p->colour >> 10);
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACONTRAST,
                                      p->contrast >> 10);
                meye.picture = *p;
                mutex_unlock(&meye.lock);
@@ -1043,11 +1043,11 @@ static int meye_do_ioctl(struct inode *inode, struct file *file,
                    meye.params.quality != jp->quality)
                        mchip_hic_stop();       /* need restart */
                meye.params = *jp;
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERASHARPNESS,
                                      meye.params.sharpness);
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAAGC,
                                      meye.params.agc);
-               sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE,
+               sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAPICTURE,
                                      meye.params.picture);
                mutex_unlock(&meye.lock);
                break;
@@ -1287,38 +1287,38 @@ static int meye_do_ioctl(struct inode *inode, struct file *file,
                mutex_lock(&meye.lock);
                switch (c->id) {
                case V4L2_CID_BRIGHTNESS:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERABRIGHTNESS, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, c->value);
                        meye.picture.brightness = c->value << 10;
                        break;
                case V4L2_CID_HUE:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERAHUE, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERAHUE, c->value);
                        meye.picture.hue = c->value << 10;
                        break;
                case V4L2_CID_CONTRAST:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERACONTRAST, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERACONTRAST, c->value);
                        meye.picture.contrast = c->value << 10;
                        break;
                case V4L2_CID_SATURATION:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERACOLOR, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERACOLOR, c->value);
                        meye.picture.colour = c->value << 10;
                        break;
                case V4L2_CID_AGC:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERAAGC, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERAAGC, c->value);
                        meye.params.agc = c->value;
                        break;
                case V4L2_CID_SHARPNESS:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERASHARPNESS, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERASHARPNESS, c->value);
                        meye.params.sharpness = c->value;
                        break;
                case V4L2_CID_PICTURE:
-                       sonypi_camera_command(
-                               SONYPI_COMMAND_SETCAMERAPICTURE, c->value);
+                       sony_pic_camera_command(
+                               SONY_PIC_COMMAND_SETCAMERAPICTURE, c->value);
                        meye.params.picture = c->value;
                        break;
                case V4L2_CID_JPEGQUAL:
@@ -1848,7 +1848,7 @@ static int __devinit meye_probe(struct pci_dev *pcidev,
        memcpy(meye.video_dev, &meye_template, sizeof(meye_template));
        meye.video_dev->dev = &meye.mchip_dev->dev;
 
-       if ((ret = sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 1))) {
+       if ((ret = sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 1))) {
                printk(KERN_ERR "meye: unable to power on the camera\n");
                printk(KERN_ERR "meye: did you enable the camera in "
                                "sonypi using the module options ?\n");
@@ -1928,13 +1928,13 @@ static int __devinit meye_probe(struct pci_dev *pcidev,
        meye.params.picture = 0;
        meye.params.framerate = 0;
 
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, 32);
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, 32);
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, 32);
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, 32);
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, 32);
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, 0);
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, 48);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, 32);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAHUE, 32);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACOLOR, 32);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACONTRAST, 32);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERASHARPNESS, 32);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAPICTURE, 0);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAAGC, 48);
 
        printk(KERN_INFO "meye: Motion Eye Camera Driver v%s.\n",
               MEYE_DRIVER_VERSION);
@@ -1953,7 +1953,7 @@ outremap:
 outregions:
        pci_disable_device(meye.mchip_dev);
 outenabledev:
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 0);
 outsonypienable:
        kfifo_free(meye.doneq);
 outkfifoalloc2:
@@ -1986,7 +1986,7 @@ static void __devexit meye_remove(struct pci_dev *pcidev)
 
        pci_disable_device(meye.mchip_dev);
 
-       sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0);
+       sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 0);
 
        kfifo_free(meye.doneq);
        kfifo_free(meye.grabq);
index ea107cb5c845baf46b928bf880e1b54944257077..323d0074120da4aa5ad7f0c87413abd72212e219 100644 (file)
 /****************************************************************************/
 
 /* Sony Programmable I/O Controller for accessing the camera commands */
-#include <linux/sonypi.h>
+#include <linux/sony-laptop.h>
 
 /* private API definitions */
 #include <linux/meye.h>
index 80b199fa0aa9b7173fc684b1866de7c1c2e46b2f..a3c525b2616aafeaa855b03377e0f4ee4937cdc5 100644 (file)
@@ -112,14 +112,70 @@ config SONY_LAPTOP
        depends on X86 && ACPI
        select BACKLIGHT_CLASS_DEVICE
          ---help---
-         This mini-driver drives the SNC device present in the ACPI BIOS of
-         the Sony Vaio laptops.
+         This mini-driver drives the SNC and SPIC devices present in the ACPI
+         BIOS of the Sony Vaio laptops.
 
-         It gives access to some extra laptop functionalities. In its current
-         form, this driver let the user set or query the screen brightness
-         through the backlight subsystem and remove/apply power to some
+         It gives access to some extra laptop functionalities like Bluetooth,
+         screen brightness control, Fn keys and allows powering on/off some
          devices.
 
          Read <file:Documentation/sony-laptop.txt> for more information.
 
+config SONY_LAPTOP_OLD
+       bool "Sonypi compatibility"
+       depends on SONY_LAPTOP
+         ---help---
+         Build the sonypi driver compatibility code into the sony-laptop driver.
+
+config THINKPAD_ACPI
+       tristate "ThinkPad ACPI Laptop Extras"
+       depends on X86 && ACPI
+       select BACKLIGHT_CLASS_DEVICE
+       select HWMON
+       ---help---
+         This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
+         support for Fn-Fx key combinations, Bluetooth control, video
+         output switching, ThinkLight control, UltraBay eject and more.
+         For more information about this driver see 
+         <file:Documentation/thinkpad-acpi.txt> and <http://ibm-acpi.sf.net/> .
+
+         This driver was formely known as ibm-acpi.
+
+         If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
+
+config THINKPAD_ACPI_DEBUG
+       bool "Verbose debug mode"
+       depends on THINKPAD_ACPI
+       default n
+       ---help---
+         Enables extra debugging information, at the expense of a slightly
+         increase in driver size.
+
+         If you are not sure, say N here.
+
+config THINKPAD_ACPI_DOCK
+       bool "Legacy Docking Station Support"
+       depends on THINKPAD_ACPI
+       depends on ACPI_DOCK=n
+       default n
+       ---help---
+         Allows the thinkpad_acpi driver to handle docking station events.
+         This support was made obsolete by the generic ACPI docking station
+         support (CONFIG_ACPI_DOCK).  It will allow locking and removing the
+         laptop from the docking station, but will not properly connect PCI
+         devices.
+
+         If you are not sure, say N here.
+
+config THINKPAD_ACPI_BAY
+       bool "Legacy Removable Bay Support"
+       depends on THINKPAD_ACPI
+       default y
+       ---help---
+         Allows the thinkpad_acpi driver to handle removable bays.  It will
+         eletrically disable the device in the bay, and also generate
+         notifications when the bay lever is ejected or inserted.
+
+         If you are not sure, say Y here.
+
 endmenu
index 7793ccd7904962c699340c53e8ef1cfa6389e9b1..e325164591380bd93011840757808b3927e26eb1 100644 (file)
@@ -12,3 +12,4 @@ obj-$(CONFIG_TIFM_CORE)               += tifm_core.o
 obj-$(CONFIG_TIFM_7XX1)        += tifm_7xx1.o
 obj-$(CONFIG_SGI_IOC4)         += ioc4.o
 obj-$(CONFIG_SONY_LAPTOP)      += sony-laptop.o
+obj-$(CONFIG_THINKPAD_ACPI)    += thinkpad_acpi.o
index 4b232124a1abb0dea3b9e737062250e3988b968c..65c32a95e121360d3288e95fc5b96792538084ad 100644 (file)
@@ -3,7 +3,7 @@
  *
  *
  *  Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor
- *  Copyright (C) 2006 Corentin Chary
+ *  Copyright (C) 2006-2007 Corentin Chary
  *
  *  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
@@ -48,7 +48,7 @@
 #include <acpi/acpi_bus.h>
 #include <asm/uaccess.h>
 
-#define ASUS_LAPTOP_VERSION "0.40"
+#define ASUS_LAPTOP_VERSION "0.41"
 
 #define ASUS_HOTK_NAME          "Asus Laptop Support"
 #define ASUS_HOTK_CLASS         "hotkey"
@@ -81,7 +81,8 @@
 #define TLED_ON     0x08       //touchpad LED
 #define RLED_ON     0x10       //Record LED
 #define PLED_ON     0x20       //Phone LED
-#define LCD_ON      0x40       //LCD backlight
+#define GLED_ON     0x40       //Gaming LED
+#define LCD_ON      0x80       //LCD backlight
 
 #define ASUS_LOG    ASUS_HOTK_FILE ": "
 #define ASUS_ERR    KERN_ERR    ASUS_LOG
@@ -94,6 +95,19 @@ MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary");
 MODULE_DESCRIPTION(ASUS_HOTK_NAME);
 MODULE_LICENSE("GPL");
 
+/* WAPF defines the behavior of the Fn+Fx wlan key
+ * The significance of values is yet to be found, but
+ * most of the time:
+ * 0x0 will do nothing
+ * 0x1 will allow to control the device with Fn+Fx key.
+ * 0x4 will send an ACPI event (0x88) while pressing the Fn+Fx key
+ * 0x5 like 0x1 or 0x4
+ * So, if something doesn't work as you want, just try other values =)
+ */
+static uint wapf = 1;
+module_param(wapf, uint, 0644);
+MODULE_PARM_DESC(wapf, "WAPF value");
+
 #define ASUS_HANDLE(object, paths...)                                  \
        static acpi_handle  object##_handle = NULL;                     \
        static char *object##_paths[] = { paths }
@@ -103,6 +117,7 @@ ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED");
 ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED");
 ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED");        /* W1JC */
 ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED");        /* A7J */
+ASUS_HANDLE(gled_set, ASUS_HOTK_PREFIX "GLED");        /* G1, G2 (probably) */
 
 /* LEDD */
 ASUS_HANDLE(ledd_set, ASUS_HOTK_PREFIX "SLCM");
@@ -221,6 +236,7 @@ ASUS_LED(mled, "mail");
 ASUS_LED(tled, "touchpad");
 ASUS_LED(rled, "record");
 ASUS_LED(pled, "phone");
+ASUS_LED(gled, "gaming");
 
 /*
  * This function evaluates an ACPI method, given an int as parameter, the
@@ -245,32 +261,19 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val,
        return (status == AE_OK);
 }
 
-static int read_acpi_int(acpi_handle handle, const char *method, int *val,
-                        struct acpi_object_list *params)
-{
-       struct acpi_buffer output;
-       union acpi_object out_obj;
-       acpi_status status;
-
-       output.length = sizeof(out_obj);
-       output.pointer = &out_obj;
-
-       status = acpi_evaluate_object(handle, (char *)method, params, &output);
-       *val = out_obj.integer.value;
-       return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER);
-}
-
 static int read_wireless_status(int mask)
 {
-       int status;
+       ulong status;
+       acpi_status rv = AE_OK;
 
        if (!wireless_status_handle)
                return (hotk->status & mask) ? 1 : 0;
 
-       if (read_acpi_int(wireless_status_handle, NULL, &status, NULL)) {
-               return (status & mask) ? 1 : 0;
-       } else
+       rv = acpi_evaluate_integer(wireless_status_handle, NULL, NULL, &status);
+       if (ACPI_FAILURE(rv))
                printk(ASUS_WARNING "Error reading Wireless status\n");
+       else
+               return (status & mask) ? 1 : 0;
 
        return (hotk->status & mask) ? 1 : 0;
 }
@@ -285,19 +288,28 @@ static int read_status(int mask)
        return (hotk->status & mask) ? 1 : 0;
 }
 
-static void write_status(acpi_handle handle, int out, int mask, int invert)
+static void write_status(acpi_handle handle, int out, int mask)
 {
        hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask);
 
-       if (invert)             /* invert target value */
+       switch (mask) {
+       case MLED_ON:
                out = !out & 0x1;
+               break;
+       case GLED_ON:
+               out = (out & 0x1) + 1;
+               break;
+       default:
+               out &= 0x1;
+               break;
+       }
 
        if (handle && !write_acpi_int(handle, NULL, out, NULL))
-               printk(ASUS_WARNING " write failed\n");
+               printk(ASUS_WARNING " write failed %x\n", mask);
 }
 
 /* /sys/class/led handlers */
-#define ASUS_LED_HANDLER(object, mask, invert)                         \
+#define ASUS_LED_HANDLER(object, mask)                                 \
        static void object##_led_set(struct led_classdev *led_cdev,     \
                                     enum led_brightness value)         \
        {                                                               \
@@ -307,13 +319,14 @@ static void write_status(acpi_handle handle, int out, int mask, int invert)
        static void object##_led_update(struct work_struct *ignored)    \
        {                                                               \
                int value = object##_led_wk;                            \
-               write_status(object##_set_handle, value, (mask), (invert)); \
+               write_status(object##_set_handle, value, (mask));       \
        }
 
-ASUS_LED_HANDLER(mled, MLED_ON, 1);
-ASUS_LED_HANDLER(pled, PLED_ON, 0);
-ASUS_LED_HANDLER(rled, RLED_ON, 0);
-ASUS_LED_HANDLER(tled, TLED_ON, 0);
+ASUS_LED_HANDLER(mled, MLED_ON);
+ASUS_LED_HANDLER(pled, PLED_ON);
+ASUS_LED_HANDLER(rled, RLED_ON);
+ASUS_LED_HANDLER(tled, TLED_ON);
+ASUS_LED_HANDLER(gled, GLED_ON);
 
 static int get_lcd_state(void)
 {
@@ -338,7 +351,7 @@ static int set_lcd_state(int value)
                        printk(ASUS_WARNING "Error switching LCD\n");
        }
 
-       write_status(NULL, lcd, LCD_ON, 0);
+       write_status(NULL, lcd, LCD_ON);
        return 0;
 }
 
@@ -354,9 +367,11 @@ static void lcd_blank(int blank)
 
 static int read_brightness(struct backlight_device *bd)
 {
-       int value;
+       ulong value;
+       acpi_status rv = AE_OK;
 
-       if (!read_acpi_int(brightness_get_handle, NULL, &value, NULL))
+       rv = acpi_evaluate_integer(brightness_get_handle, NULL, NULL, &value);
+       if (ACPI_FAILURE(rv))
                printk(ASUS_WARNING "Error reading brightness\n");
 
        return value;
@@ -403,8 +418,10 @@ static ssize_t show_infos(struct device *dev,
                          struct device_attribute *attr, char *page)
 {
        int len = 0;
-       int temp;
+       ulong temp;
        char buf[16];           //enough for all info
+       acpi_status rv = AE_OK;
+
        /*
         * We use the easy way, we don't care of off and count, so we don't set eof
         * to 1
@@ -418,9 +435,10 @@ static ssize_t show_infos(struct device *dev,
         * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
         * The significance of others is yet to be found.
         */
-       if (read_acpi_int(hotk->handle, "SFUN", &temp, NULL))
-               len +=
-                   sprintf(page + len, "SFUN value         : 0x%04x\n", temp);
+       rv = acpi_evaluate_integer(hotk->handle, "SFUN", NULL, &temp);
+       if (!ACPI_FAILURE(rv))
+               len += sprintf(page + len, "SFUN value         : 0x%04x\n",
+                              (uint) temp);
        /*
         * Another value for userspace: the ASYM method returns 0x02 for
         * battery low and 0x04 for battery critical, its readings tend to be
@@ -428,9 +446,10 @@ static ssize_t show_infos(struct device *dev,
         * Note: since not all the laptops provide this method, errors are
         * silently ignored.
         */
-       if (read_acpi_int(hotk->handle, "ASYM", &temp, NULL))
-               len +=
-                   sprintf(page + len, "ASYM value         : 0x%04x\n", temp);
+       rv = acpi_evaluate_integer(hotk->handle, "ASYM", NULL, &temp);
+       if (!ACPI_FAILURE(rv))
+               len += sprintf(page + len, "ASYM value         : 0x%04x\n",
+                              (uint) temp);
        if (asus_info) {
                snprintf(buf, 16, "%d", asus_info->length);
                len += sprintf(page + len, "DSDT length        : %s\n", buf);
@@ -465,7 +484,7 @@ static int parse_arg(const char *buf, unsigned long count, int *val)
 }
 
 static ssize_t store_status(const char *buf, size_t count,
-                           acpi_handle handle, int mask, int invert)
+                           acpi_handle handle, int mask)
 {
        int rv, value;
        int out = 0;
@@ -474,7 +493,7 @@ static ssize_t store_status(const char *buf, size_t count,
        if (rv > 0)
                out = value ? 1 : 0;
 
-       write_status(handle, out, mask, invert);
+       write_status(handle, out, mask);
 
        return rv;
 }
@@ -515,7 +534,7 @@ static ssize_t show_wlan(struct device *dev,
 static ssize_t store_wlan(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t count)
 {
-       return store_status(buf, count, wl_switch_handle, WL_ON, 0);
+       return store_status(buf, count, wl_switch_handle, WL_ON);
 }
 
 /*
@@ -531,7 +550,7 @@ static ssize_t store_bluetooth(struct device *dev,
                               struct device_attribute *attr, const char *buf,
                               size_t count)
 {
-       return store_status(buf, count, bt_switch_handle, BT_ON, 0);
+       return store_status(buf, count, bt_switch_handle, BT_ON);
 }
 
 /*
@@ -547,12 +566,15 @@ static void set_display(int value)
 
 static int read_display(void)
 {
-       int value = 0;
+       ulong value = 0;
+       acpi_status rv = AE_OK;
 
        /* In most of the case, we know how to set the display, but sometime
           we can't read it */
        if (display_get_handle) {
-               if (!read_acpi_int(display_get_handle, NULL, &value, NULL))
+               rv = acpi_evaluate_integer(display_get_handle, NULL,
+                                          NULL, &value);
+               if (ACPI_FAILURE(rv))
                        printk(ASUS_WARNING "Error reading display status\n");
        }
 
@@ -656,10 +678,10 @@ static void asus_hotk_notify(acpi_handle handle, u32 event, void *data)
         * switched
         */
        if (event == ATKD_LCD_ON) {
-               write_status(NULL, 1, LCD_ON, 0);
+               write_status(NULL, 1, LCD_ON);
                lcd_blank(FB_BLANK_UNBLANK);
        } else if (event == ATKD_LCD_OFF) {
-               write_status(NULL, 0, LCD_ON, 0);
+               write_status(NULL, 0, LCD_ON);
                lcd_blank(FB_BLANK_POWERDOWN);
        }
 
@@ -771,7 +793,7 @@ static int asus_hotk_get_info(void)
 {
        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
        union acpi_object *model = NULL;
-       int bsts_result, hwrs_result;
+       ulong bsts_result, hwrs_result;
        char *string = NULL;
        acpi_status status;
 
@@ -794,11 +816,16 @@ static int asus_hotk_get_info(void)
        }
 
        /* This needs to be called for some laptops to init properly */
-       if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result, NULL))
+       status =
+           acpi_evaluate_integer(hotk->handle, "BSTS", NULL, &bsts_result);
+       if (ACPI_FAILURE(status))
                printk(ASUS_WARNING "Error calling BSTS\n");
        else if (bsts_result)
                printk(ASUS_NOTICE "BSTS called, 0x%02x returned\n",
-                      bsts_result);
+                      (uint) bsts_result);
+
+       /* This too ... */
+       write_acpi_int(hotk->handle, "CWAP", wapf, NULL);
 
        /*
         * Try to match the object returned by INIT to the specific model.
@@ -831,6 +858,7 @@ static int asus_hotk_get_info(void)
        ASUS_HANDLE_INIT(tled_set);
        ASUS_HANDLE_INIT(rled_set);
        ASUS_HANDLE_INIT(pled_set);
+       ASUS_HANDLE_INIT(gled_set);
 
        ASUS_HANDLE_INIT(ledd_set);
 
@@ -840,7 +868,9 @@ static int asus_hotk_get_info(void)
         * The significance of others is yet to be found.
         * If we don't find the method, we assume the device are present.
         */
-       if (!read_acpi_int(hotk->handle, "HRWS", &hwrs_result, NULL))
+       status =
+           acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &hwrs_result);
+       if (ACPI_FAILURE(status))
                hwrs_result = WL_HWRS | BT_HWRS;
 
        if (hwrs_result & WL_HWRS)
@@ -928,11 +958,15 @@ static int asus_hotk_add(struct acpi_device *device)
        asus_hotk_found = 1;
 
        /* WLED and BLED are on by default */
-       write_status(bt_switch_handle, 1, BT_ON, 0);
-       write_status(wl_switch_handle, 1, WL_ON, 0);
+       write_status(bt_switch_handle, 1, BT_ON);
+       write_status(wl_switch_handle, 1, WL_ON);
+
+       /* If the h/w switch is off, we need to check the real status */
+       write_status(NULL, read_status(BT_ON), BT_ON);
+       write_status(NULL, read_status(WL_ON), WL_ON);
 
        /* LCD Backlight is on by default */
-       write_status(NULL, 1, LCD_ON, 0);
+       write_status(NULL, 1, LCD_ON);
 
        /* LED display is off by default */
        hotk->ledd_status = 0xFFF;
@@ -991,6 +1025,7 @@ static void asus_led_exit(void)
        ASUS_LED_UNREGISTER(tled);
        ASUS_LED_UNREGISTER(pled);
        ASUS_LED_UNREGISTER(rled);
+       ASUS_LED_UNREGISTER(gled);
 
        destroy_workqueue(led_workqueue);
 }
@@ -1062,6 +1097,10 @@ static int asus_led_init(struct device *dev)
        if (rv)
                return rv;
 
+       rv = ASUS_LED_REGISTER(gled, dev);
+       if (rv)
+               return rv;
+
        led_workqueue = create_singlethread_workqueue("led_workqueue");
        if (!led_workqueue)
                return -ENOMEM;
index ac708bc2f9f3ce3bf158c74cf376072d7ac8726b..c15c1f61bd1bfad853b8c6ea343d6cdd47e98135 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * ACPI Sony Notebook Control Driver (SNC)
+ * ACPI Sony Notebook Control Driver (SNC and SPIC)
  *
  * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
  * Copyright (C) 2007 Mattia Dongili <malattia@linux.it>
@@ -7,6 +7,25 @@
  * Parts of this driver inspired from asus_acpi.c and ibm_acpi.c
  * which are copyrighted by their respective authors.
  *
+ * The SNY6001 driver part is based on the sonypi driver which includes
+ * material from:
+ *
+ * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net>
+ *
+ * Copyright (C) 2005 Narayanan R S <nars@kadamba.org>
+ *
+ * Copyright (C) 2001-2002 Alcôve <www.alcove.com>
+ *
+ * Copyright (C) 2001 Michael Ashley <m.ashley@unsw.edu.au>
+ *
+ * Copyright (C) 2001 Junichi Morita <jun1m@mars.dti.ne.jp>
+ *
+ * Copyright (C) 2000 Takaya Kinjo <t-kinjo@tc4.so-net.ne.jp>
+ *
+ * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com>
+ *
+ * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras.
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
 #include <linux/backlight.h>
 #include <linux/platform_device.h>
 #include <linux/err.h>
+#include <linux/dmi.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/kfifo.h>
+#include <linux/workqueue.h>
+#include <linux/acpi.h>
 #include <acpi/acpi_drivers.h>
 #include <acpi/acpi_bus.h>
 #include <asm/uaccess.h>
+#include <linux/sonypi.h>
+#include <linux/sony-laptop.h>
+#ifdef CONFIG_SONY_LAPTOP_OLD
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#endif
 
-#define ACPI_SNC_CLASS         "sony"
-#define ACPI_SNC_HID           "SNY5001"
-#define ACPI_SNC_DRIVER_NAME   "ACPI Sony Notebook Control Driver v0.4"
+#define DRV_PFX                        "sony-laptop: "
+#define dprintk(msg...)                do {                    \
+       if (debug) printk(KERN_WARNING DRV_PFX  msg);   \
+} while (0)
 
-/* the device uses 1-based values, while the backlight subsystem uses
-   0-based values */
-#define SONY_MAX_BRIGHTNESS    8
+#define SONY_LAPTOP_DRIVER_VERSION     "0.5"
 
-#define LOG_PFX                        KERN_WARNING "sony-laptop: "
+#define SONY_NC_CLASS          "sony-nc"
+#define SONY_NC_HID            "SNY5001"
+#define SONY_NC_DRIVER_NAME    "Sony Notebook Control Driver"
+
+#define SONY_PIC_CLASS         "sony-pic"
+#define SONY_PIC_HID           "SNY6001"
+#define SONY_PIC_DRIVER_NAME   "Sony Programmable IO Control Driver"
 
 MODULE_AUTHOR("Stelian Pop, Mattia Dongili");
-MODULE_DESCRIPTION(ACPI_SNC_DRIVER_NAME);
+MODULE_DESCRIPTION("Sony laptop extras driver (SPIC and SNC ACPI device)");
 MODULE_LICENSE("GPL");
+MODULE_VERSION(SONY_LAPTOP_DRIVER_VERSION);
 
 static int debug;
 module_param(debug, int, 0);
 MODULE_PARM_DESC(debug, "set this to 1 (and RTFM) if you want to help "
                 "the development of this driver");
 
-static ssize_t sony_acpi_show(struct device *, struct device_attribute *,
+static int no_spic;            /* = 0 */
+module_param(no_spic, int, 0444);
+MODULE_PARM_DESC(no_spic,
+                "set this if you don't want to enable the SPIC device");
+
+static int compat;             /* = 0 */
+module_param(compat, int, 0444);
+MODULE_PARM_DESC(compat,
+                "set this if you want to enable backward compatibility mode");
+
+static unsigned long mask = 0xffffffff;
+module_param(mask, ulong, 0644);
+MODULE_PARM_DESC(mask,
+                "set this to the mask of event you want to enable (see doc)");
+
+static int camera;             /* = 0 */
+module_param(camera, int, 0444);
+MODULE_PARM_DESC(camera,
+                "set this to 1 to enable Motion Eye camera controls "
+                "(only use it if you have a C1VE or C1VN model)");
+
+#ifdef CONFIG_SONY_LAPTOP_OLD
+static int minor = -1;
+module_param(minor, int, 0);
+MODULE_PARM_DESC(minor,
+                "minor number of the misc device for the SPIC compatibility code, "
+                "default is -1 (automatic)");
+#endif
+
+/*********** Input Devices ***********/
+
+#define SONY_LAPTOP_BUF_SIZE   128
+struct sony_laptop_input_s {
+       atomic_t                users;
+       struct input_dev        *jog_dev;
+       struct input_dev        *key_dev;
+       struct kfifo            *fifo;
+       spinlock_t              fifo_lock;
+       struct workqueue_struct *wq;
+};
+static struct sony_laptop_input_s sony_laptop_input = {
+       .users = ATOMIC_INIT(0),
+};
+
+struct sony_laptop_keypress {
+       struct input_dev *dev;
+       int key;
+};
+
+/* Correspondance table between sonypi events and input layer events */
+static struct {
+       int sonypiev;
+       int inputev;
+} sony_laptop_inputkeys[] = {
+       { SONYPI_EVENT_CAPTURE_PRESSED,         KEY_CAMERA },
+       { SONYPI_EVENT_FNKEY_ONLY,              KEY_FN },
+       { SONYPI_EVENT_FNKEY_ESC,               KEY_FN_ESC },
+       { SONYPI_EVENT_FNKEY_F1,                KEY_FN_F1 },
+       { SONYPI_EVENT_FNKEY_F2,                KEY_FN_F2 },
+       { SONYPI_EVENT_FNKEY_F3,                KEY_FN_F3 },
+       { SONYPI_EVENT_FNKEY_F4,                KEY_FN_F4 },
+       { SONYPI_EVENT_FNKEY_F5,                KEY_FN_F5 },
+       { SONYPI_EVENT_FNKEY_F6,                KEY_FN_F6 },
+       { SONYPI_EVENT_FNKEY_F7,                KEY_FN_F7 },
+       { SONYPI_EVENT_FNKEY_F8,                KEY_FN_F8 },
+       { SONYPI_EVENT_FNKEY_F9,                KEY_FN_F9 },
+       { SONYPI_EVENT_FNKEY_F10,               KEY_FN_F10 },
+       { SONYPI_EVENT_FNKEY_F11,               KEY_FN_F11 },
+       { SONYPI_EVENT_FNKEY_F12,               KEY_FN_F12 },
+       { SONYPI_EVENT_FNKEY_1,                 KEY_FN_1 },
+       { SONYPI_EVENT_FNKEY_2,                 KEY_FN_2 },
+       { SONYPI_EVENT_FNKEY_D,                 KEY_FN_D },
+       { SONYPI_EVENT_FNKEY_E,                 KEY_FN_E },
+       { SONYPI_EVENT_FNKEY_F,                 KEY_FN_F },
+       { SONYPI_EVENT_FNKEY_S,                 KEY_FN_S },
+       { SONYPI_EVENT_FNKEY_B,                 KEY_FN_B },
+       { SONYPI_EVENT_BLUETOOTH_PRESSED,       KEY_BLUE },
+       { SONYPI_EVENT_BLUETOOTH_ON,            KEY_BLUE },
+       { SONYPI_EVENT_PKEY_P1,                 KEY_PROG1 },
+       { SONYPI_EVENT_PKEY_P2,                 KEY_PROG2 },
+       { SONYPI_EVENT_PKEY_P3,                 KEY_PROG3 },
+       { SONYPI_EVENT_BACK_PRESSED,            KEY_BACK },
+       { SONYPI_EVENT_HELP_PRESSED,            KEY_HELP },
+       { SONYPI_EVENT_ZOOM_PRESSED,            KEY_ZOOM },
+       { SONYPI_EVENT_THUMBPHRASE_PRESSED,     BTN_THUMB },
+       { 0, 0 },
+};
+
+/* release buttons after a short delay if pressed */
+static void do_sony_laptop_release_key(struct work_struct *work)
+{
+       struct sony_laptop_keypress kp;
+
+       while (kfifo_get(sony_laptop_input.fifo, (unsigned char *)&kp,
+                        sizeof(kp)) == sizeof(kp)) {
+               msleep(10);
+               input_report_key(kp.dev, kp.key, 0);
+               input_sync(kp.dev);
+       }
+}
+static DECLARE_WORK(sony_laptop_release_key_work,
+               do_sony_laptop_release_key);
+
+/* forward event to the input subsytem */
+static void sony_laptop_report_input_event(u8 event)
+{
+       struct input_dev *jog_dev = sony_laptop_input.jog_dev;
+       struct input_dev *key_dev = sony_laptop_input.key_dev;
+       struct sony_laptop_keypress kp = { NULL };
+       int i;
+
+       if (event == SONYPI_EVENT_FNKEY_RELEASED) {
+               /* Nothing, not all VAIOs generate this event */
+               return;
+       }
+
+       /* report events */
+       switch (event) {
+       /* jog_dev events */
+       case SONYPI_EVENT_JOGDIAL_UP:
+       case SONYPI_EVENT_JOGDIAL_UP_PRESSED:
+               input_report_rel(jog_dev, REL_WHEEL, 1);
+               input_sync(jog_dev);
+               return;
+
+       case SONYPI_EVENT_JOGDIAL_DOWN:
+       case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED:
+               input_report_rel(jog_dev, REL_WHEEL, -1);
+               input_sync(jog_dev);
+               return;
+
+       /* key_dev events */
+       case SONYPI_EVENT_JOGDIAL_PRESSED:
+               kp.key = BTN_MIDDLE;
+               kp.dev = jog_dev;
+               break;
+
+       default:
+               for (i = 0; sony_laptop_inputkeys[i].sonypiev; i++)
+                       if (event == sony_laptop_inputkeys[i].sonypiev) {
+                               kp.dev = key_dev;
+                               kp.key = sony_laptop_inputkeys[i].inputev;
+                               break;
+                       }
+               break;
+       }
+
+       if (kp.dev) {
+               input_report_key(kp.dev, kp.key, 1);
+               input_sync(kp.dev);
+               kfifo_put(sony_laptop_input.fifo,
+                         (unsigned char *)&kp, sizeof(kp));
+
+               if (!work_pending(&sony_laptop_release_key_work))
+                       queue_work(sony_laptop_input.wq,
+                                       &sony_laptop_release_key_work);
+       } else
+               dprintk("unknown input event %.2x\n", event);
+}
+
+static int sony_laptop_setup_input(void)
+{
+       struct input_dev *jog_dev;
+       struct input_dev *key_dev;
+       int i;
+       int error;
+
+       /* don't run again if already initialized */
+       if (atomic_add_return(1, &sony_laptop_input.users) > 1)
+               return 0;
+
+       /* kfifo */
+       spin_lock_init(&sony_laptop_input.fifo_lock);
+       sony_laptop_input.fifo =
+               kfifo_alloc(SONY_LAPTOP_BUF_SIZE, GFP_KERNEL,
+                           &sony_laptop_input.fifo_lock);
+       if (IS_ERR(sony_laptop_input.fifo)) {
+               printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n");
+               error = PTR_ERR(sony_laptop_input.fifo);
+               goto err_dec_users;
+       }
+
+       /* init workqueue */
+       sony_laptop_input.wq = create_singlethread_workqueue("sony-laptop");
+       if (!sony_laptop_input.wq) {
+               printk(KERN_ERR DRV_PFX
+                               "Unabe to create workqueue.\n");
+               error = -ENXIO;
+               goto err_free_kfifo;
+       }
+
+       /* input keys */
+       key_dev = input_allocate_device();
+       if (!key_dev) {
+               error = -ENOMEM;
+               goto err_destroy_wq;
+       }
+
+       key_dev->name = "Sony Vaio Keys";
+       key_dev->id.bustype = BUS_ISA;
+       key_dev->id.vendor = PCI_VENDOR_ID_SONY;
+
+       /* Initialize the Input Drivers: special keys */
+       key_dev->evbit[0] = BIT(EV_KEY);
+       for (i = 0; sony_laptop_inputkeys[i].sonypiev; i++)
+               if (sony_laptop_inputkeys[i].inputev)
+                       set_bit(sony_laptop_inputkeys[i].inputev,
+                                       key_dev->keybit);
+
+       error = input_register_device(key_dev);
+       if (error)
+               goto err_free_keydev;
+
+       sony_laptop_input.key_dev = key_dev;
+
+       /* jogdial */
+       jog_dev = input_allocate_device();
+       if (!jog_dev) {
+               error = -ENOMEM;
+               goto err_unregister_keydev;
+       }
+
+       jog_dev->name = "Sony Vaio Jogdial";
+       jog_dev->id.bustype = BUS_ISA;
+       jog_dev->id.vendor = PCI_VENDOR_ID_SONY;
+
+       jog_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
+       jog_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_MIDDLE);
+       jog_dev->relbit[0] = BIT(REL_WHEEL);
+
+       error = input_register_device(jog_dev);
+       if (error)
+               goto err_free_jogdev;
+
+       sony_laptop_input.jog_dev = jog_dev;
+
+       return 0;
+
+err_free_jogdev:
+       input_free_device(jog_dev);
+
+err_unregister_keydev:
+       input_unregister_device(key_dev);
+       /* to avoid kref underflow below at input_free_device */
+       key_dev = NULL;
+
+err_free_keydev:
+       input_free_device(key_dev);
+
+err_destroy_wq:
+       destroy_workqueue(sony_laptop_input.wq);
+
+err_free_kfifo:
+       kfifo_free(sony_laptop_input.fifo);
+
+err_dec_users:
+       atomic_dec(&sony_laptop_input.users);
+       return error;
+}
+
+static void sony_laptop_remove_input(void)
+{
+       /* cleanup only after the last user has gone */
+       if (!atomic_dec_and_test(&sony_laptop_input.users))
+               return;
+
+       /* flush workqueue first */
+       flush_workqueue(sony_laptop_input.wq);
+
+       /* destroy input devs */
+       input_unregister_device(sony_laptop_input.key_dev);
+       sony_laptop_input.key_dev = NULL;
+
+       if (sony_laptop_input.jog_dev) {
+               input_unregister_device(sony_laptop_input.jog_dev);
+               sony_laptop_input.jog_dev = NULL;
+       }
+
+       destroy_workqueue(sony_laptop_input.wq);
+       kfifo_free(sony_laptop_input.fifo);
+}
+
+/*********** Platform Device ***********/
+
+static atomic_t sony_pf_users = ATOMIC_INIT(0);
+static struct platform_driver sony_pf_driver = {
+       .driver = {
+                  .name = "sony-laptop",
+                  .owner = THIS_MODULE,
+                  }
+};
+static struct platform_device *sony_pf_device;
+
+static int sony_pf_add(void)
+{
+       int ret = 0;
+
+       /* don't run again if already initialized */
+       if (atomic_add_return(1, &sony_pf_users) > 1)
+               return 0;
+
+       ret = platform_driver_register(&sony_pf_driver);
+       if (ret)
+               goto out;
+
+       sony_pf_device = platform_device_alloc("sony-laptop", -1);
+       if (!sony_pf_device) {
+               ret = -ENOMEM;
+               goto out_platform_registered;
+       }
+
+       ret = platform_device_add(sony_pf_device);
+       if (ret)
+               goto out_platform_alloced;
+
+       return 0;
+
+      out_platform_alloced:
+       platform_device_put(sony_pf_device);
+       sony_pf_device = NULL;
+      out_platform_registered:
+       platform_driver_unregister(&sony_pf_driver);
+      out:
+       atomic_dec(&sony_pf_users);
+       return ret;
+}
+
+static void sony_pf_remove(void)
+{
+       /* deregister only after the last user has gone */
+       if (!atomic_dec_and_test(&sony_pf_users))
+               return;
+
+       platform_device_del(sony_pf_device);
+       platform_device_put(sony_pf_device);
+       platform_driver_unregister(&sony_pf_driver);
+}
+
+/*********** SNC (SNY5001) Device ***********/
+
+/* the device uses 1-based values, while the backlight subsystem uses
+   0-based values */
+#define SONY_MAX_BRIGHTNESS    8
+
+#define SNC_VALIDATE_IN                0
+#define SNC_VALIDATE_OUT       1
+
+static ssize_t sony_nc_sysfs_show(struct device *, struct device_attribute *,
                              char *);
-static ssize_t sony_acpi_store(struct device *, struct device_attribute *,
+static ssize_t sony_nc_sysfs_store(struct device *, struct device_attribute *,
                               const char *, size_t);
 static int boolean_validate(const int, const int);
 static int brightness_default_validate(const int, const int);
 
-#define SNC_VALIDATE_IN                0
-#define SNC_VALIDATE_OUT       1
-
-struct sony_acpi_value {
+struct sony_nc_value {
        char *name;             /* name of the entry */
        char **acpiget;         /* names of the ACPI get function */
        char **acpiset;         /* names of the ACPI set function */
@@ -75,65 +458,65 @@ struct sony_acpi_value {
        struct device_attribute devattr;        /* sysfs atribute */
 };
 
-#define HANDLE_NAMES(_name, _values...) \
+#define SNC_HANDLE_NAMES(_name, _values...) \
        static char *snc_##_name[] = { _values, NULL }
 
-#define SONY_ACPI_VALUE(_name, _getters, _setters, _validate, _debug) \
+#define SNC_HANDLE(_name, _getters, _setters, _validate, _debug) \
        { \
                .name           = __stringify(_name), \
                .acpiget        = _getters, \
                .acpiset        = _setters, \
                .validate       = _validate, \
                .debug          = _debug, \
-               .devattr        = __ATTR(_name, 0, sony_acpi_show, sony_acpi_store), \
+               .devattr        = __ATTR(_name, 0, sony_nc_sysfs_show, sony_nc_sysfs_store), \
        }
 
-#define SONY_ACPI_VALUE_NULL   { .name = NULL }
+#define SNC_HANDLE_NULL        { .name = NULL }
 
-HANDLE_NAMES(fnkey_get, "GHKE");
+SNC_HANDLE_NAMES(fnkey_get, "GHKE");
 
-HANDLE_NAMES(brightness_def_get, "GPBR");
-HANDLE_NAMES(brightness_def_set, "SPBR");
+SNC_HANDLE_NAMES(brightness_def_get, "GPBR");
+SNC_HANDLE_NAMES(brightness_def_set, "SPBR");
 
-HANDLE_NAMES(cdpower_get, "GCDP");
-HANDLE_NAMES(cdpower_set, "SCDP", "CDPW");
+SNC_HANDLE_NAMES(cdpower_get, "GCDP");
+SNC_HANDLE_NAMES(cdpower_set, "SCDP", "CDPW");
 
-HANDLE_NAMES(audiopower_get, "GAZP");
-HANDLE_NAMES(audiopower_set, "AZPW");
+SNC_HANDLE_NAMES(audiopower_get, "GAZP");
+SNC_HANDLE_NAMES(audiopower_set, "AZPW");
 
-HANDLE_NAMES(lanpower_get, "GLNP");
-HANDLE_NAMES(lanpower_set, "LNPW");
+SNC_HANDLE_NAMES(lanpower_get, "GLNP");
+SNC_HANDLE_NAMES(lanpower_set, "LNPW");
 
-HANDLE_NAMES(PID_get, "GPID");
+SNC_HANDLE_NAMES(PID_get, "GPID");
 
-HANDLE_NAMES(CTR_get, "GCTR");
-HANDLE_NAMES(CTR_set, "SCTR");
+SNC_HANDLE_NAMES(CTR_get, "GCTR");
+SNC_HANDLE_NAMES(CTR_set, "SCTR");
 
-HANDLE_NAMES(PCR_get, "GPCR");
-HANDLE_NAMES(PCR_set, "SPCR");
+SNC_HANDLE_NAMES(PCR_get, "GPCR");
+SNC_HANDLE_NAMES(PCR_set, "SPCR");
 
-HANDLE_NAMES(CMI_get, "GCMI");
-HANDLE_NAMES(CMI_set, "SCMI");
+SNC_HANDLE_NAMES(CMI_get, "GCMI");
+SNC_HANDLE_NAMES(CMI_set, "SCMI");
 
-static struct sony_acpi_value sony_acpi_values[] = {
-       SONY_ACPI_VALUE(brightness_default, snc_brightness_def_get,
+static struct sony_nc_value sony_nc_values[] = {
+       SNC_HANDLE(brightness_default, snc_brightness_def_get,
                        snc_brightness_def_set, brightness_default_validate, 0),
-       SONY_ACPI_VALUE(fnkey, snc_fnkey_get, NULL, NULL, 0),
-       SONY_ACPI_VALUE(cdpower, snc_cdpower_get, snc_cdpower_set, boolean_validate, 0),
-       SONY_ACPI_VALUE(audiopower, snc_audiopower_get, snc_audiopower_set,
+       SNC_HANDLE(fnkey, snc_fnkey_get, NULL, NULL, 0),
+       SNC_HANDLE(cdpower, snc_cdpower_get, snc_cdpower_set, boolean_validate, 0),
+       SNC_HANDLE(audiopower, snc_audiopower_get, snc_audiopower_set,
                        boolean_validate, 0),
-       SONY_ACPI_VALUE(lanpower, snc_lanpower_get, snc_lanpower_set,
+       SNC_HANDLE(lanpower, snc_lanpower_get, snc_lanpower_set,
                        boolean_validate, 1),
        /* unknown methods */
-       SONY_ACPI_VALUE(PID, snc_PID_get, NULL, NULL, 1),
-       SONY_ACPI_VALUE(CTR, snc_CTR_get, snc_CTR_set, NULL, 1),
-       SONY_ACPI_VALUE(PCR, snc_PCR_get, snc_PCR_set, NULL, 1),
-       SONY_ACPI_VALUE(CMI, snc_CMI_get, snc_CMI_set, NULL, 1),
-       SONY_ACPI_VALUE_NULL
+       SNC_HANDLE(PID, snc_PID_get, NULL, NULL, 1),
+       SNC_HANDLE(CTR, snc_CTR_get, snc_CTR_set, NULL, 1),
+       SNC_HANDLE(PCR, snc_PCR_get, snc_PCR_set, NULL, 1),
+       SNC_HANDLE(CMI, snc_CMI_get, snc_CMI_set, NULL, 1),
+       SNC_HANDLE_NULL
 };
 
-static acpi_handle sony_acpi_handle;
-static struct acpi_device *sony_acpi_acpi_device = NULL;
+static acpi_handle sony_nc_acpi_handle;
+static struct acpi_device *sony_nc_acpi_device = NULL;
 
 /*
  * acpi_evaluate_object wrappers
@@ -153,7 +536,7 @@ static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
                return 0;
        }
 
-       printk(LOG_PFX "acpi_callreadfunc failed\n");
+       printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n");
 
        return -1;
 }
@@ -179,7 +562,7 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
        if (status == AE_OK) {
                if (result != NULL) {
                        if (out_obj.type != ACPI_TYPE_INTEGER) {
-                               printk(LOG_PFX "acpi_evaluate_object bad "
+                               printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad "
                                       "return type\n");
                                return -1;
                        }
@@ -188,13 +571,13 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
                return 0;
        }
 
-       printk(LOG_PFX "acpi_evaluate_object failed\n");
+       printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n");
 
        return -1;
 }
 
 /*
- * sony_acpi_values input/output validate functions
+ * sony_nc_values input/output validate functions
  */
 
 /* brightness_default_validate:
@@ -229,19 +612,19 @@ static int boolean_validate(const int direction, const int value)
 }
 
 /*
- * Sysfs show/store common to all sony_acpi_values
+ * Sysfs show/store common to all sony_nc_values
  */
-static ssize_t sony_acpi_show(struct device *dev, struct device_attribute *attr,
+static ssize_t sony_nc_sysfs_show(struct device *dev, struct device_attribute *attr,
                              char *buffer)
 {
        int value;
-       struct sony_acpi_value *item =
-           container_of(attr, struct sony_acpi_value, devattr);
+       struct sony_nc_value *item =
+           container_of(attr, struct sony_nc_value, devattr);
 
        if (!*item->acpiget)
                return -EIO;
 
-       if (acpi_callgetfunc(sony_acpi_handle, *item->acpiget, &value) < 0)
+       if (acpi_callgetfunc(sony_nc_acpi_handle, *item->acpiget, &value) < 0)
                return -EIO;
 
        if (item->validate)
@@ -250,13 +633,13 @@ static ssize_t sony_acpi_show(struct device *dev, struct device_attribute *attr,
        return snprintf(buffer, PAGE_SIZE, "%d\n", value);
 }
 
-static ssize_t sony_acpi_store(struct device *dev,
+static ssize_t sony_nc_sysfs_store(struct device *dev,
                               struct device_attribute *attr,
                               const char *buffer, size_t count)
 {
        int value;
-       struct sony_acpi_value *item =
-           container_of(attr, struct sony_acpi_value, devattr);
+       struct sony_nc_value *item =
+           container_of(attr, struct sony_nc_value, devattr);
 
        if (!item->acpiset)
                return -EIO;
@@ -272,118 +655,20 @@ static ssize_t sony_acpi_store(struct device *dev,
        if (value < 0)
                return value;
 
-       if (acpi_callsetfunc(sony_acpi_handle, *item->acpiset, value, NULL) < 0)
+       if (acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, value, NULL) < 0)
                return -EIO;
        item->value = value;
        item->valid = 1;
        return count;
 }
 
-/*
- * Platform device
- */
-static struct platform_driver sncpf_driver = {
-       .driver = {
-                  .name = "sony-laptop",
-                  .owner = THIS_MODULE,
-                  }
-};
-static struct platform_device *sncpf_device;
-
-static int sony_snc_pf_add(void)
-{
-       acpi_handle handle;
-       struct sony_acpi_value *item;
-       int ret = 0;
-
-       ret = platform_driver_register(&sncpf_driver);
-       if (ret)
-               goto out;
-
-       sncpf_device = platform_device_alloc("sony-laptop", -1);
-       if (!sncpf_device) {
-               ret = -ENOMEM;
-               goto out_platform_registered;
-       }
-
-       ret = platform_device_add(sncpf_device);
-       if (ret)
-               goto out_platform_alloced;
-
-       for (item = sony_acpi_values; item->name; ++item) {
-
-               if (!debug && item->debug)
-                       continue;
-
-               /* find the available acpiget as described in the DSDT */
-               for (; item->acpiget && *item->acpiget; ++item->acpiget) {
-                       if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle,
-                                                        *item->acpiget,
-                                                        &handle))) {
-                               if (debug)
-                                       printk(LOG_PFX "Found %s getter: %s\n",
-                                              item->name, *item->acpiget);
-                               item->devattr.attr.mode |= S_IRUGO;
-                               break;
-                       }
-               }
-
-               /* find the available acpiset as described in the DSDT */
-               for (; item->acpiset && *item->acpiset; ++item->acpiset) {
-                       if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle,
-                                                        *item->acpiset,
-                                                        &handle))) {
-                               if (debug)
-                                       printk(LOG_PFX "Found %s setter: %s\n",
-                                              item->name, *item->acpiset);
-                               item->devattr.attr.mode |= S_IWUSR;
-                               break;
-                       }
-               }
-
-               if (item->devattr.attr.mode != 0) {
-                       ret =
-                           device_create_file(&sncpf_device->dev,
-                                              &item->devattr);
-                       if (ret)
-                               goto out_sysfs;
-               }
-       }
-
-       return 0;
-
-      out_sysfs:
-       for (item = sony_acpi_values; item->name; ++item) {
-               device_remove_file(&sncpf_device->dev, &item->devattr);
-       }
-       platform_device_del(sncpf_device);
-      out_platform_alloced:
-       platform_device_put(sncpf_device);
-      out_platform_registered:
-       platform_driver_unregister(&sncpf_driver);
-      out:
-       return ret;
-}
-
-static void sony_snc_pf_remove(void)
-{
-       struct sony_acpi_value *item;
-
-       for (item = sony_acpi_values; item->name; ++item) {
-               device_remove_file(&sncpf_device->dev, &item->devattr);
-       }
-
-       platform_device_del(sncpf_device);
-       platform_device_put(sncpf_device);
-       platform_driver_unregister(&sncpf_driver);
-}
 
 /*
  * Backlight device
  */
 static int sony_backlight_update_status(struct backlight_device *bd)
 {
-       return acpi_callsetfunc(sony_acpi_handle, "SBRT",
+       return acpi_callsetfunc(sony_nc_acpi_handle, "SBRT",
                                bd->props.brightness + 1, NULL);
 }
 
@@ -391,7 +676,7 @@ static int sony_backlight_get_brightness(struct backlight_device *bd)
 {
        int value;
 
-       if (acpi_callgetfunc(sony_acpi_handle, "GBRT", &value))
+       if (acpi_callgetfunc(sony_nc_acpi_handle, "GBRT", &value))
                return 0;
        /* brightness levels are 1-based, while backlight ones are 0-based */
        return value - 1;
@@ -408,9 +693,9 @@ static struct backlight_ops sony_backlight_ops = {
  */
 static void sony_acpi_notify(acpi_handle handle, u32 event, void *data)
 {
-       if (debug)
-               printk(LOG_PFX "sony_acpi_notify, event: %d\n", event);
-       acpi_bus_generate_event(sony_acpi_acpi_device, 1, event);
+       dprintk("sony_acpi_notify, event: %d\n", event);
+       sony_laptop_report_input_event(event);
+       acpi_bus_generate_event(sony_nc_acpi_device, 1, event);
 }
 
 static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
@@ -422,7 +707,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
        node = (struct acpi_namespace_node *)handle;
        operand = (union acpi_operand_object *)node->object;
 
-       printk(LOG_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
+       printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
               (u32) operand->method.param_count);
 
        return AE_OK;
@@ -431,16 +716,16 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
 /*
  * ACPI device
  */
-static int sony_acpi_resume(struct acpi_device *device)
+static int sony_nc_resume(struct acpi_device *device)
 {
-       struct sony_acpi_value *item;
+       struct sony_nc_value *item;
 
-       for (item = sony_acpi_values; item->name; item++) {
+       for (item = sony_nc_values; item->name; item++) {
                int ret;
 
                if (!item->valid)
                        continue;
-               ret = acpi_callsetfunc(sony_acpi_handle, *item->acpiset,
+               ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset,
                                       item->value, NULL);
                if (ret < 0) {
                        printk("%s: %d\n", __FUNCTION__, ret);
@@ -450,42 +735,55 @@ static int sony_acpi_resume(struct acpi_device *device)
        return 0;
 }
 
-static int sony_acpi_add(struct acpi_device *device)
+static int sony_nc_add(struct acpi_device *device)
 {
        acpi_status status;
        int result = 0;
        acpi_handle handle;
+       struct sony_nc_value *item;
+
+       printk(KERN_INFO DRV_PFX "%s v%s.\n",
+               SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION);
 
-       sony_acpi_acpi_device = device;
+       sony_nc_acpi_device = device;
+       strcpy(acpi_device_class(device), "sony/hotkey");
 
-       sony_acpi_handle = device->handle;
+       sony_nc_acpi_handle = device->handle;
 
        if (debug) {
-               status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_acpi_handle,
+               status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_nc_acpi_handle,
                                             1, sony_walk_callback, NULL, NULL);
                if (ACPI_FAILURE(status)) {
-                       printk(LOG_PFX "unable to walk acpi resources\n");
+                       printk(KERN_WARNING DRV_PFX "unable to walk acpi resources\n");
                        result = -ENODEV;
                        goto outwalk;
                }
        }
 
-       status = acpi_install_notify_handler(sony_acpi_handle,
+       /* setup input devices and helper fifo */
+       result = sony_laptop_setup_input();
+       if (result) {
+               printk(KERN_ERR DRV_PFX
+                               "Unabe to create input devices.\n");
+               goto outwalk;
+       }
+
+       status = acpi_install_notify_handler(sony_nc_acpi_handle,
                                             ACPI_DEVICE_NOTIFY,
                                             sony_acpi_notify, NULL);
        if (ACPI_FAILURE(status)) {
-               printk(LOG_PFX "unable to install notify handler\n");
+               printk(KERN_WARNING DRV_PFX "unable to install notify handler\n");
                result = -ENODEV;
-               goto outwalk;
+               goto outinput;
        }
 
-       if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle, "GBRT", &handle))) {
+       if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT", &handle))) {
                sony_backlight_device = backlight_device_register("sony", NULL,
                                                                  NULL,
                                                                  &sony_backlight_ops);
 
                if (IS_ERR(sony_backlight_device)) {
-                       printk(LOG_PFX "unable to register backlight device\n");
+                       printk(KERN_WARNING DRV_PFX "unable to register backlight device\n");
                        sony_backlight_device = NULL;
                } else {
                        sony_backlight_device->props.brightness =
@@ -497,68 +795,1497 @@ static int sony_acpi_add(struct acpi_device *device)
 
        }
 
-       if (sony_snc_pf_add())
+       result = sony_pf_add();
+       if (result)
                goto outbacklight;
 
-       printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully installed\n");
+       /* create sony_pf sysfs attributes related to the SNC device */
+       for (item = sony_nc_values; item->name; ++item) {
+
+               if (!debug && item->debug)
+                       continue;
+
+               /* find the available acpiget as described in the DSDT */
+               for (; item->acpiget && *item->acpiget; ++item->acpiget) {
+                       if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle,
+                                                        *item->acpiget,
+                                                        &handle))) {
+                               dprintk("Found %s getter: %s\n",
+                                               item->name, *item->acpiget);
+                               item->devattr.attr.mode |= S_IRUGO;
+                               break;
+                       }
+               }
+
+               /* find the available acpiset as described in the DSDT */
+               for (; item->acpiset && *item->acpiset; ++item->acpiset) {
+                       if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle,
+                                                        *item->acpiset,
+                                                        &handle))) {
+                               dprintk("Found %s setter: %s\n",
+                                               item->name, *item->acpiset);
+                               item->devattr.attr.mode |= S_IWUSR;
+                               break;
+                       }
+               }
+
+               if (item->devattr.attr.mode != 0) {
+                       result =
+                           device_create_file(&sony_pf_device->dev,
+                                              &item->devattr);
+                       if (result)
+                               goto out_sysfs;
+               }
+       }
 
        return 0;
 
+      out_sysfs:
+       for (item = sony_nc_values; item->name; ++item) {
+               device_remove_file(&sony_pf_device->dev, &item->devattr);
+       }
+       sony_pf_remove();
+
       outbacklight:
        if (sony_backlight_device)
                backlight_device_unregister(sony_backlight_device);
 
-       status = acpi_remove_notify_handler(sony_acpi_handle,
+       status = acpi_remove_notify_handler(sony_nc_acpi_handle,
                                            ACPI_DEVICE_NOTIFY,
                                            sony_acpi_notify);
        if (ACPI_FAILURE(status))
-               printk(LOG_PFX "unable to remove notify handler\n");
+               printk(KERN_WARNING DRV_PFX "unable to remove notify handler\n");
+
+      outinput:
+       sony_laptop_remove_input();
+
       outwalk:
        return result;
 }
 
-static int sony_acpi_remove(struct acpi_device *device, int type)
+static int sony_nc_remove(struct acpi_device *device, int type)
 {
        acpi_status status;
+       struct sony_nc_value *item;
 
        if (sony_backlight_device)
                backlight_device_unregister(sony_backlight_device);
 
-       sony_acpi_acpi_device = NULL;
+       sony_nc_acpi_device = NULL;
 
-       status = acpi_remove_notify_handler(sony_acpi_handle,
+       status = acpi_remove_notify_handler(sony_nc_acpi_handle,
                                            ACPI_DEVICE_NOTIFY,
                                            sony_acpi_notify);
        if (ACPI_FAILURE(status))
-               printk(LOG_PFX "unable to remove notify handler\n");
+               printk(KERN_WARNING DRV_PFX "unable to remove notify handler\n");
 
-       sony_snc_pf_remove();
+       for (item = sony_nc_values; item->name; ++item) {
+               device_remove_file(&sony_pf_device->dev, &item->devattr);
+       }
 
-       printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully removed\n");
+       sony_pf_remove();
+       sony_laptop_remove_input();
+       dprintk(SONY_NC_DRIVER_NAME " removed.\n");
 
        return 0;
 }
 
-static struct acpi_driver sony_acpi_driver = {
-       .name = ACPI_SNC_DRIVER_NAME,
-       .class = ACPI_SNC_CLASS,
-       .ids = ACPI_SNC_HID,
+static struct acpi_driver sony_nc_driver = {
+       .name = SONY_NC_DRIVER_NAME,
+       .class = SONY_NC_CLASS,
+       .ids = SONY_NC_HID,
+       .owner = THIS_MODULE,
        .ops = {
-               .add = sony_acpi_add,
-               .remove = sony_acpi_remove,
-               .resume = sony_acpi_resume,
+               .add = sony_nc_add,
+               .remove = sony_nc_remove,
+               .resume = sony_nc_resume,
                },
 };
 
-static int __init sony_acpi_init(void)
-{
-       return acpi_bus_register_driver(&sony_acpi_driver);
+/*********** SPIC (SNY6001) Device ***********/
+
+#define SONYPI_DEVICE_TYPE1    0x00000001
+#define SONYPI_DEVICE_TYPE2    0x00000002
+#define SONYPI_DEVICE_TYPE3    0x00000004
+
+#define SONY_PIC_EV_MASK       0xff
+
+struct sony_pic_ioport {
+       struct acpi_resource_io io;
+       struct list_head        list;
+};
+
+struct sony_pic_irq {
+       struct acpi_resource_irq        irq;
+       struct list_head                list;
+};
+
+struct sony_pic_dev {
+       int                     model;
+       u8                      camera_power;
+       u8                      bluetooth_power;
+       u8                      wwan_power;
+       struct acpi_device      *acpi_dev;
+       struct sony_pic_irq     *cur_irq;
+       struct sony_pic_ioport  *cur_ioport;
+       struct list_head        interrupts;
+       struct list_head        ioports;
+       struct mutex            lock;
+};
+
+static struct sony_pic_dev spic_dev = {
+       .interrupts     = LIST_HEAD_INIT(spic_dev.interrupts),
+       .ioports        = LIST_HEAD_INIT(spic_dev.ioports),
+};
+
+/* Event masks */
+#define SONYPI_JOGGER_MASK                     0x00000001
+#define SONYPI_CAPTURE_MASK                    0x00000002
+#define SONYPI_FNKEY_MASK                      0x00000004
+#define SONYPI_BLUETOOTH_MASK                  0x00000008
+#define SONYPI_PKEY_MASK                       0x00000010
+#define SONYPI_BACK_MASK                       0x00000020
+#define SONYPI_HELP_MASK                       0x00000040
+#define SONYPI_LID_MASK                                0x00000080
+#define SONYPI_ZOOM_MASK                       0x00000100
+#define SONYPI_THUMBPHRASE_MASK                        0x00000200
+#define SONYPI_MEYE_MASK                       0x00000400
+#define SONYPI_MEMORYSTICK_MASK                        0x00000800
+#define SONYPI_BATTERY_MASK                    0x00001000
+#define SONYPI_WIRELESS_MASK                   0x00002000
+
+struct sonypi_event {
+       u8      data;
+       u8      event;
+};
+
+/* The set of possible button release events */
+static struct sonypi_event sonypi_releaseev[] = {
+       { 0x00, SONYPI_EVENT_ANYBUTTON_RELEASED },
+       { 0, 0 }
+};
+
+/* The set of possible jogger events  */
+static struct sonypi_event sonypi_joggerev[] = {
+       { 0x1f, SONYPI_EVENT_JOGDIAL_UP },
+       { 0x01, SONYPI_EVENT_JOGDIAL_DOWN },
+       { 0x5f, SONYPI_EVENT_JOGDIAL_UP_PRESSED },
+       { 0x41, SONYPI_EVENT_JOGDIAL_DOWN_PRESSED },
+       { 0x1e, SONYPI_EVENT_JOGDIAL_FAST_UP },
+       { 0x02, SONYPI_EVENT_JOGDIAL_FAST_DOWN },
+       { 0x5e, SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED },
+       { 0x42, SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED },
+       { 0x1d, SONYPI_EVENT_JOGDIAL_VFAST_UP },
+       { 0x03, SONYPI_EVENT_JOGDIAL_VFAST_DOWN },
+       { 0x5d, SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED },
+       { 0x43, SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED },
+       { 0x40, SONYPI_EVENT_JOGDIAL_PRESSED },
+       { 0, 0 }
+};
+
+/* The set of possible capture button events */
+static struct sonypi_event sonypi_captureev[] = {
+       { 0x05, SONYPI_EVENT_CAPTURE_PARTIALPRESSED },
+       { 0x07, SONYPI_EVENT_CAPTURE_PRESSED },
+       { 0x01, SONYPI_EVENT_CAPTURE_PARTIALRELEASED },
+       { 0, 0 }
+};
+
+/* The set of possible fnkeys events */
+static struct sonypi_event sonypi_fnkeyev[] = {
+       { 0x10, SONYPI_EVENT_FNKEY_ESC },
+       { 0x11, SONYPI_EVENT_FNKEY_F1 },
+       { 0x12, SONYPI_EVENT_FNKEY_F2 },
+       { 0x13, SONYPI_EVENT_FNKEY_F3 },
+       { 0x14, SONYPI_EVENT_FNKEY_F4 },
+       { 0x15, SONYPI_EVENT_FNKEY_F5 },
+       { 0x16, SONYPI_EVENT_FNKEY_F6 },
+       { 0x17, SONYPI_EVENT_FNKEY_F7 },
+       { 0x18, SONYPI_EVENT_FNKEY_F8 },
+       { 0x19, SONYPI_EVENT_FNKEY_F9 },
+       { 0x1a, SONYPI_EVENT_FNKEY_F10 },
+       { 0x1b, SONYPI_EVENT_FNKEY_F11 },
+       { 0x1c, SONYPI_EVENT_FNKEY_F12 },
+       { 0x1f, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x21, SONYPI_EVENT_FNKEY_1 },
+       { 0x22, SONYPI_EVENT_FNKEY_2 },
+       { 0x31, SONYPI_EVENT_FNKEY_D },
+       { 0x32, SONYPI_EVENT_FNKEY_E },
+       { 0x33, SONYPI_EVENT_FNKEY_F },
+       { 0x34, SONYPI_EVENT_FNKEY_S },
+       { 0x35, SONYPI_EVENT_FNKEY_B },
+       { 0x36, SONYPI_EVENT_FNKEY_ONLY },
+       { 0, 0 }
+};
+
+/* The set of possible program key events */
+static struct sonypi_event sonypi_pkeyev[] = {
+       { 0x01, SONYPI_EVENT_PKEY_P1 },
+       { 0x02, SONYPI_EVENT_PKEY_P2 },
+       { 0x04, SONYPI_EVENT_PKEY_P3 },
+       { 0x5c, SONYPI_EVENT_PKEY_P1 },
+       { 0, 0 }
+};
+
+/* The set of possible bluetooth events */
+static struct sonypi_event sonypi_blueev[] = {
+       { 0x55, SONYPI_EVENT_BLUETOOTH_PRESSED },
+       { 0x59, SONYPI_EVENT_BLUETOOTH_ON },
+       { 0x5a, SONYPI_EVENT_BLUETOOTH_OFF },
+       { 0, 0 }
+};
+
+/* The set of possible wireless events */
+static struct sonypi_event sonypi_wlessev[] = {
+       { 0x59, SONYPI_EVENT_WIRELESS_ON },
+       { 0x5a, SONYPI_EVENT_WIRELESS_OFF },
+       { 0, 0 }
+};
+
+/* The set of possible back button events */
+static struct sonypi_event sonypi_backev[] = {
+       { 0x20, SONYPI_EVENT_BACK_PRESSED },
+       { 0, 0 }
+};
+
+/* The set of possible help button events */
+static struct sonypi_event sonypi_helpev[] = {
+       { 0x3b, SONYPI_EVENT_HELP_PRESSED },
+       { 0, 0 }
+};
+
+
+/* The set of possible lid events */
+static struct sonypi_event sonypi_lidev[] = {
+       { 0x51, SONYPI_EVENT_LID_CLOSED },
+       { 0x50, SONYPI_EVENT_LID_OPENED },
+       { 0, 0 }
+};
+
+/* The set of possible zoom events */
+static struct sonypi_event sonypi_zoomev[] = {
+       { 0x39, SONYPI_EVENT_ZOOM_PRESSED },
+       { 0, 0 }
+};
+
+/* The set of possible thumbphrase events */
+static struct sonypi_event sonypi_thumbphraseev[] = {
+       { 0x3a, SONYPI_EVENT_THUMBPHRASE_PRESSED },
+       { 0, 0 }
+};
+
+/* The set of possible motioneye camera events */
+static struct sonypi_event sonypi_meyeev[] = {
+       { 0x00, SONYPI_EVENT_MEYE_FACE },
+       { 0x01, SONYPI_EVENT_MEYE_OPPOSITE },
+       { 0, 0 }
+};
+
+/* The set of possible memorystick events */
+static struct sonypi_event sonypi_memorystickev[] = {
+       { 0x53, SONYPI_EVENT_MEMORYSTICK_INSERT },
+       { 0x54, SONYPI_EVENT_MEMORYSTICK_EJECT },
+       { 0, 0 }
+};
+
+/* The set of possible battery events */
+static struct sonypi_event sonypi_batteryev[] = {
+       { 0x20, SONYPI_EVENT_BATTERY_INSERT },
+       { 0x30, SONYPI_EVENT_BATTERY_REMOVE },
+       { 0, 0 }
+};
+
+static struct sonypi_eventtypes {
+       int                     model;
+       u8                      data;
+       unsigned long           mask;
+       struct sonypi_event *   events;
+} sony_pic_eventtypes[] = {
+       { SONYPI_DEVICE_TYPE1, 0, 0xffffffff, sonypi_releaseev },
+       { SONYPI_DEVICE_TYPE1, 0x70, SONYPI_MEYE_MASK, sonypi_meyeev },
+       { SONYPI_DEVICE_TYPE1, 0x30, SONYPI_LID_MASK, sonypi_lidev },
+       { SONYPI_DEVICE_TYPE1, 0x60, SONYPI_CAPTURE_MASK, sonypi_captureev },
+       { SONYPI_DEVICE_TYPE1, 0x10, SONYPI_JOGGER_MASK, sonypi_joggerev },
+       { SONYPI_DEVICE_TYPE1, 0x20, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { SONYPI_DEVICE_TYPE1, 0x30, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+       { SONYPI_DEVICE_TYPE1, 0x40, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { SONYPI_DEVICE_TYPE1, 0x30, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { SONYPI_DEVICE_TYPE1, 0x40, SONYPI_BATTERY_MASK, sonypi_batteryev },
+
+       { SONYPI_DEVICE_TYPE2, 0, 0xffffffff, sonypi_releaseev },
+       { SONYPI_DEVICE_TYPE2, 0x38, SONYPI_LID_MASK, sonypi_lidev },
+       { SONYPI_DEVICE_TYPE2, 0x11, SONYPI_JOGGER_MASK, sonypi_joggerev },
+       { SONYPI_DEVICE_TYPE2, 0x61, SONYPI_CAPTURE_MASK, sonypi_captureev },
+       { SONYPI_DEVICE_TYPE2, 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { SONYPI_DEVICE_TYPE2, 0x31, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+       { SONYPI_DEVICE_TYPE2, 0x08, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { SONYPI_DEVICE_TYPE2, 0x11, SONYPI_BACK_MASK, sonypi_backev },
+       { SONYPI_DEVICE_TYPE2, 0x21, SONYPI_HELP_MASK, sonypi_helpev },
+       { SONYPI_DEVICE_TYPE2, 0x21, SONYPI_ZOOM_MASK, sonypi_zoomev },
+       { SONYPI_DEVICE_TYPE2, 0x20, SONYPI_THUMBPHRASE_MASK, sonypi_thumbphraseev },
+       { SONYPI_DEVICE_TYPE2, 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { SONYPI_DEVICE_TYPE2, 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+       { SONYPI_DEVICE_TYPE2, 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+
+       { SONYPI_DEVICE_TYPE3, 0, 0xffffffff, sonypi_releaseev },
+       { SONYPI_DEVICE_TYPE3, 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { SONYPI_DEVICE_TYPE3, 0x31, SONYPI_WIRELESS_MASK, sonypi_wlessev },
+       { SONYPI_DEVICE_TYPE3, 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { SONYPI_DEVICE_TYPE3, 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+       { SONYPI_DEVICE_TYPE3, 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { 0 }
+};
+
+static int sony_pic_detect_device_type(void)
+{
+       struct pci_dev *pcidev;
+       int model = 0;
+
+       if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                                    PCI_DEVICE_ID_INTEL_82371AB_3, NULL)))
+               model = SONYPI_DEVICE_TYPE1;
+
+       else if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                                         PCI_DEVICE_ID_INTEL_ICH6_1, NULL)))
+               model = SONYPI_DEVICE_TYPE3;
+
+       else if ((pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                                         PCI_DEVICE_ID_INTEL_ICH7_1, NULL)))
+               model = SONYPI_DEVICE_TYPE3;
+
+       else
+               model = SONYPI_DEVICE_TYPE2;
+
+       if (pcidev)
+               pci_dev_put(pcidev);
+
+       printk(KERN_INFO DRV_PFX "detected Type%d model\n",
+                       model == SONYPI_DEVICE_TYPE1 ? 1 :
+                       model == SONYPI_DEVICE_TYPE2 ? 2 : 3);
+       return model;
+}
+
+#define ITERATIONS_LONG                10000
+#define ITERATIONS_SHORT       10
+#define wait_on_command(command, iterations) {                         \
+       unsigned int n = iterations;                                    \
+       while (--n && (command))                                        \
+               udelay(1);                                              \
+       if (!n)                                                         \
+               dprintk("command failed at %s : %s (line %d)\n",        \
+                               __FILE__, __FUNCTION__, __LINE__);      \
+}
+
+static u8 sony_pic_call1(u8 dev)
+{
+       u8 v1, v2;
+
+       wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2,
+                       ITERATIONS_LONG);
+       outb(dev, spic_dev.cur_ioport->io.minimum + 4);
+       v1 = inb_p(spic_dev.cur_ioport->io.minimum + 4);
+       v2 = inb_p(spic_dev.cur_ioport->io.minimum);
+       dprintk("sony_pic_call1: 0x%.4x\n", (v2 << 8) | v1);
+       return v2;
+}
+
+static u8 sony_pic_call2(u8 dev, u8 fn)
+{
+       u8 v1;
+
+       wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2,
+                       ITERATIONS_LONG);
+       outb(dev, spic_dev.cur_ioport->io.minimum + 4);
+       wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2,
+                       ITERATIONS_LONG);
+       outb(fn, spic_dev.cur_ioport->io.minimum);
+       v1 = inb_p(spic_dev.cur_ioport->io.minimum);
+       dprintk("sony_pic_call2: 0x%.4x\n", v1);
+       return v1;
+}
+
+static u8 sony_pic_call3(u8 dev, u8 fn, u8 v)
+{
+       u8 v1;
+
+       wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, ITERATIONS_LONG);
+       outb(dev, spic_dev.cur_ioport->io.minimum + 4);
+       wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, ITERATIONS_LONG);
+       outb(fn, spic_dev.cur_ioport->io.minimum);
+       wait_on_command(inb_p(spic_dev.cur_ioport->io.minimum + 4) & 2, ITERATIONS_LONG);
+       outb(v, spic_dev.cur_ioport->io.minimum);
+       v1 = inb_p(spic_dev.cur_ioport->io.minimum);
+       dprintk("sony_pic_call3: 0x%.4x\n", v1);
+       return v1;
+}
+
+/* camera tests and poweron/poweroff */
+#define SONYPI_CAMERA_PICTURE          5
+#define SONYPI_CAMERA_CONTROL          0x10
+
+#define SONYPI_CAMERA_BRIGHTNESS               0
+#define SONYPI_CAMERA_CONTRAST                 1
+#define SONYPI_CAMERA_HUE                      2
+#define SONYPI_CAMERA_COLOR                    3
+#define SONYPI_CAMERA_SHARPNESS                        4
+
+#define SONYPI_CAMERA_EXPOSURE_MASK            0xC
+#define SONYPI_CAMERA_WHITE_BALANCE_MASK       0x3
+#define SONYPI_CAMERA_PICTURE_MODE_MASK                0x30
+#define SONYPI_CAMERA_MUTE_MASK                        0x40
+
+/* the rest don't need a loop until not 0xff */
+#define SONYPI_CAMERA_AGC                      6
+#define SONYPI_CAMERA_AGC_MASK                 0x30
+#define SONYPI_CAMERA_SHUTTER_MASK             0x7
+
+#define SONYPI_CAMERA_SHUTDOWN_REQUEST         7
+#define SONYPI_CAMERA_CONTROL                  0x10
+
+#define SONYPI_CAMERA_STATUS                   7
+#define SONYPI_CAMERA_STATUS_READY             0x2
+#define SONYPI_CAMERA_STATUS_POSITION          0x4
+
+#define SONYPI_DIRECTION_BACKWARDS             0x4
+
+#define SONYPI_CAMERA_REVISION                         8
+#define SONYPI_CAMERA_ROMVERSION               9
+
+static int __sony_pic_camera_ready(void)
+{
+       u8 v;
+
+       v = sony_pic_call2(0x8f, SONYPI_CAMERA_STATUS);
+       return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY));
+}
+
+static int __sony_pic_camera_off(void)
+{
+       if (!camera) {
+               printk(KERN_WARNING DRV_PFX "camera control not enabled\n");
+               return -ENODEV;
+       }
+
+       wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE,
+                               SONYPI_CAMERA_MUTE_MASK),
+                       ITERATIONS_SHORT);
+
+       if (spic_dev.camera_power) {
+               sony_pic_call2(0x91, 0);
+               spic_dev.camera_power = 0;
+       }
+       return 0;
+}
+
+static int __sony_pic_camera_on(void)
+{
+       int i, j, x;
+
+       if (!camera) {
+               printk(KERN_WARNING DRV_PFX "camera control not enabled\n");
+               return -ENODEV;
+       }
+
+       if (spic_dev.camera_power)
+               return 0;
+
+       for (j = 5; j > 0; j--) {
+
+               for (x = 0; x < 100 && sony_pic_call2(0x91, 0x1); x++)
+                       msleep(10);
+               sony_pic_call1(0x93);
+
+               for (i = 400; i > 0; i--) {
+                       if (__sony_pic_camera_ready())
+                               break;
+                       msleep(10);
+               }
+               if (i)
+                       break;
+       }
+
+       if (j == 0) {
+               printk(KERN_WARNING DRV_PFX "failed to power on camera\n");
+               return -ENODEV;
+       }
+
+       wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTROL,
+                               0x5a),
+                       ITERATIONS_SHORT);
+
+       spic_dev.camera_power = 1;
+       return 0;
+}
+
+/* External camera command (exported to the motion eye v4l driver) */
+int sony_pic_camera_command(int command, u8 value)
+{
+       if (!camera)
+               return -EIO;
+
+       mutex_lock(&spic_dev.lock);
+
+       switch (command) {
+       case SONY_PIC_COMMAND_SETCAMERA:
+               if (value)
+                       __sony_pic_camera_on();
+               else
+                       __sony_pic_camera_off();
+               break;
+       case SONY_PIC_COMMAND_SETCAMERABRIGHTNESS:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_BRIGHTNESS, value),
+                               ITERATIONS_SHORT);
+               break;
+       case SONY_PIC_COMMAND_SETCAMERACONTRAST:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTRAST, value),
+                               ITERATIONS_SHORT);
+               break;
+       case SONY_PIC_COMMAND_SETCAMERAHUE:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_HUE, value),
+                               ITERATIONS_SHORT);
+               break;
+       case SONY_PIC_COMMAND_SETCAMERACOLOR:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_COLOR, value),
+                               ITERATIONS_SHORT);
+               break;
+       case SONY_PIC_COMMAND_SETCAMERASHARPNESS:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_SHARPNESS, value),
+                               ITERATIONS_SHORT);
+               break;
+       case SONY_PIC_COMMAND_SETCAMERAPICTURE:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, value),
+                               ITERATIONS_SHORT);
+               break;
+       case SONY_PIC_COMMAND_SETCAMERAAGC:
+               wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_AGC, value),
+                               ITERATIONS_SHORT);
+               break;
+       default:
+               printk(KERN_ERR DRV_PFX "sony_pic_camera_command invalid: %d\n",
+                      command);
+               break;
+       }
+       mutex_unlock(&spic_dev.lock);
+       return 0;
+}
+EXPORT_SYMBOL(sony_pic_camera_command);
+
+/* gprs/edge modem (SZ460N and SZ210P), thanks to Joshua Wise */
+static void sony_pic_set_wwanpower(u8 state)
+{
+       state = !!state;
+       mutex_lock(&spic_dev.lock);
+       if (spic_dev.wwan_power == state) {
+               mutex_unlock(&spic_dev.lock);
+               return;
+       }
+       sony_pic_call2(0xB0, state);
+       spic_dev.wwan_power = state;
+       mutex_unlock(&spic_dev.lock);
+}
+
+static ssize_t sony_pic_wwanpower_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buffer, size_t count)
+{
+       unsigned long value;
+       if (count > 31)
+               return -EINVAL;
+
+       value = simple_strtoul(buffer, NULL, 10);
+       sony_pic_set_wwanpower(value);
+
+       return count;
+}
+
+static ssize_t sony_pic_wwanpower_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count;
+       mutex_lock(&spic_dev.lock);
+       count = snprintf(buffer, PAGE_SIZE, "%d\n", spic_dev.wwan_power);
+       mutex_unlock(&spic_dev.lock);
+       return count;
+}
+
+/* bluetooth subsystem power state */
+static void __sony_pic_set_bluetoothpower(u8 state)
+{
+       state = !!state;
+       if (spic_dev.bluetooth_power == state)
+               return;
+       sony_pic_call2(0x96, state);
+       sony_pic_call1(0x82);
+       spic_dev.bluetooth_power = state;
+}
+
+static ssize_t sony_pic_bluetoothpower_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buffer, size_t count)
+{
+       unsigned long value;
+       if (count > 31)
+               return -EINVAL;
+
+       value = simple_strtoul(buffer, NULL, 10);
+       mutex_lock(&spic_dev.lock);
+       __sony_pic_set_bluetoothpower(value);
+       mutex_unlock(&spic_dev.lock);
+
+       return count;
+}
+
+static ssize_t sony_pic_bluetoothpower_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       mutex_lock(&spic_dev.lock);
+       count = snprintf(buffer, PAGE_SIZE, "%d\n", spic_dev.bluetooth_power);
+       mutex_unlock(&spic_dev.lock);
+       return count;
+}
+
+/* fan speed */
+/* FAN0 information (reverse engineered from ACPI tables) */
+#define SONY_PIC_FAN0_STATUS   0x93
+static int sony_pic_set_fanspeed(unsigned long value)
+{
+       return ec_write(SONY_PIC_FAN0_STATUS, value);
+}
+
+static int sony_pic_get_fanspeed(u8 *value)
+{
+       return ec_read(SONY_PIC_FAN0_STATUS, value);
+}
+
+static ssize_t sony_pic_fanspeed_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buffer, size_t count)
+{
+       unsigned long value;
+       if (count > 31)
+               return -EINVAL;
+
+       value = simple_strtoul(buffer, NULL, 10);
+       if (sony_pic_set_fanspeed(value))
+               return -EIO;
+
+       return count;
+}
+
+static ssize_t sony_pic_fanspeed_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       u8 value = 0;
+       if (sony_pic_get_fanspeed(&value))
+               return -EIO;
+
+       return snprintf(buffer, PAGE_SIZE, "%d\n", value);
+}
+
+#define SPIC_ATTR(_name, _mode)                                        \
+struct device_attribute spic_attr_##_name = __ATTR(_name,      \
+               _mode, sony_pic_## _name ##_show,               \
+               sony_pic_## _name ##_store)
+
+static SPIC_ATTR(bluetoothpower, 0644);
+static SPIC_ATTR(wwanpower, 0644);
+static SPIC_ATTR(fanspeed, 0644);
+
+static struct attribute *spic_attributes[] = {
+       &spic_attr_bluetoothpower.attr,
+       &spic_attr_wwanpower.attr,
+       &spic_attr_fanspeed.attr,
+       NULL
+};
+
+static struct attribute_group spic_attribute_group = {
+       .attrs = spic_attributes
+};
+
+/******** SONYPI compatibility **********/
+#ifdef CONFIG_SONY_LAPTOP_OLD
+
+/* battery / brightness / temperature  addresses */
+#define SONYPI_BAT_FLAGS       0x81
+#define SONYPI_LCD_LIGHT       0x96
+#define SONYPI_BAT1_PCTRM      0xa0
+#define SONYPI_BAT1_LEFT       0xa2
+#define SONYPI_BAT1_MAXRT      0xa4
+#define SONYPI_BAT2_PCTRM      0xa8
+#define SONYPI_BAT2_LEFT       0xaa
+#define SONYPI_BAT2_MAXRT      0xac
+#define SONYPI_BAT1_MAXTK      0xb0
+#define SONYPI_BAT1_FULL       0xb2
+#define SONYPI_BAT2_MAXTK      0xb8
+#define SONYPI_BAT2_FULL       0xba
+#define SONYPI_TEMP_STATUS     0xC1
+
+struct sonypi_compat_s {
+       struct fasync_struct    *fifo_async;
+       struct kfifo            *fifo;
+       spinlock_t              fifo_lock;
+       wait_queue_head_t       fifo_proc_list;
+       atomic_t                open_count;
+};
+static struct sonypi_compat_s sonypi_compat = {
+       .open_count = ATOMIC_INIT(0),
+};
+
+static int sonypi_misc_fasync(int fd, struct file *filp, int on)
+{
+       int retval;
+
+       retval = fasync_helper(fd, filp, on, &sonypi_compat.fifo_async);
+       if (retval < 0)
+               return retval;
+       return 0;
+}
+
+static int sonypi_misc_release(struct inode *inode, struct file *file)
+{
+       sonypi_misc_fasync(-1, file, 0);
+       atomic_dec(&sonypi_compat.open_count);
+       return 0;
+}
+
+static int sonypi_misc_open(struct inode *inode, struct file *file)
+{
+       /* Flush input queue on first open */
+       if (atomic_inc_return(&sonypi_compat.open_count) == 1)
+               kfifo_reset(sonypi_compat.fifo);
+       return 0;
+}
+
+static ssize_t sonypi_misc_read(struct file *file, char __user *buf,
+                               size_t count, loff_t *pos)
+{
+       ssize_t ret;
+       unsigned char c;
+
+       if ((kfifo_len(sonypi_compat.fifo) == 0) &&
+           (file->f_flags & O_NONBLOCK))
+               return -EAGAIN;
+
+       ret = wait_event_interruptible(sonypi_compat.fifo_proc_list,
+                                      kfifo_len(sonypi_compat.fifo) != 0);
+       if (ret)
+               return ret;
+
+       while (ret < count &&
+              (kfifo_get(sonypi_compat.fifo, &c, sizeof(c)) == sizeof(c))) {
+               if (put_user(c, buf++))
+                       return -EFAULT;
+               ret++;
+       }
+
+       if (ret > 0) {
+               struct inode *inode = file->f_path.dentry->d_inode;
+               inode->i_atime = current_fs_time(inode->i_sb);
+       }
+
+       return ret;
+}
+
+static unsigned int sonypi_misc_poll(struct file *file, poll_table *wait)
+{
+       poll_wait(file, &sonypi_compat.fifo_proc_list, wait);
+       if (kfifo_len(sonypi_compat.fifo))
+               return POLLIN | POLLRDNORM;
+       return 0;
+}
+
+static int ec_read16(u8 addr, u16 *value)
+{
+       u8 val_lb, val_hb;
+       if (ec_read(addr, &val_lb))
+               return -1;
+       if (ec_read(addr + 1, &val_hb))
+               return -1;
+       *value = val_lb | (val_hb << 8);
+       return 0;
+}
+
+static int sonypi_misc_ioctl(struct inode *ip, struct file *fp,
+                            unsigned int cmd, unsigned long arg)
+{
+       int ret = 0;
+       void __user *argp = (void __user *)arg;
+       u8 val8;
+       u16 val16;
+       int value;
+
+       mutex_lock(&spic_dev.lock);
+       switch (cmd) {
+       case SONYPI_IOCGBRT:
+               if (sony_backlight_device == NULL) {
+                       ret = -EIO;
+                       break;
+               }
+               if (acpi_callgetfunc(sony_nc_acpi_handle, "GBRT", &value)) {
+                       ret = -EIO;
+                       break;
+               }
+               val8 = ((value & 0xff) - 1) << 5;
+               if (copy_to_user(argp, &val8, sizeof(val8)))
+                               ret = -EFAULT;
+               break;
+       case SONYPI_IOCSBRT:
+               if (sony_backlight_device == NULL) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_from_user(&val8, argp, sizeof(val8))) {
+                       ret = -EFAULT;
+                       break;
+               }
+               if (acpi_callsetfunc(sony_nc_acpi_handle, "SBRT",
+                               (val8 >> 5) + 1, NULL)) {
+                       ret = -EIO;
+                       break;
+               }
+               /* sync the backlight device status */
+               sony_backlight_device->props.brightness =
+                   sony_backlight_get_brightness(sony_backlight_device);
+               break;
+       case SONYPI_IOCGBAT1CAP:
+               if (ec_read16(SONYPI_BAT1_FULL, &val16)) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_to_user(argp, &val16, sizeof(val16)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCGBAT1REM:
+               if (ec_read16(SONYPI_BAT1_LEFT, &val16)) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_to_user(argp, &val16, sizeof(val16)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCGBAT2CAP:
+               if (ec_read16(SONYPI_BAT2_FULL, &val16)) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_to_user(argp, &val16, sizeof(val16)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCGBAT2REM:
+               if (ec_read16(SONYPI_BAT2_LEFT, &val16)) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_to_user(argp, &val16, sizeof(val16)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCGBATFLAGS:
+               if (ec_read(SONYPI_BAT_FLAGS, &val8)) {
+                       ret = -EIO;
+                       break;
+               }
+               val8 &= 0x07;
+               if (copy_to_user(argp, &val8, sizeof(val8)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCGBLUE:
+               val8 = spic_dev.bluetooth_power;
+               if (copy_to_user(argp, &val8, sizeof(val8)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCSBLUE:
+               if (copy_from_user(&val8, argp, sizeof(val8))) {
+                       ret = -EFAULT;
+                       break;
+               }
+               __sony_pic_set_bluetoothpower(val8);
+               break;
+       /* FAN Controls */
+       case SONYPI_IOCGFAN:
+               if (sony_pic_get_fanspeed(&val8)) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_to_user(argp, &val8, sizeof(val8)))
+                       ret = -EFAULT;
+               break;
+       case SONYPI_IOCSFAN:
+               if (copy_from_user(&val8, argp, sizeof(val8))) {
+                       ret = -EFAULT;
+                       break;
+               }
+               if (sony_pic_set_fanspeed(val8))
+                       ret = -EIO;
+               break;
+       /* GET Temperature (useful under APM) */
+       case SONYPI_IOCGTEMP:
+               if (ec_read(SONYPI_TEMP_STATUS, &val8)) {
+                       ret = -EIO;
+                       break;
+               }
+               if (copy_to_user(argp, &val8, sizeof(val8)))
+                       ret = -EFAULT;
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       mutex_unlock(&spic_dev.lock);
+       return ret;
+}
+
+static const struct file_operations sonypi_misc_fops = {
+       .owner          = THIS_MODULE,
+       .read           = sonypi_misc_read,
+       .poll           = sonypi_misc_poll,
+       .open           = sonypi_misc_open,
+       .release        = sonypi_misc_release,
+       .fasync         = sonypi_misc_fasync,
+       .ioctl          = sonypi_misc_ioctl,
+};
+
+static struct miscdevice sonypi_misc_device = {
+       .minor          = MISC_DYNAMIC_MINOR,
+       .name           = "sonypi",
+       .fops           = &sonypi_misc_fops,
+};
+
+static void sonypi_compat_report_event(u8 event)
+{
+       kfifo_put(sonypi_compat.fifo, (unsigned char *)&event, sizeof(event));
+       kill_fasync(&sonypi_compat.fifo_async, SIGIO, POLL_IN);
+       wake_up_interruptible(&sonypi_compat.fifo_proc_list);
+}
+
+static int sonypi_compat_init(void)
+{
+       int error;
+
+       spin_lock_init(&sonypi_compat.fifo_lock);
+       sonypi_compat.fifo = kfifo_alloc(SONY_LAPTOP_BUF_SIZE, GFP_KERNEL,
+                                        &sonypi_compat.fifo_lock);
+       if (IS_ERR(sonypi_compat.fifo)) {
+               printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n");
+               return PTR_ERR(sonypi_compat.fifo);
+       }
+
+       init_waitqueue_head(&sonypi_compat.fifo_proc_list);
+
+       if (minor != -1)
+               sonypi_misc_device.minor = minor;
+       error = misc_register(&sonypi_misc_device);
+       if (error) {
+               printk(KERN_ERR DRV_PFX "misc_register failed\n");
+               goto err_free_kfifo;
+       }
+       if (minor == -1)
+               printk(KERN_INFO DRV_PFX "device allocated minor is %d\n",
+                      sonypi_misc_device.minor);
+
+       return 0;
+
+err_free_kfifo:
+       kfifo_free(sonypi_compat.fifo);
+       return error;
+}
+
+static void sonypi_compat_exit(void)
+{
+       misc_deregister(&sonypi_misc_device);
+       kfifo_free(sonypi_compat.fifo);
+}
+#else
+static int sonypi_compat_init(void) { return 0; }
+static void sonypi_compat_exit(void) { }
+static void sonypi_compat_report_event(u8 event) { }
+#endif /* CONFIG_SONY_LAPTOP_OLD */
+
+/*
+ * ACPI callbacks
+ */
+static acpi_status
+sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
+{
+       u32 i;
+       struct sony_pic_dev *dev = (struct sony_pic_dev *)context;
+
+       switch (resource->type) {
+       case ACPI_RESOURCE_TYPE_START_DEPENDENT:
+       case ACPI_RESOURCE_TYPE_END_DEPENDENT:
+               return AE_OK;
+
+       case ACPI_RESOURCE_TYPE_IRQ:
+               {
+                       struct acpi_resource_irq *p = &resource->data.irq;
+                       struct sony_pic_irq *interrupt = NULL;
+                       if (!p || !p->interrupt_count) {
+                               /*
+                                * IRQ descriptors may have no IRQ# bits set,
+                                * particularly those those w/ _STA disabled
+                                */
+                               dprintk("Blank IRQ resource\n");
+                               return AE_OK;
+                       }
+                       for (i = 0; i < p->interrupt_count; i++) {
+                               if (!p->interrupts[i]) {
+                                       printk(KERN_WARNING DRV_PFX
+                                                       "Invalid IRQ %d\n",
+                                                       p->interrupts[i]);
+                                       continue;
+                               }
+                               interrupt = kzalloc(sizeof(*interrupt),
+                                               GFP_KERNEL);
+                               if (!interrupt)
+                                       return AE_ERROR;
+
+                               list_add_tail(&interrupt->list, &dev->interrupts);
+                               interrupt->irq.triggering = p->triggering;
+                               interrupt->irq.polarity = p->polarity;
+                               interrupt->irq.sharable = p->sharable;
+                               interrupt->irq.interrupt_count = 1;
+                               interrupt->irq.interrupts[0] = p->interrupts[i];
+                       }
+                       return AE_OK;
+               }
+       case ACPI_RESOURCE_TYPE_IO:
+               {
+                       struct acpi_resource_io *io = &resource->data.io;
+                       struct sony_pic_ioport *ioport = NULL;
+                       if (!io) {
+                               dprintk("Blank IO resource\n");
+                               return AE_OK;
+                       }
+
+                       ioport = kzalloc(sizeof(*ioport), GFP_KERNEL);
+                       if (!ioport)
+                               return AE_ERROR;
+
+                       list_add_tail(&ioport->list, &dev->ioports);
+                       memcpy(&ioport->io, io, sizeof(*io));
+                       return AE_OK;
+               }
+       default:
+               dprintk("Resource %d isn't an IRQ nor an IO port\n",
+                               resource->type);
+
+       case ACPI_RESOURCE_TYPE_END_TAG:
+               return AE_OK;
+       }
+       return AE_CTRL_TERMINATE;
+}
+
+static int sony_pic_possible_resources(struct acpi_device *device)
+{
+       int result = 0;
+       acpi_status status = AE_OK;
+
+       if (!device)
+               return -EINVAL;
+
+       /* get device status */
+       /* see acpi_pci_link_get_current acpi_pci_link_get_possible */
+       dprintk("Evaluating _STA\n");
+       result = acpi_bus_get_status(device);
+       if (result) {
+               printk(KERN_WARNING DRV_PFX "Unable to read status\n");
+               goto end;
+       }
+
+       if (!device->status.enabled)
+               dprintk("Device disabled\n");
+       else
+               dprintk("Device enabled\n");
+
+       /*
+        * Query and parse 'method'
+        */
+       dprintk("Evaluating %s\n", METHOD_NAME__PRS);
+       status = acpi_walk_resources(device->handle, METHOD_NAME__PRS,
+                       sony_pic_read_possible_resource, &spic_dev);
+       if (ACPI_FAILURE(status)) {
+               printk(KERN_WARNING DRV_PFX
+                               "Failure evaluating %s\n",
+                               METHOD_NAME__PRS);
+               result = -ENODEV;
+       }
+end:
+       return result;
+}
+
+/*
+ *  Disable the spic device by calling its _DIS method
+ */
+static int sony_pic_disable(struct acpi_device *device)
+{
+       if (ACPI_FAILURE(acpi_evaluate_object(device->handle, "_DIS", 0, NULL)))
+               return -ENXIO;
+
+       dprintk("Device disabled\n");
+       return 0;
+}
+
+
+/*
+ *  Based on drivers/acpi/pci_link.c:acpi_pci_link_set
+ *
+ *  Call _SRS to set current resources
+ */
+static int sony_pic_enable(struct acpi_device *device,
+               struct sony_pic_ioport *ioport, struct sony_pic_irq *irq)
+{
+       acpi_status status;
+       int result = 0;
+       struct {
+               struct acpi_resource io_res;
+               struct acpi_resource irq_res;
+               struct acpi_resource end;
+       } *resource;
+       struct acpi_buffer buffer = { 0, NULL };
+
+       if (!ioport || !irq)
+               return -EINVAL;
+
+       /* init acpi_buffer */
+       resource = kzalloc(sizeof(*resource) + 1, GFP_KERNEL);
+       if (!resource)
+               return -ENOMEM;
+
+       buffer.length = sizeof(*resource) + 1;
+       buffer.pointer = resource;
+
+       /* setup io resource */
+       resource->io_res.type = ACPI_RESOURCE_TYPE_IO;
+       resource->io_res.length = sizeof(struct acpi_resource);
+       memcpy(&resource->io_res.data.io, &ioport->io,
+                       sizeof(struct acpi_resource_io));
+
+       /* setup irq resource */
+       resource->irq_res.type = ACPI_RESOURCE_TYPE_IRQ;
+       resource->irq_res.length = sizeof(struct acpi_resource);
+       memcpy(&resource->irq_res.data.irq, &irq->irq,
+                       sizeof(struct acpi_resource_irq));
+       /* we requested a shared irq */
+       resource->irq_res.data.irq.sharable = ACPI_SHARED;
+
+       resource->end.type = ACPI_RESOURCE_TYPE_END_TAG;
+
+       /* Attempt to set the resource */
+       dprintk("Evaluating _SRS\n");
+       status = acpi_set_current_resources(device->handle, &buffer);
+
+       /* check for total failure */
+       if (ACPI_FAILURE(status)) {
+               printk(KERN_ERR DRV_PFX "Error evaluating _SRS");
+               result = -ENODEV;
+               goto end;
+       }
+
+       /* Necessary device initializations calls (from sonypi) */
+       sony_pic_call1(0x82);
+       sony_pic_call2(0x81, 0xff);
+       sony_pic_call1(compat ? 0x92 : 0x82);
+
+end:
+       kfree(resource);
+       return result;
+}
+
+/*****************
+ *
+ * ISR: some event is available
+ *
+ *****************/
+static irqreturn_t sony_pic_irq(int irq, void *dev_id)
+{
+       int i, j;
+       u32 port_val = 0;
+       u8 ev = 0;
+       u8 data_mask = 0;
+       u8 device_event = 0;
+
+       struct sony_pic_dev *dev = (struct sony_pic_dev *) dev_id;
+
+       acpi_os_read_port(dev->cur_ioport->io.minimum, &port_val,
+                       dev->cur_ioport->io.address_length);
+       ev = port_val & SONY_PIC_EV_MASK;
+       data_mask = 0xff & (port_val >> (dev->cur_ioport->io.address_length - 8));
+
+       dprintk("event (0x%.8x [%.2x] [%.2x]) at port 0x%.4x\n",
+                       port_val, ev, data_mask, dev->cur_ioport->io.minimum);
+
+       if (ev == 0x00 || ev == 0xff)
+               return IRQ_HANDLED;
+
+       for (i = 0; sony_pic_eventtypes[i].model; i++) {
+
+               if (spic_dev.model != sony_pic_eventtypes[i].model)
+                       continue;
+
+               if ((data_mask & sony_pic_eventtypes[i].data) !=
+                   sony_pic_eventtypes[i].data)
+                       continue;
+
+               if (!(mask & sony_pic_eventtypes[i].mask))
+                       continue;
+
+               for (j = 0; sony_pic_eventtypes[i].events[j].event; j++) {
+                       if (ev == sony_pic_eventtypes[i].events[j].data) {
+                               device_event =
+                                       sony_pic_eventtypes[i].events[j].event;
+                               goto found;
+                       }
+               }
+       }
+       return IRQ_HANDLED;
+
+found:
+       sony_laptop_report_input_event(device_event);
+       acpi_bus_generate_event(spic_dev.acpi_dev, 1, device_event);
+       sonypi_compat_report_event(device_event);
+
+       return IRQ_HANDLED;
+}
+
+/*****************
+ *
+ *  ACPI driver
+ *
+ *****************/
+static int sony_pic_remove(struct acpi_device *device, int type)
+{
+       struct sony_pic_ioport *io, *tmp_io;
+       struct sony_pic_irq *irq, *tmp_irq;
+
+       sonypi_compat_exit();
+
+       if (sony_pic_disable(device)) {
+               printk(KERN_ERR DRV_PFX "Couldn't disable device.\n");
+               return -ENXIO;
+       }
+
+       free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);
+       release_region(spic_dev.cur_ioport->io.minimum,
+                       spic_dev.cur_ioport->io.address_length);
+
+       sony_laptop_remove_input();
+
+       /* pf attrs */
+       sysfs_remove_group(&sony_pf_device->dev.kobj, &spic_attribute_group);
+       sony_pf_remove();
+
+       list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
+               list_del(&io->list);
+               kfree(io);
+       }
+       list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) {
+               list_del(&irq->list);
+               kfree(irq);
+       }
+       spic_dev.cur_ioport = NULL;
+       spic_dev.cur_irq = NULL;
+
+       dprintk(SONY_PIC_DRIVER_NAME " removed.\n");
+       return 0;
+}
+
+static int sony_pic_add(struct acpi_device *device)
+{
+       int result;
+       struct sony_pic_ioport *io, *tmp_io;
+       struct sony_pic_irq *irq, *tmp_irq;
+
+       printk(KERN_INFO DRV_PFX "%s v%s.\n",
+               SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION);
+
+       spic_dev.acpi_dev = device;
+       strcpy(acpi_device_class(device), "sony/hotkey");
+       spic_dev.model = sony_pic_detect_device_type();
+       mutex_init(&spic_dev.lock);
+
+       /* read _PRS resources */
+       result = sony_pic_possible_resources(device);
+       if (result) {
+               printk(KERN_ERR DRV_PFX
+                               "Unabe to read possible resources.\n");
+               goto err_free_resources;
+       }
+
+       /* setup input devices and helper fifo */
+       result = sony_laptop_setup_input();
+       if (result) {
+               printk(KERN_ERR DRV_PFX
+                               "Unabe to create input devices.\n");
+               goto err_free_resources;
+       }
+
+       /* request io port */
+       list_for_each_entry(io, &spic_dev.ioports, list) {
+               if (request_region(io->io.minimum, io->io.address_length,
+                                       "Sony Programable I/O Device")) {
+                       dprintk("I/O port: 0x%.4x (0x%.4x) + 0x%.2x\n",
+                                       io->io.minimum, io->io.maximum,
+                                       io->io.address_length);
+                       spic_dev.cur_ioport = io;
+                       break;
+               }
+       }
+       if (!spic_dev.cur_ioport) {
+               printk(KERN_ERR DRV_PFX "Failed to request_region.\n");
+               result = -ENODEV;
+               goto err_remove_input;
+       }
+
+       /* request IRQ */
+       list_for_each_entry(irq, &spic_dev.interrupts, list) {
+               if (!request_irq(irq->irq.interrupts[0], sony_pic_irq,
+                                       IRQF_SHARED, "sony-laptop", &spic_dev)) {
+                       dprintk("IRQ: %d - triggering: %d - "
+                                       "polarity: %d - shr: %d\n",
+                                       irq->irq.interrupts[0],
+                                       irq->irq.triggering,
+                                       irq->irq.polarity,
+                                       irq->irq.sharable);
+                       spic_dev.cur_irq = irq;
+                       break;
+               }
+       }
+       if (!spic_dev.cur_irq) {
+               printk(KERN_ERR DRV_PFX "Failed to request_irq.\n");
+               result = -ENODEV;
+               goto err_release_region;
+       }
+
+       /* set resource status _SRS */
+       result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq);
+       if (result) {
+               printk(KERN_ERR DRV_PFX "Couldn't enable device.\n");
+               goto err_free_irq;
+       }
+
+       spic_dev.bluetooth_power = -1;
+       /* create device attributes */
+       result = sony_pf_add();
+       if (result)
+               goto err_disable_device;
+
+       result = sysfs_create_group(&sony_pf_device->dev.kobj, &spic_attribute_group);
+       if (result)
+               goto err_remove_pf;
+
+       if (sonypi_compat_init())
+               goto err_remove_pf;
+
+       return 0;
+
+err_remove_pf:
+       sony_pf_remove();
+
+err_disable_device:
+       sony_pic_disable(device);
+
+err_free_irq:
+       free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);
+
+err_release_region:
+       release_region(spic_dev.cur_ioport->io.minimum,
+                       spic_dev.cur_ioport->io.address_length);
+
+err_remove_input:
+       sony_laptop_remove_input();
+
+err_free_resources:
+       list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
+               list_del(&io->list);
+               kfree(io);
+       }
+       list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) {
+               list_del(&irq->list);
+               kfree(irq);
+       }
+       spic_dev.cur_ioport = NULL;
+       spic_dev.cur_irq = NULL;
+
+       return result;
+}
+
+static int sony_pic_suspend(struct acpi_device *device, pm_message_t state)
+{
+       if (sony_pic_disable(device))
+               return -ENXIO;
+       return 0;
+}
+
+static int sony_pic_resume(struct acpi_device *device)
+{
+       sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq);
+       return 0;
+}
+
+static struct acpi_driver sony_pic_driver = {
+       .name = SONY_PIC_DRIVER_NAME,
+       .class = SONY_PIC_CLASS,
+       .ids = SONY_PIC_HID,
+       .owner = THIS_MODULE,
+       .ops = {
+               .add = sony_pic_add,
+               .remove = sony_pic_remove,
+               .suspend = sony_pic_suspend,
+               .resume = sony_pic_resume,
+               },
+};
+
+static struct dmi_system_id __initdata sonypi_dmi_table[] = {
+       {
+               .ident = "Sony Vaio",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "PCG-"),
+               },
+       },
+       {
+               .ident = "Sony Vaio",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "VGN-"),
+               },
+       },
+       { }
+};
+
+static int __init sony_laptop_init(void)
+{
+       int result;
+
+       if (!no_spic && dmi_check_system(sonypi_dmi_table)) {
+               result = acpi_bus_register_driver(&sony_pic_driver);
+               if (result) {
+                       printk(KERN_ERR DRV_PFX
+                                       "Unable to register SPIC driver.");
+                       goto out;
+               }
+       }
+
+       result = acpi_bus_register_driver(&sony_nc_driver);
+       if (result) {
+               printk(KERN_ERR DRV_PFX "Unable to register SNC driver.");
+               goto out_unregister_pic;
+       }
+
+       return 0;
+
+out_unregister_pic:
+       if (!no_spic)
+               acpi_bus_unregister_driver(&sony_pic_driver);
+out:
+       return result;
 }
 
-static void __exit sony_acpi_exit(void)
+static void __exit sony_laptop_exit(void)
 {
-       acpi_bus_unregister_driver(&sony_acpi_driver);
+       acpi_bus_unregister_driver(&sony_nc_driver);
+       if (!no_spic)
+               acpi_bus_unregister_driver(&sony_pic_driver);
 }
 
-module_init(sony_acpi_init);
-module_exit(sony_acpi_exit);
+module_init(sony_laptop_init);
+module_exit(sony_laptop_exit);
diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c
new file mode 100644 (file)
index 0000000..6c36a55
--- /dev/null
@@ -0,0 +1,4312 @@
+/*
+ *  thinkpad_acpi.c - ThinkPad ACPI Extras
+ *
+ *
+ *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
+ *  Copyright (C) 2006-2007 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#define IBM_VERSION "0.14"
+#define TPACPI_SYSFS_VERSION 0x000100
+
+/*
+ *  Changelog:
+ *  2007-03-27  0.14   renamed to thinkpad_acpi and moved to
+ *                     drivers/misc.
+ *
+ *  2006-11-22 0.13    new maintainer
+ *                     changelog now lives in git commit history, and will
+ *                     not be updated further in-file.
+ *
+ *  2005-08-17  0.12   fix compilation on 2.6.13-rc kernels
+ *  2005-03-17 0.11    support for 600e, 770x
+ *                         thanks to Jamie Lentin <lentinj@dial.pipex.com>
+ *                     support for 770e, G41
+ *                     G40 and G41 don't have a thinklight
+ *                     temperatures no longer experimental
+ *                     experimental brightness control
+ *                     experimental volume control
+ *                     experimental fan enable/disable
+ *  2005-01-16 0.10    fix module loading on R30, R31
+ *  2005-01-16 0.9     support for 570, R30, R31
+ *                     ultrabay support on A22p, A3x
+ *                     limit arg for cmos, led, beep, drop experimental status
+ *                     more capable led control on A21e, A22p, T20-22, X20
+ *                     experimental temperatures and fan speed
+ *                     experimental embedded controller register dump
+ *                     mark more functions as __init, drop incorrect __exit
+ *                     use MODULE_VERSION
+ *                         thanks to Henrik Brix Andersen <brix@gentoo.org>
+ *                     fix parameter passing on module loading
+ *                         thanks to Rusty Russell <rusty@rustcorp.com.au>
+ *                         thanks to Jim Radford <radford@blackbean.org>
+ *  2004-11-08 0.8     fix init error case, don't return from a macro
+ *                         thanks to Chris Wright <chrisw@osdl.org>
+ *  2004-10-23 0.7     fix module loading on A21e, A22p, T20, T21, X20
+ *                     fix led control on A21e
+ *  2004-10-19 0.6     use acpi_bus_register_driver() to claim HKEY device
+ *  2004-10-18 0.5     thinklight support on A21e, G40, R32, T20, T21, X20
+ *                     proc file format changed
+ *                     video_switch command
+ *                     experimental cmos control
+ *                     experimental led control
+ *                     experimental acpi sounds
+ *  2004-09-16 0.4     support for module parameters
+ *                     hotkey mask can be prefixed by 0x
+ *                     video output switching
+ *                     video expansion control
+ *                     ultrabay eject support
+ *                     removed lcd brightness/on/off control, didn't work
+ *  2004-08-17 0.3     support for R40
+ *                     lcd off, brightness control
+ *                     thinklight on/off
+ *  2004-08-14 0.2     support for T series, X20
+ *                     bluetooth enable/disable
+ *                     hotkey events disabled by default
+ *                     removed fan control, currently useless
+ *  2004-08-09 0.1     initial release, support for X series
+ */
+
+#include "thinkpad_acpi.h"
+
+MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
+MODULE_DESCRIPTION(IBM_DESC);
+MODULE_VERSION(IBM_VERSION);
+MODULE_LICENSE("GPL");
+
+/* Please remove this in year 2009 */
+MODULE_ALIAS("ibm_acpi");
+
+#define __unused __attribute__ ((unused))
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * ACPI Helpers and device model
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * ACPI basic handles
+ */
+
+static acpi_handle root_handle = NULL;
+
+#define IBM_HANDLE(object, parent, paths...)                   \
+       static acpi_handle  object##_handle;                    \
+       static acpi_handle *object##_parent = &parent##_handle; \
+       static char        *object##_path;                      \
+       static char        *object##_paths[] = { paths }
+
+IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0",     /* 240, 240x */
+          "\\_SB.PCI.ISA.EC",  /* 570 */
+          "\\_SB.PCI0.ISA0.EC0",       /* 600e/x, 770e, 770x */
+          "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */
+          "\\_SB.PCI0.AD4S.EC0",       /* i1400, R30 */
+          "\\_SB.PCI0.ICH3.EC0",       /* R31 */
+          "\\_SB.PCI0.LPC.EC", /* all others */
+          );
+
+IBM_HANDLE(ecrd, ec, "ECRD");  /* 570 */
+IBM_HANDLE(ecwr, ec, "ECWR");  /* 570 */
+
+
+/*************************************************************************
+ * Misc ACPI handles
+ */
+
+IBM_HANDLE(cmos, root, "\\UCMS",       /* R50, R50e, R50p, R51, T4x, X31, X40 */
+          "\\CMOS",            /* A3x, G4x, R32, T23, T30, X22-24, X30 */
+          "\\CMS",             /* R40, R40e */
+          );                   /* all others */
+
+IBM_HANDLE(hkey, ec, "\\_SB.HKEY",     /* 600e/x, 770e, 770x */
+          "^HKEY",             /* R30, R31 */
+          "HKEY",              /* all others */
+          );                   /* 570 */
+
+
+/*************************************************************************
+ * ACPI helpers
+ */
+
+static int acpi_evalf(acpi_handle handle,
+                     void *res, char *method, char *fmt, ...)
+{
+       char *fmt0 = fmt;
+       struct acpi_object_list params;
+       union acpi_object in_objs[IBM_MAX_ACPI_ARGS];
+       struct acpi_buffer result, *resultp;
+       union acpi_object out_obj;
+       acpi_status status;
+       va_list ap;
+       char res_type;
+       int success;
+       int quiet;
+
+       if (!*fmt) {
+               printk(IBM_ERR "acpi_evalf() called with empty format\n");
+               return 0;
+       }
+
+       if (*fmt == 'q') {
+               quiet = 1;
+               fmt++;
+       } else
+               quiet = 0;
+
+       res_type = *(fmt++);
+
+       params.count = 0;
+       params.pointer = &in_objs[0];
+
+       va_start(ap, fmt);
+       while (*fmt) {
+               char c = *(fmt++);
+               switch (c) {
+               case 'd':       /* int */
+                       in_objs[params.count].integer.value = va_arg(ap, int);
+                       in_objs[params.count++].type = ACPI_TYPE_INTEGER;
+                       break;
+                       /* add more types as needed */
+               default:
+                       printk(IBM_ERR "acpi_evalf() called "
+                              "with invalid format character '%c'\n", c);
+                       return 0;
+               }
+       }
+       va_end(ap);
+
+       if (res_type != 'v') {
+               result.length = sizeof(out_obj);
+               result.pointer = &out_obj;
+               resultp = &result;
+       } else
+               resultp = NULL;
+
+       status = acpi_evaluate_object(handle, method, &params, resultp);
+
+       switch (res_type) {
+       case 'd':               /* int */
+               if (res)
+                       *(int *)res = out_obj.integer.value;
+               success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
+               break;
+       case 'v':               /* void */
+               success = status == AE_OK;
+               break;
+               /* add more types as needed */
+       default:
+               printk(IBM_ERR "acpi_evalf() called "
+                      "with invalid format character '%c'\n", res_type);
+               return 0;
+       }
+
+       if (!success && !quiet)
+               printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
+                      method, fmt0, status);
+
+       return success;
+}
+
+static void __unused acpi_print_int(acpi_handle handle, char *method)
+{
+       int i;
+
+       if (acpi_evalf(handle, &i, method, "d"))
+               printk(IBM_INFO "%s = 0x%x\n", method, i);
+       else
+               printk(IBM_ERR "error calling %s\n", method);
+}
+
+static int acpi_ec_read(int i, u8 * p)
+{
+       int v;
+
+       if (ecrd_handle) {
+               if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
+                       return 0;
+               *p = v;
+       } else {
+               if (ec_read(i, p) < 0)
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int acpi_ec_write(int i, u8 v)
+{
+       if (ecwr_handle) {
+               if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
+                       return 0;
+       } else {
+               if (ec_write(i, v) < 0)
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int _sta(acpi_handle handle)
+{
+       int status;
+
+       if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
+               status = 0;
+
+       return status;
+}
+
+static int issue_thinkpad_cmos_command(int cmos_cmd)
+{
+       if (!cmos_handle)
+               return -ENXIO;
+
+       if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd))
+               return -EIO;
+
+       return 0;
+}
+
+/*************************************************************************
+ * ACPI device model
+ */
+
+static void drv_acpi_handle_init(char *name,
+                          acpi_handle *handle, acpi_handle parent,
+                          char **paths, int num_paths, char **path)
+{
+       int i;
+       acpi_status status;
+
+       vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
+               name);
+
+       for (i = 0; i < num_paths; i++) {
+               status = acpi_get_handle(parent, paths[i], handle);
+               if (ACPI_SUCCESS(status)) {
+                       *path = paths[i];
+                       dbg_printk(TPACPI_DBG_INIT,
+                                  "Found ACPI handle %s for %s\n",
+                                  *path, name);
+                       return;
+               }
+       }
+
+       vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
+                   name);
+       *handle = NULL;
+}
+
+static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+       struct ibm_struct *ibm = data;
+
+       if (!ibm || !ibm->acpi || !ibm->acpi->notify)
+               return;
+
+       ibm->acpi->notify(ibm, event);
+}
+
+static int __init setup_acpi_notify(struct ibm_struct *ibm)
+{
+       acpi_status status;
+       int rc;
+
+       BUG_ON(!ibm->acpi);
+
+       if (!*ibm->acpi->handle)
+               return 0;
+
+       vdbg_printk(TPACPI_DBG_INIT,
+               "setting up ACPI notify for %s\n", ibm->name);
+
+       rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
+       if (rc < 0) {
+               printk(IBM_ERR "acpi_bus_get_device(%s) failed: %d\n",
+                       ibm->name, rc);
+               return -ENODEV;
+       }
+
+       acpi_driver_data(ibm->acpi->device) = ibm;
+       sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
+               IBM_ACPI_EVENT_PREFIX,
+               ibm->name);
+
+       status = acpi_install_notify_handler(*ibm->acpi->handle,
+                       ibm->acpi->type, dispatch_acpi_notify, ibm);
+       if (ACPI_FAILURE(status)) {
+               if (status == AE_ALREADY_EXISTS) {
+                       printk(IBM_NOTICE "another device driver is already handling %s events\n",
+                               ibm->name);
+               } else {
+                       printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n",
+                               ibm->name, status);
+               }
+               return -ENODEV;
+       }
+       ibm->flags.acpi_notify_installed = 1;
+       return 0;
+}
+
+static int __init tpacpi_device_add(struct acpi_device *device)
+{
+       return 0;
+}
+
+static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
+{
+       int rc;
+
+       dbg_printk(TPACPI_DBG_INIT,
+               "registering %s as an ACPI driver\n", ibm->name);
+
+       BUG_ON(!ibm->acpi);
+
+       ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
+       if (!ibm->acpi->driver) {
+               printk(IBM_ERR "kzalloc(ibm->driver) failed\n");
+               return -ENOMEM;
+       }
+
+       sprintf(ibm->acpi->driver->name, "%s_%s", IBM_NAME, ibm->name);
+       ibm->acpi->driver->ids = ibm->acpi->hid;
+       ibm->acpi->driver->ops.add = &tpacpi_device_add;
+
+       rc = acpi_bus_register_driver(ibm->acpi->driver);
+       if (rc < 0) {
+               printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n",
+                      ibm->acpi->hid, rc);
+               kfree(ibm->acpi->driver);
+               ibm->acpi->driver = NULL;
+       } else if (!rc)
+               ibm->flags.acpi_driver_registered = 1;
+
+       return rc;
+}
+
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Procfs Helpers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+static int dispatch_procfs_read(char *page, char **start, off_t off,
+                       int count, int *eof, void *data)
+{
+       struct ibm_struct *ibm = data;
+       int len;
+
+       if (!ibm || !ibm->read)
+               return -EINVAL;
+
+       len = ibm->read(page);
+       if (len < 0)
+               return len;
+
+       if (len <= off + count)
+               *eof = 1;
+       *start = page + off;
+       len -= off;
+       if (len > count)
+               len = count;
+       if (len < 0)
+               len = 0;
+
+       return len;
+}
+
+static int dispatch_procfs_write(struct file *file,
+                       const char __user * userbuf,
+                       unsigned long count, void *data)
+{
+       struct ibm_struct *ibm = data;
+       char *kernbuf;
+       int ret;
+
+       if (!ibm || !ibm->write)
+               return -EINVAL;
+
+       kernbuf = kmalloc(count + 2, GFP_KERNEL);
+       if (!kernbuf)
+               return -ENOMEM;
+
+       if (copy_from_user(kernbuf, userbuf, count)) {
+               kfree(kernbuf);
+               return -EFAULT;
+       }
+
+       kernbuf[count] = 0;
+       strcat(kernbuf, ",");
+       ret = ibm->write(kernbuf);
+       if (ret == 0)
+               ret = count;
+
+       kfree(kernbuf);
+
+       return ret;
+}
+
+static char *next_cmd(char **cmds)
+{
+       char *start = *cmds;
+       char *end;
+
+       while ((end = strchr(start, ',')) && end == start)
+               start = end + 1;
+
+       if (!end)
+               return NULL;
+
+       *end = 0;
+       *cmds = end + 1;
+       return start;
+}
+
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Device model: hwmon and platform
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+static struct platform_device *tpacpi_pdev = NULL;
+static struct class_device *tpacpi_hwmon = NULL;
+
+static struct platform_driver tpacpi_pdriver = {
+       .driver = {
+               .name = IBM_DRVR_NAME,
+               .owner = THIS_MODULE,
+       },
+};
+
+
+/*************************************************************************
+ * thinkpad-acpi driver attributes
+ */
+
+/* interface_version --------------------------------------------------- */
+static ssize_t tpacpi_driver_interface_version_show(
+                               struct device_driver *drv,
+                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
+}
+
+static DRIVER_ATTR(interface_version, S_IRUGO,
+               tpacpi_driver_interface_version_show, NULL);
+
+/* debug_level --------------------------------------------------------- */
+static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
+                                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
+}
+
+static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
+                                               const char *buf, size_t count)
+{
+       unsigned long t;
+
+       if (parse_strtoul(buf, 0xffff, &t))
+               return -EINVAL;
+
+       dbg_level = t;
+
+       return count;
+}
+
+static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
+               tpacpi_driver_debug_show, tpacpi_driver_debug_store);
+
+/* version ------------------------------------------------------------- */
+static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
+                                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION);
+}
+
+static DRIVER_ATTR(version, S_IRUGO,
+               tpacpi_driver_version_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+static struct driver_attribute* tpacpi_driver_attributes[] = {
+       &driver_attr_debug_level, &driver_attr_version,
+       &driver_attr_interface_version,
+};
+
+static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
+{
+       int i, res;
+
+       i = 0;
+       res = 0;
+       while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
+               res = driver_create_file(drv, tpacpi_driver_attributes[i]);
+               i++;
+       }
+
+       return res;
+}
+
+static void tpacpi_remove_driver_attributes(struct device_driver *drv)
+{
+       int i;
+
+       for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
+               driver_remove_file(drv, tpacpi_driver_attributes[i]);
+}
+
+/*************************************************************************
+ * sysfs support helpers
+ */
+
+struct attribute_set_obj {
+       struct attribute_set s;
+       struct attribute *a;
+} __attribute__((packed));
+
+static struct attribute_set *create_attr_set(unsigned int max_members,
+                                               const char* name)
+{
+       struct attribute_set_obj *sobj;
+
+       if (max_members == 0)
+               return NULL;
+
+       /* Allocates space for implicit NULL at the end too */
+       sobj = kzalloc(sizeof(struct attribute_set_obj) +
+                   max_members * sizeof(struct attribute *),
+                   GFP_KERNEL);
+       if (!sobj)
+               return NULL;
+       sobj->s.max_members = max_members;
+       sobj->s.group.attrs = &sobj->a;
+       sobj->s.group.name = name;
+
+       return &sobj->s;
+}
+
+/* not multi-threaded safe, use it in a single thread per set */
+static int add_to_attr_set(struct attribute_set* s, struct attribute *attr)
+{
+       if (!s || !attr)
+               return -EINVAL;
+
+       if (s->members >= s->max_members)
+               return -ENOMEM;
+
+       s->group.attrs[s->members] = attr;
+       s->members++;
+
+       return 0;
+}
+
+static int add_many_to_attr_set(struct attribute_set* s,
+                       struct attribute **attr,
+                       unsigned int count)
+{
+       int i, res;
+
+       for (i = 0; i < count; i++) {
+               res = add_to_attr_set(s, attr[i]);
+               if (res)
+                       return res;
+       }
+
+       return 0;
+}
+
+static void delete_attr_set(struct attribute_set* s, struct kobject *kobj)
+{
+       sysfs_remove_group(kobj, &s->group);
+       destroy_attr_set(s);
+}
+
+static int parse_strtoul(const char *buf,
+               unsigned long max, unsigned long *value)
+{
+       char *endp;
+
+       *value = simple_strtoul(buf, &endp, 0);
+       while (*endp && isspace(*endp))
+               endp++;
+       if (*endp || *value > max)
+               return -EINVAL;
+
+       return 0;
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Subdrivers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * thinkpad-acpi init subdriver
+ */
+
+static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
+{
+       printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
+       printk(IBM_INFO "%s\n", IBM_URL);
+
+       if (ibm_thinkpad_ec_found)
+               printk(IBM_INFO "ThinkPad EC firmware %s\n",
+                      ibm_thinkpad_ec_found);
+
+       return 0;
+}
+
+static int thinkpad_acpi_driver_read(char *p)
+{
+       int len = 0;
+
+       len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC);
+       len += sprintf(p + len, "version:\t%s\n", IBM_VERSION);
+
+       return len;
+}
+
+static struct ibm_struct thinkpad_acpi_driver_data = {
+       .name = "driver",
+       .read = thinkpad_acpi_driver_read,
+};
+
+/*************************************************************************
+ * Hotkey subdriver
+ */
+
+static int hotkey_orig_status;
+static int hotkey_orig_mask;
+
+static struct attribute_set *hotkey_dev_attributes = NULL;
+
+/* sysfs hotkey enable ------------------------------------------------- */
+static ssize_t hotkey_enable_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       int res, status, mask;
+
+       res = hotkey_get(&status, &mask);
+       if (res)
+               return res;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t hotkey_enable_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+{
+       unsigned long t;
+       int res, status, mask;
+
+       if (parse_strtoul(buf, 1, &t))
+               return -EINVAL;
+
+       res = hotkey_get(&status, &mask);
+       if (!res)
+               res = hotkey_set(t, mask);
+
+       return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_hotkey_enable =
+       __ATTR(enable, S_IWUSR | S_IRUGO,
+               hotkey_enable_show, hotkey_enable_store);
+
+/* sysfs hotkey mask --------------------------------------------------- */
+static ssize_t hotkey_mask_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       int res, status, mask;
+
+       res = hotkey_get(&status, &mask);
+       if (res)
+               return res;
+
+       return snprintf(buf, PAGE_SIZE, "0x%04x\n", mask);
+}
+
+static ssize_t hotkey_mask_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+{
+       unsigned long t;
+       int res, status, mask;
+
+       if (parse_strtoul(buf, 0xffff, &t))
+               return -EINVAL;
+
+       res = hotkey_get(&status, &mask);
+       if (!res)
+               hotkey_set(status, t);
+
+       return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_hotkey_mask =
+       __ATTR(mask, S_IWUSR | S_IRUGO,
+               hotkey_mask_show, hotkey_mask_store);
+
+/* sysfs hotkey bios_enabled ------------------------------------------- */
+static ssize_t hotkey_bios_enabled_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_orig_status);
+}
+
+static struct device_attribute dev_attr_hotkey_bios_enabled =
+       __ATTR(bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL);
+
+/* sysfs hotkey bios_mask ---------------------------------------------- */
+static ssize_t hotkey_bios_mask_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "0x%04x\n", hotkey_orig_mask);
+}
+
+static struct device_attribute dev_attr_hotkey_bios_mask =
+       __ATTR(bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *hotkey_mask_attributes[] = {
+       &dev_attr_hotkey_mask.attr,
+       &dev_attr_hotkey_bios_enabled.attr,
+       &dev_attr_hotkey_bios_mask.attr,
+};
+
+static int __init hotkey_init(struct ibm_init_struct *iibm)
+{
+       int res;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(hkey);
+       mutex_init(&hotkey_mutex);
+
+       /* hotkey not supported on 570 */
+       tp_features.hotkey = hkey_handle != NULL;
+
+       vdbg_printk(TPACPI_DBG_INIT, "hotkeys are %s\n",
+               str_supported(tp_features.hotkey));
+
+       if (tp_features.hotkey) {
+               hotkey_dev_attributes = create_attr_set(4,
+                                               TPACPI_HOTKEY_SYSFS_GROUP);
+               if (!hotkey_dev_attributes)
+                       return -ENOMEM;
+               res = add_to_attr_set(hotkey_dev_attributes,
+                               &dev_attr_hotkey_enable.attr);
+               if (res)
+                       return res;
+
+               /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+                  A30, R30, R31, T20-22, X20-21, X22-24 */
+               tp_features.hotkey_mask =
+                       acpi_evalf(hkey_handle, NULL, "DHKN", "qv");
+
+               vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n",
+                       str_supported(tp_features.hotkey_mask));
+
+               res = hotkey_get(&hotkey_orig_status, &hotkey_orig_mask);
+               if (!res && tp_features.hotkey_mask) {
+                       res = add_many_to_attr_set(hotkey_dev_attributes,
+                               hotkey_mask_attributes,
+                               ARRAY_SIZE(hotkey_mask_attributes));
+               }
+               if (!res)
+                       res = register_attr_set_with_sysfs(
+                                       hotkey_dev_attributes,
+                                       &tpacpi_pdev->dev.kobj);
+
+               if (res)
+                       return res;
+       }
+
+       return (tp_features.hotkey)? 0 : 1;
+}
+
+static void hotkey_exit(void)
+{
+       int res;
+
+       if (tp_features.hotkey) {
+               dbg_printk(TPACPI_DBG_EXIT, "restoring original hotkey mask\n");
+               res = hotkey_set(hotkey_orig_status, hotkey_orig_mask);
+               if (res)
+                       printk(IBM_ERR "failed to restore hotkey to BIOS defaults\n");
+       }
+
+       if (hotkey_dev_attributes) {
+               delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+               hotkey_dev_attributes = NULL;
+       }
+}
+
+static void hotkey_notify(struct ibm_struct *ibm, u32 event)
+{
+       int hkey;
+
+       if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d"))
+               acpi_bus_generate_event(ibm->acpi->device, event, hkey);
+       else {
+               printk(IBM_ERR "unknown hotkey event %d\n", event);
+               acpi_bus_generate_event(ibm->acpi->device, event, 0);
+       }
+}
+
+/*
+ * Call with hotkey_mutex held
+ */
+static int hotkey_get(int *status, int *mask)
+{
+       if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
+               return -EIO;
+
+       if (tp_features.hotkey_mask)
+               if (!acpi_evalf(hkey_handle, mask, "DHKN", "d"))
+                       return -EIO;
+
+       return 0;
+}
+
+/*
+ * Call with hotkey_mutex held
+ */
+static int hotkey_set(int status, int mask)
+{
+       int i;
+
+       if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
+               return -EIO;
+
+       if (tp_features.hotkey_mask)
+               for (i = 0; i < 32; i++) {
+                       int bit = ((1 << i) & mask) != 0;
+                       if (!acpi_evalf(hkey_handle,
+                                       NULL, "MHKM", "vdd", i + 1, bit))
+                               return -EIO;
+               }
+
+       return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int hotkey_read(char *p)
+{
+       int res, status, mask;
+       int len = 0;
+
+       if (!tp_features.hotkey) {
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+               return len;
+       }
+
+       res = mutex_lock_interruptible(&hotkey_mutex);
+       if (res < 0)
+               return res;
+       res = hotkey_get(&status, &mask);
+       mutex_unlock(&hotkey_mutex);
+       if (res)
+               return res;
+
+       len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
+       if (tp_features.hotkey_mask) {
+               len += sprintf(p + len, "mask:\t\t0x%04x\n", mask);
+               len += sprintf(p + len,
+                              "commands:\tenable, disable, reset, <mask>\n");
+       } else {
+               len += sprintf(p + len, "mask:\t\tnot supported\n");
+               len += sprintf(p + len, "commands:\tenable, disable, reset\n");
+       }
+
+       return len;
+}
+
+static int hotkey_write(char *buf)
+{
+       int res, status, mask;
+       char *cmd;
+       int do_cmd = 0;
+
+       if (!tp_features.hotkey)
+               return -ENODEV;
+
+       res = mutex_lock_interruptible(&hotkey_mutex);
+       if (res < 0)
+               return res;
+
+       res = hotkey_get(&status, &mask);
+       if (res)
+               goto errexit;
+
+       res = 0;
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "enable") == 0) {
+                       status = 1;
+               } else if (strlencmp(cmd, "disable") == 0) {
+                       status = 0;
+               } else if (strlencmp(cmd, "reset") == 0) {
+                       status = hotkey_orig_status;
+                       mask = hotkey_orig_mask;
+               } else if (sscanf(cmd, "0x%x", &mask) == 1) {
+                       /* mask set */
+               } else if (sscanf(cmd, "%x", &mask) == 1) {
+                       /* mask set */
+               } else {
+                       res = -EINVAL;
+                       goto errexit;
+               }
+               do_cmd = 1;
+       }
+
+       if (do_cmd)
+               res = hotkey_set(status, mask);
+
+errexit:
+       mutex_unlock(&hotkey_mutex);
+       return res;
+}
+
+static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
+       .hid = IBM_HKEY_HID,
+       .notify = hotkey_notify,
+       .handle = &hkey_handle,
+       .type = ACPI_DEVICE_NOTIFY,
+};
+
+static struct ibm_struct hotkey_driver_data = {
+       .name = "hotkey",
+       .read = hotkey_read,
+       .write = hotkey_write,
+       .exit = hotkey_exit,
+       .acpi = &ibm_hotkey_acpidriver,
+};
+
+/*************************************************************************
+ * Bluetooth subdriver
+ */
+
+/* sysfs bluetooth enable ---------------------------------------------- */
+static ssize_t bluetooth_enable_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       int status;
+
+       status = bluetooth_get_radiosw();
+       if (status < 0)
+               return status;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
+}
+
+static ssize_t bluetooth_enable_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+{
+       unsigned long t;
+       int res;
+
+       if (parse_strtoul(buf, 1, &t))
+               return -EINVAL;
+
+       res = bluetooth_set_radiosw(t);
+
+       return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_bluetooth_enable =
+       __ATTR(enable, S_IWUSR | S_IRUGO,
+               bluetooth_enable_show, bluetooth_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *bluetooth_attributes[] = {
+       &dev_attr_bluetooth_enable.attr,
+       NULL
+};
+
+static const struct attribute_group bluetooth_attr_group = {
+       .name = TPACPI_BLUETH_SYSFS_GROUP,
+       .attrs = bluetooth_attributes,
+};
+
+static int __init bluetooth_init(struct ibm_init_struct *iibm)
+{
+       int res;
+       int status = 0;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(hkey);
+
+       /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+          G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
+       tp_features.bluetooth = hkey_handle &&
+           acpi_evalf(hkey_handle, &status, "GBDC", "qd");
+
+       vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s, status 0x%02x\n",
+               str_supported(tp_features.bluetooth),
+               status);
+
+       if (tp_features.bluetooth) {
+               if (!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
+                       /* no bluetooth hardware present in system */
+                       tp_features.bluetooth = 0;
+                       dbg_printk(TPACPI_DBG_INIT,
+                                  "bluetooth hardware not installed\n");
+               } else {
+                       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                                       &bluetooth_attr_group);
+                       if (res)
+                               return res;
+               }
+       }
+
+       return (tp_features.bluetooth)? 0 : 1;
+}
+
+static void bluetooth_exit(void)
+{
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+                       &bluetooth_attr_group);
+}
+
+static int bluetooth_get_radiosw(void)
+{
+       int status;
+
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+
+       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+               return -EIO;
+
+       return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0);
+}
+
+static int bluetooth_set_radiosw(int radio_on)
+{
+       int status;
+
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+
+       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+               return -EIO;
+       if (radio_on)
+               status |= TP_ACPI_BLUETOOTH_RADIOSSW;
+       else
+               status &= ~TP_ACPI_BLUETOOTH_RADIOSSW;
+       if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
+               return -EIO;
+
+       return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int bluetooth_read(char *p)
+{
+       int len = 0;
+       int status = bluetooth_get_radiosw();
+
+       if (!tp_features.bluetooth)
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       else {
+               len += sprintf(p + len, "status:\t\t%s\n",
+                               (status)? "enabled" : "disabled");
+               len += sprintf(p + len, "commands:\tenable, disable\n");
+       }
+
+       return len;
+}
+
+static int bluetooth_write(char *buf)
+{
+       char *cmd;
+
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "enable") == 0) {
+                       bluetooth_set_radiosw(1);
+               } else if (strlencmp(cmd, "disable") == 0) {
+                       bluetooth_set_radiosw(0);
+               } else
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct bluetooth_driver_data = {
+       .name = "bluetooth",
+       .read = bluetooth_read,
+       .write = bluetooth_write,
+       .exit = bluetooth_exit,
+};
+
+/*************************************************************************
+ * Wan subdriver
+ */
+
+/* sysfs wan enable ---------------------------------------------------- */
+static ssize_t wan_enable_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       int status;
+
+       status = wan_get_radiosw();
+       if (status < 0)
+               return status;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
+}
+
+static ssize_t wan_enable_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+{
+       unsigned long t;
+       int res;
+
+       if (parse_strtoul(buf, 1, &t))
+               return -EINVAL;
+
+       res = wan_set_radiosw(t);
+
+       return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_wan_enable =
+       __ATTR(enable, S_IWUSR | S_IRUGO,
+               wan_enable_show, wan_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *wan_attributes[] = {
+       &dev_attr_wan_enable.attr,
+       NULL
+};
+
+static const struct attribute_group wan_attr_group = {
+       .name = TPACPI_WAN_SYSFS_GROUP,
+       .attrs = wan_attributes,
+};
+
+static int __init wan_init(struct ibm_init_struct *iibm)
+{
+       int res;
+       int status = 0;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(hkey);
+
+       tp_features.wan = hkey_handle &&
+           acpi_evalf(hkey_handle, &status, "GWAN", "qd");
+
+       vdbg_printk(TPACPI_DBG_INIT, "wan is %s, status 0x%02x\n",
+               str_supported(tp_features.wan),
+               status);
+
+       if (tp_features.wan) {
+               if (!(status & TP_ACPI_WANCARD_HWPRESENT)) {
+                       /* no wan hardware present in system */
+                       tp_features.wan = 0;
+                       dbg_printk(TPACPI_DBG_INIT,
+                                  "wan hardware not installed\n");
+               } else {
+                       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                                       &wan_attr_group);
+                       if (res)
+                               return res;
+               }
+       }
+
+       return (tp_features.wan)? 0 : 1;
+}
+
+static void wan_exit(void)
+{
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+               &wan_attr_group);
+}
+
+static int wan_get_radiosw(void)
+{
+       int status;
+
+       if (!tp_features.wan)
+               return -ENODEV;
+
+       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+               return -EIO;
+
+       return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0);
+}
+
+static int wan_set_radiosw(int radio_on)
+{
+       int status;
+
+       if (!tp_features.wan)
+               return -ENODEV;
+
+       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+               return -EIO;
+       if (radio_on)
+               status |= TP_ACPI_WANCARD_RADIOSSW;
+       else
+               status &= ~TP_ACPI_WANCARD_RADIOSSW;
+       if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
+               return -EIO;
+
+       return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int wan_read(char *p)
+{
+       int len = 0;
+       int status = wan_get_radiosw();
+
+       if (!tp_features.wan)
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       else {
+               len += sprintf(p + len, "status:\t\t%s\n",
+                               (status)? "enabled" : "disabled");
+               len += sprintf(p + len, "commands:\tenable, disable\n");
+       }
+
+       return len;
+}
+
+static int wan_write(char *buf)
+{
+       char *cmd;
+
+       if (!tp_features.wan)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "enable") == 0) {
+                       wan_set_radiosw(1);
+               } else if (strlencmp(cmd, "disable") == 0) {
+                       wan_set_radiosw(0);
+               } else
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct wan_driver_data = {
+       .name = "wan",
+       .read = wan_read,
+       .write = wan_write,
+       .exit = wan_exit,
+       .flags.experimental = 1,
+};
+
+/*************************************************************************
+ * Video subdriver
+ */
+
+static enum video_access_mode video_supported;
+static int video_orig_autosw;
+
+IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA",     /* 570 */
+          "\\_SB.PCI0.AGP0.VID0",      /* 600e/x, 770x */
+          "\\_SB.PCI0.VID0",   /* 770e */
+          "\\_SB.PCI0.VID",    /* A21e, G4x, R50e, X30, X40 */
+          "\\_SB.PCI0.AGP.VID",        /* all others */
+          );                           /* R30, R31 */
+
+IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */
+
+static int __init video_init(struct ibm_init_struct *iibm)
+{
+       int ivga;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(vid);
+       IBM_ACPIHANDLE_INIT(vid2);
+
+       if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
+               /* G41, assume IVGA doesn't change */
+               vid_handle = vid2_handle;
+
+       if (!vid_handle)
+               /* video switching not supported on R30, R31 */
+               video_supported = TPACPI_VIDEO_NONE;
+       else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
+               /* 570 */
+               video_supported = TPACPI_VIDEO_570;
+       else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
+               /* 600e/x, 770e, 770x */
+               video_supported = TPACPI_VIDEO_770;
+       else
+               /* all others */
+               video_supported = TPACPI_VIDEO_NEW;
+
+       vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
+               str_supported(video_supported != TPACPI_VIDEO_NONE),
+               video_supported);
+
+       return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
+}
+
+static void video_exit(void)
+{
+       dbg_printk(TPACPI_DBG_EXIT,
+                  "restoring original video autoswitch mode\n");
+       if (video_autosw_set(video_orig_autosw))
+               printk(IBM_ERR "error while trying to restore original "
+                       "video autoswitch mode\n");
+}
+
+static int video_outputsw_get(void)
+{
+       int status = 0;
+       int i;
+
+       switch (video_supported) {
+       case TPACPI_VIDEO_570:
+               if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd",
+                                TP_ACPI_VIDEO_570_PHSCMD))
+                       return -EIO;
+               status = i & TP_ACPI_VIDEO_570_PHSMASK;
+               break;
+       case TPACPI_VIDEO_770:
+               if (!acpi_evalf(NULL, &i, "\\VCDL", "d"))
+                       return -EIO;
+               if (i)
+                       status |= TP_ACPI_VIDEO_S_LCD;
+               if (!acpi_evalf(NULL, &i, "\\VCDC", "d"))
+                       return -EIO;
+               if (i)
+                       status |= TP_ACPI_VIDEO_S_CRT;
+               break;
+       case TPACPI_VIDEO_NEW:
+               if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) ||
+                   !acpi_evalf(NULL, &i, "\\VCDC", "d"))
+                       return -EIO;
+               if (i)
+                       status |= TP_ACPI_VIDEO_S_CRT;
+
+               if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) ||
+                   !acpi_evalf(NULL, &i, "\\VCDL", "d"))
+                       return -EIO;
+               if (i)
+                       status |= TP_ACPI_VIDEO_S_LCD;
+               if (!acpi_evalf(NULL, &i, "\\VCDD", "d"))
+                       return -EIO;
+               if (i)
+                       status |= TP_ACPI_VIDEO_S_DVI;
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       return status;
+}
+
+static int video_outputsw_set(int status)
+{
+       int autosw;
+       int res = 0;
+
+       switch (video_supported) {
+       case TPACPI_VIDEO_570:
+               res = acpi_evalf(NULL, NULL,
+                                "\\_SB.PHS2", "vdd",
+                                TP_ACPI_VIDEO_570_PHS2CMD,
+                                status | TP_ACPI_VIDEO_570_PHS2SET);
+               break;
+       case TPACPI_VIDEO_770:
+               autosw = video_autosw_get();
+               if (autosw < 0)
+                       return autosw;
+
+               res = video_autosw_set(1);
+               if (res)
+                       return res;
+               res = acpi_evalf(vid_handle, NULL,
+                                "ASWT", "vdd", status * 0x100, 0);
+               if (!autosw && video_autosw_set(autosw)) {
+                       printk(IBM_ERR "video auto-switch left enabled due to error\n");
+                       return -EIO;
+               }
+               break;
+       case TPACPI_VIDEO_NEW:
+               res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
+                       acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       return (res)? 0 : -EIO;
+}
+
+static int video_autosw_get(void)
+{
+       int autosw = 0;
+
+       switch (video_supported) {
+       case TPACPI_VIDEO_570:
+               if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d"))
+                       return -EIO;
+               break;
+       case TPACPI_VIDEO_770:
+       case TPACPI_VIDEO_NEW:
+               if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d"))
+                       return -EIO;
+               break;
+       default:
+               return -ENOSYS;
+       }
+
+       return autosw & 1;
+}
+
+static int video_autosw_set(int enable)
+{
+       if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0))
+               return -EIO;
+       return 0;
+}
+
+static int video_outputsw_cycle(void)
+{
+       int autosw = video_autosw_get();
+       int res;
+
+       if (autosw < 0)
+               return autosw;
+
+       switch (video_supported) {
+       case TPACPI_VIDEO_570:
+               res = video_autosw_set(1);
+               if (res)
+                       return res;
+               res = acpi_evalf(ec_handle, NULL, "_Q16", "v");
+               break;
+       case TPACPI_VIDEO_770:
+       case TPACPI_VIDEO_NEW:
+               res = video_autosw_set(1);
+               if (res)
+                       return res;
+               res = acpi_evalf(vid_handle, NULL, "VSWT", "v");
+               break;
+       default:
+               return -ENOSYS;
+       }
+       if (!autosw && video_autosw_set(autosw)) {
+               printk(IBM_ERR "video auto-switch left enabled due to error\n");
+               return -EIO;
+       }
+
+       return (res)? 0 : -EIO;
+}
+
+static int video_expand_toggle(void)
+{
+       switch (video_supported) {
+       case TPACPI_VIDEO_570:
+               return acpi_evalf(ec_handle, NULL, "_Q17", "v")?
+                       0 : -EIO;
+       case TPACPI_VIDEO_770:
+               return acpi_evalf(vid_handle, NULL, "VEXP", "v")?
+                       0 : -EIO;
+       case TPACPI_VIDEO_NEW:
+               return acpi_evalf(NULL, NULL, "\\VEXP", "v")?
+                       0 : -EIO;
+       default:
+               return -ENOSYS;
+       }
+       /* not reached */
+}
+
+static int video_read(char *p)
+{
+       int status, autosw;
+       int len = 0;
+
+       if (video_supported == TPACPI_VIDEO_NONE) {
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+               return len;
+       }
+
+       status = video_outputsw_get();
+       if (status < 0)
+               return status;
+
+       autosw = video_autosw_get();
+       if (autosw < 0)
+               return autosw;
+
+       len += sprintf(p + len, "status:\t\tsupported\n");
+       len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
+       len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
+       if (video_supported == TPACPI_VIDEO_NEW)
+               len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
+       len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
+       len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
+       len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
+       if (video_supported == TPACPI_VIDEO_NEW)
+               len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
+       len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
+       len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
+
+       return len;
+}
+
+static int video_write(char *buf)
+{
+       char *cmd;
+       int enable, disable, status;
+       int res;
+
+       if (video_supported == TPACPI_VIDEO_NONE)
+               return -ENODEV;
+
+       enable = 0;
+       disable = 0;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "lcd_enable") == 0) {
+                       enable |= TP_ACPI_VIDEO_S_LCD;
+               } else if (strlencmp(cmd, "lcd_disable") == 0) {
+                       disable |= TP_ACPI_VIDEO_S_LCD;
+               } else if (strlencmp(cmd, "crt_enable") == 0) {
+                       enable |= TP_ACPI_VIDEO_S_CRT;
+               } else if (strlencmp(cmd, "crt_disable") == 0) {
+                       disable |= TP_ACPI_VIDEO_S_CRT;
+               } else if (video_supported == TPACPI_VIDEO_NEW &&
+                          strlencmp(cmd, "dvi_enable") == 0) {
+                       enable |= TP_ACPI_VIDEO_S_DVI;
+               } else if (video_supported == TPACPI_VIDEO_NEW &&
+                          strlencmp(cmd, "dvi_disable") == 0) {
+                       disable |= TP_ACPI_VIDEO_S_DVI;
+               } else if (strlencmp(cmd, "auto_enable") == 0) {
+                       res = video_autosw_set(1);
+                       if (res)
+                               return res;
+               } else if (strlencmp(cmd, "auto_disable") == 0) {
+                       res = video_autosw_set(0);
+                       if (res)
+                               return res;
+               } else if (strlencmp(cmd, "video_switch") == 0) {
+                       res = video_outputsw_cycle();
+                       if (res)
+                               return res;
+               } else if (strlencmp(cmd, "expand_toggle") == 0) {
+                       res = video_expand_toggle();
+                       if (res)
+                               return res;
+               } else
+                       return -EINVAL;
+       }
+
+       if (enable || disable) {
+               status = video_outputsw_get();
+               if (status < 0)
+                       return status;
+               res = video_outputsw_set((status & ~disable) | enable);
+               if (res)
+                       return res;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct video_driver_data = {
+       .name = "video",
+       .read = video_read,
+       .write = video_write,
+       .exit = video_exit,
+};
+
+/*************************************************************************
+ * Light (thinklight) subdriver
+ */
+
+IBM_HANDLE(lght, root, "\\LGHT");      /* A21e, A2xm/p, T20-22, X20-21 */
+IBM_HANDLE(ledb, ec, "LEDB");          /* G4x */
+
+static int __init light_init(struct ibm_init_struct *iibm)
+{
+       vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(ledb);
+       IBM_ACPIHANDLE_INIT(lght);
+       IBM_ACPIHANDLE_INIT(cmos);
+
+       /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
+       tp_features.light = (cmos_handle || lght_handle) && !ledb_handle;
+
+       if (tp_features.light)
+               /* light status not supported on
+                  570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
+               tp_features.light_status =
+                       acpi_evalf(ec_handle, NULL, "KBLT", "qv");
+
+       vdbg_printk(TPACPI_DBG_INIT, "light is %s\n",
+               str_supported(tp_features.light));
+
+       return (tp_features.light)? 0 : 1;
+}
+
+static int light_read(char *p)
+{
+       int len = 0;
+       int status = 0;
+
+       if (!tp_features.light) {
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       } else if (!tp_features.light_status) {
+               len += sprintf(p + len, "status:\t\tunknown\n");
+               len += sprintf(p + len, "commands:\ton, off\n");
+       } else {
+               if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
+                       return -EIO;
+               len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
+               len += sprintf(p + len, "commands:\ton, off\n");
+       }
+
+       return len;
+}
+
+static int light_write(char *buf)
+{
+       int cmos_cmd, lght_cmd;
+       char *cmd;
+       int success;
+
+       if (!tp_features.light)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "on") == 0) {
+                       cmos_cmd = 0x0c;
+                       lght_cmd = 1;
+               } else if (strlencmp(cmd, "off") == 0) {
+                       cmos_cmd = 0x0d;
+                       lght_cmd = 0;
+               } else
+                       return -EINVAL;
+
+               success = cmos_handle ?
+                   acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) :
+                   acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd);
+               if (!success)
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct light_driver_data = {
+       .name = "light",
+       .read = light_read,
+       .write = light_write,
+};
+
+/*************************************************************************
+ * Dock subdriver
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_DOCK
+
+IBM_HANDLE(dock, root, "\\_SB.GDCK",   /* X30, X31, X40 */
+          "\\_SB.PCI0.DOCK",   /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */
+          "\\_SB.PCI0.PCI1.DOCK",      /* all others */
+          "\\_SB.PCI.ISA.SLCE",        /* 570 */
+    );                         /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */
+
+/* don't list other alternatives as we install a notify handler on the 570 */
+IBM_HANDLE(pci, root, "\\_SB.PCI");    /* 570 */
+
+static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
+       {
+        .notify = dock_notify,
+        .handle = &dock_handle,
+        .type = ACPI_SYSTEM_NOTIFY,
+       },
+       {
+        .hid = IBM_PCI_HID,
+        .notify = dock_notify,
+        .handle = &pci_handle,
+        .type = ACPI_SYSTEM_NOTIFY,
+       },
+};
+
+static struct ibm_struct dock_driver_data[2] = {
+       {
+        .name = "dock",
+        .read = dock_read,
+        .write = dock_write,
+        .acpi = &ibm_dock_acpidriver[0],
+       },
+       {
+        .name = "dock",
+        .acpi = &ibm_dock_acpidriver[1],
+       },
+};
+
+#define dock_docked() (_sta(dock_handle) & 1)
+
+static int __init dock_init(struct ibm_init_struct *iibm)
+{
+       vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(dock);
+
+       vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n",
+               str_supported(dock_handle != NULL));
+
+       return (dock_handle)? 0 : 1;
+}
+
+static int __init dock_init2(struct ibm_init_struct *iibm)
+{
+       int dock2_needed;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n");
+
+       if (dock_driver_data[0].flags.acpi_driver_registered &&
+           dock_driver_data[0].flags.acpi_notify_installed) {
+               IBM_ACPIHANDLE_INIT(pci);
+               dock2_needed = (pci_handle != NULL);
+               vdbg_printk(TPACPI_DBG_INIT,
+                           "dock PCI handler for the TP 570 is %s\n",
+                           str_supported(dock2_needed));
+       } else {
+               vdbg_printk(TPACPI_DBG_INIT,
+               "dock subdriver part 2 not required\n");
+               dock2_needed = 0;
+       }
+
+       return (dock2_needed)? 0 : 1;
+}
+
+static void dock_notify(struct ibm_struct *ibm, u32 event)
+{
+       int docked = dock_docked();
+       int pci = ibm->acpi->hid && strstr(ibm->acpi->hid, IBM_PCI_HID);
+
+       if (event == 1 && !pci) /* 570 */
+               acpi_bus_generate_event(ibm->acpi->device, event, 1);   /* button */
+       else if (event == 1 && pci)     /* 570 */
+               acpi_bus_generate_event(ibm->acpi->device, event, 3);   /* dock */
+       else if (event == 3 && docked)
+               acpi_bus_generate_event(ibm->acpi->device, event, 1);   /* button */
+       else if (event == 3 && !docked)
+               acpi_bus_generate_event(ibm->acpi->device, event, 2);   /* undock */
+       else if (event == 0 && docked)
+               acpi_bus_generate_event(ibm->acpi->device, event, 3);   /* dock */
+       else {
+               printk(IBM_ERR "unknown dock event %d, status %d\n",
+                      event, _sta(dock_handle));
+               acpi_bus_generate_event(ibm->acpi->device, event, 0);   /* unknown */
+       }
+}
+
+static int dock_read(char *p)
+{
+       int len = 0;
+       int docked = dock_docked();
+
+       if (!dock_handle)
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       else if (!docked)
+               len += sprintf(p + len, "status:\t\tundocked\n");
+       else {
+               len += sprintf(p + len, "status:\t\tdocked\n");
+               len += sprintf(p + len, "commands:\tdock, undock\n");
+       }
+
+       return len;
+}
+
+static int dock_write(char *buf)
+{
+       char *cmd;
+
+       if (!dock_docked())
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "undock") == 0) {
+                       if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) ||
+                           !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1))
+                               return -EIO;
+               } else if (strlencmp(cmd, "dock") == 0) {
+                       if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
+                               return -EIO;
+               } else
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+#endif /* CONFIG_THINKPAD_ACPI_DOCK */
+
+/*************************************************************************
+ * Bay subdriver
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_BAY
+IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST",       /* 570 */
+          "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */
+          "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */
+          "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */
+          );                           /* A21e, R30, R31 */
+IBM_HANDLE(bay_ej, bay, "_EJ3",        /* 600e/x, A2xm/p, A3x */
+          "_EJ0",              /* all others */
+          );                   /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
+IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV",    /* A3x, R32 */
+          "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */
+          );                           /* all others */
+IBM_HANDLE(bay2_ej, bay2, "_EJ3",      /* 600e/x, 770e, A3x */
+          "_EJ0",                      /* 770x */
+          );                           /* all others */
+
+static int __init bay_init(struct ibm_init_struct *iibm)
+{
+       vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(bay);
+       if (bay_handle)
+               IBM_ACPIHANDLE_INIT(bay_ej);
+       IBM_ACPIHANDLE_INIT(bay2);
+       if (bay2_handle)
+               IBM_ACPIHANDLE_INIT(bay2_ej);
+
+       tp_features.bay_status = bay_handle &&
+               acpi_evalf(bay_handle, NULL, "_STA", "qv");
+       tp_features.bay_status2 = bay2_handle &&
+               acpi_evalf(bay2_handle, NULL, "_STA", "qv");
+
+       tp_features.bay_eject = bay_handle && bay_ej_handle &&
+               (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental);
+       tp_features.bay_eject2 = bay2_handle && bay2_ej_handle &&
+               (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental);
+
+       vdbg_printk(TPACPI_DBG_INIT,
+               "bay 1: status %s, eject %s; bay 2: status %s, eject %s\n",
+               str_supported(tp_features.bay_status),
+               str_supported(tp_features.bay_eject),
+               str_supported(tp_features.bay_status2),
+               str_supported(tp_features.bay_eject2));
+
+       return (tp_features.bay_status || tp_features.bay_eject ||
+               tp_features.bay_status2 || tp_features.bay_eject2)? 0 : 1;
+}
+
+static void bay_notify(struct ibm_struct *ibm, u32 event)
+{
+       acpi_bus_generate_event(ibm->acpi->device, event, 0);
+}
+
+#define bay_occupied(b) (_sta(b##_handle) & 1)
+
+static int bay_read(char *p)
+{
+       int len = 0;
+       int occupied = bay_occupied(bay);
+       int occupied2 = bay_occupied(bay2);
+       int eject, eject2;
+
+       len += sprintf(p + len, "status:\t\t%s\n",
+               tp_features.bay_status ?
+                       (occupied ? "occupied" : "unoccupied") :
+                               "not supported");
+       if (tp_features.bay_status2)
+               len += sprintf(p + len, "status2:\t%s\n", occupied2 ?
+                              "occupied" : "unoccupied");
+
+       eject = tp_features.bay_eject && occupied;
+       eject2 = tp_features.bay_eject2 && occupied2;
+
+       if (eject && eject2)
+               len += sprintf(p + len, "commands:\teject, eject2\n");
+       else if (eject)
+               len += sprintf(p + len, "commands:\teject\n");
+       else if (eject2)
+               len += sprintf(p + len, "commands:\teject2\n");
+
+       return len;
+}
+
+static int bay_write(char *buf)
+{
+       char *cmd;
+
+       if (!tp_features.bay_eject && !tp_features.bay_eject2)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (tp_features.bay_eject && strlencmp(cmd, "eject") == 0) {
+                       if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1))
+                               return -EIO;
+               } else if (tp_features.bay_eject2 &&
+                          strlencmp(cmd, "eject2") == 0) {
+                       if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1))
+                               return -EIO;
+               } else
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct tp_acpi_drv_struct ibm_bay_acpidriver = {
+       .notify = bay_notify,
+       .handle = &bay_handle,
+       .type = ACPI_SYSTEM_NOTIFY,
+};
+
+static struct ibm_struct bay_driver_data = {
+       .name = "bay",
+       .read = bay_read,
+       .write = bay_write,
+       .acpi = &ibm_bay_acpidriver,
+};
+
+#endif /* CONFIG_THINKPAD_ACPI_BAY */
+
+/*************************************************************************
+ * CMOS subdriver
+ */
+
+/* sysfs cmos_command -------------------------------------------------- */
+static ssize_t cmos_command_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+{
+       unsigned long cmos_cmd;
+       int res;
+
+       if (parse_strtoul(buf, 21, &cmos_cmd))
+               return -EINVAL;
+
+       res = issue_thinkpad_cmos_command(cmos_cmd);
+       return (res)? res : count;
+}
+
+static struct device_attribute dev_attr_cmos_command =
+       __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store);
+
+/* --------------------------------------------------------------------- */
+
+static int __init cmos_init(struct ibm_init_struct *iibm)
+{
+       int res;
+
+       vdbg_printk(TPACPI_DBG_INIT,
+               "initializing cmos commands subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(cmos);
+
+       vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
+               str_supported(cmos_handle != NULL));
+
+       res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+       if (res)
+               return res;
+
+       return (cmos_handle)? 0 : 1;
+}
+
+static void cmos_exit(void)
+{
+       device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+}
+
+static int cmos_read(char *p)
+{
+       int len = 0;
+
+       /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+          R30, R31, T20-22, X20-21 */
+       if (!cmos_handle)
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       else {
+               len += sprintf(p + len, "status:\t\tsupported\n");
+               len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
+       }
+
+       return len;
+}
+
+static int cmos_write(char *buf)
+{
+       char *cmd;
+       int cmos_cmd, res;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
+                   cmos_cmd >= 0 && cmos_cmd <= 21) {
+                       /* cmos_cmd set */
+               } else
+                       return -EINVAL;
+
+               res = issue_thinkpad_cmos_command(cmos_cmd);
+               if (res)
+                       return res;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct cmos_driver_data = {
+       .name = "cmos",
+       .read = cmos_read,
+       .write = cmos_write,
+       .exit = cmos_exit,
+};
+
+/*************************************************************************
+ * LED subdriver
+ */
+
+static enum led_access_mode led_supported;
+
+IBM_HANDLE(led, ec, "SLED",    /* 570 */
+          "SYSL",              /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+          "LED",               /* all others */
+          );                   /* R30, R31 */
+
+static int __init led_init(struct ibm_init_struct *iibm)
+{
+       vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(led);
+
+       if (!led_handle)
+               /* led not supported on R30, R31 */
+               led_supported = TPACPI_LED_NONE;
+       else if (strlencmp(led_path, "SLED") == 0)
+               /* 570 */
+               led_supported = TPACPI_LED_570;
+       else if (strlencmp(led_path, "SYSL") == 0)
+               /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+               led_supported = TPACPI_LED_OLD;
+       else
+               /* all others */
+               led_supported = TPACPI_LED_NEW;
+
+       vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
+               str_supported(led_supported), led_supported);
+
+       return (led_supported != TPACPI_LED_NONE)? 0 : 1;
+}
+
+#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking"))
+
+static int led_read(char *p)
+{
+       int len = 0;
+
+       if (!led_supported) {
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+               return len;
+       }
+       len += sprintf(p + len, "status:\t\tsupported\n");
+
+       if (led_supported == TPACPI_LED_570) {
+               /* 570 */
+               int i, status;
+               for (i = 0; i < 8; i++) {
+                       if (!acpi_evalf(ec_handle,
+                                       &status, "GLED", "dd", 1 << i))
+                               return -EIO;
+                       len += sprintf(p + len, "%d:\t\t%s\n",
+                                      i, led_status(status));
+               }
+       }
+
+       len += sprintf(p + len, "commands:\t"
+                      "<led> on, <led> off, <led> blink (<led> is 0-7)\n");
+
+       return len;
+}
+
+/* off, on, blink */
+static const int led_sled_arg1[] = { 0, 1, 3 };
+static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */
+static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */
+static const int led_led_arg1[] = { 0, 0x80, 0xc0 };
+
+static int led_write(char *buf)
+{
+       char *cmd;
+       int led, ind, ret;
+
+       if (!led_supported)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7)
+                       return -EINVAL;
+
+               if (strstr(cmd, "off")) {
+                       ind = 0;
+               } else if (strstr(cmd, "on")) {
+                       ind = 1;
+               } else if (strstr(cmd, "blink")) {
+                       ind = 2;
+               } else
+                       return -EINVAL;
+
+               if (led_supported == TPACPI_LED_570) {
+                       /* 570 */
+                       led = 1 << led;
+                       if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
+                                       led, led_sled_arg1[ind]))
+                               return -EIO;
+               } else if (led_supported == TPACPI_LED_OLD) {
+                       /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
+                       led = 1 << led;
+                       ret = ec_write(TPACPI_LED_EC_HLMS, led);
+                       if (ret >= 0)
+                               ret =
+                                   ec_write(TPACPI_LED_EC_HLBL,
+                                            led * led_exp_hlbl[ind]);
+                       if (ret >= 0)
+                               ret =
+                                   ec_write(TPACPI_LED_EC_HLCL,
+                                            led * led_exp_hlcl[ind]);
+                       if (ret < 0)
+                               return ret;
+               } else {
+                       /* all others */
+                       if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
+                                       led, led_led_arg1[ind]))
+                               return -EIO;
+               }
+       }
+
+       return 0;
+}
+
+static struct ibm_struct led_driver_data = {
+       .name = "led",
+       .read = led_read,
+       .write = led_write,
+};
+
+/*************************************************************************
+ * Beep subdriver
+ */
+
+IBM_HANDLE(beep, ec, "BEEP");  /* all except R30, R31 */
+
+static int __init beep_init(struct ibm_init_struct *iibm)
+{
+       vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
+
+       IBM_ACPIHANDLE_INIT(beep);
+
+       vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
+               str_supported(beep_handle != NULL));
+
+       return (beep_handle)? 0 : 1;
+}
+
+static int beep_read(char *p)
+{
+       int len = 0;
+
+       if (!beep_handle)
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       else {
+               len += sprintf(p + len, "status:\t\tsupported\n");
+               len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
+       }
+
+       return len;
+}
+
+static int beep_write(char *buf)
+{
+       char *cmd;
+       int beep_cmd;
+
+       if (!beep_handle)
+               return -ENODEV;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
+                   beep_cmd >= 0 && beep_cmd <= 17) {
+                       /* beep_cmd set */
+               } else
+                       return -EINVAL;
+               if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct beep_driver_data = {
+       .name = "beep",
+       .read = beep_read,
+       .write = beep_write,
+};
+
+/*************************************************************************
+ * Thermal subdriver
+ */
+
+static enum thermal_access_mode thermal_read_mode;
+
+/* sysfs temp##_input -------------------------------------------------- */
+
+static ssize_t thermal_temp_input_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct sensor_device_attribute *sensor_attr =
+                                       to_sensor_dev_attr(attr);
+       int idx = sensor_attr->index;
+       s32 value;
+       int res;
+
+       res = thermal_get_sensor(idx, &value);
+       if (res)
+               return res;
+       if (value == TP_EC_THERMAL_TMP_NA * 1000)
+               return -ENXIO;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \
+        SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, thermal_temp_input_show, NULL, _idxB)
+
+static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = {
+       THERMAL_SENSOR_ATTR_TEMP(1, 0),
+       THERMAL_SENSOR_ATTR_TEMP(2, 1),
+       THERMAL_SENSOR_ATTR_TEMP(3, 2),
+       THERMAL_SENSOR_ATTR_TEMP(4, 3),
+       THERMAL_SENSOR_ATTR_TEMP(5, 4),
+       THERMAL_SENSOR_ATTR_TEMP(6, 5),
+       THERMAL_SENSOR_ATTR_TEMP(7, 6),
+       THERMAL_SENSOR_ATTR_TEMP(8, 7),
+       THERMAL_SENSOR_ATTR_TEMP(9, 8),
+       THERMAL_SENSOR_ATTR_TEMP(10, 9),
+       THERMAL_SENSOR_ATTR_TEMP(11, 10),
+       THERMAL_SENSOR_ATTR_TEMP(12, 11),
+       THERMAL_SENSOR_ATTR_TEMP(13, 12),
+       THERMAL_SENSOR_ATTR_TEMP(14, 13),
+       THERMAL_SENSOR_ATTR_TEMP(15, 14),
+       THERMAL_SENSOR_ATTR_TEMP(16, 15),
+};
+
+#define THERMAL_ATTRS(X) \
+       &sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
+
+static struct attribute *thermal_temp_input_attr[] = {
+       THERMAL_ATTRS(8),
+       THERMAL_ATTRS(9),
+       THERMAL_ATTRS(10),
+       THERMAL_ATTRS(11),
+       THERMAL_ATTRS(12),
+       THERMAL_ATTRS(13),
+       THERMAL_ATTRS(14),
+       THERMAL_ATTRS(15),
+       THERMAL_ATTRS(0),
+       THERMAL_ATTRS(1),
+       THERMAL_ATTRS(2),
+       THERMAL_ATTRS(3),
+       THERMAL_ATTRS(4),
+       THERMAL_ATTRS(5),
+       THERMAL_ATTRS(6),
+       THERMAL_ATTRS(7),
+       NULL
+};
+
+static const struct attribute_group thermal_temp_input16_group = {
+       .attrs = thermal_temp_input_attr
+};
+
+static const struct attribute_group thermal_temp_input8_group = {
+       .attrs = &thermal_temp_input_attr[8]
+};
+
+#undef THERMAL_SENSOR_ATTR_TEMP
+#undef THERMAL_ATTRS
+
+/* --------------------------------------------------------------------- */
+
+static int __init thermal_init(struct ibm_init_struct *iibm)
+{
+       u8 t, ta1, ta2;
+       int i;
+       int acpi_tmp7;
+       int res;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
+
+       acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
+
+       if (ibm_thinkpad_ec_found && experimental) {
+               /*
+                * Direct EC access mode: sensors at registers
+                * 0x78-0x7F, 0xC0-0xC7.  Registers return 0x00 for
+                * non-implemented, thermal sensors return 0x80 when
+                * not available
+                */
+
+               ta1 = ta2 = 0;
+               for (i = 0; i < 8; i++) {
+                       if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) {
+                               ta1 |= t;
+                       } else {
+                               ta1 = 0;
+                               break;
+                       }
+                       if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
+                               ta2 |= t;
+                       } else {
+                               ta1 = 0;
+                               break;
+                       }
+               }
+               if (ta1 == 0) {
+                       /* This is sheer paranoia, but we handle it anyway */
+                       if (acpi_tmp7) {
+                               printk(IBM_ERR
+                                      "ThinkPad ACPI EC access misbehaving, "
+                                      "falling back to ACPI TMPx access mode\n");
+                               thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
+                       } else {
+                               printk(IBM_ERR
+                                      "ThinkPad ACPI EC access misbehaving, "
+                                      "disabling thermal sensors access\n");
+                               thermal_read_mode = TPACPI_THERMAL_NONE;
+                       }
+               } else {
+                       thermal_read_mode =
+                           (ta2 != 0) ?
+                           TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
+               }
+       } else if (acpi_tmp7) {
+               if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
+                       /* 600e/x, 770e, 770x */
+                       thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
+               } else {
+                       /* Standard ACPI TMPx access, max 8 sensors */
+                       thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
+               }
+       } else {
+               /* temperatures not supported on 570, G4x, R30, R31, R32 */
+               thermal_read_mode = TPACPI_THERMAL_NONE;
+       }
+
+       vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
+               str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
+               thermal_read_mode);
+
+       switch(thermal_read_mode) {
+       case TPACPI_THERMAL_TPEC_16:
+               res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                               &thermal_temp_input16_group);
+               if (res)
+                       return res;
+               break;
+       case TPACPI_THERMAL_TPEC_8:
+       case TPACPI_THERMAL_ACPI_TMP07:
+       case TPACPI_THERMAL_ACPI_UPDT:
+               res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                               &thermal_temp_input8_group);
+               if (res)
+                       return res;
+               break;
+       case TPACPI_THERMAL_NONE:
+       default:
+               return 1;
+       }
+
+       return 0;
+}
+
+static void thermal_exit(void)
+{
+       switch(thermal_read_mode) {
+       case TPACPI_THERMAL_TPEC_16:
+               sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+                                  &thermal_temp_input16_group);
+               break;
+       case TPACPI_THERMAL_TPEC_8:
+       case TPACPI_THERMAL_ACPI_TMP07:
+       case TPACPI_THERMAL_ACPI_UPDT:
+               sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+                                  &thermal_temp_input16_group);
+               break;
+       case TPACPI_THERMAL_NONE:
+       default:
+               break;
+       }
+}
+
+/* idx is zero-based */
+static int thermal_get_sensor(int idx, s32 *value)
+{
+       int t;
+       s8 tmp;
+       char tmpi[5];
+
+       t = TP_EC_THERMAL_TMP0;
+
+       switch (thermal_read_mode) {
+#if TPACPI_MAX_THERMAL_SENSORS >= 16
+       case TPACPI_THERMAL_TPEC_16:
+               if (idx >= 8 && idx <= 15) {
+                       t = TP_EC_THERMAL_TMP8;
+                       idx -= 8;
+               }
+               /* fallthrough */
+#endif
+       case TPACPI_THERMAL_TPEC_8:
+               if (idx <= 7) {
+                       if (!acpi_ec_read(t + idx, &tmp))
+                               return -EIO;
+                       *value = tmp * 1000;
+                       return 0;
+               }
+               break;
+
+       case TPACPI_THERMAL_ACPI_UPDT:
+               if (idx <= 7) {
+                       snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+                       if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+                               return -EIO;
+                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+                               return -EIO;
+                       *value = (t - 2732) * 100;
+                       return 0;
+               }
+               break;
+
+       case TPACPI_THERMAL_ACPI_TMP07:
+               if (idx <= 7) {
+                       snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+                               return -EIO;
+                       *value = t * 1000;
+                       return 0;
+               }
+               break;
+
+       case TPACPI_THERMAL_NONE:
+       default:
+               return -ENOSYS;
+       }
+
+       return -EINVAL;
+}
+
+static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
+{
+       int res, i;
+       int n;
+
+       n = 8;
+       i = 0;
+
+       if (!s)
+               return -EINVAL;
+
+       if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
+               n = 16;
+
+       for(i = 0 ; i < n; i++) {
+               res = thermal_get_sensor(i, &s->temp[i]);
+               if (res)
+                       return res;
+       }
+
+       return n;
+}
+
+static int thermal_read(char *p)
+{
+       int len = 0;
+       int n, i;
+       struct ibm_thermal_sensors_struct t;
+
+       n = thermal_get_sensors(&t);
+       if (unlikely(n < 0))
+               return n;
+
+       len += sprintf(p + len, "temperatures:\t");
+
+       if (n > 0) {
+               for (i = 0; i < (n - 1); i++)
+                       len += sprintf(p + len, "%d ", t.temp[i] / 1000);
+               len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
+       } else
+               len += sprintf(p + len, "not supported\n");
+
+       return len;
+}
+
+static struct ibm_struct thermal_driver_data = {
+       .name = "thermal",
+       .read = thermal_read,
+       .exit = thermal_exit,
+};
+
+/*************************************************************************
+ * EC Dump subdriver
+ */
+
+static u8 ecdump_regs[256];
+
+static int ecdump_read(char *p)
+{
+       int len = 0;
+       int i, j;
+       u8 v;
+
+       len += sprintf(p + len, "EC      "
+                      " +00 +01 +02 +03 +04 +05 +06 +07"
+                      " +08 +09 +0a +0b +0c +0d +0e +0f\n");
+       for (i = 0; i < 256; i += 16) {
+               len += sprintf(p + len, "EC 0x%02x:", i);
+               for (j = 0; j < 16; j++) {
+                       if (!acpi_ec_read(i + j, &v))
+                               break;
+                       if (v != ecdump_regs[i + j])
+                               len += sprintf(p + len, " *%02x", v);
+                       else
+                               len += sprintf(p + len, "  %02x", v);
+                       ecdump_regs[i + j] = v;
+               }
+               len += sprintf(p + len, "\n");
+               if (j != 16)
+                       break;
+       }
+
+       /* These are way too dangerous to advertise openly... */
+#if 0
+       len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
+                      " (<offset> is 00-ff, <value> is 00-ff)\n");
+       len += sprintf(p + len, "commands:\t0x<offset> <value>  "
+                      " (<offset> is 00-ff, <value> is 0-255)\n");
+#endif
+       return len;
+}
+
+static int ecdump_write(char *buf)
+{
+       char *cmd;
+       int i, v;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) {
+                       /* i and v set */
+               } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) {
+                       /* i and v set */
+               } else
+                       return -EINVAL;
+               if (i >= 0 && i < 256 && v >= 0 && v < 256) {
+                       if (!acpi_ec_write(i, v))
+                               return -EIO;
+               } else
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct ibm_struct ecdump_driver_data = {
+       .name = "ecdump",
+       .read = ecdump_read,
+       .write = ecdump_write,
+       .flags.experimental = 1,
+};
+
+/*************************************************************************
+ * Backlight/brightness subdriver
+ */
+
+static struct backlight_device *ibm_backlight_device = NULL;
+
+static struct backlight_ops ibm_backlight_data = {
+        .get_brightness = brightness_get,
+        .update_status  = brightness_update_status,
+};
+
+static int __init brightness_init(struct ibm_init_struct *iibm)
+{
+       int b;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
+
+       b = brightness_get(NULL);
+       if (b < 0)
+               return b;
+
+       ibm_backlight_device = backlight_device_register(
+                                       TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL,
+                                       &ibm_backlight_data);
+       if (IS_ERR(ibm_backlight_device)) {
+               printk(IBM_ERR "Could not register backlight device\n");
+               return PTR_ERR(ibm_backlight_device);
+       }
+       vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n");
+
+       ibm_backlight_device->props.max_brightness = 7;
+       ibm_backlight_device->props.brightness = b;
+       backlight_update_status(ibm_backlight_device);
+
+       return 0;
+}
+
+static void brightness_exit(void)
+{
+       if (ibm_backlight_device) {
+               vdbg_printk(TPACPI_DBG_EXIT,
+                           "calling backlight_device_unregister()\n");
+               backlight_device_unregister(ibm_backlight_device);
+               ibm_backlight_device = NULL;
+       }
+}
+
+static int brightness_update_status(struct backlight_device *bd)
+{
+       return brightness_set(
+               (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+                bd->props.power == FB_BLANK_UNBLANK) ?
+                               bd->props.brightness : 0);
+}
+
+static int brightness_get(struct backlight_device *bd)
+{
+       u8 level;
+       if (!acpi_ec_read(brightness_offset, &level))
+               return -EIO;
+
+       level &= 0x7;
+
+       return level;
+}
+
+static int brightness_set(int value)
+{
+       int cmos_cmd, inc, i;
+       int current_value = brightness_get(NULL);
+
+       value &= 7;
+
+       cmos_cmd = value > current_value ? TP_CMOS_BRIGHTNESS_UP : TP_CMOS_BRIGHTNESS_DOWN;
+       inc = value > current_value ? 1 : -1;
+       for (i = current_value; i != value; i += inc) {
+               if (issue_thinkpad_cmos_command(cmos_cmd))
+                       return -EIO;
+               if (!acpi_ec_write(brightness_offset, i + inc))
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static int brightness_read(char *p)
+{
+       int len = 0;
+       int level;
+
+       if ((level = brightness_get(NULL)) < 0) {
+               len += sprintf(p + len, "level:\t\tunreadable\n");
+       } else {
+               len += sprintf(p + len, "level:\t\t%d\n", level & 0x7);
+               len += sprintf(p + len, "commands:\tup, down\n");
+               len += sprintf(p + len, "commands:\tlevel <level>"
+                              " (<level> is 0-7)\n");
+       }
+
+       return len;
+}
+
+static int brightness_write(char *buf)
+{
+       int level;
+       int new_level;
+       char *cmd;
+
+       while ((cmd = next_cmd(&buf))) {
+               if ((level = brightness_get(NULL)) < 0)
+                       return level;
+               level &= 7;
+
+               if (strlencmp(cmd, "up") == 0) {
+                       new_level = level == 7 ? 7 : level + 1;
+               } else if (strlencmp(cmd, "down") == 0) {
+                       new_level = level == 0 ? 0 : level - 1;
+               } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
+                          new_level >= 0 && new_level <= 7) {
+                       /* new_level set */
+               } else
+                       return -EINVAL;
+
+               brightness_set(new_level);
+       }
+
+       return 0;
+}
+
+static struct ibm_struct brightness_driver_data = {
+       .name = "brightness",
+       .read = brightness_read,
+       .write = brightness_write,
+       .exit = brightness_exit,
+};
+
+/*************************************************************************
+ * Volume subdriver
+ */
+
+static int volume_read(char *p)
+{
+       int len = 0;
+       u8 level;
+
+       if (!acpi_ec_read(volume_offset, &level)) {
+               len += sprintf(p + len, "level:\t\tunreadable\n");
+       } else {
+               len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
+               len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
+               len += sprintf(p + len, "commands:\tup, down, mute\n");
+               len += sprintf(p + len, "commands:\tlevel <level>"
+                              " (<level> is 0-15)\n");
+       }
+
+       return len;
+}
+
+static int volume_write(char *buf)
+{
+       int cmos_cmd, inc, i;
+       u8 level, mute;
+       int new_level, new_mute;
+       char *cmd;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (!acpi_ec_read(volume_offset, &level))
+                       return -EIO;
+               new_mute = mute = level & 0x40;
+               new_level = level = level & 0xf;
+
+               if (strlencmp(cmd, "up") == 0) {
+                       if (mute)
+                               new_mute = 0;
+                       else
+                               new_level = level == 15 ? 15 : level + 1;
+               } else if (strlencmp(cmd, "down") == 0) {
+                       if (mute)
+                               new_mute = 0;
+                       else
+                               new_level = level == 0 ? 0 : level - 1;
+               } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
+                          new_level >= 0 && new_level <= 15) {
+                       /* new_level set */
+               } else if (strlencmp(cmd, "mute") == 0) {
+                       new_mute = 0x40;
+               } else
+                       return -EINVAL;
+
+               if (new_level != level) {       /* mute doesn't change */
+                       cmos_cmd = new_level > level ? TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN;
+                       inc = new_level > level ? 1 : -1;
+
+                       if (mute && (issue_thinkpad_cmos_command(cmos_cmd) ||
+                                    !acpi_ec_write(volume_offset, level)))
+                               return -EIO;
+
+                       for (i = level; i != new_level; i += inc)
+                               if (issue_thinkpad_cmos_command(cmos_cmd) ||
+                                   !acpi_ec_write(volume_offset, i + inc))
+                                       return -EIO;
+
+                       if (mute && (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) ||
+                                    !acpi_ec_write(volume_offset,
+                                                   new_level + mute)))
+                               return -EIO;
+               }
+
+               if (new_mute != mute) { /* level doesn't change */
+                       cmos_cmd = new_mute ? TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
+
+                       if (issue_thinkpad_cmos_command(cmos_cmd) ||
+                           !acpi_ec_write(volume_offset, level + new_mute))
+                               return -EIO;
+               }
+       }
+
+       return 0;
+}
+
+static struct ibm_struct volume_driver_data = {
+       .name = "volume",
+       .read = volume_read,
+       .write = volume_write,
+};
+
+/*************************************************************************
+ * Fan subdriver
+ */
+
+/*
+ * FAN ACCESS MODES
+ *
+ * TPACPI_FAN_RD_ACPI_GFAN:
+ *     ACPI GFAN method: returns fan level
+ *
+ *     see TPACPI_FAN_WR_ACPI_SFAN
+ *     EC 0x2f (HFSP) not available if GFAN exists
+ *
+ * TPACPI_FAN_WR_ACPI_SFAN:
+ *     ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
+ *
+ *     EC 0x2f (HFSP) might be available *for reading*, but do not use
+ *     it for writing.
+ *
+ * TPACPI_FAN_WR_TPEC:
+ *     ThinkPad EC register 0x2f (HFSP): fan control loop mode
+ *     Supported on almost all ThinkPads
+ *
+ *     Fan speed changes of any sort (including those caused by the
+ *     disengaged mode) are usually done slowly by the firmware as the
+ *     maximum ammount of fan duty cycle change per second seems to be
+ *     limited.
+ *
+ *     Reading is not available if GFAN exists.
+ *     Writing is not available if SFAN exists.
+ *
+ *     Bits
+ *      7      automatic mode engaged;
+ *             (default operation mode of the ThinkPad)
+ *             fan level is ignored in this mode.
+ *      6      full speed mode (takes precedence over bit 7);
+ *             not available on all thinkpads.  May disable
+ *             the tachometer while the fan controller ramps up
+ *             the speed (which can take up to a few *minutes*).
+ *             Speeds up fan to 100% duty-cycle, which is far above
+ *             the standard RPM levels.  It is not impossible that
+ *             it could cause hardware damage.
+ *     5-3     unused in some models.  Extra bits for fan level
+ *             in others, but still useless as all values above
+ *             7 map to the same speed as level 7 in these models.
+ *     2-0     fan level (0..7 usually)
+ *                     0x00 = stop
+ *                     0x07 = max (set when temperatures critical)
+ *             Some ThinkPads may have other levels, see
+ *             TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
+ *
+ *     FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
+ *     boot. Apparently the EC does not intialize it, so unless ACPI DSDT
+ *     does so, its initial value is meaningless (0x07).
+ *
+ *     For firmware bugs, refer to:
+ *     http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
+ *
+ *     ----
+ *
+ *     ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
+ *     Main fan tachometer reading (in RPM)
+ *
+ *     This register is present on all ThinkPads with a new-style EC, and
+ *     it is known not to be present on the A21m/e, and T22, as there is
+ *     something else in offset 0x84 according to the ACPI DSDT.  Other
+ *     ThinkPads from this same time period (and earlier) probably lack the
+ *     tachometer as well.
+ *
+ *     Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare
+ *     was never fixed by IBM to report the EC firmware version string
+ *     probably support the tachometer (like the early X models), so
+ *     detecting it is quite hard.  We need more data to know for sure.
+ *
+ *     FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
+ *     might result.
+ *
+ *     FIRMWARE BUG: may go stale while the EC is switching to full speed
+ *     mode.
+ *
+ *     For firmware bugs, refer to:
+ *     http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
+ *
+ * TPACPI_FAN_WR_ACPI_FANS:
+ *     ThinkPad X31, X40, X41.  Not available in the X60.
+ *
+ *     FANS ACPI handle: takes three arguments: low speed, medium speed,
+ *     high speed.  ACPI DSDT seems to map these three speeds to levels
+ *     as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
+ *     (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
+ *
+ *     The speeds are stored on handles
+ *     (FANA:FAN9), (FANC:FANB), (FANE:FAND).
+ *
+ *     There are three default speed sets, acessible as handles:
+ *     FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
+ *
+ *     ACPI DSDT switches which set is in use depending on various
+ *     factors.
+ *
+ *     TPACPI_FAN_WR_TPEC is also available and should be used to
+ *     command the fan.  The X31/X40/X41 seems to have 8 fan levels,
+ *     but the ACPI tables just mention level 7.
+ */
+
+static enum fan_status_access_mode fan_status_access_mode;
+static enum fan_control_access_mode fan_control_access_mode;
+static enum fan_control_commands fan_control_commands;
+
+static u8 fan_control_initial_status;
+static u8 fan_control_desired_level;
+
+static void fan_watchdog_fire(struct work_struct *ignored);
+static int fan_watchdog_maxinterval;
+static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
+
+IBM_HANDLE(fans, ec, "FANS");  /* X31, X40, X41 */
+IBM_HANDLE(gfan, ec, "GFAN",   /* 570 */
+          "\\FSPD",            /* 600e/x, 770e, 770x */
+          );                   /* all others */
+IBM_HANDLE(sfan, ec, "SFAN",   /* 570 */
+          "JFNS",              /* 770x-JL */
+          );                   /* all others */
+
+/*
+ * SYSFS fan layout: hwmon compatible (device)
+ *
+ * pwm*_enable:
+ *     0: "disengaged" mode
+ *     1: manual mode
+ *     2: native EC "auto" mode (recommended, hardware default)
+ *
+ * pwm*: set speed in manual mode, ignored otherwise.
+ *     0 is level 0; 255 is level 7. Intermediate points done with linear
+ *     interpolation.
+ *
+ * fan*_input: tachometer reading, RPM
+ *
+ *
+ * SYSFS fan layout: extensions
+ *
+ * fan_watchdog (driver):
+ *     fan watchdog interval in seconds, 0 disables (default), max 120
+ */
+
+/* sysfs fan pwm1_enable ----------------------------------------------- */
+static ssize_t fan_pwm1_enable_show(struct device *dev,
+                                   struct device_attribute *attr,
+                                   char *buf)
+{
+       int res, mode;
+       u8 status;
+
+       res = fan_get_status_safe(&status);
+       if (res)
+               return res;
+
+       if (unlikely(tp_features.fan_ctrl_status_undef)) {
+               if (status != fan_control_initial_status) {
+                       tp_features.fan_ctrl_status_undef = 0;
+               } else {
+                       /* Return most likely status. In fact, it
+                        * might be the only possible status */
+                       status = TP_EC_FAN_AUTO;
+               }
+       }
+
+       if (status & TP_EC_FAN_FULLSPEED) {
+               mode = 0;
+       } else if (status & TP_EC_FAN_AUTO) {
+               mode = 2;
+       } else
+               mode = 1;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", mode);
+}
+
+static ssize_t fan_pwm1_enable_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       unsigned long t;
+       int res, level;
+
+       if (parse_strtoul(buf, 2, &t))
+               return -EINVAL;
+
+       switch (t) {
+       case 0:
+               level = TP_EC_FAN_FULLSPEED;
+               break;
+       case 1:
+               level = TPACPI_FAN_LAST_LEVEL;
+               break;
+       case 2:
+               level = TP_EC_FAN_AUTO;
+               break;
+       case 3:
+               /* reserved for software-controlled auto mode */
+               return -ENOSYS;
+       default:
+               return -EINVAL;
+       }
+
+       res = fan_set_level_safe(level);
+       if (res == -ENXIO)
+               return -EINVAL;
+       else if (res < 0)
+               return res;
+
+       fan_watchdog_reset();
+
+       return count;
+}
+
+static struct device_attribute dev_attr_fan_pwm1_enable =
+       __ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+               fan_pwm1_enable_show, fan_pwm1_enable_store);
+
+/* sysfs fan pwm1 ------------------------------------------------------ */
+static ssize_t fan_pwm1_show(struct device *dev,
+                            struct device_attribute *attr,
+                            char *buf)
+{
+       int res;
+       u8 status;
+
+       res = fan_get_status_safe(&status);
+       if (res)
+               return res;
+
+       if (unlikely(tp_features.fan_ctrl_status_undef)) {
+               if (status != fan_control_initial_status) {
+                       tp_features.fan_ctrl_status_undef = 0;
+               } else {
+                       status = TP_EC_FAN_AUTO;
+               }
+       }
+
+       if ((status &
+            (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
+               status = fan_control_desired_level;
+
+       if (status > 7)
+               status = 7;
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
+}
+
+static ssize_t fan_pwm1_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
+{
+       unsigned long s;
+       int rc;
+       u8 status, newlevel;
+
+       if (parse_strtoul(buf, 255, &s))
+               return -EINVAL;
+
+       /* scale down from 0-255 to 0-7 */
+       newlevel = (s >> 5) & 0x07;
+
+       rc = mutex_lock_interruptible(&fan_mutex);
+       if (rc < 0)
+               return rc;
+
+       rc = fan_get_status(&status);
+       if (!rc && (status &
+                   (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+               rc = fan_set_level(newlevel);
+               if (rc == -ENXIO)
+                       rc = -EINVAL;
+               else if (!rc) {
+                       fan_update_desired_level(newlevel);
+                       fan_watchdog_reset();
+               }
+       }
+
+       mutex_unlock(&fan_mutex);
+       return (rc)? rc : count;
+}
+
+static struct device_attribute dev_attr_fan_pwm1 =
+       __ATTR(pwm1, S_IWUSR | S_IRUGO,
+               fan_pwm1_show, fan_pwm1_store);
+
+/* sysfs fan fan1_input ------------------------------------------------ */
+static ssize_t fan_fan1_input_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       int res;
+       unsigned int speed;
+
+       res = fan_get_speed(&speed);
+       if (res < 0)
+               return res;
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", speed);
+}
+
+static struct device_attribute dev_attr_fan_fan1_input =
+       __ATTR(fan1_input, S_IRUGO,
+               fan_fan1_input_show, NULL);
+
+/* sysfs fan fan_watchdog (driver) ------------------------------------- */
+static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
+                                    char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
+}
+
+static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
+                                     const char *buf, size_t count)
+{
+       unsigned long t;
+
+       if (parse_strtoul(buf, 120, &t))
+               return -EINVAL;
+
+       if (!fan_control_allowed)
+               return -EPERM;
+
+       fan_watchdog_maxinterval = t;
+       fan_watchdog_reset();
+
+       return count;
+}
+
+static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
+               fan_fan_watchdog_show, fan_fan_watchdog_store);
+
+/* --------------------------------------------------------------------- */
+static struct attribute *fan_attributes[] = {
+       &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
+       &dev_attr_fan_fan1_input.attr,
+       NULL
+};
+
+static const struct attribute_group fan_attr_group = {
+       .attrs = fan_attributes,
+};
+
+static int __init fan_init(struct ibm_init_struct *iibm)
+{
+       int rc;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
+
+       mutex_init(&fan_mutex);
+       fan_status_access_mode = TPACPI_FAN_NONE;
+       fan_control_access_mode = TPACPI_FAN_WR_NONE;
+       fan_control_commands = 0;
+       fan_watchdog_maxinterval = 0;
+       tp_features.fan_ctrl_status_undef = 0;
+       fan_control_desired_level = 7;
+
+       IBM_ACPIHANDLE_INIT(fans);
+       IBM_ACPIHANDLE_INIT(gfan);
+       IBM_ACPIHANDLE_INIT(sfan);
+
+       if (gfan_handle) {
+               /* 570, 600e/x, 770e, 770x */
+               fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
+       } else {
+               /* all other ThinkPads: note that even old-style
+                * ThinkPad ECs supports the fan control register */
+               if (likely(acpi_ec_read(fan_status_offset,
+                                       &fan_control_initial_status))) {
+                       fan_status_access_mode = TPACPI_FAN_RD_TPEC;
+
+                       /* In some ThinkPads, neither the EC nor the ACPI
+                        * DSDT initialize the fan status, and it ends up
+                        * being set to 0x07 when it *could* be either
+                        * 0x07 or 0x80.
+                        *
+                        * Enable for TP-1Y (T43), TP-78 (R51e),
+                        * TP-76 (R52), TP-70 (T43, R52), which are known
+                        * to be buggy. */
+                       if (fan_control_initial_status == 0x07 &&
+                           ibm_thinkpad_ec_found &&
+                           ((ibm_thinkpad_ec_found[0] == '1' &&
+                             ibm_thinkpad_ec_found[1] == 'Y') ||
+                            (ibm_thinkpad_ec_found[0] == '7' &&
+                             (ibm_thinkpad_ec_found[1] == '6' ||
+                              ibm_thinkpad_ec_found[1] == '8' ||
+                              ibm_thinkpad_ec_found[1] == '0'))
+                           )) {
+                               printk(IBM_NOTICE
+                                      "fan_init: initial fan status is "
+                                      "unknown, assuming it is in auto "
+                                      "mode\n");
+                               tp_features.fan_ctrl_status_undef = 1;
+                       }
+               } else {
+                       printk(IBM_ERR
+                              "ThinkPad ACPI EC access misbehaving, "
+                              "fan status and control unavailable\n");
+                       return 1;
+               }
+       }
+
+       if (sfan_handle) {
+               /* 570, 770x-JL */
+               fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
+               fan_control_commands |=
+                   TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
+       } else {
+               if (!gfan_handle) {
+                       /* gfan without sfan means no fan control */
+                       /* all other models implement TP EC 0x2f control */
+
+                       if (fans_handle) {
+                               /* X31, X40, X41 */
+                               fan_control_access_mode =
+                                   TPACPI_FAN_WR_ACPI_FANS;
+                               fan_control_commands |=
+                                   TPACPI_FAN_CMD_SPEED |
+                                   TPACPI_FAN_CMD_LEVEL |
+                                   TPACPI_FAN_CMD_ENABLE;
+                       } else {
+                               fan_control_access_mode = TPACPI_FAN_WR_TPEC;
+                               fan_control_commands |=
+                                   TPACPI_FAN_CMD_LEVEL |
+                                   TPACPI_FAN_CMD_ENABLE;
+                       }
+               }
+       }
+
+       vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
+               str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
+                 fan_control_access_mode != TPACPI_FAN_WR_NONE),
+               fan_status_access_mode, fan_control_access_mode);
+
+       /* fan control master switch */
+       if (!fan_control_allowed) {
+               fan_control_access_mode = TPACPI_FAN_WR_NONE;
+               fan_control_commands = 0;
+               dbg_printk(TPACPI_DBG_INIT,
+                          "fan control features disabled by parameter\n");
+       }
+
+       /* update fan_control_desired_level */
+       if (fan_status_access_mode != TPACPI_FAN_NONE)
+               fan_get_status_safe(NULL);
+
+       if (fan_status_access_mode != TPACPI_FAN_NONE ||
+           fan_control_access_mode != TPACPI_FAN_WR_NONE) {
+               rc = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                                        &fan_attr_group);
+               if (!(rc < 0))
+                       rc = driver_create_file(&tpacpi_pdriver.driver,
+                                       &driver_attr_fan_watchdog);
+               if (rc < 0)
+                       return rc;
+               return 0;
+       } else
+               return 1;
+}
+
+/*
+ * Call with fan_mutex held
+ */
+static void fan_update_desired_level(u8 status)
+{
+       if ((status &
+            (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+               if (status > 7)
+                       fan_control_desired_level = 7;
+               else
+                       fan_control_desired_level = status;
+       }
+}
+
+static int fan_get_status(u8 *status)
+{
+       u8 s;
+
+       /* TODO:
+        * Add TPACPI_FAN_RD_ACPI_FANS ? */
+
+       switch (fan_status_access_mode) {
+       case TPACPI_FAN_RD_ACPI_GFAN:
+               /* 570, 600e/x, 770e, 770x */
+
+               if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
+                       return -EIO;
+
+               if (likely(status))
+                       *status = s & 0x07;
+
+               break;
+
+       case TPACPI_FAN_RD_TPEC:
+               /* all except 570, 600e/x, 770e, 770x */
+               if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
+                       return -EIO;
+
+               if (likely(status))
+                       *status = s;
+
+               break;
+
+       default:
+               return -ENXIO;
+       }
+
+       return 0;
+}
+
+static int fan_get_status_safe(u8 *status)
+{
+       int rc;
+       u8 s;
+
+       rc = mutex_lock_interruptible(&fan_mutex);
+       if (rc < 0)
+               return rc;
+       rc = fan_get_status(&s);
+       if (!rc)
+               fan_update_desired_level(s);
+       mutex_unlock(&fan_mutex);
+
+       if (status)
+               *status = s;
+
+       return rc;
+}
+
+static void fan_exit(void)
+{
+       vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n");
+
+       /* FIXME: can we really do this unconditionally? */
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj, &fan_attr_group);
+       driver_remove_file(&tpacpi_pdriver.driver, &driver_attr_fan_watchdog);
+
+       cancel_delayed_work(&fan_watchdog_task);
+       flush_scheduled_work();
+}
+
+static int fan_get_speed(unsigned int *speed)
+{
+       u8 hi, lo;
+
+       switch (fan_status_access_mode) {
+       case TPACPI_FAN_RD_TPEC:
+               /* all except 570, 600e/x, 770e, 770x */
+               if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
+                            !acpi_ec_read(fan_rpm_offset + 1, &hi)))
+                       return -EIO;
+
+               if (likely(speed))
+                       *speed = (hi << 8) | lo;
+
+               break;
+
+       default:
+               return -ENXIO;
+       }
+
+       return 0;
+}
+
+static void fan_watchdog_fire(struct work_struct *ignored)
+{
+       int rc;
+
+       printk(IBM_NOTICE "fan watchdog: enabling fan\n");
+       rc = fan_set_enable();
+       if (rc < 0) {
+               printk(IBM_ERR "fan watchdog: error %d while enabling fan, "
+                       "will try again later...\n", -rc);
+               /* reschedule for later */
+               fan_watchdog_reset();
+       }
+}
+
+static void fan_watchdog_reset(void)
+{
+       static int fan_watchdog_active = 0;
+
+       if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
+               return;
+
+       if (fan_watchdog_active)
+               cancel_delayed_work(&fan_watchdog_task);
+
+       if (fan_watchdog_maxinterval > 0) {
+               fan_watchdog_active = 1;
+               if (!schedule_delayed_work(&fan_watchdog_task,
+                               msecs_to_jiffies(fan_watchdog_maxinterval
+                                                * 1000))) {
+                       printk(IBM_ERR "failed to schedule the fan watchdog, "
+                              "watchdog will not trigger\n");
+               }
+       } else
+               fan_watchdog_active = 0;
+}
+
+static int fan_set_level(int level)
+{
+       if (!fan_control_allowed)
+               return -EPERM;
+
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               if (level >= 0 && level <= 7) {
+                       if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
+                               return -EIO;
+               } else
+                       return -EINVAL;
+               break;
+
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               if ((level != TP_EC_FAN_AUTO) &&
+                   (level != TP_EC_FAN_FULLSPEED) &&
+                   ((level < 0) || (level > 7)))
+                       return -EINVAL;
+
+               /* safety net should the EC not support AUTO
+                * or FULLSPEED mode bits and just ignore them */
+               if (level & TP_EC_FAN_FULLSPEED)
+                       level |= 7;     /* safety min speed 7 */
+               else if (level & TP_EC_FAN_FULLSPEED)
+                       level |= 4;     /* safety min speed 4 */
+
+               if (!acpi_ec_write(fan_status_offset, level))
+                       return -EIO;
+               else
+                       tp_features.fan_ctrl_status_undef = 0;
+               break;
+
+       default:
+               return -ENXIO;
+       }
+       return 0;
+}
+
+static int fan_set_level_safe(int level)
+{
+       int rc;
+
+       if (!fan_control_allowed)
+               return -EPERM;
+
+       rc = mutex_lock_interruptible(&fan_mutex);
+       if (rc < 0)
+               return rc;
+
+       if (level == TPACPI_FAN_LAST_LEVEL)
+               level = fan_control_desired_level;
+
+       rc = fan_set_level(level);
+       if (!rc)
+               fan_update_desired_level(level);
+
+       mutex_unlock(&fan_mutex);
+       return rc;
+}
+
+static int fan_set_enable(void)
+{
+       u8 s;
+       int rc;
+
+       if (!fan_control_allowed)
+               return -EPERM;
+
+       rc = mutex_lock_interruptible(&fan_mutex);
+       if (rc < 0)
+               return rc;
+
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               rc = fan_get_status(&s);
+               if (rc < 0)
+                       break;
+
+               /* Don't go out of emergency fan mode */
+               if (s != 7) {
+                       s &= 0x07;
+                       s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+               }
+
+               if (!acpi_ec_write(fan_status_offset, s))
+                       rc = -EIO;
+               else {
+                       tp_features.fan_ctrl_status_undef = 0;
+                       rc = 0;
+               }
+               break;
+
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               rc = fan_get_status(&s);
+               if (rc < 0)
+                       break;
+
+               s &= 0x07;
+
+               /* Set fan to at least level 4 */
+               s |= 4;
+
+               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
+                       rc= -EIO;
+               else
+                       rc = 0;
+               break;
+
+       default:
+               rc = -ENXIO;
+       }
+
+       mutex_unlock(&fan_mutex);
+       return rc;
+}
+
+static int fan_set_disable(void)
+{
+       int rc;
+
+       if (!fan_control_allowed)
+               return -EPERM;
+
+       rc = mutex_lock_interruptible(&fan_mutex);
+       if (rc < 0)
+               return rc;
+
+       rc = 0;
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               if (!acpi_ec_write(fan_status_offset, 0x00))
+                       rc = -EIO;
+               else {
+                       fan_control_desired_level = 0;
+                       tp_features.fan_ctrl_status_undef = 0;
+               }
+               break;
+
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
+                       rc = -EIO;
+               else
+                       fan_control_desired_level = 0;
+               break;
+
+       default:
+               rc = -ENXIO;
+       }
+
+
+       mutex_unlock(&fan_mutex);
+       return rc;
+}
+
+static int fan_set_speed(int speed)
+{
+       int rc;
+
+       if (!fan_control_allowed)
+               return -EPERM;
+
+       rc = mutex_lock_interruptible(&fan_mutex);
+       if (rc < 0)
+               return rc;
+
+       rc = 0;
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_FANS:
+               if (speed >= 0 && speed <= 65535) {
+                       if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
+                                       speed, speed, speed))
+                               rc = -EIO;
+               } else
+                       rc = -EINVAL;
+               break;
+
+       default:
+               rc = -ENXIO;
+       }
+
+       mutex_unlock(&fan_mutex);
+       return rc;
+}
+
+static int fan_read(char *p)
+{
+       int len = 0;
+       int rc;
+       u8 status;
+       unsigned int speed = 0;
+
+       switch (fan_status_access_mode) {
+       case TPACPI_FAN_RD_ACPI_GFAN:
+               /* 570, 600e/x, 770e, 770x */
+               if ((rc = fan_get_status_safe(&status)) < 0)
+                       return rc;
+
+               len += sprintf(p + len, "status:\t\t%s\n"
+                              "level:\t\t%d\n",
+                              (status != 0) ? "enabled" : "disabled", status);
+               break;
+
+       case TPACPI_FAN_RD_TPEC:
+               /* all except 570, 600e/x, 770e, 770x */
+               if ((rc = fan_get_status_safe(&status)) < 0)
+                       return rc;
+
+               if (unlikely(tp_features.fan_ctrl_status_undef)) {
+                       if (status != fan_control_initial_status)
+                               tp_features.fan_ctrl_status_undef = 0;
+                       else
+                               /* Return most likely status. In fact, it
+                                * might be the only possible status */
+                               status = TP_EC_FAN_AUTO;
+               }
+
+               len += sprintf(p + len, "status:\t\t%s\n",
+                              (status != 0) ? "enabled" : "disabled");
+
+               if ((rc = fan_get_speed(&speed)) < 0)
+                       return rc;
+
+               len += sprintf(p + len, "speed:\t\t%d\n", speed);
+
+               if (status & TP_EC_FAN_FULLSPEED)
+                       /* Disengaged mode takes precedence */
+                       len += sprintf(p + len, "level:\t\tdisengaged\n");
+               else if (status & TP_EC_FAN_AUTO)
+                       len += sprintf(p + len, "level:\t\tauto\n");
+               else
+                       len += sprintf(p + len, "level:\t\t%d\n", status);
+               break;
+
+       case TPACPI_FAN_NONE:
+       default:
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+       }
+
+       if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
+               len += sprintf(p + len, "commands:\tlevel <level>");
+
+               switch (fan_control_access_mode) {
+               case TPACPI_FAN_WR_ACPI_SFAN:
+                       len += sprintf(p + len, " (<level> is 0-7)\n");
+                       break;
+
+               default:
+                       len += sprintf(p + len, " (<level> is 0-7, "
+                                      "auto, disengaged, full-speed)\n");
+                       break;
+               }
+       }
+
+       if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
+               len += sprintf(p + len, "commands:\tenable, disable\n"
+                              "commands:\twatchdog <timeout> (<timeout> is 0 (off), "
+                              "1-120 (seconds))\n");
+
+       if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
+               len += sprintf(p + len, "commands:\tspeed <speed>"
+                              " (<speed> is 0-65535)\n");
+
+       return len;
+}
+
+static int fan_write_cmd_level(const char *cmd, int *rc)
+{
+       int level;
+
+       if (strlencmp(cmd, "level auto") == 0)
+               level = TP_EC_FAN_AUTO;
+       else if ((strlencmp(cmd, "level disengaged") == 0) |
+                (strlencmp(cmd, "level full-speed") == 0))
+               level = TP_EC_FAN_FULLSPEED;
+       else if (sscanf(cmd, "level %d", &level) != 1)
+               return 0;
+
+       if ((*rc = fan_set_level_safe(level)) == -ENXIO)
+               printk(IBM_ERR "level command accepted for unsupported "
+                      "access mode %d", fan_control_access_mode);
+
+       return 1;
+}
+
+static int fan_write_cmd_enable(const char *cmd, int *rc)
+{
+       if (strlencmp(cmd, "enable") != 0)
+               return 0;
+
+       if ((*rc = fan_set_enable()) == -ENXIO)
+               printk(IBM_ERR "enable command accepted for unsupported "
+                      "access mode %d", fan_control_access_mode);
+
+       return 1;
+}
+
+static int fan_write_cmd_disable(const char *cmd, int *rc)
+{
+       if (strlencmp(cmd, "disable") != 0)
+               return 0;
+
+       if ((*rc = fan_set_disable()) == -ENXIO)
+               printk(IBM_ERR "disable command accepted for unsupported "
+                      "access mode %d", fan_control_access_mode);
+
+       return 1;
+}
+
+static int fan_write_cmd_speed(const char *cmd, int *rc)
+{
+       int speed;
+
+       /* TODO:
+        * Support speed <low> <medium> <high> ? */
+
+       if (sscanf(cmd, "speed %d", &speed) != 1)
+               return 0;
+
+       if ((*rc = fan_set_speed(speed)) == -ENXIO)
+               printk(IBM_ERR "speed command accepted for unsupported "
+                      "access mode %d", fan_control_access_mode);
+
+       return 1;
+}
+
+static int fan_write_cmd_watchdog(const char *cmd, int *rc)
+{
+       int interval;
+
+       if (sscanf(cmd, "watchdog %d", &interval) != 1)
+               return 0;
+
+       if (interval < 0 || interval > 120)
+               *rc = -EINVAL;
+       else
+               fan_watchdog_maxinterval = interval;
+
+       return 1;
+}
+
+static int fan_write(char *buf)
+{
+       char *cmd;
+       int rc = 0;
+
+       while (!rc && (cmd = next_cmd(&buf))) {
+               if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) &&
+                     fan_write_cmd_level(cmd, &rc)) &&
+                   !((fan_control_commands & TPACPI_FAN_CMD_ENABLE) &&
+                     (fan_write_cmd_enable(cmd, &rc) ||
+                      fan_write_cmd_disable(cmd, &rc) ||
+                      fan_write_cmd_watchdog(cmd, &rc))) &&
+                   !((fan_control_commands & TPACPI_FAN_CMD_SPEED) &&
+                     fan_write_cmd_speed(cmd, &rc))
+                   )
+                       rc = -EINVAL;
+               else if (!rc)
+                       fan_watchdog_reset();
+       }
+
+       return rc;
+}
+
+static struct ibm_struct fan_driver_data = {
+       .name = "fan",
+       .read = fan_read,
+       .write = fan_write,
+       .exit = fan_exit,
+};
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Infrastructure
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/* /proc support */
+static struct proc_dir_entry *proc_dir = NULL;
+
+/* Subdriver registry */
+static LIST_HEAD(tpacpi_all_drivers);
+
+
+/*
+ * Module and infrastructure proble, init and exit handling
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUG
+static const char * __init str_supported(int is_supported)
+{
+       static char text_unsupported[] __initdata = "not supported";
+
+       return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
+}
+#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
+
+static int __init ibm_init(struct ibm_init_struct *iibm)
+{
+       int ret;
+       struct ibm_struct *ibm = iibm->data;
+       struct proc_dir_entry *entry;
+
+       BUG_ON(ibm == NULL);
+
+       INIT_LIST_HEAD(&ibm->all_drivers);
+
+       if (ibm->flags.experimental && !experimental)
+               return 0;
+
+       dbg_printk(TPACPI_DBG_INIT,
+               "probing for %s\n", ibm->name);
+
+       if (iibm->init) {
+               ret = iibm->init(iibm);
+               if (ret > 0)
+                       return 0;       /* probe failed */
+               if (ret)
+                       return ret;
+
+               ibm->flags.init_called = 1;
+       }
+
+       if (ibm->acpi) {
+               if (ibm->acpi->hid) {
+                       ret = register_tpacpi_subdriver(ibm);
+                       if (ret)
+                               goto err_out;
+               }
+
+               if (ibm->acpi->notify) {
+                       ret = setup_acpi_notify(ibm);
+                       if (ret == -ENODEV) {
+                               printk(IBM_NOTICE "disabling subdriver %s\n",
+                                       ibm->name);
+                               ret = 0;
+                               goto err_out;
+                       }
+                       if (ret < 0)
+                               goto err_out;
+               }
+       }
+
+       dbg_printk(TPACPI_DBG_INIT,
+               "%s installed\n", ibm->name);
+
+       if (ibm->read) {
+               entry = create_proc_entry(ibm->name,
+                                         S_IFREG | S_IRUGO | S_IWUSR,
+                                         proc_dir);
+               if (!entry) {
+                       printk(IBM_ERR "unable to create proc entry %s\n",
+                              ibm->name);
+                       ret = -ENODEV;
+                       goto err_out;
+               }
+               entry->owner = THIS_MODULE;
+               entry->data = ibm;
+               entry->read_proc = &dispatch_procfs_read;
+               if (ibm->write)
+                       entry->write_proc = &dispatch_procfs_write;
+               ibm->flags.proc_created = 1;
+       }
+
+       list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers);
+
+       return 0;
+
+err_out:
+       dbg_printk(TPACPI_DBG_INIT,
+               "%s: at error exit path with result %d\n",
+               ibm->name, ret);
+
+       ibm_exit(ibm);
+       return (ret < 0)? ret : 0;
+}
+
+static void ibm_exit(struct ibm_struct *ibm)
+{
+       dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
+
+       list_del_init(&ibm->all_drivers);
+
+       if (ibm->flags.acpi_notify_installed) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                       "%s: acpi_remove_notify_handler\n", ibm->name);
+               BUG_ON(!ibm->acpi);
+               acpi_remove_notify_handler(*ibm->acpi->handle,
+                                          ibm->acpi->type,
+                                          dispatch_acpi_notify);
+               ibm->flags.acpi_notify_installed = 0;
+               ibm->flags.acpi_notify_installed = 0;
+       }
+
+       if (ibm->flags.proc_created) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                       "%s: remove_proc_entry\n", ibm->name);
+               remove_proc_entry(ibm->name, proc_dir);
+               ibm->flags.proc_created = 0;
+       }
+
+       if (ibm->flags.acpi_driver_registered) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                       "%s: acpi_bus_unregister_driver\n", ibm->name);
+               BUG_ON(!ibm->acpi);
+               acpi_bus_unregister_driver(ibm->acpi->driver);
+               kfree(ibm->acpi->driver);
+               ibm->acpi->driver = NULL;
+               ibm->flags.acpi_driver_registered = 0;
+       }
+
+       if (ibm->flags.init_called && ibm->exit) {
+               ibm->exit();
+               ibm->flags.init_called = 0;
+       }
+
+       dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
+}
+
+/* Probing */
+
+static char *ibm_thinkpad_ec_found = NULL;
+
+static char* __init check_dmi_for_ec(void)
+{
+       struct dmi_device *dev = NULL;
+       char ec_fw_string[18];
+
+       /*
+        * ThinkPad T23 or newer, A31 or newer, R50e or newer,
+        * X32 or newer, all Z series;  Some models must have an
+        * up-to-date BIOS or they will not be detected.
+        *
+        * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+        */
+       while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+               if (sscanf(dev->name,
+                          "IBM ThinkPad Embedded Controller -[%17c",
+                          ec_fw_string) == 1) {
+                       ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
+                       ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
+                       return kstrdup(ec_fw_string, GFP_KERNEL);
+               }
+       }
+       return NULL;
+}
+
+static int __init probe_for_thinkpad(void)
+{
+       int is_thinkpad;
+
+       if (acpi_disabled)
+               return -ENODEV;
+
+       /*
+        * Non-ancient models have better DMI tagging, but very old models
+        * don't.
+        */
+       is_thinkpad = dmi_name_in_vendors("ThinkPad");
+
+       /* ec is required because many other handles are relative to it */
+       IBM_ACPIHANDLE_INIT(ec);
+       if (!ec_handle) {
+               if (is_thinkpad)
+                       printk(IBM_ERR
+                               "Not yet supported ThinkPad detected!\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Risks a regression on very old machines, but reduces potential
+        * false positives a damn great deal
+        */
+       if (!is_thinkpad)
+               is_thinkpad = dmi_name_in_vendors("IBM");
+
+       if (!is_thinkpad && !force_load)
+               return -ENODEV;
+
+       return 0;
+}
+
+
+/* Module init, exit, parameters */
+
+static struct ibm_init_struct ibms_init[] __initdata = {
+       {
+               .init = thinkpad_acpi_driver_init,
+               .data = &thinkpad_acpi_driver_data,
+       },
+       {
+               .init = hotkey_init,
+               .data = &hotkey_driver_data,
+       },
+       {
+               .init = bluetooth_init,
+               .data = &bluetooth_driver_data,
+       },
+       {
+               .init = wan_init,
+               .data = &wan_driver_data,
+       },
+       {
+               .init = video_init,
+               .data = &video_driver_data,
+       },
+       {
+               .init = light_init,
+               .data = &light_driver_data,
+       },
+#ifdef CONFIG_THINKPAD_ACPI_DOCK
+       {
+               .init = dock_init,
+               .data = &dock_driver_data[0],
+       },
+       {
+               .init = dock_init2,
+               .data = &dock_driver_data[1],
+       },
+#endif
+#ifdef CONFIG_THINKPAD_ACPI_BAY
+       {
+               .init = bay_init,
+               .data = &bay_driver_data,
+       },
+#endif
+       {
+               .init = cmos_init,
+               .data = &cmos_driver_data,
+       },
+       {
+               .init = led_init,
+               .data = &led_driver_data,
+       },
+       {
+               .init = beep_init,
+               .data = &beep_driver_data,
+       },
+       {
+               .init = thermal_init,
+               .data = &thermal_driver_data,
+       },
+       {
+               .data = &ecdump_driver_data,
+       },
+       {
+               .init = brightness_init,
+               .data = &brightness_driver_data,
+       },
+       {
+               .data = &volume_driver_data,
+       },
+       {
+               .init = fan_init,
+               .data = &fan_driver_data,
+       },
+};
+
+static int __init set_ibm_param(const char *val, struct kernel_param *kp)
+{
+       unsigned int i;
+       struct ibm_struct *ibm;
+
+       for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
+               ibm = ibms_init[i].data;
+               BUG_ON(ibm == NULL);
+
+               if (strcmp(ibm->name, kp->name) == 0 && ibm->write) {
+                       if (strlen(val) > sizeof(ibms_init[i].param) - 2)
+                               return -ENOSPC;
+                       strcpy(ibms_init[i].param, val);
+                       strcat(ibms_init[i].param, ",");
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
+static int experimental;
+module_param(experimental, int, 0);
+
+static u32 dbg_level;
+module_param_named(debug, dbg_level, uint, 0);
+
+static int force_load;
+module_param(force_load, int, 0);
+
+static int fan_control_allowed;
+module_param_named(fan_control, fan_control_allowed, int, 0);
+
+#define IBM_PARAM(feature) \
+       module_param_call(feature, set_ibm_param, NULL, NULL, 0)
+
+IBM_PARAM(hotkey);
+IBM_PARAM(bluetooth);
+IBM_PARAM(video);
+IBM_PARAM(light);
+#ifdef CONFIG_THINKPAD_ACPI_DOCK
+IBM_PARAM(dock);
+#endif
+#ifdef CONFIG_THINKPAD_ACPI_BAY
+IBM_PARAM(bay);
+#endif /* CONFIG_THINKPAD_ACPI_BAY */
+IBM_PARAM(cmos);
+IBM_PARAM(led);
+IBM_PARAM(beep);
+IBM_PARAM(ecdump);
+IBM_PARAM(brightness);
+IBM_PARAM(volume);
+IBM_PARAM(fan);
+
+static int __init thinkpad_acpi_module_init(void)
+{
+       int ret, i;
+
+       /* Driver-level probe */
+       ret = probe_for_thinkpad();
+       if (ret)
+               return ret;
+
+       /* Driver initialization */
+       ibm_thinkpad_ec_found = check_dmi_for_ec();
+       IBM_ACPIHANDLE_INIT(ecrd);
+       IBM_ACPIHANDLE_INIT(ecwr);
+
+       proc_dir = proc_mkdir(IBM_PROC_DIR, acpi_root_dir);
+       if (!proc_dir) {
+               printk(IBM_ERR "unable to create proc dir " IBM_PROC_DIR);
+               thinkpad_acpi_module_exit();
+               return -ENODEV;
+       }
+       proc_dir->owner = THIS_MODULE;
+
+       ret = platform_driver_register(&tpacpi_pdriver);
+       if (ret) {
+               printk(IBM_ERR "unable to register platform driver\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
+       if (ret) {
+               printk(IBM_ERR "unable to create sysfs driver attributes\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+
+
+       /* Device initialization */
+       tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1,
+                                                       NULL, 0);
+       if (IS_ERR(tpacpi_pdev)) {
+               ret = PTR_ERR(tpacpi_pdev);
+               tpacpi_pdev = NULL;
+               printk(IBM_ERR "unable to register platform device\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       tpacpi_hwmon = hwmon_device_register(&tpacpi_pdev->dev);
+       if (IS_ERR(tpacpi_hwmon)) {
+               ret = PTR_ERR(tpacpi_hwmon);
+               tpacpi_hwmon = NULL;
+               printk(IBM_ERR "unable to register hwmon device\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
+               ret = ibm_init(&ibms_init[i]);
+               if (ret >= 0 && *ibms_init[i].param)
+                       ret = ibms_init[i].data->write(ibms_init[i].param);
+               if (ret < 0) {
+                       thinkpad_acpi_module_exit();
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static void thinkpad_acpi_module_exit(void)
+{
+       struct ibm_struct *ibm, *itmp;
+
+       list_for_each_entry_safe_reverse(ibm, itmp,
+                                        &tpacpi_all_drivers,
+                                        all_drivers) {
+               ibm_exit(ibm);
+       }
+
+       dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
+
+       if (tpacpi_hwmon)
+               hwmon_device_unregister(tpacpi_hwmon);
+
+       if (tpacpi_pdev)
+               platform_device_unregister(tpacpi_pdev);
+
+       tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
+       platform_driver_unregister(&tpacpi_pdriver);
+
+       if (proc_dir)
+               remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);
+
+       kfree(ibm_thinkpad_ec_found);
+}
+
+module_init(thinkpad_acpi_module_init);
+module_exit(thinkpad_acpi_module_exit);
diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h
new file mode 100644 (file)
index 0000000..440145a
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ *  thinkpad_acpi.h - ThinkPad ACPI Extras
+ *
+ *
+ *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
+ *  Copyright (C) 2006-2007 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#ifndef __THINKPAD_ACPI_H__
+#define __THINKPAD_ACPI_H__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+
+#include <linux/proc_fs.h>
+#include <linux/sysfs.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <asm/uaccess.h>
+
+#include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+
+#include <acpi/acpi_drivers.h>
+#include <acpi/acnamesp.h>
+
+
+/****************************************************************************
+ * Main driver
+ */
+
+#define IBM_NAME "thinkpad"
+#define IBM_DESC "ThinkPad ACPI Extras"
+#define IBM_FILE "thinkpad_acpi"
+#define IBM_URL "http://ibm-acpi.sf.net/"
+#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net"
+
+#define IBM_PROC_DIR "ibm"
+#define IBM_ACPI_EVENT_PREFIX "ibm"
+#define IBM_DRVR_NAME IBM_FILE
+
+#define IBM_LOG IBM_FILE ": "
+#define IBM_ERR           KERN_ERR    IBM_LOG
+#define IBM_NOTICE KERN_NOTICE IBM_LOG
+#define IBM_INFO   KERN_INFO   IBM_LOG
+#define IBM_DEBUG  KERN_DEBUG  IBM_LOG
+
+#define IBM_MAX_ACPI_ARGS 3
+
+/* ThinkPad CMOS commands */
+#define TP_CMOS_VOLUME_DOWN    0
+#define TP_CMOS_VOLUME_UP      1
+#define TP_CMOS_VOLUME_MUTE    2
+#define TP_CMOS_BRIGHTNESS_UP  4
+#define TP_CMOS_BRIGHTNESS_DOWN        5
+
+#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
+#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
+#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
+
+/* Debugging */
+#define TPACPI_DBG_ALL         0xffff
+#define TPACPI_DBG_ALL         0xffff
+#define TPACPI_DBG_INIT                0x0001
+#define TPACPI_DBG_EXIT                0x0002
+#define dbg_printk(a_dbg_level, format, arg...) \
+       do { if (dbg_level & a_dbg_level) \
+               printk(IBM_DEBUG "%s: " format, __func__ , ## arg); } while (0)
+#ifdef CONFIG_THINKPAD_ACPI_DEBUG
+#define vdbg_printk(a_dbg_level, format, arg...) \
+       dbg_printk(a_dbg_level, format, ## arg)
+static const char *str_supported(int is_supported);
+#else
+#define vdbg_printk(a_dbg_level, format, arg...)
+#endif
+
+/* ACPI HIDs */
+#define IBM_HKEY_HID    "IBM0068"
+#define IBM_PCI_HID     "PNP0A03"
+
+/* ACPI helpers */
+static int __must_check acpi_evalf(acpi_handle handle,
+                     void *res, char *method, char *fmt, ...);
+static int __must_check acpi_ec_read(int i, u8 * p);
+static int __must_check acpi_ec_write(int i, u8 v);
+static int __must_check _sta(acpi_handle handle);
+
+/* ACPI handles */
+static acpi_handle root_handle;                        /* root namespace */
+static acpi_handle ec_handle;                  /* EC */
+static acpi_handle ecrd_handle, ecwr_handle;   /* 570 EC access */
+static acpi_handle cmos_handle, hkey_handle;   /* basic thinkpad handles */
+
+static void drv_acpi_handle_init(char *name,
+                  acpi_handle *handle, acpi_handle parent,
+                  char **paths, int num_paths, char **path);
+#define IBM_ACPIHANDLE_INIT(object)                                            \
+       drv_acpi_handle_init(#object, &object##_handle, *object##_parent,       \
+               object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
+
+/* ThinkPad ACPI helpers */
+static int issue_thinkpad_cmos_command(int cmos_cmd);
+
+/* procfs support */
+static struct proc_dir_entry *proc_dir;
+
+/* procfs helpers */
+static int dispatch_procfs_read(char *page, char **start, off_t off,
+               int count, int *eof, void *data);
+static int dispatch_procfs_write(struct file *file,
+               const char __user * userbuf,
+               unsigned long count, void *data);
+static char *next_cmd(char **cmds);
+
+/* sysfs support */
+struct attribute_set {
+       unsigned int members, max_members;
+       struct attribute_group group;
+};
+
+static struct attribute_set *create_attr_set(unsigned int max_members,
+                                               const char* name);
+#define destroy_attr_set(_set) \
+       kfree(_set);
+static int add_to_attr_set(struct attribute_set* s, struct attribute *attr);
+static int add_many_to_attr_set(struct attribute_set* s,
+                       struct attribute **attr,
+                       unsigned int count);
+#define register_attr_set_with_sysfs(_attr_set, _kobj) \
+       sysfs_create_group(_kobj, &_attr_set->group)
+static void delete_attr_set(struct attribute_set* s, struct kobject *kobj);
+
+static int parse_strtoul(const char *buf, unsigned long max,
+                       unsigned long *value);
+
+/* Device model */
+static struct platform_device *tpacpi_pdev;
+static struct class_device *tpacpi_hwmon;
+static struct platform_driver tpacpi_pdriver;
+static int tpacpi_create_driver_attributes(struct device_driver *drv);
+static void tpacpi_remove_driver_attributes(struct device_driver *drv);
+
+/* Module */
+static int experimental;
+static u32 dbg_level;
+static int force_load;
+static char *ibm_thinkpad_ec_found;
+
+static char* check_dmi_for_ec(void);
+static int thinkpad_acpi_module_init(void);
+static void thinkpad_acpi_module_exit(void);
+
+
+/****************************************************************************
+ * Subdrivers
+ */
+
+struct ibm_struct;
+
+struct tp_acpi_drv_struct {
+       char *hid;
+       struct acpi_driver *driver;
+
+       void (*notify) (struct ibm_struct *, u32);
+       acpi_handle *handle;
+       u32 type;
+       struct acpi_device *device;
+};
+
+struct ibm_struct {
+       char *name;
+
+       int (*read) (char *);
+       int (*write) (char *);
+       void (*exit) (void);
+
+       struct list_head all_drivers;
+
+       struct tp_acpi_drv_struct *acpi;
+
+       struct {
+               u8 acpi_driver_registered:1;
+               u8 acpi_notify_installed:1;
+               u8 proc_created:1;
+               u8 init_called:1;
+               u8 experimental:1;
+       } flags;
+};
+
+struct ibm_init_struct {
+       char param[32];
+
+       int (*init) (struct ibm_init_struct *);
+       struct ibm_struct *data;
+};
+
+static struct {
+#ifdef CONFIG_THINKPAD_ACPI_BAY
+       u16 bay_status:1;
+       u16 bay_eject:1;
+       u16 bay_status2:1;
+       u16 bay_eject2:1;
+#endif
+       u16 bluetooth:1;
+       u16 hotkey:1;
+       u16 hotkey_mask:1;
+       u16 light:1;
+       u16 light_status:1;
+       u16 wan:1;
+       u16 fan_ctrl_status_undef:1;
+} tp_features;
+
+static struct list_head tpacpi_all_drivers;
+
+static struct ibm_init_struct ibms_init[];
+static int set_ibm_param(const char *val, struct kernel_param *kp);
+static int ibm_init(struct ibm_init_struct *iibm);
+static void ibm_exit(struct ibm_struct *ibm);
+
+
+/*
+ * procfs master subdriver
+ */
+static int thinkpad_acpi_driver_init(struct ibm_init_struct *iibm);
+static int thinkpad_acpi_driver_read(char *p);
+
+
+/*
+ * Bay subdriver
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_BAY
+static acpi_handle bay_handle, bay_ej_handle;
+static acpi_handle bay2_handle, bay2_ej_handle;
+
+static int bay_init(struct ibm_init_struct *iibm);
+static void bay_notify(struct ibm_struct *ibm, u32 event);
+static int bay_read(char *p);
+static int bay_write(char *buf);
+#endif /* CONFIG_THINKPAD_ACPI_BAY */
+
+
+/*
+ * Beep subdriver
+ */
+
+static acpi_handle beep_handle;
+
+static int beep_read(char *p);
+static int beep_write(char *buf);
+
+
+/*
+ * Bluetooth subdriver
+ */
+
+#define TPACPI_BLUETH_SYSFS_GROUP "bluetooth"
+
+enum {
+       /* ACPI GBDC/SBDC bits */
+       TP_ACPI_BLUETOOTH_HWPRESENT     = 0x01, /* Bluetooth hw available */
+       TP_ACPI_BLUETOOTH_RADIOSSW      = 0x02, /* Bluetooth radio enabled */
+       TP_ACPI_BLUETOOTH_UNK           = 0x04, /* unknown function */
+};
+
+static int bluetooth_init(struct ibm_init_struct *iibm);
+static int bluetooth_get_radiosw(void);
+static int bluetooth_set_radiosw(int radio_on);
+static int bluetooth_read(char *p);
+static int bluetooth_write(char *buf);
+
+
+/*
+ * Brightness (backlight) subdriver
+ */
+
+#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
+
+static struct backlight_device *ibm_backlight_device;
+static int brightness_offset = 0x31;
+
+static int brightness_init(struct ibm_init_struct *iibm);
+static void brightness_exit(void);
+static int brightness_get(struct backlight_device *bd);
+static int brightness_set(int value);
+static int brightness_update_status(struct backlight_device *bd);
+static int brightness_read(char *p);
+static int brightness_write(char *buf);
+
+
+/*
+ * CMOS subdriver
+ */
+
+static int cmos_read(char *p);
+static int cmos_write(char *buf);
+
+
+/*
+ * Dock subdriver
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_DOCK
+static acpi_handle pci_handle;
+static acpi_handle dock_handle;
+
+static void dock_notify(struct ibm_struct *ibm, u32 event);
+static int dock_read(char *p);
+static int dock_write(char *buf);
+#endif /* CONFIG_THINKPAD_ACPI_DOCK */
+
+
+/*
+ * EC dump subdriver
+ */
+
+static int ecdump_read(char *p) ;
+static int ecdump_write(char *buf);
+
+
+/*
+ * Fan subdriver
+ */
+
+enum {                                 /* Fan control constants */
+       fan_status_offset = 0x2f,       /* EC register 0x2f */
+       fan_rpm_offset = 0x84,          /* EC register 0x84: LSB, 0x85 MSB (RPM)
+                                        * 0x84 must be read before 0x85 */
+
+       TP_EC_FAN_FULLSPEED = 0x40,     /* EC fan mode: full speed */
+       TP_EC_FAN_AUTO      = 0x80,     /* EC fan mode: auto fan control */
+
+       TPACPI_FAN_LAST_LEVEL = 0x100,  /* Use cached last-seen fan level */
+};
+
+enum fan_status_access_mode {
+       TPACPI_FAN_NONE = 0,            /* No fan status or control */
+       TPACPI_FAN_RD_ACPI_GFAN,        /* Use ACPI GFAN */
+       TPACPI_FAN_RD_TPEC,             /* Use ACPI EC regs 0x2f, 0x84-0x85 */
+};
+
+enum fan_control_access_mode {
+       TPACPI_FAN_WR_NONE = 0,         /* No fan control */
+       TPACPI_FAN_WR_ACPI_SFAN,        /* Use ACPI SFAN */
+       TPACPI_FAN_WR_TPEC,             /* Use ACPI EC reg 0x2f */
+       TPACPI_FAN_WR_ACPI_FANS,        /* Use ACPI FANS and EC reg 0x2f */
+};
+
+enum fan_control_commands {
+       TPACPI_FAN_CMD_SPEED    = 0x0001,       /* speed command */
+       TPACPI_FAN_CMD_LEVEL    = 0x0002,       /* level command  */
+       TPACPI_FAN_CMD_ENABLE   = 0x0004,       /* enable/disable cmd,
+                                                * and also watchdog cmd */
+};
+
+static int fan_control_allowed;
+
+static enum fan_status_access_mode fan_status_access_mode;
+static enum fan_control_access_mode fan_control_access_mode;
+static enum fan_control_commands fan_control_commands;
+static u8 fan_control_initial_status;
+static u8 fan_control_desired_level;
+static int fan_watchdog_maxinterval;
+
+static struct mutex fan_mutex;
+
+static acpi_handle fans_handle, gfan_handle, sfan_handle;
+
+static int fan_init(struct ibm_init_struct *iibm);
+static void fan_exit(void);
+static int fan_get_status(u8 *status);
+static int fan_get_status_safe(u8 *status);
+static int fan_get_speed(unsigned int *speed);
+static void fan_update_desired_level(u8 status);
+static void fan_watchdog_fire(struct work_struct *ignored);
+static void fan_watchdog_reset(void);
+static int fan_set_level(int level);
+static int fan_set_level_safe(int level);
+static int fan_set_enable(void);
+static int fan_set_disable(void);
+static int fan_set_speed(int speed);
+static int fan_read(char *p);
+static int fan_write(char *buf);
+static int fan_write_cmd_level(const char *cmd, int *rc);
+static int fan_write_cmd_enable(const char *cmd, int *rc);
+static int fan_write_cmd_disable(const char *cmd, int *rc);
+static int fan_write_cmd_speed(const char *cmd, int *rc);
+static int fan_write_cmd_watchdog(const char *cmd, int *rc);
+
+
+/*
+ * Hotkey subdriver
+ */
+
+#define TPACPI_HOTKEY_SYSFS_GROUP "hotkey"
+
+static int hotkey_orig_status;
+static int hotkey_orig_mask;
+
+static struct mutex hotkey_mutex;
+
+static int hotkey_init(struct ibm_init_struct *iibm);
+static void hotkey_exit(void);
+static int hotkey_get(int *status, int *mask);
+static int hotkey_set(int status, int mask);
+static void hotkey_notify(struct ibm_struct *ibm, u32 event);
+static int hotkey_read(char *p);
+static int hotkey_write(char *buf);
+
+
+/*
+ * LED subdriver
+ */
+
+enum led_access_mode {
+       TPACPI_LED_NONE = 0,
+       TPACPI_LED_570, /* 570 */
+       TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+       TPACPI_LED_NEW, /* all others */
+};
+
+enum { /* For TPACPI_LED_OLD */
+       TPACPI_LED_EC_HLCL = 0x0c,      /* EC reg to get led to power on */
+       TPACPI_LED_EC_HLBL = 0x0d,      /* EC reg to blink a lit led */
+       TPACPI_LED_EC_HLMS = 0x0e,      /* EC reg to select led to command */
+};
+
+static enum led_access_mode led_supported;
+static acpi_handle led_handle;
+
+static int led_init(struct ibm_init_struct *iibm);
+static int led_read(char *p);
+static int led_write(char *buf);
+
+/*
+ * Light (thinklight) subdriver
+ */
+
+static acpi_handle lght_handle, ledb_handle;
+
+static int light_init(struct ibm_init_struct *iibm);
+static int light_read(char *p);
+static int light_write(char *buf);
+
+
+/*
+ * Thermal subdriver
+ */
+
+enum thermal_access_mode {
+       TPACPI_THERMAL_NONE = 0,        /* No thermal support */
+       TPACPI_THERMAL_ACPI_TMP07,      /* Use ACPI TMP0-7 */
+       TPACPI_THERMAL_ACPI_UPDT,       /* Use ACPI TMP0-7 with UPDT */
+       TPACPI_THERMAL_TPEC_8,          /* Use ACPI EC regs, 8 sensors */
+       TPACPI_THERMAL_TPEC_16,         /* Use ACPI EC regs, 16 sensors */
+};
+
+enum { /* TPACPI_THERMAL_TPEC_* */
+       TP_EC_THERMAL_TMP0 = 0x78,      /* ACPI EC regs TMP 0..7 */
+       TP_EC_THERMAL_TMP8 = 0xC0,      /* ACPI EC regs TMP 8..15 */
+       TP_EC_THERMAL_TMP_NA = -128,    /* ACPI EC sensor not available */
+};
+
+#define TPACPI_MAX_THERMAL_SENSORS 16  /* Max thermal sensors supported */
+struct ibm_thermal_sensors_struct {
+       s32 temp[TPACPI_MAX_THERMAL_SENSORS];
+};
+
+static enum thermal_access_mode thermal_read_mode;
+
+static int thermal_init(struct ibm_init_struct *iibm);
+static int thermal_get_sensor(int idx, s32 *value);
+static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s);
+static int thermal_read(char *p);
+
+
+/*
+ * Video subdriver
+ */
+
+enum video_access_mode {
+       TPACPI_VIDEO_NONE = 0,
+       TPACPI_VIDEO_570,       /* 570 */
+       TPACPI_VIDEO_770,       /* 600e/x, 770e, 770x */
+       TPACPI_VIDEO_NEW,       /* all others */
+};
+
+enum { /* video status flags, based on VIDEO_570 */
+       TP_ACPI_VIDEO_S_LCD = 0x01,     /* LCD output enabled */
+       TP_ACPI_VIDEO_S_CRT = 0x02,     /* CRT output enabled */
+       TP_ACPI_VIDEO_S_DVI = 0x08,     /* DVI output enabled */
+};
+
+enum {  /* TPACPI_VIDEO_570 constants */
+       TP_ACPI_VIDEO_570_PHSCMD = 0x87,        /* unknown magic constant :( */
+       TP_ACPI_VIDEO_570_PHSMASK = 0x03,       /* PHS bits that map to
+                                                * video_status_flags */
+       TP_ACPI_VIDEO_570_PHS2CMD = 0x8b,       /* unknown magic constant :( */
+       TP_ACPI_VIDEO_570_PHS2SET = 0x80,       /* unknown magic constant :( */
+};
+
+static enum video_access_mode video_supported;
+static int video_orig_autosw;
+static acpi_handle vid_handle, vid2_handle;
+
+static int video_init(struct ibm_init_struct *iibm);
+static void video_exit(void);
+static int video_outputsw_get(void);
+static int video_outputsw_set(int status);
+static int video_autosw_get(void);
+static int video_autosw_set(int enable);
+static int video_outputsw_cycle(void);
+static int video_expand_toggle(void);
+static int video_read(char *p);
+static int video_write(char *buf);
+
+
+/*
+ * Volume subdriver
+ */
+
+static int volume_offset = 0x30;
+
+static int volume_read(char *p);
+static int volume_write(char *buf);
+
+
+/*
+ * Wan subdriver
+ */
+
+#define TPACPI_WAN_SYSFS_GROUP "wwan"
+
+enum {
+       /* ACPI GWAN/SWAN bits */
+       TP_ACPI_WANCARD_HWPRESENT       = 0x01, /* Wan hw available */
+       TP_ACPI_WANCARD_RADIOSSW        = 0x02, /* Wan radio enabled */
+       TP_ACPI_WANCARD_UNK             = 0x04, /* unknown function */
+};
+
+static int wan_init(struct ibm_init_struct *iibm);
+static int wan_get_radiosw(void);
+static int wan_set_radiosw(int radio_on);
+static int wan_read(char *p);
+static int wan_write(char *buf);
+
+
+#endif /* __THINKPAD_ACPI_H */
index 9793533276ecc1958fd1d8a1cf5c599b6e08b292..400bb90084cf479b39f1fc1f495111e76c7e0273 100644 (file)
@@ -126,7 +126,7 @@ static unsigned char status_sunbpp_to_pc(struct parport *p)
        if (!(value_tcr & P_TCR_BUSY))
                bits |= PARPORT_STATUS_BUSY;
 
-       dprintk((KERN_DEBUG "tcr 0x%x ir 0x%x\n", regs->p_tcr, regs->p_ir));
+       dprintk((KERN_DEBUG "tcr 0x%x ir 0x%x\n", value_tcr, value_ir));
        dprintk((KERN_DEBUG "read status 0x%x\n", bits));
        return bits;
 }
@@ -147,7 +147,7 @@ static unsigned char control_sunbpp_to_pc(struct parport *p)
        if (value_or & P_OR_SLCT_IN)
                bits |= PARPORT_CONTROL_SELECT;
 
-       dprintk((KERN_DEBUG "tcr 0x%x or 0x%x\n", regs->p_tcr, regs->p_or));
+       dprintk((KERN_DEBUG "tcr 0x%x or 0x%x\n", value_tcr, value_or));
        dprintk((KERN_DEBUG "read control 0x%x\n", bits));
        return bits;
 }
@@ -165,7 +165,8 @@ static unsigned char parport_sunbpp_frob_control(struct parport *p,
        unsigned char value_tcr = sbus_readb(&regs->p_tcr);
        unsigned char value_or = sbus_readb(&regs->p_or);
 
-       dprintk((KERN_DEBUG "frob1: tcr 0x%x or 0x%x\n", regs->p_tcr, regs->p_or));
+       dprintk((KERN_DEBUG "frob1: tcr 0x%x or 0x%x\n",
+                value_tcr, value_or));
        if (mask & PARPORT_CONTROL_STROBE) {
                if (val & PARPORT_CONTROL_STROBE) {
                        value_tcr &= ~P_TCR_DS;
@@ -197,7 +198,8 @@ static unsigned char parport_sunbpp_frob_control(struct parport *p,
 
        sbus_writeb(value_or, &regs->p_or);
        sbus_writeb(value_tcr, &regs->p_tcr);
-       dprintk((KERN_DEBUG "frob2: tcr 0x%x or 0x%x\n", regs->p_tcr, regs->p_or));
+       dprintk((KERN_DEBUG "frob2: tcr 0x%x or 0x%x\n",
+                value_tcr, value_or));
        return parport_sunbpp_read_control(p);
 }
 
index eec28c142a590f28f7a9be87d4fbf7f74641d1ea..5041c9dfbe3b9e70158a7c31063da2d64936b290 100644 (file)
@@ -249,7 +249,7 @@ static int oprompci2node(void __user *argp, struct device_node *dp, struct openp
 #ifdef CONFIG_PCI
                struct pci_dev *pdev;
                struct pcidev_cookie *pcp;
-               pdev = pci_find_slot (((int *) op->oprom_array)[0],
+               pdev = pci_get_bus_and_slot (((int *) op->oprom_array)[0],
                                      ((int *) op->oprom_array)[1]);
 
                pcp = pdev->sysdata;
@@ -260,6 +260,7 @@ static int oprompci2node(void __user *argp, struct device_node *dp, struct openp
                        op->oprom_size = sizeof(int);
                        err = copyout(argp, op, bufsize + sizeof(int));
                }
+               pci_dev_put(pdev);
 #endif
        }
 
diff --git a/include/linux/sony-laptop.h b/include/linux/sony-laptop.h
new file mode 100644 (file)
index 0000000..e2e036d
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef _SONYLAPTOP_H_
+#define _SONYLAPTOP_H_
+
+#include <linux/types.h>
+
+#ifdef __KERNEL__
+
+/* used only for communication between v4l and sony-laptop */
+
+#define SONY_PIC_COMMAND_GETCAMERA              1      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERA              2
+#define SONY_PIC_COMMAND_GETCAMERABRIGHTNESS    3      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERABRIGHTNESS    4
+#define SONY_PIC_COMMAND_GETCAMERACONTRAST      5      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERACONTRAST      6
+#define SONY_PIC_COMMAND_GETCAMERAHUE           7      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERAHUE           8
+#define SONY_PIC_COMMAND_GETCAMERACOLOR                 9      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERACOLOR                10
+#define SONY_PIC_COMMAND_GETCAMERASHARPNESS    11      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERASHARPNESS    12
+#define SONY_PIC_COMMAND_GETCAMERAPICTURE      13      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERAPICTURE      14
+#define SONY_PIC_COMMAND_GETCAMERAAGC          15      /* obsolete */
+#define SONY_PIC_COMMAND_SETCAMERAAGC          16
+#define SONY_PIC_COMMAND_GETCAMERADIRECTION    17      /* obsolete */
+#define SONY_PIC_COMMAND_GETCAMERAROMVERSION   18      /* obsolete */
+#define SONY_PIC_COMMAND_GETCAMERAREVISION     19      /* obsolete */
+
+int sony_pic_camera_command(int command, u8 value);
+
+#endif /* __KERNEL__ */
+
+#endif /* _SONYLAPTOP_H_ */
index fc920f63452bf4a11694eedb226163cc5c52f985..cac06c43f004d269ca58c313853f24548142beba 100644 (file)
@@ -776,6 +776,8 @@ static void nl_fib_lookup(struct fib_result_nl *frn, struct fib_table *tb )
                                       .nl_u = { .ip4_u = { .daddr = frn->fl_addr,
                                                            .tos = frn->fl_tos,
                                                            .scope = frn->fl_scope } } };
+
+       frn->err = -ENOENT;
        if (tb) {
                local_bh_disable();
 
@@ -787,6 +789,7 @@ static void nl_fib_lookup(struct fib_result_nl *frn, struct fib_table *tb )
                        frn->nh_sel = res.nh_sel;
                        frn->type = res.type;
                        frn->scope = res.scope;
+                       fib_res_put(&res);
                }
                local_bh_enable();
        }
@@ -801,6 +804,9 @@ static void nl_fib_input(struct sock *sk, int len)
        struct fib_table *tb;
 
        skb = skb_dequeue(&sk->sk_receive_queue);
+       if (skb == NULL)
+               return;
+
        nlh = (struct nlmsghdr *)skb->data;
        if (skb->len < NLMSG_SPACE(0) || skb->len < nlh->nlmsg_len ||
            nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*frn))) {
@@ -813,7 +819,7 @@ static void nl_fib_input(struct sock *sk, int len)
 
        nl_fib_lookup(frn, tb);
 
-       pid = nlh->nlmsg_pid;           /*pid of sending process */
+       pid = NETLINK_CB(skb).pid;       /* pid of sending process */
        NETLINK_CB(skb).pid = 0;         /* from kernel */
        NETLINK_CB(skb).dst_group = 0;  /* unicast */
        netlink_unicast(sk, skb, pid, MSG_DONTWAIT);