Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/roland...
[sfrench/cifs-2.6.git] / drivers / platform / x86 / thinkpad_acpi.c
index cf61d6a8ef6f6410f8a684410fc1f03a9669d0fe..e67e4feb35cb814f85ad2df10bc1836c00cc79b6 100644 (file)
@@ -21,8 +21,8 @@
  *  02110-1301, USA.
  */
 
-#define TPACPI_VERSION "0.23"
-#define TPACPI_SYSFS_VERSION 0x020500
+#define TPACPI_VERSION "0.24"
+#define TPACPI_SYSFS_VERSION 0x020700
 
 /*
  *  Changelog:
@@ -61,6 +61,7 @@
 
 #include <linux/nvram.h>
 #include <linux/proc_fs.h>
+#include <linux/seq_file.h>
 #include <linux/sysfs.h>
 #include <linux/backlight.h>
 #include <linux/fb.h>
 #include <linux/jiffies.h>
 #include <linux/workqueue.h>
 
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
 #include <acpi/acpi_drivers.h>
 
 #include <linux/pci_ids.h>
@@ -231,6 +236,7 @@ enum tpacpi_hkey_event_t {
 #define TPACPI_DBG_HKEY                0x0008
 #define TPACPI_DBG_FAN         0x0010
 #define TPACPI_DBG_BRGHT       0x0020
+#define TPACPI_DBG_MIXER       0x0040
 
 #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
 #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
@@ -256,7 +262,7 @@ struct tp_acpi_drv_struct {
 struct ibm_struct {
        char *name;
 
-       int (*read) (char *);
+       int (*read) (struct seq_file *);
        int (*write) (char *);
        void (*exit) (void);
        void (*resume) (void);
@@ -298,6 +304,7 @@ static struct {
        u32 fan_ctrl_status_undef:1;
        u32 second_fan:1;
        u32 beep_needs_two_args:1;
+       u32 mixer_no_level_control:1;
        u32 input_device_registered:1;
        u32 platform_drv_registered:1;
        u32 platform_drv_attrs_registered:1;
@@ -309,6 +316,7 @@ static struct {
 
 static struct {
        u16 hotkey_mask_ff:1;
+       u16 volume_ctrl_forbidden:1;
 } tp_warned;
 
 struct thinkpad_id_data {
@@ -425,6 +433,12 @@ static void tpacpi_log_usertask(const char * const what)
          .ec = TPACPI_MATCH_ANY,               \
          .quirks = (__quirk) }
 
+#define TPACPI_QEC_LNV(__id1, __id2, __quirk)  \
+       { .vendor = PCI_VENDOR_ID_LENOVO,       \
+         .bios = TPACPI_MATCH_ANY,             \
+         .ec = TPID(__id1, __id2),             \
+         .quirks = (__quirk) }
+
 struct tpacpi_quirk {
        unsigned int vendor;
        u16 bios;
@@ -776,36 +790,25 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
  ****************************************************************************
  ****************************************************************************/
 
-static int dispatch_procfs_read(char *page, char **start, off_t off,
-                       int count, int *eof, void *data)
+static int dispatch_proc_show(struct seq_file *m, void *v)
 {
-       struct ibm_struct *ibm = data;
-       int len;
+       struct ibm_struct *ibm = m->private;
 
        if (!ibm || !ibm->read)
                return -EINVAL;
+       return ibm->read(m);
+}
 
-       len = ibm->read(page);
-       if (len < 0)
-               return len;
-
-       if (len <= off + count)
-               *eof = 1;
-       *start = page + off;
-       len -= off;
-       if (len > count)
-               len = count;
-       if (len < 0)
-               len = 0;
-
-       return len;
+static int dispatch_proc_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, dispatch_proc_show, PDE(inode)->data);
 }
 
-static int dispatch_procfs_write(struct file *file,
+static ssize_t dispatch_proc_write(struct file *file,
                        const char __user *userbuf,
-                       unsigned long count, void *data)
+                       size_t count, loff_t *pos)
 {
-       struct ibm_struct *ibm = data;
+       struct ibm_struct *ibm = PDE(file->f_path.dentry->d_inode)->data;
        char *kernbuf;
        int ret;
 
@@ -834,6 +837,15 @@ static int dispatch_procfs_write(struct file *file,
        return ret;
 }
 
+static const struct file_operations dispatch_proc_fops = {
+       .owner          = THIS_MODULE,
+       .open           = dispatch_proc_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+       .write          = dispatch_proc_write,
+};
+
 static char *next_cmd(char **cmds)
 {
        char *start = *cmds;
@@ -1261,6 +1273,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
        struct tpacpi_rfk *atp_rfk;
        int res;
        bool sw_state = false;
+       bool hw_state;
        int sw_status;
 
        BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
@@ -1295,7 +1308,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
                        rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
                }
        }
-       rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
+       hw_state = tpacpi_rfk_check_hwblock_state();
+       rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
 
        res = rfkill_register(atp_rfk->rfkill);
        if (res < 0) {
@@ -1308,6 +1322,9 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
        }
 
        tpacpi_rfkill_switches[id] = atp_rfk;
+
+       printk(TPACPI_INFO "rfkill switch %s: radio is %sblocked\n",
+               name, (sw_state || hw_state) ? "" : "un");
        return 0;
 }
 
@@ -1380,12 +1397,10 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
 }
 
 /* procfs -------------------------------------------------------------- */
-static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p)
+static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
 {
-       int len = 0;
-
        if (id >= TPACPI_RFK_SW_MAX)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
+               seq_printf(m, "status:\t\tnot supported\n");
        else {
                int status;
 
@@ -1399,13 +1414,13 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p)
                                return status;
                }
 
-               len += sprintf(p + len, "status:\t\t%s\n",
+               seq_printf(m, "status:\t\t%s\n",
                                (status == TPACPI_RFK_RADIO_ON) ?
                                        "enabled" : "disabled");
-               len += sprintf(p + len, "commands:\tenable, disable\n");
+               seq_printf(m, "commands:\tenable, disable\n");
        }
 
-       return len;
+       return 0;
 }
 
 static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
@@ -1776,7 +1791,7 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
 
        TPV_QL1('7', '9',  'E', '3',  '5', '0'), /* T60/p */
        TPV_QL1('7', 'C',  'D', '2',  '2', '2'), /* R60, R60i */
-       TPV_QL0('7', 'E',  'D', '0'),            /* R60e, R60i */
+       TPV_QL1('7', 'E',  'D', '0',  '1', '5'), /* R60e, R60i */
 
        /*      BIOS FW    BIOS VERS  EC FW     EC VERS */
        TPV_QI2('1', 'W',  '9', '0',  '1', 'V', '2', '8'), /* R50e (1) */
@@ -1792,8 +1807,8 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
        TPV_QI1('7', '4',  '6', '4',  '2', '7'), /* X41 (0) */
        TPV_QI1('7', '5',  '6', '0',  '2', '0'), /* X41t (0) */
 
-       TPV_QL0('7', 'B',  'D', '7'),            /* X60/s */
-       TPV_QL0('7', 'J',  '3', '0'),            /* X60t */
+       TPV_QL1('7', 'B',  'D', '7',  '4', '0'), /* X60/s */
+       TPV_QL1('7', 'J',  '3', '0',  '1', '3'), /* X60t */
 
        /* (0) - older versions lack DMI EC fw string and functionality */
        /* (1) - older versions known to lack functionality */
@@ -1883,14 +1898,11 @@ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
        return 0;
 }
 
-static int thinkpad_acpi_driver_read(char *p)
+static int thinkpad_acpi_driver_read(struct seq_file *m)
 {
-       int len = 0;
-
-       len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC);
-       len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION);
-
-       return len;
+       seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
+       seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
+       return 0;
 }
 
 static struct ibm_struct thinkpad_acpi_driver_data = {
@@ -2186,7 +2198,8 @@ static int hotkey_mask_set(u32 mask)
                       fwmask, hotkey_acpi_mask);
        }
 
