diff options
Diffstat (limited to 'sound/pci')
-rw-r--r-- | sound/pci/hda/patch_analog.c | 266 |
1 files changed, 261 insertions, 5 deletions
diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index a186b3da20b7..5d8328a64207 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -35,6 +35,10 @@ struct ad198x_spec { struct hda_gen_spec gen; + /* for auto parser */ + int smux_paths[4]; + unsigned int cur_smux; + const struct snd_kcontrol_new *mixers[6]; int num_mixers; unsigned int beep_amp; /* beep amp value, set via set_beep_amp() */ @@ -1519,13 +1523,94 @@ static const char * const ad1983_models[AD1983_MODELS] = { [AD1983_BASIC] = "basic", }; +/* + * SPDIF mux control for AD1983 auto-parser + */ +static int ad1983_auto_smux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + static const char * const texts2[] = { "PCM", "ADC" }; + static const char * const texts3[] = { "PCM", "ADC1", "ADC2" }; + hda_nid_t dig_out = spec->gen.multiout.dig_out_nid; + int num_conns = snd_hda_get_num_conns(codec, dig_out); + + if (num_conns == 2) + return snd_hda_enum_helper_info(kcontrol, uinfo, 2, texts2); + else if (num_conns == 3) + return snd_hda_enum_helper_info(kcontrol, uinfo, 3, texts3); + else + return -EINVAL; +} + +static int ad1983_auto_smux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_smux; + return 0; +} + +static int ad1983_auto_smux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + unsigned int val = ucontrol->value.enumerated.item[0]; + hda_nid_t dig_out = spec->gen.multiout.dig_out_nid; + int num_conns = snd_hda_get_num_conns(codec, dig_out); + + if (val >= num_conns) + return -EINVAL; + if (spec->cur_smux == val) + return 0; + spec->cur_smux = val; + snd_hda_codec_write_cache(codec, dig_out, 0, + AC_VERB_SET_CONNECT_SEL, val); + return 1; +} + +static struct snd_kcontrol_new ad1983_auto_smux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + .info = ad1983_auto_smux_enum_info, + .get = ad1983_auto_smux_enum_get, + .put = ad1983_auto_smux_enum_put, +}; + +static int ad1983_add_spdif_mux_ctl(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + hda_nid_t dig_out = spec->gen.multiout.dig_out_nid; + int num_conns; + + if (!dig_out) + return 0; + num_conns = snd_hda_get_num_conns(codec, dig_out); + if (num_conns != 2 && num_conns != 3) + return 0; + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &ad1983_auto_smux_mixer)) + return -ENOMEM; + return 0; +} + static int ad1983_parse_auto_config(struct hda_codec *codec) { struct ad198x_spec *spec = codec->spec; + int err; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); - return ad198x_parse_auto_config(codec); + err = ad198x_parse_auto_config(codec); + if (err < 0) + return err; + err = ad1983_add_spdif_mux_ctl(codec); + if (err < 0) + return err; + return 0; } static int patch_ad1983(struct hda_codec *codec) @@ -1950,11 +2035,18 @@ static const struct snd_pci_quirk ad1981_cfg_tbl[] = { static int ad1981_parse_auto_config(struct hda_codec *codec) { struct ad198x_spec *spec = codec->spec; + int err; spec->gen.mixer_nid = 0x0e; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x0d, 0, HDA_OUTPUT); - return ad198x_parse_auto_config(codec); + err = ad198x_parse_auto_config(codec); + if (err < 0) + return err; + err = ad1983_add_spdif_mux_ctl(codec); + if (err < 0) + return err; + return 0; } static int patch_ad1981(struct hda_codec *codec) @@ -2820,17 +2912,167 @@ static const struct hda_amp_list ad1988_loopbacks[] = { }; #endif +static int ad1988_auto_smux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + static const char * const texts[] = { + "PCM", "ADC1", "ADC2", "ADC3", + }; + int num_conns = snd_hda_get_num_conns(codec, 0x0b) + 1; + if (num_conns > 4) + num_conns = 4; + return snd_hda_enum_helper_info(kcontrol, uinfo, num_conns, texts); +} + +static int ad1988_auto_smux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_smux; + return 0; +} + +static int ad1988_auto_smux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + unsigned int val = ucontrol->value.enumerated.item[0]; + struct nid_path *path; + int num_conns = snd_hda_get_num_conns(codec, 0x0b) + 1; + + if (val >= num_conns) + return -EINVAL; + if (spec->cur_smux == val) + return 0; + + mutex_lock(&codec->control_mutex); + codec->cached_write = 1; + path = snd_hda_get_path_from_idx(codec, + spec->smux_paths[spec->cur_smux]); + if (path) + snd_hda_activate_path(codec, path, false, true); + path = snd_hda_get_path_from_idx(codec, spec->smux_paths[val]); + if (path) + snd_hda_activate_path(codec, path, true, true); + spec->cur_smux = val; + codec->cached_write = 0; + mutex_unlock(&codec->control_mutex); + snd_hda_codec_flush_cache(codec); /* flush the updates */ + return 1; +} + +static struct snd_kcontrol_new ad1988_auto_smux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + .info = ad1988_auto_smux_enum_info, + .get = ad1988_auto_smux_enum_get, + .put = ad1988_auto_smux_enum_put, +}; + +static int ad1988_auto_init(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i, err; + + err = snd_hda_gen_init(codec); + if (err < 0) + return err; + if (!spec->gen.autocfg.dig_outs) + return 0; + + for (i = 0; i < 4; i++) { + struct nid_path *path; + path = snd_hda_get_path_from_idx(codec, spec->smux_paths[i]); + if (path) + snd_hda_activate_path(codec, path, path->active, false); + } + + return 0; +} + +static int ad1988_add_spdif_mux_ctl(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i, num_conns; + /* we create four static faked paths, since AD codecs have odd + * widget connections regarding the SPDIF out source + */ + static struct nid_path fake_paths[4] = { + { + .depth = 3, + .path = { 0x02, 0x1d, 0x1b }, + .idx = { 0, 0, 0 }, + .multi = { 0, 0, 0 }, + }, + { + .depth = 4, + .path = { 0x08, 0x0b, 0x1d, 0x1b }, + .idx = { 0, 0, 1, 0 }, + .multi = { 0, 1, 0, 0 }, + }, + { + .depth = 4, + .path = { 0x09, 0x0b, 0x1d, 0x1b }, + .idx = { 0, 1, 1, 0 }, + .multi = { 0, 1, 0, 0 }, + }, + { + .depth = 4, + .path = { 0x0f, 0x0b, 0x1d, 0x1b }, + .idx = { 0, 2, 1, 0 }, + .multi = { 0, 1, 0, 0 }, + }, + }; + + /* SPDIF source mux appears to be present only on AD1988A */ + if (!spec->gen.autocfg.dig_outs || + get_wcaps_type(get_wcaps(codec, 0x1d)) != AC_WID_AUD_MIX) + return 0; + + num_conns = snd_hda_get_num_conns(codec, 0x0b) + 1; + if (num_conns != 3 && num_conns != 4) + return 0; + + for (i = 0; i < num_conns; i++) { + struct nid_path *path = snd_array_new(&spec->gen.paths); + if (!path) + return -ENOMEM; + *path = fake_paths[i]; + if (!i) + path->active = 1; + spec->smux_paths[i] = snd_hda_get_path_idx(codec, path); + } + + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &ad1988_auto_smux_mixer)) + return -ENOMEM; + + codec->patch_ops.init = ad1988_auto_init; + + return 0; +} + /* */ static int ad1988_parse_auto_config(struct hda_codec *codec) { struct ad198x_spec *spec = codec->spec; + int err; spec->gen.mixer_nid = 0x20; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); - return ad198x_parse_auto_config(codec); + err = ad198x_parse_auto_config(codec); + if (err < 0) + return err; + err = ad1988_add_spdif_mux_ctl(codec); + if (err < 0) + return err; + return 0; } /* @@ -3174,11 +3416,18 @@ static const char * const ad1884_models[AD1884_MODELS] = { static int ad1884_parse_auto_config(struct hda_codec *codec) { struct ad198x_spec *spec = codec->spec; + int err; spec->gen.mixer_nid = 0x20; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); - return ad198x_parse_auto_config(codec); + err = ad198x_parse_auto_config(codec); + if (err < 0) + return err; + err = ad1983_add_spdif_mux_ctl(codec); + if (err < 0) + return err; + return 0; } static int patch_ad1884_auto(struct hda_codec *codec) @@ -4635,11 +4884,18 @@ static const char * const ad1882_models[AD1986A_MODELS] = { static int ad1882_parse_auto_config(struct hda_codec *codec) { struct ad198x_spec *spec = codec->spec; + int err; spec->gen.mixer_nid = 0x20; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); - return ad198x_parse_auto_config(codec); + err = ad198x_parse_auto_config(codec); + if (err < 0) + return err; + err = ad1988_add_spdif_mux_ctl(codec); + if (err < 0) + return err; + return 0; } static int patch_ad1882(struct hda_codec *codec) |