Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platf...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 22 May 2010 00:13:24 +0000 (17:13 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 22 May 2010 00:13:24 +0000 (17:13 -0700)
* 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86: (32 commits)
  Move N014, N051 and CR620 dmi information to load scm dmi table
  drivers/platform/x86/eeepc-wmi.c: fix build warning
  X86 platfrom wmi: Add debug facility to dump WMI data in a readable way
  X86 platform wmi: Also log GUID string when an event happens and debug is set
  X86 platform wmi: Introduce debug param to log all WMI events
  Clean up all objects used by scm model when driver initial fail or exit
  msi-laptop: fix up some coding style issues found by checkpatch
  msi-laptop: Add i8042 filter to sync sw state with BIOS when function key pressed
  msi-laptop: Set rfkill init state when msi-laptop intiial
  msi-laptop: Add MSI CR620 notebook dmi information to scm models table
  msi-laptop: Add N014 N051 dmi information to scm models table
  drivers/platform/x86: Use kmemdup
  drivers/platform/x86: Use kzalloc
  drivers/platform/x86: Clarify the MRST IPC driver description slightly
  eeepc-wmi: depends on BACKLIGHT_CLASS_DEVICE
  IPC driver for Intel Mobile Internet Device (MID) platforms
  classmate-laptop: Add RFKILL support.
  thinkpad-acpi: document backlight level writeback at driver init
  thinkpad-acpi: clean up ACPI handles handling
  thinkpad-acpi: don't depend on led_path for led firmware type (v2)
  ...

Documentation/laptops/thinkpad-acpi.txt
arch/x86/include/asm/intel_scu_ipc.h [new file with mode: 0644]
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/classmate-laptop.c
drivers/platform/x86/eeepc-wmi.c
drivers/platform/x86/fujitsu-laptop.c
drivers/platform/x86/intel_scu_ipc.c [new file with mode: 0644]
drivers/platform/x86/msi-laptop.c
drivers/platform/x86/thinkpad_acpi.c
drivers/platform/x86/wmi.c

index 39c0a09d0105414b1eaba23e4196398be8412332..fc15538d8b460928262bdd8a6e97477e0df50791 100644 (file)
@@ -292,13 +292,13 @@ sysfs notes:
 
                Warning: when in NVRAM mode, the volume up/down/mute
                keys are synthesized according to changes in the mixer,
-               so you have to use volume up or volume down to unmute,
-               as per the ThinkPad volume mixer user interface.  When
-               in ACPI event mode, volume up/down/mute are reported as
-               separate events, but this behaviour may be corrected in
-               future releases of this driver, in which case the
-               ThinkPad volume mixer user interface semantics will be
-               enforced.
+               which uses a single volume up or volume down hotkey
+               press to unmute, as per the ThinkPad volume mixer user
+               interface.  When in ACPI event mode, volume up/down/mute
+               events are reported by the firmware and can behave
+               differently (and that behaviour changes with firmware
+               version -- not just with firmware models -- as well as
+               OSI(Linux) state).
 
        hotkey_poll_freq:
                frequency in Hz for hot key polling. It must be between
@@ -309,7 +309,7 @@ sysfs notes:
                will cause hot key presses that require NVRAM polling
                to never be reported.
 
-               Setting hotkey_poll_freq too low will cause repeated
+               Setting hotkey_poll_freq too low may cause repeated
                pressings of the same hot key to be misreported as a
                single key press, or to not even be detected at all.
                The recommended polling frequency is 10Hz.
@@ -397,6 +397,7 @@ ACPI        Scan
 event  code    Key             Notes
 
 0x1001 0x00    FN+F1           -
+
 0x1002 0x01    FN+F2           IBM: battery (rare)
                                Lenovo: Screen lock
 
@@ -404,7 +405,8 @@ event       code    Key             Notes
                                this hot key, even with hot keys
                                disabled or with Fn+F3 masked
                                off
-                               IBM: screen lock
+                               IBM: screen lock, often turns
+                               off the ThinkLight as side-effect
                                Lenovo: battery
 
 0x1004 0x03    FN+F4           Sleep button (ACPI sleep button
@@ -433,7 +435,8 @@ event       code    Key             Notes
                                Do you feel lucky today?
 
 0x1008 0x07    FN+F8           IBM: toggle screen expand
-                               Lenovo: configure UltraNav
+                               Lenovo: configure UltraNav,
+                               or toggle screen expand
 
 0x1009 0x08    FN+F9           -
        ..      ..              ..
@@ -444,7 +447,7 @@ event       code    Key             Notes
                                either through the ACPI event,
                                or through a hotkey event.
                                The firmware may refuse to
-                               generate further FN+F4 key
+                               generate further FN+F12 key
                                press events until a S3 or S4
                                ACPI sleep cycle is performed,
                                or some time passes.
@@ -512,15 +515,19 @@ events for switches:
 SW_RFKILL_ALL  T60 and later hardware rfkill rocker switch
 SW_TABLET_MODE Tablet ThinkPads HKEY events 0x5009 and 0x500A
 
-Non hot-key ACPI HKEY event map:
+Non hotkey ACPI HKEY event map:
+-------------------------------
+
+Events that are not propagated by the driver, except for legacy
+compatibility purposes when hotkey_report_mode is set to 1:
+
 0x5001         Lid closed
 0x5002         Lid opened
 0x5009         Tablet swivel: switched to tablet mode
 0x500A         Tablet swivel: switched to normal mode
 0x7000         Radio Switch may have changed state
 
-The above events are not propagated by the driver, except for legacy
-compatibility purposes when hotkey_report_mode is set to 1.
+Events that are never propagated by the driver:
 
 0x2304         System is waking up from suspend to undock
 0x2305         System is waking up from suspend to eject bay
@@ -528,14 +535,39 @@ compatibility purposes when hotkey_report_mode is set to 1.
 0x2405         System is waking up from hibernation to eject bay
 0x5010         Brightness level changed/control event
 
-The above events are never propagated by the driver.
+Events that are propagated by the driver to userspace:
 
+0x2313         ALARM: System is waking up from suspend because
+               the battery is nearly empty
+0x2413         ALARM: System is waking up from hibernation because
+               the battery is nearly empty
 0x3003         Bay ejection (see 0x2x05) complete, can sleep again
+0x3006         Bay hotplug request (hint to power up SATA link when
+               the optical drive tray is ejected)
 0x4003         Undocked (see 0x2x04), can sleep again
 0x500B         Tablet pen inserted into its storage bay
 0x500C         Tablet pen removed from its storage bay
-
-The above events are propagated by the driver.
+0x6011         ALARM: battery is too hot
+0x6012         ALARM: battery is extremely hot
+0x6021         ALARM: a sensor is too hot
+0x6022         ALARM: a sensor is extremely hot
+0x6030         System thermal table changed
+
+Battery nearly empty alarms are a last resort attempt to get the
+operating system to hibernate or shutdown cleanly (0x2313), or shutdown
+cleanly (0x2413) before power is lost.  They must be acted upon, as the
+wake up caused by the firmware will have negated most safety nets...
+
+When any of the "too hot" alarms happen, according to Lenovo the user
+should suspend or hibernate the laptop (and in the case of battery
+alarms, unplug the AC adapter) to let it cool down.  These alarms do
+signal that something is wrong, they should never happen on normal
+operating conditions.
+
+The "extremely hot" alarms are emergencies.  According to Lenovo, the
+operating system is to force either an immediate suspend or hibernate
+cycle, or a system shutdown.  Obviously, something is very wrong if this
+happens.
 
 Compatibility notes:
 
diff --git a/arch/x86/include/asm/intel_scu_ipc.h b/arch/x86/include/asm/intel_scu_ipc.h
new file mode 100644 (file)
index 0000000..4470c9a
--- /dev/null
@@ -0,0 +1,55 @@
+#ifndef _ASM_X86_INTEL_SCU_IPC_H_
+#define  _ASM_X86_INTEL_SCU_IPC_H_
+
+/* Read single register */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data);
+
+/* Read two sequential registers */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data);
+
+/* Read four sequential registers */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data);
+
+/* Read a vector */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len);
+
+/* Write single register */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data);
+
+/* Write two sequential registers */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data);
+
+/* Write four sequential registers */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data);
+
+/* Write a vector */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len);
+
+/* Update single register based on the mask */
+int intel_scu_ipc_update_register(u16 addr, u8 data, u8 mask);
+
+/*
+ * Indirect register read
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *data);
+
+/*
+ * Indirect register write
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 data);
+
+/* Issue commands to the SCU with or without data */
+int intel_scu_ipc_simple_command(int cmd, int sub);
+int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
+                                                       u32 *out, int outlen);
+/* I2C control api */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
+
+/* Update FW version */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length);
+
+#endif
index 6c3320d7505533804881a56fb349534b4eea0e4c..3e1b8a288719d2047e911025e9fc6c1c8714d578 100644 (file)
@@ -390,6 +390,7 @@ config EEEPC_WMI
        depends on ACPI_WMI
        depends on INPUT
        depends on EXPERIMENTAL
+       depends on BACKLIGHT_CLASS_DEVICE
        select INPUT_SPARSEKMAP
        ---help---
          Say Y here if you want to support WMI-based hotkeys on Eee PC laptops.
@@ -527,4 +528,13 @@ config ACPI_CMPC
          keys as input device, backlight device, tablet and accelerometer
          devices.
 
+config INTEL_SCU_IPC
+       bool "Intel SCU IPC Support"
+       depends on X86_MRST
+       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.
+
 endif # X86_PLATFORM_DEVICES
index a906490e3530a91f44b41b571af0b9737d85feae..8770bfe71431ec6435feacd02b4d2211741b7a39 100644 (file)
@@ -25,3 +25,4 @@ obj-$(CONFIG_ACPI_ASUS)               += asus_acpi.o
 obj-$(CONFIG_TOPSTAR_LAPTOP)   += topstar-laptop.o
 obj-$(CONFIG_ACPI_TOSHIBA)     += toshiba_acpi.o
 obj-$(CONFIG_TOSHIBA_BT_RFKILL)        += toshiba_bluetooth.o
+obj-$(CONFIG_INTEL_SCU_IPC)    += intel_scu_ipc.o
index 7f9e5ddc949841ba3c80d15aa75d94a60e9385c0..3bf399fe2bbc14bb2e61051fdac4aa7d28c53bb9 100644 (file)
@@ -24,6 +24,7 @@
 #include <acpi/acpi_drivers.h>
 #include <linux/backlight.h>
 #include <linux/input.h>
+#include <linux/rfkill.h>
 
 MODULE_LICENSE("GPL");
 
@@ -37,7 +38,7 @@ struct cmpc_accel {
 
 #define CMPC_ACCEL_HID         "ACCE0000"
 #define CMPC_TABLET_HID                "TBLT0000"
-#define CMPC_BL_HID            "IPML200"
+#define CMPC_IPML_HID  "IPML200"
 #define CMPC_KEYS_HID          "FnBT0000"
 
 /*
@@ -461,43 +462,168 @@ static const struct backlight_ops cmpc_bl_ops = {
        .update_status = cmpc_bl_update_status
 };
 
-static int cmpc_bl_add(struct acpi_device *acpi)
+/*
+ * RFKILL code.
+ */
+
+static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle,
+                                       unsigned long long *value)
 {
-       struct backlight_properties props;
+       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;
+
+       handle = data;
+       status = cmpc_get_rfkill_wlan(handle, &state);
+       if (ACPI_FAILURE(status))
+               return -ENODEV;
+       if (blocked)
+               state &= ~1;
+       else
+               state |= 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.max_brightness = 7;
-       bd = backlight_device_register("cmpc_bl", &acpi->dev, acpi->handle,
-                                      &cmpc_bl_ops, &props);
-       if (IS_ERR(bd))
-               return PTR_ERR(bd);
-       dev_set_drvdata(&acpi->dev, bd);
+       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);
+       /* rfkill_alloc may fail if RFKILL is disabled. We should still work
+        * anyway. */
+       if (!IS_ERR(ipml->rf)) {
+               retval = rfkill_register(ipml->rf);
+               if (retval) {
+                       rfkill_destroy(ipml->rf);
+                       ipml->rf = NULL;
+               }
+       } else {
+               ipml->rf = NULL;
+       }
+
+       dev_set_drvdata(&acpi->dev, ipml);
        return 0;
+
+out_bd:
+       kfree(ipml);
+       return retval;
 }
 
-static int cmpc_bl_remove(struct acpi_device *acpi, int type)
+static int cmpc_ipml_remove(struct acpi_device *acpi, int type)
 {
-       struct backlight_device *bd;
+       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);
 
-       bd = dev_get_drvdata(&acpi->dev);
-       backlight_device_unregister(bd);
        return 0;
 }
 
