mirror of
https://github.com/adulau/aha.git
synced 2024-12-27 19:26:25 +00:00
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
2 changed files with 239 additions and 5 deletions
|
@ -1096,6 +1096,7 @@ Volume control
|
|||
--------------
|
||||
|
||||
procfs: /proc/acpi/ibm/volume
|
||||
ALSA: "ThinkPad Console Audio Control", default ID: "ThinkPadEC"
|
||||
|
||||
NOTE: by default, the volume control interface operates in read-only
|
||||
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
|
||||
ibm-acpi-devel@lists.sourceforge.net.
|
||||
|
||||
The ALSA mixer interface to this feature is still missing, but patches
|
||||
to add it exist. That problem should be addressed in the not so
|
||||
distant future.
|
||||
The driver supports the standard ALSA module parameters. If the ALSA
|
||||
mixer is disabled, the driver will disable all volume functionality.
|
||||
|
||||
|
||||
Fan control and monitoring: fan speed, fan enable/disable
|
||||
|
@ -1478,3 +1478,4 @@ Sysfs interface changelog:
|
|||
|
||||
0x020700: Support for mute-only mixers.
|
||||
Volume control in read-only mode by default.
|
||||
Marker for ALSA mixer support.
|
||||
|
|
|
@ -76,6 +76,10 @@
|
|||
#include <linux/jiffies.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
#include <acpi/acpi_drivers.h>
|
||||
|
||||
#include <linux/pci_ids.h>
|
||||
|
@ -6402,6 +6406,22 @@ static struct ibm_struct brightness_driver_data = {
|
|||
* 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 {
|
||||
TP_EC_AUDIO = 0x30,
|
||||
|
||||
|
@ -6584,11 +6604,104 @@ static int volume_set_volume(const u8 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)
|
||||
{
|
||||
tpacpi_volume_checkpoint_nvram();
|
||||
}
|
||||
|
||||
static void volume_resume(void)
|
||||
{
|
||||
volume_alsa_notify_change();
|
||||
}
|
||||
|
||||
static void volume_shutdown(void)
|
||||
{
|
||||
tpacpi_volume_checkpoint_nvram();
|
||||
|
@ -6596,9 +6709,87 @@ static void volume_shutdown(void)
|
|||
|
||||
static void volume_exit(void)
|
||||
{
|
||||
if (alsa_card) {
|
||||
snd_card_free(alsa_card);
|
||||
alsa_card = NULL;
|
||||
}
|
||||
|
||||
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_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)
|
||||
{
|
||||
unsigned long quirks;
|
||||
int rc;
|
||||
|
||||
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)
|
||||
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,
|
||||
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",
|
||||
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
|
||||
"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");
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -6807,6 +7024,7 @@ static int volume_write(char *buf)
|
|||
new_mute ? "" : "un", new_level);
|
||||
rc = volume_set_status(new_mute | new_level);
|
||||
}
|
||||
volume_alsa_notify_change();
|
||||
|
||||
return (rc == -EINTR) ? -ERESTARTSYS : rc;
|
||||
}
|
||||
|
@ -6817,6 +7035,7 @@ static struct ibm_struct volume_driver_data = {
|
|||
.write = volume_write,
|
||||
.exit = volume_exit,
|
||||
.suspend = volume_suspend,
|
||||
.resume = volume_resume,
|
||||
.shutdown = volume_shutdown,
|
||||
};
|
||||
|
||||
|
@ -8115,10 +8334,16 @@ static void tpacpi_driver_event(const unsigned int hkey_event)
|
|||
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)
|
||||
{
|
||||
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 "
|
||||
"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) \
|
||||
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
|
||||
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
|
||||
|
|
Loading…
Reference in a new issue