summaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/thinkpad_acpi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/thinkpad_acpi.c')
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c166
1 files changed, 130 insertions, 36 deletions
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 3bbc6eb60de..c3d11fabc46 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -972,7 +972,6 @@ static void tpacpi_shutdown_handler(struct platform_device *pdev)
static struct platform_driver tpacpi_pdriver = {
.driver = {
.name = TPACPI_DRVR_NAME,
- .owner = THIS_MODULE,
.pm = &tpacpi_pm,
},
.shutdown = tpacpi_shutdown_handler,
@@ -981,7 +980,6 @@ static struct platform_driver tpacpi_pdriver = {
static struct platform_driver tpacpi_hwmon_pdriver = {
.driver = {
.name = TPACPI_HWMON_DRVR_NAME,
- .owner = THIS_MODULE,
},
};
@@ -3440,7 +3438,7 @@ err_exit:
delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
hotkey_dev_attributes = NULL;
- return (res < 0)? res : 1;
+ return (res < 0) ? res : 1;
}
/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser
@@ -4576,7 +4574,7 @@ static int __init video_init(struct ibm_init_struct *iibm)
str_supported(video_supported != TPACPI_VIDEO_NONE),
video_supported);
- return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
+ return (video_supported != TPACPI_VIDEO_NONE) ? 0 : 1;
}
static void video_exit(void)
@@ -4669,7 +4667,7 @@ static int video_outputsw_set(int status)
return -ENOSYS;
}
- return (res)? 0 : -EIO;
+ return (res) ? 0 : -EIO;
}
static int video_autosw_get(void)
@@ -4695,7 +4693,7 @@ static int video_autosw_get(void)
static int video_autosw_set(int enable)
{
- if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0))
+ if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable) ? 1 : 0))
return -EIO;
return 0;
}
@@ -4730,20 +4728,20 @@ static int video_outputsw_cycle(void)
return -EIO;
}
- return (res)? 0 : -EIO;
+ return (res) ? 0 : -EIO;
}
static int video_expand_toggle(void)
{
switch (video_supported) {
case TPACPI_VIDEO_570:
- return acpi_evalf(ec_handle, NULL, "_Q17", "v")?
+ return acpi_evalf(ec_handle, NULL, "_Q17", "v") ?
0 : -EIO;
case TPACPI_VIDEO_770:
- return acpi_evalf(vid_handle, NULL, "VEXP", "v")?
+ return acpi_evalf(vid_handle, NULL, "VEXP", "v") ?
0 : -EIO;
case TPACPI_VIDEO_NEW:
- return acpi_evalf(NULL, NULL, "\\VEXP", "v")?
+ return acpi_evalf(NULL, NULL, "\\VEXP", "v") ?
0 : -EIO;
default:
return -ENOSYS;
@@ -4887,14 +4885,14 @@ static int light_set_status(int status)
if (tp_features.light) {
if (cmos_handle) {
rc = acpi_evalf(cmos_handle, NULL, NULL, "vd",
- (status)?
+ (status) ?
TP_CMOS_THINKLIGHT_ON :
TP_CMOS_THINKLIGHT_OFF);
} else {
rc = acpi_evalf(lght_handle, NULL, NULL, "vd",
- (status)? 1 : 0);
+ (status) ? 1 : 0);
}
- return (rc)? 0 : -EIO;
+ return (rc) ? 0 : -EIO;
}
return -ENXIO;
@@ -4923,7 +4921,7 @@ static void light_sysfs_set(struct led_classdev *led_cdev,
static enum led_brightness light_sysfs_get(struct led_classdev *led_cdev)
{
- return (light_get_status() == 1)? LED_FULL : LED_OFF;
+ return (light_get_status() == 1) ? LED_FULL : LED_OFF;
}
static struct tpacpi_led_classdev tpacpi_led_thinklight = {
@@ -5045,7 +5043,7 @@ static ssize_t cmos_command_store(struct device *dev,
return -EINVAL;
res = issue_thinkpad_cmos_command(cmos_cmd);
- return (res)? res : count;
+ return (res) ? res : count;
}
static struct device_attribute dev_attr_cmos_command =
@@ -5069,7 +5067,7 @@ static int __init cmos_init(struct ibm_init_struct *iibm)
if (res)
return res;
- return (cmos_handle)? 0 : 1;
+ return (cmos_handle) ? 0 : 1;
}
static void cmos_exit(void)
@@ -5179,9 +5177,9 @@ static int led_get_status(const unsigned int led)
if (!acpi_evalf(ec_handle,
&status, "GLED", "dd", 1 << led))
return -EIO;
- led_s = (status == 0)?
+ led_s = (status == 0) ?
TPACPI_LED_OFF :
- ((status == 1)?
+ ((status == 1) ?
TPACPI_LED_ON :
TPACPI_LED_BLINK);
tpacpi_led_state_cache[led] = led_s;
@@ -5578,7 +5576,7 @@ static int __init beep_init(struct ibm_init_struct *iibm)
tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1);
- return (beep_handle)? 0 : 1;
+ return (beep_handle) ? 0 : 1;
}
static int beep_read(struct seq_file *m)
@@ -6527,7 +6525,7 @@ static int brightness_write(char *buf)
if (!rc && ibm_backlight_device)
backlight_force_update(ibm_backlight_device,
BACKLIGHT_UPDATE_SYSFS);
- return (rc == -EINTR)? -ERESTARTSYS : rc;
+ return (rc == -EINTR) ? -ERESTARTSYS : rc;
}
static struct ibm_struct brightness_driver_data = {
@@ -6559,6 +6557,17 @@ static struct ibm_struct brightness_driver_data = {
* bits 3-0 (volume). Other bits in NVRAM may have other functions,
* such as bit 7 which is used to detect repeated presses of MUTE,
* and we leave them unchanged.
+ *
+ * On newer Lenovo ThinkPads, the EC can automatically change the volume
+ * in response to user input. Unfortunately, this rarely works well.
+ * The laptop changes the state of its internal MUTE gate and, on some
+ * models, sends KEY_MUTE, causing any user code that responds to the
+ * mute button to get confused. The hardware MUTE gate is also
+ * unnecessary, since user code can handle the mute button without
+ * kernel or EC help.
+ *
+ * To avoid confusing userspace, we simply disable all EC-based mute
+ * and volume controls when possible.
*/
#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
@@ -6613,11 +6622,21 @@ enum tpacpi_volume_capabilities {
TPACPI_VOL_CAP_MAX
};
+enum tpacpi_mute_btn_mode {
+ TP_EC_MUTE_BTN_LATCH = 0, /* Mute mutes; up/down unmutes */
+ /* We don't know what mode 1 is. */
+ TP_EC_MUTE_BTN_NONE = 2, /* Mute and up/down are just keys */
+ TP_EC_MUTE_BTN_TOGGLE = 3, /* Mute toggles; up/down unmutes */
+};
+
static enum tpacpi_volume_access_mode volume_mode =
TPACPI_VOL_MODE_MAX;
static enum tpacpi_volume_capabilities volume_capabilities;
static bool volume_control_allowed;
+static bool software_mute_requested = true;
+static bool software_mute_active;
+static int software_mute_orig_mode;
/*
* Used to syncronize writers to TP_EC_AUDIO and
@@ -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,57 @@ unlock:
return rc;
}
+static int volume_set_software_mute(bool startup)
+{
+ int result;
+
+ if (!tpacpi_is_lenovo())
+ return -ENODEV;
+
+ if (startup) {
+ if (!acpi_evalf(ec_handle, &software_mute_orig_mode,
+ "HAUM", "qd"))
+ return -EIO;
+
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "Initial HAUM setting was %d\n",
+ software_mute_orig_mode);
+ }
+
+ if (!acpi_evalf(ec_handle, &result, "SAUM", "qdd",
+ (int)TP_EC_MUTE_BTN_NONE))
+ return -EIO;
+
+ if (result != TP_EC_MUTE_BTN_NONE)
+ pr_warn("Unexpected SAUM result %d\n",
+ result);
+
+ /*
+ * In software mute mode, the standard codec controls take
+ * precendence, so we unmute the ThinkPad HW switch at
+ * startup. Just on case there are SAUM-capable ThinkPads
+ * with level controls, set max HW volume as well.
+ */
+ if (tp_features.mixer_no_level_control)
+ result = volume_set_mute(false);
+ else
+ result = volume_set_status(TP_EC_VOLUME_MAX);
+
+ if (result != 0)
+ pr_warn("Failed to unmute the HW mute switch\n");
+
+ return 0;
+}
+
+static void volume_exit_software_mute(void)
+{
+ int r;
+
+ if (!acpi_evalf(ec_handle, &r, "SAUM", "qdd", software_mute_orig_mode)
+ || r != software_mute_orig_mode)
+ pr_warn("Failed to restore mute mode\n");
+}
+
static int volume_alsa_set_volume(const u8 vol)
{
dbg_printk(TPACPI_DBG_MIXER,
@@ -6885,7 +6963,12 @@ static void volume_suspend(void)
static void volume_resume(void)
{
- volume_alsa_notify_change();
+ if (software_mute_active) {
+ if (volume_set_software_mute(false) < 0)
+ pr_warn("Failed to restore software mute\n");
+ } else {
+ volume_alsa_notify_change();
+ }
}
static void volume_shutdown(void)
@@ -6901,6 +6984,9 @@ static void volume_exit(void)
}
tpacpi_volume_checkpoint_nvram();
+
+ if (software_mute_active)
+ volume_exit_software_mute();
}
static int __init volume_create_alsa_mixer(void)
@@ -7085,16 +7171,20 @@ static int __init volume_init(struct ibm_init_struct *iibm)
"mute is supported, volume control is %s\n",
str_supported(!tp_features.mixer_no_level_control));
- rc = volume_create_alsa_mixer();
- if (rc) {
- pr_err("Could not create the ALSA mixer interface\n");
- return rc;
- }
+ if (software_mute_requested && volume_set_software_mute(true) == 0) {
+ software_mute_active = true;
+ } else {
+ rc = volume_create_alsa_mixer();
+ if (rc) {
+ pr_err("Could not create the ALSA mixer interface\n");
+ return rc;
+ }
- pr_info("Console audio control enabled, mode: %s\n",
- (volume_control_allowed) ?
- "override (read/write)" :
- "monitor (read only)");
+ pr_info("Console audio control enabled, mode: %s\n",
+ (volume_control_allowed) ?
+ "override (read/write)" :
+ "monitor (read only)");
+ }
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
"registering volume hotkeys as change notification\n");
@@ -7984,7 +8074,7 @@ static ssize_t fan_pwm1_store(struct device *dev,
}
mutex_unlock(&fan_mutex);
- return (rc)? rc : count;
+ return (rc) ? rc : count;
}
static struct device_attribute dev_attr_fan_pwm1 =
@@ -8662,7 +8752,7 @@ static const char * __init str_supported(int is_supported)
{
static char text_unsupported[] __initdata = "not supported";
- return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
+ return (is_supported) ? &text_unsupported[4] : &text_unsupported[0];
}
#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
@@ -8783,7 +8873,7 @@ err_out:
ibm->name, ret);
ibm_exit(ibm);
- return (ret < 0)? ret : 0;
+ return (ret < 0) ? ret : 0;
}
/* Probing */
@@ -8794,7 +8884,7 @@ static bool __pure __init tpacpi_is_fw_digit(const char c)
}
/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */
-static bool __pure __init tpacpi_is_valid_fw_id(const char* const s,
+static bool __pure __init tpacpi_is_valid_fw_id(const char * const s,
const char t)
{
return s && strlen(s) >= 8 &&
@@ -8878,13 +8968,13 @@ static int __must_check __init get_thinkpad_model_data(
}
s = dmi_get_system_info(DMI_PRODUCT_VERSION);
- if (s && !(strnicmp(s, "ThinkPad", 8) && strnicmp(s, "Lenovo", 6))) {
+ if (s && !(strncasecmp(s, "ThinkPad", 8) && strncasecmp(s, "Lenovo", 6))) {
tp->model_str = kstrdup(s, GFP_KERNEL);
if (!tp->model_str)
return -ENOMEM;
} else {
s = dmi_get_system_info(DMI_BIOS_VENDOR);
- if (s && !(strnicmp(s, "Lenovo", 6))) {
+ if (s && !(strncasecmp(s, "Lenovo", 6))) {
tp->model_str = kstrdup(s, GFP_KERNEL);
if (!tp->model_str)
return -ENOMEM;
@@ -9091,6 +9181,10 @@ MODULE_PARM_DESC(volume_control,
"Enables software override for the console audio "
"control when true");
+module_param_named(software_mute, software_mute_requested, bool, 0444);
+MODULE_PARM_DESC(software_mute,
+ "Request full software mute control");
+
/* ALSA module API parameters */
module_param_named(index, alsa_index, int, 0444);
MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");