-       hotkey_mask_warn_incomplete_mask();
+       if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
+               hotkey_mask_warn_incomplete_mask();
 
        return rc;
 }
@@ -3182,6 +3195,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        int res, i;
        int status;
        int hkeyv;
+       bool radiosw_state  = false;
+       bool tabletsw_state = false;
 
        unsigned long quirks;
 
@@ -3287,6 +3302,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
        if (dbg_wlswemul) {
                tp_features.hotkey_wlsw = 1;
+               radiosw_state = !!tpacpi_wlsw_emulstate;
                printk(TPACPI_INFO
                        "radio switch emulation enabled\n");
        } else
@@ -3294,6 +3310,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        /* Not all thinkpads have a hardware radio switch */
        if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
                tp_features.hotkey_wlsw = 1;
+               radiosw_state = !!status;
                printk(TPACPI_INFO
                        "radio switch found; radios are %s\n",
                        enabled(status, 0));
@@ -3305,11 +3322,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        /* For X41t, X60t, X61t Tablets... */
        if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
                tp_features.hotkey_tablet = 1;
+               tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK);
                printk(TPACPI_INFO
                        "possible tablet mode switch found; "
                        "ThinkPad in %s mode\n",
-                       (status & TP_HOTKEY_TABLET_MASK)?
-                               "tablet" : "laptop");
+                       (tabletsw_state) ? "tablet" : "laptop");
                res = add_to_attr_set(hotkey_dev_attributes,
                                &dev_attr_hotkey_tablet_mode.attr);
        }
@@ -3344,16 +3361,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
                        TPACPI_HOTKEY_MAP_SIZE);
        }
 
-       set_bit(EV_KEY, tpacpi_inputdev->evbit);
-       set_bit(EV_MSC, tpacpi_inputdev->evbit);
-       set_bit(MSC_SCAN, tpacpi_inputdev->mscbit);
+       input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN);
        tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
        tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
        tpacpi_inputdev->keycode = hotkey_keycode_map;
        for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
                if (hotkey_keycode_map[i] != KEY_RESERVED) {
-                       set_bit(hotkey_keycode_map[i],
-                               tpacpi_inputdev->keybit);
+                       input_set_capability(tpacpi_inputdev, EV_KEY,
+                                               hotkey_keycode_map[i]);
                } else {
                        if (i < sizeof(hotkey_reserved_mask)*8)
                                hotkey_reserved_mask |= 1 << i;
@@ -3361,12 +3376,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        }
 
        if (tp_features.hotkey_wlsw) {
-               set_bit(EV_SW, tpacpi_inputdev->evbit);
-               set_bit(SW_RFKILL_ALL, tpacpi_inputdev->swbit);
+               input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
+               input_report_switch(tpacpi_inputdev,
+                                   SW_RFKILL_ALL, radiosw_state);
        }
        if (tp_features.hotkey_tablet) {
-               set_bit(EV_SW, tpacpi_inputdev->evbit);
-               set_bit(SW_TABLET_MODE, tpacpi_inputdev->swbit);
+               input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
+               input_report_switch(tpacpi_inputdev,
+                                   SW_TABLET_MODE, tabletsw_state);
        }
 
        /* Do not issue duplicate brightness change events to
@@ -3433,8 +3450,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
        tpacpi_inputdev->close = &hotkey_inputdev_close;
 
        hotkey_poll_setup_safe(true);
-       tpacpi_send_radiosw_update();
-       tpacpi_input_send_tabletsw();
 
        return 0;
 
@@ -3542,49 +3557,57 @@ static bool hotkey_notify_usrevent(const u32 hkey,
        }
 }
 
+static void thermal_dump_all_sensors(void);
+
 static bool hotkey_notify_thermal(const u32 hkey,
                                 bool *send_acpi_ev,
                                 bool *ignore_acpi_ev)
 {
+       bool known = true;
+
        /* 0x6000-0x6FFF: thermal alarms */
        *send_acpi_ev = true;
        *ignore_acpi_ev = false;
 
        switch (hkey) {
+       case TP_HKEY_EV_THM_TABLE_CHANGED:
+               printk(TPACPI_INFO
+                       "EC reports that Thermal Table has changed\n");
+               /* recommended action: do nothing, we don't have
+                * Lenovo ATM information */
+               return true;
        case TP_HKEY_EV_ALARM_BAT_HOT:
                printk(TPACPI_CRIT
                        "THERMAL ALARM: battery is too hot!\n");
                /* recommended action: warn user through gui */
-               return true;
+               break;
        case TP_HKEY_EV_ALARM_BAT_XHOT:
                printk(TPACPI_ALERT
                        "THERMAL EMERGENCY: battery is extremely hot!\n");
                /* recommended action: immediate sleep/hibernate */
-               return true;
+               break;
        case TP_HKEY_EV_ALARM_SENSOR_HOT:
                printk(TPACPI_CRIT
                        "THERMAL ALARM: "
                        "a sensor reports something is too hot!\n");
                /* recommended action: warn user through gui, that */
                /* some internal component is too hot */
-               return true;
+               break;
        case TP_HKEY_EV_ALARM_SENSOR_XHOT:
                printk(TPACPI_ALERT
                        "THERMAL EMERGENCY: "
                        "a sensor reports something is extremely hot!\n");
                /* recommended action: immediate sleep/hibernate */
-               return true;
-       case TP_HKEY_EV_THM_TABLE_CHANGED:
-               printk(TPACPI_INFO
-                       "EC reports that Thermal Table has changed\n");
-               /* recommended action: do nothing, we don't have
-                * Lenovo ATM information */
-               return true;
+               break;
        default:
                printk(TPACPI_ALERT
                         "THERMAL ALERT: unknown thermal alarm received\n");
-               return false;
+               known = false;
        }
+
+       thermal_dump_all_sensors();
+
+       return known;
 }
 
 static void hotkey_notify(struct ibm_struct *ibm, u32 event)
@@ -3727,14 +3750,13 @@ static void hotkey_resume(void)
 }
 
 /* procfs -------------------------------------------------------------- */
-static int hotkey_read(char *p)
+static int hotkey_read(struct seq_file *m)
 {
        int res, status;
-       int len = 0;
 
        if (!tp_features.hotkey) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-               return len;
+               seq_printf(m, "status:\t\tnot supported\n");
+               return 0;
        }
 
        if (mutex_lock_killable(&hotkey_mutex))
@@ -3746,17 +3768,16 @@ static int hotkey_read(char *p)
        if (res)
                return res;
 
-       len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
+       seq_printf(m, "status:\t\t%s\n", enabled(status, 0));
        if (hotkey_all_mask) {
-               len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_user_mask);
-               len += sprintf(p + len,
-                              "commands:\tenable, disable, reset, <mask>\n");
+               seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
+               seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
        } else {
-               len += sprintf(p + len, "mask:\t\tnot supported\n");
-               len += sprintf(p + len, "commands:\tenable, disable, reset\n");
+               seq_printf(m, "mask:\t\tnot supported\n");
+               seq_printf(m, "commands:\tenable, disable, reset\n");
        }
 
-       return len;
+       return 0;
 }
 
 static void hotkey_enabledisable_warn(bool enable)
