Skip to content

Commit

Permalink
ALSA: hda - Add the inverted digital mic workaround to Realtek codecs
Browse files Browse the repository at this point in the history
Some laptops are equipped with ForteMedia digital mics that give the
differential input.  With such devices, summing stereo streams into a
mono (like PulseAudio does) results in almost silence.

This patch provides a workaround for this bug by adding a new mixer
switch to turn on/off the right channel of digital mic, just like a
similar fix for Conexant codecs.

When the new switch "Inverted Internal Mic Capture Switch" is off and
the current input source is the digital mic, the right channel of the
recording stream is muted.  When another input source is selected, the
right channel is restored.

Tested-by: Eliot Blennerhassett <eliot@blennerhassett.gen.nz>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
  • Loading branch information
Takashi Iwai committed Jun 25, 2012
1 parent b43d224 commit 125821a
Showing 1 changed file with 125 additions and 2 deletions.
127 changes: 125 additions & 2 deletions sound/pci/hda/patch_realtek.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ struct alc_spec {
hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS];
unsigned int dyn_adc_idx[HDA_MAX_NUM_INPUTS];
int int_mic_idx, ext_mic_idx, dock_mic_idx; /* for auto-mic */
hda_nid_t inv_dmic_pin;

/* hooks */
void (*init_hook)(struct hda_codec *codec);
Expand Down Expand Up @@ -201,6 +202,8 @@ struct alc_spec {
unsigned int vol_in_capsrc:1; /* use capsrc volume (ADC has no vol) */
unsigned int parse_flags; /* passed to snd_hda_parse_pin_defcfg() */
unsigned int shared_mic_hp:1; /* HP/Mic-in sharing */
unsigned int inv_dmic_fixup:1; /* has inverted digital-mic workaround */
unsigned int inv_dmic_muted:1; /* R-ch of inv d-mic is muted? */

/* auto-mute control */
int automute_mode;
Expand Down Expand Up @@ -298,6 +301,7 @@ static inline hda_nid_t get_capsrc(struct alc_spec *spec, int idx)
}

static void call_update_outputs(struct hda_codec *codec);
static void alc_inv_dmic_sync(struct hda_codec *codec, bool force);

/* select the given imux item; either unmute exclusively or select the route */
static int alc_mux_select(struct hda_codec *codec, unsigned int adc_idx,
Expand Down Expand Up @@ -368,6 +372,7 @@ static int alc_mux_select(struct hda_codec *codec, unsigned int adc_idx,
AC_VERB_SET_CONNECT_SEL,
imux->items[idx].index);
}
alc_inv_dmic_sync(codec, true);
return 1;
}

Expand Down Expand Up @@ -1556,14 +1561,14 @@ typedef int (*getput_call_t)(struct snd_kcontrol *kcontrol,

static int alc_cap_getput_caller(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
getput_call_t func, bool check_adc_switch)
getput_call_t func, bool is_put)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
int i, err = 0;

mutex_lock(&codec->control_mutex);
if (check_adc_switch && spec->dyn_adc_switch) {
if (is_put && spec->dyn_adc_switch) {
for (i = 0; i < spec->num_adc_nids; i++) {
kcontrol->private_value =
HDA_COMPOSE_AMP_VAL(spec->adc_nids[i],
Expand All @@ -1584,6 +1589,8 @@ static int alc_cap_getput_caller(struct snd_kcontrol *kcontrol,
3, 0, HDA_INPUT);
err = func(kcontrol, ucontrol);
}
if (err >= 0 && is_put)
alc_inv_dmic_sync(codec, false);
error:
mutex_unlock(&codec->control_mutex);
return err;
Expand Down Expand Up @@ -1675,6 +1682,108 @@ DEFINE_CAPMIX_NOSRC(1);
DEFINE_CAPMIX_NOSRC(2);
DEFINE_CAPMIX_NOSRC(3);

/*
* Inverted digital-mic handling
*
* First off, it's a bit tricky. The "Inverted Internal Mic Capture Switch"
* gives the additional mute only to the right channel of the digital mic
* capture stream. This is a workaround for avoiding the almost silence
* by summing the stereo stream from some (known to be ForteMedia)
* digital mic unit.
*
* The logic is to call alc_inv_dmic_sync() after each action (possibly)
* modifying ADC amp. When the mute flag is set, it mutes the R-channel
* without caching so that the cache can still keep the original value.
* The cached value is then restored when the flag is set off or any other
* than d-mic is used as the current input source.
*/
static void alc_inv_dmic_sync(struct hda_codec *codec, bool force)
{
struct alc_spec *spec = codec->spec;
int i;

if (!spec->inv_dmic_fixup)
return;
if (!spec->inv_dmic_muted && !force)
return;
for (i = 0; i < spec->num_adc_nids; i++) {
int src = spec->dyn_adc_switch ? 0 : i;
bool dmic_fixup = false;
hda_nid_t nid;
int parm, dir, v;

if (spec->inv_dmic_muted &&
spec->imux_pins[spec->cur_mux[src]] == spec->inv_dmic_pin)
dmic_fixup = true;
if (!dmic_fixup && !force)
continue;
if (spec->vol_in_capsrc) {
nid = spec->capsrc_nids[i];
parm = AC_AMP_SET_RIGHT | AC_AMP_SET_OUTPUT;
dir = HDA_OUTPUT;
} else {
nid = spec->adc_nids[i];
parm = AC_AMP_SET_RIGHT | AC_AMP_SET_INPUT;
dir = HDA_INPUT;
}
/* we care only right channel */
v = snd_hda_codec_amp_read(codec, nid, 1, dir, 0);
if (v & 0x80) /* if already muted, we don't need to touch */
continue;
if (dmic_fixup) /* add mute for d-mic */
v |= 0x80;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
parm | v);
}
}

static int alc_inv_dmic_sw_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;

ucontrol->value.integer.value[0] = !spec->inv_dmic_muted;
return 0;
}

static int alc_inv_dmic_sw_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
unsigned int val = !ucontrol->value.integer.value[0];

if (val == spec->inv_dmic_muted)
return 0;
spec->inv_dmic_muted = val;
alc_inv_dmic_sync(codec, true);
return 0;
}

