Merge branch 'drivers-platform' into release
authorLen Brown <len.brown@intel.com>
Fri, 9 Jan 2009 09:01:26 +0000 (04:01 -0500)
committerLen Brown <len.brown@intel.com>
Fri, 9 Jan 2009 09:56:56 +0000 (04:56 -0500)
Conflicts:
drivers/misc/Kconfig

Signed-off-by: Len Brown <len.brown@intel.com>
1  2 
drivers/Makefile
drivers/acpi/Makefile
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/platform/x86/Kconfig
drivers/platform/x86/fujitsu-laptop.c
drivers/platform/x86/panasonic-laptop.c
drivers/platform/x86/sony-laptop.c
drivers/platform/x86/tc1100-wmi.c
drivers/platform/x86/thinkpad_acpi.c

index 6326f4dbbdab443f31443f6d6462d9c958cc5acc,46d4828042c358d1448cf45c65d473c0bfe05450..c1bf41737936ab00be4a87563a0bb0638074785d
@@@ -104,4 -100,6 +104,5 @@@ obj-$(CONFIG_PPC_PS3)              += ps3
  obj-$(CONFIG_OF)              += of/
  obj-$(CONFIG_SSB)             += ssb/
  obj-$(CONFIG_VIRTIO)          += virtio/
 -obj-$(CONFIG_REGULATOR)               += regulator/
  obj-$(CONFIG_STAGING)         += staging/
+ obj-y                         += platform/
Simple merge
Simple merge
index 5de863a0e3956dae444215549e770bdb9a2049bc,d5749a7bc7775d1bddd58016e74c17f276087806..9cf8ae6e4b39f53107cf46887ee4b70633ccd5db
@@@ -8,17 -7,9 +7,10 @@@ obj-$(CONFIG_HDPU_FEATURES)    += hdpuftrs
  obj-$(CONFIG_ATMEL_PWM)               += atmel_pwm.o
  obj-$(CONFIG_ATMEL_SSC)               += atmel-ssc.o
  obj-$(CONFIG_ATMEL_TCLIB)     += atmel_tclib.o
- obj-$(CONFIG_HP_WMI)          += hp-wmi.o
  obj-$(CONFIG_ICS932S401)      += ics932s401.o
- obj-$(CONFIG_TC1100_WMI)      += tc1100-wmi.o
  obj-$(CONFIG_LKDTM)           += lkdtm.o
  obj-$(CONFIG_TIFM_CORE)               += tifm_core.o
 +obj-$(CONFIG_DELL_LAPTOP)     += dell-laptop.o
  obj-$(CONFIG_TIFM_7XX1)               += tifm_7xx1.o
  obj-$(CONFIG_PHANTOM)         += phantom.o
  obj-$(CONFIG_SGI_IOC4)                += ioc4.o
index 0000000000000000000000000000000000000000,31f3ce2ac01cfc5e8261e8a8b9aa812e30e13066..e65448e99b48b9f0f233d77b8f0bd1dd36532161
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,375 +1,375 @@@
 -      depends on BACKLIGHT_CLASS_DEVICE
 -      depends on HWMON
+ #
+ # X86 Platform Specific Drivers
+ #
+ menuconfig X86_PLATFORM_DEVICES
+       bool "X86 Platform Specific Device Drivers"
+       default y
+       ---help---
+         Say Y here to get to see options for device drivers for various
+         x86 platforms, including vendor-specific laptop extension drivers.
+         This option alone does not add any kernel code.
+         If you say N, all options in this submenu will be skipped and disabled.
+ if X86_PLATFORM_DEVICES
+ config ACER_WMI
+       tristate "Acer WMI Laptop Extras (EXPERIMENTAL)"
+       depends on EXPERIMENTAL
+       depends on ACPI
+       depends on LEDS_CLASS
+       depends on NEW_LEDS
+       depends on BACKLIGHT_CLASS_DEVICE
+       depends on SERIO_I8042
+       depends on RFKILL
+       select ACPI_WMI
+       ---help---
+         This is a driver for newer Acer (and Wistron) laptops. It adds
+         wireless radio and bluetooth control, and on some laptops,
+         exposes the mail LED and LCD backlight.
+         For more information about this driver see
+         <file:Documentation/laptops/acer-wmi.txt>
+         If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
+         here.
+ config ASUS_LAPTOP
+       tristate "Asus Laptop Extras (EXPERIMENTAL)"
+       depends on ACPI
+       depends on EXPERIMENTAL && !ACPI_ASUS
+       depends on LEDS_CLASS
+       depends on NEW_LEDS
+       depends on BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This is the new Linux driver for Asus laptops. It may also support some
+         MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate
+         standard ACPI events that go through /proc/acpi/events. It also adds
+         support for video output switching, LCD backlight control, Bluetooth and
+         Wlan control, and most importantly, allows you to blink those fancy LEDs.
+         For more information and a userspace daemon for handling the extra
+         buttons see <http://acpi4asus.sf.net/>.
+         If you have an ACPI-compatible ASUS laptop, say Y or M here.
+ config FUJITSU_LAPTOP
+       tristate "Fujitsu Laptop Extras"
+       depends on ACPI
+       depends on INPUT
+       depends on BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This is a driver for laptops built by Fujitsu:
+           * P2xxx/P5xxx/S6xxx/S7xxx series Lifebooks
+           * Possibly other Fujitsu laptop models
+           * Tested with S6410 and S7020
+         It adds support for LCD brightness control and some hotkeys.
+         If you have a Fujitsu laptop, say Y or M here.
+ config FUJITSU_LAPTOP_DEBUG
+       bool "Verbose debug mode for Fujitsu Laptop Extras"
+       depends on FUJITSU_LAPTOP
+       default n
+       ---help---
+         Enables extra debug output from the fujitsu extras driver, at the
+         expense of a slight increase in driver size.
+         If you are not sure, say N here.
+ config TC1100_WMI
+       tristate "HP Compaq TC1100 Tablet WMI Extras (EXPERIMENTAL)"
+       depends on !X86_64
+       depends on EXPERIMENTAL
+       depends on ACPI
+       select ACPI_WMI
+       ---help---
+         This is a driver for the WMI extensions (wireless and bluetooth power
+         control) of the HP Compaq TC1100 tablet.
+ config HP_WMI
+       tristate "HP WMI extras"
+       depends on ACPI_WMI
+       depends on INPUT
+       depends on RFKILL
+       help
+        Say Y here if you want to support WMI-based hotkeys on HP laptops and
+        to read data from WMI such as docking or ambient light sensor state.
+        To compile this driver as a module, choose M here: the module will
+        be called hp-wmi.
+ config MSI_LAPTOP
+       tristate "MSI Laptop Extras"
+       depends on ACPI
+       depends on BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This is a driver for laptops built by MSI (MICRO-STAR
+         INTERNATIONAL):
+         MSI MegaBook S270 (MS-1013)
+         Cytron/TCM/Medion/Tchibo MD96100/SAM2000
+         It adds support for Bluetooth, WLAN and LCD brightness control.
+         More information about this driver is available at
+         <http://0pointer.de/lennart/tchibo.html>.
+         If you have an MSI S270 laptop, say Y or M here.
+ config PANASONIC_LAPTOP
+       tristate "Panasonic Laptop Extras"
+       depends on INPUT && ACPI
+       depends on BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This driver adds support for access to backlight control and hotkeys
+         on Panasonic Let's Note laptops.
+         If you have a Panasonic Let's note laptop (such as the R1(N variant),
+         R2, R3, R5, T2, W2 and Y2 series), say Y.
+ config COMPAL_LAPTOP
+       tristate "Compal Laptop Extras"
+       depends on ACPI
+       depends on BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This is a driver for laptops built by Compal:
+         Compal FL90/IFL90
+         Compal FL91/IFL91
+         Compal FL92/JFL92
+         Compal FT00/IFT00
+         It adds support for Bluetooth, WLAN and LCD brightness control.
+         If you have an Compal FL9x/IFL9x/FT00 laptop, say Y or M here.
+ config SONY_LAPTOP
+       tristate "Sony Laptop Extras"
+       depends on ACPI
+       select BACKLIGHT_CLASS_DEVICE
+       depends on INPUT
+         ---help---
+         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 like Bluetooth,
+         screen brightness control, Fn keys and allows powering on/off some
+         devices.
+         Read <file:Documentation/laptops/sony-laptop.txt> for more information.
+ config SONYPI_COMPAT
+       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 ACPI
+       select BACKLIGHT_LCD_SUPPORT
+       select BACKLIGHT_CLASS_DEVICE
+       select HWMON
+       select NVRAM
+       select INPUT
+       select NEW_LEDS
+       select LEDS_CLASS
+       select NET
+       select RFKILL
+       ---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/laptops/thinkpad-acpi.txt> and
+         <http://ibm-acpi.sf.net/> .
+         This driver was formerly 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
+         electrically 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.
+ config THINKPAD_ACPI_VIDEO
+       bool "Video output control support"
+       depends on THINKPAD_ACPI
+       default y
+       ---help---
+         Allows the thinkpad_acpi driver to provide an interface to control
+         the various video output ports.
+         This feature often won't work well, depending on ThinkPad model,
+         display state, video output devices in use, whether there is a X
+         server running, phase of the moon, and the current mood of
+         Schroedinger's cat.  If you can use X.org's RandR to control
+         your ThinkPad's video output ports instead of this feature,
+         don't think twice: do it and say N here to save some memory.
+         If you are not sure, say Y here.
+ config THINKPAD_ACPI_HOTKEY_POLL
+       bool "Support NVRAM polling for hot keys"
+       depends on THINKPAD_ACPI
+       default y
+       ---help---
+         Some thinkpad models benefit from NVRAM polling to detect a few of
+         the hot key press events.  If you know your ThinkPad model does not
+         need to do NVRAM polling to support any of the hot keys you use,
+         unselecting this option will save about 1kB of memory.
+         ThinkPads T40 and newer, R52 and newer, and X31 and newer are
+         unlikely to need NVRAM polling in their latest BIOS versions.
+         NVRAM polling can detect at most the following keys: ThinkPad/Access
+         IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
+         Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
+         If you are not sure, say Y here.  The driver enables polling only if
+         it is strictly necessary to do so.
+ config INTEL_MENLOW
+       tristate "Thermal Management driver for Intel menlow platform"
+       depends on ACPI_THERMAL
+       select THERMAL
+       ---help---
+         ACPI thermal management enhancement driver on
+         Intel Menlow platform.
+         If unsure, say N.
+ config EEEPC_LAPTOP
+       tristate "Eee PC Hotkey Driver (EXPERIMENTAL)"
+       depends on ACPI
 -      depends on RFKILL
+       depends on EXPERIMENTAL
++      select BACKLIGHT_CLASS_DEVICE
++      select HWMON
++      select RFKILL
+       ---help---
+         This driver supports the Fn-Fx keys on Eee PC laptops.
+         It also adds the ability to switch camera/wlan on/off.
+         If you have an Eee PC laptop, say Y or M here.
+ config ACPI_WMI
+       tristate "WMI (EXPERIMENTAL)"
+       depends on ACPI
+       depends on EXPERIMENTAL
+       help
+         This driver adds support for the ACPI-WMI (Windows Management
+         Instrumentation) mapper device (PNP0C14) found on some systems.
+         ACPI-WMI is a proprietary extension to ACPI to expose parts of the
+         ACPI firmware to userspace - this is done through various vendor
+         defined methods and data blocks in a PNP0C14 device, which are then
+         made available for userspace to call.
+         The implementation of this in Linux currently only exposes this to
+         other kernel space drivers.
+         This driver is a required dependency to build the firmware specific
+         drivers needed on many machines, including Acer and HP laptops.
+         It is safe to enable this driver even if your DSDT doesn't define
+         any ACPI-WMI devices.
+ config ACPI_ASUS
+       tristate "ASUS/Medion Laptop Extras"
+       depends on ACPI
+       select BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This driver provides support for extra features of ACPI-compatible
+         ASUS laptops. As some of Medion laptops are made by ASUS, it may also
+         support some Medion laptops (such as 9675 for example).  It makes all
+         the extra buttons generate standard ACPI events that go through
+         /proc/acpi/events, and (on some models) adds support for changing the
+         display brightness and output, switching the LCD backlight on and off,
+         and most importantly, allows you to blink those fancy LEDs intended
+         for reporting mail and wireless status.
+         Note: display switching code is currently considered EXPERIMENTAL,
+         toying with these values may even lock your machine.
+         All settings are changed via /proc/acpi/asus directory entries. Owner
+         and group for these entries can be set with asus_uid and asus_gid
+         parameters.
+         More information and a userspace daemon for handling the extra buttons
+         at <http://sourceforge.net/projects/acpi4asus/>.
+         If you have an ACPI-compatible ASUS laptop, say Y or M here. This
+         driver is still under development, so if your laptop is unsupported or
+         something works not quite as expected, please use the mailing list
+         available on the above page (acpi4asus-user@lists.sourceforge.net).
+         NOTE: This driver is deprecated and will probably be removed soon,
+         use asus-laptop instead.
+ config ACPI_TOSHIBA
+       tristate "Toshiba Laptop Extras"
+       depends on ACPI
+       depends on INPUT
+       select INPUT_POLLDEV
+       select NET
+       select RFKILL
+       select BACKLIGHT_CLASS_DEVICE
+       ---help---
+         This driver adds support for access to certain system settings
+         on "legacy free" Toshiba laptops.  These laptops can be recognized by
+         their lack of a BIOS setup menu and APM support.
+         On these machines, all system configuration is handled through the
+         ACPI.  This driver is required for access to controls not covered
+         by the general ACPI drivers, such as LCD brightness, video output,
+         etc.
+         This driver differs from the non-ACPI Toshiba laptop driver (located
+         under "Processor type and features") in several aspects.
+         Configuration is accessed by reading and writing text files in the
+         /proc tree instead of by program interface to /dev.  Furthermore, no
+         power management functions are exposed, as those are handled by the
+         general ACPI drivers.
+         More information about this driver is available at
+         <http://memebeam.org/toys/ToshibaAcpiDriver>.
+         If you have a legacy free Toshiba laptop (such as the Libretto L1
+         series), say Y.
+ endif # X86_PLATFORM_DEVICES
index 0000000000000000000000000000000000000000,a7dd3e9fb79de5aa4efde7b293f139d90c87e994..65dc41540c62bdbe72bd0a68f88c042a7f06fd12
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1126 +1,1293 @@@
 -#define FUJITSU_DRIVER_VERSION "0.4.3"
+ /*-*-linux-c-*-*/
+ /*
+   Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@physics.adelaide.edu.au>
+   Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
++  Copyright (C) 2008 Tony Vroon <tony@linx.net>
+   Based on earlier work:
+     Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
+     Adrian Yee <brewt-fujitsu@brewt.org>
+   Templated from msi-laptop.c and thinkpad_acpi.c which is copyright
+   by its respective authors.
+   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.
+  */
+ /*
+  * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional
+  * features made available on a range of Fujitsu laptops including the
+  * P2xxx/P5xxx/S6xxx/S7xxx series.
+  *
+  * This driver exports a few files in /sys/devices/platform/fujitsu-laptop/;
+  * others may be added at a later date.
+  *
+  *   lcd_level - Screen brightness: contains a single integer in the
+  *   range 0..7. (rw)
+  *
+  * In addition to these platform device attributes the driver
+  * registers itself in the Linux backlight control subsystem and is
+  * available to userspace under /sys/class/backlight/fujitsu-laptop/.
+  *
+  * Hotkeys present on certain Fujitsu laptops (eg: the S6xxx series) are
+  * also supported by this driver.
+  *
+  * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
+  * P8010.  It should work on most P-series and S-series Lifebooks, but
+  * YMMV.
+  *
+  * The module parameter use_alt_lcd_levels switches between different ACPI
+  * brightness controls which are used by different Fujitsu laptops.  In most
+  * cases the correct method is automatically detected. "use_alt_lcd_levels=1"
+  * is applicable for a Fujitsu Lifebook S6410 if autodetection fails.
+  *
+  */
+ #include <linux/module.h>
+ #include <linux/kernel.h>
+ #include <linux/init.h>
+ #include <linux/acpi.h>
+ #include <linux/dmi.h>
+ #include <linux/backlight.h>
+ #include <linux/input.h>
+ #include <linux/kfifo.h>
+ #include <linux/video_output.h>
+ #include <linux/platform_device.h>
++#ifdef CONFIG_LEDS_CLASS
++#include <linux/leds.h>
++#endif
 -static int disable_brightness_keys = -1;
++#define FUJITSU_DRIVER_VERSION "0.5.0"
+ #define FUJITSU_LCD_N_LEVELS 8
+ #define ACPI_FUJITSU_CLASS              "fujitsu"
+ #define ACPI_FUJITSU_HID                "FUJ02B1"
+ #define ACPI_FUJITSU_DRIVER_NAME      "Fujitsu laptop FUJ02B1 ACPI brightness driver"
+ #define ACPI_FUJITSU_DEVICE_NAME        "Fujitsu FUJ02B1"
+ #define ACPI_FUJITSU_HOTKEY_HID       "FUJ02E3"
+ #define ACPI_FUJITSU_HOTKEY_DRIVER_NAME "Fujitsu laptop FUJ02E3 ACPI hotkeys driver"
+ #define ACPI_FUJITSU_HOTKEY_DEVICE_NAME "Fujitsu FUJ02E3"
+ #define ACPI_FUJITSU_NOTIFY_CODE1     0x80
+ #define ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS     0x86
+ #define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS     0x87
++/* FUNC interface - command values */
++#define FUNC_RFKILL   0x1000
++#define FUNC_LEDS     0x1001
++#define FUNC_BUTTONS  0x1002
++#define FUNC_BACKLIGHT  0x1004
++
++/* FUNC interface - responses */
++#define UNSUPPORTED_CMD 0x80000000
++
++#ifdef CONFIG_LEDS_CLASS
++/* FUNC interface - LED control */
++#define FUNC_LED_OFF  0x1
++#define FUNC_LED_ON   0x30001
++#define KEYBOARD_LAMPS        0x100
++#define LOGOLAMP_POWERON 0x2000
++#define LOGOLAMP_ALWAYS  0x4000
++#endif
++
+ /* Hotkey details */
+ #define KEY1_CODE     0x410   /* codes for the keys in the GIRB register */
+ #define KEY2_CODE     0x411
+ #define KEY3_CODE     0x412
+ #define KEY4_CODE     0x413
+ #define MAX_HOTKEY_RINGBUFFER_SIZE 100
+ #define RINGBUFFERSIZE 40
+ /* Debugging */
+ #define FUJLAPTOP_LOG    ACPI_FUJITSU_HID ": "
+ #define FUJLAPTOP_ERR    KERN_ERR FUJLAPTOP_LOG
+ #define FUJLAPTOP_NOTICE   KERN_NOTICE FUJLAPTOP_LOG
+ #define FUJLAPTOP_INFO           KERN_INFO FUJLAPTOP_LOG
+ #define FUJLAPTOP_DEBUG    KERN_DEBUG FUJLAPTOP_LOG
+ #define FUJLAPTOP_DBG_ALL       0xffff
+ #define FUJLAPTOP_DBG_ERROR     0x0001
+ #define FUJLAPTOP_DBG_WARN      0x0002
+ #define FUJLAPTOP_DBG_INFO      0x0004
+ #define FUJLAPTOP_DBG_TRACE     0x0008
+ #define dbg_printk(a_dbg_level, format, arg...) \
+       do { if (dbg_level & a_dbg_level) \
+               printk(FUJLAPTOP_DEBUG "%s: " format, __func__ , ## arg); \
+       } while (0)
+ #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+ #define vdbg_printk(a_dbg_level, format, arg...) \
+       dbg_printk(a_dbg_level, format, ## arg)
+ #else
+ #define vdbg_printk(a_dbg_level, format, arg...)
+ #endif
+ /* Device controlling the backlight and associated keys */
+ struct fujitsu_t {
+       acpi_handle acpi_handle;
+       struct acpi_device *dev;
+       struct input_dev *input;
+       char phys[32];
+       struct backlight_device *bl_device;
+       struct platform_device *pf_device;
+       int keycode1, keycode2, keycode3, keycode4;
+       unsigned int max_brightness;
+       unsigned int brightness_changed;
+       unsigned int brightness_level;
+ };
+ static struct fujitsu_t *fujitsu;
+ static int use_alt_lcd_levels = -1;
 -
 -      unsigned int irb;       /* info about the pressed buttons */
+ static int disable_brightness_adjust = -1;
+ /* Device used to access other hotkeys on the laptop */
+ struct fujitsu_hotkey_t {
+       acpi_handle acpi_handle;
+       struct acpi_device *dev;
+       struct input_dev *input;
+       char phys[32];
+       struct platform_device *pf_device;
+       struct kfifo *fifo;
+       spinlock_t fifo_lock;
 -static int get_lcd_level_alt(void)
 -{
 -      unsigned long long state = 0;
 -      acpi_status status = AE_OK;
 -
 -      vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLS\n");
 -
 -      status =
 -          acpi_evaluate_integer(fujitsu->acpi_handle, "GBLS", NULL, &state);
 -      if (status < 0)
 -              return status;
 -
 -      fujitsu->brightness_level = state & 0x0fffffff;
 -
 -      if (state & 0x80000000)
 -              fujitsu->brightness_changed = 1;
 -      else
 -              fujitsu->brightness_changed = 0;
 -
 -      return fujitsu->brightness_level;
 -}
 -
++      int rfkill_state;
++      int logolamp_registered;
++      int kblamps_registered;
+ };
+ static struct fujitsu_hotkey_t *fujitsu_hotkey;
+ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event,
+                                      void *data);
++#ifdef CONFIG_LEDS_CLASS
++static enum led_brightness logolamp_get(struct led_classdev *cdev);
++static void logolamp_set(struct led_classdev *cdev,
++                             enum led_brightness brightness);
++
++struct led_classdev logolamp_led = {
++ .name = "fujitsu::logolamp",
++ .brightness_get = logolamp_get,
++ .brightness_set = logolamp_set
++};
++
++static enum led_brightness kblamps_get(struct led_classdev *cdev);
++static void kblamps_set(struct led_classdev *cdev,
++                             enum led_brightness brightness);
++
++struct led_classdev kblamps_led = {
++ .name = "fujitsu::kblamps",
++ .brightness_get = kblamps_get,
++ .brightness_set = kblamps_set
++};
++#endif
++
+ #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+ static u32 dbg_level = 0x03;
+ #endif
+ static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data);
++/* Fujitsu ACPI interface function */
++
++static int call_fext_func(int cmd, int arg0, int arg1, int arg2)
++{
++      acpi_status status = AE_OK;
++      union acpi_object params[4] = {
++      { .type = ACPI_TYPE_INTEGER },
++      { .type = ACPI_TYPE_INTEGER },
++      { .type = ACPI_TYPE_INTEGER },
++      { .type = ACPI_TYPE_INTEGER }
++      };
++      struct acpi_object_list arg_list = { 4, &params[0] };
++      struct acpi_buffer output;
++      union acpi_object out_obj;
++      acpi_handle handle = NULL;
++
++      status = acpi_get_handle(fujitsu_hotkey->acpi_handle, "FUNC", &handle);
++      if (ACPI_FAILURE(status)) {
++              vdbg_printk(FUJLAPTOP_DBG_ERROR,
++                              "FUNC interface is not present\n");
++              return -ENODEV;
++      }
++
++      params[0].integer.value = cmd;
++      params[1].integer.value = arg0;
++      params[2].integer.value = arg1;
++      params[3].integer.value = arg2;
++
++      output.length = sizeof(out_obj);
++      output.pointer = &out_obj;
++
++      status = acpi_evaluate_object(handle, NULL, &arg_list, &output);
++      if (ACPI_FAILURE(status)) {
++              vdbg_printk(FUJLAPTOP_DBG_WARN,
++                      "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) call failed\n",
++                              cmd, arg0, arg1, arg2);
++              return -ENODEV;
++      }
++
++      if (out_obj.type != ACPI_TYPE_INTEGER) {
++              vdbg_printk(FUJLAPTOP_DBG_WARN,
++                      "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) did not "
++                      "return an integer\n",
++                      cmd, arg0, arg1, arg2);
++              return -ENODEV;
++      }
++
++      vdbg_printk(FUJLAPTOP_DBG_TRACE,
++              "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
++                      cmd, arg0, arg1, arg2, (int)out_obj.integer.value);
++      return out_obj.integer.value;
++}
++
++#ifdef CONFIG_LEDS_CLASS
++/* LED class callbacks */
++
++static void logolamp_set(struct led_classdev *cdev,
++                             enum led_brightness brightness)
++{
++      if (brightness >= LED_FULL) {
++              call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_ON);
++              call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, FUNC_LED_ON);
++      } else if (brightness >= LED_HALF) {
++              call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_ON);
++              call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, FUNC_LED_OFF);
++      } else {
++              call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_OFF);
++      }
++}
++
++static void kblamps_set(struct led_classdev *cdev,
++                             enum led_brightness brightness)
++{
++      if (brightness >= LED_FULL)
++              call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_ON);
++      else
++              call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF);
++}
++
++static enum led_brightness logolamp_get(struct led_classdev *cdev)
++{
++      enum led_brightness brightness = LED_OFF;
++      int poweron, always;
++
++      poweron = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
++      if (poweron == FUNC_LED_ON) {
++              brightness = LED_HALF;
++              always = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
++              if (always == FUNC_LED_ON)
++                      brightness = LED_FULL;
++      }
++      return brightness;
++}
++
++static enum led_brightness kblamps_get(struct led_classdev *cdev)
++{
++      enum led_brightness brightness = LED_OFF;
++
++      if (call_fext_func(FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
++              brightness = LED_FULL;
++
++      return brightness;
++}
++#endif
++
+ /* Hardware access for LCD brightness control */
+ static int set_lcd_level(int level)
+ {
+       acpi_status status = AE_OK;
+       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+       struct acpi_object_list arg_list = { 1, &arg0 };
+       acpi_handle handle = NULL;
+       vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBLL [%d]\n",
+                   level);
+       if (level < 0 || level >= fujitsu->max_brightness)
+               return -EINVAL;
+       if (!fujitsu)
+               return -EINVAL;
+       status = acpi_get_handle(fujitsu->acpi_handle, "SBLL", &handle);
+       if (ACPI_FAILURE(status)) {
+               vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBLL not present\n");
+               return -ENODEV;
+       }
+       arg0.integer.value = level;
+       status = acpi_evaluate_object(handle, NULL, &arg_list, NULL);
+       if (ACPI_FAILURE(status))
+               return -ENODEV;
+       return 0;
+ }
+ static int set_lcd_level_alt(int level)
+ {
+       acpi_status status = AE_OK;
+       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+       struct acpi_object_list arg_list = { 1, &arg0 };
+       acpi_handle handle = NULL;
+       vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBL2 [%d]\n",
+                   level);
+       if (level < 0 || level >= fujitsu->max_brightness)
+               return -EINVAL;
+       if (!fujitsu)
+               return -EINVAL;
+       status = acpi_get_handle(fujitsu->acpi_handle, "SBL2", &handle);
+       if (ACPI_FAILURE(status)) {
+               vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBL2 not present\n");
+               return -ENODEV;
+       }
+       arg0.integer.value = level;
+       status = acpi_evaluate_object(handle, NULL, &arg_list, NULL);
+       if (ACPI_FAILURE(status))
+               return -ENODEV;
+       return 0;
+ }
+ static int get_lcd_level(void)
+ {
+       unsigned long long state = 0;
+       acpi_status status = AE_OK;
+       vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLL\n");
+       status =
+           acpi_evaluate_integer(fujitsu->acpi_handle, "GBLL", NULL, &state);
+       if (status < 0)
+               return status;
+       fujitsu->brightness_level = state & 0x0fffffff;
+       if (state & 0x80000000)
+               fujitsu->brightness_changed = 1;
+       else
+               fujitsu->brightness_changed = 0;
+       return fujitsu->brightness_level;
+ }
+ static int get_max_brightness(void)
+ {
+       unsigned long long state = 0;
+       acpi_status status = AE_OK;
+       vdbg_printk(FUJLAPTOP_DBG_TRACE, "get max lcd level via RBLL\n");
+       status =
+           acpi_evaluate_integer(fujitsu->acpi_handle, "RBLL", NULL, &state);
+       if (status < 0)
+               return status;
+       fujitsu->max_brightness = state;
+       return fujitsu->max_brightness;
+ }
 -      if (use_alt_lcd_levels)
 -              return get_lcd_level_alt();
 -      else
 -              return get_lcd_level();
+ /* Backlight device stuff */
+ static int bl_get_brightness(struct backlight_device *b)
+ {
 -              return set_lcd_level_alt(b->props.brightness);
++      return get_lcd_level();
+ }
+ static int bl_update_status(struct backlight_device *b)
+ {
++      int ret;
++      if (b->props.power == 4)
++              ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3);
++      else
++              ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0);
++      if (ret != 0)
++              vdbg_printk(FUJLAPTOP_DBG_ERROR,
++                      "Unable to adjust backlight power, error code %i\n",
++                      ret);
++
+       if (use_alt_lcd_levels)
 -              return set_lcd_level(b->props.brightness);
++              ret = set_lcd_level_alt(b->props.brightness);
+       else
 -      if (use_alt_lcd_levels)
 -              ret = get_lcd_level_alt();
 -      else
 -              ret = get_lcd_level();