@@ -3863,15 +3884,6 @@ enum {
 
 #define TPACPI_RFK_BLUETOOTH_SW_NAME   "tpacpi_bluetooth_sw"
 
-static void bluetooth_suspend(pm_message_t state)
-{
-       /* Try to make sure radio will resume powered off */
-       if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd",
-                  TP_ACPI_BLTH_PWR_OFF_ON_RESUME))
-               vdbg_printk(TPACPI_DBG_RFKILL,
-                       "bluetooth power down on resume request failed\n");
-}
-
 static int bluetooth_get_status(void)
 {
        int status;
@@ -3905,10 +3917,9 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state)
 #endif
 
        /* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */
+       status = TP_ACPI_BLUETOOTH_RESUMECTRL;
        if (state == TPACPI_RFK_RADIO_ON)
-               status = TP_ACPI_BLUETOOTH_RADIOSSW;
-       else
-               status = 0;
+               status |= TP_ACPI_BLUETOOTH_RADIOSSW;
 
        if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
                return -EIO;
@@ -4032,9 +4043,9 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
 }
 
 /* procfs -------------------------------------------------------------- */
-static int bluetooth_read(char *p)
+static int bluetooth_read(struct seq_file *m)
 {
-       return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, p);
+       return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m);
 }
 
 static int bluetooth_write(char *buf)
@@ -4047,7 +4058,6 @@ static struct ibm_struct bluetooth_driver_data = {
        .read = bluetooth_read,
        .write = bluetooth_write,
        .exit = bluetooth_exit,
-       .suspend = bluetooth_suspend,
        .shutdown = bluetooth_shutdown,
 };
 
@@ -4065,15 +4075,6 @@ enum {
 
 #define TPACPI_RFK_WWAN_SW_NAME                "tpacpi_wwan_sw"
 
-static void wan_suspend(pm_message_t state)
-{
-       /* Try to make sure radio will resume powered off */
-       if (!acpi_evalf(NULL, NULL, "\\WGSV", "qvd",
-                  TP_ACPI_WGSV_PWR_OFF_ON_RESUME))
-               vdbg_printk(TPACPI_DBG_RFKILL,
-                       "WWAN power down on resume request failed\n");
-}
-
 static int wan_get_status(void)
 {
        int status;
@@ -4106,11 +4107,10 @@ static int wan_set_status(enum tpacpi_rfkill_state state)
        }
 #endif
 
-       /* We make sure to keep TP_ACPI_WANCARD_RESUMECTRL off */
+       /* We make sure to set TP_ACPI_WANCARD_RESUMECTRL */
+       status = TP_ACPI_WANCARD_RESUMECTRL;
        if (state == TPACPI_RFK_RADIO_ON)
-               status = TP_ACPI_WANCARD_RADIOSSW;
-       else
-               status = 0;
+               status |= TP_ACPI_WANCARD_RADIOSSW;
 
        if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
                return -EIO;
@@ -4233,9 +4233,9 @@ static int __init wan_init(struct ibm_init_struct *iibm)
 }
 
 /* procfs -------------------------------------------------------------- */
-static int wan_read(char *p)
+static int wan_read(struct seq_file *m)
 {
-       return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, p);
+       return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m);
 }
 
 static int wan_write(char *buf)
@@ -4248,7 +4248,6 @@ static struct ibm_struct wan_driver_data = {
        .read = wan_read,
        .write = wan_write,
        .exit = wan_exit,
-       .suspend = wan_suspend,
        .shutdown = wan_shutdown,
 };
 
@@ -4611,14 +4610,13 @@ static int video_expand_toggle(void)
        /* not reached */
 }
 
-static int video_read(char *p)
+static int video_read(struct seq_file *m)
 {
        int status, autosw;
-       int len = 0;
 
        if (video_supported == TPACPI_VIDEO_NONE) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-               return len;
+               seq_printf(m, "status:\t\tnot supported\n");
+               return 0;
        }
 
        status = video_outputsw_get();
@@ -4629,20 +4627,20 @@ static int video_read(char *p)
        if (autosw < 0)
                return autosw;
 
-       len += sprintf(p + len, "status:\t\tsupported\n");
-       len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
-       len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
+       seq_printf(m, "status:\t\tsupported\n");
+       seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0));
+       seq_printf(m, "crt:\t\t%s\n", enabled(status, 1));
        if (video_supported == TPACPI_VIDEO_NEW)
-               len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
-       len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
-       len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
-       len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
+               seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3));
+       seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0));
+       seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
+       seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
        if (video_supported == TPACPI_VIDEO_NEW)
-               len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
-       len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
-       len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
+               seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
+       seq_printf(m, "commands:\tauto_enable, auto_disable\n");
+       seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
 
-       return len;
+       return 0;
 }
 
 static int video_write(char *buf)
@@ -4834,25 +4832,24 @@ static void light_exit(void)
                flush_workqueue(tpacpi_wq);
 }
 
-static int light_read(char *p)
+static int light_read(struct seq_file *m)
 {
-       int len = 0;
        int status;
 
        if (!tp_features.light) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
+               seq_printf(m, "status:\t\tnot supported\n");
        } else if (!tp_features.light_status) {
-               len += sprintf(p + len, "status:\t\tunknown\n");
-               len += sprintf(p + len, "commands:\ton, off\n");
+               seq_printf(m, "status:\t\tunknown\n");
+               seq_printf(m, "commands:\ton, off\n");
        } else {
                status = light_get_status();
                if (status < 0)
                        return status;
-               len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
-               len += sprintf(p + len, "commands:\ton, off\n");
+               seq_printf(m, "status:\t\t%s\n", onoff(status, 0));
+               seq_printf(m, "commands:\ton, off\n");
        }
 
-       return len;
+       return 0;
 }
 
 static int light_write(char *buf)
@@ -4930,20 +4927,18 @@ static void cmos_exit(void)
        device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
 }
 
-static int cmos_read(char *p)
+static int cmos_read(struct seq_file *m)
 {
-       int len = 0;
-
        /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
           R30, R31, T20-22, X20-21 */
        if (!cmos_handle)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
+               seq_printf(m, "status:\t\tnot supported\n");
        else {
-               len += sprintf(p + len, "status:\t\tsupported\n");
-               len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
+               seq_printf(m, "status:\t\tsupported\n");
+               seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
        }
 
-       return len;
+       return 0;
 }
 
 static int cmos_write(char *buf)
@@ -5318,15 +5313,13 @@ static int __init led_init(struct ibm_init_struct *iibm)
        ((s) == TPACPI_LED_OFF ? "off" : \
                ((s) == TPACPI_LED_ON ? "on" : "blinking"))
 
-static int led_read(char *p)
+static int led_read(struct seq_file *m)
 {
-       int len = 0;
-
        if (!led_supported) {
-               len += sprintf(p + len, "status:\t\tnot supported\n");
-               return len;
+               seq_printf(m, "status:\t\tnot supported\n");
+               return 0;
        }
-       len += sprintf(p + len, "status:\t\tsupported\n");
+       seq_printf(m, "status:\t\tsupported\n");
 
        if (led_supported == TPACPI_LED_570) {
                /* 570 */
@@ -5335,15 +5328,15 @@ static int led_read(char *p)
                        status = led_get_status(i);
                        if (status < 0)
                                return -EIO;
-                       len += sprintf(p + len, "%d:\t\t%s\n",
+                       seq_printf(m, "%d:\t\t%s\n",
                                       i, str_led_status(status));
                }
        }
 
-       len += sprintf(p + len, "commands:\t"
+       seq_printf(m, "commands:\t"
                       "<led> on, <led> off, <led> blink (<led> is 0-15)\n");
 
-       return len;
+       return 0;
 }
 
 static int led_write(char *buf)
@@ -5416,18 +5409,16 @@ static int __init beep_init(struct ibm_init_struct *iibm)
        return (beep_handle)? 0 : 1;
 }
 
