Commit eb541337 authored by Takashi Iwai's avatar Takashi Iwai

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: default avatarTakashi Iwai <tiwai@suse.de>
parent fe3eb0a7
...@@ -970,6 +970,36 @@ static void restore_init_pincfgs(struct hda_codec *codec) ...@@ -970,6 +970,36 @@ static void restore_init_pincfgs(struct hda_codec *codec)
snd_array_free(&codec->init_pins); snd_array_free(&codec->init_pins);
} }
/*
* 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 * codec destructor
*/ */
...@@ -1038,12 +1068,14 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus, ...@@ -1038,12 +1068,14 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
codec->addr = codec_addr; codec->addr = codec_addr;
mutex_init(&codec->spdif_mutex); mutex_init(&codec->spdif_mutex);
mutex_init(&codec->control_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->amp_cache, sizeof(struct hda_amp_info));
init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head)); 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->mixers, sizeof(struct hda_nid_item), 32);
snd_array_init(&codec->nids, 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->init_pins, sizeof(struct hda_pincfg), 16);
snd_array_init(&codec->driver_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) { if (codec->bus->modelname) {
codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL); codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
if (!codec->modelname) { if (!codec->modelname) {
...@@ -1181,16 +1213,51 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, ...@@ -1181,16 +1213,51 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
u32 stream_tag, u32 stream_tag,
int channel_id, int format) int channel_id, int format)
{ {
struct hda_cvt_setup *p;
unsigned int oldval, newval;
int i;
if (!nid) if (!nid)
return; return;
snd_printdd("hda_codec_setup_stream: " snd_printdd("hda_codec_setup_stream: "
"NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n", "NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
nid, stream_tag, channel_id, format); nid, stream_tag, channel_id, format);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, p = get_hda_cvt_setup(codec, nid);
(stream_tag << 4) | channel_id); if (!p)
msleep(1); return;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, format); /* 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); EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream);
...@@ -1201,17 +1268,54 @@ 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) void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid)
{ {
struct hda_cvt_setup *p;
if (!nid) if (!nid)
return; return;
snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid); 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); 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); 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 * amp access functions
...@@ -2928,6 +3032,7 @@ static void hda_call_codec_suspend(struct hda_codec *codec) ...@@ -2928,6 +3032,7 @@ static void hda_call_codec_suspend(struct hda_codec *codec)
{ {
if (codec->patch_ops.suspend) if (codec->patch_ops.suspend)
codec->patch_ops.suspend(codec, PMSG_SUSPEND); codec->patch_ops.suspend(codec, PMSG_SUSPEND);
hda_cleanup_all_streams(codec);
hda_set_power_state(codec, hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg, codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D3); AC_PWRST_D3);
...@@ -3377,6 +3482,35 @@ static int set_pcm_default_values(struct hda_codec *codec, ...@@ -3377,6 +3482,35 @@ static int set_pcm_default_values(struct hda_codec *codec,
return 0; 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 */ /* global */
const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = { const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = {
"Audio", "SPDIF", "HDMI", "Modem" "Audio", "SPDIF", "HDMI", "Modem"
......
...@@ -826,12 +826,14 @@ struct hda_codec { ...@@ -826,12 +826,14 @@ struct hda_codec {
struct mutex spdif_mutex; struct mutex spdif_mutex;
struct mutex control_mutex; struct mutex control_mutex;
struct mutex prepare_mutex;
unsigned int spdif_status; /* IEC958 status bits */ unsigned int spdif_status; /* IEC958 status bits */
unsigned short spdif_ctls; /* SPDIF control bits */ unsigned short spdif_ctls; /* SPDIF control bits */
unsigned int spdif_in_enable; /* SPDIF input enable? */ unsigned int spdif_in_enable; /* SPDIF input enable? */
hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */ hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */
struct snd_array init_pins; /* initial (BIOS) pin configurations */ struct snd_array init_pins; /* initial (BIOS) pin configurations */
struct snd_array driver_pins; /* pin configs set by codec parser */ struct snd_array driver_pins; /* pin configs set by codec parser */
struct snd_array cvt_setups; /* audio convert setups */
#ifdef CONFIG_SND_HDA_HWDEP #ifdef CONFIG_SND_HDA_HWDEP
struct snd_hwdep *hwdep; /* assigned hwdep device */ struct snd_hwdep *hwdep; /* assigned hwdep device */
...@@ -948,6 +950,16 @@ int snd_hda_codec_build_controls(struct hda_codec *codec); ...@@ -948,6 +950,16 @@ int snd_hda_codec_build_controls(struct hda_codec *codec);
*/ */
int snd_hda_build_pcms(struct hda_bus *bus); int snd_hda_build_pcms(struct hda_bus *bus);
int snd_hda_codec_build_pcms(struct hda_codec *codec); int snd_hda_codec_build_pcms(struct hda_codec *codec);
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);
void snd_hda_codec_cleanup(struct hda_codec *codec,
struct hda_pcm_stream *hinfo,
struct snd_pcm_substream *substream);
void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
u32 stream_tag, u32 stream_tag,
int channel_id, int format); int channel_id, int format);
......
...@@ -1634,7 +1634,7 @@ static int azx_pcm_hw_free(struct snd_pcm_substream *substream) ...@@ -1634,7 +1634,7 @@ static int azx_pcm_hw_free(struct snd_pcm_substream *substream)
azx_dev->period_bytes = 0; azx_dev->period_bytes = 0;
azx_dev->format_val = 0; azx_dev->format_val = 0;
hinfo->ops.cleanup(hinfo, apcm->codec, substream); snd_hda_codec_cleanup(apcm->codec, hinfo, substream);
return snd_pcm_lib_free_pages(substream); return snd_pcm_lib_free_pages(substream);
} }
...@@ -1688,8 +1688,8 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) ...@@ -1688,8 +1688,8 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
else else
azx_dev->fifo_size = 0; azx_dev->fifo_size = 0;
return hinfo->ops.prepare(hinfo, apcm->codec, azx_dev->stream_tag, return snd_hda_codec_prepare(apcm->codec, hinfo, azx_dev->stream_tag,
azx_dev->format_val, substream); azx_dev->format_val, substream);
} }
static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment