summaryrefslogtreecommitdiff
path: root/sound/pci/hda/hda_codec.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2010-08-06 13:48:11 +0200
committerTakashi Iwai <tiwai@suse.de>2010-08-06 13:48:11 +0200
commiteb541337b7a43822fce7d0c9d967ee149b2d9a96 (patch)
tree6f46656a2b8545720a818304050fbbeddd872989 /sound/pci/hda/hda_codec.c
parentfe3eb0a73ca052ee9346a1544f4c00e697cc9a4e (diff)
ALSA: hda - Make converter setups sticky
So far, we reset the converter setups like the stream-tag, the channel-id and format-id in prepare callbacks, and clear them in cleanup callbacks. This often causes a silence of the digital receiver for a couple of seconds. This patch tries to delay the converter setup changes as much as possible. The converter setups are cached and aren't reset as long as the same values are used. At suspend/resume, they are cleared to be recovered properly, too. Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/pci/hda/hda_codec.c')
-rw-r--r--sound/pci/hda/hda_codec.c150
1 files changed, 142 insertions, 8 deletions
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index a7802b99436c..720a81d711e3 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -971,6 +971,36 @@ static void restore_init_pincfgs(struct hda_codec *codec)
}
/*
+ * audio-converter setup caches
+ */
+struct hda_cvt_setup {
+ hda_nid_t nid;
+ u8 stream_tag;
+ u8 channel_id;
+ u16 format_id;
+ unsigned char active; /* cvt is currently used */
+ unsigned char dirty; /* setups should be cleared */
+};
+
+/* get or create a cache entry for the given audio converter NID */
+static struct hda_cvt_setup *
+get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_cvt_setup *p;
+ int i;
+
+ for (i = 0; i < codec->cvt_setups.used; i++) {
+ p = snd_array_elem(&codec->cvt_setups, i);
+ if (p->nid == nid)
+ return p;
+ }
+ p = snd_array_new(&codec->cvt_setups);
+ if (p)
+ p->nid = nid;
+ return p;
+}
+
+/*
* codec destructor
*/
static void snd_hda_codec_free(struct hda_codec *codec)
@@ -1038,12 +1068,14 @@ 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);
snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32);
snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
+ snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
if (codec->bus->modelname) {
codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
if (!codec->modelname) {
@@ -1181,16 +1213,51 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
u32 stream_tag,
int channel_id, int format)
{
+ struct hda_cvt_setup *p;
+ unsigned int oldval, newval;
+ int i;
+
if (!nid)
return;
snd_printdd("hda_codec_setup_stream: "
"NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
nid, stream_tag, channel_id, format);
- snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID,
- (stream_tag << 4) | channel_id);
- msleep(1);
- snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, format);
+ p = get_hda_cvt_setup(codec, nid);
+ if (!p)
+ return;
+ /* update the stream-id if changed */
+ if (p->stream_tag != stream_tag || p->channel_id != channel_id) {
+ oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+ newval = (stream_tag << 4) | channel_id;
+ if (oldval != newval)
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ newval);
+ p->stream_tag = stream_tag;
+ p->channel_id = channel_id;
+ }
+ /* update the format-id if changed */
+ if (p->format_id != format) {
+ oldval = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_STREAM_FORMAT, 0);
+ if (oldval != format) {
+ msleep(1);
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_STREAM_FORMAT,
+ format);
+ }
+ p->format_id = format;
+ }
+ p->active = 1;
+ 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;
+ }
}
EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream);
@@ -1201,17 +1268,54 @@ EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream);
*/
void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid)
{
+ struct hda_cvt_setup *p;
+
if (!nid)
return;
snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid);
+ /* here we just clear the active flag; actual clean-ups will be done
+ * in purify_inactive_streams()
+ */
+ p = get_hda_cvt_setup(codec, nid);
+ if (p)
+ p->active = 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q)
+{
+ hda_nid_t nid = q->nid;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
-#if 0 /* keep the format */
- msleep(1);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0);
-#endif
+ memset(q, 0, sizeof(*q));
+ q->nid = nid;
+}
+
+/* clean up the all conflicting obsolete streams */
+static void purify_inactive_streams(struct hda_codec *codec)
+{
+ 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);
+ }
+}
+
+/* clean up all streams; called from suspend */
+static void hda_cleanup_all_streams(struct hda_codec *codec)
+{
+ 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->stream_tag)
+ really_cleanup_stream(codec, p);
+ }
}
-EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup_stream);
/*
* amp access functions
@@ -2928,6 +3032,7 @@ static void hda_call_codec_suspend(struct hda_codec *codec)
{
if (codec->patch_ops.suspend)
codec->patch_ops.suspend(codec, PMSG_SUSPEND);
+ hda_cleanup_all_streams(codec);
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D3);
@@ -3377,6 +3482,35 @@ static int set_pcm_default_values(struct hda_codec *codec,
return 0;
}
+/*
+ * codec prepare/cleanup entries
+ */
+int snd_hda_codec_prepare(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ unsigned int stream,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ int ret;
+ mutex_lock(&codec->prepare_mutex);
+ ret = hinfo->ops.prepare(hinfo, codec, stream, format, substream);
+ if (ret >= 0)
+ purify_inactive_streams(codec);
+ mutex_unlock(&codec->prepare_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_prepare);
+
+void snd_hda_codec_cleanup(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ struct snd_pcm_substream *substream)
+{
+ mutex_lock(&codec->prepare_mutex);
+ hinfo->ops.cleanup(hinfo, codec, substream);
+ mutex_unlock(&codec->prepare_mutex);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup);
+
/* global */
const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = {
"Audio", "SPDIF", "HDMI", "Modem"