]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - sound/pci/hda/hda_codec.c
ALSA: hda - Implement a poll loop for jacks as a module parameter
[mirror_ubuntu-bionic-kernel.git] / sound / pci / hda / hda_codec.c
index 88a9c20eb7a29cbff43745f5a64afefefb52b76e..ee958a7d1647bf7dea321b2866fc148f2379bc9b 100644 (file)
@@ -94,13 +94,19 @@ int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset)
 }
 EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset);
 
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
 static void hda_power_work(struct work_struct *work);
 static void hda_keep_power_on(struct hda_codec *codec);
 #define hda_codec_is_power_on(codec)   ((codec)->power_on)
+static inline void hda_call_pm_notify(struct hda_bus *bus, bool power_up)
+{
+       if (bus->ops.pm_notify)
+               bus->ops.pm_notify(bus, power_up);
+}
 #else
 static inline void hda_keep_power_on(struct hda_codec *codec) {}
 #define hda_codec_is_power_on(codec)   1
+#define hda_call_pm_notify(bus, state) {}
 #endif
 
 /**
@@ -808,7 +814,7 @@ find_codec_preset(struct hda_codec *codec)
 {
        struct hda_codec_preset_list *tbl;
        const struct hda_codec_preset *preset;
-       int mod_requested = 0;
+       unsigned int mod_requested = 0;
 
        if (is_generic_config(codec))
                return NULL; /* use the generic parser */
@@ -1129,6 +1135,19 @@ static void restore_shutup_pins(struct hda_codec *codec)
 }
 #endif
 
+static void hda_jackpoll_work(struct work_struct *work)
+{
+       struct hda_codec *codec =
+               container_of(work, struct hda_codec, jackpoll_work.work);
+       if (!codec->jackpoll_interval)
+               return;
+
+       snd_hda_jack_set_dirty_all(codec);
+       snd_hda_jack_poll_all(codec);
+       queue_delayed_work(codec->bus->workq, &codec->jackpoll_work,
+                          codec->jackpoll_interval);
+}
+
 static void init_hda_cache(struct hda_cache_rec *cache,
                           unsigned int record_size);
 static void free_hda_cache(struct hda_cache_rec *cache);
@@ -1184,9 +1203,10 @@ static void snd_hda_codec_free(struct hda_codec *codec)
 {
        if (!codec)
                return;
+       cancel_delayed_work_sync(&codec->jackpoll_work);
        snd_hda_jack_tbl_clear(codec);
        restore_init_pincfgs(codec);
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
        cancel_delayed_work(&codec->power_work);
        flush_workqueue(codec->bus->workq);
 #endif
@@ -1199,6 +1219,10 @@ static void snd_hda_codec_free(struct hda_codec *codec)
        codec->bus->caddr_tbl[codec->addr] = NULL;
        if (codec->patch_ops.free)
                codec->patch_ops.free(codec);
+#ifdef CONFIG_PM
+       if (!codec->pm_down_notified) /* cancel leftover refcounts */
+               hda_call_pm_notify(codec->bus, false);
+#endif
        module_put(codec->owner);
        free_hda_cache(&codec->amp_cache);
        free_hda_cache(&codec->cmd_cache);
@@ -1209,7 +1233,10 @@ static void snd_hda_codec_free(struct hda_codec *codec)
        kfree(codec);
 }
 
-static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec,
+                               hda_nid_t fg, unsigned int power_state);
+
+static unsigned int hda_set_power_state(struct hda_codec *codec,
                                unsigned int power_state);
 
 /**
@@ -1226,6 +1253,7 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
 {
        struct hda_codec *codec;
        char component[31];
+       hda_nid_t fg;
        int err;
 
        if (snd_BUG_ON(!bus))
@@ -1259,8 +1287,9 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
        snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
        snd_array_init(&codec->conn_lists, sizeof(hda_nid_t), 64);
        snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16);
+       INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
 
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
        spin_lock_init(&codec->power_lock);
        INIT_DELAYED_WORK(&codec->power_work, hda_power_work);
        /* snd_hda_codec_new() marks the codec as power-up, and leave it as is.
@@ -1268,6 +1297,7 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
         * phase.
         */
        hda_keep_power_on(codec);
