Discussion:
[PATCH v2 0/2] Disable the ThinkPad HW mute/level control when possible
Andy Lutomirski
2014-10-16 18:33:03 UTC
Permalink
This series is being resurrected after several years, and it's quite
different from before, so I'm restarting the version number at 1 :)

ThinkPad volume and mute controls are a mess. For whatever reason,
ThinkPads have mute buttons that send KEY_MUTE *and* control an
invisible-to-ALSA mute switch. Some of them have volume controls that
interact with this switch as well.

This is a perennial source of problems. On most ThinkPads, if you press
mute and then unmute using GUI controls, you have no sound, because
userspace and ALSA state gets out of sync with the hardware switch.
There's a separate "sound card" that exposes the hardware switch, but
userspace code generally doesn't understand that.

There are already a few _OSI(Linux) overrides to turn all the hardware
buttons into regular buttons. Rather than quirking ACPI everywhere,
just teach thinkpad-acpi to program the buttons for full software
control and to disable hardware controls. That allows us to remove the
ACPI quirks and have normal mute controls. This approach should be
much simpler than adding even more kludgey ALSA integration for
questionable gain.

I haven't tested on my X220 (which has a mute light in addition to a
mute button), since that laptop's SATA controller died last week. It
should work, though, and if someone else could test this on a ThinkPad
with a mute light, that would be great.

The only issue I know of with this version is that unloading and
reloading the module to change the software_mute setting doesn't
work quite right. I can fix that if it matters.

Changes from v1:
- Simplified the code a bit.
- Improved suspend/hibernate behavior.

Andy Lutomirski (2):
thinkpad-acpi: Try to use full software mute control
acpi: Remove _OSI(Linux) for ThinkPads

drivers/acpi/blacklist.c | 54 --------------------
drivers/platform/x86/thinkpad_acpi.c | 98 ++++++++++++++++++++++++++++++++----
2 files changed, 88 insertions(+), 64 deletions(-)
--
1.9.3
Andy Lutomirski
2014-10-16 18:33:04 UTC
Permalink
From: Andy Lutomirski <***@MIT.EDU>

ThinkPads have hardware volume controls and three buttons to control
them. (These are separate from the standard mixer.) By default,
the buttons are:

- Mute: Mutes the hardware volume control and generates KEY_MUTE.
- Up: Unmutes, generates KEY_VOLUMEUP, and increases volume if
applicable. (Newer thinkpads only have hardware mute/unmute.)
- Down: Unmutes, generates KEY_VOLUMEDOWN, and decreases volume
if applicable.

This behavior is unfortunate, since modern userspace will also
handle the hotkeys and change the other mixer. If the software
mixer is muted and the hardware mixer is unmuted and you push mute,
hilarity ensues as they both switch state.

Rather than adding a lot of complex ALSA integration to fix this,
just disable the special ThinkPad volume controls when possible.
This turns the mute and volume buttons into regular buttons, and
standard software controls will work as expected.

ALSA already knows about the mute light on models with a mute light,
so everything should just work.

This should also allow us to remove _OSI(Linux) for all ThinkPads.

For future reference: It turns out that we can ask ACPI for one of
three behaviors directly on very new models. They are "latch" (the
default), "none" (no automatic control), and "toggle" (mute unmutes
when muted). All of the modes besides "none" seem to be a bit buggy,
though, and there is no known way to get any notification that the
hardware state has changed other than listening for a mute button
press on the i8042 port.

This doesn't restore the old settings on unload, so unloading
and then reloading with software_mute=0 won't work quite right.

Signed-off-by: Andy Lutomirski <***@mit.edu>
---
drivers/platform/x86/thinkpad_acpi.c | 98 ++++++++++++++++++++++++++++++++----
1 file changed, 88 insertions(+), 10 deletions(-)

diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 3bbc6eb60de5..5c45cbe2aec2 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -6559,6 +6559,16 @@ 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.
+ *
+ * The EC or perhaps SMM firmware can optionally 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* 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
@@ -6613,11 +6623,20 @@ 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;

/*
* Used to syncronize writers to TP_EC_AUDIO and
@@ -6635,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");
@@ -6696,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;
}

@@ -6778,6 +6805,44 @@ unlock:
return rc;
}

+static int volume_set_software_mute(bool verbose)
+{
+ int result;
+
+ if (verbose) {
+ if (!acpi_evalf(ec_handle, &result, "HAUM", "qd"))
+ return -EIO;
+
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "Initial HAUM setting was %d\n",
+ result);
+ }
+
+ 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 int volume_alsa_set_volume(const u8 vol)
{
dbg_printk(TPACPI_DBG_MIXER,
@@ -6885,7 +6950,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)
@@ -7085,16 +7155,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");
@@ -9091,6 +9165,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");
--
1.9.3
Andy Lutomirski
2014-10-16 18:33:05 UTC
Permalink
AFAICT the only reason to set _OSI(Linux) on ThinkPads is to get
sensible mute button behavior. Now that the thinkpad_acpi driver
can do this on is own, there is no reason to keep the ACPI
quirk.

Cc: Len Brown <***@intel.com>
Cc: Jerone Young <***@canonical.com>
Signed-off-by: Andy Lutomirski <***@amacapital.net>
---
drivers/acpi/blacklist.c | 54 ------------------------------------------------
1 file changed, 54 deletions(-)

diff --git a/drivers/acpi/blacklist.c b/drivers/acpi/blacklist.c
index 36eb42e3b0bb..4a3cbb5d3c55 100644
--- a/drivers/acpi/blacklist.c
+++ b/drivers/acpi/blacklist.c
@@ -265,60 +265,6 @@ static struct dmi_system_id acpi_osi_dmi_table[] __initdata = {
*/

/*
- * Lenovo has a mix of systems OSI(Linux) situations
- * and thus we can not wildcard the vendor.
- *
- * _OSI(Linux) helps sound
- * DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad R61"),
- * DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T61"),
- * T400, T500
- * _OSI(Linux) has Linux specific hooks
- * DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X61"),
- * _OSI(Linux) is a NOP:
- * DMI_MATCH(DMI_PRODUCT_VERSION, "3000 N100"),
- * DMI_MATCH(DMI_PRODUCT_VERSION, "LENOVO3000 V100"),
- */
- {
- .callback = dmi_enable_osi_linux,
- .ident = "Lenovo ThinkPad R61",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad R61"),
- },
- },
- {
- .callback = dmi_enable_osi_linux,
- .ident = "Lenovo ThinkPad T61",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T61"),
- },
- },
- {
- .callback = dmi_enable_osi_linux,
- .ident = "Lenovo ThinkPad X61",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad X61"),
- },
- },
- {
- .callback = dmi_enable_osi_linux,
- .ident = "Lenovo ThinkPad T400",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T400"),
- },
- },
- {
- .callback = dmi_enable_osi_linux,
- .ident = "Lenovo ThinkPad T500",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad T500"),
- },
- },
- /*
* Without this this EEEpc exports a non working WMI interface, with
* this it exports a working "good old" eeepc_laptop interface, fixing
* both brightness control, and rfkill not working.
--
1.9.3
Loading...