ACPI: thinkpad-acpi: spring cleanup part 3
authorHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Tue, 8 Jan 2008 15:02:45 +0000 (13:02 -0200)
committerLen Brown <len.brown@intel.com>
Sat, 2 Feb 2008 03:26:07 +0000 (22:26 -0500)
Reorder code in the file to get rid of more of the forward declarations,
and to make things cleaner and more organized.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
drivers/misc/thinkpad_acpi.c

index e435b55..b6293a4 100644 (file)
 
 #include <linux/pci_ids.h>
 
-/****************************************************************************
- * Main driver
- */
-
-#define IBM_NAME "thinkpad"
-#define IBM_DESC "ThinkPad ACPI Extras"
-#define IBM_FILE IBM_NAME "_acpi"
-#define IBM_URL "http://ibm-acpi.sf.net/"
-#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net"
-
-#define IBM_PROC_DIR "ibm"
-#define IBM_ACPI_EVENT_PREFIX "ibm"
-#define IBM_DRVR_NAME IBM_FILE
-#define IBM_HWMON_DRVR_NAME IBM_NAME "_hwmon"
-
-#define IBM_LOG IBM_FILE ": "
-#define IBM_ERR           KERN_ERR    IBM_LOG
-#define IBM_NOTICE KERN_NOTICE IBM_LOG
-#define IBM_INFO   KERN_INFO   IBM_LOG
-#define IBM_DEBUG  KERN_DEBUG  IBM_LOG
-
-#define IBM_MAX_ACPI_ARGS 3
 
 /* ThinkPad CMOS commands */
 #define TP_CMOS_VOLUME_DOWN    0
@@ -169,11 +147,70 @@ enum {
        TP_NVRAM_POS_LEVEL_VOLUME       = 0,
 };
 
-#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
-#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
-#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
+/* ACPI HIDs */
+#define IBM_HKEY_HID    "IBM0068"
+
+/* Input IDs */
+#define TPACPI_HKEY_INPUT_PRODUCT      0x5054 /* "TP" */
+#define TPACPI_HKEY_INPUT_VERSION      0x4101
+
+
+/****************************************************************************
+ * Main driver
+ */
+
+/* Module */
+#define IBM_NAME "thinkpad"
+#define IBM_DESC "ThinkPad ACPI Extras"
+#define IBM_FILE IBM_NAME "_acpi"
+#define IBM_URL "http://ibm-acpi.sf.net/"
+#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net"
+
+#define IBM_PROC_DIR "ibm"
+#define IBM_ACPI_EVENT_PREFIX "ibm"
+#define IBM_DRVR_NAME IBM_FILE
+#define IBM_HWMON_DRVR_NAME IBM_NAME "_hwmon"
+
+#define IBM_MAX_ACPI_ARGS 3
+
+MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
+MODULE_DESCRIPTION(IBM_DESC);
+MODULE_VERSION(IBM_VERSION);
+MODULE_LICENSE("GPL");
+
+/* Please remove this in year 2009 */
+MODULE_ALIAS("ibm_acpi");
+
+/*
+ * DMI matching for module autoloading
+ *
+ * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+ * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
+ *
+ * Only models listed in thinkwiki will be supported, so add yours
+ * if it is not there yet.
+ */
+#define IBM_BIOS_MODULE_ALIAS(__type) \
+       MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
+
+/* Non-ancient thinkpads */
+MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
+MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
+
+/* Ancient thinkpad BIOSes have to be identified by
+ * BIOS type or model number, and there are far less
+ * BIOS types than model numbers... */
+IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
+IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
+IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
 
 /* Debugging */
+#define IBM_LOG IBM_FILE ": "
+#define IBM_ERR           KERN_ERR    IBM_LOG
+#define IBM_NOTICE KERN_NOTICE IBM_LOG
+#define IBM_INFO   KERN_INFO   IBM_LOG
+#define IBM_DEBUG  KERN_DEBUG  IBM_LOG
+
 #define TPACPI_DBG_ALL         0xffff
 #define TPACPI_DBG_ALL         0xffff
 #define TPACPI_DBG_INIT                0x0001
@@ -189,33 +226,13 @@ static const char *str_supported(int is_supported);
 #define vdbg_printk(a_dbg_level, format, arg...)
 #endif
 
-/* Input IDs */
-#define TPACPI_HKEY_INPUT_VENDOR       PCI_VENDOR_ID_IBM
-#define TPACPI_HKEY_INPUT_PRODUCT      0x5054 /* "TP" */
-#define TPACPI_HKEY_INPUT_VERSION      0x4101
-
-/* ACPI HIDs */
-#define IBM_HKEY_HID    "IBM0068"
-
-/* sysfs support */
-struct attribute_set {
-       unsigned int members, max_members;
-       struct attribute_group group;
-};
-
-/* Helpers */
-static int parse_strtoul(const char *buf, unsigned long max,
-                       unsigned long *value);
-
-/* Module */
-static int experimental;
-static u32 dbg_level;
-static int force_load;
-static unsigned int hotkey_report_mode;
+#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
+#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
+#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
 
 
 /****************************************************************************
- * Subdrivers
+ * Driver-wide structs and misc. variables
  */
 
 struct ibm_struct;
@@ -297,39 +314,6 @@ struct thinkpad_id_data {
 };
 static struct thinkpad_id_data thinkpad_id;
 