+       hda_call_pm_notify(bus, true);
 #endif
 
        if (codec->bus->modelname) {
@@ -1301,7 +1331,8 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
                goto error;
        }
 
-       err = read_widget_caps(codec, codec->afg ? codec->afg : codec->mfg);
+       fg = codec->afg ? codec->afg : codec->mfg;
+       err = read_widget_caps(codec, fg);
        if (err < 0) {
                snd_printk(KERN_ERR "hda_codec: cannot malloc\n");
                goto error;
@@ -1311,16 +1342,22 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
                goto error;
 
        if (!codec->subsystem_id) {
-               hda_nid_t nid = codec->afg ? codec->afg : codec->mfg;
                codec->subsystem_id =
-                       snd_hda_codec_read(codec, nid, 0,
+                       snd_hda_codec_read(codec, fg, 0,
                                           AC_VERB_GET_SUBSYSTEM_ID, 0);
        }
 
+#ifdef CONFIG_PM
+       codec->d3_stop_clk = snd_hda_codec_get_supported_ps(codec, fg,
+                                       AC_PWRST_CLKSTOP);
+       if (!codec->d3_stop_clk)
+               bus->power_keep_link_on = 1;
+#endif
+       codec->epss = snd_hda_codec_get_supported_ps(codec, fg,
+                                       AC_PWRST_EPSS);
+
        /* power-up all before initialization */
-       hda_set_power_state(codec,
-                           codec->afg ? codec->afg : codec->mfg,
-                           AC_PWRST_D0);
+       hda_set_power_state(codec, AC_PWRST_D0);
 
        snd_hda_codec_proc_new(codec);
 
@@ -1386,6 +1423,44 @@ int snd_hda_codec_configure(struct hda_codec *codec)
 }
 EXPORT_SYMBOL_HDA(snd_hda_codec_configure);
 
+/* update the stream-id if changed */
+static void update_pcm_stream_id(struct hda_codec *codec,
+                                struct hda_cvt_setup *p, hda_nid_t nid,
+                                u32 stream_tag, int channel_id)
+{
+       unsigned int oldval, newval;
+
+       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 */
+static void update_pcm_format(struct hda_codec *codec, struct hda_cvt_setup *p,
+                             hda_nid_t nid, int format)
+{
+       unsigned int oldval;
+
+       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;
+       }
+}
+
 /**
  * snd_hda_codec_setup_stream - set up the codec for streaming
  * @codec: the CODEC to set up
@@ -1400,7 +1475,6 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
 {
        struct hda_codec *c;
        struct hda_cvt_setup *p;
-       unsigned int oldval, newval;
        int type;
        int i;
 
@@ -1413,29 +1487,13 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
        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;
-       }
+
+       if (codec->pcm_format_first)
+               update_pcm_format(codec, p, nid, format);
+       update_pcm_stream_id(codec, p, nid, stream_tag, channel_id);
+       if (!codec->pcm_format_first)
+               update_pcm_format(codec, p, nid, format);
+
        p->active = 1;
        p->dirty = 0;
 
@@ -2306,8 +2364,8 @@ int snd_hda_codec_reset(struct hda_codec *codec)
                return -EBUSY;
 
        /* OK, let it free */
-
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+       cancel_delayed_work_sync(&codec->jackpoll_work);
+#ifdef CONFIG_PM
        cancel_delayed_work_sync(&codec->power_work);
        codec->power_on = 0;
        codec->power_transition = 0;
@@ -2325,6 +2383,7 @@ int snd_hda_codec_reset(struct hda_codec *codec)
        }
        if (codec->patch_ops.free)
                codec->patch_ops.free(codec);
+       memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
        snd_hda_jack_tbl_clear(codec);
        codec->proc_widget_hook = NULL;
        codec->spec = NULL;
@@ -2340,7 +2399,6 @@ int snd_hda_codec_reset(struct hda_codec *codec)
        codec->num_pcms = 0;
        codec->pcm_info = NULL;
        codec->preset = NULL;
