Skip to content

Commit

Permalink
ALSA: hda - Add support of dual-ADCs for Realtek ALC275
Browse files Browse the repository at this point in the history
Some VAIO models with ALC275 have dual ADCs for both internal and external
mics, and the driver needs to switch one of them appropriately.
This patch adds a basic support for this functionality, dynamic switching
between two ADCs per jack plug state.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
  • Loading branch information
Takashi Iwai committed Jul 13, 2010
1 parent 32e0191 commit 840b64c
Showing 1 changed file with 154 additions and 24 deletions.
178 changes: 154 additions & 24 deletions sound/pci/hda/patch_realtek.c
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ struct alc_spec {
hda_nid_t *capsrc_nids;
hda_nid_t dig_in_nid; /* digital-in NID; optional */

/* capture setup for dynamic dual-adc switch */
unsigned int cur_adc_idx;
hda_nid_t cur_adc;
unsigned int cur_adc_stream_tag;
unsigned int cur_adc_format;

/* capture source */
unsigned int num_mux_defs;
const struct hda_input_mux *input_mux;
Expand Down Expand Up @@ -374,6 +380,7 @@ struct alc_spec {

/* other flags */
unsigned int no_analog :1; /* digital I/O only */
unsigned int dual_adc_switch:1; /* switch ADCs (for ALC275) */
int init_amp;

/* for virtual master */
Expand Down Expand Up @@ -1010,6 +1017,29 @@ static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
return -1;
}

/* switch the current ADC according to the jack state */
static void alc_dual_mic_adc_auto_switch(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
unsigned int present;
hda_nid_t new_adc;

present = snd_hda_jack_detect(codec, spec->ext_mic.pin);
if (present)
spec->cur_adc_idx = 1;
else
spec->cur_adc_idx = 0;
new_adc = spec->adc_nids[spec->cur_adc_idx];
if (spec->cur_adc && spec->cur_adc != new_adc) {
/* stream is running, let's swap the current ADC */
snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
spec->cur_adc = new_adc;
snd_hda_codec_setup_stream(codec, new_adc,
spec->cur_adc_stream_tag, 0,
spec->cur_adc_format);
}
}

static void alc_mic_automute(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
Expand All @@ -1024,6 +1054,11 @@ static void alc_mic_automute(struct hda_codec *codec)
if (snd_BUG_ON(!spec->adc_nids))
return;

if (spec->dual_adc_switch) {
alc_dual_mic_adc_auto_switch(codec);
return;
}

cap_nid = spec->capsrc_nids ? spec->capsrc_nids[0] : spec->adc_nids[0];

present = snd_hda_jack_detect(codec, spec->ext_mic.pin);
Expand Down Expand Up @@ -3614,6 +3649,41 @@ static int alc880_alt_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
return 0;
}

/* analog capture with dynamic dual-adc changes */
static int dualmic_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
unsigned int format,
struct snd_pcm_substream *substream)
{
struct alc_spec *spec = codec->spec;
spec->cur_adc = spec->adc_nids[spec->cur_adc_idx];
spec->cur_adc_stream_tag = stream_tag;
spec->cur_adc_format = format;
snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
return 0;
}

static int dualmic_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
struct snd_pcm_substream *substream)
{
struct alc_spec *spec = codec->spec;
snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
spec->cur_adc = 0;
return 0;
}

static struct hda_pcm_stream dualmic_pcm_analog_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
.nid = 0, /* fill later */
.ops = {
.prepare = dualmic_capture_pcm_prepare,
.cleanup = dualmic_capture_pcm_cleanup
},
};

/*
*/
Expand Down Expand Up @@ -5052,24 +5122,12 @@ static void fixup_automic_adc(struct hda_codec *codec)
spec->auto_mic = 0; /* disable auto-mic to be sure */
}

