ALSA: hda - Implement dynamic-ADC switching for VIA codecs

Some VIA codecs like VT1702 provide the input-route only to specific
ADCs such as digital-mic inputs.  These routes aren't covered by the
normal primary ADC, and for now, user had to open the capture stream
assigned to that special ADC manually for using such inputs.

This patch implements a way to switch the current ADC dynamically per
the input-source selection in such a case.  When this workaround is
activated, the driver provides only one capture stream and one input-
source control but with the full possible inputs.  The driver switches
the ADC to be used (or being used) according to the input-source on the
fly.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2011-06-22 15:23:25 +02:00
parent f2b1c9f031
commit a86a88eaf6

View File

@ -99,6 +99,14 @@ struct nid_path {
unsigned int mute_ctl;
};
/* input-path */
struct via_input {
hda_nid_t pin; /* input-pin or aa-mix */
int adc_idx; /* ADC index to be used */
int mux_idx; /* MUX index (if any) */
const char *label; /* input-source label */
};
struct via_spec {
/* codec parameterization */
const struct snd_kcontrol_new *mixers[6];
@ -135,16 +143,22 @@ struct via_spec {
hda_nid_t dig_in_nid;
/* capture source */
const struct hda_input_mux *input_mux;
bool dyn_adc_switch;
int num_inputs;
struct via_input inputs[AUTO_CFG_MAX_INS + 1];
unsigned int cur_mux[3];
/* dynamic ADC switching */
hda_nid_t cur_adc;
unsigned int cur_adc_stream_tag;
unsigned int cur_adc_format;
/* PCM information */
struct hda_pcm pcm_rec[3];
/* dynamic controls, init_verbs and input_mux */
struct auto_pin_cfg autocfg;
struct snd_array kctls;
struct hda_input_mux private_imux[2];
hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
/* HP mode source */
@ -171,6 +185,10 @@ struct via_spec {
struct hda_loopback_check loopback;
int num_loopbacks;
struct hda_amp_list loopback_list[8];
/* bind capture-volume */
struct hda_bind_ctls *bind_cap_vol;
struct hda_bind_ctls *bind_cap_sw;
};
static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec);
@ -586,12 +604,15 @@ static void via_auto_init_analog_input(struct hda_codec *codec)
/* init input-src */
for (i = 0; i < spec->num_adc_nids; i++) {
const struct hda_input_mux *imux = spec->input_mux;
if (!imux || !spec->mux_nids[i])
continue;
snd_hda_codec_write(codec, spec->mux_nids[i], 0,
AC_VERB_SET_CONNECT_SEL,
imux->items[spec->cur_mux[i]].index);
int adc_idx = spec->inputs[spec->cur_mux[i]].adc_idx;
if (spec->mux_nids[adc_idx]) {
int mux_idx = spec->inputs[spec->cur_mux[i]].mux_idx;
snd_hda_codec_write(codec, spec->mux_nids[adc_idx], 0,
AC_VERB_SET_CONNECT_SEL,
mux_idx);
}
if (spec->dyn_adc_switch)
break; /* only one input-src */
}
/* init aa-mixer */
@ -682,53 +703,6 @@ static const struct snd_kcontrol_new via_pin_power_ctl_enum = {
};
/*
* input MUX handling
*/
static int via_mux_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct via_spec *spec = codec->spec;
return snd_hda_input_mux_info(spec->input_mux, uinfo);
}
static int via_mux_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct via_spec *spec = codec->spec;
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
return 0;
}
static int via_mux_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct via_spec *spec = codec->spec;
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
int ret;
if (!spec->mux_nids[adc_idx])
return -EINVAL;
/* switch to D0 beofre change index */
if (snd_hda_codec_read(codec, spec->mux_nids[adc_idx], 0,
AC_VERB_GET_POWER_STATE, 0x00) != AC_PWRST_D0)
snd_hda_codec_write(codec, spec->mux_nids[adc_idx], 0,
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
ret = snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
spec->mux_nids[adc_idx],
&spec->cur_mux[adc_idx]);
/* update jack power state */
set_widgets_power_state(codec);
return ret;
}
static int via_independent_hp_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
@ -1149,6 +1123,53 @@ static int via_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
return 0;
}
/* analog capture with dynamic ADC switching */
static int via_dyn_adc_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 via_spec *spec = codec->spec;
int adc_idx = spec->inputs[spec->cur_mux[0]].adc_idx;
spec->cur_adc = spec->adc_nids[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 via_dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
struct snd_pcm_substream *substream)
{
struct via_spec *spec = codec->spec;
snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
spec->cur_adc = 0;
return 0;
}
/* re-setup the stream if running; called from input-src put */
static bool via_dyn_adc_pcm_resetup(struct hda_codec *codec, int cur)
{
struct via_spec *spec = codec->spec;
int adc_idx = spec->inputs[cur].adc_idx;
hda_nid_t adc = spec->adc_nids[adc_idx];
if (spec->cur_adc && spec->cur_adc != adc) {
/* stream is running, let's swap the current ADC */
__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
spec->cur_adc = adc;
snd_hda_codec_setup_stream(codec, adc,
spec->cur_adc_stream_tag, 0,
spec->cur_adc_format);
return true;
}
return false;
}
static const struct hda_pcm_stream via_pcm_analog_playback = {
.substreams = 1,
.channels_min = 2,
@ -1204,6 +1225,17 @@ static const struct hda_pcm_stream via_pcm_analog_capture = {
},
};
static const struct hda_pcm_stream via_pcm_dyn_adc_analog_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
/* NID is set in via_build_pcms */
.ops = {
.prepare = via_dyn_adc_capture_pcm_prepare,
.cleanup = via_dyn_adc_capture_pcm_cleanup,
},
};
static const struct hda_pcm_stream via_pcm_digital_playback = {
.substreams = 1,
.channels_min = 2,
@ -1336,13 +1368,19 @@ static int via_build_pcms(struct hda_codec *codec)
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
spec->multiout.max_channels;
if (!spec->stream_analog_capture)
spec->stream_analog_capture = &via_pcm_analog_capture;
if (!spec->stream_analog_capture) {
if (spec->dyn_adc_switch)
spec->stream_analog_capture =
&via_pcm_dyn_adc_analog_capture;
else
spec->stream_analog_capture = &via_pcm_analog_capture;
}
info->stream[SNDRV_PCM_STREAM_CAPTURE] =
*spec->stream_analog_capture;
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
spec->num_adc_nids;
if (!spec->dyn_adc_switch)
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
spec->num_adc_nids;
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
codec->num_pcms++;
@ -1394,7 +1432,9 @@ static void via_free(struct hda_codec *codec)
via_free_kctls(codec);
vt1708_stop_hp_work(spec);
kfree(codec->spec);
kfree(spec->bind_cap_vol);
kfree(spec->bind_cap_sw);
kfree(spec);
}
/* mute/unmute outputs */
@ -1860,7 +1900,74 @@ static int via_fill_adcs(struct hda_codec *codec)
return 0;
}
static int get_mux_nids(struct hda_codec *codec);
/* input-src control */
static int via_mux_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct via_spec *spec = codec->spec;
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = spec->num_inputs;
if (uinfo->value.enumerated.item >= spec->num_inputs)
uinfo->value.enumerated.item = spec->num_inputs - 1;
strcpy(uinfo->value.enumerated.name,
spec->inputs[uinfo->value.enumerated.item].label);
return 0;
}
static int via_mux_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct via_spec *spec = codec->spec;
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
ucontrol->value.enumerated.item[0] = spec->cur_mux[idx];
return 0;
}
static int via_mux_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct via_spec *spec = codec->spec;
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
hda_nid_t mux;
int cur;
cur = ucontrol->value.enumerated.item[0];
if (cur < 0 || cur >= spec->num_inputs)
return -EINVAL;
if (spec->cur_mux[idx] == cur)
return 0;
spec->cur_mux[idx] = cur;
if (spec->dyn_adc_switch) {
int adc_idx = spec->inputs[cur].adc_idx;
mux = spec->mux_nids[adc_idx];
via_dyn_adc_pcm_resetup(codec, cur);
} else {
mux = spec->mux_nids[idx];
if (snd_BUG_ON(!mux))
return -EINVAL;
}
if (mux) {
/* switch to D0 beofre change index */
if (snd_hda_codec_read(codec, mux, 0,
AC_VERB_GET_POWER_STATE, 0x00) != AC_PWRST_D0)
snd_hda_codec_write(codec, mux, 0,
AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
snd_hda_codec_write(codec, mux, 0,
AC_VERB_SET_CONNECT_SEL,
spec->inputs[cur].mux_idx);
}
/* update jack power state */
set_widgets_power_state(codec);
return 0;
}
static const struct snd_kcontrol_new via_input_src_ctl = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@ -1874,6 +1981,22 @@ static const struct snd_kcontrol_new via_input_src_ctl = {
.put = via_mux_enum_put,
};
static int create_input_src_ctls(struct hda_codec *codec, int count)
{
struct via_spec *spec = codec->spec;
struct snd_kcontrol_new *knew;
if (spec->num_inputs <= 1 || !count)
return 0; /* no need for single src */
knew = via_clone_control(spec, &via_input_src_ctl);
if (!knew)
return -ENOMEM;
knew->count = count;
return 0;
}
/* add the powersave loopback-list entry */
static void add_loopback_list(struct via_spec *spec, hda_nid_t mix, int idx)
{
struct hda_amp_list *list;
@ -1888,17 +2011,65 @@ static void add_loopback_list(struct via_spec *spec, hda_nid_t mix, int idx)
spec->loopback.amplist = spec->loopback_list;
}
/* create playback/capture controls for input pins */
static int via_auto_create_analog_input_ctls(struct hda_codec *codec,
const struct auto_pin_cfg *cfg)
/* check whether the path from src to dst is reachable */
static bool is_reachable_nid(struct hda_codec *codec, hda_nid_t src,
hda_nid_t dst, int depth)
{
hda_nid_t conn[8];
int i, nums;
nums = snd_hda_get_connections(codec, src, conn, ARRAY_SIZE(conn));
for (i = 0; i < nums; i++)
if (conn[i] == dst)
return true;
if (++depth > MAX_NID_PATH_DEPTH)
return false;
for (i = 0; i < nums; i++)
if (is_reachable_nid(codec, conn[i], dst, depth))
return true;
return false;
}
/* add the input-route to the given pin */
static bool add_input_route(struct hda_codec *codec, hda_nid_t pin)
{
struct via_spec *spec = codec->spec;
struct hda_input_mux *imux = &spec->private_imux[0];
int i, j, err, idx, idx2, type, type_idx = 0;
const char *prev_label = NULL;
hda_nid_t cap_nid;
hda_nid_t pin_idxs[8];
int num_idxs;
int c, idx;
spec->inputs[spec->num_inputs].adc_idx = -1;
spec->inputs[spec->num_inputs].pin = pin;
for (c = 0; c < spec->num_adc_nids; c++) {
if (spec->mux_nids[c]) {
idx = get_connection_index(codec, spec->mux_nids[c],
pin);
if (idx < 0)
continue;
spec->inputs[spec->num_inputs].mux_idx = idx;
} else {
if (!is_reachable_nid(codec, spec->adc_nids[c], pin, 0))
continue;
}
spec->inputs[spec->num_inputs].adc_idx = c;
/* Can primary ADC satisfy all inputs? */
if (!spec->dyn_adc_switch &&
spec->num_inputs > 0 && spec->inputs[0].adc_idx != c) {
snd_printd(KERN_INFO
"via: dynamic ADC switching enabled\n");
spec->dyn_adc_switch = 1;
}
return true;
}
return false;
}
static int get_mux_nids(struct hda_codec *codec);
/* parse input-routes; fill ADCs, MUXs and input-src entries */
static int parse_analog_inputs(struct hda_codec *codec)
{
struct via_spec *spec = codec->spec;
const struct auto_pin_cfg *cfg = &spec->autocfg;
int i, err;
err = via_fill_adcs(codec);
if (err < 0)
@ -1906,55 +2077,97 @@ static int via_auto_create_analog_input_ctls(struct hda_codec *codec,
err = get_mux_nids(codec);
if (err < 0)
return err;
cap_nid = spec->mux_nids[0];
num_idxs = snd_hda_get_connections(codec, cap_nid, pin_idxs,
ARRAY_SIZE(pin_idxs));
if (num_idxs <= 0)
return 0;
/* for internal loopback recording select */
for (idx = 0; idx < num_idxs; idx++) {
if (pin_idxs[idx] == spec->aa_mix_nid) {
snd_hda_add_imux_item(imux, "Stereo Mixer", idx, NULL);
break;
}
/* fill all input-routes */
for (i = 0; i < cfg->num_inputs; i++) {
if (add_input_route(codec, cfg->inputs[i].pin))
spec->inputs[spec->num_inputs++].label =
hda_get_autocfg_input_label(codec, cfg, i);
}
/* check for internal loopback recording */
if (spec->aa_mix_nid &&
add_input_route(codec, spec->aa_mix_nid))
spec->inputs[spec->num_inputs++].label = "Stereo Mixer";
return 0;
}
/* create analog-loopback volume/switch controls */
static int create_loopback_ctls(struct hda_codec *codec)
{
struct via_spec *spec = codec->spec;
const struct auto_pin_cfg *cfg = &spec->autocfg;
const char *prev_label = NULL;
int type_idx = 0;
int i, j, err, idx;
if (!spec->aa_mix_nid)
return 0;
for (i = 0; i < cfg->num_inputs; i++) {
const char *label;
type = cfg->inputs[i].type;
for (idx = 0; idx < num_idxs; idx++)
if (pin_idxs[idx] == cfg->inputs[i].pin)
break;
if (idx >= num_idxs)
continue;
label = hda_get_autocfg_input_label(codec, cfg, i);
hda_nid_t pin = cfg->inputs[i].pin;
const char *label = hda_get_autocfg_input_label(codec, cfg, i);
if (prev_label && !strcmp(label, prev_label))
type_idx++;
else
type_idx = 0;
prev_label = label;
idx2 = get_connection_index(codec, spec->aa_mix_nid,
pin_idxs[idx]);
if (idx2 >= 0) {
idx = get_connection_index(codec, spec->aa_mix_nid, pin);
if (idx >= 0) {
err = via_new_analog_input(spec, label, type_idx,
idx2, spec->aa_mix_nid);
idx, spec->aa_mix_nid);
if (err < 0)
return err;
add_loopback_list(spec, spec->aa_mix_nid, idx2);
add_loopback_list(spec, spec->aa_mix_nid, idx);
}
snd_hda_add_imux_item(imux, label, idx, NULL);
/* remember the label for smart51 control */
for (j = 0; j < spec->smart51_nums; j++) {
if (spec->smart51_pins[j] == cfg->inputs[i].pin) {
if (spec->smart51_pins[j] == pin) {
spec->smart51_idxs[j] = idx;
spec->smart51_labels[j] = label;
break;
}
}
}
return 0;
}
/* create mic-boost controls (if present) */
static int create_mic_boost_ctls(struct hda_codec *codec)
{
struct via_spec *spec = codec->spec;
const struct auto_pin_cfg *cfg = &spec->autocfg;
int i, err;
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t pin = cfg->inputs[i].pin;
unsigned int caps;
const char *label;
char name[32];
if (cfg->inputs[i].type != AUTO_PIN_MIC)
continue;
caps = query_amp_caps(codec, pin, HDA_INPUT);
if (caps == -1 || !(caps & AC_AMPCAP_NUM_STEPS))
continue;
label = hda_get_autocfg_input_label(codec, cfg, i);
snprintf(name, sizeof(name), "%s Boost Volume", label);
err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_INPUT));
if (err < 0)
return err;
}
return 0;
}
/* create capture and input-src controls for multiple streams */
static int create_multi_adc_ctls(struct hda_codec *codec)
{
struct via_spec *spec = codec->spec;
int i, err;
/* create capture mixer elements */
for (i = 0; i < spec->num_adc_nids; i++) {
@ -1977,34 +2190,89 @@ static int via_auto_create_analog_input_ctls(struct hda_codec *codec,
for (i = 0; i < spec->num_adc_nids; i++)
if (!spec->mux_nids[i])
break;
if (i) {
struct snd_kcontrol_new *knew;
knew = via_clone_control(spec, &via_input_src_ctl);
if (!knew)
return -ENOMEM;
knew->count = i;
}
err = create_input_src_ctls(codec, i);
if (err < 0)
return err;
return 0;
}
/* mic-boosts */
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t pin = cfg->inputs[i].pin;
unsigned int caps;
const char *label;
char name[32];
/* bind capture volume/switch */
static struct snd_kcontrol_new via_bind_cap_vol_ctl =
HDA_BIND_VOL("Capture Volume", 0);
static struct snd_kcontrol_new via_bind_cap_sw_ctl =
HDA_BIND_SW("Capture Switch", 0);
if (cfg->inputs[i].type != AUTO_PIN_MIC)
continue;
caps = query_amp_caps(codec, pin, HDA_INPUT);
if (caps == -1 || !(caps & AC_AMPCAP_NUM_STEPS))
continue;
label = hda_get_autocfg_input_label(codec, cfg, i);
snprintf(name, sizeof(name), "%s Boost Volume", label);
err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_INPUT));
if (err < 0)
return err;
}
static int init_bind_ctl(struct via_spec *spec, struct hda_bind_ctls **ctl_ret,
struct hda_ctl_ops *ops)
{
struct hda_bind_ctls *ctl;
int i;
ctl = kzalloc(sizeof(*ctl) + sizeof(long) * 4, GFP_KERNEL);
if (!ctl)
return -ENOMEM;
ctl->ops = ops;
for (i = 0; i < spec->num_adc_nids; i++)
ctl->values[i] =
HDA_COMPOSE_AMP_VAL(spec->adc_nids[i], 3, 0, HDA_INPUT);
*ctl_ret = ctl;
return 0;
}
/* create capture and input-src controls for dynamic ADC-switch case */
static int create_dyn_adc_ctls(struct hda_codec *codec)
{
struct via_spec *spec = codec->spec;
struct snd_kcontrol_new *knew;
int err;
/* set up the bind capture ctls */
err = init_bind_ctl(spec, &spec->bind_cap_vol, &snd_hda_bind_vol);
if (err < 0)
return err;
err = init_bind_ctl(spec, &spec->bind_cap_sw, &snd_hda_bind_sw);
if (err < 0)
return err;
/* create capture mixer elements */
knew = via_clone_control(spec, &via_bind_cap_vol_ctl);
if (!knew)
return -ENOMEM;
knew->private_value = (long)spec->bind_cap_vol;
knew = via_clone_control(spec, &via_bind_cap_sw_ctl);
if (!knew)
return -ENOMEM;
knew->private_value = (long)spec->bind_cap_sw;
/* input-source control */
err = create_input_src_ctls(codec, 1);
if (err < 0)
return err;
return 0;
}
/* parse and create capture-related stuff */
static int via_auto_create_analog_input_ctls(struct hda_codec *codec)
{
struct via_spec *spec = codec->spec;
int err;
err = parse_analog_inputs(codec);
if (err < 0)
return err;
if (spec->dyn_adc_switch)
err = create_dyn_adc_ctls(codec);
else
err = create_multi_adc_ctls(codec);
if (err < 0)
return err;
err = create_loopback_ctls(codec);
if (err < 0)
return err;
err = create_mic_boost_ctls(codec);
if (err < 0)
return err;
return 0;
}
@ -2090,7 +2358,7 @@ static int via_parse_auto_config(struct hda_codec *codec)
err = via_auto_create_speaker_ctls(codec);
if (err < 0)
return err;
err = via_auto_create_analog_input_ctls(codec, &spec->autocfg);
err = via_auto_create_analog_input_ctls(codec);
if (err < 0)
return err;
@ -2104,8 +2372,6 @@ static int via_parse_auto_config(struct hda_codec *codec)
spec->init_verbs[spec->num_iverbs++] = vt1708_init_verbs;
spec->input_mux = &spec->private_imux[0];
if (spec->hp_dac_nid && spec->hp_dep_path.depth) {
err = via_hp_build(codec);
if (err < 0)