[ALSA] hda-codec - Add virtual master controls

Add master controls using vmaster to codecs that have no real hardware
master volume registers.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Takashi Iwai 2008-01-10 16:53:55 +01:00 committed by Jaroslav Kysela
parent 3b0a5f22d4
commit 2134ea4f37
5 changed files with 276 additions and 19 deletions

View File

@ -1012,6 +1012,66 @@ int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
return 0;
}
/*
* set (static) TLV for virtual master volume; recalculated as max 0dB
*/
void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int *tlv)
{
u32 caps;
int nums, step;
caps = query_amp_caps(codec, nid, dir);
nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
step = (step + 1) * 25;
tlv[0] = SNDRV_CTL_TLVT_DB_SCALE;
tlv[1] = 2 * sizeof(unsigned int);
tlv[2] = -nums * step;
tlv[3] = step;
}
/* find a mixer control element with the given name */
struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
const char *name)
{
struct snd_ctl_elem_id id;
memset(&id, 0, sizeof(id));
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
strcpy(id.name, name);
return snd_ctl_find_id(codec->bus->card, &id);
}
/* create a virtual master control and add slaves */
int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
unsigned int *tlv, const char **slaves)
{
struct snd_kcontrol *kctl;
const char **s;
int err;
kctl = snd_ctl_make_virtual_master(name, tlv);
if (!kctl)
return -ENOMEM;
err = snd_ctl_add(codec->bus->card, kctl);
if (err < 0)
return err;
for (s = slaves; *s; s++) {
struct snd_kcontrol *sctl;
sctl = snd_hda_find_mixer_ctl(codec, *s);
if (!sctl) {
snd_printdd("Cannot find slave %s, skipped\n", *s);
continue;
}
err = snd_ctl_add_slave(kctl, sctl);
if (err < 0)
return err;
}
return 0;
}
/* switch */
int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)

View File

@ -90,6 +90,13 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
void snd_hda_codec_resume_amp(struct hda_codec *codec);
#endif
void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int *tlv);
struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
const char *name);
int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
unsigned int *tlv, const char **slaves);
/* amp value bits */
#define HDA_AMP_MUTE 0x80
#define HDA_AMP_UNMUTE 0x00

View File

