Merge tag 'platform-drivers-x86-v3.19-1' of git://git.infradead.org/users/dvhart...
[deliverable/linux.git] / drivers / platform / x86 / thinkpad_acpi.c
index 6414cfe5d8489e951d343b853fb5a6bcc3103299..c3d11fabc46f21c98c6122497958837ae291b171 100644 (file)
@@ -6557,6 +6557,17 @@ static struct ibm_struct brightness_driver_data = {
  * bits 3-0 (volume).  Other bits in NVRAM may have other functions,
  * such as bit 7 which is used to detect repeated presses of MUTE,
  * and we leave them unchanged.
+ *
+ * On newer Lenovo ThinkPads, the EC can automatically change the volume
+ * in response to user input.  Unfortunately, this rarely works well.
+ * The laptop changes the state of its internal MUTE gate and, on some
+ * models, sends KEY_MUTE, causing any user code that responds to the
+ * mute button to get confused.  The hardware MUTE gate is also
+ * unnecessary, since user code can handle the mute button without
+ * kernel or EC help.
+ *
+ * To avoid confusing userspace, we simply disable all EC-based mute
+ * and volume controls when possible.
  */
 
 #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
@@ -6611,11 +6622,21 @@ enum tpacpi_volume_capabilities {
        TPACPI_VOL_CAP_MAX
 };
 
+enum tpacpi_mute_btn_mode {
+       TP_EC_MUTE_BTN_LATCH  = 0,      /* Mute mutes; up/down unmutes */
+       /* We don't know what mode 1 is. */
+       TP_EC_MUTE_BTN_NONE   = 2,      /* Mute and up/down are just keys */
+       TP_EC_MUTE_BTN_TOGGLE = 3,      /* Mute toggles; up/down unmutes */
+};
+
 static enum tpacpi_volume_access_mode volume_mode =
        TPACPI_VOL_MODE_MAX;
 
 static enum tpacpi_volume_capabilities volume_capabilities;
 static bool volume_control_allowed;
+static bool software_mute_requested = true;
+static bool software_mute_active;
+static int software_mute_orig_mode;
 
 /*
  * Used to syncronize writers to TP_EC_AUDIO and
@@ -6633,6 +6654,8 @@ static void tpacpi_volume_checkpoint_nvram(void)
                return;
        if (!volume_control_allowed)
                return;
+       if (software_mute_active)
+               return;
 
        vdbg_printk(TPACPI_DBG_MIXER,
                "trying to checkpoint mixer state to NVRAM...\n");
@@ -6694,6 +6717,12 @@ static int volume_set_status_ec(const u8 status)
 
        dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
 
+       /*
+        * On X200s, and possibly on others, it can take a while for
+        * reads to become correct.
+        */
+       msleep(1);
+
        return 0;
 }
 
@@ -6776,6 +6805,57 @@ unlock:
        return rc;
 }
 
+static int volume_set_software_mute(bool startup)
+{
+       int result;
+
+       if (!tpacpi_is_lenovo())
+               return -ENODEV;
+
+       if (startup) {
+               if (!acpi_evalf(ec_handle, &software_mute_orig_mode,
+                               "HAUM", "qd"))
+                       return -EIO;
+
+               dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+                           "Initial HAUM setting was %d\n",
+                           software_mute_orig_mode);
+       }
+
+       if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd",
+                       (int)TP_EC_MUTE_BTN_NONE))
+               return -EIO;
+
+       if (result != TP_EC_MUTE_BTN_NONE)
+               pr_warn("Unexpected SAUM result %d\n",
+                       result);
+
+       /*
+        * In software mute mode, the standard codec controls take
+        * precendence, so we unmute the ThinkPad HW switch at
+        * startup.  Just on case there are SAUM-capable ThinkPads
+        * with level controls, set max HW volume as well.
+        */
+       if (tp_features.mixer_no_level_control)
+               result = volume_set_mute(false);
+       else
+               result = volume_set_status(TP_EC_VOLUME_MAX);
+
+       if (result != 0)
+               pr_warn("Failed to unmute the HW mute switch\n");
+
+       return 0;
+}
+
+static void volume_exit_software_mute(void)
+{
+       int r;
+
+       if (!acpi_evalf(ec_handle, &r, "SAUM", "qdd", software_mute_orig_mode)
+           || r != software_mute_orig_mode)
+               pr_warn("Failed to restore mute mode\n");
+}
+
 static int volume_alsa_set_volume(const u8 vol)
 {
        dbg_printk(TPACPI_DBG_MIXER,
@@ -6883,7 +6963,12 @@ static void volume_suspend(void)
 
 static void volume_resume(void)
 {
-       volume_alsa_notify_change();
+       if (software_mute_active) {
+               if (volume_set_software_mute(false) < 0)
+                       pr_warn("Failed to restore software mute\n");
+       } else {
+               volume_alsa_notify_change();
+       }
 }
 
 static void volume_shutdown(void)
@@ -6899,6 +6984,9 @@ static void volume_exit(void)
        }
 
        tpacpi_volume_checkpoint_nvram();
+
+       if (software_mute_active)
+               volume_exit_software_mute();
 }
 
 static int __init volume_create_alsa_mixer(void)
@@ -7083,16 +7171,20 @@ static int __init volume_init(struct ibm_init_struct *iibm)
                        "mute is supported, volume control is %s\n",
                        str_supported(!tp_features.mixer_no_level_control));
 
-       rc = volume_create_alsa_mixer();
-       if (rc) {
-               pr_err("Could not create the ALSA mixer interface\n");
-               return rc;
-       }
+       if (software_mute_requested && volume_set_software_mute(true) == 0) {
+               software_mute_active = true;
+       } else {
+               rc = volume_create_alsa_mixer();
+               if (rc) {
+                       pr_err("Could not create the ALSA mixer interface\n");
+                       return rc;
+               }
 
-       pr_info("Console audio control enabled, mode: %s\n",
-               (volume_control_allowed) ?
-                       "override (read/write)" :
-                       "monitor (read only)");
+               pr_info("Console audio control enabled, mode: %s\n",
+                       (volume_control_allowed) ?
+                               "override (read/write)" :
+                               "monitor (read only)");
+       }
 
        vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
                "registering volume hotkeys as change notification\n");
@@ -9089,6 +9181,10 @@ MODULE_PARM_DESC(volume_control,
                 "Enables software override for the console audio "
                 "control when true");
 
+module_param_named(software_mute, software_mute_requested, bool, 0444);
+MODULE_PARM_DESC(software_mute,
+                "Request full software mute control");
+
 /* ALSA module API parameters */
 module_param_named(index, alsa_index, int, 0444);
 MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
This page took 0.026932 seconds and 5 git commands to generate.