ALSA: hda - Introduce cache & flush cmd / amp writes
authorTakashi Iwai <tiwai@suse.de>
Thu, 13 Dec 2012 17:30:04 +0000 (18:30 +0100)
committerTakashi Iwai <tiwai@suse.de>
Sat, 12 Jan 2013 07:29:17 +0000 (08:29 +0100)
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>
sound/pci/hda/hda_codec.c
sound/pci/hda/hda_codec.h
sound/pci/hda/hda_local.h

index b8fb0a5adb9b54a67c687d5da6b142a917bcb261..2f890af820b2795dd190f1fa718c4f54d9c722aa 100644 (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
         */
index 8665540e55aa7cd1cce19a1cdb405dbcd2ffbbfa..cab39b23d37c89e4f89d6b548f3e8a509a8d9b21 100644 (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 {
index 4b40a5e7a8f54270cd9fe1df53d67bdca7c338e9..f765296cc943897197e77795d63471b93671cbdc 100644 (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);