-       memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
        codec->slave_dig_outs = NULL;
        codec->spdif_status_reset = 0;
        module_put(codec->owner);
@@ -3472,20 +3530,6 @@ void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
                snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE,
                                    power_state);
        }
-
-       if (power_state == AC_PWRST_D0) {
-               unsigned long end_time;
-               int state;
-               /* wait until the codec reachs to D0 */
-               end_time = jiffies + msecs_to_jiffies(500);
-               do {
-                       state = snd_hda_codec_read(codec, fg, 0,
-                                                  AC_VERB_GET_POWER_STATE, 0);
-                       if (state == power_state)
-                               break;
-                       msleep(1);
-               } while (time_after_eq(end_time, jiffies));
-       }
 }
 EXPORT_SYMBOL_HDA(snd_hda_codec_set_power_to_all);
 
@@ -3497,7 +3541,7 @@ static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec, hda_nid_t fg
 {
        int sup = snd_hda_param_read(codec, fg, AC_PAR_POWER_STATE);
 
-       if (sup < 0)
+       if (sup == -1)
                return false;
        if (sup & power_state)
                return true;
@@ -3506,36 +3550,65 @@ static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec, hda_nid_t fg
 }
 
 /*
- * set power state of the codec
+ * wait until the state is reached, returns the current state
  */
-static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
-                               unsigned int power_state)
+static unsigned int hda_sync_power_state(struct hda_codec *codec,
+                                        hda_nid_t fg,
+                                        unsigned int power_state)
 {
-       int count;
-       unsigned int state;
+       unsigned long end_time = jiffies + msecs_to_jiffies(500);
+       unsigned int state, actual_state;
 
-       if (codec->patch_ops.set_power_state) {
-               codec->patch_ops.set_power_state(codec, fg, power_state);
-               return;
+       for (;;) {
+               state = snd_hda_codec_read(codec, fg, 0,
+                                          AC_VERB_GET_POWER_STATE, 0);
+               if (state & AC_PWRST_ERROR)
+                       break;
+               actual_state = (state >> 4) & 0x0f;
+               if (actual_state == power_state)
+                       break;
+               if (time_after_eq(jiffies, end_time))
+                       break;
+               /* wait until the codec reachs to the target state */
+               msleep(1);
        }
+       return state;
+}
+
+/*
+ * set power state of the codec, and return the power state
+ */
+static unsigned int hda_set_power_state(struct hda_codec *codec,
+                                       unsigned int power_state)
+{
+       hda_nid_t fg = codec->afg ? codec->afg : codec->mfg;
+       int count;
+       unsigned int state;
 
        /* this delay seems necessary to avoid click noise at power-down */
        if (power_state == AC_PWRST_D3) {
                /* transition time less than 10ms for power down */
-               bool epss = snd_hda_codec_get_supported_ps(codec, fg, AC_PWRST_EPSS);
-               msleep(epss ? 10 : 100);
+               msleep(codec->epss ? 10 : 100);
        }
 
        /* repeat power states setting at most 10 times*/
        for (count = 0; count < 10; count++) {
-               snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE,
-                                   power_state);
-               snd_hda_codec_set_power_to_all(codec, fg, power_state, true);
-               state = snd_hda_codec_read(codec, fg, 0,
-                                          AC_VERB_GET_POWER_STATE, 0);
+               if (codec->patch_ops.set_power_state)
+                       codec->patch_ops.set_power_state(codec, fg,
+                                                        power_state);
+               else {
+                       snd_hda_codec_read(codec, fg, 0,
+                                          AC_VERB_SET_POWER_STATE,
+                                          power_state);
+                       snd_hda_codec_set_power_to_all(codec, fg, power_state,
+                                                      true);
+               }
+               state = hda_sync_power_state(codec, fg, power_state);
                if (!(state & AC_PWRST_ERROR))
                        break;
        }
+
+       return state;
 }
 
 #ifdef CONFIG_SND_HDA_HWDEP
@@ -3552,17 +3625,19 @@ static inline void hda_exec_init_verbs(struct hda_codec *codec) {}
 #ifdef CONFIG_PM
 /*
  * call suspend and power-down; used both from PM and power-save
+ * this function returns the power state in the end
  */