-static int beep_read(char *p)
+static int beep_read(struct seq_file *m)
 {
-       int len = 0;
-
        if (!beep_handle)
-               len += sprintf(p + len, "status:\t\tnot supported\n");
+               seq_printf(m, "status:\t\tnot supported\n");
        else {
-               len += sprintf(p + len, "status:\t\tsupported\n");
-               len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
+               seq_printf(m, "status:\t\tsupported\n");
+               seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
        }
 
-       return len;
+       return 0;
 }
 
 static int beep_write(char *buf)
@@ -5480,8 +5471,11 @@ enum { /* TPACPI_THERMAL_TPEC_* */
        TP_EC_THERMAL_TMP0 = 0x78,      /* ACPI EC regs TMP 0..7 */
        TP_EC_THERMAL_TMP8 = 0xC0,      /* ACPI EC regs TMP 8..15 */
        TP_EC_THERMAL_TMP_NA = -128,    /* ACPI EC sensor not available */
+
+       TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */
 };
 
+
 #define TPACPI_MAX_THERMAL_SENSORS 16  /* Max thermal sensors supported */
 struct ibm_thermal_sensors_struct {
        s32 temp[TPACPI_MAX_THERMAL_SENSORS];
@@ -5571,6 +5565,28 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
        return n;
 }
 
+static void thermal_dump_all_sensors(void)
+{
+       int n, i;
+       struct ibm_thermal_sensors_struct t;
+
+       n = thermal_get_sensors(&t);
+       if (n <= 0)
+               return;
+
+       printk(TPACPI_NOTICE
+               "temperatures (Celsius):");
+
+       for (i = 0; i < n; i++) {
+               if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA)
+                       printk(KERN_CONT " %d", (int)(t.temp[i] / 1000));
+               else
+                       printk(KERN_CONT " N/A");
+       }
+
+       printk(KERN_CONT "\n");
+}
+
 /* sysfs temp##_input -------------------------------------------------- */
 
 static ssize_t thermal_temp_input_show(struct device *dev,
@@ -5586,7 +5602,7 @@ static ssize_t thermal_temp_input_show(struct device *dev,
        res = thermal_get_sensor(idx, &value);
        if (res)
                return res;
-       if (value == TP_EC_THERMAL_TMP_NA * 1000)
+       if (value == TPACPI_THERMAL_SENSOR_NA)
                return -ENXIO;
 
        return snprintf(buf, PAGE_SIZE, "%d\n", value);
@@ -5763,9 +5779,8 @@ static void thermal_exit(void)
        }
 }
 
-static int thermal_read(char *p)
+static int thermal_read(struct seq_file *m)
 {
-       int len = 0;
        int n, i;
        struct ibm_thermal_sensors_struct t;
 
@@ -5773,16 +5788,16 @@ static int thermal_read(char *p)
        if (unlikely(n < 0))
                return n;
 
-       len += sprintf(p + len, "temperatures:\t");
+       seq_printf(m, "temperatures:\t");
 
        if (n > 0) {
                for (i = 0; i < (n - 1); i++)
-                       len += sprintf(p + len, "%d ", t.temp[i] / 1000);
-               len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
+                       seq_printf(m, "%d ", t.temp[i] / 1000);
+               seq_printf(m, "%d\n", t.temp[i] / 1000);
        } else
-               len += sprintf(p + len, "not supported\n");
+               seq_printf(m, "not supported\n");
 
-       return len;
+       return 0;
 }
 
 static struct ibm_struct thermal_driver_data = {
@@ -5797,39 +5812,38 @@ static struct ibm_struct thermal_driver_data = {
 
 static u8 ecdump_regs[256];
 
-static int ecdump_read(char *p)
+static int ecdump_read(struct seq_file *m)
 {
-       int len = 0;
        int i, j;
        u8 v;
 
-       len += sprintf(p + len, "EC      "
+       seq_printf(m, "EC      "
                       " +00 +01 +02 +03 +04 +05 +06 +07"
                       " +08 +09 +0a +0b +0c +0d +0e +0f\n");
        for (i = 0; i < 256; i += 16) {
-               len += sprintf(p + len, "EC 0x%02x:", i);
+               seq_printf(m, "EC 0x%02x:", i);
                for (j = 0; j < 16; j++) {
                        if (!acpi_ec_read(i + j, &v))
                                break;
                        if (v != ecdump_regs[i + j])
-                               len += sprintf(p + len, " *%02x", v);
+                               seq_printf(m, " *%02x", v);
                        else
-                               len += sprintf(p + len, "  %02x", v);
+                               seq_printf(m, "  %02x", v);
                        ecdump_regs[i + j] = v;
                }
-               len += sprintf(p + len, "\n");
+               seq_putc(m, '\n');
                if (j != 16)
                        break;
        }
 
        /* These are way too dangerous to advertise openly... */
 #if 0
-       len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
+       seq_printf(m, "commands:\t0x<offset> 0x<value>"
                       " (<offset> is 00-ff, <value> is 00-ff)\n");
-       len += sprintf(p + len, "commands:\t0x<offset> <value>  "
+       seq_printf(m, "commands:\t0x<offset> <value>  "
                       " (<offset> is 00-ff, <value> is 0-255)\n");
 #endif
-       return len;
+       return 0;
 }
 
 static int ecdump_write(char *buf)
@@ -6092,6 +6106,12 @@ static int brightness_get(struct backlight_device *bd)
        return status & TP_EC_BACKLIGHT_LVLMSK;
 }
 
+static void tpacpi_brightness_notify_change(void)
+{
+       backlight_force_update(ibm_backlight_device,
+                              BACKLIGHT_UPDATE_HOTKEY);
+}
+
 static struct backlight_ops ibm_backlight_data = {
        .get_brightness = brightness_get,
        .update_status  = brightness_update_status,
@@ -6120,8 +6140,8 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
 
        /* Models with Intel Extreme Graphics 2 */
        TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC),
-       TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC),
-       TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC),
+       TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+       TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
 
        /* Models with Intel GMA900 */
        TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC),    /* T43, R52 */
@@ -6246,6 +6266,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
        backlight_update_status(ibm_backlight_device);
 
+       vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+                       "brightness: registering brightness hotkeys "
+                       "as change notification\n");
+       tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+                               | TP_ACPI_HKEY_BRGHTUP_MASK
+                               | TP_ACPI_HKEY_BRGHTDWN_MASK);;
        return 0;
 }
 
@@ -6270,23 +6296,22 @@ static void brightness_exit(void)
        tpacpi_brightness_checkpoint_nvram();
 }
 
-static int brightness_read(char *p)
+static int brightness_read(struct seq_file *m)
 {
-       int len = 0;
        int level;
 
        level = brightness_get(NULL);
        if (level < 0) {
-               len += sprintf(p + len, "level:\t\tunreadable\n");
+               seq_printf(m, "level:\t\tunreadable\n");
        } else {
-               len += sprintf(p + len, "level:\t\t%d\n", level);
-               len += sprintf(p + len, "commands:\tup, down\n");
-               len += sprintf(p + len, "commands:\tlevel <level>"
+               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);
        }
 
-       return len;
+       return 0;
 }
 
 static int brightness_write(char *buf)
@@ -6322,6 +6347,9 @@ static int brightness_write(char *buf)
         * Doing it this way makes the syscall restartable in case of EINTR
         */
        rc = brightness_set(level);
+       if (!rc && ibm_backlight_device)
+               backlight_force_update(ibm_backlight_device,
+                                       BACKLIGHT_UPDATE_SYSFS);
        return (rc == -EINTR)? -ERESTARTSYS : rc;
 }
 
