diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index af47b9aca974..fb5468b4c55a 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -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)