thinkpad-acpi: support MUTE-only ThinkPads
authorHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Tue, 15 Dec 2009 23:51:09 +0000 (21:51 -0200)
committerLen Brown <len.brown@intel.com>
Wed, 16 Dec 2009 04:57:26 +0000 (23:57 -0500)
Lenovo removed the extra mixer since the T61 and thereabouts.
Newer Lenovo models only have the mute gate function, and leave
the volume control to the HDA mixer.

Until a way to automatically query the firmware about its audio
control capabilities is discovered (there might not be any), use a
white/black list.

We will likely need to ask T60 (old and new model) and Z60/Z61 users
whether they have volume control to populate the black/white list.
Meanwhile, provide a volume_capabilities parameter that can be used to
override the defaults.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Lorne Applebaum <lorne.applebaum@gmail.com>
Cc: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Len Brown <len.brown@intel.com>
Documentation/laptops/thinkpad-acpi.txt
drivers/platform/x86/thinkpad_acpi.c

index 96687d0..bd87682 100644 (file)
@@ -1098,18 +1098,31 @@ Volume control
 procfs: /proc/acpi/ibm/volume
 
 This feature allows volume control on ThinkPad models with a digital
-volume knob, as well as mute/unmute control.  The available commands are:
+volume knob (when available, not all models have it), as well as
+mute/unmute control.  The available commands are:
 
        echo up   >/proc/acpi/ibm/volume
        echo down >/proc/acpi/ibm/volume
        echo mute >/proc/acpi/ibm/volume
+       echo unmute >/proc/acpi/ibm/volume
        echo 'level <level>' >/proc/acpi/ibm/volume
 
 The <level> number range is 0 to 14 although not all of them may be
 distinct. The unmute the volume after the mute command, use either the
-up or down command (the level command will not unmute the volume).
+up or down command (the level command will not unmute the volume), or
+the unmute command.
+
 The current volume level and mute state is shown in the file.
 
+You can use the volume_capabilities parameter to tell the driver
+whether your thinkpad has volume control or mute-only control:
+volume_capabilities=1 for mixers with mute and volume control,
+volume_capabilities=2 for mixers with only mute control.
+
+If the driver misdetects the capabilities for your ThinkPad model,
+please report this to ibm-acpi-devel@lists.sourceforge.net, so that we
+can update the driver.
+
 There are two strategies for volume control.  To select which one
 should be used, use the volume_mode module parameter: volume_mode=1
 selects EC mode, and volume_mode=3 selects EC mode with NVRAM backing
@@ -1450,3 +1463,5 @@ Sysfs interface changelog:
                is deprecated and marked for removal.
 
 0x020600:      Marker for backlight change event support.
+
+0x020700:      Support for mute-only mixers.
index a2f5312..4d909d5 100644 (file)
@@ -22,7 +22,7 @@
  */
 
 #define TPACPI_VERSION "0.23"
-#define TPACPI_SYSFS_VERSION 0x020600
+#define TPACPI_SYSFS_VERSION 0x020700
 
 /*
  *  Changelog:
@@ -299,6 +299,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;
@@ -426,6 +427,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;
@@ -6416,9 +6423,17 @@ enum tpacpi_volume_access_mode {
        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;
 
 /*
  * Used to syncronize writers to TP_EC_AUDIO and
@@ -6430,7 +6445,7 @@ static void tpacpi_volume_checkpoint_nvram(void)
 {
        u8 lec = 0;
        u8 b_nvram;
-       const u8 ec_mask = TP_EC_AUDIO_LVL_MSK | TP_EC_AUDIO_MUTESW_MSK;
+       u8 ec_mask;
 
        if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
                return;
@@ -6438,6 +6453,11 @@ static void tpacpi_volume_checkpoint_nvram(void)
        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;
 
@@ -6575,8 +6595,36 @@ static void volume_exit(void)
        tpacpi_volume_checkpoint_nvram();
 }
 
+#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;
+
        vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
 
        mutex_init(&volume_mutex);
@@ -6596,6 +6644,36 @@ static int __init volume_init(struct ibm_init_struct *iibm)
                return 1;
        }
 
+       if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
+               return -EINVAL;
+
+       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;
@@ -6610,7 +6688,8 @@ static int __init volume_init(struct ibm_init_struct *iibm)
        }
 
        vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
-                       "volume is supported\n");
+                       "mute is supported, volume control is %s\n",
+                       str_supported(!tp_features.mixer_no_level_control));
 
        return 0;
 }
@@ -6623,13 +6702,21 @@ static int volume_read(char *p)
        if (volume_get_status(&status) < 0) {
                len += sprintf(p + len, "level:\t\tunreadable\n");
        } else {
-               len += sprintf(p + len, "level:\t\t%d\n",
-                               status & TP_EC_AUDIO_LVL_MSK);
+               if (tp_features.mixer_no_level_control)
+                       len += sprintf(p + len, "level:\t\tunsupported\n");
+               else
+                       len += sprintf(p + len, "level:\t\t%d\n",
+                                       status & TP_EC_AUDIO_LVL_MSK);
+
                len += sprintf(p + len, "mute:\t\t%s\n",
                                onoff(status, TP_EC_AUDIO_MUTESW));
-               len += sprintf(p + len, "commands:\tup, down, mute\n");
-               len += sprintf(p + len, "commands:\tlevel <level>"
+
+               len += sprintf(p + len, "commands:\tunmute, mute\n");
+               if (!tp_features.mixer_no_level_control) {
+                       len += sprintf(p + len, "commands:\tup, down\n");
+                       len += sprintf(p + len, "commands:\tlevel <level>"
                               " (<level> is 0-%d)\n", TP_EC_VOLUME_MAX);
+               }
        }
 
        return len;
@@ -6651,30 +6738,43 @@ static int volume_write(char *buf)
        new_mute  = s & TP_EC_AUDIO_MUTESW_MSK;
 
        while ((cmd = next_cmd(&buf))) {
-               if (strlencmp(cmd, "up") == 0) {
-                       if (new_mute)
-                               new_mute = 0;
-                       else if (new_level < TP_EC_VOLUME_MAX)
-                               new_level++;
-               } else if (strlencmp(cmd, "down") == 0) {
-                       if (new_mute)
-                               new_mute = 0;
-                       else if (new_level > 0)
-                               new_level--;
-               } else if (sscanf(cmd, "level %u", &l) == 1 &&
-                          l >= 0 && l <= TP_EC_VOLUME_MAX) {
-                               new_level = l;
-               } else if (strlencmp(cmd, "mute") == 0) {
+               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
+               else if (strlencmp(cmd, "unmute") == 0)
+                       new_mute = 0;
+               else
                        return -EINVAL;
        }
 
-       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);
+       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);
+       }
 
        return (rc == -EINTR) ? -ERESTARTSYS : rc;
 }
@@ -8410,6 +8510,11 @@ 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");
+
 #define TPACPI_PARAM(feature) \
        module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
        MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \