ALSA: hda - Add SPDIF mux control to AD codec auto-parser
authorTakashi Iwai <tiwai@suse.de>
Tue, 22 Jan 2013 14:31:33 +0000 (15:31 +0100)
committerTakashi Iwai <tiwai@suse.de>
Tue, 22 Jan 2013 15:41:56 +0000 (16:41 +0100)
AD codecs have strange implementations for choosing the SPDIF-output
mux source: the digital audio out widget may take the sources from
multiple connections, where 0x01 indicates it's a PCM while others
point ADCs.  It's obviously invalid in the HD-audio spec POV, but it's
somehow convincing, too.  And, to make things more complex, AD1988A
and AD1882 have deeper connection routes that aren't expressed
correctly.

In this patch, the SPDIF mux control is implemented in two ways:
- For easier one like AD1981, AD1983, AD1884 and AD1984, where the
  SPDIF audio out widget takes just two or three sources, we can
  simply implement via the normal input_mux and connection verb
  calls.

- For the complex routes like AD1988A (but not AD1988B) or AD1882, we
  prepare "faked" paths represented statically, and switch the paths
  using these static ones, instead of parsing the routes from the
  widget tree.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_analog.c

index a186b3d..5d8328a 100644 (file)
 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)