static const struct snd_kcontrol_new alc_inv_dmic_sw = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_ctl_boolean_mono_info,
.get = alc_inv_dmic_sw_get,
.put = alc_inv_dmic_sw_put,
};

static int alc_add_inv_dmic_mixer(struct hda_codec *codec, hda_nid_t nid)
{
struct alc_spec *spec = codec->spec;
struct snd_kcontrol_new *knew = alc_kcontrol_new(spec);
if (!knew)
return -ENOMEM;
*knew = alc_inv_dmic_sw;
knew->name = kstrdup("Inverted Internal Mic Capture Switch", GFP_KERNEL);
if (!knew->name)
return -ENOMEM;
spec->inv_dmic_fixup = 1;
spec->inv_dmic_muted = 0;
spec->inv_dmic_pin = nid;
return 0;
}

/*
* virtual master controls
*/
Expand Down Expand Up @@ -2316,6 +2425,7 @@ static int alc_resume(struct hda_codec *codec)
codec->patch_ops.init(codec);
snd_hda_codec_resume_amp(codec);
snd_hda_codec_resume_cache(codec);
alc_inv_dmic_sync(codec, true);
hda_call_check_power_status(codec, 0x01);
return 0;
}
Expand Down Expand Up @@ -6424,6 +6534,13 @@ static void alc272_fixup_mario(struct hda_codec *codec,
"hda_codec: failed to override amp caps for NID 0x2\n");
}

static void alc662_fixup_inv_dmic(struct hda_codec *codec,
const struct alc_fixup *fix, int action)
{
if (action == ALC_FIXUP_ACT_PROBE)
alc_add_inv_dmic_mixer(codec, 0x12);
}

enum {
ALC662_FIXUP_ASPIRE,
ALC662_FIXUP_IDEAPAD,
Expand All @@ -6441,6 +6558,7 @@ enum {
ALC662_FIXUP_ASUS_MODE8,
ALC662_FIXUP_NO_JACK_DETECT,
ALC662_FIXUP_ZOTAC_Z68,
ALC662_FIXUP_INV_DMIC,
};

static const struct alc_fixup alc662_fixups[] = {
Expand Down Expand Up @@ -6597,12 +6715,17 @@ static const struct alc_fixup alc662_fixups[] = {
{ }
}
},
[ALC662_FIXUP_INV_DMIC] = {
.type = ALC_FIXUP_FUNC,
.v.func = alc662_fixup_inv_dmic,
},
};

static const struct snd_pci_quirk alc662_fixup_tbl[] = {
SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2),
SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE),
SND_PCI_QUIRK(0x1025, 0x031c, "Gateway NV79", ALC662_FIXUP_SKU_IGNORE),
SND_PCI_QUIRK(0x1025, 0x0349, "eMachines eM250", ALC662_FIXUP_INV_DMIC),
SND_PCI_QUIRK(0x1025, 0x038b, "Acer Aspire 8943G", ALC662_FIXUP_ASPIRE),
SND_PCI_QUIRK(0x103c, 0x1632, "HP RP5800", ALC662_FIXUP_HP_RP5800),
SND_PCI_QUIRK(0x1043, 0x8469, "ASUS mobo", ALC662_FIXUP_NO_JACK_DETECT),
Expand Down

0 comments on commit 125821a

Please sign in to comment.