-static const struct acpi_device_id cmpc_bl_device_ids[] = {
-       {CMPC_BL_HID, 0},
+static const struct acpi_device_id cmpc_ipml_device_ids[] = {
+       {CMPC_IPML_HID, 0},
        {"", 0}
 };
 
-static struct acpi_driver cmpc_bl_acpi_driver = {
+static struct acpi_driver cmpc_ipml_acpi_driver = {
        .owner = THIS_MODULE,
        .name = "cmpc",
        .class = "cmpc",
-       .ids = cmpc_bl_device_ids,
+       .ids = cmpc_ipml_device_ids,
        .ops = {
-               .add = cmpc_bl_add,
-               .remove = cmpc_bl_remove
+               .add = cmpc_ipml_add,
+               .remove = cmpc_ipml_remove
        }
 };
 
@@ -580,7 +706,7 @@ static int cmpc_init(void)
        if (r)
                goto failed_keys;
 
-       r = acpi_bus_register_driver(&cmpc_bl_acpi_driver);
+       r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver);
        if (r)
                goto failed_bl;
 
@@ -598,7 +724,7 @@ failed_accel:
        acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
 
 failed_tablet:
-       acpi_bus_unregister_driver(&cmpc_bl_acpi_driver);
+       acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
 
 failed_bl:
        acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
@@ -611,7 +737,7 @@ static void cmpc_exit(void)
 {
        acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
        acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
-       acpi_bus_unregister_driver(&cmpc_bl_acpi_driver);
+       acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
        acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
 }
 
@@ -621,7 +747,7 @@ module_exit(cmpc_exit);
 static const struct acpi_device_id cmpc_device_ids[] = {
        {CMPC_ACCEL_HID, 0},
        {CMPC_TABLET_HID, 0},
-       {CMPC_BL_HID, 0},
+       {CMPC_IPML_HID, 0},
        {CMPC_KEYS_HID, 0},
        {"", 0}
 };
index b227eb469f49ba60667900ad24a2fccc4d8eb583..9dc50fbf3d0bc787989fe0621dd24e413e55aea6 100644 (file)
@@ -206,7 +206,7 @@ static int eeepc_wmi_backlight_notify(struct eeepc_wmi *eeepc, int code)
 {
        struct backlight_device *bd = eeepc->backlight_device;
        int old = bd->props.brightness;
-       int new;
+       int new = old;
 
        if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
                new = code - NOTIFY_BRNUP_MIN + 1;
index 47b4fd75aa344966b3933af9ca7b09acf7eee613..e325aeb37d2ef0b158ff6960ab7ecc17c0b738c2 100644 (file)
@@ -1090,10 +1090,9 @@ static int __init fujitsu_init(void)
        if (acpi_disabled)
                return -ENODEV;
 
-       fujitsu = kmalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
+       fujitsu = kzalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
        if (!fujitsu)
                return -ENOMEM;
-       memset(fujitsu, 0, sizeof(struct fujitsu_t));
        fujitsu->keycode1 = KEY_PROG1;
        fujitsu->keycode2 = KEY_PROG2;
        fujitsu->keycode3 = KEY_PROG3;
@@ -1150,12 +1149,11 @@ static int __init fujitsu_init(void)
 
        /* Register hotkey driver */
 
-       fujitsu_hotkey = kmalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
+       fujitsu_hotkey = kzalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
        if (!fujitsu_hotkey) {
                ret = -ENOMEM;
                goto fail_hotkey;
        }
-       memset(fujitsu_hotkey, 0, sizeof(struct fujitsu_hotkey_t));
 
        result = acpi_bus_register_driver(&acpi_fujitsu_hotkey_driver);
        if (result < 0) {
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
new file mode 100644 (file)
index 0000000..576c3ed
--- /dev/null
@@ -0,0 +1,829 @@
+/*
+ * 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.
+ *
+ * SCU runing 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/sysdev.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <asm/setup.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 */
+
+/* Miscelaneous Command ids */
+#define IPC_CMD_INDIRECT_RD   2 /* 32bit indirect read */
+#define IPC_CMD_INDIRECT_WR   5 /* 32bit indirect write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of 0xFF11C000
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers (base address 0xFF11C000). 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_BASE_ADDR     0xFF11C000   /* IPC1 base register address */
+#define IPC_MAX_ADDR      0x100                /* Maximum IPC regisers */
+#define IPC_WWBUF_SIZE    16           /* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE    16           /* IPC Read buffer Size */
+#define IPC_I2C_BASE      0xFF12B000   /* I2C control register base address */
+#define IPC_I2C_MAX_ADDR  0x10         /* Maximum I2C regisers */
+
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
+static void ipc_remove(struct pci_dev *pdev);
+
+struct intel_scu_ipc_dev {
+       struct pci_dev *pdev;
+       void __iomem *ipc_base;
+       void __iomem *i2c_base;
+};
+
+static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
+
+static int platform = 1;
+module_param(platform, int, 0);
+MODULE_PARM_DESC(platform, "1 for moorestown platform");
+
+
+
+
+/*
+ * 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 */
+
+/*
+ * 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(u32 cmd) /* Send ipc command */
+{
+       writel(cmd, ipcdev.ipc_base);
+}
+
+/*
+ * 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(u32 data, u32 offset) /* Write ipc data */
+{
+       writel(data, ipcdev.ipc_base + 0x80 + offset);
+}
+
+/*
+ * IPC destination Pointer (Write Only):
+ * Use content as pointer for destination write
+ */
+static inline void ipc_write_dptr(u32 data) /* Write dptr data */
+{
+       writel(data, ipcdev.ipc_base + 0x0C);
+}
+
+/*
+ * IPC Source Pointer (Write Only):
+ * Use content as pointer for read location
+*/
+static inline void ipc_write_sptr(u32 data) /* Write dptr data */
+{
+       writel(data, ipcdev.ipc_base + 0x08);
+}
+
+/*
+ * 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(void)
+{
+       return __raw_readl(ipcdev.ipc_base + 0x04);
+}
+
+static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */
+{
+       return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline u8 ipc_data_readl(u32 offset) /* Read ipc u32 data */
+{
+       return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline int busy_loop(void) /* Wait till scu status is busy */
+{
+       u32 status = 0;
+       u32 loop_count = 0;
+
+       status = ipc_read_status();
+       while (status & 1) {
+               udelay(1); /* scu processing time is in few u secods */
+               status = ipc_read_status();
+               loop_count++;
+               /* break if scu doesn't reset busy bit after huge retry */
+               if (loop_count > 100000) {
+                       dev_err(&ipcdev.pdev->dev, "IPC timed out");
+                       return -ETIMEDOUT;
+               }
+       }
+       return (status >> 1) & 1;
+}
+
+/* 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)
+{
+       int nc;
+       u32 offset = 0;
+       u32 err = 0;
+       u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };
+       u32 *wbuf = (u32 *)&cbuf;
+
+       mutex_lock(&ipclock);
+       if (ipcdev.pdev == NULL) {
+               mutex_unlock(&ipclock);
+               return -ENODEV;
+       }
+
+       if (platform == 1) {
+               /* Entry is 4 bytes for read/write, 5 bytes for read modify */
+               for (nc = 0; nc < count; nc++) {
+                       cbuf[offset] = addr[nc];
+                       cbuf[offset + 1] = addr[nc] >> 8;
+                       if (id != IPC_CMD_PCNTRL_R)
+                               cbuf[offset + 2] = data[nc];
+                       if (id == IPC_CMD_PCNTRL_M) {
+                               cbuf[offset + 3] = data[nc + 1];
+                               offset += 1;
+                       }
+                       offset += 3;
+               }
+               for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+                       ipc_data_writel(wbuf[nc], offset); /* Write wbuff */
+
+       } else {
+               for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
+                       ipc_data_writel(addr[nc], offset); /* Write addresses */
+               if (id != IPC_CMD_PCNTRL_R) {
+                       for (nc = 0; nc < count; nc++, offset++)
+                               ipc_data_writel(data[nc], offset); /* Write data */
+                       if (id == IPC_CMD_PCNTRL_M)
+                               ipc_data_writel(data[nc + 1], offset); /* Mask value*/
+               }
+       }
+
+       if (id != IPC_CMD_PCNTRL_M)
+               ipc_command((count * 3) << 16 |  id << 12 | 0 << 8 | op);
+       else
+               ipc_command((count * 4) << 16 |  id << 12 | 0 << 8 | op);
+
+       err = busy_loop();
+
+       if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+               /* Workaround: values are read as 0 without memcpy_fromio */
+               memcpy_fromio(cbuf, ipcdev.ipc_base + IPC_READ_BUFFER, 16);
+               if (platform == 1) {
+                       for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
+                               data[nc] = ipc_data_readb(offset);
+               } else {
+                       for (nc = 0; nc < count; nc++)
+                               data[nc] = ipc_data_readb(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_register_read     -       32bit indirect read
+ *     @addr: register address
+ *     @value: 32bit value return
+ *
+ *     Performs IA 32 bit indirect read, returns 0 on success, or an
+ *     error code.
+ *
+ *     Can be used when SCCB(System Controller Configuration Block) register
+ *     HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *     This function may sleep. Locking for SCU accesses is handled for
+ *     the caller.
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *value)
+{
+       u32 err = 0;
+
+       mutex_lock(&ipclock);
+       if (ipcdev.pdev == NULL) {
+               mutex_unlock(&ipclock);
+               return -ENODEV;
+       }
+       ipc_write_sptr(addr);
+       ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
+       err = busy_loop();
+       *value = ipc_data_readl(0);
+       mutex_unlock(&ipclock);
+       return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_read);
+
+/**
+ *     intel_scu_ipc_register_write    -       32bit indirect write
+ *     @addr: register address
+ *     @value: 32bit value to write
+ *
+ *     Performs IA 32 bit indirect write, returns 0 on success, or an
+ *     error code.
+ *
+ *     Can be used when SCCB(System Controller Configuration Block) register
+ *     HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *     This function may sleep. Locking for SCU accesses is handled for
+ *     the caller.
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 value)
+{
+       u32 err = 0;
+
+       mutex_lock(&ipclock);
+       if (ipcdev.pdev == NULL) {
+               mutex_unlock(&ipclock);
+               return -ENODEV;
+       }
+       ipc_write_dptr(addr);
+       ipc_data_writel(value, 0);
+       ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR);
+       err = busy_loop();
+       mutex_unlock(&ipclock);
+       return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_write);
+
+/**
+ *     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)
+{
+       u32 err = 0;
+
+       mutex_lock(&ipclock);
+       if (ipcdev.pdev == NULL) {
+               mutex_unlock(&ipclock);
+               return -ENODEV;
+       }
+       ipc_command(cmd << 12 | sub);
+       err = busy_loop();
+       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
+ *     @out: output data
+ *     @outlein: output length
+ *
+ *     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)
+{
+       u32 err = 0;
+       int i = 0;
+
+       mutex_lock(&ipclock);
+       if (ipcdev.pdev == NULL) {
+               mutex_unlock(&ipclock);
+               return -ENODEV;
+       }
+
+       for (i = 0; i < inlen; i++)
+               ipc_data_writel(*in++, 4 * i);
+
+       ipc_command(cmd << 12 | sub);
+       err = busy_loop();
+
+       for (i = 0; i < outlen; i++)
+               *out++ = ipc_data_readl(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)
+{
+       u32 cmd = 0;
+
+       mutex_lock(&ipclock);
+       cmd = (addr >> 24) & 0xFF;
+       if (cmd == IPC_I2C_READ) {
+               writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+               /* Write not getting updated without delay */
+               mdelay(1);
+               *data = readl(ipcdev.i2c_base + I2C_DATA_ADDR);
+       } else if (cmd == IPC_I2C_WRITE) {
+               writel(addr, ipcdev.i2c_base + I2C_DATA_ADDR);
+               mdelay(1);
+               writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+       } else {
+               dev_err(&ipcdev.pdev->dev,
+                       "intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+               mutex_unlock(&ipclock);
+               return -1;
+       }
+       mutex_unlock(&ipclock);
+       return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */
+#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */
+#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */
+#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */
+/* IPC inform SCU to get ready for update process */
+#define IPC_CMD_FW_UPDATE_READY  0x10FE
+/* IPC inform SCU to go for update process */
+#define IPC_CMD_FW_UPDATE_GO     0x20FE
+/* Status code for fw update */
+#define IPC_FW_UPDATE_SUCCESS  0x444f4e45 /* Status code 'DONE' */
+#define IPC_FW_UPDATE_BADN     0x4241444E /* Status code 'BADN' */
+#define IPC_FW_TXHIGH          0x54784849 /* Status code 'IPC_FW_TXHIGH' */
+#define IPC_FW_TXLOW           0x54784c4f /* Status code 'IPC_FW_TXLOW' */
+
+struct fw_update_mailbox {
+       u32    status;
+       u32    scu_flag;
+       u32    driver_flag;
+};
+
+
+/**
+ *     intel_scu_ipc_fw_update -        Firmware update utility
+ *     @buffer: firmware buffer
+ *     @length: size of firmware buffer
+ *
+ *     This function provides an interface to load the firmware into
+ *     the SCU. Returns 0 on success or -1 on failure
+ */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
+{
+       void __iomem *fw_update_base;
+       struct fw_update_mailbox __iomem *mailbox = NULL;
+       int retry_cnt = 0;
+       u32 status;
+
+       mutex_lock(&ipclock);
+       fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
+       if (fw_update_base == NULL) {
+               mutex_unlock(&ipclock);
+               return -ENOMEM;
+       }
+       mailbox = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
+                                       sizeof(struct fw_update_mailbox));
+       if (mailbox == NULL) {
+               iounmap(fw_update_base);
+               mutex_unlock(&ipclock);
+               return -ENOMEM;
+       }
+
+       ipc_command(IPC_CMD_FW_UPDATE_READY);
+
+       /* Intitialize mailbox */
+       writel(0, &mailbox->status);
+       writel(0, &mailbox->scu_flag);
+       writel(0, &mailbox->driver_flag);
+
+       /* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
+       memcpy_toio(fw_update_base, buffer, 0x800);
+
+       /* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
+       * Upon receiving this command, SCU will write the 2K MIP header
+       * from 0xFFFC0000 into NAND.
+       * SCU will write a status code into the Mailbox, and then set scu_flag.
+       */
+
+       ipc_command(IPC_CMD_FW_UPDATE_GO);
+
+       /*Driver stalls until scu_flag is set */
+       while (readl(&mailbox->scu_flag) != 1) {
+               rmb();
+               mdelay(1);
+       }
+
+       /* Driver checks Mailbox status.
+        * If the status is 'BADN', then abort (bad NAND).
+        * If the status is 'IPC_FW_TXLOW', then continue.
+        */
+       while (readl(&mailbox->status) != IPC_FW_TXLOW) {
+               rmb();
+               mdelay(10);
+       }
+       mdelay(10);
+
+update_retry:
+       if (retry_cnt > 5)
+               goto update_end;
+
+       if (readl(&mailbox->status) != IPC_FW_TXLOW)
+               goto update_end;
+       buffer = buffer + 0x800;
+       memcpy_toio(fw_update_base, buffer, 0x20000);
+       writel(1, &mailbox->driver_flag);
+       while (readl(&mailbox->scu_flag) == 1) {
+               rmb();
+               mdelay(1);
+       }
+
+       /* check for 'BADN' */
+       if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
+               goto update_end;
+
+       while (readl(&mailbox->status) != IPC_FW_TXHIGH) {
+               rmb();
+               mdelay(10);
+       }
+       mdelay(10);
+
+       if (readl(&mailbox->status) != IPC_FW_TXHIGH)
+               goto update_end;
+
+       buffer = buffer + 0x20000;
+       memcpy_toio(fw_update_base, buffer, 0x20000);
+       writel(0, &mailbox->driver_flag);
+
+       while (mailbox->scu_flag == 0) {
+               rmb();
+               mdelay(1);
+       }
+
+       /* check for 'BADN' */
+       if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
+               goto update_end;
+
+       if (readl(&mailbox->status) == IPC_FW_TXLOW) {
+               ++retry_cnt;
+               goto update_retry;
+       }
+
+update_end:
+       status = readl(&mailbox->status);
+
+       iounmap(fw_update_base);
+       iounmap(mailbox);
+       mutex_unlock(&ipclock);
+
+       if (status == IPC_FW_UPDATE_SUCCESS)
+               return 0;
+       return -1;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_update);
+
+/*
+ * 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)
+{
+       return IRQ_HANDLED;
+}
+
+/**
+ *     ipc_probe       -       probe an Intel SCU IPC
+ *     @dev: 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 *dev, const struct pci_device_id *id)
+{
+       int err;
+       resource_size_t pci_resource;
+
+       if (ipcdev.pdev)                /* We support only one SCU */
+               return -EBUSY;
+
+       ipcdev.pdev = pci_dev_get(dev);
+
+       err = pci_enable_device(dev);
+       if (err)
+               return err;
+
+       err = pci_request_regions(dev, "intel_scu_ipc");
+       if (err)
+               return err;
+
+       pci_resource = pci_resource_start(dev, 0);
+       if (!pci_resource)
+               return -ENOMEM;
+
+       if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev))
+               return -EBUSY;
+
+       ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
+       if (!ipcdev.ipc_base)
+               return -ENOMEM;
+
+       ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
+       if (!ipcdev.i2c_base) {
+               iounmap(ipcdev.ipc_base);
+               return -ENOMEM;
+       }
+       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)
+{
+       free_irq(pdev->irq, &ipcdev);
+       pci_release_regions(pdev);
+       pci_dev_put(ipcdev.pdev);
+       iounmap(ipcdev.ipc_base);
+       iounmap(ipcdev.i2c_base);
+       ipcdev.pdev = NULL;
+}
+
+static const struct pci_device_id pci_ids[] = {
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)},
+       { 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,
+};
+
+
+static int __init intel_scu_ipc_init(void)
+{
+       return  pci_register_driver(&ipc_driver);
+}
+
+static void __exit intel_scu_ipc_exit(void)
+{
+       pci_unregister_driver(&ipc_driver);
+}
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
+
+module_init(intel_scu_ipc_init);
+module_exit(intel_scu_ipc_exit);
index 996223a7c009b01f080d0130e7c5e701872fa511..afd762b58ad9a21e9a28072bae31156b8efd5725 100644 (file)
@@ -59,6 +59,7 @@
 #include <linux/backlight.h>
 #include <linux/platform_device.h>
 #include <linux/rfkill.h>
