File-copy from v4.4.100

This is the result of 'cp' from a linux-stable tree with the 'v4.4.100'
tag checked out (commit 26d6298789e695c9f627ce49a7bbd2286405798a) on
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

Please refer to that tree for all history prior to this point.

Change-Id: I8a9ee2aea93cd29c52c847d0ce33091a73ae6afe
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
new file mode 100644
index 0000000..1089eaa
--- /dev/null
+++ b/drivers/platform/x86/Kconfig
@@ -0,0 +1,947 @@
+#
+# X86 Platform Specific Drivers
+#
+
+menuconfig X86_PLATFORM_DEVICES
+	bool "X86 Platform Specific Device Drivers"
+	default y
+	depends on X86
+	---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"
+	depends on ACPI
+	select LEDS_CLASS
+	select NEW_LEDS
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on SERIO_I8042
+	depends on INPUT
+	depends on RFKILL || RFKILL = n
+	depends on ACPI_WMI
+	select INPUT_SPARSEKMAP
+	# Acer WMI depends on ACPI_VIDEO when ACPI is enabled
+        select ACPI_VIDEO if ACPI
+	---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.
+
+	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
+	  here.
+
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on ACPI && THERMAL
+	select THERMAL_GOV_BANG_BANG
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  After loading this driver the BIOS is still in control of the fan.
+	  To let the kernel handle the fan, do:
+	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
+config ALIENWARE_WMI
+	tristate "Alienware Special feature control"
+	depends on ACPI
+	depends on LEDS_CLASS
+	depends on NEW_LEDS
+	depends on ACPI_WMI
+	---help---
+	 This is a driver for controlling Alienware BIOS driven
+	 features.  It exposes an interface for controlling the AlienFX
+	 zones on Alienware machines that don't contain a dedicated AlienFX
+	 USB MCU such as the X51 and X51-R2.
+
+config ASUS_LAPTOP
+	tristate "Asus Laptop Extras"
+	depends on ACPI
+	select LEDS_CLASS
+	select NEW_LEDS
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on INPUT
+	depends on RFKILL || RFKILL = n
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	select INPUT_SPARSEKMAP
+	select INPUT_POLLDEV
+	---help---
+	  This is a driver for Asus laptops, Lenovo SL and the Pegatron
+	  Lucid tablet. It may also support some MEDION, JVC or VICTOR
+	  laptops. It makes all the extra buttons generate standard
+	  ACPI events and input events, and on the Lucid the built-in
+	  accelerometer appears as an input device.  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 see <http://acpi4asus.sf.net>.
+
+	  If you have an ACPI-compatible ASUS laptop, say Y or M here.
+
+config DELL_LAPTOP
+	tristate "Dell Laptop Extras"
+	depends on X86
+	depends on DCDBAS
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on RFKILL || RFKILL = n
+	depends on SERIO_I8042
+	select POWER_SUPPLY
+	select LEDS_CLASS
+	select NEW_LEDS
+	default n
+	---help---
+	This driver adds support for rfkill and backlight control to Dell
+	laptops (except for some models covered by the Compal driver).
+
+config DELL_WMI
+	tristate "Dell WMI extras"
+	depends on ACPI_WMI
+	depends on INPUT
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	select INPUT_SPARSEKMAP
+	---help---
+	  Say Y here if you want to support WMI-based hotkeys on Dell laptops.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called dell-wmi.
+
+config DELL_WMI_AIO
+	tristate "WMI Hotkeys for Dell All-In-One series"
+	depends on ACPI_WMI
+	depends on INPUT
+	select INPUT_SPARSEKMAP
+	---help---
+	  Say Y here if you want to support WMI-based hotkeys on Dell
+	  All-In-One machines.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called dell-wmi-aio.
+
+config DELL_SMO8800
+	tristate "Dell Latitude freefall driver (ACPI SMO88XX)"
+	depends on ACPI
+	---help---
+	  Say Y here if you want to support SMO88XX freefall devices
+	  on Dell Latitude laptops.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called dell-smo8800.
+
+config DELL_RBTN
+	tristate "Dell Airplane Mode Switch driver"
+	depends on ACPI
+	depends on INPUT
+	depends on RFKILL
+	---help---
+	  Say Y here if you want to support Dell Airplane Mode Switch ACPI
+	  device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN.
+	  This driver register rfkill device or input hotkey device depending
+	  on hardware type (hw switch slider or keyboard toggle button). For
+	  rfkill devices it receive HW switch events and set correct hard
+	  rfkill state.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called dell-rbtn.
+
+
+config FUJITSU_LAPTOP
+	tristate "Fujitsu Laptop Extras"
+	depends on ACPI
+	depends on INPUT
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on LEDS_CLASS || LEDS_CLASS=n
+	---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 FUJITSU_TABLET
+       tristate "Fujitsu Tablet Extras"
+       depends on ACPI
+       depends on INPUT
+       ---help---
+         This is a driver for tablets built by Fujitsu:
+
+           * Lifebook P1510/P1610/P1620/Txxxx
+           * Stylistic ST5xxx
+           * Possibly other Fujitsu tablet models
+
+         It adds support for the panel buttons, docking station detection,
+         tablet/notebook mode detection for convertible and
+         orientation detection for docked slates.
+
+         If you have a Fujitsu convertible or slate, say Y or M here.
+
+config AMILO_RFKILL
+	tristate "Fujitsu-Siemens Amilo rfkill support"
+	depends on RFKILL
+	depends on SERIO_I8042
+	---help---
+	  This is a driver for enabling wifi on some Fujitsu-Siemens Amilo
+	  laptops.
+
+config TC1100_WMI
+	tristate "HP Compaq TC1100 Tablet WMI Extras"
+	depends on !X86_64
+	depends on ACPI
+	depends on ACPI_WMI
+	---help---
+	  This is a driver for the WMI extensions (wireless and bluetooth power
+	  control) of the HP Compaq TC1100 tablet.
+
+config HP_ACCEL
+	tristate "HP laptop accelerometer"
+	depends on INPUT && ACPI
+	depends on SERIO_I8042
+	select SENSORS_LIS3LV02D
+	select NEW_LEDS
+	select LEDS_CLASS
+	help
+	  This driver provides support for the "Mobile Data Protection System 3D"
+	  or "3D DriveGuard" feature of HP laptops. On such systems the driver
+	  should load automatically (via ACPI alias).
+
+	  Support for a led indicating disk protection will be provided as
+	  hp::hddprotect. For more information on the feature, refer to
+	  Documentation/misc-devices/lis3lv02d.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called hp_accel.
+
+config HP_WIRELESS
+	tristate "HP wireless button"
+	depends on ACPI
+	depends on INPUT
+	help
+	 This driver provides supports for new HP wireless button for Windows 8.
+	 On such systems the driver should load automatically (via ACPI alias).
+
+	 To compile this driver as a module, choose M here: the module will
+	 be called hp-wireless.
+
+config HP_WMI
+	tristate "HP WMI extras"
+	depends on ACPI_WMI
+	depends on INPUT
+	depends on RFKILL || RFKILL = n
+	select INPUT_SPARSEKMAP
+	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
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on RFKILL
+	depends on INPUT && SERIO_I8042
+	select INPUT_SPARSEKMAP
+	---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
+	select INPUT_SPARSEKMAP
+	---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 (and others) Laptop Extras"
+	depends on ACPI
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on RFKILL
+	depends on HWMON
+	depends on POWER_SUPPLY
+	---help---
+	  This is a driver for laptops built by Compal, and some models by
+	  other brands (e.g. Dell, Toshiba).
+
+	  It adds support for rfkill, Bluetooth, WLAN, LCD brightness, hwmon
+	  and battery charging level control.
+
+	  For a (possibly incomplete) list of supported laptops, please refer
+	  to: Documentation/platform/x86-laptop-drivers.txt
+
+config SONY_LAPTOP
+	tristate "Sony Laptop Extras"
+	depends on ACPI
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on INPUT
+	depends on RFKILL
+	  ---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 IDEAPAD_LAPTOP
+	tristate "Lenovo IdeaPad Laptop Extras"
+	depends on ACPI
+	depends on RFKILL && INPUT
+	depends on SERIO_I8042
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on ACPI_WMI || ACPI_WMI = n
+	select INPUT_SPARSEKMAP
+	help
+	  This is a driver for Lenovo IdeaPad netbooks contains drivers for
+	  rfkill switch, hotkey, fan control and backlight control.
+
+config THINKPAD_ACPI
+	tristate "ThinkPad ACPI Laptop Extras"
+	depends on ACPI
+	depends on INPUT
+	depends on RFKILL || RFKILL = n
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on BACKLIGHT_CLASS_DEVICE
+	select HWMON
+	select NVRAM
+	select NEW_LEDS
+	select LEDS_CLASS
+	---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.
+
+	  Extra functionality will be available if the rfkill (CONFIG_RFKILL)
+	  and/or ALSA (CONFIG_SND) subsystems are available in the kernel.
+	  Note that if you want ThinkPad-ACPI to be built-in instead of
+	  modular, ALSA and rfkill will also have to be built-in.
+
+	  If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
+
+config THINKPAD_ACPI_ALSA_SUPPORT
+	bool "Console audio control ALSA interface"
+	depends on THINKPAD_ACPI
+	depends on SND
+	depends on SND = y || THINKPAD_ACPI = SND
+	default y
+	---help---
+	  Enables monitoring of the built-in console audio output control
+	  (headphone and speakers), which is operated by the mute and (in
+	  some ThinkPad models) volume hotkeys.
+
+	  If this option is enabled, ThinkPad-ACPI will export an ALSA card
+	  with a single read-only mixer control, which should be used for
+	  on-screen-display feedback purposes by the Desktop Environment.
+
+	  Optionally, the driver will also allow software control (the
+	  ALSA mixer will be made read-write).  Please refer to the driver
+	  documentation for details.
+
+	  All IBM models have both volume and mute control.  Newer Lenovo
+	  models only have mute control (the volume hotkeys are just normal
+	  keys and volume control is done through the main HDA mixer).
+
+config THINKPAD_ACPI_DEBUGFACILITIES
+	bool "Maintainer debug facilities"
+	depends on THINKPAD_ACPI
+	default n
+	---help---
+	  Enables extra stuff in the thinkpad-acpi which is completely useless
+	  for normal use.  Read the driver source to find out what it does.
+
+	  Say N here, unless you were told by a kernel maintainer to do
+	  otherwise.
+
+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_UNSAFE_LEDS
+	bool "Allow control of important LEDs (unsafe)"
+	depends on THINKPAD_ACPI
+	default n
+	---help---
+	  Overriding LED state on ThinkPads can mask important
+	  firmware alerts (like critical battery condition), or misled
+	  the user into damaging the hardware (undocking or ejecting
+	  the bay while buses are still active), etc.
+
+	  LED control on the ThinkPad is write-only (with very few
+	  exceptions on very ancient models), which makes it
+	  impossible to know beforehand if important information will
+	  be lost when one changes LED state.
+
+	  Users that know what they are doing can enable this option
+	  and the driver will allow control of every LED, including
+	  the ones on the dock stations.
+
+	  Never enable this option on a distribution kernel.
+
+	  Say N here, unless you are building a kernel for your own
+	  use, and need to control the important firmware LEDs.
+
+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 memory and avoid
+	  bad interactions with X.org.
+
+	  NOTE: access to this feature is limited to processes with the
+	  CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
+	  where it interacts badly with X.org.
+
+	  If you are not sure, say Y here but do try to check if you could
+	  be using X.org RandR instead.
+
+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 SENSORS_HDAPS
+	tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
+	depends on INPUT && X86
+	select INPUT_POLLDEV
+	default n
+	help
+	  This driver provides support for the IBM Hard Drive Active Protection
+	  System (hdaps), which provides an accelerometer and other misc. data.
+	  ThinkPads starting with the R50, T41, and X40 are supported.  The
+	  accelerometer data is readable via sysfs.
+
+	  This driver also provides an absolute input class device, allowing
+	  the laptop to act as a pinball machine-esque joystick.
+
+	  If your ThinkPad is not recognized by the driver, please update to latest
+	  BIOS. This is especially the case for some R52 ThinkPads.
+
+	  Say Y here if you have an applicable laptop and want to experience
+	  the awesome power of hdaps.
+
+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"
+	depends on ACPI
+	depends on INPUT
+	depends on RFKILL || RFKILL = n
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on HOTPLUG_PCI
+	depends on BACKLIGHT_CLASS_DEVICE
+	select HWMON
+	select LEDS_CLASS
+	select NEW_LEDS
+	select INPUT_SPARSEKMAP
+	---help---
+	  This driver supports the Fn-Fx keys on Eee PC laptops.
+
+	  It  also gives access to some extra laptop functionalities like
+	  Bluetooth, backlight and allows powering on/off some other
+	  devices.
+
+	  If you have an Eee PC laptop, say Y or M here. If this driver
+	  doesn't work on your Eee PC, try eeepc-wmi instead.
+
+config ASUS_WMI
+	tristate "ASUS WMI Driver"
+	depends on ACPI_WMI
+	depends on INPUT
+	depends on HWMON
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on RFKILL || RFKILL = n
+	depends on HOTPLUG_PCI
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	select INPUT_SPARSEKMAP
+	select LEDS_CLASS
+	select NEW_LEDS
+	---help---
+	  Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new
+	  Asus Notebooks).
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called asus-wmi.
+
+config ASUS_NB_WMI
+	tristate "Asus Notebook WMI Driver"
+	depends on ASUS_WMI
+	---help---
+	  This is a driver for newer Asus notebooks. It adds extra features
+	  like wireless radio and bluetooth control, leds, hotkeys, backlight...
+
+	  For more information, see
+	  <file:Documentation/ABI/testing/sysfs-platform-asus-wmi>
+
+	  If you have an ACPI-WMI compatible Asus Notebook, say Y or M
+	  here.
+
+config EEEPC_WMI
+	tristate "Eee PC WMI Driver"
+	depends on ASUS_WMI
+	---help---
+	  This is a driver for newer Eee PC laptops. It adds extra features
+	  like wireless radio and bluetooth control, leds, hotkeys, backlight...
+
+	  For more information, see
+	  <file:Documentation/ABI/testing/sysfs-platform-asus-wmi>
+
+	  If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M
+	  here.
+
+config ACPI_WMI
+	tristate "WMI"
+	depends on ACPI
+	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 MSI_WMI
+	tristate "MSI WMI extras"
+	depends on ACPI_WMI
+	depends on INPUT
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	select INPUT_SPARSEKMAP
+	help
+	 Say Y here if you want to support WMI-based hotkeys on MSI laptops.
+
+	 To compile this driver as a module, choose M here: the module will
+	 be called msi-wmi.
+
+config TOPSTAR_LAPTOP
+	tristate "Topstar Laptop Extras"
+	depends on ACPI
+	depends on INPUT
+	select INPUT_SPARSEKMAP
+	---help---
+	  This driver adds support for hotkeys found on Topstar laptops.
+
+	  If you have a Topstar laptop, say Y or M here.
+
+config ACPI_TOSHIBA
+	tristate "Toshiba Laptop Extras"
+	depends on ACPI
+	depends on ACPI_WMI
+	select LEDS_CLASS
+	select NEW_LEDS
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on INPUT
+	depends on SERIO_I8042 || SERIO_I8042 = n
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	select INPUT_POLLDEV
+	select INPUT_SPARSEKMAP
+	---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.
+
+config TOSHIBA_BT_RFKILL
+	tristate "Toshiba Bluetooth RFKill switch support"
+	depends on ACPI
+	depends on RFKILL || RFKILL = n
+	---help---
+	  This driver adds support for Bluetooth events for the RFKill
+	  switch on modern Toshiba laptops with full ACPI support and
+	  an RFKill switch.
+
+	  This driver handles RFKill events for the TOS6205 Bluetooth,
+	  and re-enables it when the switch is set back to the 'on'
+	  position.
+
+	  If you have a modern Toshiba laptop with a Bluetooth and an
+	  RFKill switch (such as the Portege R500), say Y.
+
+config TOSHIBA_HAPS
+	tristate "Toshiba HDD Active Protection Sensor"
+	depends on ACPI
+	---help---
+	  This driver adds support for the built-in accelerometer
+	  found on recent Toshiba laptops equipped with HID TOS620A
+	  device.
+
+	  This driver receives ACPI notify events 0x80 when the sensor
+	  detects a sudden move or a harsh vibration, as well as an
+	  ACPI notify event 0x81 whenever the movement or vibration has
+	  been stabilized.
+
+	  Also provides sysfs entries to get/set the desired protection
+	  level and resetting the HDD protection interface.
+
+	  If you have a recent Toshiba laptop with a built-in accelerometer
+	  device, say Y.
+
+config TOSHIBA_WMI
+	tristate "Toshiba WMI Hotkeys Driver (EXPERIMENTAL)"
+	default n
+	depends on ACPI_WMI
+	depends on INPUT
+	select INPUT_SPARSEKMAP
+	---help---
+	  This driver adds hotkey monitoring support to some Toshiba models
+	  that manage the hotkeys via WMI events.
+
+	  WARNING: This driver is incomplete as it lacks a proper keymap and the
+	  *notify function only prints the ACPI event type value. Be warned that
+	  you will need to provide some information if you have a Toshiba model
+	  with WMI event hotkeys and want to help with the develpment of this
+	  driver.
+
+	  If you have a WMI-based hotkeys Toshiba laptop, say Y or M here.
+
+config ACPI_CMPC
+	tristate "CMPC Laptop Extras"
+	depends on X86 && ACPI
+	depends on RFKILL || RFKILL=n
+	select INPUT
+	select BACKLIGHT_CLASS_DEVICE
+	default n
+	help
+	  Support for Intel Classmate PC ACPI devices, including some
+	  keys as input device, backlight device, tablet and accelerometer
+	  devices.
+
+config INTEL_SCU_IPC
+	bool "Intel SCU IPC Support"
+	depends on X86_INTEL_MID
+	default y
+	---help---
+	  IPC is used to bridge the communications between kernel and SCU on
+	  some embedded Intel x86 platforms. This is not needed for PC-type
+	  machines.
+
+config INTEL_SCU_IPC_UTIL
+	tristate "Intel SCU IPC utility driver"
+	depends on INTEL_SCU_IPC
+	default y
+	---help---
+	  The IPC Util driver provides an interface with the SCU enabling
+	  low level access for debug work and updating the firmware. Say
+	  N unless you will be doing this on an Intel MID platform.
+
+config GPIO_INTEL_PMIC
+	bool "Intel PMIC GPIO support"
+	depends on INTEL_SCU_IPC && GPIOLIB
+	---help---
+	  Say Y here to support GPIO via the SCU IPC interface
+	  on Intel MID platforms.
+
+config INTEL_MID_POWER_BUTTON
+	tristate "power button driver for Intel MID platforms"
+	depends on INTEL_SCU_IPC && INPUT
+	help
+	  This driver handles the power button on the Intel MID platforms.
+
+	  If unsure, say N.
+
+config INTEL_MFLD_THERMAL
+       tristate "Thermal driver for Intel Medfield platform"
+       depends on MFD_INTEL_MSIC && THERMAL
+       help
+         Say Y here to enable thermal driver support for the  Intel Medfield
+         platform.
+
+config INTEL_IPS
+	tristate "Intel Intelligent Power Sharing"
+	depends on ACPI
+	---help---
+	  Intel Calpella platforms support dynamic power sharing between the
+	  CPU and GPU, maximizing performance in a given TDP.  This driver,
+	  along with the CPU frequency and i915 drivers, provides that
+	  functionality.  If in doubt, say Y here; it will only load on
+	  supported platforms.
+
+config INTEL_IMR
+	bool "Intel Isolated Memory Region support"
+	default n
+	depends on X86_INTEL_QUARK && IOSF_MBI
+	---help---
+	  This option provides a means to manipulate Isolated Memory Regions.
+	  IMRs are a set of registers that define read and write access masks
+	  to prohibit certain system agents from accessing memory with 1 KiB
+	  granularity.
+
+	  IMRs make it possible to control read/write access to an address
+	  by hardware agents inside the SoC. Read and write masks can be
+	  defined for:
+		- eSRAM flush
+		- Dirty CPU snoop (write only)
+		- RMU access
+		- PCI Virtual Channel 0/Virtual Channel 1
+		- SMM mode
+		- Non SMM mode
+
+	  Quark contains a set of eight IMR registers and makes use of those
+	  registers during its bootup process.
+
+	  If you are running on a Galileo/Quark say Y here.
+
+config IBM_RTL
+	tristate "Device driver to enable PRTL support"
+	depends on X86 && PCI
+	---help---
+	 Enable support for IBM Premium Real Time Mode (PRTM).
+	 This module will allow you the enter and exit PRTM in the BIOS via
+	 sysfs on platforms that support this feature.  System in PRTM will
+	 not receive CPU-generated SMIs for recoverable errors.  Use of this
+	 feature without proper support may void your hardware warranty.
+
+	 If the proper BIOS support is found the driver will load and create
+	 /sys/devices/system/ibm_rtl/.  The "state" variable will indicate
+	 whether or not the BIOS is in PRTM.
+	 state = 0 (BIOS SMIs on)
+	 state = 1 (BIOS SMIs off)
+
+config XO1_RFKILL
+	tristate "OLPC XO-1 software RF kill switch"
+	depends on OLPC || COMPILE_TEST
+	depends on RFKILL
+	---help---
+	  Support for enabling/disabling the WLAN interface on the OLPC XO-1
+	  laptop.
+
+config XO15_EBOOK
+	tristate "OLPC XO-1.5 ebook switch"
+	depends on OLPC || COMPILE_TEST
+	depends on ACPI && INPUT
+	---help---
+	  Support for the ebook switch on the OLPC XO-1.5 laptop.
+
+	  This switch is triggered as the screen is rotated and folded down to
+	  convert the device into ebook form.
+
+config SAMSUNG_LAPTOP
+	tristate "Samsung Laptop driver"
+	depends on X86
+	depends on RFKILL || RFKILL = n
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on BACKLIGHT_CLASS_DEVICE
+	select LEDS_CLASS
+	select NEW_LEDS
+	---help---
+	  This module implements a driver for a wide range of different
+	  Samsung laptops.  It offers control over the different
+	  function keys, wireless LED, LCD backlight level.
+
+	  It may also provide some sysfs files described in
+	  <file:Documentation/ABI/testing/sysfs-platform-samsung-laptop>
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called samsung-laptop.
+
+config MXM_WMI
+       tristate "WMI support for MXM Laptop Graphics"
+       depends on ACPI_WMI
+       ---help---
+          MXM is a standard for laptop graphics cards, the WMI interface
+	  is required for switchable nvidia graphics machines
+
+config INTEL_OAKTRAIL
+	tristate "Intel Oaktrail Platform Extras"
+	depends on ACPI
+	depends on ACPI_VIDEO || ACPI_VIDEO = n
+	depends on RFKILL && BACKLIGHT_CLASS_DEVICE && ACPI
+	---help---
+	  Intel Oaktrail platform need this driver to provide interfaces to
+	  enable/disable the Camera, WiFi, BT etc. devices. If in doubt, say Y
+	  here; it will only load on supported platforms.
+
+config SAMSUNG_Q10
+	tristate "Samsung Q10 Extras"
+	depends on ACPI
+	select BACKLIGHT_CLASS_DEVICE
+	---help---
+	  This driver provides support for backlight control on Samsung Q10
+	  and related laptops, including Dell Latitude X200.
+
+config APPLE_GMUX
+	tristate "Apple Gmux Driver"
+	depends on ACPI
+	depends on PNP
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on BACKLIGHT_APPLE=n || BACKLIGHT_APPLE
+	depends on ACPI_VIDEO=n || ACPI_VIDEO
+	---help---
+	  This driver provides support for the gmux device found on many
+	  Apple laptops, which controls the display mux for the hybrid
+	  graphics as well as the backlight. Currently only backlight
+	  control is supported by the driver.
+
+config INTEL_RST
+        tristate "Intel Rapid Start Technology Driver"
+	depends on ACPI
+	---help---
+	  This driver provides support for modifying paramaters on systems
+	  equipped with Intel's Rapid Start Technology. When put in an ACPI
+	  sleep state, these devices will wake after either a configured
+	  timeout or when the system battery reaches a critical state,
+	  automatically copying memory contents to disk. On resume, the
+	  firmware will copy the memory contents back to RAM and resume the OS
+	  as usual.
+
+config INTEL_SMARTCONNECT
+        tristate "Intel Smart Connect disabling driver"
+	depends on ACPI
+	---help---
+	  Intel Smart Connect is a technology intended to permit devices to
+	  update state by resuming for a short period of time at regular
+	  intervals. If a user enables this functionality under Windows and
+	  then reboots into Linux, the system may remain configured to resume
+	  on suspend. In the absence of any userspace to support it, the system
+	  will then remain awake until something triggers another suspend.
+
+	  This driver checks to determine whether the device has Intel Smart
+	  Connect enabled, and if so disables it.
+
+config PVPANIC
+	tristate "pvpanic device support"
+	depends on ACPI
+	---help---
+	  This driver provides support for the pvpanic device.  pvpanic is
+	  a paravirtualized device provided by QEMU; it lets a virtual machine
+	  (guest) communicate panic events to the host.
+
+config INTEL_PMC_IPC
+	tristate "Intel PMC IPC Driver"
+	depends on ACPI
+	---help---
+	This driver provides support for PMC control on some Intel platforms.
+	The PMC is an ARC processor which defines IPC commands for communication
+	with other entities in the CPU.
+
+config SURFACE_PRO3_BUTTON
+	tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3 tablet"
+	depends on ACPI && INPUT
+	---help---
+	  This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3 tablet.
+endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
new file mode 100644
index 0000000..3ca78a3
--- /dev/null
+++ b/drivers/platform/x86/Makefile
@@ -0,0 +1,64 @@
+#
+# Makefile for linux/drivers/platform/x86
+# x86 Platform-Specific Drivers
+#
+obj-$(CONFIG_ASUS_LAPTOP)	+= asus-laptop.o
+obj-$(CONFIG_ASUS_WMI)		+= asus-wmi.o
+obj-$(CONFIG_ASUS_NB_WMI)	+= asus-nb-wmi.o
+obj-$(CONFIG_EEEPC_LAPTOP)	+= eeepc-laptop.o
+obj-$(CONFIG_EEEPC_WMI)		+= eeepc-wmi.o
+obj-$(CONFIG_MSI_LAPTOP)	+= msi-laptop.o
+obj-$(CONFIG_ACPI_CMPC)		+= classmate-laptop.o
+obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
+obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
+obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
+obj-$(CONFIG_DELL_WMI_AIO)	+= dell-wmi-aio.o
+obj-$(CONFIG_DELL_SMO8800)	+= dell-smo8800.o
+obj-$(CONFIG_DELL_RBTN)		+= dell-rbtn.o
+obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
+obj-$(CONFIG_HP_ACCEL)		+= hp_accel.o
+obj-$(CONFIG_HP_WIRELESS)	+= hp-wireless.o
+obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
+obj-$(CONFIG_AMILO_RFKILL)	+= amilo-rfkill.o
+obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
+obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
+obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
+obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
+obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
+obj-$(CONFIG_FUJITSU_LAPTOP)	+= fujitsu-laptop.o
+obj-$(CONFIG_FUJITSU_TABLET)	+= fujitsu-tablet.o
+obj-$(CONFIG_PANASONIC_LAPTOP)	+= panasonic-laptop.o
+obj-$(CONFIG_INTEL_MENLOW)	+= intel_menlow.o
+obj-$(CONFIG_ACPI_WMI)		+= wmi.o
+obj-$(CONFIG_MSI_WMI)		+= msi-wmi.o
+obj-$(CONFIG_TOPSTAR_LAPTOP)	+= topstar-laptop.o
+
+# toshiba_acpi must link after wmi to ensure that wmi devices are found
+# before toshiba_acpi initializes
+obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
+
+obj-$(CONFIG_TOSHIBA_BT_RFKILL)	+= toshiba_bluetooth.o
+obj-$(CONFIG_TOSHIBA_HAPS)	+= toshiba_haps.o
+obj-$(CONFIG_TOSHIBA_WMI)	+= toshiba-wmi.o
+obj-$(CONFIG_INTEL_SCU_IPC)	+= intel_scu_ipc.o
+obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
+obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
+obj-$(CONFIG_INTEL_IPS)		+= intel_ips.o
+obj-$(CONFIG_GPIO_INTEL_PMIC)	+= intel_pmic_gpio.o
+obj-$(CONFIG_XO1_RFKILL)	+= xo1-rfkill.o
+obj-$(CONFIG_XO15_EBOOK)	+= xo15-ebook.o
+obj-$(CONFIG_IBM_RTL)		+= ibm_rtl.o
+obj-$(CONFIG_SAMSUNG_LAPTOP)	+= samsung-laptop.o
+obj-$(CONFIG_MXM_WMI)		+= mxm-wmi.o
+obj-$(CONFIG_INTEL_MID_POWER_BUTTON)	+= intel_mid_powerbtn.o
+obj-$(CONFIG_INTEL_OAKTRAIL)	+= intel_oaktrail.o
+obj-$(CONFIG_SAMSUNG_Q10)	+= samsung-q10.o
+obj-$(CONFIG_APPLE_GMUX)	+= apple-gmux.o
+obj-$(CONFIG_INTEL_RST)		+= intel-rst.o
+obj-$(CONFIG_INTEL_SMARTCONNECT)	+= intel-smartconnect.o
+
+obj-$(CONFIG_PVPANIC)           += pvpanic.o
+obj-$(CONFIG_ALIENWARE_WMI)	+= alienware-wmi.o
+obj-$(CONFIG_INTEL_PMC_IPC)	+= intel_pmc_ipc.o
+obj-$(CONFIG_SURFACE_PRO3_BUTTON)	+= surfacepro3_button.o
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
new file mode 100644
index 0000000..b2cdc1a
--- /dev/null
+++ b/drivers/platform/x86/acer-wmi.c
@@ -0,0 +1,2266 @@
+/*
+ *  Acer WMI Laptop Extras
+ *
+ *  Copyright (C) 2007-2009	Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ *  Based on acer_acpi:
+ *    Copyright (C) 2005-2007	E.M. Smith
+ *    Copyright (C) 2007-2008	Carlos Corbacho <cathectic@gmail.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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/dmi.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/i8042.h>
+#include <linux/rfkill.h>
+#include <linux/workqueue.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <acpi/video.h>
+
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Magic Number
+ * Meaning is unknown - this number is required for writing to ACPI for AMW0
+ * (it's also used in acerhk when directly accessing the BIOS)
+ */
+#define ACER_AMW0_WRITE	0x9610
+
+/*
+ * Bit masks for the AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK  0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK   0x31
+
+/*
+ * Method IDs for WMID interface
+ */
+#define ACER_WMID_GET_WIRELESS_METHODID		1
+#define ACER_WMID_GET_BLUETOOTH_METHODID	2
+#define ACER_WMID_GET_BRIGHTNESS_METHODID	3
+#define ACER_WMID_SET_WIRELESS_METHODID		4
+#define ACER_WMID_SET_BLUETOOTH_METHODID	5
+#define ACER_WMID_SET_BRIGHTNESS_METHODID	6
+#define ACER_WMID_GET_THREEG_METHODID		10
+#define ACER_WMID_SET_THREEG_METHODID		11
+
+/*
+ * Acer ACPI method GUIDs
+ */
+#define AMW0_GUID1		"67C3371D-95A3-4C37-BB61-DD47B491DAAB"
+#define AMW0_GUID2		"431F16ED-0C2B-444C-B267-27DEB140CF9C"
+#define WMID_GUID1		"6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3"
+#define WMID_GUID2		"95764E09-FB56-4E83-B31A-37761F60994A"
+#define WMID_GUID3		"61EF69EA-865C-4BC3-A502-A0DEBA0CB531"
+
+/*
+ * Acer ACPI event GUIDs
+ */
+#define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026"
+
+MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB");
+MODULE_ALIAS("wmi:6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3");
+MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026");
+
+enum acer_wmi_event_ids {
+	WMID_HOTKEY_EVENT = 0x1,
+	WMID_ACCEL_EVENT = 0x5,
+};
+
+static const struct key_entry acer_wmi_keymap[] __initconst = {
+	{KE_KEY, 0x01, {KEY_WLAN} },     /* WiFi */
+	{KE_KEY, 0x03, {KEY_WLAN} },     /* WiFi */
+	{KE_KEY, 0x04, {KEY_WLAN} },     /* WiFi */
+	{KE_KEY, 0x12, {KEY_BLUETOOTH} },	/* BT */
+	{KE_KEY, 0x21, {KEY_PROG1} },    /* Backup */
+	{KE_KEY, 0x22, {KEY_PROG2} },    /* Arcade */
+	{KE_KEY, 0x23, {KEY_PROG3} },    /* P_Key */
+	{KE_KEY, 0x24, {KEY_PROG4} },    /* Social networking_Key */
+	{KE_KEY, 0x29, {KEY_PROG3} },    /* P_Key for TM8372 */
+	{KE_IGNORE, 0x41, {KEY_MUTE} },
+	{KE_IGNORE, 0x42, {KEY_PREVIOUSSONG} },
+	{KE_IGNORE, 0x4d, {KEY_PREVIOUSSONG} },
+	{KE_IGNORE, 0x43, {KEY_NEXTSONG} },
+	{KE_IGNORE, 0x4e, {KEY_NEXTSONG} },
+	{KE_IGNORE, 0x44, {KEY_PLAYPAUSE} },
+	{KE_IGNORE, 0x4f, {KEY_PLAYPAUSE} },
+	{KE_IGNORE, 0x45, {KEY_STOP} },
+	{KE_IGNORE, 0x50, {KEY_STOP} },
+	{KE_IGNORE, 0x48, {KEY_VOLUMEUP} },
+	{KE_IGNORE, 0x49, {KEY_VOLUMEDOWN} },
+	{KE_IGNORE, 0x4a, {KEY_VOLUMEDOWN} },
+	{KE_IGNORE, 0x61, {KEY_SWITCHVIDEOMODE} },
+	{KE_IGNORE, 0x62, {KEY_BRIGHTNESSUP} },
+	{KE_IGNORE, 0x63, {KEY_BRIGHTNESSDOWN} },
+	{KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} },	/* Display Switch */
+	{KE_IGNORE, 0x81, {KEY_SLEEP} },
+	{KE_KEY, 0x82, {KEY_TOUCHPAD_TOGGLE} },	/* Touch Pad Toggle */
+	{KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} },
+	{KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },
+	{KE_IGNORE, 0x83, {KEY_TOUCHPAD_TOGGLE} },
+	{KE_KEY, 0x85, {KEY_TOUCHPAD_TOGGLE} },
+	{KE_END, 0}
+};
+
+static struct input_dev *acer_wmi_input_dev;
+static struct input_dev *acer_wmi_accel_dev;
+
+struct event_return_value {
+	u8 function;
+	u8 key_num;
+	u16 device_state;
+	u32 reserved;
+} __attribute__((packed));
+
+/*
+ * GUID3 Get Device Status device flags
+ */
+#define ACER_WMID3_GDS_WIRELESS		(1<<0)	/* WiFi */
+#define ACER_WMID3_GDS_THREEG		(1<<6)	/* 3G */
+#define ACER_WMID3_GDS_WIMAX		(1<<7)	/* WiMAX */
+#define ACER_WMID3_GDS_BLUETOOTH	(1<<11)	/* BT */
+#define ACER_WMID3_GDS_TOUCHPAD		(1<<1)	/* Touchpad */
+
+struct lm_input_params {
+	u8 function_num;        /* Function Number */
+	u16 commun_devices;     /* Communication type devices default status */
+	u16 devices;            /* Other type devices default status */
+	u8 lm_status;           /* Launch Manager Status */
+	u16 reserved;
+} __attribute__((packed));
+
+struct lm_return_value {
+	u8 error_code;          /* Error Code */
+	u8 ec_return_value;     /* EC Return Value */
+	u16 reserved;
+} __attribute__((packed));
+
+struct wmid3_gds_set_input_param {     /* Set Device Status input parameter */
+	u8 function_num;        /* Function Number */
+	u8 hotkey_number;       /* Hotkey Number */
+	u16 devices;            /* Set Device */
+	u8 volume_value;        /* Volume Value */
+} __attribute__((packed));
+
+struct wmid3_gds_get_input_param {     /* Get Device Status input parameter */
+	u8 function_num;	/* Function Number */
+	u8 hotkey_number;	/* Hotkey Number */
+	u16 devices;		/* Get Device */
+} __attribute__((packed));
+
+struct wmid3_gds_return_value {	/* Get Device Status return value*/
+	u8 error_code;		/* Error Code */
+	u8 ec_return_value;	/* EC Return Value */
+	u16 devices;		/* Current Device Status */
+	u32 reserved;
+} __attribute__((packed));
+
+struct hotkey_function_type_aa {
+	u8 type;
+	u8 length;
+	u16 handle;
+	u16 commun_func_bitmap;
+	u16 application_func_bitmap;
+	u16 media_func_bitmap;
+	u16 display_func_bitmap;
+	u16 others_func_bitmap;
+	u8 commun_fn_key_number;
+} __attribute__((packed));
+
+/*
+ * Interface capability flags
+ */
+#define ACER_CAP_MAILLED		(1<<0)
+#define ACER_CAP_WIRELESS		(1<<1)
+#define ACER_CAP_BLUETOOTH		(1<<2)
+#define ACER_CAP_BRIGHTNESS		(1<<3)
+#define ACER_CAP_THREEG			(1<<4)
+#define ACER_CAP_ACCEL			(1<<5)
+#define ACER_CAP_ANY			(0xFFFFFFFF)
+
+/*
+ * Interface type flags
+ */
+enum interface_flags {
+	ACER_AMW0,
+	ACER_AMW0_V2,
+	ACER_WMID,
+	ACER_WMID_v2,
+};
+
+#define ACER_DEFAULT_WIRELESS  0
+#define ACER_DEFAULT_BLUETOOTH 0
+#define ACER_DEFAULT_MAILLED   0
+#define ACER_DEFAULT_THREEG    0
+
+static int max_brightness = 0xF;
+
+static int mailled = -1;
+static int brightness = -1;
+static int threeg = -1;
+static int force_series;
+static bool ec_raw_mode;
+static bool has_type_aa;
+static u16 commun_func_bitmap;
+static u8 commun_fn_key_number;
+
+module_param(mailled, int, 0444);
+module_param(brightness, int, 0444);
+module_param(threeg, int, 0444);
+module_param(force_series, int, 0444);
+module_param(ec_raw_mode, bool, 0444);
+MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
+MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
+MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
+MODULE_PARM_DESC(force_series, "Force a different laptop series");
+MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode");
+
+struct acer_data {
+	int mailled;
+	int threeg;
+	int brightness;
+};
+
+struct acer_debug {
+	struct dentry *root;
+	struct dentry *devices;
+	u32 wmid_devices;
+};
+
+static struct rfkill *wireless_rfkill;
+static struct rfkill *bluetooth_rfkill;
+static struct rfkill *threeg_rfkill;
+static bool rfkill_inited;
+
+/* Each low-level interface must define at least some of the following */
+struct wmi_interface {
+	/* The WMI device type */
+	u32 type;
+
+	/* The capabilities this interface provides */
+	u32 capability;
+
+	/* Private data for the current interface */
+	struct acer_data data;
+
+	/* debugfs entries associated with this interface */
+	struct acer_debug debug;
+};
+
+/* The static interface pointer, points to the currently detected interface */
+static struct wmi_interface *interface;
+
+/*
+ * Embedded Controller quirks
+ * Some laptops require us to directly access the EC to either enable or query
+ * features that are not available through WMI.
+ */
+
+struct quirk_entry {
+	u8 wireless;
+	u8 mailled;
+	s8 brightness;
+	u8 bluetooth;
+};
+
+static struct quirk_entry *quirks;
+
+static void __init set_quirks(void)
+{
+	if (!interface)
+		return;
+
+	if (quirks->mailled)
+		interface->capability |= ACER_CAP_MAILLED;
+
+	if (quirks->brightness)
+		interface->capability |= ACER_CAP_BRIGHTNESS;
+}
+
+static int __init dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 1;
+}
+
+static struct quirk_entry quirk_unknown = {
+};
+
+static struct quirk_entry quirk_acer_aspire_1520 = {
+	.brightness = -1,
+};
+
+static struct quirk_entry quirk_acer_travelmate_2490 = {
+	.mailled = 1,
+};
+
+/* This AMW0 laptop has no bluetooth */
+static struct quirk_entry quirk_medion_md_98300 = {
+	.wireless = 1,
+};
+
+static struct quirk_entry quirk_fujitsu_amilo_li_1718 = {
+	.wireless = 2,
+};
+
+static struct quirk_entry quirk_lenovo_ideapad_s205 = {
+	.wireless = 3,
+};
+
+/* The Aspire One has a dummy ACPI-WMI interface - disable it */
+static const struct dmi_system_id acer_blacklist[] __initconst = {
+	{
+		.ident = "Acer Aspire One (SSD)",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AOA110"),
+		},
+	},
+	{
+		.ident = "Acer Aspire One (HDD)",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AOA150"),
+		},
+	},
+	{}
+};
+
+static const struct dmi_system_id acer_quirks[] __initconst = {
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 1360",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1360"),
+		},
+		.driver_data = &quirk_acer_aspire_1520,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 1520",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1520"),
+		},
+		.driver_data = &quirk_acer_aspire_1520,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 3100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3100"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 3610",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3610"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5100"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5610",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5610"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5630",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5650",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5680",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 9110",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 9110"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer TravelMate 2490",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer TravelMate 4200",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4200"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Fujitsu Siemens Amilo Li 1718",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Li 1718"),
+		},
+		.driver_data = &quirk_fujitsu_amilo_li_1718,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Medion MD 98300",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "MEDION"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "WAM2030"),
+		},
+		.driver_data = &quirk_medion_md_98300,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Lenovo Ideapad S205",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "10382LG"),
+		},
+		.driver_data = &quirk_lenovo_ideapad_s205,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Lenovo Ideapad S205 (Brazos)",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Brazos"),
+		},
+		.driver_data = &quirk_lenovo_ideapad_s205,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Lenovo 3000 N200",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "0687A31"),
+		},
+		.driver_data = &quirk_fujitsu_amilo_li_1718,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Lenovo Ideapad S205-10382JG",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "10382JG"),
+		},
+		.driver_data = &quirk_lenovo_ideapad_s205,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Lenovo Ideapad S205-1038DPG",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "1038DPG"),
+		},
+		.driver_data = &quirk_lenovo_ideapad_s205,
+	},
+	{}
+};
+
+static int __init
+video_set_backlight_video_vendor(const struct dmi_system_id *d)
+{
+	interface->capability &= ~ACER_CAP_BRIGHTNESS;
+	pr_info("Brightness must be controlled by generic video driver\n");
+	return 0;
+}
+
+static const struct dmi_system_id video_vendor_dmi_table[] __initconst = {
+	{
+		.callback = video_set_backlight_video_vendor,
+		.ident = "Acer TravelMate 4750",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4750"),
+		},
+	},
+	{
+		.callback = video_set_backlight_video_vendor,
+		.ident = "Acer Extensa 5235",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Extensa 5235"),
+		},
+	},
+	{
+		.callback = video_set_backlight_video_vendor,
+		.ident = "Acer TravelMate 5760",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 5760"),
+		},
+	},
+	{
+		.callback = video_set_backlight_video_vendor,
+		.ident = "Acer Aspire 5750",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5750"),
+		},
+	},
+	{
+		.callback = video_set_backlight_video_vendor,
+		.ident = "Acer Aspire 5741",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5741"),
+		},
+	},
+	{
+		/*
+		 * Note no video_set_backlight_video_vendor, we must use the
+		 * acer interface, as there is no native backlight interface.
+		 */
+		.ident = "Acer KAV80",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "KAV80"),
+		},
+	},
+	{}
+};
+
+/* Find which quirks are needed for a particular vendor/ model pair */
+static void __init find_quirks(void)
+{
+	if (!force_series) {
+		dmi_check_system(acer_quirks);
+	} else if (force_series == 2490) {
+		quirks = &quirk_acer_travelmate_2490;
+	}
+
+	if (quirks == NULL)
+		quirks = &quirk_unknown;
+
+	set_quirks();
+}
+
+/*
+ * General interface convenience methods
+ */
+
+static bool has_cap(u32 cap)
+{
+	if ((interface->capability & cap) != 0)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * AMW0 (V1) interface
+ */
+struct wmab_args {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+};
+
+struct wmab_ret {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+	u32 eex;
+};
+
+static acpi_status wmab_execute(struct wmab_args *regbuf,
+struct acpi_buffer *result)
+{
+	struct acpi_buffer input;
+	acpi_status status;
+	input.length = sizeof(struct wmab_args);
+	input.pointer = (u8 *)regbuf;
+
+	status = wmi_evaluate_method(AMW0_GUID1, 1, 1, &input, result);
+
+	return status;
+}
+
+static acpi_status AMW0_get_u32(u32 *value, u32 cap)
+{
+	int err;
+	u8 result;
+
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		switch (quirks->mailled) {
+		default:
+			err = ec_read(0xA, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 7) & 0x1;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_WIRELESS:
+		switch (quirks->wireless) {
+		case 1:
+			err = ec_read(0x7B, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result & 0x1;
+			return AE_OK;
+		case 2:
+			err = ec_read(0x71, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result & 0x1;
+			return AE_OK;
+		case 3:
+			err = ec_read(0x78, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result & 0x1;
+			return AE_OK;
+		default:
+			err = ec_read(0xA, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 2) & 0x1;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_BLUETOOTH:
+		switch (quirks->bluetooth) {
+		default:
+			err = ec_read(0xA, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 4) & 0x1;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		switch (quirks->brightness) {
+		default:
+			err = ec_read(0x83, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result;
+			return AE_OK;
+		}
+		break;
+	default:
+		return AE_ERROR;
+	}
+	return AE_OK;
+}
+
+static acpi_status AMW0_set_u32(u32 value, u32 cap)
+{
+	struct wmab_args args;
+
+	args.eax = ACER_AMW0_WRITE;
+	args.ebx = value ? (1<<8) : 0;
+	args.ecx = args.edx = 0;
+
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_MAILLED_MASK;
+		break;
+	case ACER_CAP_WIRELESS:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_WIRELESS_MASK;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		if (value > max_brightness)
+			return AE_BAD_PARAMETER;
+		switch (quirks->brightness) {
+		default:
+			return ec_write(0x83, value);
+			break;
+		}
+	default:
+		return AE_ERROR;
+	}
+
+	/* Actually do the set */
+	return wmab_execute(&args, NULL);
+}
+
+static acpi_status __init AMW0_find_mailled(void)
+{
+	struct wmab_args args;
+	struct wmab_ret ret;
+	acpi_status status = AE_OK;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	args.eax = 0x86;
+	args.ebx = args.ecx = args.edx = 0;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		kfree(out.pointer);
+		return AE_ERROR;
+	}
+
+	if (ret.eex & 0x1)
+		interface->capability |= ACER_CAP_MAILLED;
+
+	kfree(out.pointer);
+
+	return AE_OK;
+}
+
+static int AMW0_set_cap_acpi_check_device_found __initdata;
+
+static acpi_status __init AMW0_set_cap_acpi_check_device_cb(acpi_handle handle,
+	u32 level, void *context, void **retval)
+{
+	AMW0_set_cap_acpi_check_device_found = 1;
+	return AE_OK;
+}
+
+static const struct acpi_device_id norfkill_ids[] __initconst = {
+	{ "VPC2004", 0},
+	{ "IBM0068", 0},
+	{ "LEN0068", 0},
+	{ "SNY5001", 0},	/* sony-laptop in charge */
+	{ "HPQ6601", 0},
+	{ "", 0},
+};
+
+static int __init AMW0_set_cap_acpi_check_device(void)
+{
+	const struct acpi_device_id *id;
+
+	for (id = norfkill_ids; id->id[0]; id++)
+		acpi_get_devices(id->id, AMW0_set_cap_acpi_check_device_cb,
+				NULL, NULL);
+	return AMW0_set_cap_acpi_check_device_found;
+}
+
+static acpi_status __init AMW0_set_capabilities(void)
+{
+	struct wmab_args args;
+	struct wmab_ret ret;
+	acpi_status status;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	/*
+	 * On laptops with this strange GUID (non Acer), normal probing doesn't
+	 * work.
+	 */
+	if (wmi_has_guid(AMW0_GUID2)) {
+		if ((quirks != &quirk_unknown) ||
+		    !AMW0_set_cap_acpi_check_device())
+			interface->capability |= ACER_CAP_WIRELESS;
+		return AE_OK;
+	}
+
+	args.eax = ACER_AMW0_WRITE;
+	args.ecx = args.edx = 0;
+
+	args.ebx = 0xa2 << 8;
+	args.ebx |= ACER_AMW0_WIRELESS_MASK;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		status = AE_ERROR;
+		goto out;
+	}
+
+	if (ret.eax & 0x1)
+		interface->capability |= ACER_CAP_WIRELESS;
+
+	args.ebx = 2 << 8;
+	args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+
+	/*
+	 * It's ok to use existing buffer for next wmab_execute call.
+	 * But we need to kfree(out.pointer) if next wmab_execute fail.
+	 */
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		goto out;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER
+	&& obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		status = AE_ERROR;
+		goto out;
+	}
+
+	if (ret.eax & 0x1)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	/*
+	 * This appears to be safe to enable, since all Wistron based laptops
+	 * appear to use the same EC register for brightness, even if they
+	 * differ for wireless, etc
+	 */
+	if (quirks->brightness >= 0)
+		interface->capability |= ACER_CAP_BRIGHTNESS;
+
+	status = AE_OK;
+out:
+	kfree(out.pointer);
+	return status;
+}
+
+static struct wmi_interface AMW0_interface = {
+	.type = ACER_AMW0,
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+	.type = ACER_AMW0_V2,
+};
+
+/*
+ * New interface (The WMID interface)
+ */
+static acpi_status
+WMI_execute_u32(u32 method_id, u32 in, u32 *out)
+{
+	struct acpi_buffer input = { (acpi_size) sizeof(u32), (void *)(&in) };
+	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	u32 tmp = 0;
+	acpi_status status;
+
+	status = wmi_evaluate_method(WMID_GUID1, 1, method_id, &input, &result);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) result.pointer;
+	if (obj) {
+		if (obj->type == ACPI_TYPE_BUFFER &&
+			(obj->buffer.length == sizeof(u32) ||
+			obj->buffer.length == sizeof(u64))) {
+			tmp = *((u32 *) obj->buffer.pointer);
+		} else if (obj->type == ACPI_TYPE_INTEGER) {
+			tmp = (u32) obj->integer.value;
+		}
+	}
+
+	if (out)
+		*out = tmp;
+
+	kfree(result.pointer);
+
+	return status;
+}
+
+static acpi_status WMID_get_u32(u32 *value, u32 cap)
+{
+	acpi_status status;
+	u8 tmp;
+	u32 result, method_id = 0;
+
+	switch (cap) {
+	case ACER_CAP_WIRELESS:
+		method_id = ACER_WMID_GET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		method_id = ACER_WMID_GET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		method_id = ACER_WMID_GET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		method_id = ACER_WMID_GET_THREEG_METHODID;
+		break;
+	case ACER_CAP_MAILLED:
+		if (quirks->mailled == 1) {
+			ec_read(0x9f, &tmp);
+			*value = tmp & 0x1;
+			return 0;
+		}
+	default:
+		return AE_ERROR;
+	}
+	status = WMI_execute_u32(method_id, 0, &result);
+
+	if (ACPI_SUCCESS(status))
+		*value = (u8)result;
+
+	return status;
+}
+
+static acpi_status WMID_set_u32(u32 value, u32 cap)
+{
+	u32 method_id = 0;
+	char param;
+
+	switch (cap) {
+	case ACER_CAP_BRIGHTNESS:
+		if (value > max_brightness)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_WIRELESS:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_THREEG_METHODID;
+		break;
+	case ACER_CAP_MAILLED:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		if (quirks->mailled == 1) {
+			param = value ? 0x92 : 0x93;
+			i8042_lock_chip();
+			i8042_command(&param, 0x1059);
+			i8042_unlock_chip();
+			return 0;
+		}
+		break;
+	default:
+		return AE_ERROR;
+	}
+	return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+static acpi_status wmid3_get_device_status(u32 *value, u16 device)
+{
+	struct wmid3_gds_return_value return_value;
+	acpi_status status;
+	union acpi_object *obj;
+	struct wmid3_gds_get_input_param params = {
+		.function_num = 0x1,
+		.hotkey_number = commun_fn_key_number,
+		.devices = device,
+	};
+	struct acpi_buffer input = {
+		sizeof(struct wmid3_gds_get_input_param),
+		&params
+	};
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input, &output);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = output.pointer;
+
+	if (!obj)
+		return AE_ERROR;
+	else if (obj->type != ACPI_TYPE_BUFFER) {
+		kfree(obj);
+		return AE_ERROR;
+	}
+	if (obj->buffer.length != 8) {
+		pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return AE_ERROR;
+	}
+
+	return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+	kfree(obj);
+
+	if (return_value.error_code || return_value.ec_return_value)
+		pr_warn("Get 0x%x Device Status failed: 0x%x - 0x%x\n",
+			device,
+			return_value.error_code,
+			return_value.ec_return_value);
+	else
+		*value = !!(return_value.devices & device);
+
+	return status;
+}
+
+static acpi_status wmid_v2_get_u32(u32 *value, u32 cap)
+{
+	u16 device;
+
+	switch (cap) {
+	case ACER_CAP_WIRELESS:
+		device = ACER_WMID3_GDS_WIRELESS;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		device = ACER_WMID3_GDS_BLUETOOTH;
+		break;
+	case ACER_CAP_THREEG:
+		device = ACER_WMID3_GDS_THREEG;
+		break;
+	default:
+		return AE_ERROR;
+	}
+	return wmid3_get_device_status(value, device);
+}
+
+static acpi_status wmid3_set_device_status(u32 value, u16 device)
+{
+	struct wmid3_gds_return_value return_value;
+	acpi_status status;
+	union acpi_object *obj;
+	u16 devices;
+	struct wmid3_gds_get_input_param get_params = {
+		.function_num = 0x1,
+		.hotkey_number = commun_fn_key_number,
+		.devices = commun_func_bitmap,
+	};
+	struct acpi_buffer get_input = {
+		sizeof(struct wmid3_gds_get_input_param),
+		&get_params
+	};
+	struct wmid3_gds_set_input_param set_params = {
+		.function_num = 0x2,
+		.hotkey_number = commun_fn_key_number,
+		.devices = commun_func_bitmap,
+	};
+	struct acpi_buffer set_input = {
+		sizeof(struct wmid3_gds_set_input_param),
+		&set_params
+	};
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer output2 = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &get_input, &output);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = output.pointer;
+
+	if (!obj)
+		return AE_ERROR;
+	else if (obj->type != ACPI_TYPE_BUFFER) {
+		kfree(obj);
+		return AE_ERROR;
+	}
+	if (obj->buffer.length != 8) {
+		pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return AE_ERROR;
+	}
+
+	return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+	kfree(obj);
+
+	if (return_value.error_code || return_value.ec_return_value) {
+		pr_warn("Get Current Device Status failed: 0x%x - 0x%x\n",
+			return_value.error_code,
+			return_value.ec_return_value);
+		return status;
+	}
+
+	devices = return_value.devices;
+	set_params.devices = (value) ? (devices | device) : (devices & ~device);
+
+	status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &set_input, &output2);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = output2.pointer;
+
+	if (!obj)
+		return AE_ERROR;
+	else if (obj->type != ACPI_TYPE_BUFFER) {
+		kfree(obj);
+		return AE_ERROR;
+	}
+	if (obj->buffer.length != 4) {
+		pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return AE_ERROR;
+	}
+
+	return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+	kfree(obj);
+
+	if (return_value.error_code || return_value.ec_return_value)
+		pr_warn("Set Device Status failed: 0x%x - 0x%x\n",
+			return_value.error_code,
+			return_value.ec_return_value);
+
+	return status;
+}
+
+static acpi_status wmid_v2_set_u32(u32 value, u32 cap)
+{
+	u16 device;
+
+	switch (cap) {
+	case ACER_CAP_WIRELESS:
+		device = ACER_WMID3_GDS_WIRELESS;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		device = ACER_WMID3_GDS_BLUETOOTH;
+		break;
+	case ACER_CAP_THREEG:
+		device = ACER_WMID3_GDS_THREEG;
+		break;
+	default:
+		return AE_ERROR;
+	}
+	return wmid3_set_device_status(value, device);
+}
+
+static void __init type_aa_dmi_decode(const struct dmi_header *header, void *d)
+{
+	struct hotkey_function_type_aa *type_aa;
+
+	/* We are looking for OEM-specific Type AAh */
+	if (header->type != 0xAA)
+		return;
+
+	has_type_aa = true;
+	type_aa = (struct hotkey_function_type_aa *) header;
+
+	pr_info("Function bitmap for Communication Button: 0x%x\n",
+		type_aa->commun_func_bitmap);
+	commun_func_bitmap = type_aa->commun_func_bitmap;
+
+	if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS)
+		interface->capability |= ACER_CAP_WIRELESS;
+	if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_THREEG)
+		interface->capability |= ACER_CAP_THREEG;
+	if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	commun_fn_key_number = type_aa->commun_fn_key_number;
+}
+
+static acpi_status __init WMID_set_capabilities(void)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	acpi_status status;
+	u32 devices;
+
+	status = wmi_query_block(WMID_GUID2, 1, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj) {
+		if (obj->type == ACPI_TYPE_BUFFER &&
+			(obj->buffer.length == sizeof(u32) ||
+			obj->buffer.length == sizeof(u64))) {
+			devices = *((u32 *) obj->buffer.pointer);
+		} else if (obj->type == ACPI_TYPE_INTEGER) {
+			devices = (u32) obj->integer.value;
+		} else {
+			kfree(out.pointer);
+			return AE_ERROR;
+		}
+	} else {
+		kfree(out.pointer);
+		return AE_ERROR;
+	}
+
+	pr_info("Function bitmap for Communication Device: 0x%x\n", devices);
+	if (devices & 0x07)
+		interface->capability |= ACER_CAP_WIRELESS;
+	if (devices & 0x40)
+		interface->capability |= ACER_CAP_THREEG;
+	if (devices & 0x10)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	if (!(devices & 0x20))
+		max_brightness = 0x9;
+
+	kfree(out.pointer);
+	return status;
+}
+
+static struct wmi_interface wmid_interface = {
+	.type = ACER_WMID,
+};
+
+static struct wmi_interface wmid_v2_interface = {
+	.type = ACER_WMID_v2,
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_u32(u32 *value, u32 cap)
+{
+	acpi_status status = AE_ERROR;
+
+	switch (interface->type) {
+	case ACER_AMW0:
+		status = AMW0_get_u32(value, cap);
+		break;
+	case ACER_AMW0_V2:
+		if (cap == ACER_CAP_MAILLED) {
+			status = AMW0_get_u32(value, cap);
+			break;
+		}
+	case ACER_WMID:
+		status = WMID_get_u32(value, cap);
+		break;
+	case ACER_WMID_v2:
+		if (cap & (ACER_CAP_WIRELESS |
+			   ACER_CAP_BLUETOOTH |
+			   ACER_CAP_THREEG))
+			status = wmid_v2_get_u32(value, cap);
+		else if (wmi_has_guid(WMID_GUID2))
+			status = WMID_get_u32(value, cap);
+		break;
+	}
+
+	return status;
+}
+
+static acpi_status set_u32(u32 value, u32 cap)
+{
+	acpi_status status;
+
+	if (interface->capability & cap) {
+		switch (interface->type) {
+		case ACER_AMW0:
+			return AMW0_set_u32(value, cap);
+		case ACER_AMW0_V2:
+			if (cap == ACER_CAP_MAILLED)
+				return AMW0_set_u32(value, cap);
+
+			/*
+			 * On some models, some WMID methods don't toggle
+			 * properly. For those cases, we want to run the AMW0
+			 * method afterwards to be certain we've really toggled
+			 * the device state.
+			 */
+			if (cap == ACER_CAP_WIRELESS ||
+				cap == ACER_CAP_BLUETOOTH) {
+				status = WMID_set_u32(value, cap);
+				if (ACPI_FAILURE(status))
+					return status;
+
+				return AMW0_set_u32(value, cap);
+			}
+		case ACER_WMID:
+			return WMID_set_u32(value, cap);
+		case ACER_WMID_v2:
+			if (cap & (ACER_CAP_WIRELESS |
+				   ACER_CAP_BLUETOOTH |
+				   ACER_CAP_THREEG))
+				return wmid_v2_set_u32(value, cap);
+			else if (wmi_has_guid(WMID_GUID2))
+				return WMID_set_u32(value, cap);
+		default:
+			return AE_BAD_PARAMETER;
+		}
+	}
+	return AE_BAD_PARAMETER;
+}
+
+static void __init acer_commandline_init(void)
+{
+	/*
+	 * These will all fail silently if the value given is invalid, or the
+	 * capability isn't available on the given interface
+	 */
+	if (mailled >= 0)
+		set_u32(mailled, ACER_CAP_MAILLED);
+	if (!has_type_aa && threeg >= 0)
+		set_u32(threeg, ACER_CAP_THREEG);
+	if (brightness >= 0)
+		set_u32(brightness, ACER_CAP_BRIGHTNESS);
+}
+
+/*
+ * LED device (Mail LED only, no other LEDs known yet)
+ */
+static void mail_led_set(struct led_classdev *led_cdev,
+enum led_brightness value)
+{
+	set_u32(value, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+	.name = "acer-wmi::mail",
+	.brightness_set = mail_led_set,
+};
+
+static int acer_led_init(struct device *dev)
+{
+	return led_classdev_register(dev, &mail_led);
+}
+
+static void acer_led_exit(void)
+{
+	set_u32(LED_OFF, ACER_CAP_MAILLED);
+	led_classdev_unregister(&mail_led);
+}
+
+/*
+ * Backlight device
+ */
+static struct backlight_device *acer_backlight_device;
+
+static int read_brightness(struct backlight_device *bd)
+{
+	u32 value;
+	get_u32(&value, ACER_CAP_BRIGHTNESS);
+	return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+	int intensity = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		intensity = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		intensity = 0;
+
+	set_u32(intensity, ACER_CAP_BRIGHTNESS);
+
+	return 0;
+}
+
+static const struct backlight_ops acer_bl_ops = {
+	.get_brightness = read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int acer_backlight_init(struct device *dev)
+{
+	struct backlight_properties props;
+	struct backlight_device *bd;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = max_brightness;
+	bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops,
+				       &props);
+	if (IS_ERR(bd)) {
+		pr_err("Could not register Acer backlight device\n");
+		acer_backlight_device = NULL;
+		return PTR_ERR(bd);
+	}
+
+	acer_backlight_device = bd;
+
+	bd->props.power = FB_BLANK_UNBLANK;
+	bd->props.brightness = read_brightness(bd);
+	backlight_update_status(bd);
+	return 0;
+}
+
+static void acer_backlight_exit(void)
+{
+	backlight_device_unregister(acer_backlight_device);
+}
+
+/*
+ * Accelerometer device
+ */
+static acpi_handle gsensor_handle;
+
+static int acer_gsensor_init(void)
+{
+	acpi_status status;
+	struct acpi_buffer output;
+	union acpi_object out_obj;
+
+	output.length = sizeof(out_obj);
+	output.pointer = &out_obj;
+	status = acpi_evaluate_object(gsensor_handle, "_INI", NULL, &output);
+	if (ACPI_FAILURE(status))
+		return -1;
+
+	return 0;
+}
+
+static int acer_gsensor_open(struct input_dev *input)
+{
+	return acer_gsensor_init();
+}
+
+static int acer_gsensor_event(void)
+{
+	acpi_status status;
+	struct acpi_buffer output;
+	union acpi_object out_obj[5];
+
+	if (!has_cap(ACER_CAP_ACCEL))
+		return -1;
+
+	output.length = sizeof(out_obj);
+	output.pointer = out_obj;
+
+	status = acpi_evaluate_object(gsensor_handle, "RDVL", NULL, &output);
+	if (ACPI_FAILURE(status))
+		return -1;
+
+	if (out_obj->package.count != 4)
+		return -1;
+
+	input_report_abs(acer_wmi_accel_dev, ABS_X,
+		(s16)out_obj->package.elements[0].integer.value);
+	input_report_abs(acer_wmi_accel_dev, ABS_Y,
+		(s16)out_obj->package.elements[1].integer.value);
+	input_report_abs(acer_wmi_accel_dev, ABS_Z,
+		(s16)out_obj->package.elements[2].integer.value);
+	input_sync(acer_wmi_accel_dev);
+	return 0;
+}
+
+/*
+ * Rfkill devices
+ */
+static void acer_rfkill_update(struct work_struct *ignored);
+static DECLARE_DELAYED_WORK(acer_rfkill_work, acer_rfkill_update);
+static void acer_rfkill_update(struct work_struct *ignored)
+{
+	u32 state;
+	acpi_status status;
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		status = get_u32(&state, ACER_CAP_WIRELESS);
+		if (ACPI_SUCCESS(status)) {
+			if (quirks->wireless == 3)
+				rfkill_set_hw_state(wireless_rfkill, !state);
+			else
+				rfkill_set_sw_state(wireless_rfkill, !state);
+		}
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		status = get_u32(&state, ACER_CAP_BLUETOOTH);
+		if (ACPI_SUCCESS(status))
+			rfkill_set_sw_state(bluetooth_rfkill, !state);
+	}
+
+	if (has_cap(ACER_CAP_THREEG) && wmi_has_guid(WMID_GUID3)) {
+		status = get_u32(&state, ACER_WMID3_GDS_THREEG);
+		if (ACPI_SUCCESS(status))
+			rfkill_set_sw_state(threeg_rfkill, !state);
+	}
+
+	schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));
+}
+
+static int acer_rfkill_set(void *data, bool blocked)
+{
+	acpi_status status;
+	u32 cap = (unsigned long)data;
+
+	if (rfkill_inited) {
+		status = set_u32(!blocked, cap);
+		if (ACPI_FAILURE(status))
+			return -ENODEV;
+	}
+
+	return 0;
+}
+
+static const struct rfkill_ops acer_rfkill_ops = {
+	.set_block = acer_rfkill_set,
+};
+
+static struct rfkill *acer_rfkill_register(struct device *dev,
+					   enum rfkill_type type,
+					   char *name, u32 cap)
+{
+	int err;
+	struct rfkill *rfkill_dev;
+	u32 state;
+	acpi_status status;
+
+	rfkill_dev = rfkill_alloc(name, dev, type,
+				  &acer_rfkill_ops,
+				  (void *)(unsigned long)cap);
+	if (!rfkill_dev)
+		return ERR_PTR(-ENOMEM);
+
+	status = get_u32(&state, cap);
+
+	err = rfkill_register(rfkill_dev);
+	if (err) {
+		rfkill_destroy(rfkill_dev);
+		return ERR_PTR(err);
+	}
+
+	if (ACPI_SUCCESS(status))
+		rfkill_set_sw_state(rfkill_dev, !state);
+
+	return rfkill_dev;
+}
+
+static int acer_rfkill_init(struct device *dev)
+{
+	int err;
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		wireless_rfkill = acer_rfkill_register(dev, RFKILL_TYPE_WLAN,
+			"acer-wireless", ACER_CAP_WIRELESS);
+		if (IS_ERR(wireless_rfkill)) {
+			err = PTR_ERR(wireless_rfkill);
+			goto error_wireless;
+		}
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		bluetooth_rfkill = acer_rfkill_register(dev,
+			RFKILL_TYPE_BLUETOOTH, "acer-bluetooth",
+			ACER_CAP_BLUETOOTH);
+		if (IS_ERR(bluetooth_rfkill)) {
+			err = PTR_ERR(bluetooth_rfkill);
+			goto error_bluetooth;
+		}
+	}
+
+	if (has_cap(ACER_CAP_THREEG)) {
+		threeg_rfkill = acer_rfkill_register(dev,
+			RFKILL_TYPE_WWAN, "acer-threeg",
+			ACER_CAP_THREEG);
+		if (IS_ERR(threeg_rfkill)) {
+			err = PTR_ERR(threeg_rfkill);
+			goto error_threeg;
+		}
+	}
+
+	rfkill_inited = true;
+
+	if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) &&
+	    has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
+		schedule_delayed_work(&acer_rfkill_work,
+			round_jiffies_relative(HZ));
+
+	return 0;
+
+error_threeg:
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		rfkill_unregister(bluetooth_rfkill);
+		rfkill_destroy(bluetooth_rfkill);
+	}
+error_bluetooth:
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		rfkill_unregister(wireless_rfkill);
+		rfkill_destroy(wireless_rfkill);
+	}
+error_wireless:
+	return err;
+}
+
+static void acer_rfkill_exit(void)
+{
+	if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) &&
+	    has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
+		cancel_delayed_work_sync(&acer_rfkill_work);
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		rfkill_unregister(wireless_rfkill);
+		rfkill_destroy(wireless_rfkill);
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		rfkill_unregister(bluetooth_rfkill);
+		rfkill_destroy(bluetooth_rfkill);
+	}
+
+	if (has_cap(ACER_CAP_THREEG)) {
+		rfkill_unregister(threeg_rfkill);
+		rfkill_destroy(threeg_rfkill);
+	}
+	return;
+}
+
+static void acer_wmi_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	struct event_return_value return_value;
+	acpi_status status;
+	u16 device_state;
+	const struct key_entry *key;
+	u32 scancode;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_warn("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+
+	if (!obj)
+		return;
+	if (obj->type != ACPI_TYPE_BUFFER) {
+		pr_warn("Unknown response received %d\n", obj->type);
+		kfree(obj);
+		return;
+	}
+	if (obj->buffer.length != 8) {
+		pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return;
+	}
+
+	return_value = *((struct event_return_value *)obj->buffer.pointer);
+	kfree(obj);
+
+	switch (return_value.function) {
+	case WMID_HOTKEY_EVENT:
+		device_state = return_value.device_state;
+		pr_debug("device state: 0x%x\n", device_state);
+
+		key = sparse_keymap_entry_from_scancode(acer_wmi_input_dev,
+							return_value.key_num);
+		if (!key) {
+			pr_warn("Unknown key number - 0x%x\n",
+				return_value.key_num);
+		} else {
+			scancode = return_value.key_num;
+			switch (key->keycode) {
+			case KEY_WLAN:
+			case KEY_BLUETOOTH:
+				if (has_cap(ACER_CAP_WIRELESS))
+					rfkill_set_sw_state(wireless_rfkill,
+						!(device_state & ACER_WMID3_GDS_WIRELESS));
+				if (has_cap(ACER_CAP_THREEG))
+					rfkill_set_sw_state(threeg_rfkill,
+						!(device_state & ACER_WMID3_GDS_THREEG));
+				if (has_cap(ACER_CAP_BLUETOOTH))
+					rfkill_set_sw_state(bluetooth_rfkill,
+						!(device_state & ACER_WMID3_GDS_BLUETOOTH));
+				break;
+			case KEY_TOUCHPAD_TOGGLE:
+				scancode = (device_state & ACER_WMID3_GDS_TOUCHPAD) ?
+						KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF;
+			}
+			sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true);
+		}
+		break;
+	case WMID_ACCEL_EVENT:
+		acer_gsensor_event();
+		break;
+	default:
+		pr_warn("Unknown function number - %d - %d\n",
+			return_value.function, return_value.key_num);
+		break;
+	}
+}
+
+static acpi_status __init
+wmid3_set_lm_mode(struct lm_input_params *params,
+		  struct lm_return_value *return_value)
+{
+	acpi_status status;
+	union acpi_object *obj;
+
+	struct acpi_buffer input = { sizeof(struct lm_input_params), params };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &input, &output);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = output.pointer;
+
+	if (!obj)
+		return AE_ERROR;
+	else if (obj->type != ACPI_TYPE_BUFFER) {
+		kfree(obj);
+		return AE_ERROR;
+	}
+	if (obj->buffer.length != 4) {
+		pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return AE_ERROR;
+	}
+
+	*return_value = *((struct lm_return_value *)obj->buffer.pointer);
+	kfree(obj);
+
+	return status;
+}
+
+static int __init acer_wmi_enable_ec_raw(void)
+{
+	struct lm_return_value return_value;
+	acpi_status status;
+	struct lm_input_params params = {
+		.function_num = 0x1,
+		.commun_devices = 0xFFFF,
+		.devices = 0xFFFF,
+		.lm_status = 0x00,            /* Launch Manager Deactive */
+	};
+
+	status = wmid3_set_lm_mode(&params, &return_value);
+
+	if (return_value.error_code || return_value.ec_return_value)
+		pr_warn("Enabling EC raw mode failed: 0x%x - 0x%x\n",
+			return_value.error_code,
+			return_value.ec_return_value);
+	else
+		pr_info("Enabled EC raw mode\n");
+
+	return status;
+}
+
+static int __init acer_wmi_enable_lm(void)
+{
+	struct lm_return_value return_value;
+	acpi_status status;
+	struct lm_input_params params = {
+		.function_num = 0x1,
+		.commun_devices = 0xFFFF,
+		.devices = 0xFFFF,
+		.lm_status = 0x01,            /* Launch Manager Active */
+	};
+
+	status = wmid3_set_lm_mode(&params, &return_value);
+
+	if (return_value.error_code || return_value.ec_return_value)
+		pr_warn("Enabling Launch Manager failed: 0x%x - 0x%x\n",
+			return_value.error_code,
+			return_value.ec_return_value);
+
+	return status;
+}
+
+#define ACER_WMID_ACCEL_HID	"BST0001"
+
+static acpi_status __init acer_wmi_get_handle_cb(acpi_handle ah, u32 level,
+						void *ctx, void **retval)
+{
+	struct acpi_device *dev;
+
+	if (!strcmp(ctx, "SENR")) {
+		if (acpi_bus_get_device(ah, &dev))
+			return AE_OK;
+		if (!strcmp(ACER_WMID_ACCEL_HID, acpi_device_hid(dev)))
+			return AE_OK;
+	} else
+		return AE_OK;
+
+	*(acpi_handle *)retval = ah;
+
+	return AE_CTRL_TERMINATE;
+}
+
+static int __init acer_wmi_get_handle(const char *name, const char *prop,
+					acpi_handle *ah)
+{
+	acpi_status status;
+	acpi_handle handle;
+
+	BUG_ON(!name || !ah);
+
+	handle = NULL;
+	status = acpi_get_devices(prop, acer_wmi_get_handle_cb,
+					(void *)name, &handle);
+
+	if (ACPI_SUCCESS(status)) {
+		*ah = handle;
+		return 0;
+	} else {
+		return -ENODEV;
+	}
+}
+
+static int __init acer_wmi_accel_setup(void)
+{
+	int err;
+
+	err = acer_wmi_get_handle("SENR", ACER_WMID_ACCEL_HID, &gsensor_handle);
+	if (err)
+		return err;
+
+	interface->capability |= ACER_CAP_ACCEL;
+
+	acer_wmi_accel_dev = input_allocate_device();
+	if (!acer_wmi_accel_dev)
+		return -ENOMEM;
+
+	acer_wmi_accel_dev->open = acer_gsensor_open;
+
+	acer_wmi_accel_dev->name = "Acer BMA150 accelerometer";
+	acer_wmi_accel_dev->phys = "wmi/input1";
+	acer_wmi_accel_dev->id.bustype = BUS_HOST;
+	acer_wmi_accel_dev->evbit[0] = BIT_MASK(EV_ABS);
+	input_set_abs_params(acer_wmi_accel_dev, ABS_X, -16384, 16384, 0, 0);
+	input_set_abs_params(acer_wmi_accel_dev, ABS_Y, -16384, 16384, 0, 0);
+	input_set_abs_params(acer_wmi_accel_dev, ABS_Z, -16384, 16384, 0, 0);
+
+	err = input_register_device(acer_wmi_accel_dev);
+	if (err)
+		goto err_free_dev;
+
+	return 0;
+
+err_free_dev:
+	input_free_device(acer_wmi_accel_dev);
+	return err;
+}
+
+static void acer_wmi_accel_destroy(void)
+{
+	input_unregister_device(acer_wmi_accel_dev);
+}
+
+static int __init acer_wmi_input_setup(void)
+{
+	acpi_status status;
+	int err;
+
+	acer_wmi_input_dev = input_allocate_device();
+	if (!acer_wmi_input_dev)
+		return -ENOMEM;
+
+	acer_wmi_input_dev->name = "Acer WMI hotkeys";
+	acer_wmi_input_dev->phys = "wmi/input0";
+	acer_wmi_input_dev->id.bustype = BUS_HOST;
+
+	err = sparse_keymap_setup(acer_wmi_input_dev, acer_wmi_keymap, NULL);
+	if (err)
+		goto err_free_dev;
+
+	status = wmi_install_notify_handler(ACERWMID_EVENT_GUID,
+						acer_wmi_notify, NULL);
+	if (ACPI_FAILURE(status)) {
+		err = -EIO;
+		goto err_free_keymap;
+	}
+
+	err = input_register_device(acer_wmi_input_dev);
+	if (err)
+		goto err_uninstall_notifier;
+
+	return 0;
+
+err_uninstall_notifier:
+	wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
+err_free_keymap:
+	sparse_keymap_free(acer_wmi_input_dev);
+err_free_dev:
+	input_free_device(acer_wmi_input_dev);
+	return err;
+}
+
+static void acer_wmi_input_destroy(void)
+{
+	wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
+	sparse_keymap_free(acer_wmi_input_dev);
+	input_unregister_device(acer_wmi_input_dev);
+}
+
+/*
+ * debugfs functions
+ */
+static u32 get_wmid_devices(void)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	acpi_status status;
+	u32 devices = 0;
+
+	status = wmi_query_block(WMID_GUID2, 1, &out);
+	if (ACPI_FAILURE(status))
+		return 0;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj) {
+		if (obj->type == ACPI_TYPE_BUFFER &&
+			(obj->buffer.length == sizeof(u32) ||
+			obj->buffer.length == sizeof(u64))) {
+			devices = *((u32 *) obj->buffer.pointer);
+		} else if (obj->type == ACPI_TYPE_INTEGER) {
+			devices = (u32) obj->integer.value;
+		}
+	}
+
+	kfree(out.pointer);
+	return devices;
+}
+
+/*
+ * Platform device
+ */
+static int acer_platform_probe(struct platform_device *device)
+{
+	int err;
+
+	if (has_cap(ACER_CAP_MAILLED)) {
+		err = acer_led_init(&device->dev);
+		if (err)
+			goto error_mailled;
+	}
+
+	if (has_cap(ACER_CAP_BRIGHTNESS)) {
+		err = acer_backlight_init(&device->dev);
+		if (err)
+			goto error_brightness;
+	}
+
+	err = acer_rfkill_init(&device->dev);
+	if (err)
+		goto error_rfkill;
+
+	return err;
+
+error_rfkill:
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		acer_backlight_exit();
+error_brightness:
+	if (has_cap(ACER_CAP_MAILLED))
+		acer_led_exit();
+error_mailled:
+	return err;
+}
+
+static int acer_platform_remove(struct platform_device *device)
+{
+	if (has_cap(ACER_CAP_MAILLED))
+		acer_led_exit();
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		acer_backlight_exit();
+
+	acer_rfkill_exit();
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int acer_suspend(struct device *dev)
+{
+	u32 value;
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return -ENOMEM;
+
+	if (has_cap(ACER_CAP_MAILLED)) {
+		get_u32(&value, ACER_CAP_MAILLED);
+		set_u32(LED_OFF, ACER_CAP_MAILLED);
+		data->mailled = value;
+	}
+
+	if (has_cap(ACER_CAP_BRIGHTNESS)) {
+		get_u32(&value, ACER_CAP_BRIGHTNESS);
+		data->brightness = value;
+	}
+
+	return 0;
+}
+
+static int acer_resume(struct device *dev)
+{
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return -ENOMEM;
+
+	if (has_cap(ACER_CAP_MAILLED))
+		set_u32(data->mailled, ACER_CAP_MAILLED);
+
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		set_u32(data->brightness, ACER_CAP_BRIGHTNESS);
+
+	if (has_cap(ACER_CAP_ACCEL))
+		acer_gsensor_init();
+
+	return 0;
+}
+#else
+#define acer_suspend	NULL
+#define acer_resume	NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(acer_pm, acer_suspend, acer_resume);
+
+static void acer_platform_shutdown(struct platform_device *device)
+{
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return;
+
+	if (has_cap(ACER_CAP_MAILLED))
+		set_u32(LED_OFF, ACER_CAP_MAILLED);
+}
+
+static struct platform_driver acer_platform_driver = {
+	.driver = {
+		.name = "acer-wmi",
+		.pm = &acer_pm,
+	},
+	.probe = acer_platform_probe,
+	.remove = acer_platform_remove,
+	.shutdown = acer_platform_shutdown,
+};
+
+static struct platform_device *acer_platform_device;
+
+static void remove_debugfs(void)
+{
+	debugfs_remove(interface->debug.devices);
+	debugfs_remove(interface->debug.root);
+}
+
+static int __init create_debugfs(void)
+{
+	interface->debug.root = debugfs_create_dir("acer-wmi", NULL);
+	if (!interface->debug.root) {
+		pr_err("Failed to create debugfs directory");
+		return -ENOMEM;
+	}
+
+	interface->debug.devices = debugfs_create_u32("devices", S_IRUGO,
+					interface->debug.root,
+					&interface->debug.wmid_devices);
+	if (!interface->debug.devices)
+		goto error_debugfs;
+
+	return 0;
+
+error_debugfs:
+	remove_debugfs();
+	return -ENOMEM;
+}
+
+static int __init acer_wmi_init(void)
+{
+	int err;
+
+	pr_info("Acer Laptop ACPI-WMI Extras\n");
+
+	if (dmi_check_system(acer_blacklist)) {
+		pr_info("Blacklisted hardware detected - not loading\n");
+		return -ENODEV;
+	}
+
+	find_quirks();
+
+	/*
+	 * Detect which ACPI-WMI interface we're using.
+	 */
+	if (wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+		interface = &AMW0_V2_interface;
+
+	if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+		interface = &wmid_interface;
+
+	if (wmi_has_guid(WMID_GUID3))
+		interface = &wmid_v2_interface;
+
+	if (interface)
+		dmi_walk(type_aa_dmi_decode, NULL);
+
+	if (wmi_has_guid(WMID_GUID2) && interface) {
+		if (!has_type_aa && ACPI_FAILURE(WMID_set_capabilities())) {
+			pr_err("Unable to detect available WMID devices\n");
+			return -ENODEV;
+		}
+		/* WMID always provides brightness methods */
+		interface->capability |= ACER_CAP_BRIGHTNESS;
+	} else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa) {
+		pr_err("No WMID device detection method found\n");
+		return -ENODEV;
+	}
+
+	if (wmi_has_guid(AMW0_GUID1) && !wmi_has_guid(WMID_GUID1)) {
+		interface = &AMW0_interface;
+
+		if (ACPI_FAILURE(AMW0_set_capabilities())) {
+			pr_err("Unable to detect available AMW0 devices\n");
+			return -ENODEV;
+		}
+	}
+
+	if (wmi_has_guid(AMW0_GUID1))
+		AMW0_find_mailled();
+
+	if (!interface) {
+		pr_err("No or unsupported WMI interface, unable to load\n");
+		return -ENODEV;
+	}
+
+	set_quirks();
+
+	if (dmi_check_system(video_vendor_dmi_table))
+		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
+		interface->capability &= ~ACER_CAP_BRIGHTNESS;
+
+	if (wmi_has_guid(WMID_GUID3)) {
+		if (ec_raw_mode) {
+			if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) {
+				pr_err("Cannot enable EC raw mode\n");
+				return -ENODEV;
+			}
+		} else if (ACPI_FAILURE(acer_wmi_enable_lm())) {
+			pr_err("Cannot enable Launch Manager mode\n");
+			return -ENODEV;
+		}
+	} else if (ec_raw_mode) {
+		pr_info("No WMID EC raw mode enable method\n");
+	}
+
+	if (wmi_has_guid(ACERWMID_EVENT_GUID)) {
+		err = acer_wmi_input_setup();
+		if (err)
+			return err;
+		err = acer_wmi_accel_setup();
+		if (err)
+			return err;
+	}
+
+	err = platform_driver_register(&acer_platform_driver);
+	if (err) {
+		pr_err("Unable to register platform driver\n");
+		goto error_platform_register;
+	}
+
+	acer_platform_device = platform_device_alloc("acer-wmi", -1);
+	if (!acer_platform_device) {
+		err = -ENOMEM;
+		goto error_device_alloc;
+	}
+
+	err = platform_device_add(acer_platform_device);
+	if (err)
+		goto error_device_add;
+
+	if (wmi_has_guid(WMID_GUID2)) {
+		interface->debug.wmid_devices = get_wmid_devices();
+		err = create_debugfs();
+		if (err)
+			goto error_create_debugfs;
+	}
+
+	/* Override any initial settings with values from the commandline */
+	acer_commandline_init();
+
+	return 0;
+
+error_create_debugfs:
+	platform_device_del(acer_platform_device);
+error_device_add:
+	platform_device_put(acer_platform_device);
+error_device_alloc:
+	platform_driver_unregister(&acer_platform_driver);
+error_platform_register:
+	if (wmi_has_guid(ACERWMID_EVENT_GUID))
+		acer_wmi_input_destroy();
+	if (has_cap(ACER_CAP_ACCEL))
+		acer_wmi_accel_destroy();
+
+	return err;
+}
+
+static void __exit acer_wmi_exit(void)
+{
+	if (wmi_has_guid(ACERWMID_EVENT_GUID))
+		acer_wmi_input_destroy();
+
+	if (has_cap(ACER_CAP_ACCEL))
+		acer_wmi_accel_destroy();
+
+	remove_debugfs();
+	platform_device_unregister(acer_platform_device);
+	platform_driver_unregister(&acer_platform_driver);
+
+	pr_info("Acer Laptop WMI Extras unloaded\n");
+	return;
+}
+
+module_init(acer_wmi_init);
+module_exit(acer_wmi_exit);
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..460fa67
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,813 @@
+/*
+ * acerhdf - A driver which monitors the temperature
+ *           of the aspire one netbook, turns on/off the fan
+ *           as soon as the upper/lower threshold is reached.
+ *
+ * (C) 2009 - Peter Feuerer     peter (a) piie.net
+ *                              http://piie.net
+ *     2009 Borislav Petkov	bp (a) alien8.de
+ *
+ * Inspired by and many thanks to:
+ *  o acerfand   - Rachel Greenham
+ *  o acer_ec.pl - Michael Kurz     michi.kurz (at) googlemail.com
+ *               - Petr Tomasek     tomasek (#) etf,cuni,cz
+ *               - Carlos Corbacho  cathectic (at) gmail.com
+ *  o lkml       - Matthew Garrett
+ *               - Borislav Petkov
+ *               - Andreas Mohr
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) "acerhdf: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+
+/*
+ * The driver is started with "kernel mode off" by default. That means, the BIOS
+ * is still in control of the fan. In this mode the driver allows to read the
+ * temperature of the cpu and a userspace tool may take over control of the fan.
+ * If the driver is switched to "kernel mode" (e.g. via module parameter) the
+ * driver is in full control of the fan. If you want the module to be started in
+ * kernel mode by default, define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define DRV_VER "0.7.0"
+
+/*
+ * According to the Atom N270 datasheet,
+ * (http://download.intel.com/design/processor/datashts/320032.pdf) the
+ * CPU's optimal operating limits denoted in junction temperature as
+ * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
+ * assume 89°C is critical temperature.
+ */
+#define ACERHDF_TEMP_CRIT 89000
+#define ACERHDF_FAN_OFF 0
+#define ACERHDF_FAN_AUTO 1
+
+/*
+ * No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage
+ */
+#define ACERHDF_MAX_FANON 80000
+
+/*
+ * Maximum interval between two temperature checks is 15 seconds, as the die
+ * can get hot really fast under heavy load (plus we shouldn't forget about
+ * possible impact of _external_ aggressive sources such as heaters, sun etc.)
+ */
+#define ACERHDF_MAX_INTERVAL 15
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else
+static int kernelmode;
+#endif
+
+static unsigned int interval = 10;
+static unsigned int fanon = 60000;
+static unsigned int fanoff = 53000;
+static unsigned int verbose;
+static unsigned int fanstate = ACERHDF_FAN_AUTO;
+static char force_bios[16];
+static char force_product[16];
+static unsigned int prev_interval;
+static struct thermal_zone_device *thz_dev;
+static struct thermal_cooling_device *cl_dev;
+static struct platform_device *acerhdf_dev;
+
+module_param(kernelmode, uint, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off");
+module_param(interval, uint, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, uint, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, uint, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+module_param(verbose, uint, 0600);
+MODULE_PARM_DESC(verbose, "Enable verbose dmesg output");
+module_param_string(force_bios, force_bios, 16, 0);
+MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
+module_param_string(force_product, force_product, 16, 0);
+MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check");
+
+/*
+ * cmd_off: to switch the fan completely off and check if the fan is off
+ *	cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then
+ *		the fan speed depending on the temperature
+ */
+struct fancmd {
+	u8 cmd_off;
+	u8 cmd_auto;
+};
+
+struct manualcmd {
+	u8 mreg;
+	u8 moff;
+};
+
+/* default register and command to disable fan in manual mode */
+static const struct manualcmd mcmd = {
+	.mreg = 0x94,
+	.moff = 0xff,
+};
+
+/* BIOS settings */
+struct bios_settings {
+	const char *vendor;
+	const char *product;
+	const char *version;
+	u8 fanreg;
+	u8 tempreg;
+	struct fancmd cmd;
+	int mcmd_enable;
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings bios_tbl[] = {
+	/* AOA110 */
+	{"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00}, 0},
+	{"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00}, 0},
+	/* AOA150 */
+	{"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00}, 0},
+	/* LT1005u */
+	{"Acer", "LT-10Q", "v0.3310", 0x55, 0x58, {0x20, 0x00}, 0},
+	/* Acer 1410 */
+	{"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1410", "v1.3314", 0x55, 0x58, {0x9e, 0x00}, 0},
+	/* Acer 1810xx */
+	{"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v0.3108", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v0.3113", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v0.3115", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v0.3117", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v0.3119", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v0.3120", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v1.3204", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v1.3303", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v1.3308", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v1.3310", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810TZ", "v1.3314", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1810T",  "v1.3314", 0x55, 0x58, {0x9e, 0x00}, 0},
+	/* Acer 5755G */
+	{"Acer", "Aspire 5755G",  "V1.20",   0xab, 0xb4, {0x00, 0x08}, 0},
+	{"Acer", "Aspire 5755G",  "V1.21",   0xab, 0xb3, {0x00, 0x08}, 0},
+	/* Acer 521 */
+	{"Acer", "AO521", "V1.11", 0x55, 0x58, {0x1f, 0x00}, 0},
+	/* Acer 531 */
+	{"Acer", "AO531h", "v0.3104", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00}, 0},
+	{"Acer", "AO531h", "v0.3304", 0x55, 0x58, {0x20, 0x00}, 0},
+	/* Acer 751 */
+	{"Acer", "AO751h", "V0.3206", 0x55, 0x58, {0x21, 0x00}, 0},
+	{"Acer", "AO751h", "V0.3212", 0x55, 0x58, {0x21, 0x00}, 0},
+	/* Acer 753 */
+	{"Acer", "Aspire One 753", "V1.24", 0x93, 0xac, {0x14, 0x04}, 1},
+	/* Acer 1825 */
+	{"Acer", "Aspire 1825PTZ", "V1.3118", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Acer", "Aspire 1825PTZ", "V1.3127", 0x55, 0x58, {0x9e, 0x00}, 0},
+	/* Acer Extensa 5420 */
+	{"Acer", "Extensa 5420", "V1.17", 0x93, 0xac, {0x14, 0x04}, 1},
+	/* Acer Aspire 5315 */
+	{"Acer", "Aspire 5315", "V1.19", 0x93, 0xac, {0x14, 0x04}, 1},
+	/* Acer Aspire 5739 */
+	{"Acer", "Aspire 5739G", "V1.3311", 0x55, 0x58, {0x20, 0x00}, 0},
+	/* Acer TravelMate 7730 */
+	{"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00}, 0},
+	/* Acer TravelMate TM8573T */
+	{"Acer", "TM8573T", "V1.13", 0x93, 0xa8, {0x14, 0x04}, 1},
+	/* Gateway */
+	{"Gateway", "AOA110", "v0.3103",  0x55, 0x58, {0x21, 0x00}, 0},
+	{"Gateway", "AOA150", "v0.3103",  0x55, 0x58, {0x20, 0x00}, 0},
+	{"Gateway", "LT31",   "v1.3103",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Gateway", "LT31",   "v1.3201",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Gateway", "LT31",   "v1.3302",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Gateway", "LT31",   "v1.3303t", 0x55, 0x58, {0x9e, 0x00}, 0},
+	/* Packard Bell */
+	{"Packard Bell", "DOA150",  "v0.3104",  0x55, 0x58, {0x21, 0x00}, 0},
+	{"Packard Bell", "DOA150",  "v0.3105",  0x55, 0x58, {0x20, 0x00}, 0},
+	{"Packard Bell", "AOA110",  "v0.3105",  0x55, 0x58, {0x21, 0x00}, 0},
+	{"Packard Bell", "AOA150",  "v0.3105",  0x55, 0x58, {0x20, 0x00}, 0},
+	{"Packard Bell", "ENBFT",   "V1.3118",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "ENBFT",   "V1.3127",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v1.3303",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v0.3120",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v0.3108",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v0.3113",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v0.3115",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v0.3117",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v0.3119",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMU",   "v1.3204",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMA",   "v1.3201",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMA",   "v1.3302",  0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTMA",   "v1.3303t", 0x55, 0x58, {0x9e, 0x00}, 0},
+	{"Packard Bell", "DOTVR46", "v1.3308",  0x55, 0x58, {0x9e, 0x00}, 0},
+	/* pewpew-terminator */
+	{"", "", "", 0, 0, {0, 0}, 0}
+};
+
+static const struct bios_settings *bios_cfg __read_mostly;
+
+/*
+ * this struct is used to instruct thermal layer to use bang_bang instead of
+ * default governor for acerhdf
+ */
+static struct thermal_zone_params acerhdf_zone_params = {
+	.governor_name = "bang_bang",
+};
+
+static int acerhdf_get_temp(int *temp)
+{
+	u8 read_temp;
+
+	if (ec_read(bios_cfg->tempreg, &read_temp))
+		return -EINVAL;
+
+	*temp = read_temp * 1000;
+
+	return 0;
+}
+
+static int acerhdf_get_fanstate(int *state)
+{
+	u8 fan;
+
+	if (ec_read(bios_cfg->fanreg, &fan))
+		return -EINVAL;
+
+	if (fan != bios_cfg->cmd.cmd_off)
+		*state = ACERHDF_FAN_AUTO;
+	else
+		*state = ACERHDF_FAN_OFF;
+
+	return 0;
+}
+
+static void acerhdf_change_fanstate(int state)
+{
+	unsigned char cmd;
+
+	if (verbose)
+		pr_notice("fan %s\n", state == ACERHDF_FAN_OFF ? "OFF" : "ON");
+
+	if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
+		pr_err("invalid fan state %d requested, setting to auto!\n",
+		       state);
+		state = ACERHDF_FAN_AUTO;
+	}
+
+	cmd = (state == ACERHDF_FAN_OFF) ? bios_cfg->cmd.cmd_off
+					 : bios_cfg->cmd.cmd_auto;
+	fanstate = state;
+
+	ec_write(bios_cfg->fanreg, cmd);
+
+	if (bios_cfg->mcmd_enable && state == ACERHDF_FAN_OFF) {
+		if (verbose)
+			pr_notice("turning off fan manually\n");
+		ec_write(mcmd.mreg, mcmd.moff);
+	}
+}
+
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+	if (fanon > ACERHDF_MAX_FANON) {
+		pr_err("fanon temperature too high, set to %d\n",
+		       ACERHDF_MAX_FANON);
+		fanon = ACERHDF_MAX_FANON;
+	}
+
+	if (kernelmode && prev_interval != interval) {
+		if (interval > ACERHDF_MAX_INTERVAL) {
+			pr_err("interval too high, set to %d\n",
+			       ACERHDF_MAX_INTERVAL);
+			interval = ACERHDF_MAX_INTERVAL;
+		}
+		if (verbose)
+			pr_notice("interval changed to: %d\n", interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/*
+ * This is the thermal zone callback which does the delayed polling of the fan
+ * state. We do check /sysfs-originating settings here in acerhdf_check_param()
+ * as late as the polling interval is since we can't do that in the respective
+ * accessors of the module parameters.
+ */
+static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal, int *t)
+{
+	int temp, err = 0;
+
+	acerhdf_check_param(thermal);
+
+	err = acerhdf_get_temp(&temp);
+	if (err)
+		return err;
+
+	if (verbose)
+		pr_notice("temp %d\n", temp);
+
+	*t = temp;
+	return 0;
+}
+
+static int acerhdf_bind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	/* if the cooling device is the one from acerhdf bind it */
+	if (cdev != cl_dev)
+		return 0;
+
+	if (thermal_zone_bind_cooling_device(thermal, 0, cdev,
+			THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
+			THERMAL_WEIGHT_DEFAULT)) {
+		pr_err("error binding cooling dev\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int acerhdf_unbind(struct thermal_zone_device *thermal,
+			  struct thermal_cooling_device *cdev)
+{
+	if (cdev != cl_dev)
+		return 0;
+
+	if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
+		pr_err("error unbinding cooling dev\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static inline void acerhdf_revert_to_bios_mode(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	kernelmode = 0;
+	if (thz_dev)
+		thz_dev->polling_delay = 0;
+	pr_notice("kernel mode fan control OFF\n");
+}
+static inline void acerhdf_enable_kernelmode(void)
+{
+	kernelmode = 1;
+
+	thz_dev->polling_delay = interval*1000;
+	thermal_zone_device_update(thz_dev);
+	pr_notice("kernel mode fan control ON\n");
+}
+
+static int acerhdf_get_mode(struct thermal_zone_device *thermal,
+			    enum thermal_device_mode *mode)
+{
+	if (verbose)
+		pr_notice("kernel mode fan control %d\n", kernelmode);
+
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED
+			     : THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+/*
+ * set operation mode;
+ * enabled: the thermal layer of the kernel takes care about
+ *          the temperature and the fan.
+ * disabled: the BIOS takes control of the fan.
+ */
+static int acerhdf_set_mode(struct thermal_zone_device *thermal,
+			    enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED && kernelmode)
+		acerhdf_revert_to_bios_mode();
+	else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode)
+		acerhdf_enable_kernelmode();
+
+	return 0;
+}
+
+static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip,
+				 enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	else if (trip == 1)
+		*type = THERMAL_TRIP_CRITICAL;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static int acerhdf_get_trip_hyst(struct thermal_zone_device *thermal, int trip,
+				 int *temp)
+{
+	if (trip != 0)
+		return -EINVAL;
+
+	*temp = fanon - fanoff;
+
+	return 0;
+}
+
+static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+				 int *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	else if (trip == 1)
+		*temp = ACERHDF_TEMP_CRIT;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
+				 int *temperature)
+{
+	*temperature = ACERHDF_TEMP_CRIT;
+	return 0;
+}
+
+/* bind callback functions to thermalzone */
+static struct thermal_zone_device_ops acerhdf_dev_ops = {
+	.bind = acerhdf_bind,
+	.unbind = acerhdf_unbind,
+	.get_temp = acerhdf_get_ec_temp,
+	.get_mode = acerhdf_get_mode,
+	.set_mode = acerhdf_set_mode,
+	.get_trip_type = acerhdf_get_trip_type,
+	.get_trip_hyst = acerhdf_get_trip_hyst,
+	.get_trip_temp = acerhdf_get_trip_temp,
+	.get_crit_temp = acerhdf_get_crit_temp,
+};
+
+
+/*
+ * cooling device callback functions
+ * get maximal fan cooling state
+ */
+static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
+				 unsigned long *state)
+{
+	*state = 1;
+
+	return 0;
+}
+
+static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
+				 unsigned long *state)
+{
+	int err = 0, tmp;
+
+	err = acerhdf_get_fanstate(&tmp);
+	if (err)
+		return err;
+
+	*state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0;
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
+				 unsigned long state)
+{
+	int cur_temp, cur_state, err = 0;
+
+	if (!kernelmode)
+		return 0;
+
+	err = acerhdf_get_temp(&cur_temp);
+	if (err) {
+		pr_err("error reading temperature, hand off control to BIOS\n");
+		goto err_out;
+	}
+
+	err = acerhdf_get_fanstate(&cur_state);
+	if (err) {
+		pr_err("error reading fan state, hand off control to BIOS\n");
+		goto err_out;
+	}
+
+	if (state == 0) {
+		if (cur_state == ACERHDF_FAN_AUTO)
+			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+	} else {
+		if (cur_state == ACERHDF_FAN_OFF)
+			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	}
+	return 0;
+
+err_out:
+	acerhdf_revert_to_bios_mode();
+	return -EINVAL;
+}
+
+/* bind fan callbacks to fan device */
+static struct thermal_cooling_device_ops acerhdf_cooling_ops = {
+	.get_max_state = acerhdf_get_max_state,
+	.get_cur_state = acerhdf_get_cur_state,
+	.set_cur_state = acerhdf_set_cur_state,
+};
+
+/* suspend / resume functionality */
+static int acerhdf_suspend(struct device *dev)
+{
+	if (kernelmode)
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	if (verbose)
+		pr_notice("going suspend\n");
+
+	return 0;
+}
+
+static int acerhdf_probe(struct platform_device *device)
+{
+	return 0;
+}
+
+static int acerhdf_remove(struct platform_device *device)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops acerhdf_pm_ops = {
+	.suspend = acerhdf_suspend,
+	.freeze  = acerhdf_suspend,
+};
+
+static struct platform_driver acerhdf_driver = {
+	.driver = {
+		.name  = "acerhdf",
+		.pm    = &acerhdf_pm_ops,
+	},
+	.probe = acerhdf_probe,
+	.remove = acerhdf_remove,
+};
+
+/* checks if str begins with start */
+static int str_starts_with(const char *str, const char *start)
+{
+	unsigned long str_len = 0, start_len = 0;
+
+	str_len = strlen(str);
+	start_len = strlen(start);
+
+	if (str_len >= start_len &&
+			!strncmp(str, start, start_len))
+		return 1;
+
+	return 0;
+}
+
+/* check hardware */
+static int acerhdf_check_hardware(void)
+{
+	char const *vendor, *version, *product;
+	const struct bios_settings *bt = NULL;
+
+	/* get BIOS data */
+	vendor  = dmi_get_system_info(DMI_SYS_VENDOR);
+	version = dmi_get_system_info(DMI_BIOS_VERSION);
+	product = dmi_get_system_info(DMI_PRODUCT_NAME);
+
+	if (!vendor || !version || !product) {
+		pr_err("error getting hardware information\n");
+		return -EINVAL;
+	}
+
+	pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER);
+
+	if (force_bios[0]) {
+		version = force_bios;
+		pr_info("forcing BIOS version: %s\n", version);
+		kernelmode = 0;
+	}
+
+	if (force_product[0]) {
+		product = force_product;
+		pr_info("forcing BIOS product: %s\n", product);
+		kernelmode = 0;
+	}
+
+	if (verbose)
+		pr_info("BIOS info: %s %s, product: %s\n",
+			vendor, version, product);
+
+	/* search BIOS version and vendor in BIOS settings table */
+	for (bt = bios_tbl; bt->vendor[0]; bt++) {
+		/*
+		 * check if actual hardware BIOS vendor, product and version
+		 * IDs start with the strings of BIOS table entry
+		 */
+		if (str_starts_with(vendor, bt->vendor) &&
+				str_starts_with(product, bt->product) &&
+				str_starts_with(version, bt->version)) {
+			bios_cfg = bt;
+			break;
+		}
+	}
+
+	if (!bios_cfg) {
+		pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n",
+		       vendor, product, version);
+		return -EINVAL;
+	}
+
+	/*
+	 * if started with kernel mode off, prevent the kernel from switching
+	 * off the fan
+	 */
+	if (!kernelmode) {
+		pr_notice("Fan control off, to enable do:\n");
+		pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zone0/mode\n");
+	}
+
+	return 0;
+}
+
+static int acerhdf_register_platform(void)
+{
+	int err = 0;
+
+	err = platform_driver_register(&acerhdf_driver);
+	if (err)
+		return err;
+
+	acerhdf_dev = platform_device_alloc("acerhdf", -1);
+	if (!acerhdf_dev) {
+		err = -ENOMEM;
+		goto err_device_alloc;
+	}
+	err = platform_device_add(acerhdf_dev);
+	if (err)
+		goto err_device_add;
+
+	return 0;
+
+err_device_add:
+	platform_device_put(acerhdf_dev);
+err_device_alloc:
+	platform_driver_unregister(&acerhdf_driver);
+	return err;
+}
+
+static void acerhdf_unregister_platform(void)
+{
+	platform_device_unregister(acerhdf_dev);
+	platform_driver_unregister(&acerhdf_driver);
+}
+
+static int acerhdf_register_thermal(void)
+{
+	cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+						 &acerhdf_cooling_ops);
+
+	if (IS_ERR(cl_dev))
+		return -EINVAL;
+
+	thz_dev = thermal_zone_device_register("acerhdf", 2, 0, NULL,
+					      &acerhdf_dev_ops,
+					      &acerhdf_zone_params, 0,
+					      (kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(thz_dev))
+		return -EINVAL;
+
+	if (strcmp(thz_dev->governor->name,
+				acerhdf_zone_params.governor_name)) {
+		pr_err("Didn't get thermal governor %s, perhaps not compiled into thermal subsystem.\n",
+				acerhdf_zone_params.governor_name);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void acerhdf_unregister_thermal(void)
+{
+	if (cl_dev) {
+		thermal_cooling_device_unregister(cl_dev);
+		cl_dev = NULL;
+	}
+
+	if (thz_dev) {
+		thermal_zone_device_unregister(thz_dev);
+		thz_dev = NULL;
+	}
+}
+
+static int __init acerhdf_init(void)
+{
+	int err = 0;
+
+	err = acerhdf_check_hardware();
+	if (err)
+		goto out_err;
+
+	err = acerhdf_register_platform();
+	if (err)
+		goto out_err;
+
+	err = acerhdf_register_thermal();
+	if (err)
+		goto err_unreg;
+
+	return 0;
+
+err_unreg:
+	acerhdf_unregister_thermal();
+	acerhdf_unregister_platform();
+
+out_err:
+	return err;
+}
+
+static void __exit acerhdf_exit(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	acerhdf_unregister_thermal();
+	acerhdf_unregister_platform();
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1410*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1810*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5755G:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1825PTZ:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAO521*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5739G:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*One*753:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5315:");
+MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:");
+MODULE_ALIAS("dmi:*:*Acer*:TM8573T:");
+MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:");
+MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnAOA*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOA*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMU*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnExtensa 5420*:");
+
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);
diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c
new file mode 100644
index 0000000..1e1e594
--- /dev/null
+++ b/drivers/platform/x86/alienware-wmi.c
@@ -0,0 +1,639 @@
+/*
+ * Alienware AlienFX control
+ *
+ * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <linux/leds.h>
+
+#define LEGACY_CONTROL_GUID		"A90597CE-A997-11DA-B012-B622A1EF5492"
+#define LEGACY_POWER_CONTROL_GUID	"A80593CE-A997-11DA-B012-B622A1EF5492"
+#define WMAX_CONTROL_GUID		"A70591CE-A997-11DA-B012-B622A1EF5492"
+
+#define WMAX_METHOD_HDMI_SOURCE		0x1
+#define WMAX_METHOD_HDMI_STATUS		0x2
+#define WMAX_METHOD_BRIGHTNESS		0x3
+#define WMAX_METHOD_ZONE_CONTROL	0x4
+#define WMAX_METHOD_HDMI_CABLE		0x5
+
+MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
+MODULE_DESCRIPTION("Alienware special feature control");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
+MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
+
+enum INTERFACE_FLAGS {
+	LEGACY,
+	WMAX,
+};
+
+enum LEGACY_CONTROL_STATES {
+	LEGACY_RUNNING = 1,
+	LEGACY_BOOTING = 0,
+	LEGACY_SUSPEND = 3,
+};
+
+enum WMAX_CONTROL_STATES {
+	WMAX_RUNNING = 0xFF,
+	WMAX_BOOTING = 0,
+	WMAX_SUSPEND = 3,
+};
+
+struct quirk_entry {
+	u8 num_zones;
+	u8 hdmi_mux;
+};
+
+static struct quirk_entry *quirks;
+
+static struct quirk_entry quirk_unknown = {
+	.num_zones = 2,
+	.hdmi_mux = 0,
+};
+
+static struct quirk_entry quirk_x51_family = {
+	.num_zones = 3,
+	.hdmi_mux = 0.
+};
+
+static struct quirk_entry quirk_asm100 = {
+	.num_zones = 2,
+	.hdmi_mux = 1,
+};
+
+static int __init dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 1;
+}
+
+static const struct dmi_system_id alienware_quirks[] __initconst = {
+	{
+	 .callback = dmi_matched,
+	 .ident = "Alienware X51 R1",
+	 .matches = {
+		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+		     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
+		     },
+	 .driver_data = &quirk_x51_family,
+	 },
+	{
+	 .callback = dmi_matched,
+	 .ident = "Alienware X51 R2",
+	 .matches = {
+		     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+		     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
+		     },
+	 .driver_data = &quirk_x51_family,
+	 },
+	{
+		.callback = dmi_matched,
+		.ident = "Alienware ASM100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
+		},
+		.driver_data = &quirk_asm100,
+	},
+	{}
+};
+
+struct color_platform {
+	u8 blue;
+	u8 green;
+	u8 red;
+} __packed;
+
+struct platform_zone {
+	u8 location;
+	struct device_attribute *attr;
+	struct color_platform colors;
+};
+
+struct wmax_brightness_args {
+	u32 led_mask;
+	u32 percentage;
+};
+
+struct hdmi_args {
+	u8 arg;
+};
+
+struct legacy_led_args {
+	struct color_platform colors;
+	u8 brightness;
+	u8 state;
+} __packed;
+
+struct wmax_led_args {
+	u32 led_mask;
+	struct color_platform colors;
+	u8 state;
+} __packed;
+
+static struct platform_device *platform_device;
+static struct device_attribute *zone_dev_attrs;
+static struct attribute **zone_attrs;
+static struct platform_zone *zone_data;
+
+static struct platform_driver platform_driver = {
+	.driver = {
+		   .name = "alienware-wmi",
+		   }
+};
+
+static struct attribute_group zone_attribute_group = {
+	.name = "rgb_zones",
+};
+
+static u8 interface;
+static u8 lighting_control_state;
+static u8 global_brightness;
+
+/*
+ * Helpers used for zone control
+*/
+static int parse_rgb(const char *buf, struct platform_zone *zone)
+{
+	long unsigned int rgb;
+	int ret;
+	union color_union {
+		struct color_platform cp;
+		int package;
+	} repackager;
+
+	ret = kstrtoul(buf, 16, &rgb);
+	if (ret)
+		return ret;
+
+	/* RGB triplet notation is 24-bit hexadecimal */
+	if (rgb > 0xFFFFFF)
+		return -EINVAL;
+
+	repackager.package = rgb & 0x0f0f0f0f;
+	pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
+		 repackager.cp.red, repackager.cp.green, repackager.cp.blue);
+	zone->colors = repackager.cp;
+	return 0;
+}
+
+static struct platform_zone *match_zone(struct device_attribute *attr)
+{
+	int i;
+	for (i = 0; i < quirks->num_zones; i++) {
+		if ((struct device_attribute *)zone_data[i].attr == attr) {
+			pr_debug("alienware-wmi: matched zone location: %d\n",
+				 zone_data[i].location);
+			return &zone_data[i];
+		}
+	}
+	return NULL;
+}
+
+/*
+ * Individual RGB zone control
+*/
+static int alienware_update_led(struct platform_zone *zone)
+{
+	int method_id;
+	acpi_status status;
+	char *guid;
+	struct acpi_buffer input;
+	struct legacy_led_args legacy_args;
+	struct wmax_led_args wmax_args;
+	if (interface == WMAX) {
+		wmax_args.led_mask = 1 << zone->location;
+		wmax_args.colors = zone->colors;
+		wmax_args.state = lighting_control_state;
+		guid = WMAX_CONTROL_GUID;
+		method_id = WMAX_METHOD_ZONE_CONTROL;
+
+		input.length = (acpi_size) sizeof(wmax_args);
+		input.pointer = &wmax_args;
+	} else {
+		legacy_args.colors = zone->colors;
+		legacy_args.brightness = global_brightness;
+		legacy_args.state = 0;
+		if (lighting_control_state == LEGACY_BOOTING ||
+		    lighting_control_state == LEGACY_SUSPEND) {
+			guid = LEGACY_POWER_CONTROL_GUID;
+			legacy_args.state = lighting_control_state;
+		} else
+			guid = LEGACY_CONTROL_GUID;
+		method_id = zone->location + 1;
+
+		input.length = (acpi_size) sizeof(legacy_args);
+		input.pointer = &legacy_args;
+	}
+	pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
+
+	status = wmi_evaluate_method(guid, 1, method_id, &input, NULL);
+	if (ACPI_FAILURE(status))
+		pr_err("alienware-wmi: zone set failure: %u\n", status);
+	return ACPI_FAILURE(status);
+}
+
+static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct platform_zone *target_zone;
+	target_zone = match_zone(attr);
+	if (target_zone == NULL)
+		return sprintf(buf, "red: -1, green: -1, blue: -1\n");
+	return sprintf(buf, "red: %d, green: %d, blue: %d\n",
+		       target_zone->colors.red,
+		       target_zone->colors.green, target_zone->colors.blue);
+
+}
+
+static ssize_t zone_set(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct platform_zone *target_zone;
+	int ret;
+	target_zone = match_zone(attr);
+	if (target_zone == NULL) {
+		pr_err("alienware-wmi: invalid target zone\n");
+		return 1;
+	}
+	ret = parse_rgb(buf, target_zone);
+	if (ret)
+		return ret;
+	ret = alienware_update_led(target_zone);
+	return ret ? ret : count;
+}
+
+/*
+ * LED Brightness (Global)
+*/
+static int wmax_brightness(int brightness)
+{
+	acpi_status status;
+	struct acpi_buffer input;
+	struct wmax_brightness_args args = {
+		.led_mask = 0xFF,
+		.percentage = brightness,
+	};
+	input.length = (acpi_size) sizeof(args);
+	input.pointer = &args;
+	status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
+				     WMAX_METHOD_BRIGHTNESS, &input, NULL);
+	if (ACPI_FAILURE(status))
+		pr_err("alienware-wmi: brightness set failure: %u\n", status);
+	return ACPI_FAILURE(status);
+}
+
+static void global_led_set(struct led_classdev *led_cdev,
+			   enum led_brightness brightness)
+{
+	int ret;
+	global_brightness = brightness;
+	if (interface == WMAX)
+		ret = wmax_brightness(brightness);
+	else
+		ret = alienware_update_led(&zone_data[0]);
+	if (ret)
+		pr_err("LED brightness update failed\n");
+}
+
+static enum led_brightness global_led_get(struct led_classdev *led_cdev)
+{
+	return global_brightness;
+}
+
+static struct led_classdev global_led = {
+	.brightness_set = global_led_set,
+	.brightness_get = global_led_get,
+	.name = "alienware::global_brightness",
+};
+
+/*
+ * Lighting control state device attribute (Global)
+*/
+static ssize_t show_control_state(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	if (lighting_control_state == LEGACY_BOOTING)
+		return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n");
+	else if (lighting_control_state == LEGACY_SUSPEND)
+		return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n");
+	return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n");
+}
+
+static ssize_t store_control_state(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	long unsigned int val;
+	if (strcmp(buf, "booting\n") == 0)
+		val = LEGACY_BOOTING;
+	else if (strcmp(buf, "suspend\n") == 0)
+		val = LEGACY_SUSPEND;
+	else if (interface == LEGACY)
+		val = LEGACY_RUNNING;
+	else
+		val = WMAX_RUNNING;
+	lighting_control_state = val;
+	pr_debug("alienware-wmi: updated control state to %d\n",
+		 lighting_control_state);
+	return count;
+}
+
+static DEVICE_ATTR(lighting_control_state, 0644, show_control_state,
+		   store_control_state);
+
+static int alienware_zone_init(struct platform_device *dev)
+{
+	int i;
+	char buffer[10];
+	char *name;
+
+	if (interface == WMAX) {
+		lighting_control_state = WMAX_RUNNING;
+	} else if (interface == LEGACY) {
+		lighting_control_state = LEGACY_RUNNING;
+	}
+	global_led.max_brightness = 0x0F;
+	global_brightness = global_led.max_brightness;
+
+	/*
+	 *      - zone_dev_attrs num_zones + 1 is for individual zones and then
+	 *        null terminated
+	 *      - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
+	 *        the lighting control + null terminated
+	 *      - zone_data num_zones is for the distinct zones
+	 */
+	zone_dev_attrs =
+	    kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1),
+		    GFP_KERNEL);
+	if (!zone_dev_attrs)
+		return -ENOMEM;
+
+	zone_attrs =
+	    kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2),
+		    GFP_KERNEL);
+	if (!zone_attrs)
+		return -ENOMEM;
+
+	zone_data =
+	    kzalloc(sizeof(struct platform_zone) * (quirks->num_zones),
+		    GFP_KERNEL);
+	if (!zone_data)
+		return -ENOMEM;
+
+	for (i = 0; i < quirks->num_zones; i++) {
+		sprintf(buffer, "zone%02X", i);
+		name = kstrdup(buffer, GFP_KERNEL);
+		if (name == NULL)
+			return 1;
+		sysfs_attr_init(&zone_dev_attrs[i].attr);
+		zone_dev_attrs[i].attr.name = name;
+		zone_dev_attrs[i].attr.mode = 0644;
+		zone_dev_attrs[i].show = zone_show;
+		zone_dev_attrs[i].store = zone_set;
+		zone_data[i].location = i;
+		zone_attrs[i] = &zone_dev_attrs[i].attr;
+		zone_data[i].attr = &zone_dev_attrs[i];
+	}
+	zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;
+	zone_attribute_group.attrs = zone_attrs;
+
+	led_classdev_register(&dev->dev, &global_led);
+
+	return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);
+}
+
+static void alienware_zone_exit(struct platform_device *dev)
+{
+	sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);
+	led_classdev_unregister(&global_led);
+	if (zone_dev_attrs) {
+		int i;
+		for (i = 0; i < quirks->num_zones; i++)
+			kfree(zone_dev_attrs[i].attr.name);
+	}
+	kfree(zone_dev_attrs);
+	kfree(zone_data);
+	kfree(zone_attrs);
+}
+
+/*
+	The HDMI mux sysfs node indicates the status of the HDMI input mux.
+	It can toggle between standard system GPU output and HDMI input.
+*/
+static acpi_status alienware_hdmi_command(struct hdmi_args *in_args,
+					  u32 command, int *out_data)
+{
+	acpi_status status;
+	union acpi_object *obj;
+	struct acpi_buffer input;
+	struct acpi_buffer output;
+
+	input.length = (acpi_size) sizeof(*in_args);
+	input.pointer = in_args;
+	if (out_data != NULL) {
+		output.length = ACPI_ALLOCATE_BUFFER;
+		output.pointer = NULL;
+		status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
+					     command, &input, &output);
+	} else
+		status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
+					     command, &input, NULL);
+
+	if (ACPI_SUCCESS(status) && out_data != NULL) {
+		obj = (union acpi_object *)output.pointer;
+		if (obj && obj->type == ACPI_TYPE_INTEGER)
+			*out_data = (u32) obj->integer.value;
+	}
+	return status;
+
+}
+
+static ssize_t show_hdmi_cable(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	acpi_status status;
+	u32 out_data;
+	struct hdmi_args in_args = {
+		.arg = 0,
+	};
+	status =
+	    alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_CABLE,
+				   (u32 *) &out_data);
+	if (ACPI_SUCCESS(status)) {
+		if (out_data == 0)
+			return scnprintf(buf, PAGE_SIZE,
+					 "[unconnected] connected unknown\n");
+		else if (out_data == 1)
+			return scnprintf(buf, PAGE_SIZE,
+					 "unconnected [connected] unknown\n");
+	}
+	pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
+	return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
+}
+
+static ssize_t show_hdmi_source(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	acpi_status status;
+	u32 out_data;
+	struct hdmi_args in_args = {
+		.arg = 0,
+	};
+	status =
+	    alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_STATUS,
+				   (u32 *) &out_data);
+
+	if (ACPI_SUCCESS(status)) {
+		if (out_data == 1)
+			return scnprintf(buf, PAGE_SIZE,
+					 "[input] gpu unknown\n");
+		else if (out_data == 2)
+			return scnprintf(buf, PAGE_SIZE,
+					 "input [gpu] unknown\n");
+	}
+	pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data);
+	return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n");
+}
+
+static ssize_t toggle_hdmi_source(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	acpi_status status;
+	struct hdmi_args args;
+	if (strcmp(buf, "gpu\n") == 0)
+		args.arg = 1;
+	else if (strcmp(buf, "input\n") == 0)
+		args.arg = 2;
+	else
+		args.arg = 3;
+	pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
+
+	status = alienware_hdmi_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
+
+	if (ACPI_FAILURE(status))
+		pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
+		       status);
+	return count;
+}
+
+static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);
+static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,
+		   toggle_hdmi_source);
+
+static struct attribute *hdmi_attrs[] = {
+	&dev_attr_cable.attr,
+	&dev_attr_source.attr,
+	NULL,
+};
+
+static struct attribute_group hdmi_attribute_group = {
+	.name = "hdmi",
+	.attrs = hdmi_attrs,
+};
+
+static void remove_hdmi(struct platform_device *dev)
+{
+	if (quirks->hdmi_mux > 0)
+		sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);
+}
+
+static int create_hdmi(struct platform_device *dev)
+{
+	int ret;
+
+	ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
+	if (ret)
+		goto error_create_hdmi;
+	return 0;
+
+error_create_hdmi:
+	remove_hdmi(dev);
+	return ret;
+}
+
+static int __init alienware_wmi_init(void)
+{
+	int ret;
+
+	if (wmi_has_guid(LEGACY_CONTROL_GUID))
+		interface = LEGACY;
+	else if (wmi_has_guid(WMAX_CONTROL_GUID))
+		interface = WMAX;
+	else {
+		pr_warn("alienware-wmi: No known WMI GUID found\n");
+		return -ENODEV;
+	}
+
+	dmi_check_system(alienware_quirks);
+	if (quirks == NULL)
+		quirks = &quirk_unknown;
+
+	ret = platform_driver_register(&platform_driver);
+	if (ret)
+		goto fail_platform_driver;
+	platform_device = platform_device_alloc("alienware-wmi", -1);
+	if (!platform_device) {
+		ret = -ENOMEM;
+		goto fail_platform_device1;
+	}
+	ret = platform_device_add(platform_device);
+	if (ret)
+		goto fail_platform_device2;
+
+	if (quirks->hdmi_mux > 0) {
+		ret = create_hdmi(platform_device);
+		if (ret)
+			goto fail_prep_hdmi;
+	}
+
+	ret = alienware_zone_init(platform_device);
+	if (ret)
+		goto fail_prep_zones;
+
+	return 0;
+
+fail_prep_zones:
+	alienware_zone_exit(platform_device);
+fail_prep_hdmi:
+	platform_device_del(platform_device);
+fail_platform_device2:
+	platform_device_put(platform_device);
+fail_platform_device1:
+	platform_driver_unregister(&platform_driver);
+fail_platform_driver:
+	return ret;
+}
+
+module_init(alienware_wmi_init);
+
+static void __exit alienware_wmi_exit(void)
+{
+	if (platform_device) {
+		alienware_zone_exit(platform_device);
+		remove_hdmi(platform_device);
+		platform_device_unregister(platform_device);
+		platform_driver_unregister(&platform_driver);
+	}
+}
+
+module_exit(alienware_wmi_exit);
diff --git a/drivers/platform/x86/amilo-rfkill.c b/drivers/platform/x86/amilo-rfkill.c
new file mode 100644
index 0000000..0157625
--- /dev/null
+++ b/drivers/platform/x86/amilo-rfkill.c
@@ -0,0 +1,182 @@
+/*
+ * Support for rfkill on some Fujitsu-Siemens Amilo laptops.
+ * Copyright 2011 Ben Hutchings.
+ *
+ * Based in part on the fsam7440 driver, which is:
+ * Copyright 2005 Alejandro Vidal Mata & Javier Vidal Mata.
+ * and on the fsaa1655g driver, which is:
+ * Copyright 2006 Martin Večeřa.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/i8042.h>
+#include <linux/io.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+
+/*
+ * These values were obtained from disassembling and debugging the
+ * PM.exe program installed in the Fujitsu-Siemens AMILO A1655G
+ */
+#define A1655_WIFI_COMMAND	0x10C5
+#define A1655_WIFI_ON		0x25
+#define A1655_WIFI_OFF		0x45
+
+static int amilo_a1655_rfkill_set_block(void *data, bool blocked)
+{
+	u8 param = blocked ? A1655_WIFI_OFF : A1655_WIFI_ON;
+	int rc;
+
+	i8042_lock_chip();
+	rc = i8042_command(&param, A1655_WIFI_COMMAND);
+	i8042_unlock_chip();
+	return rc;
+}
+
+static const struct rfkill_ops amilo_a1655_rfkill_ops = {
+	.set_block = amilo_a1655_rfkill_set_block
+};
+
+/*
+ * These values were obtained from disassembling the PM.exe program
+ * installed in the Fujitsu-Siemens AMILO M 7440
+ */
+#define M7440_PORT1		0x118f
+#define M7440_PORT2		0x118e
+#define M7440_RADIO_ON1		0x12
+#define M7440_RADIO_ON2		0x80
+#define M7440_RADIO_OFF1	0x10
+#define M7440_RADIO_OFF2	0x00
+
+static int amilo_m7440_rfkill_set_block(void *data, bool blocked)
+{
+	u8 val1 = blocked ? M7440_RADIO_OFF1 : M7440_RADIO_ON1;
+	u8 val2 = blocked ? M7440_RADIO_OFF2 : M7440_RADIO_ON2;
+
+	outb(val1, M7440_PORT1);
+	outb(val2, M7440_PORT2);
+
+	/* Check whether the state has changed correctly */
+	if (inb(M7440_PORT1) != val1 || inb(M7440_PORT2) != val2)
+		return -EIO;
+
+	return 0;
+}
+
+static const struct rfkill_ops amilo_m7440_rfkill_ops = {
+	.set_block = amilo_m7440_rfkill_set_block
+};
+
+static const struct dmi_system_id amilo_rfkill_id_table[] = {
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_BOARD_NAME, "AMILO A1655"),
+		},
+		.driver_data = (void *)&amilo_a1655_rfkill_ops
+	},
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_BOARD_NAME, "AMILO L1310"),
+		},
+		.driver_data = (void *)&amilo_a1655_rfkill_ops
+	},
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_BOARD_NAME, "AMILO M7440"),
+		},
+		.driver_data = (void *)&amilo_m7440_rfkill_ops
+	},
+	{}
+};
+
+static struct platform_device *amilo_rfkill_pdev;
+static struct rfkill *amilo_rfkill_dev;
+
+static int amilo_rfkill_probe(struct platform_device *device)
+{
+	int rc;
+	const struct dmi_system_id *system_id =
+		dmi_first_match(amilo_rfkill_id_table);
+
+	if (!system_id)
+		return -ENXIO;
+
+	amilo_rfkill_dev = rfkill_alloc(KBUILD_MODNAME, &device->dev,
+					RFKILL_TYPE_WLAN,
+					system_id->driver_data, NULL);
+	if (!amilo_rfkill_dev)
+		return -ENOMEM;
+
+	rc = rfkill_register(amilo_rfkill_dev);
+	if (rc)
+		goto fail;
+
+	return 0;
+
+fail:
+	rfkill_destroy(amilo_rfkill_dev);
+	return rc;
+}
+
+static int amilo_rfkill_remove(struct platform_device *device)
+{
+	rfkill_unregister(amilo_rfkill_dev);
+	rfkill_destroy(amilo_rfkill_dev);
+	return 0;
+}
+
+static struct platform_driver amilo_rfkill_driver = {
+	.driver = {
+		.name	= KBUILD_MODNAME,
+	},
+	.probe	= amilo_rfkill_probe,
+	.remove	= amilo_rfkill_remove,
+};
+
+static int __init amilo_rfkill_init(void)
+{
+	int rc;
+
+	if (dmi_first_match(amilo_rfkill_id_table) == NULL)
+		return -ENODEV;
+
+	rc = platform_driver_register(&amilo_rfkill_driver);
+	if (rc)
+		return rc;
+
+	amilo_rfkill_pdev = platform_device_register_simple(KBUILD_MODNAME, -1,
+							    NULL, 0);
+	if (IS_ERR(amilo_rfkill_pdev)) {
+		rc = PTR_ERR(amilo_rfkill_pdev);
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	platform_driver_unregister(&amilo_rfkill_driver);
+	return rc;
+}
+
+static void __exit amilo_rfkill_exit(void)
+{
+	platform_device_unregister(amilo_rfkill_pdev);
+	platform_driver_unregister(&amilo_rfkill_driver);
+}
+
+MODULE_AUTHOR("Ben Hutchings <ben@decadent.org.uk>");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(dmi, amilo_rfkill_id_table);
+
+module_init(amilo_rfkill_init);
+module_exit(amilo_rfkill_exit);
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
new file mode 100644
index 0000000..976efeb
--- /dev/null
+++ b/drivers/platform/x86/apple-gmux.c
@@ -0,0 +1,675 @@
+/*
+ *  Gmux driver for Apple laptops
+ *
+ *  Copyright (C) Canonical Ltd. <seth.forshee@canonical.com>
+ *  Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de>
+ *
+ *  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
+ *  published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/backlight.h>
+#include <linux/acpi.h>
+#include <linux/pnp.h>
+#include <linux/apple_bl.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/vga_switcheroo.h>
+#include <linux/vgaarb.h>
+#include <acpi/video.h>
+#include <asm/io.h>
+
+struct apple_gmux_data {
+	unsigned long iostart;
+	unsigned long iolen;
+	bool indexed;
+	struct mutex index_lock;
+
+	struct pci_dev *pdev;
+	struct backlight_device *bdev;
+
+	/* switcheroo data */
+	acpi_handle dhandle;
+	int gpe;
+	enum vga_switcheroo_client_id resume_client_id;
+	enum vga_switcheroo_state power_state;
+	struct completion powerchange_done;
+};
+
+static struct apple_gmux_data *apple_gmux_data;
+
+/*
+ * gmux port offsets. Many of these are not yet used, but may be in the
+ * future, and it's useful to have them documented here anyhow.
+ */
+#define GMUX_PORT_VERSION_MAJOR		0x04
+#define GMUX_PORT_VERSION_MINOR		0x05
+#define GMUX_PORT_VERSION_RELEASE	0x06
+#define GMUX_PORT_SWITCH_DISPLAY	0x10
+#define GMUX_PORT_SWITCH_GET_DISPLAY	0x11
+#define GMUX_PORT_INTERRUPT_ENABLE	0x14
+#define GMUX_PORT_INTERRUPT_STATUS	0x16
+#define GMUX_PORT_SWITCH_DDC		0x28
+#define GMUX_PORT_SWITCH_EXTERNAL	0x40
+#define GMUX_PORT_SWITCH_GET_EXTERNAL	0x41
+#define GMUX_PORT_DISCRETE_POWER	0x50
+#define GMUX_PORT_MAX_BRIGHTNESS	0x70
+#define GMUX_PORT_BRIGHTNESS		0x74
+#define GMUX_PORT_VALUE			0xc2
+#define GMUX_PORT_READ			0xd0
+#define GMUX_PORT_WRITE			0xd4
+
+#define GMUX_MIN_IO_LEN			(GMUX_PORT_BRIGHTNESS + 4)
+
+#define GMUX_INTERRUPT_ENABLE		0xff
+#define GMUX_INTERRUPT_DISABLE		0x00
+
+#define GMUX_INTERRUPT_STATUS_ACTIVE	0
+#define GMUX_INTERRUPT_STATUS_DISPLAY	(1 << 0)
+#define GMUX_INTERRUPT_STATUS_POWER	(1 << 2)
+#define GMUX_INTERRUPT_STATUS_HOTPLUG	(1 << 3)
+
+#define GMUX_BRIGHTNESS_MASK		0x00ffffff
+#define GMUX_MAX_BRIGHTNESS		GMUX_BRIGHTNESS_MASK
+
+static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port)
+{
+	return inb(gmux_data->iostart + port);
+}
+
+static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port,
+			       u8 val)
+{
+	outb(val, gmux_data->iostart + port);
+}
+
+static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port)
+{
+	return inl(gmux_data->iostart + port);
+}
+
+static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port,
+			     u32 val)
+{
+	int i;
+	u8 tmpval;
+
+	for (i = 0; i < 4; i++) {
+		tmpval = (val >> (i * 8)) & 0xff;
+		outb(tmpval, gmux_data->iostart + port + i);
+	}
+}
+
+static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data)
+{
+	int i = 200;
+	u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+
+	while (i && (gwr & 0x01)) {
+		inb(gmux_data->iostart + GMUX_PORT_READ);
+		gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+		udelay(100);
+		i--;
+	}
+
+	return !!i;
+}
+
+static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data)
+{
+	int i = 200;
+	u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+
+	while (i && !(gwr & 0x01)) {
+		gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+		udelay(100);
+		i--;
+	}
+
+	if (gwr & 0x01)
+		inb(gmux_data->iostart + GMUX_PORT_READ);
+
+	return !!i;
+}
+
+static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port)
+{
+	u8 val;
+
+	mutex_lock(&gmux_data->index_lock);
+	gmux_index_wait_ready(gmux_data);
+	outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
+	gmux_index_wait_complete(gmux_data);
+	val = inb(gmux_data->iostart + GMUX_PORT_VALUE);
+	mutex_unlock(&gmux_data->index_lock);
+
+	return val;
+}
+
+static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port,
+			      u8 val)
+{
+	mutex_lock(&gmux_data->index_lock);
+	outb(val, gmux_data->iostart + GMUX_PORT_VALUE);
+	gmux_index_wait_ready(gmux_data);
+	outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
+	gmux_index_wait_complete(gmux_data);
+	mutex_unlock(&gmux_data->index_lock);
+}
+
+static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port)
+{
+	u32 val;
+
+	mutex_lock(&gmux_data->index_lock);
+	gmux_index_wait_ready(gmux_data);
+	outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
+	gmux_index_wait_complete(gmux_data);
+	val = inl(gmux_data->iostart + GMUX_PORT_VALUE);
+	mutex_unlock(&gmux_data->index_lock);
+
+	return val;
+}
+
+static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port,
+			       u32 val)
+{
+	int i;
+	u8 tmpval;
+
+	mutex_lock(&gmux_data->index_lock);
+
+	for (i = 0; i < 4; i++) {
+		tmpval = (val >> (i * 8)) & 0xff;
+		outb(tmpval, gmux_data->iostart + GMUX_PORT_VALUE + i);
+	}
+
+	gmux_index_wait_ready(gmux_data);
+	outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
+	gmux_index_wait_complete(gmux_data);
+	mutex_unlock(&gmux_data->index_lock);
+}
+
+static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
+{
+	if (gmux_data->indexed)
+		return gmux_index_read8(gmux_data, port);
+	else
+		return gmux_pio_read8(gmux_data, port);
+}
+
+static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val)
+{
+	if (gmux_data->indexed)
+		gmux_index_write8(gmux_data, port, val);
+	else
+		gmux_pio_write8(gmux_data, port, val);
+}
+
+static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
+{
+	if (gmux_data->indexed)
+		return gmux_index_read32(gmux_data, port);
+	else
+		return gmux_pio_read32(gmux_data, port);
+}
+
+static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
+			     u32 val)
+{
+	if (gmux_data->indexed)
+		gmux_index_write32(gmux_data, port, val);
+	else
+		gmux_pio_write32(gmux_data, port, val);
+}
+
+static bool gmux_is_indexed(struct apple_gmux_data *gmux_data)
+{
+	u16 val;
+
+	outb(0xaa, gmux_data->iostart + 0xcc);
+	outb(0x55, gmux_data->iostart + 0xcd);
+	outb(0x00, gmux_data->iostart + 0xce);
+
+	val = inb(gmux_data->iostart + 0xcc) |
+		(inb(gmux_data->iostart + 0xcd) << 8);
+
+	if (val == 0x55aa)
+		return true;
+
+	return false;
+}
+
+static int gmux_get_brightness(struct backlight_device *bd)
+{
+	struct apple_gmux_data *gmux_data = bl_get_data(bd);
+	return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) &
+	       GMUX_BRIGHTNESS_MASK;
+}
+
+static int gmux_update_status(struct backlight_device *bd)
+{
+	struct apple_gmux_data *gmux_data = bl_get_data(bd);
+	u32 brightness = bd->props.brightness;
+
+	if (bd->props.state & BL_CORE_SUSPENDED)
+		return 0;
+
+	gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
+
+	return 0;
+}
+
+static const struct backlight_ops gmux_bl_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.get_brightness = gmux_get_brightness,
+	.update_status = gmux_update_status,
+};
+
+static int gmux_switchto(enum vga_switcheroo_client_id id)
+{
+	if (id == VGA_SWITCHEROO_IGD) {
+		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1);
+		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 2);
+		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 2);
+	} else {
+		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2);
+		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 3);
+		gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3);
+	}
+
+	return 0;
+}
+
+static int gmux_set_discrete_state(struct apple_gmux_data *gmux_data,
+				   enum vga_switcheroo_state state)
+{
+	reinit_completion(&gmux_data->powerchange_done);
+
+	if (state == VGA_SWITCHEROO_ON) {
+		gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1);
+		gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 3);
+		pr_debug("Discrete card powered up\n");
+	} else {
+		gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1);
+		gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 0);
+		pr_debug("Discrete card powered down\n");
+	}
+
+	gmux_data->power_state = state;
+
+	if (gmux_data->gpe >= 0 &&
+	    !wait_for_completion_interruptible_timeout(&gmux_data->powerchange_done,
+						       msecs_to_jiffies(200)))
+		pr_warn("Timeout waiting for gmux switch to complete\n");
+
+	return 0;
+}
+
+static int gmux_set_power_state(enum vga_switcheroo_client_id id,
+				enum vga_switcheroo_state state)
+{
+	if (id == VGA_SWITCHEROO_IGD)
+		return 0;
+
+	return gmux_set_discrete_state(apple_gmux_data, state);
+}
+
+static int gmux_get_client_id(struct pci_dev *pdev)
+{
+	/*
+	 * Early Macbook Pros with switchable graphics use nvidia
+	 * integrated graphics. Hardcode that the 9400M is integrated.
+	 */
+	if (pdev->vendor == PCI_VENDOR_ID_INTEL)
+		return VGA_SWITCHEROO_IGD;
+	else if (pdev->vendor == PCI_VENDOR_ID_NVIDIA &&
+		 pdev->device == 0x0863)
+		return VGA_SWITCHEROO_IGD;
+	else
+		return VGA_SWITCHEROO_DIS;
+}
+
+static enum vga_switcheroo_client_id
+gmux_active_client(struct apple_gmux_data *gmux_data)
+{
+	if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) == 2)
+		return VGA_SWITCHEROO_IGD;
+
+	return VGA_SWITCHEROO_DIS;
+}
+
+static const struct vga_switcheroo_handler gmux_handler = {
+	.switchto = gmux_switchto,
+	.power_state = gmux_set_power_state,
+	.get_client_id = gmux_get_client_id,
+};
+
+static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data)
+{
+	gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE,
+		    GMUX_INTERRUPT_DISABLE);
+}
+
+static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data)
+{
+	gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE,
+		    GMUX_INTERRUPT_ENABLE);
+}
+
+static inline u8 gmux_interrupt_get_status(struct apple_gmux_data *gmux_data)
+{
+	return gmux_read8(gmux_data, GMUX_PORT_INTERRUPT_STATUS);
+}
+
+static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data)
+{
+	u8 status;
+
+	/* to clear interrupts write back current status */
+	status = gmux_interrupt_get_status(gmux_data);
+	gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status);
+}
+
+static void gmux_notify_handler(acpi_handle device, u32 value, void *context)
+{
+	u8 status;
+	struct pnp_dev *pnp = (struct pnp_dev *)context;
+	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+
+	status = gmux_interrupt_get_status(gmux_data);
+	gmux_disable_interrupts(gmux_data);
+	pr_debug("Notify handler called: status %d\n", status);
+
+	gmux_clear_interrupts(gmux_data);
+	gmux_enable_interrupts(gmux_data);
+
+	if (status & GMUX_INTERRUPT_STATUS_POWER)
+		complete(&gmux_data->powerchange_done);
+}
+
+static int gmux_suspend(struct device *dev)
+{
+	struct pnp_dev *pnp = to_pnp_dev(dev);
+	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+
+	gmux_data->resume_client_id = gmux_active_client(gmux_data);
+	gmux_disable_interrupts(gmux_data);
+	return 0;
+}
+
+static int gmux_resume(struct device *dev)
+{
+	struct pnp_dev *pnp = to_pnp_dev(dev);
+	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+
+	gmux_enable_interrupts(gmux_data);
+	gmux_switchto(gmux_data->resume_client_id);
+	if (gmux_data->power_state == VGA_SWITCHEROO_OFF)
+		gmux_set_discrete_state(gmux_data, gmux_data->power_state);
+	return 0;
+}
+
+static struct pci_dev *gmux_get_io_pdev(void)
+{
+	struct pci_dev *pdev = NULL;
+
+	while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
+		u16 cmd;
+
+		pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+		if (!(cmd & PCI_COMMAND_IO))
+			continue;
+
+		return pdev;
+	}
+
+	return NULL;
+}
+
+static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
+{
+	struct apple_gmux_data *gmux_data;
+	struct resource *res;
+	struct backlight_properties props;
+	struct backlight_device *bdev;
+	u8 ver_major, ver_minor, ver_release;
+	int ret = -ENXIO;
+	acpi_status status;
+	unsigned long long gpe;
+	struct pci_dev *pdev = NULL;
+
+	if (apple_gmux_data)
+		return -EBUSY;
+
+	gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
+	if (!gmux_data)
+		return -ENOMEM;
+	pnp_set_drvdata(pnp, gmux_data);
+
+	res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
+	if (!res) {
+		pr_err("Failed to find gmux I/O resource\n");
+		goto err_free;
+	}
+
+	gmux_data->iostart = res->start;
+	gmux_data->iolen = res->end - res->start;
+
+	if (gmux_data->iolen < GMUX_MIN_IO_LEN) {
+		pr_err("gmux I/O region too small (%lu < %u)\n",
+		       gmux_data->iolen, GMUX_MIN_IO_LEN);
+		goto err_free;
+	}
+
+	if (!request_region(gmux_data->iostart, gmux_data->iolen,
+			    "Apple gmux")) {
+		pr_err("gmux I/O already in use\n");
+		goto err_free;
+	}
+
+	/*
+	 * Invalid version information may indicate either that the gmux
+	 * device isn't present or that it's a new one that uses indexed
+	 * io
+	 */
+
+	ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
+	ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
+	ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
+	if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
+		if (gmux_is_indexed(gmux_data)) {
+			u32 version;
+			mutex_init(&gmux_data->index_lock);
+			gmux_data->indexed = true;
+			version = gmux_read32(gmux_data,
+				GMUX_PORT_VERSION_MAJOR);
+			ver_major = (version >> 24) & 0xff;
+			ver_minor = (version >> 16) & 0xff;
+			ver_release = (version >> 8) & 0xff;
+		} else {
+			pr_info("gmux device not present or IO disabled\n");
+			ret = -ENODEV;
+			goto err_release;
+		}
+	}
+	pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor,
+		ver_release, (gmux_data->indexed ? "indexed" : "classic"));
+
+	/*
+	 * Apple systems with gmux are EFI based and normally don't use
+	 * VGA. In addition changing IO+MEM ownership between IGP and dGPU
+	 * disables IO/MEM used for backlight control on some systems.
+	 * Lock IO+MEM to GPU with active IO to prevent switch.
+	 */
+	pdev = gmux_get_io_pdev();
+	if (pdev && vga_tryget(pdev,
+			       VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM)) {
+		pr_err("IO+MEM vgaarb-locking for PCI:%s failed\n",
+			pci_name(pdev));
+		ret = -EBUSY;
+		goto err_release;
+	} else if (pdev)
+		pr_info("locked IO for PCI:%s\n", pci_name(pdev));
+	gmux_data->pdev = pdev;
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
+
+	/*
+	 * Currently it's assumed that the maximum brightness is less than
+	 * 2^24 for compatibility with old gmux versions. Cap the max
+	 * brightness at this value, but print a warning if the hardware
+	 * reports something higher so that it can be fixed.
+	 */
+	if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
+		props.max_brightness = GMUX_MAX_BRIGHTNESS;
+
+	bdev = backlight_device_register("gmux_backlight", &pnp->dev,
+					 gmux_data, &gmux_bl_ops, &props);
+	if (IS_ERR(bdev)) {
+		ret = PTR_ERR(bdev);
+		goto err_release;
+	}
+
+	gmux_data->bdev = bdev;
+	bdev->props.brightness = gmux_get_brightness(bdev);
+	backlight_update_status(bdev);
+
+	/*
+	 * The backlight situation on Macs is complicated. If the gmux is
+	 * present it's the best choice, because it always works for
+	 * backlight control and supports more levels than other options.
+	 * Disable the other backlight choices.
+	 */
+	acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+	apple_bl_unregister();
+
+	gmux_data->power_state = VGA_SWITCHEROO_ON;
+
+	gmux_data->dhandle = ACPI_HANDLE(&pnp->dev);
+	if (!gmux_data->dhandle) {
+		pr_err("Cannot find acpi handle for pnp device %s\n",
+		       dev_name(&pnp->dev));
+		ret = -ENODEV;
+		goto err_notify;
+	}
+
+	status = acpi_evaluate_integer(gmux_data->dhandle, "GMGP", NULL, &gpe);
+	if (ACPI_SUCCESS(status)) {
+		gmux_data->gpe = (int)gpe;
+
+		status = acpi_install_notify_handler(gmux_data->dhandle,
+						     ACPI_DEVICE_NOTIFY,
+						     &gmux_notify_handler, pnp);
+		if (ACPI_FAILURE(status)) {
+			pr_err("Install notify handler failed: %s\n",
+			       acpi_format_exception(status));
+			ret = -ENODEV;
+			goto err_notify;
+		}
+
+		status = acpi_enable_gpe(NULL, gmux_data->gpe);
+		if (ACPI_FAILURE(status)) {
+			pr_err("Cannot enable gpe: %s\n",
+			       acpi_format_exception(status));
+			goto err_enable_gpe;
+		}
+	} else {
+		pr_warn("No GPE found for gmux\n");
+		gmux_data->gpe = -1;
+	}
+
+	if (vga_switcheroo_register_handler(&gmux_handler)) {
+		ret = -ENODEV;
+		goto err_register_handler;
+	}
+
+	init_completion(&gmux_data->powerchange_done);
+	apple_gmux_data = gmux_data;
+	gmux_enable_interrupts(gmux_data);
+
+	return 0;
+
+err_register_handler:
+	if (gmux_data->gpe >= 0)
+		acpi_disable_gpe(NULL, gmux_data->gpe);
+err_enable_gpe:
+	if (gmux_data->gpe >= 0)
+		acpi_remove_notify_handler(gmux_data->dhandle,
+					   ACPI_DEVICE_NOTIFY,
+					   &gmux_notify_handler);
+err_notify:
+	backlight_device_unregister(bdev);
+err_release:
+	if (gmux_data->pdev)
+		vga_put(gmux_data->pdev,
+			VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM);
+	pci_dev_put(pdev);
+	release_region(gmux_data->iostart, gmux_data->iolen);
+err_free:
+	kfree(gmux_data);
+	return ret;
+}
+
+static void gmux_remove(struct pnp_dev *pnp)
+{
+	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+
+	vga_switcheroo_unregister_handler();
+	gmux_disable_interrupts(gmux_data);
+	if (gmux_data->gpe >= 0) {
+		acpi_disable_gpe(NULL, gmux_data->gpe);
+		acpi_remove_notify_handler(gmux_data->dhandle,
+					   ACPI_DEVICE_NOTIFY,
+					   &gmux_notify_handler);
+	}
+
+	if (gmux_data->pdev) {
+		vga_put(gmux_data->pdev,
+			VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM);
+		pci_dev_put(gmux_data->pdev);
+	}
+	backlight_device_unregister(gmux_data->bdev);
+
+	release_region(gmux_data->iostart, gmux_data->iolen);
+	apple_gmux_data = NULL;
+	kfree(gmux_data);
+
+	acpi_video_register();
+	apple_bl_register();
+}
+
+static const struct pnp_device_id gmux_device_ids[] = {
+	{"APP000B", 0},
+	{"", 0}
+};
+
+static const struct dev_pm_ops gmux_dev_pm_ops = {
+	.suspend = gmux_suspend,
+	.resume = gmux_resume,
+};
+
+static struct pnp_driver gmux_pnp_driver = {
+	.name		= "apple-gmux",
+	.probe		= gmux_probe,
+	.remove		= gmux_remove,
+	.id_table	= gmux_device_ids,
+	.driver		= {
+			.pm = &gmux_dev_pm_ops,
+	},
+};
+
+module_pnp_driver(gmux_pnp_driver);
+MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
+MODULE_DESCRIPTION("Apple Gmux Driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pnp, gmux_device_ids);
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
new file mode 100644
index 0000000..f2b5d0a
--- /dev/null
+++ b/drivers/platform/x86/asus-laptop.c
@@ -0,0 +1,2003 @@
+/*
+ *  asus-laptop.c - Asus Laptop Support
+ *
+ *
+ *  Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor
+ *  Copyright (C) 2006-2007 Corentin Chary
+ *  Copyright (C) 2011 Wind River Systems
+ *
+ *  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
+ *
+ *
+ *  The development page for this driver is located at
+ *  http://sourceforge.net/projects/acpi4asus/
+ *
+ *  Credits:
+ *  Pontus Fuchs   - Helper functions, cleanup
+ *  Johann Wiesner - Small compile fixes
+ *  John Belmonte  - ACPI code for Toshiba laptop was a good starting point.
+ *  Eric Burghard  - LED display support for W1N
+ *  Josh Green     - Light Sens support
+ *  Thomas Tuttle  - His first patch for led support was very helpful
+ *  Sam Lin        - GPS support
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/proc_fs.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/input-polldev.h>
+#include <linux/rfkill.h>
+#include <linux/slab.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <acpi/video.h>
+
+#define ASUS_LAPTOP_VERSION	"0.42"
+
+#define ASUS_LAPTOP_NAME	"Asus Laptop Support"
+#define ASUS_LAPTOP_CLASS	"hotkey"
+#define ASUS_LAPTOP_DEVICE_NAME	"Hotkey"
+#define ASUS_LAPTOP_FILE	KBUILD_MODNAME
+#define ASUS_LAPTOP_PREFIX	"\\_SB.ATKD."
+
+MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary");
+MODULE_DESCRIPTION(ASUS_LAPTOP_NAME);
+MODULE_LICENSE("GPL");
+
+/*
+ * WAPF defines the behavior of the Fn+Fx wlan key
+ * The significance of values is yet to be found, but
+ * most of the time:
+ * Bit | Bluetooth | WLAN
+ *  0  | Hardware  | Hardware
+ *  1  | Hardware  | Software
+ *  4  | Software  | Software
+ */
+static uint wapf = 1;
+module_param(wapf, uint, 0444);
+MODULE_PARM_DESC(wapf, "WAPF value");
+
+static char *wled_type = "unknown";
+static char *bled_type = "unknown";
+
+module_param(wled_type, charp, 0444);
+MODULE_PARM_DESC(wled_type, "Set the wled type on boot "
+		 "(unknown, led or rfkill). "
+		 "default is unknown");
+
+module_param(bled_type, charp, 0444);
+MODULE_PARM_DESC(bled_type, "Set the bled type on boot "
+		 "(unknown, led or rfkill). "
+		 "default is unknown");
+
+static int wlan_status = 1;
+static int bluetooth_status = 1;
+static int wimax_status = -1;
+static int wwan_status = -1;
+static int als_status;
+
+module_param(wlan_status, int, 0444);
+MODULE_PARM_DESC(wlan_status, "Set the wireless status on boot "
+		 "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+		 "default is -1");
+
+module_param(bluetooth_status, int, 0444);
+MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot "
+		 "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+		 "default is -1");
+
+module_param(wimax_status, int, 0444);
+MODULE_PARM_DESC(wimax_status, "Set the wireless status on boot "
+		 "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+		 "default is -1");
+
+module_param(wwan_status, int, 0444);
+MODULE_PARM_DESC(wwan_status, "Set the wireless status on boot "
+		 "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+		 "default is -1");
+
+module_param(als_status, int, 0444);
+MODULE_PARM_DESC(als_status, "Set the ALS status on boot "
+		 "(0 = disabled, 1 = enabled). "
+		 "default is 0");
+
+/*
+ * Some events we use, same for all Asus
+ */
+#define ATKD_BRNUP_MIN		0x10
+#define ATKD_BRNUP_MAX		0x1f
+#define ATKD_BRNDOWN_MIN	0x20
+#define ATKD_BRNDOWN_MAX	0x2f
+#define ATKD_BRNDOWN		0x20
+#define ATKD_BRNUP		0x2f
+#define ATKD_LCD_ON	0x33
+#define ATKD_LCD_OFF	0x34
+
+/*
+ * Known bits returned by \_SB.ATKD.HWRS
+ */
+#define WL_HWRS		0x80
+#define BT_HWRS		0x100
+
+/*
+ * Flags for hotk status
+ * WL_ON and BT_ON are also used for wireless_status()
+ */
+#define WL_RSTS		0x01	/* internal Wifi */
+#define BT_RSTS		0x02	/* internal Bluetooth */
+#define WM_RSTS		0x08    /* internal wimax */
+#define WW_RSTS		0x20    /* internal wwan */
+
+/* WLED and BLED type */
+#define TYPE_UNKNOWN	0
+#define TYPE_LED	1
+#define TYPE_RFKILL	2
+
+/* LED */
+#define METHOD_MLED		"MLED"
+#define METHOD_TLED		"TLED"
+#define METHOD_RLED		"RLED"	/* W1JC */
+#define METHOD_PLED		"PLED"	/* A7J */
+#define METHOD_GLED		"GLED"	/* G1, G2 (probably) */
+
+/* LEDD */
+#define METHOD_LEDD		"SLCM"
+
+/*
+ * Bluetooth and WLAN
+ * WLED and BLED are not handled like other XLED, because in some dsdt
+ * they also control the WLAN/Bluetooth device.
+ */
+#define METHOD_WLAN		"WLED"
+#define METHOD_BLUETOOTH	"BLED"
+
+/* WWAN and WIMAX */
+#define METHOD_WWAN		"GSMC"
+#define METHOD_WIMAX		"WMXC"
+
+#define METHOD_WL_STATUS	"RSTS"
+
+/* Brightness */
+#define METHOD_BRIGHTNESS_SET	"SPLV"
+#define METHOD_BRIGHTNESS_GET	"GPLV"
+
+/* Display */
+#define METHOD_SWITCH_DISPLAY	"SDSP"
+
+#define METHOD_ALS_CONTROL	"ALSC" /* Z71A Z71V */
+#define METHOD_ALS_LEVEL	"ALSL" /* Z71A Z71V */
+
+/* GPS */
+/* R2H use different handle for GPS on/off */
+#define METHOD_GPS_ON		"SDON"
+#define METHOD_GPS_OFF		"SDOF"
+#define METHOD_GPS_STATUS	"GPST"
+
+/* Keyboard light */
+#define METHOD_KBD_LIGHT_SET	"SLKB"
+#define METHOD_KBD_LIGHT_GET	"GLKB"
+
+/* For Pegatron Lucid tablet */
+#define DEVICE_NAME_PEGA	"Lucid"
+
+#define METHOD_PEGA_ENABLE	"ENPR"
+#define METHOD_PEGA_DISABLE	"DAPR"
+#define PEGA_WLAN	0x00
+#define PEGA_BLUETOOTH	0x01
+#define PEGA_WWAN	0x02
+#define PEGA_ALS	0x04
+#define PEGA_ALS_POWER	0x05
+
+#define METHOD_PEGA_READ	"RDLN"
+#define PEGA_READ_ALS_H	0x02
+#define PEGA_READ_ALS_L	0x03
+
+#define PEGA_ACCEL_NAME "pega_accel"
+#define PEGA_ACCEL_DESC "Pegatron Lucid Tablet Accelerometer"
+#define METHOD_XLRX "XLRX"
+#define METHOD_XLRY "XLRY"
+#define METHOD_XLRZ "XLRZ"
+#define PEGA_ACC_CLAMP 512 /* 1G accel is reported as ~256, so clamp to 2G */
+#define PEGA_ACC_RETRIES 3
+
+/*
+ * Define a specific led structure to keep the main structure clean
+ */
+struct asus_led {
+	int wk;
+	struct work_struct work;
+	struct led_classdev led;
+	struct asus_laptop *asus;
+	const char *method;
+};
+
+/*
+ * Same thing for rfkill
+ */
+struct asus_rfkill {
+	/* type of control. Maps to PEGA_* values or *_RSTS  */
+	int control_id;
+	struct rfkill *rfkill;
+	struct asus_laptop *asus;
+};
+
+/*
+ * This is the main structure, we can use it to store anything interesting
+ * about the hotk device
+ */
+struct asus_laptop {
+	char *name;		/* laptop name */
+
+	struct acpi_table_header *dsdt_info;
+	struct platform_device *platform_device;
+	struct acpi_device *device;		/* the device we are in */
+	struct backlight_device *backlight_device;
+
+	struct input_dev *inputdev;
+	struct key_entry *keymap;
+	struct input_polled_dev *pega_accel_poll;
+
+	struct asus_led wled;
+	struct asus_led bled;
+	struct asus_led mled;
+	struct asus_led tled;
+	struct asus_led rled;
+	struct asus_led pled;
+	struct asus_led gled;
+	struct asus_led kled;
+	struct workqueue_struct *led_workqueue;
+
+	int wled_type;
+	int bled_type;
+	int wireless_status;
+	bool have_rsts;
+	bool is_pega_lucid;
+	bool pega_acc_live;
+	int pega_acc_x;
+	int pega_acc_y;
+	int pega_acc_z;
+
+	struct asus_rfkill wlan;
+	struct asus_rfkill bluetooth;
+	struct asus_rfkill wwan;
+	struct asus_rfkill wimax;
+	struct asus_rfkill gps;
+
+	acpi_handle handle;	/* the handle of the hotk device */
+	u32 ledd_status;	/* status of the LED display */
+	u8 light_level;		/* light sensor level */
+	u8 light_switch;	/* light sensor switch value */
+	u16 event_count[128];	/* count for each event TODO make this better */
+};
+
+static const struct key_entry asus_keymap[] = {
+	/* Lenovo SL Specific keycodes */
+	{KE_KEY, 0x02, { KEY_SCREENLOCK } },
+	{KE_KEY, 0x05, { KEY_WLAN } },
+	{KE_KEY, 0x08, { KEY_F13 } },
+	{KE_KEY, 0x09, { KEY_PROG2 } }, /* Dock */
+	{KE_KEY, 0x17, { KEY_ZOOM } },
+	{KE_KEY, 0x1f, { KEY_BATTERY } },
+	/* End of Lenovo SL Specific keycodes */
+	{KE_KEY, ATKD_BRNDOWN, { KEY_BRIGHTNESSDOWN } },
+	{KE_KEY, ATKD_BRNUP, { KEY_BRIGHTNESSUP } },
+	{KE_KEY, 0x30, { KEY_VOLUMEUP } },
+	{KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+	{KE_KEY, 0x32, { KEY_MUTE } },
+	{KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */
+	{KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */
+	{KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
+	{KE_KEY, 0x41, { KEY_NEXTSONG } },
+	{KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */
+	{KE_KEY, 0x45, { KEY_PLAYPAUSE } },
+	{KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */
+	{KE_KEY, 0x50, { KEY_EMAIL } },
+	{KE_KEY, 0x51, { KEY_WWW } },
+	{KE_KEY, 0x55, { KEY_CALC } },
+	{KE_IGNORE, 0x57, },  /* Battery mode */
+	{KE_IGNORE, 0x58, },  /* AC mode */
+	{KE_KEY, 0x5C, { KEY_SCREENLOCK } },  /* Screenlock */
+	{KE_KEY, 0x5D, { KEY_WLAN } }, /* WLAN Toggle */
+	{KE_KEY, 0x5E, { KEY_WLAN } }, /* WLAN Enable */
+	{KE_KEY, 0x5F, { KEY_WLAN } }, /* WLAN Disable */
+	{KE_KEY, 0x60, { KEY_TOUCHPAD_ON } },
+	{KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */
+	{KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */
+	{KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */
+	{KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */
+	{KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */
+	{KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */
+	{KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */
+	{KE_KEY, 0x6A, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad Fn + F9 */
+	{KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad */
+	{KE_KEY, 0x6C, { KEY_SLEEP } }, /* Suspend */
+	{KE_KEY, 0x6D, { KEY_SLEEP } }, /* Hibernate */
+	{KE_IGNORE, 0x6E, },  /* Low Battery notification */
+	{KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
+	{KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
+	{KE_KEY, 0x82, { KEY_CAMERA } },
+	{KE_KEY, 0x88, { KEY_RFKILL  } }, /* Radio Toggle Key */
+	{KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+	{KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
+	{KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
+	{KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
+	{KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */
+	{KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */
+	{KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */
+	{KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
+	{KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
+	{KE_KEY, 0x95, { KEY_MEDIA } },
+	{KE_KEY, 0x99, { KEY_PHONE } },
+	{KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
+	{KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
+	{KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
+	{KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */
+	{KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */
+	{KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
+	{KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
+	{KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+	{KE_KEY, 0xB5, { KEY_CALC } },
+	{KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
+	{KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
+	{KE_END, 0},
+};
+
+
+/*
+ * This function evaluates an ACPI method, given an int as parameter, the
+ * method is searched within the scope of the handle, can be NULL. The output
+ * of the method is written is output, which can also be NULL
+ *
+ * returns 0 if write is successful, -1 else.
+ */
+static int write_acpi_int_ret(acpi_handle handle, const char *method, int val,
+			      struct acpi_buffer *output)
+{
+	struct acpi_object_list params;	/* list of input parameters (an int) */
+	union acpi_object in_obj;	/* the only param we use */
+	acpi_status status;
+
+	if (!handle)
+		return -1;
+
+	params.count = 1;
+	params.pointer = &in_obj;
+	in_obj.type = ACPI_TYPE_INTEGER;
+	in_obj.integer.value = val;
+
+	status = acpi_evaluate_object(handle, (char *)method, &params, output);
+	if (status == AE_OK)
+		return 0;
+	else
+		return -1;
+}
+
+static int write_acpi_int(acpi_handle handle, const char *method, int val)
+{
+	return write_acpi_int_ret(handle, method, val, NULL);
+}
+
+static int acpi_check_handle(acpi_handle handle, const char *method,
+			     acpi_handle *ret)
+{
+	acpi_status status;
+
+	if (method == NULL)
+		return -ENODEV;
+
+	if (ret)
+		status = acpi_get_handle(handle, (char *)method,
+					 ret);
+	else {
+		acpi_handle dummy;
+
+		status = acpi_get_handle(handle, (char *)method,
+					 &dummy);
+	}
+
+	if (status != AE_OK) {
+		if (ret)
+			pr_warn("Error finding %s\n", method);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static bool asus_check_pega_lucid(struct asus_laptop *asus)
+{
+	return !strcmp(asus->name, DEVICE_NAME_PEGA) &&
+	   !acpi_check_handle(asus->handle, METHOD_PEGA_ENABLE, NULL) &&
+	   !acpi_check_handle(asus->handle, METHOD_PEGA_DISABLE, NULL) &&
+	   !acpi_check_handle(asus->handle, METHOD_PEGA_READ, NULL);
+}
+
+static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable)
+{
+	char *method = enable ? METHOD_PEGA_ENABLE : METHOD_PEGA_DISABLE;
+	return write_acpi_int(asus->handle, method, unit);
+}
+
+static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method)
+{
+	int i, delta;
+	unsigned long long val;
+	for (i = 0; i < PEGA_ACC_RETRIES; i++) {
+		acpi_evaluate_integer(asus->handle, method, NULL, &val);
+
+		/* The output is noisy.  From reading the ASL
+		 * dissassembly, timeout errors are returned with 1's
+		 * in the high word, and the lack of locking around
+		 * thei hi/lo byte reads means that a transition
+		 * between (for example) -1 and 0 could be read as
+		 * 0xff00 or 0x00ff. */
+		delta = abs(curr - (short)val);
+		if (delta < 128 && !(val & ~0xffff))
+			break;
+	}
+	return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP);
+}
+
+static void pega_accel_poll(struct input_polled_dev *ipd)
+{
+	struct device *parent = ipd->input->dev.parent;
+	struct asus_laptop *asus = dev_get_drvdata(parent);
+
+	/* In some cases, the very first call to poll causes a
+	 * recursive fault under the polldev worker.  This is
+	 * apparently related to very early userspace access to the
+	 * device, and perhaps a firmware bug. Fake the first report. */
+	if (!asus->pega_acc_live) {
+		asus->pega_acc_live = true;
+		input_report_abs(ipd->input, ABS_X, 0);
+		input_report_abs(ipd->input, ABS_Y, 0);
+		input_report_abs(ipd->input, ABS_Z, 0);
+		input_sync(ipd->input);
+		return;
+	}
+
+	asus->pega_acc_x = pega_acc_axis(asus, asus->pega_acc_x, METHOD_XLRX);
+	asus->pega_acc_y = pega_acc_axis(asus, asus->pega_acc_y, METHOD_XLRY);
+	asus->pega_acc_z = pega_acc_axis(asus, asus->pega_acc_z, METHOD_XLRZ);
+
+	/* Note transform, convert to "right/up/out" in the native
+	 * landscape orientation (i.e. the vector is the direction of
+	 * "real up" in the device's cartiesian coordinates). */
+	input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x);
+	input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y);
+	input_report_abs(ipd->input, ABS_Z,  asus->pega_acc_z);
+	input_sync(ipd->input);
+}
+
+static void pega_accel_exit(struct asus_laptop *asus)
+{
+	if (asus->pega_accel_poll) {
+		input_unregister_polled_device(asus->pega_accel_poll);
+		input_free_polled_device(asus->pega_accel_poll);
+	}
+	asus->pega_accel_poll = NULL;
+}
+
+static int pega_accel_init(struct asus_laptop *asus)
+{
+	int err;
+	struct input_polled_dev *ipd;
+
+	if (!asus->is_pega_lucid)
+		return -ENODEV;
+
+	if (acpi_check_handle(asus->handle, METHOD_XLRX, NULL) ||
+	    acpi_check_handle(asus->handle, METHOD_XLRY, NULL) ||
+	    acpi_check_handle(asus->handle, METHOD_XLRZ, NULL))
+		return -ENODEV;
+
+	ipd = input_allocate_polled_device();
+	if (!ipd)
+		return -ENOMEM;
+
+	ipd->poll = pega_accel_poll;
+	ipd->poll_interval = 125;
+	ipd->poll_interval_min = 50;
+	ipd->poll_interval_max = 2000;
+
+	ipd->input->name = PEGA_ACCEL_DESC;
+	ipd->input->phys = PEGA_ACCEL_NAME "/input0";
+	ipd->input->dev.parent = &asus->platform_device->dev;
+	ipd->input->id.bustype = BUS_HOST;
+
+	set_bit(EV_ABS, ipd->input->evbit);
+	input_set_abs_params(ipd->input, ABS_X,
+			     -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
+	input_set_abs_params(ipd->input, ABS_Y,
+			     -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
+	input_set_abs_params(ipd->input, ABS_Z,
+			     -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
+
+	err = input_register_polled_device(ipd);
+	if (err)
+		goto exit;
+
+	asus->pega_accel_poll = ipd;
+	return 0;
+
+exit:
+	input_free_polled_device(ipd);
+	return err;
+}
+
+/* Generic LED function */
+static int asus_led_set(struct asus_laptop *asus, const char *method,
+			 int value)
+{
+	if (!strcmp(method, METHOD_MLED))
+		value = !value;
+	else if (!strcmp(method, METHOD_GLED))
+		value = !value + 1;
+	else
+		value = !!value;
+
+	return write_acpi_int(asus->handle, method, value);
+}
+
+/*
+ * LEDs
+ */
+/* /sys/class/led handlers */
+static void asus_led_cdev_set(struct led_classdev *led_cdev,
+			 enum led_brightness value)
+{
+	struct asus_led *led = container_of(led_cdev, struct asus_led, led);
+	struct asus_laptop *asus = led->asus;
+
+	led->wk = !!value;
+	queue_work(asus->led_workqueue, &led->work);
+}
+
+static void asus_led_cdev_update(struct work_struct *work)
+{
+	struct asus_led *led = container_of(work, struct asus_led, work);
+	struct asus_laptop *asus = led->asus;
+
+	asus_led_set(asus, led->method, led->wk);
+}
+
+static enum led_brightness asus_led_cdev_get(struct led_classdev *led_cdev)
+{
+	return led_cdev->brightness;
+}
+
+/*
+ * Keyboard backlight (also a LED)
+ */
+static int asus_kled_lvl(struct asus_laptop *asus)
+{
+	unsigned long long kblv;
+	struct acpi_object_list params;
+	union acpi_object in_obj;
+	acpi_status rv;
+
+	params.count = 1;
+	params.pointer = &in_obj;
+	in_obj.type = ACPI_TYPE_INTEGER;
+	in_obj.integer.value = 2;
+
+	rv = acpi_evaluate_integer(asus->handle, METHOD_KBD_LIGHT_GET,
+				   &params, &kblv);
+	if (ACPI_FAILURE(rv)) {
+		pr_warn("Error reading kled level\n");
+		return -ENODEV;
+	}
+	return kblv;
+}
+
+static int asus_kled_set(struct asus_laptop *asus, int kblv)
+{
+	if (kblv > 0)
+		kblv = (1 << 7) | (kblv & 0x7F);
+	else
+		kblv = 0;
+
+	if (write_acpi_int(asus->handle, METHOD_KBD_LIGHT_SET, kblv)) {
+		pr_warn("Keyboard LED display write failed\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void asus_kled_cdev_set(struct led_classdev *led_cdev,
+			      enum led_brightness value)
+{
+	struct asus_led *led = container_of(led_cdev, struct asus_led, led);
+	struct asus_laptop *asus = led->asus;
+
+	led->wk = value;
+	queue_work(asus->led_workqueue, &led->work);
+}
+
+static void asus_kled_cdev_update(struct work_struct *work)
+{
+	struct asus_led *led = container_of(work, struct asus_led, work);
+	struct asus_laptop *asus = led->asus;
+
+	asus_kled_set(asus, led->wk);
+}
+
+static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev)
+{
+	struct asus_led *led = container_of(led_cdev, struct asus_led, led);
+	struct asus_laptop *asus = led->asus;
+
+	return asus_kled_lvl(asus);
+}
+
+static void asus_led_exit(struct asus_laptop *asus)
+{
+	if (!IS_ERR_OR_NULL(asus->wled.led.dev))
+		led_classdev_unregister(&asus->wled.led);
+	if (!IS_ERR_OR_NULL(asus->bled.led.dev))
+		led_classdev_unregister(&asus->bled.led);
+	if (!IS_ERR_OR_NULL(asus->mled.led.dev))
+		led_classdev_unregister(&asus->mled.led);
+	if (!IS_ERR_OR_NULL(asus->tled.led.dev))
+		led_classdev_unregister(&asus->tled.led);
+	if (!IS_ERR_OR_NULL(asus->pled.led.dev))
+		led_classdev_unregister(&asus->pled.led);
+	if (!IS_ERR_OR_NULL(asus->rled.led.dev))
+		led_classdev_unregister(&asus->rled.led);
+	if (!IS_ERR_OR_NULL(asus->gled.led.dev))
+		led_classdev_unregister(&asus->gled.led);
+	if (!IS_ERR_OR_NULL(asus->kled.led.dev))
+		led_classdev_unregister(&asus->kled.led);
+	if (asus->led_workqueue) {
+		destroy_workqueue(asus->led_workqueue);
+		asus->led_workqueue = NULL;
+	}
+}
+
+/*  Ugly macro, need to fix that later */
+static int asus_led_register(struct asus_laptop *asus,
+			     struct asus_led *led,
+			     const char *name, const char *method)
+{
+	struct led_classdev *led_cdev = &led->led;
+
+	if (!method || acpi_check_handle(asus->handle, method, NULL))
+		return 0; /* Led not present */
+
+	led->asus = asus;
+	led->method = method;
+
+	INIT_WORK(&led->work, asus_led_cdev_update);
+	led_cdev->name = name;
+	led_cdev->brightness_set = asus_led_cdev_set;
+	led_cdev->brightness_get = asus_led_cdev_get;
+	led_cdev->max_brightness = 1;
+	return led_classdev_register(&asus->platform_device->dev, led_cdev);
+}
+
+static int asus_led_init(struct asus_laptop *asus)
+{
+	int r = 0;
+
+	/*
+	 * The Pegatron Lucid has no physical leds, but all methods are
+	 * available in the DSDT...
+	 */
+	if (asus->is_pega_lucid)
+		return 0;
+
+	/*
+	 * Functions that actually update the LED's are called from a
+	 * workqueue. By doing this as separate work rather than when the LED
+	 * subsystem asks, we avoid messing with the Asus ACPI stuff during a
+	 * potentially bad time, such as a timer interrupt.
+	 */
+	asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
+	if (!asus->led_workqueue)
+		return -ENOMEM;
+
+	if (asus->wled_type == TYPE_LED)
+		r = asus_led_register(asus, &asus->wled, "asus::wlan",
+				      METHOD_WLAN);
+	if (r)
+		goto error;
+	if (asus->bled_type == TYPE_LED)
+		r = asus_led_register(asus, &asus->bled, "asus::bluetooth",
+				      METHOD_BLUETOOTH);
+	if (r)
+		goto error;
+	r = asus_led_register(asus, &asus->mled, "asus::mail", METHOD_MLED);
+	if (r)
+		goto error;
+	r = asus_led_register(asus, &asus->tled, "asus::touchpad", METHOD_TLED);
+	if (r)
+		goto error;
+	r = asus_led_register(asus, &asus->rled, "asus::record", METHOD_RLED);
+	if (r)
+		goto error;
+	r = asus_led_register(asus, &asus->pled, "asus::phone", METHOD_PLED);
+	if (r)
+		goto error;
+	r = asus_led_register(asus, &asus->gled, "asus::gaming", METHOD_GLED);
+	if (r)
+		goto error;
+	if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL) &&
+	    !acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_GET, NULL)) {
+		struct asus_led *led = &asus->kled;
+		struct led_classdev *cdev = &led->led;
+
+		led->asus = asus;
+
+		INIT_WORK(&led->work, asus_kled_cdev_update);
+		cdev->name = "asus::kbd_backlight";
+		cdev->brightness_set = asus_kled_cdev_set;
+		cdev->brightness_get = asus_kled_cdev_get;
+		cdev->max_brightness = 3;
+		r = led_classdev_register(&asus->platform_device->dev, cdev);
+	}
+error:
+	if (r)
+		asus_led_exit(asus);
+	return r;
+}
+
+/*
+ * Backlight device
+ */
+static int asus_read_brightness(struct backlight_device *bd)
+{
+	struct asus_laptop *asus = bl_get_data(bd);
+	unsigned long long value;
+	acpi_status rv = AE_OK;
+
+	rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET,
+				   NULL, &value);
+	if (ACPI_FAILURE(rv))
+		pr_warn("Error reading brightness\n");
+
+	return value;
+}
+
+static int asus_set_brightness(struct backlight_device *bd, int value)
+{
+	struct asus_laptop *asus = bl_get_data(bd);
+
+	if (write_acpi_int(asus->handle, METHOD_BRIGHTNESS_SET, value)) {
+		pr_warn("Error changing brightness\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+	int value = bd->props.brightness;
+
+	return asus_set_brightness(bd, value);
+}
+
+static const struct backlight_ops asusbl_ops = {
+	.get_brightness = asus_read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int asus_backlight_notify(struct asus_laptop *asus)
+{
+	struct backlight_device *bd = asus->backlight_device;
+	int old = bd->props.brightness;
+
+	backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
+
+	return old;
+}
+
+static int asus_backlight_init(struct asus_laptop *asus)
+{
+	struct backlight_device *bd;
+	struct backlight_properties props;
+
+	if (acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) ||
+	    acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL))
+		return 0;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.max_brightness = 15;
+	props.type = BACKLIGHT_PLATFORM;
+
+	bd = backlight_device_register(ASUS_LAPTOP_FILE,
+				       &asus->platform_device->dev, asus,
+				       &asusbl_ops, &props);
+	if (IS_ERR(bd)) {
+		pr_err("Could not register asus backlight device\n");
+		asus->backlight_device = NULL;
+		return PTR_ERR(bd);
+	}
+
+	asus->backlight_device = bd;
+	bd->props.brightness = asus_read_brightness(bd);
+	bd->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(bd);
+	return 0;
+}
+
+static void asus_backlight_exit(struct asus_laptop *asus)
+{
+	backlight_device_unregister(asus->backlight_device);
+	asus->backlight_device = NULL;
+}
+
+/*
+ * Platform device handlers
+ */
+
+/*
+ * We write our info in page, we begin at offset off and cannot write more
+ * than count bytes. We set eof to 1 if we handle those 2 values. We return the
+ * number of bytes written in page
+ */
+static ssize_t infos_show(struct device *dev, struct device_attribute *attr,
+			  char *page)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int len = 0;
+	unsigned long long temp;
+	char buf[16];		/* enough for all info */
+	acpi_status rv = AE_OK;
+
+	/*
+	 * We use the easy way, we don't care of off and count,
+	 * so we don't set eof to 1
+	 */
+
+	len += sprintf(page, ASUS_LAPTOP_NAME " " ASUS_LAPTOP_VERSION "\n");
+	len += sprintf(page + len, "Model reference    : %s\n", asus->name);
+	/*
+	 * The SFUN method probably allows the original driver to get the list
+	 * of features supported by a given model. For now, 0x0100 or 0x0800
+	 * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
+	 * The significance of others is yet to be found.
+	 */
+	rv = acpi_evaluate_integer(asus->handle, "SFUN", NULL, &temp);
+	if (!ACPI_FAILURE(rv))
+		len += sprintf(page + len, "SFUN value         : %#x\n",
+			       (uint) temp);
+	/*
+	 * The HWRS method return informations about the hardware.
+	 * 0x80 bit is for WLAN, 0x100 for Bluetooth.
+	 * 0x40 for WWAN, 0x10 for WIMAX.
+	 * The significance of others is yet to be found.
+	 * We don't currently use this for device detection, and it
+	 * takes several seconds to run on some systems.
+	 */
+	rv = acpi_evaluate_integer(asus->handle, "HWRS", NULL, &temp);
+	if (!ACPI_FAILURE(rv))
+		len += sprintf(page + len, "HWRS value         : %#x\n",
+			       (uint) temp);
+	/*
+	 * Another value for userspace: the ASYM method returns 0x02 for
+	 * battery low and 0x04 for battery critical, its readings tend to be
+	 * more accurate than those provided by _BST.
+	 * Note: since not all the laptops provide this method, errors are
+	 * silently ignored.
+	 */
+	rv = acpi_evaluate_integer(asus->handle, "ASYM", NULL, &temp);
+	if (!ACPI_FAILURE(rv))
+		len += sprintf(page + len, "ASYM value         : %#x\n",
+			       (uint) temp);
+	if (asus->dsdt_info) {
+		snprintf(buf, 16, "%d", asus->dsdt_info->length);
+		len += sprintf(page + len, "DSDT length        : %s\n", buf);
+		snprintf(buf, 16, "%d", asus->dsdt_info->checksum);
+		len += sprintf(page + len, "DSDT checksum      : %s\n", buf);
+		snprintf(buf, 16, "%d", asus->dsdt_info->revision);
+		len += sprintf(page + len, "DSDT revision      : %s\n", buf);
+		snprintf(buf, 7, "%s", asus->dsdt_info->oem_id);
+		len += sprintf(page + len, "OEM id             : %s\n", buf);
+		snprintf(buf, 9, "%s", asus->dsdt_info->oem_table_id);
+		len += sprintf(page + len, "OEM table id       : %s\n", buf);
+		snprintf(buf, 16, "%x", asus->dsdt_info->oem_revision);
+		len += sprintf(page + len, "OEM revision       : 0x%s\n", buf);
+		snprintf(buf, 5, "%s", asus->dsdt_info->asl_compiler_id);
+		len += sprintf(page + len, "ASL comp vendor id : %s\n", buf);
+		snprintf(buf, 16, "%x", asus->dsdt_info->asl_compiler_revision);
+		len += sprintf(page + len, "ASL comp revision  : 0x%s\n", buf);
+	}
+
+	return len;
+}
+static DEVICE_ATTR_RO(infos);
+
+static int parse_arg(const char *buf, unsigned long count, int *val)
+{
+	if (!count)
+		return 0;
+	if (count > 31)
+		return -EINVAL;
+	if (sscanf(buf, "%i", val) != 1)
+		return -EINVAL;
+	return count;
+}
+
+static ssize_t sysfs_acpi_set(struct asus_laptop *asus,
+			      const char *buf, size_t count,
+			      const char *method)
+{
+	int rv, value;
+	int out = 0;
+
+	rv = parse_arg(buf, count, &value);
+	if (rv > 0)
+		out = value ? 1 : 0;
+
+	if (write_acpi_int(asus->handle, method, value))
+		return -ENODEV;
+	return rv;
+}
+
+/*
+ * LEDD display
+ */
+static ssize_t ledd_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "0x%08x\n", asus->ledd_status);
+}
+
+static ssize_t ledd_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int rv, value;
+
+	rv = parse_arg(buf, count, &value);
+	if (rv > 0) {
+		if (write_acpi_int(asus->handle, METHOD_LEDD, value)) {
+			pr_warn("LED display write failed\n");
+			return -ENODEV;
+		}
+		asus->ledd_status = (u32) value;
+	}
+	return rv;
+}
+static DEVICE_ATTR_RW(ledd);
+
+/*
+ * Wireless
+ */
+static int asus_wireless_status(struct asus_laptop *asus, int mask)
+{
+	unsigned long long status;
+	acpi_status rv = AE_OK;
+
+	if (!asus->have_rsts)
+		return (asus->wireless_status & mask) ? 1 : 0;
+
+	rv = acpi_evaluate_integer(asus->handle, METHOD_WL_STATUS,
+				   NULL, &status);
+	if (ACPI_FAILURE(rv)) {
+		pr_warn("Error reading Wireless status\n");
+		return -EINVAL;
+	}
+	return !!(status & mask);
+}
+
+/*
+ * WLAN
+ */
+static int asus_wlan_set(struct asus_laptop *asus, int status)
+{
+	if (write_acpi_int(asus->handle, METHOD_WLAN, !!status)) {
+		pr_warn("Error setting wlan status to %d\n", status);
+		return -EIO;
+	}
+	return 0;
+}
+
+static ssize_t wlan_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus_wireless_status(asus, WL_RSTS));
+}
+
+static ssize_t wlan_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sysfs_acpi_set(asus, buf, count, METHOD_WLAN);
+}
+static DEVICE_ATTR_RW(wlan);
+
+/*e
+ * Bluetooth
+ */
+static int asus_bluetooth_set(struct asus_laptop *asus, int status)
+{
+	if (write_acpi_int(asus->handle, METHOD_BLUETOOTH, !!status)) {
+		pr_warn("Error setting bluetooth status to %d\n", status);
+		return -EIO;
+	}
+	return 0;
+}
+
+static ssize_t bluetooth_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus_wireless_status(asus, BT_RSTS));
+}
+
+static ssize_t bluetooth_store(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sysfs_acpi_set(asus, buf, count, METHOD_BLUETOOTH);
+}
+static DEVICE_ATTR_RW(bluetooth);
+
+/*
+ * Wimax
+ */
+static int asus_wimax_set(struct asus_laptop *asus, int status)
+{
+	if (write_acpi_int(asus->handle, METHOD_WIMAX, !!status)) {
+		pr_warn("Error setting wimax status to %d\n", status);
+		return -EIO;
+	}
+	return 0;
+}
+
+static ssize_t wimax_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus_wireless_status(asus, WM_RSTS));
+}
+
+static ssize_t wimax_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sysfs_acpi_set(asus, buf, count, METHOD_WIMAX);
+}
+static DEVICE_ATTR_RW(wimax);
+
+/*
+ * Wwan
+ */
+static int asus_wwan_set(struct asus_laptop *asus, int status)
+{
+	if (write_acpi_int(asus->handle, METHOD_WWAN, !!status)) {
+		pr_warn("Error setting wwan status to %d\n", status);
+		return -EIO;
+	}
+	return 0;
+}
+
+static ssize_t wwan_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus_wireless_status(asus, WW_RSTS));
+}
+
+static ssize_t wwan_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sysfs_acpi_set(asus, buf, count, METHOD_WWAN);
+}
+static DEVICE_ATTR_RW(wwan);
+
+/*
+ * Display
+ */
+static void asus_set_display(struct asus_laptop *asus, int value)
+{
+	/* no sanity check needed for now */
+	if (write_acpi_int(asus->handle, METHOD_SWITCH_DISPLAY, value))
+		pr_warn("Error setting display\n");
+	return;
+}
+
+/*
+ * Experimental support for display switching. As of now: 1 should activate
+ * the LCD output, 2 should do for CRT, 4 for TV-Out and 8 for DVI.
+ * Any combination (bitwise) of these will suffice. I never actually tested 4
+ * displays hooked up simultaneously, so be warned. See the acpi4asus README
+ * for more info.
+ */
+static ssize_t display_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int rv, value;
+
+	rv = parse_arg(buf, count, &value);
+	if (rv > 0)
+		asus_set_display(asus, value);
+	return rv;
+}
+static DEVICE_ATTR_WO(display);
+
+/*
+ * Light Sens
+ */
+static void asus_als_switch(struct asus_laptop *asus, int value)
+{
+	int ret;
+
+	if (asus->is_pega_lucid) {
+		ret = asus_pega_lucid_set(asus, PEGA_ALS, value);
+		if (!ret)
+			ret = asus_pega_lucid_set(asus, PEGA_ALS_POWER, value);
+	} else {
+		ret = write_acpi_int(asus->handle, METHOD_ALS_CONTROL, value);
+	}
+	if (ret)
+		pr_warning("Error setting light sensor switch\n");
+
+	asus->light_switch = value;
+}
+
+static ssize_t ls_switch_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus->light_switch);
+}
+
+static ssize_t ls_switch_store(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int rv, value;
+
+	rv = parse_arg(buf, count, &value);
+	if (rv > 0)
+		asus_als_switch(asus, value ? 1 : 0);
+
+	return rv;
+}
+static DEVICE_ATTR_RW(ls_switch);
+
+static void asus_als_level(struct asus_laptop *asus, int value)
+{
+	if (write_acpi_int(asus->handle, METHOD_ALS_LEVEL, value))
+		pr_warn("Error setting light sensor level\n");
+	asus->light_level = value;
+}
+
+static ssize_t ls_level_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus->light_level);
+}
+
+static ssize_t ls_level_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int rv, value;
+
+	rv = parse_arg(buf, count, &value);
+	if (rv > 0) {
+		value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
+		/* 0 <= value <= 15 */
+		asus_als_level(asus, value);
+	}
+
+	return rv;
+}
+static DEVICE_ATTR_RW(ls_level);
+
+static int pega_int_read(struct asus_laptop *asus, int arg, int *result)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	int err = write_acpi_int_ret(asus->handle, METHOD_PEGA_READ, arg,
+				     &buffer);
+	if (!err) {
+		union acpi_object *obj = buffer.pointer;
+		if (obj && obj->type == ACPI_TYPE_INTEGER)
+			*result = obj->integer.value;
+		else
+			err = -EIO;
+	}
+	return err;
+}
+
+static ssize_t ls_value_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int err, hi, lo;
+
+	err = pega_int_read(asus, PEGA_READ_ALS_H, &hi);
+	if (!err)
+		err = pega_int_read(asus, PEGA_READ_ALS_L, &lo);
+	if (!err)
+		return sprintf(buf, "%d\n", 10 * hi + lo);
+	return err;
+}
+static DEVICE_ATTR_RO(ls_value);
+
+/*
+ * GPS
+ */
+static int asus_gps_status(struct asus_laptop *asus)
+{
+	unsigned long long status;
+	acpi_status rv = AE_OK;
+
+	rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS,
+				   NULL, &status);
+	if (ACPI_FAILURE(rv)) {
+		pr_warn("Error reading GPS status\n");
+		return -ENODEV;
+	}
+	return !!status;
+}
+
+static int asus_gps_switch(struct asus_laptop *asus, int status)
+{
+	const char *meth = status ? METHOD_GPS_ON : METHOD_GPS_OFF;
+
+	if (write_acpi_int(asus->handle, meth, 0x02))
+		return -ENODEV;
+	return 0;
+}
+
+static ssize_t gps_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", asus_gps_status(asus));
+}
+
+static ssize_t gps_store(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct asus_laptop *asus = dev_get_drvdata(dev);
+	int rv, value;
+	int ret;
+
+	rv = parse_arg(buf, count, &value);
+	if (rv <= 0)
+		return -EINVAL;
+	ret = asus_gps_switch(asus, !!value);
+	if (ret)
+		return ret;
+	rfkill_set_sw_state(asus->gps.rfkill, !value);
+	return rv;
+}
+static DEVICE_ATTR_RW(gps);
+
+/*
+ * rfkill
+ */
+static int asus_gps_rfkill_set(void *data, bool blocked)
+{
+	struct asus_laptop *asus = data;
+
+	return asus_gps_switch(asus, !blocked);
+}
+
+static const struct rfkill_ops asus_gps_rfkill_ops = {
+	.set_block = asus_gps_rfkill_set,
+};
+
+static int asus_rfkill_set(void *data, bool blocked)
+{
+	struct asus_rfkill *rfk = data;
+	struct asus_laptop *asus = rfk->asus;
+
+	if (rfk->control_id == WL_RSTS)
+		return asus_wlan_set(asus, !blocked);
+	else if (rfk->control_id == BT_RSTS)
+		return asus_bluetooth_set(asus, !blocked);
+	else if (rfk->control_id == WM_RSTS)
+		return asus_wimax_set(asus, !blocked);
+	else if (rfk->control_id == WW_RSTS)
+		return asus_wwan_set(asus, !blocked);
+
+	return -EINVAL;
+}
+
+static const struct rfkill_ops asus_rfkill_ops = {
+	.set_block = asus_rfkill_set,
+};
+
+static void asus_rfkill_terminate(struct asus_rfkill *rfk)
+{
+	if (!rfk->rfkill)
+		return ;
+
+	rfkill_unregister(rfk->rfkill);
+	rfkill_destroy(rfk->rfkill);
+	rfk->rfkill = NULL;
+}
+
+static void asus_rfkill_exit(struct asus_laptop *asus)
+{
+	asus_rfkill_terminate(&asus->wwan);
+	asus_rfkill_terminate(&asus->bluetooth);
+	asus_rfkill_terminate(&asus->wlan);
+	asus_rfkill_terminate(&asus->gps);
+}
+
+static int asus_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk,
+			     const char *name, int control_id, int type,
+			     const struct rfkill_ops *ops)
+{
+	int result;
+
+	rfk->control_id = control_id;
+	rfk->asus = asus;
+	rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev,
+				   type, ops, rfk);
+	if (!rfk->rfkill)
+		return -EINVAL;
+
+	result = rfkill_register(rfk->rfkill);
+	if (result) {
+		rfkill_destroy(rfk->rfkill);
+		rfk->rfkill = NULL;
+	}
+
+	return result;
+}
+
+static int asus_rfkill_init(struct asus_laptop *asus)
+{
+	int result = 0;
+
+	if (asus->is_pega_lucid)
+		return -ENODEV;
+
+	if (!acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) &&
+	    !acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) &&
+	    !acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL))
+		result = asus_rfkill_setup(asus, &asus->gps, "asus-gps",
+					   -1, RFKILL_TYPE_GPS,
+					   &asus_gps_rfkill_ops);
+	if (result)
+		goto exit;
+
+
+	if (!acpi_check_handle(asus->handle, METHOD_WLAN, NULL) &&
+	    asus->wled_type == TYPE_RFKILL)
+		result = asus_rfkill_setup(asus, &asus->wlan, "asus-wlan",
+					   WL_RSTS, RFKILL_TYPE_WLAN,
+					   &asus_rfkill_ops);
+	if (result)
+		goto exit;
+
+	if (!acpi_check_handle(asus->handle, METHOD_BLUETOOTH, NULL) &&
+	    asus->bled_type == TYPE_RFKILL)
+		result = asus_rfkill_setup(asus, &asus->bluetooth,
+					   "asus-bluetooth", BT_RSTS,
+					   RFKILL_TYPE_BLUETOOTH,
+					   &asus_rfkill_ops);
+	if (result)
+		goto exit;
+
+	if (!acpi_check_handle(asus->handle, METHOD_WWAN, NULL))
+		result = asus_rfkill_setup(asus, &asus->wwan, "asus-wwan",
+					   WW_RSTS, RFKILL_TYPE_WWAN,
+					   &asus_rfkill_ops);
+	if (result)
+		goto exit;
+
+	if (!acpi_check_handle(asus->handle, METHOD_WIMAX, NULL))
+		result = asus_rfkill_setup(asus, &asus->wimax, "asus-wimax",
+					   WM_RSTS, RFKILL_TYPE_WIMAX,
+					   &asus_rfkill_ops);
+	if (result)
+		goto exit;
+
+exit:
+	if (result)
+		asus_rfkill_exit(asus);
+
+	return result;
+}
+
+static int pega_rfkill_set(void *data, bool blocked)
+{
+	struct asus_rfkill *rfk = data;
+
+	int ret = asus_pega_lucid_set(rfk->asus, rfk->control_id, !blocked);
+	return ret;
+}
+
+static const struct rfkill_ops pega_rfkill_ops = {
+	.set_block = pega_rfkill_set,
+};
+
+static int pega_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk,
+			     const char *name, int controlid, int rfkill_type)
+{
+	return asus_rfkill_setup(asus, rfk, name, controlid, rfkill_type,
+				 &pega_rfkill_ops);
+}
+
+static int pega_rfkill_init(struct asus_laptop *asus)
+{
+	int ret = 0;
+
+	if(!asus->is_pega_lucid)
+		return -ENODEV;
+
+	ret = pega_rfkill_setup(asus, &asus->wlan, "pega-wlan",
+				PEGA_WLAN, RFKILL_TYPE_WLAN);
+	if(ret)
+		goto exit;
+
+	ret = pega_rfkill_setup(asus, &asus->bluetooth, "pega-bt",
+				PEGA_BLUETOOTH, RFKILL_TYPE_BLUETOOTH);
+	if(ret)
+		goto exit;
+
+	ret = pega_rfkill_setup(asus, &asus->wwan, "pega-wwan",
+				PEGA_WWAN, RFKILL_TYPE_WWAN);
+
+exit:
+	if (ret)
+		asus_rfkill_exit(asus);
+
+	return ret;
+}
+
+/*
+ * Input device (i.e. hotkeys)
+ */
+static void asus_input_notify(struct asus_laptop *asus, int event)
+{
+	if (!asus->inputdev)
+		return ;
+	if (!sparse_keymap_report_event(asus->inputdev, event, 1, true))
+		pr_info("Unknown key %x pressed\n", event);
+}
+
+static int asus_input_init(struct asus_laptop *asus)
+{
+	struct input_dev *input;
+	int error;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "Asus Laptop extra buttons";
+	input->phys = ASUS_LAPTOP_FILE "/input0";
+	input->id.bustype = BUS_HOST;
+	input->dev.parent = &asus->platform_device->dev;
+
+	error = sparse_keymap_setup(input, asus_keymap, NULL);
+	if (error) {
+		pr_err("Unable to setup input device keymap\n");
+		goto err_free_dev;
+	}
+	error = input_register_device(input);
+	if (error) {
+		pr_warn("Unable to register input device\n");
+		goto err_free_keymap;
+	}
+
+	asus->inputdev = input;
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(input);
+err_free_dev:
+	input_free_device(input);
+	return error;
+}
+
+static void asus_input_exit(struct asus_laptop *asus)
+{
+	if (asus->inputdev) {
+		sparse_keymap_free(asus->inputdev);
+		input_unregister_device(asus->inputdev);
+	}
+	asus->inputdev = NULL;
+}
+
+/*
+ * ACPI driver
+ */
+static void asus_acpi_notify(struct acpi_device *device, u32 event)
+{
+	struct asus_laptop *asus = acpi_driver_data(device);
+	u16 count;
+
+	/* TODO Find a better way to handle events count. */
+	count = asus->event_count[event % 128]++;
+	acpi_bus_generate_netlink_event(asus->device->pnp.device_class,
+					dev_name(&asus->device->dev), event,
+					count);
+
+	if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX)
+		event = ATKD_BRNUP;
+	else if (event >= ATKD_BRNDOWN_MIN &&
+		 event <= ATKD_BRNDOWN_MAX)
+		event = ATKD_BRNDOWN;
+
+	/* Brightness events are special */
+	if (event == ATKD_BRNDOWN || event == ATKD_BRNUP) {
+		if (asus->backlight_device != NULL) {
+			/* Update the backlight device. */
+			asus_backlight_notify(asus);
+			return ;
+		}
+	}
+
+	/* Accelerometer "coarse orientation change" event */
+	if (asus->pega_accel_poll && event == 0xEA) {
+		kobject_uevent(&asus->pega_accel_poll->input->dev.kobj,
+			       KOBJ_CHANGE);
+		return ;
+	}
+
+	asus_input_notify(asus, event);
+}
+
+static struct attribute *asus_attributes[] = {
+	&dev_attr_infos.attr,
+	&dev_attr_wlan.attr,
+	&dev_attr_bluetooth.attr,
+	&dev_attr_wimax.attr,
+	&dev_attr_wwan.attr,
+	&dev_attr_display.attr,
+	&dev_attr_ledd.attr,
+	&dev_attr_ls_value.attr,
+	&dev_attr_ls_level.attr,
+	&dev_attr_ls_switch.attr,
+	&dev_attr_gps.attr,
+	NULL
+};
+
+static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+				    struct attribute *attr,
+				    int idx)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct platform_device *pdev = to_platform_device(dev);
+	struct asus_laptop *asus = platform_get_drvdata(pdev);
+	acpi_handle handle = asus->handle;
+	bool supported;
+
+	if (asus->is_pega_lucid) {
+		/* no ls_level interface on the Lucid */
+		if (attr == &dev_attr_ls_switch.attr)
+			supported = true;
+		else if (attr == &dev_attr_ls_level.attr)
+			supported = false;
+		else
+			goto normal;
+
+		return supported ? attr->mode : 0;
+	}
+
+normal:
+	if (attr == &dev_attr_wlan.attr) {
+		supported = !acpi_check_handle(handle, METHOD_WLAN, NULL);
+
+	} else if (attr == &dev_attr_bluetooth.attr) {
+		supported = !acpi_check_handle(handle, METHOD_BLUETOOTH, NULL);
+
+	} else if (attr == &dev_attr_display.attr) {
+		supported = !acpi_check_handle(handle, METHOD_SWITCH_DISPLAY, NULL);
+
+	} else if (attr == &dev_attr_wimax.attr) {
+		supported =
+			!acpi_check_handle(asus->handle, METHOD_WIMAX, NULL);
+
+	} else if (attr == &dev_attr_wwan.attr) {
+		supported = !acpi_check_handle(asus->handle, METHOD_WWAN, NULL);
+
+	} else if (attr == &dev_attr_ledd.attr) {
+		supported = !acpi_check_handle(handle, METHOD_LEDD, NULL);
+
+	} else if (attr == &dev_attr_ls_switch.attr ||
+		   attr == &dev_attr_ls_level.attr) {
+		supported = !acpi_check_handle(handle, METHOD_ALS_CONTROL, NULL) &&
+			!acpi_check_handle(handle, METHOD_ALS_LEVEL, NULL);
+	} else if (attr == &dev_attr_ls_value.attr) {
+		supported = asus->is_pega_lucid;
+	} else if (attr == &dev_attr_gps.attr) {
+		supported = !acpi_check_handle(handle, METHOD_GPS_ON, NULL) &&
+			    !acpi_check_handle(handle, METHOD_GPS_OFF, NULL) &&
+			    !acpi_check_handle(handle, METHOD_GPS_STATUS, NULL);
+	} else {
+		supported = true;
+	}
+
+	return supported ? attr->mode : 0;
+}
+
+
+static const struct attribute_group asus_attr_group = {
+	.is_visible	= asus_sysfs_is_visible,
+	.attrs		= asus_attributes,
+};
+
+static int asus_platform_init(struct asus_laptop *asus)
+{
+	int result;
+
+	asus->platform_device = platform_device_alloc(ASUS_LAPTOP_FILE, -1);
+	if (!asus->platform_device)
+		return -ENOMEM;
+	platform_set_drvdata(asus->platform_device, asus);
+
+	result = platform_device_add(asus->platform_device);
+	if (result)
+		goto fail_platform_device;
+
+	result = sysfs_create_group(&asus->platform_device->dev.kobj,
+				    &asus_attr_group);
+	if (result)
+		goto fail_sysfs;
+
+	return 0;
+
+fail_sysfs:
+	platform_device_del(asus->platform_device);
+fail_platform_device:
+	platform_device_put(asus->platform_device);
+	return result;
+}
+
+static void asus_platform_exit(struct asus_laptop *asus)
+{
+	sysfs_remove_group(&asus->platform_device->dev.kobj, &asus_attr_group);
+	platform_device_unregister(asus->platform_device);
+}
+
+static struct platform_driver platform_driver = {
+	.driver = {
+		.name = ASUS_LAPTOP_FILE,
+	},
+};
+
+/*
+ * This function is used to initialize the context with right values. In this
+ * method, we can make all the detection we want, and modify the asus_laptop
+ * struct
+ */
+static int asus_laptop_get_info(struct asus_laptop *asus)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *model = NULL;
+	unsigned long long bsts_result;
+	char *string = NULL;
+	acpi_status status;
+
+	/*
+	 * Get DSDT headers early enough to allow for differentiating between
+	 * models, but late enough to allow acpi_bus_register_driver() to fail
+	 * before doing anything ACPI-specific. Should we encounter a machine,
+	 * which needs special handling (i.e. its hotkey device has a different
+	 * HID), this bit will be moved.
+	 */
+	status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus->dsdt_info);
+	if (ACPI_FAILURE(status))
+		pr_warn("Couldn't get the DSDT table header\n");
+
+	/* We have to write 0 on init this far for all ASUS models */
+	if (write_acpi_int_ret(asus->handle, "INIT", 0, &buffer)) {
+		pr_err("Hotkey initialization failed\n");
+		return -ENODEV;
+	}
+
+	/* This needs to be called for some laptops to init properly */
+	status =
+	    acpi_evaluate_integer(asus->handle, "BSTS", NULL, &bsts_result);
+	if (ACPI_FAILURE(status))
+		pr_warn("Error calling BSTS\n");
+	else if (bsts_result)
+		pr_notice("BSTS called, 0x%02x returned\n",
+		       (uint) bsts_result);
+
+	/* This too ... */
+	if (write_acpi_int(asus->handle, "CWAP", wapf))
+		pr_err("Error calling CWAP(%d)\n", wapf);
+	/*
+	 * Try to match the object returned by INIT to the specific model.
+	 * Handle every possible object (or the lack of thereof) the DSDT
+	 * writers might throw at us. When in trouble, we pass NULL to
+	 * asus_model_match() and try something completely different.
+	 */
+	if (buffer.pointer) {
+		model = buffer.pointer;
+		switch (model->type) {
+		case ACPI_TYPE_STRING:
+			string = model->string.pointer;
+			break;
+		case ACPI_TYPE_BUFFER:
+			string = model->buffer.pointer;
+			break;
+		default:
+			string = "";
+			break;
+		}
+	}
+	asus->name = kstrdup(string, GFP_KERNEL);
+	if (!asus->name) {
+		kfree(buffer.pointer);
+		return -ENOMEM;
+	}
+
+	if (string)
+		pr_notice("  %s model detected\n", string);
+
+	if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL))
+		asus->have_rsts = true;
+
+	kfree(model);
+
+	return AE_OK;
+}
+
+static int asus_acpi_init(struct asus_laptop *asus)
+{
+	int result = 0;
+
+	result = acpi_bus_get_status(asus->device);
+	if (result)
+		return result;
+	if (!asus->device->status.present) {
+		pr_err("Hotkey device not present, aborting\n");
+		return -ENODEV;
+	}
+
+	result = asus_laptop_get_info(asus);
+	if (result)
+		return result;
+
+	if (!strcmp(bled_type, "led"))
+		asus->bled_type = TYPE_LED;
+	else if (!strcmp(bled_type, "rfkill"))
+		asus->bled_type = TYPE_RFKILL;
+
+	if (!strcmp(wled_type, "led"))
+		asus->wled_type = TYPE_LED;
+	else if (!strcmp(wled_type, "rfkill"))
+		asus->wled_type = TYPE_RFKILL;
+
+	if (bluetooth_status >= 0)
+		asus_bluetooth_set(asus, !!bluetooth_status);
+
+	if (wlan_status >= 0)
+		asus_wlan_set(asus, !!wlan_status);
+
+	if (wimax_status >= 0)
+		asus_wimax_set(asus, !!wimax_status);
+
+	if (wwan_status >= 0)
+		asus_wwan_set(asus, !!wwan_status);
+
+	/* Keyboard Backlight is on by default */
+	if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL))
+		asus_kled_set(asus, 1);
+
+	/* LED display is off by default */
+	asus->ledd_status = 0xFFF;
+
+	/* Set initial values of light sensor and level */
+	asus->light_switch = !!als_status;
+	asus->light_level = 5;	/* level 5 for sensor sensitivity */
+
+	if (asus->is_pega_lucid) {
+		asus_als_switch(asus, asus->light_switch);
+	} else if (!acpi_check_handle(asus->handle, METHOD_ALS_CONTROL, NULL) &&
+		   !acpi_check_handle(asus->handle, METHOD_ALS_LEVEL, NULL)) {
+		asus_als_switch(asus, asus->light_switch);
+		asus_als_level(asus, asus->light_level);
+	}
+
+	return result;
+}
+
+static void asus_dmi_check(void)
+{
+	const char *model;
+
+	model = dmi_get_system_info(DMI_PRODUCT_NAME);
+	if (!model)
+		return;
+
+	/* On L1400B WLED control the sound card, don't mess with it ... */
+	if (strncmp(model, "L1400B", 6) == 0) {
+		wlan_status = -1;
+	}
+}
+
+static bool asus_device_present;
+
+static int asus_acpi_add(struct acpi_device *device)
+{
+	struct asus_laptop *asus;
+	int result;
+
+	pr_notice("Asus Laptop Support version %s\n",
+		  ASUS_LAPTOP_VERSION);
+	asus = kzalloc(sizeof(struct asus_laptop), GFP_KERNEL);
+	if (!asus)
+		return -ENOMEM;
+	asus->handle = device->handle;
+	strcpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
+	device->driver_data = asus;
+	asus->device = device;
+
+	asus_dmi_check();
+
+	result = asus_acpi_init(asus);
+	if (result)
+		goto fail_platform;
+
+	/*
+	 * Need platform type detection first, then the platform
+	 * device.  It is used as a parent for the sub-devices below.
+	 */
+	asus->is_pega_lucid = asus_check_pega_lucid(asus);
+	result = asus_platform_init(asus);
+	if (result)
+		goto fail_platform;
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		result = asus_backlight_init(asus);
+		if (result)
+			goto fail_backlight;
+	}
+
+	result = asus_input_init(asus);
+	if (result)
+		goto fail_input;
+
+	result = asus_led_init(asus);
+	if (result)
+		goto fail_led;
+
+	result = asus_rfkill_init(asus);
+	if (result && result != -ENODEV)
+		goto fail_rfkill;
+
+	result = pega_accel_init(asus);
+	if (result && result != -ENODEV)
+		goto fail_pega_accel;
+
+	result = pega_rfkill_init(asus);
+	if (result && result != -ENODEV)
+		goto fail_pega_rfkill;
+
+	asus_device_present = true;
+	return 0;
+
+fail_pega_rfkill:
+	pega_accel_exit(asus);
+fail_pega_accel:
+	asus_rfkill_exit(asus);
+fail_rfkill:
+	asus_led_exit(asus);
+fail_led:
+	asus_input_exit(asus);
+fail_input:
+	asus_backlight_exit(asus);
+fail_backlight:
+	asus_platform_exit(asus);
+fail_platform:
+	kfree(asus);
+
+	return result;
+}
+
+static int asus_acpi_remove(struct acpi_device *device)
+{
+	struct asus_laptop *asus = acpi_driver_data(device);
+
+	asus_backlight_exit(asus);
+	asus_rfkill_exit(asus);
+	asus_led_exit(asus);
+	asus_input_exit(asus);
+	pega_accel_exit(asus);
+	asus_platform_exit(asus);
+
+	kfree(asus->name);
+	kfree(asus);
+	return 0;
+}
+
+static const struct acpi_device_id asus_device_ids[] = {
+	{"ATK0100", 0},
+	{"ATK0101", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, asus_device_ids);
+
+static struct acpi_driver asus_acpi_driver = {
+	.name = ASUS_LAPTOP_NAME,
+	.class = ASUS_LAPTOP_CLASS,
+	.owner = THIS_MODULE,
+	.ids = asus_device_ids,
+	.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+	.ops = {
+		.add = asus_acpi_add,
+		.remove = asus_acpi_remove,
+		.notify = asus_acpi_notify,
+		},
+};
+
+static int __init asus_laptop_init(void)
+{
+	int result;
+
+	result = platform_driver_register(&platform_driver);
+	if (result < 0)
+		return result;
+
+	result = acpi_bus_register_driver(&asus_acpi_driver);
+	if (result < 0)
+		goto fail_acpi_driver;
+	if (!asus_device_present) {
+		result = -ENODEV;
+		goto fail_no_device;
+	}
+	return 0;
+
+fail_no_device:
+	acpi_bus_unregister_driver(&asus_acpi_driver);
+fail_acpi_driver:
+	platform_driver_unregister(&platform_driver);
+	return result;
+}
+
+static void __exit asus_laptop_exit(void)
+{
+	acpi_bus_unregister_driver(&asus_acpi_driver);
+	platform_driver_unregister(&platform_driver);
+}
+
+module_init(asus_laptop_init);
+module_exit(asus_laptop_exit);
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
new file mode 100644
index 0000000..a3661cc
--- /dev/null
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -0,0 +1,411 @@
+/*
+ * Asus Notebooks WMI hotkey driver
+ *
+ * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/fb.h>
+#include <linux/dmi.h>
+
+#include "asus-wmi.h"
+
+#define	ASUS_NB_WMI_FILE	"asus-nb-wmi"
+
+MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>");
+MODULE_DESCRIPTION("Asus Notebooks WMI Hotkey Driver");
+MODULE_LICENSE("GPL");
+
+#define ASUS_NB_WMI_EVENT_GUID	"0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C"
+
+MODULE_ALIAS("wmi:"ASUS_NB_WMI_EVENT_GUID);
+
+/*
+ * WAPF defines the behavior of the Fn+Fx wlan key
+ * The significance of values is yet to be found, but
+ * most of the time:
+ * Bit | Bluetooth | WLAN
+ *  0  | Hardware  | Hardware
+ *  1  | Hardware  | Software
+ *  4  | Software  | Software
+ */
+static int wapf = -1;
+module_param(wapf, uint, 0444);
+MODULE_PARM_DESC(wapf, "WAPF value");
+
+static struct quirk_entry *quirks;
+
+static struct quirk_entry quirk_asus_unknown = {
+	.wapf = 0,
+};
+
+/*
+ * For those machines that need software to control bt/wifi status
+ * and can't adjust brightness through ACPI interface
+ * and have duplicate events(ACPI and WMI) for display toggle
+ */
+static struct quirk_entry quirk_asus_x55u = {
+	.wapf = 4,
+	.wmi_backlight_power = true,
+	.no_display_toggle = true,
+};
+
+static struct quirk_entry quirk_asus_wapf4 = {
+	.wapf = 4,
+};
+
+static struct quirk_entry quirk_asus_x200ca = {
+	.wapf = 2,
+};
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 1;
+}
+
+static const struct dmi_system_id asus_quirks[] = {
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. U32U",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "U32U"),
+		},
+		/*
+		 * Note this machine has a Brazos APU, and most Brazos Asus
+		 * machines need quirk_asus_x55u / wmi_backlight_power but
+		 * here acpi-video seems to work fine for backlight control.
+		 */
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X401U",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X401U"),
+		},
+		.driver_data = &quirk_asus_x55u,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X401A",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X401A"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X401A1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X401A1"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X45U",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X45U"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X456UA",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X456UA"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X456UF",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X456UF"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X501U",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X501U"),
+		},
+		.driver_data = &quirk_asus_x55u,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X501A",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X501A"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X501A1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X501A1"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X550CA",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X550CA"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X550CC",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X550CC"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X550CL",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X550CL"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X550VB",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X550VB"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X551CA",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X551CA"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X55A",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X55A"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X55C",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X55C"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X55U",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X55U"),
+		},
+		.driver_data = &quirk_asus_x55u,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X55VD",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X55VD"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X75A",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X75A"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X75VBP",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X75VBP"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. 1015E",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "1015E"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. 1015U",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "1015U"),
+		},
+		.driver_data = &quirk_asus_wapf4,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK COMPUTER INC. X200CA",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X200CA"),
+		},
+		.driver_data = &quirk_asus_x200ca,
+	},
+	{},
+};
+
+static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver)
+{
+	quirks = &quirk_asus_unknown;
+	dmi_check_system(asus_quirks);
+
+	driver->quirks = quirks;
+	driver->panel_power = FB_BLANK_UNBLANK;
+
+	/* overwrite the wapf setting if the wapf paramater is specified */
+	if (wapf != -1)
+		quirks->wapf = wapf;
+	else
+		wapf = quirks->wapf;
+}
+
+static const struct key_entry asus_nb_wmi_keymap[] = {
+	{ KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0x30, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0x32, { KEY_MUTE } },
+	{ KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */
+	{ KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */
+	{ KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
+	{ KE_KEY, 0x41, { KEY_NEXTSONG } },
+	{ KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */
+	{ KE_KEY, 0x45, { KEY_PLAYPAUSE } },
+	{ KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */
+	{ KE_KEY, 0x50, { KEY_EMAIL } },
+	{ KE_KEY, 0x51, { KEY_WWW } },
+	{ KE_KEY, 0x55, { KEY_CALC } },
+	{ KE_IGNORE, 0x57, },  /* Battery mode */
+	{ KE_IGNORE, 0x58, },  /* AC mode */
+	{ KE_KEY, 0x5C, { KEY_F15 } },  /* Power Gear key */
+	{ KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */
+	{ KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */
+	{ KE_KEY, 0x5F, { KEY_WLAN } }, /* Wireless console Disable */
+	{ KE_KEY, 0x60, { KEY_TOUCHPAD_ON } },
+	{ KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */
+	{ KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */
+	{ KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */
+	{ KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */
+	{ KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */
+	{ KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */
+	{ KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */
+	{ KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } },
+	{ KE_IGNORE, 0x6E, },  /* Low Battery notification */
+	{ KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
+	{ KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
+	{ KE_KEY, 0x82, { KEY_CAMERA } },
+	{ KE_KEY, 0x88, { KEY_RFKILL  } }, /* Radio Toggle Key */
+	{ KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+	{ KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
+	{ KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
+	{ KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
+	{ KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */
+	{ KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */
+	{ KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */
+	{ KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
+	{ KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
+	{ KE_KEY, 0x95, { KEY_MEDIA } },
+	{ KE_KEY, 0x99, { KEY_PHONE } },
+	{ KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
+	{ KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
+	{ KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
+	{ KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */
+	{ KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */
+	{ KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
+	{ KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
+	{ KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+	{ KE_KEY, 0xB5, { KEY_CALC } },
+	{ KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
+	{ KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
+	{ KE_IGNORE, 0xC6, },  /* Ambient Light Sensor notification */
+	{ KE_END, 0},
+};
+
+static struct asus_wmi_driver asus_nb_wmi_driver = {
+	.name = ASUS_NB_WMI_FILE,
+	.owner = THIS_MODULE,
+	.event_guid = ASUS_NB_WMI_EVENT_GUID,
+	.keymap = asus_nb_wmi_keymap,
+	.input_name = "Asus WMI hotkeys",
+	.input_phys = ASUS_NB_WMI_FILE "/input0",
+	.detect_quirks = asus_nb_wmi_quirks,
+};
+
+
+static int __init asus_nb_wmi_init(void)
+{
+	return asus_wmi_register_driver(&asus_nb_wmi_driver);
+}
+
+static void __exit asus_nb_wmi_exit(void)
+{
+	asus_wmi_unregister_driver(&asus_nb_wmi_driver);
+}
+
+module_init(asus_nb_wmi_init);
+module_exit(asus_nb_wmi_exit);
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
new file mode 100644
index 0000000..f96f7b8
--- /dev/null
+++ b/drivers/platform/x86/asus-wmi.c
@@ -0,0 +1,2294 @@
+/*
+ * Asus PC WMI hotkey driver
+ *
+ * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/rfkill.h>
+#include <linux/pci.h>
+#include <linux/pci_hotplug.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <acpi/video.h>
+
+#include "asus-wmi.h"
+
+MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, "
+	      "Yong Wang <yong.y.wang@intel.com>");
+MODULE_DESCRIPTION("Asus Generic WMI Driver");
+MODULE_LICENSE("GPL");
+
+#define to_platform_driver(drv)					\
+	(container_of((drv), struct platform_driver, driver))
+
+#define to_asus_wmi_driver(pdrv)					\
+	(container_of((pdrv), struct asus_wmi_driver, platform_driver))
+
+#define ASUS_WMI_MGMT_GUID	"97845ED0-4E6D-11DE-8A39-0800200C9A66"
+
+#define NOTIFY_BRNUP_MIN		0x11
+#define NOTIFY_BRNUP_MAX		0x1f
+#define NOTIFY_BRNDOWN_MIN		0x20
+#define NOTIFY_BRNDOWN_MAX		0x2e
+#define NOTIFY_KBD_BRTUP		0xc4
+#define NOTIFY_KBD_BRTDWN		0xc5
+
+/* WMI Methods */
+#define ASUS_WMI_METHODID_SPEC	        0x43455053 /* BIOS SPECification */
+#define ASUS_WMI_METHODID_SFBD		0x44424653 /* Set First Boot Device */
+#define ASUS_WMI_METHODID_GLCD		0x44434C47 /* Get LCD status */
+#define ASUS_WMI_METHODID_GPID		0x44495047 /* Get Panel ID?? (Resol) */
+#define ASUS_WMI_METHODID_QMOD		0x444F4D51 /* Quiet MODe */
+#define ASUS_WMI_METHODID_SPLV		0x4C425053 /* Set Panel Light Value */
+#define ASUS_WMI_METHODID_AGFN		0x4E464741 /* FaN? */
+#define ASUS_WMI_METHODID_SFUN		0x4E554653 /* FUNCtionalities */
+#define ASUS_WMI_METHODID_SDSP		0x50534453 /* Set DiSPlay output */
+#define ASUS_WMI_METHODID_GDSP		0x50534447 /* Get DiSPlay output */
+#define ASUS_WMI_METHODID_DEVP		0x50564544 /* DEVice Policy */
+#define ASUS_WMI_METHODID_OSVR		0x5256534F /* OS VeRsion */
+#define ASUS_WMI_METHODID_DSTS		0x53544344 /* Device STatuS */
+#define ASUS_WMI_METHODID_DSTS2		0x53545344 /* Device STatuS #2*/
+#define ASUS_WMI_METHODID_BSTS		0x53545342 /* Bios STatuS ? */
+#define ASUS_WMI_METHODID_DEVS		0x53564544 /* DEVice Set */
+#define ASUS_WMI_METHODID_CFVS		0x53564643 /* CPU Frequency Volt Set */
+#define ASUS_WMI_METHODID_KBFT		0x5446424B /* KeyBoard FilTer */
+#define ASUS_WMI_METHODID_INIT		0x54494E49 /* INITialize */
+#define ASUS_WMI_METHODID_HKEY		0x59454B48 /* Hot KEY ?? */
+
+#define ASUS_WMI_UNSUPPORTED_METHOD	0xFFFFFFFE
+
+/* Wireless */
+#define ASUS_WMI_DEVID_HW_SWITCH	0x00010001
+#define ASUS_WMI_DEVID_WIRELESS_LED	0x00010002
+#define ASUS_WMI_DEVID_CWAP		0x00010003
+#define ASUS_WMI_DEVID_WLAN		0x00010011
+#define ASUS_WMI_DEVID_WLAN_LED		0x00010012
+#define ASUS_WMI_DEVID_BLUETOOTH	0x00010013
+#define ASUS_WMI_DEVID_GPS		0x00010015
+#define ASUS_WMI_DEVID_WIMAX		0x00010017
+#define ASUS_WMI_DEVID_WWAN3G		0x00010019
+#define ASUS_WMI_DEVID_UWB		0x00010021
+
+/* Leds */
+/* 0x000200XX and 0x000400XX */
+#define ASUS_WMI_DEVID_LED1		0x00020011
+#define ASUS_WMI_DEVID_LED2		0x00020012
+#define ASUS_WMI_DEVID_LED3		0x00020013
+#define ASUS_WMI_DEVID_LED4		0x00020014
+#define ASUS_WMI_DEVID_LED5		0x00020015
+#define ASUS_WMI_DEVID_LED6		0x00020016
+
+/* Backlight and Brightness */
+#define ASUS_WMI_DEVID_BACKLIGHT	0x00050011
+#define ASUS_WMI_DEVID_BRIGHTNESS	0x00050012
+#define ASUS_WMI_DEVID_KBD_BACKLIGHT	0x00050021
+#define ASUS_WMI_DEVID_LIGHT_SENSOR	0x00050022 /* ?? */
+
+/* Misc */
+#define ASUS_WMI_DEVID_CAMERA		0x00060013
+
+/* Storage */
+#define ASUS_WMI_DEVID_CARDREADER	0x00080013
+
+/* Input */
+#define ASUS_WMI_DEVID_TOUCHPAD		0x00100011
+#define ASUS_WMI_DEVID_TOUCHPAD_LED	0x00100012
+
+/* Fan, Thermal */
+#define ASUS_WMI_DEVID_THERMAL_CTRL	0x00110011
+#define ASUS_WMI_DEVID_FAN_CTRL		0x00110012
+
+/* Power */
+#define ASUS_WMI_DEVID_PROCESSOR_STATE	0x00120012
+
+/* Deep S3 / Resume on LID open */
+#define ASUS_WMI_DEVID_LID_RESUME	0x00120031
+
+/* DSTS masks */
+#define ASUS_WMI_DSTS_STATUS_BIT	0x00000001
+#define ASUS_WMI_DSTS_UNKNOWN_BIT	0x00000002
+#define ASUS_WMI_DSTS_PRESENCE_BIT	0x00010000
+#define ASUS_WMI_DSTS_USER_BIT		0x00020000
+#define ASUS_WMI_DSTS_BIOS_BIT		0x00040000
+#define ASUS_WMI_DSTS_BRIGHTNESS_MASK	0x000000FF
+#define ASUS_WMI_DSTS_MAX_BRIGTH_MASK	0x0000FF00
+
+#define ASUS_FAN_DESC			"cpu_fan"
+#define ASUS_FAN_MFUN			0x13
+#define ASUS_FAN_SFUN_READ		0x06
+#define ASUS_FAN_SFUN_WRITE		0x07
+#define ASUS_FAN_CTRL_MANUAL		1
+#define ASUS_FAN_CTRL_AUTO		2
+
+struct bios_args {
+	u32 arg0;
+	u32 arg1;
+} __packed;
+
+/*
+ * Struct that's used for all methods called via AGFN. Naming is
+ * identically to the AML code.
+ */
+struct agfn_args {
+	u16 mfun; /* probably "Multi-function" to be called */
+	u16 sfun; /* probably "Sub-function" to be called */
+	u16 len;  /* size of the hole struct, including subfunction fields */
+	u8 stas;  /* not used by now */
+	u8 err;   /* zero on success */
+} __packed;
+
+/* struct used for calling fan read and write methods */
+struct fan_args {
+	struct agfn_args agfn;	/* common fields */
+	u8 fan;			/* fan number: 0: set auto mode 1: 1st fan */
+	u32 speed;		/* read: RPM/100 - write: 0-255 */
+} __packed;
+
+/*
+ * <platform>/    - debugfs root directory
+ *   dev_id      - current dev_id
+ *   ctrl_param  - current ctrl_param
+ *   method_id   - current method_id
+ *   devs        - call DEVS(dev_id, ctrl_param) and print result
+ *   dsts        - call DSTS(dev_id)  and print result
+ *   call        - call method_id(dev_id, ctrl_param) and print result
+ */
+struct asus_wmi_debug {
+	struct dentry *root;
+	u32 method_id;
+	u32 dev_id;
+	u32 ctrl_param;
+};
+
+struct asus_rfkill {
+	struct asus_wmi *asus;
+	struct rfkill *rfkill;
+	u32 dev_id;
+};
+
+struct asus_wmi {
+	int dsts_id;
+	int spec;
+	int sfun;
+
+	struct input_dev *inputdev;
+	struct backlight_device *backlight_device;
+	struct platform_device *platform_device;
+
+	struct led_classdev wlan_led;
+	int wlan_led_wk;
+	struct led_classdev tpd_led;
+	int tpd_led_wk;
+	struct led_classdev kbd_led;
+	int kbd_led_wk;
+	struct workqueue_struct *led_workqueue;
+	struct work_struct tpd_led_work;
+	struct work_struct kbd_led_work;
+	struct work_struct wlan_led_work;
+
+	struct asus_rfkill wlan;
+	struct asus_rfkill bluetooth;
+	struct asus_rfkill wimax;
+	struct asus_rfkill wwan3g;
+	struct asus_rfkill gps;
+	struct asus_rfkill uwb;
+
+	bool asus_hwmon_fan_manual_mode;
+	int asus_hwmon_num_fans;
+	int asus_hwmon_pwm;
+
+	struct hotplug_slot *hotplug_slot;
+	struct mutex hotplug_lock;
+	struct mutex wmi_lock;
+	struct workqueue_struct *hotplug_workqueue;
+	struct work_struct hotplug_work;
+
+	struct asus_wmi_debug debug;
+
+	struct asus_wmi_driver *driver;
+};
+
+static int asus_wmi_input_init(struct asus_wmi *asus)
+{
+	int err;
+
+	asus->inputdev = input_allocate_device();
+	if (!asus->inputdev)
+		return -ENOMEM;
+
+	asus->inputdev->name = asus->driver->input_name;
+	asus->inputdev->phys = asus->driver->input_phys;
+	asus->inputdev->id.bustype = BUS_HOST;
+	asus->inputdev->dev.parent = &asus->platform_device->dev;
+	set_bit(EV_REP, asus->inputdev->evbit);
+
+	err = sparse_keymap_setup(asus->inputdev, asus->driver->keymap, NULL);
+	if (err)
+		goto err_free_dev;
+
+	err = input_register_device(asus->inputdev);
+	if (err)
+		goto err_free_keymap;
+
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(asus->inputdev);
+err_free_dev:
+	input_free_device(asus->inputdev);
+	return err;
+}
+
+static void asus_wmi_input_exit(struct asus_wmi *asus)
+{
+	if (asus->inputdev) {
+		sparse_keymap_free(asus->inputdev);
+		input_unregister_device(asus->inputdev);
+	}
+
+	asus->inputdev = NULL;
+}
+
+static int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
+				    u32 *retval)
+{
+	struct bios_args args = {
+		.arg0 = arg0,
+		.arg1 = arg1,
+	};
+	struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status status;
+	union acpi_object *obj;
+	u32 tmp = 0;
+
+	status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 1, method_id,
+				     &input, &output);
+
+	if (ACPI_FAILURE(status))
+		goto exit;
+
+	obj = (union acpi_object *)output.pointer;
+	if (obj && obj->type == ACPI_TYPE_INTEGER)
+		tmp = (u32) obj->integer.value;
+
+	if (retval)
+		*retval = tmp;
+
+	kfree(obj);
+
+exit:
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
+{
+	struct acpi_buffer input;
+	u64 phys_addr;
+	u32 retval;
+	u32 status = -1;
+
+	/*
+	 * Copy to dma capable address otherwise memory corruption occurs as
+	 * bios has to be able to access it.
+	 */
+	input.pointer = kzalloc(args.length, GFP_DMA | GFP_KERNEL);
+	input.length = args.length;
+	if (!input.pointer)
+		return -ENOMEM;
+	phys_addr = virt_to_phys(input.pointer);
+	memcpy(input.pointer, args.pointer, args.length);
+
+	status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN,
+					phys_addr, 0, &retval);
+	if (!status)
+		memcpy(args.pointer, input.pointer, args.length);
+
+	kfree(input.pointer);
+	if (status)
+		return -ENXIO;
+
+	return retval;
+}
+
+static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
+{
+	return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval);
+}
+
+static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
+				 u32 *retval)
+{
+	return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id,
+					ctrl_param, retval);
+}
+
+/* Helper for special devices with magic return codes */
+static int asus_wmi_get_devstate_bits(struct asus_wmi *asus,
+				      u32 dev_id, u32 mask)
+{
+	u32 retval = 0;
+	int err;
+
+	err = asus_wmi_get_devstate(asus, dev_id, &retval);
+
+	if (err < 0)
+		return err;
+
+	if (!(retval & ASUS_WMI_DSTS_PRESENCE_BIT))
+		return -ENODEV;
+
+	if (mask == ASUS_WMI_DSTS_STATUS_BIT) {
+		if (retval & ASUS_WMI_DSTS_UNKNOWN_BIT)
+			return -ENODEV;
+	}
+
+	return retval & mask;
+}
+
+static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id)
+{
+	return asus_wmi_get_devstate_bits(asus, dev_id,
+					  ASUS_WMI_DSTS_STATUS_BIT);
+}
+
+/*
+ * LEDs
+ */
+/*
+ * These functions actually update the LED's, and are called from a
+ * workqueue. By doing this as separate work rather than when the LED
+ * subsystem asks, we avoid messing with the Asus ACPI stuff during a
+ * potentially bad time, such as a timer interrupt.
+ */
+static void tpd_led_update(struct work_struct *work)
+{
+	int ctrl_param;
+	struct asus_wmi *asus;
+
+	asus = container_of(work, struct asus_wmi, tpd_led_work);
+
+	ctrl_param = asus->tpd_led_wk;
+	asus_wmi_set_devstate(ASUS_WMI_DEVID_TOUCHPAD_LED, ctrl_param, NULL);
+}
+
+static void tpd_led_set(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct asus_wmi *asus;
+
+	asus = container_of(led_cdev, struct asus_wmi, tpd_led);
+
+	asus->tpd_led_wk = !!value;
+	queue_work(asus->led_workqueue, &asus->tpd_led_work);
+}
+
+static int read_tpd_led_state(struct asus_wmi *asus)
+{
+	return asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_TOUCHPAD_LED);
+}
+
+static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
+{
+	struct asus_wmi *asus;
+
+	asus = container_of(led_cdev, struct asus_wmi, tpd_led);
+
+	return read_tpd_led_state(asus);
+}
+
+static void kbd_led_update(struct work_struct *work)
+{
+	int ctrl_param = 0;
+	struct asus_wmi *asus;
+
+	asus = container_of(work, struct asus_wmi, kbd_led_work);
+
+	/*
+	 * bits 0-2: level
+	 * bit 7: light on/off
+	 */
+	if (asus->kbd_led_wk > 0)
+		ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
+
+	asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
+}
+
+static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
+{
+	int retval;
+
+	/*
+	 * bits 0-2: level
+	 * bit 7: light on/off
+	 * bit 8-10: environment (0: dark, 1: normal, 2: light)
+	 * bit 17: status unknown
+	 */
+	retval = asus_wmi_get_devstate_bits(asus, ASUS_WMI_DEVID_KBD_BACKLIGHT,
+					    0xFFFF);
+
+	/* Unknown status is considered as off */
+	if (retval == 0x8000)
+		retval = 0;
+
+	if (retval >= 0) {
+		if (level)
+			*level = retval & 0x7F;
+		if (env)
+			*env = (retval >> 8) & 0x7F;
+		retval = 0;
+	}
+
+	return retval;
+}
+
+static void kbd_led_set(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct asus_wmi *asus;
+
+	asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+
+	if (value > asus->kbd_led.max_brightness)
+		value = asus->kbd_led.max_brightness;
+	else if (value < 0)
+		value = 0;
+
+	asus->kbd_led_wk = value;
+	queue_work(asus->led_workqueue, &asus->kbd_led_work);
+}
+
+static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
+{
+	struct asus_wmi *asus;
+	int retval, value;
+
+	asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+
+	retval = kbd_led_read(asus, &value, NULL);
+
+	if (retval < 0)
+		return retval;
+
+	return value;
+}
+
+static int wlan_led_unknown_state(struct asus_wmi *asus)
+{
+	u32 result;
+
+	asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+	return result & ASUS_WMI_DSTS_UNKNOWN_BIT;
+}
+
+static int wlan_led_presence(struct asus_wmi *asus)
+{
+	u32 result;
+
+	asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+	return result & ASUS_WMI_DSTS_PRESENCE_BIT;
+}
+
+static void wlan_led_update(struct work_struct *work)
+{
+	int ctrl_param;
+	struct asus_wmi *asus;
+
+	asus = container_of(work, struct asus_wmi, wlan_led_work);
+
+	ctrl_param = asus->wlan_led_wk;
+	asus_wmi_set_devstate(ASUS_WMI_DEVID_WIRELESS_LED, ctrl_param, NULL);
+}
+
+static void wlan_led_set(struct led_classdev *led_cdev,
+			 enum led_brightness value)
+{
+	struct asus_wmi *asus;
+
+	asus = container_of(led_cdev, struct asus_wmi, wlan_led);
+
+	asus->wlan_led_wk = !!value;
+	queue_work(asus->led_workqueue, &asus->wlan_led_work);
+}
+
+static enum led_brightness wlan_led_get(struct led_classdev *led_cdev)
+{
+	struct asus_wmi *asus;
+	u32 result;
+
+	asus = container_of(led_cdev, struct asus_wmi, wlan_led);
+	asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+	return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
+}
+
+static void asus_wmi_led_exit(struct asus_wmi *asus)
+{
+	if (!IS_ERR_OR_NULL(asus->kbd_led.dev))
+		led_classdev_unregister(&asus->kbd_led);
+	if (!IS_ERR_OR_NULL(asus->tpd_led.dev))
+		led_classdev_unregister(&asus->tpd_led);
+	if (!IS_ERR_OR_NULL(asus->wlan_led.dev))
+		led_classdev_unregister(&asus->wlan_led);
+	if (asus->led_workqueue)
+		destroy_workqueue(asus->led_workqueue);
+}
+
+static int asus_wmi_led_init(struct asus_wmi *asus)
+{
+	int rv = 0, led_val;
+
+	asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
+	if (!asus->led_workqueue)
+		return -ENOMEM;
+
+	if (read_tpd_led_state(asus) >= 0) {
+		INIT_WORK(&asus->tpd_led_work, tpd_led_update);
+
+		asus->tpd_led.name = "asus::touchpad";
+		asus->tpd_led.brightness_set = tpd_led_set;
+		asus->tpd_led.brightness_get = tpd_led_get;
+		asus->tpd_led.max_brightness = 1;
+
+		rv = led_classdev_register(&asus->platform_device->dev,
+					   &asus->tpd_led);
+		if (rv)
+			goto error;
+	}
+
+	led_val = kbd_led_read(asus, NULL, NULL);
+	if (led_val >= 0) {
+		INIT_WORK(&asus->kbd_led_work, kbd_led_update);
+
+		asus->kbd_led_wk = led_val;
+		asus->kbd_led.name = "asus::kbd_backlight";
+		asus->kbd_led.brightness_set = kbd_led_set;
+		asus->kbd_led.brightness_get = kbd_led_get;
+		asus->kbd_led.max_brightness = 3;
+
+		rv = led_classdev_register(&asus->platform_device->dev,
+					   &asus->kbd_led);
+		if (rv)
+			goto error;
+	}
+
+	if (wlan_led_presence(asus) && (asus->driver->quirks->wapf > 0)) {
+		INIT_WORK(&asus->wlan_led_work, wlan_led_update);
+
+		asus->wlan_led.name = "asus::wlan";
+		asus->wlan_led.brightness_set = wlan_led_set;
+		if (!wlan_led_unknown_state(asus))
+			asus->wlan_led.brightness_get = wlan_led_get;
+		asus->wlan_led.flags = LED_CORE_SUSPENDRESUME;
+		asus->wlan_led.max_brightness = 1;
+		asus->wlan_led.default_trigger = "asus-wlan";
+
+		rv = led_classdev_register(&asus->platform_device->dev,
+					   &asus->wlan_led);
+	}
+
+error:
+	if (rv)
+		asus_wmi_led_exit(asus);
+
+	return rv;
+}
+
+
+/*
+ * PCI hotplug (for wlan rfkill)
+ */
+static bool asus_wlan_rfkill_blocked(struct asus_wmi *asus)
+{
+	int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);
+
+	if (result < 0)
+		return false;
+	return !result;
+}
+
+static void asus_rfkill_hotplug(struct asus_wmi *asus)
+{
+	struct pci_dev *dev;
+	struct pci_bus *bus;
+	bool blocked;
+	bool absent;
+	u32 l;
+
+	mutex_lock(&asus->wmi_lock);
+	blocked = asus_wlan_rfkill_blocked(asus);
+	mutex_unlock(&asus->wmi_lock);
+
+	mutex_lock(&asus->hotplug_lock);
+	pci_lock_rescan_remove();
+
+	if (asus->wlan.rfkill)
+		rfkill_set_sw_state(asus->wlan.rfkill, blocked);
+
+	if (asus->hotplug_slot) {
+		bus = pci_find_bus(0, 1);
+		if (!bus) {
+			pr_warn("Unable to find PCI bus 1?\n");
+			goto out_unlock;
+		}
+
+		if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
+			pr_err("Unable to read PCI config space?\n");
+			goto out_unlock;
+		}
+		absent = (l == 0xffffffff);
+
+		if (blocked != absent) {
+			pr_warn("BIOS says wireless lan is %s, "
+				"but the pci device is %s\n",
+				blocked ? "blocked" : "unblocked",
+				absent ? "absent" : "present");
+			pr_warn("skipped wireless hotplug as probably "
+				"inappropriate for this model\n");
+			goto out_unlock;
+		}
+
+		if (!blocked) {
+			dev = pci_get_slot(bus, 0);
+			if (dev) {
+				/* Device already present */
+				pci_dev_put(dev);
+				goto out_unlock;
+			}
+			dev = pci_scan_single_device(bus, 0);
+			if (dev) {
+				pci_bus_assign_resources(bus);
+				pci_bus_add_device(dev);
+			}
+		} else {
+			dev = pci_get_slot(bus, 0);
+			if (dev) {
+				pci_stop_and_remove_bus_device(dev);
+				pci_dev_put(dev);
+			}
+		}
+	}
+
+out_unlock:
+	pci_unlock_rescan_remove();
+	mutex_unlock(&asus->hotplug_lock);
+}
+
+static void asus_rfkill_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct asus_wmi *asus = data;
+
+	if (event != ACPI_NOTIFY_BUS_CHECK)
+		return;
+
+	/*
+	 * We can't call directly asus_rfkill_hotplug because most
+	 * of the time WMBC is still being executed and not reetrant.
+	 * There is currently no way to tell ACPICA that  we want this
+	 * method to be serialized, we schedule a asus_rfkill_hotplug
+	 * call later, in a safer context.
+	 */
+	queue_work(asus->hotplug_workqueue, &asus->hotplug_work);
+}
+
+static int asus_register_rfkill_notifier(struct asus_wmi *asus, char *node)
+{
+	acpi_status status;
+	acpi_handle handle;
+
+	status = acpi_get_handle(NULL, node, &handle);
+
+	if (ACPI_SUCCESS(status)) {
+		status = acpi_install_notify_handler(handle,
+						     ACPI_SYSTEM_NOTIFY,
+						     asus_rfkill_notify, asus);
+		if (ACPI_FAILURE(status))
+			pr_warn("Failed to register notify on %s\n", node);
+	} else
+		return -ENODEV;
+
+	return 0;
+}
+
+static void asus_unregister_rfkill_notifier(struct asus_wmi *asus, char *node)
+{
+	acpi_status status = AE_OK;
+	acpi_handle handle;
+
+	status = acpi_get_handle(NULL, node, &handle);
+
+	if (ACPI_SUCCESS(status)) {
+		status = acpi_remove_notify_handler(handle,
+						    ACPI_SYSTEM_NOTIFY,
+						    asus_rfkill_notify);
+		if (ACPI_FAILURE(status))
+			pr_err("Error removing rfkill notify handler %s\n",
+			       node);
+	}
+}
+
+static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot,
+				   u8 *value)
+{
+	struct asus_wmi *asus = hotplug_slot->private;
+	int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);
+
+	if (result < 0)
+		return result;
+
+	*value = !!result;
+	return 0;
+}
+
+static void asus_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
+{
+	kfree(hotplug_slot->info);
+	kfree(hotplug_slot);
+}
+
+static struct hotplug_slot_ops asus_hotplug_slot_ops = {
+	.owner = THIS_MODULE,
+	.get_adapter_status = asus_get_adapter_status,
+	.get_power_status = asus_get_adapter_status,
+};
+
+static void asus_hotplug_work(struct work_struct *work)
+{
+	struct asus_wmi *asus;
+
+	asus = container_of(work, struct asus_wmi, hotplug_work);
+	asus_rfkill_hotplug(asus);
+}
+
+static int asus_setup_pci_hotplug(struct asus_wmi *asus)
+{
+	int ret = -ENOMEM;
+	struct pci_bus *bus = pci_find_bus(0, 1);
+
+	if (!bus) {
+		pr_err("Unable to find wifi PCI bus\n");
+		return -ENODEV;
+	}
+
+	asus->hotplug_workqueue =
+	    create_singlethread_workqueue("hotplug_workqueue");
+	if (!asus->hotplug_workqueue)
+		goto error_workqueue;
+
+	INIT_WORK(&asus->hotplug_work, asus_hotplug_work);
+
+	asus->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
+	if (!asus->hotplug_slot)
+		goto error_slot;
+
+	asus->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
+					   GFP_KERNEL);
+	if (!asus->hotplug_slot->info)
+		goto error_info;
+
+	asus->hotplug_slot->private = asus;
+	asus->hotplug_slot->release = &asus_cleanup_pci_hotplug;
+	asus->hotplug_slot->ops = &asus_hotplug_slot_ops;
+	asus_get_adapter_status(asus->hotplug_slot,
+				&asus->hotplug_slot->info->adapter_status);
+
+	ret = pci_hp_register(asus->hotplug_slot, bus, 0, "asus-wifi");
+	if (ret) {
+		pr_err("Unable to register hotplug slot - %d\n", ret);
+		goto error_register;
+	}
+
+	return 0;
+
+error_register:
+	kfree(asus->hotplug_slot->info);
+error_info:
+	kfree(asus->hotplug_slot);
+	asus->hotplug_slot = NULL;
+error_slot:
+	destroy_workqueue(asus->hotplug_workqueue);
+error_workqueue:
+	return ret;
+}
+
+/*
+ * Rfkill devices
+ */
+static int asus_rfkill_set(void *data, bool blocked)
+{
+	struct asus_rfkill *priv = data;
+	u32 ctrl_param = !blocked;
+	u32 dev_id = priv->dev_id;
+
+	/*
+	 * If the user bit is set, BIOS can't set and record the wlan status,
+	 * it will report the value read from id ASUS_WMI_DEVID_WLAN_LED
+	 * while we query the wlan status through WMI(ASUS_WMI_DEVID_WLAN).
+	 * So, we have to record wlan status in id ASUS_WMI_DEVID_WLAN_LED
+	 * while setting the wlan status through WMI.
+	 * This is also the behavior that windows app will do.
+	 */
+	if ((dev_id == ASUS_WMI_DEVID_WLAN) &&
+	     priv->asus->driver->wlan_ctrl_by_user)
+		dev_id = ASUS_WMI_DEVID_WLAN_LED;
+
+	return asus_wmi_set_devstate(dev_id, ctrl_param, NULL);
+}
+
+static void asus_rfkill_query(struct rfkill *rfkill, void *data)
+{
+	struct asus_rfkill *priv = data;
+	int result;
+
+	result = asus_wmi_get_devstate_simple(priv->asus, priv->dev_id);
+
+	if (result < 0)
+		return;
+
+	rfkill_set_sw_state(priv->rfkill, !result);
+}
+
+static int asus_rfkill_wlan_set(void *data, bool blocked)
+{
+	struct asus_rfkill *priv = data;
+	struct asus_wmi *asus = priv->asus;
+	int ret;
+
+	/*
+	 * This handler is enabled only if hotplug is enabled.
+	 * In this case, the asus_wmi_set_devstate() will
+	 * trigger a wmi notification and we need to wait
+	 * this call to finish before being able to call
+	 * any wmi method
+	 */
+	mutex_lock(&asus->wmi_lock);
+	ret = asus_rfkill_set(data, blocked);
+	mutex_unlock(&asus->wmi_lock);
+	return ret;
+}
+
+static const struct rfkill_ops asus_rfkill_wlan_ops = {
+	.set_block = asus_rfkill_wlan_set,
+	.query = asus_rfkill_query,
+};
+
+static const struct rfkill_ops asus_rfkill_ops = {
+	.set_block = asus_rfkill_set,
+	.query = asus_rfkill_query,
+};
+
+static int asus_new_rfkill(struct asus_wmi *asus,
+			   struct asus_rfkill *arfkill,
+			   const char *name, enum rfkill_type type, int dev_id)
+{
+	int result = asus_wmi_get_devstate_simple(asus, dev_id);
+	struct rfkill **rfkill = &arfkill->rfkill;
+
+	if (result < 0)
+		return result;
+
+	arfkill->dev_id = dev_id;
+	arfkill->asus = asus;
+
+	if (dev_id == ASUS_WMI_DEVID_WLAN &&
+	    asus->driver->quirks->hotplug_wireless)
+		*rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
+				       &asus_rfkill_wlan_ops, arfkill);
+	else
+		*rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
+				       &asus_rfkill_ops, arfkill);
+
+	if (!*rfkill)
+		return -EINVAL;
+
+	if ((dev_id == ASUS_WMI_DEVID_WLAN) &&
+			(asus->driver->quirks->wapf > 0))
+		rfkill_set_led_trigger_name(*rfkill, "asus-wlan");
+
+	rfkill_init_sw_state(*rfkill, !result);
+	result = rfkill_register(*rfkill);
+	if (result) {
+		rfkill_destroy(*rfkill);
+		*rfkill = NULL;
+		return result;
+	}
+	return 0;
+}
+
+static void asus_wmi_rfkill_exit(struct asus_wmi *asus)
+{
+	asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P5");
+	asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P6");
+	asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P7");
+	if (asus->wlan.rfkill) {
+		rfkill_unregister(asus->wlan.rfkill);
+		rfkill_destroy(asus->wlan.rfkill);
+		asus->wlan.rfkill = NULL;
+	}
+	/*
+	 * Refresh pci hotplug in case the rfkill state was changed after
+	 * asus_unregister_rfkill_notifier()
+	 */
+	asus_rfkill_hotplug(asus);
+	if (asus->hotplug_slot)
+		pci_hp_deregister(asus->hotplug_slot);
+	if (asus->hotplug_workqueue)
+		destroy_workqueue(asus->hotplug_workqueue);
+
+	if (asus->bluetooth.rfkill) {
+		rfkill_unregister(asus->bluetooth.rfkill);
+		rfkill_destroy(asus->bluetooth.rfkill);
+		asus->bluetooth.rfkill = NULL;
+	}
+	if (asus->wimax.rfkill) {
+		rfkill_unregister(asus->wimax.rfkill);
+		rfkill_destroy(asus->wimax.rfkill);
+		asus->wimax.rfkill = NULL;
+	}
+	if (asus->wwan3g.rfkill) {
+		rfkill_unregister(asus->wwan3g.rfkill);
+		rfkill_destroy(asus->wwan3g.rfkill);
+		asus->wwan3g.rfkill = NULL;
+	}
+	if (asus->gps.rfkill) {
+		rfkill_unregister(asus->gps.rfkill);
+		rfkill_destroy(asus->gps.rfkill);
+		asus->gps.rfkill = NULL;
+	}
+	if (asus->uwb.rfkill) {
+		rfkill_unregister(asus->uwb.rfkill);
+		rfkill_destroy(asus->uwb.rfkill);
+		asus->uwb.rfkill = NULL;
+	}
+}
+
+static int asus_wmi_rfkill_init(struct asus_wmi *asus)
+{
+	int result = 0;
+
+	mutex_init(&asus->hotplug_lock);
+	mutex_init(&asus->wmi_lock);
+
+	result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan",
+				 RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = asus_new_rfkill(asus, &asus->bluetooth,
+				 "asus-bluetooth", RFKILL_TYPE_BLUETOOTH,
+				 ASUS_WMI_DEVID_BLUETOOTH);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = asus_new_rfkill(asus, &asus->wimax, "asus-wimax",
+				 RFKILL_TYPE_WIMAX, ASUS_WMI_DEVID_WIMAX);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = asus_new_rfkill(asus, &asus->wwan3g, "asus-wwan3g",
+				 RFKILL_TYPE_WWAN, ASUS_WMI_DEVID_WWAN3G);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = asus_new_rfkill(asus, &asus->gps, "asus-gps",
+				 RFKILL_TYPE_GPS, ASUS_WMI_DEVID_GPS);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = asus_new_rfkill(asus, &asus->uwb, "asus-uwb",
+				 RFKILL_TYPE_UWB, ASUS_WMI_DEVID_UWB);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	if (!asus->driver->quirks->hotplug_wireless)
+		goto exit;
+
+	result = asus_setup_pci_hotplug(asus);
+	/*
+	 * If we get -EBUSY then something else is handling the PCI hotplug -
+	 * don't fail in this case
+	 */
+	if (result == -EBUSY)
+		result = 0;
+
+	asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P5");
+	asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P6");
+	asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P7");
+	/*
+	 * Refresh pci hotplug in case the rfkill state was changed during
+	 * setup.
+	 */
+	asus_rfkill_hotplug(asus);
+
+exit:
+	if (result && result != -ENODEV)
+		asus_wmi_rfkill_exit(asus);
+
+	if (result == -ENODEV)
+		result = 0;
+
+	return result;
+}
+
+/*
+ * Hwmon device
+ */
+static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
+					  int *speed)
+{
+	struct fan_args args = {
+		.agfn.len = sizeof(args),
+		.agfn.mfun = ASUS_FAN_MFUN,
+		.agfn.sfun = ASUS_FAN_SFUN_READ,
+		.fan = fan,
+		.speed = 0,
+	};
+	struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+	int status;
+
+	if (fan != 1)
+		return -EINVAL;
+
+	status = asus_wmi_evaluate_method_agfn(input);
+
+	if (status || args.agfn.err)
+		return -ENXIO;
+
+	if (speed)
+		*speed = args.speed;
+
+	return 0;
+}
+
+static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan,
+				     int *speed)
+{
+	struct fan_args args = {
+		.agfn.len = sizeof(args),
+		.agfn.mfun = ASUS_FAN_MFUN,
+		.agfn.sfun = ASUS_FAN_SFUN_WRITE,
+		.fan = fan,
+		.speed = speed ?  *speed : 0,
+	};
+	struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+	int status;
+
+	/* 1: for setting 1st fan's speed 0: setting auto mode */
+	if (fan != 1 && fan != 0)
+		return -EINVAL;
+
+	status = asus_wmi_evaluate_method_agfn(input);
+
+	if (status || args.agfn.err)
+		return -ENXIO;
+
+	if (speed && fan == 1)
+		asus->asus_hwmon_pwm = *speed;
+
+	return 0;
+}
+
+/*
+ * Check if we can read the speed of one fan. If true we assume we can also
+ * control it.
+ */
+static int asus_hwmon_get_fan_number(struct asus_wmi *asus, int *num_fans)
+{
+	int status;
+	int speed = 0;
+
+	*num_fans = 0;
+
+	status = asus_hwmon_agfn_fan_speed_read(asus, 1, &speed);
+	if (!status)
+		*num_fans = 1;
+
+	return 0;
+}
+
+static int asus_hwmon_fan_set_auto(struct asus_wmi *asus)
+{
+	int status;
+
+	status = asus_hwmon_agfn_fan_speed_write(asus, 0, NULL);
+	if (status)
+		return -ENXIO;
+
+	asus->asus_hwmon_fan_manual_mode = false;
+
+	return 0;
+}
+
+static int asus_hwmon_fan_rpm_show(struct device *dev, int fan)
+{
+	struct asus_wmi *asus = dev_get_drvdata(dev);
+	int value;
+	int ret;
+
+	/* no speed readable on manual mode */
+	if (asus->asus_hwmon_fan_manual_mode)
+		return -ENXIO;
+
+	ret = asus_hwmon_agfn_fan_speed_read(asus, fan+1, &value);
+	if (ret) {
+		pr_warn("reading fan speed failed: %d\n", ret);
+		return -ENXIO;
+	}
+
+	return value;
+}
+
+static void asus_hwmon_pwm_show(struct asus_wmi *asus, int fan, int *value)
+{
+	int err;
+
+	if (asus->asus_hwmon_pwm >= 0) {
+		*value = asus->asus_hwmon_pwm;
+		return;
+	}
+
+	err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, value);
+	if (err < 0)
+		return;
+
+	*value &= 0xFF;
+
+	if (*value == 1) /* Low Speed */
+		*value = 85;
+	else if (*value == 2)
+		*value = 170;
+	else if (*value == 3)
+		*value = 255;
+	else if (*value) {
+		pr_err("Unknown fan speed %#x\n", *value);
+		*value = -1;
+	}
+}
+
+static ssize_t pwm1_show(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct asus_wmi *asus = dev_get_drvdata(dev);
+	int value;
+
+	asus_hwmon_pwm_show(asus, 0, &value);
+
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t pwm1_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count) {
+	struct asus_wmi *asus = dev_get_drvdata(dev);
+	int value;
+	int state;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &value);
+
+	if (ret)
+		return ret;
+
+	value = clamp(value, 0, 255);
+
+	state = asus_hwmon_agfn_fan_speed_write(asus, 1, &value);
+	if (state)
+		pr_warn("Setting fan speed failed: %d\n", state);
+	else
+		asus->asus_hwmon_fan_manual_mode = true;
+
+	return count;
+}
+
+static ssize_t fan1_input_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	int value = asus_hwmon_fan_rpm_show(dev, 0);
+
+	return sprintf(buf, "%d\n", value < 0 ? -1 : value*100);
+
+}
+
+static ssize_t pwm1_enable_show(struct device *dev,
+						 struct device_attribute *attr,
+						 char *buf)
+{
+	struct asus_wmi *asus = dev_get_drvdata(dev);
+
+	if (asus->asus_hwmon_fan_manual_mode)
+		return sprintf(buf, "%d\n", ASUS_FAN_CTRL_MANUAL);
+
+	return sprintf(buf, "%d\n", ASUS_FAN_CTRL_AUTO);
+}
+
+static ssize_t pwm1_enable_store(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t count)
+{
+	struct asus_wmi *asus = dev_get_drvdata(dev);
+	int status = 0;
+	int state;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &state);
+
+	if (ret)
+		return ret;
+
+	if (state == ASUS_FAN_CTRL_MANUAL)
+		asus->asus_hwmon_fan_manual_mode = true;
+	else
+		status = asus_hwmon_fan_set_auto(asus);
+
+	if (status)
+		return status;
+
+	return count;
+}
+
+static ssize_t fan1_label_show(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	return sprintf(buf, "%s\n", ASUS_FAN_DESC);
+}
+
+static ssize_t asus_hwmon_temp1(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct asus_wmi *asus = dev_get_drvdata(dev);
+	u32 value;
+	int err;
+
+	err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_THERMAL_CTRL, &value);
+
+	if (err < 0)
+		return err;
+
+	value = DECI_KELVIN_TO_CELSIUS((value & 0xFFFF)) * 1000;
+
+	return sprintf(buf, "%d\n", value);
+}
+
+/* Fan1 */
+static DEVICE_ATTR_RW(pwm1);
+static DEVICE_ATTR_RW(pwm1_enable);
+static DEVICE_ATTR_RO(fan1_input);
+static DEVICE_ATTR_RO(fan1_label);
+
+/* Temperature */
+static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
+
+static struct attribute *hwmon_attributes[] = {
+	&dev_attr_pwm1.attr,
+	&dev_attr_pwm1_enable.attr,
+	&dev_attr_fan1_input.attr,
+	&dev_attr_fan1_label.attr,
+
+	&dev_attr_temp1_input.attr,
+	NULL
+};
+
+static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
+					  struct attribute *attr, int idx)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct platform_device *pdev = to_platform_device(dev->parent);
+	struct asus_wmi *asus = platform_get_drvdata(pdev);
+	int dev_id = -1;
+	int fan_attr = -1;
+	u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
+	bool ok = true;
+
+	if (attr == &dev_attr_pwm1.attr)
+		dev_id = ASUS_WMI_DEVID_FAN_CTRL;
+	else if (attr == &dev_attr_temp1_input.attr)
+		dev_id = ASUS_WMI_DEVID_THERMAL_CTRL;
+
+
+	if (attr == &dev_attr_fan1_input.attr
+	    || attr == &dev_attr_fan1_label.attr
+	    || attr == &dev_attr_pwm1.attr
+	    || attr == &dev_attr_pwm1_enable.attr) {
+		fan_attr = 1;
+	}
+
+	if (dev_id != -1) {
+		int err = asus_wmi_get_devstate(asus, dev_id, &value);
+
+		if (err < 0 && fan_attr == -1)
+			return 0; /* can't return negative here */
+	}
+
+	if (dev_id == ASUS_WMI_DEVID_FAN_CTRL) {
+		/*
+		 * We need to find a better way, probably using sfun,
+		 * bits or spec ...
+		 * Currently we disable it if:
+		 * - ASUS_WMI_UNSUPPORTED_METHOD is returned
+		 * - reverved bits are non-zero
+		 * - sfun and presence bit are not set
+		 */
+		if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000
+		    || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT)))
+			ok = false;
+		else
+			ok = fan_attr <= asus->asus_hwmon_num_fans;
+	} else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
+		/* If value is zero, something is clearly wrong */
+		if (!value)
+			ok = false;
+	} else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) {
+		ok = true;
+	} else {
+		ok = false;
+	}
+
+	return ok ? attr->mode : 0;
+}
+
+static struct attribute_group hwmon_attribute_group = {
+	.is_visible = asus_hwmon_sysfs_is_visible,
+	.attrs = hwmon_attributes
+};
+__ATTRIBUTE_GROUPS(hwmon_attribute);
+
+static int asus_wmi_hwmon_init(struct asus_wmi *asus)
+{
+	struct device *hwmon;
+
+	hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev,
+						  "asus", asus,
+						  hwmon_attribute_groups);
+	if (IS_ERR(hwmon)) {
+		pr_err("Could not register asus hwmon device\n");
+		return PTR_ERR(hwmon);
+	}
+	return 0;
+}
+
+/*
+ * Backlight
+ */
+static int read_backlight_power(struct asus_wmi *asus)
+{
+	int ret;
+	if (asus->driver->quirks->store_backlight_power)
+		ret = !asus->driver->panel_power;
+	else
+		ret = asus_wmi_get_devstate_simple(asus,
+						   ASUS_WMI_DEVID_BACKLIGHT);
+
+	if (ret < 0)
+		return ret;
+
+	return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+}
+
+static int read_brightness_max(struct asus_wmi *asus)
+{
+	u32 retval;
+	int err;
+
+	err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval);
+
+	if (err < 0)
+		return err;
+
+	retval = retval & ASUS_WMI_DSTS_MAX_BRIGTH_MASK;
+	retval >>= 8;
+
+	if (!retval)
+		return -ENODEV;
+
+	return retval;
+}
+
+static int read_brightness(struct backlight_device *bd)
+{
+	struct asus_wmi *asus = bl_get_data(bd);
+	u32 retval;
+	int err;
+
+	err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval);
+
+	if (err < 0)
+		return err;
+
+	return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
+}
+
+static u32 get_scalar_command(struct backlight_device *bd)
+{
+	struct asus_wmi *asus = bl_get_data(bd);
+	u32 ctrl_param = 0;
+
+	if ((asus->driver->brightness < bd->props.brightness) ||
+	    bd->props.brightness == bd->props.max_brightness)
+		ctrl_param = 0x00008001;
+	else if ((asus->driver->brightness > bd->props.brightness) ||
+		 bd->props.brightness == 0)
+		ctrl_param = 0x00008000;
+
+	asus->driver->brightness = bd->props.brightness;
+
+	return ctrl_param;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+	struct asus_wmi *asus = bl_get_data(bd);
+	u32 ctrl_param;
+	int power, err = 0;
+
+	power = read_backlight_power(asus);
+	if (power != -ENODEV && bd->props.power != power) {
+		ctrl_param = !!(bd->props.power == FB_BLANK_UNBLANK);
+		err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT,
+					    ctrl_param, NULL);
+		if (asus->driver->quirks->store_backlight_power)
+			asus->driver->panel_power = bd->props.power;
+
+		/* When using scalar brightness, updating the brightness
+		 * will mess with the backlight power */
+		if (asus->driver->quirks->scalar_panel_brightness)
+			return err;
+	}
+
+	if (asus->driver->quirks->scalar_panel_brightness)
+		ctrl_param = get_scalar_command(bd);
+	else
+		ctrl_param = bd->props.brightness;
+
+	err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BRIGHTNESS,
+				    ctrl_param, NULL);
+
+	return err;
+}
+
+static const struct backlight_ops asus_wmi_bl_ops = {
+	.get_brightness = read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int asus_wmi_backlight_notify(struct asus_wmi *asus, int code)
+{
+	struct backlight_device *bd = asus->backlight_device;
+	int old = bd->props.brightness;
+	int new = old;
+
+	if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
+		new = code - NOTIFY_BRNUP_MIN + 1;
+	else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX)
+		new = code - NOTIFY_BRNDOWN_MIN;
+
+	bd->props.brightness = new;
+	backlight_update_status(bd);
+	backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
+
+	return old;
+}
+
+static int asus_wmi_backlight_init(struct asus_wmi *asus)
+{
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int max;
+	int power;
+
+	max = read_brightness_max(asus);
+	if (max < 0)
+		return max;
+
+	power = read_backlight_power(asus);
+
+	if (power == -ENODEV)
+		power = FB_BLANK_UNBLANK;
+	else if (power < 0)
+		return power;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = max;
+	bd = backlight_device_register(asus->driver->name,
+				       &asus->platform_device->dev, asus,
+				       &asus_wmi_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		pr_err("Could not register backlight device\n");
+		return PTR_ERR(bd);
+	}
+
+	asus->backlight_device = bd;
+
+	if (asus->driver->quirks->store_backlight_power)
+		asus->driver->panel_power = power;
+
+	bd->props.brightness = read_brightness(bd);
+	bd->props.power = power;
+	backlight_update_status(bd);
+
+	asus->driver->brightness = bd->props.brightness;
+
+	return 0;
+}
+
+static void asus_wmi_backlight_exit(struct asus_wmi *asus)
+{
+	backlight_device_unregister(asus->backlight_device);
+
+	asus->backlight_device = NULL;
+}
+
+static int is_display_toggle(int code)
+{
+	/* display toggle keys */
+	if ((code >= 0x61 && code <= 0x67) ||
+	    (code >= 0x8c && code <= 0x93) ||
+	    (code >= 0xa0 && code <= 0xa7) ||
+	    (code >= 0xd0 && code <= 0xd5))
+		return 1;
+
+	return 0;
+}
+
+static void asus_wmi_notify(u32 value, void *context)
+{
+	struct asus_wmi *asus = context;
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+	int code;
+	int orig_code;
+	unsigned int key_value = 1;
+	bool autorelease = 1;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_err("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+
+	if (!obj || obj->type != ACPI_TYPE_INTEGER)
+		goto exit;
+
+	code = obj->integer.value;
+	orig_code = code;
+
+	if (asus->driver->key_filter) {
+		asus->driver->key_filter(asus->driver, &code, &key_value,
+					 &autorelease);
+		if (code == ASUS_WMI_KEY_IGNORE)
+			goto exit;
+	}
+
+	if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
+		code = ASUS_WMI_BRN_UP;
+	else if (code >= NOTIFY_BRNDOWN_MIN &&
+		 code <= NOTIFY_BRNDOWN_MAX)
+		code = ASUS_WMI_BRN_DOWN;
+
+	if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
+		if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+			asus_wmi_backlight_notify(asus, orig_code);
+			goto exit;
+		}
+	}
+
+	if (is_display_toggle(code) &&
+	    asus->driver->quirks->no_display_toggle)
+		goto exit;
+
+	if (!sparse_keymap_report_event(asus->inputdev, code,
+					key_value, autorelease))
+		pr_info("Unknown key %x pressed\n", code);
+
+exit:
+	kfree(obj);
+}
+
+/*
+ * Sys helpers
+ */
+static int parse_arg(const char *buf, unsigned long count, int *val)
+{
+	if (!count)
+		return 0;
+	if (sscanf(buf, "%i", val) != 1)
+		return -EINVAL;
+	return count;
+}
+
+static ssize_t store_sys_wmi(struct asus_wmi *asus, int devid,
+			     const char *buf, size_t count)
+{
+	u32 retval;
+	int rv, err, value;
+
+	value = asus_wmi_get_devstate_simple(asus, devid);
+	if (value < 0)
+		return value;
+
+	rv = parse_arg(buf, count, &value);
+	err = asus_wmi_set_devstate(devid, value, &retval);
+
+	if (err < 0)
+		return err;
+
+	return rv;
+}
+
+static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf)
+{
+	int value = asus_wmi_get_devstate_simple(asus, devid);
+
+	if (value < 0)
+		return value;
+
+	return sprintf(buf, "%d\n", value);
+}
+
+#define ASUS_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm)			\
+	static ssize_t show_##_name(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		struct asus_wmi *asus = dev_get_drvdata(dev);		\
+									\
+		return show_sys_wmi(asus, _cm, buf);			\
+	}								\
+	static ssize_t store_##_name(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		struct asus_wmi *asus = dev_get_drvdata(dev);		\
+									\
+		return store_sys_wmi(asus, _cm, buf, count);		\
+	}								\
+	static struct device_attribute dev_attr_##_name = {		\
+		.attr = {						\
+			.name = __stringify(_name),			\
+			.mode = _mode },				\
+		.show   = show_##_name,					\
+		.store  = store_##_name,				\
+	}
+
+ASUS_WMI_CREATE_DEVICE_ATTR(touchpad, 0644, ASUS_WMI_DEVID_TOUCHPAD);
+ASUS_WMI_CREATE_DEVICE_ATTR(camera, 0644, ASUS_WMI_DEVID_CAMERA);
+ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER);
+ASUS_WMI_CREATE_DEVICE_ATTR(lid_resume, 0644, ASUS_WMI_DEVID_LID_RESUME);
+
+static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	int value, rv;
+
+	if (!count || sscanf(buf, "%i", &value) != 1)
+		return -EINVAL;
+	if (value < 0 || value > 2)
+		return -EINVAL;
+
+	rv = asus_wmi_evaluate_method(ASUS_WMI_METHODID_CFVS, value, 0, NULL);
+	if (rv < 0)
+		return rv;
+
+	return count;
+}
+
+static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv);
+
+static struct attribute *platform_attributes[] = {
+	&dev_attr_cpufv.attr,
+	&dev_attr_camera.attr,
+	&dev_attr_cardr.attr,
+	&dev_attr_touchpad.attr,
+	&dev_attr_lid_resume.attr,
+	NULL
+};
+
+static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+				    struct attribute *attr, int idx)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct platform_device *pdev = to_platform_device(dev);
+	struct asus_wmi *asus = platform_get_drvdata(pdev);
+	bool ok = true;
+	int devid = -1;
+
+	if (attr == &dev_attr_camera.attr)
+		devid = ASUS_WMI_DEVID_CAMERA;
+	else if (attr == &dev_attr_cardr.attr)
+		devid = ASUS_WMI_DEVID_CARDREADER;
+	else if (attr == &dev_attr_touchpad.attr)
+		devid = ASUS_WMI_DEVID_TOUCHPAD;
+	else if (attr == &dev_attr_lid_resume.attr)
+		devid = ASUS_WMI_DEVID_LID_RESUME;
+
+	if (devid != -1)
+		ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
+
+	return ok ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+	.is_visible = asus_sysfs_is_visible,
+	.attrs = platform_attributes
+};
+
+static void asus_wmi_sysfs_exit(struct platform_device *device)
+{
+	sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+static int asus_wmi_sysfs_init(struct platform_device *device)
+{
+	return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+/*
+ * Platform device
+ */
+static int asus_wmi_platform_init(struct asus_wmi *asus)
+{
+	int rv;
+
+	/* INIT enable hotkeys on some models */
+	if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_INIT, 0, 0, &rv))
+		pr_info("Initialization: %#x\n", rv);
+
+	/* We don't know yet what to do with this version... */
+	if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SPEC, 0, 0x9, &rv)) {
+		pr_info("BIOS WMI version: %d.%d\n", rv >> 16, rv & 0xFF);
+		asus->spec = rv;
+	}
+
+	/*
+	 * The SFUN method probably allows the original driver to get the list
+	 * of features supported by a given model. For now, 0x0100 or 0x0800
+	 * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
+	 * The significance of others is yet to be found.
+	 */
+	if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SFUN, 0, 0, &rv)) {
+		pr_info("SFUN value: %#x\n", rv);
+		asus->sfun = rv;
+	}
+
+	/*
+	 * Eee PC and Notebooks seems to have different method_id for DSTS,
+	 * but it may also be related to the BIOS's SPEC.
+	 * Note, on most Eeepc, there is no way to check if a method exist
+	 * or note, while on notebooks, they returns 0xFFFFFFFE on failure,
+	 * but once again, SPEC may probably be used for that kind of things.
+	 */
+	if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL))
+		asus->dsts_id = ASUS_WMI_METHODID_DSTS;
+	else
+		asus->dsts_id = ASUS_WMI_METHODID_DSTS2;
+
+	/* CWAP allow to define the behavior of the Fn+F2 key,
+	 * this method doesn't seems to be present on Eee PCs */
+	if (asus->driver->quirks->wapf >= 0)
+		asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP,
+				      asus->driver->quirks->wapf, NULL);
+
+	return asus_wmi_sysfs_init(asus->platform_device);
+}
+
+static void asus_wmi_platform_exit(struct asus_wmi *asus)
+{
+	asus_wmi_sysfs_exit(asus->platform_device);
+}
+
+/*
+ * debugfs
+ */
+struct asus_wmi_debugfs_node {
+	struct asus_wmi *asus;
+	char *name;
+	int (*show) (struct seq_file *m, void *data);
+};
+
+static int show_dsts(struct seq_file *m, void *data)
+{
+	struct asus_wmi *asus = m->private;
+	int err;
+	u32 retval = -1;
+
+	err = asus_wmi_get_devstate(asus, asus->debug.dev_id, &retval);
+
+	if (err < 0)
+		return err;
+
+	seq_printf(m, "DSTS(%#x) = %#x\n", asus->debug.dev_id, retval);
+
+	return 0;
+}
+
+static int show_devs(struct seq_file *m, void *data)
+{
+	struct asus_wmi *asus = m->private;
+	int err;
+	u32 retval = -1;
+
+	err = asus_wmi_set_devstate(asus->debug.dev_id, asus->debug.ctrl_param,
+				    &retval);
+
+	if (err < 0)
+		return err;
+
+	seq_printf(m, "DEVS(%#x, %#x) = %#x\n", asus->debug.dev_id,
+		   asus->debug.ctrl_param, retval);
+
+	return 0;
+}
+
+static int show_call(struct seq_file *m, void *data)
+{
+	struct asus_wmi *asus = m->private;
+	struct bios_args args = {
+		.arg0 = asus->debug.dev_id,
+		.arg1 = asus->debug.ctrl_param,
+	};
+	struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+
+	status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID,
+				     1, asus->debug.method_id,
+				     &input, &output);
+
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	obj = (union acpi_object *)output.pointer;
+	if (obj && obj->type == ACPI_TYPE_INTEGER)
+		seq_printf(m, "%#x(%#x, %#x) = %#x\n", asus->debug.method_id,
+			   asus->debug.dev_id, asus->debug.ctrl_param,
+			   (u32) obj->integer.value);
+	else
+		seq_printf(m, "%#x(%#x, %#x) = t:%d\n", asus->debug.method_id,
+			   asus->debug.dev_id, asus->debug.ctrl_param,
+			   obj ? obj->type : -1);
+
+	kfree(obj);
+
+	return 0;
+}
+
+static struct asus_wmi_debugfs_node asus_wmi_debug_files[] = {
+	{NULL, "devs", show_devs},
+	{NULL, "dsts", show_dsts},
+	{NULL, "call", show_call},
+};
+
+static int asus_wmi_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct asus_wmi_debugfs_node *node = inode->i_private;
+
+	return single_open(file, node->show, node->asus);
+}
+
+static const struct file_operations asus_wmi_debugfs_io_ops = {
+	.owner = THIS_MODULE,
+	.open = asus_wmi_debugfs_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static void asus_wmi_debugfs_exit(struct asus_wmi *asus)
+{
+	debugfs_remove_recursive(asus->debug.root);
+}
+
+static int asus_wmi_debugfs_init(struct asus_wmi *asus)
+{
+	struct dentry *dent;
+	int i;
+
+	asus->debug.root = debugfs_create_dir(asus->driver->name, NULL);
+	if (!asus->debug.root) {
+		pr_err("failed to create debugfs directory\n");
+		goto error_debugfs;
+	}
+
+	dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR,
+				  asus->debug.root, &asus->debug.method_id);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR,
+				  asus->debug.root, &asus->debug.dev_id);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR,
+				  asus->debug.root, &asus->debug.ctrl_param);
+	if (!dent)
+		goto error_debugfs;
+
+	for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) {
+		struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i];
+
+		node->asus = asus;
+		dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+					   asus->debug.root, node,
+					   &asus_wmi_debugfs_io_ops);
+		if (!dent) {
+			pr_err("failed to create debug file: %s\n", node->name);
+			goto error_debugfs;
+		}
+	}
+
+	return 0;
+
+error_debugfs:
+	asus_wmi_debugfs_exit(asus);
+	return -ENOMEM;
+}
+
+static int asus_wmi_fan_init(struct asus_wmi *asus)
+{
+	int status;
+
+	asus->asus_hwmon_pwm = -1;
+	asus->asus_hwmon_num_fans = -1;
+	asus->asus_hwmon_fan_manual_mode = false;
+
+	status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
+	if (status) {
+		asus->asus_hwmon_num_fans = 0;
+		pr_warn("Could not determine number of fans: %d\n", status);
+		return -ENXIO;
+	}
+
+	pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
+	return 0;
+}
+
+/*
+ * WMI Driver
+ */
+static int asus_wmi_add(struct platform_device *pdev)
+{
+	struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
+	struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv);
+	struct asus_wmi *asus;
+	const char *chassis_type;
+	acpi_status status;
+	int err;
+	u32 result;
+
+	asus = kzalloc(sizeof(struct asus_wmi), GFP_KERNEL);
+	if (!asus)
+		return -ENOMEM;
+
+	asus->driver = wdrv;
+	asus->platform_device = pdev;
+	wdrv->platform_device = pdev;
+	platform_set_drvdata(asus->platform_device, asus);
+
+	if (wdrv->detect_quirks)
+		wdrv->detect_quirks(asus->driver);
+
+	err = asus_wmi_platform_init(asus);
+	if (err)
+		goto fail_platform;
+
+	err = asus_wmi_input_init(asus);
+	if (err)
+		goto fail_input;
+
+	err = asus_wmi_fan_init(asus); /* probably no problems on error */
+	asus_hwmon_fan_set_auto(asus);
+
+	err = asus_wmi_hwmon_init(asus);
+	if (err)
+		goto fail_hwmon;
+
+	err = asus_wmi_led_init(asus);
+	if (err)
+		goto fail_leds;
+
+	err = asus_wmi_rfkill_init(asus);
+	if (err)
+		goto fail_rfkill;
+
+	/* Some Asus desktop boards export an acpi-video backlight interface,
+	   stop this from showing up */
+	chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE);
+	if (chassis_type && !strcmp(chassis_type, "3"))
+		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
+	if (asus->driver->quirks->wmi_backlight_power)
+		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		err = asus_wmi_backlight_init(asus);
+		if (err && err != -ENODEV)
+			goto fail_backlight;
+	}
+
+	status = wmi_install_notify_handler(asus->driver->event_guid,
+					    asus_wmi_notify, asus);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Unable to register notify handler - %d\n", status);
+		err = -ENODEV;
+		goto fail_wmi_handler;
+	}
+
+	err = asus_wmi_debugfs_init(asus);
+	if (err)
+		goto fail_debugfs;
+
+	asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WLAN, &result);
+	if (result & (ASUS_WMI_DSTS_PRESENCE_BIT | ASUS_WMI_DSTS_USER_BIT))
+		asus->driver->wlan_ctrl_by_user = 1;
+
+	return 0;
+
+fail_debugfs:
+	wmi_remove_notify_handler(asus->driver->event_guid);
+fail_wmi_handler:
+	asus_wmi_backlight_exit(asus);
+fail_backlight:
+	asus_wmi_rfkill_exit(asus);
+fail_rfkill:
+	asus_wmi_led_exit(asus);
+fail_leds:
+fail_hwmon:
+	asus_wmi_input_exit(asus);
+fail_input:
+	asus_wmi_platform_exit(asus);
+fail_platform:
+	kfree(asus);
+	return err;
+}
+
+static int asus_wmi_remove(struct platform_device *device)
+{
+	struct asus_wmi *asus;
+
+	asus = platform_get_drvdata(device);
+	wmi_remove_notify_handler(asus->driver->event_guid);
+	asus_wmi_backlight_exit(asus);
+	asus_wmi_input_exit(asus);
+	asus_wmi_led_exit(asus);
+	asus_wmi_rfkill_exit(asus);
+	asus_wmi_debugfs_exit(asus);
+	asus_wmi_platform_exit(asus);
+	asus_hwmon_fan_set_auto(asus);
+
+	kfree(asus);
+	return 0;
+}
+
+/*
+ * Platform driver - hibernate/resume callbacks
+ */
+static int asus_hotk_thaw(struct device *device)
+{
+	struct asus_wmi *asus = dev_get_drvdata(device);
+
+	if (asus->wlan.rfkill) {
+		bool wlan;
+
+		/*
+		 * Work around bios bug - acpi _PTS turns off the wireless led
+		 * during suspend.  Normally it restores it on resume, but
+		 * we should kick it ourselves in case hibernation is aborted.
+		 */
+		wlan = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);
+		asus_wmi_set_devstate(ASUS_WMI_DEVID_WLAN, wlan, NULL);
+	}
+
+	return 0;
+}
+
+static int asus_hotk_resume(struct device *device)
+{
+	struct asus_wmi *asus = dev_get_drvdata(device);
+
+	if (!IS_ERR_OR_NULL(asus->kbd_led.dev))
+		queue_work(asus->led_workqueue, &asus->kbd_led_work);
+
+	return 0;
+}
+
+static int asus_hotk_restore(struct device *device)
+{
+	struct asus_wmi *asus = dev_get_drvdata(device);
+	int bl;
+
+	/* Refresh both wlan rfkill state and pci hotplug */
+	if (asus->wlan.rfkill)
+		asus_rfkill_hotplug(asus);
+
+	if (asus->bluetooth.rfkill) {
+		bl = !asus_wmi_get_devstate_simple(asus,
+						   ASUS_WMI_DEVID_BLUETOOTH);
+		rfkill_set_sw_state(asus->bluetooth.rfkill, bl);
+	}
+	if (asus->wimax.rfkill) {
+		bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WIMAX);
+		rfkill_set_sw_state(asus->wimax.rfkill, bl);
+	}
+	if (asus->wwan3g.rfkill) {
+		bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WWAN3G);
+		rfkill_set_sw_state(asus->wwan3g.rfkill, bl);
+	}
+	if (asus->gps.rfkill) {
+		bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPS);
+		rfkill_set_sw_state(asus->gps.rfkill, bl);
+	}
+	if (asus->uwb.rfkill) {
+		bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_UWB);
+		rfkill_set_sw_state(asus->uwb.rfkill, bl);
+	}
+	if (!IS_ERR_OR_NULL(asus->kbd_led.dev))
+		queue_work(asus->led_workqueue, &asus->kbd_led_work);
+
+	return 0;
+}
+
+static const struct dev_pm_ops asus_pm_ops = {
+	.thaw = asus_hotk_thaw,
+	.restore = asus_hotk_restore,
+	.resume = asus_hotk_resume,
+};
+
+static int asus_wmi_probe(struct platform_device *pdev)
+{
+	struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
+	struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv);
+	int ret;
+
+	if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) {
+		pr_warn("Management GUID not found\n");
+		return -ENODEV;
+	}
+
+	if (wdrv->event_guid && !wmi_has_guid(wdrv->event_guid)) {
+		pr_warn("Event GUID not found\n");
+		return -ENODEV;
+	}
+
+	if (wdrv->probe) {
+		ret = wdrv->probe(pdev);
+		if (ret)
+			return ret;
+	}
+
+	return asus_wmi_add(pdev);
+}
+
+static bool used;
+
+int __init_or_module asus_wmi_register_driver(struct asus_wmi_driver *driver)
+{
+	struct platform_driver *platform_driver;
+	struct platform_device *platform_device;
+
+	if (used)
+		return -EBUSY;
+
+	platform_driver = &driver->platform_driver;
+	platform_driver->remove = asus_wmi_remove;
+	platform_driver->driver.owner = driver->owner;
+	platform_driver->driver.name = driver->name;
+	platform_driver->driver.pm = &asus_pm_ops;
+
+	platform_device = platform_create_bundle(platform_driver,
+						 asus_wmi_probe,
+						 NULL, 0, NULL, 0);
+	if (IS_ERR(platform_device))
+		return PTR_ERR(platform_device);
+
+	used = true;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(asus_wmi_register_driver);
+
+void asus_wmi_unregister_driver(struct asus_wmi_driver *driver)
+{
+	platform_device_unregister(driver->platform_device);
+	platform_driver_unregister(&driver->platform_driver);
+	used = false;
+}
+EXPORT_SYMBOL_GPL(asus_wmi_unregister_driver);
+
+static int __init asus_wmi_init(void)
+{
+	if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) {
+		pr_info("Asus Management GUID not found\n");
+		return -ENODEV;
+	}
+
+	pr_info("ASUS WMI generic driver loaded\n");
+	return 0;
+}
+
+static void __exit asus_wmi_exit(void)
+{
+	pr_info("ASUS WMI generic driver unloaded\n");
+}
+
+module_init(asus_wmi_init);
+module_exit(asus_wmi_exit);
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
new file mode 100644
index 0000000..4da4c8b
--- /dev/null
+++ b/drivers/platform/x86/asus-wmi.h
@@ -0,0 +1,84 @@
+/*
+ * Asus PC WMI hotkey driver
+ *
+ * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ *  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
+ */
+
+#ifndef _ASUS_WMI_H_
+#define _ASUS_WMI_H_
+
+#include <linux/platform_device.h>
+
+#define ASUS_WMI_KEY_IGNORE (-1)
+#define ASUS_WMI_BRN_DOWN	0x20
+#define ASUS_WMI_BRN_UP		0x2f
+
+struct module;
+struct key_entry;
+struct asus_wmi;
+
+struct quirk_entry {
+	bool hotplug_wireless;
+	bool scalar_panel_brightness;
+	bool store_backlight_power;
+	bool wmi_backlight_power;
+	int wapf;
+	/*
+	 * For machines with AMD graphic chips, it will send out WMI event
+	 * and ACPI interrupt at the same time while hitting the hotkey.
+	 * To simplify the problem, we just have to ignore the WMI event,
+	 * and let the ACPI interrupt to send out the key event.
+	 */
+	int no_display_toggle;
+};
+
+struct asus_wmi_driver {
+	int			brightness;
+	int			panel_power;
+	int			wlan_ctrl_by_user;
+
+	const char		*name;
+	struct module		*owner;
+
+	const char		*event_guid;
+
+	const struct key_entry	*keymap;
+	const char		*input_name;
+	const char		*input_phys;
+	struct quirk_entry	*quirks;
+	/* Returns new code, value, and autorelease values in arguments.
+	 * Return ASUS_WMI_KEY_IGNORE in code if event should be ignored. */
+	void (*key_filter) (struct asus_wmi_driver *driver, int *code,
+			    unsigned int *value, bool *autorelease);
+
+	int (*probe) (struct platform_device *device);
+	void (*detect_quirks) (struct asus_wmi_driver *driver);
+
+	struct platform_driver	platform_driver;
+	struct platform_device *platform_device;
+};
+
+int asus_wmi_register_driver(struct asus_wmi_driver *driver);
+void asus_wmi_unregister_driver(struct asus_wmi_driver *driver);
+
+#endif /* !_ASUS_WMI_H_ */
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c
new file mode 100644
index 0000000..55cf10b
--- /dev/null
+++ b/drivers/platform/x86/classmate-laptop.c
@@ -0,0 +1,1171 @@
+/*
+ *  Copyright (C) 2009  Thadeu Lima de Souza Cascardo <cascardo@holoscopio.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.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/acpi.h>
+#include <linux/backlight.h>
+#include <linux/input.h>
+#include <linux/rfkill.h>
+
+MODULE_LICENSE("GPL");
+
+struct cmpc_accel {
+	int sensitivity;
+	int g_select;
+	int inputdev_state;
+};
+
+#define CMPC_ACCEL_DEV_STATE_CLOSED	0
+#define CMPC_ACCEL_DEV_STATE_OPEN	1
+
+#define CMPC_ACCEL_SENSITIVITY_DEFAULT		5
+#define CMPC_ACCEL_G_SELECT_DEFAULT		0
+
+#define CMPC_ACCEL_HID		"ACCE0000"
+#define CMPC_ACCEL_HID_V4	"ACCE0001"
+#define CMPC_TABLET_HID		"TBLT0000"
+#define CMPC_IPML_HID	"IPML200"
+#define CMPC_KEYS_HID		"FNBT0000"
+
+/*
+ * Generic input device code.
+ */
+
+typedef void (*input_device_init)(struct input_dev *dev);
+
+static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name,
+				       input_device_init idev_init)
+{
+	struct input_dev *inputdev;
+	int error;
+
+	inputdev = input_allocate_device();
+	if (!inputdev)
+		return -ENOMEM;
+	inputdev->name = name;
+	inputdev->dev.parent = &acpi->dev;
+	idev_init(inputdev);
+	error = input_register_device(inputdev);
+	if (error) {
+		input_free_device(inputdev);
+		return error;
+	}
+	dev_set_drvdata(&acpi->dev, inputdev);
+	return 0;
+}
+
+static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi)
+{
+	struct input_dev *inputdev = dev_get_drvdata(&acpi->dev);
+	input_unregister_device(inputdev);
+	return 0;
+}
+
+/*
+ * Accelerometer code for Classmate V4
+ */
+static acpi_status cmpc_start_accel_v4(acpi_handle handle)
+{
+	union acpi_object param[4];
+	struct acpi_object_list input;
+	acpi_status status;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x3;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = 0;
+	param[2].type = ACPI_TYPE_INTEGER;
+	param[2].integer.value = 0;
+	param[3].type = ACPI_TYPE_INTEGER;
+	param[3].integer.value = 0;
+	input.count = 4;
+	input.pointer = param;
+	status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+	return status;
+}
+
+static acpi_status cmpc_stop_accel_v4(acpi_handle handle)
+{
+	union acpi_object param[4];
+	struct acpi_object_list input;
+	acpi_status status;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x4;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = 0;
+	param[2].type = ACPI_TYPE_INTEGER;
+	param[2].integer.value = 0;
+	param[3].type = ACPI_TYPE_INTEGER;
+	param[3].integer.value = 0;
+	input.count = 4;
+	input.pointer = param;
+	status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+	return status;
+}
+
+static acpi_status cmpc_accel_set_sensitivity_v4(acpi_handle handle, int val)
+{
+	union acpi_object param[4];
+	struct acpi_object_list input;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x02;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = val;
+	param[2].type = ACPI_TYPE_INTEGER;
+	param[2].integer.value = 0;
+	param[3].type = ACPI_TYPE_INTEGER;
+	param[3].integer.value = 0;
+	input.count = 4;
+	input.pointer = param;
+	return acpi_evaluate_object(handle, "ACMD", &input, NULL);
+}
+
+static acpi_status cmpc_accel_set_g_select_v4(acpi_handle handle, int val)
+{
+	union acpi_object param[4];
+	struct acpi_object_list input;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x05;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = val;
+	param[2].type = ACPI_TYPE_INTEGER;
+	param[2].integer.value = 0;
+	param[3].type = ACPI_TYPE_INTEGER;
+	param[3].integer.value = 0;
+	input.count = 4;
+	input.pointer = param;
+	return acpi_evaluate_object(handle, "ACMD", &input, NULL);
+}
+
+static acpi_status cmpc_get_accel_v4(acpi_handle handle,
+				     int16_t *x,
+				     int16_t *y,
+				     int16_t *z)
+{
+	union acpi_object param[4];
+	struct acpi_object_list input;
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	int16_t *locs;
+	acpi_status status;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x01;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = 0;
+	param[2].type = ACPI_TYPE_INTEGER;
+	param[2].integer.value = 0;
+	param[3].type = ACPI_TYPE_INTEGER;
+	param[3].integer.value = 0;
+	input.count = 4;
+	input.pointer = param;
+	status = acpi_evaluate_object(handle, "ACMD", &input, &output);
+	if (ACPI_SUCCESS(status)) {
+		union acpi_object *obj;
+		obj = output.pointer;
+		locs = (int16_t *) obj->buffer.pointer;
+		*x = locs[0];
+		*y = locs[1];
+		*z = locs[2];
+		kfree(output.pointer);
+	}
+	return status;
+}
+
+static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event)
+{
+	if (event == 0x81) {
+		int16_t x, y, z;
+		acpi_status status;
+
+		status = cmpc_get_accel_v4(dev->handle, &x, &y, &z);
+		if (ACPI_SUCCESS(status)) {
+			struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
+
+			input_report_abs(inputdev, ABS_X, x);
+			input_report_abs(inputdev, ABS_Y, y);
+			input_report_abs(inputdev, ABS_Z, z);
+			input_sync(inputdev);
+		}
+	}
+}
+
+static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct acpi_device *acpi;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	acpi = to_acpi_device(dev);
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	return sprintf(buf, "%d\n", accel->sensitivity);
+}
+
+static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev,
+					       struct device_attribute *attr,
+					       const char *buf, size_t count)
+{
+	struct acpi_device *acpi;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+	unsigned long sensitivity;
+	int r;
+
+	acpi = to_acpi_device(dev);
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	r = kstrtoul(buf, 0, &sensitivity);
+	if (r)
+		return r;
+
+	/* sensitivity must be between 1 and 127 */
+	if (sensitivity < 1 || sensitivity > 127)
+		return -EINVAL;
+
+	accel->sensitivity = sensitivity;
+	cmpc_accel_set_sensitivity_v4(acpi->handle, sensitivity);
+
+	return strnlen(buf, count);
+}
+
+static struct device_attribute cmpc_accel_sensitivity_attr_v4 = {
+	.attr = { .name = "sensitivity", .mode = 0660 },
+	.show = cmpc_accel_sensitivity_show_v4,
+	.store = cmpc_accel_sensitivity_store_v4
+};
+
+static ssize_t cmpc_accel_g_select_show_v4(struct device *dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct acpi_device *acpi;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	acpi = to_acpi_device(dev);
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	return sprintf(buf, "%d\n", accel->g_select);
+}
+
+static ssize_t cmpc_accel_g_select_store_v4(struct device *dev,
+					    struct device_attribute *attr,
+					    const char *buf, size_t count)
+{
+	struct acpi_device *acpi;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+	unsigned long g_select;
+	int r;
+
+	acpi = to_acpi_device(dev);
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	r = kstrtoul(buf, 0, &g_select);
+	if (r)
+		return r;
+
+	/* 0 means 1.5g, 1 means 6g, everything else is wrong */
+	if (g_select != 0 && g_select != 1)
+		return -EINVAL;
+
+	accel->g_select = g_select;
+	cmpc_accel_set_g_select_v4(acpi->handle, g_select);
+
+	return strnlen(buf, count);
+}
+
+static struct device_attribute cmpc_accel_g_select_attr_v4 = {
+	.attr = { .name = "g_select", .mode = 0660 },
+	.show = cmpc_accel_g_select_show_v4,
+	.store = cmpc_accel_g_select_store_v4
+};
+
+static int cmpc_accel_open_v4(struct input_dev *input)
+{
+	struct acpi_device *acpi;
+	struct cmpc_accel *accel;
+
+	acpi = to_acpi_device(input->dev.parent);
+	accel = dev_get_drvdata(&input->dev);
+
+	cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
+	cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select);
+
+	if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) {
+		accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN;
+		return 0;
+	}
+	return -EIO;
+}
+
+static void cmpc_accel_close_v4(struct input_dev *input)
+{
+	struct acpi_device *acpi;
+	struct cmpc_accel *accel;
+
+	acpi = to_acpi_device(input->dev.parent);
+	accel = dev_get_drvdata(&input->dev);
+
+	cmpc_stop_accel_v4(acpi->handle);
+	accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED;
+}
+
+static void cmpc_accel_idev_init_v4(struct input_dev *inputdev)
+{
+	set_bit(EV_ABS, inputdev->evbit);
+	input_set_abs_params(inputdev, ABS_X, -255, 255, 16, 0);
+	input_set_abs_params(inputdev, ABS_Y, -255, 255, 16, 0);
+	input_set_abs_params(inputdev, ABS_Z, -255, 255, 16, 0);
+	inputdev->open = cmpc_accel_open_v4;
+	inputdev->close = cmpc_accel_close_v4;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cmpc_accel_suspend_v4(struct device *dev)
+{
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	inputdev = dev_get_drvdata(dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN)
+		return cmpc_stop_accel_v4(to_acpi_device(dev)->handle);
+
+	return 0;
+}
+
+static int cmpc_accel_resume_v4(struct device *dev)
+{
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	inputdev = dev_get_drvdata(dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) {
+		cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle,
+					      accel->sensitivity);
+		cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle,
+					   accel->g_select);
+
+		if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle)))
+			return -EIO;
+	}
+
+	return 0;
+}
+#endif
+
+static int cmpc_accel_add_v4(struct acpi_device *acpi)
+{
+	int error;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	accel = kmalloc(sizeof(*accel), GFP_KERNEL);
+	if (!accel)
+		return -ENOMEM;
+
+	accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED;
+
+	accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT;
+	cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
+
+	error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
+	if (error)
+		goto failed_sensitivity;
+
+	accel->g_select = CMPC_ACCEL_G_SELECT_DEFAULT;
+	cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select);
+
+	error = device_create_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
+	if (error)
+		goto failed_g_select;
+
+	error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4",
+					    cmpc_accel_idev_init_v4);
+	if (error)
+		goto failed_input;
+
+	inputdev = dev_get_drvdata(&acpi->dev);
+	dev_set_drvdata(&inputdev->dev, accel);
+
+	return 0;
+
+failed_input:
+	device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
+failed_g_select:
+	device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
+failed_sensitivity:
+	kfree(accel);
+	return error;
+}
+
+static int cmpc_accel_remove_v4(struct acpi_device *acpi)
+{
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
+	device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
+	return cmpc_remove_acpi_notify_device(acpi);
+}
+
+static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4,
+			 cmpc_accel_resume_v4);
+
+static const struct acpi_device_id cmpc_accel_device_ids_v4[] = {
+	{CMPC_ACCEL_HID_V4, 0},
+	{"", 0}
+};
+
+static struct acpi_driver cmpc_accel_acpi_driver_v4 = {
+	.owner = THIS_MODULE,
+	.name = "cmpc_accel_v4",
+	.class = "cmpc_accel_v4",
+	.ids = cmpc_accel_device_ids_v4,
+	.ops = {
+		.add = cmpc_accel_add_v4,
+		.remove = cmpc_accel_remove_v4,
+		.notify = cmpc_accel_handler_v4,
+	},
+	.drv.pm = &cmpc_accel_pm,
+};
+
+
+/*
+ * Accelerometer code for Classmate versions prior to V4
+ */
+static acpi_status cmpc_start_accel(acpi_handle handle)
+{
+	union acpi_object param[2];
+	struct acpi_object_list input;
+	acpi_status status;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x3;
+	param[1].type = ACPI_TYPE_INTEGER;
+	input.count = 2;
+	input.pointer = param;
+	status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+	return status;
+}
+
+static acpi_status cmpc_stop_accel(acpi_handle handle)
+{
+	union acpi_object param[2];
+	struct acpi_object_list input;
+	acpi_status status;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x4;
+	param[1].type = ACPI_TYPE_INTEGER;
+	input.count = 2;
+	input.pointer = param;
+	status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+	return status;
+}
+
+static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val)
+{
+	union acpi_object param[2];
+	struct acpi_object_list input;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x02;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = val;
+	input.count = 2;
+	input.pointer = param;
+	return acpi_evaluate_object(handle, "ACMD", &input, NULL);
+}
+
+static acpi_status cmpc_get_accel(acpi_handle handle,
+				  unsigned char *x,
+				  unsigned char *y,
+				  unsigned char *z)
+{
+	union acpi_object param[2];
+	struct acpi_object_list input;
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	unsigned char *locs;
+	acpi_status status;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0x01;
+	param[1].type = ACPI_TYPE_INTEGER;
+	input.count = 2;
+	input.pointer = param;
+	status = acpi_evaluate_object(handle, "ACMD", &input, &output);
+	if (ACPI_SUCCESS(status)) {
+		union acpi_object *obj;
+		obj = output.pointer;
+		locs = obj->buffer.pointer;
+		*x = locs[0];
+		*y = locs[1];
+		*z = locs[2];
+		kfree(output.pointer);
+	}
+	return status;
+}
+
+static void cmpc_accel_handler(struct acpi_device *dev, u32 event)
+{
+	if (event == 0x81) {
+		unsigned char x, y, z;
+		acpi_status status;
+
+		status = cmpc_get_accel(dev->handle, &x, &y, &z);
+		if (ACPI_SUCCESS(status)) {
+			struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
+
+			input_report_abs(inputdev, ABS_X, x);
+			input_report_abs(inputdev, ABS_Y, y);
+			input_report_abs(inputdev, ABS_Z, z);
+			input_sync(inputdev);
+		}
+	}
+}
+
+static ssize_t cmpc_accel_sensitivity_show(struct device *dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct acpi_device *acpi;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	acpi = to_acpi_device(dev);
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	return sprintf(buf, "%d\n", accel->sensitivity);
+}
+
+static ssize_t cmpc_accel_sensitivity_store(struct device *dev,
+					    struct device_attribute *attr,
+					    const char *buf, size_t count)
+{
+	struct acpi_device *acpi;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+	unsigned long sensitivity;
+	int r;
+
+	acpi = to_acpi_device(dev);
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	r = kstrtoul(buf, 0, &sensitivity);
+	if (r)
+		return r;
+
+	accel->sensitivity = sensitivity;
+	cmpc_accel_set_sensitivity(acpi->handle, sensitivity);
+
+	return strnlen(buf, count);
+}
+
+static struct device_attribute cmpc_accel_sensitivity_attr = {
+	.attr = { .name = "sensitivity", .mode = 0660 },
+	.show = cmpc_accel_sensitivity_show,
+	.store = cmpc_accel_sensitivity_store
+};
+
+static int cmpc_accel_open(struct input_dev *input)
+{
+	struct acpi_device *acpi;
+
+	acpi = to_acpi_device(input->dev.parent);
+	if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle)))
+		return 0;
+	return -EIO;
+}
+
+static void cmpc_accel_close(struct input_dev *input)
+{
+	struct acpi_device *acpi;
+
+	acpi = to_acpi_device(input->dev.parent);
+	cmpc_stop_accel(acpi->handle);
+}
+
+static void cmpc_accel_idev_init(struct input_dev *inputdev)
+{
+	set_bit(EV_ABS, inputdev->evbit);
+	input_set_abs_params(inputdev, ABS_X, 0, 255, 8, 0);
+	input_set_abs_params(inputdev, ABS_Y, 0, 255, 8, 0);
+	input_set_abs_params(inputdev, ABS_Z, 0, 255, 8, 0);
+	inputdev->open = cmpc_accel_open;
+	inputdev->close = cmpc_accel_close;
+}
+
+static int cmpc_accel_add(struct acpi_device *acpi)
+{
+	int error;
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	accel = kmalloc(sizeof(*accel), GFP_KERNEL);
+	if (!accel)
+		return -ENOMEM;
+
+	accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT;
+	cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity);
+
+	error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
+	if (error)
+		goto failed_file;
+
+	error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel",
+					    cmpc_accel_idev_init);
+	if (error)
+		goto failed_input;
+
+	inputdev = dev_get_drvdata(&acpi->dev);
+	dev_set_drvdata(&inputdev->dev, accel);
+
+	return 0;
+
+failed_input:
+	device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
+failed_file:
+	kfree(accel);
+	return error;
+}
+
+static int cmpc_accel_remove(struct acpi_device *acpi)
+{
+	struct input_dev *inputdev;
+	struct cmpc_accel *accel;
+
+	inputdev = dev_get_drvdata(&acpi->dev);
+	accel = dev_get_drvdata(&inputdev->dev);
+
+	device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
+	return cmpc_remove_acpi_notify_device(acpi);
+}
+
+static const struct acpi_device_id cmpc_accel_device_ids[] = {
+	{CMPC_ACCEL_HID, 0},
+	{"", 0}
+};
+
+static struct acpi_driver cmpc_accel_acpi_driver = {
+	.owner = THIS_MODULE,
+	.name = "cmpc_accel",
+	.class = "cmpc_accel",
+	.ids = cmpc_accel_device_ids,
+	.ops = {
+		.add = cmpc_accel_add,
+		.remove = cmpc_accel_remove,
+		.notify = cmpc_accel_handler,
+	}
+};
+
+
+/*
+ * Tablet mode code.
+ */
+static acpi_status cmpc_get_tablet(acpi_handle handle,
+				   unsigned long long *value)
+{
+	union acpi_object param;
+	struct acpi_object_list input;
+	unsigned long long output;
+	acpi_status status;
+
+	param.type = ACPI_TYPE_INTEGER;
+	param.integer.value = 0x01;
+	input.count = 1;
+	input.pointer = &param;
+	status = acpi_evaluate_integer(handle, "TCMD", &input, &output);
+	if (ACPI_SUCCESS(status))
+		*value = output;
+	return status;
+}
+
+static void cmpc_tablet_handler(struct acpi_device *dev, u32 event)
+{
+	unsigned long long val = 0;
+	struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
+
+	if (event == 0x81) {
+		if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) {
+			input_report_switch(inputdev, SW_TABLET_MODE, !val);
+			input_sync(inputdev);
+		}
+	}
+}
+
+static void cmpc_tablet_idev_init(struct input_dev *inputdev)
+{
+	unsigned long long val = 0;
+	struct acpi_device *acpi;
+
+	set_bit(EV_SW, inputdev->evbit);
+	set_bit(SW_TABLET_MODE, inputdev->swbit);
+
+	acpi = to_acpi_device(inputdev->dev.parent);
+	if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) {
+		input_report_switch(inputdev, SW_TABLET_MODE, !val);
+		input_sync(inputdev);
+	}
+}
+
+static int cmpc_tablet_add(struct acpi_device *acpi)
+{
+	return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet",
+					   cmpc_tablet_idev_init);
+}
+
+static int cmpc_tablet_remove(struct acpi_device *acpi)
+{
+	return cmpc_remove_acpi_notify_device(acpi);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cmpc_tablet_resume(struct device *dev)
+{
+	struct input_dev *inputdev = dev_get_drvdata(dev);
+
+	unsigned long long val = 0;
+	if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) {
+		input_report_switch(inputdev, SW_TABLET_MODE, !val);
+		input_sync(inputdev);
+	}
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(cmpc_tablet_pm, NULL, cmpc_tablet_resume);
+
+static const struct acpi_device_id cmpc_tablet_device_ids[] = {
+	{CMPC_TABLET_HID, 0},
+	{"", 0}
+};
+
+static struct acpi_driver cmpc_tablet_acpi_driver = {
+	.owner = THIS_MODULE,
+	.name = "cmpc_tablet",
+	.class = "cmpc_tablet",
+	.ids = cmpc_tablet_device_ids,
+	.ops = {
+		.add = cmpc_tablet_add,
+		.remove = cmpc_tablet_remove,
+		.notify = cmpc_tablet_handler,
+	},
+	.drv.pm = &cmpc_tablet_pm,
+};
+
+
+/*
+ * Backlight code.
+ */
+
+static acpi_status cmpc_get_brightness(acpi_handle handle,
+				       unsigned long long *value)
+{
+	union acpi_object param;
+	struct acpi_object_list input;
+	unsigned long long output;
+	acpi_status status;
+
+	param.type = ACPI_TYPE_INTEGER;
+	param.integer.value = 0xC0;
+	input.count = 1;
+	input.pointer = &param;
+	status = acpi_evaluate_integer(handle, "GRDI", &input, &output);
+	if (ACPI_SUCCESS(status))
+		*value = output;
+	return status;
+}
+
+static acpi_status cmpc_set_brightness(acpi_handle handle,
+				       unsigned long long value)
+{
+	union acpi_object param[2];
+	struct acpi_object_list input;
+	acpi_status status;
+	unsigned long long output;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0xC0;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = value;
+	input.count = 2;
+	input.pointer = param;
+	status = acpi_evaluate_integer(handle, "GWRI", &input, &output);
+	return status;
+}
+
+static int cmpc_bl_get_brightness(struct backlight_device *bd)
+{
+	acpi_status status;
+	acpi_handle handle;
+	unsigned long long brightness;
+
+	handle = bl_get_data(bd);
+	status = cmpc_get_brightness(handle, &brightness);
+	if (ACPI_SUCCESS(status))
+		return brightness;
+	else
+		return -1;
+}
+
+static int cmpc_bl_update_status(struct backlight_device *bd)
+{
+	acpi_status status;
+	acpi_handle handle;
+
+	handle = bl_get_data(bd);
+	status = cmpc_set_brightness(handle, bd->props.brightness);
+	if (ACPI_SUCCESS(status))
+		return 0;
+	else
+		return -1;
+}
+
+static const struct backlight_ops cmpc_bl_ops = {
+	.get_brightness = cmpc_bl_get_brightness,
+	.update_status = cmpc_bl_update_status
+};
+
+/*
+ * RFKILL code.
+ */
+
+static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle,
+					unsigned long long *value)
+{
+	union acpi_object param;
+	struct acpi_object_list input;
+	unsigned long long output;
+	acpi_status status;
+
+	param.type = ACPI_TYPE_INTEGER;
+	param.integer.value = 0xC1;
+	input.count = 1;
+	input.pointer = &param;
+	status = acpi_evaluate_integer(handle, "GRDI", &input, &output);
+	if (ACPI_SUCCESS(status))
+		*value = output;
+	return status;
+}
+
+static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle,
+					unsigned long long value)
+{
+	union acpi_object param[2];
+	struct acpi_object_list input;
+	acpi_status status;
+	unsigned long long output;
+
+	param[0].type = ACPI_TYPE_INTEGER;
+	param[0].integer.value = 0xC1;
+	param[1].type = ACPI_TYPE_INTEGER;
+	param[1].integer.value = value;
+	input.count = 2;
+	input.pointer = param;
+	status = acpi_evaluate_integer(handle, "GWRI", &input, &output);
+	return status;
+}
+
+static void cmpc_rfkill_query(struct rfkill *rfkill, void *data)
+{
+	acpi_status status;
+	acpi_handle handle;
+	unsigned long long state;
+	bool blocked;
+
+	handle = data;
+	status = cmpc_get_rfkill_wlan(handle, &state);
+	if (ACPI_SUCCESS(status)) {
+		blocked = state & 1 ? false : true;
+		rfkill_set_sw_state(rfkill, blocked);
+	}
+}
+
+static int cmpc_rfkill_block(void *data, bool blocked)
+{
+	acpi_status status;
+	acpi_handle handle;
+	unsigned long long state;
+	bool is_blocked;
+
+	handle = data;
+	status = cmpc_get_rfkill_wlan(handle, &state);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+	/* Check if we really need to call cmpc_set_rfkill_wlan */
+	is_blocked = state & 1 ? false : true;
+	if (is_blocked != blocked) {
+		state = blocked ? 0 : 1;
+		status = cmpc_set_rfkill_wlan(handle, state);
+		if (ACPI_FAILURE(status))
+			return -ENODEV;
+	}
+	return 0;
+}
+
+static const struct rfkill_ops cmpc_rfkill_ops = {
+	.query = cmpc_rfkill_query,
+	.set_block = cmpc_rfkill_block,
+};
+
+/*
+ * Common backlight and rfkill code.
+ */
+
+struct ipml200_dev {
+	struct backlight_device *bd;
+	struct rfkill *rf;
+};
+
+static int cmpc_ipml_add(struct acpi_device *acpi)
+{
+	int retval;
+	struct ipml200_dev *ipml;
+	struct backlight_properties props;
+
+	ipml = kmalloc(sizeof(*ipml), GFP_KERNEL);
+	if (ipml == NULL)
+		return -ENOMEM;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = 7;
+	ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev,
+					     acpi->handle, &cmpc_bl_ops,
+					     &props);
+	if (IS_ERR(ipml->bd)) {
+		retval = PTR_ERR(ipml->bd);
+		goto out_bd;
+	}
+
+	ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN,
+				&cmpc_rfkill_ops, acpi->handle);
+	/*
+	 * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV).
+	 * This is OK, however, since all other uses of the device will not
+	 * derefence it.
+	 */
+	if (ipml->rf) {
+		retval = rfkill_register(ipml->rf);
+		if (retval) {
+			rfkill_destroy(ipml->rf);
+			ipml->rf = NULL;
+		}
+	}
+
+	dev_set_drvdata(&acpi->dev, ipml);
+	return 0;
+
+out_bd:
+	kfree(ipml);
+	return retval;
+}
+
+static int cmpc_ipml_remove(struct acpi_device *acpi)
+{
+	struct ipml200_dev *ipml;
+
+	ipml = dev_get_drvdata(&acpi->dev);
+
+	backlight_device_unregister(ipml->bd);
+
+	if (ipml->rf) {
+		rfkill_unregister(ipml->rf);
+		rfkill_destroy(ipml->rf);
+	}
+
+	kfree(ipml);
+
+	return 0;
+}
+
+static const struct acpi_device_id cmpc_ipml_device_ids[] = {
+	{CMPC_IPML_HID, 0},
+	{"", 0}
+};
+
+static struct acpi_driver cmpc_ipml_acpi_driver = {
+	.owner = THIS_MODULE,
+	.name = "cmpc",
+	.class = "cmpc",
+	.ids = cmpc_ipml_device_ids,
+	.ops = {
+		.add = cmpc_ipml_add,
+		.remove = cmpc_ipml_remove
+	}
+};
+
+
+/*
+ * Extra keys code.
+ */
+static int cmpc_keys_codes[] = {
+	KEY_UNKNOWN,
+	KEY_WLAN,
+	KEY_SWITCHVIDEOMODE,
+	KEY_BRIGHTNESSDOWN,
+	KEY_BRIGHTNESSUP,
+	KEY_VENDOR,
+	KEY_UNKNOWN,
+	KEY_CAMERA,
+	KEY_BACK,
+	KEY_FORWARD,
+	KEY_MAX
+};
+
+static void cmpc_keys_handler(struct acpi_device *dev, u32 event)
+{
+	struct input_dev *inputdev;
+	int code = KEY_MAX;
+
+	if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes))
+		code = cmpc_keys_codes[event & 0x0F];
+	inputdev = dev_get_drvdata(&dev->dev);
+	input_report_key(inputdev, code, !(event & 0x10));
+	input_sync(inputdev);
+}
+
+static void cmpc_keys_idev_init(struct input_dev *inputdev)
+{
+	int i;
+
+	set_bit(EV_KEY, inputdev->evbit);
+	for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++)
+		set_bit(cmpc_keys_codes[i], inputdev->keybit);
+}
+
+static int cmpc_keys_add(struct acpi_device *acpi)
+{
+	return cmpc_add_acpi_notify_device(acpi, "cmpc_keys",
+					   cmpc_keys_idev_init);
+}
+
+static int cmpc_keys_remove(struct acpi_device *acpi)
+{
+	return cmpc_remove_acpi_notify_device(acpi);
+}
+
+static const struct acpi_device_id cmpc_keys_device_ids[] = {
+	{CMPC_KEYS_HID, 0},
+	{"", 0}
+};
+
+static struct acpi_driver cmpc_keys_acpi_driver = {
+	.owner = THIS_MODULE,
+	.name = "cmpc_keys",
+	.class = "cmpc_keys",
+	.ids = cmpc_keys_device_ids,
+	.ops = {
+		.add = cmpc_keys_add,
+		.remove = cmpc_keys_remove,
+		.notify = cmpc_keys_handler,
+	}
+};
+
+
+/*
+ * General init/exit code.
+ */
+
+static int cmpc_init(void)
+{
+	int r;
+
+	r = acpi_bus_register_driver(&cmpc_keys_acpi_driver);
+	if (r)
+		goto failed_keys;
+
+	r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver);
+	if (r)
+		goto failed_bl;
+
+	r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver);
+	if (r)
+		goto failed_tablet;
+
+	r = acpi_bus_register_driver(&cmpc_accel_acpi_driver);
+	if (r)
+		goto failed_accel;
+
+	r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4);
+	if (r)
+		goto failed_accel_v4;
+
+	return r;
+
+failed_accel_v4:
+	acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
+
+failed_accel:
+	acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
+
+failed_tablet:
+	acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
+
+failed_bl:
+	acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
+
+failed_keys:
+	return r;
+}
+
+static void cmpc_exit(void)
+{
+	acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4);
+	acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
+	acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
+	acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
+	acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
+}
+
+module_init(cmpc_init);
+module_exit(cmpc_exit);
+
+static const struct acpi_device_id cmpc_device_ids[] = {
+	{CMPC_ACCEL_HID, 0},
+	{CMPC_ACCEL_HID_V4, 0},
+	{CMPC_TABLET_HID, 0},
+	{CMPC_IPML_HID, 0},
+	{CMPC_KEYS_HID, 0},
+	{"", 0}
+};
+
+MODULE_DEVICE_TABLE(acpi, cmpc_device_ids);
diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c
new file mode 100644
index 0000000..e1c2b6d
--- /dev/null
+++ b/drivers/platform/x86/compal-laptop.c
@@ -0,0 +1,1139 @@
+/*-*-linux-c-*-*/
+
+/*
+  Copyright (C) 2008 Cezary Jackiewicz <cezary.jackiewicz (at) gmail.com>
+
+  based on MSI driver
+
+  Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+
+  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.
+ */
+
+/*
+ * compal-laptop.c - Compal laptop support.
+ *
+ * This driver exports a few files in /sys/devices/platform/compal-laptop/:
+ *   wake_up_XXX   Whether or not we listen to such wake up events (rw)
+ *
+ * In addition to these platform device attributes the driver
+ * registers itself in the Linux backlight control, power_supply, rfkill
+ * and hwmon subsystem and is available to userspace under:
+ *
+ *   /sys/class/backlight/compal-laptop/
+ *   /sys/class/power_supply/compal-laptop/
+ *   /sys/class/rfkill/rfkillX/
+ *   /sys/class/hwmon/hwmonX/
+ *
+ * Notes on the power_supply battery interface:
+ *   - the "minimum" design voltage is *the* design voltage
+ *   - the ambient temperature is the average battery temperature
+ *     and the value is an educated guess (see commented code below)
+ *
+ *
+ * This driver might work on other laptops produced by Compal. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * laptop as compatible.
+ *
+ * Lots of data available at:
+ * http://service1.marasst.com/Compal/JHL90_91/Service%20Manual/
+ * JHL90%20service%20manual-Final-0725.pdf
+ *
+ *
+ *
+ * Support for the Compal JHL90 added by Roald Frederickx
+ * (roald.frederickx@gmail.com):
+ * Driver got large revision. Added functionalities: backlight
+ * power, wake_on_XXX, a hwmon and power_supply interface.
+ *
+ * In case this gets merged into the kernel source: I want to dedicate this
+ * to Kasper Meerts, the awesome guy who showed me Linux and C!
+ */
+
+/* NOTE: currently the wake_on_XXX, hwmon and power_supply interfaces are
+ * only enabled on a JHL90 board until it is verified that they work on the
+ * other boards too.  See the extra_features variable. */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/power_supply.h>
+#include <linux/fb.h>
+#include <acpi/video.h>
+
+/* ======= */
+/* Defines */
+/* ======= */
+#define DRIVER_NAME "compal-laptop"
+#define DRIVER_VERSION	"0.2.7"
+
+#define BACKLIGHT_LEVEL_ADDR		0xB9
+#define BACKLIGHT_LEVEL_MAX		7
+#define BACKLIGHT_STATE_ADDR		0x59
+#define BACKLIGHT_STATE_ON_DATA		0xE1
+#define BACKLIGHT_STATE_OFF_DATA	0xE2
+
+#define WAKE_UP_ADDR			0xA4
+#define WAKE_UP_PME			(1 << 0)
+#define WAKE_UP_MODEM			(1 << 1)
+#define WAKE_UP_LAN			(1 << 2)
+#define WAKE_UP_WLAN			(1 << 4)
+#define WAKE_UP_KEY			(1 << 6)
+#define WAKE_UP_MOUSE			(1 << 7)
+
+#define WIRELESS_ADDR			0xBB
+#define WIRELESS_WLAN			(1 << 0)
+#define WIRELESS_BT			(1 << 1)
+#define WIRELESS_WLAN_EXISTS		(1 << 2)
+#define WIRELESS_BT_EXISTS		(1 << 3)
+#define WIRELESS_KILLSWITCH		(1 << 4)
+
+#define PWM_ADDRESS			0x46
+#define PWM_DISABLE_ADDR		0x59
+#define PWM_DISABLE_DATA		0xA5
+#define PWM_ENABLE_ADDR			0x59
+#define PWM_ENABLE_DATA			0xA8
+
+#define FAN_ADDRESS			0x46
+#define FAN_DATA			0x81
+#define FAN_FULL_ON_CMD			0x59 /* Doesn't seem to work. Just */
+#define FAN_FULL_ON_ENABLE		0x76 /* force the pwm signal to its */
+#define FAN_FULL_ON_DISABLE		0x77 /* maximum value instead */
+
+#define TEMP_CPU			0xB0
+#define TEMP_CPU_LOCAL			0xB1
+#define TEMP_CPU_DTS			0xB5
+#define TEMP_NORTHBRIDGE		0xB6
+#define TEMP_VGA			0xB4
+#define TEMP_SKIN			0xB2
+
+#define BAT_MANUFACTURER_NAME_ADDR	0x10
+#define BAT_MANUFACTURER_NAME_LEN	9
+#define BAT_MODEL_NAME_ADDR		0x19
+#define BAT_MODEL_NAME_LEN		6
+#define BAT_SERIAL_NUMBER_ADDR		0xC4
+#define BAT_SERIAL_NUMBER_LEN		5
+#define BAT_CHARGE_NOW			0xC2
+#define BAT_CHARGE_DESIGN		0xCA
+#define BAT_VOLTAGE_NOW			0xC6
+#define BAT_VOLTAGE_DESIGN		0xC8
+#define BAT_CURRENT_NOW			0xD0
+#define BAT_CURRENT_AVG			0xD2
+#define BAT_POWER			0xD4
+#define BAT_CAPACITY			0xCE
+#define BAT_TEMP			0xD6
+#define BAT_TEMP_AVG			0xD7
+#define BAT_STATUS0			0xC1
+#define BAT_STATUS1			0xF0
+#define BAT_STATUS2			0xF1
+#define BAT_STOP_CHARGE1		0xF2
+#define BAT_STOP_CHARGE2		0xF3
+#define BAT_CHARGE_LIMIT		0x03
+#define BAT_CHARGE_LIMIT_MAX		100
+
+#define BAT_S0_DISCHARGE		(1 << 0)
+#define BAT_S0_DISCHRG_CRITICAL		(1 << 2)
+#define BAT_S0_LOW			(1 << 3)
+#define BAT_S0_CHARGING			(1 << 1)
+#define BAT_S0_AC			(1 << 7)
+#define BAT_S1_EXISTS			(1 << 0)
+#define BAT_S1_FULL			(1 << 1)
+#define BAT_S1_EMPTY			(1 << 2)
+#define BAT_S1_LiION_OR_NiMH		(1 << 7)
+#define BAT_S2_LOW_LOW			(1 << 0)
+#define BAT_STOP_CHRG1_BAD_CELL		(1 << 1)
+#define BAT_STOP_CHRG1_COMM_FAIL	(1 << 2)
+#define BAT_STOP_CHRG1_OVERVOLTAGE	(1 << 6)
+#define BAT_STOP_CHRG1_OVERTEMPERATURE	(1 << 7)
+
+
+/* ======= */
+/* Structs */
+/* ======= */
+struct compal_data{
+	/* Fan control */
+	int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by motherboard */
+	unsigned char curr_pwm;
+
+	/* Power supply */
+	struct power_supply *psy;
+	struct power_supply_info psy_info;
+	char bat_model_name[BAT_MODEL_NAME_LEN + 1];
+	char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1];
+	char bat_serial_number[BAT_SERIAL_NUMBER_LEN + 1];
+};
+
+
+/* =============== */
+/* General globals */
+/* =============== */
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+/* Support for the wake_on_XXX, hwmon and power_supply interface. Currently
+ * only gets enabled on a JHL90 board. Might work with the others too */
+static bool extra_features;
+
+/* Nasty stuff. For some reason the fan control is very un-linear.  I've
+ * come up with these values by looping through the possible inputs and
+ * watching the output of address 0x4F (do an ec_transaction writing 0x33
+ * into 0x4F and read a few bytes from the output, like so:
+ *	u8 writeData = 0x33;
+ *	ec_transaction(0x4F, &writeData, 1, buffer, 32);
+ * That address is labeled "fan1 table information" in the service manual.
+ * It should be clear which value in 'buffer' changes). This seems to be
+ * related to fan speed. It isn't a proper 'realtime' fan speed value
+ * though, because physically stopping or speeding up the fan doesn't
+ * change it. It might be the average voltage or current of the pwm output.
+ * Nevertheless, it is more fine-grained than the actual RPM reading */
+static const unsigned char pwm_lookup_table[256] = {
+	0, 0, 0, 1, 1, 1, 2, 253, 254, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6,
+	7, 7, 7, 8, 86, 86, 9, 9, 9, 10, 10, 10, 11, 92, 92, 12, 12, 95,
+	13, 66, 66, 14, 14, 98, 15, 15, 15, 16, 16, 67, 17, 17, 72, 18, 70,
+	75, 19, 90, 90, 73, 73, 73, 21, 21, 91, 91, 91, 96, 23, 94, 94, 94,
+	94, 94, 94, 94, 94, 94, 94, 141, 141, 238, 223, 192, 139, 139, 139,
+	139, 139, 142, 142, 142, 142, 142, 78, 78, 78, 78, 78, 76, 76, 76,
+	76, 76, 79, 79, 79, 79, 79, 79, 79, 20, 20, 20, 20, 20, 22, 22, 22,
+	22, 22, 24, 24, 24, 24, 24, 24, 219, 219, 219, 219, 219, 219, 219,
+	219, 27, 27, 188, 188, 28, 28, 28, 29, 186, 186, 186, 186, 186,
+	186, 186, 186, 186, 186, 31, 31, 31, 31, 31, 32, 32, 32, 41, 33,
+	33, 33, 33, 33, 252, 252, 34, 34, 34, 43, 35, 35, 35, 36, 36, 38,
+	206, 206, 206, 206, 206, 206, 206, 206, 206, 37, 37, 37, 46, 46,
+	47, 47, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 48, 48,
+	48, 48, 48, 40, 40, 40, 49, 42, 42, 42, 42, 42, 42, 42, 42, 44,
+	189, 189, 189, 189, 54, 54, 45, 45, 45, 45, 45, 45, 45, 45, 251,
+	191, 199, 199, 199, 199, 199, 215, 215, 215, 215, 187, 187, 187,
+	187, 187, 193, 50
+};
+
+
+
+
+/* ========================= */
+/* Hardware access functions */
+/* ========================= */
+/* General access */
+static u8 ec_read_u8(u8 addr)
+{
+	u8 value;
+	ec_read(addr, &value);
+	return value;
+}
+
+static s8 ec_read_s8(u8 addr)
+{
+	return (s8)ec_read_u8(addr);
+}
+
+static u16 ec_read_u16(u8 addr)
+{
+	int hi, lo;
+	lo = ec_read_u8(addr);
+	hi = ec_read_u8(addr + 1);
+	return (hi << 8) + lo;
+}
+
+static s16 ec_read_s16(u8 addr)
+{
+	return (s16) ec_read_u16(addr);
+}
+
+static void ec_read_sequence(u8 addr, u8 *buf, int len)
+{
+	int i;
+	for (i = 0; i < len; i++)
+		ec_read(addr + i, buf + i);
+}
+
+
+/* Backlight access */
+static int set_backlight_level(int level)
+{
+	if (level < 0 || level > BACKLIGHT_LEVEL_MAX)
+		return -EINVAL;
+
+	ec_write(BACKLIGHT_LEVEL_ADDR, level);
+
+	return 0;
+}
+
+static int get_backlight_level(void)
+{
+	return (int) ec_read_u8(BACKLIGHT_LEVEL_ADDR);
+}
+
+static void set_backlight_state(bool on)
+{
+	u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA;
+	ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0);
+}
+
+
+/* Fan control access */
+static void pwm_enable_control(void)
+{
+	unsigned char writeData = PWM_ENABLE_DATA;
+	ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0);
+}
+
+static void pwm_disable_control(void)
+{
+	unsigned char writeData = PWM_DISABLE_DATA;
+	ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0);
+}
+
+static void set_pwm(int pwm)
+{
+	ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0);
+}
+
+static int get_fan_rpm(void)
+{
+	u8 value, data = FAN_DATA;
+	ec_transaction(FAN_ADDRESS, &data, 1, &value, 1);
+	return 100 * (int)value;
+}
+
+
+
+
+/* =================== */
+/* Interface functions */
+/* =================== */
+
+/* Backlight interface */
+static int bl_get_brightness(struct backlight_device *b)
+{
+	return get_backlight_level();
+}
+
+static int bl_update_status(struct backlight_device *b)
+{
+	int ret = set_backlight_level(b->props.brightness);
+	if (ret)
+		return ret;
+
+	set_backlight_state((b->props.power == FB_BLANK_UNBLANK)
+		&&    !(b->props.state & BL_CORE_SUSPENDED)
+		&&    !(b->props.state & BL_CORE_FBBLANK));
+	return 0;
+}
+
+static const struct backlight_ops compalbl_ops = {
+	.get_brightness = bl_get_brightness,
+	.update_status	= bl_update_status,
+};
+
+
+/* Wireless interface */
+static int compal_rfkill_set(void *data, bool blocked)
+{
+	unsigned long radio = (unsigned long) data;
+	u8 result = ec_read_u8(WIRELESS_ADDR);
+	u8 value;
+
+	if (!blocked)
+		value = (u8) (result | radio);
+	else
+		value = (u8) (result & ~radio);
+	ec_write(WIRELESS_ADDR, value);
+
+	return 0;
+}
+
+static void compal_rfkill_poll(struct rfkill *rfkill, void *data)
+{
+	u8 result = ec_read_u8(WIRELESS_ADDR);
+	bool hw_blocked = !(result & WIRELESS_KILLSWITCH);
+	rfkill_set_hw_state(rfkill, hw_blocked);
+}
+
+static const struct rfkill_ops compal_rfkill_ops = {
+	.poll = compal_rfkill_poll,
+	.set_block = compal_rfkill_set,
+};
+
+
+/* Wake_up interface */
+#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK)			\
+static ssize_t NAME##_show(struct device *dev,				\
+	struct device_attribute *attr, char *buf)			\
+{									\
+	return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0));	\
+}									\
+static ssize_t NAME##_store(struct device *dev,				\
+	struct device_attribute *attr, const char *buf, size_t count)	\
+{									\
+	int state;							\
+	u8 old_val = ec_read_u8(ADDR);					\
+	if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1))	\
+		return -EINVAL;						\
+	ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK));	\
+	return count;							\
+}
+
+SIMPLE_MASKED_STORE_SHOW(wake_up_pme,	WAKE_UP_ADDR, WAKE_UP_PME)
+SIMPLE_MASKED_STORE_SHOW(wake_up_modem,	WAKE_UP_ADDR, WAKE_UP_MODEM)
+SIMPLE_MASKED_STORE_SHOW(wake_up_lan,	WAKE_UP_ADDR, WAKE_UP_LAN)
+SIMPLE_MASKED_STORE_SHOW(wake_up_wlan,	WAKE_UP_ADDR, WAKE_UP_WLAN)
+SIMPLE_MASKED_STORE_SHOW(wake_up_key,	WAKE_UP_ADDR, WAKE_UP_KEY)
+SIMPLE_MASKED_STORE_SHOW(wake_up_mouse,	WAKE_UP_ADDR, WAKE_UP_MOUSE)
+
+/* Fan control interface */
+static ssize_t pwm_enable_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct compal_data *data = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", data->pwm_enable);
+}
+
+static ssize_t pwm_enable_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct compal_data *data = dev_get_drvdata(dev);
+	long val;
+	int err;
+
+	err = kstrtol(buf, 10, &val);
+	if (err)
+		return err;
+	if (val < 0)
+		return -EINVAL;
+
+	data->pwm_enable = val;
+
+	switch (val) {
+	case 0:  /* Full speed */
+		pwm_enable_control();
+		set_pwm(255);
+		break;
+	case 1:  /* As set by pwm1 */
+		pwm_enable_control();
+		set_pwm(data->curr_pwm);
+		break;
+	default: /* Control by motherboard */
+		pwm_disable_control();
+		break;
+	}
+
+	return count;
+}
+
+static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	struct compal_data *data = dev_get_drvdata(dev);
+	return sprintf(buf, "%hhu\n", data->curr_pwm);
+}
+
+static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct compal_data *data = dev_get_drvdata(dev);
+	long val;
+	int err;
+
+	err = kstrtol(buf, 10, &val);
+	if (err)
+		return err;
+	if (val < 0 || val > 255)
+		return -EINVAL;
+
+	data->curr_pwm = val;
+
+	if (data->pwm_enable != 1)
+		return count;
+	set_pwm(val);
+
+	return count;
+}
+
+static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	return sprintf(buf, "%d\n", get_fan_rpm());
+}
+
+
+/* Temperature interface */
+#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL)	\
+static ssize_t temp_##POSTFIX(struct device *dev,			\
+		struct device_attribute *attr, char *buf)		\
+{									\
+	return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS));	\
+}									\
+static ssize_t label_##POSTFIX(struct device *dev,			\
+		struct device_attribute *attr, char *buf)		\
+{									\
+	return sprintf(buf, "%s\n", LABEL);				\
+}
+
+/* Labels as in service guide */
+TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu,        TEMP_CPU,        "CPU_TEMP");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_local,  TEMP_CPU_LOCAL,  "CPU_TEMP_LOCAL");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_DTS,    TEMP_CPU_DTS,    "CPU_DTS");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(northbridge,TEMP_NORTHBRIDGE,"NorthBridge");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(vga,        TEMP_VGA,        "VGA_TEMP");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(SKIN,       TEMP_SKIN,       "SKIN_TEMP90");
+
+
+/* Power supply interface */
+static int bat_status(void)
+{
+	u8 status0 = ec_read_u8(BAT_STATUS0);
+	u8 status1 = ec_read_u8(BAT_STATUS1);
+
+	if (status0 & BAT_S0_CHARGING)
+		return POWER_SUPPLY_STATUS_CHARGING;
+	if (status0 & BAT_S0_DISCHARGE)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+	if (status1 & BAT_S1_FULL)
+		return POWER_SUPPLY_STATUS_FULL;
+	return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int bat_health(void)
+{
+	u8 status = ec_read_u8(BAT_STOP_CHARGE1);
+
+	if (status & BAT_STOP_CHRG1_OVERTEMPERATURE)
+		return POWER_SUPPLY_HEALTH_OVERHEAT;
+	if (status & BAT_STOP_CHRG1_OVERVOLTAGE)
+		return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+	if (status & BAT_STOP_CHRG1_BAD_CELL)
+		return POWER_SUPPLY_HEALTH_DEAD;
+	if (status & BAT_STOP_CHRG1_COMM_FAIL)
+		return POWER_SUPPLY_HEALTH_UNKNOWN;
+	return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int bat_is_present(void)
+{
+	u8 status = ec_read_u8(BAT_STATUS2);
+	return ((status & BAT_S1_EXISTS) != 0);
+}
+
+static int bat_technology(void)
+{
+	u8 status = ec_read_u8(BAT_STATUS1);
+
+	if (status & BAT_S1_LiION_OR_NiMH)
+		return POWER_SUPPLY_TECHNOLOGY_LION;
+	return POWER_SUPPLY_TECHNOLOGY_NiMH;
+}
+
+static int bat_capacity_level(void)
+{
+	u8 status0 = ec_read_u8(BAT_STATUS0);
+	u8 status1 = ec_read_u8(BAT_STATUS1);
+	u8 status2 = ec_read_u8(BAT_STATUS2);
+
+	if (status0 & BAT_S0_DISCHRG_CRITICAL
+			|| status1 & BAT_S1_EMPTY
+			|| status2 & BAT_S2_LOW_LOW)
+		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+	if (status0 & BAT_S0_LOW)
+		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+	if (status1 & BAT_S1_FULL)
+		return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+	return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+}
+
+static int bat_get_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	struct compal_data *data = power_supply_get_drvdata(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = bat_status();
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = bat_health();
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = bat_is_present();
+		break;
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = bat_technology();
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: /* THE design voltage... */
+		val->intval = ec_read_u16(BAT_VOLTAGE_DESIGN) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = ec_read_u16(BAT_VOLTAGE_NOW) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		val->intval = ec_read_s16(BAT_CURRENT_NOW) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_AVG:
+		val->intval = ec_read_s16(BAT_CURRENT_AVG) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_POWER_NOW:
+		val->intval = ec_read_u8(BAT_POWER) * 1000000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		val->intval = ec_read_u16(BAT_CHARGE_DESIGN) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+		val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+		val->intval = ec_read_u8(BAT_CHARGE_LIMIT);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+		val->intval = BAT_CHARGE_LIMIT_MAX;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = ec_read_u8(BAT_CAPACITY);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+		val->intval = bat_capacity_level();
+		break;
+	/* It smees that BAT_TEMP_AVG is a (2's complement?) value showing
+	 * the number of degrees, whereas BAT_TEMP is somewhat more
+	 * complicated. It looks like this is a negative nember with a
+	 * 100/256 divider and an offset of 222. Both were determined
+	 * experimentally by comparing BAT_TEMP and BAT_TEMP_AVG. */
+	case POWER_SUPPLY_PROP_TEMP:
+		val->intval = ((222 - (int)ec_read_u8(BAT_TEMP)) * 1000) >> 8;
+		break;
+	case POWER_SUPPLY_PROP_TEMP_AMBIENT: /* Ambient, Avg, ... same thing */
+		val->intval = ec_read_s8(BAT_TEMP_AVG) * 10;
+		break;
+	/* Neither the model name nor manufacturer name work for me. */
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = data->bat_model_name;
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = data->bat_manufacturer_name;
+		break;
+	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+		val->strval = data->bat_serial_number;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int bat_set_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				const union power_supply_propval *val)
+{
+	int level;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+		level = val->intval;
+		if (level < 0 || level > BAT_CHARGE_LIMIT_MAX)
+			return -EINVAL;
+		if (ec_write(BAT_CHARGE_LIMIT, level) < 0)
+			return -EIO;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int bat_writeable_property(struct power_supply *psy,
+				enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+
+
+
+/* ============== */
+/* Driver Globals */
+/* ============== */
+static DEVICE_ATTR(wake_up_pme,
+		0644, wake_up_pme_show,		wake_up_pme_store);
+static DEVICE_ATTR(wake_up_modem,
+		0644, wake_up_modem_show,	wake_up_modem_store);
+static DEVICE_ATTR(wake_up_lan,
+		0644, wake_up_lan_show,	wake_up_lan_store);
+static DEVICE_ATTR(wake_up_wlan,
+		0644, wake_up_wlan_show,	wake_up_wlan_store);
+static DEVICE_ATTR(wake_up_key,
+		0644, wake_up_key_show,	wake_up_key_store);
+static DEVICE_ATTR(wake_up_mouse,
+		0644, wake_up_mouse_show,	wake_up_mouse_store);
+
+static DEVICE_ATTR(fan1_input,  S_IRUGO, fan_show,          NULL);
+static DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu,          NULL);
+static DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local,    NULL);
+static DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS,      NULL);
+static DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge,  NULL);
+static DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga,          NULL);
+static DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN,         NULL);
+static DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu,         NULL);
+static DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local,   NULL);
+static DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS,     NULL);
+static DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL);
+static DEVICE_ATTR(temp5_label, S_IRUGO, label_vga,         NULL);
+static DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN,        NULL);
+static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store);
+static DEVICE_ATTR(pwm1_enable,
+		   S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store);
+
+static struct attribute *compal_platform_attrs[] = {
+	&dev_attr_wake_up_pme.attr,
+	&dev_attr_wake_up_modem.attr,
+	&dev_attr_wake_up_lan.attr,
+	&dev_attr_wake_up_wlan.attr,
+	&dev_attr_wake_up_key.attr,
+	&dev_attr_wake_up_mouse.attr,
+	NULL
+};
+static struct attribute_group compal_platform_attr_group = {
+	.attrs = compal_platform_attrs
+};
+
+static struct attribute *compal_hwmon_attrs[] = {
+	&dev_attr_pwm1_enable.attr,
+	&dev_attr_pwm1.attr,
+	&dev_attr_fan1_input.attr,
+	&dev_attr_temp1_input.attr,
+	&dev_attr_temp2_input.attr,
+	&dev_attr_temp3_input.attr,
+	&dev_attr_temp4_input.attr,
+	&dev_attr_temp5_input.attr,
+	&dev_attr_temp6_input.attr,
+	&dev_attr_temp1_label.attr,
+	&dev_attr_temp2_label.attr,
+	&dev_attr_temp3_label.attr,
+	&dev_attr_temp4_label.attr,
+	&dev_attr_temp5_label.attr,
+	&dev_attr_temp6_label.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(compal_hwmon);
+
+static int compal_probe(struct platform_device *);
+static int compal_remove(struct platform_device *);
+static struct platform_driver compal_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe	= compal_probe,
+	.remove	= compal_remove,
+};
+
+static enum power_supply_property compal_bat_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CURRENT_AVG,
+	POWER_SUPPLY_PROP_POWER_NOW,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_TEMP_AMBIENT,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+	POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static struct backlight_device *compalbl_device;
+
+static struct platform_device *compal_device;
+
+static struct rfkill *wifi_rfkill;
+static struct rfkill *bt_rfkill;
+
+
+
+
+
+/* =================================== */
+/* Initialization & clean-up functions */
+/* =================================== */
+
+static int dmi_check_cb(const struct dmi_system_id *id)
+{
+	pr_info("Identified laptop model '%s'\n", id->ident);
+	extra_features = false;
+	return 1;
+}
+
+static int dmi_check_cb_extra(const struct dmi_system_id *id)
+{
+	pr_info("Identified laptop model '%s', enabling extra features\n",
+		id->ident);
+	extra_features = true;
+	return 1;
+}
+
+static struct dmi_system_id __initdata compal_dmi_table[] = {
+	{
+		.ident = "FL90/IFL90",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
+			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "FL90/IFL90",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
+			DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "FL91/IFL91",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "IFL91"),
+			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "FL92/JFL92",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "JFL92"),
+			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "FT00/IFT00",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "IFT00"),
+			DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Dell Mini 9",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Dell Mini 10",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Dell Mini 10v",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Dell Mini 1012",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Dell Inspiron 11z",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Dell Mini 12",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
+		},
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "JHL90",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "JHL90"),
+			DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
+		},
+		.callback = dmi_check_cb_extra
+	},
+	{
+		.ident = "KHLB2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_NAME, "KHLB2"),
+			DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
+		},
+		.callback = dmi_check_cb_extra
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(dmi, compal_dmi_table);
+
+static const struct power_supply_desc psy_bat_desc = {
+	.name		= DRIVER_NAME,
+	.type		= POWER_SUPPLY_TYPE_BATTERY,
+	.properties	= compal_bat_properties,
+	.num_properties	= ARRAY_SIZE(compal_bat_properties),
+	.get_property	= bat_get_property,
+	.set_property	= bat_set_property,
+	.property_is_writeable = bat_writeable_property,
+};
+
+static void initialize_power_supply_data(struct compal_data *data)
+{
+	ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR,
+					data->bat_manufacturer_name,
+					BAT_MANUFACTURER_NAME_LEN);
+	data->bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN] = 0;
+
+	ec_read_sequence(BAT_MODEL_NAME_ADDR,
+					data->bat_model_name,
+					BAT_MODEL_NAME_LEN);
+	data->bat_model_name[BAT_MODEL_NAME_LEN] = 0;
+
+	scnprintf(data->bat_serial_number, BAT_SERIAL_NUMBER_LEN + 1, "%d",
+				ec_read_u16(BAT_SERIAL_NUMBER_ADDR));
+}
+
+static void initialize_fan_control_data(struct compal_data *data)
+{
+	data->pwm_enable = 2; /* Keep motherboard in control for now */
+	data->curr_pwm = 255; /* Try not to cause a CPU_on_fire exception
+				 if we take over... */
+}
+
+static int setup_rfkill(void)
+{
+	int ret;
+
+	wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev,
+				RFKILL_TYPE_WLAN, &compal_rfkill_ops,
+				(void *) WIRELESS_WLAN);
+	if (!wifi_rfkill)
+		return -ENOMEM;
+
+	ret = rfkill_register(wifi_rfkill);
+	if (ret)
+		goto err_wifi;
+
+	bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev,
+				RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops,
+				(void *) WIRELESS_BT);
+	if (!bt_rfkill) {
+		ret = -ENOMEM;
+		goto err_allocate_bt;
+	}
+	ret = rfkill_register(bt_rfkill);
+	if (ret)
+		goto err_register_bt;
+
+	return 0;
+
+err_register_bt:
+	rfkill_destroy(bt_rfkill);
+
+err_allocate_bt:
+	rfkill_unregister(wifi_rfkill);
+
+err_wifi:
+	rfkill_destroy(wifi_rfkill);
+
+	return ret;
+}
+
+static int __init compal_init(void)
+{
+	int ret;
+
+	if (acpi_disabled) {
+		pr_err("ACPI needs to be enabled for this driver to work!\n");
+		return -ENODEV;
+	}
+
+	if (!force && !dmi_check_system(compal_dmi_table)) {
+		pr_err("Motherboard not recognized (You could try the module's force-parameter)\n");
+		return -ENODEV;
+	}
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		struct backlight_properties props;
+		memset(&props, 0, sizeof(struct backlight_properties));
+		props.type = BACKLIGHT_PLATFORM;
+		props.max_brightness = BACKLIGHT_LEVEL_MAX;
+		compalbl_device = backlight_device_register(DRIVER_NAME,
+							    NULL, NULL,
+							    &compalbl_ops,
+							    &props);
+		if (IS_ERR(compalbl_device))
+			return PTR_ERR(compalbl_device);
+	}
+
+	ret = platform_driver_register(&compal_driver);
+	if (ret)
+		goto err_backlight;
+
+	compal_device = platform_device_alloc(DRIVER_NAME, -1);
+	if (!compal_device) {
+		ret = -ENOMEM;
+		goto err_platform_driver;
+	}
+
+	ret = platform_device_add(compal_device); /* This calls compal_probe */
+	if (ret)
+		goto err_platform_device;
+
+	ret = setup_rfkill();
+	if (ret)
+		goto err_rfkill;
+
+	pr_info("Driver " DRIVER_VERSION " successfully loaded\n");
+	return 0;
+
+err_rfkill:
+	platform_device_del(compal_device);
+
+err_platform_device:
+	platform_device_put(compal_device);
+
+err_platform_driver:
+	platform_driver_unregister(&compal_driver);
+
+err_backlight:
+	backlight_device_unregister(compalbl_device);
+
+	return ret;
+}
+
+static int compal_probe(struct platform_device *pdev)
+{
+	int err;
+	struct compal_data *data;
+	struct device *hwmon_dev;
+	struct power_supply_config psy_cfg = {};
+
+	if (!extra_features)
+		return 0;
+
+	/* Fan control */
+	data = devm_kzalloc(&pdev->dev, sizeof(struct compal_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	initialize_fan_control_data(data);
+
+	err = sysfs_create_group(&pdev->dev.kobj, &compal_platform_attr_group);
+	if (err)
+		return err;
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev,
+							   "compal", data,
+							   compal_hwmon_groups);
+	if (IS_ERR(hwmon_dev)) {
+		err = PTR_ERR(hwmon_dev);
+		goto remove;
+	}
+
+	/* Power supply */
+	initialize_power_supply_data(data);
+	psy_cfg.drv_data = data;
+	data->psy = power_supply_register(&compal_device->dev, &psy_bat_desc,
+					  &psy_cfg);
+	if (IS_ERR(data->psy)) {
+		err = PTR_ERR(data->psy);
+		goto remove;
+	}
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+
+remove:
+	sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group);
+	return err;
+}
+
+static void __exit compal_cleanup(void)
+{
+	platform_device_unregister(compal_device);
+	platform_driver_unregister(&compal_driver);
+	backlight_device_unregister(compalbl_device);
+	rfkill_unregister(wifi_rfkill);
+	rfkill_unregister(bt_rfkill);
+	rfkill_destroy(wifi_rfkill);
+	rfkill_destroy(bt_rfkill);
+
+	pr_info("Driver unloaded\n");
+}
+
+static int compal_remove(struct platform_device *pdev)
+{
+	struct compal_data *data;
+
+	if (!extra_features)
+		return 0;
+
+	pr_info("Unloading: resetting fan control to motherboard\n");
+	pwm_disable_control();
+
+	data = platform_get_drvdata(pdev);
+	power_supply_unregister(data->psy);
+
+	sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group);
+
+	return 0;
+}
+
+
+module_init(compal_init);
+module_exit(compal_cleanup);
+
+MODULE_AUTHOR("Cezary Jackiewicz");
+MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)");
+MODULE_DESCRIPTION("Compal Laptop Support");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c
new file mode 100644
index 0000000..aaeeae8
--- /dev/null
+++ b/drivers/platform/x86/dell-laptop.c
@@ -0,0 +1,2253 @@
+/*
+ *  Driver for Dell laptop extras
+ *
+ *  Copyright (c) Red Hat <mjg@redhat.com>
+ *  Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com>
+ *  Copyright (c) 2014 Pali Rohár <pali.rohar@gmail.com>
+ *
+ *  Based on documentation in the libsmbios package:
+ *  Copyright (C) 2005-2014 Dell Inc.
+ *
+ *  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
+ *  published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/rfkill.h>
+#include <linux/power_supply.h>
+#include <linux/acpi.h>
+#include <linux/mm.h>
+#include <linux/i8042.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <acpi/video.h>
+#include "../../firmware/dcdbas.h"
+#include "dell-rbtn.h"
+
+#define BRIGHTNESS_TOKEN 0x7d
+#define KBD_LED_OFF_TOKEN 0x01E1
+#define KBD_LED_ON_TOKEN 0x01E2
+#define KBD_LED_AUTO_TOKEN 0x01E3
+#define KBD_LED_AUTO_25_TOKEN 0x02EA
+#define KBD_LED_AUTO_50_TOKEN 0x02EB
+#define KBD_LED_AUTO_75_TOKEN 0x02EC
+#define KBD_LED_AUTO_100_TOKEN 0x02F6
+
+/* This structure will be modified by the firmware when we enter
+ * system management mode, hence the volatiles */
+
+struct calling_interface_buffer {
+	u16 class;
+	u16 select;
+	volatile u32 input[4];
+	volatile u32 output[4];
+} __packed;
+
+struct calling_interface_token {
+	u16 tokenID;
+	u16 location;
+	union {
+		u16 value;
+		u16 stringlength;
+	};
+};
+
+struct calling_interface_structure {
+	struct dmi_header header;
+	u16 cmdIOAddress;
+	u8 cmdIOCode;
+	u32 supportedCmds;
+	struct calling_interface_token tokens[];
+} __packed;
+
+struct quirk_entry {
+	u8 touchpad_led;
+
+	int needs_kbd_timeouts;
+	/*
+	 * Ordered list of timeouts expressed in seconds.
+	 * The list must end with -1
+	 */
+	int kbd_timeouts[];
+};
+
+static struct quirk_entry *quirks;
+
+static struct quirk_entry quirk_dell_vostro_v130 = {
+	.touchpad_led = 1,
+};
+
+static int __init dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 1;
+}
+
+/*
+ * These values come from Windows utility provided by Dell. If any other value
+ * is used then BIOS silently set timeout to 0 without any error message.
+ */
+static struct quirk_entry quirk_dell_xps13_9333 = {
+	.needs_kbd_timeouts = 1,
+	.kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 },
+};
+
+static int da_command_address;
+static int da_command_code;
+static int da_num_tokens;
+static struct calling_interface_token *da_tokens;
+
+static struct platform_driver platform_driver = {
+	.driver = {
+		.name = "dell-laptop",
+	}
+};
+
+static struct platform_device *platform_device;
+static struct backlight_device *dell_backlight_device;
+static struct rfkill *wifi_rfkill;
+static struct rfkill *bluetooth_rfkill;
+static struct rfkill *wwan_rfkill;
+static bool force_rfkill;
+
+module_param(force_rfkill, bool, 0444);
+MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models");
+
+static const struct dmi_system_id dell_device_table[] __initconst = {
+	{
+		.ident = "Dell laptop",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
+		},
+	},
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/
+		},
+	},
+	{
+		.ident = "Dell Computer Corporation",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
+		},
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(dmi, dell_device_table);
+
+static const struct dmi_system_id dell_quirks[] __initconst = {
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro V130",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro V131",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro 3350",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro 3555",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron N311z",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron M5110",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro 3360",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro 3460",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro 3560",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Vostro 3450",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron 5420",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron 5520",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron 5720",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron 7420",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron 7520",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell Inspiron 7720",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
+		},
+		.driver_data = &quirk_dell_vostro_v130,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Dell XPS13 9333",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
+		},
+		.driver_data = &quirk_dell_xps13_9333,
+	},
+	{ }
+};
+
+static struct calling_interface_buffer *buffer;
+static DEFINE_MUTEX(buffer_mutex);
+
+static void clear_buffer(void)
+{
+	memset(buffer, 0, sizeof(struct calling_interface_buffer));
+}
+
+static void get_buffer(void)
+{
+	mutex_lock(&buffer_mutex);
+	clear_buffer();
+}
+
+static void release_buffer(void)
+{
+	mutex_unlock(&buffer_mutex);
+}
+
+static void __init parse_da_table(const struct dmi_header *dm)
+{
+	/* Final token is a terminator, so we don't want to copy it */
+	int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
+	struct calling_interface_token *new_da_tokens;
+	struct calling_interface_structure *table =
+		container_of(dm, struct calling_interface_structure, header);
+
+	/* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
+	   6 bytes of entry */
+
+	if (dm->length < 17)
+		return;
+
+	da_command_address = table->cmdIOAddress;
+	da_command_code = table->cmdIOCode;
+
+	new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
+				 sizeof(struct calling_interface_token),
+				 GFP_KERNEL);
+
+	if (!new_da_tokens)
+		return;
+	da_tokens = new_da_tokens;
+
+	memcpy(da_tokens+da_num_tokens, table->tokens,
+	       sizeof(struct calling_interface_token) * tokens);
+
+	da_num_tokens += tokens;
+}
+
+static void __init find_tokens(const struct dmi_header *dm, void *dummy)
+{
+	switch (dm->type) {
+	case 0xd4: /* Indexed IO */
+	case 0xd5: /* Protected Area Type 1 */
+	case 0xd6: /* Protected Area Type 2 */
+		break;
+	case 0xda: /* Calling interface */
+		parse_da_table(dm);
+		break;
+	}
+}
+
+static int find_token_id(int tokenid)
+{
+	int i;
+
+	for (i = 0; i < da_num_tokens; i++) {
+		if (da_tokens[i].tokenID == tokenid)
+			return i;
+	}
+
+	return -1;
+}
+
+static int find_token_location(int tokenid)
+{
+	int id;
+
+	id = find_token_id(tokenid);
+	if (id == -1)
+		return -1;
+
+	return da_tokens[id].location;
+}
+
+static struct calling_interface_buffer *
+dell_send_request(struct calling_interface_buffer *buffer, int class,
+		  int select)
+{
+	struct smi_cmd command;
+
+	command.magic = SMI_CMD_MAGIC;
+	command.command_address = da_command_address;
+	command.command_code = da_command_code;
+	command.ebx = virt_to_phys(buffer);
+	command.ecx = 0x42534931;
+
+	buffer->class = class;
+	buffer->select = select;
+
+	dcdbas_smi_request(&command);
+
+	return buffer;
+}
+
+static inline int dell_smi_error(int value)
+{
+	switch (value) {
+	case 0: /* Completed successfully */
+		return 0;
+	case -1: /* Completed with error */
+		return -EIO;
+	case -2: /* Function not supported */
+		return -ENXIO;
+	default: /* Unknown error */
+		return -EINVAL;
+	}
+}
+
+/*
+ * Derived from information in smbios-wireless-ctl:
+ *
+ * cbSelect 17, Value 11
+ *
+ * Return Wireless Info
+ * cbArg1, byte0 = 0x00
+ *
+ *     cbRes1 Standard return codes (0, -1, -2)
+ *     cbRes2 Info bit flags:
+ *
+ *     0 Hardware switch supported (1)
+ *     1 WiFi locator supported (1)
+ *     2 WLAN supported (1)
+ *     3 Bluetooth (BT) supported (1)
+ *     4 WWAN supported (1)
+ *     5 Wireless KBD supported (1)
+ *     6 Uw b supported (1)
+ *     7 WiGig supported (1)
+ *     8 WLAN installed (1)
+ *     9 BT installed (1)
+ *     10 WWAN installed (1)
+ *     11 Uw b installed (1)
+ *     12 WiGig installed (1)
+ *     13-15 Reserved (0)
+ *     16 Hardware (HW) switch is On (1)
+ *     17 WLAN disabled (1)
+ *     18 BT disabled (1)
+ *     19 WWAN disabled (1)
+ *     20 Uw b disabled (1)
+ *     21 WiGig disabled (1)
+ *     20-31 Reserved (0)
+ *
+ *     cbRes3 NVRAM size in bytes
+ *     cbRes4, byte 0 NVRAM format version number
+ *
+ *
+ * Set QuickSet Radio Disable Flag
+ *     cbArg1, byte0 = 0x01
+ *     cbArg1, byte1
+ *     Radio ID     value:
+ *     0        Radio Status
+ *     1        WLAN ID
+ *     2        BT ID
+ *     3        WWAN ID
+ *     4        UWB ID
+ *     5        WIGIG ID
+ *     cbArg1, byte2    Flag bits:
+ *             0 QuickSet disables radio (1)
+ *             1-7 Reserved (0)
+ *
+ *     cbRes1    Standard return codes (0, -1, -2)
+ *     cbRes2    QuickSet (QS) radio disable bit map:
+ *     0 QS disables WLAN
+ *     1 QS disables BT
+ *     2 QS disables WWAN
+ *     3 QS disables UWB
+ *     4 QS disables WIGIG
+ *     5-31 Reserved (0)
+ *
+ * Wireless Switch Configuration
+ *     cbArg1, byte0 = 0x02
+ *
+ *     cbArg1, byte1
+ *     Subcommand:
+ *     0 Get config
+ *     1 Set config
+ *     2 Set WiFi locator enable/disable
+ *     cbArg1,byte2
+ *     Switch settings (if byte 1==1):
+ *     0 WLAN sw itch control (1)
+ *     1 BT sw itch control (1)
+ *     2 WWAN sw itch control (1)
+ *     3 UWB sw itch control (1)
+ *     4 WiGig sw itch control (1)
+ *     5-7 Reserved (0)
+ *    cbArg1, byte2 Enable bits (if byte 1==2):
+ *     0 Enable WiFi locator (1)
+ *
+ *    cbRes1     Standard return codes (0, -1, -2)
+ *    cbRes2 QuickSet radio disable bit map:
+ *     0 WLAN controlled by sw itch (1)
+ *     1 BT controlled by sw itch (1)
+ *     2 WWAN controlled by sw itch (1)
+ *     3 UWB controlled by sw itch (1)
+ *     4 WiGig controlled by sw itch (1)
+ *     5-6 Reserved (0)
+ *     7 Wireless sw itch config locked (1)
+ *     8 WiFi locator enabled (1)
+ *     9-14 Reserved (0)
+ *     15 WiFi locator setting locked (1)
+ *     16-31 Reserved (0)
+ *
+ * Read Local Config Data (LCD)
+ *     cbArg1, byte0 = 0x10
+ *     cbArg1, byte1 NVRAM index low byte
+ *     cbArg1, byte2 NVRAM index high byte
+ *     cbRes1 Standard return codes (0, -1, -2)
+ *     cbRes2 4 bytes read from LCD[index]
+ *     cbRes3 4 bytes read from LCD[index+4]
+ *     cbRes4 4 bytes read from LCD[index+8]
+ *
+ * Write Local Config Data (LCD)
+ *     cbArg1, byte0 = 0x11
+ *     cbArg1, byte1 NVRAM index low byte
+ *     cbArg1, byte2 NVRAM index high byte
+ *     cbArg2 4 bytes to w rite at LCD[index]
+ *     cbArg3 4 bytes to w rite at LCD[index+4]
+ *     cbArg4 4 bytes to w rite at LCD[index+8]
+ *     cbRes1 Standard return codes (0, -1, -2)
+ *
+ * Populate Local Config Data from NVRAM
+ *     cbArg1, byte0 = 0x12
+ *     cbRes1 Standard return codes (0, -1, -2)
+ *
+ * Commit Local Config Data to NVRAM
+ *     cbArg1, byte0 = 0x13
+ *     cbRes1 Standard return codes (0, -1, -2)
+ */
+
+static int dell_rfkill_set(void *data, bool blocked)
+{
+	int disable = blocked ? 1 : 0;
+	unsigned long radio = (unsigned long)data;
+	int hwswitch_bit = (unsigned long)data - 1;
+	int hwswitch;
+	int status;
+	int ret;
+
+	get_buffer();
+
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	status = buffer->output[1];
+
+	if (ret != 0)
+		goto out;
+
+	clear_buffer();
+
+	buffer->input[0] = 0x2;
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	hwswitch = buffer->output[1];
+
+	/* If the hardware switch controls this radio, and the hardware
+	   switch is disabled, always disable the radio */
+	if (ret == 0 && (hwswitch & BIT(hwswitch_bit)) &&
+	    (status & BIT(0)) && !(status & BIT(16)))
+		disable = 1;
+
+	clear_buffer();
+
+	buffer->input[0] = (1 | (radio<<8) | (disable << 16));
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+
+ out:
+	release_buffer();
+	return dell_smi_error(ret);
+}
+
+/* Must be called with the buffer held */
+static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio,
+					int status)
+{
+	if (status & BIT(0)) {
+		/* Has hw-switch, sync sw_state to BIOS */
+		int block = rfkill_blocked(rfkill);
+		clear_buffer();
+		buffer->input[0] = (1 | (radio << 8) | (block << 16));
+		dell_send_request(buffer, 17, 11);
+	} else {
+		/* No hw-switch, sync BIOS state to sw_state */
+		rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16)));
+	}
+}
+
+static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio,
+					int status, int hwswitch)
+{
+	if (hwswitch & (BIT(radio - 1)))
+		rfkill_set_hw_state(rfkill, !(status & BIT(16)));
+}
+
+static void dell_rfkill_query(struct rfkill *rfkill, void *data)
+{
+	int radio = ((unsigned long)data & 0xF);
+	int hwswitch;
+	int status;
+	int ret;
+
+	get_buffer();
+
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	status = buffer->output[1];
+
+	if (ret != 0 || !(status & BIT(0))) {
+		release_buffer();
+		return;
+	}
+
+	clear_buffer();
+
+	buffer->input[0] = 0x2;
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	hwswitch = buffer->output[1];
+
+	release_buffer();
+
+	if (ret != 0)
+		return;
+
+	dell_rfkill_update_hw_state(rfkill, radio, status, hwswitch);
+}
+
+static const struct rfkill_ops dell_rfkill_ops = {
+	.set_block = dell_rfkill_set,
+	.query = dell_rfkill_query,
+};
+
+static struct dentry *dell_laptop_dir;
+
+static int dell_debugfs_show(struct seq_file *s, void *data)
+{
+	int hwswitch_state;
+	int hwswitch_ret;
+	int status;
+	int ret;
+
+	get_buffer();
+
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	status = buffer->output[1];
+
+	clear_buffer();
+
+	buffer->input[0] = 0x2;
+	dell_send_request(buffer, 17, 11);
+	hwswitch_ret = buffer->output[0];
+	hwswitch_state = buffer->output[1];
+
+	release_buffer();
+
+	seq_printf(s, "return:\t%d\n", ret);
+	seq_printf(s, "status:\t0x%X\n", status);
+	seq_printf(s, "Bit 0 : Hardware switch supported:   %lu\n",
+		   status & BIT(0));
+	seq_printf(s, "Bit 1 : Wifi locator supported:      %lu\n",
+		  (status & BIT(1)) >> 1);
+	seq_printf(s, "Bit 2 : Wifi is supported:           %lu\n",
+		  (status & BIT(2)) >> 2);
+	seq_printf(s, "Bit 3 : Bluetooth is supported:      %lu\n",
+		  (status & BIT(3)) >> 3);
+	seq_printf(s, "Bit 4 : WWAN is supported:           %lu\n",
+		  (status & BIT(4)) >> 4);
+	seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n",
+		  (status & BIT(5)) >> 5);
+	seq_printf(s, "Bit 6 : UWB supported:               %lu\n",
+		  (status & BIT(6)) >> 6);
+	seq_printf(s, "Bit 7 : WiGig supported:             %lu\n",
+		  (status & BIT(7)) >> 7);
+	seq_printf(s, "Bit 8 : Wifi is installed:           %lu\n",
+		  (status & BIT(8)) >> 8);
+	seq_printf(s, "Bit 9 : Bluetooth is installed:      %lu\n",
+		  (status & BIT(9)) >> 9);
+	seq_printf(s, "Bit 10: WWAN is installed:           %lu\n",
+		  (status & BIT(10)) >> 10);
+	seq_printf(s, "Bit 11: UWB installed:               %lu\n",
+		  (status & BIT(11)) >> 11);
+	seq_printf(s, "Bit 12: WiGig installed:             %lu\n",
+		  (status & BIT(12)) >> 12);
+
+	seq_printf(s, "Bit 16: Hardware switch is on:       %lu\n",
+		  (status & BIT(16)) >> 16);
+	seq_printf(s, "Bit 17: Wifi is blocked:             %lu\n",
+		  (status & BIT(17)) >> 17);
+	seq_printf(s, "Bit 18: Bluetooth is blocked:        %lu\n",
+		  (status & BIT(18)) >> 18);
+	seq_printf(s, "Bit 19: WWAN is blocked:             %lu\n",
+		  (status & BIT(19)) >> 19);
+	seq_printf(s, "Bit 20: UWB is blocked:              %lu\n",
+		  (status & BIT(20)) >> 20);
+	seq_printf(s, "Bit 21: WiGig is blocked:            %lu\n",
+		  (status & BIT(21)) >> 21);
+
+	seq_printf(s, "\nhwswitch_return:\t%d\n", hwswitch_ret);
+	seq_printf(s, "hwswitch_state:\t0x%X\n", hwswitch_state);
+	seq_printf(s, "Bit 0 : Wifi controlled by switch:      %lu\n",
+		   hwswitch_state & BIT(0));
+	seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n",
+		   (hwswitch_state & BIT(1)) >> 1);
+	seq_printf(s, "Bit 2 : WWAN controlled by switch:      %lu\n",
+		   (hwswitch_state & BIT(2)) >> 2);
+	seq_printf(s, "Bit 3 : UWB controlled by switch:       %lu\n",
+		   (hwswitch_state & BIT(3)) >> 3);
+	seq_printf(s, "Bit 4 : WiGig controlled by switch:     %lu\n",
+		   (hwswitch_state & BIT(4)) >> 4);
+	seq_printf(s, "Bit 7 : Wireless switch config locked:  %lu\n",
+		   (hwswitch_state & BIT(7)) >> 7);
+	seq_printf(s, "Bit 8 : Wifi locator enabled:           %lu\n",
+		   (hwswitch_state & BIT(8)) >> 8);
+	seq_printf(s, "Bit 15: Wifi locator setting locked:    %lu\n",
+		   (hwswitch_state & BIT(15)) >> 15);
+
+	return 0;
+}
+
+static int dell_debugfs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dell_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations dell_debugfs_fops = {
+	.owner = THIS_MODULE,
+	.open = dell_debugfs_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static void dell_update_rfkill(struct work_struct *ignored)
+{
+	int hwswitch = 0;
+	int status;
+	int ret;
+
+	get_buffer();
+
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	status = buffer->output[1];
+
+	if (ret != 0)
+		goto out;
+
+	clear_buffer();
+
+	buffer->input[0] = 0x2;
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+
+	if (ret == 0 && (status & BIT(0)))
+		hwswitch = buffer->output[1];
+
+	if (wifi_rfkill) {
+		dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch);
+		dell_rfkill_update_sw_state(wifi_rfkill, 1, status);
+	}
+	if (bluetooth_rfkill) {
+		dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status,
+					    hwswitch);
+		dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status);
+	}
+	if (wwan_rfkill) {
+		dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch);
+		dell_rfkill_update_sw_state(wwan_rfkill, 3, status);
+	}
+
+ out:
+	release_buffer();
+}
+static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill);
+
+static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str,
+			      struct serio *port)
+{
+	static bool extended;
+
+	if (str & I8042_STR_AUXDATA)
+		return false;
+
+	if (unlikely(data == 0xe0)) {
+		extended = true;
+		return false;
+	} else if (unlikely(extended)) {
+		switch (data) {
+		case 0x8:
+			schedule_delayed_work(&dell_rfkill_work,
+					      round_jiffies_relative(HZ / 4));
+			break;
+		}
+		extended = false;
+	}
+
+	return false;
+}
+
+static int (*dell_rbtn_notifier_register_func)(struct notifier_block *);
+static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *);
+
+static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb,
+					  unsigned long action, void *data)
+{
+	schedule_delayed_work(&dell_rfkill_work, 0);
+	return NOTIFY_OK;
+}
+
+static struct notifier_block dell_laptop_rbtn_notifier = {
+	.notifier_call = dell_laptop_rbtn_notifier_call,
+};
+
+static int __init dell_setup_rfkill(void)
+{
+	int status, ret, whitelisted;
+	const char *product;
+
+	/*
+	 * rfkill support causes trouble on various models, mostly Inspirons.
+	 * So we whitelist certain series, and don't support rfkill on others.
+	 */
+	whitelisted = 0;
+	product = dmi_get_system_info(DMI_PRODUCT_NAME);
+	if (product &&  (strncmp(product, "Latitude", 8) == 0 ||
+			 strncmp(product, "Precision", 9) == 0))
+		whitelisted = 1;
+	if (!force_rfkill && !whitelisted)
+		return 0;
+
+	get_buffer();
+	dell_send_request(buffer, 17, 11);
+	ret = buffer->output[0];
+	status = buffer->output[1];
+	release_buffer();
+
+	/* dell wireless info smbios call is not supported */
+	if (ret != 0)
+		return 0;
+
+	/* rfkill is only tested on laptops with a hwswitch */
+	if (!(status & BIT(0)) && !force_rfkill)
+		return 0;
+
+	if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) {
+		wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev,
+					   RFKILL_TYPE_WLAN,
+					   &dell_rfkill_ops, (void *) 1);
+		if (!wifi_rfkill) {
+			ret = -ENOMEM;
+			goto err_wifi;
+		}
+		ret = rfkill_register(wifi_rfkill);
+		if (ret)
+			goto err_wifi;
+	}
+
+	if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) {
+		bluetooth_rfkill = rfkill_alloc("dell-bluetooth",
+						&platform_device->dev,
+						RFKILL_TYPE_BLUETOOTH,
+						&dell_rfkill_ops, (void *) 2);
+		if (!bluetooth_rfkill) {
+			ret = -ENOMEM;
+			goto err_bluetooth;
+		}
+		ret = rfkill_register(bluetooth_rfkill);
+		if (ret)
+			goto err_bluetooth;
+	}
+
+	if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) {
+		wwan_rfkill = rfkill_alloc("dell-wwan",
+					   &platform_device->dev,
+					   RFKILL_TYPE_WWAN,
+					   &dell_rfkill_ops, (void *) 3);
+		if (!wwan_rfkill) {
+			ret = -ENOMEM;
+			goto err_wwan;
+		}
+		ret = rfkill_register(wwan_rfkill);
+		if (ret)
+			goto err_wwan;
+	}
+
+	/*
+	 * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices
+	 * which can receive events from HW slider switch.
+	 *
+	 * Dell SMBIOS on whitelisted models supports controlling radio devices
+	 * but does not support receiving HW button switch events. We can use
+	 * i8042 filter hook function to receive keyboard data and handle
+	 * keycode for HW button.
+	 *
+	 * So if it is possible we will use Dell Airplane Mode Switch ACPI
+	 * driver for receiving HW events and Dell SMBIOS for setting rfkill
+	 * states. If ACPI driver or device is not available we will fallback to
+	 * i8042 filter hook function.
+	 *
+	 * To prevent duplicate rfkill devices which control and do same thing,
+	 * dell-rbtn driver will automatically remove its own rfkill devices
+	 * once function dell_rbtn_notifier_register() is called.
+	 */
+
+	dell_rbtn_notifier_register_func =
+		symbol_request(dell_rbtn_notifier_register);
+	if (dell_rbtn_notifier_register_func) {
+		dell_rbtn_notifier_unregister_func =
+			symbol_request(dell_rbtn_notifier_unregister);
+		if (!dell_rbtn_notifier_unregister_func) {
+			symbol_put(dell_rbtn_notifier_register);
+			dell_rbtn_notifier_register_func = NULL;
+		}
+	}
+
+	if (dell_rbtn_notifier_register_func) {
+		ret = dell_rbtn_notifier_register_func(
+			&dell_laptop_rbtn_notifier);
+		symbol_put(dell_rbtn_notifier_register);
+		dell_rbtn_notifier_register_func = NULL;
+		if (ret != 0) {
+			symbol_put(dell_rbtn_notifier_unregister);
+			dell_rbtn_notifier_unregister_func = NULL;
+		}
+	} else {
+		pr_info("Symbols from dell-rbtn acpi driver are not available\n");
+		ret = -ENODEV;
+	}
+
+	if (ret == 0) {
+		pr_info("Using dell-rbtn acpi driver for receiving events\n");
+	} else if (ret != -ENODEV) {
+		pr_warn("Unable to register dell rbtn notifier\n");
+		goto err_filter;
+	} else {
+		ret = i8042_install_filter(dell_laptop_i8042_filter);
+		if (ret) {
+			pr_warn("Unable to install key filter\n");
+			goto err_filter;
+		}
+		pr_info("Using i8042 filter function for receiving events\n");
+	}
+
+	return 0;
+err_filter:
+	if (wwan_rfkill)
+		rfkill_unregister(wwan_rfkill);
+err_wwan:
+	rfkill_destroy(wwan_rfkill);
+	if (bluetooth_rfkill)
+		rfkill_unregister(bluetooth_rfkill);
+err_bluetooth:
+	rfkill_destroy(bluetooth_rfkill);
+	if (wifi_rfkill)
+		rfkill_unregister(wifi_rfkill);
+err_wifi:
+	rfkill_destroy(wifi_rfkill);
+
+	return ret;
+}
+
+static void dell_cleanup_rfkill(void)
+{
+	if (dell_rbtn_notifier_unregister_func) {
+		dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier);
+		symbol_put(dell_rbtn_notifier_unregister);
+		dell_rbtn_notifier_unregister_func = NULL;
+	} else {
+		i8042_remove_filter(dell_laptop_i8042_filter);
+	}
+	cancel_delayed_work_sync(&dell_rfkill_work);
+	if (wifi_rfkill) {
+		rfkill_unregister(wifi_rfkill);
+		rfkill_destroy(wifi_rfkill);
+	}
+	if (bluetooth_rfkill) {
+		rfkill_unregister(bluetooth_rfkill);
+		rfkill_destroy(bluetooth_rfkill);
+	}
+	if (wwan_rfkill) {
+		rfkill_unregister(wwan_rfkill);
+		rfkill_destroy(wwan_rfkill);
+	}
+}
+
+static int dell_send_intensity(struct backlight_device *bd)
+{
+	int token;
+	int ret;
+
+	token = find_token_location(BRIGHTNESS_TOKEN);
+	if (token == -1)
+		return -ENODEV;
+
+	get_buffer();
+	buffer->input[0] = token;
+	buffer->input[1] = bd->props.brightness;
+
+	if (power_supply_is_system_supplied() > 0)
+		dell_send_request(buffer, 1, 2);
+	else
+		dell_send_request(buffer, 1, 1);
+
+	ret = dell_smi_error(buffer->output[0]);
+
+	release_buffer();
+	return ret;
+}
+
+static int dell_get_intensity(struct backlight_device *bd)
+{
+	int token;
+	int ret;
+
+	token = find_token_location(BRIGHTNESS_TOKEN);
+	if (token == -1)
+		return -ENODEV;
+
+	get_buffer();
+	buffer->input[0] = token;
+
+	if (power_supply_is_system_supplied() > 0)
+		dell_send_request(buffer, 0, 2);
+	else
+		dell_send_request(buffer, 0, 1);
+
+	if (buffer->output[0])
+		ret = dell_smi_error(buffer->output[0]);
+	else
+		ret = buffer->output[1];
+
+	release_buffer();
+	return ret;
+}
+
+static const struct backlight_ops dell_ops = {
+	.get_brightness = dell_get_intensity,
+	.update_status  = dell_send_intensity,
+};
+
+static void touchpad_led_on(void)
+{
+	int command = 0x97;
+	char data = 1;
+	i8042_command(&data, command | 1 << 12);
+}
+
+static void touchpad_led_off(void)
+{
+	int command = 0x97;
+	char data = 2;
+	i8042_command(&data, command | 1 << 12);
+}
+
+static void touchpad_led_set(struct led_classdev *led_cdev,
+	enum led_brightness value)
+{
+	if (value > 0)
+		touchpad_led_on();
+	else
+		touchpad_led_off();
+}
+
+static struct led_classdev touchpad_led = {
+	.name = "dell-laptop::touchpad",
+	.brightness_set = touchpad_led_set,
+	.flags = LED_CORE_SUSPENDRESUME,
+};
+
+static int __init touchpad_led_init(struct device *dev)
+{
+	return led_classdev_register(dev, &touchpad_led);
+}
+
+static void touchpad_led_exit(void)
+{
+	led_classdev_unregister(&touchpad_led);
+}
+
+/*
+ * Derived from information in smbios-keyboard-ctl:
+ *
+ * cbClass 4
+ * cbSelect 11
+ * Keyboard illumination
+ * cbArg1 determines the function to be performed
+ *
+ * cbArg1 0x0 = Get Feature Information
+ *  cbRES1         Standard return codes (0, -1, -2)
+ *  cbRES2, word0  Bitmap of user-selectable modes
+ *     bit 0     Always off (All systems)
+ *     bit 1     Always on (Travis ATG, Siberia)
+ *     bit 2     Auto: ALS-based On; ALS-based Off (Travis ATG)
+ *     bit 3     Auto: ALS- and input-activity-based On; input-activity based Off
+ *     bit 4     Auto: Input-activity-based On; input-activity based Off
+ *     bit 5     Auto: Input-activity-based On (illumination level 25%); input-activity based Off
+ *     bit 6     Auto: Input-activity-based On (illumination level 50%); input-activity based Off
+ *     bit 7     Auto: Input-activity-based On (illumination level 75%); input-activity based Off
+ *     bit 8     Auto: Input-activity-based On (illumination level 100%); input-activity based Off
+ *     bits 9-15 Reserved for future use
+ *  cbRES2, byte2  Reserved for future use
+ *  cbRES2, byte3  Keyboard illumination type
+ *     0         Reserved
+ *     1         Tasklight
+ *     2         Backlight
+ *     3-255     Reserved for future use
+ *  cbRES3, byte0  Supported auto keyboard illumination trigger bitmap.
+ *     bit 0     Any keystroke
+ *     bit 1     Touchpad activity
+ *     bit 2     Pointing stick
+ *     bit 3     Any mouse
+ *     bits 4-7  Reserved for future use
+ *  cbRES3, byte1  Supported timeout unit bitmap
+ *     bit 0     Seconds
+ *     bit 1     Minutes
+ *     bit 2     Hours
+ *     bit 3     Days
+ *     bits 4-7  Reserved for future use
+ *  cbRES3, byte2  Number of keyboard light brightness levels
+ *  cbRES4, byte0  Maximum acceptable seconds value (0 if seconds not supported).
+ *  cbRES4, byte1  Maximum acceptable minutes value (0 if minutes not supported).
+ *  cbRES4, byte2  Maximum acceptable hours value (0 if hours not supported).
+ *  cbRES4, byte3  Maximum acceptable days value (0 if days not supported)
+ *
+ * cbArg1 0x1 = Get Current State
+ *  cbRES1         Standard return codes (0, -1, -2)
+ *  cbRES2, word0  Bitmap of current mode state
+ *     bit 0     Always off (All systems)
+ *     bit 1     Always on (Travis ATG, Siberia)
+ *     bit 2     Auto: ALS-based On; ALS-based Off (Travis ATG)
+ *     bit 3     Auto: ALS- and input-activity-based On; input-activity based Off
+ *     bit 4     Auto: Input-activity-based On; input-activity based Off
+ *     bit 5     Auto: Input-activity-based On (illumination level 25%); input-activity based Off
+ *     bit 6     Auto: Input-activity-based On (illumination level 50%); input-activity based Off
+ *     bit 7     Auto: Input-activity-based On (illumination level 75%); input-activity based Off
+ *     bit 8     Auto: Input-activity-based On (illumination level 100%); input-activity based Off
+ *     bits 9-15 Reserved for future use
+ *     Note: Only One bit can be set
+ *  cbRES2, byte2  Currently active auto keyboard illumination triggers.
+ *     bit 0     Any keystroke
+ *     bit 1     Touchpad activity
+ *     bit 2     Pointing stick
+ *     bit 3     Any mouse
+ *     bits 4-7  Reserved for future use
+ *  cbRES2, byte3  Current Timeout
+ *     bits 7:6  Timeout units indicator:
+ *     00b       Seconds
+ *     01b       Minutes
+ *     10b       Hours
+ *     11b       Days
+ *     bits 5:0  Timeout value (0-63) in sec/min/hr/day
+ *     NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte
+ *     are set upon return from the [Get feature information] call.
+ *  cbRES3, byte0  Current setting of ALS value that turns the light on or off.
+ *  cbRES3, byte1  Current ALS reading
+ *  cbRES3, byte2  Current keyboard light level.
+ *
+ * cbArg1 0x2 = Set New State
+ *  cbRES1         Standard return codes (0, -1, -2)
+ *  cbArg2, word0  Bitmap of current mode state
+ *     bit 0     Always off (All systems)
+ *     bit 1     Always on (Travis ATG, Siberia)
+ *     bit 2     Auto: ALS-based On; ALS-based Off (Travis ATG)
+ *     bit 3     Auto: ALS- and input-activity-based On; input-activity based Off
+ *     bit 4     Auto: Input-activity-based On; input-activity based Off
+ *     bit 5     Auto: Input-activity-based On (illumination level 25%); input-activity based Off
+ *     bit 6     Auto: Input-activity-based On (illumination level 50%); input-activity based Off
+ *     bit 7     Auto: Input-activity-based On (illumination level 75%); input-activity based Off
+ *     bit 8     Auto: Input-activity-based On (illumination level 100%); input-activity based Off
+ *     bits 9-15 Reserved for future use
+ *     Note: Only One bit can be set
+ *  cbArg2, byte2  Desired auto keyboard illumination triggers. Must remain inactive to allow
+ *                 keyboard to turn off automatically.
+ *     bit 0     Any keystroke
+ *     bit 1     Touchpad activity
+ *     bit 2     Pointing stick
+ *     bit 3     Any mouse
+ *     bits 4-7  Reserved for future use
+ *  cbArg2, byte3  Desired Timeout
+ *     bits 7:6  Timeout units indicator:
+ *     00b       Seconds
+ *     01b       Minutes
+ *     10b       Hours
+ *     11b       Days
+ *     bits 5:0  Timeout value (0-63) in sec/min/hr/day
+ *  cbArg3, byte0  Desired setting of ALS value that turns the light on or off.
+ *  cbArg3, byte2  Desired keyboard light level.
+ */
+
+
+enum kbd_timeout_unit {
+	KBD_TIMEOUT_SECONDS = 0,
+	KBD_TIMEOUT_MINUTES,
+	KBD_TIMEOUT_HOURS,
+	KBD_TIMEOUT_DAYS,
+};
+
+enum kbd_mode_bit {
+	KBD_MODE_BIT_OFF = 0,
+	KBD_MODE_BIT_ON,
+	KBD_MODE_BIT_ALS,
+	KBD_MODE_BIT_TRIGGER_ALS,
+	KBD_MODE_BIT_TRIGGER,
+	KBD_MODE_BIT_TRIGGER_25,
+	KBD_MODE_BIT_TRIGGER_50,
+	KBD_MODE_BIT_TRIGGER_75,
+	KBD_MODE_BIT_TRIGGER_100,
+};
+
+#define kbd_is_als_mode_bit(bit) \
+	((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS)
+#define kbd_is_trigger_mode_bit(bit) \
+	((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100)
+#define kbd_is_level_mode_bit(bit) \
+	((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100)
+
+struct kbd_info {
+	u16 modes;
+	u8 type;
+	u8 triggers;
+	u8 levels;
+	u8 seconds;
+	u8 minutes;
+	u8 hours;
+	u8 days;
+};
+
+struct kbd_state {
+	u8 mode_bit;
+	u8 triggers;
+	u8 timeout_value;
+	u8 timeout_unit;
+	u8 als_setting;
+	u8 als_value;
+	u8 level;
+};
+
+static const int kbd_tokens[] = {
+	KBD_LED_OFF_TOKEN,
+	KBD_LED_AUTO_25_TOKEN,
+	KBD_LED_AUTO_50_TOKEN,
+	KBD_LED_AUTO_75_TOKEN,
+	KBD_LED_AUTO_100_TOKEN,
+	KBD_LED_ON_TOKEN,
+};
+
+static u16 kbd_token_bits;
+
+static struct kbd_info kbd_info;
+static bool kbd_als_supported;
+static bool kbd_triggers_supported;
+
+static u8 kbd_mode_levels[16];
+static int kbd_mode_levels_count;
+
+static u8 kbd_previous_level;
+static u8 kbd_previous_mode_bit;
+
+static bool kbd_led_present;
+
+/*
+ * NOTE: there are three ways to set the keyboard backlight level.
+ * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value).
+ * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels).
+ * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens)
+ *
+ * There are laptops which support only one of these methods. If we want to
+ * support as many machines as possible we need to implement all three methods.
+ * The first two methods use the kbd_state structure. The third uses SMBIOS
+ * tokens. If kbd_info.levels == 0, the machine does not support setting the
+ * keyboard backlight level via kbd_state.level.
+ */
+
+static int kbd_get_info(struct kbd_info *info)
+{
+	u8 units;
+	int ret;
+
+	get_buffer();
+
+	buffer->input[0] = 0x0;
+	dell_send_request(buffer, 4, 11);
+	ret = buffer->output[0];
+
+	if (ret) {
+		ret = dell_smi_error(ret);
+		goto out;
+	}
+
+	info->modes = buffer->output[1] & 0xFFFF;
+	info->type = (buffer->output[1] >> 24) & 0xFF;
+	info->triggers = buffer->output[2] & 0xFF;
+	units = (buffer->output[2] >> 8) & 0xFF;
+	info->levels = (buffer->output[2] >> 16) & 0xFF;
+
+	if (units & BIT(0))
+		info->seconds = (buffer->output[3] >> 0) & 0xFF;
+	if (units & BIT(1))
+		info->minutes = (buffer->output[3] >> 8) & 0xFF;
+	if (units & BIT(2))
+		info->hours = (buffer->output[3] >> 16) & 0xFF;
+	if (units & BIT(3))
+		info->days = (buffer->output[3] >> 24) & 0xFF;
+
+ out:
+	release_buffer();
+	return ret;
+}
+
+static unsigned int kbd_get_max_level(void)
+{
+	if (kbd_info.levels != 0)
+		return kbd_info.levels;
+	if (kbd_mode_levels_count > 0)
+		return kbd_mode_levels_count - 1;
+	return 0;
+}
+
+static int kbd_get_level(struct kbd_state *state)
+{
+	int i;
+
+	if (kbd_info.levels != 0)
+		return state->level;
+
+	if (kbd_mode_levels_count > 0) {
+		for (i = 0; i < kbd_mode_levels_count; ++i)
+			if (kbd_mode_levels[i] == state->mode_bit)
+				return i;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int kbd_set_level(struct kbd_state *state, u8 level)
+{
+	if (kbd_info.levels != 0) {
+		if (level != 0)
+			kbd_previous_level = level;
+		if (state->level == level)
+			return 0;
+		state->level = level;
+		if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF)
+			state->mode_bit = kbd_previous_mode_bit;
+		else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) {
+			kbd_previous_mode_bit = state->mode_bit;
+			state->mode_bit = KBD_MODE_BIT_OFF;
+		}
+		return 0;
+	}
+
+	if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) {
+		if (level != 0)
+			kbd_previous_level = level;
+		state->mode_bit = kbd_mode_levels[level];
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int kbd_get_state(struct kbd_state *state)
+{
+	int ret;
+
+	get_buffer();
+
+	buffer->input[0] = 0x1;
+	dell_send_request(buffer, 4, 11);
+	ret = buffer->output[0];
+
+	if (ret) {
+		ret = dell_smi_error(ret);
+		goto out;
+	}
+
+	state->mode_bit = ffs(buffer->output[1] & 0xFFFF);
+	if (state->mode_bit != 0)
+		state->mode_bit--;
+
+	state->triggers = (buffer->output[1] >> 16) & 0xFF;
+	state->timeout_value = (buffer->output[1] >> 24) & 0x3F;
+	state->timeout_unit = (buffer->output[1] >> 30) & 0x3;
+	state->als_setting = buffer->output[2] & 0xFF;
+	state->als_value = (buffer->output[2] >> 8) & 0xFF;
+	state->level = (buffer->output[2] >> 16) & 0xFF;
+
+ out:
+	release_buffer();
+	return ret;
+}
+
+static int kbd_set_state(struct kbd_state *state)
+{
+	int ret;
+
+	get_buffer();
+	buffer->input[0] = 0x2;
+	buffer->input[1] = BIT(state->mode_bit) & 0xFFFF;
+	buffer->input[1] |= (state->triggers & 0xFF) << 16;
+	buffer->input[1] |= (state->timeout_value & 0x3F) << 24;
+	buffer->input[1] |= (state->timeout_unit & 0x3) << 30;
+	buffer->input[2] = state->als_setting & 0xFF;
+	buffer->input[2] |= (state->level & 0xFF) << 16;
+	dell_send_request(buffer, 4, 11);
+	ret = buffer->output[0];
+	release_buffer();
+
+	return dell_smi_error(ret);
+}
+
+static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old)
+{
+	int ret;
+
+	ret = kbd_set_state(state);
+	if (ret == 0)
+		return 0;
+
+	/*
+	 * When setting the new state fails,try to restore the previous one.
+	 * This is needed on some machines where BIOS sets a default state when
+	 * setting a new state fails. This default state could be all off.
+	 */
+
+	if (kbd_set_state(old))
+		pr_err("Setting old previous keyboard state failed\n");
+
+	return ret;
+}
+
+static int kbd_set_token_bit(u8 bit)
+{
+	int id;
+	int ret;
+
+	if (bit >= ARRAY_SIZE(kbd_tokens))
+		return -EINVAL;
+
+	id = find_token_id(kbd_tokens[bit]);
+	if (id == -1)
+		return -EINVAL;
+
+	get_buffer();
+	buffer->input[0] = da_tokens[id].location;
+	buffer->input[1] = da_tokens[id].value;
+	dell_send_request(buffer, 1, 0);
+	ret = buffer->output[0];
+	release_buffer();
+
+	return dell_smi_error(ret);
+}
+
+static int kbd_get_token_bit(u8 bit)
+{
+	int id;
+	int ret;
+	int val;
+
+	if (bit >= ARRAY_SIZE(kbd_tokens))
+		return -EINVAL;
+
+	id = find_token_id(kbd_tokens[bit]);
+	if (id == -1)
+		return -EINVAL;
+
+	get_buffer();
+	buffer->input[0] = da_tokens[id].location;
+	dell_send_request(buffer, 0, 0);
+	ret = buffer->output[0];
+	val = buffer->output[1];
+	release_buffer();
+
+	if (ret)
+		return dell_smi_error(ret);
+
+	return (val == da_tokens[id].value);
+}
+
+static int kbd_get_first_active_token_bit(void)
+{
+	int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) {
+		ret = kbd_get_token_bit(i);
+		if (ret == 1)
+			return i;
+	}
+
+	return ret;
+}
+
+static int kbd_get_valid_token_counts(void)
+{
+	return hweight16(kbd_token_bits);
+}
+
+static inline int kbd_init_info(void)
+{
+	struct kbd_state state;
+	int ret;
+	int i;
+
+	ret = kbd_get_info(&kbd_info);
+	if (ret)
+		return ret;
+
+	kbd_get_state(&state);
+
+	/* NOTE: timeout value is stored in 6 bits so max value is 63 */
+	if (kbd_info.seconds > 63)
+		kbd_info.seconds = 63;
+	if (kbd_info.minutes > 63)
+		kbd_info.minutes = 63;
+	if (kbd_info.hours > 63)
+		kbd_info.hours = 63;
+	if (kbd_info.days > 63)
+		kbd_info.days = 63;
+
+	/* NOTE: On tested machines ON mode did not work and caused
+	 *       problems (turned backlight off) so do not use it
+	 */
+	kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON);
+
+	kbd_previous_level = kbd_get_level(&state);
+	kbd_previous_mode_bit = state.mode_bit;
+
+	if (kbd_previous_level == 0 && kbd_get_max_level() != 0)
+		kbd_previous_level = 1;
+
+	if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) {
+		kbd_previous_mode_bit =
+			ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF));
+		if (kbd_previous_mode_bit != 0)
+			kbd_previous_mode_bit--;
+	}
+
+	if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) |
+			      BIT(KBD_MODE_BIT_TRIGGER_ALS)))
+		kbd_als_supported = true;
+
+	if (kbd_info.modes & (
+	    BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) |
+	    BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) |
+	    BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100)
+	   ))
+		kbd_triggers_supported = true;
+
+	/* kbd_mode_levels[0] is reserved, see below */
+	for (i = 0; i < 16; ++i)
+		if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes))
+			kbd_mode_levels[1 + kbd_mode_levels_count++] = i;
+
+	/*
+	 * Find the first supported mode and assign to kbd_mode_levels[0].
+	 * This should be 0 (off), but we cannot depend on the BIOS to
+	 * support 0.
+	 */
+	if (kbd_mode_levels_count > 0) {
+		for (i = 0; i < 16; ++i) {
+			if (BIT(i) & kbd_info.modes) {
+				kbd_mode_levels[0] = i;
+				break;
+			}
+		}
+		kbd_mode_levels_count++;
+	}
+
+	return 0;
+
+}
+
+static inline void kbd_init_tokens(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i)
+		if (find_token_id(kbd_tokens[i]) != -1)
+			kbd_token_bits |= BIT(i);
+}
+
+static void kbd_init(void)
+{
+	int ret;
+
+	ret = kbd_init_info();
+	kbd_init_tokens();
+
+	if (kbd_token_bits != 0 || ret == 0)
+		kbd_led_present = true;
+}
+
+static ssize_t kbd_led_timeout_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct kbd_state new_state;
+	struct kbd_state state;
+	bool convert;
+	int value;
+	int ret;
+	char ch;
+	u8 unit;
+	int i;
+
+	ret = sscanf(buf, "%d %c", &value, &ch);
+	if (ret < 1)
+		return -EINVAL;
+	else if (ret == 1)
+		ch = 's';
+
+	if (value < 0)
+		return -EINVAL;
+
+	convert = false;
+
+	switch (ch) {
+	case 's':
+		if (value > kbd_info.seconds)
+			convert = true;
+		unit = KBD_TIMEOUT_SECONDS;
+		break;
+	case 'm':
+		if (value > kbd_info.minutes)
+			convert = true;
+		unit = KBD_TIMEOUT_MINUTES;
+		break;
+	case 'h':
+		if (value > kbd_info.hours)
+			convert = true;
+		unit = KBD_TIMEOUT_HOURS;
+		break;
+	case 'd':
+		if (value > kbd_info.days)
+			convert = true;
+		unit = KBD_TIMEOUT_DAYS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (quirks && quirks->needs_kbd_timeouts)
+		convert = true;
+
+	if (convert) {
+		/* Convert value from current units to seconds */
+		switch (unit) {
+		case KBD_TIMEOUT_DAYS:
+			value *= 24;
+		case KBD_TIMEOUT_HOURS:
+			value *= 60;
+		case KBD_TIMEOUT_MINUTES:
+			value *= 60;
+			unit = KBD_TIMEOUT_SECONDS;
+		}
+
+		if (quirks && quirks->needs_kbd_timeouts) {
+			for (i = 0; quirks->kbd_timeouts[i] != -1; i++) {
+				if (value <= quirks->kbd_timeouts[i]) {
+					value = quirks->kbd_timeouts[i];
+					break;
+				}
+			}
+		}
+
+		if (value <= kbd_info.seconds && kbd_info.seconds) {
+			unit = KBD_TIMEOUT_SECONDS;
+		} else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) {
+			value /= 60;
+			unit = KBD_TIMEOUT_MINUTES;
+		} else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) {
+			value /= (60 * 60);
+			unit = KBD_TIMEOUT_HOURS;
+		} else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) {
+			value /= (60 * 60 * 24);
+			unit = KBD_TIMEOUT_DAYS;
+		} else {
+			return -EINVAL;
+		}
+	}
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	new_state = state;
+	new_state.timeout_value = value;
+	new_state.timeout_unit = unit;
+
+	ret = kbd_set_state_safe(&new_state, &state);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t kbd_led_timeout_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct kbd_state state;
+	int ret;
+	int len;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	len = sprintf(buf, "%d", state.timeout_value);
+
+	switch (state.timeout_unit) {
+	case KBD_TIMEOUT_SECONDS:
+		return len + sprintf(buf+len, "s\n");
+	case KBD_TIMEOUT_MINUTES:
+		return len + sprintf(buf+len, "m\n");
+	case KBD_TIMEOUT_HOURS:
+		return len + sprintf(buf+len, "h\n");
+	case KBD_TIMEOUT_DAYS:
+		return len + sprintf(buf+len, "d\n");
+	default:
+		return -EINVAL;
+	}
+
+	return len;
+}
+
+static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR,
+		   kbd_led_timeout_show, kbd_led_timeout_store);
+
+static const char * const kbd_led_triggers[] = {
+	"keyboard",
+	"touchpad",
+	/*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */
+	"mouse",
+};
+
+static ssize_t kbd_led_triggers_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct kbd_state new_state;
+	struct kbd_state state;
+	bool triggers_enabled = false;
+	int trigger_bit = -1;
+	char trigger[21];
+	int i, ret;
+
+	ret = sscanf(buf, "%20s", trigger);
+	if (ret != 1)
+		return -EINVAL;
+
+	if (trigger[0] != '+' && trigger[0] != '-')
+		return -EINVAL;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	if (kbd_triggers_supported)
+		triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit);
+
+	if (kbd_triggers_supported) {
+		for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) {
+			if (!(kbd_info.triggers & BIT(i)))
+				continue;
+			if (!kbd_led_triggers[i])
+				continue;
+			if (strcmp(trigger+1, kbd_led_triggers[i]) != 0)
+				continue;
+			if (trigger[0] == '+' &&
+			    triggers_enabled && (state.triggers & BIT(i)))
+				return count;
+			if (trigger[0] == '-' &&
+			    (!triggers_enabled || !(state.triggers & BIT(i))))
+				return count;
+			trigger_bit = i;
+			break;
+		}
+	}
+
+	if (trigger_bit != -1) {
+		new_state = state;
+		if (trigger[0] == '+')
+			new_state.triggers |= BIT(trigger_bit);
+		else {
+			new_state.triggers &= ~BIT(trigger_bit);
+			/* NOTE: trackstick bit (2) must be disabled when
+			 *       disabling touchpad bit (1), otherwise touchpad
+			 *       bit (1) will not be disabled */
+			if (trigger_bit == 1)
+				new_state.triggers &= ~BIT(2);
+		}
+		if ((kbd_info.triggers & new_state.triggers) !=
+		    new_state.triggers)
+			return -EINVAL;
+		if (new_state.triggers && !triggers_enabled) {
+			new_state.mode_bit = KBD_MODE_BIT_TRIGGER;
+			kbd_set_level(&new_state, kbd_previous_level);
+		} else if (new_state.triggers == 0) {
+			kbd_set_level(&new_state, 0);
+		}
+		if (!(kbd_info.modes & BIT(new_state.mode_bit)))
+			return -EINVAL;
+		ret = kbd_set_state_safe(&new_state, &state);
+		if (ret)
+			return ret;
+		if (new_state.mode_bit != KBD_MODE_BIT_OFF)
+			kbd_previous_mode_bit = new_state.mode_bit;
+		return count;
+	}
+
+	return -EINVAL;
+}
+
+static ssize_t kbd_led_triggers_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct kbd_state state;
+	bool triggers_enabled;
+	int level, i, ret;
+	int len = 0;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	len = 0;
+
+	if (kbd_triggers_supported) {
+		triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit);
+		level = kbd_get_level(&state);
+		for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) {
+			if (!(kbd_info.triggers & BIT(i)))
+				continue;
+			if (!kbd_led_triggers[i])
+				continue;
+			if ((triggers_enabled || level <= 0) &&
+			    (state.triggers & BIT(i)))
+				buf[len++] = '+';
+			else
+				buf[len++] = '-';
+			len += sprintf(buf+len, "%s ", kbd_led_triggers[i]);
+		}
+	}
+
+	if (len)
+		buf[len - 1] = '\n';
+
+	return len;
+}
+
+static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR,
+		   kbd_led_triggers_show, kbd_led_triggers_store);
+
+static ssize_t kbd_led_als_enabled_store(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct kbd_state new_state;
+	struct kbd_state state;
+	bool triggers_enabled = false;
+	int enable;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &enable);
+	if (ret)
+		return ret;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	if (enable == kbd_is_als_mode_bit(state.mode_bit))
+		return count;
+
+	new_state = state;
+
+	if (kbd_triggers_supported)
+		triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit);
+
+	if (enable) {
+		if (triggers_enabled)
+			new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS;
+		else
+			new_state.mode_bit = KBD_MODE_BIT_ALS;
+	} else {
+		if (triggers_enabled) {
+			new_state.mode_bit = KBD_MODE_BIT_TRIGGER;
+			kbd_set_level(&new_state, kbd_previous_level);
+		} else {
+			new_state.mode_bit = KBD_MODE_BIT_ON;
+		}
+	}
+	if (!(kbd_info.modes & BIT(new_state.mode_bit)))
+		return -EINVAL;
+
+	ret = kbd_set_state_safe(&new_state, &state);
+	if (ret)
+		return ret;
+	kbd_previous_mode_bit = new_state.mode_bit;
+
+	return count;
+}
+
+static ssize_t kbd_led_als_enabled_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct kbd_state state;
+	bool enabled = false;
+	int ret;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+	enabled = kbd_is_als_mode_bit(state.mode_bit);
+
+	return sprintf(buf, "%d\n", enabled ? 1 : 0);
+}
+
+static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR,
+		   kbd_led_als_enabled_show, kbd_led_als_enabled_store);
+
+static ssize_t kbd_led_als_setting_store(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct kbd_state state;
+	struct kbd_state new_state;
+	u8 setting;
+	int ret;
+
+	ret = kstrtou8(buf, 10, &setting);
+	if (ret)
+		return ret;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	new_state = state;
+	new_state.als_setting = setting;
+
+	ret = kbd_set_state_safe(&new_state, &state);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t kbd_led_als_setting_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct kbd_state state;
+	int ret;
+
+	ret = kbd_get_state(&state);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%d\n", state.als_setting);
+}
+
+static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR,
+		   kbd_led_als_setting_show, kbd_led_als_setting_store);
+
+static struct attribute *kbd_led_attrs[] = {
+	&dev_attr_stop_timeout.attr,
+	&dev_attr_start_triggers.attr,
+	NULL,
+};
+
+static const struct attribute_group kbd_led_group = {
+	.attrs = kbd_led_attrs,
+};
+
+static struct attribute *kbd_led_als_attrs[] = {
+	&dev_attr_als_enabled.attr,
+	&dev_attr_als_setting.attr,
+	NULL,
+};
+
+static const struct attribute_group kbd_led_als_group = {
+	.attrs = kbd_led_als_attrs,
+};
+
+static const struct attribute_group *kbd_led_groups[] = {
+	&kbd_led_group,
+	&kbd_led_als_group,
+	NULL,
+};
+
+static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev)
+{
+	int ret;
+	u16 num;
+	struct kbd_state state;
+
+	if (kbd_get_max_level()) {
+		ret = kbd_get_state(&state);
+		if (ret)
+			return 0;
+		ret = kbd_get_level(&state);
+		if (ret < 0)
+			return 0;
+		return ret;
+	}
+
+	if (kbd_get_valid_token_counts()) {
+		ret = kbd_get_first_active_token_bit();
+		if (ret < 0)
+			return 0;
+		for (num = kbd_token_bits; num != 0 && ret > 0; --ret)
+			num &= num - 1; /* clear the first bit set */
+		if (num == 0)
+			return 0;
+		return ffs(num) - 1;
+	}
+
+	pr_warn("Keyboard brightness level control not supported\n");
+	return 0;
+}
+
+static void kbd_led_level_set(struct led_classdev *led_cdev,
+			      enum led_brightness value)
+{
+	struct kbd_state state;
+	struct kbd_state new_state;
+	u16 num;
+
+	if (kbd_get_max_level()) {
+		if (kbd_get_state(&state))
+			return;
+		new_state = state;
+		if (kbd_set_level(&new_state, value))
+			return;
+		kbd_set_state_safe(&new_state, &state);
+		return;
+	}
+
+	if (kbd_get_valid_token_counts()) {
+		for (num = kbd_token_bits; num != 0 && value > 0; --value)
+			num &= num - 1; /* clear the first bit set */
+		if (num == 0)
+			return;
+		kbd_set_token_bit(ffs(num) - 1);
+		return;
+	}
+
+	pr_warn("Keyboard brightness level control not supported\n");
+}
+
+static struct led_classdev kbd_led = {
+	.name           = "dell::kbd_backlight",
+	.brightness_set = kbd_led_level_set,
+	.brightness_get = kbd_led_level_get,
+	.groups         = kbd_led_groups,
+};
+
+static int __init kbd_led_init(struct device *dev)
+{
+	kbd_init();
+	if (!kbd_led_present)
+		return -ENODEV;
+	if (!kbd_als_supported)
+		kbd_led_groups[1] = NULL;
+	kbd_led.max_brightness = kbd_get_max_level();
+	if (!kbd_led.max_brightness) {
+		kbd_led.max_brightness = kbd_get_valid_token_counts();
+		if (kbd_led.max_brightness)
+			kbd_led.max_brightness--;
+	}
+	return led_classdev_register(dev, &kbd_led);
+}
+
+static void brightness_set_exit(struct led_classdev *led_cdev,
+				enum led_brightness value)
+{
+	/* Don't change backlight level on exit */
+};
+
+static void kbd_led_exit(void)
+{
+	if (!kbd_led_present)
+		return;
+	kbd_led.brightness_set = brightness_set_exit;
+	led_classdev_unregister(&kbd_led);
+}
+
+static int __init dell_init(void)
+{
+	int max_intensity = 0;
+	int token;
+	int ret;
+
+	if (!dmi_check_system(dell_device_table))
+		return -ENODEV;
+
+	quirks = NULL;
+	/* find if this machine support other functions */
+	dmi_check_system(dell_quirks);
+
+	dmi_walk(find_tokens, NULL);
+
+	if (!da_tokens)  {
+		pr_info("Unable to find dmi tokens\n");
+		return -ENODEV;
+	}
+
+	ret = platform_driver_register(&platform_driver);
+	if (ret)
+		goto fail_platform_driver;
+	platform_device = platform_device_alloc("dell-laptop", -1);
+	if (!platform_device) {
+		ret = -ENOMEM;
+		goto fail_platform_device1;
+	}
+	ret = platform_device_add(platform_device);
+	if (ret)
+		goto fail_platform_device2;
+
+	/*
+	 * Allocate buffer below 4GB for SMI data--only 32-bit physical addr
+	 * is passed to SMI handler.
+	 */
+	buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto fail_buffer;
+	}
+
+	ret = dell_setup_rfkill();
+
+	if (ret) {
+		pr_warn("Unable to setup rfkill\n");
+		goto fail_rfkill;
+	}
+
+	if (quirks && quirks->touchpad_led)
+		touchpad_led_init(&platform_device->dev);
+
+	kbd_led_init(&platform_device->dev);
+
+	dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL);
+	if (dell_laptop_dir != NULL)
+		debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL,
+				    &dell_debugfs_fops);
+
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
+		return 0;
+
+	token = find_token_location(BRIGHTNESS_TOKEN);
+	if (token != -1) {
+		get_buffer();
+		buffer->input[0] = token;
+		dell_send_request(buffer, 0, 2);
+		if (buffer->output[0] == 0)
+			max_intensity = buffer->output[3];
+		release_buffer();
+	}
+
+	if (max_intensity) {
+		struct backlight_properties props;
+		memset(&props, 0, sizeof(struct backlight_properties));
+		props.type = BACKLIGHT_PLATFORM;
+		props.max_brightness = max_intensity;
+		dell_backlight_device = backlight_device_register("dell_backlight",
+								  &platform_device->dev,
+								  NULL,
+								  &dell_ops,
+								  &props);
+
+		if (IS_ERR(dell_backlight_device)) {
+			ret = PTR_ERR(dell_backlight_device);
+			dell_backlight_device = NULL;
+			goto fail_backlight;
+		}
+
+		dell_backlight_device->props.brightness =
+			dell_get_intensity(dell_backlight_device);
+		backlight_update_status(dell_backlight_device);
+	}
+
+	return 0;
+
+fail_backlight:
+	dell_cleanup_rfkill();
+fail_rfkill:
+	free_page((unsigned long)buffer);
+fail_buffer:
+	platform_device_del(platform_device);
+fail_platform_device2:
+	platform_device_put(platform_device);
+fail_platform_device1:
+	platform_driver_unregister(&platform_driver);
+fail_platform_driver:
+	kfree(da_tokens);
+	return ret;
+}
+
+static void __exit dell_exit(void)
+{
+	debugfs_remove_recursive(dell_laptop_dir);
+	if (quirks && quirks->touchpad_led)
+		touchpad_led_exit();
+	kbd_led_exit();
+	backlight_device_unregister(dell_backlight_device);
+	dell_cleanup_rfkill();
+	if (platform_device) {
+		platform_device_unregister(platform_device);
+		platform_driver_unregister(&platform_driver);
+	}
+	kfree(da_tokens);
+	free_page((unsigned long)buffer);
+}
+
+/* dell-rbtn.c driver export functions which will not work correctly (and could
+ * cause kernel crash) if they are called before dell-rbtn.c init code. This is
+ * not problem when dell-rbtn.c is compiled as external module. When both files
+ * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we
+ * need to ensure that dell_init() will be called after initializing dell-rbtn.
+ * This can be achieved by late_initcall() instead module_init().
+ */
+late_initcall(dell_init);
+module_exit(dell_exit);
+
+MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
+MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("Dell laptop driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c
new file mode 100644
index 0000000..d33e9ad
--- /dev/null
+++ b/drivers/platform/x86/dell-rbtn.c
@@ -0,0 +1,479 @@
+/*
+    Dell Airplane Mode Switch driver
+    Copyright (C) 2014-2015  Pali Rohár <pali.rohar@gmail.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.
+*/
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/input.h>
+
+enum rbtn_type {
+	RBTN_UNKNOWN,
+	RBTN_TOGGLE,
+	RBTN_SLIDER,
+};
+
+struct rbtn_data {
+	enum rbtn_type type;
+	struct rfkill *rfkill;
+	struct input_dev *input_dev;
+	bool suspended;
+};
+
+
+/*
+ * acpi functions
+ */
+
+static enum rbtn_type rbtn_check(struct acpi_device *device)
+{
+	unsigned long long output;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);
+	if (ACPI_FAILURE(status))
+		return RBTN_UNKNOWN;
+
+	switch (output) {
+	case 0:
+	case 1:
+		return RBTN_TOGGLE;
+	case 2:
+	case 3:
+		return RBTN_SLIDER;
+	default:
+		return RBTN_UNKNOWN;
+	}
+}
+
+static int rbtn_get(struct acpi_device *device)
+{
+	unsigned long long output;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	return !output;
+}
+
+static int rbtn_acquire(struct acpi_device *device, bool enable)
+{
+	struct acpi_object_list input;
+	union acpi_object param;
+	acpi_status status;
+
+	param.type = ACPI_TYPE_INTEGER;
+	param.integer.value = enable;
+	input.count = 1;
+	input.pointer = &param;
+
+	status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	return 0;
+}
+
+
+/*
+ * rfkill device
+ */
+
+static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)
+{
+	struct acpi_device *device = data;
+	int state;
+
+	state = rbtn_get(device);
+	if (state < 0)
+		return;
+
+	rfkill_set_states(rfkill, state, state);
+}
+
+static int rbtn_rfkill_set_block(void *data, bool blocked)
+{
+	/* NOTE: setting soft rfkill state is not supported */
+	return -EINVAL;
+}
+
+static struct rfkill_ops rbtn_ops = {
+	.query = rbtn_rfkill_query,
+	.set_block = rbtn_rfkill_set_block,
+};
+
+static int rbtn_rfkill_init(struct acpi_device *device)
+{
+	struct rbtn_data *rbtn_data = device->driver_data;
+	int ret;
+
+	if (rbtn_data->rfkill)
+		return 0;
+
+	/*
+	 * NOTE: rbtn controls all radio devices, not only WLAN
+	 *       but rfkill interface does not support "ANY" type
+	 *       so "WLAN" type is used
+	 */
+	rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
+					 RFKILL_TYPE_WLAN, &rbtn_ops, device);
+	if (!rbtn_data->rfkill)
+		return -ENOMEM;
+
+	ret = rfkill_register(rbtn_data->rfkill);
+	if (ret) {
+		rfkill_destroy(rbtn_data->rfkill);
+		rbtn_data->rfkill = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void rbtn_rfkill_exit(struct acpi_device *device)
+{
+	struct rbtn_data *rbtn_data = device->driver_data;
+
+	if (!rbtn_data->rfkill)
+		return;
+
+	rfkill_unregister(rbtn_data->rfkill);
+	rfkill_destroy(rbtn_data->rfkill);
+	rbtn_data->rfkill = NULL;
+}
+
+static void rbtn_rfkill_event(struct acpi_device *device)
+{
+	struct rbtn_data *rbtn_data = device->driver_data;
+
+	if (rbtn_data->rfkill)
+		rbtn_rfkill_query(rbtn_data->rfkill, device);
+}
+
+
+/*
+ * input device
+ */
+
+static int rbtn_input_init(struct rbtn_data *rbtn_data)
+{
+	int ret;
+
+	rbtn_data->input_dev = input_allocate_device();
+	if (!rbtn_data->input_dev)
+		return -ENOMEM;
+
+	rbtn_data->input_dev->name = "DELL Wireless hotkeys";
+	rbtn_data->input_dev->phys = "dellabce/input0";
+	rbtn_data->input_dev->id.bustype = BUS_HOST;
+	rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);
+	set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);
+
+	ret = input_register_device(rbtn_data->input_dev);
+	if (ret) {
+		input_free_device(rbtn_data->input_dev);
+		rbtn_data->input_dev = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void rbtn_input_exit(struct rbtn_data *rbtn_data)
+{
+	input_unregister_device(rbtn_data->input_dev);
+	rbtn_data->input_dev = NULL;
+}
+
+static void rbtn_input_event(struct rbtn_data *rbtn_data)
+{
+	input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);
+	input_sync(rbtn_data->input_dev);
+	input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);
+	input_sync(rbtn_data->input_dev);
+}
+
+
+/*
+ * acpi driver
+ */
+
+static int rbtn_add(struct acpi_device *device);
+static int rbtn_remove(struct acpi_device *device);
+static void rbtn_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id rbtn_ids[] = {
+	{ "DELRBTN", 0 },
+	{ "DELLABCE", 0 },
+	{ "", 0 },
+};
+
+#ifdef CONFIG_PM_SLEEP
+static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context)
+{
+	struct rbtn_data *rbtn_data = context;
+
+	rbtn_data->suspended = false;
+}
+
+static int rbtn_suspend(struct device *dev)
+{
+	struct acpi_device *device = to_acpi_device(dev);
+	struct rbtn_data *rbtn_data = acpi_driver_data(device);
+
+	rbtn_data->suspended = true;
+
+	return 0;
+}
+
+static int rbtn_resume(struct device *dev)
+{
+	struct acpi_device *device = to_acpi_device(dev);
+	struct rbtn_data *rbtn_data = acpi_driver_data(device);
+	acpi_status status;
+
+	/*
+	 * Upon resume, some BIOSes send an ACPI notification thet triggers
+	 * an unwanted input event. In order to ignore it, we use a flag
+	 * that we set at suspend and clear once we have received the extra
+	 * ACPI notification. Since ACPI notifications are delivered
+	 * asynchronously to drivers, we clear the flag from the workqueue
+	 * used to deliver the notifications. This should be enough
+	 * to have the flag cleared only after we received the extra
+	 * notification, if any.
+	 */
+	status = acpi_os_execute(OSL_NOTIFY_HANDLER,
+			 rbtn_clear_suspended_flag, rbtn_data);
+	if (ACPI_FAILURE(status))
+		rbtn_clear_suspended_flag(rbtn_data);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume);
+
+static struct acpi_driver rbtn_driver = {
+	.name = "dell-rbtn",
+	.ids = rbtn_ids,
+	.drv.pm = &rbtn_pm_ops,
+	.ops = {
+		.add = rbtn_add,
+		.remove = rbtn_remove,
+		.notify = rbtn_notify,
+	},
+	.owner = THIS_MODULE,
+};
+
+
+/*
+ * notifier export functions
+ */
+
+static bool auto_remove_rfkill = true;
+
+static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);
+
+static int rbtn_inc_count(struct device *dev, void *data)
+{
+	struct acpi_device *device = to_acpi_device(dev);
+	struct rbtn_data *rbtn_data = device->driver_data;
+	int *count = data;
+
+	if (rbtn_data->type == RBTN_SLIDER)
+		(*count)++;
+
+	return 0;
+}
+
+static int rbtn_switch_dev(struct device *dev, void *data)
+{
+	struct acpi_device *device = to_acpi_device(dev);
+	struct rbtn_data *rbtn_data = device->driver_data;
+	bool enable = data;
+
+	if (rbtn_data->type != RBTN_SLIDER)
+		return 0;
+
+	if (enable)
+		rbtn_rfkill_init(device);
+	else
+		rbtn_rfkill_exit(device);
+
+	return 0;
+}
+
+int dell_rbtn_notifier_register(struct notifier_block *nb)
+{
+	bool first;
+	int count;
+	int ret;
+
+	count = 0;
+	ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,
+				     rbtn_inc_count);
+	if (ret || count == 0)
+		return -ENODEV;
+
+	first = !rbtn_chain_head.head;
+
+	ret = atomic_notifier_chain_register(&rbtn_chain_head, nb);
+	if (ret != 0)
+		return ret;
+
+	if (auto_remove_rfkill && first)
+		ret = driver_for_each_device(&rbtn_driver.drv, NULL,
+					     (void *)false, rbtn_switch_dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register);
+
+int dell_rbtn_notifier_unregister(struct notifier_block *nb)
+{
+	int ret;
+
+	ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb);
+	if (ret != 0)
+		return ret;
+
+	if (auto_remove_rfkill && !rbtn_chain_head.head)
+		ret = driver_for_each_device(&rbtn_driver.drv, NULL,
+					     (void *)true, rbtn_switch_dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);
+
+
+/*
+ * acpi driver functions
+ */
+
+static int rbtn_add(struct acpi_device *device)
+{
+	struct rbtn_data *rbtn_data;
+	enum rbtn_type type;
+	int ret = 0;
+
+	type = rbtn_check(device);
+	if (type == RBTN_UNKNOWN) {
+		dev_info(&device->dev, "Unknown device type\n");
+		return -EINVAL;
+	}
+
+	ret = rbtn_acquire(device, true);
+	if (ret < 0) {
+		dev_err(&device->dev, "Cannot enable device\n");
+		return ret;
+	}
+
+	rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
+	if (!rbtn_data)
+		return -ENOMEM;
+
+	rbtn_data->type = type;
+	device->driver_data = rbtn_data;
+
+	switch (rbtn_data->type) {
+	case RBTN_TOGGLE:
+		ret = rbtn_input_init(rbtn_data);
+		break;
+	case RBTN_SLIDER:
+		if (auto_remove_rfkill && rbtn_chain_head.head)
+			ret = 0;
+		else
+			ret = rbtn_rfkill_init(device);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+
+}
+
+static int rbtn_remove(struct acpi_device *device)
+{
+	struct rbtn_data *rbtn_data = device->driver_data;
+
+	switch (rbtn_data->type) {
+	case RBTN_TOGGLE:
+		rbtn_input_exit(rbtn_data);
+		break;
+	case RBTN_SLIDER:
+		rbtn_rfkill_exit(device);
+		break;
+	default:
+		break;
+	}
+
+	rbtn_acquire(device, false);
+	device->driver_data = NULL;
+
+	return 0;
+}
+
+static void rbtn_notify(struct acpi_device *device, u32 event)
+{
+	struct rbtn_data *rbtn_data = device->driver_data;
+
+	/*
+	 * Some BIOSes send a notification at resume.
+	 * Ignore it to prevent unwanted input events.
+	 */
+	if (rbtn_data->suspended) {
+		dev_dbg(&device->dev, "ACPI notification ignored\n");
+		return;
+	}
+
+	if (event != 0x80) {
+		dev_info(&device->dev, "Received unknown event (0x%x)\n",
+			 event);
+		return;
+	}
+
+	switch (rbtn_data->type) {
+	case RBTN_TOGGLE:
+		rbtn_input_event(rbtn_data);
+		break;
+	case RBTN_SLIDER:
+		rbtn_rfkill_event(device);
+		atomic_notifier_call_chain(&rbtn_chain_head, event, device);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/*
+ * module functions
+ */
+
+module_acpi_driver(rbtn_driver);
+
+module_param(auto_remove_rfkill, bool, 0444);
+
+MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when "
+				     "other modules start receiving events "
+				     "from this module and re-add them when "
+				     "the last module stops receiving events "
+				     "(default true)");
+MODULE_DEVICE_TABLE(acpi, rbtn_ids);
+MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h
new file mode 100644
index 0000000..c59cc6b
--- /dev/null
+++ b/drivers/platform/x86/dell-rbtn.h
@@ -0,0 +1,24 @@
+/*
+    Dell Airplane Mode Switch driver
+    Copyright (C) 2014-2015  Pali Rohár <pali.rohar@gmail.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.
+*/
+
+#ifndef _DELL_RBTN_H_
+#define _DELL_RBTN_H_
+
+struct notifier_block;
+
+int dell_rbtn_notifier_register(struct notifier_block *nb);
+int dell_rbtn_notifier_unregister(struct notifier_block *nb);
+
+#endif
diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c
new file mode 100644
index 0000000..0aec4fd
--- /dev/null
+++ b/drivers/platform/x86/dell-smo8800.c
@@ -0,0 +1,239 @@
+/*
+ *  dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver
+ *
+ *  Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com>
+ *  Copyright (C) 2014 Pali Rohár <pali.rohar@gmail.com>
+ *
+ *  This is loosely based on lis3lv02d driver.
+ *
+ *  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.
+ */
+
+#define DRIVER_NAME "smo8800"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+
+struct smo8800_device {
+	u32 irq;                     /* acpi device irq */
+	atomic_t counter;            /* count after last read */
+	struct miscdevice miscdev;   /* for /dev/freefall */
+	unsigned long misc_opened;   /* whether the device is open */
+	wait_queue_head_t misc_wait; /* Wait queue for the misc dev */
+	struct device *dev;          /* acpi device */
+};
+
+static irqreturn_t smo8800_interrupt_quick(int irq, void *data)
+{
+	struct smo8800_device *smo8800 = data;
+
+	atomic_inc(&smo8800->counter);
+	wake_up_interruptible(&smo8800->misc_wait);
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t smo8800_interrupt_thread(int irq, void *data)
+{
+	struct smo8800_device *smo8800 = data;
+
+	dev_info(smo8800->dev, "detected free fall\n");
+	return IRQ_HANDLED;
+}
+
+static acpi_status smo8800_get_resource(struct acpi_resource *resource,
+					void *context)
+{
+	struct acpi_resource_extended_irq *irq;
+
+	if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ)
+		return AE_OK;
+
+	irq = &resource->data.extended_irq;
+	if (!irq || !irq->interrupt_count)
+		return AE_OK;
+
+	*((u32 *)context) = irq->interrupts[0];
+	return AE_CTRL_TERMINATE;
+}
+
+static u32 smo8800_get_irq(struct acpi_device *device)
+{
+	u32 irq = 0;
+	acpi_status status;
+
+	status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+				     smo8800_get_resource, &irq);
+	if (ACPI_FAILURE(status)) {
+		dev_err(&device->dev, "acpi_walk_resources failed\n");
+		return 0;
+	}
+
+	return irq;
+}
+
+static ssize_t smo8800_misc_read(struct file *file, char __user *buf,
+				 size_t count, loff_t *pos)
+{
+	struct smo8800_device *smo8800 = container_of(file->private_data,
+					 struct smo8800_device, miscdev);
+
+	u32 data = 0;
+	unsigned char byte_data = 0;
+	ssize_t retval = 1;
+
+	if (count < 1)
+		return -EINVAL;
+
+	atomic_set(&smo8800->counter, 0);
+	retval = wait_event_interruptible(smo8800->misc_wait,
+				(data = atomic_xchg(&smo8800->counter, 0)));
+
+	if (retval)
+		return retval;
+
+	byte_data = 1;
+	retval = 1;
+
+	if (data < 255)
+		byte_data = data;
+	else
+		byte_data = 255;
+
+	if (put_user(byte_data, buf))
+		retval = -EFAULT;
+
+	return retval;
+}
+
+static int smo8800_misc_open(struct inode *inode, struct file *file)
+{
+	struct smo8800_device *smo8800 = container_of(file->private_data,
+					 struct smo8800_device, miscdev);
+
+	if (test_and_set_bit(0, &smo8800->misc_opened))
+		return -EBUSY; /* already open */
+
+	atomic_set(&smo8800->counter, 0);
+	return 0;
+}
+
+static int smo8800_misc_release(struct inode *inode, struct file *file)
+{
+	struct smo8800_device *smo8800 = container_of(file->private_data,
+					 struct smo8800_device, miscdev);
+
+	clear_bit(0, &smo8800->misc_opened); /* release the device */
+	return 0;
+}
+
+static const struct file_operations smo8800_misc_fops = {
+	.owner = THIS_MODULE,
+	.read = smo8800_misc_read,
+	.open = smo8800_misc_open,
+	.release = smo8800_misc_release,
+};
+
+static int smo8800_add(struct acpi_device *device)
+{
+	int err;
+	struct smo8800_device *smo8800;
+
+	smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL);
+	if (!smo8800) {
+		dev_err(&device->dev, "failed to allocate device data\n");
+		return -ENOMEM;
+	}
+
+	smo8800->dev = &device->dev;
+	smo8800->miscdev.minor = MISC_DYNAMIC_MINOR;
+	smo8800->miscdev.name = "freefall";
+	smo8800->miscdev.fops = &smo8800_misc_fops;
+
+	init_waitqueue_head(&smo8800->misc_wait);
+
+	err = misc_register(&smo8800->miscdev);
+	if (err) {
+		dev_err(&device->dev, "failed to register misc dev: %d\n", err);
+		return err;
+	}
+
+	device->driver_data = smo8800;
+
+	smo8800->irq = smo8800_get_irq(device);
+	if (!smo8800->irq) {
+		dev_err(&device->dev, "failed to obtain IRQ\n");
+		err = -EINVAL;
+		goto error;
+	}
+
+	err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick,
+				   smo8800_interrupt_thread,
+				   IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+				   DRIVER_NAME, smo8800);
+	if (err) {
+		dev_err(&device->dev,
+			"failed to request thread for IRQ %d: %d\n",
+			smo8800->irq, err);
+		goto error;
+	}
+
+	dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n",
+		 smo8800->irq);
+	return 0;
+
+error:
+	misc_deregister(&smo8800->miscdev);
+	return err;
+}
+
+static int smo8800_remove(struct acpi_device *device)
+{
+	struct smo8800_device *smo8800 = device->driver_data;
+
+	free_irq(smo8800->irq, smo8800);
+	misc_deregister(&smo8800->miscdev);
+	dev_dbg(&device->dev, "device /dev/freefall unregistered\n");
+	return 0;
+}
+
+static const struct acpi_device_id smo8800_ids[] = {
+	{ "SMO8800", 0 },
+	{ "SMO8801", 0 },
+	{ "SMO8810", 0 },
+	{ "SMO8811", 0 },
+	{ "SMO8820", 0 },
+	{ "SMO8821", 0 },
+	{ "SMO8830", 0 },
+	{ "SMO8831", 0 },
+	{ "", 0 },
+};
+
+MODULE_DEVICE_TABLE(acpi, smo8800_ids);
+
+static struct acpi_driver smo8800_driver = {
+	.name = DRIVER_NAME,
+	.class = "Latitude",
+	.ids = smo8800_ids,
+	.ops = {
+		.add = smo8800_add,
+		.remove = smo8800_remove,
+	},
+	.owner = THIS_MODULE,
+};
+
+module_acpi_driver(smo8800_driver);
+
+MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sonal Santan, Pali Rohár");
diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c
new file mode 100644
index 0000000..dbc97a3
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-aio.c
@@ -0,0 +1,214 @@
+/*
+ *  WMI hotkeys support for Dell All-In-One series
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/acpi.h>
+#include <linux/string.h>
+
+MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series");
+MODULE_LICENSE("GPL");
+
+#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4"
+#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8"
+
+struct dell_wmi_event {
+	u16	length;
+	/* 0x000: A hot key pressed or an event occurred
+	 * 0x00F: A sequence of hot keys are pressed */
+	u16	type;
+	u16	event[];
+};
+
+static const char *dell_wmi_aio_guids[] = {
+	EVENT_GUID1,
+	EVENT_GUID2,
+	NULL
+};
+
+MODULE_ALIAS("wmi:"EVENT_GUID1);
+MODULE_ALIAS("wmi:"EVENT_GUID2);
+
+static const struct key_entry dell_wmi_aio_keymap[] = {
+	{ KE_KEY, 0xc0, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0xc1, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0xe030, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0xe020, { KEY_MUTE } },
+	{ KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } },
+	{ KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
+	{ KE_END, 0 }
+};
+
+static struct input_dev *dell_wmi_aio_input_dev;
+
+/*
+ * The new WMI event data format will follow the dell_wmi_event structure
+ * So, we will check if the buffer matches the format
+ */
+static bool dell_wmi_aio_event_check(u8 *buffer, int length)
+{
+	struct dell_wmi_event *event = (struct dell_wmi_event *)buffer;
+
+	if (event == NULL || length < 6)
+		return false;
+
+	if ((event->type == 0 || event->type == 0xf) &&
+			event->length >= 2)
+		return true;
+
+	return false;
+}
+
+static void dell_wmi_aio_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	struct dell_wmi_event *event;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_info("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+	if (obj) {
+		unsigned int scancode = 0;
+
+		switch (obj->type) {
+		case ACPI_TYPE_INTEGER:
+			/* Most All-In-One correctly return integer scancode */
+			scancode = obj->integer.value;
+			sparse_keymap_report_event(dell_wmi_aio_input_dev,
+				scancode, 1, true);
+			break;
+		case ACPI_TYPE_BUFFER:
+			if (dell_wmi_aio_event_check(obj->buffer.pointer,
+						obj->buffer.length)) {
+				event = (struct dell_wmi_event *)
+					obj->buffer.pointer;
+				scancode = event->event[0];
+			} else {
+				/* Broken machines return the scancode in a
+				   buffer */
+				if (obj->buffer.pointer &&
+						obj->buffer.length > 0)
+					scancode = obj->buffer.pointer[0];
+			}
+			if (scancode)
+				sparse_keymap_report_event(
+					dell_wmi_aio_input_dev,
+					scancode, 1, true);
+			break;
+		}
+	}
+	kfree(obj);
+}
+
+static int __init dell_wmi_aio_input_setup(void)
+{
+	int err;
+
+	dell_wmi_aio_input_dev = input_allocate_device();
+
+	if (!dell_wmi_aio_input_dev)
+		return -ENOMEM;
+
+	dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys";
+	dell_wmi_aio_input_dev->phys = "wmi/input0";
+	dell_wmi_aio_input_dev->id.bustype = BUS_HOST;
+
+	err = sparse_keymap_setup(dell_wmi_aio_input_dev,
+			dell_wmi_aio_keymap, NULL);
+	if (err) {
+		pr_err("Unable to setup input device keymap\n");
+		goto err_free_dev;
+	}
+	err = input_register_device(dell_wmi_aio_input_dev);
+	if (err) {
+		pr_info("Unable to register input device\n");
+		goto err_free_keymap;
+	}
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(dell_wmi_aio_input_dev);
+err_free_dev:
+	input_free_device(dell_wmi_aio_input_dev);
+	return err;
+}
+
+static const char *dell_wmi_aio_find(void)
+{
+	int i;
+
+	for (i = 0; dell_wmi_aio_guids[i] != NULL; i++)
+		if (wmi_has_guid(dell_wmi_aio_guids[i]))
+			return dell_wmi_aio_guids[i];
+
+	return NULL;
+}
+
+static int __init dell_wmi_aio_init(void)
+{
+	int err;
+	const char *guid;
+
+	guid = dell_wmi_aio_find();
+	if (!guid) {
+		pr_warn("No known WMI GUID found\n");
+		return -ENXIO;
+	}
+
+	err = dell_wmi_aio_input_setup();
+	if (err)
+		return err;
+
+	err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL);
+	if (err) {
+		pr_err("Unable to register notify handler - %d\n", err);
+		sparse_keymap_free(dell_wmi_aio_input_dev);
+		input_unregister_device(dell_wmi_aio_input_dev);
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit dell_wmi_aio_exit(void)
+{
+	const char *guid;
+
+	guid = dell_wmi_aio_find();
+	wmi_remove_notify_handler(guid);
+	sparse_keymap_free(dell_wmi_aio_input_dev);
+	input_unregister_device(dell_wmi_aio_input_dev);
+}
+
+module_init(dell_wmi_aio_init);
+module_exit(dell_wmi_aio_exit);
diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c
new file mode 100644
index 0000000..f2d77fe
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi.c
@@ -0,0 +1,424 @@
+/*
+ * Dell WMI hotkeys
+ *
+ * Copyright (C) 2008 Red Hat <mjg@redhat.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/acpi.h>
+#include <linux/string.h>
+#include <linux/dmi.h>
+#include <acpi/video.h>
+
+MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
+MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
+
+static int acpi_video;
+
+MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
+
+/*
+ * Certain keys are flagged as KE_IGNORE. All of these are either
+ * notifications (rather than requests for change) or are also sent
+ * via the keyboard controller so should not be sent again.
+ */
+
+static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {
+	{ KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
+
+	{ KE_KEY, 0xe045, { KEY_PROG1 } },
+	{ KE_KEY, 0xe009, { KEY_EJECTCD } },
+
+	/* These also contain the brightness level at offset 6 */
+	{ KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
+
+	/* Battery health status button */
+	{ KE_KEY, 0xe007, { KEY_BATTERY } },
+
+	/* Radio devices state change */
+	{ KE_IGNORE, 0xe008, { KEY_RFKILL } },
+
+	/* The next device is at offset 6, the active devices are at
+	   offset 8 and the attached devices at offset 10 */
+	{ KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
+
+	{ KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
+
+	/* BIOS error detected */
+	{ KE_IGNORE, 0xe00d, { KEY_RESERVED } },
+
+	/* Wifi Catcher */
+	{ KE_KEY, 0xe011, {KEY_PROG2 } },
+
+	/* Ambient light sensor toggle */
+	{ KE_IGNORE, 0xe013, { KEY_RESERVED } },
+
+	{ KE_IGNORE, 0xe020, { KEY_MUTE } },
+
+	/* Shortcut and audio panel keys */
+	{ KE_IGNORE, 0xe025, { KEY_RESERVED } },
+	{ KE_IGNORE, 0xe026, { KEY_RESERVED } },
+
+	{ KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } },
+	{ KE_IGNORE, 0xe030, { KEY_VOLUMEUP } },
+	{ KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } },
+	{ KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } },
+	{ KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } },
+	{ KE_IGNORE, 0xe045, { KEY_NUMLOCK } },
+	{ KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } },
+	{ KE_IGNORE, 0xe0f7, { KEY_MUTE } },
+	{ KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } },
+	{ KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } },
+	{ KE_END, 0 }
+};
+
+static bool dell_new_hk_type;
+
+struct dell_bios_keymap_entry {
+	u16 scancode;
+	u16 keycode;
+};
+
+struct dell_bios_hotkey_table {
+	struct dmi_header header;
+	struct dell_bios_keymap_entry keymap[];
+
+};
+
+static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
+
+static const u16 bios_to_linux_keycode[256] __initconst = {
+
+	KEY_MEDIA,	KEY_NEXTSONG,	KEY_PLAYPAUSE, KEY_PREVIOUSSONG,
+	KEY_STOPCD,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,
+	KEY_WWW,	KEY_UNKNOWN,	KEY_VOLUMEDOWN, KEY_MUTE,
+	KEY_VOLUMEUP,	KEY_UNKNOWN,	KEY_BATTERY,	KEY_EJECTCD,
+	KEY_UNKNOWN,	KEY_SLEEP,	KEY_PROG1, KEY_BRIGHTNESSDOWN,
+	KEY_BRIGHTNESSUP,	KEY_UNKNOWN,	KEY_KBDILLUMTOGGLE,
+	KEY_UNKNOWN,	KEY_SWITCHVIDEOMODE,	KEY_UNKNOWN, KEY_UNKNOWN,
+	KEY_SWITCHVIDEOMODE,	KEY_UNKNOWN,	KEY_UNKNOWN, KEY_PROG2,
+	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,
+	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_UNKNOWN,	KEY_MICMUTE,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3
+};
+
+static struct input_dev *dell_wmi_input_dev;
+
+static void dell_wmi_process_key(int reported_key)
+{
+	const struct key_entry *key;
+
+	key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
+						reported_key);
+	if (!key) {
+		pr_info("Unknown key %x pressed\n", reported_key);
+		return;
+	}
+
+	pr_debug("Key %x pressed\n", reported_key);
+
+	/* Don't report brightness notifications that will also come via ACPI */
+	if ((key->keycode == KEY_BRIGHTNESSUP ||
+	     key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video)
+		return;
+
+	sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true);
+}
+
+static void dell_wmi_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+	acpi_size buffer_size;
+	u16 *buffer_entry, *buffer_end;
+	int len, i;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_warn("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+	if (!obj) {
+		pr_warn("no response\n");
+		return;
+	}
+
+	if (obj->type != ACPI_TYPE_BUFFER) {
+		pr_warn("bad response type %x\n", obj->type);
+		kfree(obj);
+		return;
+	}
+
+	pr_debug("Received WMI event (%*ph)\n",
+		obj->buffer.length, obj->buffer.pointer);
+
+	buffer_entry = (u16 *)obj->buffer.pointer;
+	buffer_size = obj->buffer.length/2;
+
+	if (!dell_new_hk_type) {
+		if (buffer_size >= 3 && buffer_entry[1] == 0x0)
+			dell_wmi_process_key(buffer_entry[2]);
+		else if (buffer_size >= 2)
+			dell_wmi_process_key(buffer_entry[1]);
+		else
+			pr_info("Received unknown WMI event\n");
+		kfree(obj);
+		return;
+	}
+
+	buffer_end = buffer_entry + buffer_size;
+
+	while (buffer_entry < buffer_end) {
+
+		len = buffer_entry[0];
+		if (len == 0)
+			break;
+
+		len++;
+
+		if (buffer_entry + len > buffer_end) {
+			pr_warn("Invalid length of WMI event\n");
+			break;
+		}
+
+		pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry);
+
+		switch (buffer_entry[1]) {
+		case 0x00:
+			for (i = 2; i < len; ++i) {
+				switch (buffer_entry[i]) {
+				case 0xe043:
+					/* NIC Link is Up */
+					pr_debug("NIC Link is Up\n");
+					break;
+				case 0xe044:
+					/* NIC Link is Down */
+					pr_debug("NIC Link is Down\n");
+					break;
+				case 0xe045:
+					/* Unknown event but defined in DSDT */
+				default:
+					/* Unknown event */
+					pr_info("Unknown WMI event type 0x00: "
+						"0x%x\n", (int)buffer_entry[i]);
+					break;
+				}
+			}
+			break;
+		case 0x10:
+			/* Keys pressed */
+			for (i = 2; i < len; ++i)
+				dell_wmi_process_key(buffer_entry[i]);
+			break;
+		case 0x11:
+			for (i = 2; i < len; ++i) {
+				switch (buffer_entry[i]) {
+				case 0xfff0:
+					/* Battery unplugged */
+					pr_debug("Battery unplugged\n");
+					break;
+				case 0xfff1:
+					/* Battery inserted */
+					pr_debug("Battery inserted\n");
+					break;
+				case 0x01e1:
+				case 0x02ea:
+				case 0x02eb:
+				case 0x02ec:
+				case 0x02f6:
+					/* Keyboard backlight level changed */
+					pr_debug("Keyboard backlight level "
+						 "changed\n");
+					break;
+				default:
+					/* Unknown event */
+					pr_info("Unknown WMI event type 0x11: "
+						"0x%x\n", (int)buffer_entry[i]);
+					break;
+				}
+			}
+			break;
+		default:
+			/* Unknown event */
+			pr_info("Unknown WMI event type 0x%x\n",
+				(int)buffer_entry[1]);
+			break;
+		}
+
+		buffer_entry += len;
+
+	}
+
+	kfree(obj);
+}
+
+static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
+{
+	int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
+				sizeof(struct dell_bios_keymap_entry);
+	struct key_entry *keymap;
+	int i;
+
+	keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
+	if (!keymap)
+		return NULL;
+
+	for (i = 0; i < hotkey_num; i++) {
+		const struct dell_bios_keymap_entry *bios_entry =
+					&dell_bios_hotkey_table->keymap[i];
+		u16 keycode = bios_entry->keycode < 256 ?
+				    bios_to_linux_keycode[bios_entry->keycode] :
+				    KEY_RESERVED;
+
+		if (keycode == KEY_KBDILLUMTOGGLE)
+			keymap[i].type = KE_IGNORE;
+		else
+			keymap[i].type = KE_KEY;
+		keymap[i].code = bios_entry->scancode;
+		keymap[i].keycode = keycode;
+	}
+
+	keymap[hotkey_num].type = KE_END;
+
+	return keymap;
+}
+
+static int __init dell_wmi_input_setup(void)
+{
+	int err;
+
+	dell_wmi_input_dev = input_allocate_device();
+	if (!dell_wmi_input_dev)
+		return -ENOMEM;
+
+	dell_wmi_input_dev->name = "Dell WMI hotkeys";
+	dell_wmi_input_dev->phys = "wmi/input0";
+	dell_wmi_input_dev->id.bustype = BUS_HOST;
+
+	if (dell_new_hk_type) {
+		const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
+		if (!keymap) {
+			err = -ENOMEM;
+			goto err_free_dev;
+		}
+
+		err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
+
+		/*
+		 * Sparse keymap library makes a copy of keymap so we
+		 * don't need the original one that was allocated.
+		 */
+		kfree(keymap);
+	} else {
+		err = sparse_keymap_setup(dell_wmi_input_dev,
+					  dell_wmi_legacy_keymap, NULL);
+	}
+	if (err)
+		goto err_free_dev;
+
+	err = input_register_device(dell_wmi_input_dev);
+	if (err)
+		goto err_free_keymap;
+
+	return 0;
+
+ err_free_keymap:
+	sparse_keymap_free(dell_wmi_input_dev);
+ err_free_dev:
+	input_free_device(dell_wmi_input_dev);
+	return err;
+}
+
+static void dell_wmi_input_destroy(void)
+{
+	sparse_keymap_free(dell_wmi_input_dev);
+	input_unregister_device(dell_wmi_input_dev);
+}
+
+static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
+{
+	if (dm->type == 0xb2 && dm->length > 6) {
+		dell_new_hk_type = true;
+		dell_bios_hotkey_table =
+			container_of(dm, struct dell_bios_hotkey_table, header);
+	}
+}
+
+static int __init dell_wmi_init(void)
+{
+	int err;
+	acpi_status status;
+
+	if (!wmi_has_guid(DELL_EVENT_GUID)) {
+		pr_warn("No known WMI GUID found\n");
+		return -ENODEV;
+	}
+
+	dmi_walk(find_hk_type, NULL);
+	acpi_video = acpi_video_get_backlight_type() != acpi_backlight_vendor;
+
+	err = dell_wmi_input_setup();
+	if (err)
+		return err;
+
+	status = wmi_install_notify_handler(DELL_EVENT_GUID,
+					 dell_wmi_notify, NULL);
+	if (ACPI_FAILURE(status)) {
+		dell_wmi_input_destroy();
+		pr_err("Unable to register notify handler - %d\n", status);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+module_init(dell_wmi_init);
+
+static void __exit dell_wmi_exit(void)
+{
+	wmi_remove_notify_handler(DELL_EVENT_GUID);
+	dell_wmi_input_destroy();
+}
+module_exit(dell_wmi_exit);
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
new file mode 100644
index 0000000..8cdf315
--- /dev/null
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -0,0 +1,1545 @@
+/*
+ *  eeepc-laptop.c - Asus Eee PC extras
+ *
+ *  Based on asus_acpi.c as patched for the Eee PC by Asus:
+ *  ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar
+ *  Based on eee.c from eeepc-linux
+ *
+ *  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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/rfkill.h>
+#include <linux/pci.h>
+#include <linux/pci_hotplug.h>
+#include <linux/leds.h>
+#include <linux/dmi.h>
+#include <acpi/video.h>
+
+#define EEEPC_LAPTOP_VERSION	"0.1"
+#define EEEPC_LAPTOP_NAME	"Eee PC Hotkey Driver"
+#define EEEPC_LAPTOP_FILE	"eeepc"
+
+#define EEEPC_ACPI_CLASS	"hotkey"
+#define EEEPC_ACPI_DEVICE_NAME	"Hotkey"
+#define EEEPC_ACPI_HID		"ASUS010"
+
+MODULE_AUTHOR("Corentin Chary, Eric Cooper");
+MODULE_DESCRIPTION(EEEPC_LAPTOP_NAME);
+MODULE_LICENSE("GPL");
+
+static bool hotplug_disabled;
+
+module_param(hotplug_disabled, bool, 0444);
+MODULE_PARM_DESC(hotplug_disabled,
+		 "Disable hotplug for wireless device. "
+		 "If your laptop need that, please report to "
+		 "acpi4asus-user@lists.sourceforge.net.");
+
+/*
+ * Definitions for Asus EeePC
+ */
+#define NOTIFY_BRN_MIN	0x20
+#define NOTIFY_BRN_MAX	0x2f
+
+enum {
+	DISABLE_ASL_WLAN = 0x0001,
+	DISABLE_ASL_BLUETOOTH = 0x0002,
+	DISABLE_ASL_IRDA = 0x0004,
+	DISABLE_ASL_CAMERA = 0x0008,
+	DISABLE_ASL_TV = 0x0010,
+	DISABLE_ASL_GPS = 0x0020,
+	DISABLE_ASL_DISPLAYSWITCH = 0x0040,
+	DISABLE_ASL_MODEM = 0x0080,
+	DISABLE_ASL_CARDREADER = 0x0100,
+	DISABLE_ASL_3G = 0x0200,
+	DISABLE_ASL_WIMAX = 0x0400,
+	DISABLE_ASL_HWCF = 0x0800
+};
+
+enum {
+	CM_ASL_WLAN = 0,
+	CM_ASL_BLUETOOTH,
+	CM_ASL_IRDA,
+	CM_ASL_1394,
+	CM_ASL_CAMERA,
+	CM_ASL_TV,
+	CM_ASL_GPS,
+	CM_ASL_DVDROM,
+	CM_ASL_DISPLAYSWITCH,
+	CM_ASL_PANELBRIGHT,
+	CM_ASL_BIOSFLASH,
+	CM_ASL_ACPIFLASH,
+	CM_ASL_CPUFV,
+	CM_ASL_CPUTEMPERATURE,
+	CM_ASL_FANCPU,
+	CM_ASL_FANCHASSIS,
+	CM_ASL_USBPORT1,
+	CM_ASL_USBPORT2,
+	CM_ASL_USBPORT3,
+	CM_ASL_MODEM,
+	CM_ASL_CARDREADER,
+	CM_ASL_3G,
+	CM_ASL_WIMAX,
+	CM_ASL_HWCF,
+	CM_ASL_LID,
+	CM_ASL_TYPE,
+	CM_ASL_PANELPOWER,	/*P901*/
+	CM_ASL_TPD
+};
+
+static const char *cm_getv[] = {
+	"WLDG", "BTHG", NULL, NULL,
+	"CAMG", NULL, NULL, NULL,
+	NULL, "PBLG", NULL, NULL,
+	"CFVG", NULL, NULL, NULL,
+	"USBG", NULL, NULL, "MODG",
+	"CRDG", "M3GG", "WIMG", "HWCF",
+	"LIDG",	"TYPE", "PBPG",	"TPDG"
+};
+
+static const char *cm_setv[] = {
+	"WLDS", "BTHS", NULL, NULL,
+	"CAMS", NULL, NULL, NULL,
+	"SDSP", "PBLS", "HDPS", NULL,
+	"CFVS", NULL, NULL, NULL,
+	"USBG", NULL, NULL, "MODS",
+	"CRDS", "M3GS", "WIMS", NULL,
+	NULL, NULL, "PBPS", "TPDS"
+};
+
+static const struct key_entry eeepc_keymap[] = {
+	{ KE_KEY, 0x10, { KEY_WLAN } },
+	{ KE_KEY, 0x11, { KEY_WLAN } },
+	{ KE_KEY, 0x12, { KEY_PROG1 } },
+	{ KE_KEY, 0x13, { KEY_MUTE } },
+	{ KE_KEY, 0x14, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0x15, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0x16, { KEY_DISPLAY_OFF } },
+	{ KE_KEY, 0x1a, { KEY_COFFEE } },
+	{ KE_KEY, 0x1b, { KEY_ZOOM } },
+	{ KE_KEY, 0x1c, { KEY_PROG2 } },
+	{ KE_KEY, 0x1d, { KEY_PROG3 } },
+	{ KE_KEY, NOTIFY_BRN_MIN, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, NOTIFY_BRN_MAX, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0x30, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x31, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x32, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x37, { KEY_F13 } }, /* Disable Touchpad */
+	{ KE_KEY, 0x38, { KEY_F14 } },
+	{ KE_END, 0 },
+};
+
+/*
+ * This is the main structure, we can use it to store useful information
+ */
+struct eeepc_laptop {
+	acpi_handle handle;		/* the handle of the acpi device */
+	u32 cm_supported;		/* the control methods supported
+					   by this BIOS */
+	bool cpufv_disabled;
+	bool hotplug_disabled;
+	u16 event_count[128];		/* count for each event */
+
+	struct platform_device *platform_device;
+	struct acpi_device *device;		/* the device we are in */
+	struct backlight_device *backlight_device;
+
+	struct input_dev *inputdev;
+
+	struct rfkill *wlan_rfkill;
+	struct rfkill *bluetooth_rfkill;
+	struct rfkill *wwan3g_rfkill;
+	struct rfkill *wimax_rfkill;
+
+	struct hotplug_slot *hotplug_slot;
+	struct mutex hotplug_lock;
+
+	struct led_classdev tpd_led;
+	int tpd_led_wk;
+	struct workqueue_struct *led_workqueue;
+	struct work_struct tpd_led_work;
+};
+
+/*
+ * ACPI Helpers
+ */
+static int write_acpi_int(acpi_handle handle, const char *method, int val)
+{
+	acpi_status status;
+
+	status = acpi_execute_simple_method(handle, (char *)method, val);
+
+	return (status == AE_OK ? 0 : -1);
+}
+
+static int read_acpi_int(acpi_handle handle, const char *method, int *val)
+{
+	acpi_status status;
+	unsigned long long result;
+
+	status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
+	if (ACPI_FAILURE(status)) {
+		*val = -1;
+		return -1;
+	} else {
+		*val = result;
+		return 0;
+	}
+}
+
+static int set_acpi(struct eeepc_laptop *eeepc, int cm, int value)
+{
+	const char *method = cm_setv[cm];
+
+	if (method == NULL)
+		return -ENODEV;
+	if ((eeepc->cm_supported & (0x1 << cm)) == 0)
+		return -ENODEV;
+
+	if (write_acpi_int(eeepc->handle, method, value))
+		pr_warn("Error writing %s\n", method);
+	return 0;
+}
+
+static int get_acpi(struct eeepc_laptop *eeepc, int cm)
+{
+	const char *method = cm_getv[cm];
+	int value;
+
+	if (method == NULL)
+		return -ENODEV;
+	if ((eeepc->cm_supported & (0x1 << cm)) == 0)
+		return -ENODEV;
+
+	if (read_acpi_int(eeepc->handle, method, &value))
+		pr_warn("Error reading %s\n", method);
+	return value;
+}
+
+static int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm,
+			      acpi_handle *handle)
+{
+	const char *method = cm_setv[cm];
+	acpi_status status;
+
+	if (method == NULL)
+		return -ENODEV;
+	if ((eeepc->cm_supported & (0x1 << cm)) == 0)
+		return -ENODEV;
+
+	status = acpi_get_handle(eeepc->handle, (char *)method,
+				 handle);
+	if (status != AE_OK) {
+		pr_warn("Error finding %s\n", method);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+
+/*
+ * Sys helpers
+ */
+static int parse_arg(const char *buf, int *val)
+{
+	if (sscanf(buf, "%i", val) != 1)
+		return -EINVAL;
+	return 0;
+}
+
+static ssize_t store_sys_acpi(struct device *dev, int cm,
+			      const char *buf, size_t count)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+	int rv, value;
+
+	rv = parse_arg(buf, &value);
+	if (rv < 0)
+		return rv;
+	rv = set_acpi(eeepc, cm, value);
+	if (rv < 0)
+		return -EIO;
+	return count;
+}
+
+static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+	int value = get_acpi(eeepc, cm);
+
+	if (value < 0)
+		return -EIO;
+	return sprintf(buf, "%d\n", value);
+}
+
+#define EEEPC_ACPI_SHOW_FUNC(_name, _cm)				\
+	static ssize_t _name##_show(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		return show_sys_acpi(dev, _cm, buf);			\
+	}
+
+#define EEEPC_ACPI_STORE_FUNC(_name, _cm)				\
+	static ssize_t _name##_store(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		return store_sys_acpi(dev, _cm, buf, count);		\
+	}
+
+#define EEEPC_CREATE_DEVICE_ATTR_RW(_name, _cm)				\
+	EEEPC_ACPI_SHOW_FUNC(_name, _cm)				\
+	EEEPC_ACPI_STORE_FUNC(_name, _cm)				\
+	static DEVICE_ATTR_RW(_name)
+
+#define EEEPC_CREATE_DEVICE_ATTR_WO(_name, _cm)				\
+	EEEPC_ACPI_STORE_FUNC(_name, _cm)				\
+	static DEVICE_ATTR_WO(_name)
+
+EEEPC_CREATE_DEVICE_ATTR_RW(camera, CM_ASL_CAMERA);
+EEEPC_CREATE_DEVICE_ATTR_RW(cardr, CM_ASL_CARDREADER);
+EEEPC_CREATE_DEVICE_ATTR_WO(disp, CM_ASL_DISPLAYSWITCH);
+
+struct eeepc_cpufv {
+	int num;
+	int cur;
+};
+
+static int get_cpufv(struct eeepc_laptop *eeepc, struct eeepc_cpufv *c)
+{
+	c->cur = get_acpi(eeepc, CM_ASL_CPUFV);
+	if (c->cur < 0)
+		return -ENODEV;
+
+	c->num = (c->cur >> 8) & 0xff;
+	c->cur &= 0xff;
+	if (c->num == 0 || c->num > 12)
+		return -ENODEV;
+	return 0;
+}
+
+static ssize_t available_cpufv_show(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+	struct eeepc_cpufv c;
+	int i;
+	ssize_t len = 0;
+
+	if (get_cpufv(eeepc, &c))
+		return -ENODEV;
+	for (i = 0; i < c.num; i++)
+		len += sprintf(buf + len, "%d ", i);
+	len += sprintf(buf + len, "\n");
+	return len;
+}
+
+static ssize_t cpufv_show(struct device *dev,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+	struct eeepc_cpufv c;
+
+	if (get_cpufv(eeepc, &c))
+		return -ENODEV;
+	return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
+}
+
+static ssize_t cpufv_store(struct device *dev,
+			   struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+	struct eeepc_cpufv c;
+	int rv, value;
+
+	if (eeepc->cpufv_disabled)
+		return -EPERM;
+	if (get_cpufv(eeepc, &c))
+		return -ENODEV;
+	rv = parse_arg(buf, &value);
+	if (rv < 0)
+		return rv;
+	if (value < 0 || value >= c.num)
+		return -EINVAL;
+	rv = set_acpi(eeepc, CM_ASL_CPUFV, value);
+	if (rv)
+		return rv;
+	return count;
+}
+
+static ssize_t cpufv_disabled_show(struct device *dev,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", eeepc->cpufv_disabled);
+}
+
+static ssize_t cpufv_disabled_store(struct device *dev,
+			   struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+	int rv, value;
+
+	rv = parse_arg(buf, &value);
+	if (rv < 0)
+		return rv;
+
+	switch (value) {
+	case 0:
+		if (eeepc->cpufv_disabled)
+			pr_warn("cpufv enabled (not officially supported on this model)\n");
+		eeepc->cpufv_disabled = false;
+		return count;
+	case 1:
+		return -EPERM;
+	default:
+		return -EINVAL;
+	}
+}
+
+
+static DEVICE_ATTR_RW(cpufv);
+static DEVICE_ATTR_RO(available_cpufv);
+static DEVICE_ATTR_RW(cpufv_disabled);
+
+static struct attribute *platform_attributes[] = {
+	&dev_attr_camera.attr,
+	&dev_attr_cardr.attr,
+	&dev_attr_disp.attr,
+	&dev_attr_cpufv.attr,
+	&dev_attr_available_cpufv.attr,
+	&dev_attr_cpufv_disabled.attr,
+	NULL
+};
+
+static struct attribute_group platform_attribute_group = {
+	.attrs = platform_attributes
+};
+
+static int eeepc_platform_init(struct eeepc_laptop *eeepc)
+{
+	int result;
+
+	eeepc->platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, -1);
+	if (!eeepc->platform_device)
+		return -ENOMEM;
+	platform_set_drvdata(eeepc->platform_device, eeepc);
+
+	result = platform_device_add(eeepc->platform_device);
+	if (result)
+		goto fail_platform_device;
+
+	result = sysfs_create_group(&eeepc->platform_device->dev.kobj,
+				    &platform_attribute_group);
+	if (result)
+		goto fail_sysfs;
+	return 0;
+
+fail_sysfs:
+	platform_device_del(eeepc->platform_device);
+fail_platform_device:
+	platform_device_put(eeepc->platform_device);
+	return result;
+}
+
+static void eeepc_platform_exit(struct eeepc_laptop *eeepc)
+{
+	sysfs_remove_group(&eeepc->platform_device->dev.kobj,
+			   &platform_attribute_group);
+	platform_device_unregister(eeepc->platform_device);
+}
+
+/*
+ * LEDs
+ */
+/*
+ * These functions actually update the LED's, and are called from a
+ * workqueue. By doing this as separate work rather than when the LED
+ * subsystem asks, we avoid messing with the Asus ACPI stuff during a
+ * potentially bad time, such as a timer interrupt.
+ */
+static void tpd_led_update(struct work_struct *work)
+ {
+	struct eeepc_laptop *eeepc;
+
+	eeepc = container_of(work, struct eeepc_laptop, tpd_led_work);
+
+	set_acpi(eeepc, CM_ASL_TPD, eeepc->tpd_led_wk);
+}
+
+static void tpd_led_set(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct eeepc_laptop *eeepc;
+
+	eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
+
+	eeepc->tpd_led_wk = (value > 0) ? 1 : 0;
+	queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
+}
+
+static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
+{
+	struct eeepc_laptop *eeepc;
+
+	eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
+
+	return get_acpi(eeepc, CM_ASL_TPD);
+}
+
+static int eeepc_led_init(struct eeepc_laptop *eeepc)
+{
+	int rv;
+
+	if (get_acpi(eeepc, CM_ASL_TPD) == -ENODEV)
+		return 0;
+
+	eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
+	if (!eeepc->led_workqueue)
+		return -ENOMEM;
+	INIT_WORK(&eeepc->tpd_led_work, tpd_led_update);
+
+	eeepc->tpd_led.name = "eeepc::touchpad";
+	eeepc->tpd_led.brightness_set = tpd_led_set;
+	if (get_acpi(eeepc, CM_ASL_TPD) >= 0) /* if method is available */
+		eeepc->tpd_led.brightness_get = tpd_led_get;
+	eeepc->tpd_led.max_brightness = 1;
+
+	rv = led_classdev_register(&eeepc->platform_device->dev,
+				   &eeepc->tpd_led);
+	if (rv) {
+		destroy_workqueue(eeepc->led_workqueue);
+		return rv;
+	}
+
+	return 0;
+}
+
+static void eeepc_led_exit(struct eeepc_laptop *eeepc)
+{
+	if (!IS_ERR_OR_NULL(eeepc->tpd_led.dev))
+		led_classdev_unregister(&eeepc->tpd_led);
+	if (eeepc->led_workqueue)
+		destroy_workqueue(eeepc->led_workqueue);
+}
+
+
+/*
+ * PCI hotplug (for wlan rfkill)
+ */
+static bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc)
+{
+	if (get_acpi(eeepc, CM_ASL_WLAN) == 1)
+		return false;
+	return true;
+}
+
+static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle)
+{
+	struct pci_dev *port;
+	struct pci_dev *dev;
+	struct pci_bus *bus;
+	bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
+	bool absent;
+	u32 l;
+
+	if (eeepc->wlan_rfkill)
+		rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
+
+	mutex_lock(&eeepc->hotplug_lock);
+	pci_lock_rescan_remove();
+
+	if (!eeepc->hotplug_slot)
+		goto out_unlock;
+
+	port = acpi_get_pci_dev(handle);
+	if (!port) {
+		pr_warning("Unable to find port\n");
+		goto out_unlock;
+	}
+
+	bus = port->subordinate;
+
+	if (!bus) {
+		pr_warn("Unable to find PCI bus 1?\n");
+		goto out_put_dev;
+	}
+
+	if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
+		pr_err("Unable to read PCI config space?\n");
+		goto out_put_dev;
+	}
+
+	absent = (l == 0xffffffff);
+
+	if (blocked != absent) {
+		pr_warn("BIOS says wireless lan is %s, but the pci device is %s\n",
+			blocked ? "blocked" : "unblocked",
+			absent ? "absent" : "present");
+		pr_warn("skipped wireless hotplug as probably inappropriate for this model\n");
+		goto out_put_dev;
+	}
+
+	if (!blocked) {
+		dev = pci_get_slot(bus, 0);
+		if (dev) {
+			/* Device already present */
+			pci_dev_put(dev);
+			goto out_put_dev;
+		}
+		dev = pci_scan_single_device(bus, 0);
+		if (dev) {
+			pci_bus_assign_resources(bus);
+			pci_bus_add_device(dev);
+		}
+	} else {
+		dev = pci_get_slot(bus, 0);
+		if (dev) {
+			pci_stop_and_remove_bus_device(dev);
+			pci_dev_put(dev);
+		}
+	}
+out_put_dev:
+	pci_dev_put(port);
+
+out_unlock:
+	pci_unlock_rescan_remove();
+	mutex_unlock(&eeepc->hotplug_lock);
+}
+
+static void eeepc_rfkill_hotplug_update(struct eeepc_laptop *eeepc, char *node)
+{
+	acpi_status status = AE_OK;
+	acpi_handle handle;
+
+	status = acpi_get_handle(NULL, node, &handle);
+
+	if (ACPI_SUCCESS(status))
+		eeepc_rfkill_hotplug(eeepc, handle);
+}
+
+static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct eeepc_laptop *eeepc = data;
+
+	if (event != ACPI_NOTIFY_BUS_CHECK)
+		return;
+
+	eeepc_rfkill_hotplug(eeepc, handle);
+}
+
+static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc,
+					  char *node)
+{
+	acpi_status status;
+	acpi_handle handle;
+
+	status = acpi_get_handle(NULL, node, &handle);
+
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	status = acpi_install_notify_handler(handle,
+					     ACPI_SYSTEM_NOTIFY,
+					     eeepc_rfkill_notify,
+					     eeepc);
+	if (ACPI_FAILURE(status))
+		pr_warn("Failed to register notify on %s\n", node);
+
+	/*
+	 * Refresh pci hotplug in case the rfkill state was
+	 * changed during setup.
+	 */
+	eeepc_rfkill_hotplug(eeepc, handle);
+	return 0;
+}
+
+static void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc,
+					     char *node)
+{
+	acpi_status status = AE_OK;
+	acpi_handle handle;
+
+	status = acpi_get_handle(NULL, node, &handle);
+
+	if (ACPI_FAILURE(status))
+		return;
+
+	status = acpi_remove_notify_handler(handle,
+					     ACPI_SYSTEM_NOTIFY,
+					     eeepc_rfkill_notify);
+	if (ACPI_FAILURE(status))
+		pr_err("Error removing rfkill notify handler %s\n",
+			node);
+		/*
+		 * Refresh pci hotplug in case the rfkill
+		 * state was changed after
+		 * eeepc_unregister_rfkill_notifier()
+		 */
+	eeepc_rfkill_hotplug(eeepc, handle);
+}
+
+static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
+				    u8 *value)
+{
+	struct eeepc_laptop *eeepc = hotplug_slot->private;
+	int val = get_acpi(eeepc, CM_ASL_WLAN);
+
+	if (val == 1 || val == 0)
+		*value = val;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
+{
+	kfree(hotplug_slot->info);
+	kfree(hotplug_slot);
+}
+
+static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
+	.owner = THIS_MODULE,
+	.get_adapter_status = eeepc_get_adapter_status,
+	.get_power_status = eeepc_get_adapter_status,
+};
+
+static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc)
+{
+	int ret = -ENOMEM;
+	struct pci_bus *bus = pci_find_bus(0, 1);
+
+	if (!bus) {
+		pr_err("Unable to find wifi PCI bus\n");
+		return -ENODEV;
+	}
+
+	eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
+	if (!eeepc->hotplug_slot)
+		goto error_slot;
+
+	eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
+					    GFP_KERNEL);
+	if (!eeepc->hotplug_slot->info)
+		goto error_info;
+
+	eeepc->hotplug_slot->private = eeepc;
+	eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
+	eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
+	eeepc_get_adapter_status(eeepc->hotplug_slot,
+				 &eeepc->hotplug_slot->info->adapter_status);
+
+	ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
+	if (ret) {
+		pr_err("Unable to register hotplug slot - %d\n", ret);
+		goto error_register;
+	}
+
+	return 0;
+
+error_register:
+	kfree(eeepc->hotplug_slot->info);
+error_info:
+	kfree(eeepc->hotplug_slot);
+	eeepc->hotplug_slot = NULL;
+error_slot:
+	return ret;
+}
+
+/*
+ * Rfkill devices
+ */
+static int eeepc_rfkill_set(void *data, bool blocked)
+{
+	acpi_handle handle = data;
+
+	return write_acpi_int(handle, NULL, !blocked);
+}
+
+static const struct rfkill_ops eeepc_rfkill_ops = {
+	.set_block = eeepc_rfkill_set,
+};
+
+static int eeepc_new_rfkill(struct eeepc_laptop *eeepc,
+			    struct rfkill **rfkill,
+			    const char *name,
+			    enum rfkill_type type, int cm)
+{
+	acpi_handle handle;
+	int result;
+
+	result = acpi_setter_handle(eeepc, cm, &handle);
+	if (result < 0)
+		return result;
+
+	*rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
+			       &eeepc_rfkill_ops, handle);
+
+	if (!*rfkill)
+		return -EINVAL;
+
+	rfkill_init_sw_state(*rfkill, get_acpi(eeepc, cm) != 1);
+	result = rfkill_register(*rfkill);
+	if (result) {
+		rfkill_destroy(*rfkill);
+		*rfkill = NULL;
+		return result;
+	}
+	return 0;
+}
+
+static char EEEPC_RFKILL_NODE_1[] = "\\_SB.PCI0.P0P5";
+static char EEEPC_RFKILL_NODE_2[] = "\\_SB.PCI0.P0P6";
+static char EEEPC_RFKILL_NODE_3[] = "\\_SB.PCI0.P0P7";
+
+static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc)
+{
+	eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_1);
+	eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_2);
+	eeepc_unregister_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_3);
+	if (eeepc->wlan_rfkill) {
+		rfkill_unregister(eeepc->wlan_rfkill);
+		rfkill_destroy(eeepc->wlan_rfkill);
+		eeepc->wlan_rfkill = NULL;
+	}
+
+	if (eeepc->hotplug_slot)
+		pci_hp_deregister(eeepc->hotplug_slot);
+
+	if (eeepc->bluetooth_rfkill) {
+		rfkill_unregister(eeepc->bluetooth_rfkill);
+		rfkill_destroy(eeepc->bluetooth_rfkill);
+		eeepc->bluetooth_rfkill = NULL;
+	}
+	if (eeepc->wwan3g_rfkill) {
+		rfkill_unregister(eeepc->wwan3g_rfkill);
+		rfkill_destroy(eeepc->wwan3g_rfkill);
+		eeepc->wwan3g_rfkill = NULL;
+	}
+	if (eeepc->wimax_rfkill) {
+		rfkill_unregister(eeepc->wimax_rfkill);
+		rfkill_destroy(eeepc->wimax_rfkill);
+		eeepc->wimax_rfkill = NULL;
+	}
+}
+
+static int eeepc_rfkill_init(struct eeepc_laptop *eeepc)
+{
+	int result = 0;
+
+	mutex_init(&eeepc->hotplug_lock);
+
+	result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
+				  "eeepc-wlan", RFKILL_TYPE_WLAN,
+				  CM_ASL_WLAN);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill,
+				  "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH,
+				  CM_ASL_BLUETOOTH);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
+				  "eeepc-wwan3g", RFKILL_TYPE_WWAN,
+				  CM_ASL_3G);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill,
+				  "eeepc-wimax", RFKILL_TYPE_WIMAX,
+				  CM_ASL_WIMAX);
+
+	if (result && result != -ENODEV)
+		goto exit;
+
+	if (eeepc->hotplug_disabled)
+		return 0;
+
+	result = eeepc_setup_pci_hotplug(eeepc);
+	/*
+	 * If we get -EBUSY then something else is handling the PCI hotplug -
+	 * don't fail in this case
+	 */
+	if (result == -EBUSY)
+		result = 0;
+
+	eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_1);
+	eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_2);
+	eeepc_register_rfkill_notifier(eeepc, EEEPC_RFKILL_NODE_3);
+
+exit:
+	if (result && result != -ENODEV)
+		eeepc_rfkill_exit(eeepc);
+	return result;
+}
+
+/*
+ * Platform driver - hibernate/resume callbacks
+ */
+static int eeepc_hotk_thaw(struct device *device)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(device);
+
+	if (eeepc->wlan_rfkill) {
+		int wlan;
+
+		/*
+		 * Work around bios bug - acpi _PTS turns off the wireless led
+		 * during suspend.  Normally it restores it on resume, but
+		 * we should kick it ourselves in case hibernation is aborted.
+		 */
+		wlan = get_acpi(eeepc, CM_ASL_WLAN);
+		if (wlan >= 0)
+			set_acpi(eeepc, CM_ASL_WLAN, wlan);
+	}
+
+	return 0;
+}
+
+static int eeepc_hotk_restore(struct device *device)
+{
+	struct eeepc_laptop *eeepc = dev_get_drvdata(device);
+
+	/* Refresh both wlan rfkill state and pci hotplug */
+	if (eeepc->wlan_rfkill) {
+		eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_1);
+		eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_2);
+		eeepc_rfkill_hotplug_update(eeepc, EEEPC_RFKILL_NODE_3);
+	}
+
+	if (eeepc->bluetooth_rfkill)
+		rfkill_set_sw_state(eeepc->bluetooth_rfkill,
+				    get_acpi(eeepc, CM_ASL_BLUETOOTH) != 1);
+	if (eeepc->wwan3g_rfkill)
+		rfkill_set_sw_state(eeepc->wwan3g_rfkill,
+				    get_acpi(eeepc, CM_ASL_3G) != 1);
+	if (eeepc->wimax_rfkill)
+		rfkill_set_sw_state(eeepc->wimax_rfkill,
+				    get_acpi(eeepc, CM_ASL_WIMAX) != 1);
+
+	return 0;
+}
+
+static const struct dev_pm_ops eeepc_pm_ops = {
+	.thaw = eeepc_hotk_thaw,
+	.restore = eeepc_hotk_restore,
+};
+
+static struct platform_driver platform_driver = {
+	.driver = {
+		.name = EEEPC_LAPTOP_FILE,
+		.pm = &eeepc_pm_ops,
+	}
+};
+
+/*
+ * Hwmon device
+ */
+
+#define EEEPC_EC_SC00      0x61
+#define EEEPC_EC_FAN_PWM   (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */
+#define EEEPC_EC_FAN_HRPM  (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */
+#define EEEPC_EC_FAN_LRPM  (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */
+
+#define EEEPC_EC_SFB0      0xD0
+#define EEEPC_EC_FAN_CTRL  (EEEPC_EC_SFB0 + 3) /* Byte containing SF25  */
+
+static inline int eeepc_pwm_to_lmsensors(int value)
+{
+	return value * 255 / 100;
+}
+
+static inline int eeepc_lmsensors_to_pwm(int value)
+{
+	value = clamp_val(value, 0, 255);
+	return value * 100 / 255;
+}
+
+static int eeepc_get_fan_pwm(void)
+{
+	u8 value = 0;
+
+	ec_read(EEEPC_EC_FAN_PWM, &value);
+	return eeepc_pwm_to_lmsensors(value);
+}
+
+static void eeepc_set_fan_pwm(int value)
+{
+	value = eeepc_lmsensors_to_pwm(value);
+	ec_write(EEEPC_EC_FAN_PWM, value);
+}
+
+static int eeepc_get_fan_rpm(void)
+{
+	u8 high = 0;
+	u8 low = 0;
+
+	ec_read(EEEPC_EC_FAN_HRPM, &high);
+	ec_read(EEEPC_EC_FAN_LRPM, &low);
+	return high << 8 | low;
+}
+
+#define EEEPC_EC_FAN_CTRL_BIT	0x02
+#define EEEPC_FAN_CTRL_MANUAL	1
+#define EEEPC_FAN_CTRL_AUTO	2
+
+static int eeepc_get_fan_ctrl(void)
+{
+	u8 value = 0;
+
+	ec_read(EEEPC_EC_FAN_CTRL, &value);
+	if (value & EEEPC_EC_FAN_CTRL_BIT)
+		return EEEPC_FAN_CTRL_MANUAL;
+	else
+		return EEEPC_FAN_CTRL_AUTO;
+}
+
+static void eeepc_set_fan_ctrl(int manual)
+{
+	u8 value = 0;
+
+	ec_read(EEEPC_EC_FAN_CTRL, &value);
+	if (manual == EEEPC_FAN_CTRL_MANUAL)
+		value |= EEEPC_EC_FAN_CTRL_BIT;
+	else
+		value &= ~EEEPC_EC_FAN_CTRL_BIT;
+	ec_write(EEEPC_EC_FAN_CTRL, value);
+}
+
+static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
+{
+	int rv, value;
+
+	rv = parse_arg(buf, &value);
+	if (rv < 0)
+		return rv;
+	set(value);
+	return count;
+}
+
+static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
+{
+	return sprintf(buf, "%d\n", get());
+}
+
+#define EEEPC_SENSOR_SHOW_FUNC(_name, _get)				\
+	static ssize_t _name##_show(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		return show_sys_hwmon(_get, buf);			\
+	}
+
+#define EEEPC_SENSOR_STORE_FUNC(_name, _set)				\
+	static ssize_t _name##_store(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		return store_sys_hwmon(_set, buf, count);		\
+	}
+
+#define EEEPC_CREATE_SENSOR_ATTR_RW(_name, _get, _set)			\
+	EEEPC_SENSOR_SHOW_FUNC(_name, _get)				\
+	EEEPC_SENSOR_STORE_FUNC(_name, _set)				\
+	static DEVICE_ATTR_RW(_name)
+
+#define EEEPC_CREATE_SENSOR_ATTR_RO(_name, _get)			\
+	EEEPC_SENSOR_SHOW_FUNC(_name, _get)				\
+	static DEVICE_ATTR_RO(_name)
+
+EEEPC_CREATE_SENSOR_ATTR_RO(fan1_input, eeepc_get_fan_rpm);
+EEEPC_CREATE_SENSOR_ATTR_RW(pwm1, eeepc_get_fan_pwm,
+			    eeepc_set_fan_pwm);
+EEEPC_CREATE_SENSOR_ATTR_RW(pwm1_enable, eeepc_get_fan_ctrl,
+			    eeepc_set_fan_ctrl);
+
+static struct attribute *hwmon_attrs[] = {
+	&dev_attr_pwm1.attr,
+	&dev_attr_fan1_input.attr,
+	&dev_attr_pwm1_enable.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(hwmon);
+
+static int eeepc_hwmon_init(struct eeepc_laptop *eeepc)
+{
+	struct device *dev = &eeepc->platform_device->dev;
+	struct device *hwmon;
+
+	hwmon = devm_hwmon_device_register_with_groups(dev, "eeepc", NULL,
+						       hwmon_groups);
+	if (IS_ERR(hwmon)) {
+		pr_err("Could not register eeepc hwmon device\n");
+		return PTR_ERR(hwmon);
+	}
+	return 0;
+}
+
+/*
+ * Backlight device
+ */
+static int read_brightness(struct backlight_device *bd)
+{
+	struct eeepc_laptop *eeepc = bl_get_data(bd);
+
+	return get_acpi(eeepc, CM_ASL_PANELBRIGHT);
+}
+
+static int set_brightness(struct backlight_device *bd, int value)
+{
+	struct eeepc_laptop *eeepc = bl_get_data(bd);
+
+	return set_acpi(eeepc, CM_ASL_PANELBRIGHT, value);
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+	return set_brightness(bd, bd->props.brightness);
+}
+
+static const struct backlight_ops eeepcbl_ops = {
+	.get_brightness = read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int eeepc_backlight_notify(struct eeepc_laptop *eeepc)
+{
+	struct backlight_device *bd = eeepc->backlight_device;
+	int old = bd->props.brightness;
+
+	backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
+
+	return old;
+}
+
+static int eeepc_backlight_init(struct eeepc_laptop *eeepc)
+{
+	struct backlight_properties props;
+	struct backlight_device *bd;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = 15;
+	bd = backlight_device_register(EEEPC_LAPTOP_FILE,
+				       &eeepc->platform_device->dev, eeepc,
+				       &eeepcbl_ops, &props);
+	if (IS_ERR(bd)) {
+		pr_err("Could not register eeepc backlight device\n");
+		eeepc->backlight_device = NULL;
+		return PTR_ERR(bd);
+	}
+	eeepc->backlight_device = bd;
+	bd->props.brightness = read_brightness(bd);
+	bd->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(bd);
+	return 0;
+}
+
+static void eeepc_backlight_exit(struct eeepc_laptop *eeepc)
+{
+	backlight_device_unregister(eeepc->backlight_device);
+	eeepc->backlight_device = NULL;
+}
+
+
+/*
+ * Input device (i.e. hotkeys)
+ */
+static int eeepc_input_init(struct eeepc_laptop *eeepc)
+{
+	struct input_dev *input;
+	int error;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "Asus EeePC extra buttons";
+	input->phys = EEEPC_LAPTOP_FILE "/input0";
+	input->id.bustype = BUS_HOST;
+	input->dev.parent = &eeepc->platform_device->dev;
+
+	error = sparse_keymap_setup(input, eeepc_keymap, NULL);
+	if (error) {
+		pr_err("Unable to setup input device keymap\n");
+		goto err_free_dev;
+	}
+
+	error = input_register_device(input);
+	if (error) {
+		pr_err("Unable to register input device\n");
+		goto err_free_keymap;
+	}
+
+	eeepc->inputdev = input;
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(input);
+err_free_dev:
+	input_free_device(input);
+	return error;
+}
+
+static void eeepc_input_exit(struct eeepc_laptop *eeepc)
+{
+	if (eeepc->inputdev) {
+		sparse_keymap_free(eeepc->inputdev);
+		input_unregister_device(eeepc->inputdev);
+	}
+	eeepc->inputdev = NULL;
+}
+
+/*
+ * ACPI driver
+ */
+static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event)
+{
+	if (!eeepc->inputdev)
+		return;
+	if (!sparse_keymap_report_event(eeepc->inputdev, event, 1, true))
+		pr_info("Unknown key %x pressed\n", event);
+}
+
+static void eeepc_acpi_notify(struct acpi_device *device, u32 event)
+{
+	struct eeepc_laptop *eeepc = acpi_driver_data(device);
+	int old_brightness, new_brightness;
+	u16 count;
+
+	if (event > ACPI_MAX_SYS_NOTIFY)
+		return;
+	count = eeepc->event_count[event % 128]++;
+	acpi_bus_generate_netlink_event(device->pnp.device_class,
+					dev_name(&device->dev), event,
+					count);
+
+	/* Brightness events are special */
+	if (event < NOTIFY_BRN_MIN || event > NOTIFY_BRN_MAX) {
+		eeepc_input_notify(eeepc, event);
+		return;
+	}
+
+	/* Ignore them completely if the acpi video driver is used */
+	if (!eeepc->backlight_device)
+		return;
+
+	/* Update the backlight device. */
+	old_brightness = eeepc_backlight_notify(eeepc);
+
+	/* Convert event to keypress (obsolescent hack) */
+	new_brightness = event - NOTIFY_BRN_MIN;
+
+	if (new_brightness < old_brightness) {
+		event = NOTIFY_BRN_MIN; /* brightness down */
+	} else if (new_brightness > old_brightness) {
+		event = NOTIFY_BRN_MAX; /* brightness up */
+	} else {
+		/*
+		 * no change in brightness - already at min/max,
+		 * event will be desired value (or else ignored)
+		 */
+	}
+	eeepc_input_notify(eeepc, event);
+}
+
+static void eeepc_dmi_check(struct eeepc_laptop *eeepc)
+{
+	const char *model;
+
+	model = dmi_get_system_info(DMI_PRODUCT_NAME);
+	if (!model)
+		return;
+
+	/*
+	 * Blacklist for setting cpufv (cpu speed).
+	 *
+	 * EeePC 4G ("701") implements CFVS, but it is not supported
+	 * by the pre-installed OS, and the original option to change it
+	 * in the BIOS setup screen was removed in later versions.
+	 *
+	 * Judging by the lack of "Super Hybrid Engine" on Asus product pages,
+	 * this applies to all "701" models (4G/4G Surf/2G Surf).
+	 *
+	 * So Asus made a deliberate decision not to support it on this model.
+	 * We have several reports that using it can cause the system to hang
+	 *
+	 * The hang has also been reported on a "702" (Model name "8G"?).
+	 *
+	 * We avoid dmi_check_system() / dmi_match(), because they use
+	 * substring matching.  We don't want to affect the "701SD"
+	 * and "701SDX" models, because they do support S.H.E.
+	 */
+	if (strcmp(model, "701") == 0 || strcmp(model, "702") == 0) {
+		eeepc->cpufv_disabled = true;
+		pr_info("model %s does not officially support setting cpu speed\n",
+			model);
+		pr_info("cpufv disabled to avoid instability\n");
+	}
+
+	/*
+	 * Blacklist for wlan hotplug
+	 *
+	 * Eeepc 1005HA doesn't work like others models and don't need the
+	 * hotplug code. In fact, current hotplug code seems to unplug another
+	 * device...
+	 */
+	if (strcmp(model, "1005HA") == 0 || strcmp(model, "1201N") == 0 ||
+	    strcmp(model, "1005PE") == 0) {
+		eeepc->hotplug_disabled = true;
+		pr_info("wlan hotplug disabled\n");
+	}
+}
+
+static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name)
+{
+	int dummy;
+
+	/* Some BIOSes do not report cm although it is available.
+	   Check if cm_getv[cm] works and, if yes, assume cm should be set. */
+	if (!(eeepc->cm_supported & (1 << cm))
+	    && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) {
+		pr_info("%s (%x) not reported by BIOS, enabling anyway\n",
+			name, 1 << cm);
+		eeepc->cm_supported |= 1 << cm;
+	}
+}
+
+static void cmsg_quirks(struct eeepc_laptop *eeepc)
+{
+	cmsg_quirk(eeepc, CM_ASL_LID, "LID");
+	cmsg_quirk(eeepc, CM_ASL_TYPE, "TYPE");
+	cmsg_quirk(eeepc, CM_ASL_PANELPOWER, "PANELPOWER");
+	cmsg_quirk(eeepc, CM_ASL_TPD, "TPD");
+}
+
+static int eeepc_acpi_init(struct eeepc_laptop *eeepc)
+{
+	unsigned int init_flags;
+	int result;
+
+	result = acpi_bus_get_status(eeepc->device);
+	if (result)
+		return result;
+	if (!eeepc->device->status.present) {
+		pr_err("Hotkey device not present, aborting\n");
+		return -ENODEV;
+	}
+
+	init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
+	pr_notice("Hotkey init flags 0x%x\n", init_flags);
+
+	if (write_acpi_int(eeepc->handle, "INIT", init_flags)) {
+		pr_err("Hotkey initialization failed\n");
+		return -ENODEV;
+	}
+
+	/* get control methods supported */
+	if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) {
+		pr_err("Get control methods supported failed\n");
+		return -ENODEV;
+	}
+	cmsg_quirks(eeepc);
+	pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported);
+
+	return 0;
+}
+
+static void eeepc_enable_camera(struct eeepc_laptop *eeepc)
+{
+	/*
+	 * If the following call to set_acpi() fails, it's because there's no
+	 * camera so we can ignore the error.
+	 */
+	if (get_acpi(eeepc, CM_ASL_CAMERA) == 0)
+		set_acpi(eeepc, CM_ASL_CAMERA, 1);
+}
+
+static bool eeepc_device_present;
+
+static int eeepc_acpi_add(struct acpi_device *device)
+{
+	struct eeepc_laptop *eeepc;
+	int result;
+
+	pr_notice(EEEPC_LAPTOP_NAME "\n");
+	eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
+	if (!eeepc)
+		return -ENOMEM;
+	eeepc->handle = device->handle;
+	strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
+	strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
+	device->driver_data = eeepc;
+	eeepc->device = device;
+
+	eeepc->hotplug_disabled = hotplug_disabled;
+
+	eeepc_dmi_check(eeepc);
+
+	result = eeepc_acpi_init(eeepc);
+	if (result)
+		goto fail_platform;
+	eeepc_enable_camera(eeepc);
+
+	/*
+	 * Register the platform device first.  It is used as a parent for the
+	 * sub-devices below.
+	 *
+	 * Note that if there are multiple instances of this ACPI device it
+	 * will bail out, because the platform device is registered with a
+	 * fixed name.  Of course it doesn't make sense to have more than one,
+	 * and machine-specific scripts find the fixed name convenient.  But
+	 * It's also good for us to exclude multiple instances because both
+	 * our hwmon and our wlan rfkill subdevice use global ACPI objects
+	 * (the EC and the wlan PCI slot respectively).
+	 */
+	result = eeepc_platform_init(eeepc);
+	if (result)
+		goto fail_platform;
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		result = eeepc_backlight_init(eeepc);
+		if (result)
+			goto fail_backlight;
+	}
+
+	result = eeepc_input_init(eeepc);
+	if (result)
+		goto fail_input;
+
+	result = eeepc_hwmon_init(eeepc);
+	if (result)
+		goto fail_hwmon;
+
+	result = eeepc_led_init(eeepc);
+	if (result)
+		goto fail_led;
+
+	result = eeepc_rfkill_init(eeepc);
+	if (result)
+		goto fail_rfkill;
+
+	eeepc_device_present = true;
+	return 0;
+
+fail_rfkill:
+	eeepc_led_exit(eeepc);
+fail_led:
+fail_hwmon:
+	eeepc_input_exit(eeepc);
+fail_input:
+	eeepc_backlight_exit(eeepc);
+fail_backlight:
+	eeepc_platform_exit(eeepc);
+fail_platform:
+	kfree(eeepc);
+
+	return result;
+}
+
+static int eeepc_acpi_remove(struct acpi_device *device)
+{
+	struct eeepc_laptop *eeepc = acpi_driver_data(device);
+
+	eeepc_backlight_exit(eeepc);
+	eeepc_rfkill_exit(eeepc);
+	eeepc_input_exit(eeepc);
+	eeepc_led_exit(eeepc);
+	eeepc_platform_exit(eeepc);
+
+	kfree(eeepc);
+	return 0;
+}
+
+
+static const struct acpi_device_id eeepc_device_ids[] = {
+	{EEEPC_ACPI_HID, 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
+
+static struct acpi_driver eeepc_acpi_driver = {
+	.name = EEEPC_LAPTOP_NAME,
+	.class = EEEPC_ACPI_CLASS,
+	.owner = THIS_MODULE,
+	.ids = eeepc_device_ids,
+	.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+	.ops = {
+		.add = eeepc_acpi_add,
+		.remove = eeepc_acpi_remove,
+		.notify = eeepc_acpi_notify,
+	},
+};
+
+
+static int __init eeepc_laptop_init(void)
+{
+	int result;
+
+	result = platform_driver_register(&platform_driver);
+	if (result < 0)
+		return result;
+
+	result = acpi_bus_register_driver(&eeepc_acpi_driver);
+	if (result < 0)
+		goto fail_acpi_driver;
+
+	if (!eeepc_device_present) {
+		result = -ENODEV;
+		goto fail_no_device;
+	}
+
+	return 0;
+
+fail_no_device:
+	acpi_bus_unregister_driver(&eeepc_acpi_driver);
+fail_acpi_driver:
+	platform_driver_unregister(&platform_driver);
+	return result;
+}
+
+static void __exit eeepc_laptop_exit(void)
+{
+	acpi_bus_unregister_driver(&eeepc_acpi_driver);
+	platform_driver_unregister(&platform_driver);
+}
+
+module_init(eeepc_laptop_init);
+module_exit(eeepc_laptop_exit);
diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c
new file mode 100644
index 0000000..14fd2ec
--- /dev/null
+++ b/drivers/platform/x86/eeepc-wmi.c
@@ -0,0 +1,278 @@
+/*
+ * Eee PC WMI hotkey driver
+ *
+ * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/dmi.h>
+#include <linux/fb.h>
+#include <linux/acpi.h>
+
+#include "asus-wmi.h"
+
+#define	EEEPC_WMI_FILE	"eeepc-wmi"
+
+MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>");
+MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver");
+MODULE_LICENSE("GPL");
+
+#define EEEPC_ACPI_HID		"ASUS010" /* old _HID used in eeepc-laptop */
+
+#define EEEPC_WMI_EVENT_GUID	"ABBC0F72-8EA1-11D1-00A0-C90629100000"
+
+MODULE_ALIAS("wmi:"EEEPC_WMI_EVENT_GUID);
+
+static bool hotplug_wireless;
+
+module_param(hotplug_wireless, bool, 0444);
+MODULE_PARM_DESC(hotplug_wireless,
+		 "Enable hotplug for wireless device. "
+		 "If your laptop needs that, please report to "
+		 "acpi4asus-user@lists.sourceforge.net.");
+
+/* Values for T101MT "Home" key */
+#define HOME_PRESS	0xe4
+#define HOME_HOLD	0xea
+#define HOME_RELEASE	0xe5
+
+static const struct key_entry eeepc_wmi_keymap[] = {
+	{ KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } },
+	/* Sleep already handled via generic ACPI code */
+	{ KE_KEY, 0x30, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0x32, { KEY_MUTE } },
+	{ KE_KEY, 0x5c, { KEY_F15 } }, /* Power Gear key */
+	{ KE_KEY, 0x5d, { KEY_WLAN } },
+	{ KE_KEY, 0x6b, { KEY_TOUCHPAD_TOGGLE } }, /* Toggle Touchpad */
+	{ KE_KEY, 0x82, { KEY_CAMERA } },
+	{ KE_KEY, 0x83, { KEY_CAMERA_ZOOMIN } },
+	{ KE_KEY, 0x88, { KEY_WLAN } },
+	{ KE_KEY, 0xbd, { KEY_CAMERA } },
+	{ KE_KEY, 0xcc, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0xe0, { KEY_PROG1 } }, /* Task Manager */
+	{ KE_KEY, 0xe1, { KEY_F14 } }, /* Change Resolution */
+	{ KE_KEY, HOME_PRESS, { KEY_CONFIG } }, /* Home/Express gate key */
+	{ KE_KEY, 0xe8, { KEY_SCREENLOCK } },
+	{ KE_KEY, 0xe9, { KEY_DISPLAYTOGGLE } },
+	{ KE_KEY, 0xeb, { KEY_CAMERA_ZOOMOUT } },
+	{ KE_KEY, 0xec, { KEY_CAMERA_UP } },
+	{ KE_KEY, 0xed, { KEY_CAMERA_DOWN } },
+	{ KE_KEY, 0xee, { KEY_CAMERA_LEFT } },
+	{ KE_KEY, 0xef, { KEY_CAMERA_RIGHT } },
+	{ KE_KEY, 0xf3, { KEY_MENU } },
+	{ KE_KEY, 0xf5, { KEY_HOMEPAGE } },
+	{ KE_KEY, 0xf6, { KEY_ESC } },
+	{ KE_END, 0},
+};
+
+static struct quirk_entry quirk_asus_unknown = {
+};
+
+static struct quirk_entry quirk_asus_1000h = {
+	.hotplug_wireless = true,
+};
+
+static struct quirk_entry quirk_asus_et2012_type1 = {
+	.store_backlight_power = true,
+};
+
+static struct quirk_entry quirk_asus_et2012_type3 = {
+	.scalar_panel_brightness = true,
+	.store_backlight_power = true,
+};
+
+static struct quirk_entry quirk_asus_x101ch = {
+	/* We need this when ACPI function doesn't do this well */
+	.wmi_backlight_power = true,
+};
+
+static struct quirk_entry *quirks;
+
+static void et2012_quirks(void)
+{
+	const struct dmi_device *dev = NULL;
+	char oemstring[30];
+
+	while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+		if (sscanf(dev->name, "AEMS%24c", oemstring) == 1) {
+			if (oemstring[18] == '1')
+				quirks = &quirk_asus_et2012_type1;
+			else if (oemstring[18] == '3')
+				quirks = &quirk_asus_et2012_type3;
+			break;
+		}
+	}
+}
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+	char *model;
+
+	quirks = dmi->driver_data;
+
+	model = (char *)dmi->matches[1].substr;
+	if (unlikely(strncmp(model, "ET2012", 6) == 0))
+		et2012_quirks();
+
+	return 1;
+}
+
+static const struct dmi_system_id asus_quirks[] = {
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK Computer INC. 1000H",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "1000H"),
+		},
+		.driver_data = &quirk_asus_1000h,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK Computer INC. ET2012E/I",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "ET2012"),
+		},
+		.driver_data = &quirk_asus_unknown,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK Computer INC. X101CH",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X101CH"),
+		},
+		.driver_data = &quirk_asus_x101ch,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "ASUSTeK Computer INC. 1015CX",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "1015CX"),
+		},
+		.driver_data = &quirk_asus_x101ch,
+	},
+	{},
+};
+
+static void eeepc_wmi_key_filter(struct asus_wmi_driver *asus_wmi, int *code,
+				 unsigned int *value, bool *autorelease)
+{
+	switch (*code) {
+	case HOME_PRESS:
+		*value = 1;
+		*autorelease = 0;
+		break;
+	case HOME_HOLD:
+		*code = ASUS_WMI_KEY_IGNORE;
+		break;
+	case HOME_RELEASE:
+		*code = HOME_PRESS;
+		*value = 0;
+		*autorelease = 0;
+		break;
+	}
+}
+
+static acpi_status eeepc_wmi_parse_device(acpi_handle handle, u32 level,
+						 void *context, void **retval)
+{
+	pr_warn("Found legacy ATKD device (%s)\n", EEEPC_ACPI_HID);
+	*(bool *)context = true;
+	return AE_CTRL_TERMINATE;
+}
+
+static int eeepc_wmi_check_atkd(void)
+{
+	acpi_status status;
+	bool found = false;
+
+	status = acpi_get_devices(EEEPC_ACPI_HID, eeepc_wmi_parse_device,
+				  &found, NULL);
+
+	if (ACPI_FAILURE(status) || !found)
+		return 0;
+	return -1;
+}
+
+static int eeepc_wmi_probe(struct platform_device *pdev)
+{
+	if (eeepc_wmi_check_atkd()) {
+		pr_warn("WMI device present, but legacy ATKD device is also "
+			"present and enabled\n");
+		pr_warn("You probably booted with acpi_osi=\"Linux\" or "
+			"acpi_osi=\"!Windows 2009\"\n");
+		pr_warn("Can't load eeepc-wmi, use default acpi_osi "
+			"(preferred) or eeepc-laptop\n");
+		return -EBUSY;
+	}
+	return 0;
+}
+
+static void eeepc_wmi_quirks(struct asus_wmi_driver *driver)
+{
+	quirks = &quirk_asus_unknown;
+	quirks->hotplug_wireless = hotplug_wireless;
+
+	dmi_check_system(asus_quirks);
+
+	driver->quirks = quirks;
+	driver->quirks->wapf = -1;
+	driver->panel_power = FB_BLANK_UNBLANK;
+}
+
+static struct asus_wmi_driver asus_wmi_driver = {
+	.name = EEEPC_WMI_FILE,
+	.owner = THIS_MODULE,
+	.event_guid = EEEPC_WMI_EVENT_GUID,
+	.keymap = eeepc_wmi_keymap,
+	.input_name = "Eee PC WMI hotkeys",
+	.input_phys = EEEPC_WMI_FILE "/input0",
+	.key_filter = eeepc_wmi_key_filter,
+	.probe = eeepc_wmi_probe,
+	.detect_quirks = eeepc_wmi_quirks,
+};
+
+
+static int __init eeepc_wmi_init(void)
+{
+	return asus_wmi_register_driver(&asus_wmi_driver);
+}
+
+static void __exit eeepc_wmi_exit(void)
+{
+	asus_wmi_unregister_driver(&asus_wmi_driver);
+}
+
+module_init(eeepc_wmi_init);
+module_exit(eeepc_wmi_exit);
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
new file mode 100644
index 0000000..1c62caf
--- /dev/null
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -0,0 +1,1215 @@
+/*-*-linux-c-*-*/
+
+/*
+  Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net>
+  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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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/fb.h>
+#include <linux/input.h>
+#include <linux/kfifo.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+#include <linux/leds.h>
+#endif
+#include <acpi/video.h>
+
+#define FUJITSU_DRIVER_VERSION "0.6.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
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+/* 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
+
+#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+#define vdbg_printk(a_dbg_level, format, arg...) \
+	do { if (dbg_level & a_dbg_level) \
+		printk(FUJLAPTOP_DEBUG "%s: " format, __func__ , ## arg); \
+	} while (0)
+#else
+#define vdbg_printk(a_dbg_level, format, arg...) \
+	do { } while (0)
+#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_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;
+	int rfkill_supported;
+	int rfkill_state;
+	int logolamp_registered;
+	int kblamps_registered;
+};
+
+static struct fujitsu_hotkey_t *fujitsu_hotkey;
+
+static void acpi_fujitsu_hotkey_notify(struct acpi_device *device, u32 event);
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+static enum led_brightness logolamp_get(struct led_classdev *cdev);
+static void logolamp_set(struct led_classdev *cdev,
+			       enum led_brightness brightness);
+
+static 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);
+
+static 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(struct acpi_device *device, u32 event);
+
+/* 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] };
+	unsigned long long value;
+	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;
+
+	status = acpi_evaluate_integer(handle, NULL, &arg_list, &value);
+	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;
+	}
+
+	vdbg_printk(FUJLAPTOP_DBG_TRACE,
+		"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
+			cmd, arg0, arg1, arg2, (int)value);
+	return value;
+}
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+/* 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;
+	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;
+
+	status = acpi_get_handle(fujitsu->acpi_handle, "SBLL", &handle);
+	if (ACPI_FAILURE(status)) {
+		vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBLL not present\n");
+		return -ENODEV;
+	}
+
+
+	status = acpi_execute_simple_method(handle, NULL, level);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	return 0;
+}
+
+static int set_lcd_level_alt(int level)
+{
+	acpi_status status = AE_OK;
+	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;
+
+	status = acpi_get_handle(fujitsu->acpi_handle, "SBL2", &handle);
+	if (ACPI_FAILURE(status)) {
+		vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBL2 not present\n");
+		return -ENODEV;
+	}
+
+	status = acpi_execute_simple_method(handle, NULL, level);
+	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 (ACPI_FAILURE(status))
+		return 0;
+
+	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 (ACPI_FAILURE(status))
+		return -1;
+
+	fujitsu->max_brightness = state;
+
+	return fujitsu->max_brightness;
+}
+
+/* Backlight device stuff */
+
+static int bl_get_brightness(struct backlight_device *b)
+{
+	return get_lcd_level();
+}
+
+static int bl_update_status(struct backlight_device *b)
+{
+	int ret;
+	if (b->props.power == FB_BLANK_POWERDOWN)
+		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)
+		ret = set_lcd_level_alt(b->props.brightness);
+	else
+		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 const 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;
+
+	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;
+
+	ret = get_lcd_level();
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t
+ignore_store(struct device *dev,
+	     struct device_attribute *attr, const char *buf, size_t count)
+{
+	return count;
+}
+
+static ssize_t
+show_lid_state(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	if (!(fujitsu_hotkey->rfkill_supported & 0x100))
+		return sprintf(buf, "unknown\n");
+	if (fujitsu_hotkey->rfkill_state & 0x100)
+		return sprintf(buf, "open\n");
+	else
+		return sprintf(buf, "closed\n");
+}
+
+static ssize_t
+show_dock_state(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	if (!(fujitsu_hotkey->rfkill_supported & 0x200))
+		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
+show_radios_state(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	if (!(fujitsu_hotkey->rfkill_supported & 0x20))
+		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",
+		   }
+};
+
+static void __init dmi_check_cb_common(const struct dmi_system_id *id)
+{
+	pr_info("Identified laptop model '%s'\n", id->ident);
+	if (use_alt_lcd_levels == -1) {
+		if (acpi_has_method(NULL,
+				"\\_SB.PCI0.LPCB.FJEX.SBL2"))
+			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 __init 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 1;
+}
+
+static int __init 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 1;
+}
+
+static int __init 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 1;
+}
+
+static const struct dmi_system_id fujitsu_dmi_table[] __initconst = {
+	{
+	 .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)
+{
+	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;
+
+	fujitsu->input = input = input_allocate_device();
+	if (!input) {
+		error = -ENOMEM;
+		goto err_stop;
+	}
+
+	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;
+
+	error = acpi_bus_update_power(fujitsu->acpi_handle, &state);
+	if (error) {
+		pr_err("Error reading power state\n");
+		goto err_unregister_input_dev;
+	}
+
+	pr_info("ACPI: %s [%s] (%s)\n",
+	       acpi_device_name(device), acpi_device_bid(device),
+	       !device->power.state ? "on" : "off");
+
+	fujitsu->dev = device;
+
+	if (acpi_has_method(device->handle, METHOD_NAME__INI)) {
+		vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
+		if (ACPI_FAILURE
+		    (acpi_evaluate_object
+		     (device->handle, METHOD_NAME__INI, NULL, NULL)))
+			pr_err("_INI Method failed\n");
+	}
+
+	/* do config (detect defaults) */
+	use_alt_lcd_levels = use_alt_lcd_levels == 1 ? 1 : 0;
+	disable_brightness_adjust = disable_brightness_adjust == 1 ? 1 : 0;
+	vdbg_printk(FUJLAPTOP_DBG_INFO,
+		    "config: [alt interface: %d], [adjust disable: %d]\n",
+		    use_alt_lcd_levels, disable_brightness_adjust);
+
+	if (get_max_brightness() <= 0)
+		fujitsu->max_brightness = FUJITSU_LCD_N_LEVELS;
+	get_lcd_level();
+
+	return 0;
+
+err_unregister_input_dev:
+	input_unregister_device(input);
+	input = NULL;
+err_free_input_dev:
+	input_free_device(input);
+err_stop:
+	return error;
+}
+
+static int acpi_fujitsu_remove(struct acpi_device *device)
+{
+	struct fujitsu_t *fujitsu = acpi_driver_data(device);
+	struct input_dev *input = fujitsu->input;
+
+	input_unregister_device(input);
+
+	fujitsu->acpi_handle = NULL;
+
+	return 0;
+}
+
+/* Brightness notify */
+
+static void acpi_fujitsu_notify(struct acpi_device *device, u32 event)
+{
+	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();
+		newb = fujitsu->brightness_level;
+
+		vdbg_printk(FUJLAPTOP_DBG_TRACE,
+			    "brightness button event [%i -> %i (%i)]\n",
+			    oldb, newb, fujitsu->brightness_changed);
+
+		if (oldb < newb) {
+			if (disable_brightness_adjust != 1) {
+				if (use_alt_lcd_levels)
+					set_lcd_level_alt(newb);
+				else
+					set_lcd_level(newb);
+			}
+			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);
+			}
+			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);
+	}
+}
+
+/* ACPI device for hotkey handling */
+
+static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
+{
+	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;
+
+	/* kfifo */
+	spin_lock_init(&fujitsu_hotkey->fifo_lock);
+	error = kfifo_alloc(&fujitsu_hotkey->fifo, RINGBUFFERSIZE * sizeof(int),
+			GFP_KERNEL);
+	if (error) {
+		pr_err("kfifo_alloc failed\n");
+		goto err_stop;
+	}
+
+	fujitsu_hotkey->input = input = input_allocate_device();
+	if (!input) {
+		error = -ENOMEM;
+		goto err_free_fifo;
+	}
+
+	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;
+
+	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;
+
+	error = acpi_bus_update_power(fujitsu_hotkey->acpi_handle, &state);
+	if (error) {
+		pr_err("Error reading power state\n");
+		goto err_unregister_input_dev;
+	}
+
+	pr_info("ACPI: %s [%s] (%s)\n",
+		acpi_device_name(device), acpi_device_bid(device),
+		!device->power.state ? "on" : "off");
+
+	fujitsu_hotkey->dev = device;
+
+	if (acpi_has_method(device->handle, METHOD_NAME__INI)) {
+		vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
+		if (ACPI_FAILURE
+		    (acpi_evaluate_object
+		     (device->handle, METHOD_NAME__INI, NULL, NULL)))
+			pr_err("_INI Method failed\n");
+	}
+
+	i = 0;
+	while (call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0
+		&& (i++) < MAX_HOTKEY_RINGBUFFER_SIZE)
+		; /* No action, result is discarded */
+	vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i);
+
+	fujitsu_hotkey->rfkill_supported =
+		call_fext_func(FUNC_RFKILL, 0x0, 0x0, 0x0);
+
+	/* Make sure our bitmask of supported functions is cleared if the
+	   RFKILL function block is not implemented, like on the S7020. */
+	if (fujitsu_hotkey->rfkill_supported == UNSUPPORTED_CMD)
+		fujitsu_hotkey->rfkill_supported = 0;
+
+	if (fujitsu_hotkey->rfkill_supported)
+		fujitsu_hotkey->rfkill_state =
+			call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
+
+	/* Suspect this is a keymap of the application panel, print it */
+	pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0));
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+	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 {
+			pr_err("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 {
+			pr_err("Could not register LED handler for keyboard lamps, error %i\n",
+			       result);
+		}
+	}
+#endif
+
+	return result;
+
+err_unregister_input_dev:
+	input_unregister_device(input);
+	input = NULL;
+err_free_input_dev:
+	input_free_device(input);
+err_free_fifo:
+	kfifo_free(&fujitsu_hotkey->fifo);
+err_stop:
+	return error;
+}
+
+static int acpi_fujitsu_hotkey_remove(struct acpi_device *device)
+{
+	struct fujitsu_hotkey_t *fujitsu_hotkey = acpi_driver_data(device);
+	struct input_dev *input = fujitsu_hotkey->input;
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+	if (fujitsu_hotkey->logolamp_registered)
+		led_classdev_unregister(&logolamp_led);
+
+	if (fujitsu_hotkey->kblamps_registered)
+		led_classdev_unregister(&kblamps_led);
+#endif
+
+	input_unregister_device(input);
+
+	kfifo_free(&fujitsu_hotkey->fifo);
+
+	fujitsu_hotkey->acpi_handle = NULL;
+
+	return 0;
+}
+
+static void acpi_fujitsu_hotkey_notify(struct acpi_device *device, u32 event)
+{
+	struct input_dev *input;
+	int keycode, keycode_r;
+	unsigned int irb = 1;
+	int i, status;
+
+	input = fujitsu_hotkey->input;
+
+	if (fujitsu_hotkey->rfkill_supported)
+		fujitsu_hotkey->rfkill_state =
+			call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
+
+	switch (event) {
+	case ACPI_FUJITSU_NOTIFY_CODE1:
+		i = 0;
+		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_in_locked(&fujitsu_hotkey->fifo,
+						   (unsigned char *)&keycode,
+						   sizeof(keycode),
+						   &fujitsu_hotkey->fifo_lock);
+				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_out_locked(
+					 &fujitsu_hotkey->fifo,
+					 (unsigned char *) &keycode_r,
+					 sizeof(keycode_r),
+					 &fujitsu_hotkey->fifo_lock))
+					 == 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;
+	}
+}
+
+/* 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,
+		.notify = acpi_fujitsu_notify,
+		},
+};
+
+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,
+		.notify = acpi_fujitsu_hotkey_notify,
+		},
+};
+
+static const struct acpi_device_id fujitsu_ids[] __used = {
+	{ACPI_FUJITSU_HID, 0},
+	{ACPI_FUJITSU_HOTKEY_HID, 0},
+	{"", 0}
+};
+MODULE_DEVICE_TABLE(acpi, fujitsu_ids);
+
+static int __init fujitsu_init(void)
+{
+	int ret, result, max_brightness;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	fujitsu = kzalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
+	if (!fujitsu)
+		return -ENOMEM;
+	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_get_backlight_type() == acpi_backlight_vendor) {
+		struct backlight_properties props;
+
+		memset(&props, 0, sizeof(struct backlight_properties));
+		max_brightness = fujitsu->max_brightness;
+		props.type = BACKLIGHT_PLATFORM;
+		props.max_brightness = max_brightness - 1;
+		fujitsu->bl_device = backlight_device_register("fujitsu-laptop",
+							       NULL, NULL,
+							       &fujitsubl_ops,
+							       &props);
+		if (IS_ERR(fujitsu->bl_device)) {
+			ret = PTR_ERR(fujitsu->bl_device);
+			fujitsu->bl_device = NULL;
+			goto fail_sysfs_group;
+		}
+		fujitsu->bl_device->props.brightness = fujitsu->brightness_level;
+	}
+
+	ret = platform_driver_register(&fujitsupf_driver);
+	if (ret)
+		goto fail_backlight;
+
+	/* Register hotkey driver */
+
+	fujitsu_hotkey = kzalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
+	if (!fujitsu_hotkey) {
+		ret = -ENOMEM;
+		goto fail_hotkey;
+	}
+
+	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_get_backlight_type() == acpi_backlight_vendor) {
+		if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
+			fujitsu->bl_device->props.power = FB_BLANK_POWERDOWN;
+		else
+			fujitsu->bl_device->props.power = FB_BLANK_UNBLANK;
+	}
+
+	pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n");
+
+	return 0;
+
+fail_hotkey1:
+	kfree(fujitsu_hotkey);
+fail_hotkey:
+	platform_driver_unregister(&fujitsupf_driver);
+fail_backlight:
+	backlight_device_unregister(fujitsu->bl_device);
+fail_sysfs_group:
+	sysfs_remove_group(&fujitsu->pf_device->dev.kobj,
+			   &fujitsupf_attribute_group);
+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)
+{
+	acpi_bus_unregister_driver(&acpi_fujitsu_hotkey_driver);
+
+	kfree(fujitsu_hotkey);
+
+	platform_driver_unregister(&fujitsupf_driver);
+
+	backlight_device_unregister(fujitsu->bl_device);
+
+	sysfs_remove_group(&fujitsu->pf_device->dev.kobj,
+			   &fujitsupf_attribute_group);
+
+	platform_device_unregister(fujitsu->pf_device);
+
+	acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+
+	kfree(fujitsu);
+
+	pr_info("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_adjust, uint, 0644);
+MODULE_PARM_DESC(disable_brightness_adjust, "Disable brightness adjustment .");
+#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+module_param_named(debug, dbg_level, uint, 0644);
+MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
+#endif
+
+MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
+MODULE_DESCRIPTION("Fujitsu laptop extras support");
+MODULE_VERSION(FUJITSU_DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*");
+MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1E6:*:cvrS6420:*");
+MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*");
diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c
new file mode 100644
index 0000000..baea077
--- /dev/null
+++ b/drivers/platform/x86/fujitsu-tablet.c
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2006-2012 Robert Gerlach <khnz@gmx.de>
+ * Copyright (C) 2005-2006 Jan Rychter <jan@rychter.com>
+ *
+ * You can redistribute and/or modify this program under the terms of the
+ * GNU General Public License version 2 as published 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/dmi.h>
+
+#define MODULENAME "fujitsu-tablet"
+
+#define ACPI_FUJITSU_CLASS "fujitsu"
+
+#define INVERT_TABLET_MODE_BIT      0x01
+#define INVERT_DOCK_STATE_BIT       0x02
+#define FORCE_TABLET_MODE_IF_UNDOCK 0x04
+
+#define KEYMAP_LEN 16
+
+static const struct acpi_device_id fujitsu_ids[] = {
+	{ .id = "FUJ02BD" },
+	{ .id = "FUJ02BF" },
+	{ .id = "" }
+};
+
+struct fujitsu_config {
+	unsigned short keymap[KEYMAP_LEN];
+	unsigned int quirks;
+};
+
+static unsigned short keymap_Lifebook_Tseries[KEYMAP_LEN] __initdata = {
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_SCROLLDOWN,
+	KEY_SCROLLUP,
+	KEY_ROTATE_DISPLAY,
+	KEY_LEFTCTRL,
+	KEY_BRIGHTNESSUP,
+	KEY_BRIGHTNESSDOWN,
+	KEY_BRIGHTNESS_ZERO,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_LEFTALT
+};
+
+static unsigned short keymap_Lifebook_T901[KEYMAP_LEN] __initdata = {
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_SCROLLDOWN,
+	KEY_SCROLLUP,
+	KEY_CYCLEWINDOWS,
+	KEY_LEFTCTRL,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_LEFTMETA
+};
+
+static unsigned short keymap_Lifebook_T902[KEYMAP_LEN] __initdata = {
+	KEY_RESERVED,
+	KEY_VOLUMEDOWN,
+	KEY_VOLUMEUP,
+	KEY_CYCLEWINDOWS,
+	KEY_PROG1,
+	KEY_PROG2,
+	KEY_LEFTMETA,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+};
+
+static unsigned short keymap_Lifebook_U810[KEYMAP_LEN] __initdata = {
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_PROG1,
+	KEY_PROG2,
+	KEY_ROTATE_DISPLAY,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_UP,
+	KEY_DOWN,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_LEFTCTRL,
+	KEY_LEFTALT
+};
+
+static unsigned short keymap_Stylistic_Tseries[KEYMAP_LEN] __initdata = {
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_PRINT,
+	KEY_BACKSPACE,
+	KEY_SPACE,
+	KEY_ENTER,
+	KEY_BRIGHTNESSUP,
+	KEY_BRIGHTNESSDOWN,
+	KEY_DOWN,
+	KEY_UP,
+	KEY_SCROLLUP,
+	KEY_SCROLLDOWN,
+	KEY_LEFTCTRL,
+	KEY_LEFTALT
+};
+
+static unsigned short keymap_Stylistic_ST5xxx[KEYMAP_LEN] __initdata = {
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_MAIL,
+	KEY_ROTATE_DISPLAY,
+	KEY_ESC,
+	KEY_ENTER,
+	KEY_BRIGHTNESSUP,
+	KEY_BRIGHTNESSDOWN,
+	KEY_DOWN,
+	KEY_UP,
+	KEY_SCROLLUP,
+	KEY_SCROLLDOWN,
+	KEY_LEFTCTRL,
+	KEY_LEFTALT
+};
+
+static struct {
+	struct input_dev *idev;
+	struct fujitsu_config config;
+	unsigned long prev_keymask;
+
+	char phys[21];
+
+	int irq;
+	int io_base;
+	int io_length;
+} fujitsu;
+
+static u8 fujitsu_ack(void)
+{
+	return inb(fujitsu.io_base + 2);
+}
+
+static u8 fujitsu_status(void)
+{
+	return inb(fujitsu.io_base + 6);
+}
+
+static u8 fujitsu_read_register(const u8 addr)
+{
+	outb(addr, fujitsu.io_base);
+	return inb(fujitsu.io_base + 4);
+}
+
+static void fujitsu_send_state(void)
+{
+	int state;
+	int dock, tablet_mode;
+
+	state = fujitsu_read_register(0xdd);
+
+	dock = state & 0x02;
+	if (fujitsu.config.quirks & INVERT_DOCK_STATE_BIT)
+		dock = !dock;
+
+	if ((fujitsu.config.quirks & FORCE_TABLET_MODE_IF_UNDOCK) && (!dock)) {
+		tablet_mode = 1;
+	} else{
+		tablet_mode = state & 0x01;
+		if (fujitsu.config.quirks & INVERT_TABLET_MODE_BIT)
+			tablet_mode = !tablet_mode;
+	}
+
+	input_report_switch(fujitsu.idev, SW_DOCK, dock);
+	input_report_switch(fujitsu.idev, SW_TABLET_MODE, tablet_mode);
+	input_sync(fujitsu.idev);
+}
+
+static void fujitsu_reset(void)
+{
+	int timeout = 50;
+
+	fujitsu_ack();
+
+	while ((fujitsu_status() & 0x02) && (--timeout))
+		msleep(20);
+
+	fujitsu_send_state();
+}
+
+static int input_fujitsu_setup(struct device *parent, const char *name,
+			       const char *phys)
+{
+	struct input_dev *idev;
+	int error;
+	int i;
+
+	idev = input_allocate_device();
+	if (!idev)
+		return -ENOMEM;
+
+	idev->dev.parent = parent;
+	idev->phys = phys;
+	idev->name = name;
+	idev->id.bustype = BUS_HOST;
+	idev->id.vendor  = 0x1734;	/* Fujitsu Siemens Computer GmbH */
+	idev->id.product = 0x0001;
+	idev->id.version = 0x0101;
+
+	idev->keycode = fujitsu.config.keymap;
+	idev->keycodesize = sizeof(fujitsu.config.keymap[0]);
+	idev->keycodemax = ARRAY_SIZE(fujitsu.config.keymap);
+
+	__set_bit(EV_REP, idev->evbit);
+
+	for (i = 0; i < ARRAY_SIZE(fujitsu.config.keymap); i++)
+		if (fujitsu.config.keymap[i])
+			input_set_capability(idev, EV_KEY, fujitsu.config.keymap[i]);
+
+	input_set_capability(idev, EV_MSC, MSC_SCAN);
+
+	input_set_capability(idev, EV_SW, SW_DOCK);
+	input_set_capability(idev, EV_SW, SW_TABLET_MODE);
+
+	error = input_register_device(idev);
+	if (error) {
+		input_free_device(idev);
+		return error;
+	}
+
+	fujitsu.idev = idev;
+	return 0;
+}
+
+static void input_fujitsu_remove(void)
+{
+	input_unregister_device(fujitsu.idev);
+}
+
+static irqreturn_t fujitsu_interrupt(int irq, void *dev_id)
+{
+	unsigned long keymask, changed;
+	unsigned int keycode;
+	int pressed;
+	int i;
+
+	if (unlikely(!(fujitsu_status() & 0x01)))
+		return IRQ_NONE;
+
+	fujitsu_send_state();
+
+	keymask  = fujitsu_read_register(0xde);
+	keymask |= fujitsu_read_register(0xdf) << 8;
+	keymask ^= 0xffff;
+
+	changed = keymask ^ fujitsu.prev_keymask;
+	if (changed) {
+		fujitsu.prev_keymask = keymask;
+
+		for_each_set_bit(i, &changed, KEYMAP_LEN) {
+			keycode = fujitsu.config.keymap[i];
+			pressed = keymask & changed & BIT(i);
+
+			if (pressed)
+				input_event(fujitsu.idev, EV_MSC, MSC_SCAN, i);
+
+			input_report_key(fujitsu.idev, keycode, pressed);
+			input_sync(fujitsu.idev);
+		}
+	}
+
+	fujitsu_ack();
+	return IRQ_HANDLED;
+}
+
+static void __init fujitsu_dmi_common(const struct dmi_system_id *dmi)
+{
+	pr_info("%s\n", dmi->ident);
+	memcpy(fujitsu.config.keymap, dmi->driver_data,
+			sizeof(fujitsu.config.keymap));
+}
+
+static int __init fujitsu_dmi_lifebook(const struct dmi_system_id *dmi)
+{
+	fujitsu_dmi_common(dmi);
+	fujitsu.config.quirks |= INVERT_TABLET_MODE_BIT;
+	return 1;
+}
+
+static int __init fujitsu_dmi_stylistic(const struct dmi_system_id *dmi)
+{
+	fujitsu_dmi_common(dmi);
+	fujitsu.config.quirks |= FORCE_TABLET_MODE_IF_UNDOCK;
+	fujitsu.config.quirks |= INVERT_DOCK_STATE_BIT;
+	return 1;
+}
+
+static const struct dmi_system_id dmi_ids[] __initconst = {
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Fujitsu Lifebook T901",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T901")
+		},
+		.driver_data = keymap_Lifebook_T901
+	},
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Fujitsu Lifebook T901",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T901")
+		},
+		.driver_data = keymap_Lifebook_T901
+	},
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Fujitsu Lifebook T902",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T902")
+		},
+		.driver_data = keymap_Lifebook_T902
+	},
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Fujitsu Siemens P/T Series",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK")
+		},
+		.driver_data = keymap_Lifebook_Tseries
+	},
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Fujitsu Lifebook T Series",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T")
+		},
+		.driver_data = keymap_Lifebook_Tseries
+	},
+	{
+		.callback = fujitsu_dmi_stylistic,
+		.ident = "Fujitsu Siemens Stylistic T Series",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic T")
+		},
+		.driver_data = keymap_Stylistic_Tseries
+	},
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Fujitsu LifeBook U810",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook U810")
+		},
+		.driver_data = keymap_Lifebook_U810
+	},
+	{
+		.callback = fujitsu_dmi_stylistic,
+		.ident = "Fujitsu Siemens Stylistic ST5xxx Series",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "STYLISTIC ST5")
+		},
+		.driver_data = keymap_Stylistic_ST5xxx
+	},
+	{
+		.callback = fujitsu_dmi_stylistic,
+		.ident = "Fujitsu Siemens Stylistic ST5xxx Series",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic ST5")
+		},
+		.driver_data = keymap_Stylistic_ST5xxx
+	},
+	{
+		.callback = fujitsu_dmi_lifebook,
+		.ident = "Unknown (using defaults)",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, ""),
+			DMI_MATCH(DMI_PRODUCT_NAME, "")
+		},
+		.driver_data = keymap_Lifebook_Tseries
+	},
+	{ NULL }
+};
+
+static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data)
+{
+	switch (res->type) {
+	case ACPI_RESOURCE_TYPE_IRQ:
+		fujitsu.irq = res->data.irq.interrupts[0];
+		return AE_OK;
+
+	case ACPI_RESOURCE_TYPE_IO:
+		fujitsu.io_base = res->data.io.minimum;
+		fujitsu.io_length = res->data.io.address_length;
+		return AE_OK;
+
+	case ACPI_RESOURCE_TYPE_END_TAG:
+		if (fujitsu.irq && fujitsu.io_base)
+			return AE_OK;
+		else
+			return AE_NOT_FOUND;
+
+	default:
+		return AE_ERROR;
+	}
+}
+
+static int acpi_fujitsu_add(struct acpi_device *adev)
+{
+	acpi_status status;
+	int error;
+
+	if (!adev)
+		return -EINVAL;
+
+	status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
+			fujitsu_walk_resources, NULL);
+	if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base)
+		return -ENODEV;
+
+	sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev));
+	sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS);
+
+	snprintf(fujitsu.phys, sizeof(fujitsu.phys),
+			"%s/input0", acpi_device_hid(adev));
+
+	error = input_fujitsu_setup(&adev->dev,
+		acpi_device_name(adev), fujitsu.phys);
+	if (error)
+		return error;
+
+	if (!request_region(fujitsu.io_base, fujitsu.io_length, MODULENAME)) {
+		input_fujitsu_remove();
+		return -EBUSY;
+	}
+
+	fujitsu_reset();
+
+	error = request_irq(fujitsu.irq, fujitsu_interrupt,
+			IRQF_SHARED, MODULENAME, fujitsu_interrupt);
+	if (error) {
+		release_region(fujitsu.io_base, fujitsu.io_length);
+		input_fujitsu_remove();
+		return error;
+	}
+
+	return 0;
+}
+
+static int acpi_fujitsu_remove(struct acpi_device *adev)
+{
+	free_irq(fujitsu.irq, fujitsu_interrupt);
+	release_region(fujitsu.io_base, fujitsu.io_length);
+	input_fujitsu_remove();
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_fujitsu_resume(struct device *dev)
+{
+	fujitsu_reset();
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(acpi_fujitsu_pm, NULL, acpi_fujitsu_resume);
+
+static struct acpi_driver acpi_fujitsu_driver = {
+	.name  = MODULENAME,
+	.class = "hotkey",
+	.ids   = fujitsu_ids,
+	.ops   = {
+		.add    = acpi_fujitsu_add,
+		.remove	= acpi_fujitsu_remove,
+	},
+	.drv.pm = &acpi_fujitsu_pm,
+};
+
+static int __init fujitsu_module_init(void)
+{
+	int error;
+
+	dmi_check_system(dmi_ids);
+
+	error = acpi_bus_register_driver(&acpi_fujitsu_driver);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static void __exit fujitsu_module_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+}
+
+module_init(fujitsu_module_init);
+module_exit(fujitsu_module_exit);
+
+MODULE_AUTHOR("Robert Gerlach <khnz@gmx.de>");
+MODULE_DESCRIPTION("Fujitsu tablet pc extras driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("2.5");
+
+MODULE_DEVICE_TABLE(acpi, fujitsu_ids);
diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c
new file mode 100644
index 0000000..458e6c9
--- /dev/null
+++ b/drivers/platform/x86/hdaps.c
@@ -0,0 +1,641 @@
+/*
+ * hdaps.c - driver for IBM's Hard Drive Active Protection System
+ *
+ * Copyright (C) 2005 Robert Love <rml@novell.com>
+ * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net>
+ *
+ * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
+ * starting with the R40, T41, and X40.  It provides a basic two-axis
+ * accelerometer and other data, such as the device's temperature.
+ *
+ * This driver is based on the document by Mark A. Smith available at
+ * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
+ * and error.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License v2 as published 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/input-polldev.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+
+#define HDAPS_LOW_PORT		0x1600	/* first port used by hdaps */
+#define HDAPS_NR_PORTS		0x30	/* number of ports: 0x1600 - 0x162f */
+
+#define HDAPS_PORT_STATE	0x1611	/* device state */
+#define HDAPS_PORT_YPOS		0x1612	/* y-axis position */
+#define	HDAPS_PORT_XPOS		0x1614	/* x-axis position */
+#define HDAPS_PORT_TEMP1	0x1616	/* device temperature, in Celsius */
+#define HDAPS_PORT_YVAR		0x1617	/* y-axis variance (what is this?) */
+#define HDAPS_PORT_XVAR		0x1619	/* x-axis variance (what is this?) */
+#define HDAPS_PORT_TEMP2	0x161b	/* device temperature (again?) */
+#define HDAPS_PORT_UNKNOWN	0x161c	/* what is this? */
+#define HDAPS_PORT_KMACT	0x161d	/* keyboard or mouse activity */
+
+#define STATE_FRESH		0x50	/* accelerometer data is fresh */
+
+#define KEYBD_MASK		0x20	/* set if keyboard activity */
+#define MOUSE_MASK		0x40	/* set if mouse activity */
+#define KEYBD_ISSET(n)		(!! (n & KEYBD_MASK))	/* keyboard used? */
+#define MOUSE_ISSET(n)		(!! (n & MOUSE_MASK))	/* mouse used? */
+
+#define INIT_TIMEOUT_MSECS	4000	/* wait up to 4s for device init ... */
+#define INIT_WAIT_MSECS		200	/* ... in 200ms increments */
+
+#define HDAPS_POLL_INTERVAL	50	/* poll for input every 1/20s (50 ms)*/
+#define HDAPS_INPUT_FUZZ	4	/* input event threshold */
+#define HDAPS_INPUT_FLAT	4
+
+#define HDAPS_X_AXIS		(1 << 0)
+#define HDAPS_Y_AXIS		(1 << 1)
+#define HDAPS_BOTH_AXES		(HDAPS_X_AXIS | HDAPS_Y_AXIS)
+
+static struct platform_device *pdev;
+static struct input_polled_dev *hdaps_idev;
+static unsigned int hdaps_invert;
+static u8 km_activity;
+static int rest_x;
+static int rest_y;
+
+static DEFINE_MUTEX(hdaps_mtx);
+
+/*
+ * __get_latch - Get the value from a given port.  Callers must hold hdaps_mtx.
+ */
+static inline u8 __get_latch(u16 port)
+{
+	return inb(port) & 0xff;
+}
+
+/*
+ * __check_latch - Check a port latch for a given value.  Returns zero if the
+ * port contains the given value.  Callers must hold hdaps_mtx.
+ */
+static inline int __check_latch(u16 port, u8 val)
+{
+	if (__get_latch(port) == val)
+		return 0;
+	return -EINVAL;
+}
+
+/*
+ * __wait_latch - Wait up to 100us for a port latch to get a certain value,
+ * returning zero if the value is obtained.  Callers must hold hdaps_mtx.
+ */
+static int __wait_latch(u16 port, u8 val)
+{
+	unsigned int i;
+
+	for (i = 0; i < 20; i++) {
+		if (!__check_latch(port, val))
+			return 0;
+		udelay(5);
+	}
+
+	return -EIO;
+}
+
+/*
+ * __device_refresh - request a refresh from the accelerometer.  Does not wait
+ * for refresh to complete.  Callers must hold hdaps_mtx.
+ */
+static void __device_refresh(void)
+{
+	udelay(200);
+	if (inb(0x1604) != STATE_FRESH) {
+		outb(0x11, 0x1610);
+		outb(0x01, 0x161f);
+	}
+}
+
+/*
+ * __device_refresh_sync - request a synchronous refresh from the
+ * accelerometer.  We wait for the refresh to complete.  Returns zero if
+ * successful and nonzero on error.  Callers must hold hdaps_mtx.
+ */
+static int __device_refresh_sync(void)
+{
+	__device_refresh();
+	return __wait_latch(0x1604, STATE_FRESH);
+}
+
+/*
+ * __device_complete - indicate to the accelerometer that we are done reading
+ * data, and then initiate an async refresh.  Callers must hold hdaps_mtx.
+ */
+static inline void __device_complete(void)
+{
+	inb(0x161f);
+	inb(0x1604);
+	__device_refresh();
+}
+
+/*
+ * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
+ * the given pointer.  Returns zero on success or a negative error on failure.
+ * Can sleep.
+ */
+static int hdaps_readb_one(unsigned int port, u8 *val)
+{
+	int ret;
+
+	mutex_lock(&hdaps_mtx);
+
+	/* do a sync refresh -- we need to be sure that we read fresh data */
+	ret = __device_refresh_sync();
+	if (ret)
+		goto out;
+
+	*val = inb(port);
+	__device_complete();
+
+out:
+	mutex_unlock(&hdaps_mtx);
+	return ret;
+}
+
+/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
+static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
+			     int *x, int *y)
+{
+	/* do a sync refresh -- we need to be sure that we read fresh data */
+	if (__device_refresh_sync())
+		return -EIO;
+
+	*y = inw(port2);
+	*x = inw(port1);
+	km_activity = inb(HDAPS_PORT_KMACT);
+	__device_complete();
+
+	/* hdaps_invert is a bitvector to negate the axes */
+	if (hdaps_invert & HDAPS_X_AXIS)
+		*x = -*x;
+	if (hdaps_invert & HDAPS_Y_AXIS)
+		*y = -*y;
+
+	return 0;
+}
+
+/*
+ * hdaps_read_pair - reads the values from a pair of ports, placing the values
+ * in the given pointers.  Returns zero on success.  Can sleep.
+ */
+static int hdaps_read_pair(unsigned int port1, unsigned int port2,
+			   int *val1, int *val2)
+{
+	int ret;
+
+	mutex_lock(&hdaps_mtx);
+	ret = __hdaps_read_pair(port1, port2, val1, val2);
+	mutex_unlock(&hdaps_mtx);
+
+	return ret;
+}
+
+/*
+ * hdaps_device_init - initialize the accelerometer.  Returns zero on success
+ * and negative error code on failure.  Can sleep.
+ */
+static int hdaps_device_init(void)
+{
+	int total, ret = -ENXIO;
+
+	mutex_lock(&hdaps_mtx);
+
+	outb(0x13, 0x1610);
+	outb(0x01, 0x161f);
+	if (__wait_latch(0x161f, 0x00))
+		goto out;
+
+	/*
+	 * Most ThinkPads return 0x01.
+	 *
+	 * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
+	 * have "inverted" axises.
+	 *
+	 * The 0x02 value occurs when the chip has been previously initialized.
+	 */
+	if (__check_latch(0x1611, 0x03) &&
+		     __check_latch(0x1611, 0x02) &&
+		     __check_latch(0x1611, 0x01))
+		goto out;
+
+	printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
+	       __get_latch(0x1611));
+
+	outb(0x17, 0x1610);
+	outb(0x81, 0x1611);
+	outb(0x01, 0x161f);
+	if (__wait_latch(0x161f, 0x00))
+		goto out;
+	if (__wait_latch(0x1611, 0x00))
+		goto out;
+	if (__wait_latch(0x1612, 0x60))
+		goto out;
+	if (__wait_latch(0x1613, 0x00))
+		goto out;
+	outb(0x14, 0x1610);
+	outb(0x01, 0x1611);
+	outb(0x01, 0x161f);
+	if (__wait_latch(0x161f, 0x00))
+		goto out;
+	outb(0x10, 0x1610);
+	outb(0xc8, 0x1611);
+	outb(0x00, 0x1612);
+	outb(0x02, 0x1613);
+	outb(0x01, 0x161f);
+	if (__wait_latch(0x161f, 0x00))
+		goto out;
+	if (__device_refresh_sync())
+		goto out;
+	if (__wait_latch(0x1611, 0x00))
+		goto out;
+
+	/* we have done our dance, now let's wait for the applause */
+	for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
+		int x, y;
+
+		/* a read of the device helps push it into action */
+		__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
+		if (!__wait_latch(0x1611, 0x02)) {
+			ret = 0;
+			break;
+		}
+
+		msleep(INIT_WAIT_MSECS);
+	}
+
+out:
+	mutex_unlock(&hdaps_mtx);
+	return ret;
+}
+
+
+/* Device model stuff */
+
+static int hdaps_probe(struct platform_device *dev)
+{
+	int ret;
+
+	ret = hdaps_device_init();
+	if (ret)
+		return ret;
+
+	pr_info("device successfully initialized\n");
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hdaps_resume(struct device *dev)
+{
+	return hdaps_device_init();
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
+
+static struct platform_driver hdaps_driver = {
+	.probe = hdaps_probe,
+	.driver	= {
+		.name = "hdaps",
+		.pm = &hdaps_pm,
+	},
+};
+
+/*
+ * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
+ */
+static void hdaps_calibrate(void)
+{
+	__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
+}
+
+static void hdaps_mousedev_poll(struct input_polled_dev *dev)
+{
+	struct input_dev *input_dev = dev->input;
+	int x, y;
+
+	mutex_lock(&hdaps_mtx);
+
+	if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
+		goto out;
+
+	input_report_abs(input_dev, ABS_X, x - rest_x);
+	input_report_abs(input_dev, ABS_Y, y - rest_y);
+	input_sync(input_dev);
+
+out:
+	mutex_unlock(&hdaps_mtx);
+}
+
+
+/* Sysfs Files */
+
+static ssize_t hdaps_position_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int ret, x, y;
+
+	ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "(%d,%d)\n", x, y);
+}
+
+static ssize_t hdaps_variance_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int ret, x, y;
+
+	ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "(%d,%d)\n", x, y);
+}
+
+static ssize_t hdaps_temp1_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	u8 uninitialized_var(temp);
+	int ret;
+
+	ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", temp);
+}
+
+static ssize_t hdaps_temp2_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	u8 uninitialized_var(temp);
+	int ret;
+
+	ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", temp);
+}
+
+static ssize_t hdaps_keyboard_activity_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
+}
+
+static ssize_t hdaps_mouse_activity_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
+}
+
+static ssize_t hdaps_calibrate_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
+}
+
+static ssize_t hdaps_calibrate_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	mutex_lock(&hdaps_mtx);
+	hdaps_calibrate();
+	mutex_unlock(&hdaps_mtx);
+
+	return count;
+}
+
+static ssize_t hdaps_invert_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", hdaps_invert);
+}
+
+static ssize_t hdaps_invert_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	int invert;
+
+	if (sscanf(buf, "%d", &invert) != 1 ||
+	    invert < 0 || invert > HDAPS_BOTH_AXES)
+		return -EINVAL;
+
+	hdaps_invert = invert;
+	hdaps_calibrate();
+
+	return count;
+}
+
+static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
+static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
+static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
+static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
+static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
+static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
+static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
+static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
+
+static struct attribute *hdaps_attributes[] = {
+	&dev_attr_position.attr,
+	&dev_attr_variance.attr,
+	&dev_attr_temp1.attr,
+	&dev_attr_temp2.attr,
+	&dev_attr_keyboard_activity.attr,
+	&dev_attr_mouse_activity.attr,
+	&dev_attr_calibrate.attr,
+	&dev_attr_invert.attr,
+	NULL,
+};
+
+static struct attribute_group hdaps_attribute_group = {
+	.attrs = hdaps_attributes,
+};
+
+
+/* Module stuff */
+
+/* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
+static int __init hdaps_dmi_match(const struct dmi_system_id *id)
+{
+	pr_info("%s detected\n", id->ident);
+	return 1;
+}
+
+/* hdaps_dmi_match_invert - found an inverted match. */
+static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
+{
+	hdaps_invert = (unsigned long)id->driver_data;
+	pr_info("inverting axis (%u) readings\n", hdaps_invert);
+	return hdaps_dmi_match(id);
+}
+
+#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) {	\
+	.ident = vendor " " model,			\
+	.callback = hdaps_dmi_match_invert,		\
+	.driver_data = (void *)axes,			\
+	.matches = {					\
+		DMI_MATCH(DMI_BOARD_VENDOR, vendor),	\
+		DMI_MATCH(DMI_PRODUCT_VERSION, model)	\
+	}						\
+}
+
+#define HDAPS_DMI_MATCH_NORMAL(vendor, model)		\
+	HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
+
+/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
+   "ThinkPad T42p", so the order of the entries matters.
+   If your ThinkPad is not recognized, please update to latest
+   BIOS. This is especially the case for some R52 ThinkPads. */
+static struct dmi_system_id __initdata hdaps_whitelist[] = {
+	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
+	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
+	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
+	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
+	{ .ident = NULL }
+};
+
+static int __init hdaps_init(void)
+{
+	struct input_dev *idev;
+	int ret;
+
+	if (!dmi_check_system(hdaps_whitelist)) {
+		pr_warn("supported laptop not found!\n");
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
+		ret = -ENXIO;
+		goto out;
+	}
+
+	ret = platform_driver_register(&hdaps_driver);
+	if (ret)
+		goto out_region;
+
+	pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
+	if (IS_ERR(pdev)) {
+		ret = PTR_ERR(pdev);
+		goto out_driver;
+	}
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
+	if (ret)
+		goto out_device;
+
+	hdaps_idev = input_allocate_polled_device();
+	if (!hdaps_idev) {
+		ret = -ENOMEM;
+		goto out_group;
+	}
+
+	hdaps_idev->poll = hdaps_mousedev_poll;
+	hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
+
+	/* initial calibrate for the input device */
+	hdaps_calibrate();
+
+	/* initialize the input class */
+	idev = hdaps_idev->input;
+	idev->name = "hdaps";
+	idev->phys = "isa1600/input0";
+	idev->id.bustype = BUS_ISA;
+	idev->dev.parent = &pdev->dev;
+	idev->evbit[0] = BIT_MASK(EV_ABS);
+	input_set_abs_params(idev, ABS_X,
+			-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
+	input_set_abs_params(idev, ABS_Y,
+			-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
+
+	ret = input_register_polled_device(hdaps_idev);
+	if (ret)
+		goto out_idev;
+
+	pr_info("driver successfully loaded\n");
+	return 0;
+
+out_idev:
+	input_free_polled_device(hdaps_idev);
+out_group:
+	sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
+out_device:
+	platform_device_unregister(pdev);
+out_driver:
+	platform_driver_unregister(&hdaps_driver);
+out_region:
+	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
+out:
+	pr_warn("driver init failed (ret=%d)!\n", ret);
+	return ret;
+}
+
+static void __exit hdaps_exit(void)
+{
+	input_unregister_polled_device(hdaps_idev);
+	input_free_polled_device(hdaps_idev);
+	sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
+	platform_device_unregister(pdev);
+	platform_driver_unregister(&hdaps_driver);
+	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
+
+	pr_info("driver unloaded\n");
+}
+
+module_init(hdaps_init);
+module_exit(hdaps_exit);
+
+module_param_named(invert, hdaps_invert, int, 0);
+MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
+		 "2 invert y-axis, 3 invert both axes.");
+
+MODULE_AUTHOR("Robert Love");
+MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/hp-wireless.c b/drivers/platform/x86/hp-wireless.c
new file mode 100644
index 0000000..988eedb
--- /dev/null
+++ b/drivers/platform/x86/hp-wireless.c
@@ -0,0 +1,130 @@
+/*
+ *  hp-wireless button for Windows 8
+ *
+ *  Copyright (C) 2014 Alex Hung <alex.hung@canonical.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.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alex Hung");
+MODULE_ALIAS("acpi*:HPQ6001:*");
+
+static struct input_dev *hpwl_input_dev;
+
+static const struct acpi_device_id hpwl_ids[] = {
+	{"HPQ6001", 0},
+	{"", 0},
+};
+
+static int hp_wireless_input_setup(void)
+{
+	int err;
+
+	hpwl_input_dev = input_allocate_device();
+	if (!hpwl_input_dev)
+		return -ENOMEM;
+
+	hpwl_input_dev->name = "HP Wireless hotkeys";
+	hpwl_input_dev->phys = "hpq6001/input0";
+	hpwl_input_dev->id.bustype = BUS_HOST;
+	hpwl_input_dev->evbit[0] = BIT(EV_KEY);
+	set_bit(KEY_RFKILL, hpwl_input_dev->keybit);
+
+	err = input_register_device(hpwl_input_dev);
+	if (err)
+		goto err_free_dev;
+
+	return 0;
+
+err_free_dev:
+	input_free_device(hpwl_input_dev);
+	return err;
+}
+
+static void hp_wireless_input_destroy(void)
+{
+	input_unregister_device(hpwl_input_dev);
+}
+
+static void hpwl_notify(struct acpi_device *acpi_dev, u32 event)
+{
+	if (event != 0x80) {
+		pr_info("Received unknown event (0x%x)\n", event);
+		return;
+	}
+
+	input_report_key(hpwl_input_dev, KEY_RFKILL, 1);
+	input_sync(hpwl_input_dev);
+	input_report_key(hpwl_input_dev, KEY_RFKILL, 0);
+	input_sync(hpwl_input_dev);
+}
+
+static int hpwl_add(struct acpi_device *device)
+{
+	int err;
+
+	err = hp_wireless_input_setup();
+	if (err)
+		pr_err("Failed to setup hp wireless hotkeys\n");
+
+	return err;
+}
+
+static int hpwl_remove(struct acpi_device *device)
+{
+	hp_wireless_input_destroy();
+	return 0;
+}
+
+static struct acpi_driver hpwl_driver = {
+	.name	= "hp-wireless",
+	.owner	= THIS_MODULE,
+	.ids	= hpwl_ids,
+	.ops	= {
+		.add	= hpwl_add,
+		.remove	= hpwl_remove,
+		.notify	= hpwl_notify,
+	},
+};
+
+static int __init hpwl_init(void)
+{
+	int err;
+
+	pr_info("Initializing HPQ6001 module\n");
+	err = acpi_bus_register_driver(&hpwl_driver);
+	if (err)
+		pr_err("Unable to register HP wireless control driver.\n");
+
+	return err;
+}
+
+static void __exit hpwl_exit(void)
+{
+	pr_info("Exiting HPQ6001 module\n");
+	acpi_bus_unregister_driver(&hpwl_driver);
+}
+
+module_init(hpwl_init);
+module_exit(hpwl_exit);
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
new file mode 100644
index 0000000..847f756
--- /dev/null
+++ b/drivers/platform/x86/hp-wmi.c
@@ -0,0 +1,1092 @@
+/*
+ * HP WMI hotkeys
+ *
+ * Copyright (C) 2008 Red Hat <mjg@redhat.com>
+ * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/string.h>
+
+MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
+MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
+MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
+
+#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
+#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
+
+#define HPWMI_DISPLAY_QUERY 0x1
+#define HPWMI_HDDTEMP_QUERY 0x2
+#define HPWMI_ALS_QUERY 0x3
+#define HPWMI_HARDWARE_QUERY 0x4
+#define HPWMI_WIRELESS_QUERY 0x5
+#define HPWMI_BIOS_QUERY 0x9
+#define HPWMI_FEATURE_QUERY 0xb
+#define HPWMI_HOTKEY_QUERY 0xc
+#define HPWMI_FEATURE2_QUERY 0xd
+#define HPWMI_WIRELESS2_QUERY 0x1b
+#define HPWMI_POSTCODEERROR_QUERY 0x2a
+
+enum hp_wmi_radio {
+	HPWMI_WIFI = 0,
+	HPWMI_BLUETOOTH = 1,
+	HPWMI_WWAN = 2,
+	HPWMI_GPS = 3,
+};
+
+enum hp_wmi_event_ids {
+	HPWMI_DOCK_EVENT = 1,
+	HPWMI_PARK_HDD = 2,
+	HPWMI_SMART_ADAPTER = 3,
+	HPWMI_BEZEL_BUTTON = 4,
+	HPWMI_WIRELESS = 5,
+	HPWMI_CPU_BATTERY_THROTTLE = 6,
+	HPWMI_LOCK_SWITCH = 7,
+	HPWMI_LID_SWITCH = 8,
+	HPWMI_SCREEN_ROTATION = 9,
+	HPWMI_COOLSENSE_SYSTEM_MOBILE = 0x0A,
+	HPWMI_COOLSENSE_SYSTEM_HOT = 0x0B,
+	HPWMI_PROXIMITY_SENSOR = 0x0C,
+	HPWMI_BACKLIT_KB_BRIGHTNESS = 0x0D,
+	HPWMI_PEAKSHIFT_PERIOD = 0x0F,
+	HPWMI_BATTERY_CHARGE_PERIOD = 0x10,
+};
+
+struct bios_args {
+	u32 signature;
+	u32 command;
+	u32 commandtype;
+	u32 datasize;
+	u32 data;
+};
+
+struct bios_return {
+	u32 sigpass;
+	u32 return_code;
+};
+
+enum hp_return_value {
+	HPWMI_RET_WRONG_SIGNATURE	= 0x02,
+	HPWMI_RET_UNKNOWN_COMMAND	= 0x03,
+	HPWMI_RET_UNKNOWN_CMDTYPE	= 0x04,
+	HPWMI_RET_INVALID_PARAMETERS	= 0x05,
+};
+
+enum hp_wireless2_bits {
+	HPWMI_POWER_STATE	= 0x01,
+	HPWMI_POWER_SOFT	= 0x02,
+	HPWMI_POWER_BIOS	= 0x04,
+	HPWMI_POWER_HARD	= 0x08,
+};
+
+#define IS_HWBLOCKED(x) ((x & (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) \
+			 != (HPWMI_POWER_BIOS | HPWMI_POWER_HARD))
+#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
+
+struct bios_rfkill2_device_state {
+	u8 radio_type;
+	u8 bus_type;
+	u16 vendor_id;
+	u16 product_id;
+	u16 subsys_vendor_id;
+	u16 subsys_product_id;
+	u8 rfkill_id;
+	u8 power;
+	u8 unknown[4];
+};
+
+/* 7 devices fit into the 128 byte buffer */
+#define HPWMI_MAX_RFKILL2_DEVICES	7
+
+struct bios_rfkill2_state {
+	u8 unknown[7];
+	u8 count;
+	u8 pad[8];
+	struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES];
+};
+
+static const struct key_entry hp_wmi_keymap[] = {
+	{ KE_KEY, 0x02,   { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0x03,   { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 0x20e6, { KEY_PROG1 } },
+	{ KE_KEY, 0x20e8, { KEY_MEDIA } },
+	{ KE_KEY, 0x2142, { KEY_MEDIA } },
+	{ KE_KEY, 0x213b, { KEY_INFO } },
+	{ KE_KEY, 0x2169, { KEY_ROTATE_DISPLAY } },
+	{ KE_KEY, 0x216a, { KEY_SETUP } },
+	{ KE_KEY, 0x231b, { KEY_HELP } },
+	{ KE_END, 0 }
+};
+
+static struct input_dev *hp_wmi_input_dev;
+static struct platform_device *hp_wmi_platform_dev;
+
+static struct rfkill *wifi_rfkill;
+static struct rfkill *bluetooth_rfkill;
+static struct rfkill *wwan_rfkill;
+static struct rfkill *gps_rfkill;
+
+struct rfkill2_device {
+	u8 id;
+	int num;
+	struct rfkill *rfkill;
+};
+
+static int rfkill2_count;
+static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
+
+/*
+ * hp_wmi_perform_query
+ *
+ * query:	The commandtype -> What should be queried
+ * write:	The command -> 0 read, 1 write, 3 ODM specific
+ * buffer:	Buffer used as input and/or output
+ * insize:	Size of input buffer
+ * outsize:	Size of output buffer
+ *
+ * returns zero on success
+ *         an HP WMI query specific error code (which is positive)
+ *         -EINVAL if the query was not successful at all
+ *         -EINVAL if the output buffer size exceeds buffersize
+ *
+ * Note: The buffersize must at least be the maximum of the input and output
+ *       size. E.g. Battery info query (0x7) is defined to have 1 byte input
+ *       and 128 byte output. The caller would do:
+ *       buffer = kzalloc(128, GFP_KERNEL);
+ *       ret = hp_wmi_perform_query(0x7, 0, buffer, 1, 128)
+ */
+static int hp_wmi_perform_query(int query, int write, void *buffer,
+				int insize, int outsize)
+{
+	struct bios_return *bios_return;
+	int actual_outsize;
+	union acpi_object *obj;
+	struct bios_args args = {
+		.signature = 0x55434553,
+		.command = write ? 0x2 : 0x1,
+		.commandtype = query,
+		.datasize = insize,
+		.data = 0,
+	};
+	struct acpi_buffer input = { sizeof(struct bios_args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	u32 rc;
+
+	if (WARN_ON(insize > sizeof(args.data)))
+		return -EINVAL;
+	memcpy(&args.data, buffer, insize);
+
+	wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
+
+	obj = output.pointer;
+
+	if (!obj)
+		return -EINVAL;
+	else if (obj->type != ACPI_TYPE_BUFFER) {
+		kfree(obj);
+		return -EINVAL;
+	}
+
+	bios_return = (struct bios_return *)obj->buffer.pointer;
+	rc = bios_return->return_code;
+
+	if (rc) {
+		if (rc != HPWMI_RET_UNKNOWN_CMDTYPE)
+			pr_warn("query 0x%x returned error 0x%x\n", query, rc);
+		kfree(obj);
+		return rc;
+	}
+
+	if (!outsize) {
+		/* ignore output data */
+		kfree(obj);
+		return 0;
+	}
+
+	actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
+	memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
+	memset(buffer + actual_outsize, 0, outsize - actual_outsize);
+	kfree(obj);
+	return 0;
+}
+
+static int hp_wmi_display_state(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+	return state;
+}
+
+static int hp_wmi_hddtemp_state(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+	return state;
+}
+
+static int hp_wmi_als_state(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+	return state;
+}
+
+static int hp_wmi_dock_state(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+
+	return state & 0x1;
+}
+
+static int hp_wmi_tablet_state(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+
+	return (state & 0x4) ? 1 : 0;
+}
+
+static int __init hp_wmi_bios_2008_later(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (!ret)
+		return 1;
+
+	return (ret == HPWMI_RET_UNKNOWN_CMDTYPE) ? 0 : -ENXIO;
+}
+
+static int __init hp_wmi_bios_2009_later(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (!ret)
+		return 1;
+
+	return (ret == HPWMI_RET_UNKNOWN_CMDTYPE) ? 0 : -ENXIO;
+}
+
+static int __init hp_wmi_enable_hotkeys(void)
+{
+	int value = 0x6e;
+	int ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, 1, &value,
+				       sizeof(value), 0);
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+	return 0;
+}
+
+static int hp_wmi_set_block(void *data, bool blocked)
+{
+	enum hp_wmi_radio r = (enum hp_wmi_radio) data;
+	int query = BIT(r + 8) | ((!blocked) << r);
+	int ret;
+
+	ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1,
+				   &query, sizeof(query), 0);
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+	return 0;
+}
+
+static const struct rfkill_ops hp_wmi_rfkill_ops = {
+	.set_block = hp_wmi_set_block,
+};
+
+static bool hp_wmi_get_sw_state(enum hp_wmi_radio r)
+{
+	int wireless = 0;
+	int mask;
+	hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0,
+			     &wireless, sizeof(wireless),
+			     sizeof(wireless));
+	/* TBD: Pass error */
+
+	mask = 0x200 << (r * 8);
+
+	if (wireless & mask)
+		return false;
+	else
+		return true;
+}
+
+static bool hp_wmi_get_hw_state(enum hp_wmi_radio r)
+{
+	int wireless = 0;
+	int mask;
+	hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0,
+			     &wireless, sizeof(wireless),
+			     sizeof(wireless));
+	/* TBD: Pass error */
+
+	mask = 0x800 << (r * 8);
+
+	if (wireless & mask)
+		return false;
+	else
+		return true;
+}
+
+static int hp_wmi_rfkill2_set_block(void *data, bool blocked)
+{
+	int rfkill_id = (int)(long)data;
+	char buffer[4] = { 0x01, 0x00, rfkill_id, !blocked };
+
+	if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1,
+				   buffer, sizeof(buffer), 0))
+		return -EINVAL;
+	return 0;
+}
+
+static const struct rfkill_ops hp_wmi_rfkill2_ops = {
+	.set_block = hp_wmi_rfkill2_set_block,
+};
+
+static int hp_wmi_rfkill2_refresh(void)
+{
+	int err, i;
+	struct bios_rfkill2_state state;
+
+	err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state,
+				   0, sizeof(state));
+	if (err)
+		return err;
+
+	for (i = 0; i < rfkill2_count; i++) {
+		int num = rfkill2[i].num;
+		struct bios_rfkill2_device_state *devstate;
+		devstate = &state.device[num];
+
+		if (num >= state.count ||
+		    devstate->rfkill_id != rfkill2[i].id) {
+			pr_warn("power configuration of the wireless devices unexpectedly changed\n");
+			continue;
+		}
+
+		rfkill_set_states(rfkill2[i].rfkill,
+				  IS_SWBLOCKED(devstate->power),
+				  IS_HWBLOCKED(devstate->power));
+	}
+
+	return 0;
+}
+
+static int hp_wmi_post_code_state(void)
+{
+	int state = 0;
+	int ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 0, &state,
+				       sizeof(state), sizeof(state));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+	return state;
+}
+
+static ssize_t show_display(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	int value = hp_wmi_display_state();
+	if (value < 0)
+		return -EINVAL;
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	int value = hp_wmi_hddtemp_state();
+	if (value < 0)
+		return -EINVAL;
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_als(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	int value = hp_wmi_als_state();
+	if (value < 0)
+		return -EINVAL;
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_dock(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	int value = hp_wmi_dock_state();
+	if (value < 0)
+		return -EINVAL;
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_tablet(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	int value = hp_wmi_tablet_state();
+	if (value < 0)
+		return -EINVAL;
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_postcode(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	/* Get the POST error code of previous boot failure. */
+	int value = hp_wmi_post_code_state();
+	if (value < 0)
+		return -EINVAL;
+	return sprintf(buf, "0x%x\n", value);
+}
+
+static ssize_t set_als(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+	u32 tmp = simple_strtoul(buf, NULL, 10);
+	int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp,
+				       sizeof(tmp), sizeof(tmp));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+
+	return count;
+}
+
+static ssize_t set_postcode(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+	int ret;
+	u32 tmp;
+	long unsigned int tmp2;
+
+	ret = kstrtoul(buf, 10, &tmp2);
+	if (ret || tmp2 != 1)
+		return -EINVAL;
+
+	/* Clear the POST error code. It is kept until until cleared. */
+	tmp = (u32) tmp2;
+	ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 1, &tmp,
+				       sizeof(tmp), sizeof(tmp));
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+
+	return count;
+}
+
+static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);
+static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);
+static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);
+static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL);
+static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL);
+static DEVICE_ATTR(postcode, S_IRUGO | S_IWUSR, show_postcode, set_postcode);
+
+static void hp_wmi_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	u32 event_id, event_data;
+	int key_code = 0, ret;
+	u32 *location;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_info("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+
+	if (!obj)
+		return;
+	if (obj->type != ACPI_TYPE_BUFFER) {
+		pr_info("Unknown response received %d\n", obj->type);
+		kfree(obj);
+		return;
+	}
+
+	/*
+	 * Depending on ACPI version the concatenation of id and event data
+	 * inside _WED function will result in a 8 or 16 byte buffer.
+	 */
+	location = (u32 *)obj->buffer.pointer;
+	if (obj->buffer.length == 8) {
+		event_id = *location;
+		event_data = *(location + 1);
+	} else if (obj->buffer.length == 16) {
+		event_id = *location;
+		event_data = *(location + 2);
+	} else {
+		pr_info("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return;
+	}
+	kfree(obj);
+
+	switch (event_id) {
+	case HPWMI_DOCK_EVENT:
+		if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit))
+			input_report_switch(hp_wmi_input_dev, SW_DOCK,
+					    hp_wmi_dock_state());
+		if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit))
+			input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
+					    hp_wmi_tablet_state());
+		input_sync(hp_wmi_input_dev);
+		break;
+	case HPWMI_PARK_HDD:
+		break;
+	case HPWMI_SMART_ADAPTER:
+		break;
+	case HPWMI_BEZEL_BUTTON:
+		ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0,
+					   &key_code,
+					   sizeof(key_code),
+					   sizeof(key_code));
+		if (ret)
+			break;
+
+		if (!sparse_keymap_report_event(hp_wmi_input_dev,
+						key_code, 1, true))
+			pr_info("Unknown key code - 0x%x\n", key_code);
+		break;
+	case HPWMI_WIRELESS:
+		if (rfkill2_count) {
+			hp_wmi_rfkill2_refresh();
+			break;
+		}
+
+		if (wifi_rfkill)
+			rfkill_set_states(wifi_rfkill,
+					  hp_wmi_get_sw_state(HPWMI_WIFI),
+					  hp_wmi_get_hw_state(HPWMI_WIFI));
+		if (bluetooth_rfkill)
+			rfkill_set_states(bluetooth_rfkill,
+					  hp_wmi_get_sw_state(HPWMI_BLUETOOTH),
+					  hp_wmi_get_hw_state(HPWMI_BLUETOOTH));
+		if (wwan_rfkill)
+			rfkill_set_states(wwan_rfkill,
+					  hp_wmi_get_sw_state(HPWMI_WWAN),
+					  hp_wmi_get_hw_state(HPWMI_WWAN));
+		if (gps_rfkill)
+			rfkill_set_states(gps_rfkill,
+					  hp_wmi_get_sw_state(HPWMI_GPS),
+					  hp_wmi_get_hw_state(HPWMI_GPS));
+		break;
+	case HPWMI_CPU_BATTERY_THROTTLE:
+		pr_info("Unimplemented CPU throttle because of 3 Cell battery event detected\n");
+		break;
+	case HPWMI_LOCK_SWITCH:
+		break;
+	case HPWMI_LID_SWITCH:
+		break;
+	case HPWMI_SCREEN_ROTATION:
+		break;
+	case HPWMI_COOLSENSE_SYSTEM_MOBILE:
+		break;
+	case HPWMI_COOLSENSE_SYSTEM_HOT:
+		break;
+	case HPWMI_PROXIMITY_SENSOR:
+		break;
+	case HPWMI_BACKLIT_KB_BRIGHTNESS:
+		break;
+	case HPWMI_PEAKSHIFT_PERIOD:
+		break;
+	case HPWMI_BATTERY_CHARGE_PERIOD:
+		break;
+	default:
+		pr_info("Unknown event_id - %d - 0x%x\n", event_id, event_data);
+		break;
+	}
+}
+
+static int __init hp_wmi_input_setup(void)
+{
+	acpi_status status;
+	int err;
+	int val;
+
+	hp_wmi_input_dev = input_allocate_device();
+	if (!hp_wmi_input_dev)
+		return -ENOMEM;
+
+	hp_wmi_input_dev->name = "HP WMI hotkeys";
+	hp_wmi_input_dev->phys = "wmi/input0";
+	hp_wmi_input_dev->id.bustype = BUS_HOST;
+
+	__set_bit(EV_SW, hp_wmi_input_dev->evbit);
+
+	/* Dock */
+	val = hp_wmi_dock_state();
+	if (!(val < 0)) {
+		__set_bit(SW_DOCK, hp_wmi_input_dev->swbit);
+		input_report_switch(hp_wmi_input_dev, SW_DOCK, val);
+	}
+
+	/* Tablet mode */
+	val = hp_wmi_tablet_state();
+	if (!(val < 0)) {
+		__set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
+		input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val);
+	}
+
+	err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL);
+	if (err)
+		goto err_free_dev;
+
+	/* Set initial hardware state */
+	input_sync(hp_wmi_input_dev);
+
+	if (!hp_wmi_bios_2009_later() && hp_wmi_bios_2008_later())
+		hp_wmi_enable_hotkeys();
+
+	status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL);
+	if (ACPI_FAILURE(status)) {
+		err = -EIO;
+		goto err_free_keymap;
+	}
+
+	err = input_register_device(hp_wmi_input_dev);
+	if (err)
+		goto err_uninstall_notifier;
+
+	return 0;
+
+ err_uninstall_notifier:
+	wmi_remove_notify_handler(HPWMI_EVENT_GUID);
+ err_free_keymap:
+	sparse_keymap_free(hp_wmi_input_dev);
+ err_free_dev:
+	input_free_device(hp_wmi_input_dev);
+	return err;
+}
+
+static void hp_wmi_input_destroy(void)
+{
+	wmi_remove_notify_handler(HPWMI_EVENT_GUID);
+	sparse_keymap_free(hp_wmi_input_dev);
+	input_unregister_device(hp_wmi_input_dev);
+}
+
+static void cleanup_sysfs(struct platform_device *device)
+{
+	device_remove_file(&device->dev, &dev_attr_display);
+	device_remove_file(&device->dev, &dev_attr_hddtemp);
+	device_remove_file(&device->dev, &dev_attr_als);
+	device_remove_file(&device->dev, &dev_attr_dock);
+	device_remove_file(&device->dev, &dev_attr_tablet);
+	device_remove_file(&device->dev, &dev_attr_postcode);
+}
+
+static int __init hp_wmi_rfkill_setup(struct platform_device *device)
+{
+	int err;
+	int wireless = 0;
+
+	err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless,
+				   sizeof(wireless), sizeof(wireless));
+	if (err)
+		return err;
+
+	err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, &wireless,
+				   sizeof(wireless), 0);
+	if (err)
+		return err;
+
+	if (wireless & 0x1) {
+		wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev,
+					   RFKILL_TYPE_WLAN,
+					   &hp_wmi_rfkill_ops,
+					   (void *) HPWMI_WIFI);
+		if (!wifi_rfkill)
+			return -ENOMEM;
+		rfkill_init_sw_state(wifi_rfkill,
+				     hp_wmi_get_sw_state(HPWMI_WIFI));
+		rfkill_set_hw_state(wifi_rfkill,
+				    hp_wmi_get_hw_state(HPWMI_WIFI));
+		err = rfkill_register(wifi_rfkill);
+		if (err)
+			goto register_wifi_error;
+	}
+
+	if (wireless & 0x2) {
+		bluetooth_rfkill = rfkill_alloc("hp-bluetooth", &device->dev,
+						RFKILL_TYPE_BLUETOOTH,
+						&hp_wmi_rfkill_ops,
+						(void *) HPWMI_BLUETOOTH);
+		if (!bluetooth_rfkill) {
+			err = -ENOMEM;
+			goto register_wifi_error;
+		}
+		rfkill_init_sw_state(bluetooth_rfkill,
+				     hp_wmi_get_sw_state(HPWMI_BLUETOOTH));
+		rfkill_set_hw_state(bluetooth_rfkill,
+				    hp_wmi_get_hw_state(HPWMI_BLUETOOTH));
+		err = rfkill_register(bluetooth_rfkill);
+		if (err)
+			goto register_bluetooth_error;
+	}
+
+	if (wireless & 0x4) {
+		wwan_rfkill = rfkill_alloc("hp-wwan", &device->dev,
+					   RFKILL_TYPE_WWAN,
+					   &hp_wmi_rfkill_ops,
+					   (void *) HPWMI_WWAN);
+		if (!wwan_rfkill) {
+			err = -ENOMEM;
+			goto register_bluetooth_error;
+		}
+		rfkill_init_sw_state(wwan_rfkill,
+				     hp_wmi_get_sw_state(HPWMI_WWAN));
+		rfkill_set_hw_state(wwan_rfkill,
+				    hp_wmi_get_hw_state(HPWMI_WWAN));
+		err = rfkill_register(wwan_rfkill);
+		if (err)
+			goto register_wwan_error;
+	}
+
+	if (wireless & 0x8) {
+		gps_rfkill = rfkill_alloc("hp-gps", &device->dev,
+						RFKILL_TYPE_GPS,
+						&hp_wmi_rfkill_ops,
+						(void *) HPWMI_GPS);
+		if (!gps_rfkill) {
+			err = -ENOMEM;
+			goto register_wwan_error;
+		}
+		rfkill_init_sw_state(gps_rfkill,
+				     hp_wmi_get_sw_state(HPWMI_GPS));
+		rfkill_set_hw_state(gps_rfkill,
+				    hp_wmi_get_hw_state(HPWMI_GPS));
+		err = rfkill_register(gps_rfkill);
+		if (err)
+			goto register_gps_error;
+	}
+
+	return 0;
+register_gps_error:
+	rfkill_destroy(gps_rfkill);
+	gps_rfkill = NULL;
+	if (bluetooth_rfkill)
+		rfkill_unregister(bluetooth_rfkill);
+register_wwan_error:
+	rfkill_destroy(wwan_rfkill);
+	wwan_rfkill = NULL;
+	if (gps_rfkill)
+		rfkill_unregister(gps_rfkill);
+register_bluetooth_error:
+	rfkill_destroy(bluetooth_rfkill);
+	bluetooth_rfkill = NULL;
+	if (wifi_rfkill)
+		rfkill_unregister(wifi_rfkill);
+register_wifi_error:
+	rfkill_destroy(wifi_rfkill);
+	wifi_rfkill = NULL;
+	return err;
+}
+
+static int __init hp_wmi_rfkill2_setup(struct platform_device *device)
+{
+	int err, i;
+	struct bios_rfkill2_state state;
+	err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state,
+				   0, sizeof(state));
+	if (err)
+		return err;
+
+	if (state.count > HPWMI_MAX_RFKILL2_DEVICES) {
+		pr_warn("unable to parse 0x1b query output\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < state.count; i++) {
+		struct rfkill *rfkill;
+		enum rfkill_type type;
+		char *name;
+		switch (state.device[i].radio_type) {
+		case HPWMI_WIFI:
+			type = RFKILL_TYPE_WLAN;
+			name = "hp-wifi";
+			break;
+		case HPWMI_BLUETOOTH:
+			type = RFKILL_TYPE_BLUETOOTH;
+			name = "hp-bluetooth";
+			break;
+		case HPWMI_WWAN:
+			type = RFKILL_TYPE_WWAN;
+			name = "hp-wwan";
+			break;
+		case HPWMI_GPS:
+			type = RFKILL_TYPE_GPS;
+			name = "hp-gps";
+			break;
+		default:
+			pr_warn("unknown device type 0x%x\n",
+				state.device[i].radio_type);
+			continue;
+		}
+
+		if (!state.device[i].vendor_id) {
+			pr_warn("zero device %d while %d reported\n",
+				i, state.count);
+			continue;
+		}
+
+		rfkill = rfkill_alloc(name, &device->dev, type,
+				      &hp_wmi_rfkill2_ops, (void *)(long)i);
+		if (!rfkill) {
+			err = -ENOMEM;
+			goto fail;
+		}
+
+		rfkill2[rfkill2_count].id = state.device[i].rfkill_id;
+		rfkill2[rfkill2_count].num = i;
+		rfkill2[rfkill2_count].rfkill = rfkill;
+
+		rfkill_init_sw_state(rfkill,
+				     IS_SWBLOCKED(state.device[i].power));
+		rfkill_set_hw_state(rfkill,
+				    IS_HWBLOCKED(state.device[i].power));
+
+		if (!(state.device[i].power & HPWMI_POWER_BIOS))
+			pr_info("device %s blocked by BIOS\n", name);
+
+		err = rfkill_register(rfkill);
+		if (err) {
+			rfkill_destroy(rfkill);
+			goto fail;
+		}
+
+		rfkill2_count++;
+	}
+
+	return 0;
+fail:
+	for (; rfkill2_count > 0; rfkill2_count--) {
+		rfkill_unregister(rfkill2[rfkill2_count - 1].rfkill);
+		rfkill_destroy(rfkill2[rfkill2_count - 1].rfkill);
+	}
+	return err;
+}
+
+static int __init hp_wmi_bios_setup(struct platform_device *device)
+{
+	int err;
+
+	/* clear detected rfkill devices */
+	wifi_rfkill = NULL;
+	bluetooth_rfkill = NULL;
+	wwan_rfkill = NULL;
+	gps_rfkill = NULL;
+	rfkill2_count = 0;
+
+	if (hp_wmi_rfkill_setup(device))
+		hp_wmi_rfkill2_setup(device);
+
+	err = device_create_file(&device->dev, &dev_attr_display);
+	if (err)
+		goto add_sysfs_error;
+	err = device_create_file(&device->dev, &dev_attr_hddtemp);
+	if (err)
+		goto add_sysfs_error;
+	err = device_create_file(&device->dev, &dev_attr_als);
+	if (err)
+		goto add_sysfs_error;
+	err = device_create_file(&device->dev, &dev_attr_dock);
+	if (err)
+		goto add_sysfs_error;
+	err = device_create_file(&device->dev, &dev_attr_tablet);
+	if (err)
+		goto add_sysfs_error;
+	err = device_create_file(&device->dev, &dev_attr_postcode);
+	if (err)
+		goto add_sysfs_error;
+	return 0;
+
+add_sysfs_error:
+	cleanup_sysfs(device);
+	return err;
+}
+
+static int __exit hp_wmi_bios_remove(struct platform_device *device)
+{
+	int i;
+	cleanup_sysfs(device);
+
+	for (i = 0; i < rfkill2_count; i++) {
+		rfkill_unregister(rfkill2[i].rfkill);
+		rfkill_destroy(rfkill2[i].rfkill);
+	}
+
+	if (wifi_rfkill) {
+		rfkill_unregister(wifi_rfkill);
+		rfkill_destroy(wifi_rfkill);
+	}
+	if (bluetooth_rfkill) {
+		rfkill_unregister(bluetooth_rfkill);
+		rfkill_destroy(bluetooth_rfkill);
+	}
+	if (wwan_rfkill) {
+		rfkill_unregister(wwan_rfkill);
+		rfkill_destroy(wwan_rfkill);
+	}
+	if (gps_rfkill) {
+		rfkill_unregister(gps_rfkill);
+		rfkill_destroy(gps_rfkill);
+	}
+
+	return 0;
+}
+
+static int hp_wmi_resume_handler(struct device *device)
+{
+	/*
+	 * Hardware state may have changed while suspended, so trigger
+	 * input events for the current state. As this is a switch,
+	 * the input layer will only actually pass it on if the state
+	 * changed.
+	 */
+	if (hp_wmi_input_dev) {
+		if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit))
+			input_report_switch(hp_wmi_input_dev, SW_DOCK,
+					    hp_wmi_dock_state());
+		if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit))
+			input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
+					    hp_wmi_tablet_state());
+		input_sync(hp_wmi_input_dev);
+	}
+
+	if (rfkill2_count)
+		hp_wmi_rfkill2_refresh();
+
+	if (wifi_rfkill)
+		rfkill_set_states(wifi_rfkill,
+				  hp_wmi_get_sw_state(HPWMI_WIFI),
+				  hp_wmi_get_hw_state(HPWMI_WIFI));
+	if (bluetooth_rfkill)
+		rfkill_set_states(bluetooth_rfkill,
+				  hp_wmi_get_sw_state(HPWMI_BLUETOOTH),
+				  hp_wmi_get_hw_state(HPWMI_BLUETOOTH));
+	if (wwan_rfkill)
+		rfkill_set_states(wwan_rfkill,
+				  hp_wmi_get_sw_state(HPWMI_WWAN),
+				  hp_wmi_get_hw_state(HPWMI_WWAN));
+	if (gps_rfkill)
+		rfkill_set_states(gps_rfkill,
+				  hp_wmi_get_sw_state(HPWMI_GPS),
+				  hp_wmi_get_hw_state(HPWMI_GPS));
+
+	return 0;
+}
+
+static const struct dev_pm_ops hp_wmi_pm_ops = {
+	.resume  = hp_wmi_resume_handler,
+	.restore  = hp_wmi_resume_handler,
+};
+
+static struct platform_driver hp_wmi_driver = {
+	.driver = {
+		.name = "hp-wmi",
+		.pm = &hp_wmi_pm_ops,
+	},
+	.remove = __exit_p(hp_wmi_bios_remove),
+};
+
+static int __init hp_wmi_init(void)
+{
+	int err;
+	int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
+	int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID);
+
+	if (!bios_capable && !event_capable)
+		return -ENODEV;
+
+	if (event_capable) {
+		err = hp_wmi_input_setup();
+		if (err)
+			return err;
+	}
+
+	if (bios_capable) {
+		hp_wmi_platform_dev =
+			platform_device_register_simple("hp-wmi", -1, NULL, 0);
+		if (IS_ERR(hp_wmi_platform_dev)) {
+			err = PTR_ERR(hp_wmi_platform_dev);
+			goto err_destroy_input;
+		}
+
+		err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
+		if (err)
+			goto err_unregister_device;
+	}
+
+	return 0;
+
+err_unregister_device:
+	platform_device_unregister(hp_wmi_platform_dev);
+err_destroy_input:
+	if (event_capable)
+		hp_wmi_input_destroy();
+
+	return err;
+}
+module_init(hp_wmi_init);
+
+static void __exit hp_wmi_exit(void)
+{
+	if (wmi_has_guid(HPWMI_EVENT_GUID))
+		hp_wmi_input_destroy();
+
+	if (hp_wmi_platform_dev) {
+		platform_device_unregister(hp_wmi_platform_dev);
+		platform_driver_unregister(&hp_wmi_driver);
+	}
+}
+module_exit(hp_wmi_exit);
diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c
new file mode 100644
index 0000000..10ce6cb
--- /dev/null
+++ b/drivers/platform/x86/hp_accel.c
@@ -0,0 +1,436 @@
+/*
+ *  hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS
+ *
+ *  Copyright (C) 2007-2008 Yan Burman
+ *  Copyright (C) 2008 Eric Piel
+ *  Copyright (C) 2008-2009 Pavel Machek
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/freezer.h>
+#include <linux/uaccess.h>
+#include <linux/leds.h>
+#include <linux/atomic.h>
+#include <linux/acpi.h>
+#include <linux/i8042.h>
+#include <linux/serio.h>
+#include "../../misc/lis3lv02d/lis3lv02d.h"
+
+#define DRIVER_NAME     "hp_accel"
+#define ACPI_MDPS_CLASS "accelerometer"
+
+/* Delayed LEDs infrastructure ------------------------------------ */
+
+/* Special LED class that can defer work */
+struct delayed_led_classdev {
+	struct led_classdev led_classdev;
+	struct work_struct work;
+	enum led_brightness new_brightness;
+
+	unsigned int led;		/* For driver */
+	void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value);
+};
+
+static inline void delayed_set_status_worker(struct work_struct *work)
+{
+	struct delayed_led_classdev *data =
+			container_of(work, struct delayed_led_classdev, work);
+
+	data->set_brightness(data, data->new_brightness);
+}
+
+static inline void delayed_sysfs_set(struct led_classdev *led_cdev,
+			      enum led_brightness brightness)
+{
+	struct delayed_led_classdev *data = container_of(led_cdev,
+			     struct delayed_led_classdev, led_classdev);
+	data->new_brightness = brightness;
+	schedule_work(&data->work);
+}
+
+/* HP-specific accelerometer driver ------------------------------------ */
+
+/* e0 25, e0 26, e0 27, e0 28 are scan codes that the accelerometer with acpi id
+ * HPQ6000 sends through the keyboard bus */
+#define ACCEL_1 0x25
+#define ACCEL_2 0x26
+#define ACCEL_3 0x27
+#define ACCEL_4 0x28
+
+/* For automatic insertion of the module */
+static const struct acpi_device_id lis3lv02d_device_ids[] = {
+	{"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */
+	{"HPQ6000", 0}, /* HP Mobile Data Protection System PNP */
+	{"HPQ6007", 0}, /* HP Mobile Data Protection System PNP */
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids);
+
+
+/**
+ * lis3lv02d_acpi_init - ACPI _INI method: initialize the device.
+ * @lis3: pointer to the device struct
+ *
+ * Returns 0 on success.
+ */
+static int lis3lv02d_acpi_init(struct lis3lv02d *lis3)
+{
+	struct acpi_device *dev = lis3->bus_priv;
+	if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI,
+				 NULL, NULL) != AE_OK)
+		return -EINVAL;
+
+	return 0;
+}
+
+/**
+ * lis3lv02d_acpi_read - ACPI ALRD method: read a register
+ * @lis3: pointer to the device struct
+ * @reg:    the register to read
+ * @ret:    result of the operation
+ *
+ * Returns 0 on success.
+ */
+static int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret)
+{
+	struct acpi_device *dev = lis3->bus_priv;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	unsigned long long lret;
+	acpi_status status;
+
+	arg0.integer.value = reg;
+
+	status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret);
+	*ret = lret;
+	return (status != AE_OK) ? -EINVAL : 0;
+}
+
+/**
+ * lis3lv02d_acpi_write - ACPI ALWR method: write to a register
+ * @lis3: pointer to the device struct
+ * @reg:    the register to write to
+ * @val:    the value to write
+ *
+ * Returns 0 on success.
+ */
+static int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val)
+{
+	struct acpi_device *dev = lis3->bus_priv;
+	unsigned long long ret; /* Not used when writting */
+	union acpi_object in_obj[2];
+	struct acpi_object_list args = { 2, in_obj };
+
+	in_obj[0].type          = ACPI_TYPE_INTEGER;
+	in_obj[0].integer.value = reg;
+	in_obj[1].type          = ACPI_TYPE_INTEGER;
+	in_obj[1].integer.value = val;
+
+	if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi)
+{
+	lis3_dev.ac = *((union axis_conversion *)dmi->driver_data);
+	pr_info("hardware type %s found\n", dmi->ident);
+
+	return 1;
+}
+
+/* Represents, for each axis seen by userspace, the corresponding hw axis (+1).
+ * If the value is negative, the opposite of the hw value is used. */
+#define DEFINE_CONV(name, x, y, z)			      \
+	static union axis_conversion lis3lv02d_axis_##name = \
+		{ .as_array = { x, y, z } }
+DEFINE_CONV(normal, 1, 2, 3);
+DEFINE_CONV(y_inverted, 1, -2, 3);
+DEFINE_CONV(x_inverted, -1, 2, 3);
+DEFINE_CONV(z_inverted, 1, 2, -3);
+DEFINE_CONV(xy_swap, 2, 1, 3);
+DEFINE_CONV(xy_rotated_left, -2, 1, 3);
+DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3);
+DEFINE_CONV(xy_swap_inverted, -2, -1, 3);
+DEFINE_CONV(xy_rotated_right, 2, -1, 3);
+DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3);
+
+#define AXIS_DMI_MATCH(_ident, _name, _axis) {		\
+	.ident = _ident,				\
+	.callback = lis3lv02d_dmi_matched,		\
+	.matches = {					\
+		DMI_MATCH(DMI_PRODUCT_NAME, _name)	\
+	},						\
+	.driver_data = &lis3lv02d_axis_##_axis		\
+}
+
+#define AXIS_DMI_MATCH2(_ident, _class1, _name1,	\
+				_class2, _name2,	\
+				_axis) {		\
+	.ident = _ident,				\
+	.callback = lis3lv02d_dmi_matched,		\
+	.matches = {					\
+		DMI_MATCH(DMI_##_class1, _name1),	\
+		DMI_MATCH(DMI_##_class2, _name2),	\
+	},						\
+	.driver_data = &lis3lv02d_axis_##_axis		\
+}
+static const struct dmi_system_id lis3lv02d_dmi_ids[] = {
+	/* product names are truncated to match all kinds of a same model */
+	AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted),
+	AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted),
+	AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted),
+	AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted),
+	AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted),
+	AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap),
+	AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted),
+	AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left),
+	AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted),
+	AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd),
+	AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd),
+	AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap),
+	AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right),
+	AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted),
+	AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted),
+	AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right),
+	AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap),
+	AXIS_DMI_MATCH("NC854xx", "HP EliteBook 854", y_inverted),
+	AXIS_DMI_MATCH("NC273xx", "HP EliteBook 273", y_inverted),
+	/* Intel-based HP Pavilion dv5 */
+	AXIS_DMI_MATCH2("HPDV5_I",
+			PRODUCT_NAME, "HP Pavilion dv5",
+			BOARD_NAME, "3603",
+			x_inverted),
+	/* AMD-based HP Pavilion dv5 */
+	AXIS_DMI_MATCH2("HPDV5_A",
+			PRODUCT_NAME, "HP Pavilion dv5",
+			BOARD_NAME, "3600",
+			y_inverted),
+	AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted),
+	AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted),
+	AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted),
+	AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left),
+	AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left),
+	AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted),
+	AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap),
+	AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted),
+	AXIS_DMI_MATCH("HPB655x", "HP ProBook 655", xy_swap_inverted),
+	AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd),
+	AXIS_DMI_MATCH("HPB63xx", "HP ProBook 63", xy_swap),
+	AXIS_DMI_MATCH("HPB64xx", "HP ProBook 64", xy_swap),
+	AXIS_DMI_MATCH("HPB64xx", "HP EliteBook 84", xy_swap),
+	AXIS_DMI_MATCH("HPB65xx", "HP ProBook 65", x_inverted),
+	AXIS_DMI_MATCH("HPZBook15", "HP ZBook 15", x_inverted),
+	{ NULL, }
+/* Laptop models without axis info (yet):
+ * "NC6910" "HP Compaq 6910"
+ * "NC2400" "HP Compaq nc2400"
+ * "NX74x0" "HP Compaq nx74"
+ * "NX6325" "HP Compaq nx6325"
+ * "NC4400" "HP Compaq nc4400"
+ */
+};
+
+static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value)
+{
+	struct acpi_device *dev = lis3_dev.bus_priv;
+	unsigned long long ret; /* Not used when writing */
+	union acpi_object in_obj[1];
+	struct acpi_object_list args = { 1, in_obj };
+
+	in_obj[0].type          = ACPI_TYPE_INTEGER;
+	in_obj[0].integer.value = !!value;
+
+	acpi_evaluate_integer(dev->handle, "ALED", &args, &ret);
+}
+
+static struct delayed_led_classdev hpled_led = {
+	.led_classdev = {
+		.name			= "hp::hddprotect",
+		.default_trigger	= "none",
+		.brightness_set		= delayed_sysfs_set,
+		.flags                  = LED_CORE_SUSPENDRESUME,
+	},
+	.set_brightness = hpled_set,
+};
+
+static acpi_status
+lis3lv02d_get_resource(struct acpi_resource *resource, void *context)
+{
+	if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) {
+		struct acpi_resource_extended_irq *irq;
+		u32 *device_irq = context;
+
+		irq = &resource->data.extended_irq;
+		*device_irq = irq->interrupts[0];
+	}
+
+	return AE_OK;
+}
+
+static void lis3lv02d_enum_resources(struct acpi_device *device)
+{
+	acpi_status status;
+
+	status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+					lis3lv02d_get_resource, &lis3_dev.irq);
+	if (ACPI_FAILURE(status))
+		printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n");
+}
+
+static bool hp_accel_i8042_filter(unsigned char data, unsigned char str,
+				  struct serio *port)
+{
+	static bool extended;
+
+	if (str & I8042_STR_AUXDATA)
+		return false;
+
+	if (data == 0xe0) {
+		extended = true;
+		return true;
+	} else if (unlikely(extended)) {
+		extended = false;
+
+		switch (data) {
+		case ACCEL_1:
+		case ACCEL_2:
+		case ACCEL_3:
+		case ACCEL_4:
+			return true;
+		default:
+			serio_interrupt(port, 0xe0, 0);
+			return false;
+		}
+	}
+
+	return false;
+}
+
+static int lis3lv02d_add(struct acpi_device *device)
+{
+	int ret;
+
+	if (!device)
+		return -EINVAL;
+
+	lis3_dev.bus_priv = device;
+	lis3_dev.init = lis3lv02d_acpi_init;
+	lis3_dev.read = lis3lv02d_acpi_read;
+	lis3_dev.write = lis3lv02d_acpi_write;
+	strcpy(acpi_device_name(device), DRIVER_NAME);
+	strcpy(acpi_device_class(device), ACPI_MDPS_CLASS);
+	device->driver_data = &lis3_dev;
+
+	/* obtain IRQ number of our device from ACPI */
+	lis3lv02d_enum_resources(device);
+
+	/* If possible use a "standard" axes order */
+	if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) {
+		pr_info("Using custom axes %d,%d,%d\n",
+			lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z);
+	} else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) {
+		pr_info("laptop model unknown, using default axes configuration\n");
+		lis3_dev.ac = lis3lv02d_axis_normal;
+	}
+
+	/* call the core layer do its init */
+	ret = lis3lv02d_init_device(&lis3_dev);
+	if (ret)
+		return ret;
+
+	/* filter to remove HPQ6000 accelerometer data
+	 * from keyboard bus stream */
+	if (strstr(dev_name(&device->dev), "HPQ6000"))
+		i8042_install_filter(hp_accel_i8042_filter);
+
+	INIT_WORK(&hpled_led.work, delayed_set_status_worker);
+	ret = led_classdev_register(NULL, &hpled_led.led_classdev);
+	if (ret) {
+		lis3lv02d_joystick_disable(&lis3_dev);
+		lis3lv02d_poweroff(&lis3_dev);
+		flush_work(&hpled_led.work);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int lis3lv02d_remove(struct acpi_device *device)
+{
+	if (!device)
+		return -EINVAL;
+
+	i8042_remove_filter(hp_accel_i8042_filter);
+	lis3lv02d_joystick_disable(&lis3_dev);
+	lis3lv02d_poweroff(&lis3_dev);
+
+	led_classdev_unregister(&hpled_led.led_classdev);
+	flush_work(&hpled_led.work);
+
+	return lis3lv02d_remove_fs(&lis3_dev);
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+static int lis3lv02d_suspend(struct device *dev)
+{
+	/* make sure the device is off when we suspend */
+	lis3lv02d_poweroff(&lis3_dev);
+	return 0;
+}
+
+static int lis3lv02d_resume(struct device *dev)
+{
+	lis3lv02d_poweron(&lis3_dev);
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume);
+#define HP_ACCEL_PM (&hp_accel_pm)
+#else
+#define HP_ACCEL_PM NULL
+#endif
+
+/* For the HP MDPS aka 3D Driveguard */
+static struct acpi_driver lis3lv02d_driver = {
+	.name  = DRIVER_NAME,
+	.class = ACPI_MDPS_CLASS,
+	.ids   = lis3lv02d_device_ids,
+	.ops = {
+		.add     = lis3lv02d_add,
+		.remove  = lis3lv02d_remove,
+	},
+	.drv.pm = HP_ACCEL_PM,
+};
+module_acpi_driver(lis3lv02d_driver);
+
+MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED.");
+MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c
new file mode 100644
index 0000000..c62e5e1
--- /dev/null
+++ b/drivers/platform/x86/ibm_rtl.c
@@ -0,0 +1,328 @@
+/*
+ * IBM Real-Time Linux driver
+ *
+ * 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.
+ *
+ * Copyright (C) IBM Corporation, 2010
+ *
+ * Author: Keith Mannthey <kmannth@us.ibm.com>
+ *         Vernon Mauery <vernux@us.ibm.com>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/dmi.h>
+#include <linux/efi.h>
+#include <linux/mutex.h>
+#include <asm/bios_ebda.h>
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static bool debug;
+module_param(debug, bool, 0644);
+MODULE_PARM_DESC(debug, "Show debug output");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>");
+MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>");
+
+#define RTL_ADDR_TYPE_IO    1
+#define RTL_ADDR_TYPE_MMIO  2
+
+#define RTL_CMD_ENTER_PRTM  1
+#define RTL_CMD_EXIT_PRTM   2
+
+/* The RTL table as presented by the EBDA: */
+struct ibm_rtl_table {
+	char signature[5]; /* signature should be "_RTL_" */
+	u8 version;
+	u8 rt_status;
+	u8 command;
+	u8 command_status;
+	u8 cmd_address_type;
+	u8 cmd_granularity;
+	u8 cmd_offset;
+	u16 reserve1;
+	u32 cmd_port_address; /* platform dependent address */
+	u32 cmd_port_value;   /* platform dependent value */
+} __attribute__((packed));
+
+/* to locate "_RTL_" signature do a masked 5-byte integer compare */
+#define RTL_SIGNATURE 0x0000005f4c54525fULL
+#define RTL_MASK      0x000000ffffffffffULL
+
+#define RTL_DEBUG(fmt, ...)				\
+do {							\
+	if (debug)					\
+		pr_info(fmt, ##__VA_ARGS__);		\
+} while (0)
+
+static DEFINE_MUTEX(rtl_lock);
+static struct ibm_rtl_table __iomem *rtl_table;
+static void __iomem *ebda_map;
+static void __iomem *rtl_cmd_addr;
+static u8 rtl_cmd_type;
+static u8 rtl_cmd_width;
+
+static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len)
+{
+	if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+		return ioremap(addr, len);
+	return ioport_map(addr, len);
+}
+
+static void rtl_port_unmap(void __iomem *addr)
+{
+	if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+		iounmap(addr);
+	else
+		ioport_unmap(addr);
+}
+
+static int ibm_rtl_write(u8 value)
+{
+	int ret = 0, count = 0;
+	static u32 cmd_port_val;
+
+	RTL_DEBUG("%s(%d)\n", __func__, value);
+
+	value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM;
+
+	mutex_lock(&rtl_lock);
+
+	if (ioread8(&rtl_table->rt_status) != value) {
+		iowrite8(value, &rtl_table->command);
+
+		switch (rtl_cmd_width) {
+		case 8:
+			cmd_port_val = ioread8(&rtl_table->cmd_port_value);
+			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
+			iowrite8((u8)cmd_port_val, rtl_cmd_addr);
+			break;
+		case 16:
+			cmd_port_val = ioread16(&rtl_table->cmd_port_value);
+			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
+			iowrite16((u16)cmd_port_val, rtl_cmd_addr);
+			break;
+		case 32:
+			cmd_port_val = ioread32(&rtl_table->cmd_port_value);
+			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
+			iowrite32(cmd_port_val, rtl_cmd_addr);
+			break;
+		}
+
+		while (ioread8(&rtl_table->command)) {
+			msleep(10);
+			if (count++ > 500) {
+				pr_err("Hardware not responding to "
+				       "mode switch request\n");
+				ret = -EIO;
+				break;
+			}
+
+		}
+
+		if (ioread8(&rtl_table->command_status)) {
+			RTL_DEBUG("command_status reports failed command\n");
+			ret = -EIO;
+		}
+	}
+
+	mutex_unlock(&rtl_lock);
+	return ret;
+}
+
+static ssize_t rtl_show_version(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+	return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
+}
+
+static ssize_t rtl_show_state(struct device *dev,
+                              struct device_attribute *attr,
+                              char *buf)
+{
+	return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
+}
+
+static ssize_t rtl_set_state(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf,
+                             size_t count)
+{
+	ssize_t ret;
+
+	if (count < 1 || count > 2)
+		return -EINVAL;
+
+	switch (buf[0]) {
+	case '0':
+		ret = ibm_rtl_write(0);
+		break;
+	case '1':
+		ret = ibm_rtl_write(1);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	if (ret >= 0)
+		ret = count;
+
+	return ret;
+}
+
+static struct bus_type rtl_subsys = {
+	.name = "ibm_rtl",
+	.dev_name = "ibm_rtl",
+};
+
+static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL);
+static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state);
+
+static struct device_attribute *rtl_attributes[] = {
+	&dev_attr_version,
+	&dev_attr_state,
+	NULL
+};
+
+
+static int rtl_setup_sysfs(void) {
+	int ret, i;
+
+	ret = subsys_system_register(&rtl_subsys, NULL);
+	if (!ret) {
+		for (i = 0; rtl_attributes[i]; i ++)
+			device_create_file(rtl_subsys.dev_root, rtl_attributes[i]);
+	}
+	return ret;
+}
+
+static void rtl_teardown_sysfs(void) {
+	int i;
+	for (i = 0; rtl_attributes[i]; i ++)
+		device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]);
+	bus_unregister(&rtl_subsys);
+}
+
+
+static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = {
+	{                                                  \
+		.matches = {                               \
+			DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \
+		},                                         \
+	},
+	{ }
+};
+
+static int __init ibm_rtl_init(void) {
+	unsigned long ebda_addr, ebda_size;
+	unsigned int ebda_kb;
+	int ret = -ENODEV, i;
+
+	if (force)
+		pr_warn("module loaded by force\n");
+	/* first ensure that we are running on IBM HW */
+	else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table))
+		return -ENODEV;
+
+	/* Get the address for the Extended BIOS Data Area */
+	ebda_addr = get_bios_ebda();
+	if (!ebda_addr) {
+		RTL_DEBUG("no BIOS EBDA found\n");
+		return -ENODEV;
+	}
+
+	ebda_map = ioremap(ebda_addr, 4);
+	if (!ebda_map)
+		return -ENOMEM;
+
+	/* First word in the EDBA is the Size in KB */
+	ebda_kb = ioread16(ebda_map);
+	RTL_DEBUG("EBDA is %d kB\n", ebda_kb);
+
+	if (ebda_kb == 0)
+		goto out;
+
+	iounmap(ebda_map);
+	ebda_size = ebda_kb*1024;
+
+	/* Remap the whole table */
+	ebda_map = ioremap(ebda_addr, ebda_size);
+	if (!ebda_map)
+		return -ENOMEM;
+
+	/* search for the _RTL_ signature at the start of the table */
+	for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
+		struct ibm_rtl_table __iomem * tmp;
+		tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i);
+		if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
+			phys_addr_t addr;
+			unsigned int plen;
+			RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp);
+			rtl_table = tmp;
+			/* The address, value, width and offset are platform
+			 * dependent and found in the ibm_rtl_table */
+			rtl_cmd_width = ioread8(&rtl_table->cmd_granularity);
+			rtl_cmd_type = ioread8(&rtl_table->cmd_address_type);
+			RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
+				  rtl_cmd_width, rtl_cmd_type);
+			addr = ioread32(&rtl_table->cmd_port_address);
+			RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr);
+			plen = rtl_cmd_width/sizeof(char);
+			rtl_cmd_addr = rtl_port_map(addr, plen);
+			RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr);
+			if (!rtl_cmd_addr) {
+				ret = -ENOMEM;
+				break;
+			}
+			ret = rtl_setup_sysfs();
+			break;
+		}
+	}
+
+out:
+	if (ret) {
+		iounmap(ebda_map);
+		rtl_port_unmap(rtl_cmd_addr);
+	}
+
+	return ret;
+}
+
+static void __exit ibm_rtl_exit(void)
+{
+	if (rtl_table) {
+		RTL_DEBUG("cleaning up");
+		/* do not leave the machine in SMI-free mode */
+		ibm_rtl_write(0);
+		/* unmap, unlink and remove all traces */
+		rtl_teardown_sysfs();
+		iounmap(ebda_map);
+		rtl_port_unmap(rtl_cmd_addr);
+	}
+}
+
+module_init(ibm_rtl_init);
+module_exit(ibm_rtl_exit);
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
new file mode 100644
index 0000000..09cc64b
--- /dev/null
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -0,0 +1,1075 @@
+/*
+ *  ideapad-laptop.c - Lenovo IdeaPad ACPI Extras
+ *
+ *  Copyright © 2010 Intel Corporation
+ *  Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
+ *
+ *  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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/i8042.h>
+#include <linux/dmi.h>
+#include <linux/device.h>
+#include <acpi/video.h>
+
+#define IDEAPAD_RFKILL_DEV_NUM	(3)
+
+#define CFG_BT_BIT	(16)
+#define CFG_3G_BIT	(17)
+#define CFG_WIFI_BIT	(18)
+#define CFG_CAMERA_BIT	(19)
+
+#if IS_ENABLED(CONFIG_ACPI_WMI)
+static const char ideapad_wmi_fnesc_event[] = "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6";
+#endif
+
+enum {
+	VPCCMD_R_VPC1 = 0x10,
+	VPCCMD_R_BL_MAX,
+	VPCCMD_R_BL,
+	VPCCMD_W_BL,
+	VPCCMD_R_WIFI,
+	VPCCMD_W_WIFI,
+	VPCCMD_R_BT,
+	VPCCMD_W_BT,
+	VPCCMD_R_BL_POWER,
+	VPCCMD_R_NOVO,
+	VPCCMD_R_VPC2,
+	VPCCMD_R_TOUCHPAD,
+	VPCCMD_W_TOUCHPAD,
+	VPCCMD_R_CAMERA,
+	VPCCMD_W_CAMERA,
+	VPCCMD_R_3G,
+	VPCCMD_W_3G,
+	VPCCMD_R_ODD, /* 0x21 */
+	VPCCMD_W_FAN,
+	VPCCMD_R_RF,
+	VPCCMD_W_RF,
+	VPCCMD_R_FAN = 0x2B,
+	VPCCMD_R_SPECIAL_BUTTONS = 0x31,
+	VPCCMD_W_BL_POWER = 0x33,
+};
+
+struct ideapad_rfk_priv {
+	int dev;
+	struct ideapad_private *priv;
+};
+
+struct ideapad_private {
+	struct acpi_device *adev;
+	struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
+	struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM];
+	struct platform_device *platform_device;
+	struct input_dev *inputdev;
+	struct backlight_device *blightdev;
+	struct dentry *debug;
+	unsigned long cfg;
+	bool has_hw_rfkill_switch;
+};
+
+static bool no_bt_rfkill;
+module_param(no_bt_rfkill, bool, 0444);
+MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
+
+/*
+ * ACPI Helpers
+ */
+#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
+
+static int read_method_int(acpi_handle handle, const char *method, int *val)
+{
+	acpi_status status;
+	unsigned long long result;
+
+	status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
+	if (ACPI_FAILURE(status)) {
+		*val = -1;
+		return -1;
+	} else {
+		*val = result;
+		return 0;
+	}
+}
+
+static int method_vpcr(acpi_handle handle, int cmd, int *ret)
+{
+	acpi_status status;
+	unsigned long long result;
+	struct acpi_object_list params;
+	union acpi_object in_obj;
+
+	params.count = 1;
+	params.pointer = &in_obj;
+	in_obj.type = ACPI_TYPE_INTEGER;
+	in_obj.integer.value = cmd;
+
+	status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
+
+	if (ACPI_FAILURE(status)) {
+		*ret = -1;
+		return -1;
+	} else {
+		*ret = result;
+		return 0;
+	}
+}
+
+static int method_vpcw(acpi_handle handle, int cmd, int data)
+{
+	struct acpi_object_list params;
+	union acpi_object in_obj[2];
+	acpi_status status;
+
+	params.count = 2;
+	params.pointer = in_obj;
+	in_obj[0].type = ACPI_TYPE_INTEGER;
+	in_obj[0].integer.value = cmd;
+	in_obj[1].type = ACPI_TYPE_INTEGER;
+	in_obj[1].integer.value = data;
+
+	status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
+	if (status != AE_OK)
+		return -1;
+	return 0;
+}
+
+static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
+{
+	int val;
+	unsigned long int end_jiffies;
+
+	if (method_vpcw(handle, 1, cmd))
+		return -1;
+
+	for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
+	     time_before(jiffies, end_jiffies);) {
+		schedule();
+		if (method_vpcr(handle, 1, &val))
+			return -1;
+		if (val == 0) {
+			if (method_vpcr(handle, 0, &val))
+				return -1;
+			*data = val;
+			return 0;
+		}
+	}
+	pr_err("timeout in read_ec_cmd\n");
+	return -1;
+}
+
+static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
+{
+	int val;
+	unsigned long int end_jiffies;
+
+	if (method_vpcw(handle, 0, data))
+		return -1;
+	if (method_vpcw(handle, 1, cmd))
+		return -1;
+
+	for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
+	     time_before(jiffies, end_jiffies);) {
+		schedule();
+		if (method_vpcr(handle, 1, &val))
+			return -1;
+		if (val == 0)
+			return 0;
+	}
+	pr_err("timeout in write_ec_cmd\n");
+	return -1;
+}
+
+/*
+ * debugfs
+ */
+static int debugfs_status_show(struct seq_file *s, void *data)
+{
+	struct ideapad_private *priv = s->private;
+	unsigned long value;
+
+	if (!priv)
+		return -EINVAL;
+
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value))
+		seq_printf(s, "Backlight max:\t%lu\n", value);
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value))
+		seq_printf(s, "Backlight now:\t%lu\n", value);
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value))
+		seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off");
+	seq_printf(s, "=====================\n");
+
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value))
+		seq_printf(s, "Radio status:\t%s(%lu)\n",
+			   value ? "On" : "Off", value);
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value))
+		seq_printf(s, "Wifi status:\t%s(%lu)\n",
+			   value ? "On" : "Off", value);
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value))
+		seq_printf(s, "BT status:\t%s(%lu)\n",
+			   value ? "On" : "Off", value);
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value))
+		seq_printf(s, "3G status:\t%s(%lu)\n",
+			   value ? "On" : "Off", value);
+	seq_printf(s, "=====================\n");
+
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value))
+		seq_printf(s, "Touchpad status:%s(%lu)\n",
+			   value ? "On" : "Off", value);
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value))
+		seq_printf(s, "Camera status:\t%s(%lu)\n",
+			   value ? "On" : "Off", value);
+
+	return 0;
+}
+
+static int debugfs_status_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, debugfs_status_show, inode->i_private);
+}
+
+static const struct file_operations debugfs_status_fops = {
+	.owner = THIS_MODULE,
+	.open = debugfs_status_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int debugfs_cfg_show(struct seq_file *s, void *data)
+{
+	struct ideapad_private *priv = s->private;
+
+	if (!priv) {
+		seq_printf(s, "cfg: N/A\n");
+	} else {
+		seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ",
+			   priv->cfg);
+		if (test_bit(CFG_BT_BIT, &priv->cfg))
+			seq_printf(s, "Bluetooth ");
+		if (test_bit(CFG_3G_BIT, &priv->cfg))
+			seq_printf(s, "3G ");
+		if (test_bit(CFG_WIFI_BIT, &priv->cfg))
+			seq_printf(s, "Wireless ");
+		if (test_bit(CFG_CAMERA_BIT, &priv->cfg))
+			seq_printf(s, "Camera ");
+		seq_printf(s, "\nGraphic: ");
+		switch ((priv->cfg)&0x700) {
+		case 0x100:
+			seq_printf(s, "Intel");
+			break;
+		case 0x200:
+			seq_printf(s, "ATI");
+			break;
+		case 0x300:
+			seq_printf(s, "Nvidia");
+			break;
+		case 0x400:
+			seq_printf(s, "Intel and ATI");
+			break;
+		case 0x500:
+			seq_printf(s, "Intel and Nvidia");
+			break;
+		}
+		seq_printf(s, "\n");
+	}
+	return 0;
+}
+
+static int debugfs_cfg_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, debugfs_cfg_show, inode->i_private);
+}
+
+static const struct file_operations debugfs_cfg_fops = {
+	.owner = THIS_MODULE,
+	.open = debugfs_cfg_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int ideapad_debugfs_init(struct ideapad_private *priv)
+{
+	struct dentry *node;
+
+	priv->debug = debugfs_create_dir("ideapad", NULL);
+	if (priv->debug == NULL) {
+		pr_err("failed to create debugfs directory");
+		goto errout;
+	}
+
+	node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv,
+				   &debugfs_cfg_fops);
+	if (!node) {
+		pr_err("failed to create cfg in debugfs");
+		goto errout;
+	}
+
+	node = debugfs_create_file("status", S_IRUGO, priv->debug, priv,
+				   &debugfs_status_fops);
+	if (!node) {
+		pr_err("failed to create status in debugfs");
+		goto errout;
+	}
+
+	return 0;
+
+errout:
+	return -ENOMEM;
+}
+
+static void ideapad_debugfs_exit(struct ideapad_private *priv)
+{
+	debugfs_remove_recursive(priv->debug);
+	priv->debug = NULL;
+}
+
+/*
+ * sysfs
+ */
+static ssize_t show_ideapad_cam(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	unsigned long result;
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result))
+		return sprintf(buf, "-1\n");
+	return sprintf(buf, "%lu\n", result);
+}
+
+static ssize_t store_ideapad_cam(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	int ret, state;
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+
+	if (!count)
+		return 0;
+	if (sscanf(buf, "%i", &state) != 1)
+		return -EINVAL;
+	ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state);
+	if (ret < 0)
+		return -EIO;
+	return count;
+}
+
+static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
+
+static ssize_t show_ideapad_fan(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	unsigned long result;
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result))
+		return sprintf(buf, "-1\n");
+	return sprintf(buf, "%lu\n", result);
+}
+
+static ssize_t store_ideapad_fan(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	int ret, state;
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+
+	if (!count)
+		return 0;
+	if (sscanf(buf, "%i", &state) != 1)
+		return -EINVAL;
+	if (state < 0 || state > 4 || state == 3)
+		return -EINVAL;
+	ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state);
+	if (ret < 0)
+		return -EIO;
+	return count;
+}
+
+static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan);
+
+static struct attribute *ideapad_attributes[] = {
+	&dev_attr_camera_power.attr,
+	&dev_attr_fan_mode.attr,
+	NULL
+};
+
+static umode_t ideapad_is_visible(struct kobject *kobj,
+				 struct attribute *attr,
+				 int idx)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+	bool supported;
+
+	if (attr == &dev_attr_camera_power.attr)
+		supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg));
+	else if (attr == &dev_attr_fan_mode.attr) {
+		unsigned long value;
+		supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
+					  &value);
+	} else
+		supported = true;
+
+	return supported ? attr->mode : 0;
+}
+
+static const struct attribute_group ideapad_attribute_group = {
+	.is_visible = ideapad_is_visible,
+	.attrs = ideapad_attributes
+};
+
+/*
+ * Rfkill
+ */
+struct ideapad_rfk_data {
+	char *name;
+	int cfgbit;
+	int opcode;
+	int type;
+};
+
+static const struct ideapad_rfk_data ideapad_rfk_data[] = {
+	{ "ideapad_wlan",    CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
+	{ "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH },
+	{ "ideapad_3g",        CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN },
+};
+
+static int ideapad_rfk_set(void *data, bool blocked)
+{
+	struct ideapad_rfk_priv *priv = data;
+	int opcode = ideapad_rfk_data[priv->dev].opcode;
+
+	return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked);
+}
+
+static struct rfkill_ops ideapad_rfk_ops = {
+	.set_block = ideapad_rfk_set,
+};
+
+static void ideapad_sync_rfk_state(struct ideapad_private *priv)
+{
+	unsigned long hw_blocked = 0;
+	int i;
+
+	if (priv->has_hw_rfkill_switch) {
+		if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked))
+			return;
+		hw_blocked = !hw_blocked;
+	}
+
+	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+		if (priv->rfk[i])
+			rfkill_set_hw_state(priv->rfk[i], hw_blocked);
+}
+
+static int ideapad_register_rfkill(struct ideapad_private *priv, int dev)
+{
+	int ret;
+	unsigned long sw_blocked;
+
+	if (no_bt_rfkill &&
+	    (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
+		/* Force to enable bluetooth when no_bt_rfkill=1 */
+		write_ec_cmd(priv->adev->handle,
+			     ideapad_rfk_data[dev].opcode, 1);
+		return 0;
+	}
+	priv->rfk_priv[dev].dev = dev;
+	priv->rfk_priv[dev].priv = priv;
+
+	priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name,
+				      &priv->platform_device->dev,
+				      ideapad_rfk_data[dev].type,
+				      &ideapad_rfk_ops,
+				      &priv->rfk_priv[dev]);
+	if (!priv->rfk[dev])
+		return -ENOMEM;
+
+	if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1,
+			 &sw_blocked)) {
+		rfkill_init_sw_state(priv->rfk[dev], 0);
+	} else {
+		sw_blocked = !sw_blocked;
+		rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
+	}
+
+	ret = rfkill_register(priv->rfk[dev]);
+	if (ret) {
+		rfkill_destroy(priv->rfk[dev]);
+		return ret;
+	}
+	return 0;
+}
+
+static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev)
+{
+	if (!priv->rfk[dev])
+		return;
+
+	rfkill_unregister(priv->rfk[dev]);
+	rfkill_destroy(priv->rfk[dev]);
+}
+
+/*
+ * Platform device
+ */
+static int ideapad_sysfs_init(struct ideapad_private *priv)
+{
+	return sysfs_create_group(&priv->platform_device->dev.kobj,
+				    &ideapad_attribute_group);
+}
+
+static void ideapad_sysfs_exit(struct ideapad_private *priv)
+{
+	sysfs_remove_group(&priv->platform_device->dev.kobj,
+			   &ideapad_attribute_group);
+}
+
+/*
+ * input device
+ */
+static const struct key_entry ideapad_keymap[] = {
+	{ KE_KEY, 6,  { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 7,  { KEY_CAMERA } },
+	{ KE_KEY, 11, { KEY_F16 } },
+	{ KE_KEY, 13, { KEY_WLAN } },
+	{ KE_KEY, 16, { KEY_PROG1 } },
+	{ KE_KEY, 17, { KEY_PROG2 } },
+	{ KE_KEY, 64, { KEY_PROG3 } },
+	{ KE_KEY, 65, { KEY_PROG4 } },
+	{ KE_KEY, 66, { KEY_TOUCHPAD_OFF } },
+	{ KE_KEY, 67, { KEY_TOUCHPAD_ON } },
+	{ KE_KEY, 128, { KEY_ESC } },
+
+	{ KE_END, 0 },
+};
+
+static int ideapad_input_init(struct ideapad_private *priv)
+{
+	struct input_dev *inputdev;
+	int error;
+
+	inputdev = input_allocate_device();
+	if (!inputdev)
+		return -ENOMEM;
+
+	inputdev->name = "Ideapad extra buttons";
+	inputdev->phys = "ideapad/input0";
+	inputdev->id.bustype = BUS_HOST;
+	inputdev->dev.parent = &priv->platform_device->dev;
+
+	error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
+	if (error) {
+		pr_err("Unable to setup input device keymap\n");
+		goto err_free_dev;
+	}
+
+	error = input_register_device(inputdev);
+	if (error) {
+		pr_err("Unable to register input device\n");
+		goto err_free_keymap;
+	}
+
+	priv->inputdev = inputdev;
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(inputdev);
+err_free_dev:
+	input_free_device(inputdev);
+	return error;
+}
+
+static void ideapad_input_exit(struct ideapad_private *priv)
+{
+	sparse_keymap_free(priv->inputdev);
+	input_unregister_device(priv->inputdev);
+	priv->inputdev = NULL;
+}
+
+static void ideapad_input_report(struct ideapad_private *priv,
+				 unsigned long scancode)
+{
+	sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
+}
+
+static void ideapad_input_novokey(struct ideapad_private *priv)
+{
+	unsigned long long_pressed;
+
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed))
+		return;
+	if (long_pressed)
+		ideapad_input_report(priv, 17);
+	else
+		ideapad_input_report(priv, 16);
+}
+
+static void ideapad_check_special_buttons(struct ideapad_private *priv)
+{
+	unsigned long bit, value;
+
+	read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value);
+
+	for (bit = 0; bit < 16; bit++) {
+		if (test_bit(bit, &value)) {
+			switch (bit) {
+			case 0:	/* Z580 */
+			case 6:	/* Z570 */
+				/* Thermal Management button */
+				ideapad_input_report(priv, 65);
+				break;
+			case 1:
+				/* OneKey Theater button */
+				ideapad_input_report(priv, 64);
+				break;
+			default:
+				pr_info("Unknown special button: %lu\n", bit);
+				break;
+			}
+		}
+	}
+}
+
+/*
+ * backlight
+ */
+static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
+{
+	struct ideapad_private *priv = bl_get_data(blightdev);
+	unsigned long now;
+
+	if (!priv)
+		return -EINVAL;
+
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now))
+		return -EIO;
+	return now;
+}
+
+static int ideapad_backlight_update_status(struct backlight_device *blightdev)
+{
+	struct ideapad_private *priv = bl_get_data(blightdev);
+
+	if (!priv)
+		return -EINVAL;
+
+	if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL,
+			 blightdev->props.brightness))
+		return -EIO;
+	if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER,
+			 blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
+		return -EIO;
+
+	return 0;
+}
+
+static const struct backlight_ops ideapad_backlight_ops = {
+	.get_brightness = ideapad_backlight_get_brightness,
+	.update_status = ideapad_backlight_update_status,
+};
+
+static int ideapad_backlight_init(struct ideapad_private *priv)
+{
+	struct backlight_device *blightdev;
+	struct backlight_properties props;
+	unsigned long max, now, power;
+
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max))
+		return -EIO;
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now))
+		return -EIO;
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power))
+		return -EIO;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.max_brightness = max;
+	props.type = BACKLIGHT_PLATFORM;
+	blightdev = backlight_device_register("ideapad",
+					      &priv->platform_device->dev,
+					      priv,
+					      &ideapad_backlight_ops,
+					      &props);
+	if (IS_ERR(blightdev)) {
+		pr_err("Could not register backlight device\n");
+		return PTR_ERR(blightdev);
+	}
+
+	priv->blightdev = blightdev;
+	blightdev->props.brightness = now;
+	blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+	backlight_update_status(blightdev);
+
+	return 0;
+}
+
+static void ideapad_backlight_exit(struct ideapad_private *priv)
+{
+	backlight_device_unregister(priv->blightdev);
+	priv->blightdev = NULL;
+}
+
+static void ideapad_backlight_notify_power(struct ideapad_private *priv)
+{
+	unsigned long power;
+	struct backlight_device *blightdev = priv->blightdev;
+
+	if (!blightdev)
+		return;
+	if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power))
+		return;
+	blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+}
+
+static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
+{
+	unsigned long now;
+
+	/* if we control brightness via acpi video driver */
+	if (priv->blightdev == NULL) {
+		read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now);
+		return;
+	}
+
+	backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
+}
+
+/*
+ * module init/exit
+ */
+static void ideapad_sync_touchpad_state(struct ideapad_private *priv)
+{
+	unsigned long value;
+
+	/* Without reading from EC touchpad LED doesn't switch state */
+	if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) {
+		/* Some IdeaPads don't really turn off touchpad - they only
+		 * switch the LED state. We (de)activate KBC AUX port to turn
+		 * touchpad off and on. We send KEY_TOUCHPAD_OFF and
+		 * KEY_TOUCHPAD_ON to not to get out of sync with LED */
+		unsigned char param;
+		i8042_command(&param, value ? I8042_CMD_AUX_ENABLE :
+			      I8042_CMD_AUX_DISABLE);
+		ideapad_input_report(priv, value ? 67 : 66);
+	}
+}
+
+static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct ideapad_private *priv = data;
+	unsigned long vpc1, vpc2, vpc_bit;
+
+	if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
+		return;
+	if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
+		return;
+
+	vpc1 = (vpc2 << 8) | vpc1;
+	for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
+		if (test_bit(vpc_bit, &vpc1)) {
+			switch (vpc_bit) {
+			case 9:
+				ideapad_sync_rfk_state(priv);
+				break;
+			case 13:
+			case 11:
+			case 7:
+			case 6:
+			case 1:
+				ideapad_input_report(priv, vpc_bit);
+				break;
+			case 5:
+				ideapad_sync_touchpad_state(priv);
+				break;
+			case 4:
+				ideapad_backlight_notify_brightness(priv);
+				break;
+			case 3:
+				ideapad_input_novokey(priv);
+				break;
+			case 2:
+				ideapad_backlight_notify_power(priv);
+				break;
+			case 0:
+				ideapad_check_special_buttons(priv);
+				break;
+			default:
+				pr_info("Unknown event: %lu\n", vpc_bit);
+			}
+		}
+	}
+}
+
+#if IS_ENABLED(CONFIG_ACPI_WMI)
+static void ideapad_wmi_notify(u32 value, void *context)
+{
+	switch (value) {
+	case 128:
+		ideapad_input_report(context, value);
+		break;
+	default:
+		pr_info("Unknown WMI event %u\n", value);
+	}
+}
+#endif
+
+/*
+ * Some ideapads don't have a hardware rfkill switch, reading VPCCMD_R_RF
+ * always results in 0 on these models, causing ideapad_laptop to wrongly
+ * report all radios as hardware-blocked.
+ */
+static const struct dmi_system_id no_hw_rfkill_list[] = {
+	{
+		.ident = "Lenovo G40-30",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"),
+		},
+	},
+	{
+		.ident = "Lenovo G50-30",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"),
+		},
+	},
+	{
+		.ident = "Lenovo ideapad Y700-15ISK",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ISK"),
+		},
+	},
+	{
+		.ident = "Lenovo ideapad Y700 Touch-15ISK",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700 Touch-15ISK"),
+		},
+	},
+	{
+		.ident = "Lenovo ideapad Y700-17ISK",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"),
+		},
+	},
+	{
+		.ident = "Lenovo Yoga 2 11 / 13 / Pro",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 2"),
+		},
+	},
+	{
+		.ident = "Lenovo Yoga 2 11 / 13 / Pro",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_BOARD_NAME, "Yoga2"),
+		},
+	},
+	{
+		.ident = "Lenovo Yoga 3 1170 / 1470",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3"),
+		},
+	},
+	{
+		.ident = "Lenovo Yoga 3 Pro 1370",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"),
+		},
+	},
+	{
+		.ident = "Lenovo Yoga 700",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 700"),
+		},
+	},
+	{
+		.ident = "Lenovo Yoga 900",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 900"),
+		},
+	},
+	{}
+};
+
+static int ideapad_acpi_add(struct platform_device *pdev)
+{
+	int ret, i;
+	int cfg;
+	struct ideapad_private *priv;
+	struct acpi_device *adev;
+
+	ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev);
+	if (ret)
+		return -ENODEV;
+
+	if (read_method_int(adev->handle, "_CFG", &cfg))
+		return -ENODEV;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, priv);
+	priv->cfg = cfg;
+	priv->adev = adev;
+	priv->platform_device = pdev;
+	priv->has_hw_rfkill_switch = !dmi_check_system(no_hw_rfkill_list);
+
+	ret = ideapad_sysfs_init(priv);
+	if (ret)
+		return ret;
+
+	ret = ideapad_debugfs_init(priv);
+	if (ret)
+		goto debugfs_failed;
+
+	ret = ideapad_input_init(priv);
+	if (ret)
+		goto input_failed;
+
+	/*
+	 * On some models without a hw-switch (the yoga 2 13 at least)
+	 * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
+	 */
+	if (!priv->has_hw_rfkill_switch)
+		write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1);
+
+	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+		if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg))
+			ideapad_register_rfkill(priv, i);
+
+	ideapad_sync_rfk_state(priv);
+	ideapad_sync_touchpad_state(priv);
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		ret = ideapad_backlight_init(priv);
+		if (ret && ret != -ENODEV)
+			goto backlight_failed;
+	}
+	ret = acpi_install_notify_handler(adev->handle,
+		ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv);
+	if (ret)
+		goto notification_failed;
+#if IS_ENABLED(CONFIG_ACPI_WMI)
+	ret = wmi_install_notify_handler(ideapad_wmi_fnesc_event, ideapad_wmi_notify, priv);
+	if (ret != AE_OK && ret != AE_NOT_EXIST)
+		goto notification_failed_wmi;
+#endif
+
+	return 0;
+#if IS_ENABLED(CONFIG_ACPI_WMI)
+notification_failed_wmi:
+	acpi_remove_notify_handler(priv->adev->handle,
+		ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
+#endif
+notification_failed:
+	ideapad_backlight_exit(priv);
+backlight_failed:
+	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+		ideapad_unregister_rfkill(priv, i);
+	ideapad_input_exit(priv);
+input_failed:
+	ideapad_debugfs_exit(priv);
+debugfs_failed:
+	ideapad_sysfs_exit(priv);
+	return ret;
+}
+
+static int ideapad_acpi_remove(struct platform_device *pdev)
+{
+	struct ideapad_private *priv = dev_get_drvdata(&pdev->dev);
+	int i;
+
+#if IS_ENABLED(CONFIG_ACPI_WMI)
+	wmi_remove_notify_handler(ideapad_wmi_fnesc_event);
+#endif
+	acpi_remove_notify_handler(priv->adev->handle,
+		ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
+	ideapad_backlight_exit(priv);
+	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+		ideapad_unregister_rfkill(priv, i);
+	ideapad_input_exit(priv);
+	ideapad_debugfs_exit(priv);
+	ideapad_sysfs_exit(priv);
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ideapad_acpi_resume(struct device *device)
+{
+	struct ideapad_private *priv;
+
+	if (!device)
+		return -EINVAL;
+	priv = dev_get_drvdata(device);
+
+	ideapad_sync_rfk_state(priv);
+	ideapad_sync_touchpad_state(priv);
+	return 0;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume);
+
+static const struct acpi_device_id ideapad_device_ids[] = {
+	{ "VPC2004", 0},
+	{ "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
+
+static struct platform_driver ideapad_acpi_driver = {
+	.probe = ideapad_acpi_add,
+	.remove = ideapad_acpi_remove,
+	.driver = {
+		.name   = "ideapad_acpi",
+		.pm     = &ideapad_pm,
+		.acpi_match_table = ACPI_PTR(ideapad_device_ids),
+	},
+};
+
+module_platform_driver(ideapad_acpi_driver);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("IdeaPad ACPI Extras");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel-rst.c b/drivers/platform/x86/intel-rst.c
new file mode 100644
index 0000000..7344d84
--- /dev/null
+++ b/drivers/platform/x86/intel-rst.c
@@ -0,0 +1,161 @@
+/*
+ *  Copyright 2013 Matthew Garrett <mjg59@srcf.ucam.org>
+ *
+ *  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.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+
+MODULE_LICENSE("GPL");
+
+static ssize_t irst_show_wakeup_events(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct acpi_device *acpi;
+	unsigned long long value;
+	acpi_status status;
+
+	acpi = to_acpi_device(dev);
+
+	status = acpi_evaluate_integer(acpi->handle, "GFFS", NULL, &value);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	return sprintf(buf, "%lld\n", value);
+}
+
+static ssize_t irst_store_wakeup_events(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct acpi_device *acpi;
+	acpi_status status;
+	unsigned long value;
+	int error;
+
+	acpi = to_acpi_device(dev);
+
+	error = kstrtoul(buf, 0, &value);
+
+	if (error)
+		return error;
+
+	status = acpi_execute_simple_method(acpi->handle, "SFFS", value);
+
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	return count;
+}
+
+static struct device_attribute irst_wakeup_attr = {
+	.attr = { .name = "wakeup_events", .mode = 0600 },
+	.show = irst_show_wakeup_events,
+	.store = irst_store_wakeup_events
+};
+
+static ssize_t irst_show_wakeup_time(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct acpi_device *acpi;
+	unsigned long long value;
+	acpi_status status;
+
+	acpi = to_acpi_device(dev);
+
+	status = acpi_evaluate_integer(acpi->handle, "GFTV", NULL, &value);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	return sprintf(buf, "%lld\n", value);
+}
+
+static ssize_t irst_store_wakeup_time(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct acpi_device *acpi;
+	acpi_status status;
+	unsigned long value;
+	int error;
+
+	acpi = to_acpi_device(dev);
+
+	error = kstrtoul(buf, 0, &value);
+
+	if (error)
+		return error;
+
+	status = acpi_execute_simple_method(acpi->handle, "SFTV", value);
+
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	return count;
+}
+
+static struct device_attribute irst_timeout_attr = {
+	.attr = { .name = "wakeup_time", .mode = 0600 },
+	.show = irst_show_wakeup_time,
+	.store = irst_store_wakeup_time
+};
+
+static int irst_add(struct acpi_device *acpi)
+{
+	int error;
+
+	error = device_create_file(&acpi->dev, &irst_timeout_attr);
+	if (unlikely(error))
+		return error;
+
+	error = device_create_file(&acpi->dev, &irst_wakeup_attr);
+	if (unlikely(error))
+		device_remove_file(&acpi->dev, &irst_timeout_attr);
+
+	return error;
+}
+
+static int irst_remove(struct acpi_device *acpi)
+{
+	device_remove_file(&acpi->dev, &irst_wakeup_attr);
+	device_remove_file(&acpi->dev, &irst_timeout_attr);
+
+	return 0;
+}
+
+static const struct acpi_device_id irst_ids[] = {
+	{"INT3392", 0},
+	{"", 0}
+};
+
+static struct acpi_driver irst_driver = {
+	.owner = THIS_MODULE,
+	.name = "intel_rapid_start",
+	.class = "intel_rapid_start",
+	.ids = irst_ids,
+	.ops = {
+		.add = irst_add,
+		.remove = irst_remove,
+	},
+};
+
+module_acpi_driver(irst_driver);
+
+MODULE_DEVICE_TABLE(acpi, irst_ids);
diff --git a/drivers/platform/x86/intel-smartconnect.c b/drivers/platform/x86/intel-smartconnect.c
new file mode 100644
index 0000000..04cf5df
--- /dev/null
+++ b/drivers/platform/x86/intel-smartconnect.c
@@ -0,0 +1,60 @@
+/*
+ *  Copyright 2013 Matthew Garrett <mjg59@srcf.ucam.org>
+ *
+ *  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.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+
+MODULE_LICENSE("GPL");
+
+static int smartconnect_acpi_init(struct acpi_device *acpi)
+{
+	unsigned long long value;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(acpi->handle, "GAOS", NULL, &value);
+	if (!ACPI_SUCCESS(status))
+		return -EINVAL;
+
+	if (value & 0x1) {
+		dev_info(&acpi->dev, "Disabling Intel Smart Connect\n");
+		status = acpi_execute_simple_method(acpi->handle, "SAOS", 0);
+	}
+
+	return 0;
+}
+
+static const struct acpi_device_id smartconnect_ids[] = {
+	{"INT33A0", 0},
+	{"", 0}
+};
+
+static struct acpi_driver smartconnect_driver = {
+	.owner = THIS_MODULE,
+	.name = "intel_smart_connect",
+	.class = "intel_smart_connect",
+	.ids = smartconnect_ids,
+	.ops = {
+		.add = smartconnect_acpi_init,
+	},
+};
+
+module_acpi_driver(smartconnect_driver);
+
+MODULE_DEVICE_TABLE(acpi, smartconnect_ids);
diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c
new file mode 100644
index 0000000..55663b3
--- /dev/null
+++ b/drivers/platform/x86/intel_ips.c
@@ -0,0 +1,1738 @@
+/*
+ * Copyright (c) 2009-2010 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ *
+ * Authors:
+ *	Jesse Barnes <jbarnes@virtuousgeek.org>
+ */
+
+/*
+ * Some Intel Ibex Peak based platforms support so-called "intelligent
+ * power sharing", which allows the CPU and GPU to cooperate to maximize
+ * performance within a given TDP (thermal design point).  This driver
+ * performs the coordination between the CPU and GPU, monitors thermal and
+ * power statistics in the platform, and initializes power monitoring
+ * hardware.  It also provides a few tunables to control behavior.  Its
+ * primary purpose is to safely allow CPU and GPU turbo modes to be enabled
+ * by tracking power and thermal budget; secondarily it can boost turbo
+ * performance by allocating more power or thermal budget to the CPU or GPU
+ * based on available headroom and activity.
+ *
+ * The basic algorithm is driven by a 5s moving average of temperature.  If
+ * thermal headroom is available, the CPU and/or GPU power clamps may be
+ * adjusted upwards.  If we hit the thermal ceiling or a thermal trigger,
+ * we scale back the clamp.  Aside from trigger events (when we're critically
+ * close or over our TDP) we don't adjust the clamps more than once every
+ * five seconds.
+ *
+ * The thermal device (device 31, function 6) has a set of registers that
+ * are updated by the ME firmware.  The ME should also take the clamp values
+ * written to those registers and write them to the CPU, but we currently
+ * bypass that functionality and write the CPU MSR directly.
+ *
+ * UNSUPPORTED:
+ *   - dual MCP configs
+ *
+ * TODO:
+ *   - handle CPU hotplug
+ *   - provide turbo enable/disable api
+ *
+ * Related documents:
+ *   - CDI 403777, 403778 - Auburndale EDS vol 1 & 2
+ *   - CDI 401376 - Ibex Peak EDS
+ *   - ref 26037, 26641 - IPS BIOS spec
+ *   - ref 26489 - Nehalem BIOS writer's guide
+ *   - ref 26921 - Ibex Peak BIOS Specification
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/string.h>
+#include <linux/tick.h>
+#include <linux/timer.h>
+#include <linux/dmi.h>
+#include <drm/i915_drm.h>
+#include <asm/msr.h>
+#include <asm/processor.h>
+#include "intel_ips.h"
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+
+#define PCI_DEVICE_ID_INTEL_THERMAL_SENSOR 0x3b32
+
+/*
+ * Package level MSRs for monitor/control
+ */
+#define PLATFORM_INFO	0xce
+#define   PLATFORM_TDP		(1<<29)
+#define   PLATFORM_RATIO	(1<<28)
+
+#define IA32_MISC_ENABLE	0x1a0
+#define   IA32_MISC_TURBO_EN	(1ULL<<38)
+
+#define TURBO_POWER_CURRENT_LIMIT	0x1ac
+#define   TURBO_TDC_OVR_EN	(1UL<<31)
+#define   TURBO_TDC_MASK	(0x000000007fff0000UL)
+#define   TURBO_TDC_SHIFT	(16)
+#define   TURBO_TDP_OVR_EN	(1UL<<15)
+#define   TURBO_TDP_MASK	(0x0000000000003fffUL)
+
+/*
+ * Core/thread MSRs for monitoring
+ */
+#define IA32_PERF_CTL		0x199
+#define   IA32_PERF_TURBO_DIS	(1ULL<<32)
+
+/*
+ * Thermal PCI device regs
+ */
+#define THM_CFG_TBAR	0x10
+#define THM_CFG_TBAR_HI	0x14
+
+#define THM_TSIU	0x00
+#define THM_TSE		0x01
+#define   TSE_EN	0xb8
+#define THM_TSS		0x02
+#define THM_TSTR	0x03
+#define THM_TSTTP	0x04
+#define THM_TSCO	0x08
+#define THM_TSES	0x0c
+#define THM_TSGPEN	0x0d
+#define   TSGPEN_HOT_LOHI	(1<<1)
+#define   TSGPEN_CRIT_LOHI	(1<<2)
+#define THM_TSPC	0x0e
+#define THM_PPEC	0x10
+#define THM_CTA		0x12
+#define THM_PTA		0x14
+#define   PTA_SLOPE_MASK	(0xff00)
+#define   PTA_SLOPE_SHIFT	8
+#define   PTA_OFFSET_MASK	(0x00ff)
+#define THM_MGTA	0x16
+#define   MGTA_SLOPE_MASK	(0xff00)
+#define   MGTA_SLOPE_SHIFT	8
+#define   MGTA_OFFSET_MASK	(0x00ff)
+#define THM_TRC		0x1a
+#define   TRC_CORE2_EN	(1<<15)
+#define   TRC_THM_EN	(1<<12)
+#define   TRC_C6_WAR	(1<<8)
+#define   TRC_CORE1_EN	(1<<7)
+#define   TRC_CORE_PWR	(1<<6)
+#define   TRC_PCH_EN	(1<<5)
+#define   TRC_MCH_EN	(1<<4)
+#define   TRC_DIMM4	(1<<3)
+#define   TRC_DIMM3	(1<<2)
+#define   TRC_DIMM2	(1<<1)
+#define   TRC_DIMM1	(1<<0)
+#define THM_TES		0x20
+#define THM_TEN		0x21
+#define   TEN_UPDATE_EN	1
+#define THM_PSC		0x24
+#define   PSC_NTG	(1<<0) /* No GFX turbo support */
+#define   PSC_NTPC	(1<<1) /* No CPU turbo support */
+#define   PSC_PP_DEF	(0<<2) /* Perf policy up to driver */
+#define   PSP_PP_PC	(1<<2) /* BIOS prefers CPU perf */
+#define   PSP_PP_BAL	(2<<2) /* BIOS wants balanced perf */
+#define   PSP_PP_GFX	(3<<2) /* BIOS prefers GFX perf */
+#define   PSP_PBRT	(1<<4) /* BIOS run time support */
+#define THM_CTV1	0x30
+#define   CTV_TEMP_ERROR (1<<15)
+#define   CTV_TEMP_MASK	0x3f
+#define   CTV_
+#define THM_CTV2	0x32
+#define THM_CEC		0x34 /* undocumented power accumulator in joules */
+#define THM_AE		0x3f
+#define THM_HTS		0x50 /* 32 bits */
+#define   HTS_PCPL_MASK	(0x7fe00000)
+#define   HTS_PCPL_SHIFT 21
+#define   HTS_GPL_MASK  (0x001ff000)
+#define   HTS_GPL_SHIFT 12
+#define   HTS_PP_MASK	(0x00000c00)
+#define   HTS_PP_SHIFT  10
+#define   HTS_PP_DEF	0
+#define   HTS_PP_PROC	1
+#define   HTS_PP_BAL	2
+#define   HTS_PP_GFX	3
+#define   HTS_PCTD_DIS	(1<<9)
+#define   HTS_GTD_DIS	(1<<8)
+#define   HTS_PTL_MASK  (0x000000fe)
+#define   HTS_PTL_SHIFT 1
+#define   HTS_NVV	(1<<0)
+#define THM_HTSHI	0x54 /* 16 bits */
+#define   HTS2_PPL_MASK		(0x03ff)
+#define   HTS2_PRST_MASK	(0x3c00)
+#define   HTS2_PRST_SHIFT	10
+#define   HTS2_PRST_UNLOADED	0
+#define   HTS2_PRST_RUNNING	1
+#define   HTS2_PRST_TDISOP	2 /* turbo disabled due to power */
+#define   HTS2_PRST_TDISHT	3 /* turbo disabled due to high temp */
+#define   HTS2_PRST_TDISUSR	4 /* user disabled turbo */
+#define   HTS2_PRST_TDISPLAT	5 /* platform disabled turbo */
+#define   HTS2_PRST_TDISPM	6 /* power management disabled turbo */
+#define   HTS2_PRST_TDISERR	7 /* some kind of error disabled turbo */
+#define THM_PTL		0x56
+#define THM_MGTV	0x58
+#define   TV_MASK	0x000000000000ff00
+#define   TV_SHIFT	8
+#define THM_PTV		0x60
+#define   PTV_MASK	0x00ff
+#define THM_MMGPC	0x64
+#define THM_MPPC	0x66
+#define THM_MPCPC	0x68
+#define THM_TSPIEN	0x82
+#define   TSPIEN_AUX_LOHI	(1<<0)
+#define   TSPIEN_HOT_LOHI	(1<<1)
+#define   TSPIEN_CRIT_LOHI	(1<<2)
+#define   TSPIEN_AUX2_LOHI	(1<<3)
+#define THM_TSLOCK	0x83
+#define THM_ATR		0x84
+#define THM_TOF		0x87
+#define THM_STS		0x98
+#define   STS_PCPL_MASK		(0x7fe00000)
+#define   STS_PCPL_SHIFT	21
+#define   STS_GPL_MASK		(0x001ff000)
+#define   STS_GPL_SHIFT		12
+#define   STS_PP_MASK		(0x00000c00)
+#define   STS_PP_SHIFT		10
+#define   STS_PP_DEF		0
+#define   STS_PP_PROC		1
+#define   STS_PP_BAL		2
+#define   STS_PP_GFX		3
+#define   STS_PCTD_DIS		(1<<9)
+#define   STS_GTD_DIS		(1<<8)
+#define   STS_PTL_MASK		(0x000000fe)
+#define   STS_PTL_SHIFT		1
+#define   STS_NVV		(1<<0)
+#define THM_SEC		0x9c
+#define   SEC_ACK	(1<<0)
+#define THM_TC3		0xa4
+#define THM_TC1		0xa8
+#define   STS_PPL_MASK		(0x0003ff00)
+#define   STS_PPL_SHIFT		16
+#define THM_TC2		0xac
+#define THM_DTV		0xb0
+#define THM_ITV		0xd8
+#define   ITV_ME_SEQNO_MASK 0x00ff0000 /* ME should update every ~200ms */
+#define   ITV_ME_SEQNO_SHIFT (16)
+#define   ITV_MCH_TEMP_MASK 0x0000ff00
+#define   ITV_MCH_TEMP_SHIFT (8)
+#define   ITV_PCH_TEMP_MASK 0x000000ff
+
+#define thm_readb(off) readb(ips->regmap + (off))
+#define thm_readw(off) readw(ips->regmap + (off))
+#define thm_readl(off) readl(ips->regmap + (off))
+#define thm_readq(off) readq(ips->regmap + (off))
+
+#define thm_writeb(off, val) writeb((val), ips->regmap + (off))
+#define thm_writew(off, val) writew((val), ips->regmap + (off))
+#define thm_writel(off, val) writel((val), ips->regmap + (off))
+
+static const int IPS_ADJUST_PERIOD = 5000; /* ms */
+static bool late_i915_load = false;
+
+/* For initial average collection */
+static const int IPS_SAMPLE_PERIOD = 200; /* ms */
+static const int IPS_SAMPLE_WINDOW = 5000; /* 5s moving window of samples */
+#define IPS_SAMPLE_COUNT (IPS_SAMPLE_WINDOW / IPS_SAMPLE_PERIOD)
+
+/* Per-SKU limits */
+struct ips_mcp_limits {
+	int cpu_family;
+	int cpu_model; /* includes extended model... */
+	int mcp_power_limit; /* mW units */
+	int core_power_limit;
+	int mch_power_limit;
+	int core_temp_limit; /* degrees C */
+	int mch_temp_limit;
+};
+
+/* Max temps are -10 degrees C to avoid PROCHOT# */
+
+static struct ips_mcp_limits ips_sv_limits = {
+	.mcp_power_limit = 35000,
+	.core_power_limit = 29000,
+	.mch_power_limit = 20000,
+	.core_temp_limit = 95,
+	.mch_temp_limit = 90
+};
+
+static struct ips_mcp_limits ips_lv_limits = {
+	.mcp_power_limit = 25000,
+	.core_power_limit = 21000,
+	.mch_power_limit = 13000,
+	.core_temp_limit = 95,
+	.mch_temp_limit = 90
+};
+
+static struct ips_mcp_limits ips_ulv_limits = {
+	.mcp_power_limit = 18000,
+	.core_power_limit = 14000,
+	.mch_power_limit = 11000,
+	.core_temp_limit = 95,
+	.mch_temp_limit = 90
+};
+
+struct ips_driver {
+	struct pci_dev *dev;
+	void *regmap;
+	struct task_struct *monitor;
+	struct task_struct *adjust;
+	struct dentry *debug_root;
+
+	/* Average CPU core temps (all averages in .01 degrees C for precision) */
+	u16 ctv1_avg_temp;
+	u16 ctv2_avg_temp;
+	/* GMCH average */
+	u16 mch_avg_temp;
+	/* Average for the CPU (both cores?) */
+	u16 mcp_avg_temp;
+	/* Average power consumption (in mW) */
+	u32 cpu_avg_power;
+	u32 mch_avg_power;
+
+	/* Offset values */
+	u16 cta_val;
+	u16 pta_val;
+	u16 mgta_val;
+
+	/* Maximums & prefs, protected by turbo status lock */
+	spinlock_t turbo_status_lock;
+	u16 mcp_temp_limit;
+	u16 mcp_power_limit;
+	u16 core_power_limit;
+	u16 mch_power_limit;
+	bool cpu_turbo_enabled;
+	bool __cpu_turbo_on;
+	bool gpu_turbo_enabled;
+	bool __gpu_turbo_on;
+	bool gpu_preferred;
+	bool poll_turbo_status;
+	bool second_cpu;
+	bool turbo_toggle_allowed;
+	struct ips_mcp_limits *limits;
+
+	/* Optional MCH interfaces for if i915 is in use */
+	unsigned long (*read_mch_val)(void);
+	bool (*gpu_raise)(void);
+	bool (*gpu_lower)(void);
+	bool (*gpu_busy)(void);
+	bool (*gpu_turbo_disable)(void);
+
+	/* For restoration at unload */
+	u64 orig_turbo_limit;
+	u64 orig_turbo_ratios;
+};
+
+static bool
+ips_gpu_turbo_enabled(struct ips_driver *ips);
+
+/**
+ * ips_cpu_busy - is CPU busy?
+ * @ips: IPS driver struct
+ *
+ * Check CPU for load to see whether we should increase its thermal budget.
+ *
+ * RETURNS:
+ * True if the CPU could use more power, false otherwise.
+ */
+static bool ips_cpu_busy(struct ips_driver *ips)
+{
+	if ((avenrun[0] >> FSHIFT) > 1)
+		return true;
+
+	return false;
+}
+
+/**
+ * ips_cpu_raise - raise CPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Raise the CPU power clamp by %IPS_CPU_STEP, in accordance with TDP for
+ * this platform.
+ *
+ * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR upwards (as
+ * long as we haven't hit the TDP limit for the SKU).
+ */
+static void ips_cpu_raise(struct ips_driver *ips)
+{
+	u64 turbo_override;
+	u16 cur_tdp_limit, new_tdp_limit;
+
+	if (!ips->cpu_turbo_enabled)
+		return;
+
+	rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+	cur_tdp_limit = turbo_override & TURBO_TDP_MASK;
+	new_tdp_limit = cur_tdp_limit + 8; /* 1W increase */
+
+	/* Clamp to SKU TDP limit */
+	if (((new_tdp_limit * 10) / 8) > ips->core_power_limit)
+		new_tdp_limit = cur_tdp_limit;
+
+	thm_writew(THM_MPCPC, (new_tdp_limit * 10) / 8);
+
+	turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;
+	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+	turbo_override &= ~TURBO_TDP_MASK;
+	turbo_override |= new_tdp_limit;
+
+	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+}
+
+/**
+ * ips_cpu_lower - lower CPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Lower CPU power clamp b %IPS_CPU_STEP if possible.
+ *
+ * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR down, going
+ * as low as the platform limits will allow (though we could go lower there
+ * wouldn't be much point).
+ */
+static void ips_cpu_lower(struct ips_driver *ips)
+{
+	u64 turbo_override;
+	u16 cur_limit, new_limit;
+
+	rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+	cur_limit = turbo_override & TURBO_TDP_MASK;
+	new_limit = cur_limit - 8; /* 1W decrease */
+
+	/* Clamp to SKU TDP limit */
+	if (new_limit  < (ips->orig_turbo_limit & TURBO_TDP_MASK))
+		new_limit = ips->orig_turbo_limit & TURBO_TDP_MASK;
+
+	thm_writew(THM_MPCPC, (new_limit * 10) / 8);
+
+	turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;
+	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+	turbo_override &= ~TURBO_TDP_MASK;
+	turbo_override |= new_limit;
+
+	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+}
+
+/**
+ * do_enable_cpu_turbo - internal turbo enable function
+ * @data: unused
+ *
+ * Internal function for actually updating MSRs.  When we enable/disable
+ * turbo, we need to do it on each CPU; this function is the one called
+ * by on_each_cpu() when needed.
+ */
+static void do_enable_cpu_turbo(void *data)
+{
+	u64 perf_ctl;
+
+	rdmsrl(IA32_PERF_CTL, perf_ctl);
+	if (perf_ctl & IA32_PERF_TURBO_DIS) {
+		perf_ctl &= ~IA32_PERF_TURBO_DIS;
+		wrmsrl(IA32_PERF_CTL, perf_ctl);
+	}
+}
+
+/**
+ * ips_enable_cpu_turbo - enable turbo mode on all CPUs
+ * @ips: IPS driver struct
+ *
+ * Enable turbo mode by clearing the disable bit in IA32_PERF_CTL on
+ * all logical threads.
+ */
+static void ips_enable_cpu_turbo(struct ips_driver *ips)
+{
+	/* Already on, no need to mess with MSRs */
+	if (ips->__cpu_turbo_on)
+		return;
+
+	if (ips->turbo_toggle_allowed)
+		on_each_cpu(do_enable_cpu_turbo, ips, 1);
+
+	ips->__cpu_turbo_on = true;
+}
+
+/**
+ * do_disable_cpu_turbo - internal turbo disable function
+ * @data: unused
+ *
+ * Internal function for actually updating MSRs.  When we enable/disable
+ * turbo, we need to do it on each CPU; this function is the one called
+ * by on_each_cpu() when needed.
+ */
+static void do_disable_cpu_turbo(void *data)
+{
+	u64 perf_ctl;
+
+	rdmsrl(IA32_PERF_CTL, perf_ctl);
+	if (!(perf_ctl & IA32_PERF_TURBO_DIS)) {
+		perf_ctl |= IA32_PERF_TURBO_DIS;
+		wrmsrl(IA32_PERF_CTL, perf_ctl);
+	}
+}
+
+/**
+ * ips_disable_cpu_turbo - disable turbo mode on all CPUs
+ * @ips: IPS driver struct
+ *
+ * Disable turbo mode by setting the disable bit in IA32_PERF_CTL on
+ * all logical threads.
+ */
+static void ips_disable_cpu_turbo(struct ips_driver *ips)
+{
+	/* Already off, leave it */
+	if (!ips->__cpu_turbo_on)
+		return;
+
+	if (ips->turbo_toggle_allowed)
+		on_each_cpu(do_disable_cpu_turbo, ips, 1);
+
+	ips->__cpu_turbo_on = false;
+}
+
+/**
+ * ips_gpu_busy - is GPU busy?
+ * @ips: IPS driver struct
+ *
+ * Check GPU for load to see whether we should increase its thermal budget.
+ * We need to call into the i915 driver in this case.
+ *
+ * RETURNS:
+ * True if the GPU could use more power, false otherwise.
+ */
+static bool ips_gpu_busy(struct ips_driver *ips)
+{
+	if (!ips_gpu_turbo_enabled(ips))
+		return false;
+
+	return ips->gpu_busy();
+}
+
+/**
+ * ips_gpu_raise - raise GPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Raise the GPU frequency/power if possible.  We need to call into the
+ * i915 driver in this case.
+ */
+static void ips_gpu_raise(struct ips_driver *ips)
+{
+	if (!ips_gpu_turbo_enabled(ips))
+		return;
+
+	if (!ips->gpu_raise())
+		ips->gpu_turbo_enabled = false;
+
+	return;
+}
+
+/**
+ * ips_gpu_lower - lower GPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Lower GPU frequency/power if possible.  Need to call i915.
+ */
+static void ips_gpu_lower(struct ips_driver *ips)
+{
+	if (!ips_gpu_turbo_enabled(ips))
+		return;
+
+	if (!ips->gpu_lower())
+		ips->gpu_turbo_enabled = false;
+
+	return;
+}
+
+/**
+ * ips_enable_gpu_turbo - notify the gfx driver turbo is available
+ * @ips: IPS driver struct
+ *
+ * Call into the graphics driver indicating that it can safely use
+ * turbo mode.
+ */
+static void ips_enable_gpu_turbo(struct ips_driver *ips)
+{
+	if (ips->__gpu_turbo_on)
+		return;
+	ips->__gpu_turbo_on = true;
+}
+
+/**
+ * ips_disable_gpu_turbo - notify the gfx driver to disable turbo mode
+ * @ips: IPS driver struct
+ *
+ * Request that the graphics driver disable turbo mode.
+ */
+static void ips_disable_gpu_turbo(struct ips_driver *ips)
+{
+	/* Avoid calling i915 if turbo is already disabled */
+	if (!ips->__gpu_turbo_on)
+		return;
+
+	if (!ips->gpu_turbo_disable())
+		dev_err(&ips->dev->dev, "failed to disable graphics turbo\n");
+	else
+		ips->__gpu_turbo_on = false;
+}
+
+/**
+ * mcp_exceeded - check whether we're outside our thermal & power limits
+ * @ips: IPS driver struct
+ *
+ * Check whether the MCP is over its thermal or power budget.
+ */
+static bool mcp_exceeded(struct ips_driver *ips)
+{
+	unsigned long flags;
+	bool ret = false;
+	u32 temp_limit;
+	u32 avg_power;
+
+	spin_lock_irqsave(&ips->turbo_status_lock, flags);
+
+	temp_limit = ips->mcp_temp_limit * 100;
+	if (ips->mcp_avg_temp > temp_limit)
+		ret = true;
+
+	avg_power = ips->cpu_avg_power + ips->mch_avg_power;
+	if (avg_power > ips->mcp_power_limit)
+		ret = true;
+
+	spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+	return ret;
+}
+
+/**
+ * cpu_exceeded - check whether a CPU core is outside its limits
+ * @ips: IPS driver struct
+ * @cpu: CPU number to check
+ *
+ * Check a given CPU's average temp or power is over its limit.
+ */
+static bool cpu_exceeded(struct ips_driver *ips, int cpu)
+{
+	unsigned long flags;
+	int avg;
+	bool ret = false;
+
+	spin_lock_irqsave(&ips->turbo_status_lock, flags);
+	avg = cpu ? ips->ctv2_avg_temp : ips->ctv1_avg_temp;
+	if (avg > (ips->limits->core_temp_limit * 100))
+		ret = true;
+	if (ips->cpu_avg_power > ips->core_power_limit * 100)
+		ret = true;
+	spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+	if (ret)
+		dev_info(&ips->dev->dev,
+			 "CPU power or thermal limit exceeded\n");
+
+	return ret;
+}
+
+/**
+ * mch_exceeded - check whether the GPU is over budget
+ * @ips: IPS driver struct
+ *
+ * Check the MCH temp & power against their maximums.
+ */
+static bool mch_exceeded(struct ips_driver *ips)
+{
+	unsigned long flags;
+	bool ret = false;
+
+	spin_lock_irqsave(&ips->turbo_status_lock, flags);
+	if (ips->mch_avg_temp > (ips->limits->mch_temp_limit * 100))
+		ret = true;
+	if (ips->mch_avg_power > ips->mch_power_limit)
+		ret = true;
+	spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+	return ret;
+}
+
+/**
+ * verify_limits - verify BIOS provided limits
+ * @ips: IPS structure
+ *
+ * BIOS can optionally provide non-default limits for power and temp.  Check
+ * them here and use the defaults if the BIOS values are not provided or
+ * are otherwise unusable.
+ */
+static void verify_limits(struct ips_driver *ips)
+{
+	if (ips->mcp_power_limit < ips->limits->mcp_power_limit ||
+	    ips->mcp_power_limit > 35000)
+		ips->mcp_power_limit = ips->limits->mcp_power_limit;
+
+	if (ips->mcp_temp_limit < ips->limits->core_temp_limit ||
+	    ips->mcp_temp_limit < ips->limits->mch_temp_limit ||
+	    ips->mcp_temp_limit > 150)
+		ips->mcp_temp_limit = min(ips->limits->core_temp_limit,
+					  ips->limits->mch_temp_limit);
+}
+
+/**
+ * update_turbo_limits - get various limits & settings from regs
+ * @ips: IPS driver struct
+ *
+ * Update the IPS power & temp limits, along with turbo enable flags,
+ * based on latest register contents.
+ *
+ * Used at init time and for runtime BIOS support, which requires polling
+ * the regs for updates (as a result of AC->DC transition for example).
+ *
+ * LOCKING:
+ * Caller must hold turbo_status_lock (outside of init)
+ */
+static void update_turbo_limits(struct ips_driver *ips)
+{
+	u32 hts = thm_readl(THM_HTS);
+
+	ips->cpu_turbo_enabled = !(hts & HTS_PCTD_DIS);
+	/* 
+	 * Disable turbo for now, until we can figure out why the power figures
+	 * are wrong
+	 */
+	ips->cpu_turbo_enabled = false;
+
+	if (ips->gpu_busy)
+		ips->gpu_turbo_enabled = !(hts & HTS_GTD_DIS);
+
+	ips->core_power_limit = thm_readw(THM_MPCPC);
+	ips->mch_power_limit = thm_readw(THM_MMGPC);
+	ips->mcp_temp_limit = thm_readw(THM_PTL);
+	ips->mcp_power_limit = thm_readw(THM_MPPC);
+
+	verify_limits(ips);
+	/* Ignore BIOS CPU vs GPU pref */
+}
+
+/**
+ * ips_adjust - adjust power clamp based on thermal state
+ * @data: ips driver structure
+ *
+ * Wake up every 5s or so and check whether we should adjust the power clamp.
+ * Check CPU and GPU load to determine which needs adjustment.  There are
+ * several things to consider here:
+ *   - do we need to adjust up or down?
+ *   - is CPU busy?
+ *   - is GPU busy?
+ *   - is CPU in turbo?
+ *   - is GPU in turbo?
+ *   - is CPU or GPU preferred? (CPU is default)
+ *
+ * So, given the above, we do the following:
+ *   - up (TDP available)
+ *     - CPU not busy, GPU not busy - nothing
+ *     - CPU busy, GPU not busy - adjust CPU up
+ *     - CPU not busy, GPU busy - adjust GPU up
+ *     - CPU busy, GPU busy - adjust preferred unit up, taking headroom from
+ *       non-preferred unit if necessary
+ *   - down (at TDP limit)
+ *     - adjust both CPU and GPU down if possible
+ *
+		cpu+ gpu+	cpu+gpu-	cpu-gpu+	cpu-gpu-
+cpu < gpu <	cpu+gpu+	cpu+		gpu+		nothing
+cpu < gpu >=	cpu+gpu-(mcp<)	cpu+gpu-(mcp<)	gpu-		gpu-
+cpu >= gpu <	cpu-gpu+(mcp<)	cpu-		cpu-gpu+(mcp<)	cpu-
+cpu >= gpu >=	cpu-gpu-	cpu-gpu-	cpu-gpu-	cpu-gpu-
+ *
+ */
+static int ips_adjust(void *data)
+{
+	struct ips_driver *ips = data;
+	unsigned long flags;
+
+	dev_dbg(&ips->dev->dev, "starting ips-adjust thread\n");
+
+	/*
+	 * Adjust CPU and GPU clamps every 5s if needed.  Doing it more
+	 * often isn't recommended due to ME interaction.
+	 */
+	do {
+		bool cpu_busy = ips_cpu_busy(ips);
+		bool gpu_busy = ips_gpu_busy(ips);
+
+		spin_lock_irqsave(&ips->turbo_status_lock, flags);
+		if (ips->poll_turbo_status)
+			update_turbo_limits(ips);
+		spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+		/* Update turbo status if necessary */
+		if (ips->cpu_turbo_enabled)
+			ips_enable_cpu_turbo(ips);
+		else
+			ips_disable_cpu_turbo(ips);
+
+		if (ips->gpu_turbo_enabled)
+			ips_enable_gpu_turbo(ips);
+		else
+			ips_disable_gpu_turbo(ips);
+
+		/* We're outside our comfort zone, crank them down */
+		if (mcp_exceeded(ips)) {
+			ips_cpu_lower(ips);
+			ips_gpu_lower(ips);
+			goto sleep;
+		}
+
+		if (!cpu_exceeded(ips, 0) && cpu_busy)
+			ips_cpu_raise(ips);
+		else
+			ips_cpu_lower(ips);
+
+		if (!mch_exceeded(ips) && gpu_busy)
+			ips_gpu_raise(ips);
+		else
+			ips_gpu_lower(ips);
+
+sleep:
+		schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD));
+	} while (!kthread_should_stop());
+
+	dev_dbg(&ips->dev->dev, "ips-adjust thread stopped\n");
+
+	return 0;
+}
+
+/*
+ * Helpers for reading out temp/power values and calculating their
+ * averages for the decision making and monitoring functions.
+ */
+
+static u16 calc_avg_temp(struct ips_driver *ips, u16 *array)
+{
+	u64 total = 0;
+	int i;
+	u16 avg;
+
+	for (i = 0; i < IPS_SAMPLE_COUNT; i++)
+		total += (u64)(array[i] * 100);
+
+	do_div(total, IPS_SAMPLE_COUNT);
+
+	avg = (u16)total;
+
+	return avg;
+}
+
+static u16 read_mgtv(struct ips_driver *ips)
+{
+	u16 ret;
+	u64 slope, offset;
+	u64 val;
+
+	val = thm_readq(THM_MGTV);
+	val = (val & TV_MASK) >> TV_SHIFT;
+
+	slope = offset = thm_readw(THM_MGTA);
+	slope = (slope & MGTA_SLOPE_MASK) >> MGTA_SLOPE_SHIFT;
+	offset = offset & MGTA_OFFSET_MASK;
+
+	ret = ((val * slope + 0x40) >> 7) + offset;
+
+	return 0; /* MCH temp reporting buggy */
+}
+
+static u16 read_ptv(struct ips_driver *ips)
+{
+	u16 val, slope, offset;
+
+	slope = (ips->pta_val & PTA_SLOPE_MASK) >> PTA_SLOPE_SHIFT;
+	offset = ips->pta_val & PTA_OFFSET_MASK;
+
+	val = thm_readw(THM_PTV) & PTV_MASK;
+
+	return val;
+}
+
+static u16 read_ctv(struct ips_driver *ips, int cpu)
+{
+	int reg = cpu ? THM_CTV2 : THM_CTV1;
+	u16 val;
+
+	val = thm_readw(reg);
+	if (!(val & CTV_TEMP_ERROR))
+		val = (val) >> 6; /* discard fractional component */
+	else
+		val = 0;
+
+	return val;
+}
+
+static u32 get_cpu_power(struct ips_driver *ips, u32 *last, int period)
+{
+	u32 val;
+	u32 ret;
+
+	/*
+	 * CEC is in joules/65535.  Take difference over time to
+	 * get watts.
+	 */
+	val = thm_readl(THM_CEC);
+
+	/* period is in ms and we want mW */
+	ret = (((val - *last) * 1000) / period);
+	ret = (ret * 1000) / 65535;
+	*last = val;
+
+	return 0;
+}
+
+static const u16 temp_decay_factor = 2;
+static u16 update_average_temp(u16 avg, u16 val)
+{
+	u16 ret;
+
+	/* Multiply by 100 for extra precision */
+	ret = (val * 100 / temp_decay_factor) +
+		(((temp_decay_factor - 1) * avg) / temp_decay_factor);
+	return ret;
+}
+
+static const u16 power_decay_factor = 2;
+static u16 update_average_power(u32 avg, u32 val)
+{
+	u32 ret;
+
+	ret = (val / power_decay_factor) +
+		(((power_decay_factor - 1) * avg) / power_decay_factor);
+
+	return ret;
+}
+
+static u32 calc_avg_power(struct ips_driver *ips, u32 *array)
+{
+	u64 total = 0;
+	u32 avg;
+	int i;
+
+	for (i = 0; i < IPS_SAMPLE_COUNT; i++)
+		total += array[i];
+
+	do_div(total, IPS_SAMPLE_COUNT);
+	avg = (u32)total;
+
+	return avg;
+}
+
+static void monitor_timeout(unsigned long arg)
+{
+	wake_up_process((struct task_struct *)arg);
+}
+
+/**
+ * ips_monitor - temp/power monitoring thread
+ * @data: ips driver structure
+ *
+ * This is the main function for the IPS driver.  It monitors power and
+ * tempurature in the MCP and adjusts CPU and GPU power clams accordingly.
+ *
+ * We keep a 5s moving average of power consumption and tempurature.  Using
+ * that data, along with CPU vs GPU preference, we adjust the power clamps
+ * up or down.
+ */
+static int ips_monitor(void *data)
+{
+	struct ips_driver *ips = data;
+	struct timer_list timer;
+	unsigned long seqno_timestamp, expire, last_msecs, last_sample_period;
+	int i;
+	u32 *cpu_samples, *mchp_samples, old_cpu_power;
+	u16 *mcp_samples, *ctv1_samples, *ctv2_samples, *mch_samples;
+	u8 cur_seqno, last_seqno;
+
+	mcp_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+	ctv1_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+	ctv2_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+	mch_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+	cpu_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+	mchp_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+	if (!mcp_samples || !ctv1_samples || !ctv2_samples || !mch_samples ||
+			!cpu_samples || !mchp_samples) {
+		dev_err(&ips->dev->dev,
+			"failed to allocate sample array, ips disabled\n");
+		kfree(mcp_samples);
+		kfree(ctv1_samples);
+		kfree(ctv2_samples);
+		kfree(mch_samples);
+		kfree(cpu_samples);
+		kfree(mchp_samples);
+		return -ENOMEM;
+	}
+
+	last_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >>
+		ITV_ME_SEQNO_SHIFT;
+	seqno_timestamp = get_jiffies_64();
+
+	old_cpu_power = thm_readl(THM_CEC);
+	schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD));
+
+	/* Collect an initial average */
+	for (i = 0; i < IPS_SAMPLE_COUNT; i++) {
+		u32 mchp, cpu_power;
+		u16 val;
+
+		mcp_samples[i] = read_ptv(ips);
+
+		val = read_ctv(ips, 0);
+		ctv1_samples[i] = val;
+
+		val = read_ctv(ips, 1);
+		ctv2_samples[i] = val;
+
+		val = read_mgtv(ips);
+		mch_samples[i] = val;
+
+		cpu_power = get_cpu_power(ips, &old_cpu_power,
+					  IPS_SAMPLE_PERIOD);
+		cpu_samples[i] = cpu_power;
+
+		if (ips->read_mch_val) {
+			mchp = ips->read_mch_val();
+			mchp_samples[i] = mchp;
+		}
+
+		schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD));
+		if (kthread_should_stop())
+			break;
+	}
+
+	ips->mcp_avg_temp = calc_avg_temp(ips, mcp_samples);
+	ips->ctv1_avg_temp = calc_avg_temp(ips, ctv1_samples);
+	ips->ctv2_avg_temp = calc_avg_temp(ips, ctv2_samples);
+	ips->mch_avg_temp = calc_avg_temp(ips, mch_samples);
+	ips->cpu_avg_power = calc_avg_power(ips, cpu_samples);
+	ips->mch_avg_power = calc_avg_power(ips, mchp_samples);
+	kfree(mcp_samples);
+	kfree(ctv1_samples);
+	kfree(ctv2_samples);
+	kfree(mch_samples);
+	kfree(cpu_samples);
+	kfree(mchp_samples);
+
+	/* Start the adjustment thread now that we have data */
+	wake_up_process(ips->adjust);
+
+	/*
+	 * Ok, now we have an initial avg.  From here on out, we track the
+	 * running avg using a decaying average calculation.  This allows
+	 * us to reduce the sample frequency if the CPU and GPU are idle.
+	 */
+	old_cpu_power = thm_readl(THM_CEC);
+	schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD));
+	last_sample_period = IPS_SAMPLE_PERIOD;
+
+	setup_deferrable_timer_on_stack(&timer, monitor_timeout,
+					(unsigned long)current);
+	do {
+		u32 cpu_val, mch_val;
+		u16 val;
+
+		/* MCP itself */
+		val = read_ptv(ips);
+		ips->mcp_avg_temp = update_average_temp(ips->mcp_avg_temp, val);
+
+		/* Processor 0 */
+		val = read_ctv(ips, 0);
+		ips->ctv1_avg_temp =
+			update_average_temp(ips->ctv1_avg_temp, val);
+		/* Power */
+		cpu_val = get_cpu_power(ips, &old_cpu_power,
+					last_sample_period);
+		ips->cpu_avg_power =
+			update_average_power(ips->cpu_avg_power, cpu_val);
+
+		if (ips->second_cpu) {
+			/* Processor 1 */
+			val = read_ctv(ips, 1);
+			ips->ctv2_avg_temp =
+				update_average_temp(ips->ctv2_avg_temp, val);
+		}
+
+		/* MCH */
+		val = read_mgtv(ips);
+		ips->mch_avg_temp = update_average_temp(ips->mch_avg_temp, val);
+		/* Power */
+		if (ips->read_mch_val) {
+			mch_val = ips->read_mch_val();
+			ips->mch_avg_power =
+				update_average_power(ips->mch_avg_power,
+						     mch_val);
+		}
+
+		/*
+		 * Make sure ME is updating thermal regs.
+		 * Note:
+		 * If it's been more than a second since the last update,
+		 * the ME is probably hung.
+		 */
+		cur_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >>
+			ITV_ME_SEQNO_SHIFT;
+		if (cur_seqno == last_seqno &&
+		    time_after(jiffies, seqno_timestamp + HZ)) {
+			dev_warn(&ips->dev->dev, "ME failed to update for more than 1s, likely hung\n");
+		} else {
+			seqno_timestamp = get_jiffies_64();
+			last_seqno = cur_seqno;
+		}
+
+		last_msecs = jiffies_to_msecs(jiffies);
+		expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD);
+
+		__set_current_state(TASK_INTERRUPTIBLE);
+		mod_timer(&timer, expire);
+		schedule();
+
+		/* Calculate actual sample period for power averaging */
+		last_sample_period = jiffies_to_msecs(jiffies) - last_msecs;
+		if (!last_sample_period)
+			last_sample_period = 1;
+	} while (!kthread_should_stop());
+
+	del_timer_sync(&timer);
+	destroy_timer_on_stack(&timer);
+
+	dev_dbg(&ips->dev->dev, "ips-monitor thread stopped\n");
+
+	return 0;
+}
+
+#if 0
+#define THM_DUMPW(reg) \
+	{ \
+	u16 val = thm_readw(reg); \
+	dev_dbg(&ips->dev->dev, #reg ": 0x%04x\n", val); \
+	}
+#define THM_DUMPL(reg) \
+	{ \
+	u32 val = thm_readl(reg); \
+	dev_dbg(&ips->dev->dev, #reg ": 0x%08x\n", val); \
+	}
+#define THM_DUMPQ(reg) \
+	{ \
+	u64 val = thm_readq(reg); \
+	dev_dbg(&ips->dev->dev, #reg ": 0x%016x\n", val); \
+	}
+
+static void dump_thermal_info(struct ips_driver *ips)
+{
+	u16 ptl;
+
+	ptl = thm_readw(THM_PTL);
+	dev_dbg(&ips->dev->dev, "Processor temp limit: %d\n", ptl);
+
+	THM_DUMPW(THM_CTA);
+	THM_DUMPW(THM_TRC);
+	THM_DUMPW(THM_CTV1);
+	THM_DUMPL(THM_STS);
+	THM_DUMPW(THM_PTV);
+	THM_DUMPQ(THM_MGTV);
+}
+#endif
+
+/**
+ * ips_irq_handler - handle temperature triggers and other IPS events
+ * @irq: irq number
+ * @arg: unused
+ *
+ * Handle temperature limit trigger events, generally by lowering the clamps.
+ * If we're at a critical limit, we clamp back to the lowest possible value
+ * to prevent emergency shutdown.
+ */
+static irqreturn_t ips_irq_handler(int irq, void *arg)
+{
+	struct ips_driver *ips = arg;
+	u8 tses = thm_readb(THM_TSES);
+	u8 tes = thm_readb(THM_TES);
+
+	if (!tses && !tes)
+		return IRQ_NONE;
+
+	dev_info(&ips->dev->dev, "TSES: 0x%02x\n", tses);
+	dev_info(&ips->dev->dev, "TES: 0x%02x\n", tes);
+
+	/* STS update from EC? */
+	if (tes & 1) {
+		u32 sts, tc1;
+
+		sts = thm_readl(THM_STS);
+		tc1 = thm_readl(THM_TC1);
+
+		if (sts & STS_NVV) {
+			spin_lock(&ips->turbo_status_lock);
+			ips->core_power_limit = (sts & STS_PCPL_MASK) >>
+				STS_PCPL_SHIFT;
+			ips->mch_power_limit = (sts & STS_GPL_MASK) >>
+				STS_GPL_SHIFT;
+			/* ignore EC CPU vs GPU pref */
+			ips->cpu_turbo_enabled = !(sts & STS_PCTD_DIS);
+			/* 
+			 * Disable turbo for now, until we can figure
+			 * out why the power figures are wrong
+			 */
+			ips->cpu_turbo_enabled = false;
+			if (ips->gpu_busy)
+				ips->gpu_turbo_enabled = !(sts & STS_GTD_DIS);
+			ips->mcp_temp_limit = (sts & STS_PTL_MASK) >>
+				STS_PTL_SHIFT;
+			ips->mcp_power_limit = (tc1 & STS_PPL_MASK) >>
+				STS_PPL_SHIFT;
+			verify_limits(ips);
+			spin_unlock(&ips->turbo_status_lock);
+
+			thm_writeb(THM_SEC, SEC_ACK);
+		}
+		thm_writeb(THM_TES, tes);
+	}
+
+	/* Thermal trip */
+	if (tses) {
+		dev_warn(&ips->dev->dev,
+			 "thermal trip occurred, tses: 0x%04x\n", tses);
+		thm_writeb(THM_TSES, tses);
+	}
+
+	return IRQ_HANDLED;
+}
+
+#ifndef CONFIG_DEBUG_FS
+static void ips_debugfs_init(struct ips_driver *ips) { return; }
+static void ips_debugfs_cleanup(struct ips_driver *ips) { return; }
+#else
+
+/* Expose current state and limits in debugfs if possible */
+
+struct ips_debugfs_node {
+	struct ips_driver *ips;
+	char *name;
+	int (*show)(struct seq_file *m, void *data);
+};
+
+static int show_cpu_temp(struct seq_file *m, void *data)
+{
+	struct ips_driver *ips = m->private;
+
+	seq_printf(m, "%d.%02d\n", ips->ctv1_avg_temp / 100,
+		   ips->ctv1_avg_temp % 100);
+
+	return 0;
+}
+
+static int show_cpu_power(struct seq_file *m, void *data)
+{
+	struct ips_driver *ips = m->private;
+
+	seq_printf(m, "%dmW\n", ips->cpu_avg_power);
+
+	return 0;
+}
+
+static int show_cpu_clamp(struct seq_file *m, void *data)
+{
+	u64 turbo_override;
+	int tdp, tdc;
+
+	rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+	tdp = (int)(turbo_override & TURBO_TDP_MASK);
+	tdc = (int)((turbo_override & TURBO_TDC_MASK) >> TURBO_TDC_SHIFT);
+
+	/* Convert to .1W/A units */
+	tdp = tdp * 10 / 8;
+	tdc = tdc * 10 / 8;
+
+	/* Watts Amperes */
+	seq_printf(m, "%d.%dW %d.%dA\n", tdp / 10, tdp % 10,
+		   tdc / 10, tdc % 10);
+
+	return 0;
+}
+
+static int show_mch_temp(struct seq_file *m, void *data)
+{
+	struct ips_driver *ips = m->private;
+
+	seq_printf(m, "%d.%02d\n", ips->mch_avg_temp / 100,
+		   ips->mch_avg_temp % 100);
+
+	return 0;
+}
+
+static int show_mch_power(struct seq_file *m, void *data)
+{
+	struct ips_driver *ips = m->private;
+
+	seq_printf(m, "%dmW\n", ips->mch_avg_power);
+
+	return 0;
+}
+
+static struct ips_debugfs_node ips_debug_files[] = {
+	{ NULL, "cpu_temp", show_cpu_temp },
+	{ NULL, "cpu_power", show_cpu_power },
+	{ NULL, "cpu_clamp", show_cpu_clamp },
+	{ NULL, "mch_temp", show_mch_temp },
+	{ NULL, "mch_power", show_mch_power },
+};
+
+static int ips_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct ips_debugfs_node *node = inode->i_private;
+
+	return single_open(file, node->show, node->ips);
+}
+
+static const struct file_operations ips_debugfs_ops = {
+	.owner = THIS_MODULE,
+	.open = ips_debugfs_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static void ips_debugfs_cleanup(struct ips_driver *ips)
+{
+	if (ips->debug_root)
+		debugfs_remove_recursive(ips->debug_root);
+	return;
+}
+
+static void ips_debugfs_init(struct ips_driver *ips)
+{
+	int i;
+
+	ips->debug_root = debugfs_create_dir("ips", NULL);
+	if (!ips->debug_root) {
+		dev_err(&ips->dev->dev,
+			"failed to create debugfs entries: %ld\n",
+			PTR_ERR(ips->debug_root));
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ips_debug_files); i++) {
+		struct dentry *ent;
+		struct ips_debugfs_node *node = &ips_debug_files[i];
+
+		node->ips = ips;
+		ent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+					  ips->debug_root, node,
+					  &ips_debugfs_ops);
+		if (!ent) {
+			dev_err(&ips->dev->dev,
+				"failed to create debug file: %ld\n",
+				PTR_ERR(ent));
+			goto err_cleanup;
+		}
+	}
+
+	return;
+
+err_cleanup:
+	ips_debugfs_cleanup(ips);
+	return;
+}
+#endif /* CONFIG_DEBUG_FS */
+
+/**
+ * ips_detect_cpu - detect whether CPU supports IPS
+ *
+ * Walk our list and see if we're on a supported CPU.  If we find one,
+ * return the limits for it.
+ */
+static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips)
+{
+	u64 turbo_power, misc_en;
+	struct ips_mcp_limits *limits = NULL;
+	u16 tdp;
+
+	if (!(boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 37)) {
+		dev_info(&ips->dev->dev, "Non-IPS CPU detected.\n");
+		goto out;
+	}
+
+	rdmsrl(IA32_MISC_ENABLE, misc_en);
+	/*
+	 * If the turbo enable bit isn't set, we shouldn't try to enable/disable
+	 * turbo manually or we'll get an illegal MSR access, even though
+	 * turbo will still be available.
+	 */
+	if (misc_en & IA32_MISC_TURBO_EN)
+		ips->turbo_toggle_allowed = true;
+	else
+		ips->turbo_toggle_allowed = false;
+
+	if (strstr(boot_cpu_data.x86_model_id, "CPU       M"))
+		limits = &ips_sv_limits;
+	else if (strstr(boot_cpu_data.x86_model_id, "CPU       L"))
+		limits = &ips_lv_limits;
+	else if (strstr(boot_cpu_data.x86_model_id, "CPU       U"))
+		limits = &ips_ulv_limits;
+	else {
+		dev_info(&ips->dev->dev, "No CPUID match found.\n");
+		goto out;
+	}
+
+	rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power);
+	tdp = turbo_power & TURBO_TDP_MASK;
+
+	/* Sanity check TDP against CPU */
+	if (limits->core_power_limit != (tdp / 8) * 1000) {
+		dev_info(&ips->dev->dev, "CPU TDP doesn't match expected value (found %d, expected %d)\n",
+			 tdp / 8, limits->core_power_limit / 1000);
+		limits->core_power_limit = (tdp / 8) * 1000;
+	}
+
+out:
+	return limits;
+}
+
+/**
+ * ips_get_i915_syms - try to get GPU control methods from i915 driver
+ * @ips: IPS driver
+ *
+ * The i915 driver exports several interfaces to allow the IPS driver to
+ * monitor and control graphics turbo mode.  If we can find them, we can
+ * enable graphics turbo, otherwise we must disable it to avoid exceeding
+ * thermal and power limits in the MCP.
+ */
+static bool ips_get_i915_syms(struct ips_driver *ips)
+{
+	ips->read_mch_val = symbol_get(i915_read_mch_val);
+	if (!ips->read_mch_val)
+		goto out_err;
+	ips->gpu_raise = symbol_get(i915_gpu_raise);
+	if (!ips->gpu_raise)
+		goto out_put_mch;
+	ips->gpu_lower = symbol_get(i915_gpu_lower);
+	if (!ips->gpu_lower)
+		goto out_put_raise;
+	ips->gpu_busy = symbol_get(i915_gpu_busy);
+	if (!ips->gpu_busy)
+		goto out_put_lower;
+	ips->gpu_turbo_disable = symbol_get(i915_gpu_turbo_disable);
+	if (!ips->gpu_turbo_disable)
+		goto out_put_busy;
+
+	return true;
+
+out_put_busy:
+	symbol_put(i915_gpu_busy);
+out_put_lower:
+	symbol_put(i915_gpu_lower);
+out_put_raise:
+	symbol_put(i915_gpu_raise);
+out_put_mch:
+	symbol_put(i915_read_mch_val);
+out_err:
+	return false;
+}
+
+static bool
+ips_gpu_turbo_enabled(struct ips_driver *ips)
+{
+	if (!ips->gpu_busy && late_i915_load) {
+		if (ips_get_i915_syms(ips)) {
+			dev_info(&ips->dev->dev,
+				 "i915 driver attached, reenabling gpu turbo\n");
+			ips->gpu_turbo_enabled = !(thm_readl(THM_HTS) & HTS_GTD_DIS);
+		}
+	}
+
+	return ips->gpu_turbo_enabled;
+}
+
+void
+ips_link_to_i915_driver(void)
+{
+	/* We can't cleanly get at the various ips_driver structs from
+	 * this caller (the i915 driver), so just set a flag saying
+	 * that it's time to try getting the symbols again.
+	 */
+	late_i915_load = true;
+}
+EXPORT_SYMBOL_GPL(ips_link_to_i915_driver);
+
+static const struct pci_device_id ips_id_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL,
+		     PCI_DEVICE_ID_INTEL_THERMAL_SENSOR), },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, ips_id_table);
+
+static int ips_blacklist_callback(const struct dmi_system_id *id)
+{
+	pr_info("Blacklisted intel_ips for %s\n", id->ident);
+	return 1;
+}
+
+static const struct dmi_system_id ips_blacklist[] = {
+	{
+		.callback = ips_blacklist_callback,
+		.ident = "HP ProBook",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook"),
+		},
+	},
+	{ }	/* terminating entry */
+};
+
+static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+	u64 platform_info;
+	struct ips_driver *ips;
+	u32 hts;
+	int ret = 0;
+	u16 htshi, trc, trc_required_mask;
+	u8 tse;
+
+	if (dmi_check_system(ips_blacklist))
+		return -ENODEV;
+
+	ips = kzalloc(sizeof(struct ips_driver), GFP_KERNEL);
+	if (!ips)
+		return -ENOMEM;
+
+	pci_set_drvdata(dev, ips);
+	ips->dev = dev;
+
+	ips->limits = ips_detect_cpu(ips);
+	if (!ips->limits) {
+		dev_info(&dev->dev, "IPS not supported on this CPU\n");
+		ret = -ENXIO;
+		goto error_free;
+	}
+
+	spin_lock_init(&ips->turbo_status_lock);
+
+	ret = pci_enable_device(dev);
+	if (ret) {
+		dev_err(&dev->dev, "can't enable PCI device, aborting\n");
+		goto error_free;
+	}
+
+	if (!pci_resource_start(dev, 0)) {
+		dev_err(&dev->dev, "TBAR not assigned, aborting\n");
+		ret = -ENXIO;
+		goto error_free;
+	}
+
+	ret = pci_request_regions(dev, "ips thermal sensor");
+	if (ret) {
+		dev_err(&dev->dev, "thermal resource busy, aborting\n");
+		goto error_free;
+	}
+
+
+	ips->regmap = ioremap(pci_resource_start(dev, 0),
+			      pci_resource_len(dev, 0));
+	if (!ips->regmap) {
+		dev_err(&dev->dev, "failed to map thermal regs, aborting\n");
+		ret = -EBUSY;
+		goto error_release;
+	}
+
+	tse = thm_readb(THM_TSE);
+	if (tse != TSE_EN) {
+		dev_err(&dev->dev, "thermal device not enabled (0x%02x), aborting\n", tse);
+		ret = -ENXIO;
+		goto error_unmap;
+	}
+
+	trc = thm_readw(THM_TRC);
+	trc_required_mask = TRC_CORE1_EN | TRC_CORE_PWR | TRC_MCH_EN;
+	if ((trc & trc_required_mask) != trc_required_mask) {
+		dev_err(&dev->dev, "thermal reporting for required devices not enabled, aborting\n");
+		ret = -ENXIO;
+		goto error_unmap;
+	}
+
+	if (trc & TRC_CORE2_EN)
+		ips->second_cpu = true;
+
+	update_turbo_limits(ips);
+	dev_dbg(&dev->dev, "max cpu power clamp: %dW\n",
+		ips->mcp_power_limit / 10);
+	dev_dbg(&dev->dev, "max core power clamp: %dW\n",
+		ips->core_power_limit / 10);
+	/* BIOS may update limits at runtime */
+	if (thm_readl(THM_PSC) & PSP_PBRT)
+		ips->poll_turbo_status = true;
+
+	if (!ips_get_i915_syms(ips)) {
+		dev_info(&dev->dev, "failed to get i915 symbols, graphics turbo disabled until i915 loads\n");
+		ips->gpu_turbo_enabled = false;
+	} else {
+		dev_dbg(&dev->dev, "graphics turbo enabled\n");
+		ips->gpu_turbo_enabled = true;
+	}
+
+	/*
+	 * Check PLATFORM_INFO MSR to make sure this chip is
+	 * turbo capable.
+	 */
+	rdmsrl(PLATFORM_INFO, platform_info);
+	if (!(platform_info & PLATFORM_TDP)) {
+		dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n");
+		ret = -ENODEV;
+		goto error_unmap;
+	}
+
+	/*
+	 * IRQ handler for ME interaction
+	 * Note: don't use MSI here as the PCH has bugs.
+	 */
+	pci_disable_msi(dev);
+	ret = request_irq(dev->irq, ips_irq_handler, IRQF_SHARED, "ips",
+			  ips);
+	if (ret) {
+		dev_err(&dev->dev, "request irq failed, aborting\n");
+		goto error_unmap;
+	}
+
+	/* Enable aux, hot & critical interrupts */
+	thm_writeb(THM_TSPIEN, TSPIEN_AUX2_LOHI | TSPIEN_CRIT_LOHI |
+		   TSPIEN_HOT_LOHI | TSPIEN_AUX_LOHI);
+	thm_writeb(THM_TEN, TEN_UPDATE_EN);
+
+	/* Collect adjustment values */
+	ips->cta_val = thm_readw(THM_CTA);
+	ips->pta_val = thm_readw(THM_PTA);
+	ips->mgta_val = thm_readw(THM_MGTA);
+
+	/* Save turbo limits & ratios */
+	rdmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+
+	ips_disable_cpu_turbo(ips);
+	ips->cpu_turbo_enabled = false;
+
+	/* Create thermal adjust thread */
+	ips->adjust = kthread_create(ips_adjust, ips, "ips-adjust");
+	if (IS_ERR(ips->adjust)) {
+		dev_err(&dev->dev,
+			"failed to create thermal adjust thread, aborting\n");
+		ret = -ENOMEM;
+		goto error_free_irq;
+
+	}
+
+	/*
+	 * Set up the work queue and monitor thread. The monitor thread
+	 * will wake up ips_adjust thread.
+	 */
+	ips->monitor = kthread_run(ips_monitor, ips, "ips-monitor");
+	if (IS_ERR(ips->monitor)) {
+		dev_err(&dev->dev,
+			"failed to create thermal monitor thread, aborting\n");
+		ret = -ENOMEM;
+		goto error_thread_cleanup;
+	}
+
+	hts = (ips->core_power_limit << HTS_PCPL_SHIFT) |
+		(ips->mcp_temp_limit << HTS_PTL_SHIFT) | HTS_NVV;
+	htshi = HTS2_PRST_RUNNING << HTS2_PRST_SHIFT;
+
+	thm_writew(THM_HTSHI, htshi);
+	thm_writel(THM_HTS, hts);
+
+	ips_debugfs_init(ips);
+
+	dev_info(&dev->dev, "IPS driver initialized, MCP temp limit %d\n",
+		 ips->mcp_temp_limit);
+	return ret;
+
+error_thread_cleanup:
+	kthread_stop(ips->adjust);
+error_free_irq:
+	free_irq(ips->dev->irq, ips);
+error_unmap:
+	iounmap(ips->regmap);
+error_release:
+	pci_release_regions(dev);
+error_free:
+	kfree(ips);
+	return ret;
+}
+
+static void ips_remove(struct pci_dev *dev)
+{
+	struct ips_driver *ips = pci_get_drvdata(dev);
+	u64 turbo_override;
+
+	if (!ips)
+		return;
+
+	ips_debugfs_cleanup(ips);
+
+	/* Release i915 driver */
+	if (ips->read_mch_val)
+		symbol_put(i915_read_mch_val);
+	if (ips->gpu_raise)
+		symbol_put(i915_gpu_raise);
+	if (ips->gpu_lower)
+		symbol_put(i915_gpu_lower);
+	if (ips->gpu_busy)
+		symbol_put(i915_gpu_busy);
+	if (ips->gpu_turbo_disable)
+		symbol_put(i915_gpu_turbo_disable);
+
+	rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+	turbo_override &= ~(TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN);
+	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+	wrmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+
+	free_irq(ips->dev->irq, ips);
+	if (ips->adjust)
+		kthread_stop(ips->adjust);
+	if (ips->monitor)
+		kthread_stop(ips->monitor);
+	iounmap(ips->regmap);
+	pci_release_regions(dev);
+	kfree(ips);
+	dev_dbg(&dev->dev, "IPS driver removed\n");
+}
+
+static void ips_shutdown(struct pci_dev *dev)
+{
+}
+
+static struct pci_driver ips_pci_driver = {
+	.name = "intel ips",
+	.id_table = ips_id_table,
+	.probe = ips_probe,
+	.remove = ips_remove,
+	.shutdown = ips_shutdown,
+};
+
+module_pci_driver(ips_pci_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jesse Barnes <jbarnes@virtuousgeek.org>");
+MODULE_DESCRIPTION("Intelligent Power Sharing Driver");
diff --git a/drivers/platform/x86/intel_ips.h b/drivers/platform/x86/intel_ips.h
new file mode 100644
index 0000000..73299be
--- /dev/null
+++ b/drivers/platform/x86/intel_ips.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ */
+
+void ips_link_to_i915_driver(void);
diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c
new file mode 100644
index 0000000..0a919d8
--- /dev/null
+++ b/drivers/platform/x86/intel_menlow.c
@@ -0,0 +1,537 @@
+/*
+ *  intel_menlow.c - Intel menlow Driver for thermal management extension
+ *
+ *  Copyright (C) 2008 Intel Corp
+ *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *  Copyright (C) 2008 Zhang Rui <rui.zhang@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; version 2 of the License.
+ *
+ *  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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This driver creates the sys I/F for programming the sensors.
+ *  It also implements the driver for intel menlow memory controller (hardware
+ *  id is INT0002) which makes use of the platform specific ACPI methods
+ *  to get/set bandwidth.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/thermal.h>
+#include <linux/acpi.h>
+
+MODULE_AUTHOR("Thomas Sujith");
+MODULE_AUTHOR("Zhang Rui");
+MODULE_DESCRIPTION("Intel Menlow platform specific driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Memory controller device control
+ */
+
+#define MEMORY_GET_BANDWIDTH "GTHS"
+#define MEMORY_SET_BANDWIDTH "STHS"
+#define MEMORY_ARG_CUR_BANDWIDTH 1
+#define MEMORY_ARG_MAX_BANDWIDTH 0
+
+static void intel_menlow_unregister_sensor(void);
+
+/*
+ * GTHS returning 'n' would mean that [0,n-1] states are supported
+ * In that case max_cstate would be n-1
+ * GTHS returning '0' would mean that no bandwidth control states are supported
+ */
+static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev,
+				    unsigned long *max_state)
+{
+	struct acpi_device *device = cdev->devdata;
+	acpi_handle handle = device->handle;
+	unsigned long long value;
+	struct acpi_object_list arg_list;
+	union acpi_object arg;
+	acpi_status status = AE_OK;
+
+	arg_list.count = 1;
+	arg_list.pointer = &arg;
+	arg.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH;
+	status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+				       &arg_list, &value);
+	if (ACPI_FAILURE(status))
+		return -EFAULT;
+
+	if (!value)
+		return -EINVAL;
+
+	*max_state = value - 1;
+	return 0;
+}
+
+static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev,
+				    unsigned long *value)
+{
+	struct acpi_device *device = cdev->devdata;
+	acpi_handle handle = device->handle;
+	unsigned long long result;
+	struct acpi_object_list arg_list;
+	union acpi_object arg;
+	acpi_status status = AE_OK;
+
+	arg_list.count = 1;
+	arg_list.pointer = &arg;
+	arg.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH;
+	status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+				       &arg_list, &result);
+	if (ACPI_FAILURE(status))
+		return -EFAULT;
+
+	*value = result;
+	return 0;
+}
+
+static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev,
+				    unsigned long state)
+{
+	struct acpi_device *device = cdev->devdata;
+	acpi_handle handle = device->handle;
+	struct acpi_object_list arg_list;
+	union acpi_object arg;
+	acpi_status status;
+	unsigned long long temp;
+	unsigned long max_state;
+
+	if (memory_get_max_bandwidth(cdev, &max_state))
+		return -EFAULT;
+
+	if (state > max_state)
+		return -EINVAL;
+
+	arg_list.count = 1;
+	arg_list.pointer = &arg;
+	arg.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = state;
+
+	status =
+	    acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list,
+				  &temp);
+
+	pr_info("Bandwidth value was %ld: status is %d\n", state, status);
+	if (ACPI_FAILURE(status))
+		return -EFAULT;
+
+	return 0;
+}
+
+static struct thermal_cooling_device_ops memory_cooling_ops = {
+	.get_max_state = memory_get_max_bandwidth,
+	.get_cur_state = memory_get_cur_bandwidth,
+	.set_cur_state = memory_set_cur_bandwidth,
+};
+
+/*
+ * Memory Device Management
+ */
+static int intel_menlow_memory_add(struct acpi_device *device)
+{
+	int result = -ENODEV;
+	struct thermal_cooling_device *cdev;
+
+	if (!device)
+		return -EINVAL;
+
+	if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH))
+		goto end;
+
+	if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH))
+		goto end;
+
+	cdev = thermal_cooling_device_register("Memory controller", device,
+					       &memory_cooling_ops);
+	if (IS_ERR(cdev)) {
+		result = PTR_ERR(cdev);
+		goto end;
+	}
+
+	device->driver_data = cdev;
+	result = sysfs_create_link(&device->dev.kobj,
+				&cdev->device.kobj, "thermal_cooling");
+	if (result)
+		goto unregister;
+
+	result = sysfs_create_link(&cdev->device.kobj,
+				&device->dev.kobj, "device");
+	if (result) {
+		sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+		goto unregister;
+	}
+
+ end:
+	return result;
+
+ unregister:
+	thermal_cooling_device_unregister(cdev);
+	return result;
+
+}
+
+static int intel_menlow_memory_remove(struct acpi_device *device)
+{
+	struct thermal_cooling_device *cdev = acpi_driver_data(device);
+
+	if (!device || !cdev)
+		return -EINVAL;
+
+	sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+	sysfs_remove_link(&cdev->device.kobj, "device");
+	thermal_cooling_device_unregister(cdev);
+
+	return 0;
+}
+
+static const struct acpi_device_id intel_menlow_memory_ids[] = {
+	{"INT0002", 0},
+	{"", 0},
+};
+
+static struct acpi_driver intel_menlow_memory_driver = {
+	.name = "intel_menlow_thermal_control",
+	.ids = intel_menlow_memory_ids,
+	.ops = {
+		.add = intel_menlow_memory_add,
+		.remove = intel_menlow_memory_remove,
+		},
+};
+
+/*
+ * Sensor control on menlow platform
+ */
+
+#define THERMAL_AUX0 0
+#define THERMAL_AUX1 1
+#define GET_AUX0 "GAX0"
+#define GET_AUX1 "GAX1"
+#define SET_AUX0 "SAX0"
+#define SET_AUX1 "SAX1"
+
+struct intel_menlow_attribute {
+	struct device_attribute attr;
+	struct device *device;
+	acpi_handle handle;
+	struct list_head node;
+};
+
+static LIST_HEAD(intel_menlow_attr_list);
+static DEFINE_MUTEX(intel_menlow_attr_lock);
+
+/*
+ * sensor_get_auxtrip - get the current auxtrip value from sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_get_auxtrip(acpi_handle handle, int index,
+							unsigned long long *value)
+{
+	acpi_status status;
+
+	if ((index != 0 && index != 1) || !value)
+		return -EINVAL;
+
+	status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0,
+				       NULL, value);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * sensor_set_auxtrip - set the new auxtrip value to sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_set_auxtrip(acpi_handle handle, int index, int value)
+{
+	acpi_status status;
+	union acpi_object arg = {
+		ACPI_TYPE_INTEGER
+	};
+	struct acpi_object_list args = {
+		1, &arg
+	};
+	unsigned long long temp;
+
+	if (index != 0 && index != 1)
+		return -EINVAL;
+
+	status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1,
+				       NULL, &temp);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+	if ((index && value < temp) || (!index && value > temp))
+		return -EINVAL;
+
+	arg.integer.value = value;
+	status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0,
+				       &args, &temp);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	/* do we need to check the return value of SAX0/SAX1 ? */
+
+	return 0;
+}
+
+#define to_intel_menlow_attr(_attr)	\
+	container_of(_attr, struct intel_menlow_attribute, attr)
+
+static ssize_t aux0_show(struct device *dev,
+			 struct device_attribute *dev_attr, char *buf)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	unsigned long long value;
+	int result;
+
+	result = sensor_get_auxtrip(attr->handle, 0, &value);
+
+	return result ? result : sprintf(buf, "%lu", DECI_KELVIN_TO_CELSIUS(value));
+}
+
+static ssize_t aux1_show(struct device *dev,
+			 struct device_attribute *dev_attr, char *buf)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	unsigned long long value;
+	int result;
+
+	result = sensor_get_auxtrip(attr->handle, 1, &value);
+
+	return result ? result : sprintf(buf, "%lu", DECI_KELVIN_TO_CELSIUS(value));
+}
+
+static ssize_t aux0_store(struct device *dev,
+			  struct device_attribute *dev_attr,
+			  const char *buf, size_t count)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	int value;
+	int result;
+
+	/*Sanity check; should be a positive integer */
+	if (!sscanf(buf, "%d", &value))
+		return -EINVAL;
+
+	if (value < 0)
+		return -EINVAL;
+
+	result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_DECI_KELVIN(value));
+	return result ? result : count;
+}
+
+static ssize_t aux1_store(struct device *dev,
+			  struct device_attribute *dev_attr,
+			  const char *buf, size_t count)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	int value;
+	int result;
+
+	/*Sanity check; should be a positive integer */
+	if (!sscanf(buf, "%d", &value))
+		return -EINVAL;
+
+	if (value < 0)
+		return -EINVAL;
+
+	result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_DECI_KELVIN(value));
+	return result ? result : count;
+}
+
+/* BIOS can enable/disable the thermal user application in dabney platform */
+#define BIOS_ENABLED "\\_TZ.GSTS"
+static ssize_t bios_enabled_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	acpi_status status;
+	unsigned long long bios_enabled;
+
+	status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled");
+}
+
+static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show,
+					  void *store, struct device *dev,
+					  acpi_handle handle)
+{
+	struct intel_menlow_attribute *attr;
+	int result;
+
+	attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL);
+	if (!attr)
+		return -ENOMEM;
+
+	sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */
+	attr->attr.attr.name = name;
+	attr->attr.attr.mode = mode;
+	attr->attr.show = show;
+	attr->attr.store = store;
+	attr->device = dev;
+	attr->handle = handle;
+
+	result = device_create_file(dev, &attr->attr);
+	if (result) {
+		kfree(attr);
+		return result;
+	}
+
+	mutex_lock(&intel_menlow_attr_lock);
+	list_add_tail(&attr->node, &intel_menlow_attr_list);
+	mutex_unlock(&intel_menlow_attr_lock);
+
+	return 0;
+}
+
+static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl,
+						void *context, void **rv)
+{
+	acpi_status status;
+	acpi_handle dummy;
+	struct thermal_zone_device *thermal;
+	int result;
+
+	result = acpi_bus_get_private_data(handle, (void **)&thermal);
+	if (result)
+		return 0;
+
+	/* _TZ must have the AUX0/1 methods */
+	status = acpi_get_handle(handle, GET_AUX0, &dummy);
+	if (ACPI_FAILURE(status))
+		return (status == AE_NOT_FOUND) ? AE_OK : status;
+
+	status = acpi_get_handle(handle, SET_AUX0, &dummy);
+	if (ACPI_FAILURE(status))
+		return (status == AE_NOT_FOUND) ? AE_OK : status;
+
+	result = intel_menlow_add_one_attribute("aux0", 0644,
+						aux0_show, aux0_store,
+						&thermal->device, handle);
+	if (result)
+		return AE_ERROR;
+
+	status = acpi_get_handle(handle, GET_AUX1, &dummy);
+	if (ACPI_FAILURE(status))
+		goto aux1_not_found;
+
+	status = acpi_get_handle(handle, SET_AUX1, &dummy);
+	if (ACPI_FAILURE(status))
+		goto aux1_not_found;
+
+	result = intel_menlow_add_one_attribute("aux1", 0644,
+						aux1_show, aux1_store,
+						&thermal->device, handle);
+	if (result) {
+		intel_menlow_unregister_sensor();
+		return AE_ERROR;
+	}
+
+	/*
+	 * create the "dabney_enabled" attribute which means the user app
+	 * should be loaded or not
+	 */
+
+	result = intel_menlow_add_one_attribute("bios_enabled", 0444,
+						bios_enabled_show, NULL,
+						&thermal->device, handle);
+	if (result) {
+		intel_menlow_unregister_sensor();
+		return AE_ERROR;
+	}
+
+	return AE_OK;
+
+ aux1_not_found:
+	if (status == AE_NOT_FOUND)
+		return AE_OK;
+
+	intel_menlow_unregister_sensor();
+	return status;
+}
+
+static void intel_menlow_unregister_sensor(void)
+{
+	struct intel_menlow_attribute *pos, *next;
+
+	mutex_lock(&intel_menlow_attr_lock);
+	list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) {
+		list_del(&pos->node);
+		device_remove_file(pos->device, &pos->attr);
+		kfree(pos);
+	}
+	mutex_unlock(&intel_menlow_attr_lock);
+
+	return;
+}
+
+static int __init intel_menlow_module_init(void)
+{
+	int result = -ENODEV;
+	acpi_status status;
+	unsigned long long enable;
+
+	if (acpi_disabled)
+		return result;
+
+	/* Looking for the \_TZ.GSTS method */
+	status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable);
+	if (ACPI_FAILURE(status) || !enable)
+		return -ENODEV;
+
+	/* Looking for ACPI device MEM0 with hardware id INT0002 */
+	result = acpi_bus_register_driver(&intel_menlow_memory_driver);
+	if (result)
+		return result;
+
+	/* Looking for sensors in each ACPI thermal zone */
+	status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT,
+				     ACPI_UINT32_MAX,
+				     intel_menlow_register_sensor, NULL, NULL, NULL);
+	if (ACPI_FAILURE(status)) {
+		acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit intel_menlow_module_exit(void)
+{
+	acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+	intel_menlow_unregister_sensor();
+}
+
+module_init(intel_menlow_module_init);
+module_exit(intel_menlow_module_exit);
diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c
new file mode 100644
index 0000000..3617705
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_powerbtn.c
@@ -0,0 +1,152 @@
+/*
+ * Power button driver for Medfield.
+ *
+ * Copyright (C) 2010 Intel Corp
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/intel_msic.h>
+#include <linux/pm_wakeirq.h>
+
+#define DRIVER_NAME "msic_power_btn"
+
+#define MSIC_PB_LEVEL	(1 << 3) /* 1 - release, 0 - press */
+
+/*
+ * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask
+ * power button interrupt
+ */
+#define MSIC_PWRBTNM    (1 << 0)
+
+static irqreturn_t mfld_pb_isr(int irq, void *dev_id)
+{
+	struct input_dev *input = dev_id;
+	int ret;
+	u8 pbstat;
+
+	ret = intel_msic_reg_read(INTEL_MSIC_PBSTATUS, &pbstat);
+	dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat);
+
+	if (ret < 0) {
+		dev_err(input->dev.parent, "Read error %d while reading"
+			       " MSIC_PB_STATUS\n", ret);
+	} else {
+		input_event(input, EV_KEY, KEY_POWER,
+			       !(pbstat & MSIC_PB_LEVEL));
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int mfld_pb_probe(struct platform_device *pdev)
+{
+	struct input_dev *input;
+	int irq = platform_get_irq(pdev, 0);
+	int error;
+
+	if (irq < 0)
+		return -EINVAL;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input->name = pdev->name;
+	input->phys = "power-button/input0";
+	input->id.bustype = BUS_HOST;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_KEY, KEY_POWER);
+
+	error = request_threaded_irq(irq, NULL, mfld_pb_isr, IRQF_ONESHOT,
+				     DRIVER_NAME, input);
+	if (error) {
+		dev_err(&pdev->dev, "Unable to request irq %d for mfld power"
+				"button\n", irq);
+		goto err_free_input;
+	}
+
+	device_init_wakeup(&pdev->dev, true);
+	dev_pm_set_wake_irq(&pdev->dev, irq);
+
+	error = input_register_device(input);
+	if (error) {
+		dev_err(&pdev->dev, "Unable to register input dev, error "
+				"%d\n", error);
+		goto err_free_irq;
+	}
+
+	platform_set_drvdata(pdev, input);
+
+	/*
+	 * SCU firmware might send power button interrupts to IA core before
+	 * kernel boots and doesn't get EOI from IA core. The first bit of
+	 * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new
+	 * power interrupt to Android kernel. Unmask the bit when probing
+	 * power button in kernel.
+	 * There is a very narrow race between irq handler and power button
+	 * initialization. The race happens rarely. So we needn't worry
+	 * about it.
+	 */
+	error = intel_msic_reg_update(INTEL_MSIC_IRQLVL1MSK, 0, MSIC_PWRBTNM);
+	if (error) {
+		dev_err(&pdev->dev, "Unable to clear power button interrupt, "
+				"error: %d\n", error);
+		goto err_free_irq;
+	}
+
+	return 0;
+
+err_free_irq:
+	free_irq(irq, input);
+err_free_input:
+	input_free_device(input);
+	return error;
+}
+
+static int mfld_pb_remove(struct platform_device *pdev)
+{
+	struct input_dev *input = platform_get_drvdata(pdev);
+	int irq = platform_get_irq(pdev, 0);
+
+	dev_pm_clear_wake_irq(&pdev->dev);
+	device_init_wakeup(&pdev->dev, false);
+	free_irq(irq, input);
+	input_unregister_device(input);
+
+	return 0;
+}
+
+static struct platform_driver mfld_pb_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe	= mfld_pb_probe,
+	.remove	= mfld_pb_remove,
+};
+
+module_platform_driver(mfld_pb_driver);
+
+MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield Power Button Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c
new file mode 100644
index 0000000..5c768c4
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_thermal.c
@@ -0,0 +1,569 @@
+/*
+ * intel_mid_thermal.c - Intel MID platform thermal driver
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Durgadoss R <durgadoss.r@intel.com>
+ */
+
+#define pr_fmt(fmt) "intel_mid_thermal: " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/thermal.h>
+#include <linux/mfd/intel_msic.h>
+
+/* Number of thermal sensors */
+#define MSIC_THERMAL_SENSORS	4
+
+/* ADC1 - thermal registers */
+#define MSIC_ADC_ENBL		0x10
+#define MSIC_ADC_START		0x08
+
+#define MSIC_ADCTHERM_ENBL	0x04
+#define MSIC_ADCRRDATA_ENBL	0x05
+#define MSIC_CHANL_MASK_VAL	0x0F
+
+#define MSIC_STOPBIT_MASK	16
+#define MSIC_ADCTHERM_MASK	4
+/* Number of ADC channels */
+#define ADC_CHANLS_MAX		15
+#define ADC_LOOP_MAX		(ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS)
+
+/* ADC channel code values */
+#define SKIN_SENSOR0_CODE	0x08
+#define SKIN_SENSOR1_CODE	0x09
+#define SYS_SENSOR_CODE		0x0A
+#define MSIC_DIE_SENSOR_CODE	0x03
+
+#define SKIN_THERM_SENSOR0	0
+#define SKIN_THERM_SENSOR1	1
+#define SYS_THERM_SENSOR2	2
+#define MSIC_DIE_THERM_SENSOR3	3
+
+/* ADC code range */
+#define ADC_MAX			977
+#define ADC_MIN			162
+#define ADC_VAL0C		887
+#define ADC_VAL20C		720
+#define ADC_VAL40C		508
+#define ADC_VAL60C		315
+
+/* ADC base addresses */
+#define ADC_CHNL_START_ADDR	INTEL_MSIC_ADC1ADDR0	/* increments by 1 */
+#define ADC_DATA_START_ADDR	INTEL_MSIC_ADC1SNS0H	/* increments by 2 */
+
+/* MSIC die attributes */
+#define MSIC_DIE_ADC_MIN	488
+#define MSIC_DIE_ADC_MAX	1004
+
+/* This holds the address of the first free ADC channel,
+ * among the 15 channels
+ */
+static int channel_index;
+
+struct platform_info {
+	struct platform_device *pdev;
+	struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS];
+};
+
+struct thermal_device_info {
+	unsigned int chnl_addr;
+	int direct;
+	/* This holds the current temperature in millidegree celsius */
+	long curr_temp;
+};
+
+/**
+ * to_msic_die_temp - converts adc_val to msic_die temperature
+ * @adc_val: ADC value to be converted
+ *
+ * Can sleep
+ */
+static int to_msic_die_temp(uint16_t adc_val)
+{
+	return (368 * (adc_val) / 1000) - 220;
+}
+
+/**
+ * is_valid_adc - checks whether the adc code is within the defined range
+ * @min: minimum value for the sensor
+ * @max: maximum value for the sensor
+ *
+ * Can sleep
+ */
+static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max)
+{
+	return (adc_val >= min) && (adc_val <= max);
+}
+
+/**
+ * adc_to_temp - converts the ADC code to temperature in C
+ * @direct: true if ths channel is direct index
+ * @adc_val: the adc_val that needs to be converted
+ * @tp: temperature return value
+ *
+ * Linear approximation is used to covert the skin adc value into temperature.
+ * This technique is used to avoid very long look-up table to get
+ * the appropriate temp value from ADC value.
+ * The adc code vs sensor temp curve is split into five parts
+ * to achieve very close approximate temp value with less than
+ * 0.5C error
+ */
+static int adc_to_temp(int direct, uint16_t adc_val, int *tp)
+{
+	int temp;
+
+	/* Direct conversion for die temperature */
+	if (direct) {
+		if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) {
+			*tp = to_msic_die_temp(adc_val) * 1000;
+			return 0;
+		}
+		return -ERANGE;
+	}
+
+	if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX))
+		return -ERANGE;
+
+	/* Linear approximation for skin temperature */
+	if (adc_val > ADC_VAL0C)
+		temp = 177 - (adc_val/5);
+	else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C))
+		temp = 111 - (adc_val/8);
+	else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C))
+		temp = 92 - (adc_val/10);
+	else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C))
+		temp = 91 - (adc_val/10);
+	else
+		temp = 112 - (adc_val/6);
+
+	/* Convert temperature in celsius to milli degree celsius */
+	*tp = temp * 1000;
+	return 0;
+}
+
+/**
+ * mid_read_temp - read sensors for temperature
+ * @temp: holds the current temperature for the sensor after reading
+ *
+ * reads the adc_code from the channel and converts it to real
+ * temperature. The converted value is stored in temp.
+ *
+ * Can sleep
+ */
+static int mid_read_temp(struct thermal_zone_device *tzd, int *temp)
+{
+	struct thermal_device_info *td_info = tzd->devdata;
+	uint16_t adc_val, addr;
+	uint8_t data = 0;
+	int ret;
+	int curr_temp;
+
+	addr = td_info->chnl_addr;
+
+	/* Enable the msic for conversion before reading */
+	ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL);
+	if (ret)
+		return ret;
+
+	/* Re-toggle the RRDATARD bit (temporary workaround) */
+	ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL);
+	if (ret)
+		return ret;
+
+	/* Read the higher bits of data */
+	ret = intel_msic_reg_read(addr, &data);
+	if (ret)
+		return ret;
+
+	/* Shift bits to accommodate the lower two data bits */
+	adc_val = (data << 2);
+	addr++;
+
+	ret = intel_msic_reg_read(addr, &data);/* Read lower bits */
+	if (ret)
+		return ret;
+
+	/* Adding lower two bits to the higher bits */
+	data &= 03;
+	adc_val += data;
+
+	/* Convert ADC value to temperature */
+	ret = adc_to_temp(td_info->direct, adc_val, &curr_temp);
+	if (ret == 0)
+		*temp = td_info->curr_temp = curr_temp;
+	return ret;
+}
+
+/**
+ * configure_adc - enables/disables the ADC for conversion
+ * @val: zero: disables the ADC non-zero:enables the ADC
+ *
+ * Enable/Disable the ADC depending on the argument
+ *
+ * Can sleep
+ */
+static int configure_adc(int val)
+{
+	int ret;
+	uint8_t data;
+
+	ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data);
+	if (ret)
+		return ret;
+
+	if (val) {
+		/* Enable and start the ADC */
+		data |= (MSIC_ADC_ENBL | MSIC_ADC_START);
+	} else {
+		/* Just stop the ADC */
+		data &= (~MSIC_ADC_START);
+	}
+	return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data);
+}
+
+/**
+ * set_up_therm_channel - enable thermal channel for conversion
+ * @base_addr: index of free msic ADC channel
+ *
+ * Enable all the three channels for conversion
+ *
+ * Can sleep
+ */
+static int set_up_therm_channel(u16 base_addr)
+{
+	int ret;
+
+	/* Enable all the sensor channels */
+	ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE);
+	if (ret)
+		return ret;
+
+	ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE);
+	if (ret)
+		return ret;
+
+	ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE);
+	if (ret)
+		return ret;
+
+	/* Since this is the last channel, set the stop bit
+	 * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */
+	ret = intel_msic_reg_write(base_addr + 3,
+			(MSIC_DIE_SENSOR_CODE | 0x10));
+	if (ret)
+		return ret;
+
+	/* Enable ADC and start it */
+	return configure_adc(1);
+}
+
+/**
+ * reset_stopbit - sets the stop bit to 0 on the given channel
+ * @addr: address of the channel
+ *
+ * Can sleep
+ */
+static int reset_stopbit(uint16_t addr)
+{
+	int ret;
+	uint8_t data;
+	ret = intel_msic_reg_read(addr, &data);
+	if (ret)
+		return ret;
+	/* Set the stop bit to zero */
+	return intel_msic_reg_write(addr, (data & 0xEF));
+}
+
+/**
+ * find_free_channel - finds an empty channel for conversion
+ *
+ * If the ADC is not enabled then start using 0th channel
+ * itself. Otherwise find an empty channel by looking for a
+ * channel in which the stopbit is set to 1. returns the index
+ * of the first free channel if succeeds or an error code.
+ *
+ * Context: can sleep
+ *
+ * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc
+ * code.
+ */
+static int find_free_channel(void)
+{
+	int ret;
+	int i;
+	uint8_t data;
+
+	/* check whether ADC is enabled */
+	ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data);
+	if (ret)
+		return ret;
+
+	if ((data & MSIC_ADC_ENBL) == 0)
+		return 0;
+
+	/* ADC is already enabled; Looking for an empty channel */
+	for (i = 0; i < ADC_CHANLS_MAX; i++) {
+		ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data);
+		if (ret)
+			return ret;
+
+		if (data & MSIC_STOPBIT_MASK) {
+			ret = i;
+			break;
+		}
+	}
+	return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret;
+}
+
+/**
+ * mid_initialize_adc - initializing the ADC
+ * @dev: our device structure
+ *
+ * Initialize the ADC for reading thermistor values. Can sleep.
+ */
+static int mid_initialize_adc(struct device *dev)
+{
+	u8  data;
+	u16 base_addr;
+	int ret;
+
+	/*
+	 * Ensure that adctherm is disabled before we
+	 * initialize the ADC
+	 */
+	ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data);
+	if (ret)
+		return ret;
+
+	data &= ~MSIC_ADCTHERM_MASK;
+	ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data);
+	if (ret)
+		return ret;
+
+	/* Index of the first channel in which the stop bit is set */
+	channel_index = find_free_channel();
+	if (channel_index < 0) {
+		dev_err(dev, "No free ADC channels");
+		return channel_index;
+	}
+
+	base_addr = ADC_CHNL_START_ADDR + channel_index;
+
+	if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) {
+		/* Reset stop bit for channels other than 0 and 12 */
+		ret = reset_stopbit(base_addr);
+		if (ret)
+			return ret;
+
+		/* Index of the first free channel */
+		base_addr++;
+		channel_index++;
+	}
+
+	ret = set_up_therm_channel(base_addr);
+	if (ret) {
+		dev_err(dev, "unable to enable ADC");
+		return ret;
+	}
+	dev_dbg(dev, "ADC initialization successful");
+	return ret;
+}
+
+/**
+ * initialize_sensor - sets default temp and timer ranges
+ * @index: index of the sensor
+ *
+ * Context: can sleep
+ */
+static struct thermal_device_info *initialize_sensor(int index)
+{
+	struct thermal_device_info *td_info =
+		kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL);
+
+	if (!td_info)
+		return NULL;
+
+	/* Set the base addr of the channel for this sensor */
+	td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index);
+	/* Sensor 3 is direct conversion */
+	if (index == 3)
+		td_info->direct = 1;
+	return td_info;
+}
+
+/**
+ * mid_thermal_resume - resume routine
+ * @dev: device structure
+ *
+ * mid thermal resume: re-initializes the adc. Can sleep.
+ */
+static int mid_thermal_resume(struct device *dev)
+{
+	return mid_initialize_adc(dev);
+}
+
+/**
+ * mid_thermal_suspend - suspend routine
+ * @dev: device structure
+ *
+ * mid thermal suspend implements the suspend functionality
+ * by stopping the ADC. Can sleep.
+ */
+static int mid_thermal_suspend(struct device *dev)
+{
+	/*
+	 * This just stops the ADC and does not disable it.
+	 * temporary workaround until we have a generic ADC driver.
+	 * If 0 is passed, it disables the ADC.
+	 */
+	return configure_adc(0);
+}
+
+static SIMPLE_DEV_PM_OPS(mid_thermal_pm,
+			 mid_thermal_suspend, mid_thermal_resume);
+
+/**
+ * read_curr_temp - reads the current temperature and stores in temp
+ * @temp: holds the current temperature value after reading
+ *
+ * Can sleep
+ */
+static int read_curr_temp(struct thermal_zone_device *tzd, int *temp)
+{
+	WARN_ON(tzd == NULL);
+	return mid_read_temp(tzd, temp);
+}
+
+/* Can't be const */
+static struct thermal_zone_device_ops tzd_ops = {
+	.get_temp = read_curr_temp,
+};
+
+/**
+ * mid_thermal_probe - mfld thermal initialize
+ * @pdev: platform device structure
+ *
+ * mid thermal probe initializes the hardware and registers
+ * all the sensors with the generic thermal framework. Can sleep.
+ */
+static int mid_thermal_probe(struct platform_device *pdev)
+{
+	static char *name[MSIC_THERMAL_SENSORS] = {
+		"skin0", "skin1", "sys", "msicdie"
+	};
+
+	int ret;
+	int i;
+	struct platform_info *pinfo;
+
+	pinfo = devm_kzalloc(&pdev->dev, sizeof(struct platform_info),
+			     GFP_KERNEL);
+	if (!pinfo)
+		return -ENOMEM;
+
+	/* Initializing the hardware */
+	ret = mid_initialize_adc(&pdev->dev);
+	if (ret) {
+		dev_err(&pdev->dev, "ADC init failed");
+		return ret;
+	}
+
+	/* Register each sensor with the generic thermal framework*/
+	for (i = 0; i < MSIC_THERMAL_SENSORS; i++) {
+		struct thermal_device_info *td_info = initialize_sensor(i);
+
+		if (!td_info) {
+			ret = -ENOMEM;
+			goto err;
+		}
+		pinfo->tzd[i] = thermal_zone_device_register(name[i],
+				0, 0, td_info, &tzd_ops, NULL, 0, 0);
+		if (IS_ERR(pinfo->tzd[i])) {
+			kfree(td_info);
+			ret = PTR_ERR(pinfo->tzd[i]);
+			goto err;
+		}
+	}
+
+	pinfo->pdev = pdev;
+	platform_set_drvdata(pdev, pinfo);
+	return 0;
+
+err:
+	while (--i >= 0) {
+		kfree(pinfo->tzd[i]->devdata);
+		thermal_zone_device_unregister(pinfo->tzd[i]);
+	}
+	configure_adc(0);
+	return ret;
+}
+
+/**
+ * mid_thermal_remove - mfld thermal finalize
+ * @dev: platform device structure
+ *
+ * MLFD thermal remove unregisters all the sensors from the generic
+ * thermal framework. Can sleep.
+ */
+static int mid_thermal_remove(struct platform_device *pdev)
+{
+	int i;
+	struct platform_info *pinfo = platform_get_drvdata(pdev);
+
+	for (i = 0; i < MSIC_THERMAL_SENSORS; i++) {
+		kfree(pinfo->tzd[i]->devdata);
+		thermal_zone_device_unregister(pinfo->tzd[i]);
+	}
+
+	/* Stop the ADC */
+	return configure_adc(0);
+}
+
+#define DRIVER_NAME "msic_thermal"
+
+static const struct platform_device_id therm_id_table[] = {
+	{ DRIVER_NAME, 1 },
+	{ "msic_thermal", 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(platform, therm_id_table);
+
+static struct platform_driver mid_thermal_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.pm = &mid_thermal_pm,
+	},
+	.probe = mid_thermal_probe,
+	.remove = mid_thermal_remove,
+	.id_table = therm_id_table,
+};
+
+module_platform_driver(mid_thermal_driver);
+
+MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c
new file mode 100644
index 0000000..6aa33c4
--- /dev/null
+++ b/drivers/platform/x86/intel_oaktrail.c
@@ -0,0 +1,391 @@
+/*
+ * intel_oaktrail.c - Intel OakTrail Platform support.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation
+ * Author: Yin Kangkai (kangkai.yin@intel.com)
+ *
+ * based on Compal driver, Copyright (C) 2008 Cezary Jackiewicz
+ * <cezary.jackiewicz (at) gmail.com>, based on MSI driver
+ * Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+ *
+ *  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.
+ *
+ * This driver does below things:
+ * 1. registers itself in the Linux backlight control in
+ *    /sys/class/backlight/intel_oaktrail/
+ *
+ * 2. registers in the rfkill subsystem here: /sys/class/rfkill/rfkillX/
+ *    for these components: wifi, bluetooth, wwan (3g), gps
+ *
+ * This driver might work on other products based on Oaktrail. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * product as compatible.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/fb.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/dmi.h>
+#include <linux/rfkill.h>
+#include <acpi/video.h>
+
+#define DRIVER_NAME	"intel_oaktrail"
+#define DRIVER_VERSION	"0.4ac1"
+
+/*
+ * This is the devices status address in EC space, and the control bits
+ * definition:
+ *
+ * (1 << 0):	Camera enable/disable, RW.
+ * (1 << 1):	Bluetooth enable/disable, RW.
+ * (1 << 2):	GPS enable/disable, RW.
+ * (1 << 3):	WiFi enable/disable, RW.
+ * (1 << 4):	WWAN (3G) enable/disable, RW.
+ * (1 << 5):	Touchscreen enable/disable, Read Only.
+ */
+#define OT_EC_DEVICE_STATE_ADDRESS	0xD6
+
+#define OT_EC_CAMERA_MASK	(1 << 0)
+#define OT_EC_BT_MASK		(1 << 1)
+#define OT_EC_GPS_MASK		(1 << 2)
+#define OT_EC_WIFI_MASK		(1 << 3)
+#define OT_EC_WWAN_MASK		(1 << 4)
+#define OT_EC_TS_MASK		(1 << 5)
+
+/*
+ * This is the address in EC space and commands used to control LCD backlight:
+ *
+ * Two steps needed to change the LCD backlight:
+ *   1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS;
+ *   2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS.
+ *
+ * To read the LCD back light, just read out the value from
+ * OT_EC_BL_BRIGHTNESS_ADDRESS.
+ *
+ * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX)
+ */
+#define OT_EC_BL_BRIGHTNESS_ADDRESS	0x44
+#define OT_EC_BL_BRIGHTNESS_MAX		100
+#define OT_EC_BL_CONTROL_ADDRESS	0x3A
+#define OT_EC_BL_CONTROL_ON_DATA	0x1A
+
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static struct platform_device *oaktrail_device;
+static struct backlight_device *oaktrail_bl_device;
+static struct rfkill *bt_rfkill;
+static struct rfkill *gps_rfkill;
+static struct rfkill *wifi_rfkill;
+static struct rfkill *wwan_rfkill;
+
+
+/* rfkill */
+static int oaktrail_rfkill_set(void *data, bool blocked)
+{
+	u8 value;
+	u8 result;
+	unsigned long radio = (unsigned long) data;
+
+	ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result);
+
+	if (!blocked)
+		value = (u8) (result | radio);
+	else
+		value = (u8) (result & ~radio);
+
+	ec_write(OT_EC_DEVICE_STATE_ADDRESS, value);
+
+	return 0;
+}
+
+static const struct rfkill_ops oaktrail_rfkill_ops = {
+	.set_block = oaktrail_rfkill_set,
+};
+
+static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type,
+					  unsigned long mask)
+{
+	struct rfkill *rfkill_dev;
+	u8 value;
+	int err;
+
+	rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type,
+				  &oaktrail_rfkill_ops, (void *)mask);
+	if (!rfkill_dev)
+		return ERR_PTR(-ENOMEM);
+
+	ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value);
+	rfkill_init_sw_state(rfkill_dev, (value & mask) != 1);
+
+	err = rfkill_register(rfkill_dev);
+	if (err) {
+		rfkill_destroy(rfkill_dev);
+		return ERR_PTR(err);
+	}
+
+	return rfkill_dev;
+}
+
+static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf)
+{
+	if (rf) {
+		rfkill_unregister(rf);
+		rfkill_destroy(rf);
+	}
+}
+
+static void oaktrail_rfkill_cleanup(void)
+{
+	__oaktrail_rfkill_cleanup(wifi_rfkill);
+	__oaktrail_rfkill_cleanup(bt_rfkill);
+	__oaktrail_rfkill_cleanup(gps_rfkill);
+	__oaktrail_rfkill_cleanup(wwan_rfkill);
+}
+
+static int oaktrail_rfkill_init(void)
+{
+	int ret;
+
+	wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi",
+					  RFKILL_TYPE_WLAN,
+					  OT_EC_WIFI_MASK);
+	if (IS_ERR(wifi_rfkill)) {
+		ret = PTR_ERR(wifi_rfkill);
+		wifi_rfkill = NULL;
+		goto cleanup;
+	}
+
+	bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth",
+					RFKILL_TYPE_BLUETOOTH,
+					OT_EC_BT_MASK);
+	if (IS_ERR(bt_rfkill)) {
+		ret = PTR_ERR(bt_rfkill);
+		bt_rfkill = NULL;
+		goto cleanup;
+	}
+
+	gps_rfkill = oaktrail_rfkill_new("oaktrail-gps",
+					 RFKILL_TYPE_GPS,
+					 OT_EC_GPS_MASK);
+	if (IS_ERR(gps_rfkill)) {
+		ret = PTR_ERR(gps_rfkill);
+		gps_rfkill = NULL;
+		goto cleanup;
+	}
+
+	wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan",
+					  RFKILL_TYPE_WWAN,
+					  OT_EC_WWAN_MASK);
+	if (IS_ERR(wwan_rfkill)) {
+		ret = PTR_ERR(wwan_rfkill);
+		wwan_rfkill = NULL;
+		goto cleanup;
+	}
+
+	return 0;
+
+cleanup:
+	oaktrail_rfkill_cleanup();
+	return ret;
+}
+
+
+/* backlight */
+static int get_backlight_brightness(struct backlight_device *b)
+{
+	u8 value;
+	ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value);
+
+	return value;
+}
+
+static int set_backlight_brightness(struct backlight_device *b)
+{
+	u8 percent = (u8) b->props.brightness;
+	if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX)
+		return -EINVAL;
+
+	ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent);
+	ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA);
+
+	return 0;
+}
+
+static const struct backlight_ops oaktrail_bl_ops = {
+	.get_brightness = get_backlight_brightness,
+	.update_status	= set_backlight_brightness,
+};
+
+static int oaktrail_backlight_init(void)
+{
+	struct backlight_device *bd;
+	struct backlight_properties props;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX;
+	bd = backlight_device_register(DRIVER_NAME,
+				       &oaktrail_device->dev, NULL,
+				       &oaktrail_bl_ops,
+				       &props);
+
+	if (IS_ERR(bd)) {
+		oaktrail_bl_device = NULL;
+		pr_warning("Unable to register backlight device\n");
+		return PTR_ERR(bd);
+	}
+
+	oaktrail_bl_device = bd;
+
+	bd->props.brightness = get_backlight_brightness(bd);
+	bd->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(bd);
+
+	return 0;
+}
+
+static void oaktrail_backlight_exit(void)
+{
+	backlight_device_unregister(oaktrail_bl_device);
+}
+
+static int oaktrail_probe(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int oaktrail_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct platform_driver oaktrail_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe	= oaktrail_probe,
+	.remove	= oaktrail_remove,
+};
+
+static int dmi_check_cb(const struct dmi_system_id *id)
+{
+	pr_info("Identified model '%s'\n", id->ident);
+	return 0;
+}
+
+static struct dmi_system_id __initdata oaktrail_dmi_table[] = {
+	{
+		.ident = "OakTrail platform",
+		.matches = {
+			DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"),
+		},
+		.callback = dmi_check_cb
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table);
+
+static int __init oaktrail_init(void)
+{
+	int ret;
+
+	if (acpi_disabled) {
+		pr_err("ACPI needs to be enabled for this driver to work!\n");
+		return -ENODEV;
+	}
+
+	if (!force && !dmi_check_system(oaktrail_dmi_table)) {
+		pr_err("Platform not recognized (You could try the module's force-parameter)");
+		return -ENODEV;
+	}
+
+	ret = platform_driver_register(&oaktrail_driver);
+	if (ret) {
+		pr_warning("Unable to register platform driver\n");
+		goto err_driver_reg;
+	}
+
+	oaktrail_device = platform_device_alloc(DRIVER_NAME, -1);
+	if (!oaktrail_device) {
+		pr_warning("Unable to allocate platform device\n");
+		ret = -ENOMEM;
+		goto err_device_alloc;
+	}
+
+	ret = platform_device_add(oaktrail_device);
+	if (ret) {
+		pr_warning("Unable to add platform device\n");
+		goto err_device_add;
+	}
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		ret = oaktrail_backlight_init();
+		if (ret)
+			goto err_backlight;
+	}
+
+	ret = oaktrail_rfkill_init();
+	if (ret) {
+		pr_warning("Setup rfkill failed\n");
+		goto err_rfkill;
+	}
+
+	pr_info("Driver "DRIVER_VERSION" successfully loaded\n");
+	return 0;
+
+err_rfkill:
+	oaktrail_backlight_exit();
+err_backlight:
+	platform_device_del(oaktrail_device);
+err_device_add:
+	platform_device_put(oaktrail_device);
+err_device_alloc:
+	platform_driver_unregister(&oaktrail_driver);
+err_driver_reg:
+
+	return ret;
+}
+
+static void __exit oaktrail_cleanup(void)
+{
+	oaktrail_backlight_exit();
+	oaktrail_rfkill_cleanup();
+	platform_device_unregister(oaktrail_device);
+	platform_driver_unregister(&oaktrail_driver);
+
+	pr_info("Driver unloaded\n");
+}
+
+module_init(oaktrail_init);
+module_exit(oaktrail_cleanup);
+
+MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)");
+MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c
new file mode 100644
index 0000000..28b2a12
--- /dev/null
+++ b/drivers/platform/x86/intel_pmc_ipc.c
@@ -0,0 +1,779 @@
+/*
+ * intel_pmc_ipc.c: Driver for the Intel PMC IPC mechanism
+ *
+ * (C) Copyright 2014-2015 Intel Corporation
+ *
+ * This driver is based on Intel SCU IPC driver(intel_scu_opc.c) by
+ *     Sreedhara DS <sreedhara.ds@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; version 2
+ * of the License.
+ *
+ * PMC running in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad PMC.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/pm_qos.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/sched.h>
+#include <linux/atomic.h>
+#include <linux/notifier.h>
+#include <linux/suspend.h>
+#include <linux/acpi.h>
+#include <asm/intel_pmc_ipc.h>
+#include <linux/platform_data/itco_wdt.h>
+
+/*
+ * IPC registers
+ * The IA write to IPC_CMD command register triggers an interrupt to the ARC,
+ * The ARC handles the interrupt and services it, writing optional data to
+ * the IPC1 registers, updates the IPC_STS response register with the status.
+ */
+#define IPC_CMD			0x0
+#define		IPC_CMD_MSI		0x100
+#define		IPC_CMD_SIZE		16
+#define		IPC_CMD_SUBCMD		12
+#define IPC_STATUS		0x04
+#define		IPC_STATUS_IRQ		0x4
+#define		IPC_STATUS_ERR		0x2
+#define		IPC_STATUS_BUSY		0x1
+#define IPC_SPTR		0x08
+#define IPC_DPTR		0x0C
+#define IPC_WRITE_BUFFER	0x80
+#define IPC_READ_BUFFER		0x90
+
+/*
+ * 16-byte buffer for sending data associated with IPC command.
+ */
+#define IPC_DATA_BUFFER_SIZE	16
+
+#define IPC_LOOP_CNT		3000000
+#define IPC_MAX_SEC		3
+
+#define IPC_TRIGGER_MODE_IRQ		true
+
+/* exported resources from IFWI */
+#define PLAT_RESOURCE_IPC_INDEX		0
+#define PLAT_RESOURCE_IPC_SIZE		0x1000
+#define PLAT_RESOURCE_GCR_SIZE		0x1000
+#define PLAT_RESOURCE_PUNIT_DATA_INDEX	1
+#define PLAT_RESOURCE_PUNIT_INTER_INDEX	2
+#define PLAT_RESOURCE_ACPI_IO_INDEX	0
+
+/*
+ * BIOS does not create an ACPI device for each PMC function,
+ * but exports multiple resources from one ACPI device(IPC) for
+ * multiple functions. This driver is responsible to create a
+ * platform device and to export resources for those functions.
+ */
+#define TCO_DEVICE_NAME			"iTCO_wdt"
+#define SMI_EN_OFFSET			0x30
+#define SMI_EN_SIZE			4
+#define TCO_BASE_OFFSET			0x60
+#define TCO_REGS_SIZE			16
+#define PUNIT_DEVICE_NAME		"intel_punit_ipc"
+
+static const int iTCO_version = 3;
+
+static struct intel_pmc_ipc_dev {
+	struct device *dev;
+	void __iomem *ipc_base;
+	bool irq_mode;
+	int irq;
+	int cmd;
+	struct completion cmd_complete;
+
+	/* The following PMC BARs share the same ACPI device with the IPC */
+	resource_size_t acpi_io_base;
+	int acpi_io_size;
+	struct platform_device *tco_dev;
+
+	/* gcr */
+	resource_size_t gcr_base;
+	int gcr_size;
+
+	/* punit */
+	resource_size_t punit_base;
+	int punit_size;
+	resource_size_t punit_base2;
+	int punit_size2;
+	struct platform_device *punit_dev;
+} ipcdev;
+
+static char *ipc_err_sources[] = {
+	[IPC_ERR_NONE] =
+		"no error",
+	[IPC_ERR_CMD_NOT_SUPPORTED] =
+		"command not supported",
+	[IPC_ERR_CMD_NOT_SERVICED] =
+		"command not serviced",
+	[IPC_ERR_UNABLE_TO_SERVICE] =
+		"unable to service",
+	[IPC_ERR_CMD_INVALID] =
+		"command invalid",
+	[IPC_ERR_CMD_FAILED] =
+		"command failed",
+	[IPC_ERR_EMSECURITY] =
+		"Invalid Battery",
+	[IPC_ERR_UNSIGNEDKERNEL] =
+		"Unsigned kernel",
+};
+
+/* Prevent concurrent calls to the PMC */
+static DEFINE_MUTEX(ipclock);
+
+static inline void ipc_send_command(u32 cmd)
+{
+	ipcdev.cmd = cmd;
+	if (ipcdev.irq_mode) {
+		reinit_completion(&ipcdev.cmd_complete);
+		cmd |= IPC_CMD_MSI;
+	}
+	writel(cmd, ipcdev.ipc_base + IPC_CMD);
+}
+
+static inline u32 ipc_read_status(void)
+{
+	return readl(ipcdev.ipc_base + IPC_STATUS);
+}
+
+static inline void ipc_data_writel(u32 data, u32 offset)
+{
+	writel(data, ipcdev.ipc_base + IPC_WRITE_BUFFER + offset);
+}
+
+static inline u8 ipc_data_readb(u32 offset)
+{
+	return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline u32 ipc_data_readl(u32 offset)
+{
+	return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static int intel_pmc_ipc_check_status(void)
+{
+	int status;
+	int ret = 0;
+
+	if (ipcdev.irq_mode) {
+		if (0 == wait_for_completion_timeout(
+				&ipcdev.cmd_complete, IPC_MAX_SEC * HZ))
+			ret = -ETIMEDOUT;
+	} else {
+		int loop_count = IPC_LOOP_CNT;
+
+		while ((ipc_read_status() & IPC_STATUS_BUSY) && --loop_count)
+			udelay(1);
+		if (loop_count == 0)
+			ret = -ETIMEDOUT;
+	}
+
+	status = ipc_read_status();
+	if (ret == -ETIMEDOUT) {
+		dev_err(ipcdev.dev,
+			"IPC timed out, TS=0x%x, CMD=0x%x\n",
+			status, ipcdev.cmd);
+		return ret;
+	}
+
+	if (status & IPC_STATUS_ERR) {
+		int i;
+
+		ret = -EIO;
+		i = (status >> IPC_CMD_SIZE) & 0xFF;
+		if (i < ARRAY_SIZE(ipc_err_sources))
+			dev_err(ipcdev.dev,
+				"IPC failed: %s, STS=0x%x, CMD=0x%x\n",
+				ipc_err_sources[i], status, ipcdev.cmd);
+		else
+			dev_err(ipcdev.dev,
+				"IPC failed: unknown, STS=0x%x, CMD=0x%x\n",
+				status, ipcdev.cmd);
+		if ((i == IPC_ERR_UNSIGNEDKERNEL) || (i == IPC_ERR_EMSECURITY))
+			ret = -EACCES;
+	}
+
+	return ret;
+}
+
+/**
+ * intel_pmc_ipc_simple_command() - Simple IPC command
+ * @cmd:	IPC command code.
+ * @sub:	IPC command sub type.
+ *
+ * Send a simple IPC command to PMC when don't need to specify
+ * input/output data and source/dest pointers.
+ *
+ * Return:	an IPC error code or 0 on success.
+ */
+int intel_pmc_ipc_simple_command(int cmd, int sub)
+{
+	int ret;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.dev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_send_command(sub << IPC_CMD_SUBCMD | cmd);
+	ret = intel_pmc_ipc_check_status();
+	mutex_unlock(&ipclock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(intel_pmc_ipc_simple_command);
+
+/**
+ * intel_pmc_ipc_raw_cmd() - IPC command with data and pointers
+ * @cmd:	IPC command code.
+ * @sub:	IPC command sub type.
+ * @in:		input data of this IPC command.
+ * @inlen:	input data length in bytes.
+ * @out:	output data of this IPC command.
+ * @outlen:	output data length in dwords.
+ * @sptr:	data writing to SPTR register.
+ * @dptr:	data writing to DPTR register.
+ *
+ * Send an IPC command to PMC with input/output data and source/dest pointers.
+ *
+ * Return:	an IPC error code or 0 on success.
+ */
+int intel_pmc_ipc_raw_cmd(u32 cmd, u32 sub, u8 *in, u32 inlen, u32 *out,
+			  u32 outlen, u32 dptr, u32 sptr)
+{
+	u32 wbuf[4] = { 0 };
+	int ret;
+	int i;
+
+	if (inlen > IPC_DATA_BUFFER_SIZE || outlen > IPC_DATA_BUFFER_SIZE / 4)
+		return -EINVAL;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.dev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	memcpy(wbuf, in, inlen);
+	writel(dptr, ipcdev.ipc_base + IPC_DPTR);
+	writel(sptr, ipcdev.ipc_base + IPC_SPTR);
+	/* The input data register is 32bit register and inlen is in Byte */
+	for (i = 0; i < ((inlen + 3) / 4); i++)
+		ipc_data_writel(wbuf[i], 4 * i);
+	ipc_send_command((inlen << IPC_CMD_SIZE) |
+			(sub << IPC_CMD_SUBCMD) | cmd);
+	ret = intel_pmc_ipc_check_status();
+	if (!ret) {
+		/* out is read from 32bit register and outlen is in 32bit */
+		for (i = 0; i < outlen; i++)
+			*out++ = ipc_data_readl(4 * i);
+	}
+	mutex_unlock(&ipclock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(intel_pmc_ipc_raw_cmd);
+
+/**
+ * intel_pmc_ipc_command() -  IPC command with input/output data
+ * @cmd:	IPC command code.
+ * @sub:	IPC command sub type.
+ * @in:		input data of this IPC command.
+ * @inlen:	input data length in bytes.
+ * @out:	output data of this IPC command.
+ * @outlen:	output data length in dwords.
+ *
+ * Send an IPC command to PMC with input/output data.
+ *
+ * Return:	an IPC error code or 0 on success.
+ */
+int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen,
+			  u32 *out, u32 outlen)
+{
+	return intel_pmc_ipc_raw_cmd(cmd, sub, in, inlen, out, outlen, 0, 0);
+}
+EXPORT_SYMBOL_GPL(intel_pmc_ipc_command);
+
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+	int status;
+
+	if (ipcdev.irq_mode) {
+		status = ipc_read_status();
+		writel(status | IPC_STATUS_IRQ, ipcdev.ipc_base + IPC_STATUS);
+	}
+	complete(&ipcdev.cmd_complete);
+
+	return IRQ_HANDLED;
+}
+
+static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	resource_size_t pci_resource;
+	int ret;
+	int len;
+
+	ipcdev.dev = &pci_dev_get(pdev)->dev;
+	ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ;
+
+	ret = pci_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	ret = pci_request_regions(pdev, "intel_pmc_ipc");
+	if (ret)
+		return ret;
+
+	pci_resource = pci_resource_start(pdev, 0);
+	len = pci_resource_len(pdev, 0);
+	if (!pci_resource || !len) {
+		dev_err(&pdev->dev, "Failed to get resource\n");
+		return -ENOMEM;
+	}
+
+	init_completion(&ipcdev.cmd_complete);
+
+	if (request_irq(pdev->irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) {
+		dev_err(&pdev->dev, "Failed to request irq\n");
+		return -EBUSY;
+	}
+
+	ipcdev.ipc_base = ioremap_nocache(pci_resource, len);
+	if (!ipcdev.ipc_base) {
+		dev_err(&pdev->dev, "Failed to ioremap ipc base\n");
+		free_irq(pdev->irq, &ipcdev);
+		ret = -ENOMEM;
+	}
+
+	return ret;
+}
+
+static void ipc_pci_remove(struct pci_dev *pdev)
+{
+	free_irq(pdev->irq, &ipcdev);
+	pci_release_regions(pdev);
+	pci_dev_put(pdev);
+	iounmap(ipcdev.ipc_base);
+	ipcdev.dev = NULL;
+}
+
+static const struct pci_device_id ipc_pci_ids[] = {
+	{PCI_VDEVICE(INTEL, 0x0a94), 0},
+	{PCI_VDEVICE(INTEL, 0x1a94), 0},
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, ipc_pci_ids);
+
+static struct pci_driver ipc_pci_driver = {
+	.name = "intel_pmc_ipc",
+	.id_table = ipc_pci_ids,
+	.probe = ipc_pci_probe,
+	.remove = ipc_pci_remove,
+};
+
+static ssize_t intel_pmc_ipc_simple_cmd_store(struct device *dev,
+					      struct device_attribute *attr,
+					      const char *buf, size_t count)
+{
+	int subcmd;
+	int cmd;
+	int ret;
+
+	ret = sscanf(buf, "%d %d", &cmd, &subcmd);
+	if (ret != 2) {
+		dev_err(dev, "Error args\n");
+		return -EINVAL;
+	}
+
+	ret = intel_pmc_ipc_simple_command(cmd, subcmd);
+	if (ret) {
+		dev_err(dev, "command %d error with %d\n", cmd, ret);
+		return ret;
+	}
+	return (ssize_t)count;
+}
+
+static ssize_t intel_pmc_ipc_northpeak_store(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf, size_t count)
+{
+	unsigned long val;
+	int subcmd;
+	int ret;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val)
+		subcmd = 1;
+	else
+		subcmd = 0;
+	ret = intel_pmc_ipc_simple_command(PMC_IPC_NORTHPEAK_CTRL, subcmd);
+	if (ret) {
+		dev_err(dev, "command north %d error with %d\n", subcmd, ret);
+		return ret;
+	}
+	return (ssize_t)count;
+}
+
+static DEVICE_ATTR(simplecmd, S_IWUSR,
+		   NULL, intel_pmc_ipc_simple_cmd_store);
+static DEVICE_ATTR(northpeak, S_IWUSR,
+		   NULL, intel_pmc_ipc_northpeak_store);
+
+static struct attribute *intel_ipc_attrs[] = {
+	&dev_attr_northpeak.attr,
+	&dev_attr_simplecmd.attr,
+	NULL
+};
+
+static const struct attribute_group intel_ipc_group = {
+	.attrs = intel_ipc_attrs,
+};
+
+#define PUNIT_RESOURCE_INTER		1
+static struct resource punit_res[] = {
+	/* Punit */
+	{
+		.flags = IORESOURCE_MEM,
+	},
+	{
+		.flags = IORESOURCE_MEM,
+	},
+};
+
+#define TCO_RESOURCE_ACPI_IO		0
+#define TCO_RESOURCE_SMI_EN_IO		1
+#define TCO_RESOURCE_GCR_MEM		2
+static struct resource tco_res[] = {
+	/* ACPI - TCO */
+	{
+		.flags = IORESOURCE_IO,
+	},
+	/* ACPI - SMI */
+	{
+		.flags = IORESOURCE_IO,
+	},
+	/* GCS */
+	{
+		.flags = IORESOURCE_MEM,
+	},
+};
+
+static struct itco_wdt_platform_data tco_info = {
+	.name = "Apollo Lake SoC",
+	.version = 3,
+};
+
+static int ipc_create_punit_device(void)
+{
+	struct platform_device *pdev;
+	struct resource *res;
+	int ret;
+
+	pdev = platform_device_alloc(PUNIT_DEVICE_NAME, -1);
+	if (!pdev) {
+		dev_err(ipcdev.dev, "Failed to alloc punit platform device\n");
+		return -ENOMEM;
+	}
+
+	pdev->dev.parent = ipcdev.dev;
+
+	res = punit_res;
+	res->start = ipcdev.punit_base;
+	res->end = res->start + ipcdev.punit_size - 1;
+
+	res = punit_res + PUNIT_RESOURCE_INTER;
+	res->start = ipcdev.punit_base2;
+	res->end = res->start + ipcdev.punit_size2 - 1;
+
+	ret = platform_device_add_resources(pdev, punit_res,
+					    ARRAY_SIZE(punit_res));
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add platform punit resources\n");
+		goto err;
+	}
+
+	ret = platform_device_add(pdev);
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add punit platform device\n");
+		goto err;
+	}
+	ipcdev.punit_dev = pdev;
+
+	return 0;
+err:
+	platform_device_put(pdev);
+	return ret;
+}
+
+static int ipc_create_tco_device(void)
+{
+	struct platform_device *pdev;
+	struct resource *res;
+	int ret;
+
+	pdev = platform_device_alloc(TCO_DEVICE_NAME, -1);
+	if (!pdev) {
+		dev_err(ipcdev.dev, "Failed to alloc tco platform device\n");
+		return -ENOMEM;
+	}
+
+	pdev->dev.parent = ipcdev.dev;
+
+	res = tco_res + TCO_RESOURCE_ACPI_IO;
+	res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET;
+	res->end = res->start + TCO_REGS_SIZE - 1;
+
+	res = tco_res + TCO_RESOURCE_SMI_EN_IO;
+	res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET;
+	res->end = res->start + SMI_EN_SIZE - 1;
+
+	res = tco_res + TCO_RESOURCE_GCR_MEM;
+	res->start = ipcdev.gcr_base;
+	res->end = res->start + ipcdev.gcr_size - 1;
+
+	ret = platform_device_add_resources(pdev, tco_res, ARRAY_SIZE(tco_res));
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add tco platform resources\n");
+		goto err;
+	}
+
+	ret = platform_device_add_data(pdev, &tco_info, sizeof(tco_info));
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add tco platform data\n");
+		goto err;
+	}
+
+	ret = platform_device_add(pdev);
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add tco platform device\n");
+		goto err;
+	}
+	ipcdev.tco_dev = pdev;
+
+	return 0;
+err:
+	platform_device_put(pdev);
+	return ret;
+}
+
+static int ipc_create_pmc_devices(void)
+{
+	int ret;
+
+	ret = ipc_create_tco_device();
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add tco platform device\n");
+		return ret;
+	}
+	ret = ipc_create_punit_device();
+	if (ret) {
+		dev_err(ipcdev.dev, "Failed to add punit platform device\n");
+		platform_device_unregister(ipcdev.tco_dev);
+	}
+	return ret;
+}
+
+static int ipc_plat_get_res(struct platform_device *pdev)
+{
+	struct resource *res;
+	void __iomem *addr;
+	int size;
+
+	res = platform_get_resource(pdev, IORESOURCE_IO,
+				    PLAT_RESOURCE_ACPI_IO_INDEX);
+	if (!res) {
+		dev_err(&pdev->dev, "Failed to get io resource\n");
+		return -ENXIO;
+	}
+	size = resource_size(res);
+	ipcdev.acpi_io_base = res->start;
+	ipcdev.acpi_io_size = size;
+	dev_info(&pdev->dev, "io res: %llx %x\n",
+		 (long long)res->start, (int)resource_size(res));
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM,
+				    PLAT_RESOURCE_PUNIT_DATA_INDEX);
+	if (!res) {
+		dev_err(&pdev->dev, "Failed to get punit resource\n");
+		return -ENXIO;
+	}
+	size = resource_size(res);
+	ipcdev.punit_base = res->start;
+	ipcdev.punit_size = size;
+	dev_info(&pdev->dev, "punit data res: %llx %x\n",
+		 (long long)res->start, (int)resource_size(res));
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM,
+				    PLAT_RESOURCE_PUNIT_INTER_INDEX);
+	if (!res) {
+		dev_err(&pdev->dev, "Failed to get punit inter resource\n");
+		return -ENXIO;
+	}
+	size = resource_size(res);
+	ipcdev.punit_base2 = res->start;
+	ipcdev.punit_size2 = size;
+	dev_info(&pdev->dev, "punit interface res: %llx %x\n",
+		 (long long)res->start, (int)resource_size(res));
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM,
+				    PLAT_RESOURCE_IPC_INDEX);
+	if (!res) {
+		dev_err(&pdev->dev, "Failed to get ipc resource\n");
+		return -ENXIO;
+	}
+	size = PLAT_RESOURCE_IPC_SIZE;
+	if (!request_mem_region(res->start, size, pdev->name)) {
+		dev_err(&pdev->dev, "Failed to request ipc resource\n");
+		return -EBUSY;
+	}
+	addr = ioremap_nocache(res->start, size);
+	if (!addr) {
+		dev_err(&pdev->dev, "I/O memory remapping failed\n");
+		release_mem_region(res->start, size);
+		return -ENOMEM;
+	}
+	ipcdev.ipc_base = addr;
+
+	ipcdev.gcr_base = res->start + size;
+	ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE;
+	dev_info(&pdev->dev, "ipc res: %llx %x\n",
+		 (long long)res->start, (int)resource_size(res));
+
+	return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id ipc_acpi_ids[] = {
+	{ "INT34D2", 0},
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, ipc_acpi_ids);
+#endif
+
+static int ipc_plat_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int ret;
+
+	ipcdev.dev = &pdev->dev;
+	ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ;
+	init_completion(&ipcdev.cmd_complete);
+
+	ipcdev.irq = platform_get_irq(pdev, 0);
+	if (ipcdev.irq < 0) {
+		dev_err(&pdev->dev, "Failed to get irq\n");
+		return -EINVAL;
+	}
+
+	ret = ipc_plat_get_res(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request resource\n");
+		return ret;
+	}
+
+	ret = ipc_create_pmc_devices();
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to create pmc devices\n");
+		goto err_device;
+	}
+
+	if (request_irq(ipcdev.irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) {
+		dev_err(&pdev->dev, "Failed to request irq\n");
+		ret = -EBUSY;
+		goto err_irq;
+	}
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &intel_ipc_group);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to create sysfs group %d\n",
+			ret);
+		goto err_sys;
+	}
+
+	return 0;
+err_sys:
+	free_irq(ipcdev.irq, &ipcdev);
+err_irq:
+	platform_device_unregister(ipcdev.tco_dev);
+	platform_device_unregister(ipcdev.punit_dev);
+err_device:
+	iounmap(ipcdev.ipc_base);
+	res = platform_get_resource(pdev, IORESOURCE_MEM,
+				    PLAT_RESOURCE_IPC_INDEX);
+	if (res)
+		release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE);
+	return ret;
+}
+
+static int ipc_plat_remove(struct platform_device *pdev)
+{
+	struct resource *res;
+
+	sysfs_remove_group(&pdev->dev.kobj, &intel_ipc_group);
+	free_irq(ipcdev.irq, &ipcdev);
+	platform_device_unregister(ipcdev.tco_dev);
+	platform_device_unregister(ipcdev.punit_dev);
+	iounmap(ipcdev.ipc_base);
+	res = platform_get_resource(pdev, IORESOURCE_MEM,
+				    PLAT_RESOURCE_IPC_INDEX);
+	if (res)
+		release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE);
+	ipcdev.dev = NULL;
+	return 0;
+}
+
+static struct platform_driver ipc_plat_driver = {
+	.remove = ipc_plat_remove,
+	.probe = ipc_plat_probe,
+	.driver = {
+		.name = "pmc-ipc-plat",
+		.acpi_match_table = ACPI_PTR(ipc_acpi_ids),
+	},
+};
+
+static int __init intel_pmc_ipc_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&ipc_plat_driver);
+	if (ret) {
+		pr_err("Failed to register PMC ipc platform driver\n");
+		return ret;
+	}
+	ret = pci_register_driver(&ipc_pci_driver);
+	if (ret) {
+		pr_err("Failed to register PMC ipc pci driver\n");
+		platform_driver_unregister(&ipc_plat_driver);
+		return ret;
+	}
+	return ret;
+}
+
+static void __exit intel_pmc_ipc_exit(void)
+{
+	pci_unregister_driver(&ipc_pci_driver);
+	platform_driver_unregister(&ipc_plat_driver);
+}
+
+MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
+MODULE_DESCRIPTION("Intel PMC IPC driver");
+MODULE_LICENSE("GPL");
+
+/* Some modules are dependent on this, so init earlier */
+fs_initcall(intel_pmc_ipc_init);
+module_exit(intel_pmc_ipc_exit);
diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c
new file mode 100644
index 0000000..709f0af
--- /dev/null
+++ b/drivers/platform/x86/intel_pmic_gpio.c
@@ -0,0 +1,330 @@
+/* Moorestown PMIC GPIO (access through IPC) driver
+ * Copyright (c) 2008 - 2009, Intel Corporation.
+ *
+ * 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
+ * published 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * Moorestown platform PMIC chip
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/stddef.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <asm/intel_scu_ipc.h>
+#include <linux/device.h>
+#include <linux/intel_pmic_gpio.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "pmic_gpio"
+
+/* register offset that IPC driver should use
+ * 8 GPIO + 8 GPOSW (6 controllable) + 8GPO
+ */
+enum pmic_gpio_register {
+	GPIO0		= 0xE0,
+	GPIO7		= 0xE7,
+	GPIOINT		= 0xE8,
+	GPOSWCTL0	= 0xEC,
+	GPOSWCTL5	= 0xF1,
+	GPO		= 0xF4,
+};
+
+/* bits definition for GPIO & GPOSW */
+#define GPIO_DRV 0x01
+#define GPIO_DIR 0x02
+#define GPIO_DIN 0x04
+#define GPIO_DOU 0x08
+#define GPIO_INTCTL 0x30
+#define GPIO_DBC 0xc0
+
+#define GPOSW_DRV 0x01
+#define GPOSW_DOU 0x08
+#define GPOSW_RDRV 0x30
+
+#define GPIO_UPDATE_TYPE	0x80000000
+
+#define NUM_GPIO 24
+
+struct pmic_gpio {
+	struct mutex		buslock;
+	struct gpio_chip	chip;
+	void			*gpiointr;
+	int			irq;
+	unsigned		irq_base;
+	unsigned int		update_type;
+	u32			trigger_type;
+};
+
+static void pmic_program_irqtype(int gpio, int type)
+{
+	if (type & IRQ_TYPE_EDGE_RISING)
+		intel_scu_ipc_update_register(GPIO0 + gpio, 0x20, 0x20);
+	else
+		intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x20);
+
+	if (type & IRQ_TYPE_EDGE_FALLING)
+		intel_scu_ipc_update_register(GPIO0 + gpio, 0x10, 0x10);
+	else
+		intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x10);
+};
+
+static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	if (offset >= 8) {
+		pr_err("only pin 0-7 support input\n");
+		return -1;/* we only have 8 GPIO can use as input */
+	}
+	return intel_scu_ipc_update_register(GPIO0 + offset,
+							GPIO_DIR, GPIO_DIR);
+}
+
+static int pmic_gpio_direction_output(struct gpio_chip *chip,
+			unsigned offset, int value)
+{
+	int rc = 0;
+
+	if (offset < 8)/* it is GPIO */
+		rc = intel_scu_ipc_update_register(GPIO0 + offset,
+				GPIO_DRV | (value ? GPIO_DOU : 0),
+				GPIO_DRV | GPIO_DOU | GPIO_DIR);
+	else if (offset < 16)/* it is GPOSW */
+		rc = intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8,
+				GPOSW_DRV | (value ? GPOSW_DOU : 0),
+				GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV);
+	else if (offset > 15 && offset < 24)/* it is GPO */
+		rc = intel_scu_ipc_update_register(GPO,
+				value ? 1 << (offset - 16) : 0,
+				1 << (offset - 16));
+	else {
+		pr_err("invalid PMIC GPIO pin %d!\n", offset);
+		WARN_ON(1);
+	}
+
+	return rc;
+}
+
+static int pmic_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	u8 r;
+	int ret;
+
+	/* we only have 8 GPIO pins we can use as input */
+	if (offset >= 8)
+		return -EOPNOTSUPP;
+	ret = intel_scu_ipc_ioread8(GPIO0 + offset, &r);
+	if (ret < 0)
+		return ret;
+	return r & GPIO_DIN;
+}
+
+static void pmic_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	if (offset < 8)/* it is GPIO */
+		intel_scu_ipc_update_register(GPIO0 + offset,
+			GPIO_DRV | (value ? GPIO_DOU : 0),
+			GPIO_DRV | GPIO_DOU);
+	else if (offset < 16)/* it is GPOSW */
+		intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8,
+			GPOSW_DRV | (value ? GPOSW_DOU : 0),
+			GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV);
+	else if (offset > 15 && offset < 24) /* it is GPO */
+		intel_scu_ipc_update_register(GPO,
+			value ? 1 << (offset - 16) : 0,
+			1 << (offset - 16));
+}
+
+/*
+ * This is called from genirq with pg->buslock locked and
+ * irq_desc->lock held. We can not access the scu bus here, so we
+ * store the change and update in the bus_sync_unlock() function below
+ */
+static int pmic_irq_type(struct irq_data *data, unsigned type)
+{
+	struct pmic_gpio *pg = irq_data_get_irq_chip_data(data);
+	u32 gpio = data->irq - pg->irq_base;
+
+	if (gpio >= pg->chip.ngpio)
+		return -EINVAL;
+
+	pg->trigger_type = type;
+	pg->update_type = gpio | GPIO_UPDATE_TYPE;
+	return 0;
+}
+
+static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct pmic_gpio *pg = container_of(chip, struct pmic_gpio, chip);
+
+	return pg->irq_base + offset;
+}
+
+static void pmic_bus_lock(struct irq_data *data)
+{
+	struct pmic_gpio *pg = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&pg->buslock);
+}
+
+static void pmic_bus_sync_unlock(struct irq_data *data)
+{
+	struct pmic_gpio *pg = irq_data_get_irq_chip_data(data);
+
+	if (pg->update_type) {
+		unsigned int gpio = pg->update_type & ~GPIO_UPDATE_TYPE;
+
+		pmic_program_irqtype(gpio, pg->trigger_type);
+		pg->update_type = 0;
+	}
+	mutex_unlock(&pg->buslock);
+}
+
+/* the gpiointr register is read-clear, so just do nothing. */
+static void pmic_irq_unmask(struct irq_data *data) { }
+
+static void pmic_irq_mask(struct irq_data *data) { }
+
+static struct irq_chip pmic_irqchip = {
+	.name			= "PMIC-GPIO",
+	.irq_mask		= pmic_irq_mask,
+	.irq_unmask		= pmic_irq_unmask,
+	.irq_set_type		= pmic_irq_type,
+	.irq_bus_lock		= pmic_bus_lock,
+	.irq_bus_sync_unlock	= pmic_bus_sync_unlock,
+};
+
+static irqreturn_t pmic_irq_handler(int irq, void *data)
+{
+	struct pmic_gpio *pg = data;
+	u8 intsts = *((u8 *)pg->gpiointr + 4);
+	int gpio;
+	irqreturn_t ret = IRQ_NONE;
+
+	for (gpio = 0; gpio < 8; gpio++) {
+		if (intsts & (1 << gpio)) {
+			pr_debug("pmic pin %d triggered\n", gpio);
+			generic_handle_irq(pg->irq_base + gpio);
+			ret = IRQ_HANDLED;
+		}
+	}
+	return ret;
+}
+
+static int platform_pmic_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	int irq = platform_get_irq(pdev, 0);
+	struct intel_pmic_gpio_platform_data *pdata = dev->platform_data;
+
+	struct pmic_gpio *pg;
+	int retval;
+	int i;
+
+	if (irq < 0) {
+		dev_dbg(dev, "no IRQ line\n");
+		return -EINVAL;
+	}
+
+	if (!pdata || !pdata->gpio_base || !pdata->irq_base) {
+		dev_dbg(dev, "incorrect or missing platform data\n");
+		return -EINVAL;
+	}
+
+	pg = kzalloc(sizeof(*pg), GFP_KERNEL);
+	if (!pg)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, pg);
+
+	pg->irq = irq;
+	/* setting up SRAM mapping for GPIOINT register */
+	pg->gpiointr = ioremap_nocache(pdata->gpiointr, 8);
+	if (!pg->gpiointr) {
+		pr_err("Can not map GPIOINT\n");
+		retval = -EINVAL;
+		goto err2;
+	}
+	pg->irq_base = pdata->irq_base;
+	pg->chip.label = "intel_pmic";
+	pg->chip.direction_input = pmic_gpio_direction_input;
+	pg->chip.direction_output = pmic_gpio_direction_output;
+	pg->chip.get = pmic_gpio_get;
+	pg->chip.set = pmic_gpio_set;
+	pg->chip.to_irq = pmic_gpio_to_irq;
+	pg->chip.base = pdata->gpio_base;
+	pg->chip.ngpio = NUM_GPIO;
+	pg->chip.can_sleep = 1;
+	pg->chip.dev = dev;
+
+	mutex_init(&pg->buslock);
+
+	pg->chip.dev = dev;
+	retval = gpiochip_add(&pg->chip);
+	if (retval) {
+		pr_err("Can not add pmic gpio chip\n");
+		goto err;
+	}
+
+	retval = request_irq(pg->irq, pmic_irq_handler, 0, "pmic", pg);
+	if (retval) {
+		pr_warn("Interrupt request failed\n");
+		goto fail_request_irq;
+	}
+
+	for (i = 0; i < 8; i++) {
+		irq_set_chip_and_handler_name(i + pg->irq_base,
+					      &pmic_irqchip,
+					      handle_simple_irq,
+					      "demux");
+		irq_set_chip_data(i + pg->irq_base, pg);
+	}
+	return 0;
+
+fail_request_irq:
+	gpiochip_remove(&pg->chip);
+err:
+	iounmap(pg->gpiointr);
+err2:
+	kfree(pg);
+	return retval;
+}
+
+/* at the same time, register a platform driver
+ * this supports the sfi 0.81 fw */
+static struct platform_driver platform_pmic_gpio_driver = {
+	.driver = {
+		.name		= DRIVER_NAME,
+	},
+	.probe		= platform_pmic_gpio_probe,
+};
+
+static int __init platform_pmic_gpio_init(void)
+{
+	return platform_driver_register(&platform_pmic_gpio_driver);
+}
+
+subsys_initcall(platform_pmic_gpio_init);
+
+MODULE_AUTHOR("Alek Du <alek.du@intel.com>");
+MODULE_DESCRIPTION("Intel Moorestown PMIC GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
new file mode 100644
index 0000000..f94b730
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -0,0 +1,666 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010,2015 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@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; version 2
+ * of the License.
+ *
+ * SCU running in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad SCU.
+ * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
+ * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
+ * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
+ * along with other APIs.
+ */
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/sfi.h>
+#include <linux/module.h>
+#include <asm/intel-mid.h>
+#include <asm/intel_scu_ipc.h>
+
+/* IPC defines the following message types */
+#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
+#define IPCMSG_BATTERY        0xEF /* Coulomb Counter Accumulator */
+#define IPCMSG_FW_UPDATE      0xFE /* Firmware update */
+#define IPCMSG_PCNTRL         0xFF /* Power controller unit read/write */
+#define IPCMSG_FW_REVISION    0xF4 /* Get firmware revision */
+
+/* Command id associated with message IPCMSG_PCNTRL */
+#define IPC_CMD_PCNTRL_W      0 /* Register write */
+#define IPC_CMD_PCNTRL_R      1 /* Register read */
+#define IPC_CMD_PCNTRL_M      2 /* Register read-modify-write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of PCI BAR 0.
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers. The following is the IPC mechanism
+ *
+ * 1. IA core cDMI interface claims this transaction and converts it to a
+ *    Transaction Layer Packet (TLP) message which is sent across the cDMI.
+ *
+ * 2. South Complex cDMI block receives this message and writes it to
+ *    the IPC-1 register block, causing an interrupt to the SCU
+ *
+ * 3. SCU firmware decodes this interrupt and IPC message and the appropriate
+ *    message handler is called within firmware.
+ */
+
+#define IPC_WWBUF_SIZE    20		/* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE    20		/* IPC Read buffer Size */
+#define IPC_IOC	          0x100		/* IPC command register IOC bit */
+
+#define PCI_DEVICE_ID_LINCROFT		0x082a
+#define PCI_DEVICE_ID_PENWELL		0x080e
+#define PCI_DEVICE_ID_CLOVERVIEW	0x08ea
+#define PCI_DEVICE_ID_TANGIER		0x11a0
+
+/* intel scu ipc driver data */
+struct intel_scu_ipc_pdata_t {
+	u32 i2c_base;
+	u32 i2c_len;
+	u8 irq_mode;
+};
+
+static struct intel_scu_ipc_pdata_t intel_scu_ipc_lincroft_pdata = {
+	.i2c_base = 0xff12b000,
+	.i2c_len = 0x10,
+	.irq_mode = 0,
+};
+
+/* Penwell and Cloverview */
+static struct intel_scu_ipc_pdata_t intel_scu_ipc_penwell_pdata = {
+	.i2c_base = 0xff12b000,
+	.i2c_len = 0x10,
+	.irq_mode = 1,
+};
+
+static struct intel_scu_ipc_pdata_t intel_scu_ipc_tangier_pdata = {
+	.i2c_base  = 0xff00d000,
+	.i2c_len = 0x10,
+	.irq_mode = 0,
+};
+
+struct intel_scu_ipc_dev {
+	struct device *dev;
+	void __iomem *ipc_base;
+	void __iomem *i2c_base;
+	struct completion cmd_complete;
+	u8 irq_mode;
+};
+
+static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
+
+/*
+ * IPC Read Buffer (Read Only):
+ * 16 byte buffer for receiving data from SCU, if IPC command
+ * processing results in response data
+ */
+#define IPC_READ_BUFFER		0x90
+
+#define IPC_I2C_CNTRL_ADDR	0
+#define I2C_DATA_ADDR		0x04
+
+static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
+
+/*
+ * Send ipc command
+ * Command Register (Write Only):
+ * A write to this register results in an interrupt to the SCU core processor
+ * Format:
+ * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
+ */
+static inline void ipc_command(struct intel_scu_ipc_dev *scu, u32 cmd)
+{
+	if (scu->irq_mode) {
+		reinit_completion(&scu->cmd_complete);
+		writel(cmd | IPC_IOC, scu->ipc_base);
+	}
+	writel(cmd, scu->ipc_base);
+}
+
+/*
+ * Write ipc data
+ * IPC Write Buffer (Write Only):
+ * 16-byte buffer for sending data associated with IPC command to
+ * SCU. Size of the data is specified in the IPC_COMMAND_REG register
+ */
+static inline void ipc_data_writel(struct intel_scu_ipc_dev *scu, u32 data, u32 offset)
+{
+	writel(data, scu->ipc_base + 0x80 + offset);
+}
+
+/*
+ * Status Register (Read Only):
+ * Driver will read this register to get the ready/busy status of the IPC
+ * block and error status of the IPC command that was just processed by SCU
+ * Format:
+ * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
+ */
+static inline u8 ipc_read_status(struct intel_scu_ipc_dev *scu)
+{
+	return __raw_readl(scu->ipc_base + 0x04);
+}
+
+/* Read ipc byte data */
+static inline u8 ipc_data_readb(struct intel_scu_ipc_dev *scu, u32 offset)
+{
+	return readb(scu->ipc_base + IPC_READ_BUFFER + offset);
+}
+
+/* Read ipc u32 data */
+static inline u32 ipc_data_readl(struct intel_scu_ipc_dev *scu, u32 offset)
+{
+	return readl(scu->ipc_base + IPC_READ_BUFFER + offset);
+}
+
+/* Wait till scu status is busy */
+static inline int busy_loop(struct intel_scu_ipc_dev *scu)
+{
+	u32 status = ipc_read_status(scu);
+	u32 loop_count = 100000;
+
+	/* break if scu doesn't reset busy bit after huge retry */
+	while ((status & BIT(0)) && --loop_count) {
+		udelay(1); /* scu processing time is in few u secods */
+		status = ipc_read_status(scu);
+	}
+
+	if (status & BIT(0)) {
+		dev_err(scu->dev, "IPC timed out");
+		return -ETIMEDOUT;
+	}
+
+	if (status & BIT(1))
+		return -EIO;
+
+	return 0;
+}
+
+/* Wait till ipc ioc interrupt is received or timeout in 3 HZ */
+static inline int ipc_wait_for_interrupt(struct intel_scu_ipc_dev *scu)
+{
+	int status;
+
+	if (!wait_for_completion_timeout(&scu->cmd_complete, 3 * HZ)) {
+		dev_err(scu->dev, "IPC timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	status = ipc_read_status(scu);
+	if (status & BIT(1))
+		return -EIO;
+
+	return 0;
+}
+
+static int intel_scu_ipc_check_status(struct intel_scu_ipc_dev *scu)
+{
+	return scu->irq_mode ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
+}
+
+/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
+static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
+{
+	struct intel_scu_ipc_dev *scu = &ipcdev;
+	int nc;
+	u32 offset = 0;
+	int err;
+	u8 cbuf[IPC_WWBUF_SIZE];
+	u32 *wbuf = (u32 *)&cbuf;
+
+	memset(cbuf, 0, sizeof(cbuf));
+
+	mutex_lock(&ipclock);
+
+	if (scu->dev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	for (nc = 0; nc < count; nc++, offset += 2) {
+		cbuf[offset] = addr[nc];
+		cbuf[offset + 1] = addr[nc] >> 8;
+	}
+
+	if (id == IPC_CMD_PCNTRL_R) {
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+			ipc_data_writel(scu, wbuf[nc], offset);
+		ipc_command(scu, (count * 2) << 16 | id << 12 | 0 << 8 | op);
+	} else if (id == IPC_CMD_PCNTRL_W) {
+		for (nc = 0; nc < count; nc++, offset += 1)
+			cbuf[offset] = data[nc];
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+			ipc_data_writel(scu, wbuf[nc], offset);
+		ipc_command(scu, (count * 3) << 16 | id << 12 | 0 << 8 | op);
+	} else if (id == IPC_CMD_PCNTRL_M) {
+		cbuf[offset] = data[0];
+		cbuf[offset + 1] = data[1];
+		ipc_data_writel(scu, wbuf[0], 0); /* Write wbuff */
+		ipc_command(scu, 4 << 16 | id << 12 | 0 << 8 | op);
+	}
+
+	err = intel_scu_ipc_check_status(scu);
+	if (!err && id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+		/* Workaround: values are read as 0 without memcpy_fromio */
+		memcpy_fromio(cbuf, scu->ipc_base + 0x90, 16);
+		for (nc = 0; nc < count; nc++)
+			data[nc] = ipc_data_readb(scu, nc);
+	}
+	mutex_unlock(&ipclock);
+	return err;
+}
+
+/**
+ *	intel_scu_ipc_ioread8		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read byte
+ *
+ *	Read a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data)
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread8);
+
+/**
+ *	intel_scu_ipc_ioread16		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read word
+ *
+ *	Read a register pair. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data)
+{
+	u16 x[2] = {addr, addr + 1};
+	return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread16);
+
+/**
+ *	intel_scu_ipc_ioread32		-	read a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read dword
+ *
+ *	Read four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread32);
+
+/**
+ *	intel_scu_ipc_iowrite8		-	write a byte via the SCU
+ *	@addr: register on SCU
+ *	@data: byte to write
+ *
+ *	Write a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data)
+{
+	return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
+
+/**
+ *	intel_scu_ipc_iowrite16		-	write a word via the SCU
+ *	@addr: register on SCU
+ *	@data: word to write
+ *
+ *	Write two registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data)
+{
+	u16 x[2] = {addr, addr + 1};
+	return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
+
+/**
+ *	intel_scu_ipc_iowrite32		-	write a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: dword to write
+ *
+ *	Write four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
+
+/**
+ *	intel_scu_ipc_readvv		-	read a set of registers
+ *	@addr: register list
+ *	@data: bytes to return
+ *	@len: length of array
+ *
+ *	Read registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_readv);
+
+/**
+ *	intel_scu_ipc_writev		-	write a set of registers
+ *	@addr: register list
+ *	@data: bytes to write
+ *	@len: length of array
+ *
+ *	Write registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ *
+ */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_writev);
+
+/**
+ *	intel_scu_ipc_update_register	-	r/m/w a register
+ *	@addr: register address
+ *	@bits: bits to update
+ *	@mask: mask of bits to update
+ *
+ *	Read-modify-write power control unit register. The first data argument
+ *	must be register value and second is mask value
+ *	mask is a bitmap that indicates which bits to update.
+ *	0 = masked. Don't modify this bit, 1 = modify this bit.
+ *	returns 0 on success or an error code.
+ *
+ *	This function may sleep. Locking between SCU accesses is handled
+ *	for the caller.
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask)
+{
+	u8 data[2] = { bits, mask };
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
+}
+EXPORT_SYMBOL(intel_scu_ipc_update_register);
+
+/**
+ *	intel_scu_ipc_simple_command	-	send a simple command
+ *	@cmd: command
+ *	@sub: sub type
+ *
+ *	Issue a simple command to the SCU. Do not use this interface if
+ *	you must then access data as any data values may be overwritten
+ *	by another SCU access by the time this function returns.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_simple_command(int cmd, int sub)
+{
+	struct intel_scu_ipc_dev *scu = &ipcdev;
+	int err;
+
+	mutex_lock(&ipclock);
+	if (scu->dev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(scu, sub << 12 | cmd);
+	err = intel_scu_ipc_check_status(scu);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_simple_command);
+
+/**
+ *	intel_scu_ipc_command	-	command with data
+ *	@cmd: command
+ *	@sub: sub type
+ *	@in: input data
+ *	@inlen: input length in dwords
+ *	@out: output data
+ *	@outlein: output length in dwords
+ *
+ *	Issue a command to the SCU which involves data transfers. Do the
+ *	data copies under the lock but leave it for the caller to interpret
+ */
+int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
+			  u32 *out, int outlen)
+{
+	struct intel_scu_ipc_dev *scu = &ipcdev;
+	int i, err;
+
+	mutex_lock(&ipclock);
+	if (scu->dev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < inlen; i++)
+		ipc_data_writel(scu, *in++, 4 * i);
+
+	ipc_command(scu, (inlen << 16) | (sub << 12) | cmd);
+	err = intel_scu_ipc_check_status(scu);
+
+	if (!err) {
+		for (i = 0; i < outlen; i++)
+			*out++ = ipc_data_readl(scu, 4 * i);
+	}
+
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_command);
+
+/* I2C commands */
+#define IPC_I2C_WRITE 1 /* I2C Write command */
+#define IPC_I2C_READ  2 /* I2C Read command */
+
+/**
+ *	intel_scu_ipc_i2c_cntrl		-	I2C read/write operations
+ *	@addr: I2C address + command bits
+ *	@data: data to read/write
+ *
+ *	Perform an an I2C read/write operation via the SCU. All locking is
+ *	handled for the caller. This function may sleep.
+ *
+ *	Returns an error code or 0 on success.
+ *
+ *	This has to be in the IPC driver for the locking.
+ */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
+{
+	struct intel_scu_ipc_dev *scu = &ipcdev;
+	u32 cmd = 0;
+
+	mutex_lock(&ipclock);
+	if (scu->dev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	cmd = (addr >> 24) & 0xFF;
+	if (cmd == IPC_I2C_READ) {
+		writel(addr, scu->i2c_base + IPC_I2C_CNTRL_ADDR);
+		/* Write not getting updated without delay */
+		mdelay(1);
+		*data = readl(scu->i2c_base + I2C_DATA_ADDR);
+	} else if (cmd == IPC_I2C_WRITE) {
+		writel(*data, scu->i2c_base + I2C_DATA_ADDR);
+		mdelay(1);
+		writel(addr, scu->i2c_base + IPC_I2C_CNTRL_ADDR);
+	} else {
+		dev_err(scu->dev,
+			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+		mutex_unlock(&ipclock);
+		return -EIO;
+	}
+	mutex_unlock(&ipclock);
+	return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+/*
+ * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
+ * When ioc bit is set to 1, caller api must wait for interrupt handler called
+ * which in turn unlocks the caller api. Currently this is not used
+ *
+ * This is edge triggered so we need take no action to clear anything
+ */
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+	struct intel_scu_ipc_dev *scu = dev_id;
+
+	if (scu->irq_mode)
+		complete(&scu->cmd_complete);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ *	ipc_probe	-	probe an Intel SCU IPC
+ *	@pdev: the PCI device matching
+ *	@id: entry in the match table
+ *
+ *	Enable and install an intel SCU IPC. This appears in the PCI space
+ *	but uses some hard coded addresses as well.
+ */
+static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	int platform;		/* Platform type */
+	int err;
+	struct intel_scu_ipc_dev *scu = &ipcdev;
+	struct intel_scu_ipc_pdata_t *pdata;
+
+	platform = intel_mid_identify_cpu();
+	if (platform == 0)
+		return -ENODEV;
+
+	if (scu->dev)		/* We support only one SCU */
+		return -EBUSY;
+
+	pdata = (struct intel_scu_ipc_pdata_t *)id->driver_data;
+
+	scu->dev = &pdev->dev;
+	scu->irq_mode = pdata->irq_mode;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev));
+	if (err)
+		return err;
+
+	init_completion(&scu->cmd_complete);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc",
+			       scu);
+	if (err)
+		return err;
+
+	scu->ipc_base = pcim_iomap_table(pdev)[0];
+
+	scu->i2c_base = ioremap_nocache(pdata->i2c_base, pdata->i2c_len);
+	if (!scu->i2c_base)
+		return -ENOMEM;
+
+	intel_scu_devices_create();
+
+	pci_set_drvdata(pdev, scu);
+	return 0;
+}
+
+/**
+ *	ipc_remove	-	remove a bound IPC device
+ *	@pdev: PCI device
+ *
+ *	In practice the SCU is not removable but this function is also
+ *	called for each device on a module unload or cleanup which is the
+ *	path that will get used.
+ *
+ *	Free up the mappings and release the PCI resources
+ */
+static void ipc_remove(struct pci_dev *pdev)
+{
+	struct intel_scu_ipc_dev *scu = pci_get_drvdata(pdev);
+
+	mutex_lock(&ipclock);
+	scu->dev = NULL;
+	mutex_unlock(&ipclock);
+
+	iounmap(scu->i2c_base);
+	intel_scu_devices_destroy();
+}
+
+static const struct pci_device_id pci_ids[] = {
+	{
+		PCI_VDEVICE(INTEL, PCI_DEVICE_ID_LINCROFT),
+		(kernel_ulong_t)&intel_scu_ipc_lincroft_pdata,
+	}, {
+		PCI_VDEVICE(INTEL, PCI_DEVICE_ID_PENWELL),
+		(kernel_ulong_t)&intel_scu_ipc_penwell_pdata,
+	}, {
+		PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CLOVERVIEW),
+		(kernel_ulong_t)&intel_scu_ipc_penwell_pdata,
+	}, {
+		PCI_VDEVICE(INTEL, PCI_DEVICE_ID_TANGIER),
+		(kernel_ulong_t)&intel_scu_ipc_tangier_pdata,
+	}, {
+		0,
+	}
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver ipc_driver = {
+	.name = "intel_scu_ipc",
+	.id_table = pci_ids,
+	.probe = ipc_probe,
+	.remove = ipc_remove,
+};
+
+module_pci_driver(ipc_driver);
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel_scu_ipcutil.c b/drivers/platform/x86/intel_scu_ipcutil.c
new file mode 100644
index 0000000..aa45424
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipcutil.c
@@ -0,0 +1,121 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@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; version 2
+ * of the License.
+ *
+ * This driver provides ioctl interfaces to call intel scu ipc driver api
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/intel_scu_ipc.h>
+
+static int major;
+
+/* ioctl commnds */
+#define	INTE_SCU_IPC_REGISTER_READ	0
+#define INTE_SCU_IPC_REGISTER_WRITE	1
+#define INTE_SCU_IPC_REGISTER_UPDATE	2
+
+struct scu_ipc_data {
+	u32     count;  /* No. of registers */
+	u16     addr[5]; /* Register addresses */
+	u8      data[5]; /* Register data */
+	u8      mask; /* Valid for read-modify-write */
+};
+
+/**
+ *	scu_reg_access		-	implement register access ioctls
+ *	@cmd: command we are doing (read/write/update)
+ *	@data: kernel copy of ioctl data
+ *
+ *	Allow the user to perform register accesses on the SCU via the
+ *	kernel interface
+ */
+
+static int scu_reg_access(u32 cmd, struct scu_ipc_data  *data)
+{
+	unsigned int count = data->count;
+
+	if (count == 0 || count == 3 || count > 4)
+		return -EINVAL;
+
+	switch (cmd) {
+	case INTE_SCU_IPC_REGISTER_READ:
+		return intel_scu_ipc_readv(data->addr, data->data, count);
+	case INTE_SCU_IPC_REGISTER_WRITE:
+		return intel_scu_ipc_writev(data->addr, data->data, count);
+	case INTE_SCU_IPC_REGISTER_UPDATE:
+		return intel_scu_ipc_update_register(data->addr[0],
+						    data->data[0], data->mask);
+	default:
+		return -ENOTTY;
+	}
+}
+
+/**
+ *	scu_ipc_ioctl		-	control ioctls for the SCU
+ *	@fp: file handle of the SCU device
+ *	@cmd: ioctl coce
+ *	@arg: pointer to user passed structure
+ *
+ *	Support the I/O and firmware flashing interfaces of the SCU
+ */
+static long scu_ipc_ioctl(struct file *fp, unsigned int cmd,
+							unsigned long arg)
+{
+	int ret;
+	struct scu_ipc_data  data;
+	void __user *argp = (void __user *)arg;
+
+	if (!capable(CAP_SYS_RAWIO))
+		return -EPERM;
+
+	if (copy_from_user(&data, argp, sizeof(struct scu_ipc_data)))
+		return -EFAULT;
+	ret = scu_reg_access(cmd, &data);
+	if (ret < 0)
+		return ret;
+	if (copy_to_user(argp, &data, sizeof(struct scu_ipc_data)))
+		return -EFAULT;
+	return 0;
+}
+
+static const struct file_operations scu_ipc_fops = {
+	.unlocked_ioctl = scu_ipc_ioctl,
+};
+
+static int __init ipc_module_init(void)
+{
+	major = register_chrdev(0, "intel_mid_scu", &scu_ipc_fops);
+	if (major < 0)
+		return major;
+
+	return 0;
+}
+
+static void __exit ipc_module_exit(void)
+{
+	unregister_chrdev(major, "intel_mid_scu");
+}
+
+module_init(ipc_module_init);
+module_exit(ipc_module_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Utility driver for intel scu ipc");
+MODULE_AUTHOR("Sreedhara <sreedhara.ds@intel.com>");
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c
new file mode 100644
index 0000000..4231770
--- /dev/null
+++ b/drivers/platform/x86/msi-laptop.c
@@ -0,0 +1,1200 @@
+/*-*-linux-c-*-*/
+
+/*
+  Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+
+  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.
+ */
+
+/*
+ * msi-laptop.c - MSI S270 laptop support. This laptop is sold under
+ * various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
+ *
+ * Driver also supports S271, S420 models.
+ *
+ * This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
+ *
+ *   lcd_level - Screen brightness: contains a single integer in the
+ *   range 0..8. (rw)
+ *
+ *   auto_brightness - Enable automatic brightness control: contains
+ *   either 0 or 1. If set to 1 the hardware adjusts the screen
+ *   brightness automatically when the power cord is
+ *   plugged/unplugged. (rw)
+ *
+ *   wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
+ *
+ *   bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
+ *   Please note that this file is constantly 0 if no Bluetooth
+ *   hardware is available. (ro)
+ *
+ * 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/msi-laptop-bl/.
+ *
+ * This driver might work on other laptops produced by MSI. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * laptop as MSI S270. YMMV.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/i8042.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <acpi/video.h>
+
+#define MSI_DRIVER_VERSION "0.5"
+
+#define MSI_LCD_LEVEL_MAX 9
+
+#define MSI_EC_COMMAND_WIRELESS 0x10
+#define MSI_EC_COMMAND_LCD_LEVEL 0x11
+
+#define MSI_STANDARD_EC_COMMAND_ADDRESS	0x2e
+#define MSI_STANDARD_EC_BLUETOOTH_MASK	(1 << 0)
+#define MSI_STANDARD_EC_WEBCAM_MASK	(1 << 1)
+#define MSI_STANDARD_EC_WLAN_MASK	(1 << 3)
+#define MSI_STANDARD_EC_3G_MASK		(1 << 4)
+
+/* For set SCM load flag to disable BIOS fn key */
+#define MSI_STANDARD_EC_SCM_LOAD_ADDRESS	0x2d
+#define MSI_STANDARD_EC_SCM_LOAD_MASK		(1 << 0)
+
+#define MSI_STANDARD_EC_FUNCTIONS_ADDRESS	0xe4
+/* Power LED is orange - Turbo mode */
+#define MSI_STANDARD_EC_TURBO_MASK		(1 << 1)
+/* Power LED is green - ECO mode */
+#define MSI_STANDARD_EC_ECO_MASK		(1 << 3)
+/* Touchpad is turned on */
+#define MSI_STANDARD_EC_TOUCHPAD_MASK		(1 << 4)
+/* If this bit != bit 1, turbo mode can't be toggled */
+#define MSI_STANDARD_EC_TURBO_COOLDOWN_MASK	(1 << 7)
+
+#define MSI_STANDARD_EC_FAN_ADDRESS		0x33
+/* If zero, fan rotates at maximal speed */
+#define MSI_STANDARD_EC_AUTOFAN_MASK		(1 << 0)
+
+#ifdef CONFIG_PM_SLEEP
+static int msi_laptop_resume(struct device *device);
+#endif
+static SIMPLE_DEV_PM_OPS(msi_laptop_pm, NULL, msi_laptop_resume);
+
+#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS	0x2f
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static int auto_brightness;
+module_param(auto_brightness, int, 0);
+MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
+
+static const struct key_entry msi_laptop_keymap[] = {
+	{KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} },	/* Touch Pad On */
+	{KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },/* Touch Pad On */
+	{KE_END, 0}
+};
+
+static struct input_dev *msi_laptop_input_dev;
+
+static int wlan_s, bluetooth_s, threeg_s;
+static int threeg_exists;
+static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
+
+/* MSI laptop quirks */
+struct quirk_entry {
+	bool old_ec_model;
+
+	/* Some MSI 3G netbook only have one fn key to control
+	 * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to
+	 * disable the original Wlan/Bluetooth control by BIOS when user press
+	 * fn key, then control Wlan/Bluetooth/3G by SCM (software control by
+	 * OS). Without SCM, user cann't on/off 3G module on those 3G netbook.
+	 * On Linux, msi-laptop driver will do the same thing to disable the
+	 * original BIOS control, then might need use HAL or other userland
+	 * application to do the software control that simulate with SCM.
+	 * e.g. MSI N034 netbook
+	 */
+	bool load_scm_model;
+
+	/* Some MSI laptops need delay before reading from EC */
+	bool ec_delay;
+
+	/* Some MSI Wind netbooks (e.g. MSI Wind U100) need loading SCM to get
+	 * some features working (e.g. ECO mode), but we cannot change
+	 * Wlan/Bluetooth state in software and we can only read its state.
+	 */
+	bool ec_read_only;
+};
+
+static struct quirk_entry *quirks;
+
+/* Hardware access */
+
+static int set_lcd_level(int level)
+{
+	u8 buf[2];
+
+	if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
+		return -EINVAL;
+
+	buf[0] = 0x80;
+	buf[1] = (u8) (level*31);
+
+	return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf),
+			      NULL, 0);
+}
+
+static int get_lcd_level(void)
+{
+	u8 wdata = 0, rdata;
+	int result;
+
+	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
+				&rdata, 1);
+	if (result < 0)
+		return result;
+
+	return (int) rdata / 31;
+}
+
+static int get_auto_brightness(void)
+{
+	u8 wdata = 4, rdata;
+	int result;
+
+	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
+				&rdata, 1);
+	if (result < 0)
+		return result;
+
+	return !!(rdata & 8);
+}
+
+static int set_auto_brightness(int enable)
+{
+	u8 wdata[2], rdata;
+	int result;
+
+	wdata[0] = 4;
+
+	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1,
+				&rdata, 1);
+	if (result < 0)
+		return result;
+
+	wdata[0] = 0x84;
+	wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
+
+	return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2,
+			      NULL, 0);
+}
+
+static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
+{
+	int status;
+	u8 wdata = 0, rdata;
+	int result;
+
+	if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1))
+		return -EINVAL;
+
+	if (quirks->ec_read_only)
+		return -EOPNOTSUPP;
+
+	/* read current device state */
+	result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	if (!!(rdata & mask) != status) {
+		/* reverse device bit */
+		if (rdata & mask)
+			wdata = rdata & ~mask;
+		else
+			wdata = rdata | mask;
+
+		result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);
+		if (result < 0)
+			return result;
+	}
+
+	return count;
+}
+
+static int get_wireless_state(int *wlan, int *bluetooth)
+{
+	u8 wdata = 0, rdata;
+	int result;
+
+	result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1);
+	if (result < 0)
+		return result;
+
+	if (wlan)
+		*wlan = !!(rdata & 8);
+
+	if (bluetooth)
+		*bluetooth = !!(rdata & 128);
+
+	return 0;
+}
+
+static int get_wireless_state_ec_standard(void)
+{
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK);
+
+	bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK);
+
+	threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK);
+
+	return 0;
+}
+
+static int get_threeg_exists(void)
+{
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK);
+
+	return 0;
+}
+
+/* Backlight device stuff */
+
+static int bl_get_brightness(struct backlight_device *b)
+{
+	return get_lcd_level();
+}
+
+
+static int bl_update_status(struct backlight_device *b)
+{
+	return set_lcd_level(b->props.brightness);
+}
+
+static const struct backlight_ops msibl_ops = {
+	.get_brightness = bl_get_brightness,
+	.update_status  = bl_update_status,
+};
+
+static struct backlight_device *msibl_device;
+
+/* Platform device */
+
+static ssize_t show_wlan(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	int ret, enabled = 0;
+
+	if (quirks->old_ec_model) {
+		ret = get_wireless_state(&enabled, NULL);
+	} else {
+		ret = get_wireless_state_ec_standard();
+		enabled = wlan_s;
+	}
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%i\n", enabled);
+}
+
+static ssize_t store_wlan(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK);
+}
+
+static ssize_t show_bluetooth(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	int ret, enabled = 0;
+
+	if (quirks->old_ec_model) {
+		ret = get_wireless_state(NULL, &enabled);
+	} else {
+		ret = get_wireless_state_ec_standard();
+		enabled = bluetooth_s;
+	}
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%i\n", enabled);
+}
+
+static ssize_t store_bluetooth(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK);
+}
+
+static ssize_t show_threeg(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	int ret;
+
+	/* old msi ec not support 3G */
+	if (quirks->old_ec_model)
+		return -ENODEV;
+
+	ret = get_wireless_state_ec_standard();
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%i\n", threeg_s);
+}
+
+static ssize_t store_threeg(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK);
+}
+
+static ssize_t show_lcd_level(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	int ret;
+
+	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 >= MSI_LCD_LEVEL_MAX))
+		return -EINVAL;
+
+	ret = set_lcd_level(level);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t show_auto_brightness(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	int ret;
+
+	ret = get_auto_brightness();
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%i\n", ret);
+}
+
+static ssize_t store_auto_brightness(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+
+	int enable, ret;
+
+	if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
+		return -EINVAL;
+
+	ret = set_auto_brightness(enable);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t show_touchpad(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK));
+}
+
+static ssize_t show_turbo(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK));
+}
+
+static ssize_t show_eco(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK));
+}
+
+static ssize_t show_turbo_cooldown(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	return sprintf(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) |
+		(!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1));
+}
+
+static ssize_t show_auto_fan(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_FAN_ADDRESS, &rdata);
+	if (result < 0)
+		return result;
+
+	return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK));
+}
+
+static ssize_t store_auto_fan(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+
+	int enable, result;
+
+	if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
+		return -EINVAL;
+
+	result = ec_write(MSI_STANDARD_EC_FAN_ADDRESS, enable);
+	if (result < 0)
+		return result;
+
+	return count;
+}
+
+static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
+static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness,
+		   store_auto_brightness);
+static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
+static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
+static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
+static DEVICE_ATTR(touchpad, 0444, show_touchpad, NULL);
+static DEVICE_ATTR(turbo_mode, 0444, show_turbo, NULL);
+static DEVICE_ATTR(eco_mode, 0444, show_eco, NULL);
+static DEVICE_ATTR(turbo_cooldown, 0444, show_turbo_cooldown, NULL);
+static DEVICE_ATTR(auto_fan, 0644, show_auto_fan, store_auto_fan);
+
+static struct attribute *msipf_attributes[] = {
+	&dev_attr_bluetooth.attr,
+	&dev_attr_wlan.attr,
+	&dev_attr_touchpad.attr,
+	&dev_attr_turbo_mode.attr,
+	&dev_attr_eco_mode.attr,
+	&dev_attr_turbo_cooldown.attr,
+	&dev_attr_auto_fan.attr,
+	NULL
+};
+
+static struct attribute *msipf_old_attributes[] = {
+	&dev_attr_lcd_level.attr,
+	&dev_attr_auto_brightness.attr,
+	NULL
+};
+
+static struct attribute_group msipf_attribute_group = {
+	.attrs = msipf_attributes
+};
+
+static struct attribute_group msipf_old_attribute_group = {
+	.attrs = msipf_old_attributes
+};
+
+static struct platform_driver msipf_driver = {
+	.driver = {
+		.name = "msi-laptop-pf",
+		.pm = &msi_laptop_pm,
+	},
+};
+
+static struct platform_device *msipf_device;
+
+/* Initialization */
+
+static struct quirk_entry quirk_old_ec_model = {
+	.old_ec_model = true,
+};
+
+static struct quirk_entry quirk_load_scm_model = {
+	.load_scm_model = true,
+	.ec_delay = true,
+};
+
+static struct quirk_entry quirk_load_scm_ro_model = {
+	.load_scm_model = true,
+	.ec_read_only = true,
+};
+
+static int dmi_check_cb(const struct dmi_system_id *dmi)
+{
+	pr_info("Identified laptop model '%s'\n", dmi->ident);
+
+	quirks = dmi->driver_data;
+
+	return 1;
+}
+
+static struct dmi_system_id __initdata msi_dmi_table[] = {
+	{
+		.ident = "MSI S270",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
+			DMI_MATCH(DMI_CHASSIS_VENDOR,
+				  "MICRO-STAR INT'L CO.,LTD")
+		},
+		.driver_data = &quirk_old_ec_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI S271",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1058"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "0581"),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1058")
+		},
+		.driver_data = &quirk_old_ec_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI S420",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1412"),
+			DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1412")
+		},
+		.driver_data = &quirk_old_ec_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "Medion MD96100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
+			DMI_MATCH(DMI_CHASSIS_VENDOR,
+				  "MICRO-STAR INT'L CO.,LTD")
+		},
+		.driver_data = &quirk_old_ec_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI N034",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+				"MICRO-STAR INTERNATIONAL CO., LTD"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"),
+			DMI_MATCH(DMI_CHASSIS_VENDOR,
+			"MICRO-STAR INTERNATIONAL CO., LTD")
+		},
+		.driver_data = &quirk_load_scm_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI N051",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+				"MICRO-STAR INTERNATIONAL CO., LTD"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051"),
+			DMI_MATCH(DMI_CHASSIS_VENDOR,
+			"MICRO-STAR INTERNATIONAL CO., LTD")
+		},
+		.driver_data = &quirk_load_scm_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI N014",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+				"MICRO-STAR INTERNATIONAL CO., LTD"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),
+		},
+		.driver_data = &quirk_load_scm_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI CR620",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+				"Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
+		},
+		.driver_data = &quirk_load_scm_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI U270",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+				"Micro-Star International Co., Ltd."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "U270 series"),
+		},
+		.driver_data = &quirk_load_scm_model,
+		.callback = dmi_check_cb
+	},
+	{
+		.ident = "MSI U90/U100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+				"MICRO-STAR INTERNATIONAL CO., LTD"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "U90/U100"),
+		},
+		.driver_data = &quirk_load_scm_ro_model,
+		.callback = dmi_check_cb
+	},
+	{ }
+};
+
+static int rfkill_bluetooth_set(void *data, bool blocked)
+{
+	/* Do something with blocked...*/
+	/*
+	 * blocked == false is on
+	 * blocked == true is off
+	 */
+	int result = set_device_state(blocked ? "0" : "1", 0,
+			MSI_STANDARD_EC_BLUETOOTH_MASK);
+
+	return min(result, 0);
+}
+
+static int rfkill_wlan_set(void *data, bool blocked)
+{
+	int result = set_device_state(blocked ? "0" : "1", 0,
+			MSI_STANDARD_EC_WLAN_MASK);
+
+	return min(result, 0);
+}
+
+static int rfkill_threeg_set(void *data, bool blocked)
+{
+	int result = set_device_state(blocked ? "0" : "1", 0,
+			MSI_STANDARD_EC_3G_MASK);
+
+	return min(result, 0);
+}
+
+static const struct rfkill_ops rfkill_bluetooth_ops = {
+	.set_block = rfkill_bluetooth_set
+};
+
+static const struct rfkill_ops rfkill_wlan_ops = {
+	.set_block = rfkill_wlan_set
+};
+
+static const struct rfkill_ops rfkill_threeg_ops = {
+	.set_block = rfkill_threeg_set
+};
+
+static void rfkill_cleanup(void)
+{
+	if (rfk_bluetooth) {
+		rfkill_unregister(rfk_bluetooth);
+		rfkill_destroy(rfk_bluetooth);
+	}
+
+	if (rfk_threeg) {
+		rfkill_unregister(rfk_threeg);
+		rfkill_destroy(rfk_threeg);
+	}
+
+	if (rfk_wlan) {
+		rfkill_unregister(rfk_wlan);
+		rfkill_destroy(rfk_wlan);
+	}
+}
+
+static bool msi_rfkill_set_state(struct rfkill *rfkill, bool blocked)
+{
+	if (quirks->ec_read_only)
+		return rfkill_set_hw_state(rfkill, blocked);
+	else
+		return rfkill_set_sw_state(rfkill, blocked);
+}
+
+static void msi_update_rfkill(struct work_struct *ignored)
+{
+	get_wireless_state_ec_standard();
+
+	if (rfk_wlan)
+		msi_rfkill_set_state(rfk_wlan, !wlan_s);
+	if (rfk_bluetooth)
+		msi_rfkill_set_state(rfk_bluetooth, !bluetooth_s);
+	if (rfk_threeg)
+		msi_rfkill_set_state(rfk_threeg, !threeg_s);
+}
+static DECLARE_DELAYED_WORK(msi_rfkill_dwork, msi_update_rfkill);
+static DECLARE_WORK(msi_rfkill_work, msi_update_rfkill);
+
+static void msi_send_touchpad_key(struct work_struct *ignored)
+{
+	u8 rdata;
+	int result;
+
+	result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+	if (result < 0)
+		return;
+
+	sparse_keymap_report_event(msi_laptop_input_dev,
+		(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK) ?
+		KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, 1, true);
+}
+static DECLARE_DELAYED_WORK(msi_touchpad_dwork, msi_send_touchpad_key);
+static DECLARE_WORK(msi_touchpad_work, msi_send_touchpad_key);
+
+static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
+				struct serio *port)
+{
+	static bool extended;
+
+	if (str & I8042_STR_AUXDATA)
+		return false;
+
+	/* 0x54 wwan, 0x62 bluetooth, 0x76 wlan, 0xE4 touchpad toggle*/
+	if (unlikely(data == 0xe0)) {
+		extended = true;
+		return false;
+	} else if (unlikely(extended)) {
+		extended = false;
+		switch (data) {
+		case 0xE4:
+			if (quirks->ec_delay) {
+				schedule_delayed_work(&msi_touchpad_dwork,
+					round_jiffies_relative(0.5 * HZ));
+			} else
+				schedule_work(&msi_touchpad_work);
+			break;
+		case 0x54:
+		case 0x62:
+		case 0x76:
+			if (quirks->ec_delay) {
+				schedule_delayed_work(&msi_rfkill_dwork,
+					round_jiffies_relative(0.5 * HZ));
+			} else
+				schedule_work(&msi_rfkill_work);
+			break;
+		}
+	}
+
+	return false;
+}
+
+static void msi_init_rfkill(struct work_struct *ignored)
+{
+	if (rfk_wlan) {
+		rfkill_set_sw_state(rfk_wlan, !wlan_s);
+		rfkill_wlan_set(NULL, !wlan_s);
+	}
+	if (rfk_bluetooth) {
+		rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
+		rfkill_bluetooth_set(NULL, !bluetooth_s);
+	}
+	if (rfk_threeg) {
+		rfkill_set_sw_state(rfk_threeg, !threeg_s);
+		rfkill_threeg_set(NULL, !threeg_s);
+	}
+}
+static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill);
+
+static int rfkill_init(struct platform_device *sdev)
+{
+	/* add rfkill */
+	int retval;
+
+	/* keep the hardware wireless state */
+	get_wireless_state_ec_standard();
+
+	rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev,
+				RFKILL_TYPE_BLUETOOTH,
+				&rfkill_bluetooth_ops, NULL);
+	if (!rfk_bluetooth) {
+		retval = -ENOMEM;
+		goto err_bluetooth;
+	}
+	retval = rfkill_register(rfk_bluetooth);
+	if (retval)
+		goto err_bluetooth;
+
+	rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN,
+				&rfkill_wlan_ops, NULL);
+	if (!rfk_wlan) {
+		retval = -ENOMEM;
+		goto err_wlan;
+	}
+	retval = rfkill_register(rfk_wlan);
+	if (retval)
+		goto err_wlan;
+
+	if (threeg_exists) {
+		rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev,
+				RFKILL_TYPE_WWAN, &rfkill_threeg_ops, NULL);
+		if (!rfk_threeg) {
+			retval = -ENOMEM;
+			goto err_threeg;
+		}
+		retval = rfkill_register(rfk_threeg);
+		if (retval)
+			goto err_threeg;
+	}
+
+	/* schedule to run rfkill state initial */
+	if (quirks->ec_delay) {
+		schedule_delayed_work(&msi_rfkill_init,
+			round_jiffies_relative(1 * HZ));
+	} else
+		schedule_work(&msi_rfkill_work);
+
+	return 0;
+
+err_threeg:
+	rfkill_destroy(rfk_threeg);
+	if (rfk_wlan)
+		rfkill_unregister(rfk_wlan);
+err_wlan:
+	rfkill_destroy(rfk_wlan);
+	if (rfk_bluetooth)
+		rfkill_unregister(rfk_bluetooth);
+err_bluetooth:
+	rfkill_destroy(rfk_bluetooth);
+
+	return retval;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int msi_laptop_resume(struct device *device)
+{
+	u8 data;
+	int result;
+
+	if (!quirks->load_scm_model)
+		return 0;
+
+	/* set load SCM to disable hardware control by fn key */
+	result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
+	if (result < 0)
+		return result;
+
+	result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
+		data | MSI_STANDARD_EC_SCM_LOAD_MASK);
+	if (result < 0)
+		return result;
+
+	return 0;
+}
+#endif
+
+static int __init msi_laptop_input_setup(void)
+{
+	int err;
+
+	msi_laptop_input_dev = input_allocate_device();
+	if (!msi_laptop_input_dev)
+		return -ENOMEM;
+
+	msi_laptop_input_dev->name = "MSI Laptop hotkeys";
+	msi_laptop_input_dev->phys = "msi-laptop/input0";
+	msi_laptop_input_dev->id.bustype = BUS_HOST;
+
+	err = sparse_keymap_setup(msi_laptop_input_dev,
+		msi_laptop_keymap, NULL);
+	if (err)
+		goto err_free_dev;
+
+	err = input_register_device(msi_laptop_input_dev);
+	if (err)
+		goto err_free_keymap;
+
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(msi_laptop_input_dev);
+err_free_dev:
+	input_free_device(msi_laptop_input_dev);
+	return err;
+}
+
+static void msi_laptop_input_destroy(void)
+{
+	sparse_keymap_free(msi_laptop_input_dev);
+	input_unregister_device(msi_laptop_input_dev);
+}
+
+static int __init load_scm_model_init(struct platform_device *sdev)
+{
+	u8 data;
+	int result;
+
+	if (!quirks->ec_read_only) {
+		/* allow userland write sysfs file  */
+		dev_attr_bluetooth.store = store_bluetooth;
+		dev_attr_wlan.store = store_wlan;
+		dev_attr_threeg.store = store_threeg;
+		dev_attr_bluetooth.attr.mode |= S_IWUSR;
+		dev_attr_wlan.attr.mode |= S_IWUSR;
+		dev_attr_threeg.attr.mode |= S_IWUSR;
+	}
+
+	/* disable hardware control by fn key */
+	result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
+	if (result < 0)
+		return result;
+
+	result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
+		data | MSI_STANDARD_EC_SCM_LOAD_MASK);
+	if (result < 0)
+		return result;
+
+	/* initial rfkill */
+	result = rfkill_init(sdev);
+	if (result < 0)
+		goto fail_rfkill;
+
+	/* setup input device */
+	result = msi_laptop_input_setup();
+	if (result)
+		goto fail_input;
+
+	result = i8042_install_filter(msi_laptop_i8042_filter);
+	if (result) {
+		pr_err("Unable to install key filter\n");
+		goto fail_filter;
+	}
+
+	return 0;
+
+fail_filter:
+	msi_laptop_input_destroy();
+
+fail_input:
+	rfkill_cleanup();
+
+fail_rfkill:
+
+	return result;
+
+}
+
+static int __init msi_init(void)
+{
+	int ret;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	dmi_check_system(msi_dmi_table);
+	if (!quirks)
+		/* quirks may be NULL if no match in DMI table */
+		quirks = &quirk_load_scm_model;
+	if (force)
+		quirks = &quirk_old_ec_model;
+
+	if (!quirks->old_ec_model)
+		get_threeg_exists();
+
+	if (auto_brightness < 0 || auto_brightness > 2)
+		return -EINVAL;
+
+	/* Register backlight stuff */
+
+	if (quirks->old_ec_model ||
+	    acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		struct backlight_properties props;
+		memset(&props, 0, sizeof(struct backlight_properties));
+		props.type = BACKLIGHT_PLATFORM;
+		props.max_brightness = MSI_LCD_LEVEL_MAX - 1;
+		msibl_device = backlight_device_register("msi-laptop-bl", NULL,
+							 NULL, &msibl_ops,
+							 &props);
+		if (IS_ERR(msibl_device))
+			return PTR_ERR(msibl_device);
+	}
+
+	ret = platform_driver_register(&msipf_driver);
+	if (ret)
+		goto fail_backlight;
+
+	/* Register platform stuff */
+
+	msipf_device = platform_device_alloc("msi-laptop-pf", -1);
+	if (!msipf_device) {
+		ret = -ENOMEM;
+		goto fail_platform_driver;
+	}
+
+	ret = platform_device_add(msipf_device);
+	if (ret)
+		goto fail_device_add;
+
+	if (quirks->load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
+		ret = -EINVAL;
+		goto fail_scm_model_init;
+	}
+
+	ret = sysfs_create_group(&msipf_device->dev.kobj,
+				 &msipf_attribute_group);
+	if (ret)
+		goto fail_create_group;
+
+	if (!quirks->old_ec_model) {
+		if (threeg_exists)
+			ret = device_create_file(&msipf_device->dev,
+						&dev_attr_threeg);
+		if (ret)
+			goto fail_create_attr;
+	} else {
+		ret = sysfs_create_group(&msipf_device->dev.kobj,
+					 &msipf_old_attribute_group);
+		if (ret)
+			goto fail_create_attr;
+
+		/* Disable automatic brightness control by default because
+		 * this module was probably loaded to do brightness control in
+		 * software. */
+
+		if (auto_brightness != 2)
+			set_auto_brightness(auto_brightness);
+	}
+
+	pr_info("driver " MSI_DRIVER_VERSION " successfully loaded\n");
+
+	return 0;
+
+fail_create_attr:
+	sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
+fail_create_group:
+	if (quirks->load_scm_model) {
+		i8042_remove_filter(msi_laptop_i8042_filter);
+		cancel_delayed_work_sync(&msi_rfkill_dwork);
+		cancel_work_sync(&msi_rfkill_work);
+		rfkill_cleanup();
+	}
+fail_scm_model_init:
+	platform_device_del(msipf_device);
+fail_device_add:
+	platform_device_put(msipf_device);
+fail_platform_driver:
+	platform_driver_unregister(&msipf_driver);
+fail_backlight:
+	backlight_device_unregister(msibl_device);
+
+	return ret;
+}
+
+static void __exit msi_cleanup(void)
+{
+	if (quirks->load_scm_model) {
+		i8042_remove_filter(msi_laptop_i8042_filter);
+		msi_laptop_input_destroy();
+		cancel_delayed_work_sync(&msi_rfkill_dwork);
+		cancel_work_sync(&msi_rfkill_work);
+		rfkill_cleanup();
+	}
+
+	sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
+	if (!quirks->old_ec_model && threeg_exists)
+		device_remove_file(&msipf_device->dev, &dev_attr_threeg);
+	platform_device_unregister(msipf_device);
+	platform_driver_unregister(&msipf_driver);
+	backlight_device_unregister(msibl_device);
+
+	if (quirks->old_ec_model) {
+		/* Enable automatic brightness control again */
+		if (auto_brightness != 2)
+			set_auto_brightness(1);
+	}
+
+	pr_info("driver unloaded\n");
+}
+
+module_init(msi_init);
+module_exit(msi_cleanup);
+
+MODULE_AUTHOR("Lennart Poettering");
+MODULE_DESCRIPTION("MSI Laptop Support");
+MODULE_VERSION(MSI_DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
+MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnU270series:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnU90/U100:*");
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
new file mode 100644
index 0000000..978e6d6
--- /dev/null
+++ b/drivers/platform/x86/msi-wmi.c
@@ -0,0 +1,363 @@
+/*
+ * MSI WMI hotkeys
+ *
+ * Copyright (C) 2009 Novell <trenn@suse.de>
+ *
+ * Most stuff taken over from hp-wmi
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/acpi.h>
+#include <linux/backlight.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <acpi/video.h>
+
+MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
+MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+#define DRV_NAME "msi-wmi"
+
+#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
+#define MSIWMI_MSI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
+#define MSIWMI_WIND_EVENT_GUID "5B3CC38A-40D9-7245-8AE6-1145B751BE3F"
+
+MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID);
+MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID);
+MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID);
+
+enum msi_scancodes {
+	/* Generic MSI keys (not present on MSI Wind) */
+	MSI_KEY_BRIGHTNESSUP	= 0xD0,
+	MSI_KEY_BRIGHTNESSDOWN,
+	MSI_KEY_VOLUMEUP,
+	MSI_KEY_VOLUMEDOWN,
+	MSI_KEY_MUTE,
+	/* MSI Wind keys */
+	WIND_KEY_TOUCHPAD	= 0x08,	/* Fn+F3 touchpad toggle */
+	WIND_KEY_BLUETOOTH	= 0x56,	/* Fn+F11 Bluetooth toggle */
+	WIND_KEY_CAMERA,		/* Fn+F6 webcam toggle */
+	WIND_KEY_WLAN		= 0x5f,	/* Fn+F11 Wi-Fi toggle */
+	WIND_KEY_TURBO,			/* Fn+F10 turbo mode toggle */
+	WIND_KEY_ECO		= 0x69,	/* Fn+F10 ECO mode toggle */
+};
+static struct key_entry msi_wmi_keymap[] = {
+	{ KE_KEY, MSI_KEY_BRIGHTNESSUP,		{KEY_BRIGHTNESSUP} },
+	{ KE_KEY, MSI_KEY_BRIGHTNESSDOWN,	{KEY_BRIGHTNESSDOWN} },
+	{ KE_KEY, MSI_KEY_VOLUMEUP,		{KEY_VOLUMEUP} },
+	{ KE_KEY, MSI_KEY_VOLUMEDOWN,		{KEY_VOLUMEDOWN} },
+	{ KE_KEY, MSI_KEY_MUTE,			{KEY_MUTE} },
+
+	/* These keys work without WMI. Ignore them to avoid double keycodes */
+	{ KE_IGNORE, WIND_KEY_TOUCHPAD,		{KEY_TOUCHPAD_TOGGLE} },
+	{ KE_IGNORE, WIND_KEY_BLUETOOTH,	{KEY_BLUETOOTH} },
+	{ KE_IGNORE, WIND_KEY_CAMERA,		{KEY_CAMERA} },
+	{ KE_IGNORE, WIND_KEY_WLAN,		{KEY_WLAN} },
+
+	/* These are unknown WMI events found on MSI Wind */
+	{ KE_IGNORE, 0x00 },
+	{ KE_IGNORE, 0x62 },
+	{ KE_IGNORE, 0x63 },
+
+	/* These are MSI Wind keys that should be handled via WMI */
+	{ KE_KEY, WIND_KEY_TURBO,		{KEY_PROG1} },
+	{ KE_KEY, WIND_KEY_ECO,			{KEY_PROG2} },
+
+	{ KE_END, 0 }
+};
+
+static ktime_t last_pressed;
+
+static const struct {
+	const char *guid;
+	bool quirk_last_pressed;
+} *event_wmi, event_wmis[] = {
+	{ MSIWMI_MSI_EVENT_GUID, true },
+	{ MSIWMI_WIND_EVENT_GUID, false },
+};
+
+static struct backlight_device *backlight;
+
+static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF };
+
+static struct input_dev *msi_wmi_input_dev;
+
+static int msi_wmi_query_block(int instance, int *ret)
+{
+	acpi_status status;
+	union acpi_object *obj;
+
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output);
+
+	obj = output.pointer;
+
+	if (!obj || obj->type != ACPI_TYPE_INTEGER) {
+		if (obj) {
+			pr_err("query block returned object "
+			       "type: %d - buffer length:%d\n", obj->type,
+			       obj->type == ACPI_TYPE_BUFFER ?
+			       obj->buffer.length : 0);
+		}
+		kfree(obj);
+		return -EINVAL;
+	}
+	*ret = obj->integer.value;
+	kfree(obj);
+	return 0;
+}
+
+static int msi_wmi_set_block(int instance, int value)
+{
+	acpi_status status;
+
+	struct acpi_buffer input = { sizeof(int), &value };
+
+	pr_debug("Going to set block of instance: %d - value: %d\n",
+		 instance, value);
+
+	status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input);
+
+	return ACPI_SUCCESS(status) ? 0 : 1;
+}
+
+static int bl_get(struct backlight_device *bd)
+{
+	int level, err, ret;
+
+	/* Instance 1 is "get backlight", cmp with DSDT */
+	err = msi_wmi_query_block(1, &ret);
+	if (err) {
+		pr_err("Could not query backlight: %d\n", err);
+		return -EINVAL;
+	}
+	pr_debug("Get: Query block returned: %d\n", ret);
+	for (level = 0; level < ARRAY_SIZE(backlight_map); level++) {
+		if (backlight_map[level] == ret) {
+			pr_debug("Current backlight level: 0x%X - index: %d\n",
+				 backlight_map[level], level);
+			break;
+		}
+	}
+	if (level == ARRAY_SIZE(backlight_map)) {
+		pr_err("get: Invalid brightness value: 0x%X\n", ret);
+		return -EINVAL;
+	}
+	return level;
+}
+
+static int bl_set_status(struct backlight_device *bd)
+{
+	int bright = bd->props.brightness;
+	if (bright >= ARRAY_SIZE(backlight_map) || bright < 0)
+		return -EINVAL;
+
+	/* Instance 0 is "set backlight" */
+	return msi_wmi_set_block(0, backlight_map[bright]);
+}
+
+static const struct backlight_ops msi_backlight_ops = {
+	.get_brightness	= bl_get,
+	.update_status	= bl_set_status,
+};
+
+static void msi_wmi_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	static struct key_entry *key;
+	union acpi_object *obj;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_info("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+
+	if (obj && obj->type == ACPI_TYPE_INTEGER) {
+		int eventcode = obj->integer.value;
+		pr_debug("Eventcode: 0x%x\n", eventcode);
+		key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
+				eventcode);
+		if (!key) {
+			pr_info("Unknown key pressed - %x\n", eventcode);
+			goto msi_wmi_notify_exit;
+		}
+
+		if (event_wmi->quirk_last_pressed) {
+			ktime_t cur = ktime_get_real();
+			ktime_t diff = ktime_sub(cur, last_pressed);
+			/* Ignore event if any event happened in a 50 ms
+			   timeframe -> Key press may result in 10-20 GPEs */
+			if (ktime_to_us(diff) < 1000 * 50) {
+				pr_debug("Suppressed key event 0x%X - "
+					 "Last press was %lld us ago\n",
+					 key->code, ktime_to_us(diff));
+				goto msi_wmi_notify_exit;
+			}
+			last_pressed = cur;
+		}
+
+		if (key->type == KE_KEY &&
+		/* Brightness is served via acpi video driver */
+		(backlight ||
+		(key->code != MSI_KEY_BRIGHTNESSUP &&
+		key->code != MSI_KEY_BRIGHTNESSDOWN))) {
+			pr_debug("Send key: 0x%X - Input layer keycode: %d\n",
+				 key->code, key->keycode);
+			sparse_keymap_report_entry(msi_wmi_input_dev, key, 1,
+						   true);
+		}
+	} else
+		pr_info("Unknown event received\n");
+
+msi_wmi_notify_exit:
+	kfree(response.pointer);
+}
+
+static int __init msi_wmi_backlight_setup(void)
+{
+	int err;
+	struct backlight_properties props;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
+	backlight = backlight_device_register(DRV_NAME, NULL, NULL,
+					      &msi_backlight_ops,
+					      &props);
+	if (IS_ERR(backlight))
+		return PTR_ERR(backlight);
+
+	err = bl_get(NULL);
+	if (err < 0) {
+		backlight_device_unregister(backlight);
+		return err;
+	}
+
+	backlight->props.brightness = err;
+
+	return 0;
+}
+
+static int __init msi_wmi_input_setup(void)
+{
+	int err;
+
+	msi_wmi_input_dev = input_allocate_device();
+	if (!msi_wmi_input_dev)
+		return -ENOMEM;
+
+	msi_wmi_input_dev->name = "MSI WMI hotkeys";
+	msi_wmi_input_dev->phys = "wmi/input0";
+	msi_wmi_input_dev->id.bustype = BUS_HOST;
+
+	err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL);
+	if (err)
+		goto err_free_dev;
+
+	err = input_register_device(msi_wmi_input_dev);
+
+	if (err)
+		goto err_free_keymap;
+
+	last_pressed = ktime_set(0, 0);
+
+	return 0;
+
+err_free_keymap:
+	sparse_keymap_free(msi_wmi_input_dev);
+err_free_dev:
+	input_free_device(msi_wmi_input_dev);
+	return err;
+}
+
+static int __init msi_wmi_init(void)
+{
+	int err;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(event_wmis); i++) {
+		if (!wmi_has_guid(event_wmis[i].guid))
+			continue;
+
+		err = msi_wmi_input_setup();
+		if (err) {
+			pr_err("Unable to setup input device\n");
+			return err;
+		}
+
+		err = wmi_install_notify_handler(event_wmis[i].guid,
+			msi_wmi_notify, NULL);
+		if (ACPI_FAILURE(err)) {
+			pr_err("Unable to setup WMI notify handler\n");
+			goto err_free_input;
+		}
+
+		pr_debug("Event handler installed\n");
+		event_wmi = &event_wmis[i];
+		break;
+	}
+
+	if (wmi_has_guid(MSIWMI_BIOS_GUID) &&
+	    acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+		err = msi_wmi_backlight_setup();
+		if (err) {
+			pr_err("Unable to setup backlight device\n");
+			goto err_uninstall_handler;
+		}
+		pr_debug("Backlight device created\n");
+	}
+
+	if (!event_wmi && !backlight) {
+		pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n");
+		return -ENODEV;
+	}
+
+	return 0;
+
+err_uninstall_handler:
+	if (event_wmi)
+		wmi_remove_notify_handler(event_wmi->guid);
+err_free_input:
+	if (event_wmi) {
+		sparse_keymap_free(msi_wmi_input_dev);
+		input_unregister_device(msi_wmi_input_dev);
+	}
+	return err;
+}
+
+static void __exit msi_wmi_exit(void)
+{
+	if (event_wmi) {
+		wmi_remove_notify_handler(event_wmi->guid);
+		sparse_keymap_free(msi_wmi_input_dev);
+		input_unregister_device(msi_wmi_input_dev);
+	}
+	backlight_device_unregister(backlight);
+}
+
+module_init(msi_wmi_init);
+module_exit(msi_wmi_exit);
diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c
new file mode 100644
index 0000000..f4bad83
--- /dev/null
+++ b/drivers/platform/x86/mxm-wmi.c
@@ -0,0 +1,111 @@
+/*
+ * MXM WMI driver
+ *
+ * Copyright(C) 2010 Red Hat.
+ *
+ *  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/mxm-wmi.h>
+#include <linux/acpi.h>
+
+MODULE_AUTHOR("Dave Airlie");
+MODULE_DESCRIPTION("MXM WMI Driver");
+MODULE_LICENSE("GPL");
+
+#define MXM_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0"
+
+MODULE_ALIAS("wmi:"MXM_WMMX_GUID);
+
+#define MXM_WMMX_FUNC_MXDS 0x5344584D /* "MXDS" */
+#define MXM_WMMX_FUNC_MXMX 0x53445344 /* "MXMX" */
+
+struct mxds_args {
+	u32 func;
+	u32 args;
+	u32 xarg;
+};
+
+int mxm_wmi_call_mxds(int adapter)
+{
+	struct mxds_args args = {
+		.func = MXM_WMMX_FUNC_MXDS,
+		.args = 0,
+		.xarg = 1,
+	};
+	struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status status;
+
+	printk("calling mux switch %d\n", adapter);
+
+	status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input,
+				     &output);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	printk("mux switched %d\n", status);
+	return 0;
+			    
+}
+EXPORT_SYMBOL_GPL(mxm_wmi_call_mxds);
+
+int mxm_wmi_call_mxmx(int adapter)
+{
+	struct mxds_args args = {
+		.func = MXM_WMMX_FUNC_MXMX,
+		.args = 0,
+		.xarg = 1,
+	};
+	struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status status;
+
+	printk("calling mux switch %d\n", adapter);
+
+	status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input,
+				     &output);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	printk("mux mutex set switched %d\n", status);
+	return 0;
+			    
+}
+EXPORT_SYMBOL_GPL(mxm_wmi_call_mxmx);
+
+bool mxm_wmi_supported(void)
+{
+	bool guid_valid;
+	guid_valid = wmi_has_guid(MXM_WMMX_GUID);
+	return guid_valid;
+}
+EXPORT_SYMBOL_GPL(mxm_wmi_supported);
+
+static int __init mxm_wmi_init(void)
+{
+	return 0;
+}
+
+static void __exit mxm_wmi_exit(void)
+{
+}
+
+module_init(mxm_wmi_init);
+module_exit(mxm_wmi_exit);
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
new file mode 100644
index 0000000..3f87097
--- /dev/null
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -0,0 +1,671 @@
+/*
+ *  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 <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.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);
+static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id pcc_device_ids[] = {
+	{ "MAT0012", 0},
+	{ "MAT0013", 0},
+	{ "MAT0018", 0},
+	{ "MAT0019", 0},
+	{ "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, pcc_device_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_pcc_hotkey_resume(struct device *dev);
+#endif
+static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume);
+
+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,
+				.notify =	acpi_pcc_hotkey_notify,
+			},
+	.drv.pm =	&acpi_pcc_hotkey_pm,
+};
+
+static const struct key_entry panasonic_keymap[] = {
+	{ KE_KEY, 0, { KEY_RESERVED } },
+	{ KE_KEY, 1, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 2, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 3, { KEY_DISPLAYTOGGLE } },
+	{ KE_KEY, 4, { KEY_MUTE } },
+	{ KE_KEY, 5, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 6, { KEY_VOLUMEUP } },
+	{ KE_KEY, 7, { KEY_SLEEP } },
+	{ KE_KEY, 8, { KEY_PROG1 } }, /* Change CPU boost */
+	{ KE_KEY, 9, { KEY_BATTERY } },
+	{ KE_KEY, 10, { KEY_SUSPEND } },
+	{ KE_END, 0 }
+};
+
+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;
+};
+
+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;
+
+	status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET,
+				      &params, NULL);
+
+	return (status == AE_OK) ? 0 : -EIO;
+}
+
+static inline int acpi_pcc_get_sqty(struct acpi_device *device)
+{
+	unsigned long long s;
+	acpi_status status;
+
+	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)
+{
+	acpi_status status;
+	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *hkey = NULL;
+	int i;
+
+	status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL,
+				      &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"));
+		status = AE_ERROR;
+		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)) {
+			pcc->sinf[i] = element->integer.value;
+		} else
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					 "Invalid HKEY.SINF data\n"));
+	}
+	pcc->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))
+		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))
+		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 const 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))
+		return -EIO;
+
+	return snprintf(buf, PAGE_SIZE, "%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))
+		return -EIO;
+
+	return snprintf(buf, PAGE_SIZE, "%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))
+		return -EIO;
+
+	return snprintf(buf, PAGE_SIZE, "%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))
+		return -EIO;
+
+	return snprintf(buf, PAGE_SIZE, "%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 sleep_keydown_seen;
+static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
+{
+	struct input_dev *hotk_input_dev = pcc->input_dev;
+	int rc;
+	unsigned long long result;
+
+	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;
+	}
+
+	/* hack: some firmware sends no key down for sleep / hibernate */
+	if ((result & 0xf) == 0x7 || (result & 0xf) == 0xa) {
+		if (result & 0x80)
+			sleep_keydown_seen = 1;
+		if (!sleep_keydown_seen)
+			sparse_keymap_report_event(hotk_input_dev,
+					result & 0xf, 0x80, false);
+	}
+
+	if (!sparse_keymap_report_event(hotk_input_dev,
+					result & 0xf, result & 0x80, false))
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Unknown hotkey event: %d\n", result));
+}
+
+static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
+{
+	struct pcc_acpi *pcc = acpi_driver_data(device);
+
+	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)
+{
+	struct input_dev *input_dev;
+	int error;
+
+	input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	input_dev->name = ACPI_PCC_DRIVER_NAME;
+	input_dev->phys = ACPI_PCC_INPUT_PHYS;
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = 0x0001;
+	input_dev->id.version = 0x0100;
+
+	error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL);
+	if (error) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Unable to setup input device keymap\n"));
+		goto err_free_dev;
+	}
+
+	error = input_register_device(input_dev);
+	if (error) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Unable to register input device\n"));
+		goto err_free_keymap;
+	}
+
+	pcc->input_dev = input_dev;
+	return 0;
+
+ err_free_keymap:
+	sparse_keymap_free(input_dev);
+ err_free_dev:
+	input_free_device(input_dev);
+	return error;
+}
+
+static void acpi_pcc_destroy_input(struct pcc_acpi *pcc)
+{
+	sparse_keymap_free(pcc->input_dev);
+	input_unregister_device(pcc->input_dev);
+	/*
+	 * No need to input_free_device() since core input API refcounts
+	 * and free()s the device.
+	 */
+}
+
+/* kernel module interface */
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_pcc_hotkey_resume(struct device *dev)
+{
+	struct pcc_acpi *pcc;
+
+	if (!dev)
+		return -EINVAL;
+
+	pcc = acpi_driver_data(to_acpi_device(dev));
+	if (!pcc)
+		return -EINVAL;
+
+	ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
+			  pcc->sticky_mode));
+
+	return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
+}
+#endif
+
+static int acpi_pcc_hotkey_add(struct acpi_device *device)
+{
+	struct backlight_properties props;
+	struct pcc_acpi *pcc;
+	int num_sifr, result;
+
+	if (!device)
+		return -EINVAL;
+
+	num_sifr = acpi_pcc_get_sqty(device);
+
+	if (num_sifr < 0 || num_sifr > 255) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range"));
+		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;
+	}
+
+	if (!acpi_pcc_retrieve_biosdata(pcc)) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				 "Couldn't retrieve BIOS data\n"));
+		result = -EIO;
+		goto out_input;
+	}
+	/* initialize backlight */
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT];
+	pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
+						   &pcc_backlight_ops, &props);
+	if (IS_ERR(pcc->backlight)) {
+		result = PTR_ERR(pcc->backlight);
+		goto out_input;
+	}
+
+	/* read the initial brightness setting from the hardware */
+	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_input:
+	acpi_pcc_destroy_input(pcc);
+out_sinf:
+	kfree(pcc->sinf);
+out_hotkey:
+	kfree(pcc);
+
+	return result;
+}
+
+static int acpi_pcc_hotkey_remove(struct acpi_device *device)
+{
+	struct pcc_acpi *pcc = acpi_driver_data(device);
+
+	if (!device || !pcc)
+		return -EINVAL;
+
+	sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
+
+	backlight_device_unregister(pcc->backlight);
+
+	acpi_pcc_destroy_input(pcc);
+
+	kfree(pcc->sinf);
+	kfree(pcc);
+
+	return 0;
+}
+
+module_acpi_driver(acpi_pcc_driver);
diff --git a/drivers/platform/x86/pvpanic.c b/drivers/platform/x86/pvpanic.c
new file mode 100644
index 0000000..fd86dab
--- /dev/null
+++ b/drivers/platform/x86/pvpanic.c
@@ -0,0 +1,124 @@
+/*
+ *  pvpanic.c - pvpanic Device Support
+ *
+ *  Copyright (C) 2013 Fujitsu.
+ *
+ *  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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+
+MODULE_AUTHOR("Hu Tao <hutao@cn.fujitsu.com>");
+MODULE_DESCRIPTION("pvpanic device driver");
+MODULE_LICENSE("GPL");
+
+static int pvpanic_add(struct acpi_device *device);
+static int pvpanic_remove(struct acpi_device *device);
+
+static const struct acpi_device_id pvpanic_device_ids[] = {
+	{ "QEMU0001", 0 },
+	{ "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, pvpanic_device_ids);
+
+#define PVPANIC_PANICKED	(1 << 0)
+
+static u16 port;
+
+static struct acpi_driver pvpanic_driver = {
+	.name =		"pvpanic",
+	.class =	"QEMU",
+	.ids =		pvpanic_device_ids,
+	.ops =		{
+				.add =		pvpanic_add,
+				.remove =	pvpanic_remove,
+			},
+	.owner =	THIS_MODULE,
+};
+
+static void
+pvpanic_send_event(unsigned int event)
+{
+	outb(event, port);
+}
+
+static int
+pvpanic_panic_notify(struct notifier_block *nb, unsigned long code,
+		     void *unused)
+{
+	pvpanic_send_event(PVPANIC_PANICKED);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block pvpanic_panic_nb = {
+	.notifier_call = pvpanic_panic_notify,
+	.priority = 1, /* let this called before broken drm_fb_helper */
+};
+
+
+static acpi_status
+pvpanic_walk_resources(struct acpi_resource *res, void *context)
+{
+	switch (res->type) {
+	case ACPI_RESOURCE_TYPE_END_TAG:
+		return AE_OK;
+
+	case ACPI_RESOURCE_TYPE_IO:
+		port = res->data.io.minimum;
+		return AE_OK;
+
+	default:
+		return AE_ERROR;
+	}
+}
+
+static int pvpanic_add(struct acpi_device *device)
+{
+	int ret;
+
+	ret = acpi_bus_get_status(device);
+	if (ret < 0)
+		return ret;
+
+	if (!device->status.enabled || !device->status.functional)
+		return -ENODEV;
+
+	acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+			    pvpanic_walk_resources, NULL);
+
+	if (!port)
+		return -ENODEV;
+
+	atomic_notifier_chain_register(&panic_notifier_list,
+				       &pvpanic_panic_nb);
+
+	return 0;
+}
+
+static int pvpanic_remove(struct acpi_device *device)
+{
+
+	atomic_notifier_chain_unregister(&panic_notifier_list,
+					 &pvpanic_panic_nb);
+	return 0;
+}
+
+module_acpi_driver(pvpanic_driver);
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
new file mode 100644
index 0000000..8c146e2
--- /dev/null
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -0,0 +1,1815 @@
+/*
+ * Samsung Laptop driver
+ *
+ * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
+ * Copyright (C) 2009,2011 Novell Inc.
+ *
+ * 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 published by
+ * the Free Software Foundation.
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/fb.h>
+#include <linux/dmi.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/acpi.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/ctype.h>
+#include <linux/efi.h>
+#include <linux/suspend.h>
+#include <acpi/video.h>
+
+/*
+ * This driver is needed because a number of Samsung laptops do not hook
+ * their control settings through ACPI.  So we have to poke around in the
+ * BIOS to do things like brightness values, and "special" key controls.
+ */
+
+/*
+ * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
+ * be reserved by the BIOS (which really doesn't make much sense), we tell
+ * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
+ */
+#define MAX_BRIGHT	0x07
+
+
+#define SABI_IFACE_MAIN			0x00
+#define SABI_IFACE_SUB			0x02
+#define SABI_IFACE_COMPLETE		0x04
+#define SABI_IFACE_DATA			0x05
+
+#define WL_STATUS_WLAN			0x0
+#define WL_STATUS_BT			0x2
+
+/* Structure get/set data using sabi */
+struct sabi_data {
+	union {
+		struct {
+			u32 d0;
+			u32 d1;
+			u16 d2;
+			u8  d3;
+		};
+		u8 data[11];
+	};
+};
+
+struct sabi_header_offsets {
+	u8 port;
+	u8 re_mem;
+	u8 iface_func;
+	u8 en_mem;
+	u8 data_offset;
+	u8 data_segment;
+};
+
+struct sabi_commands {
+	/*
+	 * Brightness is 0 - 8, as described above.
+	 * Value 0 is for the BIOS to use
+	 */
+	u16 get_brightness;
+	u16 set_brightness;
+
+	/*
+	 * first byte:
+	 * 0x00 - wireless is off
+	 * 0x01 - wireless is on
+	 * second byte:
+	 * 0x02 - 3G is off
+	 * 0x03 - 3G is on
+	 * TODO, verify 3G is correct, that doesn't seem right...
+	 */
+	u16 get_wireless_button;
+	u16 set_wireless_button;
+
+	/* 0 is off, 1 is on */
+	u16 get_backlight;
+	u16 set_backlight;
+
+	/*
+	 * 0x80 or 0x00 - no action
+	 * 0x81 - recovery key pressed
+	 */
+	u16 get_recovery_mode;
+	u16 set_recovery_mode;
+
+	/*
+	 * on seclinux: 0 is low, 1 is high,
+	 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
+	 */
+	u16 get_performance_level;
+	u16 set_performance_level;
+
+	/* 0x80 is off, 0x81 is on */
+	u16 get_battery_life_extender;
+	u16 set_battery_life_extender;
+
+	/* 0x80 is off, 0x81 is on */
+	u16 get_usb_charge;
+	u16 set_usb_charge;
+
+	/* the first byte is for bluetooth and the third one is for wlan */
+	u16 get_wireless_status;
+	u16 set_wireless_status;
+
+	/* 0x80 is off, 0x81 is on */
+	u16 get_lid_handling;
+	u16 set_lid_handling;
+
+	/* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
+	u16 kbd_backlight;
+
+	/*
+	 * Tell the BIOS that Linux is running on this machine.
+	 * 81 is on, 80 is off
+	 */
+	u16 set_linux;
+};
+
+struct sabi_performance_level {
+	const char *name;
+	u16 value;
+};
+
+struct sabi_config {
+	int sabi_version;
+	const char *test_string;
+	u16 main_function;
+	const struct sabi_header_offsets header_offsets;
+	const struct sabi_commands commands;
+	const struct sabi_performance_level performance_levels[4];
+	u8 min_brightness;
+	u8 max_brightness;
+};
+
+static const struct sabi_config sabi_configs[] = {
+	{
+		/* I don't know if it is really 2, but it it is
+		 * less than 3 anyway */
+		.sabi_version = 2,
+
+		.test_string = "SECLINUX",
+
+		.main_function = 0x4c49,
+
+		.header_offsets = {
+			.port = 0x00,
+			.re_mem = 0x02,
+			.iface_func = 0x03,
+			.en_mem = 0x04,
+			.data_offset = 0x05,
+			.data_segment = 0x07,
+		},
+
+		.commands = {
+			.get_brightness = 0x00,
+			.set_brightness = 0x01,
+
+			.get_wireless_button = 0x02,
+			.set_wireless_button = 0x03,
+
+			.get_backlight = 0x04,
+			.set_backlight = 0x05,
+
+			.get_recovery_mode = 0x06,
+			.set_recovery_mode = 0x07,
+
+			.get_performance_level = 0x08,
+			.set_performance_level = 0x09,
+
+			.get_battery_life_extender = 0xFFFF,
+			.set_battery_life_extender = 0xFFFF,
+
+			.get_usb_charge = 0xFFFF,
+			.set_usb_charge = 0xFFFF,
+
+			.get_wireless_status = 0xFFFF,
+			.set_wireless_status = 0xFFFF,
+
+			.get_lid_handling = 0xFFFF,
+			.set_lid_handling = 0xFFFF,
+
+			.kbd_backlight = 0xFFFF,
+
+			.set_linux = 0x0a,
+		},
+
+		.performance_levels = {
+			{
+				.name = "silent",
+				.value = 0,
+			},
+			{
+				.name = "normal",
+				.value = 1,
+			},
+			{ },
+		},
+		.min_brightness = 1,
+		.max_brightness = 8,
+	},
+	{
+		.sabi_version = 3,
+
+		.test_string = "SwSmi@",
+
+		.main_function = 0x5843,
+
+		.header_offsets = {
+			.port = 0x00,
+			.re_mem = 0x04,
+			.iface_func = 0x02,
+			.en_mem = 0x03,
+			.data_offset = 0x05,
+			.data_segment = 0x07,
+		},
+
+		.commands = {
+			.get_brightness = 0x10,
+			.set_brightness = 0x11,
+
+			.get_wireless_button = 0x12,
+			.set_wireless_button = 0x13,
+
+			.get_backlight = 0x2d,
+			.set_backlight = 0x2e,
+
+			.get_recovery_mode = 0xff,
+			.set_recovery_mode = 0xff,
+
+			.get_performance_level = 0x31,
+			.set_performance_level = 0x32,
+
+			.get_battery_life_extender = 0x65,
+			.set_battery_life_extender = 0x66,
+
+			.get_usb_charge = 0x67,
+			.set_usb_charge = 0x68,
+
+			.get_wireless_status = 0x69,
+			.set_wireless_status = 0x6a,
+
+			.get_lid_handling = 0x6d,
+			.set_lid_handling = 0x6e,
+
+			.kbd_backlight = 0x78,
+
+			.set_linux = 0xff,
+		},
+
+		.performance_levels = {
+			{
+				.name = "normal",
+				.value = 0,
+			},
+			{
+				.name = "silent",
+				.value = 1,
+			},
+			{
+				.name = "overclock",
+				.value = 2,
+			},
+			{ },
+		},
+		.min_brightness = 0,
+		.max_brightness = 8,
+	},
+	{ },
+};
+
+/*
+ * samsung-laptop/    - debugfs root directory
+ *   f0000_segment    - dump f0000 segment
+ *   command          - current command
+ *   data             - current data
+ *   d0, d1, d2, d3   - data fields
+ *   call             - call SABI using command and data
+ *
+ * This allow to call arbitrary sabi commands wihout
+ * modifying the driver at all.
+ * For example, setting the keyboard backlight brightness to 5
+ *
+ *  echo 0x78 > command
+ *  echo 0x0582 > d0
+ *  echo 0 > d1
+ *  echo 0 > d2
+ *  echo 0 > d3
+ *  cat call
+ */
+
+struct samsung_laptop_debug {
+	struct dentry *root;
+	struct sabi_data data;
+	u16 command;
+
+	struct debugfs_blob_wrapper f0000_wrapper;
+	struct debugfs_blob_wrapper data_wrapper;
+	struct debugfs_blob_wrapper sdiag_wrapper;
+};
+
+struct samsung_laptop;
+
+struct samsung_rfkill {
+	struct samsung_laptop *samsung;
+	struct rfkill *rfkill;
+	enum rfkill_type type;
+};
+
+struct samsung_laptop {
+	const struct sabi_config *config;
+
+	void __iomem *sabi;
+	void __iomem *sabi_iface;
+	void __iomem *f0000_segment;
+
+	struct mutex sabi_mutex;
+
+	struct platform_device *platform_device;
+	struct backlight_device *backlight_device;
+
+	struct samsung_rfkill wlan;
+	struct samsung_rfkill bluetooth;
+
+	struct led_classdev kbd_led;
+	int kbd_led_wk;
+	struct workqueue_struct *led_workqueue;
+	struct work_struct kbd_led_work;
+
+	struct samsung_laptop_debug debug;
+	struct samsung_quirks *quirks;
+
+	struct notifier_block pm_nb;
+
+	bool handle_backlight;
+	bool has_stepping_quirk;
+
+	char sdiag[64];
+};
+
+struct samsung_quirks {
+	bool broken_acpi_video;
+	bool four_kbd_backlight_levels;
+	bool enable_kbd_backlight;
+	bool use_native_backlight;
+	bool lid_handling;
+};
+
+static struct samsung_quirks samsung_unknown = {};
+
+static struct samsung_quirks samsung_broken_acpi_video = {
+	.broken_acpi_video = true,
+};
+
+static struct samsung_quirks samsung_use_native_backlight = {
+	.use_native_backlight = true,
+};
+
+static struct samsung_quirks samsung_np740u3e = {
+	.four_kbd_backlight_levels = true,
+	.enable_kbd_backlight = true,
+};
+
+static struct samsung_quirks samsung_lid_handling = {
+	.lid_handling = true,
+};
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force,
+		"Disable the DMI check and forces the driver to be loaded");
+
+static bool debug;
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+static int sabi_command(struct samsung_laptop *samsung, u16 command,
+			struct sabi_data *in,
+			struct sabi_data *out)
+{
+	const struct sabi_config *config = samsung->config;
+	int ret = 0;
+	u16 port = readw(samsung->sabi + config->header_offsets.port);
+	u8 complete, iface_data;
+
+	mutex_lock(&samsung->sabi_mutex);
+
+	if (debug) {
+		if (in)
+			pr_info("SABI command:0x%04x "
+				"data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
+				command, in->d0, in->d1, in->d2, in->d3);
+		else
+			pr_info("SABI command:0x%04x", command);
+	}
+
+	/* enable memory to be able to write to it */
+	outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
+
+	/* write out the command */
+	writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
+	writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
+	writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
+	if (in) {
+		writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
+		writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
+		writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
+		writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
+	}
+	outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
+
+	/* write protect memory to make it safe */
+	outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
+
+	/* see if the command actually succeeded */
+	complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
+	iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
+
+	/* iface_data = 0xFF happens when a command is not known
+	 * so we only add a warning in debug mode since we will
+	 * probably issue some unknown command at startup to find
+	 * out which features are supported */
+	if (complete != 0xaa || (iface_data == 0xff && debug))
+		pr_warn("SABI command 0x%04x failed with"
+			" completion flag 0x%02x and interface data 0x%02x",
+			command, complete, iface_data);
+
+	if (complete != 0xaa || iface_data == 0xff) {
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	if (out) {
+		out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
+		out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
+		out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
+		out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
+	}
+
+	if (debug && out) {
+		pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
+			out->d0, out->d1, out->d2, out->d3);
+	}
+
+exit:
+	mutex_unlock(&samsung->sabi_mutex);
+	return ret;
+}
+
+/* simple wrappers usable with most commands */
+static int sabi_set_commandb(struct samsung_laptop *samsung,
+			     u16 command, u8 data)
+{
+	struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
+
+	in.data[0] = data;
+	return sabi_command(samsung, command, &in, NULL);
+}
+
+static int read_brightness(struct samsung_laptop *samsung)
+{
+	const struct sabi_config *config = samsung->config;
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data sretval;
+	int user_brightness = 0;
+	int retval;
+
+	retval = sabi_command(samsung, commands->get_brightness,
+			      NULL, &sretval);
+	if (retval)
+		return retval;
+
+	user_brightness = sretval.data[0];
+	if (user_brightness > config->min_brightness)
+		user_brightness -= config->min_brightness;
+	else
+		user_brightness = 0;
+
+	return user_brightness;
+}
+
+static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
+{
+	const struct sabi_config *config = samsung->config;
+	const struct sabi_commands *commands = &samsung->config->commands;
+	u8 user_level = user_brightness + config->min_brightness;
+
+	if (samsung->has_stepping_quirk && user_level != 0) {
+		/*
+		 * short circuit if the specified level is what's already set
+		 * to prevent the screen from flickering needlessly
+		 */
+		if (user_brightness == read_brightness(samsung))
+			return;
+
+		sabi_set_commandb(samsung, commands->set_brightness, 0);
+	}
+
+	sabi_set_commandb(samsung, commands->set_brightness, user_level);
+}
+
+static int get_brightness(struct backlight_device *bd)
+{
+	struct samsung_laptop *samsung = bl_get_data(bd);
+
+	return read_brightness(samsung);
+}
+
+static void check_for_stepping_quirk(struct samsung_laptop *samsung)
+{
+	int initial_level;
+	int check_level;
+	int orig_level = read_brightness(samsung);
+
+	/*
+	 * Some laptops exhibit the strange behaviour of stepping toward
+	 * (rather than setting) the brightness except when changing to/from
+	 * brightness level 0. This behaviour is checked for here and worked
+	 * around in set_brightness.
+	 */
+
+	if (orig_level == 0)
+		set_brightness(samsung, 1);
+
+	initial_level = read_brightness(samsung);
+
+	if (initial_level <= 2)
+		check_level = initial_level + 2;
+	else
+		check_level = initial_level - 2;
+
+	samsung->has_stepping_quirk = false;
+	set_brightness(samsung, check_level);
+
+	if (read_brightness(samsung) != check_level) {
+		samsung->has_stepping_quirk = true;
+		pr_info("enabled workaround for brightness stepping quirk\n");
+	}
+
+	set_brightness(samsung, orig_level);
+}
+
+static int update_status(struct backlight_device *bd)
+{
+	struct samsung_laptop *samsung = bl_get_data(bd);
+	const struct sabi_commands *commands = &samsung->config->commands;
+
+	set_brightness(samsung, bd->props.brightness);
+
+	if (bd->props.power == FB_BLANK_UNBLANK)
+		sabi_set_commandb(samsung, commands->set_backlight, 1);
+	else
+		sabi_set_commandb(samsung, commands->set_backlight, 0);
+
+	return 0;
+}
+
+static const struct backlight_ops backlight_ops = {
+	.get_brightness	= get_brightness,
+	.update_status	= update_status,
+};
+
+static int seclinux_rfkill_set(void *data, bool blocked)
+{
+	struct samsung_rfkill *srfkill = data;
+	struct samsung_laptop *samsung = srfkill->samsung;
+	const struct sabi_commands *commands = &samsung->config->commands;
+
+	return sabi_set_commandb(samsung, commands->set_wireless_button,
+				 !blocked);
+}
+
+static struct rfkill_ops seclinux_rfkill_ops = {
+	.set_block = seclinux_rfkill_set,
+};
+
+static int swsmi_wireless_status(struct samsung_laptop *samsung,
+				 struct sabi_data *data)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+
+	return sabi_command(samsung, commands->get_wireless_status,
+			    NULL, data);
+}
+
+static int swsmi_rfkill_set(void *priv, bool blocked)
+{
+	struct samsung_rfkill *srfkill = priv;
+	struct samsung_laptop *samsung = srfkill->samsung;
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+	int ret, i;
+
+	ret = swsmi_wireless_status(samsung, &data);
+	if (ret)
+		return ret;
+
+	/* Don't set the state for non-present devices */
+	for (i = 0; i < 4; i++)
+		if (data.data[i] == 0x02)
+			data.data[1] = 0;
+
+	if (srfkill->type == RFKILL_TYPE_WLAN)
+		data.data[WL_STATUS_WLAN] = !blocked;
+	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
+		data.data[WL_STATUS_BT] = !blocked;
+
+	return sabi_command(samsung, commands->set_wireless_status,
+			    &data, &data);
+}
+
+static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
+{
+	struct samsung_rfkill *srfkill = priv;
+	struct samsung_laptop *samsung = srfkill->samsung;
+	struct sabi_data data;
+	int ret;
+
+	ret = swsmi_wireless_status(samsung, &data);
+	if (ret)
+		return ;
+
+	if (srfkill->type == RFKILL_TYPE_WLAN)
+		ret = data.data[WL_STATUS_WLAN];
+	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
+		ret = data.data[WL_STATUS_BT];
+	else
+		return ;
+
+	rfkill_set_sw_state(rfkill, !ret);
+}
+
+static struct rfkill_ops swsmi_rfkill_ops = {
+	.set_block = swsmi_rfkill_set,
+	.query = swsmi_rfkill_query,
+};
+
+static ssize_t get_performance_level(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	const struct sabi_config *config = samsung->config;
+	const struct sabi_commands *commands = &config->commands;
+	struct sabi_data sretval;
+	int retval;
+	int i;
+
+	/* Read the state */
+	retval = sabi_command(samsung, commands->get_performance_level,
+			      NULL, &sretval);
+	if (retval)
+		return retval;
+
+	/* The logic is backwards, yeah, lots of fun... */
+	for (i = 0; config->performance_levels[i].name; ++i) {
+		if (sretval.data[0] == config->performance_levels[i].value)
+			return sprintf(buf, "%s\n", config->performance_levels[i].name);
+	}
+	return sprintf(buf, "%s\n", "unknown");
+}
+
+static ssize_t set_performance_level(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	const struct sabi_config *config = samsung->config;
+	const struct sabi_commands *commands = &config->commands;
+	int i;
+
+	if (count < 1)
+		return count;
+
+	for (i = 0; config->performance_levels[i].name; ++i) {
+		const struct sabi_performance_level *level =
+			&config->performance_levels[i];
+		if (!strncasecmp(level->name, buf, strlen(level->name))) {
+			sabi_set_commandb(samsung,
+					  commands->set_performance_level,
+					  level->value);
+			break;
+		}
+	}
+
+	if (!config->performance_levels[i].name)
+		return -EINVAL;
+
+	return count;
+}
+
+static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
+		   get_performance_level, set_performance_level);
+
+static int read_battery_life_extender(struct samsung_laptop *samsung)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+	int retval;
+
+	if (commands->get_battery_life_extender == 0xFFFF)
+		return -ENODEV;
+
+	memset(&data, 0, sizeof(data));
+	data.data[0] = 0x80;
+	retval = sabi_command(samsung, commands->get_battery_life_extender,
+			      &data, &data);
+
+	if (retval)
+		return retval;
+
+	if (data.data[0] != 0 && data.data[0] != 1)
+		return -ENODEV;
+
+	return data.data[0];
+}
+
+static int write_battery_life_extender(struct samsung_laptop *samsung,
+				       int enabled)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+
+	memset(&data, 0, sizeof(data));
+	data.data[0] = 0x80 | enabled;
+	return sabi_command(samsung, commands->set_battery_life_extender,
+			    &data, NULL);
+}
+
+static ssize_t get_battery_life_extender(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	int ret;
+
+	ret = read_battery_life_extender(samsung);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_battery_life_extender(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	int ret, value;
+
+	if (!count || kstrtoint(buf, 0, &value) != 0)
+		return -EINVAL;
+
+	ret = write_battery_life_extender(samsung, !!value);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
+		   get_battery_life_extender, set_battery_life_extender);
+
+static int read_usb_charge(struct samsung_laptop *samsung)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+	int retval;
+
+	if (commands->get_usb_charge == 0xFFFF)
+		return -ENODEV;
+
+	memset(&data, 0, sizeof(data));
+	data.data[0] = 0x80;
+	retval = sabi_command(samsung, commands->get_usb_charge,
+			      &data, &data);
+
+	if (retval)
+		return retval;
+
+	if (data.data[0] != 0 && data.data[0] != 1)
+		return -ENODEV;
+
+	return data.data[0];
+}
+
+static int write_usb_charge(struct samsung_laptop *samsung,
+			    int enabled)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+
+	memset(&data, 0, sizeof(data));
+	data.data[0] = 0x80 | enabled;
+	return sabi_command(samsung, commands->set_usb_charge,
+			    &data, NULL);
+}
+
+static ssize_t get_usb_charge(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	int ret;
+
+	ret = read_usb_charge(samsung);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_usb_charge(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	int ret, value;
+
+	if (!count || kstrtoint(buf, 0, &value) != 0)
+		return -EINVAL;
+
+	ret = write_usb_charge(samsung, !!value);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
+		   get_usb_charge, set_usb_charge);
+
+static int read_lid_handling(struct samsung_laptop *samsung)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+	int retval;
+
+	if (commands->get_lid_handling == 0xFFFF)
+		return -ENODEV;
+
+	memset(&data, 0, sizeof(data));
+	retval = sabi_command(samsung, commands->get_lid_handling,
+			      &data, &data);
+
+	if (retval)
+		return retval;
+
+	return data.data[0] & 0x1;
+}
+
+static int write_lid_handling(struct samsung_laptop *samsung,
+			      int enabled)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+
+	memset(&data, 0, sizeof(data));
+	data.data[0] = 0x80 | enabled;
+	return sabi_command(samsung, commands->set_lid_handling,
+			    &data, NULL);
+}
+
+static ssize_t get_lid_handling(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	int ret;
+
+	ret = read_lid_handling(samsung);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_lid_handling(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct samsung_laptop *samsung = dev_get_drvdata(dev);
+	int ret, value;
+
+	if (!count || kstrtoint(buf, 0, &value) != 0)
+		return -EINVAL;
+
+	ret = write_lid_handling(samsung, !!value);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO,
+		   get_lid_handling, set_lid_handling);
+
+static struct attribute *platform_attributes[] = {
+	&dev_attr_performance_level.attr,
+	&dev_attr_battery_life_extender.attr,
+	&dev_attr_usb_charge.attr,
+	&dev_attr_lid_handling.attr,
+	NULL
+};
+
+static int find_signature(void __iomem *memcheck, const char *testStr)
+{
+	int i = 0;
+	int loca;
+
+	for (loca = 0; loca < 0xffff; loca++) {
+		char temp = readb(memcheck + loca);
+
+		if (temp == testStr[i]) {
+			if (i == strlen(testStr)-1)
+				break;
+			++i;
+		} else {
+			i = 0;
+		}
+	}
+	return loca;
+}
+
+static void samsung_rfkill_exit(struct samsung_laptop *samsung)
+{
+	if (samsung->wlan.rfkill) {
+		rfkill_unregister(samsung->wlan.rfkill);
+		rfkill_destroy(samsung->wlan.rfkill);
+		samsung->wlan.rfkill = NULL;
+	}
+	if (samsung->bluetooth.rfkill) {
+		rfkill_unregister(samsung->bluetooth.rfkill);
+		rfkill_destroy(samsung->bluetooth.rfkill);
+		samsung->bluetooth.rfkill = NULL;
+	}
+}
+
+static int samsung_new_rfkill(struct samsung_laptop *samsung,
+			      struct samsung_rfkill *arfkill,
+			      const char *name, enum rfkill_type type,
+			      const struct rfkill_ops *ops,
+			      int blocked)
+{
+	struct rfkill **rfkill = &arfkill->rfkill;
+	int ret;
+
+	arfkill->type = type;
+	arfkill->samsung = samsung;
+
+	*rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
+			       type, ops, arfkill);
+
+	if (!*rfkill)
+		return -EINVAL;
+
+	if (blocked != -1)
+		rfkill_init_sw_state(*rfkill, blocked);
+
+	ret = rfkill_register(*rfkill);
+	if (ret) {
+		rfkill_destroy(*rfkill);
+		*rfkill = NULL;
+		return ret;
+	}
+	return 0;
+}
+
+static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
+{
+	return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
+				  RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
+}
+
+static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
+{
+	struct sabi_data data;
+	int ret;
+
+	ret = swsmi_wireless_status(samsung, &data);
+	if (ret) {
+		/* Some swsmi laptops use the old seclinux way to control
+		 * wireless devices */
+		if (ret == -EINVAL)
+			ret = samsung_rfkill_init_seclinux(samsung);
+		return ret;
+	}
+
+	/* 0x02 seems to mean that the device is no present/available */
+
+	if (data.data[WL_STATUS_WLAN] != 0x02)
+		ret = samsung_new_rfkill(samsung, &samsung->wlan,
+					 "samsung-wlan",
+					 RFKILL_TYPE_WLAN,
+					 &swsmi_rfkill_ops,
+					 !data.data[WL_STATUS_WLAN]);
+	if (ret)
+		goto exit;
+
+	if (data.data[WL_STATUS_BT] != 0x02)
+		ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
+					 "samsung-bluetooth",
+					 RFKILL_TYPE_BLUETOOTH,
+					 &swsmi_rfkill_ops,
+					 !data.data[WL_STATUS_BT]);
+	if (ret)
+		goto exit;
+
+exit:
+	if (ret)
+		samsung_rfkill_exit(samsung);
+
+	return ret;
+}
+
+static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
+{
+	if (samsung->config->sabi_version == 2)
+		return samsung_rfkill_init_seclinux(samsung);
+	if (samsung->config->sabi_version == 3)
+		return samsung_rfkill_init_swsmi(samsung);
+	return 0;
+}
+
+static void samsung_lid_handling_exit(struct samsung_laptop *samsung)
+{
+	if (samsung->quirks->lid_handling)
+		write_lid_handling(samsung, 0);
+}
+
+static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
+{
+	int retval = 0;
+
+	if (samsung->quirks->lid_handling)
+		retval = write_lid_handling(samsung, 1);
+
+	return retval;
+}
+
+static int kbd_backlight_enable(struct samsung_laptop *samsung)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+	int retval;
+
+	if (commands->kbd_backlight == 0xFFFF)
+		return -ENODEV;
+
+	memset(&data, 0, sizeof(data));
+	data.d0 = 0xaabb;
+	retval = sabi_command(samsung, commands->kbd_backlight,
+			      &data, &data);
+
+	if (retval)
+		return retval;
+
+	if (data.d0 != 0xccdd)
+		return -ENODEV;
+	return 0;
+}
+
+static int kbd_backlight_read(struct samsung_laptop *samsung)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+	int retval;
+
+	memset(&data, 0, sizeof(data));
+	data.data[0] = 0x81;
+	retval = sabi_command(samsung, commands->kbd_backlight,
+			      &data, &data);
+
+	if (retval)
+		return retval;
+
+	return data.data[0];
+}
+
+static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
+{
+	const struct sabi_commands *commands = &samsung->config->commands;
+	struct sabi_data data;
+
+	memset(&data, 0, sizeof(data));
+	data.d0 = 0x82 | ((brightness & 0xFF) << 8);
+	return sabi_command(samsung, commands->kbd_backlight,
+			    &data, NULL);
+}
+
+static void kbd_led_update(struct work_struct *work)
+{
+	struct samsung_laptop *samsung;
+
+	samsung = container_of(work, struct samsung_laptop, kbd_led_work);
+	kbd_backlight_write(samsung, samsung->kbd_led_wk);
+}
+
+static void kbd_led_set(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct samsung_laptop *samsung;
+
+	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
+
+	if (value > samsung->kbd_led.max_brightness)
+		value = samsung->kbd_led.max_brightness;
+	else if (value < 0)
+		value = 0;
+
+	samsung->kbd_led_wk = value;
+	queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
+}
+
+static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
+{
+	struct samsung_laptop *samsung;
+
+	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
+	return kbd_backlight_read(samsung);
+}
+
+static void samsung_leds_exit(struct samsung_laptop *samsung)
+{
+	if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
+		led_classdev_unregister(&samsung->kbd_led);
+	if (samsung->led_workqueue)
+		destroy_workqueue(samsung->led_workqueue);
+}
+
+static int __init samsung_leds_init(struct samsung_laptop *samsung)
+{
+	int ret = 0;
+
+	samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
+	if (!samsung->led_workqueue)
+		return -ENOMEM;
+
+	if (kbd_backlight_enable(samsung) >= 0) {
+		INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
+
+		samsung->kbd_led.name = "samsung::kbd_backlight";
+		samsung->kbd_led.brightness_set = kbd_led_set;
+		samsung->kbd_led.brightness_get = kbd_led_get;
+		samsung->kbd_led.max_brightness = 8;
+		if (samsung->quirks->four_kbd_backlight_levels)
+			samsung->kbd_led.max_brightness = 4;
+
+		ret = led_classdev_register(&samsung->platform_device->dev,
+					   &samsung->kbd_led);
+	}
+
+	if (ret)
+		samsung_leds_exit(samsung);
+
+	return ret;
+}
+
+static void samsung_backlight_exit(struct samsung_laptop *samsung)
+{
+	if (samsung->backlight_device) {
+		backlight_device_unregister(samsung->backlight_device);
+		samsung->backlight_device = NULL;
+	}
+}
+
+static int __init samsung_backlight_init(struct samsung_laptop *samsung)
+{
+	struct backlight_device *bd;
+	struct backlight_properties props;
+
+	if (!samsung->handle_backlight)
+		return 0;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = samsung->config->max_brightness -
+		samsung->config->min_brightness;
+
+	bd = backlight_device_register("samsung",
+				       &samsung->platform_device->dev,
+				       samsung, &backlight_ops,
+				       &props);
+	if (IS_ERR(bd))
+		return PTR_ERR(bd);
+
+	samsung->backlight_device = bd;
+	samsung->backlight_device->props.brightness = read_brightness(samsung);
+	samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(samsung->backlight_device);
+
+	return 0;
+}
+
+static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
+					struct attribute *attr, int idx)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct platform_device *pdev = to_platform_device(dev);
+	struct samsung_laptop *samsung = platform_get_drvdata(pdev);
+	bool ok = true;
+
+	if (attr == &dev_attr_performance_level.attr)
+		ok = !!samsung->config->performance_levels[0].name;
+	if (attr == &dev_attr_battery_life_extender.attr)
+		ok = !!(read_battery_life_extender(samsung) >= 0);
+	if (attr == &dev_attr_usb_charge.attr)
+		ok = !!(read_usb_charge(samsung) >= 0);
+	if (attr == &dev_attr_lid_handling.attr)
+		ok = !!(read_lid_handling(samsung) >= 0);
+
+	return ok ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+	.is_visible = samsung_sysfs_is_visible,
+	.attrs = platform_attributes
+};
+
+static void samsung_sysfs_exit(struct samsung_laptop *samsung)
+{
+	struct platform_device *device = samsung->platform_device;
+
+	sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
+{
+	struct platform_device *device = samsung->platform_device;
+
+	return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
+
+}
+
+static int show_call(struct seq_file *m, void *data)
+{
+	struct samsung_laptop *samsung = m->private;
+	struct sabi_data *sdata = &samsung->debug.data;
+	int ret;
+
+	seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
+		   samsung->debug.command,
+		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
+
+	ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
+
+	if (ret) {
+		seq_printf(m, "SABI command 0x%04x failed\n",
+			   samsung->debug.command);
+		return ret;
+	}
+
+	seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
+		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
+	return 0;
+}
+
+static int samsung_debugfs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, show_call, inode->i_private);
+}
+
+static const struct file_operations samsung_laptop_call_io_ops = {
+	.owner = THIS_MODULE,
+	.open = samsung_debugfs_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static void samsung_debugfs_exit(struct samsung_laptop *samsung)
+{
+	debugfs_remove_recursive(samsung->debug.root);
+}
+
+static int samsung_debugfs_init(struct samsung_laptop *samsung)
+{
+	struct dentry *dent;
+
+	samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
+	if (!samsung->debug.root) {
+		pr_err("failed to create debugfs directory");
+		goto error_debugfs;
+	}
+
+	samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
+	samsung->debug.f0000_wrapper.size = 0xffff;
+
+	samsung->debug.data_wrapper.data = &samsung->debug.data;
+	samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
+
+	samsung->debug.sdiag_wrapper.data = samsung->sdiag;
+	samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
+
+	dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
+				  samsung->debug.root, &samsung->debug.command);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
+				  &samsung->debug.data.d0);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
+				  &samsung->debug.data.d1);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
+				  &samsung->debug.data.d2);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
+				 &samsung->debug.data.d3);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
+				   samsung->debug.root,
+				   &samsung->debug.data_wrapper);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
+				   samsung->debug.root,
+				   &samsung->debug.f0000_wrapper);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
+				   samsung->debug.root, samsung,
+				   &samsung_laptop_call_io_ops);
+	if (!dent)
+		goto error_debugfs;
+
+	dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR,
+				   samsung->debug.root,
+				   &samsung->debug.sdiag_wrapper);
+	if (!dent)
+		goto error_debugfs;
+
+	return 0;
+
+error_debugfs:
+	samsung_debugfs_exit(samsung);
+	return -ENOMEM;
+}
+
+static void samsung_sabi_exit(struct samsung_laptop *samsung)
+{
+	const struct sabi_config *config = samsung->config;
+
+	/* Turn off "Linux" mode in the BIOS */
+	if (config && config->commands.set_linux != 0xff)
+		sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
+
+	if (samsung->sabi_iface) {
+		iounmap(samsung->sabi_iface);
+		samsung->sabi_iface = NULL;
+	}
+	if (samsung->f0000_segment) {
+		iounmap(samsung->f0000_segment);
+		samsung->f0000_segment = NULL;
+	}
+
+	samsung->config = NULL;
+}
+
+static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
+				      unsigned int ifaceP)
+{
+	const struct sabi_config *config = samsung->config;
+
+	printk(KERN_DEBUG "This computer supports SABI==%x\n",
+	       loca + 0xf0000 - 6);
+
+	printk(KERN_DEBUG "SABI header:\n");
+	printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
+	       readw(samsung->sabi + config->header_offsets.port));
+	printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
+	       readb(samsung->sabi + config->header_offsets.iface_func));
+	printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
+	       readb(samsung->sabi + config->header_offsets.en_mem));
+	printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
+	       readb(samsung->sabi + config->header_offsets.re_mem));
+	printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
+	       readw(samsung->sabi + config->header_offsets.data_offset));
+	printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
+	       readw(samsung->sabi + config->header_offsets.data_segment));
+
+	printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
+}
+
+static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
+{
+	int loca = find_signature(samsung->f0000_segment, "SDiaG@");
+	int i;
+
+	if (loca == 0xffff)
+		return ;
+
+	/* Example:
+	 * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
+	 *
+	 * Product name: 90X3A
+	 * BIOS Version: 07HL
+	 */
+	loca += 1;
+	for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
+		char temp = readb(samsung->f0000_segment + loca);
+
+		if (isalnum(temp) || temp == '/' || temp == '-')
+			samsung->sdiag[i++] = temp;
+		else
+			break ;
+	}
+
+	if (debug && samsung->sdiag[0])
+		pr_info("sdiag: %s", samsung->sdiag);
+}
+
+static int __init samsung_sabi_init(struct samsung_laptop *samsung)
+{
+	const struct sabi_config *config = NULL;
+	const struct sabi_commands *commands;
+	unsigned int ifaceP;
+	int ret = 0;
+	int i;
+	int loca;
+
+	samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
+	if (!samsung->f0000_segment) {
+		if (debug || force)
+			pr_err("Can't map the segment at 0xf0000\n");
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	samsung_sabi_diag(samsung);
+
+	/* Try to find one of the signatures in memory to find the header */
+	for (i = 0; sabi_configs[i].test_string != NULL; ++i) {
+		samsung->config = &sabi_configs[i];
+		loca = find_signature(samsung->f0000_segment,
+				      samsung->config->test_string);
+		if (loca != 0xffff)
+			break;
+	}
+
+	if (loca == 0xffff) {
+		if (debug || force)
+			pr_err("This computer does not support SABI\n");
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	config = samsung->config;
+	commands = &config->commands;
+
+	/* point to the SMI port Number */
+	loca += 1;
+	samsung->sabi = (samsung->f0000_segment + loca);
+
+	/* Get a pointer to the SABI Interface */
+	ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
+	ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
+
+	if (debug)
+		samsung_sabi_infos(samsung, loca, ifaceP);
+
+	samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
+	if (!samsung->sabi_iface) {
+		pr_err("Can't remap %x\n", ifaceP);
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	/* Turn on "Linux" mode in the BIOS */
+	if (commands->set_linux != 0xff) {
+		int retval = sabi_set_commandb(samsung,
+					       commands->set_linux, 0x81);
+		if (retval) {
+			pr_warn("Linux mode was not set!\n");
+			ret = -ENODEV;
+			goto exit;
+		}
+	}
+
+	/* Check for stepping quirk */
+	if (samsung->handle_backlight)
+		check_for_stepping_quirk(samsung);
+
+	pr_info("detected SABI interface: %s\n",
+		samsung->config->test_string);
+
+exit:
+	if (ret)
+		samsung_sabi_exit(samsung);
+
+	return ret;
+}
+
+static void samsung_platform_exit(struct samsung_laptop *samsung)
+{
+	if (samsung->platform_device) {
+		platform_device_unregister(samsung->platform_device);
+		samsung->platform_device = NULL;
+	}
+}
+
+static int samsung_pm_notification(struct notifier_block *nb,
+				   unsigned long val, void *ptr)
+{
+	struct samsung_laptop *samsung;
+
+	samsung = container_of(nb, struct samsung_laptop, pm_nb);
+	if (val == PM_POST_HIBERNATION &&
+	    samsung->quirks->enable_kbd_backlight)
+		kbd_backlight_enable(samsung);
+
+	if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling)
+		write_lid_handling(samsung, 1);
+
+	return 0;
+}
+
+static int __init samsung_platform_init(struct samsung_laptop *samsung)
+{
+	struct platform_device *pdev;
+
+	pdev = platform_device_register_simple("samsung", -1, NULL, 0);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	samsung->platform_device = pdev;
+	platform_set_drvdata(samsung->platform_device, samsung);
+	return 0;
+}
+
+static struct samsung_quirks *quirks;
+
+static int __init samsung_dmi_matched(const struct dmi_system_id *d)
+{
+	quirks = d->driver_data;
+	return 0;
+}
+
+static struct dmi_system_id __initdata samsung_dmi_table[] = {
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+					"SAMSUNG ELECTRONICS CO., LTD."),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
+		},
+	},
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+					"SAMSUNG ELECTRONICS CO., LTD."),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
+		},
+	},
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+					"SAMSUNG ELECTRONICS CO., LTD."),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
+		},
+	},
+	{
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR,
+					"SAMSUNG ELECTRONICS CO., LTD."),
+			DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
+		},
+	},
+	/* DMI ids for laptops with bad Chassis Type */
+	{
+	  .ident = "R40/R41",
+	  .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
+		DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
+		},
+	},
+	/* Specific DMI ids for laptop with quirks */
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "N150P",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
+		DMI_MATCH(DMI_BOARD_NAME, "N150P"),
+		},
+	 .driver_data = &samsung_use_native_backlight,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "N145P/N250P/N260P",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
+		DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
+		},
+	 .driver_data = &samsung_use_native_backlight,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "N150/N210/N220",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
+		DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
+		},
+	 .driver_data = &samsung_broken_acpi_video,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "NF110/NF210/NF310",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
+		DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
+		},
+	 .driver_data = &samsung_broken_acpi_video,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "X360",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
+		DMI_MATCH(DMI_BOARD_NAME, "X360"),
+		},
+	 .driver_data = &samsung_broken_acpi_video,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "N250P",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
+		DMI_MATCH(DMI_BOARD_NAME, "N250P"),
+		},
+	 .driver_data = &samsung_use_native_backlight,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "NC210",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
+		DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
+		},
+	 .driver_data = &samsung_broken_acpi_video,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "730U3E/740U3E",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
+		},
+	 .driver_data = &samsung_np740u3e,
+	},
+	{
+	 .callback = samsung_dmi_matched,
+	 .ident = "300V3Z/300V4Z/300V5Z",
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"),
+		},
+	 .driver_data = &samsung_lid_handling,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
+
+static struct platform_device *samsung_platform_device;
+
+static int __init samsung_init(void)
+{
+	struct samsung_laptop *samsung;
+	int ret;
+
+	if (efi_enabled(EFI_BOOT))
+		return -ENODEV;
+
+	quirks = &samsung_unknown;
+	if (!force && !dmi_check_system(samsung_dmi_table))
+		return -ENODEV;
+
+	samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
+	if (!samsung)
+		return -ENOMEM;
+
+	mutex_init(&samsung->sabi_mutex);
+	samsung->handle_backlight = true;
+	samsung->quirks = quirks;
+
+#ifdef CONFIG_ACPI
+	if (samsung->quirks->broken_acpi_video)
+		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+	if (samsung->quirks->use_native_backlight)
+		acpi_video_set_dmi_backlight_type(acpi_backlight_native);
+
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
+		samsung->handle_backlight = false;
+#endif
+
+	ret = samsung_platform_init(samsung);
+	if (ret)
+		goto error_platform;
+
+	ret = samsung_sabi_init(samsung);
+	if (ret)
+		goto error_sabi;
+
+	ret = samsung_sysfs_init(samsung);
+	if (ret)
+		goto error_sysfs;
+
+	ret = samsung_backlight_init(samsung);
+	if (ret)
+		goto error_backlight;
+
+	ret = samsung_rfkill_init(samsung);
+	if (ret)
+		goto error_rfkill;
+
+	ret = samsung_leds_init(samsung);
+	if (ret)
+		goto error_leds;
+
+	ret = samsung_lid_handling_init(samsung);
+	if (ret)
+		goto error_lid_handling;
+
+	ret = samsung_debugfs_init(samsung);
+	if (ret)
+		goto error_debugfs;
+
+	samsung->pm_nb.notifier_call = samsung_pm_notification;
+	register_pm_notifier(&samsung->pm_nb);
+
+	samsung_platform_device = samsung->platform_device;
+	return ret;
+
+error_debugfs:
+	samsung_lid_handling_exit(samsung);
+error_lid_handling:
+	samsung_leds_exit(samsung);
+error_leds:
+	samsung_rfkill_exit(samsung);
+error_rfkill:
+	samsung_backlight_exit(samsung);
+error_backlight:
+	samsung_sysfs_exit(samsung);
+error_sysfs:
+	samsung_sabi_exit(samsung);
+error_sabi:
+	samsung_platform_exit(samsung);
+error_platform:
+	kfree(samsung);
+	return ret;
+}
+
+static void __exit samsung_exit(void)
+{
+	struct samsung_laptop *samsung;
+
+	samsung = platform_get_drvdata(samsung_platform_device);
+	unregister_pm_notifier(&samsung->pm_nb);
+
+	samsung_debugfs_exit(samsung);
+	samsung_lid_handling_exit(samsung);
+	samsung_leds_exit(samsung);
+	samsung_rfkill_exit(samsung);
+	samsung_backlight_exit(samsung);
+	samsung_sysfs_exit(samsung);
+	samsung_sabi_exit(samsung);
+	samsung_platform_exit(samsung);
+
+	kfree(samsung);
+	samsung_platform_device = NULL;
+}
+
+module_init(samsung_init);
+module_exit(samsung_exit);
+
+MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
+MODULE_DESCRIPTION("Samsung Backlight driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/samsung-q10.c b/drivers/platform/x86/samsung-q10.c
new file mode 100644
index 0000000..e6aac72
--- /dev/null
+++ b/drivers/platform/x86/samsung-q10.c
@@ -0,0 +1,163 @@
+/*
+ *  Driver for Samsung Q10 and related laptops: controls the backlight
+ *
+ *  Copyright (c) 2011 Frederick van der Wyck <fvanderwyck@gmail.com>
+ *
+ *  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
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+
+#define SAMSUNGQ10_BL_MAX_INTENSITY 7
+
+static acpi_handle ec_handle;
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force,
+		"Disable the DMI check and force the driver to be loaded");
+
+static int samsungq10_bl_set_intensity(struct backlight_device *bd)
+{
+
+	acpi_status status;
+	int i;
+
+	for (i = 0; i < SAMSUNGQ10_BL_MAX_INTENSITY; i++) {
+		status = acpi_evaluate_object(ec_handle, "_Q63", NULL, NULL);
+		if (ACPI_FAILURE(status))
+			return -EIO;
+	}
+	for (i = 0; i < bd->props.brightness; i++) {
+		status = acpi_evaluate_object(ec_handle, "_Q64", NULL, NULL);
+		if (ACPI_FAILURE(status))
+			return -EIO;
+	}
+
+	return 0;
+}
+
+static const struct backlight_ops samsungq10_bl_ops = {
+	.update_status	= samsungq10_bl_set_intensity,
+};
+
+static int samsungq10_probe(struct platform_device *pdev)
+{
+
+	struct backlight_properties props;
+	struct backlight_device *bd;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = SAMSUNGQ10_BL_MAX_INTENSITY;
+	bd = backlight_device_register("samsung", &pdev->dev, NULL,
+				       &samsungq10_bl_ops, &props);
+	if (IS_ERR(bd))
+		return PTR_ERR(bd);
+
+	platform_set_drvdata(pdev, bd);
+
+	return 0;
+}
+
+static int samsungq10_remove(struct platform_device *pdev)
+{
+
+	struct backlight_device *bd = platform_get_drvdata(pdev);
+
+	backlight_device_unregister(bd);
+
+	return 0;
+}
+
+static struct platform_driver samsungq10_driver = {
+	.driver		= {
+		.name	= KBUILD_MODNAME,
+	},
+	.probe		= samsungq10_probe,
+	.remove		= samsungq10_remove,
+};
+
+static struct platform_device *samsungq10_device;
+
+static int __init dmi_check_callback(const struct dmi_system_id *id)
+{
+	printk(KERN_INFO KBUILD_MODNAME ": found model '%s'\n", id->ident);
+	return 1;
+}
+
+static struct dmi_system_id __initdata samsungq10_dmi_table[] = {
+	{
+		.ident = "Samsung Q10",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Samsung"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "SQ10"),
+		},
+		.callback = dmi_check_callback,
+	},
+	{
+		.ident = "Samsung Q20",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG Electronics"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "SENS Q20"),
+		},
+		.callback = dmi_check_callback,
+	},
+	{
+		.ident = "Samsung Q25",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG Electronics"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "NQ25"),
+		},
+		.callback = dmi_check_callback,
+	},
+	{
+		.ident = "Dell Latitude X200",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "X200"),
+		},
+		.callback = dmi_check_callback,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(dmi, samsungq10_dmi_table);
+
+static int __init samsungq10_init(void)
+{
+	if (!force && !dmi_check_system(samsungq10_dmi_table))
+		return -ENODEV;
+
+	ec_handle = ec_get_handle();
+
+	if (!ec_handle)
+		return -ENODEV;
+
+	samsungq10_device = platform_create_bundle(&samsungq10_driver,
+						   samsungq10_probe,
+						   NULL, 0, NULL, 0);
+
+	return PTR_ERR_OR_ZERO(samsungq10_device);
+}
+
+static void __exit samsungq10_exit(void)
+{
+	platform_device_unregister(samsungq10_device);
+	platform_driver_unregister(&samsungq10_driver);
+}
+
+module_init(samsungq10_init);
+module_exit(samsungq10_exit);
+
+MODULE_AUTHOR("Frederick van der Wyck <fvanderwyck@gmail.com>");
+MODULE_DESCRIPTION("Samsung Q10 Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
new file mode 100644
index 0000000..f73c295
--- /dev/null
+++ b/drivers/platform/x86/sony-laptop.c
@@ -0,0 +1,4909 @@
+/*
+ * ACPI Sony Notebook Control Driver (SNC and SPIC)
+ *
+ * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
+ * Copyright (C) 2007-2009 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.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 <linux/slab.h>
+#include <linux/sonypi.h>
+#include <linux/sony-laptop.h>
+#include <linux/rfkill.h>
+#ifdef CONFIG_SONYPI_COMPAT
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#endif
+#include <asm/uaccess.h>
+#include <acpi/video.h>
+
+#define dprintk(fmt, ...)			\
+do {						\
+	if (debug)				\
+		pr_warn(fmt, ##__VA_ARGS__);	\
+} while (0)
+
+#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");
+
+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
+
+static int kbd_backlight = -1;
+module_param(kbd_backlight, int, 0444);
+MODULE_PARM_DESC(kbd_backlight,
+		 "set this to 0 to disable keyboard backlight, "
+		 "1 to enable it with automatic control and 2 to have it always "
+		 "on (default: no change from current value)");
+
+static int kbd_backlight_timeout = -1;
+module_param(kbd_backlight_timeout, int, 0444);
+MODULE_PARM_DESC(kbd_backlight_timeout,
+		 "meaningful values vary from 0 to 3 and their meaning depends "
+		 "on the model (default: no change from current value)");
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_thermal_resume(void);
+#endif
+static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
+		unsigned int handle);
+static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd,
+		unsigned int handle);
+
+static int sony_nc_battery_care_setup(struct platform_device *pd,
+		unsigned int handle);
+static void sony_nc_battery_care_cleanup(struct platform_device *pd);
+
+static int sony_nc_thermal_setup(struct platform_device *pd);
+static void sony_nc_thermal_cleanup(struct platform_device *pd);
+
+static int sony_nc_lid_resume_setup(struct platform_device *pd,
+				    unsigned int handle);
+static void sony_nc_lid_resume_cleanup(struct platform_device *pd);
+
+static int sony_nc_gfx_switch_setup(struct platform_device *pd,
+		unsigned int handle);
+static void sony_nc_gfx_switch_cleanup(struct platform_device *pd);
+static int __sony_nc_gfx_switch_status_get(void);
+
+static int sony_nc_highspeed_charging_setup(struct platform_device *pd);
+static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd);
+
+static int sony_nc_lowbatt_setup(struct platform_device *pd);
+static void sony_nc_lowbatt_cleanup(struct platform_device *pd);
+
+static int sony_nc_fanspeed_setup(struct platform_device *pd);
+static void sony_nc_fanspeed_cleanup(struct platform_device *pd);
+
+static int sony_nc_usb_charge_setup(struct platform_device *pd);
+static void sony_nc_usb_charge_cleanup(struct platform_device *pd);
+
+static int sony_nc_panelid_setup(struct platform_device *pd);
+static void sony_nc_panelid_cleanup(struct platform_device *pd);
+
+static int sony_nc_smart_conn_setup(struct platform_device *pd);
+static void sony_nc_smart_conn_cleanup(struct platform_device *pd);
+
+static int sony_nc_touchpad_setup(struct platform_device *pd,
+				  unsigned int handle);
+static void sony_nc_touchpad_cleanup(struct platform_device *pd);
+
+enum sony_nc_rfkill {
+	SONY_WIFI,
+	SONY_BLUETOOTH,
+	SONY_WWAN,
+	SONY_WIMAX,
+	N_SONY_RFKILL,
+};
+
+static int sony_rfkill_handle;
+static struct rfkill *sony_rfkill_devices[N_SONY_RFKILL];
+static int sony_rfkill_address[N_SONY_RFKILL] = {0x300, 0x500, 0x700, 0x900};
+static int sony_nc_rfkill_setup(struct acpi_device *device,
+		unsigned int handle);
+static void sony_nc_rfkill_cleanup(void);
+static void sony_nc_rfkill_update(void);
+
+/*********** 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 timer_list	release_key_timer;
+};
+
+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 */
+	51,	/* 64 SONYPI_EVENT_CD_EJECT_PRESSED */
+	52,	/* 65 SONYPI_EVENT_MODEKEY_PRESSED */
+	53,	/* 66 SONYPI_EVENT_PKEY_P4 */
+	54,	/* 67 SONYPI_EVENT_PKEY_P5 */
+	55,	/* 68 SONYPI_EVENT_SETTINGKEY_PRESSED */
+	56,	/* 69 SONYPI_EVENT_VOLUME_INC_PRESSED */
+	57,	/* 70 SONYPI_EVENT_VOLUME_DEC_PRESSED */
+	-1,	/* 71 SONYPI_EVENT_BRIGHTNESS_PRESSED */
+	58,	/* 72 SONYPI_EVENT_MEDIA_PRESSED */
+	59,	/* 72 SONYPI_EVENT_VENDOR_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_1,	/* 17 SONYPI_EVENT_FNKEY_1 */
+	KEY_FN_2,	/* 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 */
+	KEY_EJECTCD,	/* 51 SONYPI_EVENT_CD_EJECT_PRESSED */
+	KEY_F13,	/* 52 SONYPI_EVENT_MODEKEY_PRESSED */
+	KEY_PROG4,	/* 53 SONYPI_EVENT_PKEY_P4 */
+	KEY_F14,	/* 54 SONYPI_EVENT_PKEY_P5 */
+	KEY_F15,	/* 55 SONYPI_EVENT_SETTINGKEY_PRESSED */
+	KEY_VOLUMEUP,	/* 56 SONYPI_EVENT_VOLUME_INC_PRESSED */
+	KEY_VOLUMEDOWN,	/* 57 SONYPI_EVENT_VOLUME_DEC_PRESSED */
+	KEY_MEDIA,	/* 58 SONYPI_EVENT_MEDIA_PRESSED */
+	KEY_VENDOR,	/* 59 SONYPI_EVENT_VENDOR_PRESSED */
+};
+
+/* release buttons after a short delay if pressed */
+static void do_sony_laptop_release_key(unsigned long unused)
+{
+	struct sony_laptop_keypress kp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sony_laptop_input.fifo_lock, flags);
+
+	if (kfifo_out(&sony_laptop_input.fifo,
+		      (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) {
+		input_report_key(kp.dev, kp.key, 0);
+		input_sync(kp.dev);
+	}
+
+	/* If there is something in the fifo schedule next release. */
+	if (kfifo_len(&sony_laptop_input.fifo) != 0)
+		mod_timer(&sony_laptop_input.release_key_timer,
+			  jiffies + msecs_to_jiffies(10));
+
+	spin_unlock_irqrestore(&sony_laptop_input.fifo_lock, flags);
+}
+
+/* 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 };
+	int scancode = -1;
+
+	if (event == SONYPI_EVENT_FNKEY_RELEASED ||
+			event == SONYPI_EVENT_ANYBUTTON_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 ((scancode = sony_laptop_input_index[event]) != -1) {
+			kp.key = sony_laptop_input_keycode_map[scancode];
+			if (kp.key != KEY_UNKNOWN)
+				kp.dev = key_dev;
+		}
+		break;
+	}
+
+	if (kp.dev) {
+		/* if we have a scancode we emit it so we can always
+		    remap the key */
+		if (scancode != -1)
+			input_event(kp.dev, EV_MSC, MSC_SCAN, scancode);
+		input_report_key(kp.dev, kp.key, 1);
+		input_sync(kp.dev);
+
+		/* schedule key release */
+		kfifo_in_locked(&sony_laptop_input.fifo,
+				(unsigned char *)&kp, sizeof(kp),
+				&sony_laptop_input.fifo_lock);
+		mod_timer(&sony_laptop_input.release_key_timer,
+			  jiffies + msecs_to_jiffies(10));
+	} 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);
+	error = kfifo_alloc(&sony_laptop_input.fifo,
+			    SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
+	if (error) {
+		pr_err("kfifo_alloc failed\n");
+		goto err_dec_users;
+	}
+
+	setup_timer(&sony_laptop_input.release_key_timer,
+		    do_sony_laptop_release_key, 0);
+
+	/* input keys */
+	key_dev = input_allocate_device();
+	if (!key_dev) {
+		error = -ENOMEM;
+		goto err_free_kfifo;
+	}
+
+	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 */
+	input_set_capability(key_dev, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, key_dev->evbit);
+	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++)
+		__set_bit(sony_laptop_input_keycode_map[i], key_dev->keybit);
+	__clear_bit(KEY_RESERVED, key_dev->keybit);
+
+	error = input_register_device(key_dev);
+	if (error)
+		goto err_free_keydev;
+
+	sony_laptop_input.key_dev = key_dev;
+
+	/* jogdial */
+	jog_dev = input_allocate_device();
+	if (!jog_dev) {
+		error = -ENOMEM;
+		goto err_unregister_keydev;
+	}
+
+	jog_dev->name = "Sony Vaio Jogdial";
+	jog_dev->id.bustype = BUS_ISA;
+	jog_dev->id.vendor = PCI_VENDOR_ID_SONY;
+	jog_dev->dev.parent = &acpi_device->dev;
+
+	input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE);
+	input_set_capability(jog_dev, EV_REL, 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_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)
+{
+	struct sony_laptop_keypress kp = { NULL };
+
+	/* Cleanup only after the last user has gone */
+	if (!atomic_dec_and_test(&sony_laptop_input.users))
+		return;
+
+	del_timer_sync(&sony_laptop_input.release_key_timer);
+
+	/*
+	 * Generate key-up events for remaining keys. Note that we don't
+	 * need locking since nobody is adding new events to the kfifo.
+	 */
+	while (kfifo_out(&sony_laptop_input.fifo,
+			 (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) {
+		input_report_key(kp.dev, kp.key, 0);
+		input_sync(kp.dev);
+	}
+
+	/* 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;
+	}
+
+	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",
+		   }
+};
+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_unregister(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 attribute */
+};
+
+#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
+ * all useful calls into SNC methods take one or zero parameters and return
+ * integers or arrays.
+ */
+static union acpi_object *__call_snc_method(acpi_handle handle, char *method,
+		u64 *value)
+{
+	union acpi_object *result = NULL;
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status status;
+
+	if (value) {
+		struct acpi_object_list params;
+		union acpi_object in;
+		in.type = ACPI_TYPE_INTEGER;
+		in.integer.value = *value;
+		params.count = 1;
+		params.pointer = &in;
+		status = acpi_evaluate_object(handle, method, &params, &output);
+		dprintk("__call_snc_method: [%s:0x%.8x%.8x]\n", method,
+				(unsigned int)(*value >> 32),
+				(unsigned int)*value & 0xffffffff);
+	} else {
+		status = acpi_evaluate_object(handle, method, NULL, &output);
+		dprintk("__call_snc_method: [%s]\n", method);
+	}
+
+	if (ACPI_FAILURE(status)) {
+		pr_err("Failed to evaluate [%s]\n", method);
+		return NULL;
+	}
+
+	result = (union acpi_object *) output.pointer;
+	if (!result)
+		dprintk("No return object [%s]\n", method);
+
+	return result;
+}
+
+static int sony_nc_int_call(acpi_handle handle, char *name, int *value,
+		int *result)
+{
+	union acpi_object *object = NULL;
+	if (value) {
+		u64 v = *value;
+		object = __call_snc_method(handle, name, &v);
+	} else
+		object = __call_snc_method(handle, name, NULL);
+
+	if (!object)
+		return -EINVAL;
+
+	if (object->type != ACPI_TYPE_INTEGER) {
+		pr_warn("Invalid acpi_object: expected 0x%x got 0x%x\n",
+				ACPI_TYPE_INTEGER, object->type);
+		kfree(object);
+		return -EINVAL;
+	}
+
+	if (result)
+		*result = object->integer.value;
+
+	kfree(object);
+	return 0;
+}
+
+#define MIN(a, b)	(a > b ? b : a)
+static int sony_nc_buffer_call(acpi_handle handle, char *name, u64 *value,
+		void *buffer, size_t buflen)
+{
+	int ret = 0;
+	size_t len;
+	union acpi_object *object = __call_snc_method(handle, name, value);
+
+	if (!object)
+		return -EINVAL;
+
+	if (object->type == ACPI_TYPE_BUFFER) {
+		len = MIN(buflen, object->buffer.length);
+		memcpy(buffer, object->buffer.pointer, len);
+
+	} else if (object->type == ACPI_TYPE_INTEGER) {
+		len = MIN(buflen, sizeof(object->integer.value));
+		memcpy(buffer, &object->integer.value, len);
+
+	} else {
+		pr_warn("Invalid acpi_object: expected 0x%x got 0x%x\n",
+				ACPI_TYPE_BUFFER, object->type);
+		ret = -EINVAL;
+	}
+
+	kfree(object);
+	return ret;
+}
+
+struct sony_nc_handles {
+	u16 cap[0x10];
+	struct device_attribute devattr;
+};
+
+static struct sony_nc_handles *handles;
+
+static ssize_t sony_nc_handles_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	ssize_t len = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+		len += snprintf(buffer + len, PAGE_SIZE - len, "0x%.4x ",
+				handles->cap[i]);
+	}
+	len += snprintf(buffer + len, PAGE_SIZE - len, "\n");
+
+	return len;
+}
+
+static int sony_nc_handles_setup(struct platform_device *pd)
+{
+	int i, r, result, arg;
+
+	handles = kzalloc(sizeof(*handles), GFP_KERNEL);
+	if (!handles)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+		arg = i + 0x20;
+		r = sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg,
+					&result);
+		if (!r) {
+			dprintk("caching handle 0x%.4x (offset: 0x%.2x)\n",
+					result, i);
+			handles->cap[i] = result;
+		}
+	}
+
+	if (debug) {
+		sysfs_attr_init(&handles->devattr.attr);
+		handles->devattr.attr.name = "handles";
+		handles->devattr.attr.mode = S_IRUGO;
+		handles->devattr.show = sony_nc_handles_show;
+
+		/* allow reading capabilities via sysfs */
+		if (device_create_file(&pd->dev, &handles->devattr)) {
+			kfree(handles);
+			handles = NULL;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int sony_nc_handles_cleanup(struct platform_device *pd)
+{
+	if (handles) {
+		if (debug)
+			device_remove_file(&pd->dev, &handles->devattr);
+		kfree(handles);
+		handles = NULL;
+	}
+	return 0;
+}
+
+static int sony_find_snc_handle(int handle)
+{
+	int i;
+
+	/* not initialized yet, return early */
+	if (!handles || !handle)
+		return -EINVAL;
+
+	for (i = 0; i < 0x10; i++) {
+		if (handles->cap[i] == handle) {
+			dprintk("found handle 0x%.4x (offset: 0x%.2x)\n",
+					handle, i);
+			return i;
+		}
+	}
+	dprintk("handle 0x%.4x not found\n", handle);
+	return -EINVAL;
+}
+
+static int sony_call_snc_handle(int handle, int argument, int *result)
+{
+	int arg, ret = 0;
+	int offset = sony_find_snc_handle(handle);
+
+	if (offset < 0)
+		return offset;
+
+	arg = offset | argument;
+	ret = sony_nc_int_call(sony_nc_acpi_handle, "SN07", &arg, result);
+	dprintk("called SN07 with 0x%.4x (result: 0x%.4x)\n", arg, *result);
+	return ret;
+}
+
+/*
+ * 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, ret = 0;
+	struct sony_nc_value *item =
+	    container_of(attr, struct sony_nc_value, devattr);
+
+	if (!*item->acpiget)
+		return -EIO;
+
+	ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiget, NULL,
+				&value);
+	if (ret < 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;
+	int ret = 0;
+	struct sony_nc_value *item =
+	    container_of(attr, struct sony_nc_value, devattr);
+
+	if (!item->acpiset)
+		return -EIO;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoint(buffer, 10, &value))
+		return -EINVAL;
+
+	if (item->validate)
+		value = item->validate(SNC_VALIDATE_IN, value);
+
+	if (value < 0)
+		return value;
+
+	ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiset,
+			       &value, NULL);
+	if (ret < 0)
+		return -EIO;
+
+	item->value = value;
+	item->valid = 1;
+	return count;
+}
+
+
+/*
+ * Backlight device
+ */
+struct sony_backlight_props {
+	struct backlight_device *dev;
+	int			handle;
+	int			cmd_base;
+	u8			offset;
+	u8			maxlvl;
+};
+static struct sony_backlight_props sony_bl_props;
+
+static int sony_backlight_update_status(struct backlight_device *bd)
+{
+	int arg = bd->props.brightness + 1;
+	return sony_nc_int_call(sony_nc_acpi_handle, "SBRT", &arg, NULL);
+}
+
+static int sony_backlight_get_brightness(struct backlight_device *bd)
+{
+	int value;
+
+	if (sony_nc_int_call(sony_nc_acpi_handle, "GBRT", NULL, &value))
+		return 0;
+	/* brightness levels are 1-based, while backlight ones are 0-based */
+	return value - 1;
+}
+
+static int sony_nc_get_brightness_ng(struct backlight_device *bd)
+{
+	int result;
+	struct sony_backlight_props *sdev =
+		(struct sony_backlight_props *)bl_get_data(bd);
+
+	sony_call_snc_handle(sdev->handle, sdev->cmd_base + 0x100, &result);
+
+	return (result & 0xff) - sdev->offset;
+}
+
+static int sony_nc_update_status_ng(struct backlight_device *bd)
+{
+	int value, result;
+	struct sony_backlight_props *sdev =
+		(struct sony_backlight_props *)bl_get_data(bd);
+
+	value = bd->props.brightness + sdev->offset;
+	if (sony_call_snc_handle(sdev->handle, sdev->cmd_base | (value << 0x10),
+				&result))
+		return -EIO;
+
+	return value;
+}
+
+static const struct backlight_ops sony_backlight_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.update_status = sony_backlight_update_status,
+	.get_brightness = sony_backlight_get_brightness,
+};
+static const struct backlight_ops sony_backlight_ng_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.update_status = sony_nc_update_status_ng,
+	.get_brightness = sony_nc_get_brightness_ng,
+};
+
+/*
+ * New SNC-only Vaios event mapping to driver known keys
+ */
+struct sony_nc_event {
+	u8	data;
+	u8	event;
+};
+
+static struct sony_nc_event sony_100_events[] = {
+	{ 0x90, SONYPI_EVENT_PKEY_P1 },
+	{ 0x10, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x91, SONYPI_EVENT_PKEY_P2 },
+	{ 0x11, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x81, SONYPI_EVENT_FNKEY_F1 },
+	{ 0x01, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x82, SONYPI_EVENT_FNKEY_F2 },
+	{ 0x02, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x83, SONYPI_EVENT_FNKEY_F3 },
+	{ 0x03, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x84, SONYPI_EVENT_FNKEY_F4 },
+	{ 0x04, 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 },
+	{ 0x88, SONYPI_EVENT_FNKEY_F8 },
+	{ 0x08, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x89, SONYPI_EVENT_FNKEY_F9 },
+	{ 0x09, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x8A, SONYPI_EVENT_FNKEY_F10 },
+	{ 0x0A, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x8B, SONYPI_EVENT_FNKEY_F11 },
+	{ 0x0B, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x8C, SONYPI_EVENT_FNKEY_F12 },
+	{ 0x0C, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x9d, SONYPI_EVENT_ZOOM_PRESSED },
+	{ 0x1d, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x9f, SONYPI_EVENT_CD_EJECT_PRESSED },
+	{ 0x1f, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0xa1, SONYPI_EVENT_MEDIA_PRESSED },
+	{ 0x21, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0xa4, SONYPI_EVENT_CD_EJECT_PRESSED },
+	{ 0x24, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0xa5, SONYPI_EVENT_VENDOR_PRESSED },
+	{ 0x25, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0xa6, SONYPI_EVENT_HELP_PRESSED },
+	{ 0x26, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0xa8, SONYPI_EVENT_FNKEY_1 },
+	{ 0x28, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0, 0 },
+};
+
+static struct sony_nc_event sony_127_events[] = {
+	{ 0x81, SONYPI_EVENT_MODEKEY_PRESSED },
+	{ 0x01, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x82, SONYPI_EVENT_PKEY_P1 },
+	{ 0x02, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x83, SONYPI_EVENT_PKEY_P2 },
+	{ 0x03, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x84, SONYPI_EVENT_PKEY_P3 },
+	{ 0x04, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x85, SONYPI_EVENT_PKEY_P4 },
+	{ 0x05, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x86, SONYPI_EVENT_PKEY_P5 },
+	{ 0x06, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0x87, SONYPI_EVENT_SETTINGKEY_PRESSED },
+	{ 0x07, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0, 0 },
+};
+
+static int sony_nc_hotkeys_decode(u32 event, unsigned int handle)
+{
+	int ret = -EINVAL;
+	unsigned int result = 0;
+	struct sony_nc_event *key_event;
+
+	if (sony_call_snc_handle(handle, 0x200, &result)) {
+		dprintk("Unable to decode event 0x%.2x 0x%.2x\n", handle,
+				event);
+		return -EINVAL;
+	}
+
+	result &= 0xFF;
+
+	if (handle == 0x0100)
+		key_event = sony_100_events;
+	else
+		key_event = sony_127_events;
+
+	for (; key_event->data; key_event++) {
+		if (key_event->data == result) {
+			ret = key_event->event;
+			break;
+		}
+	}
+
+	if (!key_event->data)
+		pr_info("Unknown hotkey 0x%.2x/0x%.2x (handle 0x%.2x)\n",
+				event, result, handle);
+
+	return ret;
+}
+
+/*
+ * ACPI callbacks
+ */
+enum event_types {
+	HOTKEY = 1,
+	KILLSWITCH,
+	GFX_SWITCH
+};
+static void sony_nc_notify(struct acpi_device *device, u32 event)
+{
+	u32 real_ev = event;
+	u8 ev_type = 0;
+	int ret;
+
+	dprintk("sony_nc_notify, event: 0x%.2x\n", event);
+
+	if (event >= 0x90) {
+		unsigned int result = 0;
+		unsigned int arg = 0;
+		unsigned int handle = 0;
+		unsigned int offset = event - 0x90;
+
+		if (offset >= ARRAY_SIZE(handles->cap)) {
+			pr_err("Event 0x%x outside of capabilities list\n",
+					event);
+			return;
+		}
+		handle = handles->cap[offset];
+
+		/* list of handles known for generating events */
+		switch (handle) {
+		/* hotkey event */
+		case 0x0100:
+		case 0x0127:
+			ev_type = HOTKEY;
+			ret = sony_nc_hotkeys_decode(event, handle);
+
+			if (ret > 0) {
+				sony_laptop_report_input_event(ret);
+				real_ev = ret;
+			}
+
+			break;
+
+		/* wlan switch */
+		case 0x0124:
+		case 0x0135:
+			/* events on this handle are reported when the
+			 * switch changes position or for battery
+			 * events. We'll notify both of them but only
+			 * update the rfkill device status when the
+			 * switch is moved.
+			 */
+			ev_type = KILLSWITCH;
+			sony_call_snc_handle(handle, 0x0100, &result);
+			real_ev = result & 0x03;
+
+			/* hw switch event */
+			if (real_ev == 1)
+				sony_nc_rfkill_update();
+
+			break;
+
+		case 0x0128:
+		case 0x0146:
+			/* Hybrid GFX switching */
+			sony_call_snc_handle(handle, 0x0000, &result);
+			dprintk("GFX switch event received (reason: %s)\n",
+					(result == 0x1) ? "switch change" :
+					(result == 0x2) ? "output switch" :
+					(result == 0x3) ? "output switch" :
+					"");
+
+			ev_type = GFX_SWITCH;
+			real_ev = __sony_nc_gfx_switch_status_get();
+			break;
+
+		case 0x015B:
+			/* Hybrid GFX switching SVS151290S */
+			ev_type = GFX_SWITCH;
+			real_ev = __sony_nc_gfx_switch_status_get();
+			break;
+		default:
+			dprintk("Unknown event 0x%x for handle 0x%x\n",
+					event, handle);
+			break;
+		}
+
+		/* clear the event (and the event reason when present) */
+		arg = 1 << offset;
+		sony_nc_int_call(sony_nc_acpi_handle, "SN05", &arg, &result);
+
+	} else {
+		/* old style event */
+		ev_type = HOTKEY;
+		sony_laptop_report_input_event(real_ev);
+	}
+	acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class,
+			dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev);
+}
+
+static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
+				      void *context, void **return_value)
+{
+	struct acpi_device_info *info;
+
+	if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) {
+		pr_warn("method: name: %4.4s, args %X\n",
+			(char *)&info->name, info->param_count);
+
+		kfree(info);
+	}
+
+	return AE_OK;
+}
+
+/*
+ * ACPI device
+ */
+static void sony_nc_function_setup(struct acpi_device *device,
+		struct platform_device *pf_device)
+{
+	unsigned int i, result, bitmask, arg;
+
+	if (!handles)
+		return;
+
+	/* setup found handles here */
+	for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+		unsigned int handle = handles->cap[i];
+
+		if (!handle)
+			continue;
+
+		dprintk("setting up handle 0x%.4x\n", handle);
+
+		switch (handle) {
+		case 0x0100:
+		case 0x0101:
+		case 0x0127:
+			/* setup hotkeys */
+			sony_call_snc_handle(handle, 0, &result);
+			break;
+		case 0x0102:
+			/* setup hotkeys */
+			sony_call_snc_handle(handle, 0x100, &result);
+			break;
+		case 0x0105:
+		case 0x0148:
+			/* touchpad enable/disable */
+			result = sony_nc_touchpad_setup(pf_device, handle);
+			if (result)
+				pr_err("couldn't set up touchpad control function (%d)\n",
+						result);
+			break;
+		case 0x0115:
+		case 0x0136:
+		case 0x013f:
+			result = sony_nc_battery_care_setup(pf_device, handle);
+			if (result)
+				pr_err("couldn't set up battery care function (%d)\n",
+						result);
+			break;
+		case 0x0119:
+		case 0x015D:
+			result = sony_nc_lid_resume_setup(pf_device, handle);
+			if (result)
+				pr_err("couldn't set up lid resume function (%d)\n",
+						result);
+			break;
+		case 0x0122:
+			result = sony_nc_thermal_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up thermal profile function (%d)\n",
+						result);
+			break;
+		case 0x0128:
+		case 0x0146:
+		case 0x015B:
+			result = sony_nc_gfx_switch_setup(pf_device, handle);
+			if (result)
+				pr_err("couldn't set up GFX Switch status (%d)\n",
+						result);
+			break;
+		case 0x0131:
+			result = sony_nc_highspeed_charging_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up high speed charging function (%d)\n",
+				       result);
+			break;
+		case 0x0124:
+		case 0x0135:
+			result = sony_nc_rfkill_setup(device, handle);
+			if (result)
+				pr_err("couldn't set up rfkill support (%d)\n",
+						result);
+			break;
+		case 0x0137:
+		case 0x0143:
+		case 0x014b:
+		case 0x014c:
+		case 0x0163:
+			result = sony_nc_kbd_backlight_setup(pf_device, handle);
+			if (result)
+				pr_err("couldn't set up keyboard backlight function (%d)\n",
+						result);
+			break;
+		case 0x0121:
+			result = sony_nc_lowbatt_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up low battery function (%d)\n",
+				       result);
+			break;
+		case 0x0149:
+			result = sony_nc_fanspeed_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up fan speed function (%d)\n",
+				       result);
+			break;
+		case 0x0155:
+			result = sony_nc_usb_charge_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up USB charge support (%d)\n",
+						result);
+			break;
+		case 0x011D:
+			result = sony_nc_panelid_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up panel ID function (%d)\n",
+				       result);
+			break;
+		case 0x0168:
+			result = sony_nc_smart_conn_setup(pf_device);
+			if (result)
+				pr_err("couldn't set up smart connect support (%d)\n",
+						result);
+			break;
+		default:
+			continue;
+		}
+	}
+
+	/* Enable all events */
+	arg = 0x10;
+	if (!sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, &bitmask))
+		sony_nc_int_call(sony_nc_acpi_handle, "SN02", &bitmask,
+				&result);
+}
+
+static void sony_nc_function_cleanup(struct platform_device *pd)
+{
+	unsigned int i, result, bitmask, handle;
+
+	/* get enabled events and disable them */
+	sony_nc_int_call(sony_nc_acpi_handle, "SN01", NULL, &bitmask);
+	sony_nc_int_call(sony_nc_acpi_handle, "SN03", &bitmask, &result);
+
+	/* cleanup handles here */
+	for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+
+		handle = handles->cap[i];
+
+		if (!handle)
+			continue;
+
+		switch (handle) {
+		case 0x0105:
+		case 0x0148:
+			sony_nc_touchpad_cleanup(pd);
+			break;
+		case 0x0115:
+		case 0x0136:
+		case 0x013f:
+			sony_nc_battery_care_cleanup(pd);
+			break;
+		case 0x0119:
+		case 0x015D:
+			sony_nc_lid_resume_cleanup(pd);
+			break;
+		case 0x0122:
+			sony_nc_thermal_cleanup(pd);
+			break;
+		case 0x0128:
+		case 0x0146:
+		case 0x015B:
+			sony_nc_gfx_switch_cleanup(pd);
+			break;
+		case 0x0131:
+			sony_nc_highspeed_charging_cleanup(pd);
+			break;
+		case 0x0124:
+		case 0x0135:
+			sony_nc_rfkill_cleanup();
+			break;
+		case 0x0137:
+		case 0x0143:
+		case 0x014b:
+		case 0x014c:
+		case 0x0163:
+			sony_nc_kbd_backlight_cleanup(pd, handle);
+			break;
+		case 0x0121:
+			sony_nc_lowbatt_cleanup(pd);
+			break;
+		case 0x0149:
+			sony_nc_fanspeed_cleanup(pd);
+			break;
+		case 0x0155:
+			sony_nc_usb_charge_cleanup(pd);
+			break;
+		case 0x011D:
+			sony_nc_panelid_cleanup(pd);
+			break;
+		case 0x0168:
+			sony_nc_smart_conn_cleanup(pd);
+			break;
+		default:
+			continue;
+		}
+	}
+
+	/* finally cleanup the handles list */
+	sony_nc_handles_cleanup(pd);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_function_resume(void)
+{
+	unsigned int i, result, bitmask, arg;
+
+	dprintk("Resuming SNC device\n");
+
+	for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+		unsigned int handle = handles->cap[i];
+
+		if (!handle)
+			continue;
+
+		switch (handle) {
+		case 0x0100:
+		case 0x0101:
+		case 0x0127:
+			/* re-enable hotkeys */
+			sony_call_snc_handle(handle, 0, &result);
+			break;
+		case 0x0102:
+			/* re-enable hotkeys */
+			sony_call_snc_handle(handle, 0x100, &result);
+			break;
+		case 0x0122:
+			sony_nc_thermal_resume();
+			break;
+		case 0x0124:
+		case 0x0135:
+			sony_nc_rfkill_update();
+			break;
+		default:
+			continue;
+		}
+	}
+
+	/* Enable all events */
+	arg = 0x10;
+	if (!sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, &bitmask))
+		sony_nc_int_call(sony_nc_acpi_handle, "SN02", &bitmask,
+				&result);
+}
+
+static int sony_nc_resume(struct device *dev)
+{
+	struct sony_nc_value *item;
+
+	for (item = sony_nc_values; item->name; item++) {
+		int ret;
+
+		if (!item->valid)
+			continue;
+		ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiset,
+				       &item->value, NULL);
+		if (ret < 0) {
+			pr_err("%s: %d\n", __func__, ret);
+			break;
+		}
+	}
+
+	if (acpi_has_method(sony_nc_acpi_handle, "ECON")) {
+		int arg = 1;
+		if (sony_nc_int_call(sony_nc_acpi_handle, "ECON", &arg, NULL))
+			dprintk("ECON Method failed\n");
+	}
+
+	if (acpi_has_method(sony_nc_acpi_handle, "SN00"))
+		sony_nc_function_resume();
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(sony_nc_pm, NULL, sony_nc_resume);
+
+static void sony_nc_rfkill_cleanup(void)
+{
+	int i;
+
+	for (i = 0; i < N_SONY_RFKILL; i++) {
+		if (sony_rfkill_devices[i]) {
+			rfkill_unregister(sony_rfkill_devices[i]);
+			rfkill_destroy(sony_rfkill_devices[i]);
+		}
+	}
+}
+
+static int sony_nc_rfkill_set(void *data, bool blocked)
+{
+	int result;
+	int argument = sony_rfkill_address[(long) data] + 0x100;
+
+	if (!blocked)
+		argument |= 0x070000;
+
+	return sony_call_snc_handle(sony_rfkill_handle, argument, &result);
+}
+
+static const struct rfkill_ops sony_rfkill_ops = {
+	.set_block = sony_nc_rfkill_set,
+};
+
+static int sony_nc_setup_rfkill(struct acpi_device *device,
+				enum sony_nc_rfkill nc_type)
+{
+	int err = 0;
+	struct rfkill *rfk;
+	enum rfkill_type type;
+	const char *name;
+	int result;
+	bool hwblock, swblock;
+
+	switch (nc_type) {
+	case SONY_WIFI:
+		type = RFKILL_TYPE_WLAN;
+		name = "sony-wifi";
+		break;
+	case SONY_BLUETOOTH:
+		type = RFKILL_TYPE_BLUETOOTH;
+		name = "sony-bluetooth";
+		break;
+	case SONY_WWAN:
+		type = RFKILL_TYPE_WWAN;
+		name = "sony-wwan";
+		break;
+	case SONY_WIMAX:
+		type = RFKILL_TYPE_WIMAX;
+		name = "sony-wimax";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rfk = rfkill_alloc(name, &device->dev, type,
+			   &sony_rfkill_ops, (void *)nc_type);
+	if (!rfk)
+		return -ENOMEM;
+
+	if (sony_call_snc_handle(sony_rfkill_handle, 0x200, &result) < 0) {
+		rfkill_destroy(rfk);
+		return -1;
+	}
+	hwblock = !(result & 0x1);
+
+	if (sony_call_snc_handle(sony_rfkill_handle,
+				sony_rfkill_address[nc_type],
+				&result) < 0) {
+		rfkill_destroy(rfk);
+		return -1;
+	}
+	swblock = !(result & 0x2);
+
+	rfkill_init_sw_state(rfk, swblock);
+	rfkill_set_hw_state(rfk, hwblock);
+
+	err = rfkill_register(rfk);
+	if (err) {
+		rfkill_destroy(rfk);
+		return err;
+	}
+	sony_rfkill_devices[nc_type] = rfk;
+	return err;
+}
+
+static void sony_nc_rfkill_update(void)
+{
+	enum sony_nc_rfkill i;
+	int result;
+	bool hwblock;
+
+	sony_call_snc_handle(sony_rfkill_handle, 0x200, &result);
+	hwblock = !(result & 0x1);
+
+	for (i = 0; i < N_SONY_RFKILL; i++) {
+		int argument = sony_rfkill_address[i];
+
+		if (!sony_rfkill_devices[i])
+			continue;
+
+		if (hwblock) {
+			if (rfkill_set_hw_state(sony_rfkill_devices[i], true)) {
+				/* we already know we're blocked */
+			}
+			continue;
+		}
+
+		sony_call_snc_handle(sony_rfkill_handle, argument, &result);
+		rfkill_set_states(sony_rfkill_devices[i],
+				  !(result & 0x2), false);
+	}
+}
+
+static int sony_nc_rfkill_setup(struct acpi_device *device,
+		unsigned int handle)
+{
+	u64 offset;
+	int i;
+	unsigned char buffer[32] = { 0 };
+
+	offset = sony_find_snc_handle(handle);
+	sony_rfkill_handle = handle;
+
+	i = sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &offset, buffer,
+			32);
+	if (i < 0)
+		return i;
+
+	/* The buffer is filled with magic numbers describing the devices
+	 * available, 0xff terminates the enumeration.
+	 * Known codes:
+	 *	0x00 WLAN
+	 *	0x10 BLUETOOTH
+	 *	0x20 WWAN GPRS-EDGE
+	 *	0x21 WWAN HSDPA
+	 *	0x22 WWAN EV-DO
+	 *	0x23 WWAN GPS
+	 *	0x25 Gobi WWAN no GPS
+	 *	0x26 Gobi WWAN + GPS
+	 *	0x28 Gobi WWAN no GPS
+	 *	0x29 Gobi WWAN + GPS
+	 *	0x30 WIMAX
+	 *	0x50 Gobi WWAN no GPS
+	 *	0x51 Gobi WWAN + GPS
+	 *	0x70 no SIM card slot
+	 *	0x71 SIM card slot
+	 */
+	for (i = 0; i < ARRAY_SIZE(buffer); i++) {
+
+		if (buffer[i] == 0xff)
+			break;
+
+		dprintk("Radio devices, found 0x%.2x\n", buffer[i]);
+
+		if (buffer[i] == 0 && !sony_rfkill_devices[SONY_WIFI])
+			sony_nc_setup_rfkill(device, SONY_WIFI);
+
+		if (buffer[i] == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH])
+			sony_nc_setup_rfkill(device, SONY_BLUETOOTH);
+
+		if (((0xf0 & buffer[i]) == 0x20 ||
+					(0xf0 & buffer[i]) == 0x50) &&
+				!sony_rfkill_devices[SONY_WWAN])
+			sony_nc_setup_rfkill(device, SONY_WWAN);
+
+		if (buffer[i] == 0x30 && !sony_rfkill_devices[SONY_WIMAX])
+			sony_nc_setup_rfkill(device, SONY_WIMAX);
+	}
+	return 0;
+}
+
+/* Keyboard backlight feature */
+struct kbd_backlight {
+	unsigned int handle;
+	unsigned int base;
+	unsigned int mode;
+	unsigned int timeout;
+	struct device_attribute mode_attr;
+	struct device_attribute timeout_attr;
+};
+
+static struct kbd_backlight *kbdbl_ctl;
+
+static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value)
+{
+	int result;
+
+	if (value > 2)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(kbdbl_ctl->handle,
+				(value << 0x10) | (kbdbl_ctl->base), &result))
+		return -EIO;
+
+	/* Try to turn the light on/off immediately */
+	if (value != 1)
+		sony_call_snc_handle(kbdbl_ctl->handle,
+				(value << 0x0f) | (kbdbl_ctl->base + 0x100),
+				&result);
+
+	kbdbl_ctl->mode = value;
+
+	return 0;
+}
+
+static ssize_t sony_nc_kbd_backlight_mode_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	int ret = 0;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	ret = __sony_nc_kbd_backlight_mode_set(value);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t sony_nc_kbd_backlight_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	ssize_t count = 0;
+	count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_ctl->mode);
+	return count;
+}
+
+static int __sony_nc_kbd_backlight_timeout_set(u8 value)
+{
+	int result;
+
+	if (value > 3)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(kbdbl_ctl->handle, (value << 0x10) |
+				(kbdbl_ctl->base + 0x200), &result))
+		return -EIO;
+
+	kbdbl_ctl->timeout = value;
+
+	return 0;
+}
+
+static ssize_t sony_nc_kbd_backlight_timeout_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	int ret = 0;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	ret = __sony_nc_kbd_backlight_timeout_set(value);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t sony_nc_kbd_backlight_timeout_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	ssize_t count = 0;
+	count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_ctl->timeout);
+	return count;
+}
+
+static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
+		unsigned int handle)
+{
+	int result;
+	int ret = 0;
+
+	if (kbdbl_ctl) {
+		pr_warn("handle 0x%.4x: keyboard backlight setup already done for 0x%.4x\n",
+				handle, kbdbl_ctl->handle);
+		return -EBUSY;
+	}
+
+	/* verify the kbd backlight presence, these handles are not used for
+	 * keyboard backlight only
+	 */
+	ret = sony_call_snc_handle(handle, handle == 0x0137 ? 0x0B00 : 0x0100,
+			&result);
+	if (ret)
+		return ret;
+
+	if ((handle == 0x0137 && !(result & 0x02)) ||
+			!(result & 0x01)) {
+		dprintk("no backlight keyboard found\n");
+		return 0;
+	}
+
+	kbdbl_ctl = kzalloc(sizeof(*kbdbl_ctl), GFP_KERNEL);
+	if (!kbdbl_ctl)
+		return -ENOMEM;
+
+	kbdbl_ctl->mode = kbd_backlight;
+	kbdbl_ctl->timeout = kbd_backlight_timeout;
+	kbdbl_ctl->handle = handle;
+	if (handle == 0x0137)
+		kbdbl_ctl->base = 0x0C00;
+	else
+		kbdbl_ctl->base = 0x4000;
+
+	sysfs_attr_init(&kbdbl_ctl->mode_attr.attr);
+	kbdbl_ctl->mode_attr.attr.name = "kbd_backlight";
+	kbdbl_ctl->mode_attr.attr.mode = S_IRUGO | S_IWUSR;
+	kbdbl_ctl->mode_attr.show = sony_nc_kbd_backlight_mode_show;
+	kbdbl_ctl->mode_attr.store = sony_nc_kbd_backlight_mode_store;
+
+	sysfs_attr_init(&kbdbl_ctl->timeout_attr.attr);
+	kbdbl_ctl->timeout_attr.attr.name = "kbd_backlight_timeout";
+	kbdbl_ctl->timeout_attr.attr.mode = S_IRUGO | S_IWUSR;
+	kbdbl_ctl->timeout_attr.show = sony_nc_kbd_backlight_timeout_show;
+	kbdbl_ctl->timeout_attr.store = sony_nc_kbd_backlight_timeout_store;
+
+	ret = device_create_file(&pd->dev, &kbdbl_ctl->mode_attr);
+	if (ret)
+		goto outkzalloc;
+
+	ret = device_create_file(&pd->dev, &kbdbl_ctl->timeout_attr);
+	if (ret)
+		goto outmode;
+
+	__sony_nc_kbd_backlight_mode_set(kbdbl_ctl->mode);
+	__sony_nc_kbd_backlight_timeout_set(kbdbl_ctl->timeout);
+
+	return 0;
+
+outmode:
+	device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr);
+outkzalloc:
+	kfree(kbdbl_ctl);
+	kbdbl_ctl = NULL;
+	return ret;
+}
+
+static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd,
+		unsigned int handle)
+{
+	if (kbdbl_ctl && handle == kbdbl_ctl->handle) {
+		device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr);
+		device_remove_file(&pd->dev, &kbdbl_ctl->timeout_attr);
+		kfree(kbdbl_ctl);
+		kbdbl_ctl = NULL;
+	}
+}
+
+struct battery_care_control {
+	struct device_attribute attrs[2];
+	unsigned int handle;
+};
+static struct battery_care_control *bcare_ctl;
+
+static ssize_t sony_nc_battery_care_limit_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned int result, cmd;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	/*  limit values (2 bits):
+	 *  00 - none
+	 *  01 - 80%
+	 *  10 - 50%
+	 *  11 - 100%
+	 *
+	 *  bit 0: 0 disable BCL, 1 enable BCL
+	 *  bit 1: 1 tell to store the battery limit (see bits 6,7) too
+	 *  bits 2,3: reserved
+	 *  bits 4,5: store the limit into the EC
+	 *  bits 6,7: store the limit into the battery
+	 */
+	cmd = 0;
+
+	if (value > 0) {
+		if (value <= 50)
+			cmd = 0x20;
+
+		else if (value <= 80)
+			cmd = 0x10;
+
+		else if (value <= 100)
+			cmd = 0x30;
+
+		else
+			return -EINVAL;
+
+		/*
+		 * handle 0x0115 should allow storing on battery too;
+		 * handle 0x0136 same as 0x0115 + health status;
+		 * handle 0x013f, same as 0x0136 but no storing on the battery
+		 */
+		if (bcare_ctl->handle != 0x013f)
+			cmd = cmd | (cmd << 2);
+
+		cmd = (cmd | 0x1) << 0x10;
+	}
+
+	if (sony_call_snc_handle(bcare_ctl->handle, cmd | 0x0100, &result))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_battery_care_limit_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result, status;
+
+	if (sony_call_snc_handle(bcare_ctl->handle, 0x0000, &result))
+		return -EIO;
+
+	status = (result & 0x01) ? ((result & 0x30) >> 0x04) : 0;
+	switch (status) {
+	case 1:
+		status = 80;
+		break;
+	case 2:
+		status = 50;
+		break;
+	case 3:
+		status = 100;
+		break;
+	default:
+		status = 0;
+		break;
+	}
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t sony_nc_battery_care_health_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	ssize_t count = 0;
+	unsigned int health;
+
+	if (sony_call_snc_handle(bcare_ctl->handle, 0x0200, &health))
+		return -EIO;
+
+	count = snprintf(buffer, PAGE_SIZE, "%d\n", health & 0xff);
+
+	return count;
+}
+
+static int sony_nc_battery_care_setup(struct platform_device *pd,
+		unsigned int handle)
+{
+	int ret = 0;
+
+	bcare_ctl = kzalloc(sizeof(struct battery_care_control), GFP_KERNEL);
+	if (!bcare_ctl)
+		return -ENOMEM;
+
+	bcare_ctl->handle = handle;
+
+	sysfs_attr_init(&bcare_ctl->attrs[0].attr);
+	bcare_ctl->attrs[0].attr.name = "battery_care_limiter";
+	bcare_ctl->attrs[0].attr.mode = S_IRUGO | S_IWUSR;
+	bcare_ctl->attrs[0].show = sony_nc_battery_care_limit_show;
+	bcare_ctl->attrs[0].store = sony_nc_battery_care_limit_store;
+
+	ret = device_create_file(&pd->dev, &bcare_ctl->attrs[0]);
+	if (ret)
+		goto outkzalloc;
+
+	/* 0x0115 is for models with no health reporting capability */
+	if (handle == 0x0115)
+		return 0;
+
+	sysfs_attr_init(&bcare_ctl->attrs[1].attr);
+	bcare_ctl->attrs[1].attr.name = "battery_care_health";
+	bcare_ctl->attrs[1].attr.mode = S_IRUGO;
+	bcare_ctl->attrs[1].show = sony_nc_battery_care_health_show;
+
+	ret = device_create_file(&pd->dev, &bcare_ctl->attrs[1]);
+	if (ret)
+		goto outlimiter;
+
+	return 0;
+
+outlimiter:
+	device_remove_file(&pd->dev, &bcare_ctl->attrs[0]);
+
+outkzalloc:
+	kfree(bcare_ctl);
+	bcare_ctl = NULL;
+
+	return ret;
+}
+
+static void sony_nc_battery_care_cleanup(struct platform_device *pd)
+{
+	if (bcare_ctl) {
+		device_remove_file(&pd->dev, &bcare_ctl->attrs[0]);
+		if (bcare_ctl->handle != 0x0115)
+			device_remove_file(&pd->dev, &bcare_ctl->attrs[1]);
+
+		kfree(bcare_ctl);
+		bcare_ctl = NULL;
+	}
+}
+
+struct snc_thermal_ctrl {
+	unsigned int mode;
+	unsigned int profiles;
+	struct device_attribute mode_attr;
+	struct device_attribute profiles_attr;
+};
+static struct snc_thermal_ctrl *th_handle;
+
+#define THM_PROFILE_MAX 3
+static const char * const snc_thermal_profiles[] = {
+	"balanced",
+	"silent",
+	"performance"
+};
+
+static int sony_nc_thermal_mode_set(unsigned short mode)
+{
+	unsigned int result;
+
+	/* the thermal profile seems to be a two bit bitmask:
+	 * lsb -> silent
+	 * msb -> performance
+	 * no bit set is the normal operation and is always valid
+	 * Some vaio models only have "balanced" and "performance"
+	 */
+	if ((mode && !(th_handle->profiles & mode)) || mode >= THM_PROFILE_MAX)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(0x0122, mode << 0x10 | 0x0200, &result))
+		return -EIO;
+
+	th_handle->mode = mode;
+
+	return 0;
+}
+
+static int sony_nc_thermal_mode_get(void)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0122, 0x0100, &result))
+		return -EIO;
+
+	return result & 0xff;
+}
+
+static ssize_t sony_nc_thermal_profiles_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	short cnt;
+	size_t idx = 0;
+
+	for (cnt = 0; cnt < THM_PROFILE_MAX; cnt++) {
+		if (!cnt || (th_handle->profiles & cnt))
+			idx += snprintf(buffer + idx, PAGE_SIZE - idx, "%s ",
+					snc_thermal_profiles[cnt]);
+	}
+	idx += snprintf(buffer + idx, PAGE_SIZE - idx, "\n");
+
+	return idx;
+}
+
+static ssize_t sony_nc_thermal_mode_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned short cmd;
+	size_t len = count;
+
+	if (count == 0)
+		return -EINVAL;
+
+	/* skip the newline if present */
+	if (buffer[len - 1] == '\n')
+		len--;
+
+	for (cmd = 0; cmd < THM_PROFILE_MAX; cmd++)
+		if (strncmp(buffer, snc_thermal_profiles[cmd], len) == 0)
+			break;
+
+	if (sony_nc_thermal_mode_set(cmd))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_thermal_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	ssize_t count = 0;
+	int mode = sony_nc_thermal_mode_get();
+
+	if (mode < 0)
+		return mode;
+
+	count = snprintf(buffer, PAGE_SIZE, "%s\n", snc_thermal_profiles[mode]);
+
+	return count;
+}
+
+static int sony_nc_thermal_setup(struct platform_device *pd)
+{
+	int ret = 0;
+	th_handle = kzalloc(sizeof(struct snc_thermal_ctrl), GFP_KERNEL);
+	if (!th_handle)
+		return -ENOMEM;
+
+	ret = sony_call_snc_handle(0x0122, 0x0000, &th_handle->profiles);
+	if (ret) {
+		pr_warn("couldn't to read the thermal profiles\n");
+		goto outkzalloc;
+	}
+
+	ret = sony_nc_thermal_mode_get();
+	if (ret < 0) {
+		pr_warn("couldn't to read the current thermal profile");
+		goto outkzalloc;
+	}
+	th_handle->mode = ret;
+
+	sysfs_attr_init(&th_handle->profiles_attr.attr);
+	th_handle->profiles_attr.attr.name = "thermal_profiles";
+	th_handle->profiles_attr.attr.mode = S_IRUGO;
+	th_handle->profiles_attr.show = sony_nc_thermal_profiles_show;
+
+	sysfs_attr_init(&th_handle->mode_attr.attr);
+	th_handle->mode_attr.attr.name = "thermal_control";
+	th_handle->mode_attr.attr.mode = S_IRUGO | S_IWUSR;
+	th_handle->mode_attr.show = sony_nc_thermal_mode_show;
+	th_handle->mode_attr.store = sony_nc_thermal_mode_store;
+
+	ret = device_create_file(&pd->dev, &th_handle->profiles_attr);
+	if (ret)
+		goto outkzalloc;
+
+	ret = device_create_file(&pd->dev, &th_handle->mode_attr);
+	if (ret)
+		goto outprofiles;
+
+	return 0;
+
+outprofiles:
+	device_remove_file(&pd->dev, &th_handle->profiles_attr);
+outkzalloc:
+	kfree(th_handle);
+	th_handle = NULL;
+	return ret;
+}
+
+static void sony_nc_thermal_cleanup(struct platform_device *pd)
+{
+	if (th_handle) {
+		device_remove_file(&pd->dev, &th_handle->profiles_attr);
+		device_remove_file(&pd->dev, &th_handle->mode_attr);
+		kfree(th_handle);
+		th_handle = NULL;
+	}
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_thermal_resume(void)
+{
+	unsigned int status = sony_nc_thermal_mode_get();
+
+	if (status != th_handle->mode)
+		sony_nc_thermal_mode_set(th_handle->mode);
+}
+#endif
+
+/* resume on LID open */
+#define LID_RESUME_S5	0
+#define LID_RESUME_S4	1
+#define LID_RESUME_S3	2
+#define LID_RESUME_MAX	3
+struct snc_lid_resume_control {
+	struct device_attribute attrs[LID_RESUME_MAX];
+	unsigned int status;
+	int handle;
+};
+static struct snc_lid_resume_control *lid_ctl;
+
+static ssize_t sony_nc_lid_resume_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+	unsigned int pos = LID_RESUME_S5;
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	/* the value we have to write to SNC is a bitmask:
+	 * +--------------+
+	 * | S3 | S4 | S5 |
+	 * +--------------+
+	 *   2    1    0
+	 */
+	while (pos < LID_RESUME_MAX) {
+		if (&lid_ctl->attrs[pos].attr == &attr->attr)
+			break;
+		pos++;
+	}
+	if (pos == LID_RESUME_MAX)
+		return -EINVAL;
+
+	if (value)
+		value = lid_ctl->status | (1 << pos);
+	else
+		value = lid_ctl->status & ~(1 << pos);
+
+	if (sony_call_snc_handle(lid_ctl->handle, value << 0x10 | 0x0100,
+				&result))
+		return -EIO;
+
+	lid_ctl->status = value;
+
+	return count;
+}
+
+static ssize_t sony_nc_lid_resume_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buffer)
+{
+	unsigned int pos = LID_RESUME_S5;
+
+	while (pos < LID_RESUME_MAX) {
+		if (&lid_ctl->attrs[pos].attr == &attr->attr)
+			return snprintf(buffer, PAGE_SIZE, "%d\n",
+					(lid_ctl->status >> pos) & 0x01);
+		pos++;
+	}
+	return -EINVAL;
+}
+
+static int sony_nc_lid_resume_setup(struct platform_device *pd,
+					unsigned int handle)
+{
+	unsigned int result;
+	int i;
+
+	if (sony_call_snc_handle(handle, 0x0000, &result))
+		return -EIO;
+
+	lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL);
+	if (!lid_ctl)
+		return -ENOMEM;
+
+	lid_ctl->status = result & 0x7;
+	lid_ctl->handle = handle;
+
+	sysfs_attr_init(&lid_ctl->attrs[0].attr);
+	lid_ctl->attrs[LID_RESUME_S5].attr.name = "lid_resume_S5";
+	lid_ctl->attrs[LID_RESUME_S5].attr.mode = S_IRUGO | S_IWUSR;
+	lid_ctl->attrs[LID_RESUME_S5].show = sony_nc_lid_resume_show;
+	lid_ctl->attrs[LID_RESUME_S5].store = sony_nc_lid_resume_store;
+
+	if (handle == 0x0119) {
+		sysfs_attr_init(&lid_ctl->attrs[1].attr);
+		lid_ctl->attrs[LID_RESUME_S4].attr.name = "lid_resume_S4";
+		lid_ctl->attrs[LID_RESUME_S4].attr.mode = S_IRUGO | S_IWUSR;
+		lid_ctl->attrs[LID_RESUME_S4].show = sony_nc_lid_resume_show;
+		lid_ctl->attrs[LID_RESUME_S4].store = sony_nc_lid_resume_store;
+
+		sysfs_attr_init(&lid_ctl->attrs[2].attr);
+		lid_ctl->attrs[LID_RESUME_S3].attr.name = "lid_resume_S3";
+		lid_ctl->attrs[LID_RESUME_S3].attr.mode = S_IRUGO | S_IWUSR;
+		lid_ctl->attrs[LID_RESUME_S3].show = sony_nc_lid_resume_show;
+		lid_ctl->attrs[LID_RESUME_S3].store = sony_nc_lid_resume_store;
+	}
+	for (i = 0; i < LID_RESUME_MAX &&
+			lid_ctl->attrs[i].attr.name; i++) {
+		result = device_create_file(&pd->dev, &lid_ctl->attrs[i]);
+		if (result)
+			goto liderror;
+	}
+
+	return 0;
+
+liderror:
+	for (i--; i >= 0; i--)
+		device_remove_file(&pd->dev, &lid_ctl->attrs[i]);
+
+	kfree(lid_ctl);
+	lid_ctl = NULL;
+
+	return result;
+}
+
+static void sony_nc_lid_resume_cleanup(struct platform_device *pd)
+{
+	int i;
+
+	if (lid_ctl) {
+		for (i = 0; i < LID_RESUME_MAX; i++) {
+			if (!lid_ctl->attrs[i].attr.name)
+				break;
+
+			device_remove_file(&pd->dev, &lid_ctl->attrs[i]);
+		}
+
+		kfree(lid_ctl);
+		lid_ctl = NULL;
+	}
+}
+
+/* GFX Switch position */
+enum gfx_switch {
+	SPEED,
+	STAMINA,
+	AUTO
+};
+struct snc_gfx_switch_control {
+	struct device_attribute attr;
+	unsigned int handle;
+};
+static struct snc_gfx_switch_control *gfxs_ctl;
+
+/* returns 0 for speed, 1 for stamina */
+static int __sony_nc_gfx_switch_status_get(void)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(gfxs_ctl->handle,
+				gfxs_ctl->handle == 0x015B ? 0x0000 : 0x0100,
+				&result))
+		return -EIO;
+
+	switch (gfxs_ctl->handle) {
+	case 0x0146:
+		/* 1: discrete GFX (speed)
+		 * 0: integrated GFX (stamina)
+		 */
+		return result & 0x1 ? SPEED : STAMINA;
+		break;
+	case 0x015B:
+		/* 0: discrete GFX (speed)
+		 * 1: integrated GFX (stamina)
+		 */
+		return result & 0x1 ? STAMINA : SPEED;
+		break;
+	case 0x0128:
+		/* it's a more elaborated bitmask, for now:
+		 * 2: integrated GFX (stamina)
+		 * 0: discrete GFX (speed)
+		 */
+		dprintk("GFX Status: 0x%x\n", result);
+		return result & 0x80 ? AUTO :
+			result & 0x02 ? STAMINA : SPEED;
+		break;
+	}
+	return -EINVAL;
+}
+
+static ssize_t sony_nc_gfx_switch_status_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buffer)
+{
+	int pos = __sony_nc_gfx_switch_status_get();
+
+	if (pos < 0)
+		return pos;
+
+	return snprintf(buffer, PAGE_SIZE, "%s\n",
+					pos == SPEED ? "speed" :
+					pos == STAMINA ? "stamina" :
+					pos == AUTO ? "auto" : "unknown");
+}
+
+static int sony_nc_gfx_switch_setup(struct platform_device *pd,
+		unsigned int handle)
+{
+	unsigned int result;
+
+	gfxs_ctl = kzalloc(sizeof(struct snc_gfx_switch_control), GFP_KERNEL);
+	if (!gfxs_ctl)
+		return -ENOMEM;
+
+	gfxs_ctl->handle = handle;
+
+	sysfs_attr_init(&gfxs_ctl->attr.attr);
+	gfxs_ctl->attr.attr.name = "gfx_switch_status";
+	gfxs_ctl->attr.attr.mode = S_IRUGO;
+	gfxs_ctl->attr.show = sony_nc_gfx_switch_status_show;
+
+	result = device_create_file(&pd->dev, &gfxs_ctl->attr);
+	if (result)
+		goto gfxerror;
+
+	return 0;
+
+gfxerror:
+	kfree(gfxs_ctl);
+	gfxs_ctl = NULL;
+
+	return result;
+}
+
+static void sony_nc_gfx_switch_cleanup(struct platform_device *pd)
+{
+	if (gfxs_ctl) {
+		device_remove_file(&pd->dev, &gfxs_ctl->attr);
+
+		kfree(gfxs_ctl);
+		gfxs_ctl = NULL;
+	}
+}
+
+/* High speed charging function */
+static struct device_attribute *hsc_handle;
+
+static ssize_t sony_nc_highspeed_charging_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(0x0131, value << 0x10 | 0x0200, &result))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_highspeed_charging_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0131, 0x0100, &result))
+		return -EIO;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01);
+}
+
+static int sony_nc_highspeed_charging_setup(struct platform_device *pd)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0131, 0x0000, &result) || !(result & 0x01)) {
+		/* some models advertise the handle but have no implementation
+		 * for it
+		 */
+		pr_info("No High Speed Charging capability found\n");
+		return 0;
+	}
+
+	hsc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!hsc_handle)
+		return -ENOMEM;
+
+	sysfs_attr_init(&hsc_handle->attr);
+	hsc_handle->attr.name = "battery_highspeed_charging";
+	hsc_handle->attr.mode = S_IRUGO | S_IWUSR;
+	hsc_handle->show = sony_nc_highspeed_charging_show;
+	hsc_handle->store = sony_nc_highspeed_charging_store;
+
+	result = device_create_file(&pd->dev, hsc_handle);
+	if (result) {
+		kfree(hsc_handle);
+		hsc_handle = NULL;
+		return result;
+	}
+
+	return 0;
+}
+
+static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd)
+{
+	if (hsc_handle) {
+		device_remove_file(&pd->dev, hsc_handle);
+		kfree(hsc_handle);
+		hsc_handle = NULL;
+	}
+}
+
+/* low battery function */
+static struct device_attribute *lowbatt_handle;
+
+static ssize_t sony_nc_lowbatt_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(0x0121, value << 8, &result))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_lowbatt_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0121, 0x0200, &result))
+		return -EIO;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", result & 1);
+}
+
+static int sony_nc_lowbatt_setup(struct platform_device *pd)
+{
+	unsigned int result;
+
+	lowbatt_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!lowbatt_handle)
+		return -ENOMEM;
+
+	sysfs_attr_init(&lowbatt_handle->attr);
+	lowbatt_handle->attr.name = "lowbatt_hibernate";
+	lowbatt_handle->attr.mode = S_IRUGO | S_IWUSR;
+	lowbatt_handle->show = sony_nc_lowbatt_show;
+	lowbatt_handle->store = sony_nc_lowbatt_store;
+
+	result = device_create_file(&pd->dev, lowbatt_handle);
+	if (result) {
+		kfree(lowbatt_handle);
+		lowbatt_handle = NULL;
+		return result;
+	}
+
+	return 0;
+}
+
+static void sony_nc_lowbatt_cleanup(struct platform_device *pd)
+{
+	if (lowbatt_handle) {
+		device_remove_file(&pd->dev, lowbatt_handle);
+		kfree(lowbatt_handle);
+		lowbatt_handle = NULL;
+	}
+}
+
+/* fan speed function */
+static struct device_attribute *fan_handle, *hsf_handle;
+
+static ssize_t sony_nc_hsfan_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(0x0149, value << 0x10 | 0x0200, &result))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_hsfan_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0149, 0x0100, &result))
+		return -EIO;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01);
+}
+
+static ssize_t sony_nc_fanspeed_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0149, 0x0300, &result))
+		return -EIO;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0xff);
+}
+
+static int sony_nc_fanspeed_setup(struct platform_device *pd)
+{
+	unsigned int result;
+
+	fan_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!fan_handle)
+		return -ENOMEM;
+
+	hsf_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!hsf_handle) {
+		result = -ENOMEM;
+		goto out_hsf_handle_alloc;
+	}
+
+	sysfs_attr_init(&fan_handle->attr);
+	fan_handle->attr.name = "fanspeed";
+	fan_handle->attr.mode = S_IRUGO;
+	fan_handle->show = sony_nc_fanspeed_show;
+	fan_handle->store = NULL;
+
+	sysfs_attr_init(&hsf_handle->attr);
+	hsf_handle->attr.name = "fan_forced";
+	hsf_handle->attr.mode = S_IRUGO | S_IWUSR;
+	hsf_handle->show = sony_nc_hsfan_show;
+	hsf_handle->store = sony_nc_hsfan_store;
+
+	result = device_create_file(&pd->dev, fan_handle);
+	if (result)
+		goto out_fan_handle;
+
+	result = device_create_file(&pd->dev, hsf_handle);
+	if (result)
+		goto out_hsf_handle;
+
+	return 0;
+
+out_hsf_handle:
+	device_remove_file(&pd->dev, fan_handle);
+
+out_fan_handle:
+	kfree(hsf_handle);
+	hsf_handle = NULL;
+
+out_hsf_handle_alloc:
+	kfree(fan_handle);
+	fan_handle = NULL;
+	return result;
+}
+
+static void sony_nc_fanspeed_cleanup(struct platform_device *pd)
+{
+	if (fan_handle) {
+		device_remove_file(&pd->dev, fan_handle);
+		kfree(fan_handle);
+		fan_handle = NULL;
+	}
+	if (hsf_handle) {
+		device_remove_file(&pd->dev, hsf_handle);
+		kfree(hsf_handle);
+		hsf_handle = NULL;
+	}
+}
+
+/* USB charge function */
+static struct device_attribute *uc_handle;
+
+static ssize_t sony_nc_usb_charge_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(0x0155, value << 0x10 | 0x0100, &result))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_usb_charge_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0155, 0x0000, &result))
+		return -EIO;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01);
+}
+
+static int sony_nc_usb_charge_setup(struct platform_device *pd)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x0155, 0x0000, &result) || !(result & 0x01)) {
+		/* some models advertise the handle but have no implementation
+		 * for it
+		 */
+		pr_info("No USB Charge capability found\n");
+		return 0;
+	}
+
+	uc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!uc_handle)
+		return -ENOMEM;
+
+	sysfs_attr_init(&uc_handle->attr);
+	uc_handle->attr.name = "usb_charge";
+	uc_handle->attr.mode = S_IRUGO | S_IWUSR;
+	uc_handle->show = sony_nc_usb_charge_show;
+	uc_handle->store = sony_nc_usb_charge_store;
+
+	result = device_create_file(&pd->dev, uc_handle);
+	if (result) {
+		kfree(uc_handle);
+		uc_handle = NULL;
+		return result;
+	}
+
+	return 0;
+}
+
+static void sony_nc_usb_charge_cleanup(struct platform_device *pd)
+{
+	if (uc_handle) {
+		device_remove_file(&pd->dev, uc_handle);
+		kfree(uc_handle);
+		uc_handle = NULL;
+	}
+}
+
+/* Panel ID function */
+static struct device_attribute *panel_handle;
+
+static ssize_t sony_nc_panelid_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(0x011D, 0x0000, &result))
+		return -EIO;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", result);
+}
+
+static int sony_nc_panelid_setup(struct platform_device *pd)
+{
+	unsigned int result;
+
+	panel_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!panel_handle)
+		return -ENOMEM;
+
+	sysfs_attr_init(&panel_handle->attr);
+	panel_handle->attr.name = "panel_id";
+	panel_handle->attr.mode = S_IRUGO;
+	panel_handle->show = sony_nc_panelid_show;
+	panel_handle->store = NULL;
+
+	result = device_create_file(&pd->dev, panel_handle);
+	if (result) {
+		kfree(panel_handle);
+		panel_handle = NULL;
+		return result;
+	}
+
+	return 0;
+}
+
+static void sony_nc_panelid_cleanup(struct platform_device *pd)
+{
+	if (panel_handle) {
+		device_remove_file(&pd->dev, panel_handle);
+		kfree(panel_handle);
+		panel_handle = NULL;
+	}
+}
+
+/* smart connect function */
+static struct device_attribute *sc_handle;
+
+static ssize_t sony_nc_smart_conn_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	if (sony_call_snc_handle(0x0168, value << 0x10, &result))
+		return -EIO;
+
+	return count;
+}
+
+static int sony_nc_smart_conn_setup(struct platform_device *pd)
+{
+	unsigned int result;
+
+	sc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+	if (!sc_handle)
+		return -ENOMEM;
+
+	sysfs_attr_init(&sc_handle->attr);
+	sc_handle->attr.name = "smart_connect";
+	sc_handle->attr.mode = S_IWUSR;
+	sc_handle->show = NULL;
+	sc_handle->store = sony_nc_smart_conn_store;
+
+	result = device_create_file(&pd->dev, sc_handle);
+	if (result) {
+		kfree(sc_handle);
+		sc_handle = NULL;
+		return result;
+	}
+
+	return 0;
+}
+
+static void sony_nc_smart_conn_cleanup(struct platform_device *pd)
+{
+	if (sc_handle) {
+		device_remove_file(&pd->dev, sc_handle);
+		kfree(sc_handle);
+		sc_handle = NULL;
+	}
+}
+
+/* Touchpad enable/disable */
+struct touchpad_control {
+	struct device_attribute attr;
+	int handle;
+};
+static struct touchpad_control *tp_ctl;
+
+static ssize_t sony_nc_touchpad_store(struct device *dev,
+		struct device_attribute *attr, const char *buffer, size_t count)
+{
+	unsigned int result;
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value) || value > 1)
+		return -EINVAL;
+
+	/* sysfs: 0 disabled, 1 enabled
+	 * EC: 0 enabled, 1 disabled
+	 */
+	if (sony_call_snc_handle(tp_ctl->handle,
+				(!value << 0x10) | 0x100, &result))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t sony_nc_touchpad_show(struct device *dev,
+		struct device_attribute *attr, char *buffer)
+{
+	unsigned int result;
+
+	if (sony_call_snc_handle(tp_ctl->handle, 0x000, &result))
+		return -EINVAL;
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", !(result & 0x01));
+}
+
+static int sony_nc_touchpad_setup(struct platform_device *pd,
+		unsigned int handle)
+{
+	int ret = 0;
+
+	tp_ctl = kzalloc(sizeof(struct touchpad_control), GFP_KERNEL);
+	if (!tp_ctl)
+		return -ENOMEM;
+
+	tp_ctl->handle = handle;
+
+	sysfs_attr_init(&tp_ctl->attr.attr);
+	tp_ctl->attr.attr.name = "touchpad";
+	tp_ctl->attr.attr.mode = S_IRUGO | S_IWUSR;
+	tp_ctl->attr.show = sony_nc_touchpad_show;
+	tp_ctl->attr.store = sony_nc_touchpad_store;
+
+	ret = device_create_file(&pd->dev, &tp_ctl->attr);
+	if (ret) {
+		kfree(tp_ctl);
+		tp_ctl = NULL;
+	}
+
+	return ret;
+}
+
+static void sony_nc_touchpad_cleanup(struct platform_device *pd)
+{
+	if (tp_ctl) {
+		device_remove_file(&pd->dev, &tp_ctl->attr);
+		kfree(tp_ctl);
+		tp_ctl = NULL;
+	}
+}
+
+static void sony_nc_backlight_ng_read_limits(int handle,
+		struct sony_backlight_props *props)
+{
+	u64 offset;
+	int i;
+	int lvl_table_len = 0;
+	u8 min = 0xff, max = 0x00;
+	unsigned char buffer[32] = { 0 };
+
+	props->handle = handle;
+	props->offset = 0;
+	props->maxlvl = 0xff;
+
+	offset = sony_find_snc_handle(handle);
+
+	/* try to read the boundaries from ACPI tables, if we fail the above
+	 * defaults should be reasonable
+	 */
+	i = sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &offset, buffer,
+			32);
+	if (i < 0)
+		return;
+
+	switch (handle) {
+	case 0x012f:
+	case 0x0137:
+		lvl_table_len = 9;
+		break;
+	case 0x143:
+	case 0x14b:
+	case 0x14c:
+		lvl_table_len = 16;
+		break;
+	}
+
+	/* the buffer lists brightness levels available, brightness levels are
+	 * from position 0 to 8 in the array, other values are used by ALS
+	 * control.
+	 */
+	for (i = 0; i < lvl_table_len && i < ARRAY_SIZE(buffer); i++) {
+
+		dprintk("Brightness level: %d\n", buffer[i]);
+
+		if (!buffer[i])
+			break;
+
+		if (buffer[i] > max)
+			max = buffer[i];
+		if (buffer[i] < min)
+			min = buffer[i];
+	}
+	props->offset = min;
+	props->maxlvl = max;
+	dprintk("Brightness levels: min=%d max=%d\n", props->offset,
+			props->maxlvl);
+}
+
+static void sony_nc_backlight_setup(void)
+{
+	int max_brightness = 0;
+	const struct backlight_ops *ops = NULL;
+	struct backlight_properties props;
+
+	if (sony_find_snc_handle(0x12f) >= 0) {
+		ops = &sony_backlight_ng_ops;
+		sony_bl_props.cmd_base = 0x0100;
+		sony_nc_backlight_ng_read_limits(0x12f, &sony_bl_props);
+		max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+	} else if (sony_find_snc_handle(0x137) >= 0) {
+		ops = &sony_backlight_ng_ops;
+		sony_bl_props.cmd_base = 0x0100;
+		sony_nc_backlight_ng_read_limits(0x137, &sony_bl_props);
+		max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+	} else if (sony_find_snc_handle(0x143) >= 0) {
+		ops = &sony_backlight_ng_ops;
+		sony_bl_props.cmd_base = 0x3000;
+		sony_nc_backlight_ng_read_limits(0x143, &sony_bl_props);
+		max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+	} else if (sony_find_snc_handle(0x14b) >= 0) {
+		ops = &sony_backlight_ng_ops;
+		sony_bl_props.cmd_base = 0x3000;
+		sony_nc_backlight_ng_read_limits(0x14b, &sony_bl_props);
+		max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+	} else if (sony_find_snc_handle(0x14c) >= 0) {
+		ops = &sony_backlight_ng_ops;
+		sony_bl_props.cmd_base = 0x3000;
+		sony_nc_backlight_ng_read_limits(0x14c, &sony_bl_props);
+		max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+	} else if (acpi_has_method(sony_nc_acpi_handle, "GBRT")) {
+		ops = &sony_backlight_ops;
+		max_brightness = SONY_MAX_BRIGHTNESS - 1;
+
+	} else
+		return;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = max_brightness;
+	sony_bl_props.dev = backlight_device_register("sony", NULL,
+						      &sony_bl_props,
+						      ops, &props);
+
+	if (IS_ERR(sony_bl_props.dev)) {
+		pr_warn("unable to register backlight device\n");
+		sony_bl_props.dev = NULL;
+	} else
+		sony_bl_props.dev->props.brightness =
+			ops->get_brightness(sony_bl_props.dev);
+}
+
+static void sony_nc_backlight_cleanup(void)
+{
+	backlight_device_unregister(sony_bl_props.dev);
+}
+
+static int sony_nc_add(struct acpi_device *device)
+{
+	acpi_status status;
+	int result = 0;
+	struct sony_nc_value *item;
+
+	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;
+	}
+
+	result = sony_pf_add();
+	if (result)
+		goto outpresent;
+
+	if (debug) {
+		status = acpi_walk_namespace(ACPI_TYPE_METHOD,
+				sony_nc_acpi_handle, 1, sony_walk_callback,
+				NULL, NULL, NULL);
+		if (ACPI_FAILURE(status)) {
+			pr_warn("unable to walk acpi resources\n");
+			result = -ENODEV;
+			goto outpresent;
+		}
+	}
+
+	result = sony_laptop_setup_input(device);
+	if (result) {
+		pr_err("Unable to create input devices\n");
+		goto outplatform;
+	}
+
+	if (acpi_has_method(sony_nc_acpi_handle, "ECON")) {
+		int arg = 1;
+		if (sony_nc_int_call(sony_nc_acpi_handle, "ECON", &arg, NULL))
+			dprintk("ECON Method failed\n");
+	}
+
+	if (acpi_has_method(sony_nc_acpi_handle, "SN00")) {
+		dprintk("Doing SNC setup\n");
+		/* retrieve the available handles */
+		result = sony_nc_handles_setup(sony_pf_device);
+		if (!result)
+			sony_nc_function_setup(device, sony_pf_device);
+	}
+
+	if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
+		sony_nc_backlight_setup();
+
+	/* 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_has_method(sony_nc_acpi_handle,
+							*item->acpiget)) {
+				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_has_method(sony_nc_acpi_handle,
+							*item->acpiset)) {
+				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;
+		}
+	}
+
+	pr_info("SNC setup done.\n");
+	return 0;
+
+out_sysfs:
+	for (item = sony_nc_values; item->name; ++item) {
+		device_remove_file(&sony_pf_device->dev, &item->devattr);
+	}
+	sony_nc_backlight_cleanup();
+	sony_nc_function_cleanup(sony_pf_device);
+	sony_nc_handles_cleanup(sony_pf_device);
+
+outplatform:
+	sony_laptop_remove_input();
+
+outpresent:
+	sony_pf_remove();
+
+outwalk:
+	sony_nc_rfkill_cleanup();
+	return result;
+}
+
+static int sony_nc_remove(struct acpi_device *device)
+{
+	struct sony_nc_value *item;
+
+	sony_nc_backlight_cleanup();
+
+	sony_nc_acpi_device = NULL;
+
+	for (item = sony_nc_values; item->name; ++item) {
+		device_remove_file(&sony_pf_device->dev, &item->devattr);
+	}
+
+	sony_nc_function_cleanup(sony_pf_device);
+	sony_nc_handles_cleanup(sony_pf_device);
+	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,
+		.notify = sony_nc_notify,
+		},
+	.drv.pm = &sony_nc_pm,
+};
+
+/*********** SPIC (SNY6001) Device ***********/
+
+#define SONYPI_DEVICE_TYPE1	0x00000001
+#define SONYPI_DEVICE_TYPE2	0x00000002
+#define SONYPI_DEVICE_TYPE3	0x00000004
+
+#define SONYPI_TYPE1_OFFSET	0x04
+#define SONYPI_TYPE2_OFFSET	0x12
+#define SONYPI_TYPE3_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 sony_pic_dev {
+	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;
+	struct sonypi_eventtypes	*event_types;
+	int                             (*handle_irq)(const u8, const u8);
+	int				model;
+	u16				evport_offset;
+	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),
+};
+
+static int spic_drv_registered;
+
+/* 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 },
+	{ 0x20, SONYPI_EVENT_PKEY_P1 },
+	{ 0, 0 }
+};
+
+/* The set of possible bluetooth events */
+static struct sonypi_event sonypi_blueev[] = {
+	{ 0x55, SONYPI_EVENT_BLUETOOTH_PRESSED },
+	{ 0x59, SONYPI_EVENT_BLUETOOTH_ON },
+	{ 0x5a, SONYPI_EVENT_BLUETOOTH_OFF },
+	{ 0, 0 }
+};
+
+/* The set of possible wireless events */
+static struct sonypi_event sonypi_wlessev[] = {
+	{ 0x59, SONYPI_EVENT_IGNORE },
+	{ 0x5a, SONYPI_EVENT_IGNORE },
+	{ 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 },
+	{ 0x04, SONYPI_EVENT_ZOOM_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible thumbphrase events */
+static struct sonypi_event sonypi_thumbphraseev[] = {
+	{ 0x3a, SONYPI_EVENT_THUMBPHRASE_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible motioneye camera events */
+static struct sonypi_event sonypi_meyeev[] = {
+	{ 0x00, SONYPI_EVENT_MEYE_FACE },
+	{ 0x01, SONYPI_EVENT_MEYE_OPPOSITE },
+	{ 0, 0 }
+};
+
+/* The set of possible memorystick events */
+static struct sonypi_event sonypi_memorystickev[] = {
+	{ 0x53, SONYPI_EVENT_MEMORYSTICK_INSERT },
+	{ 0x54, SONYPI_EVENT_MEMORYSTICK_EJECT },
+	{ 0, 0 }
+};
+
+/* The set of possible battery events */
+static struct sonypi_event sonypi_batteryev[] = {
+	{ 0x20, SONYPI_EVENT_BATTERY_INSERT },
+	{ 0x30, SONYPI_EVENT_BATTERY_REMOVE },
+	{ 0, 0 }
+};
+
+/* The set of possible volume events */
+static struct sonypi_event sonypi_volumeev[] = {
+	{ 0x01, SONYPI_EVENT_VOLUME_INC_PRESSED },
+	{ 0x02, SONYPI_EVENT_VOLUME_DEC_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible brightness events */
+static struct sonypi_event sonypi_brightnessev[] = {
+	{ 0x80, SONYPI_EVENT_BRIGHTNESS_PRESSED },
+	{ 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 },
+	{ 0x05, SONYPI_PKEY_MASK, sonypi_pkeyev },
+	{ 0x05, SONYPI_ZOOM_MASK, sonypi_zoomev },
+	{ 0x05, SONYPI_CAPTURE_MASK, sonypi_captureev },
+	{ 0x05, SONYPI_PKEY_MASK, sonypi_volumeev },
+	{ 0x05, SONYPI_PKEY_MASK, sonypi_brightnessev },
+	{ 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 type3_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 Type3 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 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->model = SONYPI_DEVICE_TYPE1;
+		dev->evport_offset = SONYPI_TYPE1_OFFSET;
+		dev->event_types = type1_events;
+		goto out;
+	}
+
+	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+			PCI_DEVICE_ID_INTEL_ICH6_1, NULL);
+	if (pcidev) {
+		dev->model = SONYPI_DEVICE_TYPE2;
+		dev->evport_offset = SONYPI_TYPE2_OFFSET;
+		dev->event_types = type2_events;
+		goto out;
+	}
+
+	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+			PCI_DEVICE_ID_INTEL_ICH7_1, NULL);
+	if (pcidev) {
+		dev->model = SONYPI_DEVICE_TYPE3;
+		dev->handle_irq = type3_handle_irq;
+		dev->evport_offset = SONYPI_TYPE3_OFFSET;
+		dev->event_types = type3_events;
+		goto out;
+	}
+
+	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+			PCI_DEVICE_ID_INTEL_ICH8_4, NULL);
+	if (pcidev) {
+		dev->model = SONYPI_DEVICE_TYPE3;
+		dev->handle_irq = type3_handle_irq;
+		dev->evport_offset = SONYPI_TYPE3_OFFSET;
+		dev->event_types = type3_events;
+		goto out;
+	}
+
+	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+			PCI_DEVICE_ID_INTEL_ICH9_1, NULL);
+	if (pcidev) {
+		dev->model = SONYPI_DEVICE_TYPE3;
+		dev->handle_irq = type3_handle_irq;
+		dev->evport_offset = SONYPI_TYPE3_OFFSET;
+		dev->event_types = type3_events;
+		goto out;
+	}
+
+	/* default */
+	dev->model = SONYPI_DEVICE_TYPE2;
+	dev->evport_offset = SONYPI_TYPE2_OFFSET;
+	dev->event_types = type2_events;
+
+out:
+	pci_dev_put(pcidev);
+
+	pr_info("detected Type%d model\n",
+		dev->model == SONYPI_DEVICE_TYPE1 ? 1 :
+		dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3);
+}
+
+/* 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) {
+		pr_warn("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) {
+		pr_warn("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) {
+		pr_warn("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:
+		pr_err("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;
+	if (spic_dev.wwan_power == state)
+		return;
+	sony_pic_call2(0xB0, state);
+	sony_pic_call1(0x82);
+	spic_dev.wwan_power = state;
+}
+
+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;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	mutex_lock(&spic_dev.lock);
+	__sony_pic_set_wwanpower(value);
+	mutex_unlock(&spic_dev.lock);
+
+	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;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	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;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	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)
+{
+	return fasync_helper(fd, filp, on, &sonypi_compat.fifo_async);
+}
+
+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 */
+	unsigned long flags;
+
+	spin_lock_irqsave(&sonypi_compat.fifo_lock, flags);
+
+	if (atomic_inc_return(&sonypi_compat.open_count) == 1)
+		kfifo_reset(&sonypi_compat.fifo);
+
+	spin_unlock_irqrestore(&sonypi_compat.fifo_lock, flags);
+
+	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_out_locked(&sonypi_compat.fifo, &c, sizeof(c),
+			  &sonypi_compat.fifo_lock) == sizeof(c))) {
+		if (put_user(c, buf++))
+			return -EFAULT;
+		ret++;
+	}
+
+	if (ret > 0) {
+		struct inode *inode = file_inode(file);
+		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 long sonypi_misc_ioctl(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_bl_props.dev == NULL) {
+			ret = -EIO;
+			break;
+		}
+		if (sony_nc_int_call(sony_nc_acpi_handle, "GBRT", NULL,
+					&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_bl_props.dev == NULL) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_from_user(&val8, argp, sizeof(val8))) {
+			ret = -EFAULT;
+			break;
+		}
+		value = (val8 >> 5) + 1;
+		if (sony_nc_int_call(sony_nc_acpi_handle, "SBRT", &value,
+					NULL)) {
+			ret = -EIO;
+			break;
+		}
+		/* sync the backlight device status */
+		sony_bl_props.dev->props.brightness =
+		    sony_backlight_get_brightness(sony_bl_props.dev);
+		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,
+	.unlocked_ioctl	= sonypi_misc_ioctl,
+	.llseek		= noop_llseek,
+};
+
+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_in_locked(&sonypi_compat.fifo, (unsigned char *)&event,
+			sizeof(event), &sonypi_compat.fifo_lock);
+	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);
+	error =
+	 kfifo_alloc(&sonypi_compat.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
+	if (error) {
+		pr_err("kfifo_alloc failed\n");
+		return error;
+	}
+
+	init_waitqueue_head(&sonypi_compat.fifo_proc_list);
+
+	if (minor != -1)
+		sonypi_misc_device.minor = minor;
+	error = misc_register(&sonypi_misc_device);
+	if (error) {
+		pr_err("misc_register failed\n");
+		goto err_free_kfifo;
+	}
+	if (minor == -1)
+		pr_info("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]) {
+					pr_warn("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 {
+				pr_err("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) {
+		pr_warn("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)) {
+		pr_warn("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.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;
+		resource->res4.length = sizeof(struct acpi_resource);
+	}
+	/* 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;
+		resource->res3.length = sizeof(struct acpi_resource);
+	}
+
+	/* 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)) {
+		pr_err("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->evport_offset);
+
+	dprintk("event ([%.2x] [%.2x]) at port 0x%.4x(+0x%.2x)\n",
+			ev, data_mask, dev->cur_ioport->io1.minimum,
+			dev->evport_offset);
+
+	if (ev == 0x00 || ev == 0xff)
+		return IRQ_HANDLED;
+
+	for (i = 0; dev->event_types[i].mask; i++) {
+
+		if ((data_mask & dev->event_types[i].data) !=
+		    dev->event_types[i].data)
+			continue;
+
+		if (!(mask & dev->event_types[i].mask))
+			continue;
+
+		for (j = 0; dev->event_types[i].events[j].event; j++) {
+			if (ev == dev->event_types[i].events[j].data) {
+				device_event =
+					dev->event_types[i].events[j].event;
+				/* some events may require ignoring */
+				if (!device_event)
+					return IRQ_HANDLED;
+				goto found;
+			}
+		}
+	}
+	/* Still not able to decode the event try to pass
+	 * it over to the minidriver
+	 */
+	if (dev->handle_irq && dev->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->evport_offset);
+	return IRQ_HANDLED;
+
+found:
+	sony_laptop_report_input_event(device_event);
+	sonypi_compat_report_event(device_event);
+	return IRQ_HANDLED;
+}
+
+/*****************
+ *
+ *  ACPI driver
+ *
+ *****************/
+static int sony_pic_remove(struct acpi_device *device)
+{
+	struct sony_pic_ioport *io, *tmp_io;
+	struct sony_pic_irq *irq, *tmp_irq;
+
+	if (sony_pic_disable(device)) {
+		pr_err("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;
+
+	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) {
+		pr_err("Unable to read possible resources\n");
+		goto err_free_resources;
+	}
+
+	/* setup input devices and helper fifo */
+	result = sony_laptop_setup_input(device);
+	if (result) {
+		pr_err("Unable to create input devices\n");
+		goto err_free_resources;
+	}
+
+	result = sonypi_compat_init();
+	if (result)
+		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 Programmable 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 Programmable 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) {
+		pr_err("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,
+					0, "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) {
+		pr_err("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) {
+		pr_err("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;
+
+	pr_info("SPIC setup done.\n");
+	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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sony_pic_suspend(struct device *dev)
+{
+	if (sony_pic_disable(to_acpi_device(dev)))
+		return -ENXIO;
+	return 0;
+}
+
+static int sony_pic_resume(struct device *dev)
+{
+	sony_pic_enable(to_acpi_device(dev),
+			spic_dev.cur_ioport, spic_dev.cur_irq);
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(sony_pic_pm, sony_pic_suspend, sony_pic_resume);
+
+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,
+		},
+	.drv.pm = &sony_pic_pm,
+};
+
+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) {
+			pr_err("Unable to register SPIC driver\n");
+			goto out;
+		}
+		spic_drv_registered = 1;
+	}
+
+	result = acpi_bus_register_driver(&sony_nc_driver);
+	if (result) {
+		pr_err("Unable to register SNC driver\n");
+		goto out_unregister_pic;
+	}
+
+	return 0;
+
+out_unregister_pic:
+	if (spic_drv_registered)
+		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 (spic_drv_registered)
+		acpi_bus_unregister_driver(&sony_pic_driver);
+}
+
+module_init(sony_laptop_init);
+module_exit(sony_laptop_exit);
diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c
new file mode 100644
index 0000000..f7dade3
--- /dev/null
+++ b/drivers/platform/x86/surfacepro3_button.c
@@ -0,0 +1,216 @@
+/*
+ * power/home/volume button support for
+ * Microsoft Surface Pro 3 tablet.
+ *
+ * Copyright (c) 2015 Intel Corporation.
+ * All rights reserved.
+ *
+ * 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; version 2
+ * of the License.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/acpi.h>
+#include <acpi/button.h>
+
+#define SURFACE_BUTTON_HID		"MSHW0028"
+#define SURFACE_BUTTON_OBJ_NAME		"VGBI"
+#define SURFACE_BUTTON_DEVICE_NAME	"Surface Pro 3 Buttons"
+
+#define SURFACE_BUTTON_NOTIFY_PRESS_POWER	0xc6
+#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER	0xc7
+
+#define SURFACE_BUTTON_NOTIFY_PRESS_HOME	0xc4
+#define SURFACE_BUTTON_NOTIFY_RELEASE_HOME	0xc5
+
+#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP	0xc0
+#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP	0xc1
+
+#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN	0xc2
+#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN	0xc3
+
+ACPI_MODULE_NAME("surface pro 3 button");
+
+MODULE_AUTHOR("Chen Yu");
+MODULE_DESCRIPTION("Surface Pro3 Button Driver");
+MODULE_LICENSE("GPL v2");
+
+/*
+ * Power button, Home button, Volume buttons support is supposed to
+ * be covered by drivers/input/misc/soc_button_array.c, which is implemented
+ * according to "Windows ACPI Design Guide for SoC Platforms".
+ * However surface pro3 seems not to obey the specs, instead it uses
+ * device VGBI(MSHW0028) for dispatching the events.
+ * We choose acpi_driver rather than platform_driver/i2c_driver because
+ * although VGBI has an i2c resource connected to i2c controller, it
+ * is not embedded in any i2c controller's scope, thus neither platform_device
+ * will be created, nor i2c_client will be enumerated, we have to use
+ * acpi_driver.
+ */
+static const struct acpi_device_id surface_button_device_ids[] = {
+	{SURFACE_BUTTON_HID,    0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, surface_button_device_ids);
+
+struct surface_button {
+	unsigned int type;
+	struct input_dev *input;
+	char phys[32];			/* for input device */
+	unsigned long pushed;
+	bool suspended;
+};
+
+static void surface_button_notify(struct acpi_device *device, u32 event)
+{
+	struct surface_button *button = acpi_driver_data(device);
+	struct input_dev *input;
+	int key_code = KEY_RESERVED;
+	bool pressed = false;
+
+	switch (event) {
+	/* Power button press,release handle */
+	case SURFACE_BUTTON_NOTIFY_PRESS_POWER:
+		pressed = true;
+		/*fall through*/
+	case SURFACE_BUTTON_NOTIFY_RELEASE_POWER:
+		key_code = KEY_POWER;
+		break;
+	/* Home button press,release handle */
+	case SURFACE_BUTTON_NOTIFY_PRESS_HOME:
+		pressed = true;
+		/*fall through*/
+	case SURFACE_BUTTON_NOTIFY_RELEASE_HOME:
+		key_code = KEY_LEFTMETA;
+		break;
+	/* Volume up button press,release handle */
+	case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP:
+		pressed = true;
+		/*fall through*/
+	case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP:
+		key_code = KEY_VOLUMEUP;
+		break;
+	/* Volume down button press,release handle */
+	case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN:
+		pressed = true;
+		/*fall through*/
+	case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN:
+		key_code = KEY_VOLUMEDOWN;
+		break;
+	default:
+		dev_info_ratelimited(&device->dev,
+				  "Unsupported event [0x%x]\n", event);
+		break;
+	}
+	input = button->input;
+	if (KEY_RESERVED == key_code)
+		return;
+	if (pressed)
+		pm_wakeup_event(&device->dev, 0);
+	if (button->suspended)
+		return;
+	input_report_key(input, key_code, pressed?1:0);
+	input_sync(input);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int surface_button_suspend(struct device *dev)
+{
+	struct acpi_device *device = to_acpi_device(dev);
+	struct surface_button *button = acpi_driver_data(device);
+
+	button->suspended = true;
+	return 0;
+}
+
+static int surface_button_resume(struct device *dev)
+{
+	struct acpi_device *device = to_acpi_device(dev);
+	struct surface_button *button = acpi_driver_data(device);
+
+	button->suspended = false;
+	return 0;
+}
+#endif
+
+static int surface_button_add(struct acpi_device *device)
+{
+	struct surface_button *button;
+	struct input_dev *input;
+	const char *hid = acpi_device_hid(device);
+	char *name;
+	int error;
+
+	if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
+	    strlen(SURFACE_BUTTON_OBJ_NAME)))
+		return -ENODEV;
+
+	button = kzalloc(sizeof(struct surface_button), GFP_KERNEL);
+	if (!button)
+		return -ENOMEM;
+
+	device->driver_data = button;
+	button->input = input = input_allocate_device();
+	if (!input) {
+		error = -ENOMEM;
+		goto err_free_button;
+	}
+
+	name = acpi_device_name(device);
+	strcpy(name, SURFACE_BUTTON_DEVICE_NAME);
+	snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid);
+
+	input->name = name;
+	input->phys = button->phys;
+	input->id.bustype = BUS_HOST;
+	input->dev.parent = &device->dev;
+	input_set_capability(input, EV_KEY, KEY_POWER);
+	input_set_capability(input, EV_KEY, KEY_LEFTMETA);
+	input_set_capability(input, EV_KEY, KEY_VOLUMEUP);
+	input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN);
+
+	error = input_register_device(input);
+	if (error)
+		goto err_free_input;
+	dev_info(&device->dev,
+			"%s [%s]\n", name, acpi_device_bid(device));
+	return 0;
+
+ err_free_input:
+	input_free_device(input);
+ err_free_button:
+	kfree(button);
+	return error;
+}
+
+static int surface_button_remove(struct acpi_device *device)
+{
+	struct surface_button *button = acpi_driver_data(device);
+
+	input_unregister_device(button->input);
+	kfree(button);
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(surface_button_pm,
+		surface_button_suspend, surface_button_resume);
+
+static struct acpi_driver surface_button_driver = {
+	.name = "surface_pro3_button",
+	.class = "SurfacePro3",
+	.ids = surface_button_device_ids,
+	.ops = {
+		.add = surface_button_add,
+		.remove = surface_button_remove,
+		.notify = surface_button_notify,
+	},
+	.drv.pm = &surface_button_pm,
+};
+
+module_acpi_driver(surface_button_driver);
diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c
new file mode 100644
index 0000000..89aa976
--- /dev/null
+++ b/drivers/platform/x86/tc1100-wmi.c
@@ -0,0 +1,280 @@
+/*
+ *  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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+
+#define GUID "C364AC71-36DB-495A-8494-B439D472A505"
+
+#define TC1100_INSTANCE_WIRELESS		1
+#define TC1100_INSTANCE_JOGDIAL		2
+
+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 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_INTEGER) {
+		tmp = obj->integer.value;
+	} else {
+		tmp = 0;
+	}
+
+	if (result.length > 0)
+		kfree(result.pointer);
+
+	switch (instance) {
+	case TC1100_INSTANCE_WIRELESS:
+		*out = (tmp == 3) ? 1 : 0;
+		return 0;
+	case TC1100_INSTANCE_JOGDIAL:
+		*out = (tmp == 1) ? 0 : 1;
+		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_IRUGO | S_IWUSR, \
+	show_bool_##value, set_bool_##value);
+
+show_set_bool(wireless, TC1100_INSTANCE_WIRELESS);
+show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL);
+
+static struct attribute *tc1100_attributes[] = {
+	&dev_attr_wireless.attr,
+	&dev_attr_jogdial.attr,
+	NULL
+};
+
+static struct attribute_group tc1100_attribute_group = {
+	.attrs	= tc1100_attributes,
+};
+
+/* --------------------------------------------------------------------------
+				Driver Model
+   -------------------------------------------------------------------------- */
+
+static int __init tc1100_probe(struct platform_device *device)
+{
+	return sysfs_create_group(&device->dev.kobj, &tc1100_attribute_group);
+}
+
+
+static int tc1100_remove(struct platform_device *device)
+{
+	sysfs_remove_group(&device->dev.kobj, &tc1100_attribute_group);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int tc1100_suspend(struct device *dev)
+{
+	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 0;
+}
+
+static int tc1100_resume(struct 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 0;
+}
+
+static const struct dev_pm_ops tc1100_pm_ops = {
+	.suspend	= tc1100_suspend,
+	.resume		= tc1100_resume,
+	.freeze		= tc1100_suspend,
+	.restore	= tc1100_resume,
+};
+#endif
+
+static struct platform_driver tc1100_driver = {
+	.driver = {
+		.name = "tc1100-wmi",
+#ifdef CONFIG_PM
+		.pm = &tc1100_pm_ops,
+#endif
+	},
+	.remove = tc1100_remove,
+};
+
+static int __init tc1100_init(void)
+{
+	int error;
+
+	if (!wmi_has_guid(GUID))
+		return -ENODEV;
+
+	tc1100_device = platform_device_alloc("tc1100-wmi", -1);
+	if (!tc1100_device)
+		return -ENOMEM;
+
+	error = platform_device_add(tc1100_device);
+	if (error)
+		goto err_device_put;
+
+	error = platform_driver_probe(&tc1100_driver, tc1100_probe);
+	if (error)
+		goto err_device_del;
+
+	pr_info("HP Compaq TC1100 Tablet WMI Extras loaded\n");
+	return 0;
+
+ err_device_del:
+	platform_device_del(tc1100_device);
+ err_device_put:
+	platform_device_put(tc1100_device);
+	return error;
+}
+
+static void __exit tc1100_exit(void)
+{
+	platform_device_unregister(tc1100_device);
+	platform_driver_unregister(&tc1100_driver);
+}
+
+module_init(tc1100_init);
+module_exit(tc1100_exit);
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
new file mode 100644
index 0000000..0bed473
--- /dev/null
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -0,0 +1,9611 @@
+/*
+ *  thinkpad_acpi.c - ThinkPad ACPI Extras
+ *
+ *
+ *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
+ *  Copyright (C) 2006-2009 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define TPACPI_VERSION "0.25"
+#define TPACPI_SYSFS_VERSION 0x020700
+
+/*
+ *  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/sched.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/nvram.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.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 <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/acpi.h>
+#include <linux/pci_ids.h>
+#include <linux/thinkpad_acpi.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <asm/uaccess.h>
+#include <acpi/video.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,
+};
+
+/* Misc NVRAM-related */
+enum {
+	TP_NVRAM_LEVEL_VOLUME_MAX = 14,
+};
+
+/* ACPI HIDs */
+#define TPACPI_ACPI_IBM_HKEY_HID	"IBM0068"
+#define TPACPI_ACPI_LENOVO_HKEY_HID	"LEN0068"
+#define TPACPI_ACPI_EC_HID		"PNP0C09"
+
+/* Input IDs */
+#define TPACPI_HKEY_INPUT_PRODUCT	0x5054 /* "TP" */
+#define TPACPI_HKEY_INPUT_VERSION	0x4101
+
+/* ACPI \WGSV commands */
+enum {
+	TP_ACPI_WGSV_GET_STATE		= 0x01, /* Get state information */
+	TP_ACPI_WGSV_PWR_ON_ON_RESUME	= 0x02, /* Resume WWAN powered on */
+	TP_ACPI_WGSV_PWR_OFF_ON_RESUME	= 0x03,	/* Resume WWAN powered off */
+	TP_ACPI_WGSV_SAVE_STATE		= 0x04, /* Save state for S4/S5 */
+};
+
+/* TP_ACPI_WGSV_GET_STATE bits */
+enum {
+	TP_ACPI_WGSV_STATE_WWANEXIST	= 0x0001, /* WWAN hw available */
+	TP_ACPI_WGSV_STATE_WWANPWR	= 0x0002, /* WWAN radio enabled */
+	TP_ACPI_WGSV_STATE_WWANPWRRES	= 0x0004, /* WWAN state at resume */
+	TP_ACPI_WGSV_STATE_WWANBIOSOFF	= 0x0008, /* WWAN disabled in BIOS */
+	TP_ACPI_WGSV_STATE_BLTHEXIST	= 0x0001, /* BLTH hw available */
+	TP_ACPI_WGSV_STATE_BLTHPWR	= 0x0002, /* BLTH radio enabled */
+	TP_ACPI_WGSV_STATE_BLTHPWRRES	= 0x0004, /* BLTH state at resume */
+	TP_ACPI_WGSV_STATE_BLTHBIOSOFF	= 0x0008, /* BLTH disabled in BIOS */
+	TP_ACPI_WGSV_STATE_UWBEXIST	= 0x0010, /* UWB hw available */
+	TP_ACPI_WGSV_STATE_UWBPWR	= 0x0020, /* UWB radio enabled */
+};
+
+/* HKEY events */
+enum tpacpi_hkey_event_t {
+	/* Hotkey-related */
+	TP_HKEY_EV_HOTKEY_BASE		= 0x1001, /* first hotkey (FN+F1) */
+	TP_HKEY_EV_BRGHT_UP		= 0x1010, /* Brightness up */
+	TP_HKEY_EV_BRGHT_DOWN		= 0x1011, /* Brightness down */
+	TP_HKEY_EV_VOL_UP		= 0x1015, /* Volume up or unmute */
+	TP_HKEY_EV_VOL_DOWN		= 0x1016, /* Volume down or unmute */
+	TP_HKEY_EV_VOL_MUTE		= 0x1017, /* Mixer output mute */
+
+	/* Reasons for waking up from S3/S4 */
+	TP_HKEY_EV_WKUP_S3_UNDOCK	= 0x2304, /* undock requested, S3 */
+	TP_HKEY_EV_WKUP_S4_UNDOCK	= 0x2404, /* undock requested, S4 */
+	TP_HKEY_EV_WKUP_S3_BAYEJ	= 0x2305, /* bay ejection req, S3 */
+	TP_HKEY_EV_WKUP_S4_BAYEJ	= 0x2405, /* bay ejection req, S4 */
+	TP_HKEY_EV_WKUP_S3_BATLOW	= 0x2313, /* battery empty, S3 */
+	TP_HKEY_EV_WKUP_S4_BATLOW	= 0x2413, /* battery empty, S4 */
+
+	/* Auto-sleep after eject request */
+	TP_HKEY_EV_BAYEJ_ACK		= 0x3003, /* bay ejection complete */
+	TP_HKEY_EV_UNDOCK_ACK		= 0x4003, /* undock complete */
+
+	/* Misc bay events */
+	TP_HKEY_EV_OPTDRV_EJ		= 0x3006, /* opt. drive tray ejected */
+	TP_HKEY_EV_HOTPLUG_DOCK		= 0x4010, /* docked into hotplug dock
+						     or port replicator */
+	TP_HKEY_EV_HOTPLUG_UNDOCK	= 0x4011, /* undocked from hotplug
+						     dock or port replicator */
+
+	/* User-interface events */
+	TP_HKEY_EV_LID_CLOSE		= 0x5001, /* laptop lid closed */
+	TP_HKEY_EV_LID_OPEN		= 0x5002, /* laptop lid opened */
+	TP_HKEY_EV_TABLET_TABLET	= 0x5009, /* tablet swivel up */
+	TP_HKEY_EV_TABLET_NOTEBOOK	= 0x500a, /* tablet swivel down */
+	TP_HKEY_EV_PEN_INSERTED		= 0x500b, /* tablet pen inserted */
+	TP_HKEY_EV_PEN_REMOVED		= 0x500c, /* tablet pen removed */
+	TP_HKEY_EV_BRGHT_CHANGED	= 0x5010, /* backlight control event */
+
+	/* Key-related user-interface events */
+	TP_HKEY_EV_KEY_NUMLOCK		= 0x6000, /* NumLock key pressed */
+	TP_HKEY_EV_KEY_FN		= 0x6005, /* Fn key pressed? E420 */
+	TP_HKEY_EV_KEY_FN_ESC           = 0x6060, /* Fn+Esc key pressed X240 */
+
+	/* Thermal events */
+	TP_HKEY_EV_ALARM_BAT_HOT	= 0x6011, /* battery too hot */
+	TP_HKEY_EV_ALARM_BAT_XHOT	= 0x6012, /* battery critically hot */
+	TP_HKEY_EV_ALARM_SENSOR_HOT	= 0x6021, /* sensor too hot */
+	TP_HKEY_EV_ALARM_SENSOR_XHOT	= 0x6022, /* sensor critically hot */
+	TP_HKEY_EV_THM_TABLE_CHANGED	= 0x6030, /* thermal table changed */
+
+	/* AC-related events */
+	TP_HKEY_EV_AC_CHANGED		= 0x6040, /* AC status changed */
+
+	/* Misc */
+	TP_HKEY_EV_RFKILL_CHANGED	= 0x7000, /* rfkill switch changed */
+};
+
+/****************************************************************************
+ * 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
+
+/* Debugging printk groups */
+#define TPACPI_DBG_ALL		0xffff
+#define TPACPI_DBG_DISCLOSETASK	0x8000
+#define TPACPI_DBG_INIT		0x0001
+#define TPACPI_DBG_EXIT		0x0002
+#define TPACPI_DBG_RFKILL	0x0004
+#define TPACPI_DBG_HKEY		0x0008
+#define TPACPI_DBG_FAN		0x0010
+#define TPACPI_DBG_BRGHT	0x0020
+#define TPACPI_DBG_MIXER	0x0040
+
+#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) (struct seq_file *);
+	int (*write) (char *);
+	void (*exit) (void);
+	void (*resume) (void);
+	void (*suspend) (void);
+	void (*shutdown) (void);
+
+	struct list_head all_drivers;
+
+	struct tp_acpi_drv_struct *acpi;
+
+	struct {
+		u8 acpi_driver_registered:1;
+		u8 acpi_notify_installed:1;
+		u8 proc_created:1;
+		u8 init_called:1;
+		u8 experimental:1;
+	} flags;
+};
+
+struct ibm_init_struct {
+	char param[32];
+
+	int (*init) (struct ibm_init_struct *);
+	umode_t base_procfs_mode;
+	struct ibm_struct *data;
+};
+
+static struct {
+	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_acpimode:1;
+	u32 bright_unkfw:1;
+	u32 wan:1;
+	u32 uwb:1;
+	u32 fan_ctrl_status_undef:1;
+	u32 second_fan:1;
+	u32 beep_needs_two_args:1;
+	u32 mixer_no_level_control: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;
+	u32 has_adaptive_kbd:1;
+} tp_features;
+
+static struct {
+	u16 hotkey_mask_ff:1;
+	u16 volume_ctrl_forbidden: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;		/* 1Y = 0x5931, 0 = unknown */
+	u16 ec_model;
+	u16 bios_release;	/* 1ZETK1WW = 0x314b, 0 = unknown */
+	u16 ec_release;
+
+	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;
+
+enum led_status_t {
+	TPACPI_LED_OFF = 0,
+	TPACPI_LED_ON,
+	TPACPI_LED_BLINK,
+};
+
+/* Special LED class that can defer work */
+struct tpacpi_led_classdev {
+	struct led_classdev led_classdev;
+	struct work_struct work;
+	enum led_status_t new_state;
+	int led;
+};
+
+/* brightness level capabilities */
+static unsigned int bright_maxlvl;	/* 0 = unknown */
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+static int dbg_wlswemul;
+static bool tpacpi_wlsw_emulstate;
+static int dbg_bluetoothemul;
+static bool tpacpi_bluetooth_emulstate;
+static int dbg_wwanemul;
+static bool tpacpi_wwan_emulstate;
+static int dbg_uwbemul;
+static bool tpacpi_uwb_emulstate;
+#endif
+
+
+/*************************************************************************
+ *  Debugging helpers
+ */
+
+#define dbg_printk(a_dbg_level, format, arg...)				\
+do {									\
+	if (dbg_level & (a_dbg_level))					\
+		printk(KERN_DEBUG pr_fmt("%s: " format),		\
+		       __func__, ##arg);				\
+} while (0)
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUG
+#define vdbg_printk dbg_printk
+static const char *str_supported(int is_supported);
+#else
+static inline const char *str_supported(int is_supported) { return ""; }
+#define vdbg_printk(a_dbg_level, format, arg...)	\
+	do { if (0) no_printk(format, ##arg); } while (0)
+#endif
+
+static void tpacpi_log_usertask(const char * const what)
+{
+	printk(KERN_DEBUG pr_fmt("%s: access by process with PID %d\n"),
+	       what, task_tgid_vnr(current));
+}
+
+#define tpacpi_disclose_usertask(what, format, arg...)			\
+do {									\
+	if (unlikely((dbg_level & TPACPI_DBG_DISCLOSETASK) &&		\
+		     (tpacpi_lifecycle == TPACPI_LIFE_RUNNING))) {	\
+		printk(KERN_DEBUG pr_fmt("%s: PID %d: " format),	\
+		       what, task_tgid_vnr(current), ## arg);		\
+	}								\
+} while (0)
+
+/*
+ * Quirk handling helpers
+ *
+ * ThinkPad IDs and versions seen in the field so far
+ * are two-characters from the set [0-9A-Z], i.e. base 36.
+ *
+ * We use values well outside that range as specials.
+ */
+
+#define TPACPI_MATCH_ANY		0xffffU
+#define TPACPI_MATCH_UNKNOWN		0U
+
+/* TPID('1', 'Y') == 0x5931 */
+#define TPID(__c1, __c2) (((__c2) << 8) | (__c1))
+
+#define TPACPI_Q_IBM(__id1, __id2, __quirk)	\
+	{ .vendor = PCI_VENDOR_ID_IBM,		\
+	  .bios = TPID(__id1, __id2),		\
+	  .ec = TPACPI_MATCH_ANY,		\
+	  .quirks = (__quirk) }
+
+#define TPACPI_Q_LNV(__id1, __id2, __quirk)	\
+	{ .vendor = PCI_VENDOR_ID_LENOVO,	\
+	  .bios = TPID(__id1, __id2),		\
+	  .ec = TPACPI_MATCH_ANY,		\
+	  .quirks = (__quirk) }
+
+#define TPACPI_QEC_LNV(__id1, __id2, __quirk)	\
+	{ .vendor = PCI_VENDOR_ID_LENOVO,	\
+	  .bios = TPACPI_MATCH_ANY,		\
+	  .ec = TPID(__id1, __id2),		\
+	  .quirks = (__quirk) }
+
+struct tpacpi_quirk {
+	unsigned int vendor;
+	u16 bios;
+	u16 ec;
+	unsigned long quirks;
+};
+
+/**
+ * tpacpi_check_quirks() - search BIOS/EC version on a list
+ * @qlist:		array of &struct tpacpi_quirk
+ * @qlist_size:		number of elements in @qlist
+ *
+ * Iterates over a quirks list until one is found that matches the
+ * ThinkPad's vendor, BIOS and EC model.
+ *
+ * Returns 0 if nothing matches, otherwise returns the quirks field of
+ * the matching &struct tpacpi_quirk entry.
+ *
+ * The match criteria is: vendor, ec and bios much match.
+ */
+static unsigned long __init tpacpi_check_quirks(
+			const struct tpacpi_quirk *qlist,
+			unsigned int qlist_size)
+{
+	while (qlist_size) {
+		if ((qlist->vendor == thinkpad_id.vendor ||
+				qlist->vendor == TPACPI_MATCH_ANY) &&
+		    (qlist->bios == thinkpad_id.bios_model ||
+				qlist->bios == TPACPI_MATCH_ANY) &&
+		    (qlist->ec == thinkpad_id.ec_model ||
+				qlist->ec == TPACPI_MATCH_ANY))
+			return qlist->quirks;
+
+		qlist_size--;
+		qlist++;
+	}
+	return 0;
+}
+
+static inline bool __pure __init tpacpi_is_lenovo(void)
+{
+	return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
+}
+
+static inline bool __pure __init tpacpi_is_ibm(void)
+{
+	return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * ACPI Helpers and device model
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * ACPI basic handles
+ */
+
+static acpi_handle root_handle;
+static acpi_handle ec_handle;
+
+#define TPACPI_HANDLE(object, parent, paths...)			\
+	static acpi_handle  object##_handle;			\
+	static const acpi_handle * const object##_parent __initconst =	\
+						&parent##_handle; \
+	static char *object##_paths[] __initdata = { paths }
+
+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 */
+
+/*************************************************************************
+ * ACPI helpers
+ */
+
+static int acpi_evalf(acpi_handle handle,
+		      int *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) {
+		pr_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:
+			pr_err("acpi_evalf() called "
+			       "with invalid format character '%c'\n", c);
+			va_end(ap);
+			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 */
+		success = (status == AE_OK &&
+			   out_obj.type == ACPI_TYPE_INTEGER);
+		if (success && res)
+			*res = out_obj.integer.value;
+		break;
+	case 'v':		/* void */
+		success = status == AE_OK;
+		break;
+		/* add more types as needed */
+	default:
+		pr_err("acpi_evalf() called "
+		       "with invalid format character '%c'\n", res_type);
+		return 0;
+	}
+
+	if (!success && !quiet)
+		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
+		       method, fmt0, acpi_format_exception(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;
+}
+
+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))
+
+static void __init drv_acpi_handle_init(const char *name,
+			   acpi_handle *handle, const acpi_handle parent,
+			   char **paths, const int num_paths)
+{
+	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)) {
+			dbg_printk(TPACPI_DBG_INIT,
+				   "Found ACPI handle %s for %s\n",
+				   paths[i], name);
+			return;
+		}
+	}
+
+	vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
+		    name);
+	*handle = NULL;
+}
+
+static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
+			u32 level, void *context, void **return_value)
+{
+	struct acpi_device *dev;
+	if (!strcmp(context, "video")) {
+		if (acpi_bus_get_device(handle, &dev))
+			return AE_OK;
+		if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev)))
+			return AE_OK;
+	}
+
+	*(acpi_handle *)return_value = handle;
+
+	return AE_CTRL_TERMINATE;
+}
+
+static void __init tpacpi_acpi_handle_locate(const char *name,
+		const char *hid,
+		acpi_handle *handle)
+{
+	acpi_status status;
+	acpi_handle device_found;
+
+	BUG_ON(!name || !handle);
+	vdbg_printk(TPACPI_DBG_INIT,
+			"trying to locate ACPI handle for %s, using HID %s\n",
+			name, hid ? hid : "NULL");
+
+	memset(&device_found, 0, sizeof(device_found));
+	status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback,
+				  (void *)name, &device_found);
+
+	*handle = NULL;
+
+	if (ACPI_SUCCESS(status)) {
+		*handle = device_found;
+		dbg_printk(TPACPI_DBG_INIT,
+			   "Found ACPI handle for %s\n", name);
+	} else {
+		vdbg_printk(TPACPI_DBG_INIT,
+			    "Could not locate an ACPI handle for %s: %s\n",
+			    name, acpi_format_exception(status));
+	}
+}
+
+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) {
+		pr_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) {
+			pr_notice("another device driver is already "
+				  "handling %s events\n", ibm->name);
+		} else {
+			pr_err("acpi_install_notify_handler(%s) failed: %s\n",
+			       ibm->name, acpi_format_exception(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) {
+		pr_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) {
+		pr_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_proc_show(struct seq_file *m, void *v)
+{
+	struct ibm_struct *ibm = m->private;
+
+	if (!ibm || !ibm->read)
+		return -EINVAL;
+	return ibm->read(m);
+}
+
+static int dispatch_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, dispatch_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t dispatch_proc_write(struct file *file,
+			const char __user *userbuf,
+			size_t count, loff_t *pos)
+{
+	struct ibm_struct *ibm = PDE_DATA(file_inode(file));
+	char *kernbuf;
+	int ret;
+
+	if (!ibm || !ibm->write)
+		return -EINVAL;
+	if (count > PAGE_SIZE - 2)
+		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 const struct file_operations dispatch_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= dispatch_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= dispatch_proc_write,
+};
+
+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);
+
+#ifdef CONFIG_PM_SLEEP
+static int tpacpi_suspend_handler(struct device *dev)
+{
+	struct ibm_struct *ibm, *itmp;
+
+	list_for_each_entry_safe(ibm, itmp,
+				 &tpacpi_all_drivers,
+				 all_drivers) {
+		if (ibm->suspend)
+			(ibm->suspend)();
+	}
+
+	return 0;
+}
+
+static int tpacpi_resume_handler(struct device *dev)
+{
+	struct ibm_struct *ibm, *itmp;
+
+	list_for_each_entry_safe(ibm, itmp,
+				 &tpacpi_all_drivers,
+				 all_drivers) {
+		if (ibm->resume)
+			(ibm->resume)();
+	}
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(tpacpi_pm,
+			 tpacpi_suspend_handler, tpacpi_resume_handler);
+
+static void tpacpi_shutdown_handler(struct platform_device *pdev)
+{
+	struct ibm_struct *ibm, *itmp;
+
+	list_for_each_entry_safe(ibm, itmp,
+				 &tpacpi_all_drivers,
+				 all_drivers) {
+		if (ibm->shutdown)
+			(ibm->shutdown)();
+	}
+}
+
+static struct platform_driver tpacpi_pdriver = {
+	.driver = {
+		.name = TPACPI_DRVR_NAME,
+		.pm = &tpacpi_pm,
+	},
+	.shutdown = tpacpi_shutdown_handler,
+};
+
+static struct platform_driver tpacpi_hwmon_pdriver = {
+	.driver = {
+		.name = TPACPI_HWMON_DRVR_NAME,
+	},
+};
+
+/*************************************************************************
+ * 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;
+
+	*value = simple_strtoul(skip_spaces(buf), &endp, 0);
+	endp = skip_spaces(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))
+		pr_notice("ACPI backlight control delay disabled\n");
+}
+
+static void printk_deprecated_attribute(const char * const what,
+					const char * const details)
+{
+	tpacpi_log_usertask("deprecated sysfs attribute");
+	pr_warn("WARNING: sysfs attribute %s is deprecated and "
+		"will be removed. %s\n",
+		what, details);
+}
+
+/*************************************************************************
+ * rfkill and radio control support helpers
+ */
+
+/*
+ * ThinkPad-ACPI firmware handling model:
+ *
+ * WLSW (master wireless switch) is event-driven, and is common to all
+ * firmware-controlled radios.  It cannot be controlled, just monitored,
+ * as expected.  It overrides all radio state in firmware
+ *
+ * The kernel, a masked-off hotkey, and WLSW can change the radio state
+ * (TODO: verify how WLSW interacts with the returned radio state).
+ *
+ * The only time there are shadow radio state changes, is when
+ * masked-off hotkeys are used.
+ */
+
+/*
+ * Internal driver API for radio state:
+ *
+ * int: < 0 = error, otherwise enum tpacpi_rfkill_state
+ * bool: true means radio blocked (off)
+ */
+enum tpacpi_rfkill_state {
+	TPACPI_RFK_RADIO_OFF = 0,
+	TPACPI_RFK_RADIO_ON
+};
+
+/* rfkill switches */
+enum tpacpi_rfk_id {
+	TPACPI_RFK_BLUETOOTH_SW_ID = 0,
+	TPACPI_RFK_WWAN_SW_ID,
+	TPACPI_RFK_UWB_SW_ID,
+	TPACPI_RFK_SW_MAX
+};
+
+static const char *tpacpi_rfkill_names[] = {
+	[TPACPI_RFK_BLUETOOTH_SW_ID] = "bluetooth",
+	[TPACPI_RFK_WWAN_SW_ID] = "wwan",
+	[TPACPI_RFK_UWB_SW_ID] = "uwb",
+	[TPACPI_RFK_SW_MAX] = NULL
+};
+
+/* ThinkPad-ACPI rfkill subdriver */
+struct tpacpi_rfk {
+	struct rfkill *rfkill;
+	enum tpacpi_rfk_id id;
+	const struct tpacpi_rfk_ops *ops;
+};
+
+struct tpacpi_rfk_ops {
+	/* firmware interface */
+	int (*get_status)(void);
+	int (*set_status)(const enum tpacpi_rfkill_state);
+};
+
+static struct tpacpi_rfk *tpacpi_rfkill_switches[TPACPI_RFK_SW_MAX];
+
+/* Query FW and update rfkill sw state for a given rfkill switch */
+static int tpacpi_rfk_update_swstate(const struct tpacpi_rfk *tp_rfk)
+{
+	int status;
+
+	if (!tp_rfk)
+		return -ENODEV;
+
+	status = (tp_rfk->ops->get_status)();
+	if (status < 0)
+		return status;
+
+	rfkill_set_sw_state(tp_rfk->rfkill,
+			    (status == TPACPI_RFK_RADIO_OFF));
+
+	return status;
+}
+
+/* Query FW and update rfkill sw state for all rfkill switches */
+static void tpacpi_rfk_update_swstate_all(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < TPACPI_RFK_SW_MAX; i++)
+		tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[i]);
+}
+
+/*
+ * Sync the HW-blocking state of all rfkill switches,
+ * do notice it causes the rfkill core to schedule uevents
+ */
+static void tpacpi_rfk_update_hwblock_state(bool blocked)
+{
+	unsigned int i;
+	struct tpacpi_rfk *tp_rfk;
+
+	for (i = 0; i < TPACPI_RFK_SW_MAX; i++) {
+		tp_rfk = tpacpi_rfkill_switches[i];
+		if (tp_rfk) {
+			if (rfkill_set_hw_state(tp_rfk->rfkill,
+						blocked)) {
+				/* ignore -- we track sw block */
+			}
+		}
+	}
+}
+
+/* Call to get the WLSW state from the firmware */
+static int hotkey_get_wlsw(void);
+
+/* Call to query WLSW state and update all rfkill switches */
+static bool tpacpi_rfk_check_hwblock_state(void)
+{
+	int res = hotkey_get_wlsw();
+	int hw_blocked;
+
+	/* When unknown or unsupported, we have to assume it is unblocked */
+	if (res < 0)
+		return false;
+
+	hw_blocked = (res == TPACPI_RFK_RADIO_OFF);
+	tpacpi_rfk_update_hwblock_state(hw_blocked);
+
+	return hw_blocked;
+}
+
+static int tpacpi_rfk_hook_set_block(void *data, bool blocked)
+{
+	struct tpacpi_rfk *tp_rfk = data;
+	int res;
+
+	dbg_printk(TPACPI_DBG_RFKILL,
+		   "request to change radio state to %s\n",
+		   blocked ? "blocked" : "unblocked");
+
+	/* try to set radio state */
+	res = (tp_rfk->ops->set_status)(blocked ?
+				TPACPI_RFK_RADIO_OFF : TPACPI_RFK_RADIO_ON);
+
+	/* and update the rfkill core with whatever the FW really did */
+	tpacpi_rfk_update_swstate(tp_rfk);
+
+	return (res < 0) ? res : 0;
+}
+
+static const struct rfkill_ops tpacpi_rfk_rfkill_ops = {
+	.set_block = tpacpi_rfk_hook_set_block,
+};
+
+static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
+			const struct tpacpi_rfk_ops *tp_rfkops,
+			const enum rfkill_type rfktype,
+			const char *name,
+			const bool set_default)
+{
+	struct tpacpi_rfk *atp_rfk;
+	int res;
+	bool sw_state = false;
+	bool hw_state;
+	int sw_status;
+
+	BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
+
+	atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
+	if (atp_rfk)
+		atp_rfk->rfkill = rfkill_alloc(name,
+						&tpacpi_pdev->dev,
+						rfktype,
+						&tpacpi_rfk_rfkill_ops,
+						atp_rfk);
+	if (!atp_rfk || !atp_rfk->rfkill) {
+		pr_err("failed to allocate memory for rfkill class\n");
+		kfree(atp_rfk);
+		return -ENOMEM;
+	}
+
+	atp_rfk->id = id;
+	atp_rfk->ops = tp_rfkops;
+
+	sw_status = (tp_rfkops->get_status)();
+	if (sw_status < 0) {
+		pr_err("failed to read initial state for %s, error %d\n",
+		       name, sw_status);
+	} else {
+		sw_state = (sw_status == TPACPI_RFK_RADIO_OFF);
+		if (set_default) {
+			/* try to keep the initial state, since we ask the
+			 * firmware to preserve it across S5 in NVRAM */
+			rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
+		}
+	}
+	hw_state = tpacpi_rfk_check_hwblock_state();
+	rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
+
+	res = rfkill_register(atp_rfk->rfkill);
+	if (res < 0) {
+		pr_err("failed to register %s rfkill switch: %d\n", name, res);
+		rfkill_destroy(atp_rfk->rfkill);
+		kfree(atp_rfk);
+		return res;
+	}
+
+	tpacpi_rfkill_switches[id] = atp_rfk;
+
+	pr_info("rfkill switch %s: radio is %sblocked\n",
+		name, (sw_state || hw_state) ? "" : "un");
+	return 0;
+}
+
+static void tpacpi_destroy_rfkill(const enum tpacpi_rfk_id id)
+{
+	struct tpacpi_rfk *tp_rfk;
+
+	BUG_ON(id >= TPACPI_RFK_SW_MAX);
+
+	tp_rfk = tpacpi_rfkill_switches[id];
+	if (tp_rfk) {
+		rfkill_unregister(tp_rfk->rfkill);
+		rfkill_destroy(tp_rfk->rfkill);
+		tpacpi_rfkill_switches[id] = NULL;
+		kfree(tp_rfk);
+	}
+}
+
+static void printk_deprecated_rfkill_attribute(const char * const what)
+{
+	printk_deprecated_attribute(what,
+			"Please switch to generic rfkill before year 2010");
+}
+
+/* sysfs <radio> enable ------------------------------------------------ */
+static ssize_t tpacpi_rfk_sysfs_enable_show(const enum tpacpi_rfk_id id,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	int status;
+
+	printk_deprecated_rfkill_attribute(attr->attr.name);
+
+	/* This is in the ABI... */
+	if (tpacpi_rfk_check_hwblock_state()) {
+		status = TPACPI_RFK_RADIO_OFF;
+	} else {
+		status = tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
+		if (status < 0)
+			return status;
+	}
+
+	return snprintf(buf, PAGE_SIZE, "%d\n",
+			(status == TPACPI_RFK_RADIO_ON) ? 1 : 0);
+}
+
+static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	unsigned long t;
+	int res;
+
+	printk_deprecated_rfkill_attribute(attr->attr.name);
+
+	if (parse_strtoul(buf, 1, &t))
+		return -EINVAL;
+
+	tpacpi_disclose_usertask(attr->attr.name, "set to %ld\n", t);
+
+	/* This is in the ABI... */
+	if (tpacpi_rfk_check_hwblock_state() && !!t)
+		return -EPERM;
+
+	res = tpacpi_rfkill_switches[id]->ops->set_status((!!t) ?
+				TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF);
+	tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
+
+	return (res < 0) ? res : count;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
+{
+	if (id >= TPACPI_RFK_SW_MAX)
+		seq_printf(m, "status:\t\tnot supported\n");
+	else {
+		int status;
+
+		/* This is in the ABI... */
+		if (tpacpi_rfk_check_hwblock_state()) {
+			status = TPACPI_RFK_RADIO_OFF;
+		} else {
+			status = tpacpi_rfk_update_swstate(
+						tpacpi_rfkill_switches[id]);
+			if (status < 0)
+				return status;
+		}
+
+		seq_printf(m, "status:\t\t%s\n",
+				(status == TPACPI_RFK_RADIO_ON) ?
+					"enabled" : "disabled");
+		seq_printf(m, "commands:\tenable, disable\n");
+	}
+
+	return 0;
+}
+
+static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
+{
+	char *cmd;
+	int status = -1;
+	int res = 0;
+
+	if (id >= TPACPI_RFK_SW_MAX)
+		return -ENODEV;
+
+	while ((cmd = next_cmd(&buf))) {
+		if (strlencmp(cmd, "enable") == 0)
+			status = TPACPI_RFK_RADIO_ON;
+		else if (strlencmp(cmd, "disable") == 0)
+			status = TPACPI_RFK_RADIO_OFF;
+		else
+			return -EINVAL;
+	}
+
+	if (status != -1) {
+		tpacpi_disclose_usertask("procfs", "attempt to %s %s\n",
+				(status == TPACPI_RFK_RADIO_ON) ?
+						"enable" : "disable",
+				tpacpi_rfkill_names[id]);
+		res = (tpacpi_rfkill_switches[id]->ops->set_status)(status);
+		tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
+	}
+
+	return res;
+}
+
+/*************************************************************************
+ * 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);
+
+/* --------------------------------------------------------------------- */
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+
+/* wlsw_emulstate ------------------------------------------------------ */
+static ssize_t tpacpi_driver_wlsw_emulstate_show(struct device_driver *drv,
+						char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wlsw_emulstate);
+}
+
+static ssize_t tpacpi_driver_wlsw_emulstate_store(struct device_driver *drv,
+						const char *buf, size_t count)
+{
+	unsigned long t;
+
+	if (parse_strtoul(buf, 1, &t))
+		return -EINVAL;
+
+	if (tpacpi_wlsw_emulstate != !!t) {
+		tpacpi_wlsw_emulstate = !!t;
+		tpacpi_rfk_update_hwblock_state(!t);	/* negative logic */
+	}
+
+	return count;
+}
+
+static DRIVER_ATTR(wlsw_emulstate, S_IWUSR | S_IRUGO,
+		tpacpi_driver_wlsw_emulstate_show,
+		tpacpi_driver_wlsw_emulstate_store);
+
+/* bluetooth_emulstate ------------------------------------------------- */
+static ssize_t tpacpi_driver_bluetooth_emulstate_show(
+					struct device_driver *drv,
+					char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_bluetooth_emulstate);
+}
+
+static ssize_t tpacpi_driver_bluetooth_emulstate_store(
+					struct device_driver *drv,
+					const char *buf, size_t count)
+{
+	unsigned long t;
+
+	if (parse_strtoul(buf, 1, &t))
+		return -EINVAL;
+
+	tpacpi_bluetooth_emulstate = !!t;
+
+	return count;
+}
+
+static DRIVER_ATTR(bluetooth_emulstate, S_IWUSR | S_IRUGO,
+		tpacpi_driver_bluetooth_emulstate_show,
+		tpacpi_driver_bluetooth_emulstate_store);
+
+/* wwan_emulstate ------------------------------------------------- */
+static ssize_t tpacpi_driver_wwan_emulstate_show(
+					struct device_driver *drv,
+					char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wwan_emulstate);
+}
+
+static ssize_t tpacpi_driver_wwan_emulstate_store(
+					struct device_driver *drv,
+					const char *buf, size_t count)
+{
+	unsigned long t;
+
+	if (parse_strtoul(buf, 1, &t))
+		return -EINVAL;
+
+	tpacpi_wwan_emulstate = !!t;
+
+	return count;
+}
+
+static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO,
+		tpacpi_driver_wwan_emulstate_show,
+		tpacpi_driver_wwan_emulstate_store);
+
+/* uwb_emulstate ------------------------------------------------- */
+static ssize_t tpacpi_driver_uwb_emulstate_show(
+					struct device_driver *drv,
+					char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_uwb_emulstate);
+}
+
+static ssize_t tpacpi_driver_uwb_emulstate_store(
+					struct device_driver *drv,
+					const char *buf, size_t count)
+{
+	unsigned long t;
+
+	if (parse_strtoul(buf, 1, &t))
+		return -EINVAL;
+
+	tpacpi_uwb_emulstate = !!t;
+
+	return count;
+}
+
+static DRIVER_ATTR(uwb_emulstate, S_IWUSR | S_IRUGO,
+		tpacpi_driver_uwb_emulstate_show,
+		tpacpi_driver_uwb_emulstate_store);
+#endif
+
+/* --------------------------------------------------------------------- */
+
+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++;
+	}
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (!res && dbg_wlswemul)
+		res = driver_create_file(drv, &driver_attr_wlsw_emulstate);
+	if (!res && dbg_bluetoothemul)
+		res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
+	if (!res && dbg_wwanemul)
+		res = driver_create_file(drv, &driver_attr_wwan_emulstate);
+	if (!res && dbg_uwbemul)
+		res = driver_create_file(drv, &driver_attr_uwb_emulstate);
+#endif
+
+	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]);
+
+#ifdef THINKPAD_ACPI_DEBUGFACILITIES
+	driver_remove_file(drv, &driver_attr_wlsw_emulstate);
+	driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
+	driver_remove_file(drv, &driver_attr_wwan_emulstate);
+	driver_remove_file(drv, &driver_attr_uwb_emulstate);
+#endif
+}
+
+/*************************************************************************
+ * Firmware Data
+ */
+
+/*
+ * Table of recommended minimum BIOS versions
+ *
+ * Reasons for listing:
+ *    1. Stable BIOS, listed because the unknown amount of
+ *       bugs and bad ACPI behaviour on older versions
+ *
+ *    2. BIOS or EC fw with known bugs that trigger on Linux
+ *
+ *    3. BIOS with known reduced functionality in older versions
+ *
+ *  We recommend the latest BIOS and EC version.
+ *  We only support the latest BIOS and EC fw version as a rule.
+ *
+ *  Sources: IBM ThinkPad Public Web Documents (update changelogs),
+ *  Information from users in ThinkWiki
+ *
+ *  WARNING: we use this table also to detect that the machine is
+ *  a ThinkPad in some cases, so don't remove entries lightly.
+ */
+
+#define TPV_Q(__v, __id1, __id2, __bv1, __bv2)		\
+	{ .vendor	= (__v),			\
+	  .bios		= TPID(__id1, __id2),		\
+	  .ec		= TPACPI_MATCH_ANY,		\
+	  .quirks	= TPACPI_MATCH_ANY << 16	\
+			  | (__bv1) << 8 | (__bv2) }
+
+#define TPV_Q_X(__v, __bid1, __bid2, __bv1, __bv2,	\
+		__eid, __ev1, __ev2)			\
+	{ .vendor	= (__v),			\
+	  .bios		= TPID(__bid1, __bid2),		\
+	  .ec		= __eid,			\
+	  .quirks	= (__ev1) << 24 | (__ev2) << 16 \
+			  | (__bv1) << 8 | (__bv2) }
+
+#define TPV_QI0(__id1, __id2, __bv1, __bv2) \
+	TPV_Q(PCI_VENDOR_ID_IBM, __id1, __id2, __bv1, __bv2)
+
+/* Outdated IBM BIOSes often lack the EC id string */
+#define TPV_QI1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
+	TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, 	\
+		__bv1, __bv2, TPID(__id1, __id2),	\
+		__ev1, __ev2),				\
+	TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, 	\
+		__bv1, __bv2, TPACPI_MATCH_UNKNOWN,	\
+		__ev1, __ev2)
+
+/* Outdated IBM BIOSes often lack the EC id string */
+#define TPV_QI2(__bid1, __bid2, __bv1, __bv2,		\
+		__eid1, __eid2, __ev1, __ev2) 		\
+	TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, 	\
+		__bv1, __bv2, TPID(__eid1, __eid2),	\
+		__ev1, __ev2),				\
+	TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, 	\
+		__bv1, __bv2, TPACPI_MATCH_UNKNOWN,	\
+		__ev1, __ev2)
+
+#define TPV_QL0(__id1, __id2, __bv1, __bv2) \
+	TPV_Q(PCI_VENDOR_ID_LENOVO, __id1, __id2, __bv1, __bv2)
+
+#define TPV_QL1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
+	TPV_Q_X(PCI_VENDOR_ID_LENOVO, __id1, __id2, 	\
+		__bv1, __bv2, TPID(__id1, __id2),	\
+		__ev1, __ev2)
+
+#define TPV_QL2(__bid1, __bid2, __bv1, __bv2,		\
+		__eid1, __eid2, __ev1, __ev2) 		\
+	TPV_Q_X(PCI_VENDOR_ID_LENOVO, __bid1, __bid2, 	\
+		__bv1, __bv2, TPID(__eid1, __eid2),	\
+		__ev1, __ev2)
+
+static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
+	/*  Numeric models ------------------ */
+	/*      FW MODEL   BIOS VERS	      */
+	TPV_QI0('I', 'M',  '6', '5'),		 /* 570 */
+	TPV_QI0('I', 'U',  '2', '6'),		 /* 570E */
+	TPV_QI0('I', 'B',  '5', '4'),		 /* 600 */
+	TPV_QI0('I', 'H',  '4', '7'),		 /* 600E */
+	TPV_QI0('I', 'N',  '3', '6'),		 /* 600E */
+	TPV_QI0('I', 'T',  '5', '5'),		 /* 600X */
+	TPV_QI0('I', 'D',  '4', '8'),		 /* 770, 770E, 770ED */
+	TPV_QI0('I', 'I',  '4', '2'),		 /* 770X */
+	TPV_QI0('I', 'O',  '2', '3'),		 /* 770Z */
+
+	/* A-series ------------------------- */
+	/*      FW MODEL   BIOS VERS  EC VERS */
+	TPV_QI0('I', 'W',  '5', '9'),		 /* A20m */
+	TPV_QI0('I', 'V',  '6', '9'),		 /* A20p */
+	TPV_QI0('1', '0',  '2', '6'),		 /* A21e, A22e */
+	TPV_QI0('K', 'U',  '3', '6'),		 /* A21e */
+	TPV_QI0('K', 'X',  '3', '6'),		 /* A21m, A22m */
+	TPV_QI0('K', 'Y',  '3', '8'),		 /* A21p, A22p */
+	TPV_QI0('1', 'B',  '1', '7'),		 /* A22e */
+	TPV_QI0('1', '3',  '2', '0'),		 /* A22m */
+	TPV_QI0('1', 'E',  '7', '3'),		 /* A30/p (0) */
+	TPV_QI1('1', 'G',  '4', '1',  '1', '7'), /* A31/p (0) */
+	TPV_QI1('1', 'N',  '1', '6',  '0', '7'), /* A31/p (0) */
+
+	/* G-series ------------------------- */
+	/*      FW MODEL   BIOS VERS	      */
+	TPV_QI0('1', 'T',  'A', '6'),		 /* G40 */
+	TPV_QI0('1', 'X',  '5', '7'),		 /* G41 */
+
+	/* R-series, T-series --------------- */
+	/*      FW MODEL   BIOS VERS  EC VERS */
+	TPV_QI0('1', 'C',  'F', '0'),		 /* R30 */
+	TPV_QI0('1', 'F',  'F', '1'),		 /* R31 */
+	TPV_QI0('1', 'M',  '9', '7'),		 /* R32 */
+	TPV_QI0('1', 'O',  '6', '1'),		 /* R40 */
+	TPV_QI0('1', 'P',  '6', '5'),		 /* R40 */
+	TPV_QI0('1', 'S',  '7', '0'),		 /* R40e */
+	TPV_QI1('1', 'R',  'D', 'R',  '7', '1'), /* R50/p, R51,
+						    T40/p, T41/p, T42/p (1) */
+	TPV_QI1('1', 'V',  '7', '1',  '2', '8'), /* R50e, R51 (1) */
+	TPV_QI1('7', '8',  '7', '1',  '0', '6'), /* R51e (1) */
+	TPV_QI1('7', '6',  '6', '9',  '1', '6'), /* R52 (1) */
+	TPV_QI1('7', '0',  '6', '9',  '2', '8'), /* R52, T43 (1) */
+
+	TPV_QI0('I', 'Y',  '6', '1'),		 /* T20 */
+	TPV_QI0('K', 'Z',  '3', '4'),		 /* T21 */
+	TPV_QI0('1', '6',  '3', '2'),		 /* T22 */
+	TPV_QI1('1', 'A',  '6', '4',  '2', '3'), /* T23 (0) */
+	TPV_QI1('1', 'I',  '7', '1',  '2', '0'), /* T30 (0) */
+	TPV_QI1('1', 'Y',  '6', '5',  '2', '9'), /* T43/p (1) */
+
+	TPV_QL1('7', '9',  'E', '3',  '5', '0'), /* T60/p */
+	TPV_QL1('7', 'C',  'D', '2',  '2', '2'), /* R60, R60i */
+	TPV_QL1('7', 'E',  'D', '0',  '1', '5'), /* R60e, R60i */
+
+	/*      BIOS FW    BIOS VERS  EC FW     EC VERS */
+	TPV_QI2('1', 'W',  '9', '0',  '1', 'V', '2', '8'), /* R50e (1) */
+	TPV_QL2('7', 'I',  '3', '4',  '7', '9', '5', '0'), /* T60/p wide */
+
+	/* X-series ------------------------- */
+	/*      FW MODEL   BIOS VERS  EC VERS */
+	TPV_QI0('I', 'Z',  '9', 'D'),		 /* X20, X21 */
+	TPV_QI0('1', 'D',  '7', '0'),		 /* X22, X23, X24 */
+	TPV_QI1('1', 'K',  '4', '8',  '1', '8'), /* X30 (0) */
+	TPV_QI1('1', 'Q',  '9', '7',  '2', '3'), /* X31, X32 (0) */
+	TPV_QI1('1', 'U',  'D', '3',  'B', '2'), /* X40 (0) */
+	TPV_QI1('7', '4',  '6', '4',  '2', '7'), /* X41 (0) */
+	TPV_QI1('7', '5',  '6', '0',  '2', '0'), /* X41t (0) */
+
+	TPV_QL1('7', 'B',  'D', '7',  '4', '0'), /* X60/s */
+	TPV_QL1('7', 'J',  '3', '0',  '1', '3'), /* X60t */
+
+	/* (0) - older versions lack DMI EC fw string and functionality */
+	/* (1) - older versions known to lack functionality */
+};
+
+#undef TPV_QL1
+#undef TPV_QL0
+#undef TPV_QI2
+#undef TPV_QI1
+#undef TPV_QI0
+#undef TPV_Q_X
+#undef TPV_Q
+
+static void __init tpacpi_check_outdated_fw(void)
+{
+	unsigned long fwvers;
+	u16 ec_version, bios_version;
+
+	fwvers = tpacpi_check_quirks(tpacpi_bios_version_qtable,
+				ARRAY_SIZE(tpacpi_bios_version_qtable));
+
+	if (!fwvers)
+		return;
+
+	bios_version = fwvers & 0xffffU;
+	ec_version = (fwvers >> 16) & 0xffffU;
+
+	/* note that unknown versions are set to 0x0000 and we use that */
+	if ((bios_version > thinkpad_id.bios_release) ||
+	    (ec_version > thinkpad_id.ec_release &&
+				ec_version != TPACPI_MATCH_ANY)) {
+		/*
+		 * The changelogs would let us track down the exact
+		 * reason, but it is just too much of a pain to track
+		 * it.  We only list BIOSes that are either really
+		 * broken, or really stable to begin with, so it is
+		 * best if the user upgrades the firmware anyway.
+		 */
+		pr_warn("WARNING: Outdated ThinkPad BIOS/EC firmware\n");
+		pr_warn("WARNING: This firmware may be missing critical bug "
+			"fixes and/or important features\n");
+	}
+}
+
+static bool __init tpacpi_is_fw_known(void)
+{
+	return tpacpi_check_quirks(tpacpi_bios_version_qtable,
+			ARRAY_SIZE(tpacpi_bios_version_qtable)) != 0;
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Subdrivers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * thinkpad-acpi metadata subdriver
+ */
+
+static int thinkpad_acpi_driver_read(struct seq_file *m)
+{
+	seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
+	seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
+	return 0;
+}
+
+static struct ibm_struct thinkpad_acpi_driver_data = {
+	.name = "driver",
+	.read = thinkpad_acpi_driver_read,
+};
+
+/*************************************************************************
+ * Hotkey subdriver
+ */
+
+/*
+ * ThinkPad firmware event model
+ *
+ * The ThinkPad firmware has two main event interfaces: normal ACPI
+ * notifications (which follow the ACPI standard), and a private event
+ * interface.
+ *
+ * The private event interface also issues events for the hotkeys.  As
+ * the driver gained features, the event handling code ended up being
+ * built around the hotkey subdriver.  This will need to be refactored
+ * to a more formal event API eventually.
+ *
+ * Some "hotkeys" are actually supposed to be used as event reports,
+ * such as "brightness has changed", "volume has changed", depending on
+ * the ThinkPad model and how the firmware is operating.
+ *
+ * Unlike other classes, hotkey-class events have mask/unmask control on
+ * non-ancient firmware.  However, how it behaves changes a lot with the
+ * firmware model and version.
+ */
+
+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,
+	TP_ACPI_HOTKEYSCAN_UNK1,
+	TP_ACPI_HOTKEYSCAN_UNK2,
+	TP_ACPI_HOTKEYSCAN_UNK3,
+	TP_ACPI_HOTKEYSCAN_UNK4,
+	TP_ACPI_HOTKEYSCAN_UNK5,
+	TP_ACPI_HOTKEYSCAN_UNK6,
+	TP_ACPI_HOTKEYSCAN_UNK7,
+	TP_ACPI_HOTKEYSCAN_UNK8,
+
+	TP_ACPI_HOTKEYSCAN_MUTE2,
+	TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO,
+	TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL,
+	TP_ACPI_HOTKEYSCAN_CLOUD,
+	TP_ACPI_HOTKEYSCAN_UNK9,
+	TP_ACPI_HOTKEYSCAN_VOICE,
+	TP_ACPI_HOTKEYSCAN_UNK10,
+	TP_ACPI_HOTKEYSCAN_GESTURES,
+	TP_ACPI_HOTKEYSCAN_UNK11,
+	TP_ACPI_HOTKEYSCAN_UNK12,
+	TP_ACPI_HOTKEYSCAN_UNK13,
+	TP_ACPI_HOTKEYSCAN_CONFIG,
+	TP_ACPI_HOTKEYSCAN_NEW_TAB,
+	TP_ACPI_HOTKEYSCAN_RELOAD,
+	TP_ACPI_HOTKEYSCAN_BACK,
+	TP_ACPI_HOTKEYSCAN_MIC_DOWN,
+	TP_ACPI_HOTKEYSCAN_MIC_UP,
+	TP_ACPI_HOTKEYSCAN_MIC_CANCELLATION,
+	TP_ACPI_HOTKEYSCAN_CAMERA_MODE,
+	TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY,
+
+	/* Hotkey keymap size */
+	TPACPI_HOTKEY_MAP_LEN
+};
+
+enum {	/* Keys/events 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;
+};
+
+/* kthread for the hotkey poller */
+static struct task_struct *tpacpi_hotkey_task;
+
+/*
+ * Acquire mutex to write poller control variables as an
+ * atomic block.
+ *
+ * Increment hotkey_config_change when changing them if you
+ * want the kthread to forget old state.
+ *
+ * See HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
+ */
+static struct mutex hotkey_thread_data_mutex;
+static unsigned int hotkey_config_change;
+
+/*
+ * hotkey poller control variables
+ *
+ * Must be atomic or readers will also need to acquire mutex
+ *
+ * HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
+ * should be used only when the changes need to be taken as
+ * a block, OR when one needs to force the kthread to forget
+ * old state.
+ */
+static u32 hotkey_source_mask;		/* bit mask 0=ACPI,1=NVRAM */
+static unsigned int hotkey_poll_freq = 10; /* Hz */
+
+#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 /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+#define hotkey_source_mask 0U
+#define HOTKEY_CONFIG_CRITICAL_START
+#define HOTKEY_CONFIG_CRITICAL_END
+
+#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 u32 hotkey_orig_mask;		/* events the BIOS had enabled */
+static u32 hotkey_all_mask;		/* all events supported in fw */
+static u32 hotkey_reserved_mask;	/* events better left disabled */
+static u32 hotkey_driver_mask;		/* events needed by the driver */
+static u32 hotkey_user_mask;		/* events visible to userspace */
+static u32 hotkey_acpi_mask;		/* events enabled in firmware */
+
+static u16 *hotkey_keycode_map;
+
+static struct attribute_set *hotkey_dev_attributes;
+
+static void tpacpi_driver_event(const unsigned int hkey_event);
+static void hotkey_driver_event(const unsigned int scancode);
+static void hotkey_poll_setup(const bool may_warn);
+
+/* HKEY.MHKG() return bits */
+#define TP_HOTKEY_TABLET_MASK (1 << 3)
+
+static int hotkey_get_wlsw(void)
+{
+	int status;
+
+	if (!tp_features.hotkey_wlsw)
+		return -ENODEV;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_wlswemul)
+		return (tpacpi_wlsw_emulstate) ?
+				TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+	if (!acpi_evalf(hkey_handle, &status, "WLSW", "d"))
+		return -EIO;
+
+	return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+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;
+}
+
+/*
+ * Reads current event mask from firmware, and updates
+ * hotkey_acpi_mask accordingly.  Also resets any bits
+ * from hotkey_user_mask that are unavailable to be
+ * delivered (shadow requirement of the userspace ABI).
+ *
+ * Call with hotkey_mutex held
+ */
+static int hotkey_mask_get(void)
+{
+	if (tp_features.hotkey_mask) {
+		u32 m = 0;
+
+		if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
+			return -EIO;
+
+		hotkey_acpi_mask = m;
+	} else {
+		/* no mask support doesn't mean no event support... */
+		hotkey_acpi_mask = hotkey_all_mask;
+	}
+
+	/* sync userspace-visible mask */
+	hotkey_user_mask &= (hotkey_acpi_mask | hotkey_source_mask);
+
+	return 0;
+}
+
+static void hotkey_mask_warn_incomplete_mask(void)
+{
+	/* log only what the user can fix... */
+	const u32 wantedmask = hotkey_driver_mask &
+		~(hotkey_acpi_mask | hotkey_source_mask) &
+		(hotkey_all_mask | TPACPI_HKEY_NVRAM_KNOWN_MASK);
+
+	if (wantedmask)
+		pr_notice("required events 0x%08x not enabled!\n", wantedmask);
+}
+
+/*
+ * Set the firmware mask when supported
+ *
+ * Also calls hotkey_mask_get to update hotkey_acpi_mask.
+ *
+ * NOTE: does not set bits in hotkey_user_mask, but may reset them.
+ *
+ * Call with hotkey_mutex held
+ */
+static int hotkey_mask_set(u32 mask)
+{
+	int i;
+	int rc = 0;
+
+	const u32 fwmask = mask & ~hotkey_source_mask;
+
+	if (tp_features.hotkey_mask) {
+		for (i = 0; i < 32; i++) {
+			if (!acpi_evalf(hkey_handle,
+					NULL, "MHKM", "vdd", i + 1,
+					!!(mask & (1 << i)))) {
+				rc = -EIO;
+				break;
+			}
+		}
+	}
+
+	/*
+	 * We *must* make an inconditional call to hotkey_mask_get to
+	 * refresh hotkey_acpi_mask and update hotkey_user_mask
+	 *
+	 * Take the opportunity to also log when we cannot _enable_
+	 * a given event.
+	 */
+	if (!hotkey_mask_get() && !rc && (fwmask & ~hotkey_acpi_mask)) {
+		pr_notice("asked for hotkey mask 0x%08x, but "
+			  "firmware forced it to 0x%08x\n",
+			  fwmask, hotkey_acpi_mask);
+	}
+
+	if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
+		hotkey_mask_warn_incomplete_mask();
+
+	return rc;
+}
+
+/*
+ * Sets hotkey_user_mask and tries to set the firmware mask
+ *
+ * Call with hotkey_mutex held
+ */
+static int hotkey_user_mask_set(const u32 mask)
+{
+	int rc;
+
+	/* Give people a chance to notice they are doing something that
+	 * is bound to go boom on their users sooner or later */
+	if (!tp_warned.hotkey_mask_ff &&
+	    (mask == 0xffff || mask == 0xffffff ||
+	     mask == 0xffffffff)) {
+		tp_warned.hotkey_mask_ff = 1;
+		pr_notice("setting the hotkey mask to 0x%08x is likely "
+			  "not the best way to go about it\n", mask);
+		pr_notice("please consider using the driver defaults, "
+			  "and refer to up-to-date thinkpad-acpi "
+			  "documentation\n");
+	}
+
+	/* Try to enable what the user asked for, plus whatever we need.
+	 * this syncs everything but won't enable bits in hotkey_user_mask */
+	rc = hotkey_mask_set((mask | hotkey_driver_mask) & ~hotkey_source_mask);
+
+	/* Enable the available bits in hotkey_user_mask */
+	hotkey_user_mask = mask & (hotkey_acpi_mask | hotkey_source_mask);
+
+	return rc;
+}
+
+/*
+ * Sets the driver hotkey mask.
+ *
+ * Can be called even if the hotkey subdriver is inactive
+ */
+static int tpacpi_hotkey_driver_mask_set(const u32 mask)
+{
+	int rc;
+
+	/* Do the right thing if hotkey_init has not been called yet */
+	if (!tp_features.hotkey) {
+		hotkey_driver_mask = mask;
+		return 0;
+	}
+
+	mutex_lock(&hotkey_mutex);
+
+	HOTKEY_CONFIG_CRITICAL_START
+	hotkey_driver_mask = mask;
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+	hotkey_source_mask |= (mask & ~hotkey_all_mask);
+#endif
+	HOTKEY_CONFIG_CRITICAL_END
+
+	rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) &
+							~hotkey_source_mask);
+	hotkey_poll_setup(true);
+
+	mutex_unlock(&hotkey_mutex);
+
+	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(bool enable)
+{
+	if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", enable ? 1 : 0))
+		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);
+	}
+}
+
+/* Do NOT call without validating scancode first */
+static void tpacpi_input_send_key(const unsigned int scancode)
+{
+	const unsigned int keycode = hotkey_keycode_map[scancode];
+
+	if (keycode != KEY_RESERVED) {
+		mutex_lock(&tpacpi_inputdev_send_mutex);
+
+		input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
+		input_report_key(tpacpi_inputdev, keycode, 1);
+		input_sync(tpacpi_inputdev);
+
+		input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
+		input_report_key(tpacpi_inputdev, keycode, 0);
+		input_sync(tpacpi_inputdev);
+
+		mutex_unlock(&tpacpi_inputdev_send_mutex);
+	}
+}
+
+/* Do NOT call without validating scancode first */
+static void tpacpi_input_send_key_masked(const unsigned int scancode)
+{
+	hotkey_driver_event(scancode);
+	if (hotkey_user_mask & (1 << scancode))
+		tpacpi_input_send_key(scancode);
+}
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
+
+/* Do NOT call without validating scancode first */
+static void tpacpi_hotkey_send_key(unsigned int scancode)
+{
+	tpacpi_input_send_key_masked(scancode);
+}
+
+static void hotkey_read_nvram(struct tp_nvram_state *n, const 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 ((event_mask & (1 << __scancode)) && \
+	    oldn->__member != newn->__member) \
+		tpacpi_hotkey_send_key(__scancode); \
+} while (0)
+
+#define TPACPI_MAY_SEND_KEY(__scancode) \
+do { \
+	if (event_mask & (1 << __scancode)) \
+		tpacpi_hotkey_send_key(__scancode); \
+} while (0)
+
+static void issue_volchange(const unsigned int oldvol,
+			    const unsigned int newvol,
+			    const u32 event_mask)
+{
+	unsigned int i = oldvol;
+
+	while (i > newvol) {
+		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+		i--;
+	}
+	while (i < newvol) {
+		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+		i++;
+	}
+}
+
+static void issue_brightnesschange(const unsigned int oldbrt,
+				   const unsigned int newbrt,
+				   const u32 event_mask)
+{
+	unsigned int i = oldbrt;
+
+	while (i > newbrt) {
+		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+		i--;
+	}
+	while (i < newbrt) {
+		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+		i++;
+	}
+}
+
+static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
+					   struct tp_nvram_state *newn,
+					   const u32 event_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
+	 *
+	 * This code is supposed to duplicate the IBM firmware behaviour:
+	 * - Pressing MUTE issues mute hotkey message, even when already mute
+	 * - Pressing Volume up/down issues volume up/down hotkey messages,
+	 *   even when already at maximum or minimum volume
+	 * - The act of unmuting issues volume up/down notification,
+	 *   depending which key was used to unmute
+	 *
+	 * We are constrained to what the NVRAM can tell us, which is not much
+	 * and certainly not enough if more than one volume hotkey was pressed
+	 * since the last poll cycle.
+	 *
+	 * Just to make our life interesting, some newer Lenovo ThinkPads have
+	 * bugs in the BIOS and may fail to update volume_toggle properly.
+	 */
+	if (newn->mute) {
+		/* muted */
+		if (!oldn->mute ||
+		    oldn->volume_toggle != newn->volume_toggle ||
+		    oldn->volume_level != newn->volume_level) {
+			/* recently muted, or repeated mute keypress, or
+			 * multiple presses ending in mute */
+			issue_volchange(oldn->volume_level, newn->volume_level,
+				event_mask);
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
+		}
+	} else {
+		/* unmute */
+		if (oldn->mute) {
+			/* recently unmuted, issue 'unmute' keypress */
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+		}
+		if (oldn->volume_level != newn->volume_level) {
+			issue_volchange(oldn->volume_level, newn->volume_level,
+				event_mask);
+		} else if (oldn->volume_toggle != newn->volume_toggle) {
+			/* repeated vol up/down keypress at end of scale ? */
+			if (newn->volume_level == 0)
+				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+			else if (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX)
+				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+		}
+	}
+
+	/* handle brightness */
+	if (oldn->brightness_level != newn->brightness_level) {
+		issue_brightnesschange(oldn->brightness_level,
+				       newn->brightness_level, event_mask);
+	} else if (oldn->brightness_toggle != newn->brightness_toggle) {
+		/* repeated key presses that didn't change state */
+		if (newn->brightness_level == 0)
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+		else if (newn->brightness_level >= bright_maxlvl
+				&& !tp_features.bright_unkfw)
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+	}
+
+#undef TPACPI_COMPARE_KEY
+#undef TPACPI_MAY_SEND_KEY
+}
+
+/*
+ * Polling driver
+ *
+ * We track all events in hotkey_source_mask all the time, since
+ * most of them are edge-based.  We only issue those requested by
+ * hotkey_user_mask or hotkey_driver_mask, though.
+ */
+static int hotkey_kthread(void *data)
+{
+	struct tp_nvram_state s[2];
+	u32 poll_mask, event_mask;
+	unsigned int si, so;
+	unsigned long t;
+	unsigned int change_detector;
+	unsigned int poll_freq;
+	bool was_frozen;
+
+	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;
+	poll_mask = hotkey_source_mask;
+	event_mask = hotkey_source_mask &
+			(hotkey_driver_mask | hotkey_user_mask);
+	poll_freq = hotkey_poll_freq;
+	mutex_unlock(&hotkey_thread_data_mutex);
+	hotkey_read_nvram(&s[so], poll_mask);
+
+	while (!kthread_should_stop()) {
+		if (t == 0) {
+			if (likely(poll_freq))
+				t = 1000/poll_freq;
+			else
+				t = 100;	/* should never happen... */
+		}
+		t = msleep_interruptible(t);
+		if (unlikely(kthread_freezable_should_stop(&was_frozen)))
+			break;
+
+		if (t > 0 && !was_frozen)
+			continue;
+
+		mutex_lock(&hotkey_thread_data_mutex);
+		if (was_frozen || hotkey_config_change != change_detector) {
+			/* forget old state on thaw or config change */
+			si = so;
+			t = 0;
+			change_detector = hotkey_config_change;
+		}
+		poll_mask = hotkey_source_mask;
+		event_mask = hotkey_source_mask &
+				(hotkey_driver_mask | hotkey_user_mask);
+		poll_freq = hotkey_poll_freq;
+		mutex_unlock(&hotkey_thread_data_mutex);
+
+		if (likely(poll_mask)) {
+			hotkey_read_nvram(&s[si], poll_mask);
+			if (likely(si != so)) {
+				hotkey_compare_and_issue_event(&s[so], &s[si],
+								event_mask);
+			}
+		}
+
+		so = si;
+		si ^= 1;
+	}
+
+exit:
+	return 0;
+}
+
+/* call with hotkey_mutex held */
+static void hotkey_poll_stop_sync(void)
+{
+	if (tpacpi_hotkey_task) {
+		kthread_stop(tpacpi_hotkey_task);
+		tpacpi_hotkey_task = NULL;
+	}
+}
+
+/* call with hotkey_mutex held */
+static void hotkey_poll_setup(const bool may_warn)
+{
+	const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;
+	const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask;
+
+	if (hotkey_poll_freq > 0 &&
+	    (poll_driver_mask ||
+	     (poll_user_mask && tpacpi_inputdev->users > 0))) {
+		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;
+				pr_err("could not create kernel thread "
+				       "for hotkey polling\n");
+			}
+		}
+	} else {
+		hotkey_poll_stop_sync();
+		if (may_warn && (poll_driver_mask || poll_user_mask) &&
+		    hotkey_poll_freq == 0) {
+			pr_notice("hot keys 0x%08x and/or events 0x%08x "
+				  "require polling, which is currently "
+				  "disabled\n",
+				  poll_user_mask, poll_driver_mask);
+		}
+	}
+}
+
+static void hotkey_poll_setup_safe(const bool may_warn)
+{
+	mutex_lock(&hotkey_mutex);
+	hotkey_poll_setup(may_warn);
+	mutex_unlock(&hotkey_mutex);
+}
+
+/* call with hotkey_mutex held */
+static void hotkey_poll_set_freq(unsigned int freq)
+{
+	if (!freq)
+		hotkey_poll_stop_sync();
+
+	hotkey_poll_freq = freq;
+}
+
+#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+static void hotkey_poll_setup(const bool __unused)
+{
+}
+
+static void hotkey_poll_setup_safe(const bool __unused)
+{
+}
+
+#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+static int hotkey_inputdev_open(struct input_dev *dev)
+{
+	switch (tpacpi_lifecycle) {
+	case TPACPI_LIFE_INIT:
+	case TPACPI_LIFE_RUNNING:
+		hotkey_poll_setup_safe(false);
+		return 0;
+	case TPACPI_LIFE_EXITING:
+		return -EBUSY;
+	}
+
+	/* 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_EXITING &&
+	    !(hotkey_source_mask & hotkey_driver_mask))
+		hotkey_poll_setup_safe(false);
+}
+
+/* sysfs hotkey enable ------------------------------------------------- */
+static ssize_t hotkey_enable_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	int res, status;
+
+	printk_deprecated_attribute("hotkey_enable",
+			"Hotkey reporting is always enabled");
+
+	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;
+
+	printk_deprecated_attribute("hotkey_enable",
+			"Hotkeys can be disabled through hotkey_mask");
+
+	if (parse_strtoul(buf, 1, &t))
+		return -EINVAL;
+
+	if (t == 0)
+		return -EPERM;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(hotkey_enable);
+
+/* sysfs hotkey mask --------------------------------------------------- */
+static ssize_t hotkey_mask_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_user_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_killable(&hotkey_mutex))
+		return -ERESTARTSYS;
+
+	res = hotkey_user_mask_set(t);
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+	hotkey_poll_setup(true);
+#endif
+
+	mutex_unlock(&hotkey_mutex);
+
+	tpacpi_disclose_usertask("hotkey_mask", "set to 0x%08lx\n", t);
+
+	return (res) ? res : count;
+}
+
+static DEVICE_ATTR_RW(hotkey_mask);
+
+/* sysfs hotkey bios_enabled ------------------------------------------- */
+static ssize_t hotkey_bios_enabled_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	return sprintf(buf, "0\n");
+}
+
+static DEVICE_ATTR_RO(hotkey_bios_enabled);
+
+/* sysfs hotkey bios_mask ---------------------------------------------- */
+static ssize_t hotkey_bios_mask_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	printk_deprecated_attribute("hotkey_bios_mask",
+			"This attribute is useless.");
+	return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask);
+}
+
+static DEVICE_ATTR_RO(hotkey_bios_mask);
+
+/* 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 DEVICE_ATTR_RO(hotkey_all_mask);
+
+/* 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 DEVICE_ATTR_RO(hotkey_recommended_mask);
+
+#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;
+	u32 r_ev;
+	int rc;
+
+	if (parse_strtoul(buf, 0xffffffffUL, &t) ||
+		((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
+		return -EINVAL;
+
+	if (mutex_lock_killable(&hotkey_mutex))
+		return -ERESTARTSYS;
+
+	HOTKEY_CONFIG_CRITICAL_START
+	hotkey_source_mask = t;
+	HOTKEY_CONFIG_CRITICAL_END
+
+	rc = hotkey_mask_set((hotkey_user_mask | hotkey_driver_mask) &
+			~hotkey_source_mask);
+	hotkey_poll_setup(true);
+
+	/* check if events needed by the driver got disabled */
+	r_ev = hotkey_driver_mask & ~(hotkey_acpi_mask & hotkey_all_mask)
+		& ~hotkey_source_mask & TPACPI_HKEY_NVRAM_KNOWN_MASK;
+
+	mutex_unlock(&hotkey_mutex);
+
+	if (rc < 0)
+		pr_err("hotkey_source_mask: "
+		       "failed to update the firmware event mask!\n");
+
+	if (r_ev)
+		pr_notice("hotkey_source_mask: "
+			  "some important events were disabled: 0x%04x\n",
+			  r_ev);
+
+	tpacpi_disclose_usertask("hotkey_source_mask", "set to 0x%08lx\n", t);
+
+	return (rc < 0) ? rc : count;
+}
+
+static DEVICE_ATTR_RW(hotkey_source_mask);
+
+/* 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_killable(&hotkey_mutex))
+		return -ERESTARTSYS;
+
+	hotkey_poll_set_freq(t);
+	hotkey_poll_setup(true);
+
+	mutex_unlock(&hotkey_mutex);
+
+	tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(hotkey_poll_freq);
+
+#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;
+	res = hotkey_get_wlsw();
+	if (res < 0)
+		return res;
+
+	/* Opportunistic update */
+	tpacpi_rfk_update_hwblock_state((res == TPACPI_RFK_RADIO_OFF));
+
+	return snprintf(buf, PAGE_SIZE, "%d\n",
+			(res == TPACPI_RFK_RADIO_OFF) ? 0 : 1);
+}
+
+static DEVICE_ATTR_RO(hotkey_radio_sw);
+
+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 DEVICE_ATTR_RO(hotkey_tablet_mode);
+
+static void hotkey_tablet_mode_notify_change(void)
+{
+	if (tp_features.hotkey_tablet)
+		sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+			     "hotkey_tablet_mode");
+}
+
+/* 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 DEVICE_ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL);
+
+static void hotkey_wakeup_reason_notify_change(void)
+{
+	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 DEVICE_ATTR(wakeup_hotunplug_complete, S_IRUGO,
+		   hotkey_wakeup_hotunplug_complete_show, NULL);
+
+static void hotkey_wakeup_hotunplug_complete_notify_change(void)
+{
+	sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+		     "wakeup_hotunplug_complete");
+}
+
+/* sysfs adaptive kbd mode --------------------------------------------- */
+
+static int adaptive_keyboard_get_mode(void);
+static int adaptive_keyboard_set_mode(int new_mode);
+
+enum ADAPTIVE_KEY_MODE {
+	HOME_MODE,
+	WEB_BROWSER_MODE,
+	WEB_CONFERENCE_MODE,
+	FUNCTION_MODE,
+	LAYFLAT_MODE
+};
+
+static ssize_t adaptive_kbd_mode_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	int current_mode;
+
+	current_mode = adaptive_keyboard_get_mode();
+	if (current_mode < 0)
+		return current_mode;
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", current_mode);
+}
+
+static ssize_t adaptive_kbd_mode_store(struct device *dev,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	unsigned long t;
+	int res;
+
+	if (parse_strtoul(buf, LAYFLAT_MODE, &t))
+		return -EINVAL;
+
+	res = adaptive_keyboard_set_mode(t);
+	return (res < 0) ? res : count;
+}
+
+static DEVICE_ATTR_RW(adaptive_kbd_mode);
+
+static struct attribute *adaptive_kbd_attributes[] = {
+	&dev_attr_adaptive_kbd_mode.attr,
+	NULL
+};
+
+static const struct attribute_group adaptive_kbd_attr_group = {
+	.attrs = adaptive_kbd_attributes,
+};
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *hotkey_attributes[] __initdata = {
+	&dev_attr_hotkey_enable.attr,
+	&dev_attr_hotkey_bios_enabled.attr,
+	&dev_attr_hotkey_bios_mask.attr,
+	&dev_attr_wakeup_reason.attr,
+	&dev_attr_wakeup_hotunplug_complete.attr,
+	&dev_attr_hotkey_mask.attr,
+	&dev_attr_hotkey_all_mask.attr,
+	&dev_attr_hotkey_recommended_mask.attr,
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+	&dev_attr_hotkey_source_mask.attr,
+	&dev_attr_hotkey_poll_freq.attr,
+#endif
+};
+
+/*
+ * Sync both the hw and sw blocking state of all switches
+ */
+static void tpacpi_send_radiosw_update(void)
+{
+	int wlsw;
+
+	/*
+	 * We must sync all rfkill controllers *before* issuing any
+	 * rfkill input events, or we will race the rfkill core input
+	 * handler.
+	 *
+	 * tpacpi_inputdev_send_mutex works as a synchronization point
+	 * for the above.
+	 *
+	 * We optimize to avoid numerous calls to hotkey_get_wlsw.
+	 */
+
+	wlsw = hotkey_get_wlsw();
+
+	/* Sync hw blocking state first if it is hw-blocked */
+	if (wlsw == TPACPI_RFK_RADIO_OFF)
+		tpacpi_rfk_update_hwblock_state(true);
+
+	/* Sync sw blocking state */
+	tpacpi_rfk_update_swstate_all();
+
+	/* Sync hw blocking state last if it is hw-unblocked */
+	if (wlsw == TPACPI_RFK_RADIO_ON)
+		tpacpi_rfk_update_hwblock_state(false);
+
+	/* Issue rfkill input event for WLSW switch */
+	if (!(wlsw < 0)) {
+		mutex_lock(&tpacpi_inputdev_send_mutex);
+
+		input_report_switch(tpacpi_inputdev,
+				    SW_RFKILL_ALL, (wlsw > 0));
+		input_sync(tpacpi_inputdev);
+
+		mutex_unlock(&tpacpi_inputdev_send_mutex);
+	}
+
+	/*
+	 * this can be unconditional, as we will poll state again
+	 * if userspace uses the notify to read data
+	 */
+	hotkey_radio_sw_notify_change();
+}
+
+static void hotkey_exit(void)
+{
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+	mutex_lock(&hotkey_mutex);
+	hotkey_poll_stop_sync();
+	mutex_unlock(&hotkey_mutex);
+#endif
+
+	if (hotkey_dev_attributes)
+		delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+
+	dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY,
+		   "restoring original HKEY status and mask\n");
+	/* yes, there is a bitwise or below, we want the
+	 * functions to be called even if one of them fail */
+	if (((tp_features.hotkey_mask &&
+	      hotkey_mask_set(hotkey_orig_mask)) |
+	     hotkey_status_set(false)) != 0)
+		pr_err("failed to restore hot key mask "
+		       "to BIOS defaults\n");
+}
+
+static void __init hotkey_unmap(const unsigned int scancode)
+{
+	if (hotkey_keycode_map[scancode] != KEY_RESERVED) {
+		clear_bit(hotkey_keycode_map[scancode],
+			  tpacpi_inputdev->keybit);
+		hotkey_keycode_map[scancode] = KEY_RESERVED;
+	}
+}
+
+/*
+ * HKEY quirks:
+ *   TPACPI_HK_Q_INIMASK:	Supports FN+F3,FN+F4,FN+F12
+ */
+
+#define	TPACPI_HK_Q_INIMASK	0x0001
+
+static const struct tpacpi_quirk tpacpi_hotkey_qtable[] __initconst = {
+	TPACPI_Q_IBM('I', 'H', TPACPI_HK_Q_INIMASK), /* 600E */
+	TPACPI_Q_IBM('I', 'N', TPACPI_HK_Q_INIMASK), /* 600E */
+	TPACPI_Q_IBM('I', 'D', TPACPI_HK_Q_INIMASK), /* 770, 770E, 770ED */
+	TPACPI_Q_IBM('I', 'W', TPACPI_HK_Q_INIMASK), /* A20m */
+	TPACPI_Q_IBM('I', 'V', TPACPI_HK_Q_INIMASK), /* A20p */
+	TPACPI_Q_IBM('1', '0', TPACPI_HK_Q_INIMASK), /* A21e, A22e */
+	TPACPI_Q_IBM('K', 'U', TPACPI_HK_Q_INIMASK), /* A21e */
+	TPACPI_Q_IBM('K', 'X', TPACPI_HK_Q_INIMASK), /* A21m, A22m */
+	TPACPI_Q_IBM('K', 'Y', TPACPI_HK_Q_INIMASK), /* A21p, A22p */
+	TPACPI_Q_IBM('1', 'B', TPACPI_HK_Q_INIMASK), /* A22e */
+	TPACPI_Q_IBM('1', '3', TPACPI_HK_Q_INIMASK), /* A22m */
+	TPACPI_Q_IBM('1', 'E', TPACPI_HK_Q_INIMASK), /* A30/p (0) */
+	TPACPI_Q_IBM('1', 'C', TPACPI_HK_Q_INIMASK), /* R30 */
+	TPACPI_Q_IBM('1', 'F', TPACPI_HK_Q_INIMASK), /* R31 */
+	TPACPI_Q_IBM('I', 'Y', TPACPI_HK_Q_INIMASK), /* T20 */
+	TPACPI_Q_IBM('K', 'Z', TPACPI_HK_Q_INIMASK), /* T21 */
+	TPACPI_Q_IBM('1', '6', TPACPI_HK_Q_INIMASK), /* T22 */
+	TPACPI_Q_IBM('I', 'Z', TPACPI_HK_Q_INIMASK), /* X20, X21 */
+	TPACPI_Q_IBM('1', 'D', TPACPI_HK_Q_INIMASK), /* X22, X23, X24 */
+};
+
+typedef u16 tpacpi_keymap_entry_t;
+typedef tpacpi_keymap_entry_t tpacpi_keymap_t[TPACPI_HOTKEY_MAP_LEN];
+
+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.
+	 */
+
+	enum keymap_index {
+		TPACPI_KEYMAP_IBM_GENERIC = 0,
+		TPACPI_KEYMAP_LENOVO_GENERIC,
+	};
+
+	static const tpacpi_keymap_t tpacpi_keymaps[] __initconst = {
+	/* Generic keymap for IBM ThinkPads */
+	[TPACPI_KEYMAP_IBM_GENERIC] = {
+		/* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
+		KEY_FN_F1,	KEY_BATTERY,	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 */
+		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,
+
+		/* No assignments, only used for Adaptive keyboards. */
+		KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+		KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+		KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+		KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+		KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+		},
+
+	/* Generic keymap for Lenovo ThinkPads */
+	[TPACPI_KEYMAP_LENOVO_GENERIC] = {
+		/* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
+		KEY_FN_F1,	KEY_COFFEE,	KEY_BATTERY,	KEY_SLEEP,
+		KEY_WLAN,	KEY_CAMERA, 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 should be enabled --only-- when ACPI video
+		 * is disabled (i.e. in "vendor" mode), and are handled
+		 * in a special way by the init code */
+		KEY_BRIGHTNESSUP,	/* 0x0F: FN+HOME (brightness up) */
+		KEY_BRIGHTNESSDOWN,	/* 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,
+
+		/*
+		 * The mic mute button only sends 0x1a.  It does not
+		 * automatically mute the mic or change the mute light.
+		 */
+		KEY_MICMUTE,	/* 0x1a: Mic mute (since ?400 or so) */
+
+		/* (assignments unknown, please report if found) */
+		KEY_UNKNOWN,
+
+		/* Extra keys in use since the X240 / T440 / T540 */
+		KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_FILE,
+
+		/*
+		 * These are the adaptive keyboard keycodes for Carbon X1 2014.
+		 * The first item in this list is the Mute button which is
+		 * emitted with 0x103 through
+		 * adaptive_keyboard_hotkey_notify_hotkey() when the sound
+		 * symbol is held.
+		 * We'll need to offset those by 0x20.
+		 */
+		KEY_RESERVED,        /* Mute held, 0x103 */
+		KEY_BRIGHTNESS_MIN,  /* Backlight off */
+		KEY_RESERVED,        /* Clipping tool */
+		KEY_RESERVED,        /* Cloud */
+		KEY_RESERVED,
+		KEY_VOICECOMMAND,    /* Voice */
+		KEY_RESERVED,
+		KEY_RESERVED,        /* Gestures */
+		KEY_RESERVED,
+		KEY_RESERVED,
+		KEY_RESERVED,
+		KEY_CONFIG,          /* Settings */
+		KEY_RESERVED,        /* New tab */
+		KEY_REFRESH,         /* Reload */
+		KEY_BACK,            /* Back */
+		KEY_RESERVED,        /* Microphone down */
+		KEY_RESERVED,        /* Microphone up */
+		KEY_RESERVED,        /* Microphone cancellation */
+		KEY_RESERVED,        /* Camera mode */
+		KEY_RESERVED,        /* Rotate display, 0x116 */
+		},
+	};
+
+	static const struct tpacpi_quirk tpacpi_keymap_qtable[] __initconst = {
+		/* Generic maps (fallback) */
+		{
+		  .vendor = PCI_VENDOR_ID_IBM,
+		  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+		  .quirks = TPACPI_KEYMAP_IBM_GENERIC,
+		},
+		{
+		  .vendor = PCI_VENDOR_ID_LENOVO,
+		  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+		  .quirks = TPACPI_KEYMAP_LENOVO_GENERIC,
+		},
+	};
+
+#define TPACPI_HOTKEY_MAP_SIZE		sizeof(tpacpi_keymap_t)
+#define TPACPI_HOTKEY_MAP_TYPESIZE	sizeof(tpacpi_keymap_entry_t)
+
+	int res, i;
+	int status;
+	int hkeyv;
+	bool radiosw_state  = false;
+	bool tabletsw_state = false;
+
+	unsigned long quirks;
+	unsigned long keymap_id;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+			"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_data_mutex);
+#endif
+
+	/* hotkey not supported on 570 */
+	tp_features.hotkey = hkey_handle != NULL;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+		"hotkeys are %s\n",
+		str_supported(tp_features.hotkey));
+
+	if (!tp_features.hotkey)
+		return 1;
+
+	/*
+	 * Check if we have an adaptive keyboard, like on the
+	 * Lenovo Carbon X1 2014 (2nd Gen).
+	 */
+	if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
+		if ((hkeyv >> 8) == 2) {
+			tp_features.has_adaptive_kbd = true;
+			res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+					&adaptive_kbd_attr_group);
+			if (res)
+				goto err_exit;
+		}
+	}
+
+	quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable,
+				     ARRAY_SIZE(tpacpi_hotkey_qtable));
+
+	tpacpi_disable_brightness_delay();
+
+	/* MUST have enough space for all attributes to be added to
+	 * hotkey_dev_attributes */
+	hotkey_dev_attributes = create_attr_set(
+					ARRAY_SIZE(hotkey_attributes) + 2,
+					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 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) {
+			pr_err("unknown version of the HKEY interface: 0x%x\n",
+			       hkeyv);
+			pr_err("please report this to %s\n", TPACPI_MAIL);
+		} else {
+			/*
+			 * MHKV 0x100 in A31, R40, R40e,
+			 * T4x, X31, and later
+			 */
+			vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+				"firmware HKEY interface version: 0x%x\n",
+				hkeyv);
+
+			/* Paranoia check AND init hotkey_all_mask */
+			if (!acpi_evalf(hkey_handle, &hotkey_all_mask,
+					"MHKA", "qd")) {
+				pr_err("missing MHKA handler, "
+				       "please report this to %s\n",
+				       TPACPI_MAIL);
+				/* Fallback: pre-init for FN+F3,F4,F12 */
+				hotkey_all_mask = 0x080cU;
+			} else {
+				tp_features.hotkey_mask = 1;
+			}
+		}
+	}
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+		"hotkey masks are %s\n",
+		str_supported(tp_features.hotkey_mask));
+
+	/* Init hotkey_all_mask if not initialized yet */
+	if (!tp_features.hotkey_mask && !hotkey_all_mask &&
+	    (quirks & TPACPI_HK_Q_INIMASK))
+		hotkey_all_mask = 0x080cU;  /* FN+F12, FN+F4, FN+F3 */
+
+	/* Init hotkey_acpi_mask and hotkey_orig_mask */
+	if (tp_features.hotkey_mask) {
+		/* hotkey_source_mask *must* be zero for
+		 * the first hotkey_mask_get to return hotkey_orig_mask */
+		res = hotkey_mask_get();
+		if (res)
+			goto err_exit;
+
+		hotkey_orig_mask = hotkey_acpi_mask;
+	} else {
+		hotkey_orig_mask = hotkey_all_mask;
+		hotkey_acpi_mask = hotkey_all_mask;
+	}
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_wlswemul) {
+		tp_features.hotkey_wlsw = 1;
+		radiosw_state = !!tpacpi_wlsw_emulstate;
+		pr_info("radio switch emulation enabled\n");
+	} else
+#endif
+	/* Not all thinkpads have a hardware radio switch */
+	if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
+		tp_features.hotkey_wlsw = 1;
+		radiosw_state = !!status;
+		pr_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;
+		tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK);
+		pr_info("possible tablet mode switch found; "
+			"ThinkPad in %s mode\n",
+			(tabletsw_state) ? "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) {
+		pr_err("failed to allocate memory for key map\n");
+		res = -ENOMEM;
+		goto err_exit;
+	}
+
+	keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable,
+					ARRAY_SIZE(tpacpi_keymap_qtable));
+	BUG_ON(keymap_id >= ARRAY_SIZE(tpacpi_keymaps));
+	dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+		   "using keymap number %lu\n", keymap_id);
+
+	memcpy(hotkey_keycode_map, &tpacpi_keymaps[keymap_id],
+		TPACPI_HOTKEY_MAP_SIZE);
+
+	input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN);
+	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) {
+			input_set_capability(tpacpi_inputdev, EV_KEY,
+						hotkey_keycode_map[i]);
+		} else {
+			if (i < sizeof(hotkey_reserved_mask)*8)
+				hotkey_reserved_mask |= 1 << i;
+		}
+	}
+
+	if (tp_features.hotkey_wlsw) {
+		input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
+		input_report_switch(tpacpi_inputdev,
+				    SW_RFKILL_ALL, radiosw_state);
+	}
+	if (tp_features.hotkey_tablet) {
+		input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
+		input_report_switch(tpacpi_inputdev,
+				    SW_TABLET_MODE, tabletsw_state);
+	}
+
+	/* Do not issue duplicate brightness change events to
+	 * userspace. tpacpi_detect_brightness_capabilities() must have
+	 * been called before this point  */
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor) {
+		pr_info("This ThinkPad has standard ACPI backlight "
+			"brightness control, supported by the ACPI "
+			"video driver\n");
+		pr_notice("Disabling thinkpad-acpi brightness events "
+			  "by default...\n");
+
+		/* Disable brightness up/down on Lenovo thinkpads when
+		 * ACPI is handling them, otherwise it is plain impossible
+		 * for userspace to do something even remotely sane */
+		hotkey_reserved_mask |=
+			(1 << TP_ACPI_HOTKEYSCAN_FNHOME)
+			| (1 << TP_ACPI_HOTKEYSCAN_FNEND);
+		hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNHOME);
+		hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND);
+	}
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+	hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
+				& ~hotkey_all_mask
+				& ~hotkey_reserved_mask;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+		    "hotkey source mask 0x%08x, polling freq %u\n",
+		    hotkey_source_mask, hotkey_poll_freq);
+#endif
+
+	dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+			"enabling firmware HKEY event interface...\n");
+	res = hotkey_status_set(true);
+	if (res) {
+		hotkey_exit();
+		return res;
+	}
+	res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)
+			       | hotkey_driver_mask)
+			      & ~hotkey_source_mask);
+	if (res < 0 && res != -ENXIO) {
+		hotkey_exit();
+		return res;
+	}
+	hotkey_user_mask = (hotkey_acpi_mask | hotkey_source_mask)
+				& ~hotkey_reserved_mask;
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+		"initial masks: user=0x%08x, fw=0x%08x, poll=0x%08x\n",
+		hotkey_user_mask, hotkey_acpi_mask, hotkey_source_mask);
+
+	tpacpi_inputdev->open = &hotkey_inputdev_open;
+	tpacpi_inputdev->close = &hotkey_inputdev_close;
+
+	hotkey_poll_setup_safe(true);
+
+	return 0;
+
+err_exit:
+	delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+	sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+			&adaptive_kbd_attr_group);
+
+	hotkey_dev_attributes = NULL;
+
+	return (res < 0) ? res : 1;
+}
+
+/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser
+ * mode, Web conference mode, Function mode and Lay-flat mode.
+ * We support Home mode and Function mode currently.
+ *
+ * Will consider support rest of modes in future.
+ *
+ */
+static const int adaptive_keyboard_modes[] = {
+	HOME_MODE,
+/*	WEB_BROWSER_MODE = 2,
+	WEB_CONFERENCE_MODE = 3, */
+	FUNCTION_MODE
+};
+
+#define DFR_CHANGE_ROW			0x101
+#define DFR_SHOW_QUICKVIEW_ROW		0x102
+#define FIRST_ADAPTIVE_KEY		0x103
+#define ADAPTIVE_KEY_OFFSET		0x020
+
+/* press Fn key a while second, it will switch to Function Mode. Then
+ * release Fn key, previous mode be restored.
+ */
+static bool adaptive_keyboard_mode_is_saved;
+static int adaptive_keyboard_prev_mode;
+
+static int adaptive_keyboard_get_mode(void)
+{
+	int mode = 0;
+
+	if (!acpi_evalf(hkey_handle, &mode, "GTRW", "dd", 0)) {
+		pr_err("Cannot read adaptive keyboard mode\n");
+		return -EIO;
+	}
+
+	return mode;
+}
+
+static int adaptive_keyboard_set_mode(int new_mode)
+{
+	if (new_mode < 0 ||
+		new_mode > LAYFLAT_MODE)
+		return -EINVAL;
+
+	if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) {
+		pr_err("Cannot set adaptive keyboard mode\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int adaptive_keyboard_get_next_mode(int mode)
+{
+	size_t i;
+	size_t max_mode = ARRAY_SIZE(adaptive_keyboard_modes) - 1;
+
+	for (i = 0; i <= max_mode; i++) {
+		if (adaptive_keyboard_modes[i] == mode)
+			break;
+	}
+
+	if (i >= max_mode)
+		i = 0;
+	else
+		i++;
+
+	return adaptive_keyboard_modes[i];
+}
+
+static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode)
+{
+	int current_mode = 0;
+	int new_mode = 0;
+	int keycode;
+
+	switch (scancode) {
+	case DFR_CHANGE_ROW:
+		if (adaptive_keyboard_mode_is_saved) {
+			new_mode = adaptive_keyboard_prev_mode;
+			adaptive_keyboard_mode_is_saved = false;
+		} else {
+			current_mode = adaptive_keyboard_get_mode();
+			if (current_mode < 0)
+				return false;
+			new_mode = adaptive_keyboard_get_next_mode(
+					current_mode);
+		}
+
+		if (adaptive_keyboard_set_mode(new_mode) < 0)
+			return false;
+
+		return true;
+
+	case DFR_SHOW_QUICKVIEW_ROW:
+		current_mode = adaptive_keyboard_get_mode();
+		if (current_mode < 0)
+			return false;
+
+		adaptive_keyboard_prev_mode = current_mode;
+		adaptive_keyboard_mode_is_saved = true;
+
+		if (adaptive_keyboard_set_mode (FUNCTION_MODE) < 0)
+			return false;
+		return true;
+
+	default:
+		if (scancode < FIRST_ADAPTIVE_KEY ||
+		    scancode >= FIRST_ADAPTIVE_KEY + TPACPI_HOTKEY_MAP_LEN -
+				ADAPTIVE_KEY_OFFSET) {
+			pr_info("Unhandled adaptive keyboard key: 0x%x\n",
+					scancode);
+			return false;
+		}
+		keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + ADAPTIVE_KEY_OFFSET];
+		if (keycode != KEY_RESERVED) {
+			mutex_lock(&tpacpi_inputdev_send_mutex);
+
+			input_report_key(tpacpi_inputdev, keycode, 1);
+			input_sync(tpacpi_inputdev);
+
+			input_report_key(tpacpi_inputdev, keycode, 0);
+			input_sync(tpacpi_inputdev);
+
+			mutex_unlock(&tpacpi_inputdev_send_mutex);
+		}
+		return true;
+	}
+}
+
+static bool hotkey_notify_hotkey(const u32 hkey,
+				 bool *send_acpi_ev,
+				 bool *ignore_acpi_ev)
+{
+	/* 0x1000-0x1FFF: key presses */
+	unsigned int scancode = hkey & 0xfff;
+	*send_acpi_ev = true;
+	*ignore_acpi_ev = false;
+
+	/* HKEY event 0x1001 is scancode 0x00 */
+	if (scancode > 0 && scancode <= TPACPI_HOTKEY_MAP_LEN) {
+		scancode--;
+		if (!(hotkey_source_mask & (1 << scancode))) {
+			tpacpi_input_send_key_masked(scancode);
+			*send_acpi_ev = false;
+		} else {
+			*ignore_acpi_ev = true;
+		}
+		return true;
+	} else {
+		return adaptive_keyboard_hotkey_notify_hotkey(scancode);
+	}
+	return false;
+}
+
+static bool hotkey_notify_wakeup(const u32 hkey,
+				 bool *send_acpi_ev,
+				 bool *ignore_acpi_ev)
+{
+	/* 0x2000-0x2FFF: Wakeup reason */
+	*send_acpi_ev = true;
+	*ignore_acpi_ev = false;
+
+	switch (hkey) {
+	case TP_HKEY_EV_WKUP_S3_UNDOCK: /* suspend, undock */
+	case TP_HKEY_EV_WKUP_S4_UNDOCK: /* hibernation, undock */
+		hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK;
+		*ignore_acpi_ev = true;
+		break;
+
+	case TP_HKEY_EV_WKUP_S3_BAYEJ: /* suspend, bay eject */
+	case TP_HKEY_EV_WKUP_S4_BAYEJ: /* hibernation, bay eject */
+		hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ;
+		*ignore_acpi_ev = true;
+		break;
+
+	case TP_HKEY_EV_WKUP_S3_BATLOW: /* Battery on critical low level/S3 */
+	case TP_HKEY_EV_WKUP_S4_BATLOW: /* Battery on critical low level/S4 */
+		pr_alert("EMERGENCY WAKEUP: battery almost empty\n");
+		/* how to auto-heal: */
+		/* 2313: woke up from S3, go to S4/S5 */
+		/* 2413: woke up from S4, go to S5 */
+		break;
+
+	default:
+		return false;
+	}
+
+	if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) {
+		pr_info("woke up due to a hot-unplug request...\n");
+		hotkey_wakeup_reason_notify_change();
+	}
+	return true;
+}
+
+static bool hotkey_notify_dockevent(const u32 hkey,
+				 bool *send_acpi_ev,
+				 bool *ignore_acpi_ev)
+{
+	/* 0x4000-0x4FFF: dock-related events */
+	*send_acpi_ev = true;
+	*ignore_acpi_ev = false;
+
+	switch (hkey) {
+	case TP_HKEY_EV_UNDOCK_ACK:
+		/* ACPI undock operation completed after wakeup */
+		hotkey_autosleep_ack = 1;
+		pr_info("undocked\n");
+		hotkey_wakeup_hotunplug_complete_notify_change();
+		return true;
+
+	case TP_HKEY_EV_HOTPLUG_DOCK: /* docked to port replicator */
+		pr_info("docked into hotplug port replicator\n");
+		return true;
+	case TP_HKEY_EV_HOTPLUG_UNDOCK: /* undocked from port replicator */
+		pr_info("undocked from hotplug port replicator\n");
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static bool hotkey_notify_usrevent(const u32 hkey,
+				 bool *send_acpi_ev,
+				 bool *ignore_acpi_ev)
+{
+	/* 0x5000-0x5FFF: human interface helpers */
+	*send_acpi_ev = true;
+	*ignore_acpi_ev = false;
+
+	switch (hkey) {
+	case TP_HKEY_EV_PEN_INSERTED:  /* X61t: tablet pen inserted into bay */
+	case TP_HKEY_EV_PEN_REMOVED:   /* X61t: tablet pen removed from bay */
+		return true;
+
+	case TP_HKEY_EV_TABLET_TABLET:   /* X41t-X61t: tablet mode */
+	case TP_HKEY_EV_TABLET_NOTEBOOK: /* X41t-X61t: normal mode */
+		tpacpi_input_send_tabletsw();
+		hotkey_tablet_mode_notify_change();
+		*send_acpi_ev = false;
+		return true;
+
+	case TP_HKEY_EV_LID_CLOSE:	/* Lid closed */
+	case TP_HKEY_EV_LID_OPEN:	/* Lid opened */
+	case TP_HKEY_EV_BRGHT_CHANGED:	/* brightness changed */
+		/* do not propagate these events */
+		*ignore_acpi_ev = true;
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static void thermal_dump_all_sensors(void);
+
+static bool hotkey_notify_6xxx(const u32 hkey,
+				 bool *send_acpi_ev,
+				 bool *ignore_acpi_ev)
+{
+	bool known = true;
+
+	/* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */
+	*send_acpi_ev = true;
+	*ignore_acpi_ev = false;
+
+	switch (hkey) {
+	case TP_HKEY_EV_THM_TABLE_CHANGED:
+		pr_info("EC reports that Thermal Table has changed\n");
+		/* recommended action: do nothing, we don't have
+		 * Lenovo ATM information */
+		return true;
+	case TP_HKEY_EV_ALARM_BAT_HOT:
+		pr_crit("THERMAL ALARM: battery is too hot!\n");
+		/* recommended action: warn user through gui */
+		break;
+	case TP_HKEY_EV_ALARM_BAT_XHOT:
+		pr_alert("THERMAL EMERGENCY: battery is extremely hot!\n");
+		/* recommended action: immediate sleep/hibernate */
+		break;
+	case TP_HKEY_EV_ALARM_SENSOR_HOT:
+		pr_crit("THERMAL ALARM: "
+			"a sensor reports something is too hot!\n");
+		/* recommended action: warn user through gui, that */
+		/* some internal component is too hot */
+		break;
+	case TP_HKEY_EV_ALARM_SENSOR_XHOT:
+		pr_alert("THERMAL EMERGENCY: "
+			 "a sensor reports something is extremely hot!\n");
+		/* recommended action: immediate sleep/hibernate */
+		break;
+	case TP_HKEY_EV_AC_CHANGED:
+		/* X120e, X121e, X220, X220i, X220t, X230, T420, T420s, W520:
+		 * AC status changed; can be triggered by plugging or
+		 * unplugging AC adapter, docking or undocking. */
+
+		/* fallthrough */
+
+	case TP_HKEY_EV_KEY_NUMLOCK:
+	case TP_HKEY_EV_KEY_FN:
+	case TP_HKEY_EV_KEY_FN_ESC:
+		/* key press events, we just ignore them as long as the EC
+		 * is still reporting them in the normal keyboard stream */
+		*send_acpi_ev = false;
+		*ignore_acpi_ev = true;
+		return true;
+
+	default:
+		pr_warn("unknown possible thermal alarm or keyboard event received\n");
+		known = false;
+	}
+
+	thermal_dump_all_sensors();
+
+	return known;
+}
+
+static void hotkey_notify(struct ibm_struct *ibm, u32 event)
+{
+	u32 hkey;
+	bool send_acpi_ev;
+	bool ignore_acpi_ev;
+	bool known_ev;
+
+	if (event != 0x80) {
+		pr_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,
+					dev_name(&ibm->acpi->device->dev),
+					event, 0);
+		return;
+	}
+
+	while (1) {
+		if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
+			pr_err("failed to retrieve HKEY event\n");
+			return;
+		}
+
+		if (hkey == 0) {
+			/* queue empty */
+			return;
+		}
+
+		send_acpi_ev = true;
+		ignore_acpi_ev = false;
+
+		switch (hkey >> 12) {
+		case 1:
+			/* 0x1000-0x1FFF: key presses */
+			known_ev = hotkey_notify_hotkey(hkey, &send_acpi_ev,
+						 &ignore_acpi_ev);
+			break;
+		case 2:
+			/* 0x2000-0x2FFF: Wakeup reason */
+			known_ev = hotkey_notify_wakeup(hkey, &send_acpi_ev,
+						 &ignore_acpi_ev);
+			break;
+		case 3:
+			/* 0x3000-0x3FFF: bay-related wakeups */
+			switch (hkey) {
+			case TP_HKEY_EV_BAYEJ_ACK:
+				hotkey_autosleep_ack = 1;
+				pr_info("bay ejected\n");
+				hotkey_wakeup_hotunplug_complete_notify_change();
+				known_ev = true;
+				break;
+			case TP_HKEY_EV_OPTDRV_EJ:
+				/* FIXME: kick libata if SATA link offline */
+				known_ev = true;
+				break;
+			default:
+				known_ev = false;
+			}
+			break;
+		case 4:
+			/* 0x4000-0x4FFF: dock-related events */
+			known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev,
+						&ignore_acpi_ev);
+			break;
+		case 5:
+			/* 0x5000-0x5FFF: human interface helpers */
+			known_ev = hotkey_notify_usrevent(hkey, &send_acpi_ev,
+						 &ignore_acpi_ev);
+			break;
+		case 6:
+			/* 0x6000-0x6FFF: thermal alarms/notices and
+			 *                keyboard events */
+			known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev,
+						 &ignore_acpi_ev);
+			break;
+		case 7:
+			/* 0x7000-0x7FFF: misc */
+			if (tp_features.hotkey_wlsw &&
+					hkey == TP_HKEY_EV_RFKILL_CHANGED) {
+				tpacpi_send_radiosw_update();
+				send_acpi_ev = 0;
+				known_ev = true;
+				break;
+			}
+			/* fallthrough to default */
+		default:
+			known_ev = false;
+		}
+		if (!known_ev) {
+			pr_notice("unhandled HKEY event 0x%04x\n", hkey);
+			pr_notice("please report the conditions when this "
+				  "event happened to %s\n", TPACPI_MAIL);
+		}
+
+		/* netlink events */
+		if (!ignore_acpi_ev && send_acpi_ev) {
+			acpi_bus_generate_netlink_event(
+					ibm->acpi->device->pnp.device_class,
+					dev_name(&ibm->acpi->device->dev),
+					event, hkey);
+		}
+	}
+}
+
+static void hotkey_suspend(void)
+{
+	/* Do these on suspend, we get the events on early resume! */
+	hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;
+	hotkey_autosleep_ack = 0;
+
+	/* save previous mode of adaptive keyboard of X1 Carbon */
+	if (tp_features.has_adaptive_kbd) {
+		if (!acpi_evalf(hkey_handle, &adaptive_keyboard_prev_mode,
+					"GTRW", "dd", 0)) {
+			pr_err("Cannot read adaptive keyboard mode.\n");
+		}
+	}
+}
+
+static void hotkey_resume(void)
+{
+	tpacpi_disable_brightness_delay();
+
+	if (hotkey_status_set(true) < 0 ||
+	    hotkey_mask_set(hotkey_acpi_mask) < 0)
+		pr_err("error while attempting to reset the event "
+		       "firmware interface\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(false);
+
+	/* restore previous mode of adapive keyboard of X1 Carbon */
+	if (tp_features.has_adaptive_kbd) {
+		if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd",
+					adaptive_keyboard_prev_mode)) {
+			pr_err("Cannot set adaptive keyboard mode.\n");
+		}
+	}
+}
+
+/* procfs -------------------------------------------------------------- */
+static int hotkey_read(struct seq_file *m)
+{
+	int res, status;
+
+	if (!tp_features.hotkey) {
+		seq_printf(m, "status:\t\tnot supported\n");
+		return 0;
+	}
+
+	if (mutex_lock_killable(&hotkey_mutex))
+		return -ERESTARTSYS;
+	res = hotkey_status_get(&status);
+	if (!res)
+		res = hotkey_mask_get();
+	mutex_unlock(&hotkey_mutex);
+	if (res)
+		return res;
+
+	seq_printf(m, "status:\t\t%s\n", enabled(status, 0));
+	if (hotkey_all_mask) {
+		seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
+		seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
+	} else {
+		seq_printf(m, "mask:\t\tnot supported\n");
+		seq_printf(m, "commands:\tenable, disable, reset\n");
+	}
+
+	return 0;
+}
+
+static void hotkey_enabledisable_warn(bool enable)
+{
+	tpacpi_log_usertask("procfs hotkey enable/disable");
+	if (!WARN((tpacpi_lifecycle == TPACPI_LIFE_RUNNING || !enable),
+		  pr_fmt("hotkey enable/disable functionality has been "
+			 "removed from the driver.  "
+			 "Hotkeys are always enabled.\n")))
+		pr_err("Please remove the hotkey=enable module "
+		       "parameter, it is deprecated.  "
+		       "Hotkeys are always enabled.\n");
+}
+
+static int hotkey_write(char *buf)
+{
+	int res;
+	u32 mask;
+	char *cmd;
+
+	if (!tp_features.hotkey)
+		return -ENODEV;
+
+	if (mutex_lock_killable(&hotkey_mutex))
+		return -ERESTARTSYS;
+
+	mask = hotkey_user_mask;
+
+	res = 0;
+	while ((cmd = next_cmd(&buf))) {
+		if (strlencmp(cmd, "enable") == 0) {
+			hotkey_enabledisable_warn(1);
+		} else if (strlencmp(cmd, "disable") == 0) {
+			hotkey_enabledisable_warn(0);
+			res = -EPERM;
+		} else if (strlencmp(cmd, "reset") == 0) {
+			mask = (hotkey_all_mask | hotkey_source_mask)
+				& ~hotkey_reserved_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 (!res) {
+		tpacpi_disclose_usertask("procfs hotkey",
+			"set mask to 0x%08x\n", mask);
+		res = hotkey_user_mask_set(mask);
+	}
+
+errexit:
+	mutex_unlock(&hotkey_mutex);
+	return res;
+}
+
+static const struct acpi_device_id ibm_htk_device_ids[] = {
+	{TPACPI_ACPI_IBM_HKEY_HID, 0},
+	{TPACPI_ACPI_LENOVO_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_RESUMECTRL	= 0x04,	/* Bluetooth state at resume:
+						   0 = disable, 1 = enable */
+};
+
+enum {
+	/* ACPI \BLTH commands */
+	TP_ACPI_BLTH_GET_ULTRAPORT_ID	= 0x00, /* Get Ultraport BT ID */
+	TP_ACPI_BLTH_GET_PWR_ON_RESUME	= 0x01, /* Get power-on-resume state */
+	TP_ACPI_BLTH_PWR_ON_ON_RESUME	= 0x02, /* Resume powered on */
+	TP_ACPI_BLTH_PWR_OFF_ON_RESUME	= 0x03,	/* Resume powered off */
+	TP_ACPI_BLTH_SAVE_STATE		= 0x05, /* Save state for S4/S5 */
+};
+
+#define TPACPI_RFK_BLUETOOTH_SW_NAME	"tpacpi_bluetooth_sw"
+
+static int bluetooth_get_status(void)
+{
+	int status;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_bluetoothemul)
+		return (tpacpi_bluetooth_emulstate) ?
+		       TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+	if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+		return -EIO;
+
+	return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
+			TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int bluetooth_set_status(enum tpacpi_rfkill_state state)
+{
+	int status;
+
+	vdbg_printk(TPACPI_DBG_RFKILL,
+		"will attempt to %s bluetooth\n",
+		(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_bluetoothemul) {
+		tpacpi_bluetooth_emulstate = (state == TPACPI_RFK_RADIO_ON);
+		return 0;
+	}
+#endif
+
+	if (state == TPACPI_RFK_RADIO_ON)
+		status = TP_ACPI_BLUETOOTH_RADIOSSW
+			  | TP_ACPI_BLUETOOTH_RESUMECTRL;
+	else
+		status = 0;
+
+	if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
+		return -EIO;
+
+	return 0;
+}
+
+/* sysfs bluetooth enable ---------------------------------------------- */
+static ssize_t bluetooth_enable_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_BLUETOOTH_SW_ID,
+			attr, buf);
+}
+
+static ssize_t bluetooth_enable_store(struct device *dev,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_BLUETOOTH_SW_ID,
+				attr, buf, count);
+}
+
+static DEVICE_ATTR_RW(bluetooth_enable);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *bluetooth_attributes[] = {
+	&dev_attr_bluetooth_enable.attr,
+	NULL
+};
+
+static const struct attribute_group bluetooth_attr_group = {
+	.attrs = bluetooth_attributes,
+};
+
+static const struct tpacpi_rfk_ops bluetooth_tprfk_ops = {
+	.get_status = bluetooth_get_status,
+	.set_status = bluetooth_set_status,
+};
+
+static void bluetooth_shutdown(void)
+{
+	/* Order firmware to save current state to NVRAM */
+	if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd",
+			TP_ACPI_BLTH_SAVE_STATE))
+		pr_notice("failed to save bluetooth state to NVRAM\n");
+	else
+		vdbg_printk(TPACPI_DBG_RFKILL,
+			"bluetooth state saved to NVRAM\n");
+}
+
+static void bluetooth_exit(void)
+{
+	sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+			&bluetooth_attr_group);
+
+	tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
+
+	bluetooth_shutdown();
+}
+
+static int __init bluetooth_init(struct ibm_init_struct *iibm)
+{
+	int res;
+	int status = 0;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+			"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 | TPACPI_DBG_RFKILL,
+		"bluetooth is %s, status 0x%02x\n",
+		str_supported(tp_features.bluetooth),
+		status);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_bluetoothemul) {
+		tp_features.bluetooth = 1;
+		pr_info("bluetooth switch emulation enabled\n");
+	} else
+#endif
+	if (tp_features.bluetooth &&
+	    !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
+		/* no bluetooth hardware present in system */
+		tp_features.bluetooth = 0;
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+			   "bluetooth hardware not installed\n");
+	}
+
+	if (!tp_features.bluetooth)
+		return 1;
+
+	res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
+				&bluetooth_tprfk_ops,
+				RFKILL_TYPE_BLUETOOTH,
+				TPACPI_RFK_BLUETOOTH_SW_NAME,
+				true);
+	if (res)
+		return res;
+
+	res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+				&bluetooth_attr_group);
+	if (res) {
+		tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
+		return res;
+	}
+
+	return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int bluetooth_read(struct seq_file *m)
+{
+	return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m);
+}
+
+static int bluetooth_write(char *buf)
+{
+	return tpacpi_rfk_procfs_write(TPACPI_RFK_BLUETOOTH_SW_ID, buf);
+}
+
+static struct ibm_struct bluetooth_driver_data = {
+	.name = "bluetooth",
+	.read = bluetooth_read,
+	.write = bluetooth_write,
+	.exit = bluetooth_exit,
+	.shutdown = bluetooth_shutdown,
+};
+
+/*************************************************************************
+ * 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_RESUMECTRL	= 0x04,	/* Wan state at resume:
+						   0 = disable, 1 = enable */
+};
+
+#define TPACPI_RFK_WWAN_SW_NAME		"tpacpi_wwan_sw"
+
+static int wan_get_status(void)
+{
+	int status;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_wwanemul)
+		return (tpacpi_wwan_emulstate) ?
+		       TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+	if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+		return -EIO;
+
+	return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
+			TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int wan_set_status(enum tpacpi_rfkill_state state)
+{
+	int status;
+
+	vdbg_printk(TPACPI_DBG_RFKILL,
+		"will attempt to %s wwan\n",
+		(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_wwanemul) {
+		tpacpi_wwan_emulstate = (state == TPACPI_RFK_RADIO_ON);
+		return 0;
+	}
+#endif
+
+	if (state == TPACPI_RFK_RADIO_ON)
+		status = TP_ACPI_WANCARD_RADIOSSW
+			 | TP_ACPI_WANCARD_RESUMECTRL;
+	else
+		status = 0;
+
+	if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
+		return -EIO;
+
+	return 0;
+}
+
+/* sysfs wan enable ---------------------------------------------------- */
+static ssize_t wan_enable_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_WWAN_SW_ID,
+			attr, buf);
+}
+
+static ssize_t wan_enable_store(struct device *dev,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_WWAN_SW_ID,
+			attr, buf, count);
+}
+
+static DEVICE_ATTR(wwan_enable, S_IWUSR | S_IRUGO,
+		   wan_enable_show, wan_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *wan_attributes[] = {
+	&dev_attr_wwan_enable.attr,
+	NULL
+};
+
+static const struct attribute_group wan_attr_group = {
+	.attrs = wan_attributes,
+};
+
+static const struct tpacpi_rfk_ops wan_tprfk_ops = {
+	.get_status = wan_get_status,
+	.set_status = wan_set_status,
+};
+
+static void wan_shutdown(void)
+{
+	/* Order firmware to save current state to NVRAM */
+	if (!acpi_evalf(NULL, NULL, "\\WGSV", "vd",
+			TP_ACPI_WGSV_SAVE_STATE))
+		pr_notice("failed to save WWAN state to NVRAM\n");
+	else
+		vdbg_printk(TPACPI_DBG_RFKILL,
+			"WWAN state saved to NVRAM\n");
+}
+
+static void wan_exit(void)
+{
+	sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+		&wan_attr_group);
+
+	tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
+
+	wan_shutdown();
+}
+
+static int __init wan_init(struct ibm_init_struct *iibm)
+{
+	int res;
+	int status = 0;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+			"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 | TPACPI_DBG_RFKILL,
+		"wan is %s, status 0x%02x\n",
+		str_supported(tp_features.wan),
+		status);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_wwanemul) {
+		tp_features.wan = 1;
+		pr_info("wwan switch emulation enabled\n");
+	} else
+#endif
+	if (tp_features.wan &&
+	    !(status & TP_ACPI_WANCARD_HWPRESENT)) {
+		/* no wan hardware present in system */
+		tp_features.wan = 0;
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+			   "wan hardware not installed\n");
+	}
+
+	if (!tp_features.wan)
+		return 1;
+
+	res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
+				&wan_tprfk_ops,
+				RFKILL_TYPE_WWAN,
+				TPACPI_RFK_WWAN_SW_NAME,
+				true);
+	if (res)
+		return res;
+
+	res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+				&wan_attr_group);
+
+	if (res) {
+		tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
+		return res;
+	}
+
+	return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int wan_read(struct seq_file *m)
+{
+	return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m);
+}
+
+static int wan_write(char *buf)
+{
+	return tpacpi_rfk_procfs_write(TPACPI_RFK_WWAN_SW_ID, buf);
+}
+
+static struct ibm_struct wan_driver_data = {
+	.name = "wan",
+	.read = wan_read,
+	.write = wan_write,
+	.exit = wan_exit,
+	.shutdown = wan_shutdown,
+};
+
+/*************************************************************************
+ * UWB subdriver
+ */
+
+enum {
+	/* ACPI GUWB/SUWB bits */
+	TP_ACPI_UWB_HWPRESENT	= 0x01,	/* UWB hw available */
+	TP_ACPI_UWB_RADIOSSW	= 0x02,	/* UWB radio enabled */
+};
+
+#define TPACPI_RFK_UWB_SW_NAME	"tpacpi_uwb_sw"
+
+static int uwb_get_status(void)
+{
+	int status;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_uwbemul)
+		return (tpacpi_uwb_emulstate) ?
+		       TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+	if (!acpi_evalf(hkey_handle, &status, "GUWB", "d"))
+		return -EIO;
+
+	return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ?
+			TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int uwb_set_status(enum tpacpi_rfkill_state state)
+{
+	int status;
+
+	vdbg_printk(TPACPI_DBG_RFKILL,
+		"will attempt to %s UWB\n",
+		(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_uwbemul) {
+		tpacpi_uwb_emulstate = (state == TPACPI_RFK_RADIO_ON);
+		return 0;
+	}
+#endif
+
+	if (state == TPACPI_RFK_RADIO_ON)
+		status = TP_ACPI_UWB_RADIOSSW;
+	else
+		status = 0;
+
+	if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status))
+		return -EIO;
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static const struct tpacpi_rfk_ops uwb_tprfk_ops = {
+	.get_status = uwb_get_status,
+	.set_status = uwb_set_status,
+};
+
+static void uwb_exit(void)
+{
+	tpacpi_destroy_rfkill(TPACPI_RFK_UWB_SW_ID);
+}
+
+static int __init uwb_init(struct ibm_init_struct *iibm)
+{
+	int res;
+	int status = 0;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+			"initializing uwb subdriver\n");
+
+	TPACPI_ACPIHANDLE_INIT(hkey);
+
+	tp_features.uwb = hkey_handle &&
+	    acpi_evalf(hkey_handle, &status, "GUWB", "qd");
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+		"uwb is %s, status 0x%02x\n",
+		str_supported(tp_features.uwb),
+		status);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+	if (dbg_uwbemul) {
+		tp_features.uwb = 1;
+		pr_info("uwb switch emulation enabled\n");
+	} else
+#endif
+	if (tp_features.uwb &&
+	    !(status & TP_ACPI_UWB_HWPRESENT)) {
+		/* no uwb hardware present in system */
+		tp_features.uwb = 0;
+		dbg_printk(TPACPI_DBG_INIT,
+			   "uwb hardware not installed\n");
+	}
+
+	if (!tp_features.uwb)
+		return 1;
+
+	res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID,
+				&uwb_tprfk_ops,
+				RFKILL_TYPE_UWB,
+				TPACPI_RFK_UWB_SW_NAME,
+				false);
+	return res;
+}
+
+static struct ibm_struct uwb_driver_data = {
+	.name = "uwb",
+	.exit = uwb_exit,
+	.flags.experimental = 1,
+};
+
+/*************************************************************************
+ * 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(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.VGA",	/* X100e and a few others */
+	      "\\_SB.PCI0.AGP.VID",	/* all others */
+	);				/* R30, R31 */
+
+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);
+	if (tpacpi_is_ibm())
+		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 (tpacpi_is_ibm() &&
+		 acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
+		/* 570 */
+		video_supported = TPACPI_VIDEO_570;
+	else if (tpacpi_is_ibm() &&
+		 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))
+		pr_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)) {
+			pr_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)) {
+		pr_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(struct seq_file *m)
+{
+	int status, autosw;
+
+	if (video_supported == TPACPI_VIDEO_NONE) {
+		seq_printf(m, "status:\t\tnot supported\n");
+		return 0;
+	}
+
+	/* Even reads can crash X.org, so... */
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	status = video_outputsw_get();
+	if (status < 0)
+		return status;
+
+	autosw = video_autosw_get();
+	if (autosw < 0)
+		return autosw;
+
+	seq_printf(m, "status:\t\tsupported\n");
+	seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0));
+	seq_printf(m, "crt:\t\t%s\n", enabled(status, 1));
+	if (video_supported == TPACPI_VIDEO_NEW)
+		seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3));
+	seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0));
+	seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
+	seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
+	if (video_supported == TPACPI_VIDEO_NEW)
+		seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
+	seq_printf(m, "commands:\tauto_enable, auto_disable\n");
+	seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
+
+	return 0;
+}
+
+static int video_write(char *buf)
+{
+	char *cmd;
+	int enable, disable, status;
+	int res;
+
+	if (video_supported == TPACPI_VIDEO_NONE)
+		return -ENODEV;
+
+	/* Even reads can crash X.org, let alone writes... */
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	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_state != TPACPI_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_state = (brightness != LED_OFF) ?
+				TPACPI_LED_ON : TPACPI_LED_OFF;
+	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");
+
+	if (tpacpi_is_ibm()) {
+		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);
+	flush_workqueue(tpacpi_wq);
+}
+
+static int light_read(struct seq_file *m)
+{
+	int status;
+
+	if (!tp_features.light) {
+		seq_printf(m, "status:\t\tnot supported\n");
+	} else if (!tp_features.light_status) {
+		seq_printf(m, "status:\t\tunknown\n");
+		seq_printf(m, "commands:\ton, off\n");
+	} else {
+		status = light_get_status();
+		if (status < 0)
+			return status;
+		seq_printf(m, "status:\t\t%s\n", onoff(status, 0));
+		seq_printf(m, "commands:\ton, off\n");
+	}
+
+	return 0;
+}
+
+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,
+};
+
+/*************************************************************************
+ * 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 DEVICE_ATTR_WO(cmos_command);
+
+/* --------------------------------------------------------------------- */
+
+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(struct seq_file *m)
+{
+	/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+	   R30, R31, T20-22, X20-21 */
+	if (!cmos_handle)
+		seq_printf(m, "status:\t\tnot supported\n");
+	else {
+		seq_printf(m, "status:\t\tsupported\n");
+		seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
+	}
+
+	return 0;
+}
+
+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 */
+};
+
+static enum led_access_mode led_supported;
+
+static acpi_handle led_handle;
+
+#define TPACPI_LED_NUMLEDS 16
+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",
+	"tpacpi::dock_status1",
+	"tpacpi::dock_status2",
+	"tpacpi::unknown_led2",
+	"tpacpi::unknown_led3",
+	"tpacpi::thinkvantage",
+};
+#define TPACPI_SAFE_LEDS	0x1081U
+
+static inline bool tpacpi_is_led_restricted(const unsigned int led)
+{
+#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
+	return false;
+#else
+	return (1U & (TPACPI_SAFE_LEDS >> led)) == 0;
+#endif
+}
+
+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 (unlikely(led > 7))
+			return -EINVAL;
+		if (unlikely(tpacpi_is_led_restricted(led)))
+			return -EPERM;
+		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 (unlikely(led > 7))
+			return -EINVAL;
+		if (unlikely(tpacpi_is_led_restricted(led)))
+			return -EPERM;
+		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 (unlikely(led >= TPACPI_LED_NUMLEDS))
+			return -EINVAL;
+		if (unlikely(tpacpi_is_led_restricted(led)))
+			return -EPERM;
+		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_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_set_status(data->led, data->new_state);
+}
+
+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);
+
+	if (brightness == LED_OFF)
+		data->new_state = TPACPI_LED_OFF;
+	else if (tpacpi_led_state_cache[data->led] != TPACPI_LED_BLINK)
+		data->new_state = TPACPI_LED_ON;
+	else
+		data->new_state = TPACPI_LED_BLINK;
+
+	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_state = 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);
+	}
+
+	flush_workqueue(tpacpi_wq);
+	kfree(tpacpi_leds);
+}
+
+static int __init tpacpi_init_led(unsigned int led)
+{
+	int rc;
+
+	tpacpi_leds[led].led = led;
+
+	/* LEDs with no name don't get registered */
+	if (!tpacpi_led_names[led])
+		return 0;
+
+	tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set;
+	tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set;
+	if (led_supported == TPACPI_LED_570)
+		tpacpi_leds[led].led_classdev.brightness_get =
+						&led_sysfs_get;
+
+	tpacpi_leds[led].led_classdev.name = tpacpi_led_names[led];
+
+	INIT_WORK(&tpacpi_leds[led].work, led_set_status_worker);
+
+	rc = led_classdev_register(&tpacpi_pdev->dev,
+				&tpacpi_leds[led].led_classdev);
+	if (rc < 0)
+		tpacpi_leds[led].led_classdev.name = NULL;
+
+	return rc;
+}
+
+static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
+	TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */
+	TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */
+	TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */
+
+	TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */
+	TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */
+	TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */
+	TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */
+	TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */
+	TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */
+	TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */
+	TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */
+
+	TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */
+	TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */
+	TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */
+	TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */
+	TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */
+
+	TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */
+	TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */
+	TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */
+	TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */
+
+	/* (1) - may have excess leds enabled on MSB */
+
+	/* Defaults (order matters, keep last, don't reorder!) */
+	{ /* Lenovo */
+	  .vendor = PCI_VENDOR_ID_LENOVO,
+	  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+	  .quirks = 0x1fffU,
+	},
+	{ /* IBM ThinkPads with no EC version string */
+	  .vendor = PCI_VENDOR_ID_IBM,
+	  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN,
+	  .quirks = 0x00ffU,
+	},
+	{ /* IBM ThinkPads with EC version string */
+	  .vendor = PCI_VENDOR_ID_IBM,
+	  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+	  .quirks = 0x00bfU,
+	},
+};
+
+#undef TPACPI_LEDQ_IBM
+#undef TPACPI_LEDQ_LNV
+
+static enum led_access_mode __init led_init_detect_mode(void)
+{
+	acpi_status status;
+
+	if (tpacpi_is_ibm()) {
+		/* 570 */
+		status = acpi_get_handle(ec_handle, "SLED", &led_handle);
+		if (ACPI_SUCCESS(status))
+			return TPACPI_LED_570;
+
+		/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+		status = acpi_get_handle(ec_handle, "SYSL", &led_handle);
+		if (ACPI_SUCCESS(status))
+			return TPACPI_LED_OLD;
+	}
+
+	/* most others */
+	status = acpi_get_handle(ec_handle, "LED", &led_handle);
+	if (ACPI_SUCCESS(status))
+		return TPACPI_LED_NEW;
+
+	/* R30, R31, and unknown firmwares */
+	led_handle = NULL;
+	return TPACPI_LED_NONE;
+}
+
+static int __init led_init(struct ibm_init_struct *iibm)
+{
+	unsigned int i;
+	int rc;
+	unsigned long useful_leds;
+
+	vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
+
+	led_supported = led_init_detect_mode();
+
+	if (led_supported != TPACPI_LED_NONE) {
+		useful_leds = tpacpi_check_quirks(led_useful_qtable,
+				ARRAY_SIZE(led_useful_qtable));
+
+		if (!useful_leds) {
+			led_handle = NULL;
+			led_supported = TPACPI_LED_NONE;
+		}
+	}
+
+	vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
+		str_supported(led_supported), led_supported);
+
+	if (led_supported == TPACPI_LED_NONE)
+		return 1;
+
+	tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,
+			      GFP_KERNEL);
+	if (!tpacpi_leds) {
+		pr_err("Out of memory for LED data\n");
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
+		tpacpi_leds[i].led = -1;
+
+		if (!tpacpi_is_led_restricted(i) &&
+		    test_bit(i, &useful_leds)) {
+			rc = tpacpi_init_led(i);
+			if (rc < 0) {
+				led_exit();
+				return rc;
+			}
+		}
+	}
+
+#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
+	pr_notice("warning: userspace override of important "
+		  "firmware LEDs is enabled\n");
+#endif
+	return 0;
+}
+
+#define str_led_status(s) \
+	((s) == TPACPI_LED_OFF ? "off" : \
+		((s) == TPACPI_LED_ON ? "on" : "blinking"))
+
+static int led_read(struct seq_file *m)
+{
+	if (!led_supported) {
+		seq_printf(m, "status:\t\tnot supported\n");
+		return 0;
+	}
+	seq_printf(m, "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;
+			seq_printf(m, "%d:\t\t%s\n",
+				       i, str_led_status(status));
+		}
+	}
+
+	seq_printf(m, "commands:\t"
+		       "<led> on, <led> off, <led> blink (<led> is 0-15)\n");
+
+	return 0;
+}
+
+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)
+			return -EINVAL;
+
+		if (led < 0 || led > (TPACPI_LED_NUMLEDS - 1) ||
+				tpacpi_leds[led].led < 0)
+			return -ENODEV;
+
+		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 */
+
+#define TPACPI_BEEP_Q1 0x0001
+
+static const struct tpacpi_quirk beep_quirk_table[] __initconst = {
+	TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */
+	TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */
+};
+
+static int __init beep_init(struct ibm_init_struct *iibm)
+{
+	unsigned long quirks;
+
+	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));
+
+	quirks = tpacpi_check_quirks(beep_quirk_table,
+				     ARRAY_SIZE(beep_quirk_table));
+
+	tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1);
+
+	return (beep_handle) ? 0 : 1;
+}
+
+static int beep_read(struct seq_file *m)
+{
+	if (!beep_handle)
+		seq_printf(m, "status:\t\tnot supported\n");
+	else {
+		seq_printf(m, "status:\t\tsupported\n");
+		seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
+	}
+
+	return 0;
+}
+
+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 (tp_features.beep_needs_two_args) {
+			if (!acpi_evalf(beep_handle, NULL, NULL, "vdd",
+					beep_cmd, 0))
+				return -EIO;
+		} else {
+			if (!acpi_evalf(beep_handle, NULL, NULL, "vd",
+					beep_cmd))
+				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 */
+
+	TPACPI_THERMAL_SENSOR_NA = -128000, /* 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;
+}
+
+static void thermal_dump_all_sensors(void)
+{
+	int n, i;
+	struct ibm_thermal_sensors_struct t;
+
+	n = thermal_get_sensors(&t);
+	if (n <= 0)
+		return;
+
+	pr_notice("temperatures (Celsius):");
+
+	for (i = 0; i < n; i++) {
+		if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA)
+			pr_cont(" %d", (int)(t.temp[i] / 1000));
+		else
+			pr_cont(" N/A");
+	}
+
+	pr_cont("\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 == TPACPI_THERMAL_SENSOR_NA)
+		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) {
+				pr_err("ThinkPad ACPI EC access misbehaving, "
+				       "falling back to ACPI TMPx access "
+				       "mode\n");
+				thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
+			} else {
+				pr_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 (tpacpi_is_ibm() &&
+		    acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
+			/* 600e/x, 770e, 770x */
+			thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
+		} else {
+			/* IBM/LENOVO DSDT EC.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_input8_group);
+		break;
+	case TPACPI_THERMAL_NONE:
+	default:
+		break;
+	}
+}
+
+static int thermal_read(struct seq_file *m)
+{
+	int n, i;
+	struct ibm_thermal_sensors_struct t;
+
+	n = thermal_get_sensors(&t);
+	if (unlikely(n < 0))
+		return n;
+
+	seq_printf(m, "temperatures:\t");
+
+	if (n > 0) {
+		for (i = 0; i < (n - 1); i++)
+			seq_printf(m, "%d ", t.temp[i] / 1000);
+		seq_printf(m, "%d\n", t.temp[i] / 1000);
+	} else
+		seq_printf(m, "not supported\n");
+
+	return 0;
+}
+
+static struct ibm_struct thermal_driver_data = {
+	.name = "thermal",
+	.read = thermal_read,
+	.exit = thermal_exit,
+};
+
+/*************************************************************************
+ * Backlight/brightness subdriver
+ */
+
+#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
+
+/*
+ * ThinkPads can read brightness from two places: EC HBRV (0x31), or
+ * CMOS NVRAM byte 0x5E, bits 0-3.
+ *
+ * EC HBRV (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 HBRV layout
+ *
+ * WARNING: The X61 has been verified to use HBRV for something else, so
+ * this should be used _only_ on IBM ThinkPads, and maybe with some careful
+ * testing on the very early *60 Lenovo models...
+ */
+
+enum {
+	TP_EC_BACKLIGHT = 0x31,
+
+	/* TP_EC_BACKLIGHT bitmasks */
+	TP_EC_BACKLIGHT_LVLMSK = 0x1F,
+	TP_EC_BACKLIGHT_CMDMSK = 0xE0,
+	TP_EC_BACKLIGHT_MAPSW = 0x20,
+};
+
+enum tpacpi_brightness_access_mode {
+	TPACPI_BRGHT_MODE_AUTO = 0,	/* Not implemented yet */
+	TPACPI_BRGHT_MODE_EC,		/* EC control */
+	TPACPI_BRGHT_MODE_UCMS_STEP,	/* UCMS step-based control */
+	TPACPI_BRGHT_MODE_ECNVRAM,	/* EC control w/ NVRAM store */
+	TPACPI_BRGHT_MODE_MAX
+};
+
+static struct backlight_device *ibm_backlight_device;
+
+static enum tpacpi_brightness_access_mode brightness_mode =
+		TPACPI_BRGHT_MODE_MAX;
+
+static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
+
+static struct mutex brightness_mutex;
+
+/* NVRAM brightness access,
+ * call with brightness_mutex held! */
+static unsigned int tpacpi_brightness_nvram_get(void)
+{
+	u8 lnvram;
+
+	lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
+		  & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+		  >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+	lnvram &= bright_maxlvl;
+
+	return lnvram;
+}
+
+static void tpacpi_brightness_checkpoint_nvram(void)
+{
+	u8 lec = 0;
+	u8 b_nvram;
+
+	if (brightness_mode != TPACPI_BRGHT_MODE_ECNVRAM)
+		return;
+
+	vdbg_printk(TPACPI_DBG_BRGHT,
+		"trying to checkpoint backlight level to NVRAM...\n");
+
+	if (mutex_lock_killable(&brightness_mutex) < 0)
+		return;
+
+	if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+		goto unlock;
+	lec &= TP_EC_BACKLIGHT_LVLMSK;
+	b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
+
+	if (lec != ((b_nvram & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+			     >> TP_NVRAM_POS_LEVEL_BRIGHTNESS)) {
+		/* NVRAM needs update */
+		b_nvram &= ~(TP_NVRAM_MASK_LEVEL_BRIGHTNESS <<
+				TP_NVRAM_POS_LEVEL_BRIGHTNESS);
+		b_nvram |= lec;
+		nvram_write_byte(b_nvram, TP_NVRAM_ADDR_BRIGHTNESS);
+		dbg_printk(TPACPI_DBG_BRGHT,
+			   "updated NVRAM backlight level to %u (0x%02x)\n",
+			   (unsigned int) lec, (unsigned int) b_nvram);
+	} else
+		vdbg_printk(TPACPI_DBG_BRGHT,
+			   "NVRAM backlight level already is %u (0x%02x)\n",
+			   (unsigned int) lec, (unsigned int) b_nvram);
+
+unlock:
+	mutex_unlock(&brightness_mutex);
+}
+
+
+/* call with brightness_mutex held! */
+static int tpacpi_brightness_get_raw(int *status)
+{
+	u8 lec = 0;
+
+	switch (brightness_mode) {
+	case TPACPI_BRGHT_MODE_UCMS_STEP:
+		*status = tpacpi_brightness_nvram_get();
+		return 0;
+	case TPACPI_BRGHT_MODE_EC:
+	case TPACPI_BRGHT_MODE_ECNVRAM:
+		if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+			return -EIO;
+		*status = lec;
+		return 0;
+	default:
+		return -ENXIO;
+	}
+}
+
+/* call with brightness_mutex held! */
+/* do NOT call with illegal backlight level value */
+static int tpacpi_brightness_set_ec(unsigned int value)
+{
+	u8 lec = 0;
+
+	if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+		return -EIO;
+
+	if (unlikely(!acpi_ec_write(TP_EC_BACKLIGHT,
+				(lec & TP_EC_BACKLIGHT_CMDMSK) |
+				(value & TP_EC_BACKLIGHT_LVLMSK))))
+		return -EIO;
+
+	return 0;
+}
+
+/* call with brightness_mutex held! */
+static int tpacpi_brightness_set_ucmsstep(unsigned int value)
+{
+	int cmos_cmd, inc;
+	unsigned int current_value, i;
+
+	current_value = tpacpi_brightness_nvram_get();
+
+	if (value == current_value)
+		return 0;
+
+	cmos_cmd = (value > current_value) ?
+			TP_CMOS_BRIGHTNESS_UP :
+			TP_CMOS_BRIGHTNESS_DOWN;
+	inc = (value > current_value) ? 1 : -1;
+
+	for (i = current_value; i != value; i += inc)
+		if (issue_thinkpad_cmos_command(cmos_cmd))
+			return -EIO;
+
+	return 0;
+}
+
+/* May return EINTR which can always be mapped to ERESTARTSYS */
+static int brightness_set(unsigned int value)
+{
+	int res;
+
+	if (value > bright_maxlvl)
+		return -EINVAL;
+
+	vdbg_printk(TPACPI_DBG_BRGHT,
+			"set backlight level to %d\n", value);
+
+	res = mutex_lock_killable(&brightness_mutex);
+	if (res < 0)
+		return res;
+
+	switch (brightness_mode) {
+	case TPACPI_BRGHT_MODE_EC:
+	case TPACPI_BRGHT_MODE_ECNVRAM:
+		res = tpacpi_brightness_set_ec(value);
+		break;
+	case TPACPI_BRGHT_MODE_UCMS_STEP:
+		res = tpacpi_brightness_set_ucmsstep(value);
+		break;
+	default:
+		res = -ENXIO;
+	}
+
+	mutex_unlock(&brightness_mutex);
+	return res;
+}
+
+/* sysfs backlight class ----------------------------------------------- */
+
+static int brightness_update_status(struct backlight_device *bd)
+{
+	unsigned int level =
+		(bd->props.fb_blank == FB_BLANK_UNBLANK &&
+		 bd->props.power == FB_BLANK_UNBLANK) ?
+				bd->props.brightness : 0;
+
+	dbg_printk(TPACPI_DBG_BRGHT,
+			"backlight: attempt to set level to %d\n",
+			level);
+
+	/* it is the backlight class's job (caller) to handle
+	 * EINTR and other errors properly */
+	return brightness_set(level);
+}
+
+static int brightness_get(struct backlight_device *bd)
+{
+	int status, res;
+
+	res = mutex_lock_killable(&brightness_mutex);
+	if (res < 0)
+		return 0;
+
+	res = tpacpi_brightness_get_raw(&status);
+
+	mutex_unlock(&brightness_mutex);
+
+	if (res < 0)
+		return 0;
+
+	return status & TP_EC_BACKLIGHT_LVLMSK;
+}
+
+static void tpacpi_brightness_notify_change(void)
+{
+	backlight_force_update(ibm_backlight_device,
+			       BACKLIGHT_UPDATE_HOTKEY);
+}
+
+static const struct backlight_ops ibm_backlight_data = {
+	.get_brightness = brightness_get,
+	.update_status  = brightness_update_status,
+};
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Call _BCL method of video device.  On some ThinkPads this will
+ * switch the firmware to the ACPI brightness control mode.
+ */
+
+static int __init tpacpi_query_bcl_levels(acpi_handle handle)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	struct acpi_device *device, *child;
+	int rc;
+
+	if (acpi_bus_get_device(handle, &device))
+		return 0;
+
+	rc = 0;
+	list_for_each_entry(child, &device->children, node) {
+		acpi_status status = acpi_evaluate_object(child->handle, "_BCL",
+							  NULL, &buffer);
+		if (ACPI_FAILURE(status))
+			continue;
+
+		obj = (union acpi_object *)buffer.pointer;
+		if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
+			pr_err("Unknown _BCL data, please report this to %s\n",
+				TPACPI_MAIL);
+			rc = 0;
+		} else {
+			rc = obj->package.count;
+		}
+		break;
+	}
+
+	kfree(buffer.pointer);
+	return rc;
+}
+
+
+/*
+ * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
+ */
+static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)
+{
+	acpi_handle video_device;
+	int bcl_levels = 0;
+
+	tpacpi_acpi_handle_locate("video", NULL, &video_device);
+	if (video_device)
+		bcl_levels = tpacpi_query_bcl_levels(video_device);
+
+	tp_features.bright_acpimode = (bcl_levels > 0);
+
+	return (bcl_levels > 2) ? (bcl_levels - 2) : 0;
+}
+
+/*
+ * These are only useful for models that have only one possibility
+ * of GPU.  If the BIOS model handles both ATI and Intel, don't use
+ * these quirks.
+ */
+#define TPACPI_BRGHT_Q_NOEC	0x0001	/* Must NOT use EC HBRV */
+#define TPACPI_BRGHT_Q_EC	0x0002  /* Should or must use EC HBRV */
+#define TPACPI_BRGHT_Q_ASK	0x8000	/* Ask for user report */
+
+static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
+	/* Models with ATI GPUs known to require ECNVRAM mode */
+	TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC),	/* T43/p ATI */
+
+	/* Models with ATI GPUs that can use ECNVRAM */
+	TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC),	/* R50,51 T40-42 */
+	TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+	TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_EC),	/* R52 */
+	TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+
+	/* Models with Intel Extreme Graphics 2 */
+	TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC),	/* X40 */
+	TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+	TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+
+	/* Models with Intel GMA900 */
+	TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC),	/* T43, R52 */
+	TPACPI_Q_IBM('7', '4', TPACPI_BRGHT_Q_NOEC),	/* X41 */
+	TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC),	/* X41 Tablet */
+};
+
+/*
+ * Returns < 0 for error, otherwise sets tp_features.bright_*
+ * and bright_maxlvl.
+ */
+static void __init tpacpi_detect_brightness_capabilities(void)
+{
+	unsigned int b;
+
+	vdbg_printk(TPACPI_DBG_INIT,
+		    "detecting firmware brightness interface capabilities\n");
+
+	/* we could run a quirks check here (same table used by
+	 * brightness_init) if needed */
+
+	/*
+	 * 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();
+	switch (b) {
+	case 16:
+		bright_maxlvl = 15;
+		pr_info("detected a 16-level brightness capable ThinkPad\n");
+		break;
+	case 8:
+	case 0:
+		bright_maxlvl = 7;
+		pr_info("detected a 8-level brightness capable ThinkPad\n");
+		break;
+	default:
+		pr_info("Unsupported brightness interface\n");
+		tp_features.bright_unkfw = 1;
+		bright_maxlvl = b - 1;
+	}
+}
+
+static int __init brightness_init(struct ibm_init_struct *iibm)
+{
+	struct backlight_properties props;
+	int b;
+	unsigned long quirks;
+
+	vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
+
+	mutex_init(&brightness_mutex);
+
+	quirks = tpacpi_check_quirks(brightness_quirk_table,
+				ARRAY_SIZE(brightness_quirk_table));
+
+	/* tpacpi_detect_brightness_capabilities() must have run already */
+
+	/* if it is unknown, we don't handle it: it wouldn't be safe */
+	if (tp_features.bright_unkfw)
+		return 1;
+
+	if (!brightness_enable) {
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+			   "brightness support disabled by "
+			   "module parameter\n");
+		return 1;
+	}
+
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor) {
+		if (brightness_enable > 1) {
+			pr_info("Standard ACPI backlight interface "
+				"available, not loading native one\n");
+			return 1;
+		} else if (brightness_enable == 1) {
+			pr_warn("Cannot enable backlight brightness support, "
+				"ACPI is already handling it.  Refer to the "
+				"acpi_backlight kernel parameter.\n");
+			return 1;
+		}
+	} else if (tp_features.bright_acpimode && brightness_enable > 1) {
+		pr_notice("Standard ACPI backlight interface not "
+			  "available, thinkpad_acpi native "
+			  "brightness control enabled\n");
+	}
+
+	/*
+	 * Check for module parameter bogosity, note that we
+	 * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
+	 * able to detect "unspecified"
+	 */
+	if (brightness_mode > TPACPI_BRGHT_MODE_MAX)
+		return -EINVAL;
+
+	/* TPACPI_BRGHT_MODE_AUTO not implemented yet, just use default */
+	if (brightness_mode == TPACPI_BRGHT_MODE_AUTO ||
+	    brightness_mode == TPACPI_BRGHT_MODE_MAX) {
+		if (quirks & TPACPI_BRGHT_Q_EC)
+			brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM;
+		else
+			brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
+
+		dbg_printk(TPACPI_DBG_BRGHT,
+			   "driver auto-selected brightness_mode=%d\n",
+			   brightness_mode);
+	}
+
+	/* Safety */
+	if (!tpacpi_is_ibm() &&
+	    (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
+	     brightness_mode == TPACPI_BRGHT_MODE_EC))
+		return -EINVAL;
+
+	if (tpacpi_brightness_get_raw(&b) < 0)
+		return 1;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = bright_maxlvl;
+	props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
+	ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
+							 NULL, NULL,
+							 &ibm_backlight_data,
+							 &props);
+	if (IS_ERR(ibm_backlight_device)) {
+		int rc = PTR_ERR(ibm_backlight_device);
+		ibm_backlight_device = NULL;
+		pr_err("Could not register backlight device\n");
+		return rc;
+	}
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+			"brightness is supported\n");
+
+	if (quirks & TPACPI_BRGHT_Q_ASK) {
+		pr_notice("brightness: will use unverified default: "
+			  "brightness_mode=%d\n", brightness_mode);
+		pr_notice("brightness: please report to %s whether it works well "
+			  "or not on your ThinkPad\n", TPACPI_MAIL);
+	}
+
+	/* Added by mistake in early 2007.  Probably useless, but it could
+	 * be working around some unknown firmware problem where the value
+	 * read at startup doesn't match the real hardware state... so leave
+	 * it in place just in case */
+	backlight_update_status(ibm_backlight_device);
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+			"brightness: registering brightness hotkeys "
+			"as change notification\n");
+	tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+				| TP_ACPI_HKEY_BRGHTUP_MASK
+				| TP_ACPI_HKEY_BRGHTDWN_MASK);
+	return 0;
+}
+
+static void brightness_suspend(void)
+{
+	tpacpi_brightness_checkpoint_nvram();
+}
+
+static void brightness_shutdown(void)
+{
+	tpacpi_brightness_checkpoint_nvram();
+}
+
+static void brightness_exit(void)
+{
+	if (ibm_backlight_device) {
+		vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_BRGHT,
+			    "calling backlight_device_unregister()\n");
+		backlight_device_unregister(ibm_backlight_device);
+	}
+
+	tpacpi_brightness_checkpoint_nvram();
+}
+
+static int brightness_read(struct seq_file *m)
+{
+	int level;
+
+	level = brightness_get(NULL);
+	if (level < 0) {
+		seq_printf(m, "level:\t\tunreadable\n");
+	} else {
+		seq_printf(m, "level:\t\t%d\n", level);
+		seq_printf(m, "commands:\tup, down\n");
+		seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
+			       bright_maxlvl);
+	}
+
+	return 0;
+}
+
+static int brightness_write(char *buf)
+{
+	int level;
+	int rc;
+	char *cmd;
+
+	level = brightness_get(NULL);
+	if (level < 0)
+		return level;
+
+	while ((cmd = next_cmd(&buf))) {
+		if (strlencmp(cmd, "up") == 0) {
+			if (level < bright_maxlvl)
+				level++;
+		} else if (strlencmp(cmd, "down") == 0) {
+			if (level > 0)
+				level--;
+		} else if (sscanf(cmd, "level %d", &level) == 1 &&
+			   level >= 0 && level <= bright_maxlvl) {
+			/* new level set */
+		} else
+			return -EINVAL;
+	}
+
+	tpacpi_disclose_usertask("procfs brightness",
+			"set level to %d\n", level);
+
+	/*
+	 * 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);
+	if (!rc && ibm_backlight_device)
+		backlight_force_update(ibm_backlight_device,
+					BACKLIGHT_UPDATE_SYSFS);
+	return (rc == -EINTR) ? -ERESTARTSYS : rc;
+}
+
+static struct ibm_struct brightness_driver_data = {
+	.name = "brightness",
+	.read = brightness_read,
+	.write = brightness_write,
+	.exit = brightness_exit,
+	.suspend = brightness_suspend,
+	.shutdown = brightness_shutdown,
+};
+
+/*************************************************************************
+ * Volume subdriver
+ */
+
+/*
+ * IBM ThinkPads have a simple volume controller with MUTE gating.
+ * Very early Lenovo ThinkPads follow the IBM ThinkPad spec.
+ *
+ * Since the *61 series (and probably also the later *60 series), Lenovo
+ * ThinkPads only implement the MUTE gate.
+ *
+ * EC register 0x30
+ *   Bit 6: MUTE (1 mutes sound)
+ *   Bit 3-0: Volume
+ *   Other bits should be zero as far as we know.
+ *
+ * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and
+ * bits 3-0 (volume).  Other bits in NVRAM may have other functions,
+ * such as bit 7 which is used to detect repeated presses of MUTE,
+ * and we leave them unchanged.
+ *
+ * On newer Lenovo ThinkPads, the EC can automatically change the volume
+ * in response to user input.  Unfortunately, this rarely works well.
+ * The laptop changes the state of its internal MUTE gate and, on some
+ * models, sends KEY_MUTE, causing any user code that responds to the
+ * mute button to get confused.  The hardware MUTE gate is also
+ * unnecessary, since user code can handle the mute button without
+ * kernel or EC help.
+ *
+ * To avoid confusing userspace, we simply disable all EC-based mute
+ * and volume controls when possible.
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
+
+#define TPACPI_ALSA_DRVNAME  "ThinkPad EC"
+#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
+#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
+
+#if SNDRV_CARDS <= 32
+#define DEFAULT_ALSA_IDX		~((1 << (SNDRV_CARDS - 3)) - 1)
+#else
+#define DEFAULT_ALSA_IDX		~((1 << (32 - 3)) - 1)
+#endif
+static int alsa_index = DEFAULT_ALSA_IDX; /* last three slots */
+static char *alsa_id = "ThinkPadEC";
+static bool alsa_enable = SNDRV_DEFAULT_ENABLE1;
+
+struct tpacpi_alsa_data {
+	struct snd_card *card;
+	struct snd_ctl_elem_id *ctl_mute_id;
+	struct snd_ctl_elem_id *ctl_vol_id;
+};
+
+static struct snd_card *alsa_card;
+
+enum {
+	TP_EC_AUDIO = 0x30,
+
+	/* TP_EC_AUDIO bits */
+	TP_EC_AUDIO_MUTESW = 6,
+
+	/* TP_EC_AUDIO bitmasks */
+	TP_EC_AUDIO_LVL_MSK = 0x0F,
+	TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW),
+
+	/* Maximum volume */
+	TP_EC_VOLUME_MAX = 14,
+};
+
+enum tpacpi_volume_access_mode {
+	TPACPI_VOL_MODE_AUTO = 0,	/* Not implemented yet */
+	TPACPI_VOL_MODE_EC,		/* Pure EC control */
+	TPACPI_VOL_MODE_UCMS_STEP,	/* UCMS step-based control: N/A */
+	TPACPI_VOL_MODE_ECNVRAM,	/* EC control w/ NVRAM store */
+	TPACPI_VOL_MODE_MAX
+};
+
+enum tpacpi_volume_capabilities {
+	TPACPI_VOL_CAP_AUTO = 0,	/* Use white/blacklist */
+	TPACPI_VOL_CAP_VOLMUTE,		/* Output vol and mute */
+	TPACPI_VOL_CAP_MUTEONLY,	/* Output mute only */
+	TPACPI_VOL_CAP_MAX
+};
+
+enum tpacpi_mute_btn_mode {
+	TP_EC_MUTE_BTN_LATCH  = 0,	/* Mute mutes; up/down unmutes */
+	/* We don't know what mode 1 is. */
+	TP_EC_MUTE_BTN_NONE   = 2,	/* Mute and up/down are just keys */
+	TP_EC_MUTE_BTN_TOGGLE = 3,	/* Mute toggles; up/down unmutes */
+};
+
+static enum tpacpi_volume_access_mode volume_mode =
+	TPACPI_VOL_MODE_MAX;
+
+static enum tpacpi_volume_capabilities volume_capabilities;
+static bool volume_control_allowed;
+static bool software_mute_requested = true;
+static bool software_mute_active;
+static int software_mute_orig_mode;
+
+/*
+ * Used to syncronize writers to TP_EC_AUDIO and
+ * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
+ */
+static struct mutex volume_mutex;
+
+static void tpacpi_volume_checkpoint_nvram(void)
+{
+	u8 lec = 0;
+	u8 b_nvram;
+	u8 ec_mask;
+
+	if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
+		return;
+	if (!volume_control_allowed)
+		return;
+	if (software_mute_active)
+		return;
+
+	vdbg_printk(TPACPI_DBG_MIXER,
+		"trying to checkpoint mixer state to NVRAM...\n");
+
+	if (tp_features.mixer_no_level_control)
+		ec_mask = TP_EC_AUDIO_MUTESW_MSK;
+	else
+		ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
+
+	if (mutex_lock_killable(&volume_mutex) < 0)
+		return;
+
+	if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec)))
+		goto unlock;
+	lec &= ec_mask;
+	b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+
+	if (lec != (b_nvram & ec_mask)) {
+		/* NVRAM needs update */
+		b_nvram &= ~ec_mask;
+		b_nvram |= lec;
+		nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER);
+		dbg_printk(TPACPI_DBG_MIXER,
+			   "updated NVRAM mixer status to 0x%02x (0x%02x)\n",
+			   (unsigned int) lec, (unsigned int) b_nvram);
+	} else {
+		vdbg_printk(TPACPI_DBG_MIXER,
+			   "NVRAM mixer status already is 0x%02x (0x%02x)\n",
+			   (unsigned int) lec, (unsigned int) b_nvram);
+	}
+
+unlock:
+	mutex_unlock(&volume_mutex);
+}
+
+static int volume_get_status_ec(u8 *status)
+{
+	u8 s;
+
+	if (!acpi_ec_read(TP_EC_AUDIO, &s))
+		return -EIO;
+
+	*status = s;
+
+	dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s);
+
+	return 0;
+}
+
+static int volume_get_status(u8 *status)
+{
+	return volume_get_status_ec(status);
+}
+
+static int volume_set_status_ec(const u8 status)
+{
+	if (!acpi_ec_write(TP_EC_AUDIO, status))
+		return -EIO;
+
+	dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
+
+	/*
+	 * On X200s, and possibly on others, it can take a while for
+	 * reads to become correct.
+	 */
+	msleep(1);
+
+	return 0;
+}
+
+static int volume_set_status(const u8 status)
+{
+	return volume_set_status_ec(status);
+}
+
+/* returns < 0 on error, 0 on no change, 1 on change */
+static int __volume_set_mute_ec(const bool mute)
+{
+	int rc;
+	u8 s, n;
+
+	if (mutex_lock_killable(&volume_mutex) < 0)
+		return -EINTR;
+
+	rc = volume_get_status_ec(&s);
+	if (rc)
+		goto unlock;
+
+	n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
+		     s & ~TP_EC_AUDIO_MUTESW_MSK;
+
+	if (n != s) {
+		rc = volume_set_status_ec(n);
+		if (!rc)
+			rc = 1;
+	}
+
+unlock:
+	mutex_unlock(&volume_mutex);
+	return rc;
+}
+
+static int volume_alsa_set_mute(const bool mute)
+{
+	dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to %smute\n",
+		   (mute) ? "" : "un");
+	return __volume_set_mute_ec(mute);
+}
+
+static int volume_set_mute(const bool mute)
+{
+	int rc;
+
+	dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
+		   (mute) ? "" : "un");
+
+	rc = __volume_set_mute_ec(mute);
+	return (rc < 0) ? rc : 0;
+}
+
+/* returns < 0 on error, 0 on no change, 1 on change */
+static int __volume_set_volume_ec(const u8 vol)
+{
+	int rc;
+	u8 s, n;
+
+	if (vol > TP_EC_VOLUME_MAX)
+		return -EINVAL;
+
+	if (mutex_lock_killable(&volume_mutex) < 0)
+		return -EINTR;
+
+	rc = volume_get_status_ec(&s);
+	if (rc)
+		goto unlock;
+
+	n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
+
+	if (n != s) {
+		rc = volume_set_status_ec(n);
+		if (!rc)
+			rc = 1;
+	}
+
+unlock:
+	mutex_unlock(&volume_mutex);
+	return rc;
+}
+
+static int volume_set_software_mute(bool startup)
+{
+	int result;
+
+	if (!tpacpi_is_lenovo())
+		return -ENODEV;
+
+	if (startup) {
+		if (!acpi_evalf(ec_handle, &software_mute_orig_mode,
+				"HAUM", "qd"))
+			return -EIO;
+
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+			    "Initial HAUM setting was %d\n",
+			    software_mute_orig_mode);
+	}
+
+	if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd",
+			(int)TP_EC_MUTE_BTN_NONE))
+		return -EIO;
+
+	if (result != TP_EC_MUTE_BTN_NONE)
+		pr_warn("Unexpected SAUM result %d\n",
+			result);
+
+	/*
+	 * In software mute mode, the standard codec controls take
+	 * precendence, so we unmute the ThinkPad HW switch at
+	 * startup.  Just on case there are SAUM-capable ThinkPads
+	 * with level controls, set max HW volume as well.
+	 */
+	if (tp_features.mixer_no_level_control)
+		result = volume_set_mute(false);
+	else
+		result = volume_set_status(TP_EC_VOLUME_MAX);
+
+	if (result != 0)
+		pr_warn("Failed to unmute the HW mute switch\n");
+
+	return 0;
+}
+
+static void volume_exit_software_mute(void)
+{
+	int r;
+
+	if (!acpi_evalf(ec_handle, &r, "SAUM", "qdd", software_mute_orig_mode)
+	    || r != software_mute_orig_mode)
+		pr_warn("Failed to restore mute mode\n");
+}
+
+static int volume_alsa_set_volume(const u8 vol)
+{
+	dbg_printk(TPACPI_DBG_MIXER,
+		   "ALSA: trying to set volume level to %hu\n", vol);
+	return __volume_set_volume_ec(vol);
+}
+
+static void volume_alsa_notify_change(void)
+{
+	struct tpacpi_alsa_data *d;
+
+	if (alsa_card && alsa_card->private_data) {
+		d = alsa_card->private_data;
+		if (d->ctl_mute_id)
+			snd_ctl_notify(alsa_card,
+					SNDRV_CTL_EVENT_MASK_VALUE,
+					d->ctl_mute_id);
+		if (d->ctl_vol_id)
+			snd_ctl_notify(alsa_card,
+					SNDRV_CTL_EVENT_MASK_VALUE,
+					d->ctl_vol_id);
+	}
+}
+
+static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = TP_EC_VOLUME_MAX;
+	return 0;
+}
+
+static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u8 s;
+	int rc;
+
+	rc = volume_get_status(&s);
+	if (rc < 0)
+		return rc;
+
+	ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK;
+	return 0;
+}
+
+static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	tpacpi_disclose_usertask("ALSA", "set volume to %ld\n",
+				 ucontrol->value.integer.value[0]);
+	return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
+}
+
+#define volume_alsa_mute_info snd_ctl_boolean_mono_info
+
+static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u8 s;
+	int rc;
+
+	rc = volume_get_status(&s);
+	if (rc < 0)
+		return rc;
+
+	ucontrol->value.integer.value[0] =
+				(s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1;
+	return 0;
+}
+
+static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	tpacpi_disclose_usertask("ALSA", "%smute\n",
+				 ucontrol->value.integer.value[0] ?
+					"un" : "");
+	return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
+}
+
+static struct snd_kcontrol_new volume_alsa_control_vol __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Console Playback Volume",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = volume_alsa_vol_info,
+	.get = volume_alsa_vol_get,
+};
+
+static struct snd_kcontrol_new volume_alsa_control_mute __initdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Console Playback Switch",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = volume_alsa_mute_info,
+	.get = volume_alsa_mute_get,
+};
+
+static void volume_suspend(void)
+{
+	tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_resume(void)
+{
+	if (software_mute_active) {
+		if (volume_set_software_mute(false) < 0)
+			pr_warn("Failed to restore software mute\n");
+	} else {
+		volume_alsa_notify_change();
+	}
+}
+
+static void volume_shutdown(void)
+{
+	tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_exit(void)
+{
+	if (alsa_card) {
+		snd_card_free(alsa_card);
+		alsa_card = NULL;
+	}
+
+	tpacpi_volume_checkpoint_nvram();
+
+	if (software_mute_active)
+		volume_exit_software_mute();
+}
+
+static int __init volume_create_alsa_mixer(void)
+{
+	struct snd_card *card;
+	struct tpacpi_alsa_data *data;
+	struct snd_kcontrol *ctl_vol;
+	struct snd_kcontrol *ctl_mute;
+	int rc;
+
+	rc = snd_card_new(&tpacpi_pdev->dev,
+			  alsa_index, alsa_id, THIS_MODULE,
+			  sizeof(struct tpacpi_alsa_data), &card);
+	if (rc < 0 || !card) {
+		pr_err("Failed to create ALSA card structures: %d\n", rc);
+		return 1;
+	}
+
+	BUG_ON(!card->private_data);
+	data = card->private_data;
+	data->card = card;
+
+	strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
+		sizeof(card->driver));
+	strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
+		sizeof(card->shortname));
+	snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
+		 (thinkpad_id.ec_version_str) ?
+			thinkpad_id.ec_version_str : "(unknown)");
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO,
+		 (thinkpad_id.ec_version_str) ?
+			thinkpad_id.ec_version_str : "unknown");
+
+	if (volume_control_allowed) {
+		volume_alsa_control_vol.put = volume_alsa_vol_put;
+		volume_alsa_control_vol.access =
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+
+		volume_alsa_control_mute.put = volume_alsa_mute_put;
+		volume_alsa_control_mute.access =
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	}
+
+	if (!tp_features.mixer_no_level_control) {
+		ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
+		rc = snd_ctl_add(card, ctl_vol);
+		if (rc < 0) {
+			pr_err("Failed to create ALSA volume control: %d\n",
+			       rc);
+			goto err_exit;
+		}
+		data->ctl_vol_id = &ctl_vol->id;
+	}
+
+	ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
+	rc = snd_ctl_add(card, ctl_mute);
+	if (rc < 0) {
+		pr_err("Failed to create ALSA mute control: %d\n", rc);
+		goto err_exit;
+	}
+	data->ctl_mute_id = &ctl_mute->id;
+
+	rc = snd_card_register(card);
+	if (rc < 0) {
+		pr_err("Failed to register ALSA card: %d\n", rc);
+		goto err_exit;
+	}
+
+	alsa_card = card;
+	return 0;
+
+err_exit:
+	snd_card_free(card);
+	return 1;
+}
+
+#define TPACPI_VOL_Q_MUTEONLY	0x0001	/* Mute-only control available */
+#define TPACPI_VOL_Q_LEVEL	0x0002  /* Volume control available */
+
+static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
+	/* Whitelist volume level on all IBM by default */
+	{ .vendor = PCI_VENDOR_ID_IBM,
+	  .bios   = TPACPI_MATCH_ANY,
+	  .ec     = TPACPI_MATCH_ANY,
+	  .quirks = TPACPI_VOL_Q_LEVEL },
+
+	/* Lenovo models with volume control (needs confirmation) */
+	TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */
+	TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */
+	TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */
+	TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */
+	TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */
+	TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */
+	TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */
+
+	/* Whitelist mute-only on all Lenovo by default */
+	{ .vendor = PCI_VENDOR_ID_LENOVO,
+	  .bios   = TPACPI_MATCH_ANY,
+	  .ec	  = TPACPI_MATCH_ANY,
+	  .quirks = TPACPI_VOL_Q_MUTEONLY }
+};
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+	unsigned long quirks;
+	int rc;
+
+	vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
+
+	mutex_init(&volume_mutex);
+
+	/*
+	 * Check for module parameter bogosity, note that we
+	 * init volume_mode to TPACPI_VOL_MODE_MAX in order to be
+	 * able to detect "unspecified"
+	 */
+	if (volume_mode > TPACPI_VOL_MODE_MAX)
+		return -EINVAL;
+
+	if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
+		pr_err("UCMS step volume mode not implemented, "
+		       "please contact %s\n", TPACPI_MAIL);
+		return 1;
+	}
+
+	if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
+		return -EINVAL;
+
+	/*
+	 * The ALSA mixer is our primary interface.
+	 * When disabled, don't install the subdriver at all
+	 */
+	if (!alsa_enable) {
+		vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+			    "ALSA mixer disabled by parameter, "
+			    "not loading volume subdriver...\n");
+		return 1;
+	}
+
+	quirks = tpacpi_check_quirks(volume_quirk_table,
+				     ARRAY_SIZE(volume_quirk_table));
+
+	switch (volume_capabilities) {
+	case TPACPI_VOL_CAP_AUTO:
+		if (quirks & TPACPI_VOL_Q_MUTEONLY)
+			tp_features.mixer_no_level_control = 1;
+		else if (quirks & TPACPI_VOL_Q_LEVEL)
+			tp_features.mixer_no_level_control = 0;
+		else
+			return 1; /* no mixer */
+		break;
+	case TPACPI_VOL_CAP_VOLMUTE:
+		tp_features.mixer_no_level_control = 0;
+		break;
+	case TPACPI_VOL_CAP_MUTEONLY:
+		tp_features.mixer_no_level_control = 1;
+		break;
+	default:
+		return 1;
+	}
+
+	if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+				"using user-supplied volume_capabilities=%d\n",
+				volume_capabilities);
+
+	if (volume_mode == TPACPI_VOL_MODE_AUTO ||
+	    volume_mode == TPACPI_VOL_MODE_MAX) {
+		volume_mode = TPACPI_VOL_MODE_ECNVRAM;
+
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+				"driver auto-selected volume_mode=%d\n",
+				volume_mode);
+	} else {
+		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+				"using user-supplied volume_mode=%d\n",
+				volume_mode);
+	}
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+			"mute is supported, volume control is %s\n",
+			str_supported(!tp_features.mixer_no_level_control));
+
+	if (software_mute_requested && volume_set_software_mute(true) == 0) {
+		software_mute_active = true;
+	} else {
+		rc = volume_create_alsa_mixer();
+		if (rc) {
+			pr_err("Could not create the ALSA mixer interface\n");
+			return rc;
+		}
+
+		pr_info("Console audio control enabled, mode: %s\n",
+			(volume_control_allowed) ?
+				"override (read/write)" :
+				"monitor (read only)");
+	}
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+		"registering volume hotkeys as change notification\n");
+	tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+			| TP_ACPI_HKEY_VOLUP_MASK
+			| TP_ACPI_HKEY_VOLDWN_MASK
+			| TP_ACPI_HKEY_MUTE_MASK);
+
+	return 0;
+}
+
+static int volume_read(struct seq_file *m)
+{
+	u8 status;
+
+	if (volume_get_status(&status) < 0) {
+		seq_printf(m, "level:\t\tunreadable\n");
+	} else {
+		if (tp_features.mixer_no_level_control)
+			seq_printf(m, "level:\t\tunsupported\n");
+		else
+			seq_printf(m, "level:\t\t%d\n",
+					status & TP_EC_AUDIO_LVL_MSK);
+
+		seq_printf(m, "mute:\t\t%s\n",
+				onoff(status, TP_EC_AUDIO_MUTESW));
+
+		if (volume_control_allowed) {
+			seq_printf(m, "commands:\tunmute, mute\n");
+			if (!tp_features.mixer_no_level_control) {
+				seq_printf(m,
+					       "commands:\tup, down\n");
+				seq_printf(m,
+					       "commands:\tlevel <level>"
+					       " (<level> is 0-%d)\n",
+					       TP_EC_VOLUME_MAX);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int volume_write(char *buf)
+{
+	u8 s;
+	u8 new_level, new_mute;
+	int l;
+	char *cmd;
+	int rc;
+
+	/*
+	 * We do allow volume control at driver startup, so that the
+	 * user can set initial state through the volume=... parameter hack.
+	 */
+	if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) {
+		if (unlikely(!tp_warned.volume_ctrl_forbidden)) {
+			tp_warned.volume_ctrl_forbidden = 1;
+			pr_notice("Console audio control in monitor mode, "
+				  "changes are not allowed\n");
+			pr_notice("Use the volume_control=1 module parameter "
+				  "to enable volume control\n");
+		}
+		return -EPERM;
+	}
+
+	rc = volume_get_status(&s);
+	if (rc < 0)
+		return rc;
+
+	new_level = s & TP_EC_AUDIO_LVL_MSK;
+	new_mute  = s & TP_EC_AUDIO_MUTESW_MSK;
+
+	while ((cmd = next_cmd(&buf))) {
+		if (!tp_features.mixer_no_level_control) {
+			if (strlencmp(cmd, "up") == 0) {
+				if (new_mute)
+					new_mute = 0;
+				else if (new_level < TP_EC_VOLUME_MAX)
+					new_level++;
+				continue;
+			} else if (strlencmp(cmd, "down") == 0) {
+				if (new_mute)
+					new_mute = 0;
+				else if (new_level > 0)
+					new_level--;
+				continue;
+			} else if (sscanf(cmd, "level %u", &l) == 1 &&
+				   l >= 0 && l <= TP_EC_VOLUME_MAX) {
+					new_level = l;
+				continue;
+			}
+		}
+		if (strlencmp(cmd, "mute") == 0)
+			new_mute = TP_EC_AUDIO_MUTESW_MSK;
+		else if (strlencmp(cmd, "unmute") == 0)
+			new_mute = 0;
+		else
+			return -EINVAL;
+	}
+
+	if (tp_features.mixer_no_level_control) {
+		tpacpi_disclose_usertask("procfs volume", "%smute\n",
+					new_mute ? "" : "un");
+		rc = volume_set_mute(!!new_mute);
+	} else {
+		tpacpi_disclose_usertask("procfs volume",
+					"%smute and set level to %d\n",
+					new_mute ? "" : "un", new_level);
+		rc = volume_set_status(new_mute | new_level);
+	}
+	volume_alsa_notify_change();
+
+	return (rc == -EINTR) ? -ERESTARTSYS : rc;
+}
+
+static struct ibm_struct volume_driver_data = {
+	.name = "volume",
+	.read = volume_read,
+	.write = volume_write,
+	.exit = volume_exit,
+	.suspend = volume_suspend,
+	.resume = volume_resume,
+	.shutdown = volume_shutdown,
+};
+
+#else /* !CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+#define alsa_card NULL
+
+static void inline volume_alsa_notify_change(void)
+{
+}
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+	pr_info("volume: disabled as there is no ALSA support in this kernel\n");
+
+	return 1;
+}
+
+static struct ibm_struct volume_driver_data = {
+	.name = "volume",
+};
+
+#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+/*************************************************************************
+ * 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 amount 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 initialize 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 firmware
+ *	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
+ *
+ *	----
+ *
+ *	ThinkPad EC register 0x31 bit 0 (only on select models)
+ *
+ *	When bit 0 of EC register 0x31 is zero, the tachometer registers
+ *	show the speed of the main fan.  When bit 0 of EC register 0x31
+ *	is one, the tachometer registers show the speed of the auxiliary
+ *	fan.
+ *
+ *	Fan control seems to affect both fans, regardless of the state
+ *	of this bit.
+ *
+ *	So far, only the firmware for the X60/X61 non-tablet versions
+ *	seem to support this (firmware TP-7M).
+ *
+ * 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, accessible 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 */
+	fan_select_offset = 0x31,	/* EC register 0x31 (Firmware 7M)
+					   bit 0 selects which fan is active */
+
+	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 bool 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 */
+
+/*
+ * Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the
+ * HFSP register at boot, so it contains 0x07 but the Thinkpad could
+ * be in auto mode (0x80).
+ *
+ * This is corrected by any write to HFSP either by the driver, or
+ * by the firmware.
+ *
+ * We assume 0x07 really means auto mode while this quirk is active,
+ * as this is far more likely than the ThinkPad being in level 7,
+ * which is only used by the firmware during thermal emergencies.
+ *
+ * Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52),
+ * TP-70 (T43, R52), which are known to be buggy.
+ */
+
+static void fan_quirk1_setup(void)
+{
+	if (fan_control_initial_status == 0x07) {
+		pr_notice("fan_init: initial fan status is unknown, "
+			  "assuming it is in auto mode\n");
+		tp_features.fan_ctrl_status_undef = 1;
+	}
+}
+
+static void fan_quirk1_handle(u8 *fan_status)
+{
+	if (unlikely(tp_features.fan_ctrl_status_undef)) {
+		if (*fan_status != fan_control_initial_status) {
+			/* something changed the HFSP regisnter since
+			 * driver init time, so it is not undefined
+			 * anymore */
+			tp_features.fan_ctrl_status_undef = 0;
+		} else {
+			/* Return most likely status. In fact, it
+			 * might be the only possible status */
+			*fan_status = TP_EC_FAN_AUTO;
+		}
+	}
+}
+
+/* Select main fan on X60/X61, NOOP on others */
+static bool fan_select_fan1(void)
+{
+	if (tp_features.second_fan) {
+		u8 val;
+
+		if (ec_read(fan_select_offset, &val) < 0)
+			return false;
+		val &= 0xFEU;
+		if (ec_write(fan_select_offset, val) < 0)
+			return false;
+	}
+	return true;
+}
+
+/* Select secondary fan on X60/X61 */
+static bool fan_select_fan2(void)
+{
+	u8 val;
+
+	if (!tp_features.second_fan)
+		return false;
+
+	if (ec_read(fan_select_offset, &val) < 0)
+		return false;
+	val |= 0x01U;
+	if (ec_write(fan_select_offset, val) < 0)
+		return false;
+
+	return true;
+}
+
+/*
+ * 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 */
+		int res;
+
+		if (unlikely(!acpi_evalf(gfan_handle, &res, NULL, "d")))
+			return -EIO;
+
+		if (likely(status))
+			*status = res & 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;
+			fan_quirk1_handle(status);
+		}
+
+		break;
+
+	default:
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static int fan_get_status_safe(u8 *status)
+{
+	int rc;
+	u8 s;
+
+	if (mutex_lock_killable(&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(!fan_select_fan1()))
+			return -EIO;
+		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 fan2_get_speed(unsigned int *speed)
+{
+	u8 hi, lo;
+	bool rc;
+
+	switch (fan_status_access_mode) {
+	case TPACPI_FAN_RD_TPEC:
+		/* all except 570, 600e/x, 770e, 770x */
+		if (unlikely(!fan_select_fan2()))
+			return -EIO;
+		rc = !acpi_ec_read(fan_rpm_offset, &lo) ||
+			     !acpi_ec_read(fan_rpm_offset + 1, &hi);
+		fan_select_fan1(); /* play it safe */
+		if (rc)
+			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;
+	}
+
+	vdbg_printk(TPACPI_DBG_FAN,
+		"fan control: set fan control register to 0x%02x\n", level);
+	return 0;
+}
+
+static int fan_set_level_safe(int level)
+{
+	int rc;
+
+	if (!fan_control_allowed)
+		return -EPERM;
+
+	if (mutex_lock_killable(&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_killable(&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);
+
+	if (!rc)
+		vdbg_printk(TPACPI_DBG_FAN,
+			"fan control: set fan control register to 0x%02x\n",
+			s);
+	return rc;
+}
+
+static int fan_set_disable(void)
+{
+	int rc;
+
+	if (!fan_control_allowed)
+		return -EPERM;
+
+	if (mutex_lock_killable(&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;
+	}
+
+	if (!rc)
+		vdbg_printk(TPACPI_DBG_FAN,
+			"fan control: set fan control register to 0\n");
+
+	mutex_unlock(&fan_mutex);
+	return rc;
+}
+
+static int fan_set_speed(int speed)
+{
+	int rc;
+
+	if (!fan_control_allowed)
+		return -EPERM;
+
+	if (mutex_lock_killable(&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)
+{
+	if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
+		return;
+
+	if (fan_watchdog_maxinterval > 0 &&
+	    tpacpi_lifecycle != TPACPI_LIFE_EXITING)
+		mod_delayed_work(tpacpi_wq, &fan_watchdog_task,
+			msecs_to_jiffies(fan_watchdog_maxinterval * 1000));
+	else
+		cancel_delayed_work(&fan_watchdog_task);
+}
+
+static void fan_watchdog_fire(struct work_struct *ignored)
+{
+	int rc;
+
+	if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+		return;
+
+	pr_notice("fan watchdog: enabling fan\n");
+	rc = fan_set_enable();
+	if (rc < 0) {
+		pr_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 (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;
+
+	tpacpi_disclose_usertask("hwmon pwm1_enable",
+			"set fan mode to %lu\n", t);
+
+	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 DEVICE_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 ((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;
+
+	tpacpi_disclose_usertask("hwmon pwm1",
+			"set fan speed to %lu\n", s);
+
+	/* scale down from 0-255 to 0-7 */
+	newlevel = (s >> 5) & 0x07;
+
+	if (mutex_lock_killable(&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 DEVICE_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 DEVICE_ATTR(fan1_input, S_IRUGO, fan_fan1_input_show, NULL);
+
+/* sysfs fan fan2_input ------------------------------------------------ */
+static ssize_t fan_fan2_input_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	int res;
+	unsigned int speed;
+
+	res = fan2_get_speed(&speed);
+	if (res < 0)
+		return res;
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", speed);
+}
+
+static DEVICE_ATTR(fan2_input, S_IRUGO, fan_fan2_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();
+
+	tpacpi_disclose_usertask("fan_watchdog", "set to %lu\n", t);
+
+	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_pwm1_enable.attr, &dev_attr_pwm1.attr,
+	&dev_attr_fan1_input.attr,
+	NULL, /* for fan2_input */
+	NULL
+};
+
+static const struct attribute_group fan_attr_group = {
+	.attrs = fan_attributes,
+};
+
+#define	TPACPI_FAN_Q1	0x0001		/* Unitialized HFSP */
+#define TPACPI_FAN_2FAN	0x0002		/* EC 0x31 bit 0 selects fan2 */
+
+#define TPACPI_FAN_QI(__id1, __id2, __quirks)	\
+	{ .vendor = PCI_VENDOR_ID_IBM,		\
+	  .bios = TPACPI_MATCH_ANY,		\
+	  .ec = TPID(__id1, __id2),		\
+	  .quirks = __quirks }
+
+#define TPACPI_FAN_QL(__id1, __id2, __quirks)	\
+	{ .vendor = PCI_VENDOR_ID_LENOVO,	\
+	  .bios = TPACPI_MATCH_ANY,		\
+	  .ec = TPID(__id1, __id2),		\
+	  .quirks = __quirks }
+
+static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
+	TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1),
+	TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1),
+	TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1),
+	TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1),
+	TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN),
+};
+
+#undef TPACPI_FAN_QL
+#undef TPACPI_FAN_QI
+
+static int __init fan_init(struct ibm_init_struct *iibm)
+{
+	int rc;
+	unsigned long quirks;
+
+	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
+			"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;
+	tp_features.second_fan = 0;
+	fan_control_desired_level = 7;
+
+	if (tpacpi_is_ibm()) {
+		TPACPI_ACPIHANDLE_INIT(fans);
+		TPACPI_ACPIHANDLE_INIT(gfan);
+		TPACPI_ACPIHANDLE_INIT(sfan);
+	}
+
+	quirks = tpacpi_check_quirks(fan_quirk_table,
+				     ARRAY_SIZE(fan_quirk_table));
+
+	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;
+			if (quirks & TPACPI_FAN_Q1)
+				fan_quirk1_setup();
+			if (quirks & TPACPI_FAN_2FAN) {
+				tp_features.second_fan = 1;
+				dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
+					"secondary fan support enabled\n");
+			}
+		} else {
+			pr_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 | TPACPI_DBG_FAN,
+		"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 | TPACPI_DBG_FAN,
+			   "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) {
+		if (tp_features.second_fan) {
+			/* attach second fan tachometer */
+			fan_attributes[ARRAY_SIZE(fan_attributes)-2] =
+					&dev_attr_fan2_input.attr;
+		}
+		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 | TPACPI_DBG_FAN,
+		    "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(void)
+{
+	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)
+		pr_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) {
+		pr_notice("restoring fan level to 0x%02x\n",
+			  fan_control_resume_level);
+		rc = fan_set_level_safe(fan_control_resume_level);
+		if (rc < 0)
+			pr_notice("failed to restore fan level: %d\n", rc);
+	}
+}
+
+static int fan_read(struct seq_file *m)
+{
+	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;
+
+		seq_printf(m, "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;
+
+		seq_printf(m, "status:\t\t%s\n",
+			       (status != 0) ? "enabled" : "disabled");
+
+		rc = fan_get_speed(&speed);
+		if (rc < 0)
+			return rc;
+
+		seq_printf(m, "speed:\t\t%d\n", speed);
+
+		if (status & TP_EC_FAN_FULLSPEED)
+			/* Disengaged mode takes precedence */
+			seq_printf(m, "level:\t\tdisengaged\n");
+		else if (status & TP_EC_FAN_AUTO)
+			seq_printf(m, "level:\t\tauto\n");
+		else
+			seq_printf(m, "level:\t\t%d\n", status);
+		break;
+
+	case TPACPI_FAN_NONE:
+	default:
+		seq_printf(m, "status:\t\tnot supported\n");
+	}
+
+	if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
+		seq_printf(m, "commands:\tlevel <level>");
+
+		switch (fan_control_access_mode) {
+		case TPACPI_FAN_WR_ACPI_SFAN:
+			seq_printf(m, " (<level> is 0-7)\n");
+			break;
+
+		default:
+			seq_printf(m, " (<level> is 0-7, "
+				       "auto, disengaged, full-speed)\n");
+			break;
+		}
+	}
+
+	if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
+		seq_printf(m, "commands:\tenable, disable\n"
+			       "commands:\twatchdog <timeout> (<timeout> "
+			       "is 0 (off), 1-120 (seconds))\n");
+
+	if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
+		seq_printf(m, "commands:\tspeed <speed>"
+			       " (<speed> is 0-65535)\n");
+
+	return 0;
+}
+
+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)
+		pr_err("level command accepted for unsupported access mode %d\n",
+		       fan_control_access_mode);
+	else if (!*rc)
+		tpacpi_disclose_usertask("procfs fan",
+			"set level to %d\n", level);
+
+	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)
+		pr_err("enable command accepted for unsupported access mode %d\n",
+		       fan_control_access_mode);
+	else if (!*rc)
+		tpacpi_disclose_usertask("procfs fan", "enable\n");
+
+	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)
+		pr_err("disable command accepted for unsupported access mode %d\n",
+		       fan_control_access_mode);
+	else if (!*rc)
+		tpacpi_disclose_usertask("procfs fan", "disable\n");
+
+	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)
+		pr_err("speed command accepted for unsupported access mode %d\n",
+		       fan_control_access_mode);
+	else if (!*rc)
+		tpacpi_disclose_usertask("procfs fan",
+			"set speed to %d\n", speed);
+
+	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;
+		tpacpi_disclose_usertask("procfs fan",
+			"set watchdog timer to %d\n",
+			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,
+};
+
+/*************************************************************************
+ * Mute LED subdriver
+ */
+
+
+struct tp_led_table {
+	acpi_string name;
+	int on_value;
+	int off_value;
+	int state;
+};
+
+static struct tp_led_table led_tables[] = {
+	[TPACPI_LED_MUTE] = {
+		.name = "SSMS",
+		.on_value = 1,
+		.off_value = 0,
+	},
+	[TPACPI_LED_MICMUTE] = {
+		.name = "MMTS",
+		.on_value = 2,
+		.off_value = 0,
+	},
+};
+
+static int mute_led_on_off(struct tp_led_table *t, bool state)
+{
+	acpi_handle temp;
+	int output;
+
+	if (!ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) {
+		pr_warn("Thinkpad ACPI has no %s interface.\n", t->name);
+		return -EIO;
+	}
+
+	if (!acpi_evalf(hkey_handle, &output, t->name, "dd",
+			state ? t->on_value : t->off_value))
+		return -EIO;
+
+	t->state = state;
+	return state;
+}
+
+int tpacpi_led_set(int whichled, bool on)
+{
+	struct tp_led_table *t;
+
+	if (whichled < 0 || whichled >= TPACPI_LED_MAX)
+		return -EINVAL;
+
+	t = &led_tables[whichled];
+	if (t->state < 0 || t->state == on)
+		return t->state;
+	return mute_led_on_off(t, on);
+}
+EXPORT_SYMBOL_GPL(tpacpi_led_set);
+
+static int mute_led_init(struct ibm_init_struct *iibm)
+{
+	acpi_handle temp;
+	int i;
+
+	for (i = 0; i < TPACPI_LED_MAX; i++) {
+		struct tp_led_table *t = &led_tables[i];
+		if (ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp)))
+			mute_led_on_off(t, false);
+		else
+			t->state = -ENODEV;
+	}
+	return 0;
+}
+
+static void mute_led_exit(void)
+{
+	int i;
+
+	for (i = 0; i < TPACPI_LED_MAX; i++)
+		tpacpi_led_set(i, false);
+}
+
+static void mute_led_resume(void)
+{
+	int i;
+
+	for (i = 0; i < TPACPI_LED_MAX; i++) {
+		struct tp_led_table *t = &led_tables[i];
+		if (t->state >= 0)
+			mute_led_on_off(t, t->state);
+	}
+}
+
+static struct ibm_struct mute_led_driver_data = {
+	.name = "mute_led",
+	.exit = mute_led_exit,
+	.resume = mute_led_resume,
+};
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Infrastructure
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * HKEY event callout for other subdrivers go here
+ * (yes, it is ugly, but it is quick, safe, and gets the job done
+ */
+static void tpacpi_driver_event(const unsigned int hkey_event)
+{
+	if (ibm_backlight_device) {
+		switch (hkey_event) {
+		case TP_HKEY_EV_BRGHT_UP:
+		case TP_HKEY_EV_BRGHT_DOWN:
+			tpacpi_brightness_notify_change();
+		}
+	}
+	if (alsa_card) {
+		switch (hkey_event) {
+		case TP_HKEY_EV_VOL_UP:
+		case TP_HKEY_EV_VOL_DOWN:
+		case TP_HKEY_EV_VOL_MUTE:
+			volume_alsa_notify_change();
+		}
+	}
+}
+
+static void hotkey_driver_event(const unsigned int scancode)
+{
+	tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
+}
+
+/* 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 DEVICE_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 bool 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;
+	}
+
+	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) {
+				pr_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) {
+		umode_t mode = iibm->base_procfs_mode;
+
+		if (!mode)
+			mode = S_IRUGO;
+		if (ibm->write)
+			mode |= S_IWUSR;
+		entry = proc_create_data(ibm->name, mode, proc_dir,
+					 &dispatch_proc_fops, ibm);
+		if (!entry) {
+			pr_err("unable to create proc entry %s\n", ibm->name);
+			ret = -ENODEV;
+			goto err_out;
+		}
+		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 */
+
+static bool __pure __init tpacpi_is_fw_digit(const char c)
+{
+	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z');
+}
+
+static bool __pure __init tpacpi_is_valid_fw_id(const char * const s,
+						const char t)
+{
+	/*
+	 * Most models: xxyTkkWW (#.##c)
+	 * Ancient 570/600 and -SL lacks (#.##c)
+	 */
+	if (s && strlen(s) >= 8 &&
+		tpacpi_is_fw_digit(s[0]) &&
+		tpacpi_is_fw_digit(s[1]) &&
+		s[2] == t &&
+		(s[3] == 'T' || s[3] == 'N') &&
+		tpacpi_is_fw_digit(s[4]) &&
+		tpacpi_is_fw_digit(s[5]))
+		return true;
+
+	/* New models: xxxyTkkW (#.##c); T550 and some others */
+	return s && strlen(s) >= 8 &&
+		tpacpi_is_fw_digit(s[0]) &&
+		tpacpi_is_fw_digit(s[1]) &&
+		tpacpi_is_fw_digit(s[2]) &&
+		s[3] == t &&
+		(s[4] == 'T' || s[4] == 'N') &&
+		tpacpi_is_fw_digit(s[5]) &&
+		tpacpi_is_fw_digit(s[6]);
+}
+
+/* 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;
+
+	/* Really ancient ThinkPad 240X will fail this, which is fine */
+	if (!(tpacpi_is_valid_fw_id(tp->bios_version_str, 'E') ||
+	      tpacpi_is_valid_fw_id(tp->bios_version_str, 'C')))
+		return 0;
+
+	tp->bios_model = tp->bios_version_str[0]
+			 | (tp->bios_version_str[1] << 8);
+	tp->bios_release = (tp->bios_version_str[4] << 8)
+			 | tp->bios_version_str[5];
+
+	/*
+	 * 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;
+
+			if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) {
+				tp->ec_model = ec_fw_string[0]
+						| (ec_fw_string[1] << 8);
+				tp->ec_release = (ec_fw_string[4] << 8)
+						| ec_fw_string[5];
+			} else {
+				pr_notice("ThinkPad firmware release %s "
+					  "doesn't match the known patterns\n",
+					  ec_fw_string);
+				pr_notice("please report this to %s\n",
+					  TPACPI_MAIL);
+			}
+			break;
+		}
+	}
+
+	s = dmi_get_system_info(DMI_PRODUCT_VERSION);
+	if (s && !(strncasecmp(s, "ThinkPad", 8) && strncasecmp(s, "Lenovo", 6))) {
+		tp->model_str = kstrdup(s, GFP_KERNEL);
+		if (!tp->model_str)
+			return -ENOMEM;
+	} else {
+		s = dmi_get_system_info(DMI_BIOS_VENDOR);
+		if (s && !(strncasecmp(s, "Lenovo", 6))) {
+			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;
+
+	/* It would be dangerous to run the driver in this case */
+	if (!tpacpi_is_ibm() && !tpacpi_is_lenovo())
+		return -ENODEV;
+
+	/*
+	 * Non-ancient models have better DMI tagging, but very old models
+	 * don't.  tpacpi_is_fw_known() is a cheat to help in that case.
+	 */
+	is_thinkpad = (thinkpad_id.model_str != NULL) ||
+		      (thinkpad_id.ec_model != 0) ||
+		      tpacpi_is_fw_known();
+
+	/* The EC handler is required */
+	tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle);
+	if (!ec_handle) {
+		if (is_thinkpad)
+			pr_err("Not yet supported ThinkPad detected!\n");
+		return -ENODEV;
+	}
+
+	if (!is_thinkpad && !force_load)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __init thinkpad_acpi_init_banner(void)
+{
+	pr_info("%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
+	pr_info("%s\n", TPACPI_URL);
+
+	pr_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");
+
+	BUG_ON(!thinkpad_id.vendor);
+
+	if (thinkpad_id.model_str)
+		pr_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");
+}
+
+/* Module init, exit, parameters */
+
+static struct ibm_init_struct ibms_init[] __initdata = {
+	{
+		.data = &thinkpad_acpi_driver_data,
+	},
+	{
+		.init = hotkey_init,
+		.data = &hotkey_driver_data,
+	},
+	{
+		.init = bluetooth_init,
+		.data = &bluetooth_driver_data,
+	},
+	{
+		.init = wan_init,
+		.data = &wan_driver_data,
+	},
+	{
+		.init = uwb_init,
+		.data = &uwb_driver_data,
+	},
+#ifdef CONFIG_THINKPAD_ACPI_VIDEO
+	{
+		.init = video_init,
+		.base_procfs_mode = S_IRUSR,
+		.data = &video_driver_data,
+	},
+#endif
+	{
+		.init = light_init,
+		.data = &light_driver_data,
+	},
+	{
+		.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,
+	},
+	{
+		.init = brightness_init,
+		.data = &brightness_driver_data,
+	},
+	{
+		.init = volume_init,
+		.data = &volume_driver_data,
+	},
+	{
+		.init = fan_init,
+		.data = &fan_driver_data,
+	},
+	{
+		.init = mute_led_init,
+		.data = &mute_led_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, 0444);
+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, 0444);
+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, 0444);
+MODULE_PARM_DESC(fan_control,
+		 "Enables setting fan parameters features when true");
+
+module_param_named(brightness_mode, brightness_mode, uint, 0444);
+MODULE_PARM_DESC(brightness_mode,
+		 "Selects brightness control strategy: "
+		 "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM");
+
+module_param(brightness_enable, uint, 0444);
+MODULE_PARM_DESC(brightness_enable,
+		 "Enables backlight control when 1, disables when 0");
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
+module_param_named(volume_mode, volume_mode, uint, 0444);
+MODULE_PARM_DESC(volume_mode,
+		 "Selects volume control strategy: "
+		 "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
+
+module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
+MODULE_PARM_DESC(volume_capabilities,
+		 "Selects the mixer capabilites: "
+		 "0=auto, 1=volume and mute, 2=mute only");
+
+module_param_named(volume_control, volume_control_allowed, bool, 0444);
+MODULE_PARM_DESC(volume_control,
+		 "Enables software override for the console audio "
+		 "control when true");
+
+module_param_named(software_mute, software_mute_requested, bool, 0444);
+MODULE_PARM_DESC(software_mute,
+		 "Request full software mute control");
+
+/* ALSA module API parameters */
+module_param_named(index, alsa_index, int, 0444);
+MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
+module_param_named(id, alsa_id, charp, 0444);
+MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
+module_param_named(enable, alsa_enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer");
+#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+#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);
+TPACPI_PARAM(cmos);
+TPACPI_PARAM(led);
+TPACPI_PARAM(beep);
+TPACPI_PARAM(brightness);
+TPACPI_PARAM(volume);
+TPACPI_PARAM(fan);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+module_param(dbg_wlswemul, uint, 0444);
+MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
+module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
+MODULE_PARM_DESC(wlsw_state,
+		 "Initial state of the emulated WLSW switch");
+
+module_param(dbg_bluetoothemul, uint, 0444);
+MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
+module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
+MODULE_PARM_DESC(bluetooth_state,
+		 "Initial state of the emulated bluetooth switch");
+
+module_param(dbg_wwanemul, uint, 0444);
+MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
+module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
+MODULE_PARM_DESC(wwan_state,
+		 "Initial state of the emulated WWAN switch");
+
+module_param(dbg_uwbemul, uint, 0444);
+MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation");
+module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0);
+MODULE_PARM_DESC(uwb_state,
+		 "Initial state of the emulated UWB switch");
+#endif
+
+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);
+		kfree(hotkey_keycode_map);
+	}
+
+	if (tpacpi_hwmon)
+		hwmon_device_unregister(tpacpi_hwmon);
+
+	if (tp_features.sensors_pdev_attrs_registered)
+		device_remove_file(&tpacpi_sensors_pdev->dev, &dev_attr_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);
+	kfree(thinkpad_id.nummodel_str);
+}
+
+
+static int __init thinkpad_acpi_module_init(void)
+{
+	int ret, i;
+
+	tpacpi_lifecycle = TPACPI_LIFE_INIT;
+
+	/* Driver-level probe */
+
+	ret = get_thinkpad_model_data(&thinkpad_id);
+	if (ret) {
+		pr_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 */
+
+	thinkpad_acpi_init_banner();
+	tpacpi_check_outdated_fw();
+
+	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) {
+		pr_err("unable to create proc dir " TPACPI_PROC_DIR "\n");
+		thinkpad_acpi_module_exit();
+		return -ENODEV;
+	}
+
+	ret = platform_driver_register(&tpacpi_pdriver);
+	if (ret) {
+		pr_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) {
+		pr_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) {
+		pr_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;
+		pr_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;
+		pr_err("unable to register hwmon platform device\n");
+		thinkpad_acpi_module_exit();
+		return ret;
+	}
+	ret = device_create_file(&tpacpi_sensors_pdev->dev, &dev_attr_name);
+	if (ret) {
+		pr_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;
+		pr_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) {
+		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;
+		tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
+		tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
+		tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
+	}
+
+	/* Init subdriver dependencies */
+	tpacpi_detect_brightness_capabilities();
+
+	/* Init subdrivers */
+	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;
+		}
+	}
+
+	tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
+
+	ret = input_register_device(tpacpi_inputdev);
+	if (ret < 0) {
+		pr_err("unable to register input device\n");
+		thinkpad_acpi_module_exit();
+		return ret;
+	} else {
+		tp_features.input_device_registered = 1;
+	}
+
+	return 0;
+}
+
+MODULE_ALIAS(TPACPI_DRVR_SHORTNAME);
+
+/*
+ * This will autoload the driver in almost every ThinkPad
+ * in widespread use.
+ *
+ * Only _VERY_ old models, like the 240, 240x and 570 lack
+ * the HKEY event interface.
+ */
+MODULE_DEVICE_TABLE(acpi, ibm_htk_device_ids);
+
+/*
+ * 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*")
+
+/* 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[MU]");		/* 570, 570e */
+
+MODULE_AUTHOR("Borislav Deianov <borislav@users.sf.net>");
+MODULE_AUTHOR("Henrique de Moraes Holschuh <hmh@hmh.eng.br>");
+MODULE_DESCRIPTION(TPACPI_DESC);
+MODULE_VERSION(TPACPI_VERSION);
+MODULE_LICENSE("GPL");
+
+module_init(thinkpad_acpi_module_init);
+module_exit(thinkpad_acpi_module_exit);
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c
new file mode 100644
index 0000000..e597de0
--- /dev/null
+++ b/drivers/platform/x86/topstar-laptop.c
@@ -0,0 +1,187 @@
+/*
+ * ACPI driver for Topstar notebooks (hotkeys support only)
+ *
+ * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
+ *
+ * Implementation inspired by existing x86 platform drivers, in special
+ * asus/eepc/fujitsu-laptop, thanks to their authors
+ *
+ * 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
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+
+#define ACPI_TOPSTAR_CLASS "topstar"
+
+struct topstar_hkey {
+	struct input_dev *inputdev;
+};
+
+static const struct key_entry topstar_keymap[] = {
+	{ KE_KEY, 0x80, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 0x83, { KEY_VOLUMEUP } },
+	{ KE_KEY, 0x84, { KEY_VOLUMEDOWN } },
+	{ KE_KEY, 0x85, { KEY_MUTE } },
+	{ KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */
+	{ KE_KEY, 0x88, { KEY_WLAN } },
+	{ KE_KEY, 0x8a, { KEY_WWW } },
+	{ KE_KEY, 0x8b, { KEY_MAIL } },
+	{ KE_KEY, 0x8c, { KEY_MEDIA } },
+
+	/* Known non hotkey events don't handled or that we don't care yet */
+	{ KE_IGNORE, 0x82, }, /* backlight event */
+	{ KE_IGNORE, 0x8e, },
+	{ KE_IGNORE, 0x8f, },
+	{ KE_IGNORE, 0x90, },
+
+	/*
+	 * 'G key' generate two event codes, convert to only
+	 * one event/key code for now, consider replacing by
+	 * a switch (3G switch - SW_3G?)
+	 */
+	{ KE_KEY, 0x96, { KEY_F14 } },
+	{ KE_KEY, 0x97, { KEY_F14 } },
+
+	{ KE_END, 0 }
+};
+
+static void acpi_topstar_notify(struct acpi_device *device, u32 event)
+{
+	static bool dup_evnt[2];
+	bool *dup;
+	struct topstar_hkey *hkey = acpi_driver_data(device);
+
+	/* 0x83 and 0x84 key events comes duplicated... */
+	if (event == 0x83 || event == 0x84) {
+		dup = &dup_evnt[event - 0x83];
+		if (*dup) {
+			*dup = false;
+			return;
+		}
+		*dup = true;
+	}
+
+	if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true))
+		pr_info("unknown event = 0x%02x\n", event);
+}
+
+static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state)
+{
+	acpi_status status;
+
+	status = acpi_execute_simple_method(device->handle, "FNCX",
+						state ? 0x86 : 0x87);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Unable to switch FNCX notifications\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int acpi_topstar_init_hkey(struct topstar_hkey *hkey)
+{
+	struct input_dev *input;
+	int error;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "Topstar Laptop extra buttons";
+	input->phys = "topstar/input0";
+	input->id.bustype = BUS_HOST;
+
+	error = sparse_keymap_setup(input, topstar_keymap, NULL);
+	if (error) {
+		pr_err("Unable to setup input device keymap\n");
+		goto err_free_dev;
+	}
+
+	error = input_register_device(input);
+	if (error) {
+		pr_err("Unable to register input device\n");
+		goto err_free_keymap;
+	}
+
+	hkey->inputdev = input;
+	return 0;
+
+ err_free_keymap:
+	sparse_keymap_free(input);
+ err_free_dev:
+	input_free_device(input);
+	return error;
+}
+
+static int acpi_topstar_add(struct acpi_device *device)
+{
+	struct topstar_hkey *tps_hkey;
+
+	tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL);
+	if (!tps_hkey)
+		return -ENOMEM;
+
+	strcpy(acpi_device_name(device), "Topstar TPSACPI");
+	strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS);
+
+	if (acpi_topstar_fncx_switch(device, true))
+		goto add_err;
+
+	if (acpi_topstar_init_hkey(tps_hkey))
+		goto add_err;
+
+	device->driver_data = tps_hkey;
+	return 0;
+
+add_err:
+	kfree(tps_hkey);
+	return -ENODEV;
+}
+
+static int acpi_topstar_remove(struct acpi_device *device)
+{
+	struct topstar_hkey *tps_hkey = acpi_driver_data(device);
+
+	acpi_topstar_fncx_switch(device, false);
+
+	sparse_keymap_free(tps_hkey->inputdev);
+	input_unregister_device(tps_hkey->inputdev);
+	kfree(tps_hkey);
+
+	return 0;
+}
+
+static const struct acpi_device_id topstar_device_ids[] = {
+	{ "TPSACPI01", 0 },
+	{ "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, topstar_device_ids);
+
+static struct acpi_driver acpi_topstar_driver = {
+	.name = "Topstar laptop ACPI driver",
+	.class = ACPI_TOPSTAR_CLASS,
+	.ids = topstar_device_ids,
+	.ops = {
+		.add = acpi_topstar_add,
+		.remove = acpi_topstar_remove,
+		.notify = acpi_topstar_notify,
+	},
+};
+module_acpi_driver(acpi_topstar_driver);
+
+MODULE_AUTHOR("Herton Ronaldo Krzesinski");
+MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/toshiba-wmi.c b/drivers/platform/x86/toshiba-wmi.c
new file mode 100644
index 0000000..2df07ee
--- /dev/null
+++ b/drivers/platform/x86/toshiba-wmi.c
@@ -0,0 +1,150 @@
+/*
+ * toshiba_wmi.c - Toshiba WMI Hotkey Driver
+ *
+ * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/dmi.h>
+
+MODULE_AUTHOR("Azael Avalos");
+MODULE_DESCRIPTION("Toshiba WMI Hotkey Driver");
+MODULE_LICENSE("GPL");
+
+#define WMI_EVENT_GUID	"59142400-C6A3-40FA-BADB-8A2652834100"
+
+MODULE_ALIAS("wmi:"WMI_EVENT_GUID);
+
+static struct input_dev *toshiba_wmi_input_dev;
+
+static const struct key_entry toshiba_wmi_keymap[] __initconst = {
+	/* TODO: Add keymap values once found... */
+	/*{ KE_KEY, 0x00, { KEY_ } },*/
+	{ KE_END, 0 }
+};
+
+static void toshiba_wmi_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+	if (!obj)
+		return;
+
+	/* TODO: Add proper checks once we have data */
+	pr_debug("Unknown event received, obj type %x\n", obj->type);
+
+	kfree(response.pointer);
+}
+
+static struct dmi_system_id toshiba_wmi_dmi_table[] __initdata = {
+	{
+		.ident = "Toshiba laptop",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+		},
+	},
+	{}
+};
+
+static int __init toshiba_wmi_input_setup(void)
+{
+	acpi_status status;
+	int err;
+
+	toshiba_wmi_input_dev = input_allocate_device();
+	if (!toshiba_wmi_input_dev)
+		return -ENOMEM;
+
+	toshiba_wmi_input_dev->name = "Toshiba WMI hotkeys";
+	toshiba_wmi_input_dev->phys = "wmi/input0";
+	toshiba_wmi_input_dev->id.bustype = BUS_HOST;
+
+	err = sparse_keymap_setup(toshiba_wmi_input_dev,
+				  toshiba_wmi_keymap, NULL);
+	if (err)
+		goto err_free_dev;
+
+	status = wmi_install_notify_handler(WMI_EVENT_GUID,
+					    toshiba_wmi_notify, NULL);
+	if (ACPI_FAILURE(status)) {
+		err = -EIO;
+		goto err_free_keymap;
+	}
+
+	err = input_register_device(toshiba_wmi_input_dev);
+	if (err)
+		goto err_remove_notifier;
+
+	return 0;
+
+ err_remove_notifier:
+	wmi_remove_notify_handler(WMI_EVENT_GUID);
+ err_free_keymap:
+	sparse_keymap_free(toshiba_wmi_input_dev);
+ err_free_dev:
+	input_free_device(toshiba_wmi_input_dev);
+	return err;
+}
+
+static void toshiba_wmi_input_destroy(void)
+{
+	wmi_remove_notify_handler(WMI_EVENT_GUID);
+	sparse_keymap_free(toshiba_wmi_input_dev);
+	input_unregister_device(toshiba_wmi_input_dev);
+}
+
+static int __init toshiba_wmi_init(void)
+{
+	int ret;
+
+	if (!wmi_has_guid(WMI_EVENT_GUID) ||
+	    !dmi_check_system(toshiba_wmi_dmi_table))
+		return -ENODEV;
+
+	ret = toshiba_wmi_input_setup();
+	if (ret) {
+		pr_err("Failed to setup input device\n");
+		return ret;
+	}
+
+	pr_info("Toshiba WMI Hotkey Driver\n");
+
+	return 0;
+}
+
+static void __exit toshiba_wmi_exit(void)
+{
+	if (wmi_has_guid(WMI_EVENT_GUID))
+		toshiba_wmi_input_destroy();
+}
+
+module_init(toshiba_wmi_init);
+module_exit(toshiba_wmi_exit);
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
new file mode 100644
index 0000000..f774cb5
--- /dev/null
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -0,0 +1,2896 @@
+/*
+ *  toshiba_acpi.c - Toshiba Laptop ACPI Extras
+ *
+ *  Copyright (C) 2002-2004 John Belmonte
+ *  Copyright (C) 2008 Philip Langdale
+ *  Copyright (C) 2010 Pierre Ducroquet
+ *  Copyright (C) 2014-2015 Azael Avalos
+ *
+ *  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.
+ *
+ *  The full GNU General Public License is included in this distribution in
+ *  the file called "COPYING".
+ *
+ *  The devolpment page for this driver is located at
+ *  http://memebeam.org/toys/ToshibaAcpiDriver.
+ *
+ *  Credits:
+ *	Jonathan A. Buzzard - Toshiba HCI info, and critical tips on reverse
+ *		engineering the Windows drivers
+ *	Yasushi Nagato - changes for linux kernel 2.4 -> 2.5
+ *	Rob Miller - TV out and hotkeys help
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define TOSHIBA_ACPI_VERSION	"0.23"
+#define PROC_INTERFACE_VERSION	1
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/backlight.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/i8042.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/toshiba.h>
+#include <acpi/video.h>
+
+MODULE_AUTHOR("John Belmonte");
+MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver");
+MODULE_LICENSE("GPL");
+
+#define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100"
+
+/* Scan code for Fn key on TOS1900 models */
+#define TOS1900_FN_SCAN		0x6e
+
+/* Toshiba ACPI method paths */
+#define METHOD_VIDEO_OUT	"\\_SB_.VALX.DSSX"
+
+/*
+ * The Toshiba configuration interface is composed of the HCI and the SCI,
+ * which are defined as follows:
+ *
+ * HCI is Toshiba's "Hardware Control Interface" which is supposed to
+ * be uniform across all their models.  Ideally we would just call
+ * dedicated ACPI methods instead of using this primitive interface.
+ * However the ACPI methods seem to be incomplete in some areas (for
+ * example they allow setting, but not reading, the LCD brightness value),
+ * so this is still useful.
+ *
+ * SCI stands for "System Configuration Interface" which aim is to
+ * conceal differences in hardware between different models.
+ */
+
+#define TCI_WORDS			6
+
+/* Operations */
+#define HCI_SET				0xff00
+#define HCI_GET				0xfe00
+#define SCI_OPEN			0xf100
+#define SCI_CLOSE			0xf200
+#define SCI_GET				0xf300
+#define SCI_SET				0xf400
+
+/* Return codes */
+#define TOS_SUCCESS			0x0000
+#define TOS_SUCCESS2			0x0001
+#define TOS_OPEN_CLOSE_OK		0x0044
+#define TOS_FAILURE			0x1000
+#define TOS_NOT_SUPPORTED		0x8000
+#define TOS_ALREADY_OPEN		0x8100
+#define TOS_NOT_OPENED			0x8200
+#define TOS_INPUT_DATA_ERROR		0x8300
+#define TOS_WRITE_PROTECTED		0x8400
+#define TOS_NOT_PRESENT			0x8600
+#define TOS_FIFO_EMPTY			0x8c00
+#define TOS_DATA_NOT_AVAILABLE		0x8d20
+#define TOS_NOT_INITIALIZED		0x8d50
+#define TOS_NOT_INSTALLED		0x8e00
+
+/* Registers */
+#define HCI_FAN				0x0004
+#define HCI_TR_BACKLIGHT		0x0005
+#define HCI_SYSTEM_EVENT		0x0016
+#define HCI_VIDEO_OUT			0x001c
+#define HCI_HOTKEY_EVENT		0x001e
+#define HCI_LCD_BRIGHTNESS		0x002a
+#define HCI_ACCELEROMETER		0x006d
+#define HCI_KBD_ILLUMINATION		0x0095
+#define HCI_ECO_MODE			0x0097
+#define HCI_ACCELEROMETER2		0x00a6
+#define HCI_SYSTEM_INFO			0xc000
+#define SCI_PANEL_POWER_ON		0x010d
+#define SCI_ILLUMINATION		0x014e
+#define SCI_USB_SLEEP_CHARGE		0x0150
+#define SCI_KBD_ILLUM_STATUS		0x015c
+#define SCI_USB_SLEEP_MUSIC		0x015e
+#define SCI_USB_THREE			0x0169
+#define SCI_TOUCHPAD			0x050e
+#define SCI_KBD_FUNCTION_KEYS		0x0522
+
+/* Field definitions */
+#define HCI_ACCEL_MASK			0x7fff
+#define HCI_HOTKEY_DISABLE		0x0b
+#define HCI_HOTKEY_ENABLE		0x09
+#define HCI_HOTKEY_SPECIAL_FUNCTIONS	0x10
+#define HCI_LCD_BRIGHTNESS_BITS		3
+#define HCI_LCD_BRIGHTNESS_SHIFT	(16-HCI_LCD_BRIGHTNESS_BITS)
+#define HCI_LCD_BRIGHTNESS_LEVELS	(1 << HCI_LCD_BRIGHTNESS_BITS)
+#define HCI_MISC_SHIFT			0x10
+#define HCI_SYSTEM_TYPE1		0x10
+#define HCI_SYSTEM_TYPE2		0x11
+#define HCI_VIDEO_OUT_LCD		0x1
+#define HCI_VIDEO_OUT_CRT		0x2
+#define HCI_VIDEO_OUT_TV		0x4
+#define SCI_KBD_MODE_MASK		0x1f
+#define SCI_KBD_MODE_FNZ		0x1
+#define SCI_KBD_MODE_AUTO		0x2
+#define SCI_KBD_MODE_ON			0x8
+#define SCI_KBD_MODE_OFF		0x10
+#define SCI_KBD_TIME_MAX		0x3c001a
+#define SCI_USB_CHARGE_MODE_MASK	0xff
+#define SCI_USB_CHARGE_DISABLED		0x00
+#define SCI_USB_CHARGE_ALTERNATE	0x09
+#define SCI_USB_CHARGE_TYPICAL		0x11
+#define SCI_USB_CHARGE_AUTO		0x21
+#define SCI_USB_CHARGE_BAT_MASK		0x7
+#define SCI_USB_CHARGE_BAT_LVL_OFF	0x1
+#define SCI_USB_CHARGE_BAT_LVL_ON	0x4
+#define SCI_USB_CHARGE_BAT_LVL		0x0200
+#define SCI_USB_CHARGE_RAPID_DSP	0x0300
+
+struct toshiba_acpi_dev {
+	struct acpi_device *acpi_dev;
+	const char *method_hci;
+	struct input_dev *hotkey_dev;
+	struct work_struct hotkey_work;
+	struct backlight_device *backlight_dev;
+	struct led_classdev led_dev;
+	struct led_classdev kbd_led;
+	struct led_classdev eco_led;
+	struct miscdevice miscdev;
+
+	int force_fan;
+	int last_key_event;
+	int key_event_valid;
+	int kbd_type;
+	int kbd_mode;
+	int kbd_time;
+	int usbsc_bat_level;
+	int usbsc_mode_base;
+	int hotkey_event_type;
+
+	unsigned int illumination_supported:1;
+	unsigned int video_supported:1;
+	unsigned int fan_supported:1;
+	unsigned int system_event_supported:1;
+	unsigned int ntfy_supported:1;
+	unsigned int info_supported:1;
+	unsigned int tr_backlight_supported:1;
+	unsigned int kbd_illum_supported:1;
+	unsigned int touchpad_supported:1;
+	unsigned int eco_supported:1;
+	unsigned int accelerometer_supported:1;
+	unsigned int usb_sleep_charge_supported:1;
+	unsigned int usb_rapid_charge_supported:1;
+	unsigned int usb_sleep_music_supported:1;
+	unsigned int kbd_function_keys_supported:1;
+	unsigned int panel_power_on_supported:1;
+	unsigned int usb_three_supported:1;
+	unsigned int sysfs_created:1;
+	unsigned int special_functions;
+
+	bool kbd_led_registered;
+	bool illumination_led_registered;
+	bool eco_led_registered;
+};
+
+static struct toshiba_acpi_dev *toshiba_acpi;
+
+static const struct acpi_device_id toshiba_device_ids[] = {
+	{"TOS6200", 0},
+	{"TOS6207", 0},
+	{"TOS6208", 0},
+	{"TOS1900", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, toshiba_device_ids);
+
+static const struct key_entry toshiba_acpi_keymap[] = {
+	{ KE_KEY, 0x9e, { KEY_RFKILL } },
+	{ KE_KEY, 0x101, { KEY_MUTE } },
+	{ KE_KEY, 0x102, { KEY_ZOOMOUT } },
+	{ KE_KEY, 0x103, { KEY_ZOOMIN } },
+	{ KE_KEY, 0x10f, { KEY_TAB } },
+	{ KE_KEY, 0x12c, { KEY_KBDILLUMTOGGLE } },
+	{ KE_KEY, 0x139, { KEY_ZOOMRESET } },
+	{ KE_KEY, 0x13b, { KEY_COFFEE } },
+	{ KE_KEY, 0x13c, { KEY_BATTERY } },
+	{ KE_KEY, 0x13d, { KEY_SLEEP } },
+	{ KE_KEY, 0x13e, { KEY_SUSPEND } },
+	{ KE_KEY, 0x13f, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x140, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 0x141, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0x142, { KEY_WLAN } },
+	{ KE_KEY, 0x143, { KEY_TOUCHPAD_TOGGLE } },
+	{ KE_KEY, 0x17f, { KEY_FN } },
+	{ KE_KEY, 0xb05, { KEY_PROG2 } },
+	{ KE_KEY, 0xb06, { KEY_WWW } },
+	{ KE_KEY, 0xb07, { KEY_MAIL } },
+	{ KE_KEY, 0xb30, { KEY_STOP } },
+	{ KE_KEY, 0xb31, { KEY_PREVIOUSSONG } },
+	{ KE_KEY, 0xb32, { KEY_NEXTSONG } },
+	{ KE_KEY, 0xb33, { KEY_PLAYPAUSE } },
+	{ KE_KEY, 0xb5a, { KEY_MEDIA } },
+	{ KE_IGNORE, 0x1430, { KEY_RESERVED } }, /* Wake from sleep */
+	{ KE_IGNORE, 0x1501, { KEY_RESERVED } }, /* Output changed */
+	{ KE_IGNORE, 0x1502, { KEY_RESERVED } }, /* HDMI plugged/unplugged */
+	{ KE_IGNORE, 0x1ABE, { KEY_RESERVED } }, /* Protection level set */
+	{ KE_IGNORE, 0x1ABF, { KEY_RESERVED } }, /* Protection level off */
+	{ KE_END, 0 },
+};
+
+static const struct key_entry toshiba_acpi_alt_keymap[] = {
+	{ KE_KEY, 0x102, { KEY_ZOOMOUT } },
+	{ KE_KEY, 0x103, { KEY_ZOOMIN } },
+	{ KE_KEY, 0x12c, { KEY_KBDILLUMTOGGLE } },
+	{ KE_KEY, 0x139, { KEY_ZOOMRESET } },
+	{ KE_KEY, 0x13c, { KEY_BRIGHTNESSDOWN } },
+	{ KE_KEY, 0x13d, { KEY_BRIGHTNESSUP } },
+	{ KE_KEY, 0x13e, { KEY_SWITCHVIDEOMODE } },
+	{ KE_KEY, 0x13f, { KEY_TOUCHPAD_TOGGLE } },
+	{ KE_KEY, 0x157, { KEY_MUTE } },
+	{ KE_KEY, 0x158, { KEY_WLAN } },
+	{ KE_END, 0 },
+};
+
+/*
+ * List of models which have a broken acpi-video backlight interface and thus
+ * need to use the toshiba (vendor) interface instead.
+ */
+static const struct dmi_system_id toshiba_vendor_backlight_dmi[] = {
+	{}
+};
+
+/*
+ * Utility
+ */
+
+static inline void _set_bit(u32 *word, u32 mask, int value)
+{
+	*word = (*word & ~mask) | (mask * value);
+}
+
+/*
+ * ACPI interface wrappers
+ */
+
+static int write_acpi_int(const char *methodName, int val)
+{
+	acpi_status status;
+
+	status = acpi_execute_simple_method(NULL, (char *)methodName, val);
+	return (status == AE_OK) ? 0 : -EIO;
+}
+
+/*
+ * Perform a raw configuration call.  Here we don't care about input or output
+ * buffer format.
+ */
+static acpi_status tci_raw(struct toshiba_acpi_dev *dev,
+			   const u32 in[TCI_WORDS], u32 out[TCI_WORDS])
+{
+	struct acpi_object_list params;
+	union acpi_object in_objs[TCI_WORDS];
+	struct acpi_buffer results;
+	union acpi_object out_objs[TCI_WORDS + 1];
+	acpi_status status;
+	int i;
+
+	params.count = TCI_WORDS;
+	params.pointer = in_objs;
+	for (i = 0; i < TCI_WORDS; ++i) {
+		in_objs[i].type = ACPI_TYPE_INTEGER;
+		in_objs[i].integer.value = in[i];
+	}
+
+	results.length = sizeof(out_objs);
+	results.pointer = out_objs;
+
+	status = acpi_evaluate_object(dev->acpi_dev->handle,
+				      (char *)dev->method_hci, &params,
+				      &results);
+	if ((status == AE_OK) && (out_objs->package.count <= TCI_WORDS)) {
+		for (i = 0; i < out_objs->package.count; ++i)
+			out[i] = out_objs->package.elements[i].integer.value;
+	}
+
+	return status;
+}
+
+/*
+ * Common hci tasks
+ *
+ * In addition to the ACPI status, the HCI system returns a result which
+ * may be useful (such as "not supported").
+ */
+
+static u32 hci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
+{
+	u32 in[TCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status = tci_raw(dev, in, out);
+
+	return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE;
+}
+
+static u32 hci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
+{
+	u32 in[TCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status = tci_raw(dev, in, out);
+
+	if (ACPI_FAILURE(status))
+		return TOS_FAILURE;
+
+	*out1 = out[2];
+
+	return out[0];
+}
+
+/*
+ * Common sci tasks
+ */
+
+static int sci_open(struct toshiba_acpi_dev *dev)
+{
+	u32 in[TCI_WORDS] = { SCI_OPEN, 0, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	status = tci_raw(dev, in, out);
+	if  (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to open SCI failed\n");
+		return 0;
+	}
+
+	if (out[0] == TOS_OPEN_CLOSE_OK) {
+		return 1;
+	} else if (out[0] == TOS_ALREADY_OPEN) {
+		pr_info("Toshiba SCI already opened\n");
+		return 1;
+	} else if (out[0] == TOS_NOT_SUPPORTED) {
+		/*
+		 * Some BIOSes do not have the SCI open/close functions
+		 * implemented and return 0x8000 (Not Supported), failing to
+		 * register some supported features.
+		 *
+		 * Simply return 1 if we hit those affected laptops to make the
+		 * supported features work.
+		 *
+		 * In the case that some laptops really do not support the SCI,
+		 * all the SCI dependent functions check for TOS_NOT_SUPPORTED,
+		 * and thus, not registering support for the queried feature.
+		 */
+		return 1;
+	} else if (out[0] == TOS_NOT_PRESENT) {
+		pr_info("Toshiba SCI is not present\n");
+	}
+
+	return 0;
+}
+
+static void sci_close(struct toshiba_acpi_dev *dev)
+{
+	u32 in[TCI_WORDS] = { SCI_CLOSE, 0, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to close SCI failed\n");
+		return;
+	}
+
+	if (out[0] == TOS_OPEN_CLOSE_OK)
+		return;
+	else if (out[0] == TOS_NOT_OPENED)
+		pr_info("Toshiba SCI not opened\n");
+	else if (out[0] == TOS_NOT_PRESENT)
+		pr_info("Toshiba SCI is not present\n");
+}
+
+static u32 sci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1)
+{
+	u32 in[TCI_WORDS] = { SCI_GET, reg, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status = tci_raw(dev, in, out);
+
+	if (ACPI_FAILURE(status))
+		return TOS_FAILURE;
+
+	*out1 = out[2];
+
+	return out[0];
+}
+
+static u32 sci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1)
+{
+	u32 in[TCI_WORDS] = { SCI_SET, reg, in1, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status = tci_raw(dev, in, out);
+
+	return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE;
+}
+
+/* Illumination support */
+static void toshiba_illumination_available(struct toshiba_acpi_dev *dev)
+{
+	u32 in[TCI_WORDS] = { SCI_GET, SCI_ILLUMINATION, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	dev->illumination_supported = 0;
+	dev->illumination_led_registered = false;
+
+	if (!sci_open(dev))
+		return;
+
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status))
+		pr_err("ACPI call to query Illumination support failed\n");
+	else if (out[0] == TOS_SUCCESS)
+		dev->illumination_supported = 1;
+}
+
+static void toshiba_illumination_set(struct led_classdev *cdev,
+				     enum led_brightness brightness)
+{
+	struct toshiba_acpi_dev *dev = container_of(cdev,
+			struct toshiba_acpi_dev, led_dev);
+	u32 result;
+	u32 state;
+
+	/* First request : initialize communication. */
+	if (!sci_open(dev))
+		return;
+
+	/* Switch the illumination on/off */
+	state = brightness ? 1 : 0;
+	result = sci_write(dev, SCI_ILLUMINATION, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call for illumination failed\n");
+}
+
+static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev)
+{
+	struct toshiba_acpi_dev *dev = container_of(cdev,
+			struct toshiba_acpi_dev, led_dev);
+	u32 state, result;
+
+	/* First request : initialize communication. */
+	if (!sci_open(dev))
+		return LED_OFF;
+
+	/* Check the illumination */
+	result = sci_read(dev, SCI_ILLUMINATION, &state);
+	sci_close(dev);
+	if (result == TOS_FAILURE) {
+		pr_err("ACPI call for illumination failed\n");
+		return LED_OFF;
+	} else if (result != TOS_SUCCESS) {
+		return LED_OFF;
+	}
+
+	return state ? LED_FULL : LED_OFF;
+}
+
+/* KBD Illumination */
+static void toshiba_kbd_illum_available(struct toshiba_acpi_dev *dev)
+{
+	u32 in[TCI_WORDS] = { SCI_GET, SCI_KBD_ILLUM_STATUS, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	dev->kbd_illum_supported = 0;
+	dev->kbd_led_registered = false;
+
+	if (!sci_open(dev))
+		return;
+
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to query kbd illumination support failed\n");
+	} else if (out[0] == TOS_SUCCESS) {
+		/*
+		 * Check for keyboard backlight timeout max value,
+		 * previous kbd backlight implementation set this to
+		 * 0x3c0003, and now the new implementation set this
+		 * to 0x3c001a, use this to distinguish between them.
+		 */
+		if (out[3] == SCI_KBD_TIME_MAX)
+			dev->kbd_type = 2;
+		else
+			dev->kbd_type = 1;
+		/* Get the current keyboard backlight mode */
+		dev->kbd_mode = out[2] & SCI_KBD_MODE_MASK;
+		/* Get the current time (1-60 seconds) */
+		dev->kbd_time = out[2] >> HCI_MISC_SHIFT;
+		/* Flag as supported */
+		dev->kbd_illum_supported = 1;
+	}
+}
+
+static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_KBD_ILLUM_STATUS, time);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set KBD backlight status failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_kbd_illum_status_get(struct toshiba_acpi_dev *dev, u32 *time)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_KBD_ILLUM_STATUS, time);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get KBD backlight status failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev)
+{
+	struct toshiba_acpi_dev *dev = container_of(cdev,
+			struct toshiba_acpi_dev, kbd_led);
+	u32 result;
+	u32 state;
+
+	/* Check the keyboard backlight state */
+	result = hci_read(dev, HCI_KBD_ILLUMINATION, &state);
+	if (result == TOS_FAILURE) {
+		pr_err("ACPI call to get the keyboard backlight failed\n");
+		return LED_OFF;
+	} else if (result != TOS_SUCCESS) {
+		return LED_OFF;
+	}
+
+	return state ? LED_FULL : LED_OFF;
+}
+
+static void toshiba_kbd_backlight_set(struct led_classdev *cdev,
+				     enum led_brightness brightness)
+{
+	struct toshiba_acpi_dev *dev = container_of(cdev,
+			struct toshiba_acpi_dev, kbd_led);
+	u32 result;
+	u32 state;
+
+	/* Set the keyboard backlight state */
+	state = brightness ? 1 : 0;
+	result = hci_write(dev, HCI_KBD_ILLUMINATION, state);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set KBD Illumination mode failed\n");
+}
+
+/* TouchPad support */
+static int toshiba_touchpad_set(struct toshiba_acpi_dev *dev, u32 state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_TOUCHPAD, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set the touchpad failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_touchpad_get(struct toshiba_acpi_dev *dev, u32 *state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_TOUCHPAD, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to query the touchpad failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+/* Eco Mode support */
+static void toshiba_eco_mode_available(struct toshiba_acpi_dev *dev)
+{
+	acpi_status status;
+	u32 in[TCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+
+	dev->eco_supported = 0;
+	dev->eco_led_registered = false;
+
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get ECO led failed\n");
+	} else if (out[0] == TOS_INPUT_DATA_ERROR) {
+		/*
+		 * If we receive 0x8300 (Input Data Error), it means that the
+		 * LED device is present, but that we just screwed the input
+		 * parameters.
+		 *
+		 * Let's query the status of the LED to see if we really have a
+		 * success response, indicating the actual presense of the LED,
+		 * bail out otherwise.
+		 */
+		in[3] = 1;
+		status = tci_raw(dev, in, out);
+		if (ACPI_FAILURE(status))
+			pr_err("ACPI call to get ECO led failed\n");
+		else if (out[0] == TOS_SUCCESS)
+			dev->eco_supported = 1;
+	}
+}
+
+static enum led_brightness
+toshiba_eco_mode_get_status(struct led_classdev *cdev)
+{
+	struct toshiba_acpi_dev *dev = container_of(cdev,
+			struct toshiba_acpi_dev, eco_led);
+	u32 in[TCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 1, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get ECO led failed\n");
+		return LED_OFF;
+	} else if (out[0] != TOS_SUCCESS) {
+		return LED_OFF;
+	}
+
+	return out[2] ? LED_FULL : LED_OFF;
+}
+
+static void toshiba_eco_mode_set_status(struct led_classdev *cdev,
+				     enum led_brightness brightness)
+{
+	struct toshiba_acpi_dev *dev = container_of(cdev,
+			struct toshiba_acpi_dev, eco_led);
+	u32 in[TCI_WORDS] = { HCI_SET, HCI_ECO_MODE, 0, 1, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	/* Switch the Eco Mode led on/off */
+	in[2] = (brightness) ? 1 : 0;
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status))
+		pr_err("ACPI call to set ECO led failed\n");
+}
+
+/* Accelerometer support */
+static void toshiba_accelerometer_available(struct toshiba_acpi_dev *dev)
+{
+	u32 in[TCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER2, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	dev->accelerometer_supported = 0;
+
+	/*
+	 * Check if the accelerometer call exists,
+	 * this call also serves as initialization
+	 */
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status))
+		pr_err("ACPI call to query the accelerometer failed\n");
+	else if (out[0] == TOS_SUCCESS)
+		dev->accelerometer_supported = 1;
+}
+
+static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev,
+				     u32 *xy, u32 *z)
+{
+	u32 in[TCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER, 0, 1, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	/* Check the Accelerometer status */
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to query the accelerometer failed\n");
+		return -EIO;
+	} else if (out[0] == TOS_NOT_SUPPORTED) {
+		return -ENODEV;
+	} else if (out[0] == TOS_SUCCESS) {
+		*xy = out[2];
+		*z = out[4];
+		return 0;
+	}
+
+	return -EIO;
+}
+
+/* Sleep (Charge and Music) utilities support */
+static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev)
+{
+	u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	dev->usb_sleep_charge_supported = 0;
+
+	if (!sci_open(dev))
+		return;
+
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get USB Sleep and Charge mode failed\n");
+		sci_close(dev);
+		return;
+	} else if (out[0] == TOS_NOT_SUPPORTED) {
+		sci_close(dev);
+		return;
+	} else if (out[0] == TOS_SUCCESS) {
+		dev->usbsc_mode_base = out[4];
+	}
+
+	in[5] = SCI_USB_CHARGE_BAT_LVL;
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get USB Sleep and Charge mode failed\n");
+	} else if (out[0] == TOS_SUCCESS) {
+		dev->usbsc_bat_level = out[2];
+		/* Flag as supported */
+		dev->usb_sleep_charge_supported = 1;
+	}
+
+}
+
+static int toshiba_usb_sleep_charge_get(struct toshiba_acpi_dev *dev,
+					u32 *mode)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_USB_SLEEP_CHARGE, mode);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set USB S&C mode failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_usb_sleep_charge_set(struct toshiba_acpi_dev *dev,
+					u32 mode)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_USB_SLEEP_CHARGE, mode);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set USB S&C mode failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev,
+					      u32 *mode)
+{
+	u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	in[5] = SCI_USB_CHARGE_BAT_LVL;
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get USB S&C battery level failed\n");
+	} else if (out[0] == TOS_NOT_SUPPORTED) {
+		return -ENODEV;
+	} else if (out[0] == TOS_SUCCESS) {
+		*mode = out[2];
+		return 0;
+	}
+
+	return -EIO;
+}
+
+static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev,
+					      u32 mode)
+{
+	u32 in[TCI_WORDS] = { SCI_SET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	in[2] = mode;
+	in[5] = SCI_USB_CHARGE_BAT_LVL;
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status))
+		pr_err("ACPI call to set USB S&C battery level failed\n");
+	else if (out[0] == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return out[0] == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev,
+					u32 *state)
+{
+	u32 in[TCI_WORDS] = { SCI_GET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	in[5] = SCI_USB_CHARGE_RAPID_DSP;
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get USB Rapid Charge failed\n");
+	} else if (out[0] == TOS_NOT_SUPPORTED) {
+		return -ENODEV;
+	} else if (out[0] == TOS_SUCCESS || out[0] == TOS_SUCCESS2) {
+		*state = out[2];
+		return 0;
+	}
+
+	return -EIO;
+}
+
+static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev,
+					u32 state)
+{
+	u32 in[TCI_WORDS] = { SCI_SET, SCI_USB_SLEEP_CHARGE, 0, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	in[2] = state;
+	in[5] = SCI_USB_CHARGE_RAPID_DSP;
+	status = tci_raw(dev, in, out);
+	sci_close(dev);
+	if (ACPI_FAILURE(status))
+		pr_err("ACPI call to set USB Rapid Charge failed\n");
+	else if (out[0] == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return (out[0] == TOS_SUCCESS || out[0] == TOS_SUCCESS2) ? 0 : -EIO;
+}
+
+static int toshiba_usb_sleep_music_get(struct toshiba_acpi_dev *dev, u32 *state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_USB_SLEEP_MUSIC, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get Sleep and Music failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_usb_sleep_music_set(struct toshiba_acpi_dev *dev, u32 state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_USB_SLEEP_MUSIC, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set Sleep and Music failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+/* Keyboard function keys */
+static int toshiba_function_keys_get(struct toshiba_acpi_dev *dev, u32 *mode)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_KBD_FUNCTION_KEYS, mode);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get KBD function keys failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO;
+}
+
+static int toshiba_function_keys_set(struct toshiba_acpi_dev *dev, u32 mode)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_KBD_FUNCTION_KEYS, mode);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set KBD function keys failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO;
+}
+
+/* Panel Power ON */
+static int toshiba_panel_power_on_get(struct toshiba_acpi_dev *dev, u32 *state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_PANEL_POWER_ON, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get Panel Power ON failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int toshiba_panel_power_on_set(struct toshiba_acpi_dev *dev, u32 state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_PANEL_POWER_ON, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set Panel Power ON failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+/* USB Three */
+static int toshiba_usb_three_get(struct toshiba_acpi_dev *dev, u32 *state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_read(dev, SCI_USB_THREE, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get USB 3 failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO;
+}
+
+static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state)
+{
+	u32 result;
+
+	if (!sci_open(dev))
+		return -EIO;
+
+	result = sci_write(dev, SCI_USB_THREE, state);
+	sci_close(dev);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set USB 3 failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return (result == TOS_SUCCESS || result == TOS_SUCCESS2) ? 0 : -EIO;
+}
+
+/* Hotkey Event type */
+static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev,
+					 u32 *type)
+{
+	u32 in[TCI_WORDS] = { HCI_GET, HCI_SYSTEM_INFO, 0x03, 0, 0, 0 };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	status = tci_raw(dev, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to get System type failed\n");
+	} else if (out[0] == TOS_NOT_SUPPORTED) {
+		return -ENODEV;
+	} else if (out[0] == TOS_SUCCESS) {
+		*type = out[3];
+		return 0;
+	}
+
+	return -EIO;
+}
+
+/* Transflective Backlight */
+static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, u32 *status)
+{
+	u32 result = hci_read(dev, HCI_TR_BACKLIGHT, status);
+
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get Transflective Backlight failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, u32 status)
+{
+	u32 result = hci_write(dev, HCI_TR_BACKLIGHT, !status);
+
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set Transflective Backlight failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static struct proc_dir_entry *toshiba_proc_dir;
+
+/* LCD Brightness */
+static int __get_lcd_brightness(struct toshiba_acpi_dev *dev)
+{
+	u32 result;
+	u32 value;
+	int brightness = 0;
+
+	if (dev->tr_backlight_supported) {
+		int ret = get_tr_backlight_status(dev, &value);
+
+		if (ret)
+			return ret;
+		if (value)
+			return 0;
+		brightness++;
+	}
+
+	result = hci_read(dev, HCI_LCD_BRIGHTNESS, &value);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get LCD Brightness failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+	if (result == TOS_SUCCESS)
+		return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT);
+
+	return -EIO;
+}
+
+static int get_lcd_brightness(struct backlight_device *bd)
+{
+	struct toshiba_acpi_dev *dev = bl_get_data(bd);
+
+	return __get_lcd_brightness(dev);
+}
+
+static int lcd_proc_show(struct seq_file *m, void *v)
+{
+	struct toshiba_acpi_dev *dev = m->private;
+	int levels;
+	int value;
+
+	if (!dev->backlight_dev)
+		return -ENODEV;
+
+	levels = dev->backlight_dev->props.max_brightness + 1;
+	value = get_lcd_brightness(dev->backlight_dev);
+	if (value >= 0) {
+		seq_printf(m, "brightness:              %d\n", value);
+		seq_printf(m, "brightness_levels:       %d\n", levels);
+		return 0;
+	}
+
+	pr_err("Error reading LCD brightness\n");
+
+	return -EIO;
+}
+
+static int lcd_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, lcd_proc_show, PDE_DATA(inode));
+}
+
+static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value)
+{
+	u32 result;
+
+	if (dev->tr_backlight_supported) {
+		int ret = set_tr_backlight_status(dev, !value);
+
+		if (ret)
+			return ret;
+		if (value)
+			value--;
+	}
+
+	value = value << HCI_LCD_BRIGHTNESS_SHIFT;
+	result = hci_write(dev, HCI_LCD_BRIGHTNESS, value);
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set LCD Brightness failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int set_lcd_status(struct backlight_device *bd)
+{
+	struct toshiba_acpi_dev *dev = bl_get_data(bd);
+
+	return set_lcd_brightness(dev, bd->props.brightness);
+}
+
+static ssize_t lcd_proc_write(struct file *file, const char __user *buf,
+			      size_t count, loff_t *pos)
+{
+	struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+	char cmd[42];
+	size_t len;
+	int levels = dev->backlight_dev->props.max_brightness + 1;
+	int value;
+
+	len = min(count, sizeof(cmd) - 1);
+	if (copy_from_user(cmd, buf, len))
+		return -EFAULT;
+	cmd[len] = '\0';
+
+	if (sscanf(cmd, " brightness : %i", &value) != 1 &&
+	    value < 0 && value > levels)
+		return -EINVAL;
+
+	if (set_lcd_brightness(dev, value))
+		return -EIO;
+
+	return count;
+}
+
+static const struct file_operations lcd_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= lcd_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= lcd_proc_write,
+};
+
+/* Video-Out */
+static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status)
+{
+	u32 result = hci_read(dev, HCI_VIDEO_OUT, status);
+
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get Video-Out failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int video_proc_show(struct seq_file *m, void *v)
+{
+	struct toshiba_acpi_dev *dev = m->private;
+	u32 value;
+
+	if (!get_video_status(dev, &value)) {
+		int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0;
+		int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0;
+		int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0;
+
+		seq_printf(m, "lcd_out:                 %d\n", is_lcd);
+		seq_printf(m, "crt_out:                 %d\n", is_crt);
+		seq_printf(m, "tv_out:                  %d\n", is_tv);
+		return 0;
+	}
+
+	return -EIO;
+}
+
+static int video_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, video_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t video_proc_write(struct file *file, const char __user *buf,
+				size_t count, loff_t *pos)
+{
+	struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+	char *buffer;
+	char *cmd;
+	int remain = count;
+	int lcd_out = -1;
+	int crt_out = -1;
+	int tv_out = -1;
+	int value;
+	int ret;
+	u32 video_out;
+
+	cmd = kmalloc(count + 1, GFP_KERNEL);
+	if (!cmd)
+		return -ENOMEM;
+	if (copy_from_user(cmd, buf, count)) {
+		kfree(cmd);
+		return -EFAULT;
+	}
+	cmd[count] = '\0';
+
+	buffer = cmd;
+
+	/*
+	 * Scan expression.  Multiple expressions may be delimited with ;
+	 * NOTE: To keep scanning simple, invalid fields are ignored.
+	 */
+	while (remain) {
+		if (sscanf(buffer, " lcd_out : %i", &value) == 1)
+			lcd_out = value & 1;
+		else if (sscanf(buffer, " crt_out : %i", &value) == 1)
+			crt_out = value & 1;
+		else if (sscanf(buffer, " tv_out : %i", &value) == 1)
+			tv_out = value & 1;
+		/* Advance to one character past the next ; */
+		do {
+			++buffer;
+			--remain;
+		} while (remain && *(buffer - 1) != ';');
+	}
+
+	kfree(cmd);
+
+	ret = get_video_status(dev, &video_out);
+	if (!ret) {
+		unsigned int new_video_out = video_out;
+
+		if (lcd_out != -1)
+			_set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out);
+		if (crt_out != -1)
+			_set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out);
+		if (tv_out != -1)
+			_set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out);
+		/*
+		 * To avoid unnecessary video disruption, only write the new
+		 * video setting if something changed.
+		 */
+		if (new_video_out != video_out)
+			ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out);
+	}
+
+	return ret ? -EIO : count;
+}
+
+static const struct file_operations video_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= video_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= video_proc_write,
+};
+
+/* Fan status */
+static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status)
+{
+	u32 result = hci_read(dev, HCI_FAN, status);
+
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to get Fan status failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int set_fan_status(struct toshiba_acpi_dev *dev, u32 status)
+{
+	u32 result = hci_write(dev, HCI_FAN, status);
+
+	if (result == TOS_FAILURE)
+		pr_err("ACPI call to set Fan status failed\n");
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return result == TOS_SUCCESS ? 0 : -EIO;
+}
+
+static int fan_proc_show(struct seq_file *m, void *v)
+{
+	struct toshiba_acpi_dev *dev = m->private;
+	u32 value;
+
+	if (get_fan_status(dev, &value))
+		return -EIO;
+
+	seq_printf(m, "running:                 %d\n", (value > 0));
+	seq_printf(m, "force_on:                %d\n", dev->force_fan);
+
+	return 0;
+}
+
+static int fan_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, fan_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t fan_proc_write(struct file *file, const char __user *buf,
+			      size_t count, loff_t *pos)
+{
+	struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+	char cmd[42];
+	size_t len;
+	int value;
+
+	len = min(count, sizeof(cmd) - 1);
+	if (copy_from_user(cmd, buf, len))
+		return -EFAULT;
+	cmd[len] = '\0';
+
+	if (sscanf(cmd, " force_on : %i", &value) != 1 &&
+	    value != 0 && value != 1)
+		return -EINVAL;
+
+	if (set_fan_status(dev, value))
+		return -EIO;
+
+	dev->force_fan = value;
+
+	return count;
+}
+
+static const struct file_operations fan_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= fan_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= fan_proc_write,
+};
+
+static int keys_proc_show(struct seq_file *m, void *v)
+{
+	struct toshiba_acpi_dev *dev = m->private;
+
+	seq_printf(m, "hotkey_ready:            %d\n", dev->key_event_valid);
+	seq_printf(m, "hotkey:                  0x%04x\n", dev->last_key_event);
+
+	return 0;
+}
+
+static int keys_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, keys_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t keys_proc_write(struct file *file, const char __user *buf,
+			       size_t count, loff_t *pos)
+{
+	struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+	char cmd[42];
+	size_t len;
+	int value;
+
+	len = min(count, sizeof(cmd) - 1);
+	if (copy_from_user(cmd, buf, len))
+		return -EFAULT;
+	cmd[len] = '\0';
+
+	if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0)
+		dev->key_event_valid = 0;
+	else
+		return -EINVAL;
+
+	return count;
+}
+
+static const struct file_operations keys_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= keys_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= keys_proc_write,
+};
+
+static int version_proc_show(struct seq_file *m, void *v)
+{
+	seq_printf(m, "driver:                  %s\n", TOSHIBA_ACPI_VERSION);
+	seq_printf(m, "proc_interface:          %d\n", PROC_INTERFACE_VERSION);
+	return 0;
+}
+
+static int version_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, version_proc_show, PDE_DATA(inode));
+}
+
+static const struct file_operations version_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= version_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+/*
+ * Proc and module init
+ */
+
+#define PROC_TOSHIBA		"toshiba"
+
+static void create_toshiba_proc_entries(struct toshiba_acpi_dev *dev)
+{
+	if (dev->backlight_dev)
+		proc_create_data("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+				 &lcd_proc_fops, dev);
+	if (dev->video_supported)
+		proc_create_data("video", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+				 &video_proc_fops, dev);
+	if (dev->fan_supported)
+		proc_create_data("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+				 &fan_proc_fops, dev);
+	if (dev->hotkey_dev)
+		proc_create_data("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+				 &keys_proc_fops, dev);
+	proc_create_data("version", S_IRUGO, toshiba_proc_dir,
+			 &version_proc_fops, dev);
+}
+
+static void remove_toshiba_proc_entries(struct toshiba_acpi_dev *dev)
+{
+	if (dev->backlight_dev)
+		remove_proc_entry("lcd", toshiba_proc_dir);
+	if (dev->video_supported)
+		remove_proc_entry("video", toshiba_proc_dir);
+	if (dev->fan_supported)
+		remove_proc_entry("fan", toshiba_proc_dir);
+	if (dev->hotkey_dev)
+		remove_proc_entry("keys", toshiba_proc_dir);
+	remove_proc_entry("version", toshiba_proc_dir);
+}
+
+static const struct backlight_ops toshiba_backlight_data = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.get_brightness = get_lcd_brightness,
+	.update_status  = set_lcd_status,
+};
+
+/*
+ * Sysfs files
+ */
+static ssize_t version_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%s\n", TOSHIBA_ACPI_VERSION);
+}
+static DEVICE_ATTR_RO(version);
+
+static ssize_t fan_store(struct device *dev,
+			 struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int state;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	ret = set_fan_status(toshiba, state);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t fan_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 value;
+	int ret;
+
+	ret = get_fan_status(toshiba, &value);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%d\n", value);
+}
+static DEVICE_ATTR_RW(fan);
+
+static ssize_t kbd_backlight_mode_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int mode;
+	int ret;
+
+
+	ret = kstrtoint(buf, 0, &mode);
+	if (ret)
+		return ret;
+
+	/* Check for supported modes depending on keyboard backlight type */
+	if (toshiba->kbd_type == 1) {
+		/* Type 1 supports SCI_KBD_MODE_FNZ and SCI_KBD_MODE_AUTO */
+		if (mode != SCI_KBD_MODE_FNZ && mode != SCI_KBD_MODE_AUTO)
+			return -EINVAL;
+	} else if (toshiba->kbd_type == 2) {
+		/* Type 2 doesn't support SCI_KBD_MODE_FNZ */
+		if (mode != SCI_KBD_MODE_AUTO && mode != SCI_KBD_MODE_ON &&
+		    mode != SCI_KBD_MODE_OFF)
+			return -EINVAL;
+	}
+
+	/*
+	 * Set the Keyboard Backlight Mode where:
+	 *	Auto - KBD backlight turns off automatically in given time
+	 *	FN-Z - KBD backlight "toggles" when hotkey pressed
+	 *	ON   - KBD backlight is always on
+	 *	OFF  - KBD backlight is always off
+	 */
+
+	/* Only make a change if the actual mode has changed */
+	if (toshiba->kbd_mode != mode) {
+		/* Shift the time to "base time" (0x3c0000 == 60 seconds) */
+		int time = toshiba->kbd_time << HCI_MISC_SHIFT;
+
+		/* OR the "base time" to the actual method format */
+		if (toshiba->kbd_type == 1) {
+			/* Type 1 requires the current mode */
+			time |= toshiba->kbd_mode;
+		} else if (toshiba->kbd_type == 2) {
+			/* Type 2 requires the desired mode */
+			time |= mode;
+		}
+
+		ret = toshiba_kbd_illum_status_set(toshiba, time);
+		if (ret)
+			return ret;
+
+		toshiba->kbd_mode = mode;
+	}
+
+	return count;
+}
+
+static ssize_t kbd_backlight_mode_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 time;
+
+	if (toshiba_kbd_illum_status_get(toshiba, &time) < 0)
+		return -EIO;
+
+	return sprintf(buf, "%i\n", time & SCI_KBD_MODE_MASK);
+}
+static DEVICE_ATTR_RW(kbd_backlight_mode);
+
+static ssize_t kbd_type_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", toshiba->kbd_type);
+}
+static DEVICE_ATTR_RO(kbd_type);
+
+static ssize_t available_kbd_modes_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+
+	if (toshiba->kbd_type == 1)
+		return sprintf(buf, "0x%x 0x%x\n",
+			       SCI_KBD_MODE_FNZ, SCI_KBD_MODE_AUTO);
+
+	return sprintf(buf, "0x%x 0x%x 0x%x\n",
+		       SCI_KBD_MODE_AUTO, SCI_KBD_MODE_ON, SCI_KBD_MODE_OFF);
+}
+static DEVICE_ATTR_RO(available_kbd_modes);
+
+static ssize_t kbd_backlight_timeout_store(struct device *dev,
+					   struct device_attribute *attr,
+					   const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int time;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &time);
+	if (ret)
+		return ret;
+
+	/* Check for supported values depending on kbd_type */
+	if (toshiba->kbd_type == 1) {
+		if (time < 0 || time > 60)
+			return -EINVAL;
+	} else if (toshiba->kbd_type == 2) {
+		if (time < 1 || time > 60)
+			return -EINVAL;
+	}
+
+	/* Set the Keyboard Backlight Timeout */
+
+	/* Only make a change if the actual timeout has changed */
+	if (toshiba->kbd_time != time) {
+		/* Shift the time to "base time" (0x3c0000 == 60 seconds) */
+		time = time << HCI_MISC_SHIFT;
+		/* OR the "base time" to the actual method format */
+		if (toshiba->kbd_type == 1)
+			time |= SCI_KBD_MODE_FNZ;
+		else if (toshiba->kbd_type == 2)
+			time |= SCI_KBD_MODE_AUTO;
+
+		ret = toshiba_kbd_illum_status_set(toshiba, time);
+		if (ret)
+			return ret;
+
+		toshiba->kbd_time = time >> HCI_MISC_SHIFT;
+	}
+
+	return count;
+}
+
+static ssize_t kbd_backlight_timeout_show(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 time;
+
+	if (toshiba_kbd_illum_status_get(toshiba, &time) < 0)
+		return -EIO;
+
+	return sprintf(buf, "%i\n", time >> HCI_MISC_SHIFT);
+}
+static DEVICE_ATTR_RW(kbd_backlight_timeout);
+
+static ssize_t touchpad_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int state;
+	int ret;
+
+	/* Set the TouchPad on/off, 0 - Disable | 1 - Enable */
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	ret = toshiba_touchpad_set(toshiba, state);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t touchpad_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 state;
+	int ret;
+
+	ret = toshiba_touchpad_get(toshiba, &state);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%i\n", state);
+}
+static DEVICE_ATTR_RW(touchpad);
+
+static ssize_t position_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 xyval, zval, tmp;
+	u16 x, y, z;
+	int ret;
+
+	xyval = zval = 0;
+	ret = toshiba_accelerometer_get(toshiba, &xyval, &zval);
+	if (ret < 0)
+		return ret;
+
+	x = xyval & HCI_ACCEL_MASK;
+	tmp = xyval >> HCI_MISC_SHIFT;
+	y = tmp & HCI_ACCEL_MASK;
+	z = zval & HCI_ACCEL_MASK;
+
+	return sprintf(buf, "%d %d %d\n", x, y, z);
+}
+static DEVICE_ATTR_RO(position);
+
+static ssize_t usb_sleep_charge_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 mode;
+	int ret;
+
+	ret = toshiba_usb_sleep_charge_get(toshiba, &mode);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%x\n", mode & SCI_USB_CHARGE_MODE_MASK);
+}
+
+static ssize_t usb_sleep_charge_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 mode;
+	int state;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+	/*
+	 * Check for supported values, where:
+	 * 0 - Disabled
+	 * 1 - Alternate (Non USB conformant devices that require more power)
+	 * 2 - Auto (USB conformant devices)
+	 * 3 - Typical
+	 */
+	if (state != 0 && state != 1 && state != 2 && state != 3)
+		return -EINVAL;
+
+	/* Set the USB charging mode to internal value */
+	mode = toshiba->usbsc_mode_base;
+	if (state == 0)
+		mode |= SCI_USB_CHARGE_DISABLED;
+	else if (state == 1)
+		mode |= SCI_USB_CHARGE_ALTERNATE;
+	else if (state == 2)
+		mode |= SCI_USB_CHARGE_AUTO;
+	else if (state == 3)
+		mode |= SCI_USB_CHARGE_TYPICAL;
+
+	ret = toshiba_usb_sleep_charge_set(toshiba, mode);
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW(usb_sleep_charge);
+
+static ssize_t sleep_functions_on_battery_show(struct device *dev,
+					       struct device_attribute *attr,
+					       char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 state;
+	int bat_lvl;
+	int status;
+	int ret;
+	int tmp;
+
+	ret = toshiba_sleep_functions_status_get(toshiba, &state);
+	if (ret < 0)
+		return ret;
+
+	/* Determine the status: 0x4 - Enabled | 0x1 - Disabled */
+	tmp = state & SCI_USB_CHARGE_BAT_MASK;
+	status = (tmp == 0x4) ? 1 : 0;
+	/* Determine the battery level set */
+	bat_lvl = state >> HCI_MISC_SHIFT;
+
+	return sprintf(buf, "%d %d\n", status, bat_lvl);
+}
+
+static ssize_t sleep_functions_on_battery_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 status;
+	int value;
+	int ret;
+	int tmp;
+
+	ret = kstrtoint(buf, 0, &value);
+	if (ret)
+		return ret;
+
+	/*
+	 * Set the status of the function:
+	 * 0 - Disabled
+	 * 1-100 - Enabled
+	 */
+	if (value < 0 || value > 100)
+		return -EINVAL;
+
+	if (value == 0) {
+		tmp = toshiba->usbsc_bat_level << HCI_MISC_SHIFT;
+		status = tmp | SCI_USB_CHARGE_BAT_LVL_OFF;
+	} else {
+		tmp = value << HCI_MISC_SHIFT;
+		status = tmp | SCI_USB_CHARGE_BAT_LVL_ON;
+	}
+	ret = toshiba_sleep_functions_status_set(toshiba, status);
+	if (ret < 0)
+		return ret;
+
+	toshiba->usbsc_bat_level = status >> HCI_MISC_SHIFT;
+
+	return count;
+}
+static DEVICE_ATTR_RW(sleep_functions_on_battery);
+
+static ssize_t usb_rapid_charge_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 state;
+	int ret;
+
+	ret = toshiba_usb_rapid_charge_get(toshiba, &state);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", state);
+}
+
+static ssize_t usb_rapid_charge_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int state;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	ret = toshiba_usb_rapid_charge_set(toshiba, state);
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW(usb_rapid_charge);
+
+static ssize_t usb_sleep_music_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 state;
+	int ret;
+
+	ret = toshiba_usb_sleep_music_get(toshiba, &state);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", state);
+}
+
+static ssize_t usb_sleep_music_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int state;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	ret = toshiba_usb_sleep_music_set(toshiba, state);
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW(usb_sleep_music);
+
+static ssize_t kbd_function_keys_show(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int mode;
+	int ret;
+
+	ret = toshiba_function_keys_get(toshiba, &mode);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", mode);
+}
+
+static ssize_t kbd_function_keys_store(struct device *dev,
+				       struct device_attribute *attr,
+				       const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int mode;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &mode);
+	if (ret)
+		return ret;
+	/*
+	 * Check for the function keys mode where:
+	 * 0 - Normal operation (F{1-12} as usual and hotkeys via FN-F{1-12})
+	 * 1 - Special functions (Opposite of the above setting)
+	 */
+	if (mode != 0 && mode != 1)
+		return -EINVAL;
+
+	ret = toshiba_function_keys_set(toshiba, mode);
+	if (ret)
+		return ret;
+
+	pr_info("Reboot for changes to KBD Function Keys to take effect");
+
+	return count;
+}
+static DEVICE_ATTR_RW(kbd_function_keys);
+
+static ssize_t panel_power_on_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 state;
+	int ret;
+
+	ret = toshiba_panel_power_on_get(toshiba, &state);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", state);
+}
+
+static ssize_t panel_power_on_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int state;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	ret = toshiba_panel_power_on_set(toshiba, state);
+	if (ret)
+		return ret;
+
+	pr_info("Reboot for changes to Panel Power ON to take effect");
+
+	return count;
+}
+static DEVICE_ATTR_RW(panel_power_on);
+
+static ssize_t usb_three_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	u32 state;
+	int ret;
+
+	ret = toshiba_usb_three_get(toshiba, &state);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%d\n", state);
+}
+
+static ssize_t usb_three_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
+	int state;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &state);
+	if (ret)
+		return ret;
+	/*
+	 * Check for USB 3 mode where:
+	 * 0 - Disabled (Acts like a USB 2 port, saving power)
+	 * 1 - Enabled
+	 */
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	ret = toshiba_usb_three_set(toshiba, state);
+	if (ret)
+		return ret;
+
+	pr_info("Reboot for changes to USB 3 to take effect");
+
+	return count;
+}
+static DEVICE_ATTR_RW(usb_three);
+
+static struct attribute *toshiba_attributes[] = {
+	&dev_attr_version.attr,
+	&dev_attr_fan.attr,
+	&dev_attr_kbd_backlight_mode.attr,
+	&dev_attr_kbd_type.attr,
+	&dev_attr_available_kbd_modes.attr,
+	&dev_attr_kbd_backlight_timeout.attr,
+	&dev_attr_touchpad.attr,
+	&dev_attr_position.attr,
+	&dev_attr_usb_sleep_charge.attr,
+	&dev_attr_sleep_functions_on_battery.attr,
+	&dev_attr_usb_rapid_charge.attr,
+	&dev_attr_usb_sleep_music.attr,
+	&dev_attr_kbd_function_keys.attr,
+	&dev_attr_panel_power_on.attr,
+	&dev_attr_usb_three.attr,
+	NULL,
+};
+
+static umode_t toshiba_sysfs_is_visible(struct kobject *kobj,
+					struct attribute *attr, int idx)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct toshiba_acpi_dev *drv = dev_get_drvdata(dev);
+	bool exists = true;
+
+	if (attr == &dev_attr_fan.attr)
+		exists = (drv->fan_supported) ? true : false;
+	else if (attr == &dev_attr_kbd_backlight_mode.attr)
+		exists = (drv->kbd_illum_supported) ? true : false;
+	else if (attr == &dev_attr_kbd_backlight_timeout.attr)
+		exists = (drv->kbd_mode == SCI_KBD_MODE_AUTO) ? true : false;
+	else if (attr == &dev_attr_touchpad.attr)
+		exists = (drv->touchpad_supported) ? true : false;
+	else if (attr == &dev_attr_position.attr)
+		exists = (drv->accelerometer_supported) ? true : false;
+	else if (attr == &dev_attr_usb_sleep_charge.attr)
+		exists = (drv->usb_sleep_charge_supported) ? true : false;
+	else if (attr == &dev_attr_sleep_functions_on_battery.attr)
+		exists = (drv->usb_sleep_charge_supported) ? true : false;
+	else if (attr == &dev_attr_usb_rapid_charge.attr)
+		exists = (drv->usb_rapid_charge_supported) ? true : false;
+	else if (attr == &dev_attr_usb_sleep_music.attr)
+		exists = (drv->usb_sleep_music_supported) ? true : false;
+	else if (attr == &dev_attr_kbd_function_keys.attr)
+		exists = (drv->kbd_function_keys_supported) ? true : false;
+	else if (attr == &dev_attr_panel_power_on.attr)
+		exists = (drv->panel_power_on_supported) ? true : false;
+	else if (attr == &dev_attr_usb_three.attr)
+		exists = (drv->usb_three_supported) ? true : false;
+
+	return exists ? attr->mode : 0;
+}
+
+static struct attribute_group toshiba_attr_group = {
+	.is_visible = toshiba_sysfs_is_visible,
+	.attrs = toshiba_attributes,
+};
+
+/*
+ * Misc device
+ */
+static int toshiba_acpi_smm_bridge(SMMRegisters *regs)
+{
+	u32 in[TCI_WORDS] = { regs->eax, regs->ebx, regs->ecx,
+			      regs->edx, regs->esi, regs->edi };
+	u32 out[TCI_WORDS];
+	acpi_status status;
+
+	status = tci_raw(toshiba_acpi, in, out);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI call to query SMM registers failed\n");
+		return -EIO;
+	}
+
+	/* Fillout the SMM struct with the TCI call results */
+	regs->eax = out[0];
+	regs->ebx = out[1];
+	regs->ecx = out[2];
+	regs->edx = out[3];
+	regs->esi = out[4];
+	regs->edi = out[5];
+
+	return 0;
+}
+
+static long toshiba_acpi_ioctl(struct file *fp, unsigned int cmd,
+			       unsigned long arg)
+{
+	SMMRegisters __user *argp = (SMMRegisters __user *)arg;
+	SMMRegisters regs;
+	int ret;
+
+	if (!argp)
+		return -EINVAL;
+
+	switch (cmd) {
+	case TOSH_SMM:
+		if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))
+			return -EFAULT;
+		ret = toshiba_acpi_smm_bridge(&regs);
+		if (ret)
+			return ret;
+		if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
+			return -EFAULT;
+		break;
+	case TOSHIBA_ACPI_SCI:
+		if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))
+			return -EFAULT;
+		/* Ensure we are being called with a SCI_{GET, SET} register */
+		if (regs.eax != SCI_GET && regs.eax != SCI_SET)
+			return -EINVAL;
+		if (!sci_open(toshiba_acpi))
+			return -EIO;
+		ret = toshiba_acpi_smm_bridge(&regs);
+		sci_close(toshiba_acpi);
+		if (ret)
+			return ret;
+		if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
+			return -EFAULT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct file_operations toshiba_acpi_fops = {
+	.owner		= THIS_MODULE,
+	.unlocked_ioctl = toshiba_acpi_ioctl,
+	.llseek		= noop_llseek,
+};
+
+/*
+ * Hotkeys
+ */
+static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
+{
+	acpi_status status;
+	u32 result;
+
+	status = acpi_evaluate_object(dev->acpi_dev->handle,
+				      "ENAB", NULL, NULL);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	/*
+	 * Enable the "Special Functions" mode only if they are
+	 * supported and if they are activated.
+	 */
+	if (dev->kbd_function_keys_supported && dev->special_functions)
+		result = hci_write(dev, HCI_HOTKEY_EVENT,
+				   HCI_HOTKEY_SPECIAL_FUNCTIONS);
+	else
+		result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE);
+
+	if (result == TOS_FAILURE)
+		return -EIO;
+	else if (result == TOS_NOT_SUPPORTED)
+		return -ENODEV;
+
+	return 0;
+}
+
+static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str,
+				      struct serio *port)
+{
+	if (str & I8042_STR_AUXDATA)
+		return false;
+
+	if (unlikely(data == 0xe0))
+		return false;
+
+	if ((data & 0x7f) == TOS1900_FN_SCAN) {
+		schedule_work(&toshiba_acpi->hotkey_work);
+		return true;
+	}
+
+	return false;
+}
+
+static void toshiba_acpi_hotkey_work(struct work_struct *work)
+{
+	acpi_handle ec_handle = ec_get_handle();
+	acpi_status status;
+
+	if (!ec_handle)
+		return;
+
+	status = acpi_evaluate_object(ec_handle, "NTFY", NULL, NULL);
+	if (ACPI_FAILURE(status))
+		pr_err("ACPI NTFY method execution failed\n");
+}
+
+/*
+ * Returns hotkey scancode, or < 0 on failure.
+ */
+static int toshiba_acpi_query_hotkey(struct toshiba_acpi_dev *dev)
+{
+	unsigned long long value;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(dev->acpi_dev->handle, "INFO",
+				      NULL, &value);
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI INFO method execution failed\n");
+		return -EIO;
+	}
+
+	return value;
+}
+
+static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev,
+				       int scancode)
+{
+	if (scancode == 0x100)
+		return;
+
+	/* Act on key press; ignore key release */
+	if (scancode & 0x80)
+		return;
+
+	if (!sparse_keymap_report_event(dev->hotkey_dev, scancode, 1, true))
+		pr_info("Unknown key %x\n", scancode);
+}
+
+static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev)
+{
+	if (dev->info_supported) {
+		int scancode = toshiba_acpi_query_hotkey(dev);
+
+		if (scancode < 0) {
+			pr_err("Failed to query hotkey event\n");
+		} else if (scancode != 0) {
+			toshiba_acpi_report_hotkey(dev, scancode);
+			dev->key_event_valid = 1;
+			dev->last_key_event = scancode;
+		}
+	} else if (dev->system_event_supported) {
+		u32 result;
+		u32 value;
+		int retries = 3;
+
+		do {
+			result = hci_read(dev, HCI_SYSTEM_EVENT, &value);
+			switch (result) {
+			case TOS_SUCCESS:
+				toshiba_acpi_report_hotkey(dev, (int)value);
+				dev->key_event_valid = 1;
+				dev->last_key_event = value;
+				break;
+			case TOS_NOT_SUPPORTED:
+				/*
+				 * This is a workaround for an unresolved
+				 * issue on some machines where system events
+				 * sporadically become disabled.
+				 */
+				result = hci_write(dev, HCI_SYSTEM_EVENT, 1);
+				if (result == TOS_SUCCESS)
+					pr_notice("Re-enabled hotkeys\n");
+				/* Fall through */
+			default:
+				retries--;
+				break;
+			}
+		} while (retries && result != TOS_FIFO_EMPTY);
+	}
+}
+
+static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
+{
+	const struct key_entry *keymap = toshiba_acpi_keymap;
+	acpi_handle ec_handle;
+	int error;
+
+	if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) {
+		pr_info("WMI event detected, hotkeys will not be monitored\n");
+		return 0;
+	}
+
+	error = toshiba_acpi_enable_hotkeys(dev);
+	if (error)
+		return error;
+
+	if (toshiba_hotkey_event_type_get(dev, &dev->hotkey_event_type))
+		pr_notice("Unable to query Hotkey Event Type\n");
+
+	dev->hotkey_dev = input_allocate_device();
+	if (!dev->hotkey_dev)
+		return -ENOMEM;
+
+	dev->hotkey_dev->name = "Toshiba input device";
+	dev->hotkey_dev->phys = "toshiba_acpi/input0";
+	dev->hotkey_dev->id.bustype = BUS_HOST;
+
+	if (dev->hotkey_event_type == HCI_SYSTEM_TYPE1 ||
+	    !dev->kbd_function_keys_supported)
+		keymap = toshiba_acpi_keymap;
+	else if (dev->hotkey_event_type == HCI_SYSTEM_TYPE2 ||
+		 dev->kbd_function_keys_supported)
+		keymap = toshiba_acpi_alt_keymap;
+	else
+		pr_info("Unknown event type received %x\n",
+			dev->hotkey_event_type);
+	error = sparse_keymap_setup(dev->hotkey_dev, keymap, NULL);
+	if (error)
+		goto err_free_dev;
+
+	/*
+	 * For some machines the SCI responsible for providing hotkey
+	 * notification doesn't fire. We can trigger the notification
+	 * whenever the Fn key is pressed using the NTFY method, if
+	 * supported, so if it's present set up an i8042 key filter
+	 * for this purpose.
+	 */
+	ec_handle = ec_get_handle();
+	if (ec_handle && acpi_has_method(ec_handle, "NTFY")) {
+		INIT_WORK(&dev->hotkey_work, toshiba_acpi_hotkey_work);
+
+		error = i8042_install_filter(toshiba_acpi_i8042_filter);
+		if (error) {
+			pr_err("Error installing key filter\n");
+			goto err_free_keymap;
+		}
+
+		dev->ntfy_supported = 1;
+	}
+
+	/*
+	 * Determine hotkey query interface. Prefer using the INFO
+	 * method when it is available.
+	 */
+	if (acpi_has_method(dev->acpi_dev->handle, "INFO"))
+		dev->info_supported = 1;
+	else if (hci_write(dev, HCI_SYSTEM_EVENT, 1) == TOS_SUCCESS)
+		dev->system_event_supported = 1;
+
+	if (!dev->info_supported && !dev->system_event_supported) {
+		pr_warn("No hotkey query interface found\n");
+		goto err_remove_filter;
+	}
+
+	error = input_register_device(dev->hotkey_dev);
+	if (error) {
+		pr_info("Unable to register input device\n");
+		goto err_remove_filter;
+	}
+
+	return 0;
+
+ err_remove_filter:
+	if (dev->ntfy_supported)
+		i8042_remove_filter(toshiba_acpi_i8042_filter);
+ err_free_keymap:
+	sparse_keymap_free(dev->hotkey_dev);
+ err_free_dev:
+	input_free_device(dev->hotkey_dev);
+	dev->hotkey_dev = NULL;
+	return error;
+}
+
+static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev)
+{
+	struct backlight_properties props;
+	int brightness;
+	int ret;
+
+	/*
+	 * Some machines don't support the backlight methods at all, and
+	 * others support it read-only. Either of these is pretty useless,
+	 * so only register the backlight device if the backlight method
+	 * supports both reads and writes.
+	 */
+	brightness = __get_lcd_brightness(dev);
+	if (brightness < 0)
+		return 0;
+	/*
+	 * If transflective backlight is supported and the brightness is zero
+	 * (lowest brightness level), the set_lcd_brightness function will
+	 * activate the transflective backlight, making the LCD appear to be
+	 * turned off, simply increment the brightness level to avoid that.
+	 */
+	if (dev->tr_backlight_supported && brightness == 0)
+		brightness++;
+	ret = set_lcd_brightness(dev, brightness);
+	if (ret) {
+		pr_debug("Backlight method is read-only, disabling backlight support\n");
+		return 0;
+	}
+
+	/*
+	 * Tell acpi-video-detect code to prefer vendor backlight on all
+	 * systems with transflective backlight and on dmi matched systems.
+	 */
+	if (dev->tr_backlight_supported ||
+	    dmi_check_system(toshiba_vendor_backlight_dmi))
+		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
+
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
+		return 0;
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
+
+	/* Adding an extra level and having 0 change to transflective mode */
+	if (dev->tr_backlight_supported)
+		props.max_brightness++;
+
+	dev->backlight_dev = backlight_device_register("toshiba",
+						       &dev->acpi_dev->dev,
+						       dev,
+						       &toshiba_backlight_data,
+						       &props);
+	if (IS_ERR(dev->backlight_dev)) {
+		ret = PTR_ERR(dev->backlight_dev);
+		pr_err("Could not register toshiba backlight device\n");
+		dev->backlight_dev = NULL;
+		return ret;
+	}
+
+	dev->backlight_dev->props.brightness = brightness;
+	return 0;
+}
+
+static void print_supported_features(struct toshiba_acpi_dev *dev)
+{
+	pr_info("Supported laptop features:");
+
+	if (dev->hotkey_dev)
+		pr_cont(" hotkeys");
+	if (dev->backlight_dev)
+		pr_cont(" backlight");
+	if (dev->video_supported)
+		pr_cont(" video-out");
+	if (dev->fan_supported)
+		pr_cont(" fan");
+	if (dev->tr_backlight_supported)
+		pr_cont(" transflective-backlight");
+	if (dev->illumination_supported)
+		pr_cont(" illumination");
+	if (dev->kbd_illum_supported)
+		pr_cont(" keyboard-backlight");
+	if (dev->touchpad_supported)
+		pr_cont(" touchpad");
+	if (dev->eco_supported)
+		pr_cont(" eco-led");
+	if (dev->accelerometer_supported)
+		pr_cont(" accelerometer-axes");
+	if (dev->usb_sleep_charge_supported)
+		pr_cont(" usb-sleep-charge");
+	if (dev->usb_rapid_charge_supported)
+		pr_cont(" usb-rapid-charge");
+	if (dev->usb_sleep_music_supported)
+		pr_cont(" usb-sleep-music");
+	if (dev->kbd_function_keys_supported)
+		pr_cont(" special-function-keys");
+	if (dev->panel_power_on_supported)
+		pr_cont(" panel-power-on");
+	if (dev->usb_three_supported)
+		pr_cont(" usb3");
+
+	pr_cont("\n");
+}
+
+static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
+{
+	struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
+
+	misc_deregister(&dev->miscdev);
+
+	remove_toshiba_proc_entries(dev);
+
+	if (dev->sysfs_created)
+		sysfs_remove_group(&dev->acpi_dev->dev.kobj,
+				   &toshiba_attr_group);
+
+	if (dev->ntfy_supported) {
+		i8042_remove_filter(toshiba_acpi_i8042_filter);
+		cancel_work_sync(&dev->hotkey_work);
+	}
+
+	if (dev->hotkey_dev) {
+		input_unregister_device(dev->hotkey_dev);
+		sparse_keymap_free(dev->hotkey_dev);
+	}
+
+	backlight_device_unregister(dev->backlight_dev);
+
+	if (dev->illumination_led_registered)
+		led_classdev_unregister(&dev->led_dev);
+
+	if (dev->kbd_led_registered)
+		led_classdev_unregister(&dev->kbd_led);
+
+	if (dev->eco_led_registered)
+		led_classdev_unregister(&dev->eco_led);
+
+	if (toshiba_acpi)
+		toshiba_acpi = NULL;
+
+	kfree(dev);
+
+	return 0;
+}
+
+static const char *find_hci_method(acpi_handle handle)
+{
+	if (acpi_has_method(handle, "GHCI"))
+		return "GHCI";
+
+	if (acpi_has_method(handle, "SPFC"))
+		return "SPFC";
+
+	return NULL;
+}
+
+static int toshiba_acpi_add(struct acpi_device *acpi_dev)
+{
+	struct toshiba_acpi_dev *dev;
+	const char *hci_method;
+	u32 dummy;
+	int ret = 0;
+
+	if (toshiba_acpi)
+		return -EBUSY;
+
+	pr_info("Toshiba Laptop ACPI Extras version %s\n",
+	       TOSHIBA_ACPI_VERSION);
+
+	hci_method = find_hci_method(acpi_dev->handle);
+	if (!hci_method) {
+		pr_err("HCI interface not found\n");
+		return -ENODEV;
+	}
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+	dev->acpi_dev = acpi_dev;
+	dev->method_hci = hci_method;
+	dev->miscdev.minor = MISC_DYNAMIC_MINOR;
+	dev->miscdev.name = "toshiba_acpi";
+	dev->miscdev.fops = &toshiba_acpi_fops;
+
+	ret = misc_register(&dev->miscdev);
+	if (ret) {
+		pr_err("Failed to register miscdevice\n");
+		kfree(dev);
+		return ret;
+	}
+
+	acpi_dev->driver_data = dev;
+	dev_set_drvdata(&acpi_dev->dev, dev);
+
+	/* Query the BIOS for supported features */
+
+	/*
+	 * The "Special Functions" are always supported by the laptops
+	 * with the new keyboard layout, query for its presence to help
+	 * determine the keymap layout to use.
+	 */
+	ret = toshiba_function_keys_get(dev, &dev->special_functions);
+	dev->kbd_function_keys_supported = !ret;
+
+	dev->hotkey_event_type = 0;
+	if (toshiba_acpi_setup_keyboard(dev))
+		pr_info("Unable to activate hotkeys\n");
+
+	/* Determine whether or not BIOS supports transflective backlight */
+	ret = get_tr_backlight_status(dev, &dummy);
+	dev->tr_backlight_supported = !ret;
+
+	ret = toshiba_acpi_setup_backlight(dev);
+	if (ret)
+		goto error;
+
+	toshiba_illumination_available(dev);
+	if (dev->illumination_supported) {
+		dev->led_dev.name = "toshiba::illumination";
+		dev->led_dev.max_brightness = 1;
+		dev->led_dev.brightness_set = toshiba_illumination_set;
+		dev->led_dev.brightness_get = toshiba_illumination_get;
+		if (!led_classdev_register(&acpi_dev->dev, &dev->led_dev))
+			dev->illumination_led_registered = true;
+	}
+
+	toshiba_eco_mode_available(dev);
+	if (dev->eco_supported) {
+		dev->eco_led.name = "toshiba::eco_mode";
+		dev->eco_led.max_brightness = 1;
+		dev->eco_led.brightness_set = toshiba_eco_mode_set_status;
+		dev->eco_led.brightness_get = toshiba_eco_mode_get_status;
+		if (!led_classdev_register(&dev->acpi_dev->dev, &dev->eco_led))
+			dev->eco_led_registered = true;
+	}
+
+	toshiba_kbd_illum_available(dev);
+	/*
+	 * Only register the LED if KBD illumination is supported
+	 * and the keyboard backlight operation mode is set to FN-Z
+	 */
+	if (dev->kbd_illum_supported && dev->kbd_mode == SCI_KBD_MODE_FNZ) {
+		dev->kbd_led.name = "toshiba::kbd_backlight";
+		dev->kbd_led.max_brightness = 1;
+		dev->kbd_led.brightness_set = toshiba_kbd_backlight_set;
+		dev->kbd_led.brightness_get = toshiba_kbd_backlight_get;
+		if (!led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led))
+			dev->kbd_led_registered = true;
+	}
+
+	ret = toshiba_touchpad_get(dev, &dummy);
+	dev->touchpad_supported = !ret;
+
+	toshiba_accelerometer_available(dev);
+
+	toshiba_usb_sleep_charge_available(dev);
+
+	ret = toshiba_usb_rapid_charge_get(dev, &dummy);
+	dev->usb_rapid_charge_supported = !ret;
+
+	ret = toshiba_usb_sleep_music_get(dev, &dummy);
+	dev->usb_sleep_music_supported = !ret;
+
+	ret = toshiba_panel_power_on_get(dev, &dummy);
+	dev->panel_power_on_supported = !ret;
+
+	ret = toshiba_usb_three_get(dev, &dummy);
+	dev->usb_three_supported = !ret;
+
+	ret = get_video_status(dev, &dummy);
+	dev->video_supported = !ret;
+
+	ret = get_fan_status(dev, &dummy);
+	dev->fan_supported = !ret;
+
+	print_supported_features(dev);
+
+	ret = sysfs_create_group(&dev->acpi_dev->dev.kobj,
+				 &toshiba_attr_group);
+	if (ret) {
+		dev->sysfs_created = 0;
+		goto error;
+	}
+	dev->sysfs_created = !ret;
+
+	create_toshiba_proc_entries(dev);
+
+	toshiba_acpi = dev;
+
+	return 0;
+
+error:
+	toshiba_acpi_remove(acpi_dev);
+	return ret;
+}
+
+static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
+{
+	struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
+	int ret;
+
+	switch (event) {
+	case 0x80: /* Hotkeys and some system events */
+		/*
+		 * Machines with this WMI GUID aren't supported due to bugs in
+		 * their AML.
+		 *
+		 * Return silently to avoid triggering a netlink event.
+		 */
+		if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID))
+			return;
+		toshiba_acpi_process_hotkeys(dev);
+		break;
+	case 0x81: /* Dock events */
+	case 0x82:
+	case 0x83:
+		pr_info("Dock event received %x\n", event);
+		break;
+	case 0x88: /* Thermal events */
+		pr_info("Thermal event received\n");
+		break;
+	case 0x8f: /* LID closed */
+	case 0x90: /* LID is closed and Dock has been ejected */
+		break;
+	case 0x8c: /* SATA power events */
+	case 0x8b:
+		pr_info("SATA power event received %x\n", event);
+		break;
+	case 0x92: /* Keyboard backlight mode changed */
+		/* Update sysfs entries */
+		ret = sysfs_update_group(&acpi_dev->dev.kobj,
+					 &toshiba_attr_group);
+		if (ret)
+			pr_err("Unable to update sysfs entries\n");
+		break;
+	case 0x85: /* Unknown */
+	case 0x8d: /* Unknown */
+	case 0x8e: /* Unknown */
+	case 0x94: /* Unknown */
+	case 0x95: /* Unknown */
+	default:
+		pr_info("Unknown event received %x\n", event);
+		break;
+	}
+
+	acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
+					dev_name(&acpi_dev->dev),
+					event, 0);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_acpi_suspend(struct device *device)
+{
+	struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
+
+	if (dev->hotkey_dev) {
+		u32 result;
+
+		result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE);
+		if (result != TOS_SUCCESS)
+			pr_info("Unable to disable hotkeys\n");
+	}
+
+	return 0;
+}
+
+static int toshiba_acpi_resume(struct device *device)
+{
+	struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
+
+	if (dev->hotkey_dev) {
+		int error = toshiba_acpi_enable_hotkeys(dev);
+
+		if (error)
+			pr_info("Unable to re-enable hotkeys\n");
+	}
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(toshiba_acpi_pm,
+			 toshiba_acpi_suspend, toshiba_acpi_resume);
+
+static struct acpi_driver toshiba_acpi_driver = {
+	.name	= "Toshiba ACPI driver",
+	.owner	= THIS_MODULE,
+	.ids	= toshiba_device_ids,
+	.flags	= ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+	.ops	= {
+		.add		= toshiba_acpi_add,
+		.remove		= toshiba_acpi_remove,
+		.notify		= toshiba_acpi_notify,
+	},
+	.drv.pm	= &toshiba_acpi_pm,
+};
+
+static int __init toshiba_acpi_init(void)
+{
+	int ret;
+
+	toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir);
+	if (!toshiba_proc_dir) {
+		pr_err("Unable to create proc dir " PROC_TOSHIBA "\n");
+		return -ENODEV;
+	}
+
+	ret = acpi_bus_register_driver(&toshiba_acpi_driver);
+	if (ret) {
+		pr_err("Failed to register ACPI driver: %d\n", ret);
+		remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
+	}
+
+	return ret;
+}
+
+static void __exit toshiba_acpi_exit(void)
+{
+	acpi_bus_unregister_driver(&toshiba_acpi_driver);
+	if (toshiba_proc_dir)
+		remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
+}
+
+module_init(toshiba_acpi_init);
+module_exit(toshiba_acpi_exit);
diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c
new file mode 100644
index 0000000..c5e4508
--- /dev/null
+++ b/drivers/platform/x86/toshiba_bluetooth.c
@@ -0,0 +1,298 @@
+/*
+ * Toshiba Bluetooth Enable Driver
+ *
+ * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com>
+ * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.com>
+ *
+ * Thanks to Matthew Garrett for background info on ACPI innards which
+ * normal people aren't meant to understand :-)
+ *
+ * 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
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+
+#define BT_KILLSWITCH_MASK	0x01
+#define BT_PLUGGED_MASK		0x40
+#define BT_POWER_MASK		0x80
+
+MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>");
+MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver");
+MODULE_LICENSE("GPL");
+
+struct toshiba_bluetooth_dev {
+	struct acpi_device *acpi_dev;
+	struct rfkill *rfk;
+
+	bool killswitch;
+	bool plugged;
+	bool powered;
+};
+
+static int toshiba_bt_rfkill_add(struct acpi_device *device);
+static int toshiba_bt_rfkill_remove(struct acpi_device *device);
+static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id bt_device_ids[] = {
+	{ "TOS6205", 0},
+	{ "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, bt_device_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_bt_resume(struct device *dev);
+#endif
+static SIMPLE_DEV_PM_OPS(toshiba_bt_pm, NULL, toshiba_bt_resume);
+
+static struct acpi_driver toshiba_bt_rfkill_driver = {
+	.name =		"Toshiba BT",
+	.class =	"Toshiba",
+	.ids =		bt_device_ids,
+	.ops =		{
+				.add =		toshiba_bt_rfkill_add,
+				.remove =	toshiba_bt_rfkill_remove,
+				.notify =	toshiba_bt_rfkill_notify,
+			},
+	.owner = 	THIS_MODULE,
+	.drv.pm =	&toshiba_bt_pm,
+};
+
+static int toshiba_bluetooth_present(acpi_handle handle)
+{
+	acpi_status result;
+	u64 bt_present;
+
+	/*
+	 * Some Toshiba laptops may have a fake TOS6205 device in
+	 * their ACPI BIOS, so query the _STA method to see if there
+	 * is really anything there.
+	 */
+	result = acpi_evaluate_integer(handle, "_STA", NULL, &bt_present);
+	if (ACPI_FAILURE(result)) {
+		pr_err("ACPI call to query Bluetooth presence failed");
+		return -ENXIO;
+	} else if (!bt_present) {
+		pr_info("Bluetooth device not present\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int toshiba_bluetooth_status(acpi_handle handle)
+{
+	acpi_status result;
+	u64 status;
+
+	result = acpi_evaluate_integer(handle, "BTST", NULL, &status);
+	if (ACPI_FAILURE(result)) {
+		pr_err("Could not get Bluetooth device status\n");
+		return -ENXIO;
+	}
+
+	return status;
+}
+
+static int toshiba_bluetooth_enable(acpi_handle handle)
+{
+	acpi_status result;
+
+	result = acpi_evaluate_object(handle, "AUSB", NULL, NULL);
+	if (ACPI_FAILURE(result)) {
+		pr_err("Could not attach USB Bluetooth device\n");
+		return -ENXIO;
+	}
+
+	result = acpi_evaluate_object(handle, "BTPO", NULL, NULL);
+	if (ACPI_FAILURE(result)) {
+		pr_err("Could not power ON Bluetooth device\n");
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static int toshiba_bluetooth_disable(acpi_handle handle)
+{
+	acpi_status result;
+
+	result = acpi_evaluate_object(handle, "BTPF", NULL, NULL);
+	if (ACPI_FAILURE(result)) {
+		pr_err("Could not power OFF Bluetooth device\n");
+		return -ENXIO;
+	}
+
+	result = acpi_evaluate_object(handle, "DUSB", NULL, NULL);
+	if (ACPI_FAILURE(result)) {
+		pr_err("Could not detach USB Bluetooth device\n");
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+/* Helper function */
+static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev)
+{
+	int status;
+
+	status = toshiba_bluetooth_status(bt_dev->acpi_dev->handle);
+	if (status < 0) {
+		pr_err("Could not sync bluetooth device status\n");
+		return status;
+	}
+
+	bt_dev->killswitch = (status & BT_KILLSWITCH_MASK) ? true : false;
+	bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false;
+	bt_dev->powered = (status & BT_POWER_MASK) ? true : false;
+
+	pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n",
+		 status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered);
+
+	return 0;
+}
+
+/* RFKill handlers */
+static int bt_rfkill_set_block(void *data, bool blocked)
+{
+	struct toshiba_bluetooth_dev *bt_dev = data;
+	int ret;
+
+	ret = toshiba_bluetooth_sync_status(bt_dev);
+	if (ret)
+		return ret;
+
+	if (!bt_dev->killswitch)
+		return 0;
+
+	if (blocked)
+		ret = toshiba_bluetooth_disable(bt_dev->acpi_dev->handle);
+	else
+		ret = toshiba_bluetooth_enable(bt_dev->acpi_dev->handle);
+
+	return ret;
+}
+
+static void bt_rfkill_poll(struct rfkill *rfkill, void *data)
+{
+	struct toshiba_bluetooth_dev *bt_dev = data;
+
+	if (toshiba_bluetooth_sync_status(bt_dev))
+		return;
+
+	/*
+	 * Note the Toshiba Bluetooth RFKill switch seems to be a strange
+	 * fish. It only provides a BT event when the switch is flipped to
+	 * the 'on' position. When flipping it to 'off', the USB device is
+	 * simply pulled away underneath us, without any BT event being
+	 * delivered.
+	 */
+	rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+}
+
+static const struct rfkill_ops rfk_ops = {
+	.set_block = bt_rfkill_set_block,
+	.poll = bt_rfkill_poll,
+};
+
+/* ACPI driver functions */
+static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
+{
+	struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
+
+	if (toshiba_bluetooth_sync_status(bt_dev))
+		return;
+
+	rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_bt_resume(struct device *dev)
+{
+	struct toshiba_bluetooth_dev *bt_dev;
+	int ret;
+
+	bt_dev = acpi_driver_data(to_acpi_device(dev));
+
+	ret = toshiba_bluetooth_sync_status(bt_dev);
+	if (ret)
+		return ret;
+
+	rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+
+	return 0;
+}
+#endif
+
+static int toshiba_bt_rfkill_add(struct acpi_device *device)
+{
+	struct toshiba_bluetooth_dev *bt_dev;
+	int result;
+
+	result = toshiba_bluetooth_present(device->handle);
+	if (result)
+		return result;
+
+	pr_info("Toshiba ACPI Bluetooth device driver\n");
+
+	bt_dev = kzalloc(sizeof(*bt_dev), GFP_KERNEL);
+	if (!bt_dev)
+		return -ENOMEM;
+	bt_dev->acpi_dev = device;
+	device->driver_data = bt_dev;
+	dev_set_drvdata(&device->dev, bt_dev);
+
+	result = toshiba_bluetooth_sync_status(bt_dev);
+	if (result) {
+		kfree(bt_dev);
+		return result;
+	}
+
+	bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth",
+				   &device->dev,
+				   RFKILL_TYPE_BLUETOOTH,
+				   &rfk_ops,
+				   bt_dev);
+	if (!bt_dev->rfk) {
+		pr_err("Unable to allocate rfkill device\n");
+		kfree(bt_dev);
+		return -ENOMEM;
+	}
+
+	rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
+
+	result = rfkill_register(bt_dev->rfk);
+	if (result) {
+		pr_err("Unable to register rfkill device\n");
+		rfkill_destroy(bt_dev->rfk);
+		kfree(bt_dev);
+	}
+
+	return result;
+}
+
+static int toshiba_bt_rfkill_remove(struct acpi_device *device)
+{
+	struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
+
+	/* clean up */
+	if (bt_dev->rfk) {
+		rfkill_unregister(bt_dev->rfk);
+		rfkill_destroy(bt_dev->rfk);
+	}
+
+	kfree(bt_dev);
+
+	return toshiba_bluetooth_disable(device->handle);
+}
+
+module_acpi_driver(toshiba_bt_rfkill_driver);
diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c
new file mode 100644
index 0000000..7f2afc6
--- /dev/null
+++ b/drivers/platform/x86/toshiba_haps.c
@@ -0,0 +1,273 @@
+/*
+ * Toshiba HDD Active Protection Sensor (HAPS) driver
+ *
+ * Copyright (C) 2014 Azael Avalos <coproscefalo@gmail.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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+
+MODULE_AUTHOR("Azael Avalos <coproscefalo@gmail.com>");
+MODULE_DESCRIPTION("Toshiba HDD Active Protection Sensor");
+MODULE_LICENSE("GPL");
+
+struct toshiba_haps_dev {
+	struct acpi_device *acpi_dev;
+
+	int protection_level;
+};
+
+static struct toshiba_haps_dev *toshiba_haps;
+
+/* HAPS functions */
+static int toshiba_haps_reset_protection(acpi_handle handle)
+{
+	acpi_status status;
+
+	status = acpi_evaluate_object(handle, "RSSS", NULL, NULL);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Unable to reset the HDD protection\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int toshiba_haps_protection_level(acpi_handle handle, int level)
+{
+	acpi_status status;
+
+	status = acpi_execute_simple_method(handle, "PTLV", level);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Error while setting the protection level\n");
+		return -EIO;
+	}
+
+	pr_info("HDD protection level set to: %d\n", level);
+
+	return 0;
+}
+
+/* sysfs files */
+static ssize_t protection_level_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%i\n", haps->protection_level);
+}
+
+static ssize_t protection_level_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
+	int level;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &level);
+	if (ret)
+		return ret;
+	/*
+	 * Check for supported levels, which can be:
+	 * 0 - Disabled | 1 - Low | 2 - Medium | 3 - High
+	 */
+	if (level < 0 || level > 3)
+		return -EINVAL;
+
+	/* Set the sensor level */
+	ret = toshiba_haps_protection_level(haps->acpi_dev->handle, level);
+	if (ret != 0)
+		return ret;
+
+	haps->protection_level = level;
+
+	return count;
+}
+static DEVICE_ATTR_RW(protection_level);
+
+static ssize_t reset_protection_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct toshiba_haps_dev *haps = dev_get_drvdata(dev);
+	int reset;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &reset);
+	if (ret)
+		return ret;
+	/* The only accepted value is 1 */
+	if (reset != 1)
+		return -EINVAL;
+
+	/* Reset the protection interface */
+	ret = toshiba_haps_reset_protection(haps->acpi_dev->handle);
+	if (ret != 0)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO(reset_protection);
+
+static struct attribute *haps_attributes[] = {
+	&dev_attr_protection_level.attr,
+	&dev_attr_reset_protection.attr,
+	NULL,
+};
+
+static struct attribute_group haps_attr_group = {
+	.attrs = haps_attributes,
+};
+
+/*
+ * ACPI stuff
+ */
+static void toshiba_haps_notify(struct acpi_device *device, u32 event)
+{
+	pr_info("Received event: 0x%x", event);
+
+	acpi_bus_generate_netlink_event(device->pnp.device_class,
+					dev_name(&device->dev),
+					event, 0);
+}
+
+static int toshiba_haps_remove(struct acpi_device *device)
+{
+	sysfs_remove_group(&device->dev.kobj, &haps_attr_group);
+
+	if (toshiba_haps)
+		toshiba_haps = NULL;
+
+	return 0;
+}
+
+/* Helper function */
+static int toshiba_haps_available(acpi_handle handle)
+{
+	acpi_status status;
+	u64 hdd_present;
+
+	/*
+	 * A non existent device as well as having (only)
+	 * Solid State Drives can cause the call to fail.
+	 */
+	status = acpi_evaluate_integer(handle, "_STA", NULL,
+				       &hdd_present);
+	if (ACPI_FAILURE(status) || !hdd_present) {
+		pr_info("HDD protection not available or using SSD\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+static int toshiba_haps_add(struct acpi_device *acpi_dev)
+{
+	struct toshiba_haps_dev *haps;
+	int ret;
+
+	if (toshiba_haps)
+		return -EBUSY;
+
+	if (!toshiba_haps_available(acpi_dev->handle))
+		return -ENODEV;
+
+	pr_info("Toshiba HDD Active Protection Sensor device\n");
+
+	haps = kzalloc(sizeof(struct toshiba_haps_dev), GFP_KERNEL);
+	if (!haps)
+		return -ENOMEM;
+
+	haps->acpi_dev = acpi_dev;
+	haps->protection_level = 2;
+	acpi_dev->driver_data = haps;
+	dev_set_drvdata(&acpi_dev->dev, haps);
+
+	/* Set the protection level, currently at level 2 (Medium) */
+	ret = toshiba_haps_protection_level(acpi_dev->handle, 2);
+	if (ret != 0)
+		return ret;
+
+	ret = sysfs_create_group(&acpi_dev->dev.kobj, &haps_attr_group);
+	if (ret)
+		return ret;
+
+	toshiba_haps = haps;
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_haps_suspend(struct device *device)
+{
+	struct toshiba_haps_dev *haps;
+	int ret;
+
+	haps = acpi_driver_data(to_acpi_device(device));
+
+	/* Deactivate the protection on suspend */
+	ret = toshiba_haps_protection_level(haps->acpi_dev->handle, 0);
+
+	return ret;
+}
+
+static int toshiba_haps_resume(struct device *device)
+{
+	struct toshiba_haps_dev *haps;
+	int ret;
+
+	haps = acpi_driver_data(to_acpi_device(device));
+
+	/* Set the stored protection level */
+	ret = toshiba_haps_protection_level(haps->acpi_dev->handle,
+					    haps->protection_level);
+
+	/* Reset the protection on resume */
+	ret = toshiba_haps_reset_protection(haps->acpi_dev->handle);
+	if (ret != 0)
+		return ret;
+
+	return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(toshiba_haps_pm,
+			 toshiba_haps_suspend, toshiba_haps_resume);
+
+static const struct acpi_device_id haps_device_ids[] = {
+	{"TOS620A", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, haps_device_ids);
+
+static struct acpi_driver toshiba_haps_driver = {
+	.name = "Toshiba HAPS",
+	.owner = THIS_MODULE,
+	.ids = haps_device_ids,
+	.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+	.ops = {
+		.add =		toshiba_haps_add,
+		.remove =	toshiba_haps_remove,
+		.notify =	toshiba_haps_notify,
+	},
+	.drv.pm = &toshiba_haps_pm,
+};
+
+module_acpi_driver(toshiba_haps_driver);
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
new file mode 100644
index 0000000..eb391a2
--- /dev/null
+++ b/drivers/platform/x86/wmi.c
@@ -0,0 +1,930 @@
+/*
+ *  ACPI-WMI mapping driver
+ *
+ *  Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ *  GUID parsing code from ldm.c is:
+ *   Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org>
+ *   Copyright (c) 2001-2007 Anton Altaparmakov
+ *   Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+ACPI_MODULE_NAME("wmi");
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
+MODULE_LICENSE("GPL");
+
+#define ACPI_WMI_CLASS "wmi"
+
+static LIST_HEAD(wmi_block_list);
+
+struct guid_block {
+	char guid[16];
+	union {
+		char object_id[2];
+		struct {
+			unsigned char notify_id;
+			unsigned char reserved;
+		};
+	};
+	u8 instance_count;
+	u8 flags;
+};
+
+struct wmi_block {
+	struct list_head list;
+	struct guid_block gblock;
+	acpi_handle handle;
+	wmi_notify_handler handler;
+	void *handler_data;
+	struct device dev;
+};
+
+
+/*
+ * If the GUID data block is marked as expensive, we must enable and
+ * explicitily disable data collection.
+ */
+#define ACPI_WMI_EXPENSIVE   0x1
+#define ACPI_WMI_METHOD      0x2	/* GUID is a method */
+#define ACPI_WMI_STRING      0x4	/* GUID takes & returns a string */
+#define ACPI_WMI_EVENT       0x8	/* GUID is an event */
+
+static bool debug_event;
+module_param(debug_event, bool, 0444);
+MODULE_PARM_DESC(debug_event,
+		 "Log WMI Events [0/1]");
+
+static bool debug_dump_wdg;
+module_param(debug_dump_wdg, bool, 0444);
+MODULE_PARM_DESC(debug_dump_wdg,
+		 "Dump available WMI interfaces [0/1]");
+
+static int acpi_wmi_remove(struct acpi_device *device);
+static int acpi_wmi_add(struct acpi_device *device);
+static void acpi_wmi_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id wmi_device_ids[] = {
+	{"PNP0C14", 0},
+	{"pnp0c14", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
+
+static struct acpi_driver acpi_wmi_driver = {
+	.name = "wmi",
+	.class = ACPI_WMI_CLASS,
+	.ids = wmi_device_ids,
+	.ops = {
+		.add = acpi_wmi_add,
+		.remove = acpi_wmi_remove,
+		.notify = acpi_wmi_notify,
+	},
+};
+
+/*
+ * GUID parsing functions
+ */
+
+/**
+ * wmi_parse_hexbyte - Convert a ASCII hex number to a byte
+ * @src:  Pointer to at least 2 characters to convert.
+ *
+ * Convert a two character ASCII hex string to a number.
+ *
+ * Return:  0-255  Success, the byte was parsed correctly
+ *          -1     Error, an invalid character was supplied
+ */
+static int wmi_parse_hexbyte(const u8 *src)
+{
+	int h;
+	int value;
+
+	/* high part */
+	h = value = hex_to_bin(src[0]);
+	if (value < 0)
+		return -1;
+
+	/* low part */
+	value = hex_to_bin(src[1]);
+	if (value >= 0)
+		return (h << 4) | value;
+	return -1;
+}
+
+/**
+ * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary
+ * @src:   Memory block holding binary GUID (16 bytes)
+ * @dest:  Memory block to hold byte swapped binary GUID (16 bytes)
+ *
+ * Byte swap a binary GUID to match it's real GUID value
+ */
+static void wmi_swap_bytes(u8 *src, u8 *dest)
+{
+	int i;
+
+	for (i = 0; i <= 3; i++)
+		memcpy(dest + i, src + (3 - i), 1);
+
+	for (i = 0; i <= 1; i++)
+		memcpy(dest + 4 + i, src + (5 - i), 1);
+
+	for (i = 0; i <= 1; i++)
+		memcpy(dest + 6 + i, src + (7 - i), 1);
+
+	memcpy(dest + 8, src + 8, 8);
+}
+
+/**
+ * wmi_parse_guid - Convert GUID from ASCII to binary
+ * @src:   36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dest:  Memory block to hold binary GUID (16 bytes)
+ *
+ * N.B. The GUID need not be NULL terminated.
+ *
+ * Return:  'true'   @dest contains binary GUID
+ *          'false'  @dest contents are undefined
+ */
+static bool wmi_parse_guid(const u8 *src, u8 *dest)
+{
+	static const int size[] = { 4, 2, 2, 2, 6 };
+	int i, j, v;
+
+	if (src[8]  != '-' || src[13] != '-' ||
+		src[18] != '-' || src[23] != '-')
+		return false;
+
+	for (j = 0; j < 5; j++, src++) {
+		for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) {
+			v = wmi_parse_hexbyte(src);
+			if (v < 0)
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static bool find_guid(const char *guid_string, struct wmi_block **out)
+{
+	char tmp[16], guid_input[16];
+	struct wmi_block *wblock;
+	struct guid_block *block;
+	struct list_head *p;
+
+	wmi_parse_guid(guid_string, tmp);
+	wmi_swap_bytes(tmp, guid_input);
+
+	list_for_each(p, &wmi_block_list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		block = &wblock->gblock;
+
+		if (memcmp(block->guid, guid_input, 16) == 0) {
+			if (out)
+				*out = wblock;
+			return true;
+		}
+	}
+	return false;
+}
+
+static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable)
+{
+	struct guid_block *block = NULL;
+	char method[5];
+	acpi_status status;
+	acpi_handle handle;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	snprintf(method, 5, "WE%02X", block->notify_id);
+	status = acpi_execute_simple_method(handle, method, enable);
+
+	if (status != AE_OK && status != AE_NOT_FOUND)
+		return status;
+	else
+		return AE_OK;
+}
+
+/*
+ * Exported WMI functions
+ */
+/**
+ * wmi_evaluate_method - Evaluate a WMI method
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * @method_id: Method ID to call
+ * &in: Buffer containing input for the method call
+ * &out: Empty buffer to return the method results
+ *
+ * Call an ACPI-WMI method
+ */
+acpi_status wmi_evaluate_method(const char *guid_string, u8 instance,
+u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	acpi_status status;
+	struct acpi_object_list input;
+	union acpi_object params[3];
+	char method[5] = "WM";
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_ERROR;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (!(block->flags & ACPI_WMI_METHOD))
+		return AE_BAD_DATA;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	input.count = 2;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = instance;
+	params[1].type = ACPI_TYPE_INTEGER;
+	params[1].integer.value = method_id;
+
+	if (in) {
+		input.count = 3;
+
+		if (block->flags & ACPI_WMI_STRING) {
+			params[2].type = ACPI_TYPE_STRING;
+		} else {
+			params[2].type = ACPI_TYPE_BUFFER;
+		}
+		params[2].buffer.length = in->length;
+		params[2].buffer.pointer = in->pointer;
+	}
+
+	strncat(method, block->object_id, 2);
+
+	status = acpi_evaluate_object(handle, method, &input, out);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_evaluate_method);
+
+/**
+ * wmi_query_block - Return contents of a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &out: Empty buffer to return the contents of the data block to
+ *
+ * Return the contents of an ACPI-WMI data block to a buffer
+ */
+acpi_status wmi_query_block(const char *guid_string, u8 instance,
+struct acpi_buffer *out)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	acpi_status status, wc_status = AE_ERROR;
+	struct acpi_object_list input;
+	union acpi_object wq_params[1];
+	char method[5];
+	char wc_method[5] = "WC";
+
+	if (!guid_string || !out)
+		return AE_BAD_PARAMETER;
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_ERROR;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	/* Check GUID is a data block */
+	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+		return AE_ERROR;
+
+	input.count = 1;
+	input.pointer = wq_params;
+	wq_params[0].type = ACPI_TYPE_INTEGER;
+	wq_params[0].integer.value = instance;
+
+	/*
+	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
+	 * enable collection.
+	 */
+	if (block->flags & ACPI_WMI_EXPENSIVE) {
+		strncat(wc_method, block->object_id, 2);
+
+		/*
+		 * Some GUIDs break the specification by declaring themselves
+		 * expensive, but have no corresponding WCxx method. So we
+		 * should not fail if this happens.
+		 */
+		if (acpi_has_method(handle, wc_method))
+			wc_status = acpi_execute_simple_method(handle,
+								wc_method, 1);
+	}
+
+	strcpy(method, "WQ");
+	strncat(method, block->object_id, 2);
+
+	status = acpi_evaluate_object(handle, method, &input, out);
+
+	/*
+	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
+	 * the WQxx method failed - we should disable collection anyway.
+	 */
+	if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) {
+		status = acpi_execute_simple_method(handle, wc_method, 0);
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_query_block);
+
+/**
+ * wmi_set_block - Write to a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &in: Buffer containing new values for the data block
+ *
+ * Write the contents of the input buffer to an ACPI-WMI data block
+ */
+acpi_status wmi_set_block(const char *guid_string, u8 instance,
+const struct acpi_buffer *in)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	struct acpi_object_list input;
+	union acpi_object params[2];
+	char method[5] = "WS";
+
+	if (!guid_string || !in)
+		return AE_BAD_DATA;
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_ERROR;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	/* Check GUID is a data block */
+	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+		return AE_ERROR;
+
+	input.count = 2;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = instance;
+
+	if (block->flags & ACPI_WMI_STRING) {
+		params[1].type = ACPI_TYPE_STRING;
+	} else {
+		params[1].type = ACPI_TYPE_BUFFER;
+	}
+	params[1].buffer.length = in->length;
+	params[1].buffer.pointer = in->pointer;
+
+	strncat(method, block->object_id, 2);
+
+	return acpi_evaluate_object(handle, method, &input, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_set_block);
+
+static void wmi_dump_wdg(const struct guid_block *g)
+{
+	pr_info("%pUL:\n", g->guid);
+	pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]);
+	pr_info("\tnotify_id: %02X\n", g->notify_id);
+	pr_info("\treserved: %02X\n", g->reserved);
+	pr_info("\tinstance_count: %d\n", g->instance_count);
+	pr_info("\tflags: %#x", g->flags);
+	if (g->flags) {
+		if (g->flags & ACPI_WMI_EXPENSIVE)
+			pr_cont(" ACPI_WMI_EXPENSIVE");
+		if (g->flags & ACPI_WMI_METHOD)
+			pr_cont(" ACPI_WMI_METHOD");
+		if (g->flags & ACPI_WMI_STRING)
+			pr_cont(" ACPI_WMI_STRING");
+		if (g->flags & ACPI_WMI_EVENT)
+			pr_cont(" ACPI_WMI_EVENT");
+	}
+	pr_cont("\n");
+
+}
+
+static void wmi_notify_debug(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_info("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+
+	if (!obj)
+		return;
+
+	pr_info("DEBUG Event ");
+	switch(obj->type) {
+	case ACPI_TYPE_BUFFER:
+		pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length);
+		break;
+	case ACPI_TYPE_STRING:
+		pr_cont("STRING_TYPE - %s\n", obj->string.pointer);
+		break;
+	case ACPI_TYPE_INTEGER:
+		pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value);
+		break;
+	case ACPI_TYPE_PACKAGE:
+		pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count);
+		break;
+	default:
+		pr_cont("object type 0x%X\n", obj->type);
+	}
+	kfree(obj);
+}
+
+/**
+ * wmi_install_notify_handler - Register handler for WMI events
+ * @handler: Function to handle notifications
+ * @data: Data to be returned to handler when event is fired
+ *
+ * Register a handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_install_notify_handler(const char *guid,
+wmi_notify_handler handler, void *data)
+{
+	struct wmi_block *block;
+	acpi_status status = AE_NOT_EXIST;
+	char tmp[16], guid_input[16];
+	struct list_head *p;
+
+	if (!guid || !handler)
+		return AE_BAD_PARAMETER;
+
+	wmi_parse_guid(guid, tmp);
+	wmi_swap_bytes(tmp, guid_input);
+
+	list_for_each(p, &wmi_block_list) {
+		acpi_status wmi_status;
+		block = list_entry(p, struct wmi_block, list);
+
+		if (memcmp(block->gblock.guid, guid_input, 16) == 0) {
+			if (block->handler &&
+			    block->handler != wmi_notify_debug)
+				return AE_ALREADY_ACQUIRED;
+
+			block->handler = handler;
+			block->handler_data = data;
+
+			wmi_status = wmi_method_enable(block, 1);
+			if ((wmi_status != AE_OK) ||
+			    ((wmi_status == AE_OK) && (status == AE_NOT_EXIST)))
+				status = wmi_status;
+		}
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
+
+/**
+ * wmi_uninstall_notify_handler - Unregister handler for WMI events
+ *
+ * Unregister handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_remove_notify_handler(const char *guid)
+{
+	struct wmi_block *block;
+	acpi_status status = AE_NOT_EXIST;
+	char tmp[16], guid_input[16];
+	struct list_head *p;
+
+	if (!guid)
+		return AE_BAD_PARAMETER;
+
+	wmi_parse_guid(guid, tmp);
+	wmi_swap_bytes(tmp, guid_input);
+
+	list_for_each(p, &wmi_block_list) {
+		acpi_status wmi_status;
+		block = list_entry(p, struct wmi_block, list);
+
+		if (memcmp(block->gblock.guid, guid_input, 16) == 0) {
+			if (!block->handler ||
+			    block->handler == wmi_notify_debug)
+				return AE_NULL_ENTRY;
+
+			if (debug_event) {
+				block->handler = wmi_notify_debug;
+				status = AE_OK;
+			} else {
+				wmi_status = wmi_method_enable(block, 0);
+				block->handler = NULL;
+				block->handler_data = NULL;
+				if ((wmi_status != AE_OK) ||
+				    ((wmi_status == AE_OK) &&
+				     (status == AE_NOT_EXIST)))
+					status = wmi_status;
+			}
+		}
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
+
+/**
+ * wmi_get_event_data - Get WMI data associated with an event
+ *
+ * @event: Event to find
+ * @out: Buffer to hold event data. out->pointer should be freed with kfree()
+ *
+ * Returns extra data associated with an event in WMI.
+ */
+acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
+{
+	struct acpi_object_list input;
+	union acpi_object params[1];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+
+	input.count = 1;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = event;
+
+	list_for_each(p, &wmi_block_list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		gblock = &wblock->gblock;
+
+		if ((gblock->flags & ACPI_WMI_EVENT) &&
+			(gblock->notify_id == event))
+			return acpi_evaluate_object(wblock->handle, "_WED",
+				&input, out);
+	}
+
+	return AE_NOT_FOUND;
+}
+EXPORT_SYMBOL_GPL(wmi_get_event_data);
+
+/**
+ * wmi_has_guid - Check if a GUID is available
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ *
+ * Check if a given GUID is defined by _WDG
+ */
+bool wmi_has_guid(const char *guid_string)
+{
+	return find_guid(guid_string, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_has_guid);
+
+/*
+ * sysfs interface
+ */
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct wmi_block *wblock;
+
+	wblock = dev_get_drvdata(dev);
+	if (!wblock) {
+		strcat(buf, "\n");
+		return strlen(buf);
+	}
+
+	return sprintf(buf, "wmi:%pUL\n", wblock->gblock.guid);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *wmi_attrs[] = {
+	&dev_attr_modalias.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(wmi);
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	char guid_string[37];
+
+	struct wmi_block *wblock;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+
+	wblock = dev_get_drvdata(dev);
+	if (!wblock)
+		return -ENOMEM;
+
+	sprintf(guid_string, "%pUL", wblock->gblock.guid);
+
+	strcpy(&env->buf[env->buflen - 1], "wmi:");
+	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+	env->buflen += 40;
+
+	return 0;
+}
+
+static void wmi_dev_free(struct device *dev)
+{
+	struct wmi_block *wmi_block = container_of(dev, struct wmi_block, dev);
+
+	kfree(wmi_block);
+}
+
+static struct class wmi_class = {
+	.name = "wmi",
+	.dev_release = wmi_dev_free,
+	.dev_uevent = wmi_dev_uevent,
+	.dev_groups = wmi_groups,
+};
+
+static int wmi_create_device(const struct guid_block *gblock,
+			     struct wmi_block *wblock, acpi_handle handle)
+{
+	wblock->dev.class = &wmi_class;
+
+	dev_set_name(&wblock->dev, "%pUL", gblock->guid);
+
+	dev_set_drvdata(&wblock->dev, wblock);
+
+	return device_register(&wblock->dev);
+}
+
+static void wmi_free_devices(void)
+{
+	struct wmi_block *wblock, *next;
+
+	/* Delete devices for all the GUIDs */
+	list_for_each_entry_safe(wblock, next, &wmi_block_list, list) {
+		list_del(&wblock->list);
+		if (wblock->dev.class)
+			device_unregister(&wblock->dev);
+		else
+			kfree(wblock);
+	}
+}
+
+static bool guid_already_parsed(const char *guid_string)
+{
+	struct wmi_block *wblock;
+
+	list_for_each_entry(wblock, &wmi_block_list, list)
+		if (memcmp(wblock->gblock.guid, guid_string, 16) == 0)
+			return true;
+
+	return false;
+}
+
+/*
+ * Parse the _WDG method for the GUID data blocks
+ */
+static int parse_wdg(acpi_handle handle)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	const struct guid_block *gblock;
+	struct wmi_block *wblock;
+	acpi_status status;
+	int retval;
+	u32 i, total;
+
+	status = acpi_evaluate_object(handle, "_WDG", NULL, &out);
+	if (ACPI_FAILURE(status))
+		return -ENXIO;
+
+	obj = (union acpi_object *) out.pointer;
+	if (!obj)
+		return -ENXIO;
+
+	if (obj->type != ACPI_TYPE_BUFFER) {
+		retval = -ENXIO;
+		goto out_free_pointer;
+	}
+
+	gblock = (const struct guid_block *)obj->buffer.pointer;
+	total = obj->buffer.length / sizeof(struct guid_block);
+
+	for (i = 0; i < total; i++) {
+		if (debug_dump_wdg)
+			wmi_dump_wdg(&gblock[i]);
+
+		wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
+		if (!wblock)
+			return -ENOMEM;
+
+		wblock->handle = handle;
+		wblock->gblock = gblock[i];
+
+		/*
+		  Some WMI devices, like those for nVidia hooks, have a
+		  duplicate GUID. It's not clear what we should do in this
+		  case yet, so for now, we'll just ignore the duplicate
+		  for device creation.
+		*/
+		if (!guid_already_parsed(gblock[i].guid)) {
+			retval = wmi_create_device(&gblock[i], wblock, handle);
+			if (retval) {
+				wmi_free_devices();
+				goto out_free_pointer;
+			}
+		}
+
+		list_add_tail(&wblock->list, &wmi_block_list);
+
+		if (debug_event) {
+			wblock->handler = wmi_notify_debug;
+			wmi_method_enable(wblock, 1);
+		}
+	}
+
+	retval = 0;
+
+out_free_pointer:
+	kfree(out.pointer);
+
+	return retval;
+}
+
+/*
+ * WMI can have EmbeddedControl access regions. In which case, we just want to
+ * hand these off to the EC driver.
+ */
+static acpi_status
+acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
+		      u32 bits, u64 *value,
+		      void *handler_context, void *region_context)
+{
+	int result = 0, i = 0;
+	u8 temp = 0;
+
+	if ((address > 0xFF) || !value)
+		return AE_BAD_PARAMETER;
+
+	if (function != ACPI_READ && function != ACPI_WRITE)
+		return AE_BAD_PARAMETER;
+
+	if (bits != 8)
+		return AE_BAD_PARAMETER;
+
+	if (function == ACPI_READ) {
+		result = ec_read(address, &temp);
+		(*value) |= ((u64)temp) << i;
+	} else {
+		temp = 0xff & ((*value) >> i);
+		result = ec_write(address, temp);
+	}
+
+	switch (result) {
+	case -EINVAL:
+		return AE_BAD_PARAMETER;
+		break;
+	case -ENODEV:
+		return AE_NOT_FOUND;
+		break;
+	case -ETIME:
+		return AE_TIME;
+		break;
+	default:
+		return AE_OK;
+	}
+}
+
+static void acpi_wmi_notify(struct acpi_device *device, u32 event)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+	struct list_head *p;
+
+	list_for_each(p, &wmi_block_list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		block = &wblock->gblock;
+
+		if ((block->flags & ACPI_WMI_EVENT) &&
+			(block->notify_id == event)) {
+			if (wblock->handler)
+				wblock->handler(event, wblock->handler_data);
+			if (debug_event) {
+				pr_info("DEBUG Event GUID: %pUL\n",
+					wblock->gblock.guid);
+			}
+
+			acpi_bus_generate_netlink_event(
+				device->pnp.device_class, dev_name(&device->dev),
+				event, 0);
+			break;
+		}
+	}
+}
+
+static int acpi_wmi_remove(struct acpi_device *device)
+{
+	acpi_remove_address_space_handler(device->handle,
+				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
+	wmi_free_devices();
+
+	return 0;
+}
+
+static int acpi_wmi_add(struct acpi_device *device)
+{
+	acpi_status status;
+	int error;
+
+	status = acpi_install_address_space_handler(device->handle,
+						    ACPI_ADR_SPACE_EC,
+						    &acpi_wmi_ec_space_handler,
+						    NULL, NULL);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Error installing EC region handler\n");
+		return -ENODEV;
+	}
+
+	error = parse_wdg(device->handle);
+	if (error) {
+		acpi_remove_address_space_handler(device->handle,
+						  ACPI_ADR_SPACE_EC,
+						  &acpi_wmi_ec_space_handler);
+		pr_err("Failed to parse WDG method\n");
+		return error;
+	}
+
+	return 0;
+}
+
+static int __init acpi_wmi_init(void)
+{
+	int error;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	error = class_register(&wmi_class);
+	if (error)
+		return error;
+
+	error = acpi_bus_register_driver(&acpi_wmi_driver);
+	if (error) {
+		pr_err("Error loading mapper\n");
+		class_unregister(&wmi_class);
+		return error;
+	}
+
+	pr_info("Mapper loaded\n");
+	return 0;
+}
+
+static void __exit acpi_wmi_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_wmi_driver);
+	class_unregister(&wmi_class);
+
+	pr_info("Mapper unloaded\n");
+}
+
+subsys_initcall(acpi_wmi_init);
+module_exit(acpi_wmi_exit);
diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c
new file mode 100644
index 0000000..e46fa9c
--- /dev/null
+++ b/drivers/platform/x86/xo1-rfkill.c
@@ -0,0 +1,83 @@
+/*
+ * Support for rfkill through the OLPC XO-1 laptop embedded controller
+ *
+ * Copyright (C) 2010 One Laptop per Child
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/olpc-ec.h>
+
+static bool card_blocked;
+
+static int rfkill_set_block(void *data, bool blocked)
+{
+	unsigned char cmd;
+	int r;
+
+	if (blocked == card_blocked)
+		return 0;
+
+	if (blocked)
+		cmd = EC_WLAN_ENTER_RESET;
+	else
+		cmd = EC_WLAN_LEAVE_RESET;
+
+	r = olpc_ec_cmd(cmd, NULL, 0, NULL, 0);
+	if (r == 0)
+		card_blocked = blocked;
+
+	return r;
+}
+
+static const struct rfkill_ops rfkill_ops = {
+	.set_block = rfkill_set_block,
+};
+
+static int xo1_rfkill_probe(struct platform_device *pdev)
+{
+	struct rfkill *rfk;
+	int r;
+
+	rfk = rfkill_alloc(pdev->name, &pdev->dev, RFKILL_TYPE_WLAN,
+			   &rfkill_ops, NULL);
+	if (!rfk)
+		return -ENOMEM;
+
+	r = rfkill_register(rfk);
+	if (r) {
+		rfkill_destroy(rfk);
+		return r;
+	}
+
+	platform_set_drvdata(pdev, rfk);
+	return 0;
+}
+
+static int xo1_rfkill_remove(struct platform_device *pdev)
+{
+	struct rfkill *rfk = platform_get_drvdata(pdev);
+	rfkill_unregister(rfk);
+	rfkill_destroy(rfk);
+	return 0;
+}
+
+static struct platform_driver xo1_rfkill_driver = {
+	.driver = {
+		.name = "xo1-rfkill",
+	},
+	.probe		= xo1_rfkill_probe,
+	.remove		= xo1_rfkill_remove,
+};
+
+module_platform_driver(xo1_rfkill_driver);
+
+MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:xo1-rfkill");
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
new file mode 100644
index 0000000..49cbcce
--- /dev/null
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -0,0 +1,172 @@
+/*
+ *  OLPC XO-1.5 ebook switch driver
+ *  (based on generic ACPI button driver)
+ *
+ *  Copyright (C) 2009 Paul Fox <pgf@laptop.org>
+ *  Copyright (C) 2010 One Laptop per Child
+ *
+ *  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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/acpi.h>
+
+#define MODULE_NAME "xo15-ebook"
+
+#define XO15_EBOOK_CLASS		MODULE_NAME
+#define XO15_EBOOK_TYPE_UNKNOWN	0x00
+#define XO15_EBOOK_NOTIFY_STATUS	0x80
+
+#define XO15_EBOOK_SUBCLASS		"ebook"
+#define XO15_EBOOK_HID			"XO15EBK"
+#define XO15_EBOOK_DEVICE_NAME		"EBook Switch"
+
+ACPI_MODULE_NAME(MODULE_NAME);
+
+MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver");
+MODULE_LICENSE("GPL");
+
+static const struct acpi_device_id ebook_device_ids[] = {
+	{ XO15_EBOOK_HID, 0 },
+	{ "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, ebook_device_ids);
+
+struct ebook_switch {
+	struct input_dev *input;
+	char phys[32];			/* for input device */
+};
+
+static int ebook_send_state(struct acpi_device *device)
+{
+	struct ebook_switch *button = acpi_driver_data(device);
+	unsigned long long state;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(device->handle, "EBK", NULL, &state);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	/* input layer checks if event is redundant */
+	input_report_switch(button->input, SW_TABLET_MODE, !state);
+	input_sync(button->input);
+	return 0;
+}
+
+static void ebook_switch_notify(struct acpi_device *device, u32 event)
+{
+	switch (event) {
+	case ACPI_FIXED_HARDWARE_EVENT:
+	case XO15_EBOOK_NOTIFY_STATUS:
+		ebook_send_state(device);
+		break;
+	default:
+		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+				  "Unsupported event [0x%x]\n", event));
+		break;
+	}
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ebook_switch_resume(struct device *dev)
+{
+	return ebook_send_state(to_acpi_device(dev));
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(ebook_switch_pm, NULL, ebook_switch_resume);
+
+static int ebook_switch_add(struct acpi_device *device)
+{
+	struct ebook_switch *button;
+	struct input_dev *input;
+	const char *hid = acpi_device_hid(device);
+	char *name, *class;
+	int error;
+
+	button = kzalloc(sizeof(struct ebook_switch), GFP_KERNEL);
+	if (!button)
+		return -ENOMEM;
+
+	device->driver_data = button;
+
+	button->input = input = input_allocate_device();
+	if (!input) {
+		error = -ENOMEM;
+		goto err_free_button;
+	}
+
+	name = acpi_device_name(device);
+	class = acpi_device_class(device);
+
+	if (strcmp(hid, XO15_EBOOK_HID)) {
+		pr_err("Unsupported hid [%s]\n", hid);
+		error = -ENODEV;
+		goto err_free_input;
+	}
+
+	strcpy(name, XO15_EBOOK_DEVICE_NAME);
+	sprintf(class, "%s/%s", XO15_EBOOK_CLASS, XO15_EBOOK_SUBCLASS);
+
+	snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid);
+
+	input->name = name;
+	input->phys = button->phys;
+	input->id.bustype = BUS_HOST;
+	input->dev.parent = &device->dev;
+
+	input->evbit[0] = BIT_MASK(EV_SW);
+	set_bit(SW_TABLET_MODE, input->swbit);
+
+	error = input_register_device(input);
+	if (error)
+		goto err_free_input;
+
+	ebook_send_state(device);
+
+	if (device->wakeup.flags.valid) {
+		/* Button's GPE is run-wake GPE */
+		acpi_enable_gpe(device->wakeup.gpe_device,
+				device->wakeup.gpe_number);
+		device_set_wakeup_enable(&device->dev, true);
+	}
+
+	return 0;
+
+ err_free_input:
+	input_free_device(input);
+ err_free_button:
+	kfree(button);
+	return error;
+}
+
+static int ebook_switch_remove(struct acpi_device *device)
+{
+	struct ebook_switch *button = acpi_driver_data(device);
+
+	input_unregister_device(button->input);
+	kfree(button);
+	return 0;
+}
+
+static struct acpi_driver xo15_ebook_driver = {
+	.name = MODULE_NAME,
+	.class = XO15_EBOOK_CLASS,
+	.ids = ebook_device_ids,
+	.ops = {
+		.add = ebook_switch_add,
+		.remove = ebook_switch_remove,
+		.notify = ebook_switch_notify,
+	},
+	.drv.pm = &ebook_switch_pm,
+};
+module_acpi_driver(xo15_ebook_driver);