++              ret = set_lcd_level(b->props.brightness);
++      if (ret != 0)
++              vdbg_printk(FUJLAPTOP_DBG_ERROR,
++                      "Unable to adjust LCD brightness, error code %i\n",
++                      ret);
++      return ret;
+ }
+ static struct backlight_ops fujitsubl_ops = {
+       .get_brightness = bl_get_brightness,
+       .update_status = bl_update_status,
+ };
+ /* Platform LCD brightness device */
+ static ssize_t
+ show_max_brightness(struct device *dev,
+                   struct device_attribute *attr, char *buf)
+ {
+       int ret;
+       ret = get_max_brightness();
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%i\n", ret);
+ }
+ static ssize_t
+ show_brightness_changed(struct device *dev,
+                       struct device_attribute *attr, char *buf)
+ {
+       int ret;
+       ret = fujitsu->brightness_changed;
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%i\n", ret);
+ }
+ static ssize_t show_lcd_level(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+ {
+       int ret;
 -      if (use_alt_lcd_levels)
 -              ret = get_lcd_level_alt();
 -      else
 -              ret = get_lcd_level();
++      ret = get_lcd_level();
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%i\n", ret);
+ }
+ static ssize_t store_lcd_level(struct device *dev,
+                              struct device_attribute *attr, const char *buf,
+                              size_t count)
+ {
+       int level, ret;
+       if (sscanf(buf, "%i", &level) != 1
+           || (level < 0 || level >= fujitsu->max_brightness))
+               return -EINVAL;
+       if (use_alt_lcd_levels)
+               ret = set_lcd_level_alt(level);
+       else
+               ret = set_lcd_level(level);
+       if (ret < 0)
+               return ret;
 -/* Hardware access for hotkey device */
 -
 -static int get_irb(void)
++      ret = get_lcd_level();
+       if (ret < 0)
+               return ret;
+       return count;
+ }
 -      unsigned long long state = 0;
 -      acpi_status status = AE_OK;
 -
 -      vdbg_printk(FUJLAPTOP_DBG_TRACE, "Get irb\n");
 -
 -      status =
 -          acpi_evaluate_integer(fujitsu_hotkey->acpi_handle, "GIRB", NULL,
 -                                &state);
 -      if (status < 0)
 -              return status;
++static ssize_t
++ignore_store(struct device *dev,
++           struct device_attribute *attr, const char *buf, size_t count)
+ {
 -      fujitsu_hotkey->irb = state;
++      return count;
++}
 -      return fujitsu_hotkey->irb;
++static ssize_t
++show_lid_state(struct device *dev,
++                      struct device_attribute *attr, char *buf)
++{
++      if (fujitsu_hotkey->rfkill_state == UNSUPPORTED_CMD)
++              return sprintf(buf, "unknown\n");
++      if (fujitsu_hotkey->rfkill_state & 0x100)
++              return sprintf(buf, "open\n");
++      else
++              return sprintf(buf, "closed\n");
++}
 -ignore_store(struct device *dev,
 -           struct device_attribute *attr, const char *buf, size_t count)
++static ssize_t
++show_dock_state(struct device *dev,
++                      struct device_attribute *attr, char *buf)
++{
++      if (fujitsu_hotkey->rfkill_state == UNSUPPORTED_CMD)
++              return sprintf(buf, "unknown\n");
++      if (fujitsu_hotkey->rfkill_state & 0x200)
++              return sprintf(buf, "docked\n");
++      else
++              return sprintf(buf, "undocked\n");
+ }
+ static ssize_t
 -      return count;
++show_radios_state(struct device *dev,
++                      struct device_attribute *attr, char *buf)
+ {
 -      int have_blnf;
++      if (fujitsu_hotkey->rfkill_state == UNSUPPORTED_CMD)
++              return sprintf(buf, "unknown\n");
++      if (fujitsu_hotkey->rfkill_state & 0x20)
++              return sprintf(buf, "on\n");
++      else
++              return sprintf(buf, "killed\n");
+ }
+ static DEVICE_ATTR(max_brightness, 0444, show_max_brightness, ignore_store);
+ static DEVICE_ATTR(brightness_changed, 0444, show_brightness_changed,
+                  ignore_store);
+ static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
++static DEVICE_ATTR(lid, 0444, show_lid_state, ignore_store);
++static DEVICE_ATTR(dock, 0444, show_dock_state, ignore_store);
++static DEVICE_ATTR(radios, 0444, show_radios_state, ignore_store);
+ static struct attribute *fujitsupf_attributes[] = {
+       &dev_attr_brightness_changed.attr,
+       &dev_attr_max_brightness.attr,
+       &dev_attr_lcd_level.attr,
++      &dev_attr_lid.attr,
++      &dev_attr_dock.attr,
++      &dev_attr_radios.attr,
+       NULL
+ };
+ static struct attribute_group fujitsupf_attribute_group = {
+       .attrs = fujitsupf_attributes
+ };
+ static struct platform_driver fujitsupf_driver = {
+       .driver = {
+                  .name = "fujitsu-laptop",
+                  .owner = THIS_MODULE,
+                  }
+ };
+ static void dmi_check_cb_common(const struct dmi_system_id *id)
+ {
+       acpi_handle handle;
 -      have_blnf = ACPI_SUCCESS
 -          (acpi_get_handle(NULL, "\\_SB.PCI0.GFX0.LCD.BLNF", &handle));
+       printk(KERN_INFO "fujitsu-laptop: Identified laptop model '%s'.\n",
+              id->ident);
 -              vdbg_printk(FUJLAPTOP_DBG_TRACE, "auto-detecting usealt\n");
 -              use_alt_lcd_levels = 1;
 -      }
 -      if (disable_brightness_keys == -1) {
 -              vdbg_printk(FUJLAPTOP_DBG_TRACE,
 -                          "auto-detecting disable_keys\n");
 -              disable_brightness_keys = have_blnf ? 1 : 0;
 -      }
 -      if (disable_brightness_adjust == -1) {
 -              vdbg_printk(FUJLAPTOP_DBG_TRACE,
 -                          "auto-detecting disable_adjust\n");
 -              disable_brightness_adjust = have_blnf ? 0 : 1;
+       if (use_alt_lcd_levels == -1) {
 -      disable_brightness_keys = disable_brightness_keys == 1 ? 1 : 0;
++              if (ACPI_SUCCESS(acpi_get_handle(NULL,
++                              "\\_SB.PCI0.LPCB.FJEX.SBL2", &handle)))
++                      use_alt_lcd_levels = 1;
++              else
++                      use_alt_lcd_levels = 0;
++              vdbg_printk(FUJLAPTOP_DBG_TRACE, "auto-detected usealt as "
++                      "%i\n", use_alt_lcd_levels);
+       }
+ }
+ static int dmi_check_cb_s6410(const struct dmi_system_id *id)
+ {
+       dmi_check_cb_common(id);
+       fujitsu->keycode1 = KEY_SCREENLOCK;     /* "Lock" */
+       fujitsu->keycode2 = KEY_HELP;   /* "Mobility Center" */
+       return 0;
+ }
+ static int dmi_check_cb_s6420(const struct dmi_system_id *id)
+ {
+       dmi_check_cb_common(id);
+       fujitsu->keycode1 = KEY_SCREENLOCK;     /* "Lock" */
+       fujitsu->keycode2 = KEY_HELP;   /* "Mobility Center" */
+       return 0;
+ }
+ static int dmi_check_cb_p8010(const struct dmi_system_id *id)
+ {
+       dmi_check_cb_common(id);
+       fujitsu->keycode1 = KEY_HELP;   /* "Support" */
+       fujitsu->keycode3 = KEY_SWITCHVIDEOMODE;        /* "Presentation" */
+       fujitsu->keycode4 = KEY_WWW;    /* "Internet" */
+       return 0;
+ }
+ static struct dmi_system_id fujitsu_dmi_table[] = {
+       {
+        .ident = "Fujitsu Siemens S6410",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"),
+                    },
+        .callback = dmi_check_cb_s6410},
+       {
+        .ident = "Fujitsu Siemens S6420",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"),
+                    },
+        .callback = dmi_check_cb_s6420},
+       {
+        .ident = "Fujitsu LifeBook P8010",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"),
+                    },
+        .callback = dmi_check_cb_p8010},
+       {}
+ };
+ /* ACPI device for LCD brightness control */
+ static int acpi_fujitsu_add(struct acpi_device *device)
+ {
+       acpi_status status;
+       acpi_handle handle;
+       int result = 0;
+       int state = 0;
+       struct input_dev *input;
+       int error;
+       if (!device)
+               return -EINVAL;
+       fujitsu->acpi_handle = device->handle;
+       sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_DEVICE_NAME);
+       sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
+       device->driver_data = fujitsu;
+       status = acpi_install_notify_handler(device->handle,
+                                            ACPI_DEVICE_NOTIFY,
+                                            acpi_fujitsu_notify, fujitsu);
+       if (ACPI_FAILURE(status)) {
+               printk(KERN_ERR "Error installing notify handler\n");
+               error = -ENODEV;
+               goto err_stop;
+       }
+       fujitsu->input = input = input_allocate_device();
+       if (!input) {
+               error = -ENOMEM;
+               goto err_uninstall_notify;
+       }
+       snprintf(fujitsu->phys, sizeof(fujitsu->phys),
+                "%s/video/input0", acpi_device_hid(device));
+       input->name = acpi_device_name(device);
+       input->phys = fujitsu->phys;
+       input->id.bustype = BUS_HOST;
+       input->id.product = 0x06;
+       input->dev.parent = &device->dev;
+       input->evbit[0] = BIT(EV_KEY);
+       set_bit(KEY_BRIGHTNESSUP, input->keybit);
+       set_bit(KEY_BRIGHTNESSDOWN, input->keybit);
+       set_bit(KEY_UNKNOWN, input->keybit);
+       error = input_register_device(input);
+       if (error)
+               goto err_free_input_dev;
+       result = acpi_bus_get_power(fujitsu->acpi_handle, &state);
+       if (result) {
+               printk(KERN_ERR "Error reading power state\n");
+               goto end;
+       }
+       printk(KERN_INFO PREFIX "%s [%s] (%s)\n",
+              acpi_device_name(device), acpi_device_bid(device),
+              !device->power.state ? "on" : "off");
+       fujitsu->dev = device;
+       if (ACPI_SUCCESS
+           (acpi_get_handle(device->handle, METHOD_NAME__INI, &handle))) {
+               vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
+               if (ACPI_FAILURE
+                   (acpi_evaluate_object
+                    (device->handle, METHOD_NAME__INI, NULL, NULL)))
+                       printk(KERN_ERR "_INI Method failed\n");
+       }
+       /* do config (detect defaults) */
+       use_alt_lcd_levels = use_alt_lcd_levels == 1 ? 1 : 0;
 -                  "config: [alt interface: %d], [key disable: %d], [adjust disable: %d]\n",
 -                  use_alt_lcd_levels, disable_brightness_keys,
 -                  disable_brightness_adjust);
+       disable_brightness_adjust = disable_brightness_adjust == 1 ? 1 : 0;
+       vdbg_printk(FUJLAPTOP_DBG_INFO,
 -      if (use_alt_lcd_levels)
 -              get_lcd_level_alt();
 -      else
 -              get_lcd_level();
++                  "config: [alt interface: %d], [adjust disable: %d]\n",
++                  use_alt_lcd_levels, disable_brightness_adjust);
+       if (get_max_brightness() <= 0)
+               fujitsu->max_brightness = FUJITSU_LCD_N_LEVELS;
 -              get_lcd_level();  /* the alt version always yields changed */
++      get_lcd_level();
+       return result;
+ end:
+ err_free_input_dev:
+       input_free_device(input);
+ err_uninstall_notify:
+       acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+                                  acpi_fujitsu_notify);
+ err_stop:
+       return result;
+ }
+ static int acpi_fujitsu_remove(struct acpi_device *device, int type)
+ {
+       acpi_status status;
+       struct fujitsu_t *fujitsu = NULL;
+       if (!device || !acpi_driver_data(device))
+               return -EINVAL;
+       fujitsu = acpi_driver_data(device);
+       status = acpi_remove_notify_handler(fujitsu->acpi_handle,
+                                           ACPI_DEVICE_NOTIFY,
+                                           acpi_fujitsu_notify);
+       if (!device || !acpi_driver_data(device))
+               return -EINVAL;
+       fujitsu->acpi_handle = NULL;
+       return 0;
+ }
+ /* Brightness notify */
+ static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data)
+ {
+       struct input_dev *input;
+       int keycode;
+       int oldb, newb;
+       input = fujitsu->input;
+       switch (event) {
+       case ACPI_FUJITSU_NOTIFY_CODE1:
+               keycode = 0;
+               oldb = fujitsu->brightness_level;
 -              if (oldb == newb && fujitsu->brightness_changed) {
 -                      keycode = 0;
 -                      if (disable_brightness_keys != 1) {
 -                              if (oldb == 0) {
 -                                      acpi_bus_generate_proc_event
 -                                          (fujitsu->dev,
 -                                           ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS,
 -                                           0);
 -                                      keycode = KEY_BRIGHTNESSDOWN;
 -                              } else if (oldb ==
 -                                         (fujitsu->max_brightness) - 1) {
 -                                      acpi_bus_generate_proc_event
 -                                          (fujitsu->dev,
 -                                           ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS,
 -                                           0);
 -                                      keycode = KEY_BRIGHTNESSUP;
 -                              }
 -                      }
 -              } else if (oldb < newb) {
++              get_lcd_level();
+               newb = fujitsu->brightness_level;
+               vdbg_printk(FUJLAPTOP_DBG_TRACE,
+                           "brightness button event [%i -> %i (%i)]\n",
+                           oldb, newb, fujitsu->brightness_changed);
 -                      if (disable_brightness_keys != 1) {
 -                              acpi_bus_generate_proc_event(fujitsu->dev,
 -                                      ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, 0);
 -                              keycode = KEY_BRIGHTNESSUP;
 -                      }
++              if (oldb < newb) {
+                       if (disable_brightness_adjust != 1) {
+                               if (use_alt_lcd_levels)
+                                       set_lcd_level_alt(newb);
+                               else
+                                       set_lcd_level(newb);
+                       }
 -                      if (disable_brightness_keys != 1) {
 -                              acpi_bus_generate_proc_event(fujitsu->dev,
 -                                      ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, 0);
 -                              keycode = KEY_BRIGHTNESSDOWN;
 -                      }
 -              } else {
 -                      keycode = KEY_UNKNOWN;
++                      acpi_bus_generate_proc_event(fujitsu->dev,
++                              ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, 0);
++                      keycode = KEY_BRIGHTNESSUP;
+               } else if (oldb > newb) {
+                       if (disable_brightness_adjust != 1) {
+                               if (use_alt_lcd_levels)
+                                       set_lcd_level_alt(newb);
+                               else
+                                       set_lcd_level(newb);
+                       }
 -      input->evbit[0] = BIT(EV_KEY);
++                      acpi_bus_generate_proc_event(fujitsu->dev,
++                              ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, 0);
++                      keycode = KEY_BRIGHTNESSDOWN;
+               }
+               break;
+       default:
+               keycode = KEY_UNKNOWN;
+               vdbg_printk(FUJLAPTOP_DBG_WARN,
+                           "unsupported event [0x%x]\n", event);
+               break;
+       }
+       if (keycode != 0) {
+               input_report_key(input, keycode, 1);
+               input_sync(input);
+               input_report_key(input, keycode, 0);
+               input_sync(input);
+       }
+       return;
+ }
+ /* ACPI device for hotkey handling */
+ static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
+ {
+       acpi_status status;
+       acpi_handle handle;
+       int result = 0;
+       int state = 0;
+       struct input_dev *input;
+       int error;
+       int i;
+       if (!device)
+               return -EINVAL;
+       fujitsu_hotkey->acpi_handle = device->handle;
+       sprintf(acpi_device_name(device), "%s",
+               ACPI_FUJITSU_HOTKEY_DEVICE_NAME);
+       sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
+       device->driver_data = fujitsu_hotkey;
+       status = acpi_install_notify_handler(device->handle,
+                                            ACPI_DEVICE_NOTIFY,
+                                            acpi_fujitsu_hotkey_notify,
+                                            fujitsu_hotkey);
+       if (ACPI_FAILURE(status)) {
+               printk(KERN_ERR "Error installing notify handler\n");
+               error = -ENODEV;
+               goto err_stop;
+       }
+       /* kfifo */
+       spin_lock_init(&fujitsu_hotkey->fifo_lock);
+       fujitsu_hotkey->fifo =
+           kfifo_alloc(RINGBUFFERSIZE * sizeof(int), GFP_KERNEL,
+                       &fujitsu_hotkey->fifo_lock);
+       if (IS_ERR(fujitsu_hotkey->fifo)) {
+               printk(KERN_ERR "kfifo_alloc failed\n");
+               error = PTR_ERR(fujitsu_hotkey->fifo);
+               goto err_stop;
+       }
+       fujitsu_hotkey->input = input = input_allocate_device();
+       if (!input) {
+               error = -ENOMEM;
+               goto err_uninstall_notify;
+       }
+       snprintf(fujitsu_hotkey->phys, sizeof(fujitsu_hotkey->phys),
+                "%s/video/input0", acpi_device_hid(device));
+       input->name = acpi_device_name(device);
+       input->phys = fujitsu_hotkey->phys;
+       input->id.bustype = BUS_HOST;
+       input->id.product = 0x06;
+       input->dev.parent = &device->dev;
 -      i = 0;                  /* Discard hotkey ringbuffer */
 -      while (get_irb() != 0 && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) ;
++
++      set_bit(EV_KEY, input->evbit);
+       set_bit(fujitsu->keycode1, input->keybit);
+       set_bit(fujitsu->keycode2, input->keybit);
+       set_bit(fujitsu->keycode3, input->keybit);
+       set_bit(fujitsu->keycode4, input->keybit);
+       set_bit(KEY_UNKNOWN, input->keybit);
+       error = input_register_device(input);
+       if (error)
+               goto err_free_input_dev;
+       result = acpi_bus_get_power(fujitsu_hotkey->acpi_handle, &state);
+       if (result) {
+               printk(KERN_ERR "Error reading power state\n");
+               goto end;
+       }
+       printk(KERN_INFO PREFIX "%s [%s] (%s)\n",
+              acpi_device_name(device), acpi_device_bid(device),
+              !device->power.state ? "on" : "off");
+       fujitsu_hotkey->dev = device;
+       if (ACPI_SUCCESS
+           (acpi_get_handle(device->handle, METHOD_NAME__INI, &handle))) {
+               vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
+               if (ACPI_FAILURE
+                   (acpi_evaluate_object
+                    (device->handle, METHOD_NAME__INI, NULL, NULL)))
+                       printk(KERN_ERR "_INI Method failed\n");
+       }
 -      vdbg_printk(FUJLAPTOP_DBG_TRACE, "Hotkey event\n");
++      i = 0;
++      while (call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0
++              && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE)
++              ; /* No action, result is discarded */
+       vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i);
++      fujitsu_hotkey->rfkill_state =
++              call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
++
++      /* Suspect this is a keymap of the application panel, print it */
++      printk(KERN_INFO "fujitsu-laptop: BTNI: [0x%x]\n",
++              call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0));
++
++      #ifdef CONFIG_LEDS_CLASS
++      if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
++              result = led_classdev_register(&fujitsu->pf_device->dev,
++                                              &logolamp_led);
++              if (result == 0) {
++                      fujitsu_hotkey->logolamp_registered = 1;
++              } else {
++                      printk(KERN_ERR "fujitsu-laptop: Could not register "
++                      "LED handler for logo lamp, error %i\n", result);
++              }
++      }
++
++      if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
++         (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
++              result = led_classdev_register(&fujitsu->pf_device->dev,
++                                              &kblamps_led);
++              if (result == 0) {
++                      fujitsu_hotkey->kblamps_registered = 1;
++              } else {
++                      printk(KERN_ERR "fujitsu-laptop: Could not register "
++                      "LED handler for keyboard lamps, error %i\n", result);
++              }
++      }
++      #endif
++
+       return result;
+ end:
+ err_free_input_dev:
+       input_free_device(input);
+ err_uninstall_notify:
+       acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+                                  acpi_fujitsu_hotkey_notify);
+       kfifo_free(fujitsu_hotkey->fifo);
+ err_stop:
+       return result;
+ }
+ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device, int type)
+ {
+       acpi_status status;
+       struct fujitsu_hotkey_t *fujitsu_hotkey = NULL;
+       if (!device || !acpi_driver_data(device))
+               return -EINVAL;
+       fujitsu_hotkey = acpi_driver_data(device);
+       status = acpi_remove_notify_handler(fujitsu_hotkey->acpi_handle,
+                                           ACPI_DEVICE_NOTIFY,
+                                           acpi_fujitsu_hotkey_notify);
+       fujitsu_hotkey->acpi_handle = NULL;
+       kfifo_free(fujitsu_hotkey->fifo);
+       return 0;
+ }
+ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event,
+                                      void *data)
+ {
+       struct input_dev *input;
+       int keycode, keycode_r;
+       unsigned int irb = 1;
+       int i, status;
+       input = fujitsu_hotkey->input;
 -              while ((irb = get_irb()) != 0
 -                     && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) {
 -                      vdbg_printk(FUJLAPTOP_DBG_TRACE, "GIRB result [%x]\n",
 -                                  irb);
 -
++      fujitsu_hotkey->rfkill_state =
++              call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
+       switch (event) {
+       case ACPI_FUJITSU_NOTIFY_CODE1:
+               i = 0;
 -module_param(disable_brightness_keys, uint, 0644);
 -MODULE_PARM_DESC(disable_brightness_keys,
 -               "Disable brightness keys (eg. if they are already handled by the generic ACPI_VIDEO device).");
++              while ((irb =
++                      call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0
++                              && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) {
+                       switch (irb & 0x4ff) {
+                       case KEY1_CODE:
+                               keycode = fujitsu->keycode1;
+                               break;
+                       case KEY2_CODE:
+                               keycode = fujitsu->keycode2;
+                               break;
+                       case KEY3_CODE:
+                               keycode = fujitsu->keycode3;
+                               break;
+                       case KEY4_CODE:
+                               keycode = fujitsu->keycode4;
+                               break;
+                       case 0:
+                               keycode = 0;
+                               break;
+                       default:
+                               vdbg_printk(FUJLAPTOP_DBG_WARN,
+                                           "Unknown GIRB result [%x]\n", irb);
+                               keycode = -1;
+                               break;
+                       }
+                       if (keycode > 0) {
+                               vdbg_printk(FUJLAPTOP_DBG_TRACE,
+                                       "Push keycode into ringbuffer [%d]\n",
+                                       keycode);
+                               status = kfifo_put(fujitsu_hotkey->fifo,
+                                                  (unsigned char *)&keycode,
+                                                  sizeof(keycode));
+                               if (status != sizeof(keycode)) {
+                                       vdbg_printk(FUJLAPTOP_DBG_WARN,
+                                           "Could not push keycode [0x%x]\n",
+                                           keycode);
+                               } else {
+                                       input_report_key(input, keycode, 1);
+                                       input_sync(input);
+                               }
+                       } else if (keycode == 0) {
+                               while ((status =
+                                       kfifo_get
+                                       (fujitsu_hotkey->fifo, (unsigned char *)
+                                        &keycode_r,
+                                        sizeof
+                                        (keycode_r))) == sizeof(keycode_r)) {
+                                       input_report_key(input, keycode_r, 0);
+                                       input_sync(input);
+                                       vdbg_printk(FUJLAPTOP_DBG_TRACE,
+                                         "Pop keycode from ringbuffer [%d]\n",
+                                         keycode_r);
+                               }
+                       }
+               }
+               break;
+       default:
+               keycode = KEY_UNKNOWN;
+               vdbg_printk(FUJLAPTOP_DBG_WARN,
+                           "Unsupported event [0x%x]\n", event);
+               input_report_key(input, keycode, 1);
+               input_sync(input);
+               input_report_key(input, keycode, 0);
+               input_sync(input);
+               break;
+       }
+       return;
+ }
+ /* Initialization */
+ static const struct acpi_device_id fujitsu_device_ids[] = {
+       {ACPI_FUJITSU_HID, 0},
+       {"", 0},
+ };
+ static struct acpi_driver acpi_fujitsu_driver = {
+       .name = ACPI_FUJITSU_DRIVER_NAME,
+       .class = ACPI_FUJITSU_CLASS,
+       .ids = fujitsu_device_ids,
+       .ops = {
+               .add = acpi_fujitsu_add,
+               .remove = acpi_fujitsu_remove,
+               },
+ };
+ static const struct acpi_device_id fujitsu_hotkey_device_ids[] = {
+       {ACPI_FUJITSU_HOTKEY_HID, 0},
+       {"", 0},
+ };
+ static struct acpi_driver acpi_fujitsu_hotkey_driver = {
+       .name = ACPI_FUJITSU_HOTKEY_DRIVER_NAME,
+       .class = ACPI_FUJITSU_CLASS,
+       .ids = fujitsu_hotkey_device_ids,
+       .ops = {
+               .add = acpi_fujitsu_hotkey_add,
+               .remove = acpi_fujitsu_hotkey_remove,
+               },
+ };
+ static int __init fujitsu_init(void)
+ {
+       int ret, result, max_brightness;
+       if (acpi_disabled)
+               return -ENODEV;
+       fujitsu = kmalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
+       if (!fujitsu)
+               return -ENOMEM;
+       memset(fujitsu, 0, sizeof(struct fujitsu_t));
+       fujitsu->keycode1 = KEY_PROG1;
+       fujitsu->keycode2 = KEY_PROG2;
+       fujitsu->keycode3 = KEY_PROG3;
+       fujitsu->keycode4 = KEY_PROG4;
+       dmi_check_system(fujitsu_dmi_table);
+       result = acpi_bus_register_driver(&acpi_fujitsu_driver);
+       if (result < 0) {
+               ret = -ENODEV;
+               goto fail_acpi;
+       }
+       /* Register platform stuff */
+       fujitsu->pf_device = platform_device_alloc("fujitsu-laptop", -1);
+       if (!fujitsu->pf_device) {
+               ret = -ENOMEM;
+               goto fail_platform_driver;
+       }
+       ret = platform_device_add(fujitsu->pf_device);
+       if (ret)
+               goto fail_platform_device1;
+       ret =
+           sysfs_create_group(&fujitsu->pf_device->dev.kobj,
+                              &fujitsupf_attribute_group);
+       if (ret)
+               goto fail_platform_device2;
+       /* Register backlight stuff */
+       if (!acpi_video_backlight_support()) {
+               fujitsu->bl_device =
+                       backlight_device_register("fujitsu-laptop", NULL, NULL,
+                                                 &fujitsubl_ops);
+               if (IS_ERR(fujitsu->bl_device))
+                       return PTR_ERR(fujitsu->bl_device);
+               max_brightness = fujitsu->max_brightness;
+               fujitsu->bl_device->props.max_brightness = max_brightness - 1;
+               fujitsu->bl_device->props.brightness = fujitsu->brightness_level;
+       }
+       ret = platform_driver_register(&fujitsupf_driver);
+       if (ret)
+               goto fail_backlight;
+       /* Register hotkey driver */
+       fujitsu_hotkey = kmalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
+       if (!fujitsu_hotkey) {
+               ret = -ENOMEM;
+               goto fail_hotkey;
+       }
+       memset(fujitsu_hotkey, 0, sizeof(struct fujitsu_hotkey_t));
+       result = acpi_bus_register_driver(&acpi_fujitsu_hotkey_driver);
+       if (result < 0) {
+               ret = -ENODEV;
+               goto fail_hotkey1;
+       }
++      /* Sync backlight power status (needs FUJ02E3 device, hence deferred) */
++
++      if (!acpi_video_backlight_support()) {
++              if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
++                      fujitsu->bl_device->props.power = 4;
++              else
++                      fujitsu->bl_device->props.power = 0;
++      }
++
+       printk(KERN_INFO "fujitsu-laptop: driver " FUJITSU_DRIVER_VERSION
+              " successfully loaded.\n");
+       return 0;
+ fail_hotkey1:
+       kfree(fujitsu_hotkey);
+ fail_hotkey:
+       platform_driver_unregister(&fujitsupf_driver);
+ fail_backlight:
+       if (fujitsu->bl_device)
+               backlight_device_unregister(fujitsu->bl_device);
+ fail_platform_device2:
+       platform_device_del(fujitsu->pf_device);
+ fail_platform_device1:
+       platform_device_put(fujitsu->pf_device);
+ fail_platform_driver:
+       acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+ fail_acpi:
+       kfree(fujitsu);
+       return ret;
+ }
+ static void __exit fujitsu_cleanup(void)
+ {
++      #ifdef CONFIG_LEDS_CLASS
++      if (fujitsu_hotkey->logolamp_registered != 0)
++              led_classdev_unregister(&logolamp_led);
++
++      if (fujitsu_hotkey->kblamps_registered != 0)
++              led_classdev_unregister(&kblamps_led);
++      #endif
++
+       sysfs_remove_group(&fujitsu->pf_device->dev.kobj,
+                          &fujitsupf_attribute_group);
+       platform_device_unregister(fujitsu->pf_device);
+       platform_driver_unregister(&fujitsupf_driver);
+       if (fujitsu->bl_device)
+               backlight_device_unregister(fujitsu->bl_device);
+       acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+       kfree(fujitsu);
+       acpi_bus_unregister_driver(&acpi_fujitsu_hotkey_driver);
+       kfree(fujitsu_hotkey);
+       printk(KERN_INFO "fujitsu-laptop: driver unloaded.\n");
+ }
+ module_init(fujitsu_init);
+ module_exit(fujitsu_cleanup);
+ module_param(use_alt_lcd_levels, uint, 0644);
+ MODULE_PARM_DESC(use_alt_lcd_levels,
+                "Use alternative interface for lcd_levels (needed for Lifebook s6410).");
 -MODULE_AUTHOR("Jonathan Woithe, Peter Gruber");
+ module_param(disable_brightness_adjust, uint, 0644);
+ MODULE_PARM_DESC(disable_brightness_adjust, "Disable brightness adjustment .");
+ #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+ module_param_named(debug, dbg_level, uint, 0644);
+ MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
+ #endif
++MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
+ MODULE_DESCRIPTION("Fujitsu laptop extras support");
+ MODULE_VERSION(FUJITSU_DRIVER_VERSION);
+ MODULE_LICENSE("GPL");
+ MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*");
++MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1E6:*:cvrS6420:*");
+ MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*");
+ static struct pnp_device_id pnp_ids[] = {
+       {.id = "FUJ02bf"},
+       {.id = "FUJ02B1"},
+       {.id = "FUJ02E3"},
+       {.id = ""}
+ };
+ MODULE_DEVICE_TABLE(pnp, pnp_ids);
index 0000000000000000000000000000000000000000,4a1bc64485d5a61523a2eaee5975555c843ac859..f30db367c82eed36540b9db55f7e6b9abb72267b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,766 +1,744 @@@
 -      ACPI_FUNCTION_TRACE("acpi_pcc_write_sset");
 -
+ /*
+  *  Panasonic HotKey and LCD brightness control driver
+  *  (C) 2004 Hiroshi Miura <miura@da-cha.org>
+  *  (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/
+  *  (C) YOKOTA Hiroshi <yokota (at) netlab. is. tsukuba. ac. jp>
+  *  (C) 2004 David Bronaugh <dbronaugh>
+  *  (C) 2006-2008 Harald Welte <laforge@gnumonks.org>
+  *
+  *  derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte
+  *
+  *  This program is free software; you can redistribute it and/or modify
+  *  it under the terms of the GNU General Public License version 2 as
+  *  publicshed by the Free Software Foundation.
+  *
+  *  This program is distributed in the hope that it will be useful,
+  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  *  GNU General Public License for more details.
+  *
+  *  You should have received a copy of the GNU General Public License
+  *  along with this program; if not, write to the Free Software
+  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+  *
+  *---------------------------------------------------------------------------
+  *
+  * ChangeLog:
+  *    Sep.23, 2008    Harald Welte <laforge@gnumonks.org>
+  *            -v0.95  rename driver from drivers/acpi/pcc_acpi.c to
+  *                    drivers/misc/panasonic-laptop.c
+  *
+  *    Jul.04, 2008    Harald Welte <laforge@gnumonks.org>
+  *            -v0.94  replace /proc interface with device attributes
+  *                    support {set,get}keycode on th input device
+  *
+  *      Jun.27, 2008  Harald Welte <laforge@gnumonks.org>
+  *            -v0.92  merge with 2.6.26-rc6 input API changes
+  *                    remove broken <= 2.6.15 kernel support
+  *                    resolve all compiler warnings
+  *                    various coding style fixes (checkpatch.pl)
+  *                    add support for backlight api
+  *                    major code restructuring
+  *
+  *    Dac.28, 2007    Harald Welte <laforge@gnumonks.org>
+  *            -v0.91  merge with 2.6.24-rc6 ACPI changes
+  *
+  *    Nov.04, 2006    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.9   remove warning about section reference.
+  *                    remove acpi_os_free
+  *                    add /proc/acpi/pcc/brightness interface for HAL access
+  *                    merge dbronaugh's enhancement
+  *                    Aug.17, 2004 David Bronaugh (dbronaugh)
+  *                            - Added screen brightness setting interface
+  *                              Thanks to FreeBSD crew (acpi_panasonic.c)
+  *                              for the ideas I needed to accomplish it
+  *
+  *    May.29, 2006    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.8.4 follow to change keyinput structure
+  *                    thanks Fabian Yamaguchi <fabs@cs.tu-berlin.de>,
+  *                    Jacob Bower <jacob.bower@ic.ac.uk> and
+  *                    Hiroshi Yokota for providing solutions.
+  *
+  *    Oct.02, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.8.2 merge code of YOKOTA Hiroshi
+  *                                    <yokota@netlab.is.tsukuba.ac.jp>.
+  *                    Add sticky key mode interface.
+  *                    Refactoring acpi_pcc_generate_keyinput().
+  *
+  *    Sep.15, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.8   Generate key input event on input subsystem.
+  *                    This is based on yet another driver written by
+  *                                                    Ryuta Nakanishi.
+  *
+  *    Sep.10, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.7   Change proc interface functions using seq_file
+  *                    facility as same as other ACPI drivers.
+  *
+  *    Aug.28, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.6.4 Fix a silly error with status checking
+  *
+  *    Aug.25, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            -v0.6.3 replace read_acpi_int by standard function
+  *                                                    acpi_evaluate_integer
+  *                    some clean up and make smart copyright notice.
+  *                    fix return value of pcc_acpi_get_key()
+  *                    fix checking return value of acpi_bus_register_driver()
+  *
+  *      Aug.22, 2004    David Bronaugh <dbronaugh@linuxboxen.org>
+  *              -v0.6.2 Add check on ACPI data (num_sifr)
+  *                      Coding style cleanups, better error messages/handling
+  *                    Fixed an off-by-one error in memory allocation
+  *
+  *      Aug.21, 2004    David Bronaugh <dbronaugh@linuxboxen.org>
+  *              -v0.6.1 Fix a silly error with status checking
+  *
+  *      Aug.20, 2004    David Bronaugh <dbronaugh@linuxboxen.org>
+  *              - v0.6  Correct brightness controls to reflect reality
+  *                      based on information gleaned by Hiroshi Miura
+  *                      and discussions with Hiroshi Miura
+  *
+  *    Aug.10, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            - v0.5  support LCD brightness control
+  *                    based on the disclosed information by MEI.
+  *
+  *    Jul.25, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            - v0.4  first post version
+  *                    add function to retrive SIFR
+  *
+  *    Jul.24, 2004    Hiroshi Miura <miura@da-cha.org>
+  *            - v0.3  get proper status of hotkey
+  *
+  *      Jul.22, 2004  Hiroshi Miura <miura@da-cha.org>
+  *            - v0.2  add HotKey handler
+  *
+  *      Jul.17, 2004  Hiroshi Miura <miura@da-cha.org>
+  *            - v0.1  start from toshiba_acpi driver written by John Belmonte
+  *
+  */
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/init.h>
+ #include <linux/types.h>
+ #include <linux/backlight.h>
+ #include <linux/ctype.h>
+ #include <linux/seq_file.h>
+ #include <linux/uaccess.h>
+ #include <acpi/acpi_bus.h>
+ #include <acpi/acpi_drivers.h>
+ #include <linux/input.h>
+ #ifndef ACPI_HOTKEY_COMPONENT
+ #define ACPI_HOTKEY_COMPONENT 0x10000000
+ #endif
+ #define _COMPONENT            ACPI_HOTKEY_COMPONENT
+ MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
+ MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
+ MODULE_LICENSE("GPL");
+ #define LOGPREFIX "pcc_acpi: "
+ /* Define ACPI PATHs */
+ /* Lets note hotkeys */
+ #define METHOD_HKEY_QUERY     "HINF"
+ #define METHOD_HKEY_SQTY      "SQTY"
+ #define METHOD_HKEY_SINF      "SINF"
+ #define METHOD_HKEY_SSET      "SSET"
+ #define HKEY_NOTIFY            0x80
+ #define ACPI_PCC_DRIVER_NAME  "Panasonic Laptop Support"
+ #define ACPI_PCC_DEVICE_NAME  "Hotkey"
+ #define ACPI_PCC_CLASS                "pcc"
+ #define ACPI_PCC_INPUT_PHYS   "panasonic/hkey0"
+ /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
+    ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
+ */
+ enum SINF_BITS { SINF_NUM_BATTERIES = 0,
+                SINF_LCD_TYPE,
+                SINF_AC_MAX_BRIGHT,
+                SINF_AC_MIN_BRIGHT,
+                SINF_AC_CUR_BRIGHT,
+                SINF_DC_MAX_BRIGHT,
+                SINF_DC_MIN_BRIGHT,
+                SINF_DC_CUR_BRIGHT,
+                SINF_MUTE,
+                SINF_RESERVED,
+                SINF_ENV_STATE,
+                SINF_STICKY_KEY = 0x80,
+       };
+ /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
+ static int acpi_pcc_hotkey_add(struct acpi_device *device);
+ static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type);
+ static int acpi_pcc_hotkey_resume(struct acpi_device *device);
+ static const struct acpi_device_id pcc_device_ids[] = {
+       { "MAT0012", 0},
+       { "MAT0013", 0},
+       { "MAT0018", 0},
+       { "MAT0019", 0},
+       { "", 0},
+ };
+ static struct acpi_driver acpi_pcc_driver = {
+       .name =         ACPI_PCC_DRIVER_NAME,
+       .class =        ACPI_PCC_CLASS,
+       .ids =          pcc_device_ids,
+       .ops =          {
+                               .add =          acpi_pcc_hotkey_add,
+                               .remove =       acpi_pcc_hotkey_remove,
+                               .resume =       acpi_pcc_hotkey_resume,
+                       },
+ };
+ #define KEYMAP_SIZE           11
+ static const int initial_keymap[KEYMAP_SIZE] = {
+       /*  0 */ KEY_RESERVED,
+       /*  1 */ KEY_BRIGHTNESSDOWN,
+       /*  2 */ KEY_BRIGHTNESSUP,
+       /*  3 */ KEY_DISPLAYTOGGLE,
+       /*  4 */ KEY_MUTE,
+       /*  5 */ KEY_VOLUMEDOWN,
+       /*  6 */ KEY_VOLUMEUP,
+       /*  7 */ KEY_SLEEP,
+       /*  8 */ KEY_PROG1, /* Change CPU boost */
+       /*  9 */ KEY_BATTERY,
+       /* 10 */ KEY_SUSPEND,
+ };
+ struct pcc_acpi {
+       acpi_handle             handle;
+       unsigned long           num_sifr;
+       int                     sticky_mode;
+       u32                     *sinf;
+       struct acpi_device      *device;
+       struct input_dev        *input_dev;
+       struct backlight_device *backlight;
+       int                     keymap[KEYMAP_SIZE];
+ };
+ struct pcc_keyinput {
+       struct acpi_hotkey      *hotkey;
+ };
+ /* method access functions */
+ static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val)
+ {
+       union acpi_object in_objs[] = {
+               { .integer.type  = ACPI_TYPE_INTEGER,
+                 .integer.value = func, },
+               { .integer.type  = ACPI_TYPE_INTEGER,
+                 .integer.value = val, },
+       };
+       struct acpi_object_list params = {
+               .count   = ARRAY_SIZE(in_objs),
+               .pointer = in_objs,
+       };
+       acpi_status status = AE_OK;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_get_sqty");
 -
+       status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET,
+                                     &params, NULL);
+       return status == AE_OK;
+ }
+ static inline int acpi_pcc_get_sqty(struct acpi_device *device)
+ {
+       unsigned long long s;
+       acpi_status status;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_retrieve_biosdata");
 -
+       status = acpi_evaluate_integer(device->handle, METHOD_HKEY_SQTY,
+                                      NULL, &s);
+       if (ACPI_SUCCESS(status))
+               return s;
+       else {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "evaluation error HKEY.SQTY\n"));
+               return -EINVAL;
+       }
+ }
+ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc, u32 *sinf)
+ {
+       acpi_status status;
+       struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+       union acpi_object *hkey = NULL;
+       int i;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_generate_keyinput");
 -
+       status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, 0,
+                                     &buffer);
+       if (ACPI_FAILURE(status)) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "evaluation error HKEY.SINF\n"));
+               return 0;
+       }
+       hkey = buffer.pointer;
+       if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
+               goto end;
+       }
+       if (pcc->num_sifr < hkey->package.count) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                "SQTY reports bad SINF length\n"));
+               status = AE_ERROR;
+               goto end;
+       }
+       for (i = 0; i < hkey->package.count; i++) {
+               union acpi_object *element = &(hkey->package.elements[i]);
+               if (likely(element->type == ACPI_TYPE_INTEGER)) {
+                       sinf[i] = element->integer.value;
+               } else
+                       ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                        "Invalid HKEY.SINF data\n"));
+       }
+       sinf[hkey->package.count] = -1;
+ end:
+       kfree(buffer.pointer);
+       return status == AE_OK;
+ }
+ /* backlight API interface functions */
+ /* This driver currently treats AC and DC brightness identical,
+  * since we don't need to invent an interface to the core ACPI
+  * logic to receive events in case a power supply is plugged in
+  * or removed */
+ static int bl_get(struct backlight_device *bd)
+ {
+       struct pcc_acpi *pcc = bl_get_data(bd);
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
+               return -EIO;
+       return pcc->sinf[SINF_AC_CUR_BRIGHT];
+ }
+ static int bl_set_status(struct backlight_device *bd)
+ {
+       struct pcc_acpi *pcc = bl_get_data(bd);
+       int bright = bd->props.brightness;
+       int rc;
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
+               return -EIO;
+       if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT])
+               bright = pcc->sinf[SINF_AC_MIN_BRIGHT];
+       if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT])
+               bright = pcc->sinf[SINF_DC_MIN_BRIGHT];
+       if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] ||
+           bright > pcc->sinf[SINF_AC_MAX_BRIGHT])
+               return -EINVAL;
+       rc = acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, bright);
+       if (rc < 0)
+               return rc;
+       return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright);
+ }
+ static struct backlight_ops pcc_backlight_ops = {
+       .get_brightness = bl_get,
+       .update_status  = bl_set_status,
+ };
+ /* sysfs user interface functions */
+ static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
+                           char *buf)
+ {
+       struct acpi_device *acpi = to_acpi_device(dev);
+       struct pcc_acpi *pcc = acpi_driver_data(acpi);
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
+               return -EIO;
+       return sprintf(buf, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
+ }
+ static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
+                           char *buf)
+ {
+       struct acpi_device *acpi = to_acpi_device(dev);
+       struct pcc_acpi *pcc = acpi_driver_data(acpi);
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
+               return -EIO;
+       return sprintf(buf, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
+ }
+ static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
+                        char *buf)
+ {
+       struct acpi_device *acpi = to_acpi_device(dev);
+       struct pcc_acpi *pcc = acpi_driver_data(acpi);
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
+               return -EIO;
+       return sprintf(buf, "%u\n", pcc->sinf[SINF_MUTE]);
+ }
+ static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
+                          char *buf)
+ {
+       struct acpi_device *acpi = to_acpi_device(dev);
+       struct pcc_acpi *pcc = acpi_driver_data(acpi);
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf))
+               return -EIO;
+       return sprintf(buf, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
+ }
+ static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
+                         const char *buf, size_t count)
+ {
+       struct acpi_device *acpi = to_acpi_device(dev);
+       struct pcc_acpi *pcc = acpi_driver_data(acpi);
+       int val;
+       if (count && sscanf(buf, "%i", &val) == 1 &&
+           (val == 0 || val == 1)) {
+               acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val);
+               pcc->sticky_mode = val;
+       }
+       return count;
+ }
+ static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
+ static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
+ static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
+ static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
+ static struct attribute *pcc_sysfs_entries[] = {
+       &dev_attr_numbatt.attr,
+       &dev_attr_lcdtype.attr,
+       &dev_attr_mute.attr,
+       &dev_attr_sticky_key.attr,
+       NULL,
+ };
+ static struct attribute_group pcc_attr_group = {
+       .name   = NULL,         /* put in device directory */
+       .attrs  = pcc_sysfs_entries,
+ };
+ /* hotkey input device driver */
+ static int pcc_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+ {
+       struct pcc_acpi *pcc = input_get_drvdata(dev);
+       if (scancode >= ARRAY_SIZE(pcc->keymap))
+               return -EINVAL;
+       *keycode = pcc->keymap[scancode];
+       return 0;
+ }
+ static int keymap_get_by_keycode(struct pcc_acpi *pcc, int keycode)
+ {
+       int i;
+       for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++) {
+               if (pcc->keymap[i] == keycode)
+                       return i+1;
+       }
+       return 0;
+ }
+ static int pcc_setkeycode(struct input_dev *dev, int scancode, int keycode)
+ {
+       struct pcc_acpi *pcc = input_get_drvdata(dev);
+       int oldkeycode;
+       if (scancode >= ARRAY_SIZE(pcc->keymap))
+               return -EINVAL;
+       if (keycode < 0 || keycode > KEY_MAX)
+               return -EINVAL;
+       oldkeycode = pcc->keymap[scancode];
+       pcc->keymap[scancode] = keycode;
+       set_bit(keycode, dev->keybit);
+       if (!keymap_get_by_keycode(pcc, oldkeycode))
+               clear_bit(oldkeycode, dev->keybit);
+       return 0;
+ }
+ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
+ {
+       struct input_dev *hotk_input_dev = pcc->input_dev;
+       int rc;
+       int key_code, hkey_num;
+       unsigned long long result;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_notify");
 -
+       rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
+                                  NULL, &result);
+       if (!ACPI_SUCCESS(rc)) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                "error getting hotkey status\n"));
+               return;
+       }
+       acpi_bus_generate_proc_event(pcc->device, HKEY_NOTIFY, result);
+       hkey_num = result & 0xf;
+       if (hkey_num < 0 || hkey_num > ARRAY_SIZE(pcc->keymap)) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "hotkey number out of range: %d\n",
+                                 hkey_num));
+               return;
+       }
+       key_code = pcc->keymap[hkey_num];
+       if (key_code != KEY_RESERVED) {
+               int pushed = (result & 0x80) ? TRUE : FALSE;
+               input_report_key(hotk_input_dev, key_code, pushed);
+               input_sync(hotk_input_dev);
+       }
+       return;
+ }
+ static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data)
+ {
+       struct pcc_acpi *pcc = (struct pcc_acpi *) data;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_init_input");
 -
+       switch (event) {
+       case HKEY_NOTIFY:
+               acpi_pcc_generate_keyinput(pcc);
+               break;
+       default:
+               /* nothing to do */
+               break;
+       }
+ }
+ static int acpi_pcc_init_input(struct pcc_acpi *pcc)
+ {
+       int i, rc;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_resume");
 -
+       pcc->input_dev = input_allocate_device();
+       if (!pcc->input_dev) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "Couldn't allocate input device for hotkey"));
+               return -ENOMEM;
+       }
+       pcc->input_dev->evbit[0] = BIT(EV_KEY);
+       pcc->input_dev->name = ACPI_PCC_DRIVER_NAME;
+       pcc->input_dev->phys = ACPI_PCC_INPUT_PHYS;
+       pcc->input_dev->id.bustype = BUS_HOST;
+       pcc->input_dev->id.vendor = 0x0001;
+       pcc->input_dev->id.product = 0x0001;
+       pcc->input_dev->id.version = 0x0100;
+       pcc->input_dev->getkeycode = pcc_getkeycode;
+       pcc->input_dev->setkeycode = pcc_setkeycode;
+       /* load initial keymap */
+       memcpy(pcc->keymap, initial_keymap, sizeof(pcc->keymap));
+       for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++)
+               __set_bit(pcc->keymap[i], pcc->input_dev->keybit);
+       __clear_bit(KEY_RESERVED, pcc->input_dev->keybit);
+       input_set_drvdata(pcc->input_dev, pcc);
+       rc = input_register_device(pcc->input_dev);
+       if (rc < 0)
+               input_free_device(pcc->input_dev);
+       return rc;
+ }
+ /* kernel module interface */
+ static int acpi_pcc_hotkey_resume(struct acpi_device *device)
+ {
+       struct pcc_acpi *pcc = acpi_driver_data(device);
+       acpi_status status = AE_OK;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_add");
 -
+       if (device == NULL || pcc == NULL)
+               return -EINVAL;
+       ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
+                         pcc->sticky_mode));
+       status = acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
+       return status == AE_OK ? 0 : -EINVAL;
+ }
+ static int acpi_pcc_hotkey_add(struct acpi_device *device)
+ {
+       acpi_status status;
+       struct pcc_acpi *pcc;
+       int num_sifr, result;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_init");
 -
+       if (!device)
+               return -EINVAL;
+       num_sifr = acpi_pcc_get_sqty(device);
+       if (num_sifr > 255) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr too large"));
+               return -ENODEV;
+       }
+       pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
+       if (!pcc) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "Couldn't allocate mem for pcc"));
+               return -ENOMEM;
+       }
+       pcc->sinf = kzalloc(sizeof(u32) * (num_sifr + 1), GFP_KERNEL);
+       if (!pcc->sinf) {
+               result = -ENOMEM;
+               goto out_hotkey;
+       }
+       pcc->device = device;
+       pcc->handle = device->handle;
+       pcc->num_sifr = num_sifr;
+       device->driver_data = pcc;
+       strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
+       strcpy(acpi_device_class(device), ACPI_PCC_CLASS);
+       result = acpi_pcc_init_input(pcc);
+       if (result) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "Error installing keyinput handler\n"));
+               goto out_sinf;
+       }
+       /* initialize hotkey input device */
+       status = acpi_install_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY,
+                                            acpi_pcc_hotkey_notify, pcc);
+       if (ACPI_FAILURE(status)) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "Error installing notify handler\n"));
+               result = -ENODEV;
+               goto out_input;
+       }
+       /* initialize backlight */
+       pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
+                                                  &pcc_backlight_ops);
+       if (IS_ERR(pcc->backlight))
+               goto out_notify;
+       if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                "Couldn't retrieve BIOS data\n"));
+               goto out_backlight;
+       }
+       /* read the initial brightness setting from the hardware */
+       pcc->backlight->props.max_brightness =
+                                       pcc->sinf[SINF_AC_MAX_BRIGHT];
+       pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
+       /* read the initial sticky key mode from the hardware */
+       pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
+       /* add sysfs attributes */
+       result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
+       if (result)
+               goto out_backlight;
+       return 0;
+ out_backlight:
+       backlight_device_unregister(pcc->backlight);
+ out_notify:
+       acpi_remove_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY,
+                                  acpi_pcc_hotkey_notify);
+ out_input:
+       input_unregister_device(pcc->input_dev);
+       /* no need to input_free_device() since core input API refcount and
+        * free()s the device */
+ out_sinf:
+       kfree(pcc->sinf);
+ out_hotkey:
+       kfree(pcc);
+       return result;
+ }
+ static int __init acpi_pcc_init(void)
+ {
+       int result = 0;
 -      ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_remove");
 -
+       if (acpi_disabled)
+               return -ENODEV;
+       result = acpi_bus_register_driver(&acpi_pcc_driver);
+       if (result < 0) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                                 "Error registering hotkey driver\n"));
+               return -ENODEV;
+       }
+       return 0;
+ }
+ static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type)
+ {
+       struct pcc_acpi *pcc = acpi_driver_data(device);
 -      ACPI_FUNCTION_TRACE("acpi_pcc_exit");
 -
+       if (!device || !pcc)
+               return -EINVAL;
+       sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
+       backlight_device_unregister(pcc->backlight);
+       acpi_remove_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY,
+                                  acpi_pcc_hotkey_notify);
+       input_unregister_device(pcc->input_dev);
+       /* no need to input_free_device() since core input API refcount and
+        * free()s the device */
+       kfree(pcc->sinf);
+       kfree(pcc);
+       return 0;
+ }
+ static void __exit acpi_pcc_exit(void)
+ {
+       acpi_bus_unregister_driver(&acpi_pcc_driver);
+ }
+ module_init(acpi_pcc_init);
+ module_exit(acpi_pcc_exit);
index 0000000000000000000000000000000000000000,571b211608d1a7224575d99fe3ca5e27cccd03a2..537959d07148a9c3f1e76ec0637b6de3c65ac1cb
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,2781 +1,2784 @@@
 -      struct acpi_namespace_node *node;
 -      union acpi_operand_object *operand;