+#include <linux/i8042.h>
 
 #define MSI_DRIVER_VERSION "0.5"
 
@@ -118,7 +119,8 @@ static int set_lcd_level(int level)
        buf[0] = 0x80;
        buf[1] = (u8) (level*31);
 
-       return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1);
+       return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf),
+                             NULL, 0, 1);
 }
 
 static int get_lcd_level(void)
@@ -126,7 +128,8 @@ static int get_lcd_level(void)
        u8 wdata = 0, rdata;
        int result;
 
-       result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
+       result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
+                               &rdata, 1, 1);
        if (result < 0)
                return result;
 
@@ -138,7 +141,8 @@ static int get_auto_brightness(void)
        u8 wdata = 4, rdata;
        int result;
 
-       result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
+       result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
+                               &rdata, 1, 1);
        if (result < 0)
                return result;
 
@@ -152,14 +156,16 @@ static int set_auto_brightness(int enable)
 
        wdata[0] = 4;
 
-       result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1);
+       result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1,
+                               &rdata, 1, 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, 1);
+       return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2,
+                             NULL, 0, 1);
 }
 
 static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
@@ -254,7 +260,7 @@ static int bl_update_status(struct backlight_device *b)
        return set_lcd_level(b->props.brightness);
 }
 
-static struct backlight_ops msibl_ops = {
+static const struct backlight_ops msibl_ops = {
        .get_brightness = bl_get_brightness,
        .update_status  = bl_update_status,
 };
@@ -353,7 +359,8 @@ static ssize_t store_lcd_level(struct device *dev,
 
        int level, ret;
 
-       if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX))
+       if (sscanf(buf, "%i", &level) != 1 ||
+           (level < 0 || level >= MSI_LCD_LEVEL_MAX))
                return -EINVAL;
 
        ret = set_lcd_level(level);
@@ -393,7 +400,8 @@ static ssize_t store_auto_brightness(struct device *dev,
 }
 
 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(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);
@@ -424,8 +432,9 @@ static struct platform_device *msipf_device;
 
 static int dmi_check_cb(const struct dmi_system_id *id)
 {
-        printk("msi-laptop: Identified laptop model '%s'.\n", id->ident);
-        return 0;
+       printk(KERN_INFO "msi-laptop: Identified laptop model '%s'.\n",
+              id->ident);
+       return 0;
 }
 
 static struct dmi_system_id __initdata msi_dmi_table[] = {
@@ -435,7 +444,8 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
                        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")
+                       DMI_MATCH(DMI_CHASSIS_VENDOR,
+                                 "MICRO-STAR INT'L CO.,LTD")
                },
                .callback = dmi_check_cb
        },
@@ -465,7 +475,8 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
                        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")
+                       DMI_MATCH(DMI_CHASSIS_VENDOR,
+                                 "MICRO-STAR INT'L CO.,LTD")
                },
                .callback = dmi_check_cb
        },