-static void hda_call_codec_suspend(struct hda_codec *codec)
+static unsigned int hda_call_codec_suspend(struct hda_codec *codec, bool in_wq)
 {
+       unsigned int state;
+
        if (codec->patch_ops.suspend)
                codec->patch_ops.suspend(codec);
        hda_cleanup_all_streams(codec);
-       hda_set_power_state(codec,
-                           codec->afg ? codec->afg : codec->mfg,
-                           AC_PWRST_D3);
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-       cancel_delayed_work(&codec->power_work);
+       state = hda_set_power_state(codec, AC_PWRST_D3);
+       /* Cancel delayed work if we aren't currently running from it. */
+       if (!in_wq)
+               cancel_delayed_work_sync(&codec->power_work);
        spin_lock(&codec->power_lock);
        snd_hda_update_power_acct(codec);
        trace_hda_power_down(codec);
@@ -3570,7 +3645,7 @@ static void hda_call_codec_suspend(struct hda_codec *codec)
        codec->power_transition = 0;
        codec->power_jiffies = jiffies;
        spin_unlock(&codec->power_lock);
-#endif
+       return state;
 }
 
 /*
@@ -3582,13 +3657,10 @@ static void hda_call_codec_resume(struct hda_codec *codec)
         * in the resume / power-save sequence
         */
        hda_keep_power_on(codec);
-       hda_set_power_state(codec,
-                           codec->afg ? codec->afg : codec->mfg,
-                           AC_PWRST_D0);
+       hda_set_power_state(codec, AC_PWRST_D0);
        restore_pincfgs(codec); /* restore all current pin configs */
        restore_shutup_pins(codec);
        hda_exec_init_verbs(codec);
-       snd_hda_jack_set_dirty_all(codec);
        if (codec->patch_ops.resume)
                codec->patch_ops.resume(codec);
        else {
@@ -3597,6 +3669,13 @@ static void hda_call_codec_resume(struct hda_codec *codec)
                snd_hda_codec_resume_amp(codec);
                snd_hda_codec_resume_cache(codec);
        }
+
+       if (codec->jackpoll_interval)
+               hda_jackpoll_work(&codec->jackpoll_work.work);
+       else {
+               snd_hda_jack_set_dirty_all(codec);
+               snd_hda_jack_report_sync(codec);
+       }
        snd_hda_power_down(codec); /* flag down before returning */
 }
 #endif /* CONFIG_PM */
@@ -3631,6 +3710,36 @@ int /*__devinit*/ snd_hda_build_controls(struct hda_bus *bus)
 }
 EXPORT_SYMBOL_HDA(snd_hda_build_controls);
 
+/*
+ * add standard channel maps if not specified
+ */
+static int add_std_chmaps(struct hda_codec *codec)
+{
+       int i, str, err;
+
+       for (i = 0; i < codec->num_pcms; i++) {
+               for (str = 0; str < 2; str++) {
+                       struct snd_pcm *pcm = codec->pcm_info[i].pcm;
+                       struct hda_pcm_stream *hinfo =
+                               &codec->pcm_info[i].stream[str];
+                       struct snd_pcm_chmap *chmap;
+
+                       if (codec->pcm_info[i].own_chmap)
+                               continue;
+                       if (!pcm || !hinfo->substreams)
+                               continue;
+                       err = snd_pcm_add_chmap_ctls(pcm, str,
+                                                    snd_pcm_std_chmaps,
+                                                    hinfo->channels_max,
+                                                    0, &chmap);
+                       if (err < 0)
+                               return err;
+                       chmap->channel_mask = SND_PCM_CHMAP_MASK_2468;
+               }
+       }
+       return 0;
+}
+
 int snd_hda_codec_build_controls(struct hda_codec *codec)
 {
        int err = 0;
@@ -3642,6 +3751,16 @@ int snd_hda_codec_build_controls(struct hda_codec *codec)
                err = codec->patch_ops.build_controls(codec);
        if (err < 0)
                return err;
+
+       /* we create chmaps here instead of build_pcms */
+       err = add_std_chmaps(codec);
+       if (err < 0)
+               return err;
+
+       if (codec->jackpoll_interval)
+               hda_jackpoll_work(&codec->jackpoll_work.work);
+       else
+               snd_hda_jack_report_sync(codec); /* call at the last init point */
        return 0;
 }
 
