ALSA: hda: slave digital out support

Added support for playing a stream on multiple digital outs. This is done
by defining codec->slave_dig_outs as array of hda_nid_t with a null-terminated entry to set the
slave SPDIF outs, in which the slave outs have cloned settings of the master out (e.g. dig_out_nid).

Signed-off-by: Matthew Ranostay <mranostay@embeddedalley.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index 4f32911..696d77e 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -1454,12 +1454,22 @@
 	codec->spdif_ctls = val;
 
 	if (change) {
+		hda_nid_t *d;
 		snd_hda_codec_write_cache(codec, nid, 0,
 					  AC_VERB_SET_DIGI_CONVERT_1,
 					  val & 0xff);
 		snd_hda_codec_write_cache(codec, nid, 0,
 					  AC_VERB_SET_DIGI_CONVERT_2,
 					  val >> 8);
+
+		for (d = codec->slave_dig_outs; *d; d++) {
+			snd_hda_codec_write_cache(codec, *d, 0,
+					  AC_VERB_SET_DIGI_CONVERT_1,
+					  val & 0xff);
+			snd_hda_codec_write_cache(codec, *d, 0,
+					  AC_VERB_SET_DIGI_CONVERT_2,
+					  val >> 8);
+		}
 	}
 
 	mutex_unlock(&codec->spdif_mutex);
@@ -1491,10 +1501,16 @@
 		val |= AC_DIG1_ENABLE;
 	change = codec->spdif_ctls != val;
 	if (change) {
+		hda_nid_t *d;
 		codec->spdif_ctls = val;
 		snd_hda_codec_write_cache(codec, nid, 0,
 					  AC_VERB_SET_DIGI_CONVERT_1,
 					  val & 0xff);
+
+		for (d = codec->slave_dig_outs; *d; d++)
+			snd_hda_codec_write_cache(codec, *d, 0,
+					  AC_VERB_SET_DIGI_CONVERT_1,
+					  val & 0xff);
 		/* unmute amp switch (if any) */
 		if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) &&
 		    (val & AC_DIG1_ENABLE))
@@ -1643,9 +1659,14 @@
 	mutex_lock(&codec->spdif_mutex);
 	change = codec->spdif_in_enable != val;
 	if (change) {
+		hda_nid_t *d;
 		codec->spdif_in_enable = val;
 		snd_hda_codec_write_cache(codec, nid, 0,
 					  AC_VERB_SET_DIGI_CONVERT_1, val);
+
+		for (d = codec->slave_dig_outs; *d; d++)
+			snd_hda_codec_write_cache(codec, *d, 0,
+					  AC_VERB_SET_DIGI_CONVERT_1, val);
 	}
 	mutex_unlock(&codec->spdif_mutex);
 	return change;
@@ -2589,15 +2610,30 @@
 static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid,
 				 unsigned int stream_tag, unsigned int format)
 {
+	hda_nid_t *d;
+
 	/* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
-	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) {
 		snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1,
+			    codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+
+		for (d = codec->slave_dig_outs; *d; d++)
+			snd_hda_codec_write(codec, *d, 0,
+					AC_VERB_SET_DIGI_CONVERT_1,
 				    codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+	}
 	snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
 	/* turn on again (if needed) */
-	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) {
 		snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1,
 				    codec->spdif_ctls & 0xff);
+
+		for (d = codec->slave_dig_outs; *d; d++)
+			snd_hda_codec_write(codec, *d, 0,
+					AC_VERB_SET_DIGI_CONVERT_1,
+				    codec->spdif_ctls & 0xff);
+	}
+
 }
 
 /*
@@ -2621,8 +2657,12 @@
 				  unsigned int format,
 				  struct snd_pcm_substream *substream)
 {
+	hda_nid_t *nid;
 	mutex_lock(&codec->spdif_mutex);
 	setup_dig_out_stream(codec, mout->dig_out_nid, stream_tag, format);
+	if (codec->slave_dig_outs)
+		for (nid = codec->slave_dig_outs; *nid; nid++)
+			setup_dig_out_stream(codec, *nid, stream_tag, format);
 	mutex_unlock(&codec->spdif_mutex);
 	return 0;
 }
@@ -2689,6 +2729,7 @@
 				     struct snd_pcm_substream *substream)
 {
 	hda_nid_t *nids = mout->dac_nids;
+	hda_nid_t *d;
 	int chs = substream->runtime->channels;
 	int i;
 
@@ -2702,9 +2743,16 @@
 			mout->dig_out_used = HDA_DIG_ANALOG_DUP;
 			setup_dig_out_stream(codec, mout->dig_out_nid,
 					     stream_tag, format);
+			if (codec->slave_dig_outs)
+				for (d = codec->slave_dig_outs; *d; d++)
+					setup_dig_out_stream(codec, *d,
+						stream_tag, format);
 		} else {
 			mout->dig_out_used = 0;
 			snd_hda_codec_cleanup_stream(codec, mout->dig_out_nid);
+			if (codec->slave_dig_outs)
+				for (d = codec->slave_dig_outs; *d; d++)
+					snd_hda_codec_cleanup_stream(codec, *d);
 		}
 	}
 	mutex_unlock(&codec->spdif_mutex);
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index 780e2ff..60468f5 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -725,6 +725,7 @@
 	unsigned int spdif_status;	/* IEC958 status bits */
 	unsigned short spdif_ctls;	/* SPDIF control bits */
 	unsigned int spdif_in_enable;	/* SPDIF input enable? */
+	hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */
 
 	struct snd_hwdep *hwdep;	/* assigned hwdep device */