@@ -484,6 +495,35 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
                },
                .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")
+               },
+               .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"),
+               },
+               .callback = dmi_check_cb
+       },
+       {
+               .ident = "MSI CR620",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR,
+                               "Micro-Star International"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
+               },
+               .callback = dmi_check_cb
+       },
        { }
 };
 
@@ -552,11 +592,71 @@ static void rfkill_cleanup(void)
        }
 }
 
+static void msi_update_rfkill(struct work_struct *ignored)
+{
+       get_wireless_state_ec_standard();
+
+       if (rfk_wlan)
+               rfkill_set_sw_state(rfk_wlan, !wlan_s);
+       if (rfk_bluetooth)
+               rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
+       if (rfk_threeg)
+               rfkill_set_sw_state(rfk_threeg, !threeg_s);
+}
+static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill);
+
+static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
+                               struct serio *port)
+{
+       static bool extended;
+
+       if (str & 0x20)
+               return false;
+
+       /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan*/
+       if (unlikely(data == 0xe0)) {
+               extended = true;
+               return false;
+       } else if (unlikely(extended)) {
+               switch (data) {
+               case 0x54:
+               case 0x62:
+               case 0x76:
+                       schedule_delayed_work(&msi_rfkill_work,
+                               round_jiffies_relative(0.5 * HZ));
+                       break;
+               }
+               extended = false;
+       }
+
+       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);
@@ -590,6 +690,10 @@ static int rfkill_init(struct platform_device *sdev)
                        goto err_threeg;
        }
 
+       /* schedule to run rfkill state initial */
+       schedule_delayed_work(&msi_rfkill_init,
+                               round_jiffies_relative(1 * HZ));
+
        return 0;
 
 err_threeg:
@@ -653,9 +757,24 @@ static int load_scm_model_init(struct platform_device *sdev)
        /* initial rfkill */
        result = rfkill_init(sdev);
        if (result < 0)
-               return result;
+               goto fail_rfkill;
+
+       result = i8042_install_filter(msi_laptop_i8042_filter);
+       if (result) {
+               printk(KERN_ERR
+                       "msi-laptop: Unable to install key filter\n");
+               goto fail_filter;
+       }
 
        return 0;
+
+fail_filter:
+       rfkill_cleanup();
+
+fail_rfkill:
+
+       return result;
+
 }
 
 static int __init msi_init(void)
@@ -714,7 +833,8 @@ static int __init msi_init(void)
                goto fail_platform_device1;
        }
 
-       ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
+       ret = sysfs_create_group(&msipf_device->dev.kobj,
+                                &msipf_attribute_group);
        if (ret)
                goto fail_platform_device2;
 
@@ -739,6 +859,11 @@ static int __init msi_init(void)
 
 fail_platform_device2:
 
+       if (load_scm_model) {
+               i8042_remove_filter(msi_laptop_i8042_filter);
+               cancel_delayed_work_sync(&msi_rfkill_work);
+               rfkill_cleanup();
+       }
        platform_device_del(msipf_device);
 
 fail_platform_device1:
@@ -758,6 +883,11 @@ fail_backlight:
 
 static void __exit msi_cleanup(void)
 {
+       if (load_scm_model) {
+               i8042_remove_filter(msi_laptop_i8042_filter);
+               cancel_delayed_work_sync(&msi_rfkill_work);
+               rfkill_cleanup();
+       }
 
        sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
        if (!old_ec_model && threeg_exists)
@@ -766,8 +896,6 @@ static void __exit msi_cleanup(void)
        platform_driver_unregister(&msipf_driver);
        backlight_device_unregister(msibl_device);
 
-       rfkill_cleanup();
-
        /* Enable automatic brightness control again */
        if (auto_brightness != 2)
                set_auto_brightness(1);
@@ -788,3 +916,6 @@ MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-105
 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:*");
index 63290b33c8796e203113309536ed8716a42ef1f7..4bdb13796e24eba6b494f1cd9ad315b1072652c1 100644 (file)
@@ -122,8 +122,14 @@ enum {
        TP_NVRAM_POS_LEVEL_VOLUME       = 0,
 };
 
+/* Misc NVRAM-related */
+enum {
+       TP_NVRAM_LEVEL_VOLUME_MAX = 14,
+};
+
 /* ACPI HIDs */
 #define TPACPI_ACPI_HKEY_HID           "IBM0068"
+#define TPACPI_ACPI_EC_HID             "PNP0C09"
 
 /* Input IDs */
 #define TPACPI_HKEY_INPUT_PRODUCT      0x5054 /* "TP" */
@@ -299,8 +305,8 @@ static struct {
        u32 hotkey_tablet:1;
        u32 light:1;
        u32 light_status:1;
-       u32 bright_16levels:1;
        u32 bright_acpimode:1;
+       u32 bright_unkfw:1;
        u32 wan:1;
        u32 uwb:1;
        u32 fan_ctrl_status_undef:1;
@@ -363,6 +369,9 @@ struct tpacpi_led_classdev {
        unsigned int led;
 };
 
+/* brightness level capabilities */
+static unsigned int bright_maxlvl;     /* 0 = unknown */
+
 #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
 static int dbg_wlswemul;
 static int tpacpi_wlsw_emulstate;
@@ -480,6 +489,15 @@ static unsigned long __init tpacpi_check_quirks(
        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;
+}
 
 /****************************************************************************
  ****************************************************************************
@@ -494,21 +512,13 @@ static unsigned long __init tpacpi_check_quirks(
  */
 
 static acpi_handle root_handle;
+static acpi_handle ec_handle;
 
 #define TPACPI_HANDLE(object, parent, paths...)                        \
        static acpi_handle  object##_handle;                    \
-       static acpi_handle *object##_parent = &parent##_handle; \
-       static char        *object##_path;                      \
-       static char        *object##_paths[] = { paths }
-
-TPACPI_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0",  /* 240, 240x */
-          "\\_SB.PCI.ISA.EC",  /* 570 */
-          "\\_SB.PCI0.ISA0.EC0",       /* 600e/x, 770e, 770x */
-          "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */
-          "\\_SB.PCI0.AD4S.EC0",       /* i1400, R30 */
-          "\\_SB.PCI0.ICH3.EC0",       /* R31 */
-          "\\_SB.PCI0.LPC.EC", /* all others */
-          );
+       static const acpi_handle *object##_parent __initdata =  \
+                                               &parent##_handle; \
+       static char *object##_paths[] __initdata = { paths }
 
 TPACPI_HANDLE(ecrd, ec, "ECRD");       /* 570 */
 TPACPI_HANDLE(ecwr, ec, "ECWR");       /* 570 */
@@ -528,6 +538,7 @@ 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 */
 
@@ -594,9 +605,10 @@ static int acpi_evalf(acpi_handle handle,
 
        switch (res_type) {
        case 'd':               /* int */
-               if (res)
+               success = (status == AE_OK &&
+                          out_obj.type == ACPI_TYPE_INTEGER);
+               if (success && res)
                        *(int *)res = out_obj.integer.value;
-               success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
                break;
        case 'v':               /* void */
                success = status == AE_OK;
@@ -609,8 +621,8 @@ static int acpi_evalf(acpi_handle handle,
        }
 
        if (!success && !quiet)
-               printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
-                      method, fmt0, status);
+               printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %s\n",
+                      method, fmt0, acpi_format_exception(status));
 
        return success;
 }
@@ -661,11 +673,11 @@ static int issue_thinkpad_cmos_command(int cmos_cmd)
 
 #define TPACPI_ACPIHANDLE_INIT(object) \
        drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
-               object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
+               object##_paths, ARRAY_SIZE(object##_paths))
 
-static void drv_acpi_handle_init(char *name,
-                          acpi_handle *handle, acpi_handle parent,
-                          char **paths, int num_paths, char **path)
+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;
@@ -676,10 +688,9 @@ static void drv_acpi_handle_init(char *name,
        for (i = 0; i < num_paths; i++) {
                status = acpi_get_handle(parent, paths[i], handle);
                if (ACPI_SUCCESS(status)) {
-                       *path = paths[i];
                        dbg_printk(TPACPI_DBG_INIT,
                                   "Found ACPI handle %s for %s\n",
-                                  *path, name);
+                                  paths[i], name);
                        return;
                }
        }
@@ -689,6 +700,43 @@ static void drv_acpi_handle_init(char *name,
        *handle = NULL;
 }
 
