From: Len Brown <len.brown@intel.com>
Date: Fri, 9 Jan 2009 09:01:26 +0000 (-0500)
Subject: Merge branch 'drivers-platform' into release
X-Git-Tag: firefly_0821_release~15934^2
X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=d97c0defba25a959a990f6d4759f43075540832e;p=firefly-linux-kernel-4.4.55.git

Merge branch 'drivers-platform' into release

Conflicts:
	drivers/misc/Kconfig

Signed-off-by: Len Brown <len.brown@intel.com>
---

d97c0defba25a959a990f6d4759f43075540832e
diff --cc drivers/Makefile
index 6326f4dbbdab,46d4828042c3..c1bf41737936
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@@ -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/
diff --cc drivers/misc/Makefile
index 5de863a0e395,d5749a7bc777..9cf8ae6e4b39
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@@ -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
diff --cc drivers/platform/x86/Kconfig
index 000000000000,31f3ce2ac01c..e65448e99b48
mode 000000,100644..100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@@ -1,0 -1,375 +1,375 @@@
+ #
+ # 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 BACKLIGHT_CLASS_DEVICE
 -	depends on HWMON
+ 	depends on EXPERIMENTAL
 -	depends on RFKILL
++	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
diff --cc drivers/platform/x86/fujitsu-laptop.c
index 000000000000,a7dd3e9fb79d..65dc41540c62
mode 000000,100644..100644
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@@ -1,0 -1,1126 +1,1293 @@@
+ /*-*-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
+ 
 -#define FUJITSU_DRIVER_VERSION "0.4.3"
++#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;
 -static int disable_brightness_keys = -1;
+ 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;
 -
 -	unsigned int irb;	/* info about the pressed buttons */
++	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;
+ }
+ 
 -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;
 -}
 -
+ /* Backlight device stuff */
+ 
+ static int bl_get_brightness(struct backlight_device *b)
+ {
 -	if (use_alt_lcd_levels)
 -		return get_lcd_level_alt();
 -	else
 -		return get_lcd_level();
++	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_alt(b->props.brightness);
++		ret = set_lcd_level_alt(b->props.brightness);
+ 	else
 -		return set_lcd_level(b->props.brightness);
++		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;
+ 
 -	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 count;
+ }
+ 
 -/* Hardware access for hotkey device */
 -
 -static int get_irb(void)
++static ssize_t
++ignore_store(struct device *dev,
++	     struct device_attribute *attr, const char *buf, size_t 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;
++	return count;
++}
+ 
 -	fujitsu_hotkey->irb = state;
++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");
++}
+ 
 -	return fujitsu_hotkey->irb;
++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
 -ignore_store(struct device *dev,
 -	     struct device_attribute *attr, const char *buf, size_t count)
++show_radios_state(struct device *dev,
++			struct device_attribute *attr, char *buf)
+ {
 -	return count;
++	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;
 -	int have_blnf;
+ 	printk(KERN_INFO "fujitsu-laptop: Identified laptop model '%s'.\n",
+ 	       id->ident);
 -	have_blnf = ACPI_SUCCESS
 -	    (acpi_get_handle(NULL, "\\_SB.PCI0.GFX0.LCD.BLNF", &handle));
+ 	if (use_alt_lcd_levels == -1) {
 -		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 (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;
 -	disable_brightness_keys = disable_brightness_keys == 1 ? 1 : 0;
+ 	disable_brightness_adjust = disable_brightness_adjust == 1 ? 1 : 0;
+ 	vdbg_printk(FUJLAPTOP_DBG_INFO,
 -		    "config: [alt interface: %d], [key disable: %d], [adjust disable: %d]\n",
 -		    use_alt_lcd_levels, disable_brightness_keys,
 -		    disable_brightness_adjust);
++		    "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;
 -	if (use_alt_lcd_levels)
 -		get_lcd_level_alt();
 -	else
 -		get_lcd_level();
++	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;
 -		get_lcd_level();  /* the alt version always yields changed */
++		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 (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) {
++		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_INC_BRIGHTNESS, 0);
 -				keycode = KEY_BRIGHTNESSUP;
 -			}
++			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);
+ 			}
 -			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_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;
 -	input->evbit[0] = BIT(EV_KEY);
++
++	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");
+ 	}
+ 
 -	i = 0;			/* Discard hotkey ringbuffer */
 -	while (get_irb() != 0 && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) ;
++	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;
+ 
 -	vdbg_printk(FUJLAPTOP_DBG_TRACE, "Hotkey event\n");
++	fujitsu_hotkey->rfkill_state =
++		call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
+ 
+ 	switch (event) {
+ 	case ACPI_FUJITSU_NOTIFY_CODE1:
+ 		i = 0;
 -		while ((irb = get_irb()) != 0
 -		       && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) {
 -			vdbg_printk(FUJLAPTOP_DBG_TRACE, "GIRB result [%x]\n",
 -				    irb);
 -
++		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_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).");
+ 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");
++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);
diff --cc drivers/platform/x86/panasonic-laptop.c
index 000000000000,4a1bc64485d5..f30db367c82e
mode 000000,100644..100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@@ -1,0 -1,766 +1,744 @@@
+ /*
+  *  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_write_sset");
 -
+ 	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_get_sqty");
 -
+ 	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_retrieve_biosdata");
 -
+ 	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_generate_keyinput");
 -
+ 	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_hotkey_notify");
 -
+ 	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_init_input");
 -
+ 	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_resume");
 -
+ 	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_hotkey_add");
 -
+ 	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_init");
 -
+ 	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_hotkey_remove");
 -
+ 	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_FUNCTION_TRACE("acpi_pcc_exit");
 -
+ 	acpi_bus_unregister_driver(&acpi_pcc_driver);
+ }
+ 
+ module_init(acpi_pcc_init);
+ module_exit(acpi_pcc_exit);
diff --cc drivers/platform/x86/sony-laptop.c
index 000000000000,571b211608d1..537959d07148
mode 000000,100644..100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@@ -1,0 -1,2781 +1,2784 @@@
+ /*
+  * 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)
+ {
 -	struct acpi_namespace_node *node;
 -	union acpi_operand_object *operand;
++	struct acpi_device_info *info;
++	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+ 
 -	node = (struct acpi_namespace_node *)handle;
 -	operand = (union acpi_operand_object *)node->object;
++	if (ACPI_SUCCESS(acpi_get_object_info(handle, &buffer))) {
++		info = buffer.pointer;
+ 
 -	printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
 -	       (u32) operand->method.param_count);
++		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);
diff --cc drivers/platform/x86/tc1100-wmi.c
index 000000000000,f25e4c974dcf..b4a4aa9ee482
mode 000000,100644..100644
--- a/drivers/platform/x86/tc1100-wmi.c
+++ b/drivers/platform/x86/tc1100-wmi.c
@@@ -1,0 -1,290 +1,289 @@@
+ /*
+  *  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/actypes.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);
diff --cc drivers/platform/x86/thinkpad_acpi.c
index 000000000000,899766e16fa8..3478453eba7a
mode 000000,100644..100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@@ -1,0 -1,6949 +1,6948 @@@
+ /*
+  *  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 <acpi/acnamesp.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);