Skip to content

Commit

Permalink
ALSA: hda - Initialize vmaster slave volumes
Browse files Browse the repository at this point in the history
When the driver is changed to use vmaster or a new slave element is
added by the improvement of the parser code, user may face often the
silent output because of the muted slave mixer although Master volume
is properly set.  And they complain.  And I get upset.

Although such a mixer element should be initialized via "alsactl init",
it'd be more user-friendly if the known output slaves are unmuted and
set to 0dB so that user can control the volume only with Master as
default.  Since Master is still set muted as default even with this
change, no risk of the speaker blow up, too.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
  • Loading branch information
Takashi Iwai committed Mar 9, 2012
1 parent 785f857 commit 18478e8
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 7 deletions.
65 changes: 62 additions & 3 deletions sound/pci/hda/hda_codec.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <linux/mm.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
Expand Down Expand Up @@ -2340,13 +2341,64 @@ static int check_slave_present(void *data, struct snd_kcontrol *sctl)
return 1;
}

/* guess the value corresponding to 0dB */
static int get_kctl_0dB_offset(struct snd_kcontrol *kctl)
{
int _tlv[4];
const int *tlv = NULL;
int val = -1;

if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
/* FIXME: set_fs() hack for obtaining user-space TLV data */
mm_segment_t fs = get_fs();
set_fs(get_ds());
if (!kctl->tlv.c(kctl, 0, sizeof(_tlv), _tlv))
tlv = _tlv;
set_fs(fs);
} else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)
tlv = kctl->tlv.p;
if (tlv && tlv[0] == SNDRV_CTL_TLVT_DB_SCALE)
val = -tlv[2] / tlv[3];
return val;
}

/* call kctl->put with the given value(s) */
static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
{
struct snd_ctl_elem_value *ucontrol;
ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL);
if (!ucontrol)
return -ENOMEM;
ucontrol->value.integer.value[0] = val;
ucontrol->value.integer.value[1] = val;
kctl->put(kctl, ucontrol);
kfree(ucontrol);
return 0;
}

/* initialize the slave volume with 0dB */
static int init_slave_0dB(void *data, struct snd_kcontrol *slave)
{
int offset = get_kctl_0dB_offset(slave);
if (offset > 0)
put_kctl_with_value(slave, offset);
return 0;
}

/* unmute the slave */
static int init_slave_unmute(void *data, struct snd_kcontrol *slave)
{
return put_kctl_with_value(slave, 1);
}

/**
* snd_hda_add_vmaster - create a virtual master control and add slaves
* @codec: HD-audio codec
* @name: vmaster control name
* @tlv: TLV data (optional)
* @slaves: slave control names (optional)
* @suffix: suffix string to each slave name (optional)
* @init_slave_vol: initialize slaves to unmute/0dB
*
* Create a virtual master control with the given name. The TLV data
* must be either NULL or a valid data.
Expand All @@ -2357,9 +2409,9 @@ static int check_slave_present(void *data, struct snd_kcontrol *sctl)
*
* This function returns zero if successful or a negative error code.
*/
int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
unsigned int *tlv, const char * const *slaves,
const char *suffix)
const char *suffix, bool init_slave_vol)
{
struct snd_kcontrol *kctl;
int err;
Expand All @@ -2380,9 +2432,16 @@ int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
(map_slave_func_t)snd_ctl_add_slave, kctl);
if (err < 0)
return err;

/* init with master mute & zero volume */
put_kctl_with_value(kctl, 0);
if (init_slave_vol)
map_slaves(codec, slaves, suffix,
tlv ? init_slave_0dB : init_slave_unmute, kctl);

return 0;
}
EXPORT_SYMBOL_HDA(snd_hda_add_vmaster);
EXPORT_SYMBOL_HDA(__snd_hda_add_vmaster);

/**
* snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch
Expand Down
6 changes: 4 additions & 2 deletions sound/pci/hda/hda_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ 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,
int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
unsigned int *tlv, const char * const *slaves,
const char *suffix);
const char *suffix, bool init_slave_vol);
#define snd_hda_add_vmaster(codec, name, tlv, slaves, suffix) \
__snd_hda_add_vmaster(codec, name, tlv, slaves, suffix, true)
int snd_hda_codec_reset(struct hda_codec *codec);

/* amp value bits */
Expand Down
8 changes: 6 additions & 2 deletions sound/pci/hda/patch_analog.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ struct ad198x_spec {
unsigned int inv_jack_detect: 1;/* inverted jack-detection */
unsigned int inv_eapd: 1; /* inverted EAPD implementation */
unsigned int analog_beep: 1; /* analog beep input present */
unsigned int avoid_init_slave_vol:1;

#ifdef CONFIG_SND_HDA_POWER_SAVE
struct hda_loopback_check loopback;
Expand Down Expand Up @@ -223,11 +224,12 @@ static int ad198x_build_controls(struct hda_codec *codec)
unsigned int vmaster_tlv[4];
snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
HDA_OUTPUT, vmaster_tlv);
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
err = __snd_hda_add_vmaster(codec, "Master Playback Volume",
vmaster_tlv,
(spec->slave_vols ?
spec->slave_vols : ad_slave_pfxs),
"Playback Volume");
"Playback Volume",
!spec->avoid_init_slave_vol);
if (err < 0)
return err;
}
Expand Down Expand Up @@ -3604,6 +3606,8 @@ static int patch_ad1884(struct hda_codec *codec)
spec->vmaster_nid = 0x04;
/* we need to cover all playback volumes */
spec->slave_vols = ad1884_slave_vols;
/* slaves may contain input volumes, so we can't raise to 0dB blindly */
spec->avoid_init_slave_vol = 1;

codec->patch_ops = ad198x_patch_ops;

Expand Down

0 comments on commit 18478e8

Please sign in to comment.