+static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
+                       u32 level, void *context, void **return_value)
+{
+       *(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 || !hid || !handle);
+       vdbg_printk(TPACPI_DBG_INIT,
+                       "trying to locate ACPI handle for %s, using HID %s\n",
+                       name, hid);
+
+       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;
@@ -736,8 +784,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
                               "handling %s events\n", ibm->name);
                } else {
                        printk(TPACPI_ERR
-                              "acpi_install_notify_handler(%s) failed: %d\n",
-                              ibm->name, status);
+                              "acpi_install_notify_handler(%s) failed: %s\n",
+                              ibm->name, acpi_format_exception(status));
                }
                return -ENODEV;
        }
@@ -1035,80 +1083,6 @@ static void tpacpi_disable_brightness_delay(void)
                        "ACPI backlight control delay disabled\n");
 }
 
-static int __init tpacpi_query_bcl_levels(acpi_handle handle)
-{
-       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
-       union acpi_object *obj;
-       int rc;
-
-       if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
-               obj = (union acpi_object *)buffer.pointer;
-               if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
-                       printk(TPACPI_ERR "Unknown _BCL data, "
-                              "please report this to %s\n", TPACPI_MAIL);
-                       rc = 0;
-               } else {
-                       rc = obj->package.count;
-               }
-       } else {
-               return 0;
-       }
-
-       kfree(buffer.pointer);
-       return rc;
-}
-
-static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
-                                       u32 lvl, void *context, void **rv)
-{
-       char name[ACPI_PATH_SEGMENT_LENGTH];
-       struct acpi_buffer buffer = { sizeof(name), &name };
-
-       if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
-           !strncmp("_BCL", name, sizeof(name) - 1)) {
-               BUG_ON(!rv || !*rv);
-               **(int **)rv = tpacpi_query_bcl_levels(handle);
-               return AE_CTRL_TERMINATE;
-       } else {
-               return AE_OK;
-       }
-}
-
-/*
- * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
- */
-static int __init tpacpi_check_std_acpi_brightness_support(void)
-{
-       int status;
-       int bcl_levels = 0;
-       void *bcl_ptr = &bcl_levels;
-
-       if (!vid_handle) {
-               TPACPI_ACPIHANDLE_INIT(vid);
-       }
-       if (!vid_handle)
-               return 0;
-
-       /*
-        * Search for a _BCL method, and execute it.  This is safe on all
-        * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
-        * BIOS in ACPI backlight control mode.  We do NOT have to care
-        * about calling the _BCL method in an enabled video device, any
-        * will do for our purposes.
-        */
-
-       status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
-                                    tpacpi_acpi_walk_find_bcl, NULL, NULL,
-                                    &bcl_ptr);
-
-       if (ACPI_SUCCESS(status) && bcl_levels > 2) {
-               tp_features.bright_acpimode = 1;
-               return (bcl_levels - 2);
-       }
-
-       return 0;
-}
-
 static void printk_deprecated_attribute(const char * const what,
                                        const char * const details)
 {
@@ -1872,34 +1846,9 @@ static bool __init tpacpi_is_fw_known(void)
  ****************************************************************************/
 
 /*************************************************************************
- * thinkpad-acpi init subdriver
+ * thinkpad-acpi metadata subdriver
  */
 
-static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
-{
-       printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
-       printk(TPACPI_INFO "%s\n", TPACPI_URL);
-
-       printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
-               (thinkpad_id.bios_version_str) ?
-                       thinkpad_id.bios_version_str : "unknown",
-               (thinkpad_id.ec_version_str) ?
-                       thinkpad_id.ec_version_str : "unknown");
-
-       if (thinkpad_id.vendor && thinkpad_id.model_str)
-               printk(TPACPI_INFO "%s %s, model %s\n",
-                       (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
-                               "IBM" : ((thinkpad_id.vendor ==
-                                               PCI_VENDOR_ID_LENOVO) ?
-                                       "Lenovo" : "Unknown vendor"),
-                       thinkpad_id.model_str,
-                       (thinkpad_id.nummodel_str) ?
-                               thinkpad_id.nummodel_str : "unknown");
-
-       tpacpi_check_outdated_fw();
-       return 0;
-}
-
 static int thinkpad_acpi_driver_read(struct seq_file *m)
 {
        seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
@@ -2405,6 +2354,36 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
                        tpacpi_hotkey_send_key(__scancode); \
        } while (0)
 
+       void issue_volchange(const unsigned int oldvol,
+                            const unsigned int newvol)
+       {
+               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++;
+               }
+       }
+
+       void issue_brightnesschange(const unsigned int oldbrt,
+                                   const unsigned int newbrt)
+       {
+               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++;
+               }
+       }
+
        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);
@@ -2414,41 +2393,61 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
 
        TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
 