/* choose the ADC/MUX containing the input pin and initialize the setup */
static void fixup_single_adc(struct hda_codec *codec)
/* set the default connection to that pin */
static int init_capsrc_for_pin(struct hda_codec *codec, hda_nid_t pin)
{
struct alc_spec *spec = codec->spec;
hda_nid_t pin = 0;
int i;

/* search for the input pin; there must be only one */
for (i = 0; i < AUTO_PIN_LAST; i++) {
if (spec->autocfg.input_pins[i]) {
pin = spec->autocfg.input_pins[i];
break;
}
}
if (!pin)
return;

/* set the default connection to that pin */
for (i = 0; i < spec->num_adc_nids; i++) {
hda_nid_t cap = spec->capsrc_nids ?
spec->capsrc_nids[i] : spec->adc_nids[i];
Expand All @@ -5078,11 +5136,6 @@ static void fixup_single_adc(struct hda_codec *codec)
idx = get_connection_index(codec, cap, pin);
if (idx < 0)
continue;
/* use only this ADC */
if (spec->capsrc_nids)
spec->capsrc_nids += i;
spec->adc_nids += i;
spec->num_adc_nids = 1;
/* select or unmute this route */
if (get_wcaps_type(get_wcaps(codec, cap)) == AC_WID_AUD_MIX) {
snd_hda_codec_amp_stereo(codec, cap, HDA_INPUT, idx,
Expand All @@ -5091,10 +5144,45 @@ static void fixup_single_adc(struct hda_codec *codec)
snd_hda_codec_write_cache(codec, cap, 0,
AC_VERB_SET_CONNECT_SEL, idx);
}
return i; /* return the found index */
}
return -1; /* not found */
}

/* choose the ADC/MUX containing the input pin and initialize the setup */
static void fixup_single_adc(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
hda_nid_t pin = 0;
int i;

/* search for the input pin; there must be only one */
for (i = 0; i < AUTO_PIN_LAST; i++) {
if (spec->autocfg.input_pins[i]) {
pin = spec->autocfg.input_pins[i];
break;
}
}
if (!pin)
return;
i = init_capsrc_for_pin(codec, pin);
if (i >= 0) {
/* use only this ADC */
if (spec->capsrc_nids)
spec->capsrc_nids += i;
spec->adc_nids += i;
spec->num_adc_nids = 1;
}
}

/* initialize dual adcs */
static void fixup_dual_adc_switch(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
init_capsrc_for_pin(codec, spec->ext_mic.pin);
init_capsrc_for_pin(codec, spec->int_mic.pin);
}

static void set_capture_mixer(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
Expand All @@ -5108,15 +5196,20 @@ static void set_capture_mixer(struct hda_codec *codec)
};
if (spec->num_adc_nids > 0 && spec->num_adc_nids <= 3) {
int mux = 0;
if (spec->auto_mic)
int num_adcs = spec->num_adc_nids;
if (spec->dual_adc_switch)
fixup_dual_adc_switch(codec);
else if (spec->auto_mic)
fixup_automic_adc(codec);
else if (spec->input_mux) {
if (spec->input_mux->num_items > 1)
mux = 1;
else if (spec->input_mux->num_items == 1)
fixup_single_adc(codec);
}
spec->cap_mixer = caps[mux][spec->num_adc_nids - 1];
if (spec->dual_adc_switch)
num_adcs = 1;
spec->cap_mixer = caps[mux][num_adcs - 1];
}
}

Expand Down Expand Up @@ -14141,6 +14234,36 @@ static int alc269_mic2_mute_check_ps(struct hda_codec *codec, hda_nid_t nid)
}
#endif /* CONFIG_SND_HDA_POWER_SAVE */

static int alc275_setup_dual_adc(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;

if (codec->vendor_id != 0x10ec0275 || !spec->auto_mic)
return 0;
if ((spec->ext_mic.pin >= 0x18 && spec->int_mic.pin <= 0x13) ||
(spec->ext_mic.pin <= 0x12 && spec->int_mic.pin >= 0x18)) {
if (spec->ext_mic.pin <= 0x12) {
spec->private_adc_nids[0] = 0x08;
spec->private_adc_nids[1] = 0x11;
spec->private_capsrc_nids[0] = 0x23;
spec->private_capsrc_nids[1] = 0x22;
} else {
spec->private_adc_nids[0] = 0x11;
spec->private_adc_nids[1] = 0x08;
spec->private_capsrc_nids[0] = 0x22;
spec->private_capsrc_nids[1] = 0x23;
}
spec->adc_nids = spec->private_adc_nids;
spec->capsrc_nids = spec->private_capsrc_nids;
spec->num_adc_nids = 2;
spec->dual_adc_switch = 1;
snd_printdd("realtek: enabling dual ADC switchg (%02x:%02x)\n",
spec->adc_nids[0], spec->adc_nids[1]);
return 1;
}
return 0;
}

/*
* BIOS auto configuration
*/
Expand Down Expand Up @@ -14180,11 +14303,14 @@ static int alc269_parse_auto_config(struct hda_codec *codec)

spec->num_mux_defs = 1;
spec->input_mux = &spec->private_imux[0];
fillup_priv_adc_nids(codec, alc269_adc_candidates,
sizeof(alc269_adc_candidates));

if (!alc275_setup_dual_adc(codec))
fillup_priv_adc_nids(codec, alc269_adc_candidates,
sizeof(alc269_adc_candidates));

/* set default input source */
snd_hda_codec_write_cache(codec, spec->capsrc_nids[0],
if (!spec->dual_adc_switch)
snd_hda_codec_write_cache(codec, spec->capsrc_nids[0],
0, AC_VERB_SET_CONNECT_SEL,
spec->input_mux->items[0].index);

Expand Down Expand Up @@ -14480,6 +14606,10 @@ static int patch_alc269(struct hda_codec *codec)
*/
spec->stream_analog_playback = &alc269_44k_pcm_analog_playback;
spec->stream_analog_capture = &alc269_44k_pcm_analog_capture;
} else if (spec->dual_adc_switch) {
spec->stream_analog_playback = &alc269_pcm_analog_playback;
/* switch ADC dynamically */
spec->stream_analog_capture = &dualmic_pcm_analog_capture;
} else {
spec->stream_analog_playback = &alc269_pcm_analog_playback;
spec->stream_analog_capture = &alc269_pcm_analog_capture;
Expand Down

0 comments on commit 840b64c

Please sign in to comment.