* [RFC PATCH] thinkpad-acpi: Improve hardware volume controls
@ 2011-05-09 22:18 Andy Lutomirski
2011-05-09 23:53 ` Henrique de Moraes Holschuh
` (2 more replies)
0 siblings, 3 replies; 24+ messages in thread
From: Andy Lutomirski @ 2011-05-09 22:18 UTC (permalink / raw)
To: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86
Cc: Andy Lutomirski
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.
For better or worse, the ThinkPad ACPI code checks _OSI(Linux) and
changes the behavior of the buttons to generate keypresses and do
nothing else. This is an improvement, since the hardware mixer
isn't really necessary. We only set _OSI(Linux) on a very small
set of modles, though.
It's worse on very new ThinkPads like the X220, which have a mute
indicator controlled by the hardware mixer. The only way to make
it work is to have the mute button control the hardware mixer (or
to have some userspace hack to change the hardware mixer when you
ask for "mute").
It turns out that we can ask ACPI for one of three behaviors
directly. They are "latch" (the default), "none" (no automatic
control), and "toggle" (mute unmutes when muted). So we make
two changes:
1. On load, check if we can use that ACPI call. If so, set
"latch" mode (it's read-only and that's the default).
2. Don't generate KEY_MUTE in any mode other than "none".
As an added bonus, we fix an old bug: the hardware mute control
doesn't generate an ALSA change notification on newer ThinkPads.
(If the ACPI method to control the mode isn't available or if
the laptop has hardware *volume*, then we preserve the old
behavior. The latter is because I don't have a machine to test
on.)
Signed-off-by: Andy Lutomirski <luto@mit.edu>
---
This wants testing on more than just an X220. I might be able to test on
an X200 and a T61p soon.
If you like this, I'll write the corresponding documentation patch and
another patch to remove the _OSI(Linux) quirks for the few models that
have them.
Henrique, do you know of anywhere to find AML dumps from different models?
It would be nice to see what SAUM looks like.
drivers/platform/x86/thinkpad_acpi.c | 274 ++++++++++++++++++++++++++++++++++
1 files changed, 274 insertions(+), 0 deletions(-)
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 4025d84..23cbdb4 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -384,6 +384,8 @@ static int dbg_uwbemul;
static int tpacpi_uwb_emulstate;
#endif
+static bool volume_should_send_key(int keycode);
+
/*************************************************************************
* Debugging helpers
@@ -2273,6 +2275,15 @@ static void tpacpi_input_send_key(const unsigned int scancode)
{
const unsigned int keycode = hotkey_keycode_map[scancode];
+ /*
+ * If the firmware already handled the mute key, do not forward it
+ * to userspace. We still want it unmasked because we generate
+ * an ALSA notification.
+ */
+
+ if (!volume_should_send_key(keycode))
+ return;
+
if (keycode != KEY_RESERVED) {
mutex_lock(&tpacpi_inputdev_send_mutex);
@@ -6453,6 +6464,15 @@ 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 firmware can optionally automatically change the volume
+ * in response to user input. Historically, we've assumed that this
+ * feature is off (and we've quirked _OSI="Linux" to get this behavior),
+ * so, if we can't find the explicit control we keep the historical
+ * behavior.
+ *
+ * On new Lenovos (e.g. X220), the mute button has an indicator light,
+ * so it's nice to get this right.
*/
#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
@@ -6502,12 +6522,28 @@ enum tpacpi_volume_capabilities {
TPACPI_VOL_CAP_MAX
};
+enum tpacpi_volume_autocontrol {
+ TPACPI_VOL_AUTO_LATCH = 0, /* Mute mutes; up/down unmutes */
+ /* 1 might be the same as 2 */
+ TPACPI_VOL_AUTO_NONE = 2, /* No automatic control at all */
+ TPACPI_VOL_AUTO_TOGGLE = 3, /* Mute toggles; up/down unmutes */
+};
+
+static const char *tpacpi_volume_autocontrol_names[] = {
+ [TPACPI_VOL_AUTO_LATCH] = "latch",
+ [TPACPI_VOL_AUTO_NONE] = "none",
+ [TPACPI_VOL_AUTO_TOGGLE] = "toggle",
+};
+
static enum tpacpi_volume_access_mode volume_mode =
TPACPI_VOL_MODE_MAX;
static enum tpacpi_volume_capabilities volume_capabilities;
static int volume_control_allowed;
+static enum tpacpi_volume_autocontrol volume_autocontrol = TPACPI_VOL_AUTO_NONE;
+static bool volume_autocontrol_configurable = false;
+
/*
* Used to syncronize writers to TP_EC_AUDIO and
* TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
@@ -6667,6 +6703,188 @@ unlock:
return rc;
}
+static int volume_set_autocontrol(enum tpacpi_volume_autocontrol val)
+{
+ int rc = 0;
+ int result;
+
+ if (!volume_autocontrol_configurable)
+ return -EIO;
+
+ if (mutex_lock_killable(&volume_mutex) < 0)
+ return -EINTR;
+
+ if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd", (int)val)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ /* On success, SAUM returns what it programmed. */
+ if (result != val) {
+ rc = -EIO;
+ goto out;
+ }
+
+ volume_autocontrol = val;
+
+out:
+ mutex_unlock(&volume_mutex);
+ return rc;
+}
+
+/*
+ * Do not generate an input event for hotkeys that firmware handles.
+ *
+ * We need to filter two different ways because on some models the event
+ * comes from the keyboard.
+ */
+
+static bool volume_should_send_key(int keycode)
+{
+ return keycode != KEY_MUTE ||
+ volume_autocontrol == TPACPI_VOL_AUTO_NONE;
+}
+
+static void volume_alsa_notify_change(void);
+
+bool volume_input_filter(struct input_handle *handle,
+ unsigned int type, unsigned int code, int value)
+{
+ if (volume_autocontrol == TPACPI_VOL_AUTO_NONE)
+ return false; /* Full software control */
+
+ /* XXX: This is an absurd way to generate alsa notifications. */
+
+ if (code == KEY_MUTE) {
+ /* Notify ALSA but eat the keystroke. */
+ volume_alsa_notify_change();
+ return true;
+ } else if (code == KEY_VOLUMEUP || code == KEY_VOLUMEDOWN) {
+ /* Notify ALSA but keep the keystroke. */
+ volume_alsa_notify_change();
+ }
+
+ return false;
+}
+
+static int volume_input_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int error;
+
+ handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = "thinkpad_acpi";
+
+ error = input_register_handle(handle);
+ if (error)
+ goto err_free_handle;
+
+ error = input_open_device(handle);
+ if (error)
+ goto err_unregister_handle;
+
+ dbg_printk(TPACPI_DBG_MIXER,
+ "Filtering input device %s\n", dev->phys);
+
+ return 0;
+
+err_unregister_handle:
+ input_unregister_handle(handle);
+err_free_handle:
+ kfree(handle);
+ return error;
+}
+
+static void volume_input_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id input_handler_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
+ INPUT_DEVICE_ID_MATCH_KEYBIT |
+ INPUT_DEVICE_ID_MATCH_BUS,
+ .bustype = BUS_I8042,
+ .evbit = { BIT_MASK(EV_KEY) },
+ .keybit = { [BIT_WORD(KEY_MUTE)] = BIT_MASK(KEY_MUTE) },
+ },
+ {}
+};
+
+static struct input_handler tpacpi_input_handler = {
+ .name = "thinkpad_acpi",
+ .connect = volume_input_connect,
+ .disconnect = volume_input_disconnect,
+ .filter = volume_input_filter,
+ .id_table = input_handler_ids,
+};
+
+static bool input_handler_registered = false;
+
+static ssize_t volume_autocontrol_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t ret;
+
+ ret = mutex_lock_killable(&volume_mutex);
+ if (ret < 0)
+ return ret;
+
+ ret = snprintf(buf, PAGE_SIZE, "%s\n",
+ tpacpi_volume_autocontrol_names[volume_autocontrol]);
+
+ mutex_unlock(&volume_mutex);
+ return ret;
+}
+
+static ssize_t volume_autocontrol_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int i;
+ const char *p;
+ size_t len;
+
+ p = memchr(buf, '\n', count);
+ len = p ? p - buf : count;
+
+ for (i = 0; i < ARRAY_SIZE(tpacpi_volume_autocontrol_names); i++) {
+ const char *name = tpacpi_volume_autocontrol_names[i];
+ if (name && !strncmp(name, buf, len)) {
+ int ret = volume_set_autocontrol(i);
+ if (ret < 0)
+ return ret;
+ return count;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static struct device_attribute dev_attr_volume_autocontrol =
+ __ATTR(volume_autocontrol, 0,
+ volume_autocontrol_show, volume_autocontrol_store);
+
+static struct attribute *volume_attributes[] = {
+ &dev_attr_volume_autocontrol.attr,
+ NULL
+};
+
+static const struct attribute_group volume_attr_group = {
+ .attrs = volume_attributes,
+};
+
static int volume_alsa_set_volume(const u8 vol)
{
dbg_printk(TPACPI_DBG_MIXER,
@@ -6774,6 +6992,10 @@ static void volume_suspend(pm_message_t state)
static void volume_resume(void)
{
+ if (volume_autocontrol_configurable &&
+ volume_set_autocontrol(volume_autocontrol) < 0)
+ printk(TPACPI_ERR "failed to restore volume autocontrol\n");
+
volume_alsa_notify_change();
}
@@ -6784,6 +7006,11 @@ static void volume_shutdown(void)
static void volume_exit(void)
{
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj, &volume_attr_group);
+
+ if (input_handler_registered)
+ input_unregister_handler(&tpacpi_input_handler);
+
if (alsa_card) {
snd_card_free(alsa_card);
alsa_card = NULL;
@@ -6998,7 +7225,54 @@ static int __init volume_init(struct ibm_init_struct *iibm)
| TP_ACPI_HKEY_VOLDWN_MASK
| TP_ACPI_HKEY_MUTE_MASK);
+ /* Try to initialize autocontrol */
+ if (tp_features.mixer_no_level_control) {
+ /*
+ * Someone needs to figure out how this works on models
+ * with level control before automatically enabling it.
+ */
+
+ volume_autocontrol_configurable = true;
+ if (volume_set_autocontrol(TPACPI_VOL_AUTO_LATCH) == 0) {
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "volume autocontrol reset to latch\n");
+ if (input_register_handler(&tpacpi_input_handler) == 0)
+ input_handler_registered = true;
+ else
+ printk(TPACPI_ERR
+ "failed to register input handler\n");
+ } else {
+ volume_autocontrol_configurable = false;
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "failed to set volume autocontrol\n");
+ }
+ }
+
+ if (!volume_autocontrol_configurable) {
+ /* Purely for historical reasons. */
+ volume_autocontrol = TPACPI_VOL_AUTO_NONE;
+ }
+
+ dev_attr_volume_autocontrol.attr.mode =
+ (volume_autocontrol_configurable ? S_IWUSR | S_IRUGO : S_IRUGO);
+ rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, &volume_attr_group);
+ if (rc < 0)
+ goto err;
+
return 0;
+
+err:
+ if (input_handler_registered) {
+ input_unregister_handler(&tpacpi_input_handler);
+ input_handler_registered = false;
+ }
+
+ if (alsa_card) {
+ snd_card_free(alsa_card);
+ alsa_card = NULL;
+ }
+
+ return rc;
}
static int volume_read(struct seq_file *m)
--
1.7.4.4
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-09 22:18 [RFC PATCH] thinkpad-acpi: Improve hardware volume controls Andy Lutomirski @ 2011-05-09 23:53 ` Henrique de Moraes Holschuh 2011-05-10 0:38 ` Andrew Lutomirski 2011-05-12 13:48 ` Matthew Garrett 2011-05-16 13:49 ` [PATCH v4] " Andy Lutomirski 2 siblings, 1 reply; 24+ messages in thread From: Henrique de Moraes Holschuh @ 2011-05-09 23:53 UTC (permalink / raw) To: Andy Lutomirski; +Cc: ibm-acpi-devel, platform-driver-x86 Interesting. Let me test it on my T43, it has hardware volume and seems to cover a class of box you couldn't test. I like the idea, but I might have some questions to ask you about it. More on this after I test the patch in the next few days. -- "One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie." -- The Silicon Valley Tarot Henrique Holschuh ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-09 23:53 ` Henrique de Moraes Holschuh @ 2011-05-10 0:38 ` Andrew Lutomirski 2011-05-10 1:04 ` [ibm-acpi-devel] " Henrique de Moraes Holschuh 0 siblings, 1 reply; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-10 0:38 UTC (permalink / raw) To: Henrique de Moraes Holschuh; +Cc: ibm-acpi-devel, platform-driver-x86 On Mon, May 9, 2011 at 7:53 PM, Henrique de Moraes Holschuh <hmh@hmh.eng.br> wrote: > Interesting. Let me test it on my T43, it has hardware volume and seems to > cover a class of box you couldn't test. > > I like the idea, but I might have some questions to ask you about it. More > on this after I test the patch in the next few days. FWIW, this patch (other than having the wrong default on X220) makes Linux better than Windows :) Do you know how to read a field from the EC by name? I think I can pull out the default value from the HAUM field, but I don't know how to access it through acpica and installing a custom method just to read a register seems like a bad idea. (The register's in the same place on X200 and X220, though, so maybe it's stable enough to just read. But using the field listing in the DSDT would make me happy.) Also, do you know how to ask the kernel what the most recent acpi interrupt was? I'd like to find a better way to detect the mute button than watching for the keyboard event. --Andy > > -- > "One disk to rule them all, One disk to find them. One disk to bring > them all and in the darkness grind them. In the Land of Redmond > where the shadows lie." -- The Silicon Valley Tarot > Henrique Holschuh > ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [ibm-acpi-devel] [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-10 0:38 ` Andrew Lutomirski @ 2011-05-10 1:04 ` Henrique de Moraes Holschuh 2011-05-10 2:36 ` Andrew Lutomirski 0 siblings, 1 reply; 24+ messages in thread From: Henrique de Moraes Holschuh @ 2011-05-10 1:04 UTC (permalink / raw) To: Andrew Lutomirski; +Cc: ibm-acpi-devel, platform-driver-x86 On Mon, 09 May 2011, Andrew Lutomirski wrote: > FWIW, this patch (other than having the wrong default on X220) makes > Linux better than Windows :) Please expand on that, especially the "wrong default on X220"... > Do you know how to read a field from the EC by name? I think I can If it is in the DSDT as a field of a "EmbeddedController" area, you just evaluate it. There are several examples in the driver (e.g. the TMP* evals in the thermal monitor subdriver). > Also, do you know how to ask the kernel what the most recent acpi > interrupt was? I'd like to find a better way to detect the mute > button than watching for the keyboard event. No, and I'd never ACK it if there was one. ACPI serves GPEs (interrupts) like crazy for random reasons, Never ever play racy games like that. Don't we get an HKEY notification for mute presses in the X220? -- "One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie." -- The Silicon Valley Tarot Henrique Holschuh ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [ibm-acpi-devel] [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-10 1:04 ` [ibm-acpi-devel] " Henrique de Moraes Holschuh @ 2011-05-10 2:36 ` Andrew Lutomirski 2011-05-10 3:41 ` Andrew Lutomirski 2011-05-10 10:25 ` [ibm-acpi-devel] [RFC PATCH] " Henrique de Moraes Holschuh 0 siblings, 2 replies; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-10 2:36 UTC (permalink / raw) To: Henrique de Moraes Holschuh; +Cc: ibm-acpi-devel, platform-driver-x86 On Mon, May 9, 2011 at 9:04 PM, Henrique de Moraes Holschuh <hmh@hmh.eng.br> wrote: > On Mon, 09 May 2011, Andrew Lutomirski wrote: >> FWIW, this patch (other than having the wrong default on X220) makes >> Linux better than Windows :) > > Please expand on that, especially the "wrong default on X220"... > >> Do you know how to read a field from the EC by name? I think I can > > If it is in the DSDT as a field of a "EmbeddedController" area, you just > evaluate it. There are several examples in the driver (e.g. the TMP* evals > in the thermal monitor subdriver). Indeed :) I'll send an updated patch tomorrow. > >> Also, do you know how to ask the kernel what the most recent acpi >> interrupt was? I'd like to find a better way to detect the mute >> button than watching for the keyboard event. > > No, and I'd never ACK it if there was one. ACPI serves GPEs (interrupts) > like crazy for random reasons, Never ever play racy games like that. > > Don't we get an HKEY notification for mute presses in the X220? I just meant to figure out what's going on. We're not getting an HKEY event, I think, but something's happening and a bit of acpi debug fiddling says that _Q43 is getting called. I have no idea what that means, though. --Andy ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [ibm-acpi-devel] [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-10 2:36 ` Andrew Lutomirski @ 2011-05-10 3:41 ` Andrew Lutomirski 2011-05-10 10:27 ` Henrique de Moraes Holschuh 2011-05-10 10:25 ` [ibm-acpi-devel] [RFC PATCH] " Henrique de Moraes Holschuh 1 sibling, 1 reply; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-10 3:41 UTC (permalink / raw) To: Henrique de Moraes Holschuh; +Cc: ibm-acpi-devel, platform-driver-x86 On Mon, May 9, 2011 at 10:36 PM, Andrew Lutomirski <luto@mit.edu> wrote: > On Mon, May 9, 2011 at 9:04 PM, Henrique de Moraes Holschuh > <hmh@hmh.eng.br> wrote: >> On Mon, 09 May 2011, Andrew Lutomirski wrote: >>> FWIW, this patch (other than having the wrong default on X220) makes >>> Linux better than Windows :) >> >> Please expand on that, especially the "wrong default on X220"... >> >>> Do you know how to read a field from the EC by name? I think I can >> >> If it is in the DSDT as a field of a "EmbeddedController" area, you just >> evaluate it. There are several examples in the driver (e.g. the TMP* evals >> in the thermal monitor subdriver). > > Indeed :) I'll send an updated patch tomorrow. > >> >>> Also, do you know how to ask the kernel what the most recent acpi >>> interrupt was? I'd like to find a better way to detect the mute >>> button than watching for the keyboard event. >> >> No, and I'd never ACK it if there was one. ACPI serves GPEs (interrupts) >> like crazy for random reasons, Never ever play racy games like that. >> >> Don't we get an HKEY notification for mute presses in the X220? > > I just meant to figure out what's going on. We're not getting an HKEY > event, I think, but something's happening and a bit of acpi debug > fiddling says that _Q43 is getting called. I have no idea what that > means, though. I read some more of the spec. It looks like the EC sends query 43 when the mute status changes. The problem is that _Q43 just calls _UCMS, which issues an SMI but does nothing else. So we don't get notified. And unless we change acpi_ec_sync_query to allow more than one handler for the same query, we can't even get notified without breaking _Q43. So we're screwed. I guess relying on the keyboard event isn't *that* bad. --Andy ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [ibm-acpi-devel] [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-10 3:41 ` Andrew Lutomirski @ 2011-05-10 10:27 ` Henrique de Moraes Holschuh 2011-05-10 10:39 ` Andrew Lutomirski 0 siblings, 1 reply; 24+ messages in thread From: Henrique de Moraes Holschuh @ 2011-05-10 10:27 UTC (permalink / raw) To: Andrew Lutomirski; +Cc: ibm-acpi-devel, platform-driver-x86 On Mon, 09 May 2011, Andrew Lutomirski wrote: > I read some more of the spec. It looks like the EC sends query 43 > when the mute status changes. > > The problem is that _Q43 just calls _UCMS, which issues an SMI but > does nothing else. So we don't get notified. And unless we change > acpi_ec_sync_query to allow more than one handler for the same query, > we can't even get notified without breaking _Q43. So we're screwed. We can 1) attach to _Q43 directly (must be whitelist-based using a quirk list), OR 2) ask Lenovo for _Q43 to call the HKEY notifier chain, which should still be possible for the 2011 boxes, and with some luck, the 2010 boxes as well. I will only do (2) after we went through some testing with your patch. -- "One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie." -- The Silicon Valley Tarot Henrique Holschuh ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [ibm-acpi-devel] [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-10 10:27 ` Henrique de Moraes Holschuh @ 2011-05-10 10:39 ` Andrew Lutomirski [not found] ` <BANLkTikgKj9yBUTLiFVj4tP2LNSzbExo1A-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 0 siblings, 1 reply; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-10 10:39 UTC (permalink / raw) To: Henrique de Moraes Holschuh; +Cc: ibm-acpi-devel, platform-driver-x86 On Tue, May 10, 2011 at 6:27 AM, Henrique de Moraes Holschuh <hmh@hmh.eng.br> wrote: > On Mon, 09 May 2011, Andrew Lutomirski wrote: >> I read some more of the spec. It looks like the EC sends query 43 >> when the mute status changes. >> >> The problem is that _Q43 just calls _UCMS, which issues an SMI but >> does nothing else. So we don't get notified. And unless we change >> acpi_ec_sync_query to allow more than one handler for the same query, >> we can't even get notified without breaking _Q43. So we're screwed. > > We can > > 1) attach to _Q43 directly (must be whitelist-based using a quirk list), > > OR > > 2) ask Lenovo for _Q43 to call the HKEY notifier chain, which should > still be possible for the 2011 boxes, and with some luck, the 2010 boxes > as well. > > I will only do (2) after we went through some testing with your patch. :) You might have better luck if you tell Lenovo that, on Windows, you can unmute the mute button, hit mute on the software mixer, then keep hitting the mute button and watching as the software and hardware mute settings stay exactly opposite of each other. But if we want models like the X200 to work nicely with volume autocontrol enabled on Linux, then we probably have to hook the keyboard input and steal the keypress (or find some magic register that turns off the keypress, but I'm hesitant to start writing to random EC registers and hoping for good luck). --Andy ^ permalink raw reply [flat|nested] 24+ messages in thread
[parent not found: <BANLkTikgKj9yBUTLiFVj4tP2LNSzbExo1A-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* [PATCH v2] thinkpad-acpi: Improve hardware volume controls [not found] ` <BANLkTikgKj9yBUTLiFVj4tP2LNSzbExo1A-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> @ 2011-05-10 10:44 ` Andy Lutomirski 0 siblings, 0 replies; 24+ messages in thread From: Andy Lutomirski @ 2011-05-10 10:44 UTC (permalink / raw) To: Henrique de Moraes Holschuh Cc: ibm-acpi-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Andy Lutomirski, platform-driver-x86-u79uwXL29TY76Z2rM5mHXA 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. For better or worse, the ThinkPad ACPI code checks _OSI(Linux) and changes the behavior of the buttons to generate keypresses and do nothing else. This is an improvement, since the hardware mixer isn't really necessary. We only set _OSI(Linux) on a very small set of modles, though. It's worse on very new ThinkPads like the X220, which have a mute indicator controlled by the hardware mixer. The only way to make it work is to have the mute button control the hardware mixer (or to have some userspace hack to change the hardware mixer when you ask for "mute"). It turns out that we can ask ACPI for one of three behaviors directly. They are "latch" (the default), "none" (no automatic control), and "toggle" (mute unmutes when muted). So we let the user control the mode through sysfs, and we don't generate KEY_MUTE in any mode other than "none". As an added bonus, we fix an old bug: the hardware mute control doesn't generate an ALSA change notification on newer ThinkPads. (This will need some adjustment on machines that have hardware *volume* control instead of just mute, I suspect. I don't own any such machines, though.) Signed-off-by: Andy Lutomirski <luto-3s7WtUTddSA@public.gmane.org> --- Changes from v1: - Read HAUM on startup, which gives the correct default (toggle) on X220 and should preserve the "none" behavior on systems that set acpi_osi=Linux. - Enable all of the controls on systems with hardware volume control, for ease of testing. - Back out HKEY modifications that didn't do anything -- that code was already correct. drivers/platform/x86/thinkpad_acpi.c | 271 ++++++++++++++++++++++++++++++++++ 1 files changed, 271 insertions(+), 0 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 4025d84..a277dd0 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -6453,6 +6453,15 @@ 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 firmware can optionally automatically change the volume + * in response to user input. Historically, we've assumed that this + * feature is off (and we've quirked _OSI="Linux" to get this behavior), + * so, if we can't find the explicit control we keep the historical + * behavior. + * + * On new Lenovos (e.g. X220), the mute button has an indicator light, + * so it's nice to get this right. */ #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT @@ -6502,12 +6511,28 @@ enum tpacpi_volume_capabilities { TPACPI_VOL_CAP_MAX }; +enum tpacpi_volume_autocontrol { + TPACPI_VOL_AUTO_LATCH = 0, /* Mute mutes; up/down unmutes */ + /* 1 might be the same as 2 */ + TPACPI_VOL_AUTO_NONE = 2, /* No automatic control at all */ + TPACPI_VOL_AUTO_TOGGLE = 3, /* Mute toggles; up/down unmutes */ +}; + +static const char *tpacpi_volume_autocontrol_names[] = { + [TPACPI_VOL_AUTO_LATCH] = "latch", + [TPACPI_VOL_AUTO_NONE] = "none", + [TPACPI_VOL_AUTO_TOGGLE] = "toggle", +}; + static enum tpacpi_volume_access_mode volume_mode = TPACPI_VOL_MODE_MAX; static enum tpacpi_volume_capabilities volume_capabilities; static int volume_control_allowed; +static enum tpacpi_volume_autocontrol volume_autocontrol = TPACPI_VOL_AUTO_NONE; +static bool volume_autocontrol_configurable = false; + /* * Used to syncronize writers to TP_EC_AUDIO and * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write @@ -6667,6 +6692,192 @@ unlock: return rc; } +static int volume_set_autocontrol(enum tpacpi_volume_autocontrol val) +{ + int rc = 0; + int result; + + if (!volume_autocontrol_configurable) + return -EIO; + + if (mutex_lock_killable(&volume_mutex) < 0) + return -EINTR; + + if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd", (int)val)) { + rc = -EIO; + goto out; + } + + /* On success, SAUM returns what it programmed. */ + if (result != val) { + rc = -EIO; + goto out; + } + + volume_autocontrol = val; + +out: + mutex_unlock(&volume_mutex); + return rc; +} + +/* This should only be used at startup. We keep a shadow for later use. */ +static int volume_read_autocontrol(enum tpacpi_volume_autocontrol *ret) +{ + int result; + + if (!acpi_evalf(ec_handle, &result, "HAUM", "qd")) + return -EIO; + + if (result < 0 || + result >= ARRAY_SIZE(tpacpi_volume_autocontrol_names) || + !tpacpi_volume_autocontrol_names[result]) + return -EINVAL; + + *ret = result; + return 0; +} + +static void volume_alsa_notify_change(void); + +bool volume_input_filter(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + if (volume_autocontrol == TPACPI_VOL_AUTO_NONE) + return false; /* Full software control */ + + /* XXX: This is an absurd way to generate alsa notifications. */ + + if (code == KEY_MUTE) { + /* Notify ALSA but eat the keystroke. */ + volume_alsa_notify_change(); + return true; + } else if (code == KEY_VOLUMEUP || code == KEY_VOLUMEDOWN) { + /* Notify ALSA but keep the keystroke. */ + volume_alsa_notify_change(); + } + + return false; +} + +static int volume_input_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "thinkpad_acpi"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + dbg_printk(TPACPI_DBG_MIXER, + "Filtering input device %s\n", dev->phys); + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return error; +} + +static void volume_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id input_handler_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_BUS, + .bustype = BUS_I8042, + .evbit = { BIT_MASK(EV_KEY) }, + .keybit = { [BIT_WORD(KEY_MUTE)] = BIT_MASK(KEY_MUTE) }, + }, + {} +}; + +static struct input_handler tpacpi_input_handler = { + .name = "thinkpad_acpi", + .connect = volume_input_connect, + .disconnect = volume_input_disconnect, + .filter = volume_input_filter, + .id_table = input_handler_ids, +}; + +static bool input_handler_registered = false; + +static ssize_t volume_autocontrol_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + + ret = mutex_lock_killable(&volume_mutex); + if (ret < 0) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%s\n", + tpacpi_volume_autocontrol_names[volume_autocontrol]); + + mutex_unlock(&volume_mutex); + return ret; +} + +static ssize_t volume_autocontrol_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int i; + const char *p; + size_t len; + + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + + for (i = 0; i < ARRAY_SIZE(tpacpi_volume_autocontrol_names); i++) { + const char *name = tpacpi_volume_autocontrol_names[i]; + if (name && !strncmp(name, buf, len)) { + int ret = volume_set_autocontrol(i); + if (ret < 0) + return ret; + return count; + } + } + + return -EINVAL; +} + +static struct device_attribute dev_attr_volume_autocontrol = + __ATTR(volume_autocontrol, 0, + volume_autocontrol_show, volume_autocontrol_store); + +static struct attribute *volume_attributes[] = { + &dev_attr_volume_autocontrol.attr, + NULL +}; + +static const struct attribute_group volume_attr_group = { + .attrs = volume_attributes, +}; + static int volume_alsa_set_volume(const u8 vol) { dbg_printk(TPACPI_DBG_MIXER, @@ -6774,6 +6985,10 @@ static void volume_suspend(pm_message_t state) static void volume_resume(void) { + if (volume_autocontrol_configurable && + volume_set_autocontrol(volume_autocontrol) < 0) + printk(TPACPI_ERR "failed to restore volume autocontrol\n"); + volume_alsa_notify_change(); } @@ -6784,6 +6999,11 @@ static void volume_shutdown(void) static void volume_exit(void) { + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &volume_attr_group); + + if (input_handler_registered) + input_unregister_handler(&tpacpi_input_handler); + if (alsa_card) { snd_card_free(alsa_card); alsa_card = NULL; @@ -6998,7 +7218,58 @@ static int __init volume_init(struct ibm_init_struct *iibm) | TP_ACPI_HKEY_VOLDWN_MASK | TP_ACPI_HKEY_MUTE_MASK); + /* Try to initialize autocontrol */ + volume_autocontrol_configurable = true; + rc = volume_read_autocontrol(&volume_autocontrol); + if (rc != 0) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "failed to read volume autocontrol\n"); + volume_autocontrol_configurable = false; + + /* Maintain historical behavior. */ + volume_autocontrol = TPACPI_VOL_AUTO_NONE; + } else { + /* Write the value we just read, just in case. */ + if (volume_set_autocontrol(volume_autocontrol) == 0) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "volume autocontrol available\n"); + } else { + volume_autocontrol_configurable = false; + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "failed to write volume autocontrol\n"); + /* Keep the value we read, though. */ + } + } + + if (volume_autocontrol != TPACPI_VOL_AUTO_NONE || + volume_autocontrol_configurable) { + if (input_register_handler(&tpacpi_input_handler) == 0) + input_handler_registered = true; + else + printk(TPACPI_ERR + "failed to register input handler\n"); + } + + dev_attr_volume_autocontrol.attr.mode = + (volume_autocontrol_configurable ? S_IWUSR | S_IRUGO : S_IRUGO); + rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, &volume_attr_group); + if (rc < 0) + goto err; + return 0; + +err: + if (input_handler_registered) { + input_unregister_handler(&tpacpi_input_handler); + input_handler_registered = false; + } + + if (alsa_card) { + snd_card_free(alsa_card); + alsa_card = NULL; + } + + return rc; } static int volume_read(struct seq_file *m) -- 1.7.4.4 ------------------------------------------------------------------------------ Achieve unprecedented app performance and reliability What every C/C++ and Fortran developer should know. Learn how Intel has extended the reach of its next-generation tools to help boost performance applications - inlcuding clusters. http://p.sf.net/sfu/intel-dev2devmay ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [ibm-acpi-devel] [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-10 2:36 ` Andrew Lutomirski 2011-05-10 3:41 ` Andrew Lutomirski @ 2011-05-10 10:25 ` Henrique de Moraes Holschuh 1 sibling, 0 replies; 24+ messages in thread From: Henrique de Moraes Holschuh @ 2011-05-10 10:25 UTC (permalink / raw) To: Andrew Lutomirski; +Cc: ibm-acpi-devel, platform-driver-x86 On Mon, 09 May 2011, Andrew Lutomirski wrote: > > Don't we get an HKEY notification for mute presses in the X220? > > I just meant to figure out what's going on. We're not getting an HKEY > event, I think, but something's happening and a bit of acpi debug > fiddling says that _Q43 is getting called. I have no idea what that > means, though. It means you get the proper GPE (_Q43), now you just need to check what that method does in the DSDT or the SSDTs. -- "One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie." -- The Silicon Valley Tarot Henrique Holschuh ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-09 22:18 [RFC PATCH] thinkpad-acpi: Improve hardware volume controls Andy Lutomirski 2011-05-09 23:53 ` Henrique de Moraes Holschuh @ 2011-05-12 13:48 ` Matthew Garrett 2011-05-12 14:24 ` Andrew Lutomirski 2011-05-16 13:49 ` [PATCH v4] " Andy Lutomirski 2 siblings, 1 reply; 24+ messages in thread From: Matthew Garrett @ 2011-05-12 13:48 UTC (permalink / raw) To: Andy Lutomirski Cc: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86 On Mon, May 09, 2011 at 06:18:46PM -0400, Andy Lutomirski wrote: > Henrique, do you know of anywhere to find AML dumps from different models? > It would be nice to see what SAUM looks like. It looks like SAUM was introduced with the *61 machines, and it's identical from then on. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-12 13:48 ` Matthew Garrett @ 2011-05-12 14:24 ` Andrew Lutomirski 2011-05-12 14:39 ` Matthew Garrett 2011-05-12 21:43 ` [RFC PATCH] " Henrique de Moraes Holschuh 0 siblings, 2 replies; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-12 14:24 UTC (permalink / raw) To: Matthew Garrett Cc: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86 On Thu, May 12, 2011 at 9:48 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: > On Mon, May 09, 2011 at 06:18:46PM -0400, Andy Lutomirski wrote: > >> Henrique, do you know of anywhere to find AML dumps from different models? >> It would be nice to see what SAUM looks like. > > It looks like SAUM was introduced with the *61 machines, and it's > identical from then on. I wonder the machines with SAUM are the same as the machines on which pressing mute generates an i8042 keystroke instead of an HKEY. If so, it looks like there's some code (or at least comments) in thinkpad_acpi that mention doing the right thing when the HKEY mute event in generated (e.g. the driver won't send KEY_MUTE to the input layer), so something like my patch along with removing the _OSI(Linux) hack for the newer models might make everything work right. Still, someone who has an older laptop should test it, because all I can do is pretend I carefully inspected all the possible code paths. (Even if it works, don't apply this patch for 2.6.40 as it stands because the ALSA change notification on KEY_MUTE is crap and should at least ignore key release events.) --Andy > > -- > Matthew Garrett | mjg59@srcf.ucam.org > ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-12 14:24 ` Andrew Lutomirski @ 2011-05-12 14:39 ` Matthew Garrett 2011-05-12 14:50 ` Andrew Lutomirski 2011-05-12 21:43 ` [RFC PATCH] " Henrique de Moraes Holschuh 1 sibling, 1 reply; 24+ messages in thread From: Matthew Garrett @ 2011-05-12 14:39 UTC (permalink / raw) To: Andrew Lutomirski Cc: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86 On Thu, May 12, 2011 at 10:24:10AM -0400, Andrew Lutomirski wrote: > On Thu, May 12, 2011 at 9:48 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: > > It looks like SAUM was introduced with the *61 machines, and it's > > identical from then on. > > I wonder the machines with SAUM are the same as the machines on which > pressing mute generates an i8042 keystroke instead of an HKEY. If so, > it looks like there's some code (or at least comments) in > thinkpad_acpi that mention doing the right thing when the HKEY mute > event in generated (e.g. the driver won't send KEY_MUTE to the input > layer), so something like my patch along with removing the _OSI(Linux) > hack for the newer models might make everything work right. I think so. The machines we have in the OSI blacklist all appear to have the SAUM method, so I think we can take your patch and drop the blacklist. Good work! > Still, someone who has an older laptop should test it, because all I > can do is pretend I carefully inspected all the possible code paths. I can't see any way this would cause problems, except in the case where the method exists but doesn't do anything. I'd be surprised if that's a real problem. > (Even if it works, don't apply this patch for 2.6.40 as it stands > because the ALSA change notification on KEY_MUTE is crap and should at > least ignore key release events.) Are you working with the ALSA people on that? -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-12 14:39 ` Matthew Garrett @ 2011-05-12 14:50 ` Andrew Lutomirski [not found] ` <BANLkTikX7mMu2me6+O1e-Ub=Cbzerd=J3Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 0 siblings, 1 reply; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-12 14:50 UTC (permalink / raw) To: Matthew Garrett, Dmitry Torokhov Cc: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86, linux-input [Hi, Dmitry -- there's an input layer question in here for you] On Thu, May 12, 2011 at 10:39 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: > On Thu, May 12, 2011 at 10:24:10AM -0400, Andrew Lutomirski wrote: >> On Thu, May 12, 2011 at 9:48 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: >> > It looks like SAUM was introduced with the *61 machines, and it's >> > identical from then on. >> >> I wonder the machines with SAUM are the same as the machines on which >> pressing mute generates an i8042 keystroke instead of an HKEY. If so, >> it looks like there's some code (or at least comments) in >> thinkpad_acpi that mention doing the right thing when the HKEY mute >> event in generated (e.g. the driver won't send KEY_MUTE to the input >> layer), so something like my patch along with removing the _OSI(Linux) >> hack for the newer models might make everything work right. > > I think so. The machines we have in the OSI blacklist all appear to have > the SAUM method, so I think we can take your patch and drop the > blacklist. Good work! > >> Still, someone who has an older laptop should test it, because all I >> can do is pretend I carefully inspected all the possible code paths. > > I can't see any way this would cause problems, except in the case where > the method exists but doesn't do anything. I'd be surprised if that's a > real problem. > >> (Even if it works, don't apply this patch for 2.6.40 as it stands >> because the ALSA change notification on KEY_MUTE is crap and should at >> least ignore key release events.) > > Are you working with the ALSA people on that? I don't think I need help from ALSA. I just need to stop telling ALSA that the volume changes twice every time that mute is pressed. I'll send v3 out in the next day or two with the fix. I do, however, have a question for the input people. Dmitry: Lenovo makes laptops which are kind enough to tell us that the volume changed by sending a keystroke over the atkbd-based keyboard. (wtf!) I've modified the thinkpad-acpi driver to register an input handler to catch those events coming from the keyboard and send them to ALSA where they belong. But if there's a keyboard grab, it won't work. Would you accept a patch to the input layer to allow filters (or maybe just filters that specifically request it) to run even if there's a grab? Without a change to the input layer, if someone grabs the keyboard then the volume controls will start getting screwed up. If X grabs the keyboard it's even worse because pulseaudio will start getting extra confused and preventing that is the whole point of this patch. (Yes, it's gross, but the way around it I can see would be to inject a custom ACPI method, and that still might not be enough to prevent userspace from seeing a keystroke that it's really not supposed to see. And IMHO custom ACPI methods are even worse than input filters.) --Andy -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 24+ messages in thread
[parent not found: <BANLkTikX7mMu2me6+O1e-Ub=Cbzerd=J3Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls [not found] ` <BANLkTikX7mMu2me6+O1e-Ub=Cbzerd=J3Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> @ 2011-05-12 15:39 ` Dmitry Torokhov 2011-05-12 19:33 ` Andrew Lutomirski 0 siblings, 1 reply; 24+ messages in thread From: Dmitry Torokhov @ 2011-05-12 15:39 UTC (permalink / raw) To: Andrew Lutomirski Cc: Matthew Garrett, platform-driver-x86-u79uwXL29TY76Z2rM5mHXA, linux-input-u79uwXL29TY76Z2rM5mHXA, Henrique de Moraes Holschuh, ibm-acpi-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f Hi Andrew, On Thu, May 12, 2011 at 10:50:54AM -0400, Andrew Lutomirski wrote: > > I do, however, have a question for the input people. Dmitry: Lenovo > makes laptops which are kind enough to tell us that the volume changed > by sending a keystroke over the atkbd-based keyboard. (wtf!) I've > modified the thinkpad-acpi driver to register an input handler to > catch those events coming from the keyboard and send them to ALSA > where they belong. But if there's a keyboard grab, it won't work. > Would you accept a patch to the input layer to allow filters (or maybe > just filters that specifically request it) to run even if there's a > grab? There is a filter on i8042 level that was introduced specifically for cases when events not having any relation to the input are routed via KBC interface. It looks like this is the one you want to use. See include/linux/i8042.h::i8042_install_filter(). It allows for such events to completely bypass input layer. Hope this helps. -- Dmitry ------------------------------------------------------------------------------ Achieve unprecedented app performance and reliability What every C/C++ and Fortran developer should know. Learn how Intel has extended the reach of its next-generation tools to help boost performance applications - inlcuding clusters. http://p.sf.net/sfu/intel-dev2devmay ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-12 15:39 ` Dmitry Torokhov @ 2011-05-12 19:33 ` Andrew Lutomirski 2011-05-12 20:42 ` Dmitry Torokhov 0 siblings, 1 reply; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-12 19:33 UTC (permalink / raw) To: Dmitry Torokhov Cc: Matthew Garrett, Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86, linux-input On Thu, May 12, 2011 at 11:39 AM, Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote: > Hi Andrew, > > On Thu, May 12, 2011 at 10:50:54AM -0400, Andrew Lutomirski wrote: >> >> I do, however, have a question for the input people. Dmitry: Lenovo >> makes laptops which are kind enough to tell us that the volume changed >> by sending a keystroke over the atkbd-based keyboard. (wtf!) I've >> modified the thinkpad-acpi driver to register an input handler to >> catch those events coming from the keyboard and send them to ALSA >> where they belong. But if there's a keyboard grab, it won't work. >> Would you accept a patch to the input layer to allow filters (or maybe >> just filters that specifically request it) to run even if there's a >> grab? > > There is a filter on i8042 level that was introduced specifically for > cases when events not having any relation to the input are routed via > KBC interface. It looks like this is the one you want to use. See > include/linux/i8042.h::i8042_install_filter(). It allows for such events > to completely bypass input layer. > > Hope this helps. Sort of. dell-laptop and msi-laptop are content to take some action on the keys they see but still leave the keys in the input stream, so their job is a bit easier. I need to swallow one kind of extended key, detect and not swallow two others, and ignore all the ones that are normal keys. But that means that I don't know whether I should filter out 0xe0 until it's too late. So either I'd need a function to feed an event back into i8042 or I need to filter a little farther downstream when the keys are resolved into keycodes (or scancodes -- I'm not really up on the terminology). --andy ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-12 19:33 ` Andrew Lutomirski @ 2011-05-12 20:42 ` Dmitry Torokhov 2011-05-14 15:34 ` [PATCH v3] " Andy Lutomirski 0 siblings, 1 reply; 24+ messages in thread From: Dmitry Torokhov @ 2011-05-12 20:42 UTC (permalink / raw) To: Andrew Lutomirski Cc: Matthew Garrett, Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86, linux-input On Thu, May 12, 2011 at 03:33:54PM -0400, Andrew Lutomirski wrote: > On Thu, May 12, 2011 at 11:39 AM, Dmitry Torokhov > <dmitry.torokhov@gmail.com> wrote: > > Hi Andrew, > > > > On Thu, May 12, 2011 at 10:50:54AM -0400, Andrew Lutomirski wrote: > >> > >> I do, however, have a question for the input people. Dmitry: Lenovo > >> makes laptops which are kind enough to tell us that the volume changed > >> by sending a keystroke over the atkbd-based keyboard. (wtf!) I've > >> modified the thinkpad-acpi driver to register an input handler to > >> catch those events coming from the keyboard and send them to ALSA > >> where they belong. But if there's a keyboard grab, it won't work. > >> Would you accept a patch to the input layer to allow filters (or maybe > >> just filters that specifically request it) to run even if there's a > >> grab? > > > > There is a filter on i8042 level that was introduced specifically for > > cases when events not having any relation to the input are routed via > > KBC interface. It looks like this is the one you want to use. See > > include/linux/i8042.h::i8042_install_filter(). It allows for such events > > to completely bypass input layer. > > > > Hope this helps. > > Sort of. > > dell-laptop and msi-laptop are content to take some action on the keys > they see but still leave the keys in the input stream, so their job is > a bit easier. > > I need to swallow one kind of extended key, detect and not swallow two > others, and ignore all the ones that are normal keys. But that means > that I don't know whether I should filter out 0xe0 until it's too > late. > > So either I'd need a function to feed an event back into i8042 or I > need to filter a little farther downstream when the keys are resolved > into keycodes (or scancodes -- I'm not really up on the terminology). You can use serio_interrupt() to inject additional bytes into serio data stream. Thanks. -- Dmitry ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v3] thinkpad-acpi: Improve hardware volume controls 2011-05-12 20:42 ` Dmitry Torokhov @ 2011-05-14 15:34 ` Andy Lutomirski 2011-05-14 15:47 ` Matthew Garrett 0 siblings, 1 reply; 24+ messages in thread From: Andy Lutomirski @ 2011-05-14 15:34 UTC (permalink / raw) To: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86, Matthew Garrett, Dmitry Torokhov <dmitr> Cc: Bruce Hill, Andy Lutomirski 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. For better or worse, the ThinkPad ACPI code checks _OSI(Linux) and changes the behavior of the buttons to generate keypresses and do nothing else. This is an improvement, since the hardware mixer isn't really necessary. We only set _OSI(Linux) on a very small set of modles, though. It's worse on very new ThinkPads like the X220, which have a mute indicator controlled by the hardware mixer. The only way to make it work is to have the mute button control the hardware mixer (or to have some userspace hack to change the hardware mixer when you ask for "mute"). It turns out that we can ask ACPI for one of three behaviors directly. They are "latch" (the default), "none" (no automatic control), and "toggle" (mute unmutes when muted). So we let the user control the mode through sysfs, and we don't generate KEY_MUTE in any mode other than "none". As an added bonus, we fix an old bug: the hardware mute control doesn't generate an ALSA change notification on newer ThinkPads. Signed-off-by: Andy Lutomirski <luto@mit.edu> --- Dmitry- This would IMO be prettier if we could install the hook at the serio level instead of the i8042 level. As it stands, there's some duplicated logic and some ugly code to figure out exactly what to send to serio_interrupt when we get a normal extended key. Changes from v2: - Use an i8042 platform filter instead of an input filter. - Don't generate ALSA notification on hotkey release. Changes from v1: - Read HAUM on startup, which gives the correct default (toggle) on X220 and should preserve the "none" behavior on systems that set acpi_osi=Linux. - Enable all of the controls on systems with hardware volume control, for ease of testing. - Back out HKEY modifications that didn't do anything -- that code was already correct. drivers/platform/x86/thinkpad_acpi.c | 243 ++++++++++++++++++++++++++++++++++ 1 files changed, 243 insertions(+), 0 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 4025d84..91d1a18 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -73,6 +73,8 @@ #include <linux/leds.h> #include <linux/rfkill.h> #include <asm/uaccess.h> +#include <linux/i8042.h> +#include <linux/serio.h> #include <linux/dmi.h> #include <linux/jiffies.h> @@ -6453,6 +6455,15 @@ 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 firmware can optionally automatically change the volume + * in response to user input. Historically, we've assumed that this + * feature is off (and we've quirked _OSI="Linux" to get this behavior), + * so, if we can't find the explicit control we keep the historical + * behavior. + * + * On new Lenovos (e.g. X220), the mute button has an indicator light, + * so it's nice to get this right. */ #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT @@ -6502,12 +6513,28 @@ enum tpacpi_volume_capabilities { TPACPI_VOL_CAP_MAX }; +enum tpacpi_volume_autocontrol { + TPACPI_VOL_AUTO_LATCH = 0, /* Mute mutes; up/down unmutes */ + /* 1 might be the same as 2 */ + TPACPI_VOL_AUTO_NONE = 2, /* No automatic control at all */ + TPACPI_VOL_AUTO_TOGGLE = 3, /* Mute toggles; up/down unmutes */ +}; + +static const char *tpacpi_volume_autocontrol_names[] = { + [TPACPI_VOL_AUTO_LATCH] = "latch", + [TPACPI_VOL_AUTO_NONE] = "none", + [TPACPI_VOL_AUTO_TOGGLE] = "toggle", +}; + static enum tpacpi_volume_access_mode volume_mode = TPACPI_VOL_MODE_MAX; static enum tpacpi_volume_capabilities volume_capabilities; static int volume_control_allowed; +static enum tpacpi_volume_autocontrol volume_autocontrol = TPACPI_VOL_AUTO_NONE; +static bool volume_autocontrol_configurable = false; + /* * Used to syncronize writers to TP_EC_AUDIO and * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write @@ -6667,6 +6694,167 @@ unlock: return rc; } +static int volume_set_autocontrol(enum tpacpi_volume_autocontrol val) +{ + int rc = 0; + int result; + + if (!volume_autocontrol_configurable) + return -EIO; + + if (mutex_lock_killable(&volume_mutex) < 0) + return -EINTR; + + if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd", (int)val)) { + rc = -EIO; + goto out; + } + + /* On success, SAUM returns what it programmed. */ + if (result != val) { + rc = -EIO; + goto out; + } + + volume_autocontrol = val; + +out: + mutex_unlock(&volume_mutex); + return rc; +} + +/* This should only be used at startup. We keep a shadow for later use. */ +static int volume_read_autocontrol(enum tpacpi_volume_autocontrol *ret) +{ + int result; + + if (!acpi_evalf(ec_handle, &result, "HAUM", "qd")) + return -EIO; + + if (result < 0 || + result >= ARRAY_SIZE(tpacpi_volume_autocontrol_names) || + !tpacpi_volume_autocontrol_names[result]) + return -EINVAL; + + *ret = result; + return 0; +} + +static void volume_alsa_notify_change(void); + +static bool tpacpi_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + bool steal_event = false; + + /* Was the last event extended and, if so, what was its str? */ + static bool extended; + static unsigned char extended_str; + static struct serio *extended_port; + + bool prev_extended; + unsigned char prev_extended_str; + struct serio *prev_extended_port; + + /* Don't mess with the AUX port. */ + if (port->id.type != SERIO_8042_XL) + return false; + + /* Save the previous extended prefix, if any */ + prev_extended = extended; + prev_extended_str = extended_str; + prev_extended_port = extended_port; + extended = 0; + + /* Paranoia: we shouldn't see AUX messages here. */ + WARN_ON_ONCE(str & 0x20); + + if (data == 0xe0) { + /* Beginning of an extended event. Steal it for now. */ + extended = true; + extended_str = str; + extended_port = port; + steal_event = true; + } else if (prev_extended && + volume_autocontrol != TPACPI_VOL_AUTO_NONE) { + /* Rest of an extended event. Which one? */ + switch (data) { + case 0x20: /* press mute (or hold mute) */ + volume_alsa_notify_change(); + /* fall through */ + case 0xA0: /* release mute */ + steal_event = true; + break; + + case 0x2E: /* volume up */ + case 0x30: /* volume down */ + volume_alsa_notify_change(); + break; + } + } + + /* Reinject the previous 0xe0 if we ate it. */ + if (prev_extended && !WARN_ON_ONCE(prev_extended_port != port)) + serio_interrupt(port, 0xe0, + (prev_extended_str & 80) ? SERIO_PARITY : 0); + + return steal_event; +} + +static ssize_t volume_autocontrol_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + + ret = mutex_lock_killable(&volume_mutex); + if (ret < 0) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%s\n", + tpacpi_volume_autocontrol_names[volume_autocontrol]); + + mutex_unlock(&volume_mutex); + return ret; +} + +static ssize_t volume_autocontrol_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int i; + const char *p; + size_t len; + + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + + for (i = 0; i < ARRAY_SIZE(tpacpi_volume_autocontrol_names); i++) { + const char *name = tpacpi_volume_autocontrol_names[i]; + if (name && !strncmp(name, buf, len)) { + int ret = volume_set_autocontrol(i); + if (ret < 0) + return ret; + return count; + } + } + + return -EINVAL; +} + +static struct device_attribute dev_attr_volume_autocontrol = + __ATTR(volume_autocontrol, 0, + volume_autocontrol_show, volume_autocontrol_store); + +static struct attribute *volume_attributes[] = { + &dev_attr_volume_autocontrol.attr, + NULL +}; + +static const struct attribute_group volume_attr_group = { + .attrs = volume_attributes, +}; + static int volume_alsa_set_volume(const u8 vol) { dbg_printk(TPACPI_DBG_MIXER, @@ -6774,6 +6962,10 @@ static void volume_suspend(pm_message_t state) static void volume_resume(void) { + if (volume_autocontrol_configurable && + volume_set_autocontrol(volume_autocontrol) < 0) + printk(TPACPI_ERR "failed to restore volume autocontrol\n"); + volume_alsa_notify_change(); } @@ -6784,6 +6976,11 @@ static void volume_shutdown(void) static void volume_exit(void) { + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &volume_attr_group); + + /* Safe even if not registered. */ + i8042_remove_filter(tpacpi_i8042_filter); + if (alsa_card) { snd_card_free(alsa_card); alsa_card = NULL; @@ -6998,7 +7195,53 @@ static int __init volume_init(struct ibm_init_struct *iibm) | TP_ACPI_HKEY_VOLDWN_MASK | TP_ACPI_HKEY_MUTE_MASK); + /* Try to initialize autocontrol */ + volume_autocontrol_configurable = true; + rc = volume_read_autocontrol(&volume_autocontrol); + if (rc != 0) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "failed to read volume autocontrol\n"); + volume_autocontrol_configurable = false; + + /* Maintain historical behavior. */ + volume_autocontrol = TPACPI_VOL_AUTO_NONE; + } else { + /* Write the value we just read, just in case. */ + if (volume_set_autocontrol(volume_autocontrol) == 0) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "volume autocontrol available\n"); + } else { + volume_autocontrol_configurable = false; + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "failed to write volume autocontrol\n"); + /* Keep the value we read, though. */ + } + } + + if (volume_autocontrol != TPACPI_VOL_AUTO_NONE || + volume_autocontrol_configurable) { + if (i8042_install_filter(&tpacpi_i8042_filter) != 0) + printk(TPACPI_ERR + "failed to register i8042 filter\n"); + } + + dev_attr_volume_autocontrol.attr.mode = + (volume_autocontrol_configurable ? S_IWUSR | S_IRUGO : S_IRUGO); + rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, &volume_attr_group); + if (rc < 0) + goto err; + return 0; + +err: + i8042_remove_filter(&tpacpi_i8042_filter); + + if (alsa_card) { + snd_card_free(alsa_card); + alsa_card = NULL; + } + + return rc; } static int volume_read(struct seq_file *m) -- 1.7.5.1 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v3] thinkpad-acpi: Improve hardware volume controls 2011-05-14 15:34 ` [PATCH v3] " Andy Lutomirski @ 2011-05-14 15:47 ` Matthew Garrett 2011-05-14 15:55 ` Andrew Lutomirski 0 siblings, 1 reply; 24+ messages in thread From: Matthew Garrett @ 2011-05-14 15:47 UTC (permalink / raw) To: Andy Lutomirski Cc: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86, Dmitry Torokhov, Bruce Hill On Sat, May 14, 2011 at 11:34:25AM -0400, Andy Lutomirski wrote: > It turns out that we can ask ACPI for one of three behaviors > directly. They are "latch" (the default), "none" (no automatic > control), and "toggle" (mute unmutes when muted). So we let the > user control the mode through sysfs, and we don't generate KEY_MUTE > in any mode other than "none". > > As an added bonus, we fix an old bug: the hardware mute control > doesn't generate an ALSA change notification on newer ThinkPads. Which of these modes make sense for userspace? "none" sounds like it'd be fine, except that the LED wouldn't work on new machines. So should we just be setting "toggle" in all cases? The optimal situation would be that we delete the OSI blacklist at the same time as we add this code. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3] thinkpad-acpi: Improve hardware volume controls 2011-05-14 15:47 ` Matthew Garrett @ 2011-05-14 15:55 ` Andrew Lutomirski 2011-05-14 18:41 ` Henrique de Moraes Holschuh 0 siblings, 1 reply; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-14 15:55 UTC (permalink / raw) To: Matthew Garrett Cc: Henrique de Moraes Holschuh, ibm-acpi-devel, platform-driver-x86, Dmitry Torokhov, Bruce Hill On Sat, May 14, 2011 at 11:47 AM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: > On Sat, May 14, 2011 at 11:34:25AM -0400, Andy Lutomirski wrote: > >> It turns out that we can ask ACPI for one of three behaviors >> directly. They are "latch" (the default), "none" (no automatic >> control), and "toggle" (mute unmutes when muted). So we let the >> user control the mode through sysfs, and we don't generate KEY_MUTE >> in any mode other than "none". >> >> As an added bonus, we fix an old bug: the hardware mute control >> doesn't generate an ALSA change notification on newer ThinkPads. > > Which of these modes make sense for userspace? "none" sounds like it'd > be fine, except that the LED wouldn't work on new machines. So should we > just be setting "toggle" in all cases? The optimal situation would be > that we delete the OSI blacklist at the same time as we add this code. It's somewhat of a matter of preference. I think that on Windows on everything older than X220, pressing mute unconditionally mutes the sound, so it's a nice way to tell the system "don't make noise, regardless of what current settings are." On the X220, there's a dedicated mute indicator and the button toggles it. So if set "none" then we lose the use of the indicator and the ability to reliably mute the system without thought. The behavior I implemented reads the default from BIOS so that we do the right thing (or at least the thing that Lenovo intended) on all of these models unless the user writes something different to volume_autocontrol. (Disclaimer: I haven't actually tested the code on anything other than X220.) Later on, maybe we could convince PulseAudio to notice the mixer we expose and do something even nicer (like show a hardware mute OSD). --Andy ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3] thinkpad-acpi: Improve hardware volume controls 2011-05-14 15:55 ` Andrew Lutomirski @ 2011-05-14 18:41 ` Henrique de Moraes Holschuh 2011-05-15 19:22 ` Andrew Lutomirski 0 siblings, 1 reply; 24+ messages in thread From: Henrique de Moraes Holschuh @ 2011-05-14 18:41 UTC (permalink / raw) To: Andrew Lutomirski Cc: Matthew Garrett, ibm-acpi-devel, platform-driver-x86, Dmitry Torokhov, Bruce Hill On Sat, 14 May 2011, Andrew Lutomirski wrote: > I think that on Windows on everything older than X220, pressing mute > unconditionally mutes the sound, so it's a nice way to tell the system > "don't make noise, regardless of what current settings are." Yay, so the thinkpads have been un-dumbeddown. I love uplifting! > The behavior I implemented reads the default from BIOS so that we do > the right thing (or at least the thing that Lenovo intended) on all of Does the BIOS have a setup "control" for the user to chose MUTE behaviour? > Later on, maybe we could convince PulseAudio to notice the mixer we > expose and do something even nicer (like show a hardware mute OSD). *PLEASE* do. I once talked with the ALSA guys about actually doing a piggyback mixer control and attaching it to the HDA/AC97 mixer of the internal soundcard, but it was not exactly easy or kosher to do that at that level. -- "One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie." -- The Silicon Valley Tarot Henrique Holschuh ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v3] thinkpad-acpi: Improve hardware volume controls 2011-05-14 18:41 ` Henrique de Moraes Holschuh @ 2011-05-15 19:22 ` Andrew Lutomirski 0 siblings, 0 replies; 24+ messages in thread From: Andrew Lutomirski @ 2011-05-15 19:22 UTC (permalink / raw) To: Henrique de Moraes Holschuh Cc: Matthew Garrett, ibm-acpi-devel, platform-driver-x86, Dmitry Torokhov, Bruce Hill On Sat, May 14, 2011 at 2:41 PM, Henrique de Moraes Holschuh <hmh@hmh.eng.br> wrote: > On Sat, 14 May 2011, Andrew Lutomirski wrote: >> I think that on Windows on everything older than X220, pressing mute >> unconditionally mutes the sound, so it's a nice way to tell the system >> "don't make noise, regardless of what current settings are." > > Yay, so the thinkpads have been un-dumbeddown. I love uplifting! > >> The behavior I implemented reads the default from BIOS so that we do >> the right thing (or at least the thing that Lenovo intended) on all of > > Does the BIOS have a setup "control" for the user to chose MUTE behaviour? Not that I can find. Bad news, though: the SAUM control is different on the X200. The settings are: 0: latching mode. (mute unconditionally mutes but does not send any keystroke) 1: same as 3? 2: software mode. (mute sends e020, which is standard for a mute button) 3: weird. (mute sends e060 and does not seem to affect the mute state) So, two differences from x220. First, toggle is screwy and there seems to be no point in using it. Second, latching mode does not send any keystroke. I suspect that 0 was always the default and 2 was the value that got programmed if OSI=Linux, and 1 and 3 were just some random garbage. Then, when the X220 was designed with a light, the designers wanted toggle mode and repurposed an unused value. So it's probably safe to assume that 0 and 2 will remain at least similarly-behaving in future models. There are a few possible things to do: 1. Read HAUM at startup. If it's 0, assume that only latching mode and software mute are valid. If it's 2 (as it is on the X220), then assume that toggle is valid as well. 2. On some or all models that have SAUM, read the initial state to figure out what the default should be, then call SAUM(2) and just emulate the mute button in software. Benefits of the former: - It's easy - No regressions :) Benefits of the latter: - X200 gets ALSA notifications when the volume changes. - Toggle is available on all recent models. - We can emulate toggle a little better than the EC does: holding down the mute button really shouldn't flip it back and forth rapidly. Downsides of the latter: if the kernel dies in such a way that it's making noise, the mute button won't shut it up. In either case, I think we should write the initial value back to hardware on module unload so we don't get confused if we get loaded again. I'll email the pulseaudio list and ask what they want to do about the mixer. Ignoring it is fine, if slightly confusing on models without a light. (But then we've behaved that way on everything except the three models in the OSI=Linux list forever.) --Andy ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH] thinkpad-acpi: Improve hardware volume controls 2011-05-12 14:24 ` Andrew Lutomirski 2011-05-12 14:39 ` Matthew Garrett @ 2011-05-12 21:43 ` Henrique de Moraes Holschuh 1 sibling, 0 replies; 24+ messages in thread From: Henrique de Moraes Holschuh @ 2011-05-12 21:43 UTC (permalink / raw) To: Andrew Lutomirski; +Cc: Matthew Garrett, ibm-acpi-devel, platform-driver-x86 On Thu, 12 May 2011, Andrew Lutomirski wrote: > Still, someone who has an older laptop should test it, because all I > can do is pretend I carefully inspected all the possible code paths. Will do, will do. Please be patient. I will have thinkpad-acpi time this weekend :-) As you all noticed by now, you really must know what you're doing when messing with the thinkpad volume stuff. I will carefully review the patch, and we should be able to massage it into something that can be merged during the weekend and next week. -- "One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie." -- The Silicon Valley Tarot Henrique Holschuh ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4] thinkpad-acpi: Improve hardware volume controls 2011-05-09 22:18 [RFC PATCH] thinkpad-acpi: Improve hardware volume controls Andy Lutomirski 2011-05-09 23:53 ` Henrique de Moraes Holschuh 2011-05-12 13:48 ` Matthew Garrett @ 2011-05-16 13:49 ` Andy Lutomirski 2 siblings, 0 replies; 24+ messages in thread From: Andy Lutomirski @ 2011-05-16 13:49 UTC (permalink / raw) To: Henrique de Moraes Holschuh Cc: Andy Lutomirski, Matthew Garrett, ibm-acpi-devel, platform-driver-x86 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. For better or worse, the ThinkPad ACPI code checks _OSI(Linux) and changes the behavior of the buttons to generate keypresses and do nothing else. This is an improvement, since the hardware mixer isn't really necessary. We only set _OSI(Linux) on a very small set of modles, though. It's worse on very new ThinkPads like the X220, which have a mute indicator controlled by the hardware mixer. The only way to make it work is to have the mute button control the hardware mixer (or to have some userspace hack to change the hardware mixer when you ask for "mute"). 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). So we let the user control the mode through sysfs, and we don't generate KEY_MUTE in any mode other than "none". (Actually, that's a lie. No model I've tested gets all of these modes quite right, we we just emulate them in software.) As an added bonus, we fix an old bug: the hardware mute control doesn't generate an ALSA change notification on newer ThinkPads. Signed-off-by: Andy Lutomirski <luto@mit.edu> --- Changes from v3: - Emulate autocontrol instead of letting firmware do it. This is much better behaved on X200s and on X220 it lets us turn off typematic repeat of the mute button (which is IMO just silly). - Wait 1ms when changing volume -- the X200s gets inconsistent results otherwise (and 200us is not long enough). Changes from v2: - Use an i8042 platform filter instead of an input filter. - Don't generate ALSA notification on hotkey release. Changes from v1: - Read HAUM on startup, which gives the correct default (toggle) on X220 and should preserve the "none" behavior on systems that set acpi_osi=Linux. - Enable all of the controls on systems with hardware volume control, for ease of testing. - Back out HKEY modifications that didn't do anything -- that code was already correct. drivers/platform/x86/thinkpad_acpi.c | 381 +++++++++++++++++++++++++++++++++- 1 files changed, 380 insertions(+), 1 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 4025d84..4a00a23 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -73,6 +73,8 @@ #include <linux/leds.h> #include <linux/rfkill.h> #include <asm/uaccess.h> +#include <linux/i8042.h> +#include <linux/serio.h> #include <linux/dmi.h> #include <linux/jiffies.h> @@ -6453,6 +6455,15 @@ 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 firmware can optionally automatically change the volume + * in response to user input. Historically, we've assumed that this + * feature is off (and we've quirked _OSI="Linux" to get this behavior), + * so, if we can't find the explicit control we keep the historical + * behavior. + * + * On new Lenovos (e.g. X220), the mute button has an indicator light, + * so it's nice to get this right. */ #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT @@ -6502,12 +6513,29 @@ enum tpacpi_volume_capabilities { TPACPI_VOL_CAP_MAX }; +enum tpacpi_volume_autocontrol { + TPACPI_VOL_AUTO_LATCH = 0, /* Mute mutes; up/down unmutes */ + /* 1 might be the same as 2 */ + TPACPI_VOL_AUTO_NONE = 2, /* No automatic control at all */ + TPACPI_VOL_AUTO_TOGGLE = 3, /* Mute toggles; up/down unmutes */ +}; + +static const char *tpacpi_volume_autocontrol_names[] = { + [TPACPI_VOL_AUTO_LATCH] = "latch", + [TPACPI_VOL_AUTO_NONE] = "none", + [TPACPI_VOL_AUTO_TOGGLE] = "toggle", +}; + static enum tpacpi_volume_access_mode volume_mode = TPACPI_VOL_MODE_MAX; static enum tpacpi_volume_capabilities volume_capabilities; static int volume_control_allowed; +static enum tpacpi_volume_autocontrol volume_autocontrol = TPACPI_VOL_AUTO_NONE; +static enum tpacpi_volume_autocontrol initial_volume_autocontrol; +static bool volume_autocontrol_configurable = false; + /* * Used to syncronize writers to TP_EC_AUDIO and * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write @@ -6585,6 +6613,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, it can take awhile for reads to become + * correct. + */ + msleep(1); + return 0; } @@ -6611,8 +6645,15 @@ static int __volume_set_mute_ec(const bool mute) if (n != s) { rc = volume_set_status_ec(n); - if (!rc) + if (!rc) { rc = 1; + + /* + * On X200s, it can take awhile for reads to become + * correct. + */ + msleep(1); + } } unlock: @@ -6620,6 +6661,22 @@ unlock: return rc; } +static int volume_toggle_mute(void) +{ + int rc; + u8 s; + + if (mutex_lock_killable(&volume_mutex) < 0) + return -EINTR; + + rc = volume_get_status_ec(&s); + if (rc == 0) + rc = volume_set_status_ec(s ^ TP_EC_AUDIO_MUTESW_MSK); + + mutex_unlock(&volume_mutex); + return rc; +} + static int volume_alsa_set_mute(const bool mute) { dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to %smute\n", @@ -6667,6 +6724,308 @@ unlock: return rc; } +static int volume_set_autocontrol(enum tpacpi_volume_autocontrol val) +{ + int rc = 0; + int result; + + if (!volume_autocontrol_configurable) + return -EIO; + + if (mutex_lock_killable(&volume_mutex) < 0) + return -EINTR; + + if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd", (int)val)) { + rc = -EIO; + goto out; + } + + /* On success, SAUM returns what it programmed. */ + if (result != val) { + rc = -EIO; + goto out; + } + + volume_autocontrol = val; + +out: + mutex_unlock(&volume_mutex); + return rc; +} + +/* This should only be used at startup. We keep a shadow for later use. */ +static int volume_read_autocontrol(enum tpacpi_volume_autocontrol *ret) +{ + int result; + + if (!acpi_evalf(ec_handle, &result, "HAUM", "qd")) + return -EIO; + + if (result < 0 || + result >= ARRAY_SIZE(tpacpi_volume_autocontrol_names) || + !tpacpi_volume_autocontrol_names[result]) + return -EINVAL; + + *ret = result; + return 0; +} + +static void volume_alsa_notify_change(void); + +/* + * Intercept mute, volume up, and volume down, and emulate what firmware + * wants to do. (The firmware does it differently on different models, + * and every model I've tested gets it at least a little wrong.) + */ + +static void volume_emulate_mute_worker(struct work_struct *work) +{ + if (volume_autocontrol == TPACPI_VOL_AUTO_TOGGLE) { + volume_toggle_mute(); + volume_alsa_notify_change(); + } else { + if (__volume_set_mute_ec(true) == 1) + volume_alsa_notify_change(); + } + +} +static struct work_struct volume_emulate_mute_work; + +static bool volume_emulate_mute(void) +{ + if (volume_autocontrol == TPACPI_VOL_AUTO_NONE) { + return false; /* just pass the event */ + } else { + queue_work(tpacpi_wq, &volume_emulate_mute_work); + return true; + } +} + +static void volume_emulate_updown_worker(struct work_struct *work) +{ + if (__volume_set_mute_ec(false) == 1) + volume_alsa_notify_change(); + + /* + * There are no known models that have both SAUM and hardware + * volume (as opposed to just mute), so that's all we do. + */ +} +static struct work_struct volume_emulate_updown_work; + +static void volume_emulate_updown(void) +{ + if (volume_autocontrol != TPACPI_VOL_AUTO_NONE) + queue_work(tpacpi_wq, &volume_emulate_updown_work); +} + +static bool tpacpi_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + bool steal_event = false; + + /* Was the last event extended and, if so, what was its str? */ + static bool extended; + static unsigned char extended_str; + static struct serio *extended_port; + + bool prev_extended; + unsigned char prev_extended_str; + struct serio *prev_extended_port; + + /* Which buttons are pressed? */ + static bool mute_pressed = false; + /* Don't mess with the AUX port. */ + if (port->id.type != SERIO_8042_XL) + return false; + + /* Save the previous extended prefix, if any */ + prev_extended = extended; + prev_extended_str = extended_str; + prev_extended_port = extended_port; + extended = 0; + + /* Paranoia: we shouldn't see AUX messages here. */ + WARN_ON_ONCE(str & 0x20); + + if (data == 0xe0) { + /* Beginning of an extended event. Steal it for now. */ + extended = true; + extended_str = str; + extended_port = port; + steal_event = true; + } else if (prev_extended) { + /* Rest of an extended event. Which one? */ + switch (data) { + case 0x20: /* press mute (or hold mute) */ + if (mute_pressed) { + steal_event = true; /* no typematic repeat */ + } else { + steal_event = volume_emulate_mute(); + mute_pressed = true; + } + break; + + case 0xA0: /* release mute */ + steal_event = + (volume_autocontrol != TPACPI_VOL_AUTO_NONE); + mute_pressed = false; + break; + + case 0x2E: /* volume up */ + case 0x30: /* volume down */ + volume_emulate_updown(); + volume_alsa_notify_change(); + break; + } + } + + /* Reinject the previous 0xe0 if we ate it. */ + if (!steal_event && prev_extended && + !WARN_ON_ONCE(prev_extended_port != port)) + serio_interrupt(port, 0xe0, + (prev_extended_str & 80) ? SERIO_PARITY : 0); + + return steal_event; +} + +static ssize_t volume_autocontrol_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + + ret = mutex_lock_killable(&volume_mutex); + if (ret < 0) + return ret; + + ret = snprintf(buf, PAGE_SIZE, "%s\n", + tpacpi_volume_autocontrol_names[volume_autocontrol]); + + mutex_unlock(&volume_mutex); + return ret; +} + +static ssize_t volume_autocontrol_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int i; + const char *p; + size_t len; + + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + + for (i = 0; i < ARRAY_SIZE(tpacpi_volume_autocontrol_names); i++) { + const char *name = tpacpi_volume_autocontrol_names[i]; + if (name && !strncmp(name, buf, len)) { + int ret = mutex_lock_killable(&volume_mutex); + if (ret == 0) { + volume_autocontrol = i; + mutex_unlock(&volume_mutex); + } + if (ret < 0) + return ret; + return count; + } + } + + return -EINVAL; +} + +static struct device_attribute dev_attr_volume_autocontrol = + __ATTR(volume_autocontrol, 0, + volume_autocontrol_show, volume_autocontrol_store); + +static struct attribute *volume_attributes[] = { + &dev_attr_volume_autocontrol.attr, + NULL +}; + +static const struct attribute_group volume_attr_group = { + .attrs = volume_attributes, +}; + +static int volume_autocontrol_init(void) +{ + int rc = 0; + + /* Try to initialize autocontrol */ + volume_autocontrol_configurable = true; + rc = volume_read_autocontrol(&initial_volume_autocontrol); + if (rc != 0) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "failed to read volume autocontrol\n"); + goto out; + } + + /* Write the value we just read, to make sure we can restore. */ + rc = volume_set_autocontrol(initial_volume_autocontrol); + if (rc != 0) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "failed test write to volume autocontrol\n"); + goto out_norestore; + } + + /* Turn off hardware control, because we'll emulate it. */ + rc = volume_set_autocontrol(TPACPI_VOL_AUTO_NONE); + if (rc != 0) { + printk(TPACPI_ERR + "failed to disable hardware volume autocontrol\n"); + goto out_norestore; + } + + /* We're in business. Install the i8042 filter for emulation. */ + INIT_WORK(&volume_emulate_mute_work, volume_emulate_mute_worker); + INIT_WORK(&volume_emulate_updown_work, volume_emulate_updown_worker); + rc = i8042_install_filter(&tpacpi_i8042_filter); + if (rc != 0) { + printk(TPACPI_ERR "failed to register i8042 filter\n"); + goto out; + } + + dev_attr_volume_autocontrol.attr.mode = + (volume_autocontrol_configurable ? S_IWUSR | S_IRUGO : S_IRUGO); + rc = sysfs_create_group(&tpacpi_pdev->dev.kobj, &volume_attr_group); + if (rc != 0) + printk(TPACPI_ERR "failed to init volume_autocontrol group\n"); + + volume_autocontrol = initial_volume_autocontrol; + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "volume autocontrol available; initial state %d\n", + (int)initial_volume_autocontrol); + +out: + if (rc) + volume_set_autocontrol(initial_volume_autocontrol); + +out_norestore: + if (rc) { + volume_autocontrol_configurable = false; + i8042_remove_filter(&tpacpi_i8042_filter); /* always safe */ + + /* Maintain historical behavior. */ + volume_autocontrol = TPACPI_VOL_AUTO_NONE; + } + + return rc; +} + +void volume_autocontrol_exit(void) +{ + i8042_remove_filter(&tpacpi_i8042_filter); /* always safe */ + + if (work_pending(&volume_emulate_mute_work) || + work_pending(&volume_emulate_updown_work)) + flush_workqueue(tpacpi_wq); + + volume_set_autocontrol(initial_volume_autocontrol); + volume_autocontrol_configurable = false; + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &volume_attr_group); + +} + static int volume_alsa_set_volume(const u8 vol) { dbg_printk(TPACPI_DBG_MIXER, @@ -6774,6 +7133,10 @@ static void volume_suspend(pm_message_t state) static void volume_resume(void) { + if (volume_autocontrol_configurable && + volume_set_autocontrol(TPACPI_VOL_AUTO_NONE) < 0) + printk(TPACPI_ERR "failed to restore volume autocontrol\n"); + volume_alsa_notify_change(); } @@ -6784,6 +7147,8 @@ static void volume_shutdown(void) static void volume_exit(void) { + volume_autocontrol_exit(); + if (alsa_card) { snd_card_free(alsa_card); alsa_card = NULL; @@ -6998,7 +7363,21 @@ static int __init volume_init(struct ibm_init_struct *iibm) | TP_ACPI_HKEY_VOLDWN_MASK | TP_ACPI_HKEY_MUTE_MASK); + rc = volume_autocontrol_init(); + if (rc < 0) + goto err; + return 0; + +err: + i8042_remove_filter(&tpacpi_i8042_filter); + + if (alsa_card) { + snd_card_free(alsa_card); + alsa_card = NULL; + } + + return rc; } static int volume_read(struct seq_file *m) -- 1.7.5.1 ^ permalink raw reply related [flat|nested] 24+ messages in thread
end of thread, other threads:[~2011-05-16 13:49 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-05-09 22:18 [RFC PATCH] thinkpad-acpi: Improve hardware volume controls Andy Lutomirski
2011-05-09 23:53 ` Henrique de Moraes Holschuh
2011-05-10 0:38 ` Andrew Lutomirski
2011-05-10 1:04 ` [ibm-acpi-devel] " Henrique de Moraes Holschuh
2011-05-10 2:36 ` Andrew Lutomirski
2011-05-10 3:41 ` Andrew Lutomirski
2011-05-10 10:27 ` Henrique de Moraes Holschuh
2011-05-10 10:39 ` Andrew Lutomirski
[not found] ` <BANLkTikgKj9yBUTLiFVj4tP2LNSzbExo1A-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-05-10 10:44 ` [PATCH v2] " Andy Lutomirski
2011-05-10 10:25 ` [ibm-acpi-devel] [RFC PATCH] " Henrique de Moraes Holschuh
2011-05-12 13:48 ` Matthew Garrett
2011-05-12 14:24 ` Andrew Lutomirski
2011-05-12 14:39 ` Matthew Garrett
2011-05-12 14:50 ` Andrew Lutomirski
[not found] ` <BANLkTikX7mMu2me6+O1e-Ub=Cbzerd=J3Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-05-12 15:39 ` Dmitry Torokhov
2011-05-12 19:33 ` Andrew Lutomirski
2011-05-12 20:42 ` Dmitry Torokhov
2011-05-14 15:34 ` [PATCH v3] " Andy Lutomirski
2011-05-14 15:47 ` Matthew Garrett
2011-05-14 15:55 ` Andrew Lutomirski
2011-05-14 18:41 ` Henrique de Moraes Holschuh
2011-05-15 19:22 ` Andrew Lutomirski
2011-05-12 21:43 ` [RFC PATCH] " Henrique de Moraes Holschuh
2011-05-16 13:49 ` [PATCH v4] " Andy Lutomirski
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.