@@ -4184,7 +4303,7 @@ int snd_hda_codec_build_pcms(struct hda_codec *codec)
  *
  * This function returns 0 if successful, or a negative error code.
  */
-int __devinit snd_hda_build_pcms(struct hda_bus *bus)
+int snd_hda_build_pcms(struct hda_bus *bus)
 {
        struct hda_codec *codec;
 
@@ -4364,12 +4483,13 @@ int snd_hda_add_new_ctls(struct hda_codec *codec,
 }
 EXPORT_SYMBOL_HDA(snd_hda_add_new_ctls);
 
-#ifdef CONFIG_SND_HDA_POWER_SAVE
+#ifdef CONFIG_PM
 static void hda_power_work(struct work_struct *work)
 {
        struct hda_codec *codec =
                container_of(work, struct hda_codec, power_work.work);
        struct hda_bus *bus = codec->bus;
+       unsigned int state;
 
        spin_lock(&codec->power_lock);
        if (codec->power_transition > 0) { /* during power-up sequence? */
@@ -4383,9 +4503,12 @@ static void hda_power_work(struct work_struct *work)
        }
        spin_unlock(&codec->power_lock);
 
-       hda_call_codec_suspend(codec);
-       if (bus->ops.pm_notify)
-               bus->ops.pm_notify(bus);
+       state = hda_call_codec_suspend(codec, true);
+       codec->pm_down_notified = 0;
+       if (!bus->power_keep_link_on && (state & AC_PWRST_CLK_STOP_OK)) {
+               codec->pm_down_notified = 1;
+               hda_call_pm_notify(bus, false);
+       }
 }
 
 static void hda_keep_power_on(struct hda_codec *codec)
@@ -4411,19 +4534,16 @@ void snd_hda_update_power_acct(struct hda_codec *codec)
 /* Transition to powered up, if wait_power_down then wait for a pending
  * transition to D3 to complete. A pending D3 transition is indicated
  * with power_transition == -1. */
+/* call this with codec->power_lock held! */
 static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down)
 {
        struct hda_bus *bus = codec->bus;
 
-       spin_lock(&codec->power_lock);
-       codec->power_count++;
        /* Return if power_on or transitioning to power_on, unless currently
         * powering down. */
        if ((codec->power_on || codec->power_transition > 0) &&
-           !(wait_power_down && codec->power_transition < 0)) {
-               spin_unlock(&codec->power_lock);
+           !(wait_power_down && codec->power_transition < 0))
                return;
-       }
        spin_unlock(&codec->power_lock);
 
        cancel_delayed_work_sync(&codec->power_work);
@@ -4433,9 +4553,11 @@ static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down)
         * then there is no need to go through power up here.
         */
        if (codec->power_on) {
-               spin_unlock(&codec->power_lock);
+               if (codec->power_transition < 0)
+                       codec->power_transition = 0;
                return;
        }
+
        trace_hda_power_up(codec);
        snd_hda_update_power_acct(codec);
        codec->power_on = 1;
@@ -4443,71 +4565,54 @@ static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down)
        codec->power_transition = 1; /* avoid reentrance */
        spin_unlock(&codec->power_lock);
 
-       if (bus->ops.pm_notify)
-               bus->ops.pm_notify(bus);
+       if (codec->pm_down_notified) {
+               codec->pm_down_notified = 0;
+               hda_call_pm_notify(bus, true);
+       }
+
        hda_call_codec_resume(codec);
 
        spin_lock(&codec->power_lock);
        codec->power_transition = 0;
