[alsa-devel] purify_inactive_streams and Azalia controllers with >1 codec
NVIDIA GPUs that contain Azalia controllers currently all have 1 controller, each having 4 codecs, each having 1 converter.
I think there's a logic bug in hda_codec.c:purify_inactive_streams(); streams are controller-wide, but purify_inactive_streams() only cleans up the converters a single codec as far as I can tell.
I think the solution is to rewrite purify_inactive_streams() as follows:
static void purify_inactive_streams(struct hda_codec *codec) { struct hda_codec *itercodec; int i;
list_for_each_entry(itercodec, &codec->bus->codec_list, list) { for (i = 0; i < itercodec->cvt_setups.used; i++) { struct hda_cvt_setup *p = snd_array_elem(&itercodec->cvt_setups, i); if (p->dirty) really_cleanup_stream(itercodec, p); } } }
This alone doesn't work; had_codec.c:snd_hda_codec_setup_stream() also needs a similar loop when setting the dirty flag.
However, this still doesn't work; as best I can tell, patch_nvhdmi.c:nvhdmi_dig_playback_pcm_prepare_8ch_89() never calls snd_hda_codec_setup_stream(). This is where I need help.
patch_nvhdmi.c:nvhdmi_dig_playback_pcm_prepare_8ch_89() and patch_intelhdmi.c:nvhdmi_dig_playback_pcm_prepare_8ch_89() are identical; both call patch_hdmi.c:hdmi_setup_stream() to do the bulk of the work, which doesn't ever call snd_hda_codec_setup_stream().
However, patch_atihdmi.c:atihdmi_dig_playback_pcm_prepare() does call snd_hda_multi_out_dig_prepare which eventually calls snd_hda_codec_setup_stream().
I'm not sure what the correct fix is here; shouldn't all 3 patch_*hdmi.c work in the same way, and also call snd_hda_codec_setup_stream()?
The end-result of this is that once an audio stream has been played on the correct converter, any subsequent streams played on any converter will also be sent out the original converter. This is somewhat confusing!
Thanks for any pointers.
At Thu, 19 Aug 2010 12:08:59 -0700, Stephen Warren wrote:
NVIDIA GPUs that contain Azalia controllers currently all have 1 controller, each having 4 codecs, each having 1 converter.
I think there's a logic bug in hda_codec.c:purify_inactive_streams(); streams are controller-wide, but purify_inactive_streams() only cleans up the converters a single codec as far as I can tell.
Good catch.
I think the solution is to rewrite purify_inactive_streams() as follows:
static void purify_inactive_streams(struct hda_codec *codec) { struct hda_codec *itercodec; int i;
list_for_each_entry(itercodec, &codec->bus->codec_list, list) { for (i = 0; i < itercodec->cvt_setups.used; i++) { struct hda_cvt_setup *p = snd_array_elem(&itercodec->cvt_setups, i); if (p->dirty) really_cleanup_stream(itercodec, p); } } }
This alone doesn't work; had_codec.c:snd_hda_codec_setup_stream() also needs a similar loop when setting the dirty flag.
However, this still doesn't work; as best I can tell, patch_nvhdmi.c:nvhdmi_dig_playback_pcm_prepare_8ch_89() never calls snd_hda_codec_setup_stream(). This is where I need help.
patch_nvhdmi.c:nvhdmi_dig_playback_pcm_prepare_8ch_89() and patch_intelhdmi.c:nvhdmi_dig_playback_pcm_prepare_8ch_89() are identical; both call patch_hdmi.c:hdmi_setup_stream() to do the bulk of the work, which doesn't ever call snd_hda_codec_setup_stream().
It's because Intel HDMI code already tries to be sticky wrt converter assignment. Now the global core code does it, thus it conflicts. The simple fix is just to set up the stream normally via snd_hda_codec_setup_stream() there.
Below is a quick fix patch. Could you give it a try?
thanks,
Takashi
--- diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index dd8fb86..3827092 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -589,6 +589,7 @@ int /*__devinit*/ snd_hda_bus_new(struct snd_card *card, bus->ops = temp->ops;
mutex_init(&bus->cmd_mutex); + mutex_init(&bus->prepare_mutex); INIT_LIST_HEAD(&bus->codec_list);
snprintf(bus->workq_name, sizeof(bus->workq_name), @@ -1068,7 +1069,6 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus, codec->addr = codec_addr; mutex_init(&codec->spdif_mutex); mutex_init(&codec->control_mutex); - mutex_init(&codec->prepare_mutex); init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info)); init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head)); snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32); @@ -1213,6 +1213,7 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag, int channel_id, int format) { + struct hda_codec *c; struct hda_cvt_setup *p; unsigned int oldval, newval; int i; @@ -1253,10 +1254,12 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, p->dirty = 0;
/* make other inactive cvts with the same stream-tag dirty */ - for (i = 0; i < codec->cvt_setups.used; i++) { - p = snd_array_elem(&codec->cvt_setups, i); - if (!p->active && p->stream_tag == stream_tag) - p->dirty = 1; + list_for_each_entry(c, &codec->bus->codec_list, list) { + for (i = 0; i < c->cvt_setups.used; i++) { + p = snd_array_elem(&c->cvt_setups, i); + if (!p->active && p->stream_tag == stream_tag) + p->dirty = 1; + } } } EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream); @@ -1306,12 +1309,16 @@ static void really_cleanup_stream(struct hda_codec *codec, /* clean up the all conflicting obsolete streams */ static void purify_inactive_streams(struct hda_codec *codec) { + struct hda_codec *c; int i;
- for (i = 0; i < codec->cvt_setups.used; i++) { - struct hda_cvt_setup *p = snd_array_elem(&codec->cvt_setups, i); - if (p->dirty) - really_cleanup_stream(codec, p); + list_for_each_entry(c, &codec->bus->codec_list, list) { + for (i = 0; i < c->cvt_setups.used; i++) { + struct hda_cvt_setup *p; + p = snd_array_elem(&c->cvt_setups, i); + if (p->dirty) + really_cleanup_stream(c, p); + } } }
@@ -3502,11 +3509,11 @@ int snd_hda_codec_prepare(struct hda_codec *codec, struct snd_pcm_substream *substream) { int ret; - mutex_lock(&codec->prepare_mutex); + mutex_lock(&codec->bus->prepare_mutex); ret = hinfo->ops.prepare(hinfo, codec, stream, format, substream); if (ret >= 0) purify_inactive_streams(codec); - mutex_unlock(&codec->prepare_mutex); + mutex_unlock(&codec->bus->prepare_mutex); return ret; } EXPORT_SYMBOL_HDA(snd_hda_codec_prepare); @@ -3515,9 +3522,9 @@ void snd_hda_codec_cleanup(struct hda_codec *codec, struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream) { - mutex_lock(&codec->prepare_mutex); + mutex_lock(&codec->bus->prepare_mutex); hinfo->ops.cleanup(hinfo, codec, substream); - mutex_unlock(&codec->prepare_mutex); + mutex_unlock(&codec->bus->prepare_mutex); } EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup);
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 4303353..62c7022 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -648,6 +648,7 @@ struct hda_bus { struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1];
struct mutex cmd_mutex; + struct mutex prepare_mutex;
/* unsolicited event queue */ struct hda_bus_unsolicited *unsol; @@ -826,7 +827,6 @@ struct hda_codec {
struct mutex spdif_mutex; struct mutex control_mutex; - struct mutex prepare_mutex; unsigned int spdif_status; /* IEC958 status bits */ unsigned short spdif_ctls; /* SPDIF control bits */ unsigned int spdif_in_enable; /* SPDIF input enable? */ diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 2bc0f07..afd6022 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -707,8 +707,6 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag, int format) { struct hdmi_spec *spec = codec->spec; - int tag; - int fmt; int pinctl; int new_pinctl = 0; int i; @@ -745,24 +743,7 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid, return -EINVAL; }
- tag = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0) >> 4; - fmt = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_STREAM_FORMAT, 0); - - snd_printdd("hdmi_setup_stream: " - "NID=0x%x, %sstream=0x%x, %sformat=0x%x\n", - nid, - tag == stream_tag ? "" : "new-", - stream_tag, - fmt == format ? "" : "new-", - format); - - if (tag != stream_tag) - snd_hda_codec_write(codec, nid, 0, - AC_VERB_SET_CHANNEL_STREAMID, - stream_tag << 4); - if (fmt != format) - snd_hda_codec_write(codec, nid, 0, - AC_VERB_SET_STREAM_FORMAT, format); + snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format); return 0; }
diff --git a/sound/pci/hda/patch_intelhdmi.c b/sound/pci/hda/patch_intelhdmi.c index d382d3c..36a9b83 100644 --- a/sound/pci/hda/patch_intelhdmi.c +++ b/sound/pci/hda/patch_intelhdmi.c @@ -69,20 +69,12 @@ static int intel_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo, return hdmi_setup_stream(codec, hinfo->nid, stream_tag, format); }
-static int intel_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) -{ - return 0; -} - static struct hda_pcm_stream intel_hdmi_pcm_playback = { .substreams = 1, .channels_min = 2, .ops = { .open = hdmi_pcm_open, .prepare = intel_hdmi_playback_pcm_prepare, - .cleanup = intel_hdmi_playback_pcm_cleanup, }, };
diff --git a/sound/pci/hda/patch_nvhdmi.c b/sound/pci/hda/patch_nvhdmi.c index f636870..69b950d 100644 --- a/sound/pci/hda/patch_nvhdmi.c +++ b/sound/pci/hda/patch_nvhdmi.c @@ -326,13 +326,6 @@ static int nvhdmi_dig_playback_pcm_prepare_8ch(struct hda_pcm_stream *hinfo, return 0; }
-static int nvhdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) -{ - return 0; -} - static int nvhdmi_dig_playback_pcm_prepare_2ch(struct hda_pcm_stream *hinfo, struct hda_codec *codec, unsigned int stream_tag, @@ -350,7 +343,6 @@ static struct hda_pcm_stream nvhdmi_pcm_digital_playback_8ch_89 = { .ops = { .open = hdmi_pcm_open, .prepare = nvhdmi_dig_playback_pcm_prepare_8ch_89, - .cleanup = nvhdmi_playback_pcm_cleanup, }, };
Takashi Iwai wrote:
At Thu, 19 Aug 2010 12:08:59 -0700, Stephen Warren wrote:
NVIDIA GPUs that contain Azalia controllers currently all have 1 controller, each having 4 codecs, each having 1 converter.
I think there's a logic bug in hda_codec.c:purify_inactive_streams(); streams are controller-wide, but purify_inactive_streams() only cleans up the converters a single codec as far as I can tell.
... Below is a quick fix patch. Could you give it a try?
Yes, that seems to work great.
Could you explain more why patch_atihdmi.c mostly calls snd_hda_multi_out_* yet patch_nvhdmi.c and patch_intelhdmi.c mostly call hdmi_*?
Thanks very much.
At Thu, 19 Aug 2010 14:58:07 -0700, Stephen Warren wrote:
Takashi Iwai wrote:
At Thu, 19 Aug 2010 12:08:59 -0700, Stephen Warren wrote:
NVIDIA GPUs that contain Azalia controllers currently all have 1 controller, each having 4 codecs, each having 1 converter.
I think there's a logic bug in hda_codec.c:purify_inactive_streams(); streams are controller-wide, but purify_inactive_streams() only cleans up the converters a single codec as far as I can tell.
... Below is a quick fix patch. Could you give it a try?
Yes, that seems to work great.
Thanks for checking. I'll merge it.
Could you explain more why patch_atihdmi.c mostly calls snd_hda_multi_out_* yet patch_nvhdmi.c and patch_intelhdmi.c mostly call hdmi_*?
It's simply because patch_atihdmi.c is based on a very old code with static configuration. A few models in patch_nvhdmi.c do so by the same reason :)
My plan is to merge all these three modules into a single generic hdmi module. I'll start merging ATI code to Intel since ATI HDMI codecs are pretty simple.
Takashi
participants (2)
-
Stephen Warren
-
Takashi Iwai