|
@@ -22,7 +22,7 @@
|
|
|
*/
|
|
|
|
|
|
#define TPACPI_VERSION "0.23"
|
|
|
-#define TPACPI_SYSFS_VERSION 0x020600
|
|
|
+#define TPACPI_SYSFS_VERSION 0x020700
|
|
|
|
|
|
/*
|
|
|
* Changelog:
|
|
@@ -299,6 +299,7 @@ static struct {
|
|
|
u32 fan_ctrl_status_undef:1;
|
|
|
u32 second_fan:1;
|
|
|
u32 beep_needs_two_args:1;
|
|
|
+ u32 mixer_no_level_control:1;
|
|
|
u32 input_device_registered:1;
|
|
|
u32 platform_drv_registered:1;
|
|
|
u32 platform_drv_attrs_registered:1;
|
|
@@ -426,6 +427,12 @@ static void tpacpi_log_usertask(const char * const what)
|
|
|
.ec = TPACPI_MATCH_ANY, \
|
|
|
.quirks = (__quirk) }
|
|
|
|
|
|
+#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \
|
|
|
+ { .vendor = PCI_VENDOR_ID_LENOVO, \
|
|
|
+ .bios = TPACPI_MATCH_ANY, \
|
|
|
+ .ec = TPID(__id1, __id2), \
|
|
|
+ .quirks = (__quirk) }
|
|
|
+
|
|
|
struct tpacpi_quirk {
|
|
|
unsigned int vendor;
|
|
|
u16 bios;
|
|
@@ -6416,9 +6423,17 @@ enum tpacpi_volume_access_mode {
|
|
|
TPACPI_VOL_MODE_MAX
|
|
|
};
|
|
|
|
|
|
+enum tpacpi_volume_capabilities {
|
|
|
+ TPACPI_VOL_CAP_AUTO = 0, /* Use white/blacklist */
|
|
|
+ TPACPI_VOL_CAP_VOLMUTE, /* Output vol and mute */
|
|
|
+ TPACPI_VOL_CAP_MUTEONLY, /* Output mute only */
|
|
|
+ TPACPI_VOL_CAP_MAX
|
|
|
+};
|
|
|
+
|
|
|
static enum tpacpi_volume_access_mode volume_mode =
|
|
|
TPACPI_VOL_MODE_MAX;
|
|
|
|
|
|
+static enum tpacpi_volume_capabilities volume_capabilities;
|
|
|
|
|
|
/*
|
|
|
* Used to syncronize writers to TP_EC_AUDIO and
|
|
@@ -6430,7 +6445,7 @@ static void tpacpi_volume_checkpoint_nvram(void)
|
|
|
{
|
|
|
u8 lec = 0;
|
|
|
u8 b_nvram;
|
|
|
- const u8 ec_mask = TP_EC_AUDIO_LVL_MSK | TP_EC_AUDIO_MUTESW_MSK;
|
|
|
+ u8 ec_mask;
|
|
|
|
|
|
if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
|
|
|
return;
|
|
@@ -6438,6 +6453,11 @@ static void tpacpi_volume_checkpoint_nvram(void)
|
|
|
vdbg_printk(TPACPI_DBG_MIXER,
|
|
|
"trying to checkpoint mixer state to NVRAM...\n");
|
|
|
|
|
|
+ if (tp_features.mixer_no_level_control)
|
|
|
+ ec_mask = TP_EC_AUDIO_MUTESW_MSK;
|
|
|
+ else
|
|
|
+ ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
|
|
|
+
|
|
|
if (mutex_lock_killable(&volume_mutex) < 0)
|
|
|
return;
|
|
|
|
|
@@ -6575,8 +6595,36 @@ static void volume_exit(void)
|
|
|
tpacpi_volume_checkpoint_nvram();
|
|
|
}
|
|
|
|
|
|
+#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */
|
|
|
+#define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */
|
|
|
+
|
|
|
+static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
|
|
|
+ /* Whitelist volume level on all IBM by default */
|
|
|
+ { .vendor = PCI_VENDOR_ID_IBM,
|
|
|
+ .bios = TPACPI_MATCH_ANY,
|
|
|
+ .ec = TPACPI_MATCH_ANY,
|
|
|
+ .quirks = TPACPI_VOL_Q_LEVEL },
|
|
|
+
|
|
|
+ /* Lenovo models with volume control (needs confirmation) */
|
|
|
+ TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */
|
|
|
+ TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */
|
|
|
+ TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */
|
|
|
+ TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */
|
|
|
+ TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */
|
|
|
+ TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */
|
|
|
+ TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */
|
|
|
+
|
|
|
+ /* Whitelist mute-only on all Lenovo by default */
|
|
|
+ { .vendor = PCI_VENDOR_ID_LENOVO,
|
|
|
+ .bios = TPACPI_MATCH_ANY,
|
|
|
+ .ec = TPACPI_MATCH_ANY,
|
|
|
+ .quirks = TPACPI_VOL_Q_MUTEONLY }
|
|
|
+};
|
|
|
+
|
|
|
static int __init volume_init(struct ibm_init_struct *iibm)
|
|
|
{
|
|
|
+ unsigned long quirks;
|
|
|
+
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
|
|
|
|
|
|
mutex_init(&volume_mutex);
|
|
@@ -6596,6 +6644,36 @@ static int __init volume_init(struct ibm_init_struct *iibm)
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
+ if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ quirks = tpacpi_check_quirks(volume_quirk_table,
|
|
|
+ ARRAY_SIZE(volume_quirk_table));
|
|
|
+
|
|
|
+ switch (volume_capabilities) {
|
|
|
+ case TPACPI_VOL_CAP_AUTO:
|
|
|
+ if (quirks & TPACPI_VOL_Q_MUTEONLY)
|
|
|
+ tp_features.mixer_no_level_control = 1;
|
|
|
+ else if (quirks & TPACPI_VOL_Q_LEVEL)
|
|
|
+ tp_features.mixer_no_level_control = 0;
|
|
|
+ else
|
|
|
+ return 1; /* no mixer */
|
|
|
+ break;
|
|
|
+ case TPACPI_VOL_CAP_VOLMUTE:
|
|
|
+ tp_features.mixer_no_level_control = 0;
|
|
|
+ break;
|
|
|
+ case TPACPI_VOL_CAP_MUTEONLY:
|
|
|
+ tp_features.mixer_no_level_control = 1;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
|
|
|
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
+ "using user-supplied volume_capabilities=%d\n",
|
|
|
+ volume_capabilities);
|
|
|
+
|
|
|
if (volume_mode == TPACPI_VOL_MODE_AUTO ||
|
|
|
volume_mode == TPACPI_VOL_MODE_MAX) {
|
|
|
volume_mode = TPACPI_VOL_MODE_ECNVRAM;
|
|
@@ -6610,7 +6688,8 @@ static int __init volume_init(struct ibm_init_struct *iibm)
|
|
|
}
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
- "volume is supported\n");
|
|
|
+ "mute is supported, volume control is %s\n",
|
|
|
+ str_supported(!tp_features.mixer_no_level_control));
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -6623,13 +6702,21 @@ static int volume_read(char *p)
|
|
|
if (volume_get_status(&status) < 0) {
|
|
|
len += sprintf(p + len, "level:\t\tunreadable\n");
|
|
|
} else {
|
|
|
- len += sprintf(p + len, "level:\t\t%d\n",
|
|
|
- status & TP_EC_AUDIO_LVL_MSK);
|
|
|
+ if (tp_features.mixer_no_level_control)
|
|
|
+ len += sprintf(p + len, "level:\t\tunsupported\n");
|
|
|
+ else
|
|
|
+ len += sprintf(p + len, "level:\t\t%d\n",
|
|
|
+ status & TP_EC_AUDIO_LVL_MSK);
|
|
|
+
|
|
|
len += sprintf(p + len, "mute:\t\t%s\n",
|
|
|
onoff(status, TP_EC_AUDIO_MUTESW));
|
|
|
- len += sprintf(p + len, "commands:\tup, down, mute\n");
|
|
|
- len += sprintf(p + len, "commands:\tlevel <level>"
|
|
|
+
|
|
|
+ len += sprintf(p + len, "commands:\tunmute, mute\n");
|
|
|
+ if (!tp_features.mixer_no_level_control) {
|
|
|
+ len += sprintf(p + len, "commands:\tup, down\n");
|
|
|
+ len += sprintf(p + len, "commands:\tlevel <level>"
|
|
|
" (<level> is 0-%d)\n", TP_EC_VOLUME_MAX);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return len;
|
|
@@ -6651,30 +6738,43 @@ static int volume_write(char *buf)
|
|
|
new_mute = s & TP_EC_AUDIO_MUTESW_MSK;
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
- if (strlencmp(cmd, "up") == 0) {
|
|
|
- if (new_mute)
|
|
|
- new_mute = 0;
|
|
|
- else if (new_level < TP_EC_VOLUME_MAX)
|
|
|
- new_level++;
|
|
|
- } else if (strlencmp(cmd, "down") == 0) {
|
|
|
- if (new_mute)
|
|
|
- new_mute = 0;
|
|
|
- else if (new_level > 0)
|
|
|
- new_level--;
|
|
|
- } else if (sscanf(cmd, "level %u", &l) == 1 &&
|
|
|
- l >= 0 && l <= TP_EC_VOLUME_MAX) {
|
|
|
- new_level = l;
|
|
|
- } else if (strlencmp(cmd, "mute") == 0) {
|
|
|
+ if (!tp_features.mixer_no_level_control) {
|
|
|
+ if (strlencmp(cmd, "up") == 0) {
|
|
|
+ if (new_mute)
|
|
|
+ new_mute = 0;
|
|
|
+ else if (new_level < TP_EC_VOLUME_MAX)
|
|
|
+ new_level++;
|
|
|
+ continue;
|
|
|
+ } else if (strlencmp(cmd, "down") == 0) {
|
|
|
+ if (new_mute)
|
|
|
+ new_mute = 0;
|
|
|
+ else if (new_level > 0)
|
|
|
+ new_level--;
|
|
|
+ continue;
|
|
|
+ } else if (sscanf(cmd, "level %u", &l) == 1 &&
|
|
|
+ l >= 0 && l <= TP_EC_VOLUME_MAX) {
|
|
|
+ new_level = l;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (strlencmp(cmd, "mute") == 0)
|
|
|
new_mute = TP_EC_AUDIO_MUTESW_MSK;
|
|
|
- } else
|
|
|
+ else if (strlencmp(cmd, "unmute") == 0)
|
|
|
+ new_mute = 0;
|
|
|
+ else
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- tpacpi_disclose_usertask("procfs volume",
|
|
|
- "%smute and set level to %d\n",
|
|
|
- new_mute ? "" : "un", new_level);
|
|
|
-
|
|
|
- rc = volume_set_status(new_mute | new_level);
|
|
|
+ if (tp_features.mixer_no_level_control) {
|
|
|
+ tpacpi_disclose_usertask("procfs volume", "%smute\n",
|
|
|
+ new_mute ? "" : "un");
|
|
|
+ rc = volume_set_mute(!!new_mute);
|
|
|
+ } else {
|
|
|
+ tpacpi_disclose_usertask("procfs volume",
|
|
|
+ "%smute and set level to %d\n",
|
|
|
+ new_mute ? "" : "un", new_level);
|
|
|
+ rc = volume_set_status(new_mute | new_level);
|
|
|
+ }
|
|
|
|
|
|
return (rc == -EINTR) ? -ERESTARTSYS : rc;
|
|
|
}
|
|
@@ -8410,6 +8510,11 @@ MODULE_PARM_DESC(volume_mode,
|
|
|
"Selects volume control strategy: "
|
|
|
"0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
|
|
|
|
|
|
+module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
|
|
|
+MODULE_PARM_DESC(volume_capabilities,
|
|
|
+ "Selects the mixer capabilites: "
|
|
|
+ "0=auto, 1=volume and mute, 2=mute only");
|
|
|
+
|
|
|
#define TPACPI_PARAM(feature) \
|
|
|
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
|
|
|
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
|