-static LIST_HEAD(tpacpi_all_drivers);
-
-MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
-MODULE_DESCRIPTION(IBM_DESC);
-MODULE_VERSION(IBM_VERSION);
-MODULE_LICENSE("GPL");
-
-/* Please remove this in year 2009 */
-MODULE_ALIAS("ibm_acpi");
-
-/*
- * DMI matching for module autoloading
- *
- * See http://thinkwiki.org/wiki/List_of_DMI_IDs
- * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
- *
- * Only models listed in thinkwiki will be supported, so add yours
- * if it is not there yet.
- */
-#define IBM_BIOS_MODULE_ALIAS(__type) \
-       MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
-
-/* Non-ancient thinkpads */
-MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
-MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
-
-/* Ancient thinkpad BIOSes have to be identified by
- * BIOS type or model number, and there are far less
- * BIOS types than model numbers... */
-IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
-IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
-IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
-
 #define __unused __attribute__ ((unused))
 
 static enum {
@@ -338,6 +322,9 @@ static enum {
        TPACPI_LIFE_EXITING,
 } tpacpi_lifecycle;
 
+static int experimental;
+static u32 dbg_level;
+
 /****************************************************************************
  ****************************************************************************
  *
@@ -370,11 +357,6 @@ IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */
 IBM_HANDLE(ecrd, ec, "ECRD");  /* 570 */
 IBM_HANDLE(ecwr, ec, "ECWR");  /* 570 */
 
-
-/*************************************************************************
- * Misc ACPI handles
- */
-
 IBM_HANDLE(cmos, root, "\\UCMS",       /* R50, R50e, R50p, R51, T4x, X31, X40 */
           "\\CMOS",            /* A3x, G4x, R32, T23, T30, X22-24, X30 */
           "\\CMS",             /* R40, R40e */
@@ -749,7 +731,7 @@ static struct platform_device *tpacpi_sensors_pdev;
 static struct device *tpacpi_hwmon;
 static struct input_dev *tpacpi_inputdev;
 static struct mutex tpacpi_inputdev_send_mutex;
-
+static LIST_HEAD(tpacpi_all_drivers);
 
 static int tpacpi_resume_handler(struct platform_device *pdev)
 {
@@ -781,86 +763,14 @@ static struct platform_driver tpacpi_hwmon_pdriver = {
 };
 
 /*************************************************************************
- * thinkpad-acpi driver attributes
+ * sysfs support helpers
  */
 
-/* interface_version --------------------------------------------------- */
-static ssize_t tpacpi_driver_interface_version_show(
-                               struct device_driver *drv,
-                               char *buf)
-{
-       return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
-}
-
-static DRIVER_ATTR(interface_version, S_IRUGO,
-               tpacpi_driver_interface_version_show, NULL);
-
-/* debug_level --------------------------------------------------------- */
-static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
-                                               char *buf)
-{
-       return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
-}
-
-static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
-                                               const char *buf, size_t count)
-{
-       unsigned long t;
-
-       if (parse_strtoul(buf, 0xffff, &t))
-               return -EINVAL;
-
-       dbg_level = t;
-
-       return count;
-}
-
-static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
-               tpacpi_driver_debug_show, tpacpi_driver_debug_store);
-
-/* version ------------------------------------------------------------- */
-static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
-                                               char *buf)
-{
-       return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION);
-}
-
-static DRIVER_ATTR(version, S_IRUGO,
-               tpacpi_driver_version_show, NULL);
-
-/* --------------------------------------------------------------------- */
-
-static struct driver_attribute* tpacpi_driver_attributes[] = {
-       &driver_attr_debug_level, &driver_attr_version,
-       &driver_attr_interface_version,
+struct attribute_set {
+       unsigned int members, max_members;
+       struct attribute_group group;
 };
 
-static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
-{
-       int i, res;
-
-       i = 0;
-       res = 0;
-       while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
-               res = driver_create_file(drv, tpacpi_driver_attributes[i]);
-               i++;
-       }
-
-       return res;
-}
-
-static void tpacpi_remove_driver_attributes(struct device_driver *drv)
-{
-       int i;
-
-       for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
-               driver_remove_file(drv, tpacpi_driver_attributes[i]);
-}
-
-/*************************************************************************
- * sysfs support helpers
- */
-
 struct attribute_set_obj {
        struct attribute_set s;
        struct attribute *a;
@@ -945,28 +855,105 @@ static int parse_strtoul(const char *buf,
        return 0;
 }
 
-/****************************************************************************
- ****************************************************************************
- *
- * Subdrivers
- *
- ****************************************************************************
- ****************************************************************************/
-
 /*************************************************************************
- * thinkpad-acpi init subdriver
+ * thinkpad-acpi driver attributes
  */
 
-static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
+/* interface_version --------------------------------------------------- */
+static ssize_t tpacpi_driver_interface_version_show(
+                               struct device_driver *drv,
+                               char *buf)
 {
-       printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
-       printk(IBM_INFO "%s\n", IBM_URL);
-
-       printk(IBM_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");
+       return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
+}
+
+static DRIVER_ATTR(interface_version, S_IRUGO,
+               tpacpi_driver_interface_version_show, NULL);
+
+/* debug_level --------------------------------------------------------- */
+static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
+                                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
+}
+
+static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
+                                               const char *buf, size_t count)
+{
+       unsigned long t;
+
+       if (parse_strtoul(buf, 0xffff, &t))
+               return -EINVAL;
+
+       dbg_level = t;
+
+       return count;
+}
+
+static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
+               tpacpi_driver_debug_show, tpacpi_driver_debug_store);
+
+/* version ------------------------------------------------------------- */
+static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
+                                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION);
+}
+
+static DRIVER_ATTR(version, S_IRUGO,
+               tpacpi_driver_version_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+static struct driver_attribute* tpacpi_driver_attributes[] = {
+       &driver_attr_debug_level, &driver_attr_version,
+       &driver_attr_interface_version,
+};
+
+static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
+{
+       int i, res;
+
+       i = 0;
+       res = 0;
+       while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
+               res = driver_create_file(drv, tpacpi_driver_attributes[i]);
+               i++;
+       }
+
+       return res;
+}
+
+static void tpacpi_remove_driver_attributes(struct device_driver *drv)
+{
+       int i;
+
+       for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
+               driver_remove_file(drv, tpacpi_driver_attributes[i]);
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Subdrivers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * thinkpad-acpi init subdriver
+ */
+
+static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
+{
+       printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
+       printk(IBM_INFO "%s\n", IBM_URL);
+
+       printk(IBM_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(IBM_INFO "%s %s\n",
@@ -1094,6 +1081,8 @@ static u32 hotkey_all_mask;
 static u32 hotkey_reserved_mask;
 static u32 hotkey_mask;
 
+static unsigned int hotkey_report_mode;
+
 static u16 *hotkey_keycode_map;
 
 static struct attribute_set *hotkey_dev_attributes;
@@ -3637,8 +3626,87 @@ struct ibm_thermal_sensors_struct {
 
 static enum thermal_access_mode thermal_read_mode;
 
-static int thermal_get_sensor(int idx, s32 *value);
-static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s);
+/* idx is zero-based */
+static int thermal_get_sensor(int idx, s32 *value)
+{
+       int t;
+       s8 tmp;
+       char tmpi[5];
+
+       t = TP_EC_THERMAL_TMP0;
+
+       switch (thermal_read_mode) {
+#if TPACPI_MAX_THERMAL_SENSORS >= 16
+       case TPACPI_THERMAL_TPEC_16:
+               if (idx >= 8 && idx <= 15) {
+                       t = TP_EC_THERMAL_TMP8;
+                       idx -= 8;
+               }
+               /* fallthrough */
+#endif
+       case TPACPI_THERMAL_TPEC_8:
+               if (idx <= 7) {
+                       if (!acpi_ec_read(t + idx, &tmp))
+                               return -EIO;
+                       *value = tmp * 1000;
+                       return 0;
+               }
+               break;
+
+       case TPACPI_THERMAL_ACPI_UPDT:
+               if (idx <= 7) {
+                       snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+                       if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+                               return -EIO;
+                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+                               return -EIO;
+                       *value = (t - 2732) * 100;
+                       return 0;
+               }
+               break;
+
+       case TPACPI_THERMAL_ACPI_TMP07:
+               if (idx <= 7) {
+                       snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+                               return -EIO;
+                       if (t > 127 || t < -127)
+                               t = TP_EC_THERMAL_TMP_NA;
+                       *value = t * 1000;
+                       return 0;
+               }
+               break;
+
+       case TPACPI_THERMAL_NONE:
+       default:
+               return -ENOSYS;
+       }
+
+       return -EINVAL;
+}
+
+static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
+{
+       int res, i;
+       int n;
+
+       n = 8;
+       i = 0;
+
+       if (!s)
+               return -EINVAL;
+
+       if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
+               n = 16;
+
+       for(i = 0 ; i < n; i++) {
+               res = thermal_get_sensor(i, &s->temp[i]);
+               if (res)
+                       return res;
+       }
+
+       return n;
+}
 
 /* sysfs temp##_input -------------------------------------------------- */
 
@@ -3830,88 +3898,6 @@ static void thermal_exit(void)
        }
 }
 
-/* idx is zero-based */
-static int thermal_get_sensor(int idx, s32 *value)
-{
-       int t;
-       s8 tmp;
-       char tmpi[5];
-
-       t = TP_EC_THERMAL_TMP0;
-
-       switch (thermal_read_mode) {
-#if TPACPI_MAX_THERMAL_SENSORS >= 16
-       case TPACPI_THERMAL_TPEC_16:
-               if (idx >= 8 && idx <= 15) {
-                       t = TP_EC_THERMAL_TMP8;
-                       idx -= 8;
-               }
-               /* fallthrough */
-#endif
-       case TPACPI_THERMAL_TPEC_8:
-               if (idx <= 7) {
-                       if (!acpi_ec_read(t + idx, &tmp))
-                               return -EIO;
-                       *value = tmp * 1000;
-                       return 0;
-               }
-               break;
-
-       case TPACPI_THERMAL_ACPI_UPDT:
-               if (idx <= 7) {
-                       snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
-                       if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
-                               return -EIO;
-                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
-                               return -EIO;
-                       *value = (t - 2732) * 100;
-                       return 0;
-               }
-               break;
-
-       case TPACPI_THERMAL_ACPI_TMP07:
-               if (idx <= 7) {
-                       snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
-                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
-                               return -EIO;
-                       if (t > 127 || t < -127)
-                               t = TP_EC_THERMAL_TMP_NA;
-                       *value = t * 1000;
-                       return 0;
-               }
-               break;
-
-       case TPACPI_THERMAL_NONE:
-       default:
-               return -ENOSYS;
-       }
-
-       return -EINVAL;
-}
-
-static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
-{
-       int res, i;
-       int n;
-
-       n = 8;
-       i = 0;
-
-       if (!s)
-               return -EINVAL;
-
-       if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
-               n = 16;
-
-       for(i = 0 ; i < n; i++) {
-               res = thermal_get_sensor(i, &s->temp[i]);
-               if (res)
-                       return res;
-       }
-
-       return n;
-}
-
 static int thermal_read(char *p)
 {
        int len = 0;
@@ -4021,16 +4007,103 @@ static int brightness_offset = 0x31;
 static int brightness_mode;
 static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
 
-static int brightness_get(struct backlight_device *bd);
-static int brightness_set(int value);
-static int brightness_update_status(struct backlight_device *bd);
+static struct mutex brightness_mutex;
 
-static struct backlight_ops ibm_backlight_data = {
-        .get_brightness = brightness_get,
+/*
+ * ThinkPads can read brightness from two places: EC 0x31, or
+ * CMOS NVRAM byte 0x5E, bits 0-3.
+ */
+static int brightness_get(struct backlight_device *bd)
+{
+       u8 lec = 0, lcmos = 0, level = 0;
+
+       if (brightness_mode & 1) {
+               if (!acpi_ec_read(brightness_offset, &lec))
+                       return -EIO;
+               lec &= (tp_features.bright_16levels)? 0x0f : 0x07;
+               level = lec;
+       };
+       if (brightness_mode & 2) {
+               lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
+                        & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+                       >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+               lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
+               level = lcmos;
+       }
+
+       if (brightness_mode == 3 && lec != lcmos) {
+               printk(IBM_ERR
+                       "CMOS NVRAM (%u) and EC (%u) do not agree "
+                       "on display brightness level\n",
+                       (unsigned int) lcmos,
+                       (unsigned int) lec);
+               return -EIO;
+       }
+
+       return level;
+}
+
+/* May return EINTR which can always be mapped to ERESTARTSYS */
+static int brightness_set(int value)
+{
+       int cmos_cmd, inc, i, res;
+       int current_value;
+
+       if (value > ((tp_features.bright_16levels)? 15 : 7))
+               return -EINVAL;
+
+       res = mutex_lock_interruptible(&brightness_mutex);
+       if (res < 0)
+               return res;
+
+       current_value = brightness_get(NULL);
+       if (current_value < 0) {
+               res = current_value;
+               goto errout;
+       }
+
+       cmos_cmd = value > current_value ?
+                       TP_CMOS_BRIGHTNESS_UP :
+                       TP_CMOS_BRIGHTNESS_DOWN;
+       inc = (value > current_value)? 1 : -1;
+
+       res = 0;
+       for (i = current_value; i != value; i += inc) {
+               if ((brightness_mode & 2) &&
+                   issue_thinkpad_cmos_command(cmos_cmd)) {
+                       res = -EIO;
+                       goto errout;
+               }
+               if ((brightness_mode & 1) &&
+                   !acpi_ec_write(brightness_offset, i + inc)) {
+                       res = -EIO;
+                       goto errout;;
+               }
+       }
+
+errout:
+       mutex_unlock(&brightness_mutex);
+       return res;
+}
+
+/* sysfs backlight class ----------------------------------------------- */
+
+static int brightness_update_status(struct backlight_device *bd)
+{
+       /* it is the backlight class's job (caller) to handle
+        * EINTR and other errors properly */
+       return brightness_set(
+               (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+                bd->props.power == FB_BLANK_UNBLANK) ?
+                               bd->props.brightness : 0);
+}
+
+static struct backlight_ops ibm_backlight_data = {
+        .get_brightness = brightness_get,
         .update_status  = brightness_update_status,
 };
 
-static struct mutex brightness_mutex;
+/* --------------------------------------------------------------------- */
 
 static int __init tpacpi_query_bcll_levels(acpi_handle handle)
 {
@@ -4196,93 +4269,6 @@ static void brightness_exit(void)
        }
 }
 
-static int brightness_update_status(struct backlight_device *bd)
-{
-       /* it is the backlight class's job (caller) to handle
-        * EINTR and other errors properly */
-       return brightness_set(
-               (bd->props.fb_blank == FB_BLANK_UNBLANK &&
-                bd->props.power == FB_BLANK_UNBLANK) ?
-                               bd->props.brightness : 0);
-}
-
-/*
- * ThinkPads can read brightness from two places: EC 0x31, or
- * CMOS NVRAM byte 0x5E, bits 0-3.
- */
-static int brightness_get(struct backlight_device *bd)
-{
-       u8 lec = 0, lcmos = 0, level = 0;
-
-       if (brightness_mode & 1) {
-               if (!acpi_ec_read(brightness_offset, &lec))
-                       return -EIO;
-               lec &= (tp_features.bright_16levels)? 0x0f : 0x07;
-               level = lec;
-       };
-       if (brightness_mode & 2) {
-               lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
-                        & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
-                       >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
-               lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
-               level = lcmos;
-       }
-
-       if (brightness_mode == 3 && lec != lcmos) {
-               printk(IBM_ERR
-                       "CMOS NVRAM (%u) and EC (%u) do not agree "
-                       "on display brightness level\n",
-                       (unsigned int) lcmos,
-                       (unsigned int) lec);
-               return -EIO;
-       }
-
-       return level;
-}
-
-/* May return EINTR which can always be mapped to ERESTARTSYS */
-static int brightness_set(int value)
-{
-       int cmos_cmd, inc, i, res;
-       int current_value;
-
-       if (value > ((tp_features.bright_16levels)? 15 : 7))
-               return -EINVAL;
-
-       res = mutex_lock_interruptible(&brightness_mutex);
-       if (res < 0)
-               return res;
-
-       current_value = brightness_get(NULL);
-       if (current_value < 0) {
-               res = current_value;
-               goto errout;
-       }
-
-       cmos_cmd = value > current_value ?
-                       TP_CMOS_BRIGHTNESS_UP :
-                       TP_CMOS_BRIGHTNESS_DOWN;
-       inc = (value > current_value)? 1 : -1;
-
-       res = 0;
-       for (i = current_value; i != value; i += inc) {
-               if ((brightness_mode & 2) &&
-                   issue_thinkpad_cmos_command(cmos_cmd)) {
-                       res = -EIO;
-                       goto errout;
-               }
-               if ((brightness_mode & 1) &&
-                   !acpi_ec_write(brightness_offset, i + inc)) {
-                       res = -EIO;
-                       goto errout;;
-               }
-       }
-
-errout:
-       mutex_unlock(&brightness_mutex);
-       return res;
-}
-
 static int brightness_read(char *p)
 {
        int len = 0;
@@ -4581,16 +4567,7 @@ static int fan_watchdog_maxinterval;
 
 static struct mutex fan_mutex;
 
-static int fan_get_status(u8 *status);
-static int fan_get_status_safe(u8 *status);
-static int fan_get_speed(unsigned int *speed);
-static void fan_update_desired_level(u8 status);
 static void fan_watchdog_fire(struct work_struct *ignored);
-static void fan_watchdog_reset(void);
-static int fan_set_level(int level);
-static int fan_set_level_safe(int level);
-static int fan_set_enable(void);
-
 static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
 
 IBM_HANDLE(fans, ec, "FANS");  /* X31, X40, X41 */
@@ -4602,458 +4579,278 @@ IBM_HANDLE(sfan, ec, "SFAN",  /* 570 */
           );                   /* all others */
 
 /*
- * SYSFS fan layout: hwmon compatible (device)
- *
- * pwm*_enable:
- *     0: "disengaged" mode
- *     1: manual mode
- *     2: native EC "auto" mode (recommended, hardware default)
- *
- * pwm*: set speed in manual mode, ignored otherwise.
- *     0 is level 0; 255 is level 7. Intermediate points done with linear
- *     interpolation.
- *
- * fan*_input: tachometer reading, RPM
- *
- *
- * SYSFS fan layout: extensions
- *
- * fan_watchdog (driver):
- *     fan watchdog interval in seconds, 0 disables (default), max 120
+ * Call with fan_mutex held
  */
-
-/* sysfs fan pwm1_enable ----------------------------------------------- */
-static ssize_t fan_pwm1_enable_show(struct device *dev,
-                                   struct device_attribute *attr,
-                                   char *buf)
+static void fan_update_desired_level(u8 status)
 {
-       int res, mode;
-       u8 status;
+       if ((status &
+            (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+               if (status > 7)
+                       fan_control_desired_level = 7;
+               else
+                       fan_control_desired_level = status;
+       }
+}
 
-       res = fan_get_status_safe(&status);
-       if (res)
-               return res;
+static int fan_get_status(u8 *status)
+{
+       u8 s;
 
-       if (unlikely(tp_features.fan_ctrl_status_undef)) {
-               if (status != fan_control_initial_status) {
-                       tp_features.fan_ctrl_status_undef = 0;
-               } else {
-                       /* Return most likely status. In fact, it
-                        * might be the only possible status */
-                       status = TP_EC_FAN_AUTO;
-               }
-       }
+       /* TODO:
+        * Add TPACPI_FAN_RD_ACPI_FANS ? */
 
-       if (status & TP_EC_FAN_FULLSPEED) {
-               mode = 0;
-       } else if (status & TP_EC_FAN_AUTO) {
-               mode = 2;
-       } else
-               mode = 1;
+       switch (fan_status_access_mode) {
+       case TPACPI_FAN_RD_ACPI_GFAN:
+               /* 570, 600e/x, 770e, 770x */
 
-       return snprintf(buf, PAGE_SIZE, "%d\n", mode);
-}
+               if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
+                       return -EIO;
 
-static ssize_t fan_pwm1_enable_store(struct device *dev,
-                                    struct device_attribute *attr,
-                                    const char *buf, size_t count)
-{
-       unsigned long t;
-       int res, level;
+               if (likely(status))
+                       *status = s & 0x07;
 
-       if (parse_strtoul(buf, 2, &t))
-               return -EINVAL;
+               break;
+
+       case TPACPI_FAN_RD_TPEC:
+               /* all except 570, 600e/x, 770e, 770x */
+               if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
+                       return -EIO;
+
+               if (likely(status))
+                       *status = s;
 
-       switch (t) {
-       case 0:
-               level = TP_EC_FAN_FULLSPEED;
-               break;
-       case 1:
-               level = TPACPI_FAN_LAST_LEVEL;
-               break;
-       case 2:
-               level = TP_EC_FAN_AUTO;
                break;
-       case 3:
-               /* reserved for software-controlled auto mode */
-               return -ENOSYS;
+
        default:
-               return -EINVAL;
+               return -ENXIO;
        }
 
-       res = fan_set_level_safe(level);
-       if (res == -ENXIO)
-               return -EINVAL;
-       else if (res < 0)
-               return res;
+       return 0;
+}
 
-       fan_watchdog_reset();
+static int fan_get_status_safe(u8 *status)
+{
+       int rc;
+       u8 s;
 
-       return count;
-}
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
+       rc = fan_get_status(&s);
+       if (!rc)
+               fan_update_desired_level(s);
+       mutex_unlock(&fan_mutex);
 
-static struct device_attribute dev_attr_fan_pwm1_enable =
-       __ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
-               fan_pwm1_enable_show, fan_pwm1_enable_store);
+       if (status)
+               *status = s;
 
-/* sysfs fan pwm1 ------------------------------------------------------ */
-static ssize_t fan_pwm1_show(struct device *dev,
-                            struct device_attribute *attr,
-                            char *buf)
+       return rc;
+}
+
+static int fan_get_speed(unsigned int *speed)
 {
-       int res;
-       u8 status;
+       u8 hi, lo;
 
-       res = fan_get_status_safe(&status);
-       if (res)
-               return res;
+       switch (fan_status_access_mode) {
+       case TPACPI_FAN_RD_TPEC:
+               /* all except 570, 600e/x, 770e, 770x */
+               if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
+                            !acpi_ec_read(fan_rpm_offset + 1, &hi)))
+                       return -EIO;
 
-       if (unlikely(tp_features.fan_ctrl_status_undef)) {
-               if (status != fan_control_initial_status) {
-                       tp_features.fan_ctrl_status_undef = 0;
-               } else {
-                       status = TP_EC_FAN_AUTO;
-               }
-       }
+               if (likely(speed))
+                       *speed = (hi << 8) | lo;
 
-       if ((status &
-            (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
-               status = fan_control_desired_level;
+               break;
 
-       if (status > 7)
-               status = 7;
+       default:
+               return -ENXIO;
+       }
 
-       return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
+       return 0;
 }
 
-static ssize_t fan_pwm1_store(struct device *dev,
-                             struct device_attribute *attr,
-                             const char *buf, size_t count)
+static int fan_set_level(int level)
 {
-       unsigned long s;
-       int rc;
-       u8 status, newlevel;
+       if (!fan_control_allowed)
+               return -EPERM;
 
-       if (parse_strtoul(buf, 255, &s))
-               return -EINVAL;
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               if (level >= 0 && level <= 7) {
+                       if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
+                               return -EIO;
+               } else
+                       return -EINVAL;
+               break;
 
-       /* scale down from 0-255 to 0-7 */
-       newlevel = (s >> 5) & 0x07;
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               if ((level != TP_EC_FAN_AUTO) &&
+                   (level != TP_EC_FAN_FULLSPEED) &&
+                   ((level < 0) || (level > 7)))
+                       return -EINVAL;
 
-       if (mutex_lock_interruptible(&fan_mutex))
-               return -ERESTARTSYS;
+               /* safety net should the EC not support AUTO
+                * or FULLSPEED mode bits and just ignore them */
+               if (level & TP_EC_FAN_FULLSPEED)
+                       level |= 7;     /* safety min speed 7 */
+               else if (level & TP_EC_FAN_FULLSPEED)
+                       level |= 4;     /* safety min speed 4 */
 
-       rc = fan_get_status(&status);
-       if (!rc && (status &
-                   (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
-               rc = fan_set_level(newlevel);
-               if (rc == -ENXIO)
-                       rc = -EINVAL;
-               else if (!rc) {
-                       fan_update_desired_level(newlevel);
-                       fan_watchdog_reset();
-               }
-       }
+               if (!acpi_ec_write(fan_status_offset, level))
+                       return -EIO;
+               else
+                       tp_features.fan_ctrl_status_undef = 0;
+               break;
 
-       mutex_unlock(&fan_mutex);
-       return (rc)? rc : count;
+       default:
+               return -ENXIO;
+       }
+       return 0;
 }
 
-static struct device_attribute dev_attr_fan_pwm1 =
-       __ATTR(pwm1, S_IWUSR | S_IRUGO,
-               fan_pwm1_show, fan_pwm1_store);
-
-/* sysfs fan fan1_input ------------------------------------------------ */
-static ssize_t fan_fan1_input_show(struct device *dev,
-                          struct device_attribute *attr,
-                          char *buf)
+static int fan_set_level_safe(int level)
 {
-       int res;
-       unsigned int speed;
+       int rc;
 
-       res = fan_get_speed(&speed);
-       if (res < 0)
-               return res;
+       if (!fan_control_allowed)
+               return -EPERM;
 
-       return snprintf(buf, PAGE_SIZE, "%u\n", speed);
-}
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
 
-static struct device_attribute dev_attr_fan_fan1_input =
-       __ATTR(fan1_input, S_IRUGO,
-               fan_fan1_input_show, NULL);
+       if (level == TPACPI_FAN_LAST_LEVEL)
+               level = fan_control_desired_level;
 
-/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
-static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
-                                    char *buf)
-{
-       return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
+       rc = fan_set_level(level);
+       if (!rc)
+               fan_update_desired_level(level);
+
+       mutex_unlock(&fan_mutex);
+       return rc;
 }
 
-static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
-                                     const char *buf, size_t count)
+static int fan_set_enable(void)
 {
-       unsigned long t;
-
-       if (parse_strtoul(buf, 120, &t))
-               return -EINVAL;
+       u8 s;
+       int rc;
 
        if (!fan_control_allowed)
                return -EPERM;
 
-       fan_watchdog_maxinterval = t;
-       fan_watchdog_reset();
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
 
-       return count;
-}
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               rc = fan_get_status(&s);
+               if (rc < 0)
+                       break;
 
-static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
-               fan_fan_watchdog_show, fan_fan_watchdog_store);
+               /* Don't go out of emergency fan mode */
+               if (s != 7) {
+                       s &= 0x07;
+                       s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+               }
 
-/* --------------------------------------------------------------------- */
-static struct attribute *fan_attributes[] = {
-       &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
-       &dev_attr_fan_fan1_input.attr,
-       NULL
-};
+               if (!acpi_ec_write(fan_status_offset, s))
+                       rc = -EIO;
+               else {
+                       tp_features.fan_ctrl_status_undef = 0;
+                       rc = 0;
+               }
+               break;
 
-static const struct attribute_group fan_attr_group = {
-       .attrs = fan_attributes,
-};
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               rc = fan_get_status(&s);
+               if (rc < 0)
+                       break;
 
-static int __init fan_init(struct ibm_init_struct *iibm)
-{
-       int rc;
+               s &= 0x07;
 
-       vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
+               /* Set fan to at least level 4 */
+               s |= 4;
 
-       mutex_init(&fan_mutex);
-       fan_status_access_mode = TPACPI_FAN_NONE;
-       fan_control_access_mode = TPACPI_FAN_WR_NONE;
-       fan_control_commands = 0;
-       fan_watchdog_maxinterval = 0;
-       tp_features.fan_ctrl_status_undef = 0;
-       fan_control_desired_level = 7;
+               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
+                       rc= -EIO;
+               else
+                       rc = 0;
+               break;
 
-       IBM_ACPIHANDLE_INIT(fans);
-       IBM_ACPIHANDLE_INIT(gfan);
-       IBM_ACPIHANDLE_INIT(sfan);
+       default:
+               rc = -ENXIO;
+       }
 
-       if (gfan_handle) {
-               /* 570, 600e/x, 770e, 770x */
-               fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
-       } else {
-               /* all other ThinkPads: note that even old-style
-                * ThinkPad ECs supports the fan control register */
-               if (likely(acpi_ec_read(fan_status_offset,
-                                       &fan_control_initial_status))) {
-                       fan_status_access_mode = TPACPI_FAN_RD_TPEC;
-
-                       /* In some ThinkPads, neither the EC nor the ACPI
-                        * DSDT initialize the fan status, and it ends up
-                        * being set to 0x07 when it *could* be either
-                        * 0x07 or 0x80.
-                        *
-                        * Enable for TP-1Y (T43), TP-78 (R51e),
-                        * TP-76 (R52), TP-70 (T43, R52), which are known
-                        * to be buggy. */
-                       if (fan_control_initial_status == 0x07) {
-                               switch (thinkpad_id.ec_model) {
-                               case 0x5931: /* TP-1Y */
-                               case 0x3837: /* TP-78 */
-                               case 0x3637: /* TP-76 */
-                               case 0x3037: /* TP-70 */
-                                       printk(IBM_NOTICE
-                                              "fan_init: initial fan status is "
-                                              "unknown, assuming it is in auto "
-                                              "mode\n");
-                                       tp_features.fan_ctrl_status_undef = 1;
-                                       ;;
-                               }
-                       }
-               } else {
-                       printk(IBM_ERR
-                              "ThinkPad ACPI EC access misbehaving, "
-                              "fan status and control unavailable\n");
-                       return 1;
-               }
-       }
-
-       if (sfan_handle) {
-               /* 570, 770x-JL */
-               fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
-               fan_control_commands |=
-                   TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
-       } else {
-               if (!gfan_handle) {
-                       /* gfan without sfan means no fan control */
-                       /* all other models implement TP EC 0x2f control */
-
-                       if (fans_handle) {
-                               /* X31, X40, X41 */
-                               fan_control_access_mode =
-                                   TPACPI_FAN_WR_ACPI_FANS;
-                               fan_control_commands |=
-                                   TPACPI_FAN_CMD_SPEED |
-                                   TPACPI_FAN_CMD_LEVEL |
-                                   TPACPI_FAN_CMD_ENABLE;
-                       } else {
-                               fan_control_access_mode = TPACPI_FAN_WR_TPEC;
-                               fan_control_commands |=
-                                   TPACPI_FAN_CMD_LEVEL |
-                                   TPACPI_FAN_CMD_ENABLE;
-                       }
-               }
-       }
-
-       vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
-               str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
-                 fan_control_access_mode != TPACPI_FAN_WR_NONE),
-               fan_status_access_mode, fan_control_access_mode);
-
-       /* fan control master switch */
-       if (!fan_control_allowed) {
-               fan_control_access_mode = TPACPI_FAN_WR_NONE;
-               fan_control_commands = 0;
-               dbg_printk(TPACPI_DBG_INIT,
-                          "fan control features disabled by parameter\n");
-       }
-
-       /* update fan_control_desired_level */
-       if (fan_status_access_mode != TPACPI_FAN_NONE)
-               fan_get_status_safe(NULL);
-
-       if (fan_status_access_mode != TPACPI_FAN_NONE ||
-           fan_control_access_mode != TPACPI_FAN_WR_NONE) {
-               rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
-                                        &fan_attr_group);
-               if (!(rc < 0))
-                       rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
-                                       &driver_attr_fan_watchdog);
-               if (rc < 0)
-                       return rc;
-               return 0;
-       } else
-               return 1;
-}
-
-/*
- * Call with fan_mutex held
- */
-static void fan_update_desired_level(u8 status)
-{
-       if ((status &
-            (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
-               if (status > 7)
-                       fan_control_desired_level = 7;
-               else
-                       fan_control_desired_level = status;
-       }
+       mutex_unlock(&fan_mutex);
+       return rc;
 }
 
-static int fan_get_status(u8 *status)
+static int fan_set_disable(void)
 {
-       u8 s;
-
-       /* TODO:
-        * Add TPACPI_FAN_RD_ACPI_FANS ? */
-
-       switch (fan_status_access_mode) {
-       case TPACPI_FAN_RD_ACPI_GFAN:
-               /* 570, 600e/x, 770e, 770x */
+       int rc;
 
-               if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
-                       return -EIO;
+       if (!fan_control_allowed)
+               return -EPERM;
 
-               if (likely(status))
-                       *status = s & 0x07;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
 
+       rc = 0;
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_FANS:
+       case TPACPI_FAN_WR_TPEC:
+               if (!acpi_ec_write(fan_status_offset, 0x00))
+                       rc = -EIO;
+               else {
+                       fan_control_desired_level = 0;
+                       tp_features.fan_ctrl_status_undef = 0;
+               }
                break;
 
-       case TPACPI_FAN_RD_TPEC:
-               /* all except 570, 600e/x, 770e, 770x */
-               if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
-                       return -EIO;
-
-               if (likely(status))
-                       *status = s;
-
+       case TPACPI_FAN_WR_ACPI_SFAN:
+               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
+                       rc = -EIO;
+               else
+                       fan_control_desired_level = 0;
                break;
 
        default:
-               return -ENXIO;
+               rc = -ENXIO;
        }
 
-       return 0;
-}
-
-static int fan_get_status_safe(u8 *status)
-{
-       int rc;
-       u8 s;
 
-       if (mutex_lock_interruptible(&fan_mutex))
-               return -ERESTARTSYS;
-       rc = fan_get_status(&s);
-       if (!rc)
-               fan_update_desired_level(s);
        mutex_unlock(&fan_mutex);
-
-       if (status)
-               *status = s;
-
        return rc;
 }
 
-static void fan_exit(void)
-{
-       vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n");
-
-       /* FIXME: can we really do this unconditionally? */
-       sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
-       driver_remove_file(&tpacpi_hwmon_pdriver.driver, &driver_attr_fan_watchdog);
-
-       cancel_delayed_work(&fan_watchdog_task);
-       flush_scheduled_work();
-}
-
-static int fan_get_speed(unsigned int *speed)
+static int fan_set_speed(int speed)
 {
-       u8 hi, lo;
+       int rc;
 
-       switch (fan_status_access_mode) {
-       case TPACPI_FAN_RD_TPEC:
-               /* all except 570, 600e/x, 770e, 770x */
-               if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
-                            !acpi_ec_read(fan_rpm_offset + 1, &hi)))
-                       return -EIO;
+       if (!fan_control_allowed)
+               return -EPERM;
 
-               if (likely(speed))
-                       *speed = (hi << 8) | lo;
+       if (mutex_lock_interruptible(&fan_mutex))
+               return -ERESTARTSYS;
 
+       rc = 0;
+       switch (fan_control_access_mode) {
+       case TPACPI_FAN_WR_ACPI_FANS:
+               if (speed >= 0 && speed <= 65535) {
+                       if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
+                                       speed, speed, speed))
+                               rc = -EIO;
+               } else
+                       rc = -EINVAL;
                break;
 
        default:
-               return -ENXIO;
+               rc = -ENXIO;
        }
 
