ALSA: hda - Introduce cache & flush cmd / amp writes

For optimizing the verb executions, a new mechanism to cache the verbs
and amp update commands is introduced.  With the new "write to cache
and flush" way, you can reduce the same verbs that have been written
multiple times.

When codec->cached_write flag is set, the further
snd_hda_codec_write_cache() and snd_hda_codec_amp_stereo() calls will
be performed only on the command or amp cache table, but not sent to
the hardware yet.  Once after you call all commands and update amps,
call snd_hda_codec_resume_amp() and snd_hda_codec_resume_cache().
Then all cached writes and amp updates will be written to the
hardware, and the dirty flags are cleared.

In this implementation, the existing cache table is reused, so
actually no big code change is seen here.  Each cache entry has a new
dirty flag now (so the cache key is now reduced to 31bit).

As a good side-effect by this change, snd_hda_codec_resume_*() will no
longer execute verbs that have been already issued during the resume
phase by checking the dirty flags.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2012-12-13 18:30:04 +01:00
parent 8092e60654
commit c370dd6e9f
3 changed files with 65 additions and 26 deletions

View File

@ -1610,6 +1610,7 @@ static struct hda_cache_head *get_alloc_hash(struct hda_cache_rec *cache,
cur = snd_array_index(&cache->buf, info);
info->key = key;
info->val = 0;
info->dirty = 0;
idx = key % (u16)ARRAY_SIZE(cache->hash);
info->next = cache->hash[idx];
cache->hash[idx] = cur;
@ -1873,8 +1874,11 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
return 0;
}
info->vol[ch] = val;
if (codec->cached_write)
info->head.dirty = 1;
mutex_unlock(&codec->hash_mutex);
put_vol_mute(codec, info, nid, ch, direction, idx, val);
if (!codec->cached_write)
put_vol_mute(codec, info, nid, ch, direction, idx, val);
return 1;
}
EXPORT_SYMBOL_HDA(snd_hda_codec_amp_update);
@ -1905,7 +1909,6 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
}
EXPORT_SYMBOL_HDA(snd_hda_codec_amp_stereo);
#ifdef CONFIG_PM
/**
* snd_hda_codec_resume_amp - Resume all AMP commands from the cache
* @codec: HD-audio codec
@ -1914,13 +1917,17 @@ EXPORT_SYMBOL_HDA(snd_hda_codec_amp_stereo);
*/
void snd_hda_codec_resume_amp(struct hda_codec *codec)
{
struct hda_amp_info *buffer = codec->amp_cache.buf.list;
int i;
for (i = 0; i < codec->amp_cache.buf.used; i++, buffer++) {
u32 key = buffer->head.key;
mutex_lock(&codec->hash_mutex);
for (i = 0; i < codec->amp_cache.buf.used; i++) {
struct hda_amp_info *buffer;
u32 key;
hda_nid_t nid;
unsigned int idx, dir, ch;
buffer = snd_array_elem(&codec->amp_cache.buf, i);
key = buffer->head.key;
if (!key)
continue;
nid = key & 0xff;
@ -1929,13 +1936,18 @@ void snd_hda_codec_resume_amp(struct hda_codec *codec)
for (ch = 0; ch < 2; ch++) {
if (!(buffer->head.val & INFO_AMP_VOL(ch)))
continue;
if (!buffer->head.dirty)
continue;
buffer->head.dirty = 0;
mutex_unlock(&codec->hash_mutex);
put_vol_mute(codec, buffer, nid, ch, dir, idx,
buffer->vol[ch]);
mutex_lock(&codec->hash_mutex);
}
}
mutex_unlock(&codec->hash_mutex);
}
EXPORT_SYMBOL_HDA(snd_hda_codec_resume_amp);
#endif /* CONFIG_PM */
static u32 get_amp_max_value(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int ofs)
@ -3375,12 +3387,11 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
}
EXPORT_SYMBOL_HDA(snd_hda_create_spdif_in_ctls);
#ifdef CONFIG_PM
/*
* command cache
*/
/* build a 32bit cache key with the widget id and the command parameter */
/* build a 31bit cache key with the widget id and the command parameter */
#define build_cmd_cache_key(nid, verb) ((verb << 8) | nid)
#define get_cmd_cache_nid(key) ((key) & 0xff)
#define get_cmd_cache_cmd(key) (((key) >> 8) & 0xffff)
@ -3400,20 +3411,27 @@ EXPORT_SYMBOL_HDA(snd_hda_create_spdif_in_ctls);
int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
int direct, unsigned int verb, unsigned int parm)
{
int err = snd_hda_codec_write(codec, nid, direct, verb, parm);
int err;
struct hda_cache_head *c;
u32 key;
if (err < 0)
return err;
if (!codec->cached_write) {
err = snd_hda_codec_write(codec, nid, direct, verb, parm);
if (err < 0)
return err;
}
/* parm may contain the verb stuff for get/set amp */
verb = verb | (parm >> 8);
parm &= 0xff;
key = build_cmd_cache_key(nid, verb);
mutex_lock(&codec->bus->cmd_mutex);
c = get_alloc_hash(&codec->cmd_cache, key);
if (c)
if (c) {
c->val = parm;
if (codec->cached_write)
c->dirty = 1;
}
mutex_unlock(&codec->bus->cmd_mutex);
return 0;
}
@ -3462,16 +3480,26 @@ EXPORT_SYMBOL_HDA(snd_hda_codec_update_cache);
*/
void snd_hda_codec_resume_cache(struct hda_codec *codec)
{
struct hda_cache_head *buffer = codec->cmd_cache.buf.list;
int i;
for (i = 0; i < codec->cmd_cache.buf.used; i++, buffer++) {
u32 key = buffer->key;
mutex_lock(&codec->hash_mutex);
for (i = 0; i < codec->cmd_cache.buf.used; i++) {
struct hda_cache_head *buffer;
u32 key;
buffer = snd_array_elem(&codec->cmd_cache.buf, i);
key = buffer->key;
if (!key)
continue;
if (!buffer->dirty)
continue;
buffer->dirty = 0;
mutex_unlock(&codec->hash_mutex);
snd_hda_codec_write(codec, get_cmd_cache_nid(key), 0,
get_cmd_cache_cmd(key), buffer->val);
mutex_lock(&codec->hash_mutex);
}
mutex_unlock(&codec->hash_mutex);
}
EXPORT_SYMBOL_HDA(snd_hda_codec_resume_cache);
@ -3492,7 +3520,6 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec,
seq->param);
}
EXPORT_SYMBOL_HDA(snd_hda_sequence_write_cache);
#endif /* CONFIG_PM */
void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
unsigned int power_state,
@ -3640,6 +3667,22 @@ static unsigned int hda_call_codec_suspend(struct hda_codec *codec, bool in_wq)
return state;
}
/* mark all entries of cmd and amp caches dirty */
static void hda_mark_cmd_cache_dirty(struct hda_codec *codec)
{
int i;
for (i = 0; i < codec->cmd_cache.buf.used; i++) {
struct hda_cache_head *cmd;
cmd = snd_array_elem(&codec->cmd_cache.buf, i);
cmd->dirty = 1;
}
for (i = 0; i < codec->amp_cache.buf.used; i++) {
struct hda_amp_info *amp;
amp = snd_array_elem(&codec->cmd_cache.buf, i);
amp->head.dirty = 1;
}
}
/*
* kick up codec; used both from PM and power-save
*/
@ -3647,6 +3690,8 @@ static void hda_call_codec_resume(struct hda_codec *codec)
{
codec->in_pm = 1;
hda_mark_cmd_cache_dirty(codec);
/* set as if powered on for avoiding re-entering the resume
* in the resume / power-save sequence
*/

View File

@ -719,9 +719,10 @@ struct hda_codec_ops {
/* record for amp information cache */
struct hda_cache_head {
u32 key; /* hash key */
u32 key:31; /* hash key */
u32 dirty:1;
u16 val; /* assigned value */
u16 next; /* next link; -1 = terminal */
u16 next;
};
struct hda_amp_info {
@ -867,6 +868,7 @@ struct hda_codec {
unsigned int no_jack_detect:1; /* Machine has no jack-detection */
unsigned int pcm_format_first:1; /* PCM format must be set first */
unsigned int epss:1; /* supporting EPSS? */
unsigned int cached_write:1; /* write only to caches */
#ifdef CONFIG_PM
unsigned int power_on :1; /* current (global) power-state */
unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */
@ -952,7 +954,6 @@ void snd_hda_sequence_write(struct hda_codec *codec,
int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
/* cached write */
#ifdef CONFIG_PM
int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
int direct, unsigned int verb, unsigned int parm);
void snd_hda_sequence_write_cache(struct hda_codec *codec,
@ -960,11 +961,6 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec,
int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid,
int direct, unsigned int verb, unsigned int parm);
void snd_hda_codec_resume_cache(struct hda_codec *codec);
#else
#define snd_hda_codec_write_cache snd_hda_codec_write
#define snd_hda_codec_update_cache snd_hda_codec_write
#define snd_hda_sequence_write_cache snd_hda_sequence_write
#endif
/* the struct for codec->pin_configs */
struct hda_pincfg {

View File

@ -133,9 +133,7 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
int direction, int idx, int mask, int val);
int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
int dir, int idx, int mask, int val);
#ifdef CONFIG_PM
void snd_hda_codec_resume_amp(struct hda_codec *codec);
#endif
void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int *tlv);