@ -78,6 +78,11 @@ struct ad198x_spec {
#ifdef CONFIG_SND_HDA_POWER_SAVE
struct hda_loopback_check loopback;
#endif
/* for virtual master */
hda_nid_t vmaster_nid;
u32 vmaster_tlv[4];
const char **slave_vols;
const char **slave_sws;
};
/*
@ -125,6 +130,28 @@ static int ad198x_init(struct hda_codec *codec)
return 0;
}
static const char *ad_slave_vols[] = {
"Front Playback Volume",
"Surround Playback Volume",
"Center Playback Volume",
"LFE Playback Volume",
"Side Playback Volume",
"Headphone Playback Volume",
"Mono Playback Volume",
NULL
};
static const char *ad_slave_sws[] = {
"Front Playback Switch",
"Surround Playback Switch",
"Center Playback Switch",
"LFE Playback Switch",
"Side Playback Switch",
"Headphone Playback Switch",
"Mono Playback Switch",
NULL
};
static int ad198x_build_controls(struct hda_codec *codec)
{
struct ad198x_spec *spec = codec->spec;
@ -146,6 +173,27 @@ static int ad198x_build_controls(struct hda_codec *codec)
if (err < 0)
return err;
}
/* if we have no master control, let's create it */
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
HDA_OUTPUT, spec->vmaster_tlv);
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
spec->vmaster_tlv,
(spec->slave_vols ?
spec->slave_vols : ad_slave_vols));
if (err < 0)
return err;
}
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
err = snd_hda_add_vmaster(codec, "Master Playback Switch",
NULL,
(spec->slave_sws ?
spec->slave_sws : ad_slave_sws));
if (err < 0)
return err;
}
return 0;
}
@ -899,6 +947,7 @@ static int patch_ad1986a(struct hda_codec *codec)
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1986a_loopbacks;
#endif
spec->vmaster_nid = 0x1b;
codec->patch_ops = ad198x_patch_ops;
@ -1141,6 +1190,7 @@ static int patch_ad1983(struct hda_codec *codec)
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1983_loopbacks;
#endif
spec->vmaster_nid = 0x05;
codec->patch_ops = ad198x_patch_ops;
@ -1537,6 +1587,7 @@ static int patch_ad1981(struct hda_codec *codec)
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1981_loopbacks;
#endif
spec->vmaster_nid = 0x05;
codec->patch_ops = ad198x_patch_ops;
@ -2850,6 +2901,7 @@ static int patch_ad1988(struct hda_codec *codec)
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1988_loopbacks;
#endif
spec->vmaster_nid = 0x04;
return 0;
}
@ -3016,6 +3068,19 @@ static struct hda_amp_list ad1884_loopbacks[] = {
};
#endif
static const char *ad1884_slave_vols[] = {
"PCM Playback Volume",
"Mic Playback Volume",
"Mono Playback Volume",
"Front Mic Playback Volume",
"Mic Playback Volume",
"CD Playback Volume",
"Internal Mic Playback Volume",
"Docking Mic Playback Volume"
"Beep Playback Volume",
NULL
};
static int patch_ad1884(struct hda_codec *codec)
{
struct ad198x_spec *spec;
@ -3043,6 +3108,9 @@ static int patch_ad1884(struct hda_codec *codec)
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1884_loopbacks;
#endif
spec->vmaster_nid = 0x04;
/* we need to cover all playback volumes */
spec->slave_vols = ad1884_slave_vols;
codec->patch_ops = ad198x_patch_ops;
@ -3485,6 +3553,7 @@ static int patch_ad1882(struct hda_codec *codec)
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1882_loopbacks;
#endif
spec->vmaster_nid = 0x04;
codec->patch_ops = ad198x_patch_ops;

View File

@ -262,6 +262,9 @@ struct alc_spec {
unsigned int sense_updated: 1;
unsigned int jack_present: 1;
/* for virtual master */
hda_nid_t vmaster_nid;
u32 vmaster_tlv[4];
#ifdef CONFIG_SND_HDA_POWER_SAVE
struct hda_loopback_check loopback;
#endif
@ -1309,8 +1312,8 @@ static hda_nid_t alc880_f1734_dac_nids[1] = {
static struct snd_kcontrol_new alc880_f1734_mixer[] = {
HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Internal Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Internal Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
@ -1408,10 +1411,10 @@ static struct snd_kcontrol_new alc880_tcl_s700_mixer[] = {
/* Uniwill */
static struct snd_kcontrol_new alc880_uniwill_mixer[] = {
HDA_CODEC_VOLUME("HPhone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("HPhone Playback Switch", 0x0c, 2, HDA_INPUT),
HDA_CODEC_VOLUME("iSpeaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("iSpeaker Playback Switch", 0x0d, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
@ -1451,15 +1454,49 @@ static struct snd_kcontrol_new alc880_fujitsu_mixer[] = {
};
static struct snd_kcontrol_new alc880_uniwill_p53_mixer[] = {
HDA_CODEC_VOLUME("HPhone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("HPhone Playback Switch", 0x0c, 2, HDA_INPUT),
HDA_CODEC_VOLUME("iSpeaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("iSpeaker Playback Switch", 0x0d, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
{ } /* end */
};
/*
* virtual master controls
*/
/*
* slave controls for virtual master
*/
static const char *alc_slave_vols[] = {
"Front Playback Volume",
"Surround Playback Volume",
"Center Playback Volume",
"LFE Playback Volume",
"Side Playback Volume",
"Headphone Playback Volume",
"Speaker Playback Volume",
"Mono Playback Volume",
"iSpeaker Playback Volume",
"Line-Out Playback Volume",
NULL,
};
static const char *alc_slave_sws[] = {
"Front Playback Switch",
"Surround Playback Switch",
"Center Playback Switch",
"LFE Playback Switch",
"Side Playback Switch",
"Headphone Playback Switch",
"Speaker Playback Switch",
"Mono Playback Switch",
"iSpeaker Playback Switch",
NULL,
};
/*
* build control elements
*/
@ -1486,6 +1523,23 @@ static int alc_build_controls(struct hda_codec *codec)
if (err < 0)
return err;
}
/* if we have no master control, let's create it */
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
HDA_OUTPUT, spec->vmaster_tlv);
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
spec->vmaster_tlv, alc_slave_vols);
if (err < 0)
return err;
}
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
err = snd_hda_add_vmaster(codec, "Master Playback Switch",
NULL, alc_slave_sws);
if (err < 0)
return err;
}
return 0;
}
@ -2034,8 +2088,8 @@ static struct hda_channel_mode alc880_lg_ch_modes[3] = {
static struct snd_kcontrol_new alc880_lg_mixer[] = {
/* FIXME: it's not really "master" but front channels */
HDA_CODEC_VOLUME("Master Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Master Playback Switch", 0x0f, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Front Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Front Playback Switch", 0x0f, 2, HDA_INPUT),
HDA_CODEC_VOLUME("Surround Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_BIND_MUTE("Surround Playback Switch", 0x0c, 2, HDA_INPUT),
HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT),
@ -3592,6 +3646,8 @@ static int patch_alc880(struct hda_codec *codec)
}
}
spec->vmaster_nid = 0x0c;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC880_AUTO)
spec->init_hook = alc880_auto_init;
@ -4969,6 +5025,8 @@ static int patch_alc260(struct hda_codec *codec)
spec->stream_digital_playback = &alc260_pcm_digital_playback;
spec->stream_digital_capture = &alc260_pcm_digital_capture;
spec->vmaster_nid = 0x08;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC260_AUTO)
spec->init_hook = alc260_auto_init;
@ -5169,15 +5227,15 @@ static struct snd_kcontrol_new alc882_base_mixer[] = {
};
static struct snd_kcontrol_new alc885_mbp3_mixer[] = {
HDA_CODEC_VOLUME("Master Volume", 0x0c, 0x00, HDA_OUTPUT),
HDA_BIND_MUTE ("Master Switch", 0x0c, 0x02, HDA_INPUT),
HDA_CODEC_MUTE ("Speaker Switch", 0x14, 0x00, HDA_OUTPUT),
HDA_CODEC_VOLUME("Line Out Volume", 0x0d,0x00, HDA_OUTPUT),
HDA_CODEC_VOLUME("Line In Playback Volume", 0x0b, 0x02, HDA_INPUT),
HDA_CODEC_MUTE ("Line In Playback Switch", 0x0b, 0x02, HDA_INPUT),
HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
HDA_BIND_MUTE ("Front Playback Switch", 0x0c, 0x02, HDA_INPUT),
HDA_CODEC_MUTE ("Speaker Playback Switch", 0x14, 0x00, HDA_OUTPUT),
HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x0d, 0x00, HDA_OUTPUT),
HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
HDA_CODEC_MUTE ("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x00, HDA_INPUT),
HDA_CODEC_MUTE ("Mic Playback Switch", 0x0b, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("Line Boost", 0x1a, 0x00, HDA_INPUT),
HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x00, HDA_INPUT),
{ } /* end */
};
@ -6181,6 +6239,8 @@ static int patch_alc882(struct hda_codec *codec)
}
}
spec->vmaster_nid = 0x0c;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC882_AUTO)
spec->init_hook = alc882_auto_init;
@ -7763,6 +7823,8 @@ static int patch_alc883(struct hda_codec *codec)
spec->num_adc_nids = ARRAY_SIZE(alc883_adc_nids);
}
spec->vmaster_nid = 0x0c;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC883_AUTO)
spec->init_hook = alc883_auto_init;
@ -9123,6 +9185,8 @@ static int patch_alc262(struct hda_codec *codec)
}
}
spec->vmaster_nid = 0x0c;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC262_AUTO)
spec->init_hook = alc262_auto_init;
@ -9848,6 +9912,9 @@ static int patch_alc268(struct hda_codec *codec)
}
}
}
spec->vmaster_nid = 0x02;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC268_AUTO)
spec->init_hook = alc268_auto_init;
@ -11358,6 +11425,8 @@ static int patch_alc861(struct hda_codec *codec)
spec->stream_digital_playback = &alc861_pcm_digital_playback;
spec->stream_digital_capture = &alc861_pcm_digital_capture;
spec->vmaster_nid = 0x03;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC861_AUTO)
spec->init_hook = alc861_auto_init;
@ -12334,6 +12403,8 @@ static int patch_alc861vd(struct hda_codec *codec)
spec->mixers[spec->num_mixers] = alc861vd_capture_mixer;
spec->num_mixers++;
spec->vmaster_nid = 0x02;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC861VD_AUTO)
@ -13305,6 +13376,8 @@ static int patch_alc662(struct hda_codec *codec)
spec->num_adc_nids = ARRAY_SIZE(alc662_adc_nids);
}
spec->vmaster_nid = 0x02;
codec->patch_ops = alc_patch_ops;
if (board_config == ALC662_AUTO)
spec->init_hook = alc662_auto_init;