-       return 0;
-}
-
-static void fan_watchdog_fire(struct work_struct *ignored)
-{
-       int rc;
-
-       if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
-               return;
-
-       printk(IBM_NOTICE "fan watchdog: enabling fan\n");
-       rc = fan_set_enable();
-       if (rc < 0) {
-               printk(IBM_ERR "fan watchdog: error %d while enabling fan, "
-                       "will try again later...\n", -rc);
-               /* reschedule for later */
-               fan_watchdog_reset();
-       }
+       mutex_unlock(&fan_mutex);
+       return rc;
 }
 
 static void fan_watchdog_reset(void)
@@ -5079,188 +4876,368 @@ static void fan_watchdog_reset(void)
                fan_watchdog_active = 0;
 }
 
-static int fan_set_level(int level)
+static void fan_watchdog_fire(struct work_struct *ignored)
 {
-       if (!fan_control_allowed)
-               return -EPERM;
-
-       switch (fan_control_access_mode) {
-       case TPACPI_FAN_WR_ACPI_SFAN:
-               if (level >= 0 && level <= 7) {
-                       if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
-                               return -EIO;
-               } else
-                       return -EINVAL;
-               break;
+       int rc;
 
-       case TPACPI_FAN_WR_ACPI_FANS:
-       case TPACPI_FAN_WR_TPEC:
-               if ((level != TP_EC_FAN_AUTO) &&
-                   (level != TP_EC_FAN_FULLSPEED) &&
-                   ((level < 0) || (level > 7)))
-                       return -EINVAL;
+       if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+               return;
 
-               /* safety net should the EC not support AUTO
-                * or FULLSPEED mode bits and just ignore them */
-               if (level & TP_EC_FAN_FULLSPEED)
-                       level |= 7;     /* safety min speed 7 */
-               else if (level & TP_EC_FAN_FULLSPEED)
-                       level |= 4;     /* safety min speed 4 */
+       printk(IBM_NOTICE "fan watchdog: enabling fan\n");
+       rc = fan_set_enable();
+       if (rc < 0) {
+               printk(IBM_ERR "fan watchdog: error %d while enabling fan, "
+                       "will try again later...\n", -rc);
+               /* reschedule for later */
+               fan_watchdog_reset();
+       }
+}
 
-               if (!acpi_ec_write(fan_status_offset, level))
-                       return -EIO;
-               else
+/*
+ * SYSFS fan layout: hwmon compatible (device)
+ *
+ * pwm*_enable:
+ *     0: "disengaged" mode
+ *     1: manual mode
+ *     2: native EC "auto" mode (recommended, hardware default)
+ *
+ * pwm*: set speed in manual mode, ignored otherwise.
+ *     0 is level 0; 255 is level 7. Intermediate points done with linear
+ *     interpolation.
+ *
+ * fan*_input: tachometer reading, RPM
+ *
+ *
+ * SYSFS fan layout: extensions
+ *
+ * fan_watchdog (driver):
+ *     fan watchdog interval in seconds, 0 disables (default), max 120
+ */
+
+/* sysfs fan pwm1_enable ----------------------------------------------- */
+static ssize_t fan_pwm1_enable_show(struct device *dev,
+                                   struct device_attribute *attr,
+                                   char *buf)
+{
+       int res, mode;
+       u8 status;
+
+       res = fan_get_status_safe(&status);
+       if (res)
+               return res;
+
+       if (unlikely(tp_features.fan_ctrl_status_undef)) {
+               if (status != fan_control_initial_status) {
                        tp_features.fan_ctrl_status_undef = 0;
-               break;
+               } else {
+                       /* Return most likely status. In fact, it
+                        * might be the only possible status */
+                       status = TP_EC_FAN_AUTO;
+               }
+       }
+
+       if (status & TP_EC_FAN_FULLSPEED) {
+               mode = 0;
+       } else if (status & TP_EC_FAN_AUTO) {
+               mode = 2;
+       } else
+               mode = 1;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", mode);
+}
 
+static ssize_t fan_pwm1_enable_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       unsigned long t;
+       int res, level;
+
+       if (parse_strtoul(buf, 2, &t))
+               return -EINVAL;
+
+       switch (t) {
+       case 0:
+               level = TP_EC_FAN_FULLSPEED;
+               break;
+       case 1:
+               level = TPACPI_FAN_LAST_LEVEL;
+               break;
+       case 2:
+               level = TP_EC_FAN_AUTO;
+               break;
+       case 3:
+               /* reserved for software-controlled auto mode */
+               return -ENOSYS;
        default:
-               return -ENXIO;
+               return -EINVAL;
        }
-       return 0;
+
+       res = fan_set_level_safe(level);
+       if (res == -ENXIO)
+               return -EINVAL;
+       else if (res < 0)
+               return res;
+
+       fan_watchdog_reset();
+
+       return count;
 }
 
