Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 177552
b: refs/heads/master
c: a112cee
h: refs/heads/master
v: v3
  • Loading branch information
Henrique de Moraes Holschuh authored and Len Brown committed Dec 16, 2009
1 parent cc5b3e1 commit 60c9e47
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 30 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 329e4e18dfdc552f36b0642a3de5ebfa96063666
refs/heads/master: a112ceee673629afc204bf6b4a4828a6143a083f
19 changes: 17 additions & 2 deletions trunk/Documentation/laptops/thinkpad-acpi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1098,18 +1098,31 @@ Volume control
procfs: /proc/acpi/ibm/volume

This feature allows volume control on ThinkPad models with a digital
volume knob, as well as mute/unmute control. The available commands are:
volume knob (when available, not all models have it), as well as
mute/unmute control. The available commands are:

echo up >/proc/acpi/ibm/volume
echo down >/proc/acpi/ibm/volume
echo mute >/proc/acpi/ibm/volume
echo unmute >/proc/acpi/ibm/volume
echo 'level <level>' >/proc/acpi/ibm/volume

The <level> number range is 0 to 14 although not all of them may be
distinct. The unmute the volume after the mute command, use either the
up or down command (the level command will not unmute the volume).
up or down command (the level command will not unmute the volume), or
the unmute command.

The current volume level and mute state is shown in the file.

You can use the volume_capabilities parameter to tell the driver
whether your thinkpad has volume control or mute-only control:
volume_capabilities=1 for mixers with mute and volume control,
volume_capabilities=2 for mixers with only mute control.

If the driver misdetects the capabilities for your ThinkPad model,
please report this to ibm-acpi-devel@lists.sourceforge.net, so that we
can update the driver.

There are two strategies for volume control. To select which one
should be used, use the volume_mode module parameter: volume_mode=1
selects EC mode, and volume_mode=3 selects EC mode with NVRAM backing
Expand Down Expand Up @@ -1450,3 +1463,5 @@ Sysfs interface changelog:
is deprecated and marked for removal.

0x020600: Marker for backlight change event support.

0x020700: Support for mute-only mixers.
159 changes: 132 additions & 27 deletions trunk/drivers/platform/x86/thinkpad_acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*/

#define TPACPI_VERSION "0.23"
#define TPACPI_SYSFS_VERSION 0x020600
#define TPACPI_SYSFS_VERSION 0x020700

/*
* Changelog:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -6430,14 +6445,19 @@ 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;

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;

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 " \
Expand Down

0 comments on commit 60c9e47

Please sign in to comment.