-       spin_unlock(&codec->power_lock);
-}
-
-/**
- * snd_hda_power_up - Power-up the codec
- * @codec: HD-audio codec
- *
- * Increment the power-up counter and power up the hardware really when
- * not turned on yet.
- */
-void snd_hda_power_up(struct hda_codec *codec)
-{
-       __snd_hda_power_up(codec, false);
-}
-EXPORT_SYMBOL_HDA(snd_hda_power_up);
-
-/**
- * snd_hda_power_up_d3wait - Power-up the codec after waiting for any pending
- *   D3 transition to complete.  This differs from snd_hda_power_up() when
- *   power_transition == -1.  snd_hda_power_up sees this case as a nop,
- *   snd_hda_power_up_d3wait waits for the D3 transition to complete then powers
- *   back up.
- * @codec: HD-audio codec
- *
- * Cancel any power down operation hapenning on the work queue, then power up.
- */
-void snd_hda_power_up_d3wait(struct hda_codec *codec)
-{
-       /* This will cancel and wait for pending power_work to complete. */
-       __snd_hda_power_up(codec, true);
 }
-EXPORT_SYMBOL_HDA(snd_hda_power_up_d3wait);
 
 #define power_save(codec)      \
        ((codec)->bus->power_save ? *(codec)->bus->power_save : 0)
 
-/**
- * snd_hda_power_down - Power-down the codec
- * @codec: HD-audio codec
- *
- * Decrement the power-up counter and schedules the power-off work if
- * the counter rearches to zero.
- */
-void snd_hda_power_down(struct hda_codec *codec)
+/* Transition to powered down */
+static void __snd_hda_power_down(struct hda_codec *codec)
 {
-       spin_lock(&codec->power_lock);
-       --codec->power_count;
-       if (!codec->power_on || codec->power_count || codec->power_transition) {
-               spin_unlock(&codec->power_lock);
+       if (!codec->power_on || codec->power_count || codec->power_transition)
                return;
-       }
+
        if (power_save(codec)) {
                codec->power_transition = -1; /* avoid reentrance */
                queue_delayed_work(codec->bus->workq, &codec->power_work,
                                msecs_to_jiffies(power_save(codec) * 1000));
        }
+}
+
+/**
+ * snd_hda_power_save - Power-up/down/sync the codec
+ * @codec: HD-audio codec
+ * @delta: the counter delta to change
+ *
+ * Change the power-up counter via @delta, and power up or down the hardware
+ * appropriately.  For the power-down, queue to the delayed action.
+ * Passing zero to @delta means to synchronize the power state.
+ */
+void snd_hda_power_save(struct hda_codec *codec, int delta, bool d3wait)
+{
+       spin_lock(&codec->power_lock);
+       codec->power_count += delta;
+       trace_hda_power_count(codec);
+       if (delta > 0)
+               __snd_hda_power_up(codec, d3wait);
+       else
+               __snd_hda_power_down(codec);
        spin_unlock(&codec->power_lock);
 }
-EXPORT_SYMBOL_HDA(snd_hda_power_down);
+EXPORT_SYMBOL_HDA(snd_hda_power_save);
 
 /**
  * snd_hda_check_amp_list_power - Check the amp list and update the power
@@ -5046,8 +5151,9 @@ int snd_hda_suspend(struct hda_bus *bus)
        struct hda_codec *codec;
 
        list_for_each_entry(codec, &bus->codec_list, list) {
+               cancel_delayed_work_sync(&codec->jackpoll_work);
                if (hda_codec_is_power_on(codec))
-                       hda_call_codec_suspend(codec);
+                       hda_call_codec_suspend(codec, false);
        }
        return 0;
 }
@@ -5058,9 +5164,6 @@ EXPORT_SYMBOL_HDA(snd_hda_suspend);
  * @bus: the HDA bus
  *
  * Returns 0 if successful.
- *
- * This function is defined only when POWER_SAVE isn't set.
- * In the power-save mode, the codec is resumed dynamically.
  */
 int snd_hda_resume(struct hda_bus *bus)
 {
@@ -5089,6 +5192,8 @@ EXPORT_SYMBOL_HDA(snd_hda_resume);
  */
 void *snd_array_new(struct snd_array *array)
 {
+       if (snd_BUG_ON(!array->elem_size))
+               return NULL;
        if (array->used >= array->alloced) {
                int num = array->alloced + array->alloc_align;
                int size = (num + 1) * array->elem_size;