@@ -6338,101 +6366,685 @@ static struct ibm_struct brightness_driver_data = {
  * Volume subdriver
  */
 
-static int volume_offset = 0x30;
+/*
+ * IBM ThinkPads have a simple volume controller with MUTE gating.
+ * Very early Lenovo ThinkPads follow the IBM ThinkPad spec.
+ *
+ * Since the *61 series (and probably also the later *60 series), Lenovo
+ * ThinkPads only implement the MUTE gate.
+ *
+ * EC register 0x30
+ *   Bit 6: MUTE (1 mutes sound)
+ *   Bit 3-0: Volume
+ *   Other bits should be zero as far as we know.
+ *
+ * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and
+ * bits 3-0 (volume).  Other bits in NVRAM may have other functions,
+ * such as bit 7 which is used to detect repeated presses of MUTE,
+ * and we leave them unchanged.
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
+
+#define TPACPI_ALSA_DRVNAME  "ThinkPad EC"
+#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
+#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
+
+static int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */
+static char *alsa_id = "ThinkPadEC";
+static int alsa_enable = SNDRV_DEFAULT_ENABLE1;
+
+struct tpacpi_alsa_data {
+       struct snd_card *card;
+       struct snd_ctl_elem_id *ctl_mute_id;
+       struct snd_ctl_elem_id *ctl_vol_id;
+};
 
-static int volume_read(char *p)
+static struct snd_card *alsa_card;
+
+enum {
+       TP_EC_AUDIO = 0x30,
+
+       /* TP_EC_AUDIO bits */
+       TP_EC_AUDIO_MUTESW = 6,
+
+       /* TP_EC_AUDIO bitmasks */
+       TP_EC_AUDIO_LVL_MSK = 0x0F,
+       TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW),
+
+       /* Maximum volume */
+       TP_EC_VOLUME_MAX = 14,
+};
+
+enum tpacpi_volume_access_mode {
+       TPACPI_VOL_MODE_AUTO = 0,       /* Not implemented yet */
+       TPACPI_VOL_MODE_EC,             /* Pure EC control */
+       TPACPI_VOL_MODE_UCMS_STEP,      /* UCMS step-based control: N/A */
+       TPACPI_VOL_MODE_ECNVRAM,        /* EC control w/ NVRAM store */
+       TPACPI_VOL_MODE_MAX
+};
+
+enum tpacpi_volume_capabilities {
+       TPACPI_VOL_CAP_AUTO = 0,        /* Use white/blacklist */
+       TPACPI_VOL_CAP_VOLMUTE,         /* Output vol and mute */
+       TPACPI_VOL_CAP_MUTEONLY,        /* Output mute only */
+       TPACPI_VOL_CAP_MAX
+};
+
+static enum tpacpi_volume_access_mode volume_mode =
+       TPACPI_VOL_MODE_MAX;
+
+static enum tpacpi_volume_capabilities volume_capabilities;
+static int volume_control_allowed;
+
+/*
+ * Used to syncronize writers to TP_EC_AUDIO and
+ * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
+ */
+static struct mutex volume_mutex;
+
+static void tpacpi_volume_checkpoint_nvram(void)
 {
-       int len = 0;
-       u8 level;
+       u8 lec = 0;
+       u8 b_nvram;
+       u8 ec_mask;
 
-       if (!acpi_ec_read(volume_offset, &level)) {
-               len += sprintf(p + len, "level:\t\tunreadable\n");
+       if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
+               return;
+       if (!volume_control_allowed)
+               return;
+
+       vdbg_printk(TPACPI_DBG_MIXER,
+               "trying to checkpoint mixer state to NVRAM...\n");
+
+       if (tp_features.mixer_no_level_control)
+               ec_mask = TP_EC_AUDIO_MUTESW_MSK;
+       else
+               ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
+
+       if (mutex_lock_killable(&volume_mutex) < 0)
+               return;
+
+       if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec)))
+               goto unlock;
+       lec &= ec_mask;
+       b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+
+       if (lec != (b_nvram & ec_mask)) {
+               /* NVRAM needs update */
+               b_nvram &= ~ec_mask;
+               b_nvram |= lec;
+               nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER);
+               dbg_printk(TPACPI_DBG_MIXER,
+                          "updated NVRAM mixer status to 0x%02x (0x%02x)\n",
+                          (unsigned int) lec, (unsigned int) b_nvram);
        } else {
-               len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
-               len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
-               len += sprintf(p + len, "commands:\tup, down, mute\n");
-               len += sprintf(p + len, "commands:\tlevel <level>"
-                              " (<level> is 0-15)\n");
+               vdbg_printk(TPACPI_DBG_MIXER,
+                          "NVRAM mixer status already is 0x%02x (0x%02x)\n",
+                          (unsigned int) lec, (unsigned int) b_nvram);
        }
 
-       return len;
+unlock:
+       mutex_unlock(&volume_mutex);
 }
 
-static int volume_write(char *buf)
+static int volume_get_status_ec(u8 *status)
 {
-       int cmos_cmd, inc, i;
-       u8 level, mute;
-       int new_level, new_mute;
-       char *cmd;
+       u8 s;
 
-       while ((cmd = next_cmd(&buf))) {
-               if (!acpi_ec_read(volume_offset, &level))
-                       return -EIO;
-               new_mute = mute = level & 0x40;
-               new_level = level = level & 0xf;
+       if (!acpi_ec_read(TP_EC_AUDIO, &s))
+               return -EIO;
 
-               if (strlencmp(cmd, "up") == 0) {
-                       if (mute)
-                               new_mute = 0;
-                       else
-                               new_level = level == 15 ? 15 : level + 1;
-               } else if (strlencmp(cmd, "down") == 0) {
-                       if (mute)
-                               new_mute = 0;
-                       else
-                               new_level = level == 0 ? 0 : level - 1;
-               } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
-                          new_level >= 0 && new_level <= 15) {
-                       /* new_level set */
-               } else if (strlencmp(cmd, "mute") == 0) {
-                       new_mute = 0x40;
-               } else
-                       return -EINVAL;
+       *status = s;
 
-               if (new_level != level) {
-                       /* mute doesn't change */
+       dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s);
 
-                       cmos_cmd = (new_level > level) ?
-                                       TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN;
-                       inc = new_level > level ? 1 : -1;
+       return 0;
+}
 