-static int fan_set_level_safe(int level)
+static struct device_attribute dev_attr_fan_pwm1_enable =
+       __ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+               fan_pwm1_enable_show, fan_pwm1_enable_store);
+
+/* sysfs fan pwm1 ------------------------------------------------------ */
+static ssize_t fan_pwm1_show(struct device *dev,
+                            struct device_attribute *attr,
+                            char *buf)
 {
-       int rc;
+       int res;
+       u8 status;
 
-       if (!fan_control_allowed)
-               return -EPERM;
+       res = fan_get_status_safe(&status);
+       if (res)
+               return res;
 
-       if (mutex_lock_interruptible(&fan_mutex))
-               return -ERESTARTSYS;
+       if (unlikely(tp_features.fan_ctrl_status_undef)) {
+               if (status != fan_control_initial_status) {
+                       tp_features.fan_ctrl_status_undef = 0;
+               } else {
+                       status = TP_EC_FAN_AUTO;
+               }
+       }
 
-       if (level == TPACPI_FAN_LAST_LEVEL)
-               level = fan_control_desired_level;
+       if ((status &
+            (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
+               status = fan_control_desired_level;
 
-       rc = fan_set_level(level);
-       if (!rc)
-               fan_update_desired_level(level);
+       if (status > 7)
+               status = 7;
 
-       mutex_unlock(&fan_mutex);
-       return rc;
+       return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
 }
 
-static int fan_set_enable(void)
+static ssize_t fan_pwm1_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t count)
 {
-       u8 s;
+       unsigned long s;
        int rc;
+       u8 status, newlevel;
 
-       if (!fan_control_allowed)
-               return -EPERM;
+       if (parse_strtoul(buf, 255, &s))
+               return -EINVAL;
+
+       /* scale down from 0-255 to 0-7 */
+       newlevel = (s >> 5) & 0x07;
 
        if (mutex_lock_interruptible(&fan_mutex))
                return -ERESTARTSYS;
 
-       switch (fan_control_access_mode) {
-       case TPACPI_FAN_WR_ACPI_FANS:
-       case TPACPI_FAN_WR_TPEC:
-               rc = fan_get_status(&s);
-               if (rc < 0)
-                       break;
-
-               /* Don't go out of emergency fan mode */
-               if (s != 7) {
-                       s &= 0x07;
-                       s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+       rc = fan_get_status(&status);
+       if (!rc && (status &
+                   (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+               rc = fan_set_level(newlevel);
+               if (rc == -ENXIO)
+                       rc = -EINVAL;
+               else if (!rc) {
+                       fan_update_desired_level(newlevel);
+                       fan_watchdog_reset();
                }
+       }
 
-               if (!acpi_ec_write(fan_status_offset, s))
-                       rc = -EIO;
-               else {
-                       tp_features.fan_ctrl_status_undef = 0;
-                       rc = 0;
-               }
-               break;
+       mutex_unlock(&fan_mutex);
+       return (rc)? rc : count;
+}
 
-       case TPACPI_FAN_WR_ACPI_SFAN:
-               rc = fan_get_status(&s);
-               if (rc < 0)
-                       break;
+static struct device_attribute dev_attr_fan_pwm1 =
+       __ATTR(pwm1, S_IWUSR | S_IRUGO,
+               fan_pwm1_show, fan_pwm1_store);
 
-               s &= 0x07;
+/* sysfs fan fan1_input ------------------------------------------------ */
+static ssize_t fan_fan1_input_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       int res;
+       unsigned int speed;
 
-               /* Set fan to at least level 4 */
-               s |= 4;
+       res = fan_get_speed(&speed);
+       if (res < 0)
+               return res;
 
-               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
-                       rc= -EIO;
-               else
-                       rc = 0;
-               break;
+       return snprintf(buf, PAGE_SIZE, "%u\n", speed);
+}
 
-       default:
-               rc = -ENXIO;
-       }
+static struct device_attribute dev_attr_fan_fan1_input =
+       __ATTR(fan1_input, S_IRUGO,
+               fan_fan1_input_show, NULL);
 
-       mutex_unlock(&fan_mutex);
-       return rc;
+/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
+static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
+                                    char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
 }
 
-static int fan_set_disable(void)
+static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
+                                     const char *buf, size_t count)
 {
-       int rc;
+       unsigned long t;
+
+       if (parse_strtoul(buf, 120, &t))
+               return -EINVAL;
 
        if (!fan_control_allowed)
                return -EPERM;
 
-       if (mutex_lock_interruptible(&fan_mutex))
-               return -ERESTARTSYS;
+       fan_watchdog_maxinterval = t;
+       fan_watchdog_reset();
 
-       rc = 0;
-       switch (fan_control_access_mode) {
-       case TPACPI_FAN_WR_ACPI_FANS:
-       case TPACPI_FAN_WR_TPEC:
-               if (!acpi_ec_write(fan_status_offset, 0x00))
-                       rc = -EIO;
-               else {
-                       fan_control_desired_level = 0;
-                       tp_features.fan_ctrl_status_undef = 0;
+       return count;
+}
+
+static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
+               fan_fan_watchdog_show, fan_fan_watchdog_store);
+
+/* --------------------------------------------------------------------- */
+static struct attribute *fan_attributes[] = {
+       &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
+       &dev_attr_fan_fan1_input.attr,
+       NULL
+};
+
+static const struct attribute_group fan_attr_group = {
+       .attrs = fan_attributes,
+};
+
+static int __init fan_init(struct ibm_init_struct *iibm)
+{
+       int rc;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
+
+       mutex_init(&fan_mutex);
+       fan_status_access_mode = TPACPI_FAN_NONE;
+       fan_control_access_mode = TPACPI_FAN_WR_NONE;
+       fan_control_commands = 0;
+       fan_watchdog_maxinterval = 0;
+       tp_features.fan_ctrl_status_undef = 0;
+       fan_control_desired_level = 7;
+
+       IBM_ACPIHANDLE_INIT(fans);
+       IBM_ACPIHANDLE_INIT(gfan);
+       IBM_ACPIHANDLE_INIT(sfan);
+
+       if (gfan_handle) {
+               /* 570, 600e/x, 770e, 770x */
+               fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
+       } else {
+               /* all other ThinkPads: note that even old-style
+                * ThinkPad ECs supports the fan control register */
+               if (likely(acpi_ec_read(fan_status_offset,
+                                       &fan_control_initial_status))) {
+                       fan_status_access_mode = TPACPI_FAN_RD_TPEC;
+
+                       /* In some ThinkPads, neither the EC nor the ACPI
+                        * DSDT initialize the fan status, and it ends up
+                        * being set to 0x07 when it *could* be either
+                        * 0x07 or 0x80.
+                        *
+                        * Enable for TP-1Y (T43), TP-78 (R51e),
+                        * TP-76 (R52), TP-70 (T43, R52), which are known
+                        * to be buggy. */
+                       if (fan_control_initial_status == 0x07) {
+                               switch (thinkpad_id.ec_model) {
+                               case 0x5931: /* TP-1Y */
+                               case 0x3837: /* TP-78 */
+                               case 0x3637: /* TP-76 */
+                               case 0x3037: /* TP-70 */
+                                       printk(IBM_NOTICE
+                                              "fan_init: initial fan status is "
+                                              "unknown, assuming it is in auto "
+                                              "mode\n");
+                                       tp_features.fan_ctrl_status_undef = 1;
+                                       ;;
+                               }
+                       }
+               } else {
+                       printk(IBM_ERR
+                              "ThinkPad ACPI EC access misbehaving, "
+                              "fan status and control unavailable\n");
+                       return 1;
+               }
+       }
+
+       if (sfan_handle) {
+               /* 570, 770x-JL */
+               fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
+               fan_control_commands |=
+                   TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
+       } else {
+               if (!gfan_handle) {
+                       /* gfan without sfan means no fan control */
+                       /* all other models implement TP EC 0x2f control */
+
+                       if (fans_handle) {
+                               /* X31, X40, X41 */
+                               fan_control_access_mode =
+                                   TPACPI_FAN_WR_ACPI_FANS;
+                               fan_control_commands |=
+                                   TPACPI_FAN_CMD_SPEED |
+                                   TPACPI_FAN_CMD_LEVEL |
+                                   TPACPI_FAN_CMD_ENABLE;
+                       } else {
+                               fan_control_access_mode = TPACPI_FAN_WR_TPEC;
+                               fan_control_commands |=
+                                   TPACPI_FAN_CMD_LEVEL |
+                                   TPACPI_FAN_CMD_ENABLE;
+                       }
                }
-               break;
+       }
 
-       case TPACPI_FAN_WR_ACPI_SFAN:
-               if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
-                       rc = -EIO;
-               else
-                       fan_control_desired_level = 0;
-               break;
+       vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
+               str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
+                 fan_control_access_mode != TPACPI_FAN_WR_NONE),
+               fan_status_access_mode, fan_control_access_mode);
 
-       default:
-               rc = -ENXIO;
+       /* fan control master switch */
+       if (!fan_control_allowed) {
+               fan_control_access_mode = TPACPI_FAN_WR_NONE;
+               fan_control_commands = 0;
+               dbg_printk(TPACPI_DBG_INIT,
+                          "fan control features disabled by parameter\n");
        }
 
+       /* update fan_control_desired_level */
+       if (fan_status_access_mode != TPACPI_FAN_NONE)
+               fan_get_status_safe(NULL);
 
-       mutex_unlock(&fan_mutex);
-       return rc;
+       if (fan_status_access_mode != TPACPI_FAN_NONE ||
+           fan_control_access_mode != TPACPI_FAN_WR_NONE) {
+               rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
+                                        &fan_attr_group);
+               if (!(rc < 0))
+                       rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
+                                       &driver_attr_fan_watchdog);
+               if (rc < 0)
+                       return rc;
+               return 0;
+       } else
+               return 1;
 }
 