-       /* handle volume */
-       if (oldn->volume_toggle != newn->volume_toggle) {
-               if (oldn->mute != newn->mute) {
+       /*
+        * 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 minumum 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);
                        TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
                }
-               if (oldn->volume_level > newn->volume_level) {
-                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
-               } else if (oldn->volume_level < newn->volume_level) {
+       } else {
+               /* unmute */
+               if (oldn->mute) {
+                       /* recently unmuted, issue 'unmute' keypress */
                        TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
-               } else if (oldn->mute == newn->mute) {
-                       /* repeated key presses that didn't change state */
-                       if (newn->mute) {
-                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
-                       } else if (newn->volume_level != 0) {
-                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
-                       } else {
+               }
+               if (oldn->volume_level != newn->volume_level) {
+                       issue_volchange(oldn->volume_level, newn->volume_level);
+               } 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_toggle != newn->brightness_toggle) {
-               if (oldn->brightness_level < newn->brightness_level) {
-                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
-               } else if (oldn->brightness_level > newn->brightness_level) {
+       if (oldn->brightness_level != newn->brightness_level) {
+               issue_brightnesschange(oldn->brightness_level,
+                                      newn->brightness_level);
+       } 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 {
-                       /* repeated key presses that didn't change state */
-                       if (newn->brightness_level != 0) {
-                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
-                       } else {
-                               TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
-                       }
-               }
+               else if (newn->brightness_level >= bright_maxlvl
+                               && !tp_features.bright_unkfw)
+                       TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
        }
 
 #undef TPACPI_COMPARE_KEY
@@ -3353,7 +3352,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
                goto err_exit;
        }
 
-       if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) {
+       if (tpacpi_is_lenovo()) {
                dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
                           "using Lenovo default hot key map\n");
                memcpy(hotkey_keycode_map, &lenovo_keycode_map,
@@ -3391,11 +3390,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        }
 
        /* Do not issue duplicate brightness change events to
-        * userspace */
-       if (!tp_features.bright_acpimode)
-               /* update bright_acpimode... */
-               tpacpi_check_std_acpi_brightness_support();
-
+        * userspace. tpacpi_detect_brightness_capabilities() must have
+        * been called before this point  */
        if (tp_features.bright_acpimode && acpi_video_backlight_support()) {
                printk(TPACPI_INFO
                       "This ThinkPad has standard ACPI backlight "
@@ -4422,7 +4418,8 @@ static int __init video_init(struct ibm_init_struct *iibm)
        vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
 
        TPACPI_ACPIHANDLE_INIT(vid);
-       TPACPI_ACPIHANDLE_INIT(vid2);
+       if (tpacpi_is_ibm())
+               TPACPI_ACPIHANDLE_INIT(vid2);
 
        if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
                /* G41, assume IVGA doesn't change */
@@ -4431,10 +4428,12 @@ static int __init video_init(struct ibm_init_struct *iibm)
        if (!vid_handle)
                /* video switching not supported on R30, R31 */
                video_supported = TPACPI_VIDEO_NONE;
-       else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
+       else if (tpacpi_is_ibm() &&
+                acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
                /* 570 */
                video_supported = TPACPI_VIDEO_570;
-       else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
+       else if (tpacpi_is_ibm() &&
+                acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
                /* 600e/x, 770e, 770x */
                video_supported = TPACPI_VIDEO_770;
        else
@@ -4811,8 +4810,10 @@ static int __init light_init(struct ibm_init_struct *iibm)
 
        vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
 
-       TPACPI_ACPIHANDLE_INIT(ledb);
-       TPACPI_ACPIHANDLE_INIT(lght);
+       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);
 
@@ -5007,11 +5008,7 @@ enum {   /* For TPACPI_LED_OLD */
 
 static enum led_access_mode led_supported;
 
-TPACPI_HANDLE(led, ec, "SLED", /* 570 */
-          "SYSL",              /* 600e/x, 770e, 770x, A21e, A2xm/p, */
-                               /* T20-22, X20-21 */
-          "LED",               /* all others */
-          );                   /* R30, R31 */
+static acpi_handle led_handle;
 
 #define TPACPI_LED_NUMLEDS 16
 static struct tpacpi_led_classdev *tpacpi_leds;
@@ -5271,6 +5268,32 @@ static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
 #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;
@@ -5279,20 +5302,7 @@ static int __init led_init(struct ibm_init_struct *iibm)
 
        vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
 
-       TPACPI_ACPIHANDLE_INIT(led);
-
-       if (!led_handle)
-               /* led not supported on R30, R31 */
-               led_supported = TPACPI_LED_NONE;
-       else if (strlencmp(led_path, "SLED") == 0)
-               /* 570 */
-               led_supported = TPACPI_LED_570;
-       else if (strlencmp(led_path, "SYSL") == 0)
-               /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
-               led_supported = TPACPI_LED_OLD;
-       else
-               /* all others */
-               led_supported = TPACPI_LED_NEW;
+       led_supported = led_init_detect_mode();
 
        vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
                str_supported(led_supported), led_supported);
@@ -5741,11 +5751,12 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
                            TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
                }
        } else if (acpi_tmp7) {
-               if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
+               if (tpacpi_is_ibm() &&
+                   acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
                        /* 600e/x, 770e, 770x */
                        thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
                } else {
-                       /* Standard ACPI TMPx access, max 8 sensors */
+                       /* IBM/LENOVO DSDT EC.TMPx access, max 8 sensors */
                        thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
                }
        } else {
@@ -5954,7 +5965,7 @@ static unsigned int tpacpi_brightness_nvram_get(void)
        lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
                  & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
                  >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
-       lnvram &= (tp_features.bright_16levels) ? 0x0f : 0x07;
+       lnvram &= bright_maxlvl;
 
        return lnvram;
 }
@@ -6063,8 +6074,7 @@ static int brightness_set(unsigned int value)
 {
        int res;
 
-       if (value > ((tp_features.bright_16levels)? 15 : 7) ||
-           value < 0)
+       if (value > bright_maxlvl || value < 0)
                return -EINVAL;
 
        vdbg_printk(TPACPI_DBG_BRGHT,
@@ -6139,6 +6149,80 @@ static struct backlight_ops ibm_backlight_data = {
 
 /* --------------------------------------------------------------------- */
 
+static int __init tpacpi_query_bcl_levels(acpi_handle handle)
+{
+       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+       union acpi_object *obj;
+       int rc;
+
+       if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
+               obj = (union acpi_object *)buffer.pointer;
+               if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
+                       printk(TPACPI_ERR "Unknown _BCL data, "
+                              "please report this to %s\n", TPACPI_MAIL);
+                       rc = 0;
+               } else {
+                       rc = obj->package.count;
+               }
+       } else {
+               return 0;
+       }
+
+       kfree(buffer.pointer);
+       return rc;
+}
+
+static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
+                                       u32 lvl, void *context, void **rv)
+{
+       char name[ACPI_PATH_SEGMENT_LENGTH];
+       struct acpi_buffer buffer = { sizeof(name), &name };
+
+       if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
+           !strncmp("_BCL", name, sizeof(name) - 1)) {
+               BUG_ON(!rv || !*rv);
+               **(int **)rv = tpacpi_query_bcl_levels(handle);
+               return AE_CTRL_TERMINATE;
+       } else {
+               return AE_OK;
+       }
+}
+
+/*
+ * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
+ */
+static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)
+{
+       int status;
+       int bcl_levels = 0;
+       void *bcl_ptr = &bcl_levels;
+
+       if (!vid_handle)
+               TPACPI_ACPIHANDLE_INIT(vid);
+
+       if (!vid_handle)
+               return 0;
+
+       /*
+        * Search for a _BCL method, and execute it.  This is safe on all
+        * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
+        * BIOS in ACPI backlight control mode.  We do NOT have to care
+        * about calling the _BCL method in an enabled video device, any
+        * will do for our purposes.
+        */
+
+       status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
+                                    tpacpi_acpi_walk_find_bcl, NULL, NULL,
+                                    &bcl_ptr);
+
+       if (ACPI_SUCCESS(status) && bcl_levels > 2) {
+               tp_features.bright_acpimode = 1;
+               return bcl_levels - 2;
+       }
+
+       return 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
@@ -6169,6 +6253,47 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
        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;
+               printk(TPACPI_INFO
+                      "detected a 16-level brightness capable ThinkPad\n");
+               break;
+       case 8:
+       case 0:
+               bright_maxlvl = 7;
+               printk(TPACPI_INFO
+                      "detected a 8-level brightness capable ThinkPad\n");
+               break;
+       default:
+               printk(TPACPI_ERR
+                      "Unsupported brightness interface, "
+                      "please contact %s\n", TPACPI_MAIL);
+               tp_features.bright_unkfw = 1;
+               bright_maxlvl = b - 1;
+       }
+}
+
 static int __init brightness_init(struct ibm_init_struct *iibm)
 {
        struct backlight_properties props;
@@ -6182,14 +6307,13 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        quirks = tpacpi_check_quirks(brightness_quirk_table,
                                ARRAY_SIZE(brightness_quirk_table));
 
-       /*
-        * We always attempt to detect acpi support, so as to switch
-        * Lenovo Vista BIOS to ACPI brightness mode even if we are not
-        * going to publish a backlight interface
-        */
-       b = tpacpi_check_std_acpi_brightness_support();
-       if (b > 0) {
+       /* 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 (tp_features.bright_acpimode) {
                if (acpi_video_backlight_support()) {
                        if (brightness_enable > 1) {
                                printk(TPACPI_NOTICE
@@ -6218,15 +6342,6 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
                return 1;
        }
 
-       if (b > 16) {
-               printk(TPACPI_ERR
-                      "Unsupported brightness interface, "
-                      "please contact %s\n", TPACPI_MAIL);
-               return 1;
-       }
-       if (b == 16)
-               tp_features.bright_16levels = 1;
-
        /*
         * Check for module parameter bogosity, note that we
         * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
@@ -6249,7 +6364,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        }
 
        /* Safety */
-       if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM &&
+       if (!tpacpi_is_ibm() &&
            (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
             brightness_mode == TPACPI_BRGHT_MODE_EC))
                return -EINVAL;
@@ -6257,12 +6372,9 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        if (tpacpi_brightness_get_raw(&b) < 0)
                return 1;
 
-       if (tp_features.bright_16levels)
-               printk(TPACPI_INFO
-                      "detected a 16-level brightness capable ThinkPad\n");
-
        memset(&props, 0, sizeof(struct backlight_properties));
-       props.max_brightness = (tp_features.bright_16levels) ? 15 : 7;
+       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,
@@ -6285,7 +6397,10 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
                        "or not on your ThinkPad\n", TPACPI_MAIL);
        }
 
-       ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
+       /* 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,
@@ -6328,9 +6443,8 @@ static int brightness_read(struct seq_file *m)
        } 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",
-                              (tp_features.bright_16levels) ? 15 : 7);
+               seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
+                              bright_maxlvl);
        }
 
        return 0;
@@ -6341,7 +6455,6 @@ static int brightness_write(char *buf)
        int level;
        int rc;
        char *cmd;
-       int max_level = (tp_features.bright_16levels) ? 15 : 7;
 
        level = brightness_get(NULL);
        if (level < 0)
@@ -6349,13 +6462,13 @@ static int brightness_write(char *buf)
 
        while ((cmd = next_cmd(&buf))) {
                if (strlencmp(cmd, "up") == 0) {
-                       if (level < max_level)
+                       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 <= max_level) {
+                          level >= 0 && level <= bright_maxlvl) {
                        /* new level set */
                } else
                        return -EINVAL;
@@ -6669,6 +6782,8 @@ static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
 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]);
 }
 
@@ -6692,6 +6807,9 @@ static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
 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]);
 }
 
@@ -7968,9 +8086,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)
        tp_features.second_fan = 0;
        fan_control_desired_level = 7;
 
-       TPACPI_ACPIHANDLE_INIT(fans);
-       TPACPI_ACPIHANDLE_INIT(gfan);
-       TPACPI_ACPIHANDLE_INIT(sfan);
+       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));
@@ -8662,6 +8782,10 @@ static int __init probe_for_thinkpad(void)
        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.
@@ -8670,8 +8794,8 @@ static int __init probe_for_thinkpad(void)
                      (thinkpad_id.ec_model != 0) ||
                      tpacpi_is_fw_known();
 