View File

@ -170,6 +170,9 @@ struct sigmatel_spec {
struct snd_kcontrol_new *kctl_alloc;
struct hda_input_mux private_dimux;
struct hda_input_mux private_imux;
/* virtual master */
unsigned int vmaster_tlv[4];
};
static hda_nid_t stac9200_adc_nids[1] = {
@ -794,6 +797,34 @@ static struct snd_kcontrol_new stac_dmux_mixer = {
.put = stac92xx_dmux_enum_put,
};
static const char *slave_vols[] = {
"Front Playback Volume",
"Surround Playback Volume",
"Center Playback Volume",
"LFE Playback Volume",
"Side Playback Volume",
"Headphone Playback Volume",
"Headphone Playback Volume",
"Speaker Playback Volume",
"External Speaker Playback Volume",
"Speaker2 Playback Volume",
NULL
};
static const char *slave_sws[] = {
"Front Playback Switch",
"Surround Playback Switch",
"Center Playback Switch",
"LFE Playback Switch",
"Side Playback Switch",
"Headphone Playback Switch",
"Headphone Playback Switch",
"Speaker Playback Switch",
"External Speaker Playback Switch",
"Speaker2 Playback Switch",
NULL
};
static int stac92xx_build_controls(struct hda_codec *codec)
{
struct sigmatel_spec *spec = codec->spec;
@ -827,6 +858,23 @@ static int stac92xx_build_controls(struct hda_codec *codec)
if (err < 0)
return err;
}
/* if we have no master control, let's create it */
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
snd_hda_set_vmaster_tlv(codec, spec->multiout.dac_nids[0],
HDA_OUTPUT, spec->vmaster_tlv);
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
spec->vmaster_tlv, slave_vols);
if (err < 0)
return err;
}
if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
err = snd_hda_add_vmaster(codec, "Master Playback Switch",
NULL, slave_sws);
if (err < 0)
return err;
}
return 0;
}