thinkpad-acpi: basic ALSA mixer support (v2)
Add the basic ALSA mixer functionality. The mixer is event-driven, and will work fine on IBM ThinkPads. I expect Lenovo ThinkPads will cause some trouble with the event interface. Heavily based on work by Lorne Applebaum <lorne.applebaum@gmail.com> and ideas from Matthew Garrett <mjg@redhat.com>. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br> Cc: Lorne Applebaum <lorne.applebaum@gmail.com> Cc: Matthew Garrett <mjg@redhat.com> Signed-off-by: Len Brown <len.brown@intel.com>
This commit is contained in:
parent
c7ac6291ea
commit
0d204c34e8
@ -1096,6 +1096,7 @@ Volume control
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
procfs: /proc/acpi/ibm/volume
|
procfs: /proc/acpi/ibm/volume
|
||||||
|
ALSA: "ThinkPad Console Audio Control", default ID: "ThinkPadEC"
|
||||||
|
|
||||||
NOTE: by default, the volume control interface operates in read-only
|
NOTE: by default, the volume control interface operates in read-only
|
||||||
mode, as it is supposed to be used for on-screen-display purposes.
|
mode, as it is supposed to be used for on-screen-display purposes.
|
||||||
@ -1144,9 +1145,8 @@ The driver will operate in volume_mode=3 by default. If that does not
|
|||||||
work well on your ThinkPad model, please report this to
|
work well on your ThinkPad model, please report this to
|
||||||
ibm-acpi-devel@lists.sourceforge.net.
|
ibm-acpi-devel@lists.sourceforge.net.
|
||||||
|
|
||||||
The ALSA mixer interface to this feature is still missing, but patches
|
The driver supports the standard ALSA module parameters. If the ALSA
|
||||||
to add it exist. That problem should be addressed in the not so
|
mixer is disabled, the driver will disable all volume functionality.
|
||||||
distant future.
|
|
||||||
|
|
||||||
|
|
||||||
Fan control and monitoring: fan speed, fan enable/disable
|
Fan control and monitoring: fan speed, fan enable/disable
|
||||||
@ -1478,3 +1478,4 @@ Sysfs interface changelog:
|
|||||||
|
|
||||||
0x020700: Support for mute-only mixers.
|
0x020700: Support for mute-only mixers.
|
||||||
Volume control in read-only mode by default.
|
Volume control in read-only mode by default.
|
||||||
|
Marker for ALSA mixer support.
|
||||||
|
@ -76,6 +76,10 @@
|
|||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
|
#include <sound/core.h>
|
||||||
|
#include <sound/control.h>
|
||||||
|
#include <sound/initval.h>
|
||||||
|
|
||||||
#include <acpi/acpi_drivers.h>
|
#include <acpi/acpi_drivers.h>
|
||||||
|
|
||||||
#include <linux/pci_ids.h>
|
#include <linux/pci_ids.h>
|
||||||
@ -6402,6 +6406,22 @@ static struct ibm_struct brightness_driver_data = {
|
|||||||
* and we leave them unchanged.
|
* and we leave them unchanged.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define TPACPI_ALSA_DRVNAME "ThinkPad EC"
|
||||||
|
#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
|
||||||
|
#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
|
||||||
|
|
||||||
|
static int alsa_index = SNDRV_DEFAULT_IDX1;
|
||||||
|
static char *alsa_id = "ThinkPadEC";
|
||||||
|
static int alsa_enable = SNDRV_DEFAULT_ENABLE1;
|
||||||
|
|
||||||
|
struct tpacpi_alsa_data {
|
||||||
|
struct snd_card *card;
|
||||||
|
struct snd_ctl_elem_id *ctl_mute_id;
|
||||||
|
struct snd_ctl_elem_id *ctl_vol_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_card *alsa_card;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
TP_EC_AUDIO = 0x30,
|
TP_EC_AUDIO = 0x30,
|
||||||
|
|
||||||
@ -6584,11 +6604,104 @@ static int volume_set_volume(const u8 vol)
|
|||||||
return volume_set_volume_ec(vol);
|
return volume_set_volume_ec(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void volume_alsa_notify_change(void)
|
||||||
|
{
|
||||||
|
struct tpacpi_alsa_data *d;
|
||||||
|
|
||||||
|
if (alsa_card && alsa_card->private_data) {
|
||||||
|
d = alsa_card->private_data;
|
||||||
|
if (d->ctl_mute_id)
|
||||||
|
snd_ctl_notify(alsa_card,
|
||||||
|
SNDRV_CTL_EVENT_MASK_VALUE,
|
||||||
|
d->ctl_mute_id);
|
||||||
|
if (d->ctl_vol_id)
|
||||||
|
snd_ctl_notify(alsa_card,
|
||||||
|
SNDRV_CTL_EVENT_MASK_VALUE,
|
||||||
|
d->ctl_vol_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_info *uinfo)
|
||||||
|
{
|
||||||
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||||
|
uinfo->count = 1;
|
||||||
|
uinfo->value.integer.min = 0;
|
||||||
|
uinfo->value.integer.max = TP_EC_VOLUME_MAX;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
u8 s;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = volume_get_status(&s);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
return volume_set_volume(ucontrol->value.integer.value[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define volume_alsa_mute_info snd_ctl_boolean_mono_info
|
||||||
|
|
||||||
|
static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
u8 s;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = volume_get_status(&s);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
ucontrol->value.integer.value[0] =
|
||||||
|
(s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
return volume_set_mute(!ucontrol->value.integer.value[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = {
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Console Playback Volume",
|
||||||
|
.index = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||||
|
.info = volume_alsa_vol_info,
|
||||||
|
.get = volume_alsa_vol_get,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = {
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Console Playback Switch",
|
||||||
|
.index = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
||||||
|
.info = volume_alsa_mute_info,
|
||||||
|
.get = volume_alsa_mute_get,
|
||||||
|
};
|
||||||
|
|
||||||
static void volume_suspend(pm_message_t state)
|
static void volume_suspend(pm_message_t state)
|
||||||
{
|
{
|
||||||
tpacpi_volume_checkpoint_nvram();
|
tpacpi_volume_checkpoint_nvram();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void volume_resume(void)
|
||||||
|
{
|
||||||
|
volume_alsa_notify_change();
|
||||||
|
}
|
||||||
|
|
||||||
static void volume_shutdown(void)
|
static void volume_shutdown(void)
|
||||||
{
|
{
|
||||||
tpacpi_volume_checkpoint_nvram();
|
tpacpi_volume_checkpoint_nvram();
|
||||||
@ -6596,9 +6709,87 @@ static void volume_shutdown(void)
|
|||||||
|
|
||||||
static void volume_exit(void)
|
static void volume_exit(void)
|
||||||
{
|
{
|
||||||
|
if (alsa_card) {
|
||||||
|
snd_card_free(alsa_card);
|
||||||
|
alsa_card = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
tpacpi_volume_checkpoint_nvram();
|
tpacpi_volume_checkpoint_nvram();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int __init volume_create_alsa_mixer(void)
|
||||||
|
{
|
||||||
|
struct snd_card *card;
|
||||||
|
struct tpacpi_alsa_data *data;
|
||||||
|
struct snd_kcontrol *ctl_vol;
|
||||||
|
struct snd_kcontrol *ctl_mute;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE,
|
||||||
|
sizeof(struct tpacpi_alsa_data), &card);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
if (!card)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
BUG_ON(!card->private_data);
|
||||||
|
data = card->private_data;
|
||||||
|
data->card = card;
|
||||||
|
|
||||||
|
strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
|
||||||
|
sizeof(card->driver));
|
||||||
|
strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
|
||||||
|
sizeof(card->shortname));
|
||||||
|
snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
|
||||||
|
(thinkpad_id.ec_version_str) ?
|
||||||
|
thinkpad_id.ec_version_str : "(unknown)");
|
||||||
|
snprintf(card->longname, sizeof(card->longname),
|
||||||
|
"%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO,
|
||||||
|
(thinkpad_id.ec_version_str) ?
|
||||||
|
thinkpad_id.ec_version_str : "unknown");
|
||||||
|
|
||||||
|
if (volume_control_allowed) {
|
||||||
|
volume_alsa_control_vol.put = volume_alsa_vol_put;
|
||||||
|
volume_alsa_control_vol.access =
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||||
|
|
||||||
|
volume_alsa_control_mute.put = volume_alsa_mute_put;
|
||||||
|
volume_alsa_control_mute.access =
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tp_features.mixer_no_level_control) {
|
||||||
|
ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
|
||||||
|
rc = snd_ctl_add(card, ctl_vol);
|
||||||
|
if (rc < 0) {
|
||||||
|
printk(TPACPI_ERR
|
||||||
|
"Failed to create ALSA volume control\n");
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
data->ctl_vol_id = &ctl_vol->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
|
||||||
|
rc = snd_ctl_add(card, ctl_mute);
|
||||||
|
if (rc < 0) {
|
||||||
|
printk(TPACPI_ERR "Failed to create ALSA mute control\n");
|
||||||
|
goto err_out;
|
||||||
|
}
|
||||||
|
data->ctl_mute_id = &ctl_mute->id;
|
||||||
|
|
||||||
|
snd_card_set_dev(card, &tpacpi_pdev->dev);
|
||||||
|
rc = snd_card_register(card);
|
||||||
|
|
||||||
|
err_out:
|
||||||
|
if (rc < 0) {
|
||||||
|
snd_card_free(card);
|
||||||
|
card = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
alsa_card = card;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */
|
#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */
|
||||||
#define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */
|
#define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */
|
||||||
|
|
||||||
@ -6628,6 +6819,7 @@ static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
|
|||||||
static int __init volume_init(struct ibm_init_struct *iibm)
|
static int __init volume_init(struct ibm_init_struct *iibm)
|
||||||
{
|
{
|
||||||
unsigned long quirks;
|
unsigned long quirks;
|
||||||
|
int rc;
|
||||||
|
|
||||||
vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
|
vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
|
||||||
|
|
||||||
@ -6651,6 +6843,17 @@ static int __init volume_init(struct ibm_init_struct *iibm)
|
|||||||
if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
|
if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ALSA mixer is our primary interface.
|
||||||
|
* When disabled, don't install the subdriver at all
|
||||||
|
*/
|
||||||
|
if (!alsa_enable) {
|
||||||
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
||||||
|
"ALSA mixer disabled by parameter, "
|
||||||
|
"not loading volume subdriver...\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
quirks = tpacpi_check_quirks(volume_quirk_table,
|
quirks = tpacpi_check_quirks(volume_quirk_table,
|
||||||
ARRAY_SIZE(volume_quirk_table));
|
ARRAY_SIZE(volume_quirk_table));
|
||||||
|
|
||||||
@ -6695,12 +6898,26 @@ static int __init volume_init(struct ibm_init_struct *iibm)
|
|||||||
"mute is supported, volume control is %s\n",
|
"mute is supported, volume control is %s\n",
|
||||||
str_supported(!tp_features.mixer_no_level_control));
|
str_supported(!tp_features.mixer_no_level_control));
|
||||||
|
|
||||||
|
rc = volume_create_alsa_mixer();
|
||||||
|
if (rc) {
|
||||||
|
printk(TPACPI_ERR
|
||||||
|
"Could not create the ALSA mixer interface\n");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
printk(TPACPI_INFO
|
printk(TPACPI_INFO
|
||||||
"Console audio control enabled, mode: %s\n",
|
"Console audio control enabled, mode: %s\n",
|
||||||
(volume_control_allowed) ?
|
(volume_control_allowed) ?
|
||||||
"override (read/write)" :
|
"override (read/write)" :
|
||||||
"monitor (read only)");
|
"monitor (read only)");
|
||||||
|
|
||||||
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
||||||
|
"registering volume hotkeys as change notification\n");
|
||||||
|
tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
|
||||||
|
| TP_ACPI_HKEY_VOLUP_MASK
|
||||||
|
| TP_ACPI_HKEY_VOLDWN_MASK
|
||||||
|
| TP_ACPI_HKEY_MUTE_MASK);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6807,6 +7024,7 @@ static int volume_write(char *buf)
|
|||||||
new_mute ? "" : "un", new_level);
|
new_mute ? "" : "un", new_level);
|
||||||
rc = volume_set_status(new_mute | new_level);
|
rc = volume_set_status(new_mute | new_level);
|
||||||
}
|
}
|
||||||
|
volume_alsa_notify_change();
|
||||||
|
|
||||||
return (rc == -EINTR) ? -ERESTARTSYS : rc;
|
return (rc == -EINTR) ? -ERESTARTSYS : rc;
|
||||||
}
|
}
|
||||||
@ -6817,6 +7035,7 @@ static struct ibm_struct volume_driver_data = {
|
|||||||
.write = volume_write,
|
.write = volume_write,
|
||||||
.exit = volume_exit,
|
.exit = volume_exit,
|
||||||
.suspend = volume_suspend,
|
.suspend = volume_suspend,
|
||||||
|
.resume = volume_resume,
|
||||||
.shutdown = volume_shutdown,
|
.shutdown = volume_shutdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -8115,10 +8334,16 @@ static void tpacpi_driver_event(const unsigned int hkey_event)
|
|||||||
tpacpi_brightness_notify_change();
|
tpacpi_brightness_notify_change();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (alsa_card) {
|
||||||
|
switch (hkey_event) {
|
||||||
|
case TP_HKEY_EV_VOL_UP:
|
||||||
|
case TP_HKEY_EV_VOL_DOWN:
|
||||||
|
case TP_HKEY_EV_VOL_MUTE:
|
||||||
|
volume_alsa_notify_change();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void hotkey_driver_event(const unsigned int scancode)
|
static void hotkey_driver_event(const unsigned int scancode)
|
||||||
{
|
{
|
||||||
tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
|
tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
|
||||||
@ -8552,6 +8777,14 @@ MODULE_PARM_DESC(volume_control,
|
|||||||
"Enables software override for the console audio "
|
"Enables software override for the console audio "
|
||||||
"control when true");
|
"control when true");
|
||||||
|
|
||||||
|
/* ALSA module API parameters */
|
||||||
|
module_param_named(index, alsa_index, int, 0444);
|
||||||
|
MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
|
||||||
|
module_param_named(id, alsa_id, charp, 0444);
|
||||||
|
MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
|
||||||
|
module_param_named(enable, alsa_enable, bool, 0444);
|
||||||
|
MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer");
|
||||||
|
|
||||||
#define TPACPI_PARAM(feature) \
|
#define TPACPI_PARAM(feature) \
|
||||||
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
|
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
|
||||||
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
|
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
|
||||||
|
Loading…
Reference in New Issue
Block a user