-                       if (mute && (issue_thinkpad_cmos_command(cmos_cmd) ||
-                                    !acpi_ec_write(volume_offset, level)))
-                               return -EIO;
+static int volume_get_status(u8 *status)
+{
+       return volume_get_status_ec(status);
+}
+
+static int volume_set_status_ec(const u8 status)
+{
+       if (!acpi_ec_write(TP_EC_AUDIO, status))
+               return -EIO;
 
-                       for (i = level; i != new_level; i += inc)
-                               if (issue_thinkpad_cmos_command(cmos_cmd) ||
-                                   !acpi_ec_write(volume_offset, i + inc))
-                                       return -EIO;
+       dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
 
-                       if (mute &&
-                           (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) ||
-                            !acpi_ec_write(volume_offset, new_level + mute))) {
-                               return -EIO;
-                       }
+       return 0;
+}
+
+static int volume_set_status(const u8 status)
+{
+       return volume_set_status_ec(status);
+}
+
+static int volume_set_mute_ec(const bool mute)
+{
+       int rc;
+       u8 s, n;
+
+       if (mutex_lock_killable(&volume_mutex) < 0)
+               return -EINTR;
+
+       rc = volume_get_status_ec(&s);
+       if (rc)
+               goto unlock;
+
+       n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
+                    s & ~TP_EC_AUDIO_MUTESW_MSK;
+
+       if (n != s)
+               rc = volume_set_status_ec(n);
+
+unlock:
+       mutex_unlock(&volume_mutex);
+       return rc;
+}
+
+static int volume_set_mute(const bool mute)
+{
+       dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
+                  (mute) ? "" : "un");
+       return volume_set_mute_ec(mute);
+}
+
+static int volume_set_volume_ec(const u8 vol)
+{
+       int rc;
+       u8 s, n;
+
+       if (vol > TP_EC_VOLUME_MAX)
+               return -EINVAL;
+
+       if (mutex_lock_killable(&volume_mutex) < 0)
+               return -EINTR;
+
+       rc = volume_get_status_ec(&s);
+       if (rc)
+               goto unlock;
+
+       n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
+
+       if (n != s)
+               rc = volume_set_status_ec(n);
+
+unlock:
+       mutex_unlock(&volume_mutex);
+       return rc;
+}
+
+static int volume_set_volume(const u8 vol)
+{
+       dbg_printk(TPACPI_DBG_MIXER,
+                  "trying to set volume level to %hu\n", vol);
+       return volume_set_volume_ec(vol);
+}
+
+static void volume_alsa_notify_change(void)
+{
+       struct tpacpi_alsa_data *d;
+
+       if (alsa_card && alsa_card->private_data) {
+               d = alsa_card->private_data;
+               if (d->ctl_mute_id)
+                       snd_ctl_notify(alsa_card,
+                                       SNDRV_CTL_EVENT_MASK_VALUE,
+                                       d->ctl_mute_id);
+               if (d->ctl_vol_id)
+                       snd_ctl_notify(alsa_card,
+                                       SNDRV_CTL_EVENT_MASK_VALUE,
+                                       d->ctl_vol_id);
+       }
+}
+
+static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = TP_EC_VOLUME_MAX;
+       return 0;
+}
+
+static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       u8 s;
+       int rc;
+
+       rc = volume_get_status(&s);
+       if (rc < 0)
+               return rc;
+
+       ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK;
+       return 0;
+}
+
+static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       return volume_set_volume(ucontrol->value.integer.value[0]);
+}
+
+#define volume_alsa_mute_info snd_ctl_boolean_mono_info
+
+static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       u8 s;
+       int rc;
+
+       rc = volume_get_status(&s);
+       if (rc < 0)
+               return rc;
+
+       ucontrol->value.integer.value[0] =
+                               (s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1;
+       return 0;
+}
+
+static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       return volume_set_mute(!ucontrol->value.integer.value[0]);
+}
+
+static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Console Playback Volume",
+       .index = 0,
+       .access = SNDRV_CTL_ELEM_ACCESS_READ,
+       .info = volume_alsa_vol_info,
+       .get = volume_alsa_vol_get,
+};
+
+static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Console Playback Switch",
+       .index = 0,
+       .access = SNDRV_CTL_ELEM_ACCESS_READ,
+       .info = volume_alsa_mute_info,
+       .get = volume_alsa_mute_get,
+};
+
+static void volume_suspend(pm_message_t state)
+{
+       tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_resume(void)
+{
+       volume_alsa_notify_change();
+}
+
+static void volume_shutdown(void)
+{
+       tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_exit(void)
+{
+       if (alsa_card) {
+               snd_card_free(alsa_card);
+               alsa_card = NULL;
+       }
+
+       tpacpi_volume_checkpoint_nvram();
+}
+
+static int __init volume_create_alsa_mixer(void)
+{
+       struct snd_card *card;
+       struct tpacpi_alsa_data *data;
+       struct snd_kcontrol *ctl_vol;
+       struct snd_kcontrol *ctl_mute;
+       int rc;
+
+       rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE,
+                           sizeof(struct tpacpi_alsa_data), &card);
+       if (rc < 0 || !card) {
+               printk(TPACPI_ERR
+                       "Failed to create ALSA card structures: %d\n", rc);
+               return 1;
+       }
+
+       BUG_ON(!card->private_data);
+       data = card->private_data;
+       data->card = card;
+
+       strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
+               sizeof(card->driver));
+       strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
+               sizeof(card->shortname));
+       snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
+                (thinkpad_id.ec_version_str) ?
+                       thinkpad_id.ec_version_str : "(unknown)");
+       snprintf(card->longname, sizeof(card->longname),
+                "%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO,
+                (thinkpad_id.ec_version_str) ?
+                       thinkpad_id.ec_version_str : "unknown");
+
+       if (volume_control_allowed) {
+               volume_alsa_control_vol.put = volume_alsa_vol_put;
+               volume_alsa_control_vol.access =
+                               SNDRV_CTL_ELEM_ACCESS_READWRITE;
+
+               volume_alsa_control_mute.put = volume_alsa_mute_put;
+               volume_alsa_control_mute.access =
+                               SNDRV_CTL_ELEM_ACCESS_READWRITE;
+       }
+
+       if (!tp_features.mixer_no_level_control) {
+               ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
+               rc = snd_ctl_add(card, ctl_vol);
+               if (rc < 0) {
+                       printk(TPACPI_ERR
+                               "Failed to create ALSA volume control: %d\n",
+                               rc);
+                       goto err_exit;
                }
+               data->ctl_vol_id = &ctl_vol->id;
+       }
 