+ /*
+  * 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>
+  *
+  * 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
+  * (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., 675 Mass Ave, Cambridge, MA 02139, USA.
+  *
+  */
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/moduleparam.h>
+ #include <linux/init.h>
+ #include <linux/smp_lock.h>
+ #include <linux/types.h>
+ #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_SONYPI_COMPAT
+ #include <linux/poll.h>
+ #include <linux/miscdevice.h>
+ #endif
+ #define DRV_PFX                       "sony-laptop: "
+ #define dprintk(msg...)               do {                    \
+       if (debug) printk(KERN_WARNING DRV_PFX  msg);   \
+ } while (0)
+ #define SONY_LAPTOP_DRIVER_VERSION    "0.6"
+ #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("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 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_SONYPI_COMPAT
+ 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 indexes in the keymap
+  */
+ static int sony_laptop_input_index[] = {
+       -1,     /*  0 no event */
+       -1,     /*  1 SONYPI_EVENT_JOGDIAL_DOWN */
+       -1,     /*  2 SONYPI_EVENT_JOGDIAL_UP */
+       -1,     /*  3 SONYPI_EVENT_JOGDIAL_DOWN_PRESSED */
+       -1,     /*  4 SONYPI_EVENT_JOGDIAL_UP_PRESSED */
+       -1,     /*  5 SONYPI_EVENT_JOGDIAL_PRESSED */
+       -1,     /*  6 SONYPI_EVENT_JOGDIAL_RELEASED */
+        0,     /*  7 SONYPI_EVENT_CAPTURE_PRESSED */
+        1,     /*  8 SONYPI_EVENT_CAPTURE_RELEASED */
+        2,     /*  9 SONYPI_EVENT_CAPTURE_PARTIALPRESSED */
+        3,     /* 10 SONYPI_EVENT_CAPTURE_PARTIALRELEASED */
+        4,     /* 11 SONYPI_EVENT_FNKEY_ESC */
+        5,     /* 12 SONYPI_EVENT_FNKEY_F1 */
+        6,     /* 13 SONYPI_EVENT_FNKEY_F2 */
+        7,     /* 14 SONYPI_EVENT_FNKEY_F3 */
+        8,     /* 15 SONYPI_EVENT_FNKEY_F4 */
+        9,     /* 16 SONYPI_EVENT_FNKEY_F5 */
+       10,     /* 17 SONYPI_EVENT_FNKEY_F6 */
+       11,     /* 18 SONYPI_EVENT_FNKEY_F7 */
+       12,     /* 19 SONYPI_EVENT_FNKEY_F8 */
+       13,     /* 20 SONYPI_EVENT_FNKEY_F9 */
+       14,     /* 21 SONYPI_EVENT_FNKEY_F10 */
+       15,     /* 22 SONYPI_EVENT_FNKEY_F11 */
+       16,     /* 23 SONYPI_EVENT_FNKEY_F12 */
+       17,     /* 24 SONYPI_EVENT_FNKEY_1 */
+       18,     /* 25 SONYPI_EVENT_FNKEY_2 */
+       19,     /* 26 SONYPI_EVENT_FNKEY_D */
+       20,     /* 27 SONYPI_EVENT_FNKEY_E */
+       21,     /* 28 SONYPI_EVENT_FNKEY_F */
+       22,     /* 29 SONYPI_EVENT_FNKEY_S */
+       23,     /* 30 SONYPI_EVENT_FNKEY_B */
+       24,     /* 31 SONYPI_EVENT_BLUETOOTH_PRESSED */
+       25,     /* 32 SONYPI_EVENT_PKEY_P1 */
+       26,     /* 33 SONYPI_EVENT_PKEY_P2 */
+       27,     /* 34 SONYPI_EVENT_PKEY_P3 */
+       28,     /* 35 SONYPI_EVENT_BACK_PRESSED */
+       -1,     /* 36 SONYPI_EVENT_LID_CLOSED */
+       -1,     /* 37 SONYPI_EVENT_LID_OPENED */
+       29,     /* 38 SONYPI_EVENT_BLUETOOTH_ON */
+       30,     /* 39 SONYPI_EVENT_BLUETOOTH_OFF */
+       31,     /* 40 SONYPI_EVENT_HELP_PRESSED */
+       32,     /* 41 SONYPI_EVENT_FNKEY_ONLY */
+       33,     /* 42 SONYPI_EVENT_JOGDIAL_FAST_DOWN */
+       34,     /* 43 SONYPI_EVENT_JOGDIAL_FAST_UP */
+       35,     /* 44 SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED */
+       36,     /* 45 SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED */
+       37,     /* 46 SONYPI_EVENT_JOGDIAL_VFAST_DOWN */
+       38,     /* 47 SONYPI_EVENT_JOGDIAL_VFAST_UP */
+       39,     /* 48 SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED */
+       40,     /* 49 SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED */
+       41,     /* 50 SONYPI_EVENT_ZOOM_PRESSED */
+       42,     /* 51 SONYPI_EVENT_THUMBPHRASE_PRESSED */
+       43,     /* 52 SONYPI_EVENT_MEYE_FACE */
+       44,     /* 53 SONYPI_EVENT_MEYE_OPPOSITE */
+       45,     /* 54 SONYPI_EVENT_MEMORYSTICK_INSERT */
+       46,     /* 55 SONYPI_EVENT_MEMORYSTICK_EJECT */
+       -1,     /* 56 SONYPI_EVENT_ANYBUTTON_RELEASED */
+       -1,     /* 57 SONYPI_EVENT_BATTERY_INSERT */
+       -1,     /* 58 SONYPI_EVENT_BATTERY_REMOVE */
+       -1,     /* 59 SONYPI_EVENT_FNKEY_RELEASED */
+       47,     /* 60 SONYPI_EVENT_WIRELESS_ON */
+       48,     /* 61 SONYPI_EVENT_WIRELESS_OFF */
+       49,     /* 62 SONYPI_EVENT_ZOOM_IN_PRESSED */
+       50,     /* 63 SONYPI_EVENT_ZOOM_OUT_PRESSED */
+ };
+ static int sony_laptop_input_keycode_map[] = {
+       KEY_CAMERA,     /*  0 SONYPI_EVENT_CAPTURE_PRESSED */
+       KEY_RESERVED,   /*  1 SONYPI_EVENT_CAPTURE_RELEASED */
+       KEY_RESERVED,   /*  2 SONYPI_EVENT_CAPTURE_PARTIALPRESSED */
+       KEY_RESERVED,   /*  3 SONYPI_EVENT_CAPTURE_PARTIALRELEASED */
+       KEY_FN_ESC,     /*  4 SONYPI_EVENT_FNKEY_ESC */
+       KEY_FN_F1,      /*  5 SONYPI_EVENT_FNKEY_F1 */
+       KEY_FN_F2,      /*  6 SONYPI_EVENT_FNKEY_F2 */
+       KEY_FN_F3,      /*  7 SONYPI_EVENT_FNKEY_F3 */
+       KEY_FN_F4,      /*  8 SONYPI_EVENT_FNKEY_F4 */
+       KEY_FN_F5,      /*  9 SONYPI_EVENT_FNKEY_F5 */
+       KEY_FN_F6,      /* 10 SONYPI_EVENT_FNKEY_F6 */
+       KEY_FN_F7,      /* 11 SONYPI_EVENT_FNKEY_F7 */
+       KEY_FN_F8,      /* 12 SONYPI_EVENT_FNKEY_F8 */
+       KEY_FN_F9,      /* 13 SONYPI_EVENT_FNKEY_F9 */
+       KEY_FN_F10,     /* 14 SONYPI_EVENT_FNKEY_F10 */
+       KEY_FN_F11,     /* 15 SONYPI_EVENT_FNKEY_F11 */
+       KEY_FN_F12,     /* 16 SONYPI_EVENT_FNKEY_F12 */
+       KEY_FN_F1,      /* 17 SONYPI_EVENT_FNKEY_1 */
+       KEY_FN_F2,      /* 18 SONYPI_EVENT_FNKEY_2 */
+       KEY_FN_D,       /* 19 SONYPI_EVENT_FNKEY_D */
+       KEY_FN_E,       /* 20 SONYPI_EVENT_FNKEY_E */
+       KEY_FN_F,       /* 21 SONYPI_EVENT_FNKEY_F */
+       KEY_FN_S,       /* 22 SONYPI_EVENT_FNKEY_S */
+       KEY_FN_B,       /* 23 SONYPI_EVENT_FNKEY_B */
+       KEY_BLUETOOTH,  /* 24 SONYPI_EVENT_BLUETOOTH_PRESSED */
+       KEY_PROG1,      /* 25 SONYPI_EVENT_PKEY_P1 */
+       KEY_PROG2,      /* 26 SONYPI_EVENT_PKEY_P2 */
+       KEY_PROG3,      /* 27 SONYPI_EVENT_PKEY_P3 */
+       KEY_BACK,       /* 28 SONYPI_EVENT_BACK_PRESSED */
+       KEY_BLUETOOTH,  /* 29 SONYPI_EVENT_BLUETOOTH_ON */
+       KEY_BLUETOOTH,  /* 30 SONYPI_EVENT_BLUETOOTH_OFF */
+       KEY_HELP,       /* 31 SONYPI_EVENT_HELP_PRESSED */
+       KEY_FN,         /* 32 SONYPI_EVENT_FNKEY_ONLY */
+       KEY_RESERVED,   /* 33 SONYPI_EVENT_JOGDIAL_FAST_DOWN */
+       KEY_RESERVED,   /* 34 SONYPI_EVENT_JOGDIAL_FAST_UP */
+       KEY_RESERVED,   /* 35 SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED */
+       KEY_RESERVED,   /* 36 SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED */
+       KEY_RESERVED,   /* 37 SONYPI_EVENT_JOGDIAL_VFAST_DOWN */
+       KEY_RESERVED,   /* 38 SONYPI_EVENT_JOGDIAL_VFAST_UP */
+       KEY_RESERVED,   /* 39 SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED */
+       KEY_RESERVED,   /* 40 SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED */
+       KEY_ZOOM,       /* 41 SONYPI_EVENT_ZOOM_PRESSED */
+       BTN_THUMB,      /* 42 SONYPI_EVENT_THUMBPHRASE_PRESSED */
+       KEY_RESERVED,   /* 43 SONYPI_EVENT_MEYE_FACE */
+       KEY_RESERVED,   /* 44 SONYPI_EVENT_MEYE_OPPOSITE */
+       KEY_RESERVED,   /* 45 SONYPI_EVENT_MEMORYSTICK_INSERT */
+       KEY_RESERVED,   /* 46 SONYPI_EVENT_MEMORYSTICK_EJECT */
+       KEY_WLAN,       /* 47 SONYPI_EVENT_WIRELESS_ON */
+       KEY_WLAN,       /* 48 SONYPI_EVENT_WIRELESS_OFF */
+       KEY_ZOOMIN,     /* 49 SONYPI_EVENT_ZOOM_IN_PRESSED */
+       KEY_ZOOMOUT     /* 50 SONYPI_EVENT_ZOOM_OUT_PRESSED */
+ };
+ /* 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 subsystem */
+ 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 };
+       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:
+               if (event >= ARRAY_SIZE(sony_laptop_input_index)) {
+                       dprintk("sony_laptop_report_input_event, event not known: %d\n", event);
+                       break;
+               }
+               if (sony_laptop_input_index[event] != -1) {
+                       kp.key = sony_laptop_input_keycode_map[sony_laptop_input_index[event]];
+                       if (kp.key != KEY_UNKNOWN)
+                               kp.dev = key_dev;
+               }
+               break;
+       }
+       if (kp.dev) {
+               input_report_key(kp.dev, kp.key, 1);
+               /* we emit the scancode so we can always remap the key */
+               input_event(kp.dev, EV_MSC, MSC_SCAN, event);
+               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(struct acpi_device *acpi_device)
+ {
+       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;
+       key_dev->dev.parent = &acpi_device->dev;
+       /* Initialize the Input Drivers: special keys */
+       set_bit(EV_KEY, key_dev->evbit);
+       set_bit(EV_MSC, key_dev->evbit);
+       set_bit(MSC_SCAN, key_dev->mscbit);
+       key_dev->keycodesize = sizeof(sony_laptop_input_keycode_map[0]);
+       key_dev->keycodemax = ARRAY_SIZE(sony_laptop_input_keycode_map);
+       key_dev->keycode = &sony_laptop_input_keycode_map;
+       for (i = 0; i < ARRAY_SIZE(sony_laptop_input_keycode_map); i++) {
+               if (sony_laptop_input_keycode_map[i] != KEY_RESERVED) {
+                       set_bit(sony_laptop_input_keycode_map[i],
+                               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;
+       key_dev->dev.parent = &acpi_device->dev;
+       jog_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
+       jog_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_MIDDLE);
+       jog_dev->relbit[0] = BIT_MASK(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_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);
+ 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 */
+       int (*validate)(const int, const int);  /* input/output validation */
+       int value;              /* current setting */
+       int valid;              /* Has ever been set */
+       int debug;              /* active only in debug mode ? */
+       struct device_attribute devattr;        /* sysfs atribute */
+ };
+ #define SNC_HANDLE_NAMES(_name, _values...) \
+       static char *snc_##_name[] = { _values, NULL }
+ #define SNC_HANDLE(_name, _getters, _setters, _validate, _debug) \
+       { \
+               .name           = __stringify(_name), \
+               .acpiget        = _getters, \
+               .acpiset        = _setters, \
+               .validate       = _validate, \
+               .debug          = _debug, \
+               .devattr        = __ATTR(_name, 0, sony_nc_sysfs_show, sony_nc_sysfs_store), \
+       }
+ #define SNC_HANDLE_NULL       { .name = NULL }
+ SNC_HANDLE_NAMES(fnkey_get, "GHKE");
+ SNC_HANDLE_NAMES(brightness_def_get, "GPBR");
+ SNC_HANDLE_NAMES(brightness_def_set, "SPBR");
+ SNC_HANDLE_NAMES(cdpower_get, "GCDP");
+ SNC_HANDLE_NAMES(cdpower_set, "SCDP", "CDPW");
+ SNC_HANDLE_NAMES(audiopower_get, "GAZP");
+ SNC_HANDLE_NAMES(audiopower_set, "AZPW");
+ SNC_HANDLE_NAMES(lanpower_get, "GLNP");
+ SNC_HANDLE_NAMES(lanpower_set, "LNPW");
+ SNC_HANDLE_NAMES(lidstate_get, "GLID");
+ SNC_HANDLE_NAMES(indicatorlamp_get, "GILS");
+ SNC_HANDLE_NAMES(indicatorlamp_set, "SILS");
+ SNC_HANDLE_NAMES(gainbass_get, "GMGB");
+ SNC_HANDLE_NAMES(gainbass_set, "CMGB");
+ SNC_HANDLE_NAMES(PID_get, "GPID");
+ SNC_HANDLE_NAMES(CTR_get, "GCTR");
+ SNC_HANDLE_NAMES(CTR_set, "SCTR");
+ SNC_HANDLE_NAMES(PCR_get, "GPCR");
+ SNC_HANDLE_NAMES(PCR_set, "SPCR");
+ SNC_HANDLE_NAMES(CMI_get, "GCMI");
+ SNC_HANDLE_NAMES(CMI_set, "SCMI");
+ static struct sony_nc_value sony_nc_values[] = {
+       SNC_HANDLE(brightness_default, snc_brightness_def_get,
+                       snc_brightness_def_set, brightness_default_validate, 0),
+       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),
+       SNC_HANDLE(lanpower, snc_lanpower_get, snc_lanpower_set,
+                       boolean_validate, 1),
+       SNC_HANDLE(lidstate, snc_lidstate_get, NULL,
+                       boolean_validate, 0),
+       SNC_HANDLE(indicatorlamp, snc_indicatorlamp_get, snc_indicatorlamp_set,
+                       boolean_validate, 0),
+       SNC_HANDLE(gainbass, snc_gainbass_get, snc_gainbass_set,
+                       boolean_validate, 0),
+       /* unknown methods */
+       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_nc_acpi_handle;
+ static struct acpi_device *sony_nc_acpi_device = NULL;
+ /*
+  * acpi_evaluate_object wrappers
+  */
+ static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
+ {
+       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, name, NULL, &output);
+       if ((status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER)) {
+               *result = out_obj.integer.value;
+               return 0;
+       }
+       printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n");
+       return -1;
+ }
+ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
+                           int *result)
+ {
+       struct acpi_object_list params;
+       union acpi_object in_obj;
+       struct acpi_buffer output;
+       union acpi_object out_obj;
+       acpi_status status;
+       params.count = 1;
+       params.pointer = &in_obj;
+       in_obj.type = ACPI_TYPE_INTEGER;
+       in_obj.integer.value = value;
+       output.length = sizeof(out_obj);
+       output.pointer = &out_obj;
+       status = acpi_evaluate_object(handle, name, &params, &output);
+       if (status == AE_OK) {
+               if (result != NULL) {
+                       if (out_obj.type != ACPI_TYPE_INTEGER) {
+                               printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad "
+                                      "return type\n");
+                               return -1;
+                       }
+                       *result = out_obj.integer.value;
+               }
+               return 0;
+       }
+       printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n");
+       return -1;
+ }
+ /*
+  * sony_nc_values input/output validate functions
+  */
+ /* brightness_default_validate:
+  *
+  * manipulate input output values to keep consistency with the
+  * backlight framework for which brightness values are 0-based.
+  */
+ static int brightness_default_validate(const int direction, const int value)
+ {
+       switch (direction) {
+               case SNC_VALIDATE_OUT:
+                       return value - 1;
+               case SNC_VALIDATE_IN:
+                       if (value >= 0 && value < SONY_MAX_BRIGHTNESS)
+                               return value + 1;
+       }
+       return -EINVAL;
+ }
+ /* boolean_validate:
+  *
+  * on input validate boolean values 0/1, on output just pass the
+  * received value.
+  */
+ static int boolean_validate(const int direction, const int value)
+ {
+       if (direction == SNC_VALIDATE_IN) {
+               if (value != 0 && value != 1)
+                       return -EINVAL;
+       }
+       return value;
+ }
+ /*
+  * Sysfs show/store common to all sony_nc_values
+  */
+ static ssize_t sony_nc_sysfs_show(struct device *dev, struct device_attribute *attr,
+                             char *buffer)
+ {
+       int value;
+       struct sony_nc_value *item =
+           container_of(attr, struct sony_nc_value, devattr);
+       if (!*item->acpiget)
+               return -EIO;
+       if (acpi_callgetfunc(sony_nc_acpi_handle, *item->acpiget, &value) < 0)
+               return -EIO;
+       if (item->validate)
+               value = item->validate(SNC_VALIDATE_OUT, value);
+       return snprintf(buffer, PAGE_SIZE, "%d\n", value);
+ }
+ static ssize_t sony_nc_sysfs_store(struct device *dev,
+                              struct device_attribute *attr,
+                              const char *buffer, size_t count)
+ {
+       int value;
+       struct sony_nc_value *item =
+           container_of(attr, struct sony_nc_value, devattr);
+       if (!item->acpiset)
+               return -EIO;
+       if (count > 31)
+               return -EINVAL;
+       value = simple_strtoul(buffer, NULL, 10);
+       if (item->validate)
+               value = item->validate(SNC_VALIDATE_IN, value);
+       if (value < 0)
+               return value;
+       if (acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, value, NULL) < 0)
+               return -EIO;
+       item->value = value;
+       item->valid = 1;
+       return count;
+ }
+ /*
+  * Backlight device
+  */
+ static int sony_backlight_update_status(struct backlight_device *bd)
+ {
+       return acpi_callsetfunc(sony_nc_acpi_handle, "SBRT",
+                               bd->props.brightness + 1, NULL);
+ }
+ static int sony_backlight_get_brightness(struct backlight_device *bd)
+ {
+       int 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;
+ }
+ static struct backlight_device *sony_backlight_device;
+ static struct backlight_ops sony_backlight_ops = {
+       .update_status = sony_backlight_update_status,
+       .get_brightness = sony_backlight_get_brightness,
+ };
+ /*
+  * New SNC-only Vaios event mapping to driver known keys
+  */
+ struct sony_nc_event {
+       u8      data;
+       u8      event;
+ };
+ static struct sony_nc_event *sony_nc_events;
+ /* Vaio C* --maybe also FE*, N* and AR* ?-- special init sequence
+  * for Fn keys
+  */
+ static int sony_nc_C_enable(const struct dmi_system_id *id)
+ {
+       int result = 0;
+       printk(KERN_NOTICE DRV_PFX "detected %s\n", id->ident);
+       sony_nc_events = id->driver_data;
+       if (acpi_callsetfunc(sony_nc_acpi_handle, "SN02", 0x4, &result) < 0
+                       || acpi_callsetfunc(sony_nc_acpi_handle, "SN07", 0x2, &result) < 0
+                       || acpi_callsetfunc(sony_nc_acpi_handle, "SN02", 0x10, &result) < 0
+                       || acpi_callsetfunc(sony_nc_acpi_handle, "SN07", 0x0, &result) < 0
+                       || acpi_callsetfunc(sony_nc_acpi_handle, "SN03", 0x2, &result) < 0
+                       || acpi_callsetfunc(sony_nc_acpi_handle, "SN07", 0x101, &result) < 0) {
+               printk(KERN_WARNING DRV_PFX "failed to initialize SNC, some "
+                               "functionalities may be missing\n");
+               return 1;
+       }
+       return 0;
+ }
+ static struct sony_nc_event sony_C_events[] = {
+       { 0x81, SONYPI_EVENT_FNKEY_F1 },
+       { 0x01, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x85, SONYPI_EVENT_FNKEY_F5 },
+       { 0x05, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x86, SONYPI_EVENT_FNKEY_F6 },
+       { 0x06, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x87, SONYPI_EVENT_FNKEY_F7 },
+       { 0x07, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x8A, SONYPI_EVENT_FNKEY_F10 },
+       { 0x0A, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x8C, SONYPI_EVENT_FNKEY_F12 },
+       { 0x0C, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0, 0 },
+ };
+ /* SNC-only model map */
+ static const struct dmi_system_id sony_nc_ids[] = {
+               {
+                       .ident = "Sony Vaio FE Series",
+                       .callback = sony_nc_C_enable,
+                       .driver_data = sony_C_events,
+                       .matches = {
+                               DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+                               DMI_MATCH(DMI_PRODUCT_NAME, "VGN-FE"),
+                       },
+               },
+               {
+                       .ident = "Sony Vaio FZ Series",
+                       .callback = sony_nc_C_enable,
+                       .driver_data = sony_C_events,
+                       .matches = {
+                               DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+                               DMI_MATCH(DMI_PRODUCT_NAME, "VGN-FZ"),
+                       },
+               },
+               {
+                       .ident = "Sony Vaio C Series",
+                       .callback = sony_nc_C_enable,
+                       .driver_data = sony_C_events,
+                       .matches = {
+                               DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+                               DMI_MATCH(DMI_PRODUCT_NAME, "VGN-C"),
+                       },
+               },
+               {
+                       .ident = "Sony Vaio N Series",
+                       .callback = sony_nc_C_enable,
+                       .driver_data = sony_C_events,
+                       .matches = {
+                               DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+                               DMI_MATCH(DMI_PRODUCT_NAME, "VGN-N"),
+                       },
+               },
+               { }
+ };
+ /*
+  * ACPI callbacks
+  */
+ static void sony_acpi_notify(acpi_handle handle, u32 event, void *data)
+ {
+       struct sony_nc_event *evmap;
+       u32 ev = event;
+       int result;
+       if (ev == 0x92) {
+               /* read the key pressed from EC.GECR
+                * A call to SN07 with 0x0202 will do it as well respecting
+                * the current protocol on different OSes
+                *
+                * Note: the path for GECR may be
+                *   \_SB.PCI0.LPCB.EC (C, FE, AR, N and friends)
+                *   \_SB.PCI0.PIB.EC0 (VGN-FR notifications are sent directly, no GECR)
+                *
+                * TODO: we may want to do the same for the older GHKE -need
+                *       dmi list- so this snippet may become one more callback.
+                */
+               if (acpi_callsetfunc(handle, "SN07", 0x0202, &result) < 0)
+                       dprintk("sony_acpi_notify, unable to decode event 0x%.2x\n", ev);
+               else
+                       ev = result & 0xFF;
+       }
+       if (sony_nc_events)
+               for (evmap = sony_nc_events; evmap->event; evmap++) {
+                       if (evmap->data == ev) {
+                               ev = evmap->event;
+                               break;
+                       }
+               }
+       dprintk("sony_acpi_notify, event: 0x%.2x\n", ev);
+       sony_laptop_report_input_event(ev);
+       acpi_bus_generate_proc_event(sony_nc_acpi_device, 1, ev);
+ }
+ static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
+                                     void *context, void **return_value)
+ {
 -      node = (struct acpi_namespace_node *)handle;
 -      operand = (union acpi_operand_object *)node->object;
++      struct acpi_device_info *info;
++      struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
 -      printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
 -             (u32) operand->method.param_count);
++      if (ACPI_SUCCESS(acpi_get_object_info(handle, &buffer))) {
++              info = buffer.pointer;
++              printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n",
++                      (char *)&info->name, info->param_count);
++
++              kfree(buffer.pointer);
++      }
+       return AE_OK;
+ }
+ /*
+  * ACPI device
+  */
+ static int sony_nc_resume(struct acpi_device *device)
+ {
+       struct sony_nc_value *item;
+       for (item = sony_nc_values; item->name; item++) {
+               int ret;
+               if (!item->valid)
+                       continue;
+               ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset,
+                                      item->value, NULL);
+               if (ret < 0) {
+                       printk("%s: %d\n", __func__, ret);
+                       break;
+               }
+       }
+       /* set the last requested brightness level */
+       if (sony_backlight_device &&
+                       !sony_backlight_update_status(sony_backlight_device))
+               printk(KERN_WARNING DRV_PFX "unable to restore brightness level\n");
+       /* re-initialize models with specific requirements */
+       dmi_check_system(sony_nc_ids);
+       return 0;
+ }
+ 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_nc_acpi_device = device;
+       strcpy(acpi_device_class(device), "sony/hotkey");
+       sony_nc_acpi_handle = device->handle;
+       /* read device status */
+       result = acpi_bus_get_status(device);
+       /* bail IFF the above call was successful and the device is not present */
+       if (!result && !device->status.present) {
+               dprintk("Device not present\n");
+               result = -ENODEV;
+               goto outwalk;
+       }
+       if (debug) {
+               status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_nc_acpi_handle,
+                                            1, sony_walk_callback, NULL, NULL);
+               if (ACPI_FAILURE(status)) {
+                       printk(KERN_WARNING DRV_PFX "unable to walk acpi resources\n");
+                       result = -ENODEV;
+                       goto outwalk;
+               }
+       }
+       /* try to _INI the device if such method exists (ACPI spec 3.0-6.5.1
+        * should be respected as we already checked for the device presence above */
+       if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, METHOD_NAME__INI, &handle))) {
+               dprintk("Invoking _INI\n");
+               if (ACPI_FAILURE(acpi_evaluate_object(sony_nc_acpi_handle, METHOD_NAME__INI,
+                                               NULL, NULL)))
+                       dprintk("_INI Method failed\n");
+       }
+       /* setup input devices and helper fifo */
+       result = sony_laptop_setup_input(device);
+       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(KERN_WARNING DRV_PFX "unable to install notify handler (%u)\n", status);
+               result = -ENODEV;
+               goto outinput;
+       }
+       if (acpi_video_backlight_support()) {
+               printk(KERN_INFO DRV_PFX "brightness ignored, must be "
+                      "controlled by ACPI video driver\n");
+       } else 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(KERN_WARNING DRV_PFX "unable to register backlight device\n");
+                       sony_backlight_device = NULL;
+               } else {
+                       sony_backlight_device->props.brightness =
+                           sony_backlight_get_brightness
+                           (sony_backlight_device);
+                       sony_backlight_device->props.max_brightness =
+                           SONY_MAX_BRIGHTNESS - 1;
+               }
+       }
+       /* initialize models with specific requirements */
+       dmi_check_system(sony_nc_ids);
+       result = sony_pf_add();
+       if (result)
+               goto outbacklight;
+       /* 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_nc_acpi_handle,
+                                           ACPI_DEVICE_NOTIFY,
+                                           sony_acpi_notify);
+       if (ACPI_FAILURE(status))
+               printk(KERN_WARNING DRV_PFX "unable to remove notify handler\n");
+       outinput:
+       sony_laptop_remove_input();
+       outwalk:
+       return result;
+ }
+ 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_nc_acpi_device = NULL;
+       status = acpi_remove_notify_handler(sony_nc_acpi_handle,
+                                           ACPI_DEVICE_NOTIFY,
+                                           sony_acpi_notify);
+       if (ACPI_FAILURE(status))
+               printk(KERN_WARNING DRV_PFX "unable to remove notify handler\n");
+       for (item = sony_nc_values; item->name; ++item) {
+               device_remove_file(&sony_pf_device->dev, &item->devattr);
+       }
+       sony_pf_remove();
+       sony_laptop_remove_input();
+       dprintk(SONY_NC_DRIVER_NAME " removed.\n");
+       return 0;
+ }
+ static const struct acpi_device_id sony_device_ids[] = {
+       {SONY_NC_HID, 0},
+       {SONY_PIC_HID, 0},
+       {"", 0},
+ };
+ MODULE_DEVICE_TABLE(acpi, sony_device_ids);
+ static const struct acpi_device_id sony_nc_device_ids[] = {
+       {SONY_NC_HID, 0},
+       {"", 0},
+ };
+ static struct acpi_driver sony_nc_driver = {
+       .name = SONY_NC_DRIVER_NAME,
+       .class = SONY_NC_CLASS,
+       .ids = sony_nc_device_ids,
+       .owner = THIS_MODULE,
+       .ops = {
+               .add = sony_nc_add,
+               .remove = sony_nc_remove,
+               .resume = sony_nc_resume,
+               },
+ };
+ /*********** SPIC (SNY6001) Device ***********/
+ #define SONYPI_DEVICE_TYPE1   0x00000001
+ #define SONYPI_DEVICE_TYPE2   0x00000002
+ #define SONYPI_DEVICE_TYPE3   0x00000004
+ #define SONYPI_DEVICE_TYPE4   0x00000008
+ #define SONYPI_TYPE1_OFFSET   0x04
+ #define SONYPI_TYPE2_OFFSET   0x12
+ #define SONYPI_TYPE3_OFFSET   0x12
+ #define SONYPI_TYPE4_OFFSET   0x12
+ struct sony_pic_ioport {
+       struct acpi_resource_io io1;
+       struct acpi_resource_io io2;
+       struct list_head        list;
+ };
+ struct sony_pic_irq {
+       struct acpi_resource_irq        irq;
+       struct list_head                list;
+ };
+ struct sonypi_eventtypes {
+       u8                      data;
+       unsigned long           mask;
+       struct sonypi_event     *events;
+ };
+ struct device_ctrl {
+       int                             model;
+       int                             (*handle_irq)(const u8, const u8);
+       u16                             evport_offset;
+       u8                              has_camera;
+       u8                              has_bluetooth;
+       u8                              has_wwan;
+       struct sonypi_eventtypes        *event_types;
+ };
+ struct sony_pic_dev {
+       struct device_ctrl      *control;
+       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;
+       u8                      camera_power;
+       u8                      bluetooth_power;
+       u8                      wwan_power;
+ };
+ 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 },
+       { 0x40, 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 },
+       { 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 },
+       { 0x10, SONYPI_EVENT_ZOOM_IN_PRESSED },
+       { 0x20, SONYPI_EVENT_ZOOM_OUT_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 type1_events[] = {
+       { 0, 0xffffffff, sonypi_releaseev },
+       { 0x70, SONYPI_MEYE_MASK, sonypi_meyeev },
+       { 0x30, SONYPI_LID_MASK, sonypi_lidev },
+       { 0x60, SONYPI_CAPTURE_MASK, sonypi_captureev },
+       { 0x10, SONYPI_JOGGER_MASK, sonypi_joggerev },
+       { 0x20, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { 0x30, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+       { 0x40, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { 0x30, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { 0x40, SONYPI_BATTERY_MASK, sonypi_batteryev },
+       { 0 },
+ };
+ static struct sonypi_eventtypes type2_events[] = {
+       { 0, 0xffffffff, sonypi_releaseev },
+       { 0x38, SONYPI_LID_MASK, sonypi_lidev },
+       { 0x11, SONYPI_JOGGER_MASK, sonypi_joggerev },
+       { 0x61, SONYPI_CAPTURE_MASK, sonypi_captureev },
+       { 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { 0x31, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+       { 0x08, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { 0x11, SONYPI_BACK_MASK, sonypi_backev },
+       { 0x21, SONYPI_HELP_MASK, sonypi_helpev },
+       { 0x21, SONYPI_ZOOM_MASK, sonypi_zoomev },
+       { 0x20, SONYPI_THUMBPHRASE_MASK, sonypi_thumbphraseev },
+       { 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+       { 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { 0 },
+ };
+ static struct sonypi_eventtypes type3_events[] = {
+       { 0, 0xffffffff, sonypi_releaseev },
+       { 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { 0x31, SONYPI_WIRELESS_MASK, sonypi_wlessev },
+       { 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+       { 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { 0 },
+ };
+ static struct sonypi_eventtypes type4_events[] = {
+       { 0, 0xffffffff, sonypi_releaseev },
+       { 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+       { 0x31, SONYPI_WIRELESS_MASK, sonypi_wlessev },
+       { 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+       { 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+       { 0x05, SONYPI_PKEY_MASK, sonypi_pkeyev },
+       { 0x05, SONYPI_ZOOM_MASK, sonypi_zoomev },
+       { 0x05, SONYPI_CAPTURE_MASK, sonypi_captureev },
+       { 0 },
+ };
+ /* low level spic calls */
+ #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__, __func__, __LINE__);  \
+ }
+ static u8 sony_pic_call1(u8 dev)
+ {
+       u8 v1, v2;
+       wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2,
+                       ITERATIONS_LONG);
+       outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
+       v1 = inb_p(spic_dev.cur_ioport->io1.minimum + 4);
+       v2 = inb_p(spic_dev.cur_ioport->io1.minimum);
+       dprintk("sony_pic_call1(0x%.2x): 0x%.4x\n", dev, (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->io1.minimum + 4) & 2,
+                       ITERATIONS_LONG);
+       outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
+       wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2,
+                       ITERATIONS_LONG);
+       outb(fn, spic_dev.cur_ioport->io1.minimum);
+       v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
+       dprintk("sony_pic_call2(0x%.2x - 0x%.2x): 0x%.4x\n", dev, fn, 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->io1.minimum + 4) & 2, ITERATIONS_LONG);
+       outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
+       wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
+       outb(fn, spic_dev.cur_ioport->io1.minimum);
+       wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
+       outb(v, spic_dev.cur_ioport->io1.minimum);
+       v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
+       dprintk("sony_pic_call3(0x%.2x - 0x%.2x - 0x%.2x): 0x%.4x\n",
+                       dev, fn, v, v1);
+       return v1;
+ }
+ /*
+  * minidrivers for SPIC models
+  */
+ static int type4_handle_irq(const u8 data_mask, const u8 ev)
+ {
+       /*
+        * 0x31 could mean we have to take some extra action and wait for
+        * the next irq for some Type4 models, it will generate a new
+        * irq and we can read new data from the device:
+        *  - 0x5c and 0x5f requires 0xA0
+        *  - 0x61 requires 0xB3
+        */
+       if (data_mask == 0x31) {
+               if (ev == 0x5c || ev == 0x5f)
+                       sony_pic_call1(0xA0);
+               else if (ev == 0x61)
+                       sony_pic_call1(0xB3);
+               return 0;
+       }
+       return 1;
+ }
+ static struct device_ctrl spic_types[] = {
+       {
+               .model = SONYPI_DEVICE_TYPE1,
+               .handle_irq = NULL,
+               .evport_offset = SONYPI_TYPE1_OFFSET,
+               .event_types = type1_events,
+       },
+       {
+               .model = SONYPI_DEVICE_TYPE2,
+               .handle_irq = NULL,
+               .evport_offset = SONYPI_TYPE2_OFFSET,
+               .event_types = type2_events,
+       },
+       {
+               .model = SONYPI_DEVICE_TYPE3,
+               .handle_irq = NULL,
+               .evport_offset = SONYPI_TYPE3_OFFSET,
+               .event_types = type3_events,
+       },
+       {
+               .model = SONYPI_DEVICE_TYPE4,
+               .handle_irq = type4_handle_irq,
+               .evport_offset = SONYPI_TYPE4_OFFSET,
+               .event_types = type4_events,
+       },
+ };
+ static void sony_pic_detect_device_type(struct sony_pic_dev *dev)
+ {
+       struct pci_dev *pcidev;
+       pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                       PCI_DEVICE_ID_INTEL_82371AB_3, NULL);
+       if (pcidev) {
+               dev->control = &spic_types[0];
+               goto out;
+       }
+       pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                       PCI_DEVICE_ID_INTEL_ICH6_1, NULL);
+       if (pcidev) {
+               dev->control = &spic_types[2];
+               goto out;
+       }
+       pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                       PCI_DEVICE_ID_INTEL_ICH7_1, NULL);
+       if (pcidev) {
+               dev->control = &spic_types[3];
+               goto out;
+       }
+       pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+                       PCI_DEVICE_ID_INTEL_ICH8_4, NULL);
+       if (pcidev) {
+               dev->control = &spic_types[3];
+               goto out;
+       }
+       /* default */
+       dev->control = &spic_types[1];
+ out:
+       if (pcidev)
+               pci_dev_put(pcidev);
+       printk(KERN_INFO DRV_PFX "detected Type%d model\n",
+                       dev->control->model == SONYPI_DEVICE_TYPE1 ? 1 :
+                       dev->control->model == SONYPI_DEVICE_TYPE2 ? 2 :
+                       dev->control->model == SONYPI_DEVICE_TYPE3 ? 3 : 4);
+ }
+ /* 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_SONYPI_COMPAT
+ /* 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)
+ {
+       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 */
+       lock_kernel();
+       if (atomic_inc_return(&sonypi_compat.open_count) == 1)
+               kfifo_reset(sonypi_compat.fifo);
+       unlock_kernel();
+       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_SONYPI_COMPAT */
+ /*
+  * 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:
+               {
+                       /* start IO enumeration */
+                       struct sony_pic_ioport *ioport = kzalloc(sizeof(*ioport), GFP_KERNEL);
+                       if (!ioport)
+                               return AE_ERROR;
+                       list_add(&ioport->list, &dev->ioports);
+                       return AE_OK;
+               }
+       case ACPI_RESOURCE_TYPE_END_DEPENDENT:
+               /* end IO enumeration */
+               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(&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 =
+                               list_first_entry(&dev->ioports, struct sony_pic_ioport, list);
+                       if (!io) {
+                               dprintk("Blank IO resource\n");
+                               return AE_OK;
+                       }
+                       if (!ioport->io1.minimum) {
+                               memcpy(&ioport->io1, io, sizeof(*io));
+                               dprintk("IO1 at 0x%.4x (0x%.2x)\n", ioport->io1.minimum,
+                                               ioport->io1.address_length);
+                       }
+                       else if (!ioport->io2.minimum) {
+                               memcpy(&ioport->io2, io, sizeof(*io));
+                               dprintk("IO2 at 0x%.4x (0x%.2x)\n", ioport->io2.minimum,
+                                               ioport->io2.address_length);
+                       }
+                       else {
+                               printk(KERN_ERR DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n");
+                               return AE_ERROR;
+                       }
+                       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)
+ {
+       acpi_status ret = acpi_evaluate_object(device->handle, "_DIS", NULL,
+                                              NULL);
+       if (ACPI_FAILURE(ret) && ret != AE_NOT_FOUND)
+               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;
+       /* Type 1 resource layout is:
+        *    IO
+        *    IO
+        *    IRQNoFlags
+        *    End
+        *
+        * Type 2 and 3 resource layout is:
+        *    IO
+        *    IRQNoFlags
+        *    End
+        */
+       struct {
+               struct acpi_resource res1;
+               struct acpi_resource res2;
+               struct acpi_resource res3;
+               struct acpi_resource res4;
+       } *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 Type 1 resources */
+       if (spic_dev.control->model == SONYPI_DEVICE_TYPE1) {
+               /* setup io resources */
+               resource->res1.type = ACPI_RESOURCE_TYPE_IO;
+               resource->res1.length = sizeof(struct acpi_resource);
+               memcpy(&resource->res1.data.io, &ioport->io1,
+                               sizeof(struct acpi_resource_io));
+               resource->res2.type = ACPI_RESOURCE_TYPE_IO;
+               resource->res2.length = sizeof(struct acpi_resource);
+               memcpy(&resource->res2.data.io, &ioport->io2,
+                               sizeof(struct acpi_resource_io));
+               /* setup irq resource */
+               resource->res3.type = ACPI_RESOURCE_TYPE_IRQ;
+               resource->res3.length = sizeof(struct acpi_resource);
+               memcpy(&resource->res3.data.irq, &irq->irq,
+                               sizeof(struct acpi_resource_irq));
+               /* we requested a shared irq */
+               resource->res3.data.irq.sharable = ACPI_SHARED;
+               resource->res4.type = ACPI_RESOURCE_TYPE_END_TAG;
+       }
+       /* setup Type 2/3 resources */
+       else {
+               /* setup io resource */
+               resource->res1.type = ACPI_RESOURCE_TYPE_IO;
+               resource->res1.length = sizeof(struct acpi_resource);
+               memcpy(&resource->res1.data.io, &ioport->io1,
+                               sizeof(struct acpi_resource_io));
+               /* setup irq resource */
+               resource->res2.type = ACPI_RESOURCE_TYPE_IRQ;
+               resource->res2.length = sizeof(struct acpi_resource);
+               memcpy(&resource->res2.data.irq, &irq->irq,
+                               sizeof(struct acpi_resource_irq));
+               /* we requested a shared irq */
+               resource->res2.data.irq.sharable = ACPI_SHARED;
+               resource->res3.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\n");
+               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;
+       u8 ev = 0;
+       u8 data_mask = 0;
+       u8 device_event = 0;
+       struct sony_pic_dev *dev = (struct sony_pic_dev *) dev_id;
+       ev = inb_p(dev->cur_ioport->io1.minimum);
+       if (dev->cur_ioport->io2.minimum)
+               data_mask = inb_p(dev->cur_ioport->io2.minimum);
+       else
+               data_mask = inb_p(dev->cur_ioport->io1.minimum +
+                               dev->control->evport_offset);
+       dprintk("event ([%.2x] [%.2x]) at port 0x%.4x(+0x%.2x)\n",
+                       ev, data_mask, dev->cur_ioport->io1.minimum,
+                       dev->control->evport_offset);
+       if (ev == 0x00 || ev == 0xff)
+               return IRQ_HANDLED;
+       for (i = 0; dev->control->event_types[i].mask; i++) {
+               if ((data_mask & dev->control->event_types[i].data) !=
+                   dev->control->event_types[i].data)
+                       continue;
+               if (!(mask & dev->control->event_types[i].mask))
+                       continue;
+               for (j = 0; dev->control->event_types[i].events[j].event; j++) {
+                       if (ev == dev->control->event_types[i].events[j].data) {
+                               device_event =
+                                       dev->control->
+                                               event_types[i].events[j].event;
+                               goto found;
+                       }
+               }
+       }
+       /* Still not able to decode the event try to pass
+        * it over to the minidriver
+        */
+       if (dev->control->handle_irq &&
+                       dev->control->handle_irq(data_mask, ev) == 0)
+               return IRQ_HANDLED;
+       dprintk("unknown event ([%.2x] [%.2x]) at port 0x%.4x(+0x%.2x)\n",
+                       ev, data_mask, dev->cur_ioport->io1.minimum,
+                       dev->control->evport_offset);
+       return IRQ_HANDLED;
+ found:
+       sony_laptop_report_input_event(device_event);
+       acpi_bus_generate_proc_event(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;
+       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->io1.minimum,
+                       spic_dev.cur_ioport->io1.address_length);
+       if (spic_dev.cur_ioport->io2.minimum)
+               release_region(spic_dev.cur_ioport->io2.minimum,
+                               spic_dev.cur_ioport->io2.address_length);
+       sonypi_compat_exit();
+       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");
+       sony_pic_detect_device_type(&spic_dev);
+       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(device);
+       if (result) {
+               printk(KERN_ERR DRV_PFX
+                               "Unabe to create input devices.\n");
+               goto err_free_resources;
+       }
+       if (sonypi_compat_init())
+               goto err_remove_input;
+       /* request io port */
+       list_for_each_entry_reverse(io, &spic_dev.ioports, list) {
+               if (request_region(io->io1.minimum, io->io1.address_length,
+                                       "Sony Programable I/O Device")) {
+                       dprintk("I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n",
+                                       io->io1.minimum, io->io1.maximum,
+                                       io->io1.address_length);
+                       /* Type 1 have 2 ioports */
+                       if (io->io2.minimum) {
+                               if (request_region(io->io2.minimum,
+                                               io->io2.address_length,
+                                               "Sony Programable I/O Device")) {
+                                       dprintk("I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n",
+                                                       io->io2.minimum, io->io2.maximum,
+                                                       io->io2.address_length);
+                                       spic_dev.cur_ioport = io;
+                                       break;
+                               }
+                               else {
+                                       dprintk("Unable to get I/O port2: "
+                                                       "0x%.4x (0x%.4x) + 0x%.2x\n",
+                                                       io->io2.minimum, io->io2.maximum,
+                                                       io->io2.address_length);
+                                       release_region(io->io1.minimum,
+                                                       io->io1.address_length);
+                               }
+                       }
+                       else {
+                               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_compat;
+       }
+       /* request IRQ */
+       list_for_each_entry_reverse(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;
+       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->io1.minimum,
+                       spic_dev.cur_ioport->io1.address_length);
+       if (spic_dev.cur_ioport->io2.minimum)
+               release_region(spic_dev.cur_ioport->io2.minimum,
+                               spic_dev.cur_ioport->io2.address_length);
+ err_remove_compat:
+       sonypi_compat_exit();
+ 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 const struct acpi_device_id sony_pic_device_ids[] = {
+       {SONY_PIC_HID, 0},
+       {"", 0},
+ };
+ static struct acpi_driver sony_pic_driver = {
+       .name = SONY_PIC_DRIVER_NAME,
+       .class = SONY_PIC_CLASS,
+       .ids = sony_pic_device_ids,
+       .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_laptop_exit(void)
+ {
+       acpi_bus_unregister_driver(&sony_nc_driver);
+       if (!no_spic)
+               acpi_bus_unregister_driver(&sony_pic_driver);
+ }
+ module_init(sony_laptop_init);
+ module_exit(sony_laptop_exit);
index 0000000000000000000000000000000000000000,f25e4c974dcf9d7f4ff557451d234cd6d2a05428..b4a4aa9ee482edacecb4224b4245ee8db2ba0c7f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,290 +1,289 @@@
 -#include <acpi/actypes.h>
+ /*
+  *  HP Compaq TC1100 Tablet WMI Extras Driver
+  *
+  *  Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+  *  Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com>
+  *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
+  *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
+  *
+  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  *
+  *  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.
+  *
+  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  */
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/init.h>
+ #include <linux/types.h>
+ #include <acpi/acpi.h>
+ #include <acpi/acpi_bus.h>
+ #include <acpi/acpi_drivers.h>
+ #include <linux/platform_device.h>
+ #define GUID "C364AC71-36DB-495A-8494-B439D472A505"
+ #define TC1100_INSTANCE_WIRELESS              1
+ #define TC1100_INSTANCE_JOGDIAL               2
+ #define TC1100_LOGPREFIX "tc1100-wmi: "
+ #define TC1100_INFO KERN_INFO TC1100_LOGPREFIX
+ MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho");
+ MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras");
+ MODULE_LICENSE("GPL");
+ MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505");
+ static int tc1100_probe(struct platform_device *device);
+ static int tc1100_remove(struct platform_device *device);
+ static int tc1100_suspend(struct platform_device *device, pm_message_t state);
+ static int tc1100_resume(struct platform_device *device);
+ static struct platform_driver tc1100_driver = {
+       .driver = {
+               .name = "tc1100-wmi",
+               .owner = THIS_MODULE,
+       },
+       .probe = tc1100_probe,
+       .remove = tc1100_remove,
+       .suspend = tc1100_suspend,
+       .resume = tc1100_resume,
+ };
+ static struct platform_device *tc1100_device;
+ struct tc1100_data {
+       u32 wireless;
+       u32 jogdial;
+ };
+ static struct tc1100_data suspend_data;
+ /* --------------------------------------------------------------------------
+                               Device Management
+    -------------------------------------------------------------------------- */
+ static int get_state(u32 *out, u8 instance)
+ {
+       u32 tmp;
+       acpi_status status;
+       struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+       union acpi_object *obj;
+       if (!out)
+               return -EINVAL;
+       if (instance > 2)
+               return -ENODEV;
+       status = wmi_query_block(GUID, instance, &result);
+       if (ACPI_FAILURE(status))
+               return -ENODEV;
+       obj = (union acpi_object *) result.pointer;
+       if (obj && obj->type == ACPI_TYPE_BUFFER &&
+               obj->buffer.length == sizeof(u32)) {
+               tmp = *((u32 *) obj->buffer.pointer);
+       } else {
+               tmp = 0;
+       }
+       if (result.length > 0 && result.pointer)
+               kfree(result.pointer);
+       switch (instance) {
+       case TC1100_INSTANCE_WIRELESS:
+               *out = (tmp == 3) ? 1 : 0;
+               return 0;
+       case TC1100_INSTANCE_JOGDIAL:
+               *out = (tmp == 1) ? 1 : 0;
+               return 0;
+       default:
+               return -ENODEV;
+       }
+ }
+ static int set_state(u32 *in, u8 instance)
+ {
+       u32 value;
+       acpi_status status;
+       struct acpi_buffer input;
+       if (!in)
+               return -EINVAL;
+       if (instance > 2)
+               return -ENODEV;
+       switch (instance) {
+       case TC1100_INSTANCE_WIRELESS:
+               value = (*in) ? 1 : 2;
+               break;
+       case TC1100_INSTANCE_JOGDIAL:
+               value = (*in) ? 0 : 1;
+               break;
+       default:
+               return -ENODEV;
+       }
+       input.length = sizeof(u32);
+       input.pointer = &value;
+       status = wmi_set_block(GUID, instance, &input);
+       if (ACPI_FAILURE(status))
+               return -ENODEV;
+       return 0;
+ }
+ /* --------------------------------------------------------------------------
+                               FS Interface (/sys)
+    -------------------------------------------------------------------------- */
+ /*
+  * Read/ write bool sysfs macro
+  */
+ #define show_set_bool(value, instance) \
+ static ssize_t \
+ show_bool_##value(struct device *dev, struct device_attribute *attr, \
+       char *buf) \
+ { \
+       u32 result; \
+       acpi_status status = get_state(&result, instance); \
+       if (ACPI_SUCCESS(status)) \
+               return sprintf(buf, "%d\n", result); \
+       return sprintf(buf, "Read error\n"); \
+ } \
+ \
+ static ssize_t \
+ set_bool_##value(struct device *dev, struct device_attribute *attr, \
+       const char *buf, size_t count) \
+ { \
+       u32 tmp = simple_strtoul(buf, NULL, 10); \
+       acpi_status status = set_state(&tmp, instance); \
+               if (ACPI_FAILURE(status)) \
+                       return -EINVAL; \
+       return count; \
+ } \
+ static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \
+       show_bool_##value, set_bool_##value);
+ show_set_bool(wireless, TC1100_INSTANCE_WIRELESS);
+ show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL);
+ static void remove_fs(void)
+ {
+       device_remove_file(&tc1100_device->dev, &dev_attr_wireless);
+       device_remove_file(&tc1100_device->dev, &dev_attr_jogdial);
+ }
+ static int add_fs(void)
+ {
+       int ret;
+       ret = device_create_file(&tc1100_device->dev, &dev_attr_wireless);
+       if (ret)
+               goto add_sysfs_error;
+       ret = device_create_file(&tc1100_device->dev, &dev_attr_jogdial);
+       if (ret)
+               goto add_sysfs_error;
+       return ret;
+ add_sysfs_error:
+       remove_fs();
+       return ret;
+ }
+ /* --------------------------------------------------------------------------
+                               Driver Model
+    -------------------------------------------------------------------------- */
+ static int tc1100_probe(struct platform_device *device)
+ {
+       int result = 0;
+       result = add_fs();
+       return result;
+ }
+ static int tc1100_remove(struct platform_device *device)
+ {
+       remove_fs();
+       return 0;
+ }
+ static int tc1100_suspend(struct platform_device *dev, pm_message_t state)
+ {
+       int ret;
+       ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+       if (ret)
+               return ret;
+       ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+       if (ret)
+               return ret;
+       return ret;
+ }
+ static int tc1100_resume(struct platform_device *dev)
+ {
+       int ret;
+       ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+       if (ret)
+               return ret;
+       ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+       if (ret)
+               return ret;
+       return ret;
+ }
+ static int __init tc1100_init(void)
+ {
+       int result = 0;
+       if (!wmi_has_guid(GUID))
+               return -ENODEV;
+       result = platform_driver_register(&tc1100_driver);
+       if (result)
+               return result;
+       tc1100_device = platform_device_alloc("tc1100-wmi", -1);
+       platform_device_add(tc1100_device);
+       printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras loaded\n");
+       return result;
+ }
+ static void __exit tc1100_exit(void)
+ {
+       platform_device_del(tc1100_device);
+       platform_driver_unregister(&tc1100_driver);
+       printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras unloaded\n");
+ }
+ module_init(tc1100_init);
+ module_exit(tc1100_exit);
index 0000000000000000000000000000000000000000,899766e16fa822cdfd7abc240e2ce0ee7ea329e4..3478453eba7a3e8e2997cc25fcdd807f377efb86
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,6949 +1,6948 @@@
 -#include <acpi/acnamesp.h>
+ /*
+  *  thinkpad_acpi.c - ThinkPad ACPI Extras
+  *
+  *
+  *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
+  *  Copyright (C) 2006-2008 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 TPACPI_VERSION "0.21"
+ #define TPACPI_SYSFS_VERSION 0x020200
+ /*
+  *  Changelog:
+  *  2007-10-20                changelog trimmed down
+  *
+  *  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-03-17        0.11    support for 600e, 770x
+  *                        thanks to Jamie Lentin <lentinj@dial.pipex.com>
+  *
+  *  2005-01-16        0.9     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>
+  */
+ #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/kthread.h>
+ #include <linux/freezer.h>
+ #include <linux/delay.h>
+ #include <linux/nvram.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 <linux/input.h>
+ #include <linux/leds.h>
+ #include <linux/rfkill.h>
+ #include <asm/uaccess.h>
+ #include <linux/dmi.h>
+ #include <linux/jiffies.h>
+ #include <linux/workqueue.h>
+ #include <acpi/acpi_drivers.h>
+ #include <linux/pci_ids.h>
+ /* 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 TP_CMOS_THINKLIGHT_ON 12
+ #define TP_CMOS_THINKLIGHT_OFF        13
+ /* NVRAM Addresses */
+ enum tp_nvram_addr {
+       TP_NVRAM_ADDR_HK2               = 0x57,
+       TP_NVRAM_ADDR_THINKLIGHT        = 0x58,
+       TP_NVRAM_ADDR_VIDEO             = 0x59,
+       TP_NVRAM_ADDR_BRIGHTNESS        = 0x5e,
+       TP_NVRAM_ADDR_MIXER             = 0x60,
+ };
+ /* NVRAM bit masks */
+ enum {
+       TP_NVRAM_MASK_HKT_THINKPAD      = 0x08,
+       TP_NVRAM_MASK_HKT_ZOOM          = 0x20,
+       TP_NVRAM_MASK_HKT_DISPLAY       = 0x40,
+       TP_NVRAM_MASK_HKT_HIBERNATE     = 0x80,
+       TP_NVRAM_MASK_THINKLIGHT        = 0x10,
+       TP_NVRAM_MASK_HKT_DISPEXPND     = 0x30,
+       TP_NVRAM_MASK_HKT_BRIGHTNESS    = 0x20,
+       TP_NVRAM_MASK_LEVEL_BRIGHTNESS  = 0x0f,
+       TP_NVRAM_POS_LEVEL_BRIGHTNESS   = 0,
+       TP_NVRAM_MASK_MUTE              = 0x40,
+       TP_NVRAM_MASK_HKT_VOLUME        = 0x80,
+       TP_NVRAM_MASK_LEVEL_VOLUME      = 0x0f,
+       TP_NVRAM_POS_LEVEL_VOLUME       = 0,
+ };
+ /* ACPI HIDs */
+ #define TPACPI_ACPI_HKEY_HID          "IBM0068"
+ /* Input IDs */
+ #define TPACPI_HKEY_INPUT_PRODUCT     0x5054 /* "TP" */
+ #define TPACPI_HKEY_INPUT_VERSION     0x4101
+ /****************************************************************************
+  * Main driver
+  */
+ #define TPACPI_NAME "thinkpad"
+ #define TPACPI_DESC "ThinkPad ACPI Extras"
+ #define TPACPI_FILE TPACPI_NAME "_acpi"
+ #define TPACPI_URL "http://ibm-acpi.sf.net/"
+ #define TPACPI_MAIL "ibm-acpi-devel@lists.sourceforge.net"
+ #define TPACPI_PROC_DIR "ibm"
+ #define TPACPI_ACPI_EVENT_PREFIX "ibm"
+ #define TPACPI_DRVR_NAME TPACPI_FILE
+ #define TPACPI_DRVR_SHORTNAME "tpacpi"
+ #define TPACPI_HWMON_DRVR_NAME TPACPI_NAME "_hwmon"
+ #define TPACPI_NVRAM_KTHREAD_NAME "ktpacpi_nvramd"
+ #define TPACPI_WORKQUEUE_NAME "ktpacpid"
+ #define TPACPI_MAX_ACPI_ARGS 3
+ /* rfkill switches */
+ enum {
+       TPACPI_RFK_BLUETOOTH_SW_ID = 0,
+       TPACPI_RFK_WWAN_SW_ID,
+ };
+ /* Debugging */
+ #define TPACPI_LOG TPACPI_FILE ": "
+ #define TPACPI_ERR       KERN_ERR    TPACPI_LOG
+ #define TPACPI_NOTICE KERN_NOTICE TPACPI_LOG
+ #define TPACPI_INFO   KERN_INFO   TPACPI_LOG
+ #define TPACPI_DEBUG  KERN_DEBUG  TPACPI_LOG
+ #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(TPACPI_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
+ #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)))
+ /****************************************************************************
+  * Driver-wide structs and misc. variables
+  */
+ struct ibm_struct;
+ struct tp_acpi_drv_struct {
+       const struct acpi_device_id *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);
+       void (*resume) (void);
+       void (*suspend) (pm_message_t state);
+       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
+       u32 bay_status:1;
+       u32 bay_eject:1;
+       u32 bay_status2:1;
+       u32 bay_eject2:1;
+ #endif
+       u32 bluetooth:1;
+       u32 hotkey:1;
+       u32 hotkey_mask:1;
+       u32 hotkey_wlsw:1;
+       u32 hotkey_tablet:1;
+       u32 light:1;
+       u32 light_status:1;
+       u32 bright_16levels:1;
+       u32 bright_acpimode:1;
+       u32 wan:1;
+       u32 fan_ctrl_status_undef:1;
+       u32 input_device_registered:1;
+       u32 platform_drv_registered:1;
+       u32 platform_drv_attrs_registered:1;
+       u32 sensors_pdrv_registered:1;
+       u32 sensors_pdrv_attrs_registered:1;
+       u32 sensors_pdev_attrs_registered:1;
+       u32 hotkey_poll_active:1;
+ } tp_features;
+ static struct {
+       u16 hotkey_mask_ff:1;
+       u16 bright_cmos_ec_unsync:1;
+ } tp_warned;
+ struct thinkpad_id_data {
+       unsigned int vendor;    /* ThinkPad vendor:
+                                * PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */
+       char *bios_version_str; /* Something like 1ZET51WW (1.03z) */
+       char *ec_version_str;   /* Something like 1ZHT51WW-1.04a */
+       u16 bios_model;         /* Big Endian, TP-1Y = 0x5931, 0 = unknown */
+       u16 ec_model;
+       char *model_str;        /* ThinkPad T43 */
+       char *nummodel_str;     /* 9384A9C for a 9384-A9C model */
+ };
+ static struct thinkpad_id_data thinkpad_id;
+ static enum {
+       TPACPI_LIFE_INIT = 0,
+       TPACPI_LIFE_RUNNING,
+       TPACPI_LIFE_EXITING,
+ } tpacpi_lifecycle;
+ static int experimental;
+ static u32 dbg_level;
+ static struct workqueue_struct *tpacpi_wq;
+ /* Special LED class that can defer work */
+ struct tpacpi_led_classdev {
+       struct led_classdev led_classdev;
+       struct work_struct work;
+       enum led_brightness new_brightness;
+       unsigned int led;
+ };
+ /****************************************************************************
+  ****************************************************************************
+  *
+  * ACPI Helpers and device model
+  *
+  ****************************************************************************
+  ****************************************************************************/
+ /*************************************************************************
+  * ACPI basic handles
+  */
+ static acpi_handle root_handle;
+ #define TPACPI_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 }
+ TPACPI_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 */
+          );
+ TPACPI_HANDLE(ecrd, ec, "ECRD");      /* 570 */
+ TPACPI_HANDLE(ecwr, ec, "ECWR");      /* 570 */
+ TPACPI_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 */
+ TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */
+          "^HKEY",             /* R30, R31 */
+          "HKEY",              /* all others */
+          );                   /* 570 */
+ TPACPI_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 */
+ /*************************************************************************
+  * 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[TPACPI_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(TPACPI_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(TPACPI_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(TPACPI_ERR "acpi_evalf() called "
+                      "with invalid format character '%c'\n", res_type);
+               return 0;
+       }
+       if (!success && !quiet)
+               printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
+                      method, fmt0, status);
+       return success;
+ }
+ 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;
+ }
+ #if defined(CONFIG_THINKPAD_ACPI_DOCK) || defined(CONFIG_THINKPAD_ACPI_BAY)
+ static int _sta(acpi_handle handle)
+ {
+       int status;
+       if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
+               status = 0;
+       return status;
+ }
+ #endif
+ 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
+  */
+ #define TPACPI_ACPIHANDLE_INIT(object) \
+       drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
+               object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
+ 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 (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+               return;
+       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(TPACPI_ERR "acpi_bus_get_device(%s) failed: %d\n",
+                       ibm->name, rc);
+               return -ENODEV;
+       }
+       ibm->acpi->device->driver_data = ibm;
+       sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
+               TPACPI_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(TPACPI_NOTICE
+                              "another device driver is already "
+                              "handling %s events\n", ibm->name);
+               } else {
+                       printk(TPACPI_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(TPACPI_ERR
+                      "failed to allocate memory for ibm->acpi->driver\n");
+               return -ENOMEM;
+       }
+       sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_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(TPACPI_ERR "acpi_bus_register_driver(%s) failed: %d\n",
+                      ibm->name, 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: input, hwmon and platform
+  *
+  ****************************************************************************
+  ****************************************************************************/
+ static struct platform_device *tpacpi_pdev;
+ static struct platform_device *tpacpi_sensors_pdev;
+ static struct device *tpacpi_hwmon;
+ static struct input_dev *tpacpi_inputdev;
+ static struct mutex tpacpi_inputdev_send_mutex;
+ static LIST_HEAD(tpacpi_all_drivers);
+ static int tpacpi_suspend_handler(struct platform_device *pdev,
+                                 pm_message_t state)
+ {
+       struct ibm_struct *ibm, *itmp;
+       list_for_each_entry_safe(ibm, itmp,
+                                &tpacpi_all_drivers,
+                                all_drivers) {
+               if (ibm->suspend)
+                       (ibm->suspend)(state);
+       }
+       return 0;
+ }
+ static int tpacpi_resume_handler(struct platform_device *pdev)
+ {
+       struct ibm_struct *ibm, *itmp;
+       list_for_each_entry_safe(ibm, itmp,
+                                &tpacpi_all_drivers,
+                                all_drivers) {
+               if (ibm->resume)
+                       (ibm->resume)();
+       }
+       return 0;
+ }
+ static struct platform_driver tpacpi_pdriver = {
+       .driver = {
+               .name = TPACPI_DRVR_NAME,
+               .owner = THIS_MODULE,
+       },
+       .suspend = tpacpi_suspend_handler,
+       .resume = tpacpi_resume_handler,
+ };
+ static struct platform_driver tpacpi_hwmon_pdriver = {
+       .driver = {
+               .name = TPACPI_HWMON_DRVR_NAME,
+               .owner = THIS_MODULE,
+       },
+ };
+ /*************************************************************************
+  * sysfs support helpers
+  */
+ struct attribute_set {
+       unsigned int members, max_members;
+       struct attribute_group group;
+ };
+ 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;
+ }
+ #define destroy_attr_set(_set) \
+       kfree(_set);
+ /* 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);
+ }
+ #define register_attr_set_with_sysfs(_attr_set, _kobj) \
+       sysfs_create_group(_kobj, &_attr_set->group)
+ static int parse_strtoul(const char *buf,
+               unsigned long max, unsigned long *value)
+ {
+       char *endp;
+       while (*buf && isspace(*buf))
+               buf++;
+       *value = simple_strtoul(buf, &endp, 0);
+       while (*endp && isspace(*endp))
+               endp++;
+       if (*endp || *value > max)
+               return -EINVAL;
+       return 0;
+ }
+ static void tpacpi_disable_brightness_delay(void)
+ {
+       if (acpi_evalf(hkey_handle, NULL, "PWMS", "qvd", 0))
+               printk(TPACPI_NOTICE
+                       "ACPI backlight control delay disabled\n");
+ }
+ static int __init tpacpi_query_bcl_levels(acpi_handle handle)
+ {
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+       union acpi_object *obj;
+       int rc;
+       if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
+               obj = (union acpi_object *)buffer.pointer;
+               if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
+                       printk(TPACPI_ERR "Unknown _BCL data, "
+                              "please report this to %s\n", TPACPI_MAIL);
+                       rc = 0;
+               } else {
+                       rc = obj->package.count;
+               }
+       } else {
+               return 0;
+       }
+       kfree(buffer.pointer);
+       return rc;
+ }
+ static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
+                                       u32 lvl, void *context, void **rv)
+ {
+       char name[ACPI_PATH_SEGMENT_LENGTH];
+       struct acpi_buffer buffer = { sizeof(name), &name };
+       if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
+           !strncmp("_BCL", name, sizeof(name) - 1)) {
+               BUG_ON(!rv || !*rv);
+               **(int **)rv = tpacpi_query_bcl_levels(handle);
+               return AE_CTRL_TERMINATE;
+       } else {
+               return AE_OK;
+       }
+ }
+ /*
+  * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
+  */
+ static int __init tpacpi_check_std_acpi_brightness_support(void)
+ {
+       int status;
+       int bcl_levels = 0;
+       void *bcl_ptr = &bcl_levels;
+       if (!vid_handle) {
+               TPACPI_ACPIHANDLE_INIT(vid);
+       }
+       if (!vid_handle)
+               return 0;
+       /*
+        * Search for a _BCL method, and execute it.  This is safe on all
+        * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
+        * BIOS in ACPI backlight control mode.  We do NOT have to care
+        * about calling the _BCL method in an enabled video device, any
+        * will do for our purposes.
+        */
+       status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
+                                    tpacpi_acpi_walk_find_bcl, NULL,
+                                    &bcl_ptr);
+       if (ACPI_SUCCESS(status) && bcl_levels > 2) {
+               tp_features.bright_acpimode = 1;
+               return (bcl_levels - 2);
+       }
+       return 0;
+ }
+ static int __init tpacpi_new_rfkill(const unsigned int id,
+                       struct rfkill **rfk,
+                       const enum rfkill_type rfktype,
+                       const char *name,
+                       int (*toggle_radio)(void *, enum rfkill_state),
+                       int (*get_state)(void *, enum rfkill_state *))
+ {
+       int res;
+       enum rfkill_state initial_state;
+       *rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype);
+       if (!*rfk) {
+               printk(TPACPI_ERR
+                       "failed to allocate memory for rfkill class\n");
+               return -ENOMEM;
+       }
+       (*rfk)->name = name;
+       (*rfk)->get_state = get_state;
+       (*rfk)->toggle_radio = toggle_radio;
+       if (!get_state(NULL, &initial_state))
+               (*rfk)->state = initial_state;
+       res = rfkill_register(*rfk);
+       if (res < 0) {
+               printk(TPACPI_ERR
+                       "failed to register %s rfkill switch: %d\n",
+                       name, res);
+               rfkill_free(*rfk);
+               *rfk = NULL;
+               return res;
+       }
+       return 0;
+ }
+ /*************************************************************************
+  * 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",
+                       TPACPI_DESC, TPACPI_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]);
+ }
+ /****************************************************************************
+  ****************************************************************************
+  *
+  * Subdrivers
+  *
+  ****************************************************************************
+  ****************************************************************************/
+ /*************************************************************************
+  * thinkpad-acpi init subdriver
+  */
+ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
+ {
+       printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
+       printk(TPACPI_INFO "%s\n", TPACPI_URL);
+       printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
+               (thinkpad_id.bios_version_str) ?
+                       thinkpad_id.bios_version_str : "unknown",
+               (thinkpad_id.ec_version_str) ?
+                       thinkpad_id.ec_version_str : "unknown");
+       if (thinkpad_id.vendor && thinkpad_id.model_str)
+               printk(TPACPI_INFO "%s %s, model %s\n",
+                       (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
+                               "IBM" : ((thinkpad_id.vendor ==
+                                               PCI_VENDOR_ID_LENOVO) ?
+                                       "Lenovo" : "Unknown vendor"),
+                       thinkpad_id.model_str,
+                       (thinkpad_id.nummodel_str) ?
+                               thinkpad_id.nummodel_str : "unknown");
+       return 0;
+ }
+ static int thinkpad_acpi_driver_read(char *p)
+ {
+       int len = 0;
+       len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC);
+       len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION);
+       return len;
+ }
+ static struct ibm_struct thinkpad_acpi_driver_data = {
+       .name = "driver",
+       .read = thinkpad_acpi_driver_read,
+ };
+ /*************************************************************************
+  * Hotkey subdriver
+  */
+ enum {        /* hot key scan codes (derived from ACPI DSDT) */
+       TP_ACPI_HOTKEYSCAN_FNF1         = 0,
+       TP_ACPI_HOTKEYSCAN_FNF2,
+       TP_ACPI_HOTKEYSCAN_FNF3,
+       TP_ACPI_HOTKEYSCAN_FNF4,
+       TP_ACPI_HOTKEYSCAN_FNF5,
+       TP_ACPI_HOTKEYSCAN_FNF6,
+       TP_ACPI_HOTKEYSCAN_FNF7,
+       TP_ACPI_HOTKEYSCAN_FNF8,
+       TP_ACPI_HOTKEYSCAN_FNF9,
+       TP_ACPI_HOTKEYSCAN_FNF10,
+       TP_ACPI_HOTKEYSCAN_FNF11,
+       TP_ACPI_HOTKEYSCAN_FNF12,
+       TP_ACPI_HOTKEYSCAN_FNBACKSPACE,
+       TP_ACPI_HOTKEYSCAN_FNINSERT,
+       TP_ACPI_HOTKEYSCAN_FNDELETE,
+       TP_ACPI_HOTKEYSCAN_FNHOME,
+       TP_ACPI_HOTKEYSCAN_FNEND,
+       TP_ACPI_HOTKEYSCAN_FNPAGEUP,
+       TP_ACPI_HOTKEYSCAN_FNPAGEDOWN,
+       TP_ACPI_HOTKEYSCAN_FNSPACE,
+       TP_ACPI_HOTKEYSCAN_VOLUMEUP,
+       TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
+       TP_ACPI_HOTKEYSCAN_MUTE,
+       TP_ACPI_HOTKEYSCAN_THINKPAD,
+ };
+ enum {        /* Keys available through NVRAM polling */
+       TPACPI_HKEY_NVRAM_KNOWN_MASK = 0x00fb88c0U,
+       TPACPI_HKEY_NVRAM_GOOD_MASK  = 0x00fb8000U,
+ };
+ enum {        /* Positions of some of the keys in hotkey masks */
+       TP_ACPI_HKEY_DISPSWTCH_MASK     = 1 << TP_ACPI_HOTKEYSCAN_FNF7,
+       TP_ACPI_HKEY_DISPXPAND_MASK     = 1 << TP_ACPI_HOTKEYSCAN_FNF8,
+       TP_ACPI_HKEY_HIBERNATE_MASK     = 1 << TP_ACPI_HOTKEYSCAN_FNF12,
+       TP_ACPI_HKEY_BRGHTUP_MASK       = 1 << TP_ACPI_HOTKEYSCAN_FNHOME,
+       TP_ACPI_HKEY_BRGHTDWN_MASK      = 1 << TP_ACPI_HOTKEYSCAN_FNEND,
+       TP_ACPI_HKEY_THNKLGHT_MASK      = 1 << TP_ACPI_HOTKEYSCAN_FNPAGEUP,
+       TP_ACPI_HKEY_ZOOM_MASK          = 1 << TP_ACPI_HOTKEYSCAN_FNSPACE,
+       TP_ACPI_HKEY_VOLUP_MASK         = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEUP,
+       TP_ACPI_HKEY_VOLDWN_MASK        = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
+       TP_ACPI_HKEY_MUTE_MASK          = 1 << TP_ACPI_HOTKEYSCAN_MUTE,
+       TP_ACPI_HKEY_THINKPAD_MASK      = 1 << TP_ACPI_HOTKEYSCAN_THINKPAD,
+ };
+ enum {        /* NVRAM to ACPI HKEY group map */
+       TP_NVRAM_HKEY_GROUP_HK2         = TP_ACPI_HKEY_THINKPAD_MASK |
+                                         TP_ACPI_HKEY_ZOOM_MASK |
+                                         TP_ACPI_HKEY_DISPSWTCH_MASK |
+                                         TP_ACPI_HKEY_HIBERNATE_MASK,
+       TP_NVRAM_HKEY_GROUP_BRIGHTNESS  = TP_ACPI_HKEY_BRGHTUP_MASK |
+                                         TP_ACPI_HKEY_BRGHTDWN_MASK,
+       TP_NVRAM_HKEY_GROUP_VOLUME      = TP_ACPI_HKEY_VOLUP_MASK |
+                                         TP_ACPI_HKEY_VOLDWN_MASK |
+                                         TP_ACPI_HKEY_MUTE_MASK,
+ };
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ struct tp_nvram_state {
+        u16 thinkpad_toggle:1;
+        u16 zoom_toggle:1;
+        u16 display_toggle:1;
+        u16 thinklight_toggle:1;
+        u16 hibernate_toggle:1;
+        u16 displayexp_toggle:1;
+        u16 display_state:1;
+        u16 brightness_toggle:1;
+        u16 volume_toggle:1;
+        u16 mute:1;
+        u8 brightness_level;
+        u8 volume_level;
+ };
+ static struct task_struct *tpacpi_hotkey_task;
+ static u32 hotkey_source_mask;                /* bit mask 0=ACPI,1=NVRAM */
+ static int hotkey_poll_freq = 10;     /* Hz */
+ static struct mutex hotkey_thread_mutex;
+ static struct mutex hotkey_thread_data_mutex;
+ static unsigned int hotkey_config_change;
+ #else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ #define hotkey_source_mask 0U
+ #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ static struct mutex hotkey_mutex;
+ static enum { /* Reasons for waking up */
+       TP_ACPI_WAKEUP_NONE = 0,        /* None or unknown */
+       TP_ACPI_WAKEUP_BAYEJ,           /* Bay ejection request */
+       TP_ACPI_WAKEUP_UNDOCK,          /* Undock request */
+ } hotkey_wakeup_reason;
+ static int hotkey_autosleep_ack;
+ static int hotkey_orig_status;
+ static u32 hotkey_orig_mask;
+ static u32 hotkey_all_mask;
+ static u32 hotkey_reserved_mask;
+ static u32 hotkey_mask;
+ static unsigned int hotkey_report_mode;
+ static u16 *hotkey_keycode_map;
+ static struct attribute_set *hotkey_dev_attributes;
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ #define HOTKEY_CONFIG_CRITICAL_START \
+       do { \
+               mutex_lock(&hotkey_thread_data_mutex); \
+               hotkey_config_change++; \
+       } while (0);
+ #define HOTKEY_CONFIG_CRITICAL_END \
+       mutex_unlock(&hotkey_thread_data_mutex);
+ #else
+ #define HOTKEY_CONFIG_CRITICAL_START
+ #define HOTKEY_CONFIG_CRITICAL_END
+ #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ /* HKEY.MHKG() return bits */
+ #define TP_HOTKEY_TABLET_MASK (1 << 3)
+ static int hotkey_get_wlsw(int *status)
+ {
+       if (!acpi_evalf(hkey_handle, status, "WLSW", "d"))
+               return -EIO;
+       return 0;
+ }
+ static int hotkey_get_tablet_mode(int *status)
+ {
+       int s;
+       if (!acpi_evalf(hkey_handle, &s, "MHKG", "d"))
+               return -EIO;
+       *status = ((s & TP_HOTKEY_TABLET_MASK) != 0);
+       return 0;
+ }
+ /*
+  * Call with hotkey_mutex held
+  */
+ static int hotkey_mask_get(void)
+ {
+       u32 m = 0;
+       if (tp_features.hotkey_mask) {
+               if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
+                       return -EIO;
+       }
+       hotkey_mask = m | (hotkey_source_mask & hotkey_mask);
+       return 0;
+ }
+ /*
+  * Call with hotkey_mutex held
+  */
+ static int hotkey_mask_set(u32 mask)
+ {
+       int i;
+       int rc = 0;
+       if (tp_features.hotkey_mask) {
+               if (!tp_warned.hotkey_mask_ff &&
+                   (mask == 0xffff || mask == 0xffffff ||
+                    mask == 0xffffffff)) {
+                       tp_warned.hotkey_mask_ff = 1;
+                       printk(TPACPI_NOTICE
+                              "setting the hotkey mask to 0x%08x is likely "
+                              "not the best way to go about it\n", mask);
+                       printk(TPACPI_NOTICE
+                              "please consider using the driver defaults, "
+                              "and refer to up-to-date thinkpad-acpi "
+                              "documentation\n");
+               }
+               HOTKEY_CONFIG_CRITICAL_START
+               for (i = 0; i < 32; i++) {
+                       u32 m = 1 << i;
+                       /* enable in firmware mask only keys not in NVRAM
+                        * mode, but enable the key in the cached hotkey_mask
+                        * regardless of mode, or the key will end up
+                        * disabled by hotkey_mask_get() */
+                       if (!acpi_evalf(hkey_handle,
+                                       NULL, "MHKM", "vdd", i + 1,
+                                       !!((mask & ~hotkey_source_mask) & m))) {
+                               rc = -EIO;
+                               break;
+                       } else {
+                               hotkey_mask = (hotkey_mask & ~m) | (mask & m);
+                       }
+               }
+               HOTKEY_CONFIG_CRITICAL_END
+               /* hotkey_mask_get must be called unconditionally below */
+               if (!hotkey_mask_get() && !rc &&
+                   (hotkey_mask & ~hotkey_source_mask) !=
+                    (mask & ~hotkey_source_mask)) {
+                       printk(TPACPI_NOTICE
+                              "requested hot key mask 0x%08x, but "
+                              "firmware forced it to 0x%08x\n",
+                              mask, hotkey_mask);
+               }
+       } else {
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+               HOTKEY_CONFIG_CRITICAL_START
+               hotkey_mask = mask & hotkey_source_mask;
+               HOTKEY_CONFIG_CRITICAL_END
+               hotkey_mask_get();
+               if (hotkey_mask != mask) {
+                       printk(TPACPI_NOTICE
+                              "requested hot key mask 0x%08x, "
+                              "forced to 0x%08x (NVRAM poll mask is "
+                              "0x%08x): no firmware mask support\n",
+                              mask, hotkey_mask, hotkey_source_mask);
+               }
+ #else
+               hotkey_mask_get();
+               rc = -ENXIO;
+ #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+       }
+       return rc;
+ }
+ static int hotkey_status_get(int *status)
+ {
+       if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
+               return -EIO;
+       return 0;
+ }
+ static int hotkey_status_set(int status)
+ {
+       if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
+               return -EIO;
+       return 0;
+ }
+ static void tpacpi_input_send_tabletsw(void)
+ {
+       int state;
+       if (tp_features.hotkey_tablet &&
+           !hotkey_get_tablet_mode(&state)) {
+               mutex_lock(&tpacpi_inputdev_send_mutex);
+               input_report_switch(tpacpi_inputdev,
+                                   SW_TABLET_MODE, !!state);
+               input_sync(tpacpi_inputdev);
+               mutex_unlock(&tpacpi_inputdev_send_mutex);
+       }
+ }
+ static void tpacpi_input_send_key(unsigned int scancode)
+ {
+       unsigned int keycode;
+       keycode = hotkey_keycode_map[scancode];
+       if (keycode != KEY_RESERVED) {
+               mutex_lock(&tpacpi_inputdev_send_mutex);
+               input_report_key(tpacpi_inputdev, keycode, 1);
+               if (keycode == KEY_UNKNOWN)
+                       input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
+                                   scancode);
+               input_sync(tpacpi_inputdev);
+               input_report_key(tpacpi_inputdev, keycode, 0);
+               if (keycode == KEY_UNKNOWN)
+                       input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
+                                   scancode);
+               input_sync(tpacpi_inputdev);
+               mutex_unlock(&tpacpi_inputdev_send_mutex);
+       }
+ }
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
+ static void tpacpi_hotkey_send_key(unsigned int scancode)
+ {
+       tpacpi_input_send_key(scancode);
+       if (hotkey_report_mode < 2) {
+               acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device,
+                                               0x80, 0x1001 + scancode);
+       }
+ }
+ static void hotkey_read_nvram(struct tp_nvram_state *n, u32 m)
+ {
+       u8 d;
+       if (m & TP_NVRAM_HKEY_GROUP_HK2) {
+               d = nvram_read_byte(TP_NVRAM_ADDR_HK2);
+               n->thinkpad_toggle = !!(d & TP_NVRAM_MASK_HKT_THINKPAD);
+               n->zoom_toggle = !!(d & TP_NVRAM_MASK_HKT_ZOOM);
+               n->display_toggle = !!(d & TP_NVRAM_MASK_HKT_DISPLAY);
+               n->hibernate_toggle = !!(d & TP_NVRAM_MASK_HKT_HIBERNATE);
+       }
+       if (m & TP_ACPI_HKEY_THNKLGHT_MASK) {
+               d = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT);
+               n->thinklight_toggle = !!(d & TP_NVRAM_MASK_THINKLIGHT);
+       }
+       if (m & TP_ACPI_HKEY_DISPXPAND_MASK) {
+               d = nvram_read_byte(TP_NVRAM_ADDR_VIDEO);
+               n->displayexp_toggle =
+                               !!(d & TP_NVRAM_MASK_HKT_DISPEXPND);
+       }
+       if (m & TP_NVRAM_HKEY_GROUP_BRIGHTNESS) {
+               d = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
+               n->brightness_level = (d & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+                               >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+               n->brightness_toggle =
+                               !!(d & TP_NVRAM_MASK_HKT_BRIGHTNESS);
+       }
+       if (m & TP_NVRAM_HKEY_GROUP_VOLUME) {
+               d = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+               n->volume_level = (d & TP_NVRAM_MASK_LEVEL_VOLUME)
+                               >> TP_NVRAM_POS_LEVEL_VOLUME;
+               n->mute = !!(d & TP_NVRAM_MASK_MUTE);
+               n->volume_toggle = !!(d & TP_NVRAM_MASK_HKT_VOLUME);
+       }
+ }
+ #define TPACPI_COMPARE_KEY(__scancode, __member) \
+       do { \
+               if ((mask & (1 << __scancode)) && \
+                   oldn->__member != newn->__member) \
+               tpacpi_hotkey_send_key(__scancode); \
+       } while (0)
+ #define TPACPI_MAY_SEND_KEY(__scancode) \
+       do { if (mask & (1 << __scancode)) \
+               tpacpi_hotkey_send_key(__scancode); } while (0)
+ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
+                                          struct tp_nvram_state *newn,
+                                          u32 mask)
+ {
+       TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
+       TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
+       TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
+       TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF12, hibernate_toggle);
+       TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNPAGEUP, thinklight_toggle);
+       TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
+       /* handle volume */
+       if (oldn->volume_toggle != newn->volume_toggle) {
+               if (oldn->mute != newn->mute) {
+                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
+               }
+               if (oldn->volume_level > newn->volume_level) {
+                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+               } else if (oldn->volume_level < newn->volume_level) {
+                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+               } else if (oldn->mute == newn->mute) {
+                       /* repeated key presses that didn't change state */
+                       if (newn->mute) {
+                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
+                       } else if (newn->volume_level != 0) {
+                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+                       } else {
+                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+                       }
+               }
+       }
+       /* handle brightness */
+       if (oldn->brightness_toggle != newn->brightness_toggle) {
+               if (oldn->brightness_level < newn->brightness_level) {
+                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+               } else if (oldn->brightness_level > newn->brightness_level) {
+                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+               } else {
+                       /* repeated key presses that didn't change state */
+                       if (newn->brightness_level != 0) {
+                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+                       } else {
+                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+                       }
+               }
+       }
+ }
+ #undef TPACPI_COMPARE_KEY
+ #undef TPACPI_MAY_SEND_KEY
+ static int hotkey_kthread(void *data)
+ {
+       struct tp_nvram_state s[2];
+       u32 mask;
+       unsigned int si, so;
+       unsigned long t;
+       unsigned int change_detector, must_reset;
+       mutex_lock(&hotkey_thread_mutex);
+       if (tpacpi_lifecycle == TPACPI_LIFE_EXITING)
+               goto exit;
+       set_freezable();
+       so = 0;
+       si = 1;
+       t = 0;
+       /* Initial state for compares */
+       mutex_lock(&hotkey_thread_data_mutex);
+       change_detector = hotkey_config_change;
+       mask = hotkey_source_mask & hotkey_mask;
+       mutex_unlock(&hotkey_thread_data_mutex);
+       hotkey_read_nvram(&s[so], mask);
+       while (!kthread_should_stop() && hotkey_poll_freq) {
+               if (t == 0)
+                       t = 1000/hotkey_poll_freq;
+               t = msleep_interruptible(t);
+               if (unlikely(kthread_should_stop()))
+                       break;
+               must_reset = try_to_freeze();
+               if (t > 0 && !must_reset)
+                       continue;
+               mutex_lock(&hotkey_thread_data_mutex);
+               if (must_reset || hotkey_config_change != change_detector) {
+                       /* forget old state on thaw or config change */
+                       si = so;
+                       t = 0;
+                       change_detector = hotkey_config_change;
+               }
+               mask = hotkey_source_mask & hotkey_mask;
+               mutex_unlock(&hotkey_thread_data_mutex);
+               if (likely(mask)) {
+                       hotkey_read_nvram(&s[si], mask);
+                       if (likely(si != so)) {
+                               hotkey_compare_and_issue_event(&s[so], &s[si],
+                                                               mask);
+                       }
+               }
+               so = si;
+               si ^= 1;
+       }
+ exit:
+       mutex_unlock(&hotkey_thread_mutex);
+       return 0;
+ }
+ static void hotkey_poll_stop_sync(void)
+ {
+       if (tpacpi_hotkey_task) {
+               if (frozen(tpacpi_hotkey_task) ||
+                   freezing(tpacpi_hotkey_task))
+                       thaw_process(tpacpi_hotkey_task);
+               kthread_stop(tpacpi_hotkey_task);
+               tpacpi_hotkey_task = NULL;
+               mutex_lock(&hotkey_thread_mutex);
+               /* at this point, the thread did exit */
+               mutex_unlock(&hotkey_thread_mutex);
+       }
+ }
+ /* call with hotkey_mutex held */
+ static void hotkey_poll_setup(int may_warn)
+ {
+       if ((hotkey_source_mask & hotkey_mask) != 0 &&
+           hotkey_poll_freq > 0 &&
+           (tpacpi_inputdev->users > 0 || hotkey_report_mode < 2)) {
+               if (!tpacpi_hotkey_task) {
+                       tpacpi_hotkey_task = kthread_run(hotkey_kthread,
+                                       NULL, TPACPI_NVRAM_KTHREAD_NAME);
+                       if (IS_ERR(tpacpi_hotkey_task)) {
+                               tpacpi_hotkey_task = NULL;
+                               printk(TPACPI_ERR
+                                      "could not create kernel thread "
+                                      "for hotkey polling\n");
+                       }
+               }
+       } else {
+               hotkey_poll_stop_sync();
+               if (may_warn &&
+                   hotkey_source_mask != 0 && hotkey_poll_freq == 0) {
+                       printk(TPACPI_NOTICE
+                               "hot keys 0x%08x require polling, "
+                               "which is currently disabled\n",
+                               hotkey_source_mask);
+               }
+       }
+ }
+ static void hotkey_poll_setup_safe(int may_warn)
+ {
+       mutex_lock(&hotkey_mutex);
+       hotkey_poll_setup(may_warn);
+       mutex_unlock(&hotkey_mutex);
+ }
+ #else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ static void hotkey_poll_setup_safe(int __unused)
+ {
+ }
+ #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ static int hotkey_inputdev_open(struct input_dev *dev)
+ {
+       switch (tpacpi_lifecycle) {
+       case TPACPI_LIFE_INIT:
+               /*
+                * hotkey_init will call hotkey_poll_setup_safe
+                * at the appropriate moment
+                */
+               return 0;
+       case TPACPI_LIFE_EXITING:
+               return -EBUSY;
+       case TPACPI_LIFE_RUNNING:
+               hotkey_poll_setup_safe(0);
+               return 0;
+       }
+       /* Should only happen if tpacpi_lifecycle is corrupt */
+       BUG();
+       return -EBUSY;
+ }
+ static void hotkey_inputdev_close(struct input_dev *dev)
+ {
+       /* disable hotkey polling when possible */
+       if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING)
+               hotkey_poll_setup_safe(0);
+ }
+ /* sysfs hotkey enable ------------------------------------------------- */
+ static ssize_t hotkey_enable_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       int res, status;
+       res = hotkey_status_get(&status);
+       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;
+       if (parse_strtoul(buf, 1, &t))
+               return -EINVAL;
+       res = hotkey_status_set(t);
+       return (res) ? res : count;
+ }
+ static struct device_attribute dev_attr_hotkey_enable =
+       __ATTR(hotkey_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;
+       if (mutex_lock_interruptible(&hotkey_mutex))
+               return -ERESTARTSYS;
+       res = hotkey_mask_get();
+       mutex_unlock(&hotkey_mutex);
+       return (res)?
+               res : snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_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;
+       if (parse_strtoul(buf, 0xffffffffUL, &t))
+               return -EINVAL;
+       if (mutex_lock_interruptible(&hotkey_mutex))
+               return -ERESTARTSYS;
+       res = hotkey_mask_set(t);
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+       hotkey_poll_setup(1);
+ #endif
+       mutex_unlock(&hotkey_mutex);
+       return (res) ? res : count;
+ }
+ static struct device_attribute dev_attr_hotkey_mask =
+       __ATTR(hotkey_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(hotkey_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%08x\n", hotkey_orig_mask);
+ }
+ static struct device_attribute dev_attr_hotkey_bios_mask =
+       __ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
+ /* sysfs hotkey all_mask ----------------------------------------------- */
+ static ssize_t hotkey_all_mask_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "0x%08x\n",
+                               hotkey_all_mask | hotkey_source_mask);
+ }
+ static struct device_attribute dev_attr_hotkey_all_mask =
+       __ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL);
+ /* sysfs hotkey recommended_mask --------------------------------------- */
+ static ssize_t hotkey_recommended_mask_show(struct device *dev,
+                                           struct device_attribute *attr,
+                                           char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "0x%08x\n",
+                       (hotkey_all_mask | hotkey_source_mask)
+                       & ~hotkey_reserved_mask);
+ }
+ static struct device_attribute dev_attr_hotkey_recommended_mask =
+       __ATTR(hotkey_recommended_mask, S_IRUGO,
+               hotkey_recommended_mask_show, NULL);
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ /* sysfs hotkey hotkey_source_mask ------------------------------------- */
+ static ssize_t hotkey_source_mask_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_source_mask);
+ }
+ static ssize_t hotkey_source_mask_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+ {
+       unsigned long t;
+       if (parse_strtoul(buf, 0xffffffffUL, &t) ||
+               ((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
+               return -EINVAL;
+       if (mutex_lock_interruptible(&hotkey_mutex))
+               return -ERESTARTSYS;
+       HOTKEY_CONFIG_CRITICAL_START
+       hotkey_source_mask = t;
+       HOTKEY_CONFIG_CRITICAL_END
+       hotkey_poll_setup(1);
+       mutex_unlock(&hotkey_mutex);
+       return count;
+ }
+ static struct device_attribute dev_attr_hotkey_source_mask =
+       __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO,
+               hotkey_source_mask_show, hotkey_source_mask_store);
+ /* sysfs hotkey hotkey_poll_freq --------------------------------------- */
+ static ssize_t hotkey_poll_freq_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_poll_freq);
+ }
+ static ssize_t hotkey_poll_freq_store(struct device *dev,
+                           struct device_attribute *attr,
+                           const char *buf, size_t count)
+ {
+       unsigned long t;
+       if (parse_strtoul(buf, 25, &t))
+               return -EINVAL;
+       if (mutex_lock_interruptible(&hotkey_mutex))
+               return -ERESTARTSYS;
+       hotkey_poll_freq = t;
+       hotkey_poll_setup(1);
+       mutex_unlock(&hotkey_mutex);
+       return count;
+ }
+ static struct device_attribute dev_attr_hotkey_poll_freq =
+       __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO,
+               hotkey_poll_freq_show, hotkey_poll_freq_store);
+ #endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ /* sysfs hotkey radio_sw (pollable) ------------------------------------ */
+ static ssize_t hotkey_radio_sw_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       int res, s;
+       res = hotkey_get_wlsw(&s);
+       if (res < 0)
+               return res;
+       return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
+ }
+ static struct device_attribute dev_attr_hotkey_radio_sw =
+       __ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL);
+ static void hotkey_radio_sw_notify_change(void)
+ {
+       if (tp_features.hotkey_wlsw)
+               sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+                            "hotkey_radio_sw");
+ }
+ /* sysfs hotkey tablet mode (pollable) --------------------------------- */
+ static ssize_t hotkey_tablet_mode_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       int res, s;
+       res = hotkey_get_tablet_mode(&s);
+       if (res < 0)
+               return res;
+       return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
+ }
+ static struct device_attribute dev_attr_hotkey_tablet_mode =
+       __ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL);
+ static void hotkey_tablet_mode_notify_change(void)
+ {
+       if (tp_features.hotkey_tablet)
+               sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+                            "hotkey_tablet_mode");
+ }
+ /* sysfs hotkey report_mode -------------------------------------------- */
+ static ssize_t hotkey_report_mode_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "%d\n",
+               (hotkey_report_mode != 0) ? hotkey_report_mode : 1);
+ }
+ static struct device_attribute dev_attr_hotkey_report_mode =
+       __ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL);
+ /* sysfs wakeup reason (pollable) -------------------------------------- */
+ static ssize_t hotkey_wakeup_reason_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason);
+ }
+ static struct device_attribute dev_attr_hotkey_wakeup_reason =
+       __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL);
+ static void hotkey_wakeup_reason_notify_change(void)
+ {
+       if (tp_features.hotkey_mask)
+               sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+                            "wakeup_reason");
+ }
+ /* sysfs wakeup hotunplug_complete (pollable) -------------------------- */
+ static ssize_t hotkey_wakeup_hotunplug_complete_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack);
+ }
+ static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete =
+       __ATTR(wakeup_hotunplug_complete, S_IRUGO,
+              hotkey_wakeup_hotunplug_complete_show, NULL);
+ static void hotkey_wakeup_hotunplug_complete_notify_change(void)
+ {
+       if (tp_features.hotkey_mask)
+               sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+                            "wakeup_hotunplug_complete");
+ }
+ /* --------------------------------------------------------------------- */
+ static struct attribute *hotkey_attributes[] __initdata = {
+       &dev_attr_hotkey_enable.attr,
+       &dev_attr_hotkey_bios_enabled.attr,
+       &dev_attr_hotkey_report_mode.attr,
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+       &dev_attr_hotkey_mask.attr,
+       &dev_attr_hotkey_all_mask.attr,
+       &dev_attr_hotkey_recommended_mask.attr,
+       &dev_attr_hotkey_source_mask.attr,
+       &dev_attr_hotkey_poll_freq.attr,
+ #endif
+ };
+ static struct attribute *hotkey_mask_attributes[] __initdata = {
+       &dev_attr_hotkey_bios_mask.attr,
+ #ifndef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+       &dev_attr_hotkey_mask.attr,
+       &dev_attr_hotkey_all_mask.attr,
+       &dev_attr_hotkey_recommended_mask.attr,
+ #endif
+       &dev_attr_hotkey_wakeup_reason.attr,
+       &dev_attr_hotkey_wakeup_hotunplug_complete.attr,
+ };
+ static void bluetooth_update_rfk(void);
+ static void wan_update_rfk(void);
+ static void tpacpi_send_radiosw_update(void)
+ {
+       int wlsw;
+       /* Sync these BEFORE sending any rfkill events */
+       if (tp_features.bluetooth)
+               bluetooth_update_rfk();
+       if (tp_features.wan)
+               wan_update_rfk();
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
+               mutex_lock(&tpacpi_inputdev_send_mutex);
+               input_report_switch(tpacpi_inputdev,
+                                   SW_RFKILL_ALL, !!wlsw);
+               input_sync(tpacpi_inputdev);
+               mutex_unlock(&tpacpi_inputdev_send_mutex);
+       }
+       hotkey_radio_sw_notify_change();
+ }
+ static void hotkey_exit(void)
+ {
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+       hotkey_poll_stop_sync();
+ #endif
+       if (hotkey_dev_attributes)
+               delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+       kfree(hotkey_keycode_map);
+       if (tp_features.hotkey) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                          "restoring original hot key mask\n");
+               /* no short-circuit boolean operator below! */
+               if ((hotkey_mask_set(hotkey_orig_mask) |
+                    hotkey_status_set(hotkey_orig_status)) != 0)
+                       printk(TPACPI_ERR
+                              "failed to restore hot key mask "
+                              "to BIOS defaults\n");
+       }
+ }
+ static int __init hotkey_init(struct ibm_init_struct *iibm)
+ {
+       /* Requirements for changing the default keymaps:
+        *
+        * 1. Many of the keys are mapped to KEY_RESERVED for very
+        *    good reasons.  Do not change them unless you have deep
+        *    knowledge on the IBM and Lenovo ThinkPad firmware for
+        *    the various ThinkPad models.  The driver behaves
+        *    differently for KEY_RESERVED: such keys have their
+        *    hot key mask *unset* in mask_recommended, and also
+        *    in the initial hot key mask programmed into the
+        *    firmware at driver load time, which means the firm-
+        *    ware may react very differently if you change them to
+        *    something else;
+        *
+        * 2. You must be subscribed to the linux-thinkpad and
+        *    ibm-acpi-devel mailing lists, and you should read the
+        *    list archives since 2007 if you want to change the
+        *    keymaps.  This requirement exists so that you will
+        *    know the past history of problems with the thinkpad-
+        *    acpi driver keymaps, and also that you will be
+        *    listening to any bug reports;
+        *
+        * 3. Do not send thinkpad-acpi specific patches directly to
+        *    for merging, *ever*.  Send them to the linux-acpi
+        *    mailinglist for comments.  Merging is to be done only
+        *    through acpi-test and the ACPI maintainer.
+        *
+        * If the above is too much to ask, don't change the keymap.
+        * Ask the thinkpad-acpi maintainer to do it, instead.
+        */
+       static u16 ibm_keycode_map[] __initdata = {
+               /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
+               KEY_FN_F1,      KEY_FN_F2,      KEY_COFFEE,     KEY_SLEEP,
+               KEY_WLAN,       KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
+               KEY_FN_F9,      KEY_FN_F10,     KEY_FN_F11,     KEY_SUSPEND,
+               /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
+               KEY_UNKNOWN,    /* 0x0C: FN+BACKSPACE */
+               KEY_UNKNOWN,    /* 0x0D: FN+INSERT */
+               KEY_UNKNOWN,    /* 0x0E: FN+DELETE */
+               /* brightness: firmware always reacts to them, unless
+                * X.org did some tricks in the radeon BIOS scratch
+                * registers of *some* models */
+               KEY_RESERVED,   /* 0x0F: FN+HOME (brightness up) */
+               KEY_RESERVED,   /* 0x10: FN+END (brightness down) */
+               /* Thinklight: firmware always react to it */
+               KEY_RESERVED,   /* 0x11: FN+PGUP (thinklight toggle) */
+               KEY_UNKNOWN,    /* 0x12: FN+PGDOWN */
+               KEY_ZOOM,       /* 0x13: FN+SPACE (zoom) */
+               /* Volume: firmware always react to it and reprograms
+                * the built-in *extra* mixer.  Never map it to control
+                * another mixer by default. */
+               KEY_RESERVED,   /* 0x14: VOLUME UP */
+               KEY_RESERVED,   /* 0x15: VOLUME DOWN */
+               KEY_RESERVED,   /* 0x16: MUTE */
+               KEY_VENDOR,     /* 0x17: Thinkpad/AccessIBM/Lenovo */
+               /* (assignments unknown, please report if found) */
+               KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+               KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+       };
+       static u16 lenovo_keycode_map[] __initdata = {
+               /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
+               KEY_FN_F1,      KEY_COFFEE,     KEY_BATTERY,    KEY_SLEEP,
+               KEY_WLAN,       KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
+               KEY_FN_F9,      KEY_FN_F10,     KEY_FN_F11,     KEY_SUSPEND,
+               /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
+               KEY_UNKNOWN,    /* 0x0C: FN+BACKSPACE */
+               KEY_UNKNOWN,    /* 0x0D: FN+INSERT */
+               KEY_UNKNOWN,    /* 0x0E: FN+DELETE */
+               /* These either have to go through ACPI video, or
+                * act like in the IBM ThinkPads, so don't ever
+                * enable them by default */
+               KEY_RESERVED,   /* 0x0F: FN+HOME (brightness up) */
+               KEY_RESERVED,   /* 0x10: FN+END (brightness down) */
+               KEY_RESERVED,   /* 0x11: FN+PGUP (thinklight toggle) */
+               KEY_UNKNOWN,    /* 0x12: FN+PGDOWN */
+               KEY_ZOOM,       /* 0x13: FN+SPACE (zoom) */
+               /* Volume: z60/z61, T60 (BIOS version?): firmware always
+                * react to it and reprograms the built-in *extra* mixer.
+                * Never map it to control another mixer by default.
+                *
+                * T60?, T61, R60?, R61: firmware and EC tries to send
+                * these over the regular keyboard, so these are no-ops,
+                * but there are still weird bugs re. MUTE, so do not
+                * change unless you get test reports from all Lenovo
+                * models.  May cause the BIOS to interfere with the
+                * HDA mixer.
+                */
+               KEY_RESERVED,   /* 0x14: VOLUME UP */
+               KEY_RESERVED,   /* 0x15: VOLUME DOWN */
+               KEY_RESERVED,   /* 0x16: MUTE */
+               KEY_VENDOR,     /* 0x17: Thinkpad/AccessIBM/Lenovo */
+               /* (assignments unknown, please report if found) */
+               KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+               KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+       };
+ #define TPACPI_HOTKEY_MAP_LEN         ARRAY_SIZE(ibm_keycode_map)
+ #define TPACPI_HOTKEY_MAP_SIZE                sizeof(ibm_keycode_map)
+ #define TPACPI_HOTKEY_MAP_TYPESIZE    sizeof(ibm_keycode_map[0])
+       int res, i;
+       int status;
+       int hkeyv;
+       vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n");
+       BUG_ON(!tpacpi_inputdev);
+       BUG_ON(tpacpi_inputdev->open != NULL ||
+              tpacpi_inputdev->close != NULL);
+       TPACPI_ACPIHANDLE_INIT(hkey);
+       mutex_init(&hotkey_mutex);
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+       mutex_init(&hotkey_thread_mutex);
+       mutex_init(&hotkey_thread_data_mutex);
+ #endif
+       /* 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)
+               return 1;
+       tpacpi_disable_brightness_delay();
+       hotkey_dev_attributes = create_attr_set(13, NULL);
+       if (!hotkey_dev_attributes)
+               return -ENOMEM;
+       res = add_many_to_attr_set(hotkey_dev_attributes,
+                       hotkey_attributes,
+                       ARRAY_SIZE(hotkey_attributes));
+       if (res)
+               goto err_exit;
+       /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+          A30, R30, R31, T20-22, X20-21, X22-24.  Detected by checking
+          for HKEY interface version 0x100 */
+       if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
+               if ((hkeyv >> 8) != 1) {
+                       printk(TPACPI_ERR "unknown version of the "
+                              "HKEY interface: 0x%x\n", hkeyv);
+                       printk(TPACPI_ERR "please report this to %s\n",
+                              TPACPI_MAIL);
+               } else {
+                       /*
+                        * MHKV 0x100 in A31, R40, R40e,
+                        * T4x, X31, and later
+                        */
+                       tp_features.hotkey_mask = 1;
+               }
+       }
+       vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n",
+               str_supported(tp_features.hotkey_mask));
+       if (tp_features.hotkey_mask) {
+               if (!acpi_evalf(hkey_handle, &hotkey_all_mask,
+                               "MHKA", "qd")) {
+                       printk(TPACPI_ERR
+                              "missing MHKA handler, "
+                              "please report this to %s\n",
+                              TPACPI_MAIL);
+                       /* FN+F12, FN+F4, FN+F3 */
+                       hotkey_all_mask = 0x080cU;
+               }
+       }
+       /* hotkey_source_mask *must* be zero for
+        * the first hotkey_mask_get */
+       res = hotkey_status_get(&hotkey_orig_status);
+       if (res)
+               goto err_exit;
+       if (tp_features.hotkey_mask) {
+               res = hotkey_mask_get();
+               if (res)
+                       goto err_exit;
+               hotkey_orig_mask = hotkey_mask;
+               res = add_many_to_attr_set(
+                               hotkey_dev_attributes,
+                               hotkey_mask_attributes,
+                               ARRAY_SIZE(hotkey_mask_attributes));
+               if (res)
+                       goto err_exit;
+       }
+ #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+       if (tp_features.hotkey_mask) {
+               hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
+                                       & ~hotkey_all_mask;
+       } else {
+               hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK;
+       }
+       vdbg_printk(TPACPI_DBG_INIT,
+                   "hotkey source mask 0x%08x, polling freq %d\n",
+                   hotkey_source_mask, hotkey_poll_freq);
+ #endif
+       /* Not all thinkpads have a hardware radio switch */
+       if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
+               tp_features.hotkey_wlsw = 1;
+               printk(TPACPI_INFO
+                       "radio switch found; radios are %s\n",
+                       enabled(status, 0));
+       }
+       if (tp_features.hotkey_wlsw)
+               res = add_to_attr_set(hotkey_dev_attributes,
+                               &dev_attr_hotkey_radio_sw.attr);
+       /* For X41t, X60t, X61t Tablets... */
+       if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
+               tp_features.hotkey_tablet = 1;
+               printk(TPACPI_INFO
+                       "possible tablet mode switch found; "
+                       "ThinkPad in %s mode\n",
+                       (status & TP_HOTKEY_TABLET_MASK)?
+                               "tablet" : "laptop");
+               res = add_to_attr_set(hotkey_dev_attributes,
+                               &dev_attr_hotkey_tablet_mode.attr);
+       }
+       if (!res)
+               res = register_attr_set_with_sysfs(
+                               hotkey_dev_attributes,
+                               &tpacpi_pdev->dev.kobj);
+       if (res)
+               goto err_exit;
+       /* Set up key map */
+       hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE,
+                                       GFP_KERNEL);
+       if (!hotkey_keycode_map) {
+               printk(TPACPI_ERR
+                       "failed to allocate memory for key map\n");
+               res = -ENOMEM;
+               goto err_exit;
+       }
+       if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) {
+               dbg_printk(TPACPI_DBG_INIT,
+                          "using Lenovo default hot key map\n");
+               memcpy(hotkey_keycode_map, &lenovo_keycode_map,
+                       TPACPI_HOTKEY_MAP_SIZE);
+       } else {
+               dbg_printk(TPACPI_DBG_INIT,
+                          "using IBM default hot key map\n");
+               memcpy(hotkey_keycode_map, &ibm_keycode_map,
+                       TPACPI_HOTKEY_MAP_SIZE);
+       }
+       set_bit(EV_KEY, tpacpi_inputdev->evbit);
+       set_bit(EV_MSC, tpacpi_inputdev->evbit);
+       set_bit(MSC_SCAN, tpacpi_inputdev->mscbit);
+       tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
+       tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
+       tpacpi_inputdev->keycode = hotkey_keycode_map;
+       for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
+               if (hotkey_keycode_map[i] != KEY_RESERVED) {
+                       set_bit(hotkey_keycode_map[i],
+                               tpacpi_inputdev->keybit);
+               } else {
+                       if (i < sizeof(hotkey_reserved_mask)*8)
+                               hotkey_reserved_mask |= 1 << i;
+               }
+       }
+       if (tp_features.hotkey_wlsw) {
+               set_bit(EV_SW, tpacpi_inputdev->evbit);
+               set_bit(SW_RFKILL_ALL, tpacpi_inputdev->swbit);
+       }
+       if (tp_features.hotkey_tablet) {
+               set_bit(EV_SW, tpacpi_inputdev->evbit);
+               set_bit(SW_TABLET_MODE, tpacpi_inputdev->swbit);
+       }
+       /* Do not issue duplicate brightness change events to
+        * userspace */
+       if (!tp_features.bright_acpimode)
+               /* update bright_acpimode... */
+               tpacpi_check_std_acpi_brightness_support();
+       if (tp_features.bright_acpimode) {
+               printk(TPACPI_INFO
+                      "This ThinkPad has standard ACPI backlight "
+                      "brightness control, supported by the ACPI "
+                      "video driver\n");
+               printk(TPACPI_NOTICE
+                      "Disabling thinkpad-acpi brightness events "
+                      "by default...\n");
+               /* The hotkey_reserved_mask change below is not
+                * necessary while the keys are at KEY_RESERVED in the
+                * default map, but better safe than sorry, leave it
+                * here as a marker of what we have to do, especially
+                * when we finally become able to set this at runtime
+                * on response to X.org requests */
+               hotkey_reserved_mask |=
+                       (1 << TP_ACPI_HOTKEYSCAN_FNHOME)
+                       | (1 << TP_ACPI_HOTKEYSCAN_FNEND);
+       }
+       dbg_printk(TPACPI_DBG_INIT, "enabling hot key handling\n");
+       res = hotkey_status_set(1);
+       if (res) {
+               hotkey_exit();
+               return res;
+       }
+       res = hotkey_mask_set(((hotkey_all_mask | hotkey_source_mask)
+                               & ~hotkey_reserved_mask)
+                               | hotkey_orig_mask);
+       if (res < 0 && res != -ENXIO) {
+               hotkey_exit();
+               return res;
+       }
+       dbg_printk(TPACPI_DBG_INIT,
+                       "legacy hot key reporting over procfs %s\n",
+                       (hotkey_report_mode < 2) ?
+                               "enabled" : "disabled");
+       tpacpi_inputdev->open = &hotkey_inputdev_open;
+       tpacpi_inputdev->close = &hotkey_inputdev_close;
+       hotkey_poll_setup_safe(1);
+       tpacpi_send_radiosw_update();
+       tpacpi_input_send_tabletsw();
+       return 0;
+ err_exit:
+       delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+       hotkey_dev_attributes = NULL;
+       return (res < 0)? res : 1;
+ }
+ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
+ {
+       u32 hkey;
+       unsigned int scancode;
+       int send_acpi_ev;
+       int ignore_acpi_ev;
+       int unk_ev;
+       if (event != 0x80) {
+               printk(TPACPI_ERR
+                      "unknown HKEY notification event %d\n", event);
+               /* forward it to userspace, maybe it knows how to handle it */
+               acpi_bus_generate_netlink_event(
+                                       ibm->acpi->device->pnp.device_class,
+                                       ibm->acpi->device->dev.bus_id,
+                                       event, 0);
+               return;
+       }
+       while (1) {
+               if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
+                       printk(TPACPI_ERR "failed to retrieve HKEY event\n");
+                       return;
+               }
+               if (hkey == 0) {
+                       /* queue empty */
+                       return;
+               }
+               send_acpi_ev = 1;
+               ignore_acpi_ev = 0;
+               unk_ev = 0;
+               switch (hkey >> 12) {
+               case 1:
+                       /* 0x1000-0x1FFF: key presses */
+                       scancode = hkey & 0xfff;
+                       if (scancode > 0 && scancode < 0x21) {
+                               scancode--;
+                               if (!(hotkey_source_mask & (1 << scancode))) {
+                                       tpacpi_input_send_key(scancode);
+                                       send_acpi_ev = 0;
+                               } else {
+                                       ignore_acpi_ev = 1;
+                               }
+                       } else {
+                               unk_ev = 1;
+                       }
+                       break;
+               case 2:
+                       /* Wakeup reason */
+                       switch (hkey) {
+                       case 0x2304: /* suspend, undock */
+                       case 0x2404: /* hibernation, undock */
+                               hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK;
+                               ignore_acpi_ev = 1;
+                               break;
+                       case 0x2305: /* suspend, bay eject */
+                       case 0x2405: /* hibernation, bay eject */
+                               hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ;
+                               ignore_acpi_ev = 1;
+                               break;
+                       default:
+                               unk_ev = 1;
+                       }
+                       if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) {
+                               printk(TPACPI_INFO
+                                      "woke up due to a hot-unplug "
+                                      "request...\n");
+                               hotkey_wakeup_reason_notify_change();
+                       }
+                       break;
+               case 3:
+                       /* bay-related wakeups */
+                       if (hkey == 0x3003) {
+                               hotkey_autosleep_ack = 1;
+                               printk(TPACPI_INFO
+                                      "bay ejected\n");
+                               hotkey_wakeup_hotunplug_complete_notify_change();
+                       } else {
+                               unk_ev = 1;
+                       }
+                       break;
+               case 4:
+                       /* dock-related wakeups */
+                       if (hkey == 0x4003) {
+                               hotkey_autosleep_ack = 1;
+                               printk(TPACPI_INFO
+                                      "undocked\n");
+                               hotkey_wakeup_hotunplug_complete_notify_change();
+                       } else {
+                               unk_ev = 1;
+                       }
+                       break;
+               case 5:
+                       /* 0x5000-0x5FFF: human interface helpers */
+                       switch (hkey) {
+                       case 0x5010: /* Lenovo new BIOS: brightness changed */
+                       case 0x500b: /* X61t: tablet pen inserted into bay */
+                       case 0x500c: /* X61t: tablet pen removed from bay */
+                               break;
+                       case 0x5009: /* X41t-X61t: swivel up (tablet mode) */
+                       case 0x500a: /* X41t-X61t: swivel down (normal mode) */
+                               tpacpi_input_send_tabletsw();
+                               hotkey_tablet_mode_notify_change();
+                               send_acpi_ev = 0;
+                               break;
+                       case 0x5001:
+                       case 0x5002:
+                               /* LID switch events.  Do not propagate */
+                               ignore_acpi_ev = 1;
+                               break;
+                       default:
+                               unk_ev = 1;
+                       }
+                       break;
+               case 7:
+                       /* 0x7000-0x7FFF: misc */
+                       if (tp_features.hotkey_wlsw && hkey == 0x7000) {
+                               tpacpi_send_radiosw_update();
+                               send_acpi_ev = 0;
+                               break;
+                       }
+                       /* fallthrough to default */
+               default:
+                       unk_ev = 1;
+               }
+               if (unk_ev) {
+                       printk(TPACPI_NOTICE
+                              "unhandled HKEY event 0x%04x\n", hkey);
+               }
+               /* Legacy events */
+               if (!ignore_acpi_ev &&
+                   (send_acpi_ev || hotkey_report_mode < 2)) {
+                       acpi_bus_generate_proc_event(ibm->acpi->device,
+                                                    event, hkey);
+               }
+               /* netlink events */
+               if (!ignore_acpi_ev && send_acpi_ev) {
+                       acpi_bus_generate_netlink_event(
+                                       ibm->acpi->device->pnp.device_class,
+                                       ibm->acpi->device->dev.bus_id,
+                                       event, hkey);
+               }
+       }
+ }
+ static void hotkey_suspend(pm_message_t state)
+ {
+       /* Do these on suspend, we get the events on early resume! */
+       hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;
+       hotkey_autosleep_ack = 0;
+ }
+ static void hotkey_resume(void)
+ {
+       tpacpi_disable_brightness_delay();
+       if (hotkey_mask_get())
+               printk(TPACPI_ERR
+                      "error while trying to read hot key mask "
+                      "from firmware\n");
+       tpacpi_send_radiosw_update();
+       hotkey_tablet_mode_notify_change();
+       hotkey_wakeup_reason_notify_change();
+       hotkey_wakeup_hotunplug_complete_notify_change();
+       hotkey_poll_setup_safe(0);
+ }
+ /* procfs -------------------------------------------------------------- */
+ static int hotkey_read(char *p)
+ {
+       int res, status;
+       int len = 0;
+       if (!tp_features.hotkey) {
+               len += sprintf(p + len, "status:\t\tnot supported\n");
+               return len;
+       }
+       if (mutex_lock_interruptible(&hotkey_mutex))
+               return -ERESTARTSYS;
+       res = hotkey_status_get(&status);
+       if (!res)
+               res = hotkey_mask_get();
+       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%08x\n", hotkey_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;
+       u32 mask;
+       char *cmd;
+       if (!tp_features.hotkey)
+               return -ENODEV;
+       if (mutex_lock_interruptible(&hotkey_mutex))
+               return -ERESTARTSYS;
+       status = -1;
+       mask = hotkey_mask;
+       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;
+               }
+       }
+       if (status != -1)
+               res = hotkey_status_set(status);
+       if (!res && mask != hotkey_mask)
+               res = hotkey_mask_set(mask);
+ errexit:
+       mutex_unlock(&hotkey_mutex);
+       return res;
+ }
+ static const struct acpi_device_id ibm_htk_device_ids[] = {
+       {TPACPI_ACPI_HKEY_HID, 0},
+       {"", 0},
+ };
+ static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
+       .hid = ibm_htk_device_ids,
+       .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,
+       .resume = hotkey_resume,
+       .suspend = hotkey_suspend,
+       .acpi = &ibm_hotkey_acpidriver,
+ };
+ /*************************************************************************
+  * Bluetooth subdriver
+  */
+ enum {
+       /* ACPI GBDC/SBDC bits */
+       TP_ACPI_BLUETOOTH_HWPRESENT     = 0x01, /* Bluetooth hw available */
+       TP_ACPI_BLUETOOTH_RADIOSSW      = 0x02, /* Bluetooth radio enabled */
+       TP_ACPI_BLUETOOTH_UNK           = 0x04, /* unknown function */
+ };
+ static struct rfkill *tpacpi_bluetooth_rfkill;
+ static int bluetooth_get_radiosw(void)
+ {
+       int status;
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+       /* WLSW overrides bluetooth in firmware/hardware, reflect that */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
+               return RFKILL_STATE_HARD_BLOCKED;
+       if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+               return -EIO;
+       return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
+               RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
+ }
+ static void bluetooth_update_rfk(void)
+ {
+       int status;
+       if (!tpacpi_bluetooth_rfkill)
+               return;
+       status = bluetooth_get_radiosw();
+       if (status < 0)
+               return;
+       rfkill_force_state(tpacpi_bluetooth_rfkill, status);
+ }
+ static int bluetooth_set_radiosw(int radio_on, int update_rfk)
+ {
+       int status;
+       if (!tp_features.bluetooth)
+               return -ENODEV;
+       /* WLSW overrides bluetooth in firmware/hardware, but there is no
+        * reason to risk weird behaviour. */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
+           && radio_on)
+               return -EPERM;
+       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;
+       if (update_rfk)
+               bluetooth_update_rfk();
+       return 0;
+ }
+ /* 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 == RFKILL_STATE_UNBLOCKED) ? 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, 1);
+       return (res) ? res : count;
+ }
+ static struct device_attribute dev_attr_bluetooth_enable =
+       __ATTR(bluetooth_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 = {
+       .attrs = bluetooth_attributes,
+ };
+ static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state)
+ {
+       int bts = bluetooth_get_radiosw();
+       if (bts < 0)
+               return bts;
+       *state = bts;
+       return 0;
+ }
+ static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state)
+ {
+       return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
+ }
+ static void bluetooth_exit(void)
+ {
+       if (tpacpi_bluetooth_rfkill)
+               rfkill_unregister(tpacpi_bluetooth_rfkill);
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+                       &bluetooth_attr_group);
+ }
+ static int __init bluetooth_init(struct ibm_init_struct *iibm)
+ {
+       int res;
+       int status = 0;
+       vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n");
+       TPACPI_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 &&
+           !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
+               /* no bluetooth hardware present in system */
+               tp_features.bluetooth = 0;
+               dbg_printk(TPACPI_DBG_INIT,
+                          "bluetooth hardware not installed\n");
+       }
+       if (!tp_features.bluetooth)
+               return 1;
+       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                               &bluetooth_attr_group);
+       if (res)
+               return res;
+       res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
+                               &tpacpi_bluetooth_rfkill,
+                               RFKILL_TYPE_BLUETOOTH,
+                               "tpacpi_bluetooth_sw",
+                               tpacpi_bluetooth_rfk_set,
+                               tpacpi_bluetooth_rfk_get);
+       if (res) {
+               bluetooth_exit();
+               return res;
+       }
+       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 == RFKILL_STATE_UNBLOCKED) ?
+                                       "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, 1);
+               } else if (strlencmp(cmd, "disable") == 0) {
+                       bluetooth_set_radiosw(0, 1);
+               } 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
+  */
+ 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 struct rfkill *tpacpi_wan_rfkill;
+ static int wan_get_radiosw(void)
+ {
+       int status;
+       if (!tp_features.wan)
+               return -ENODEV;
+       /* WLSW overrides WWAN in firmware/hardware, reflect that */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
+               return RFKILL_STATE_HARD_BLOCKED;
+       if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+               return -EIO;
+       return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
+               RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
+ }
+ static void wan_update_rfk(void)
+ {
+       int status;
+       if (!tpacpi_wan_rfkill)
+               return;
+       status = wan_get_radiosw();
+       if (status < 0)
+               return;
+       rfkill_force_state(tpacpi_wan_rfkill, status);
+ }
+ static int wan_set_radiosw(int radio_on, int update_rfk)
+ {
+       int status;
+       if (!tp_features.wan)
+               return -ENODEV;
+       /* WLSW overrides bluetooth in firmware/hardware, but there is no
+        * reason to risk weird behaviour. */
+       if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
+           && radio_on)
+               return -EPERM;
+       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;
+       if (update_rfk)
+               wan_update_rfk();
+       return 0;
+ }
+ /* 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 == RFKILL_STATE_UNBLOCKED) ? 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, 1);
+       return (res) ? res : count;
+ }
+ static struct device_attribute dev_attr_wan_enable =
+       __ATTR(wwan_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 = {
+       .attrs = wan_attributes,
+ };
+ static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state)
+ {
+       int wans = wan_get_radiosw();
+       if (wans < 0)
+               return wans;
+       *state = wans;
+       return 0;
+ }
+ static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state)
+ {
+       return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
+ }
+ static void wan_exit(void)
+ {
+       if (tpacpi_wan_rfkill)
+               rfkill_unregister(tpacpi_wan_rfkill);
+       sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+               &wan_attr_group);
+ }
+ static int __init wan_init(struct ibm_init_struct *iibm)
+ {
+       int res;
+       int status = 0;
+       vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n");
+       TPACPI_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 &&
+           !(status & TP_ACPI_WANCARD_HWPRESENT)) {
+               /* no wan hardware present in system */
+               tp_features.wan = 0;
+               dbg_printk(TPACPI_DBG_INIT,
+                          "wan hardware not installed\n");
+       }
+       if (!tp_features.wan)
+               return 1;
+       res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+                               &wan_attr_group);
+       if (res)
+               return res;
+       res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
+                               &tpacpi_wan_rfkill,
+                               RFKILL_TYPE_WWAN,
+                               "tpacpi_wwan_sw",
+                               tpacpi_wan_rfk_set,
+                               tpacpi_wan_rfk_get);
+       if (res) {
+               wan_exit();
+               return res;
+       }
+       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 == RFKILL_STATE_UNBLOCKED) ?
+                                       "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, 1);
+               } else if (strlencmp(cmd, "disable") == 0) {
+                       wan_set_radiosw(0, 1);
+               } else
+                       return -EINVAL;
+       }
+       return 0;
+ }
+ static struct ibm_struct wan_driver_data = {
+       .name = "wan",
+       .read = wan_read,
+       .write = wan_write,
+       .exit = wan_exit,
+ };
+ /*************************************************************************
+  * Video subdriver
+  */
+ #ifdef CONFIG_THINKPAD_ACPI_VIDEO
+ 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 int video_autosw_get(void);
+ static int video_autosw_set(int enable);
+ TPACPI_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");
+       TPACPI_ACPIHANDLE_INIT(vid);
+       TPACPI_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(TPACPI_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(TPACPI_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(TPACPI_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,
+ };
+ #endif /* CONFIG_THINKPAD_ACPI_VIDEO */
+ /*************************************************************************
+  * Light (thinklight) subdriver
+  */
+ TPACPI_HANDLE(lght, root, "\\LGHT");  /* A21e, A2xm/p, T20-22, X20-21 */
+ TPACPI_HANDLE(ledb, ec, "LEDB");              /* G4x */
+ static int light_get_status(void)
+ {
+       int status = 0;
+       if (tp_features.light_status) {
+               if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
+                       return -EIO;
+               return (!!status);
+       }
+       return -ENXIO;
+ }
+ static int light_set_status(int status)
+ {
+       int rc;
+       if (tp_features.light) {
+               if (cmos_handle) {
+                       rc = acpi_evalf(cmos_handle, NULL, NULL, "vd",
+                                       (status)?
+                                               TP_CMOS_THINKLIGHT_ON :
+                                               TP_CMOS_THINKLIGHT_OFF);
+               } else {
+                       rc = acpi_evalf(lght_handle, NULL, NULL, "vd",
+                                       (status)? 1 : 0);
+               }
+               return (rc)? 0 : -EIO;
+       }
+       return -ENXIO;
+ }
+ static void light_set_status_worker(struct work_struct *work)
+ {
+       struct tpacpi_led_classdev *data =
+                       container_of(work, struct tpacpi_led_classdev, work);
+       if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
+               light_set_status((data->new_brightness != LED_OFF));
+ }
+ static void light_sysfs_set(struct led_classdev *led_cdev,
+                       enum led_brightness brightness)
+ {
+       struct tpacpi_led_classdev *data =
+               container_of(led_cdev,
+                            struct tpacpi_led_classdev,
+                            led_classdev);
+       data->new_brightness = brightness;
+       queue_work(tpacpi_wq, &data->work);
+ }
+ static enum led_brightness light_sysfs_get(struct led_classdev *led_cdev)
+ {
+       return (light_get_status() == 1)? LED_FULL : LED_OFF;
+ }
+ static struct tpacpi_led_classdev tpacpi_led_thinklight = {
+       .led_classdev = {
+               .name           = "tpacpi::thinklight",
+               .brightness_set = &light_sysfs_set,
+               .brightness_get = &light_sysfs_get,
+       }
+ };
+ static int __init light_init(struct ibm_init_struct *iibm)
+ {
+       int rc;
+       vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
+       TPACPI_ACPIHANDLE_INIT(ledb);
+       TPACPI_ACPIHANDLE_INIT(lght);
+       TPACPI_ACPIHANDLE_INIT(cmos);
+       INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
+       /* 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, light status is %s\n",
+               str_supported(tp_features.light),
+               str_supported(tp_features.light_status));
+       if (!tp_features.light)
+               return 1;
+       rc = led_classdev_register(&tpacpi_pdev->dev,
+                                  &tpacpi_led_thinklight.led_classdev);
+       if (rc < 0) {
+               tp_features.light = 0;
+               tp_features.light_status = 0;
+       } else  {
+               rc = 0;
+       }
+       return rc;
+ }
+ static void light_exit(void)
+ {
+       led_classdev_unregister(&tpacpi_led_thinklight.led_classdev);
+       if (work_pending(&tpacpi_led_thinklight.work))
+               flush_workqueue(tpacpi_wq);
+ }
+ static int light_read(char *p)
+ {
+       int len = 0;
+       int status;
+       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 {
+               status = light_get_status();
+               if (status < 0)
+                       return status;
+               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)
+ {
+       char *cmd;
+       int newstatus = 0;
+       if (!tp_features.light)
+               return -ENODEV;
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "on") == 0) {
+                       newstatus = 1;
+               } else if (strlencmp(cmd, "off") == 0) {
+                       newstatus = 0;
+               } else
+                       return -EINVAL;
+       }
+       return light_set_status(newstatus);
+ }
+ static struct ibm_struct light_driver_data = {
+       .name = "light",
+       .read = light_read,
+       .write = light_write,
+       .exit = light_exit,
+ };
+ /*************************************************************************
+  * Dock subdriver
+  */
+ #ifdef CONFIG_THINKPAD_ACPI_DOCK
+ static void dock_notify(struct ibm_struct *ibm, u32 event);
+ static int dock_read(char *p);
+ static int dock_write(char *buf);
+ TPACPI_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 */
+ TPACPI_HANDLE(pci, root, "\\_SB.PCI");        /* 570 */
+ static const struct acpi_device_id ibm_pci_device_ids[] = {
+       {PCI_ROOT_HID_STRING, 0},
+       {"", 0},
+ };
+ static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
+       {
+        .notify = dock_notify,
+        .handle = &dock_handle,
+        .type = ACPI_SYSTEM_NOTIFY,
+       },
+       {
+       /* THIS ONE MUST NEVER BE USED FOR DRIVER AUTOLOADING.
+        * We just use it to get notifications of dock hotplug
+        * in very old thinkpads */
+        .hid = ibm_pci_device_ids,
+        .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");
+       TPACPI_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) {
+               TPACPI_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 && ibm->acpi->device &&
+               acpi_match_device_ids(ibm->acpi->device, ibm_pci_device_ids);
+       int data;
+       if (event == 1 && !pci) /* 570 */
+               data = 1;       /* button */
+       else if (event == 1 && pci)     /* 570 */
+               data = 3;       /* dock */
+       else if (event == 3 && docked)
+               data = 1;       /* button */
+       else if (event == 3 && !docked)
+               data = 2;       /* undock */
+       else if (event == 0 && docked)
+               data = 3;       /* dock */
+       else {
+               printk(TPACPI_ERR "unknown dock event %d, status %d\n",
+                      event, _sta(dock_handle));
+               data = 0;       /* unknown */
+       }
+       acpi_bus_generate_proc_event(ibm->acpi->device, event, data);
+       acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
+                                         ibm->acpi->device->dev.bus_id,
+                                         event, data);
+ }
+ 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
+ TPACPI_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 */
+ TPACPI_HANDLE(bay_ej, bay, "_EJ3",    /* 600e/x, A2xm/p, A3x */
+          "_EJ0",              /* all others */
+          );                   /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
+ TPACPI_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV",        /* A3x, R32 */
+          "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */
+          );                           /* all others */
+ TPACPI_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");
+       TPACPI_ACPIHANDLE_INIT(bay);
+       if (bay_handle)
+               TPACPI_ACPIHANDLE_INIT(bay_ej);
+       TPACPI_ACPIHANDLE_INIT(bay2);
+       if (bay2_handle)
+               TPACPI_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_proc_event(ibm->acpi->device, event, 0);
+       acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
+                                         ibm->acpi->device->dev.bus_id,
+                                         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");
+       TPACPI_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
+  */
+ 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 */
+ };
+ enum led_status_t {
+       TPACPI_LED_OFF = 0,
+       TPACPI_LED_ON,
+       TPACPI_LED_BLINK,
+ };
+ static enum led_access_mode led_supported;
+ TPACPI_HANDLE(led, ec, "SLED",        /* 570 */
+          "SYSL",              /* 600e/x, 770e, 770x, A21e, A2xm/p, */
+                               /* T20-22, X20-21 */
+          "LED",               /* all others */
+          );                   /* R30, R31 */
+ #define TPACPI_LED_NUMLEDS 8
+ static struct tpacpi_led_classdev *tpacpi_leds;
+ static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS];
+ static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {
+       /* there's a limit of 19 chars + NULL before 2.6.26 */
+       "tpacpi::power",
+       "tpacpi:orange:batt",
+       "tpacpi:green:batt",
+       "tpacpi::dock_active",
+       "tpacpi::bay_active",
+       "tpacpi::dock_batt",
+       "tpacpi::unknown_led",
+       "tpacpi::standby",
+ };
+ static int led_get_status(const unsigned int led)
+ {
+       int status;
+       enum led_status_t led_s;
+       switch (led_supported) {
+       case TPACPI_LED_570:
+               if (!acpi_evalf(ec_handle,
+                               &status, "GLED", "dd", 1 << led))
+                       return -EIO;
+               led_s = (status == 0)?
+                               TPACPI_LED_OFF :
+                               ((status == 1)?
+                                       TPACPI_LED_ON :
+                                       TPACPI_LED_BLINK);
+               tpacpi_led_state_cache[led] = led_s;
+               return led_s;
+       default:
+               return -ENXIO;
+       }
+       /* not reached */
+ }
+ static int led_set_status(const unsigned int led,
+                         const enum led_status_t ledstatus)
+ {
+       /* off, on, blink. Index is led_status_t */
+       static const unsigned int led_sled_arg1[] = { 0, 1, 3 };
+       static const unsigned int led_led_arg1[] = { 0, 0x80, 0xc0 };
+       int rc = 0;
+       switch (led_supported) {
+       case TPACPI_LED_570:
+               /* 570 */
+               if (led > 7)
+                       return -EINVAL;
+               if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
+                               (1 << led), led_sled_arg1[ledstatus]))
+                       rc = -EIO;
+               break;
+       case TPACPI_LED_OLD:
+               /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
+               if (led > 7)
+                       return -EINVAL;
+               rc = ec_write(TPACPI_LED_EC_HLMS, (1 << led));
+               if (rc >= 0)
+                       rc = ec_write(TPACPI_LED_EC_HLBL,
+                                     (ledstatus == TPACPI_LED_BLINK) << led);
+               if (rc >= 0)
+                       rc = ec_write(TPACPI_LED_EC_HLCL,
+                                     (ledstatus != TPACPI_LED_OFF) << led);
+               break;
+       case TPACPI_LED_NEW:
+               /* all others */
+               if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
+                               led, led_led_arg1[ledstatus]))
+                       rc = -EIO;
+               break;
+       default:
+               rc = -ENXIO;
+       }
+       if (!rc)
+               tpacpi_led_state_cache[led] = ledstatus;
+       return rc;
+ }
+ static void led_sysfs_set_status(unsigned int led,
+                                enum led_brightness brightness)
+ {
+       led_set_status(led,
+                       (brightness == LED_OFF) ?
+                       TPACPI_LED_OFF :
+                       (tpacpi_led_state_cache[led] == TPACPI_LED_BLINK) ?
+                               TPACPI_LED_BLINK : TPACPI_LED_ON);
+ }
+ static void led_set_status_worker(struct work_struct *work)
+ {
+       struct tpacpi_led_classdev *data =
+               container_of(work, struct tpacpi_led_classdev, work);
+       if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
+               led_sysfs_set_status(data->led, data->new_brightness);
+ }
+ static void led_sysfs_set(struct led_classdev *led_cdev,
+                       enum led_brightness brightness)
+ {
+       struct tpacpi_led_classdev *data = container_of(led_cdev,
+                            struct tpacpi_led_classdev, led_classdev);
+       data->new_brightness = brightness;
+       queue_work(tpacpi_wq, &data->work);
+ }
+ static int led_sysfs_blink_set(struct led_classdev *led_cdev,
+                       unsigned long *delay_on, unsigned long *delay_off)
+ {
+       struct tpacpi_led_classdev *data = container_of(led_cdev,
+                            struct tpacpi_led_classdev, led_classdev);
+       /* Can we choose the flash rate? */
+       if (*delay_on == 0 && *delay_off == 0) {
+               /* yes. set them to the hardware blink rate (1 Hz) */
+               *delay_on = 500; /* ms */
+               *delay_off = 500; /* ms */
+       } else if ((*delay_on != 500) || (*delay_off != 500))
+               return -EINVAL;
+       data->new_brightness = TPACPI_LED_BLINK;
+       queue_work(tpacpi_wq, &data->work);
+       return 0;
+ }
+ static enum led_brightness led_sysfs_get(struct led_classdev *led_cdev)
+ {
+       int rc;
+       struct tpacpi_led_classdev *data = container_of(led_cdev,
+                            struct tpacpi_led_classdev, led_classdev);
+       rc = led_get_status(data->led);
+       if (rc == TPACPI_LED_OFF || rc < 0)
+               rc = LED_OFF;   /* no error handling in led class :( */
+       else
+               rc = LED_FULL;
+       return rc;
+ }
+ static void led_exit(void)
+ {
+       unsigned int i;
+       for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
+               if (tpacpi_leds[i].led_classdev.name)
+                       led_classdev_unregister(&tpacpi_leds[i].led_classdev);
+       }
+       kfree(tpacpi_leds);
+ }
+ static int __init led_init(struct ibm_init_struct *iibm)
+ {
+       unsigned int i;
+       int rc;
+       vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
+       TPACPI_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);
+       tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,
+                             GFP_KERNEL);
+       if (!tpacpi_leds) {
+               printk(TPACPI_ERR "Out of memory for LED data\n");
+               return -ENOMEM;
+       }
+       for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
+               tpacpi_leds[i].led = i;
+               tpacpi_leds[i].led_classdev.brightness_set = &led_sysfs_set;
+               tpacpi_leds[i].led_classdev.blink_set = &led_sysfs_blink_set;
+               if (led_supported == TPACPI_LED_570)
+                       tpacpi_leds[i].led_classdev.brightness_get =
+                                                       &led_sysfs_get;
+               tpacpi_leds[i].led_classdev.name = tpacpi_led_names[i];
+               INIT_WORK(&tpacpi_leds[i].work, led_set_status_worker);
+               rc = led_classdev_register(&tpacpi_pdev->dev,
+                                          &tpacpi_leds[i].led_classdev);
+               if (rc < 0) {
+                       tpacpi_leds[i].led_classdev.name = NULL;
+                       led_exit();
+                       return rc;
+               }
+       }
+       return (led_supported != TPACPI_LED_NONE)? 0 : 1;
+ }
+ #define str_led_status(s) \
+       ((s) == TPACPI_LED_OFF ? "off" : \
+               ((s) == TPACPI_LED_ON ? "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++) {
+                       status = led_get_status(i);
+                       if (status < 0)
+                               return -EIO;
+                       len += sprintf(p + len, "%d:\t\t%s\n",
+                                      i, str_led_status(status));
+               }
+       }
+       len += sprintf(p + len, "commands:\t"
+                      "<led> on, <led> off, <led> blink (<led> is 0-7)\n");
+       return len;
+ }
+ static int led_write(char *buf)
+ {
+       char *cmd;
+       int led, rc;
+       enum led_status_t s;
+       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")) {
+                       s = TPACPI_LED_OFF;
+               } else if (strstr(cmd, "on")) {
+                       s = TPACPI_LED_ON;
+               } else if (strstr(cmd, "blink")) {
+                       s = TPACPI_LED_BLINK;
+               } else {
+                       return -EINVAL;
+               }
+               rc = led_set_status(led, s);
+               if (rc < 0)
+                       return rc;
+       }
+       return 0;
+ }
+ static struct ibm_struct led_driver_data = {
+       .name = "led",
+       .read = led_read,
+       .write = led_write,
+       .exit = led_exit,
+ };
+ /*************************************************************************
+  * Beep subdriver
+  */
+ TPACPI_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");
+       TPACPI_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
+  */
+ 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;
+ /* 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;
+                       if (t > 127 || t < -127)
+                               t = TP_EC_THERMAL_TMP_NA;
+                       *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;
+ }
+ /* 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 (thinkpad_id.ec_model) {
+               /*
+                * 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(TPACPI_ERR
+                                      "ThinkPad ACPI EC access misbehaving, "
+                                      "falling back to ACPI TMPx access "
+                                      "mode\n");
+                               thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
+                       } else {
+                               printk(TPACPI_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_sensors_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_sensors_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_sensors_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_sensors_pdev->dev.kobj,
+                                  &thermal_temp_input16_group);
+               break;
+       case TPACPI_THERMAL_NONE:
+       default:
+               break;
+       }
+ }
+ 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
+  */
+ #define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
+ enum {
+       TP_EC_BACKLIGHT = 0x31,
+       /* TP_EC_BACKLIGHT bitmasks */
+       TP_EC_BACKLIGHT_LVLMSK = 0x1F,
+       TP_EC_BACKLIGHT_CMDMSK = 0xE0,
+       TP_EC_BACKLIGHT_MAPSW = 0x20,
+ };
+ static struct backlight_device *ibm_backlight_device;
+ static int brightness_mode;
+ static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
+ static struct mutex brightness_mutex;
+ /*
+  * ThinkPads can read brightness from two places: EC 0x31, or
+  * CMOS NVRAM byte 0x5E, bits 0-3.
+  *
+  * EC 0x31 has the following layout
+  *   Bit 7: unknown function
+  *   Bit 6: unknown function
+  *   Bit 5: Z: honour scale changes, NZ: ignore scale changes
+  *   Bit 4: must be set to zero to avoid problems
+  *   Bit 3-0: backlight brightness level
+  *
+  * brightness_get_raw returns status data in the EC 0x31 layout
+  */
+ static int brightness_get_raw(int *status)
+ {
+       u8 lec = 0, lcmos = 0, level = 0;
+       if (brightness_mode & 1) {
+               if (!acpi_ec_read(TP_EC_BACKLIGHT, &lec))
+                       return -EIO;
+               level = lec & TP_EC_BACKLIGHT_LVLMSK;
+       };
+       if (brightness_mode & 2) {
+               lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
+                        & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+                       >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+               lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
+               level = lcmos;
+       }
+       if (brightness_mode == 3) {
+               *status = lec;  /* Prefer EC, CMOS is just a backing store */
+               lec &= TP_EC_BACKLIGHT_LVLMSK;
+               if (lec == lcmos)
+                       tp_warned.bright_cmos_ec_unsync = 0;
+               else {
+                       if (!tp_warned.bright_cmos_ec_unsync) {
+                               printk(TPACPI_ERR
+                                       "CMOS NVRAM (%u) and EC (%u) do not "
+                                       "agree on display brightness level\n",
+                                       (unsigned int) lcmos,
+                                       (unsigned int) lec);
+                               tp_warned.bright_cmos_ec_unsync = 1;
+                       }
+                       return -EIO;
+               }
+       } else {
+               *status = level;
+       }
+       return 0;
+ }
+ /* May return EINTR which can always be mapped to ERESTARTSYS */
+ static int brightness_set(int value)
+ {
+       int cmos_cmd, inc, i, res;
+       int current_value;
+       int command_bits;
+       if (value > ((tp_features.bright_16levels)? 15 : 7) ||
+           value < 0)
+               return -EINVAL;
+       res = mutex_lock_interruptible(&brightness_mutex);
+       if (res < 0)
+               return res;
+       res = brightness_get_raw(&current_value);
+       if (res < 0)
+               goto errout;
+       command_bits = current_value & TP_EC_BACKLIGHT_CMDMSK;
+       current_value &= TP_EC_BACKLIGHT_LVLMSK;
+       cmos_cmd = value > current_value ?
+                       TP_CMOS_BRIGHTNESS_UP :
+                       TP_CMOS_BRIGHTNESS_DOWN;
+       inc = (value > current_value)? 1 : -1;
+       res = 0;
+       for (i = current_value; i != value; i += inc) {
+               if ((brightness_mode & 2) &&
+                   issue_thinkpad_cmos_command(cmos_cmd)) {
+                       res = -EIO;
+                       goto errout;
+               }
+               if ((brightness_mode & 1) &&
+                   !acpi_ec_write(TP_EC_BACKLIGHT,
+                                  (i + inc) | command_bits)) {
+                       res = -EIO;
+                       goto errout;;
+               }
+       }
+ errout:
+       mutex_unlock(&brightness_mutex);
+       return res;
+ }
+ /* sysfs backlight class ----------------------------------------------- */
+ static int brightness_update_status(struct backlight_device *bd)
+ {
+       /* it is the backlight class's job (caller) to handle
+        * EINTR and other errors properly */
+       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)
+ {
+       int status, res;
+       res = brightness_get_raw(&status);
+       if (res < 0)
+               return 0; /* FIXME: teach backlight about error handling */
+       return status & TP_EC_BACKLIGHT_LVLMSK;
+ }
+ 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");
+       mutex_init(&brightness_mutex);
+       /*
+        * We always attempt to detect acpi support, so as to switch
+        * Lenovo Vista BIOS to ACPI brightness mode even if we are not
+        * going to publish a backlight interface
+        */
+       b = tpacpi_check_std_acpi_brightness_support();
+       if (b > 0) {
+               if (acpi_video_backlight_support()) {
+                       if (brightness_enable > 1) {
+                               printk(TPACPI_NOTICE
+                                      "Standard ACPI backlight interface "
+                                      "available, not loading native one.\n");
+                               return 1;
+                       } else if (brightness_enable == 1) {
+                               printk(TPACPI_NOTICE
+                                      "Backlight control force enabled, even if standard "
+                                      "ACPI backlight interface is available\n");
+                       }
+               } else {
+                       if (brightness_enable > 1) {
+                               printk(TPACPI_NOTICE
+                                      "Standard ACPI backlight interface not "
+                                      "available, thinkpad_acpi native "
+                                      "brightness control enabled\n");
+                       }
+               }
+       }
+       if (!brightness_enable) {
+               dbg_printk(TPACPI_DBG_INIT,
+                          "brightness support disabled by "
+                          "module parameter\n");
+               return 1;
+       }
+       if (b > 16) {
+               printk(TPACPI_ERR
+                      "Unsupported brightness interface, "
+                      "please contact %s\n", TPACPI_MAIL);
+               return 1;
+       }
+       if (b == 16)
+               tp_features.bright_16levels = 1;
+       if (!brightness_mode) {
+               if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO)
+                       brightness_mode = 2;
+               else
+                       brightness_mode = 3;
+               dbg_printk(TPACPI_DBG_INIT, "selected brightness_mode=%d\n",
+                       brightness_mode);
+       }
+       if (brightness_mode > 3)
+               return -EINVAL;
+       if (brightness_get_raw(&b) < 0)
+               return 1;
+       if (tp_features.bright_16levels)
+               printk(TPACPI_INFO
+                      "detected a 16-level brightness capable ThinkPad\n");
+       ibm_backlight_device = backlight_device_register(
+                                       TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL,
+                                       &ibm_backlight_data);
+       if (IS_ERR(ibm_backlight_device)) {
+               printk(TPACPI_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 =
+                               (tp_features.bright_16levels)? 15 : 7;
+       ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
+       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);
+       }
+ }
+ static int brightness_read(char *p)
+ {
+       int len = 0;
+       int level;
+       level = brightness_get(NULL);
+       if (level < 0) {
+               len += sprintf(p + len, "level:\t\tunreadable\n");
+       } else {
+               len += sprintf(p + len, "level:\t\t%d\n", level);
+               len += sprintf(p + len, "commands:\tup, down\n");
+               len += sprintf(p + len, "commands:\tlevel <level>"
+                              " (<level> is 0-%d)\n",
+                              (tp_features.bright_16levels) ? 15 : 7);
+       }
+       return len;
+ }
+ static int brightness_write(char *buf)
+ {
+       int level;
+       int rc;
+       char *cmd;
+       int max_level = (tp_features.bright_16levels) ? 15 : 7;
+       level = brightness_get(NULL);
+       if (level < 0)
+               return level;
+       while ((cmd = next_cmd(&buf))) {
+               if (strlencmp(cmd, "up") == 0) {
+                       if (level < max_level)
+                               level++;
+               } else if (strlencmp(cmd, "down") == 0) {
+                       if (level > 0)
+                               level--;
+               } else if (sscanf(cmd, "level %d", &level) == 1 &&
+                          level >= 0 && level <= max_level) {
+                       /* new level set */
+               } else
+                       return -EINVAL;
+       }
+       /*
+        * Now we know what the final level should be, so we try to set it.
+        * Doing it this way makes the syscall restartable in case of EINTR
+        */
+       rc = brightness_set(level);
+       return (rc == -EINTR)? ERESTARTSYS : rc;
+ }
+ static struct ibm_struct brightness_driver_data = {
+       .name = "brightness",
+       .read = brightness_read,
+       .write = brightness_write,
+       .exit = brightness_exit,
+ };
+ /*************************************************************************
+  * Volume subdriver
+  */
+ 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;
+ }
+ 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.
+  */
+ 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 u8 fan_control_resume_level;
+ static int fan_watchdog_maxinterval;
+ static struct mutex fan_mutex;
+ static void fan_watchdog_fire(struct work_struct *ignored);
+ static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
+ TPACPI_HANDLE(fans, ec, "FANS");      /* X31, X40, X41 */
+ TPACPI_HANDLE(gfan, ec, "GFAN",       /* 570 */
+          "\\FSPD",            /* 600e/x, 770e, 770x */
+          );                   /* all others */
+ TPACPI_HANDLE(sfan, ec, "SFAN",       /* 570 */
+          "JFNS",              /* 770x-JL */
+          );                   /* all others */
+ /*
+  * 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;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       rc = fan_get_status(&s);
+       if (!rc)
+               fan_update_desired_level(s);
+       mutex_unlock(&fan_mutex);
+       if (status)
+               *status = s;
+       return rc;
+ }
+ 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 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_AUTO)
+                       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;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       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;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       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;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       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;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       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 void fan_watchdog_reset(void)
+ {
+       static int fan_watchdog_active;
+       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 &&
+           tpacpi_lifecycle != TPACPI_LIFE_EXITING) {
+               fan_watchdog_active = 1;
+               if (!queue_delayed_work(tpacpi_wq, &fan_watchdog_task,
+                               msecs_to_jiffies(fan_watchdog_maxinterval
+                                                * 1000))) {
+                       printk(TPACPI_ERR
+                              "failed to queue the fan watchdog, "
+                              "watchdog will not trigger\n");
+               }
+       } else
+               fan_watchdog_active = 0;
+ }
+ static void fan_watchdog_fire(struct work_struct *ignored)
+ {
+       int rc;
+       if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+               return;
+       printk(TPACPI_NOTICE "fan watchdog: enabling fan\n");
+       rc = fan_set_enable();
+       if (rc < 0) {
+               printk(TPACPI_ERR "fan watchdog: error %d while enabling fan, "
+                       "will try again later...\n", -rc);
+               /* reschedule for later */
+               fan_watchdog_reset();
+       }
+ }
+ /*
+  * 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;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       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 (hwmon 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;
+       TPACPI_ACPIHANDLE_INIT(fans);
+       TPACPI_ACPIHANDLE_INIT(gfan);
+       TPACPI_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) {
+                               switch (thinkpad_id.ec_model) {
+                               case 0x5931: /* TP-1Y */
+                               case 0x3837: /* TP-78 */
+                               case 0x3637: /* TP-76 */
+                               case 0x3037: /* TP-70 */
+                                       printk(TPACPI_NOTICE
+                                              "fan_init: initial fan status "
+                                              "is unknown, assuming it is "
+                                              "in auto mode\n");
+                                       tp_features.fan_ctrl_status_undef = 1;
+                                       ;;
+                               }
+                       }
+               } else {
+                       printk(TPACPI_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_sensors_pdev->dev.kobj,
+                                        &fan_attr_group);
+               if (rc < 0)
+                       return rc;
+               rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
+                                       &driver_attr_fan_watchdog);
+               if (rc < 0) {
+                       sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
+                                       &fan_attr_group);
+                       return rc;
+               }
+               return 0;
+       } else
+               return 1;
+ }
+ 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_sensors_pdev->dev.kobj, &fan_attr_group);
+       driver_remove_file(&tpacpi_hwmon_pdriver.driver,
+                          &driver_attr_fan_watchdog);
+       cancel_delayed_work(&fan_watchdog_task);
+       flush_workqueue(tpacpi_wq);
+ }
+ static void fan_suspend(pm_message_t state)
+ {
+       int rc;
+       if (!fan_control_allowed)
+               return;
+       /* Store fan status in cache */
+       fan_control_resume_level = 0;
+       rc = fan_get_status_safe(&fan_control_resume_level);
+       if (rc < 0)
+               printk(TPACPI_NOTICE
+                       "failed to read fan level for later "
+                       "restore during resume: %d\n", rc);
+       /* if it is undefined, don't attempt to restore it.
+        * KEEP THIS LAST */
+       if (tp_features.fan_ctrl_status_undef)
+               fan_control_resume_level = 0;
+ }
+ static void fan_resume(void)
+ {
+       u8 current_level = 7;
+       bool do_set = false;
+       int rc;
+       /* DSDT *always* updates status on resume */
+       tp_features.fan_ctrl_status_undef = 0;
+       if (!fan_control_allowed ||
+           !fan_control_resume_level ||
+           (fan_get_status_safe(&current_level) < 0))
+               return;
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               /* never decrease fan level */
+               do_set = (fan_control_resume_level > current_level);
+               break;
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               /* never decrease fan level, scale is:
+                * TP_EC_FAN_FULLSPEED > 7 >= TP_EC_FAN_AUTO
+                *
+                * We expect the firmware to set either 7 or AUTO, but we
+                * handle FULLSPEED out of paranoia.
+                *
+                * So, we can safely only restore FULLSPEED or 7, anything
+                * else could slow the fan.  Restoring AUTO is useless, at
+                * best that's exactly what the DSDT already set (it is the
+                * slower it uses).
+                *
+                * Always keep in mind that the DSDT *will* have set the
+                * fans to what the vendor supposes is the best level.  We
+                * muck with it only to speed the fan up.
+                */
+               if (fan_control_resume_level != 7 &&
+                   !(fan_control_resume_level & TP_EC_FAN_FULLSPEED))
+                       return;
+               else
+                       do_set = !(current_level & TP_EC_FAN_FULLSPEED) &&
+                                (current_level != fan_control_resume_level);
+               break;
+       default:
+               return;
+       }
+       if (do_set) {
+               printk(TPACPI_NOTICE
+                       "restoring fan level to 0x%02x\n",
+                       fan_control_resume_level);
+               rc = fan_set_level_safe(fan_control_resume_level);
+               if (rc < 0)
+                       printk(TPACPI_NOTICE
+                               "failed to restore fan level: %d\n", 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 */
+               rc = fan_get_status_safe(&status);
+               if (rc < 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 */
+               rc = fan_get_status_safe(&status);
+               if (rc < 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");
+               rc = fan_get_speed(&speed);
+               if (rc < 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;
+       *rc = fan_set_level_safe(level);
+       if (*rc == -ENXIO)
+               printk(TPACPI_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;
+       *rc = fan_set_enable();
+       if (*rc == -ENXIO)
+               printk(TPACPI_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;
+       *rc = fan_set_disable();
+       if (*rc == -ENXIO)
+               printk(TPACPI_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;
+       *rc = fan_set_speed(speed);
+       if (*rc == -ENXIO)
+               printk(TPACPI_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,
+       .suspend = fan_suspend,
+       .resume = fan_resume,
+ };
+ /****************************************************************************
+  ****************************************************************************
+  *
+  * Infrastructure
+  *
+  ****************************************************************************
+  ****************************************************************************/
+ /* sysfs name ---------------------------------------------------------- */
+ static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+ {
+       return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME);
+ }
+ static struct device_attribute dev_attr_thinkpad_acpi_pdev_name =
+       __ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL);
+ /* --------------------------------------------------------------------- */
+ /* /proc support */
+ static struct proc_dir_entry *proc_dir;
+ /*
+  * Module and infrastructure proble, init and exit handling
+  */
+ static int force_load;
+ #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 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);
+ }
+ 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(TPACPI_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(TPACPI_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;
+ }
+ /* Probing */
+ /* returns 0 - probe ok, or < 0 - probe error.
+  * Probe ok doesn't mean thinkpad found.
+  * On error, kfree() cleanup on tp->* is not performed, caller must do it */
+ static int __must_check __init get_thinkpad_model_data(
+                                               struct thinkpad_id_data *tp)
+ {
+       const struct dmi_device *dev = NULL;
+       char ec_fw_string[18];
+       char const *s;
+       if (!tp)
+               return -EINVAL;
+       memset(tp, 0, sizeof(*tp));
+       if (dmi_name_in_vendors("IBM"))
+               tp->vendor = PCI_VENDOR_ID_IBM;
+       else if (dmi_name_in_vendors("LENOVO"))
+               tp->vendor = PCI_VENDOR_ID_LENOVO;
+       else
+               return 0;
+       s = dmi_get_system_info(DMI_BIOS_VERSION);
+       tp->bios_version_str = kstrdup(s, GFP_KERNEL);
+       if (s && !tp->bios_version_str)
+               return -ENOMEM;
+       if (!tp->bios_version_str)
+               return 0;
+       tp->bios_model = tp->bios_version_str[0]
+                        | (tp->bios_version_str[1] << 8);
+       /*
+        * 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;
+                       tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
+                       if (!tp->ec_version_str)
+                               return -ENOMEM;
+                       tp->ec_model = ec_fw_string[0]
+                                       | (ec_fw_string[1] << 8);
+                       break;
+               }
+       }
+       s = dmi_get_system_info(DMI_PRODUCT_VERSION);
+       if (s && !strnicmp(s, "ThinkPad", 8)) {
+               tp->model_str = kstrdup(s, GFP_KERNEL);
+               if (!tp->model_str)
+                       return -ENOMEM;
+       }
+       s = dmi_get_system_info(DMI_PRODUCT_NAME);
+       tp->nummodel_str = kstrdup(s, GFP_KERNEL);
+       if (s && !tp->nummodel_str)
+               return -ENOMEM;
+       return 0;
+ }
+ 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 = (thinkpad_id.model_str != NULL);
+       /* ec is required because many other handles are relative to it */
+       TPACPI_ACPIHANDLE_INIT(ec);
+       if (!ec_handle) {
+               if (is_thinkpad)
+                       printk(TPACPI_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 = (thinkpad_id.vendor == PCI_VENDOR_ID_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,
+       },
+ #ifdef CONFIG_THINKPAD_ACPI_VIDEO
+       {
+               .init = video_init,
+               .data = &video_driver_data,
+       },
+ #endif
+       {
+               .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;
+       if (!kp || !kp->name || !val)
+               return -EINVAL;
+       for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
+               ibm = ibms_init[i].data;
+               WARN_ON(ibm == NULL);
+               if (!ibm || !ibm->name)
+                       continue;
+               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;
+ }
+ module_param(experimental, int, 0);
+ MODULE_PARM_DESC(experimental,
+                "Enables experimental features when non-zero");
+ module_param_named(debug, dbg_level, uint, 0);
+ MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
+ module_param(force_load, bool, 0);
+ MODULE_PARM_DESC(force_load,
+                "Attempts to load the driver even on a "
+                "mis-identified ThinkPad when true");
+ module_param_named(fan_control, fan_control_allowed, bool, 0);
+ MODULE_PARM_DESC(fan_control,
+                "Enables setting fan parameters features when true");
+ module_param_named(brightness_mode, brightness_mode, int, 0);
+ MODULE_PARM_DESC(brightness_mode,
+                "Selects brightness control strategy: "
+                "0=auto, 1=EC, 2=CMOS, 3=both");
+ module_param(brightness_enable, uint, 0);
+ MODULE_PARM_DESC(brightness_enable,
+                "Enables backlight control when 1, disables when 0");
+ module_param(hotkey_report_mode, uint, 0);
+ MODULE_PARM_DESC(hotkey_report_mode,
+                "used for backwards compatibility with userspace, "
+                "see documentation");
+ #define TPACPI_PARAM(feature) \
+       module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
+       MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
+                        "at module load, see documentation")
+ TPACPI_PARAM(hotkey);
+ TPACPI_PARAM(bluetooth);
+ TPACPI_PARAM(video);
+ TPACPI_PARAM(light);
+ #ifdef CONFIG_THINKPAD_ACPI_DOCK
+ TPACPI_PARAM(dock);
+ #endif
+ #ifdef CONFIG_THINKPAD_ACPI_BAY
+ TPACPI_PARAM(bay);
+ #endif /* CONFIG_THINKPAD_ACPI_BAY */
+ TPACPI_PARAM(cmos);
+ TPACPI_PARAM(led);
+ TPACPI_PARAM(beep);
+ TPACPI_PARAM(ecdump);
+ TPACPI_PARAM(brightness);
+ TPACPI_PARAM(volume);
+ TPACPI_PARAM(fan);
+ static void thinkpad_acpi_module_exit(void)
+ {
+       struct ibm_struct *ibm, *itmp;
+       tpacpi_lifecycle = TPACPI_LIFE_EXITING;
+       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_inputdev) {
+               if (tp_features.input_device_registered)
+                       input_unregister_device(tpacpi_inputdev);
+               else
+                       input_free_device(tpacpi_inputdev);
+       }
+       if (tpacpi_hwmon)
+               hwmon_device_unregister(tpacpi_hwmon);
+       if (tp_features.sensors_pdev_attrs_registered)
+               device_remove_file(&tpacpi_sensors_pdev->dev,
+                                  &dev_attr_thinkpad_acpi_pdev_name);
+       if (tpacpi_sensors_pdev)
+               platform_device_unregister(tpacpi_sensors_pdev);
+       if (tpacpi_pdev)
+               platform_device_unregister(tpacpi_pdev);
+       if (tp_features.sensors_pdrv_attrs_registered)
+               tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
+       if (tp_features.platform_drv_attrs_registered)
+               tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
+       if (tp_features.sensors_pdrv_registered)
+               platform_driver_unregister(&tpacpi_hwmon_pdriver);
+       if (tp_features.platform_drv_registered)
+               platform_driver_unregister(&tpacpi_pdriver);
+       if (proc_dir)
+               remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
+       if (tpacpi_wq)
+               destroy_workqueue(tpacpi_wq);
+       kfree(thinkpad_id.bios_version_str);
+       kfree(thinkpad_id.ec_version_str);
+       kfree(thinkpad_id.model_str);
+ }
+ static int __init thinkpad_acpi_module_init(void)
+ {
+       int ret, i;
+       tpacpi_lifecycle = TPACPI_LIFE_INIT;
+       /* Parameter checking */
+       if (hotkey_report_mode > 2)
+               return -EINVAL;
+       /* Driver-level probe */
+       ret = get_thinkpad_model_data(&thinkpad_id);
+       if (ret) {
+               printk(TPACPI_ERR
+                       "unable to get DMI data: %d\n", ret);
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       ret = probe_for_thinkpad();
+       if (ret) {
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       /* Driver initialization */
+       TPACPI_ACPIHANDLE_INIT(ecrd);
+       TPACPI_ACPIHANDLE_INIT(ecwr);
+       tpacpi_wq = create_singlethread_workqueue(TPACPI_WORKQUEUE_NAME);
+       if (!tpacpi_wq) {
+               thinkpad_acpi_module_exit();
+               return -ENOMEM;
+       }
+       proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir);
+       if (!proc_dir) {
+               printk(TPACPI_ERR
+                      "unable to create proc dir " TPACPI_PROC_DIR);
+               thinkpad_acpi_module_exit();
+               return -ENODEV;
+       }
+       proc_dir->owner = THIS_MODULE;
+       ret = platform_driver_register(&tpacpi_pdriver);
+       if (ret) {
+               printk(TPACPI_ERR
+                      "unable to register main platform driver\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       tp_features.platform_drv_registered = 1;
+       ret = platform_driver_register(&tpacpi_hwmon_pdriver);
+       if (ret) {
+               printk(TPACPI_ERR
+                      "unable to register hwmon platform driver\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       tp_features.sensors_pdrv_registered = 1;
+       ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
+       if (!ret) {
+               tp_features.platform_drv_attrs_registered = 1;
+               ret = tpacpi_create_driver_attributes(
+                                       &tpacpi_hwmon_pdriver.driver);
+       }
+       if (ret) {
+               printk(TPACPI_ERR
+                      "unable to create sysfs driver attributes\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       tp_features.sensors_pdrv_attrs_registered = 1;
+       /* Device initialization */
+       tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, -1,
+                                                       NULL, 0);
+       if (IS_ERR(tpacpi_pdev)) {
+               ret = PTR_ERR(tpacpi_pdev);
+               tpacpi_pdev = NULL;
+               printk(TPACPI_ERR "unable to register platform device\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       tpacpi_sensors_pdev = platform_device_register_simple(
+                                               TPACPI_HWMON_DRVR_NAME,
+                                               -1, NULL, 0);
+       if (IS_ERR(tpacpi_sensors_pdev)) {
+               ret = PTR_ERR(tpacpi_sensors_pdev);
+               tpacpi_sensors_pdev = NULL;
+               printk(TPACPI_ERR
+                      "unable to register hwmon platform device\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       ret = device_create_file(&tpacpi_sensors_pdev->dev,
+                                &dev_attr_thinkpad_acpi_pdev_name);
+       if (ret) {
+               printk(TPACPI_ERR
+                      "unable to create sysfs hwmon device attributes\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       tp_features.sensors_pdev_attrs_registered = 1;
+       tpacpi_hwmon = hwmon_device_register(&tpacpi_sensors_pdev->dev);
+       if (IS_ERR(tpacpi_hwmon)) {
+               ret = PTR_ERR(tpacpi_hwmon);
+               tpacpi_hwmon = NULL;
+               printk(TPACPI_ERR "unable to register hwmon device\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       }
+       mutex_init(&tpacpi_inputdev_send_mutex);
+       tpacpi_inputdev = input_allocate_device();
+       if (!tpacpi_inputdev) {
+               printk(TPACPI_ERR "unable to allocate input device\n");
+               thinkpad_acpi_module_exit();
+               return -ENOMEM;
+       } else {
+               /* Prepare input device, but don't register */
+               tpacpi_inputdev->name = "ThinkPad Extra Buttons";
+               tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
+               tpacpi_inputdev->id.bustype = BUS_HOST;
+               tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ?
+                                               thinkpad_id.vendor :
+                                               PCI_VENDOR_ID_IBM;
+               tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
+               tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
+       }
+       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;
+               }
+       }
+       ret = input_register_device(tpacpi_inputdev);
+       if (ret < 0) {
+               printk(TPACPI_ERR "unable to register input device\n");
+               thinkpad_acpi_module_exit();
+               return ret;
+       } else {
+               tp_features.input_device_registered = 1;
+       }
+       tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
+       return 0;
+ }
+ /* Please remove this in year 2009 */
+ MODULE_ALIAS("ibm_acpi");
+ MODULE_ALIAS(TPACPI_DRVR_SHORTNAME);
+ /*
+  * DMI matching for module autoloading
+  *
+  * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+  * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
+  *
+  * Only models listed in thinkwiki will be supported, so add yours
+  * if it is not there yet.
+  */
+ #define IBM_BIOS_MODULE_ALIAS(__type) \
+       MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
+ /* Non-ancient thinkpads */
+ MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
+ MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
+ /* Ancient thinkpad BIOSes have to be identified by
+  * BIOS type or model number, and there are far less
+  * BIOS types than model numbers... */
+ IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
+ IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
+ IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
+ MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
+ MODULE_DESCRIPTION(TPACPI_DESC);
+ MODULE_VERSION(TPACPI_VERSION);
+ MODULE_LICENSE("GPL");
+ module_init(thinkpad_acpi_module_init);
+ module_exit(thinkpad_acpi_module_exit);