-static int fan_set_speed(int speed)
+static void fan_exit(void)
 {
-       int rc;
-
-       if (!fan_control_allowed)
-               return -EPERM;
-
-       if (mutex_lock_interruptible(&fan_mutex))
-               return -ERESTARTSYS;
-
-       rc = 0;
-       switch (fan_control_access_mode) {
-       case TPACPI_FAN_WR_ACPI_FANS:
-               if (speed >= 0 && speed <= 65535) {
-                       if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
-                                       speed, speed, speed))
-                               rc = -EIO;
-               } else
-                       rc = -EINVAL;
-               break;
+       vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n");
 
-       default:
-               rc = -ENXIO;
-       }
+       /* FIXME: can we really do this unconditionally? */
+       sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
+       driver_remove_file(&tpacpi_hwmon_pdriver.driver, &driver_attr_fan_watchdog);
 
-       mutex_unlock(&fan_mutex);
-       return rc;
+       cancel_delayed_work(&fan_watchdog_task);
+       flush_scheduled_work();
 }
 
 static int fan_read(char *p)
@@ -5473,11 +5450,12 @@ static struct device_attribute dev_attr_thinkpad_acpi_pdev_name =
 /* /proc support */
 static struct proc_dir_entry *proc_dir;
 
-
 /*
  * Module and infrastructure proble, init and exit handling
  */
 