-               if (new_mute != mute) {
-                       /* level doesn't change */
+       ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
+       rc = snd_ctl_add(card, ctl_mute);
+       if (rc < 0) {
+               printk(TPACPI_ERR "Failed to create ALSA mute control: %d\n",
+                       rc);
+               goto err_exit;
+       }
+       data->ctl_mute_id = &ctl_mute->id;
 
-                       cmos_cmd = (new_mute) ?
-                                  TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
+       snd_card_set_dev(card, &tpacpi_pdev->dev);
+       rc = snd_card_register(card);
+       if (rc < 0) {
+               printk(TPACPI_ERR "Failed to register ALSA card: %d\n", rc);
+               goto err_exit;
+       }
 
-                       if (issue_thinkpad_cmos_command(cmos_cmd) ||
-                           !acpi_ec_write(volume_offset, level + new_mute))
-                               return -EIO;
+       alsa_card = card;
+       return 0;
+
+err_exit:
+       snd_card_free(card);
+       return 1;
+}
+
+#define TPACPI_VOL_Q_MUTEONLY  0x0001  /* Mute-only control available */
+#define TPACPI_VOL_Q_LEVEL     0x0002  /* Volume control available */
+
+static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
+       /* Whitelist volume level on all IBM by default */
+       { .vendor = PCI_VENDOR_ID_IBM,
+         .bios   = TPACPI_MATCH_ANY,
+         .ec     = TPACPI_MATCH_ANY,
+         .quirks = TPACPI_VOL_Q_LEVEL },
+
+       /* Lenovo models with volume control (needs confirmation) */
+       TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */
+       TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */
+       TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */
+       TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */
+       TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */
+       TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */
+       TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */
+
+       /* Whitelist mute-only on all Lenovo by default */
+       { .vendor = PCI_VENDOR_ID_LENOVO,
+         .bios   = TPACPI_MATCH_ANY,
+         .ec     = TPACPI_MATCH_ANY,
+         .quirks = TPACPI_VOL_Q_MUTEONLY }
+};
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+       unsigned long quirks;
+       int rc;
+
+       vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
+
+       mutex_init(&volume_mutex);
+
+       /*
+        * Check for module parameter bogosity, note that we
+        * init volume_mode to TPACPI_VOL_MODE_MAX in order to be
+        * able to detect "unspecified"
+        */
+       if (volume_mode > TPACPI_VOL_MODE_MAX)
+               return -EINVAL;
+
+       if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
+               printk(TPACPI_ERR
+                       "UCMS step volume mode not implemented, "
+                       "please contact %s\n", TPACPI_MAIL);
+               return 1;
+       }
+
+       if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
+               return -EINVAL;
+
+       /*
+        * The ALSA mixer is our primary interface.
+        * When disabled, don't install the subdriver at all
+        */
+       if (!alsa_enable) {
+               vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+                           "ALSA mixer disabled by parameter, "
+                           "not loading volume subdriver...\n");
+               return 1;
+       }
+
+       quirks = tpacpi_check_quirks(volume_quirk_table,
+                                    ARRAY_SIZE(volume_quirk_table));
+
+       switch (volume_capabilities) {
+       case TPACPI_VOL_CAP_AUTO:
+               if (quirks & TPACPI_VOL_Q_MUTEONLY)
+                       tp_features.mixer_no_level_control = 1;
+               else if (quirks & TPACPI_VOL_Q_LEVEL)
+                       tp_features.mixer_no_level_control = 0;
+               else
+                       return 1; /* no mixer */
+               break;
+       case TPACPI_VOL_CAP_VOLMUTE:
+               tp_features.mixer_no_level_control = 0;
+               break;
+       case TPACPI_VOL_CAP_MUTEONLY:
+               tp_features.mixer_no_level_control = 1;
+               break;
+       default:
+               return 1;
+       }
+
+       if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
+               dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+                               "using user-supplied volume_capabilities=%d\n",
+                               volume_capabilities);
+
+       if (volume_mode == TPACPI_VOL_MODE_AUTO ||
+           volume_mode == TPACPI_VOL_MODE_MAX) {
+               volume_mode = TPACPI_VOL_MODE_ECNVRAM;
+
+               dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+                               "driver auto-selected volume_mode=%d\n",
+                               volume_mode);
+       } else {
+               dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+                               "using user-supplied volume_mode=%d\n",
+                               volume_mode);
+       }
+
+       vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+                       "mute is supported, volume control is %s\n",
+                       str_supported(!tp_features.mixer_no_level_control));
+
+       rc = volume_create_alsa_mixer();
+       if (rc) {
+               printk(TPACPI_ERR
+                       "Could not create the ALSA mixer interface\n");
+               return rc;
+       }
+
+       printk(TPACPI_INFO
+               "Console audio control enabled, mode: %s\n",
+               (volume_control_allowed) ?
+                       "override (read/write)" :
+                       "monitor (read only)");
+
+       vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+               "registering volume hotkeys as change notification\n");
+       tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+                       | TP_ACPI_HKEY_VOLUP_MASK
+                       | TP_ACPI_HKEY_VOLDWN_MASK
+                       | TP_ACPI_HKEY_MUTE_MASK);
+
+       return 0;
+}
+
+static int volume_read(struct seq_file *m)
+{
+       u8 status;
+
+       if (volume_get_status(&status) < 0) {
+               seq_printf(m, "level:\t\tunreadable\n");
+       } else {
+               if (tp_features.mixer_no_level_control)
+                       seq_printf(m, "level:\t\tunsupported\n");
+               else
+                       seq_printf(m, "level:\t\t%d\n",
+                                       status & TP_EC_AUDIO_LVL_MSK);
+
+               seq_printf(m, "mute:\t\t%s\n",
+                               onoff(status, TP_EC_AUDIO_MUTESW));
+
+               if (volume_control_allowed) {
+                       seq_printf(m, "commands:\tunmute, mute\n");
+                       if (!tp_features.mixer_no_level_control) {
+                               seq_printf(m,
+                                              "commands:\tup, down\n");
+                               seq_printf(m,
+                                              "commands:\tlevel <level>"
+                                              " (<level> is 0-%d)\n",
+                                              TP_EC_VOLUME_MAX);
+                       }
                }
        }
 
        return 0;
 }
 
+static int volume_write(char *buf)
+{
+       u8 s;
+       u8 new_level, new_mute;
+       int l;
+       char *cmd;
+       int rc;
+
+       /*
+        * We do allow volume control at driver startup, so that the
+        * user can set initial state through the volume=... parameter hack.
+        */
+       if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) {
+               if (unlikely(!tp_warned.volume_ctrl_forbidden)) {
+                       tp_warned.volume_ctrl_forbidden = 1;
+                       printk(TPACPI_NOTICE
+                               "Console audio control in monitor mode, "
+                               "changes are not allowed.\n");
+                       printk(TPACPI_NOTICE
+                               "Use the volume_control=1 module parameter "
+                               "to enable volume control\n");
+               }
+               return -EPERM;
+       }
+
+       rc = volume_get_status(&s);
+       if (rc < 0)
+               return rc;
+
+       new_level = s & TP_EC_AUDIO_LVL_MSK;
+       new_mute  = s & TP_EC_AUDIO_MUTESW_MSK;
+
+       while ((cmd = next_cmd(&buf))) {
+               if (!tp_features.mixer_no_level_control) {
+                       if (strlencmp(cmd, "up") == 0) {
+                               if (new_mute)
+                                       new_mute = 0;
+                               else if (new_level < TP_EC_VOLUME_MAX)
+                                       new_level++;
+                               continue;
+                       } else if (strlencmp(cmd, "down") == 0) {
+                               if (new_mute)
+                                       new_mute = 0;
+                               else if (new_level > 0)
+                                       new_level--;
+                               continue;
+                       } else if (sscanf(cmd, "level %u", &l) == 1 &&
+                                  l >= 0 && l <= TP_EC_VOLUME_MAX) {
+                                       new_level = l;
+                               continue;
+                       }
+               }
+               if (strlencmp(cmd, "mute") == 0)
+                       new_mute = TP_EC_AUDIO_MUTESW_MSK;
+               else if (strlencmp(cmd, "unmute") == 0)
+                       new_mute = 0;
+               else
+                       return -EINVAL;
+       }
+
+       if (tp_features.mixer_no_level_control) {
+               tpacpi_disclose_usertask("procfs volume", "%smute\n",
+                                       new_mute ? "" : "un");
+               rc = volume_set_mute(!!new_mute);
+       } else {
+               tpacpi_disclose_usertask("procfs volume",
+                                       "%smute and set level to %d\n",
+                                       new_mute ? "" : "un", new_level);
+               rc = volume_set_status(new_mute | new_level);
+       }
+       volume_alsa_notify_change();
+
+       return (rc == -EINTR) ? -ERESTARTSYS : rc;
+}
+
 static struct ibm_struct volume_driver_data = {
        .name = "volume",
        .read = volume_read,
        .write = volume_write,
+       .exit = volume_exit,
+       .suspend = volume_suspend,
+       .resume = volume_resume,
+       .shutdown = volume_shutdown,
 };
 
+#else /* !CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+#define alsa_card NULL
+
+static void inline volume_alsa_notify_change(void)
+{
+}
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+       printk(TPACPI_INFO
+               "volume: disabled as there is no ALSA support in this kernel\n");
+
+       return 1;
+}
+
+static struct ibm_struct volume_driver_data = {
+       .name = "volume",
+};
+
+#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
 /*************************************************************************
  * Fan subdriver
  */
@@ -7507,9 +8119,8 @@ static void fan_resume(void)
        }
 }
 