-       /* ec is required because many other handles are relative to it */
-       TPACPI_ACPIHANDLE_INIT(ec);
+       /* The EC handler is required */
+       tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle);
        if (!ec_handle) {
                if (is_thinkpad)
                        printk(TPACPI_ERR
@@ -8685,12 +8809,34 @@ static int __init probe_for_thinkpad(void)
        return 0;
 }
 
+static void __init thinkpad_acpi_init_banner(void)
+{
+       printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
+       printk(TPACPI_INFO "%s\n", TPACPI_URL);
+
+       printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
+               (thinkpad_id.bios_version_str) ?
+                       thinkpad_id.bios_version_str : "unknown",
+               (thinkpad_id.ec_version_str) ?
+                       thinkpad_id.ec_version_str : "unknown");
+
+       BUG_ON(!thinkpad_id.vendor);
+
+       if (thinkpad_id.model_str)
+               printk(TPACPI_INFO "%s %s, model %s\n",
+                       (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
+                               "IBM" : ((thinkpad_id.vendor ==
+                                               PCI_VENDOR_ID_LENOVO) ?
+                                       "Lenovo" : "Unknown vendor"),
+                       thinkpad_id.model_str,
+                       (thinkpad_id.nummodel_str) ?
+                               thinkpad_id.nummodel_str : "unknown");
+}
 
 /* Module init, exit, parameters */
 
 static struct ibm_init_struct ibms_init[] __initdata = {
        {
-               .init = thinkpad_acpi_driver_init,
                .data = &thinkpad_acpi_driver_data,
        },
        {
@@ -8960,6 +9106,9 @@ static int __init thinkpad_acpi_module_init(void)
 
        /* Driver initialization */
 
+       thinkpad_acpi_init_banner();
+       tpacpi_check_outdated_fw();
+
        TPACPI_ACPIHANDLE_INIT(ecrd);
        TPACPI_ACPIHANDLE_INIT(ecwr);
 
@@ -9059,13 +9208,16 @@ static int __init thinkpad_acpi_module_init(void)
                tpacpi_inputdev->name = "ThinkPad Extra Buttons";
                tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
                tpacpi_inputdev->id.bustype = BUS_HOST;
-               tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ?
-                                               thinkpad_id.vendor :
-                                               PCI_VENDOR_ID_IBM;
+               tpacpi_inputdev->id.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)
index 39ec5b6c2e3a804518bdad78257bb766812e3274..e4eaa14ed987973e980bab9a93e5d96dea499116 100644 (file)
@@ -81,6 +81,16 @@ static struct wmi_block wmi_blocks;
 #define ACPI_WMI_STRING      0x4       /* GUID takes & returns a string */
 #define ACPI_WMI_EVENT       0x8       /* GUID is an event */
 
+static int debug_event;
+module_param(debug_event, bool, 0444);
+MODULE_PARM_DESC(debug_event,
+                "Log WMI Events [0/1]");
+
+static int 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, int type);
 static int acpi_wmi_add(struct acpi_device *device);
 static void acpi_wmi_notify(struct acpi_device *device, u32 event);
@@ -477,6 +487,64 @@ const struct acpi_buffer *in)
 }
 EXPORT_SYMBOL_GPL(wmi_set_block);
 
+static void wmi_dump_wdg(struct guid_block *g)
+{
+       char guid_string[37];
+
+       wmi_gtoa(g->guid, guid_string);
+       printk(KERN_INFO PREFIX "%s:\n", guid_string);
+       printk(KERN_INFO PREFIX "\tobject_id: %c%c\n",
+              g->object_id[0], g->object_id[1]);
+       printk(KERN_INFO PREFIX "\tnotify_id: %02X\n", g->notify_id);
+       printk(KERN_INFO PREFIX "\treserved: %02X\n", g->reserved);
+       printk(KERN_INFO PREFIX "\tinstance_count: %d\n", g->instance_count);
+       printk(KERN_INFO PREFIX "\tflags: %#x", g->flags);
+       if (g->flags) {
+               printk(" ");
+               if (g->flags & ACPI_WMI_EXPENSIVE)
+                       printk("ACPI_WMI_EXPENSIVE ");
+               if (g->flags & ACPI_WMI_METHOD)
+                       printk("ACPI_WMI_METHOD ");
+               if (g->flags & ACPI_WMI_STRING)
+                       printk("ACPI_WMI_STRING ");
+               if (g->flags & ACPI_WMI_EVENT)
+                       printk("ACPI_WMI_EVENT ");
+       }
+       printk("\n");
+
+}
+
+static void wmi_notify_debug(u32 value, void *context)
+{
+       struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+       union acpi_object *obj;
+
+       wmi_get_event_data(value, &response);
+
+       obj = (union acpi_object *)response.pointer;
+
+       if (!obj)
+               return;
+
+       printk(KERN_INFO PREFIX "DEBUG Event ");
+       switch(obj->type) {
+       case ACPI_TYPE_BUFFER:
+               printk("BUFFER_TYPE - length %d\n", obj->buffer.length);
+               break;
+       case ACPI_TYPE_STRING:
+               printk("STRING_TYPE - %s\n", obj->string.pointer);
+               break;
+       case ACPI_TYPE_INTEGER:
+               printk("INTEGER_TYPE - %llu\n", obj->integer.value);
+               break;
+       case ACPI_TYPE_PACKAGE:
+               printk("PACKAGE_TYPE - %d elements\n", obj->package.count);
+               break;
+       default:
+               printk("object type 0x%X\n", obj->type);
+       }
+}
+
 /**
  * wmi_install_notify_handler - Register handler for WMI events
  * @handler: Function to handle notifications
@@ -496,7 +564,7 @@ wmi_notify_handler handler, void *data)
        if (!find_guid(guid, &block))
                return AE_NOT_EXIST;
 
-       if (block->handler)
+       if (block->handler && block->handler != wmi_notify_debug)
                return AE_ALREADY_ACQUIRED;
 
        block->handler = handler;
@@ -516,7 +584,7 @@ EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
 acpi_status wmi_remove_notify_handler(const char *guid)
 {
        struct wmi_block *block;
-       acpi_status status;
+       acpi_status status = AE_OK;
 
        if (!guid)
                return AE_BAD_PARAMETER;
@@ -524,14 +592,16 @@ acpi_status wmi_remove_notify_handler(const char *guid)
        if (!find_guid(guid, &block))
                return AE_NOT_EXIST;
 
-       if (!block->handler)
+       if (!block->handler || block->handler == wmi_notify_debug)
                return AE_NULL_ENTRY;
 
-       status = wmi_method_enable(block, 0);
-
-       block->handler = NULL;
-       block->handler_data = NULL;
-
+       if (debug_event) {
+               block->handler = wmi_notify_debug;
+       } else {
+               status = wmi_method_enable(block, 0);
+               block->handler = NULL;
+               block->handler_data = NULL;
+       }
        return status;
 }
 EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
@@ -756,12 +826,10 @@ static __init acpi_status parse_wdg(acpi_handle handle)
 
        total = obj->buffer.length / sizeof(struct guid_block);
 
-       gblock = kzalloc(obj->buffer.length, GFP_KERNEL);
+       gblock = kmemdup(obj->buffer.pointer, obj->buffer.length, GFP_KERNEL);
        if (!gblock)
                return AE_NO_MEMORY;
 
-       memcpy(gblock, obj->buffer.pointer, obj->buffer.length);
-
        for (i = 0; i < total; i++) {
                /*
                  Some WMI devices, like those for nVidia hooks, have a
@@ -776,12 +844,19 @@ static __init acpi_status parse_wdg(acpi_handle handle)
                                guid_string);
                        continue;
                }
+               if (debug_dump_wdg)
+                       wmi_dump_wdg(&gblock[i]);
+
                wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
                if (!wblock)
                        return AE_NO_MEMORY;
 
                wblock->gblock = gblock[i];
                wblock->handle = handle;
+               if (debug_event) {
+                       wblock->handler = wmi_notify_debug;
+                       status = wmi_method_enable(wblock, 1);
+               }
                list_add_tail(&wblock->list, &wmi_blocks.list);
        }
 
@@ -840,6 +915,7 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event)
        struct guid_block *block;
        struct wmi_block *wblock;
        struct list_head *p;
+       char guid_string[37];
 
        list_for_each(p, &wmi_blocks.list) {
                wblock = list_entry(p, struct wmi_block, list);
@@ -849,6 +925,11 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event)
                        (block->notify_id == event)) {
                        if (wblock->handler)
                                wblock->handler(event, wblock->handler_data);
+                       if (debug_event) {
+                               wmi_gtoa(wblock->gblock.guid, guid_string);
+                               printk(KERN_INFO PREFIX "DEBUG Event GUID:"
+                                      " %s\n", guid_string);
+                       }
 
                        acpi_bus_generate_netlink_event(
                                device->pnp.device_class, dev_name(&device->dev),