+static int force_load;
+
 #ifdef CONFIG_THINKPAD_ACPI_DEBUG
 static const char * __init str_supported(int is_supported)
 {
@@ -5487,7 +5465,47 @@ static const char * __init str_supported(int is_supported)
 }
 #endif /* CONFIG_THINKPAD_ACPI_DEBUG */
 
-static void ibm_exit(struct ibm_struct *ibm);
+static void ibm_exit(struct ibm_struct *ibm)
+{
+       dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
+
+       list_del_init(&ibm->all_drivers);
+
+       if (ibm->flags.acpi_notify_installed) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                       "%s: acpi_remove_notify_handler\n", ibm->name);
+               BUG_ON(!ibm->acpi);
+               acpi_remove_notify_handler(*ibm->acpi->handle,
+                                          ibm->acpi->type,
+                                          dispatch_acpi_notify);
+               ibm->flags.acpi_notify_installed = 0;
+               ibm->flags.acpi_notify_installed = 0;
+       }
+
+       if (ibm->flags.proc_created) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                       "%s: remove_proc_entry\n", ibm->name);
+               remove_proc_entry(ibm->name, proc_dir);
+               ibm->flags.proc_created = 0;
+       }
+
+       if (ibm->flags.acpi_driver_registered) {
+               dbg_printk(TPACPI_DBG_EXIT,
+                       "%s: acpi_bus_unregister_driver\n", ibm->name);
+               BUG_ON(!ibm->acpi);
+               acpi_bus_unregister_driver(ibm->acpi->driver);
+               kfree(ibm->acpi->driver);
+               ibm->acpi->driver = NULL;
+               ibm->flags.acpi_driver_registered = 0;
+       }
+
+       if (ibm->flags.init_called && ibm->exit) {
+               ibm->exit();
+               ibm->flags.init_called = 0;
+       }
+
+       dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
+}
 
 static int __init ibm_init(struct ibm_init_struct *iibm)
 {
@@ -5569,48 +5587,6 @@ err_out:
        return (ret < 0)? ret : 0;
 }
 