-static int fan_read(char *p)
+static int fan_read(struct seq_file *m)
 {
-       int len = 0;
        int rc;
        u8 status;
        unsigned int speed = 0;
@@ -7521,7 +8132,7 @@ static int fan_read(char *p)
                if (rc < 0)
                        return rc;
 
-               len += sprintf(p + len, "status:\t\t%s\n"
+               seq_printf(m, "status:\t\t%s\n"
                               "level:\t\t%d\n",
                               (status != 0) ? "enabled" : "disabled", status);
                break;
@@ -7532,54 +8143,54 @@ static int fan_read(char *p)
                if (rc < 0)
                        return rc;
 
-               len += sprintf(p + len, "status:\t\t%s\n",
+               seq_printf(m, "status:\t\t%s\n",
                               (status != 0) ? "enabled" : "disabled");
 
                rc = fan_get_speed(&speed);
                if (rc < 0)
                        return rc;
 
-               len += sprintf(p + len, "speed:\t\t%d\n", speed);
+               seq_printf(m, "speed:\t\t%d\n", speed);
 
                if (status & TP_EC_FAN_FULLSPEED)
                        /* Disengaged mode takes precedence */
-                       len += sprintf(p + len, "level:\t\tdisengaged\n");
+                       seq_printf(m, "level:\t\tdisengaged\n");
                else if (status & TP_EC_FAN_AUTO)
-                       len += sprintf(p + len, "level:\t\tauto\n");
+                       seq_printf(m, "level:\t\tauto\n");
                else
-                       len += sprintf(p + len, "level:\t\t%d\n", status);
+                       seq_printf(m, "level:\t\t%d\n", status);
                break;
 
        case TPACPI_FAN_NONE:
        default:
-               len += sprintf(p + len, "status:\t\tnot supported\n");
+               seq_printf(m, "status:\t\tnot supported\n");
        }
 
        if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
-               len += sprintf(p + len, "commands:\tlevel <level>");
+               seq_printf(m, "commands:\tlevel <level>");
 
                switch (fan_control_access_mode) {
                case TPACPI_FAN_WR_ACPI_SFAN:
-                       len += sprintf(p + len, " (<level> is 0-7)\n");
+                       seq_printf(m, " (<level> is 0-7)\n");
                        break;
 
                default:
-                       len += sprintf(p + len, " (<level> is 0-7, "
+                       seq_printf(m, " (<level> is 0-7, "
                                       "auto, disengaged, full-speed)\n");
                        break;
                }
        }
 
        if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
-               len += sprintf(p + len, "commands:\tenable, disable\n"
+               seq_printf(m, "commands:\tenable, disable\n"
                               "commands:\twatchdog <timeout> (<timeout> "
                               "is 0 (off), 1-120 (seconds))\n");
 
        if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
-               len += sprintf(p + len, "commands:\tspeed <speed>"
+               seq_printf(m, "commands:\tspeed <speed>"
                               " (<speed> is 0-65535)\n");
 
-       return len;
+       return 0;
 }
 
 static int fan_write_cmd_level(const char *cmd, int *rc)
@@ -7721,10 +8332,23 @@ static struct ibm_struct fan_driver_data = {
  */
 static void tpacpi_driver_event(const unsigned int hkey_event)
 {
+       if (ibm_backlight_device) {
+               switch (hkey_event) {
+               case TP_HKEY_EV_BRGHT_UP:
+               case TP_HKEY_EV_BRGHT_DOWN:
+                       tpacpi_brightness_notify_change();
+               }
+       }
+       if (alsa_card) {
+               switch (hkey_event) {
+               case TP_HKEY_EV_VOL_UP:
+               case TP_HKEY_EV_VOL_DOWN:
+               case TP_HKEY_EV_VOL_MUTE:
+                       volume_alsa_notify_change();
+               }
+       }
 }
 
-
-
 static void hotkey_driver_event(const unsigned int scancode)
 {
        tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
@@ -7853,19 +8477,19 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
                "%s installed\n", ibm->name);
 
        if (ibm->read) {
-               entry = create_proc_entry(ibm->name,
-                                         S_IFREG | S_IRUGO | S_IWUSR,
-                                         proc_dir);
+               mode_t mode;
+
+               mode = S_IRUGO;
+               if (ibm->write)
+                       mode |= S_IWUSR;
+               entry = proc_create_data(ibm->name, mode, proc_dir,
+                                        &dispatch_proc_fops, ibm);
                if (!entry) {
                        printk(TPACPI_ERR "unable to create proc entry %s\n",
                               ibm->name);
                        ret = -ENODEV;
                        goto err_out;
                }
-               entry->data = ibm;
-               entry->read_proc = &dispatch_procfs_read;
-               if (ibm->write)
-                       entry->write_proc = &dispatch_procfs_write;
                ibm->flags.proc_created = 1;
        }
 
@@ -8077,6 +8701,7 @@ static struct ibm_init_struct ibms_init[] __initdata = {
                .data = &brightness_driver_data,
        },
        {
+               .init = volume_init,
                .data = &volume_driver_data,
        },
        {
@@ -8112,36 +8737,61 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp)
        return -EINVAL;
 }
 
-module_param(experimental, int, 0);
+module_param(experimental, int, 0444);
 MODULE_PARM_DESC(experimental,
                 "Enables experimental features when non-zero");
 
 module_param_named(debug, dbg_level, uint, 0);
 MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
 
-module_param(force_load, bool, 0);
+module_param(force_load, bool, 0444);
 MODULE_PARM_DESC(force_load,
                 "Attempts to load the driver even on a "
                 "mis-identified ThinkPad when true");
 
-module_param_named(fan_control, fan_control_allowed, bool, 0);
+module_param_named(fan_control, fan_control_allowed, bool, 0444);
 MODULE_PARM_DESC(fan_control,
                 "Enables setting fan parameters features when true");
 
-module_param_named(brightness_mode, brightness_mode, uint, 0);
+module_param_named(brightness_mode, brightness_mode, uint, 0444);
 MODULE_PARM_DESC(brightness_mode,
                 "Selects brightness control strategy: "
                 "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM");
 
-module_param(brightness_enable, uint, 0);
+module_param(brightness_enable, uint, 0444);
 MODULE_PARM_DESC(brightness_enable,
                 "Enables backlight control when 1, disables when 0");
 
-module_param(hotkey_report_mode, uint, 0);
+module_param(hotkey_report_mode, uint, 0444);
 MODULE_PARM_DESC(hotkey_report_mode,
                 "used for backwards compatibility with userspace, "
                 "see documentation");
 
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
+module_param_named(volume_mode, volume_mode, uint, 0444);
+MODULE_PARM_DESC(volume_mode,
+                "Selects volume control strategy: "
+                "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
+
+module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
+MODULE_PARM_DESC(volume_capabilities,
+                "Selects the mixer capabilites: "
+                "0=auto, 1=volume and mute, 2=mute only");
+
+module_param_named(volume_control, volume_control_allowed, bool, 0444);
+MODULE_PARM_DESC(volume_control,
+                "Enables software override for the console audio "
+                "control when true");
+
+/* ALSA module API parameters */
+module_param_named(index, alsa_index, int, 0444);
+MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
+module_param_named(id, alsa_id, charp, 0444);
+MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
+module_param_named(enable, alsa_enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer");
+#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
 #define TPACPI_PARAM(feature) \
        module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
        MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
@@ -8160,25 +8810,25 @@ TPACPI_PARAM(volume);
 TPACPI_PARAM(fan);
 
 #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
-module_param(dbg_wlswemul, uint, 0);
+module_param(dbg_wlswemul, uint, 0444);
 MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
 module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
 MODULE_PARM_DESC(wlsw_state,
                 "Initial state of the emulated WLSW switch");
 
-module_param(dbg_bluetoothemul, uint, 0);
+module_param(dbg_bluetoothemul, uint, 0444);
 MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
 module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
 MODULE_PARM_DESC(bluetooth_state,
                 "Initial state of the emulated bluetooth switch");
 
-module_param(dbg_wwanemul, uint, 0);
+module_param(dbg_wwanemul, uint, 0444);
 MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
 module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
 MODULE_PARM_DESC(wwan_state,
                 "Initial state of the emulated WWAN switch");
 
-module_param(dbg_uwbemul, uint, 0);
+module_param(dbg_uwbemul, uint, 0444);
 MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation");
 module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0);
 MODULE_PARM_DESC(uwb_state,
@@ -8371,6 +9021,7 @@ static int __init thinkpad_acpi_module_init(void)
                                                PCI_VENDOR_ID_IBM;
                tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
                tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
+               tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
        }
        for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
                ret = ibm_init(&ibms_init[i]);