-static void ibm_exit(struct ibm_struct *ibm)
-{
-       dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
-
-       list_del_init(&ibm->all_drivers);
-
-       if (ibm->flags.acpi_notify_installed) {
-               dbg_printk(TPACPI_DBG_EXIT,
-                       "%s: acpi_remove_notify_handler\n", ibm->name);
-               BUG_ON(!ibm->acpi);
-               acpi_remove_notify_handler(*ibm->acpi->handle,
-                                          ibm->acpi->type,
-                                          dispatch_acpi_notify);
-               ibm->flags.acpi_notify_installed = 0;
-               ibm->flags.acpi_notify_installed = 0;
-       }
-
-       if (ibm->flags.proc_created) {
-               dbg_printk(TPACPI_DBG_EXIT,
-                       "%s: remove_proc_entry\n", ibm->name);
-               remove_proc_entry(ibm->name, proc_dir);
-               ibm->flags.proc_created = 0;
-       }
-
-       if (ibm->flags.acpi_driver_registered) {
-               dbg_printk(TPACPI_DBG_EXIT,
-                       "%s: acpi_bus_unregister_driver\n", ibm->name);
-               BUG_ON(!ibm->acpi);
-               acpi_bus_unregister_driver(ibm->acpi->driver);
-               kfree(ibm->acpi->driver);
-               ibm->acpi->driver = NULL;
-               ibm->flags.acpi_driver_registered = 0;
-       }
-
-       if (ibm->flags.init_called && ibm->exit) {
-               ibm->exit();
-               ibm->flags.init_called = 0;
-       }
-
-       dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
-}
-
 /* Probing */
 
 static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp)
@@ -5839,7 +5815,57 @@ IBM_PARAM(brightness);
 IBM_PARAM(volume);
 IBM_PARAM(fan);
 
-static void thinkpad_acpi_module_exit(void);
+static void thinkpad_acpi_module_exit(void)
+{
+       struct ibm_struct *ibm, *itmp;
+
+       tpacpi_lifecycle = TPACPI_LIFE_EXITING;
+
+       list_for_each_entry_safe_reverse(ibm, itmp,
+                                        &tpacpi_all_drivers,
+                                        all_drivers) {
+               ibm_exit(ibm);
+       }
+
+       dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
+
+       if (tpacpi_inputdev) {
+               if (tp_features.input_device_registered)
+                       input_unregister_device(tpacpi_inputdev);
+               else
+                       input_free_device(tpacpi_inputdev);
+       }
+
+       if (tpacpi_hwmon)
+               hwmon_device_unregister(tpacpi_hwmon);
+
+       if (tp_features.sensors_pdev_attrs_registered)
+               device_remove_file(&tpacpi_sensors_pdev->dev,
+                                  &dev_attr_thinkpad_acpi_pdev_name);
+       if (tpacpi_sensors_pdev)
+               platform_device_unregister(tpacpi_sensors_pdev);
+       if (tpacpi_pdev)
+               platform_device_unregister(tpacpi_pdev);
+
+       if (tp_features.sensors_pdrv_attrs_registered)
+               tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
+       if (tp_features.platform_drv_attrs_registered)
+               tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
+
+       if (tp_features.sensors_pdrv_registered)
+               platform_driver_unregister(&tpacpi_hwmon_pdriver);
+
+       if (tp_features.platform_drv_registered)
+               platform_driver_unregister(&tpacpi_pdriver);
+
+       if (proc_dir)
+               remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);
+
+       kfree(thinkpad_id.bios_version_str);
+       kfree(thinkpad_id.ec_version_str);
+       kfree(thinkpad_id.model_str);
+}
+
 
 static int __init thinkpad_acpi_module_init(void)
 {
@@ -5978,56 +6004,5 @@ static int __init thinkpad_acpi_module_init(void)
        return 0;
 }
 
-static void thinkpad_acpi_module_exit(void)
-{
-       struct ibm_struct *ibm, *itmp;
-
-       tpacpi_lifecycle = TPACPI_LIFE_EXITING;
-
-       list_for_each_entry_safe_reverse(ibm, itmp,
-                                        &tpacpi_all_drivers,
-                                        all_drivers) {
-               ibm_exit(ibm);
-       }
-
-       dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
-
-       if (tpacpi_inputdev) {
-               if (tp_features.input_device_registered)
-                       input_unregister_device(tpacpi_inputdev);
-               else
-                       input_free_device(tpacpi_inputdev);
-       }
-
-       if (tpacpi_hwmon)
-               hwmon_device_unregister(tpacpi_hwmon);
-
-       if (tp_features.sensors_pdev_attrs_registered)
-               device_remove_file(&tpacpi_sensors_pdev->dev,
-                                  &dev_attr_thinkpad_acpi_pdev_name);
-       if (tpacpi_sensors_pdev)
-               platform_device_unregister(tpacpi_sensors_pdev);
-       if (tpacpi_pdev)
-               platform_device_unregister(tpacpi_pdev);
-
-       if (tp_features.sensors_pdrv_attrs_registered)
-               tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
-       if (tp_features.platform_drv_attrs_registered)
-               tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
-
-       if (tp_features.sensors_pdrv_registered)
-               platform_driver_unregister(&tpacpi_hwmon_pdriver);
-
-       if (tp_features.platform_drv_registered)
-               platform_driver_unregister(&tpacpi_pdriver);
-
-       if (proc_dir)
-               remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);
-
-       kfree(thinkpad_id.bios_version_str);
-       kfree(thinkpad_id.ec_version_str);
-       kfree(thinkpad_id.model_str);
-}
-
 module_init(thinkpad_acpi_module_init);
 module_exit(thinkpad_acpi_module_exit);