]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - sound/soc/soc-dapm.c
asoc: tegra: aic326x: Don't disable clock if call is active
[linux-2.6.git] / sound / soc / soc-dapm.c
index 999bb08cdfb143708312cd0da0e358caa077d766..310ee4addc69224f6fd699f38c4bbd0bb9ffc014 100644 (file)
  *      dynamic configuration of codec internal audio paths and active
  *      DACs/ADCs.
  *    o Platform power domain - can support external components i.e. amps and
- *      mic/meadphone insertion events.
+ *      mic/headphone insertion events.
  *    o Automatic Mic Bias support
  *    o Jack insertion power event initiation - e.g. hp insertion will enable
  *      sinks, dacs, etc
- *    o Delayed powerdown of audio susbsystem to reduce pops between a quick
+ *    o Delayed power down of audio subsystem to reduce pops between a quick
  *      device reopen.
  *
- *  Todo:
- *    o DAPM power change sequencing - allow for configurable per
- *      codec sequences.
- *    o Support for analogue bias optimisation.
- *    o Support for reduced codec oversampling rates.
- *    o Support for reduced codec bias currents.
  */
 
 #include <linux/module.h>
@@ -39,6 +33,8 @@
 #include <linux/platform_device.h>
 #include <linux/jiffies.h>
 #include <linux/debugfs.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 
 #include <trace/events/asoc.h>
 
+#define DAPM_UPDATE_STAT(widget, val) widget->dapm->card->dapm_stats.val++;
+
 /* dapm power sequences - make this per codec in the future */
 static int dapm_up_seq[] = {
        [snd_soc_dapm_pre] = 0,
        [snd_soc_dapm_supply] = 1,
+       [snd_soc_dapm_regulator_supply] = 1,
        [snd_soc_dapm_micbias] = 2,
+       [snd_soc_dapm_dai] = 3,
        [snd_soc_dapm_aif_in] = 3,
        [snd_soc_dapm_aif_out] = 3,
        [snd_soc_dapm_mic] = 4,
@@ -67,6 +67,7 @@ static int dapm_up_seq[] = {
        [snd_soc_dapm_out_drv] = 10,
        [snd_soc_dapm_hp] = 10,
        [snd_soc_dapm_spk] = 10,
+       [snd_soc_dapm_line] = 10,
        [snd_soc_dapm_post] = 11,
 };
 
@@ -75,6 +76,7 @@ static int dapm_down_seq[] = {
        [snd_soc_dapm_adc] = 1,
        [snd_soc_dapm_hp] = 2,
        [snd_soc_dapm_spk] = 2,
+       [snd_soc_dapm_line] = 2,
        [snd_soc_dapm_out_drv] = 2,
        [snd_soc_dapm_pga] = 4,
        [snd_soc_dapm_mixer_named_ctl] = 5,
@@ -87,6 +89,8 @@ static int dapm_down_seq[] = {
        [snd_soc_dapm_value_mux] = 9,
        [snd_soc_dapm_aif_in] = 10,
        [snd_soc_dapm_aif_out] = 10,
+       [snd_soc_dapm_dai] = 10,
+       [snd_soc_dapm_regulator_supply] = 11,
        [snd_soc_dapm_supply] = 11,
        [snd_soc_dapm_post] = 12,
 };
@@ -117,6 +121,21 @@ static void pop_dbg(struct device *dev, u32 pop_time, const char *fmt, ...)
        kfree(buf);
 }
 
+static bool dapm_dirty_widget(struct snd_soc_dapm_widget *w)
+{
+       return !list_empty(&w->dirty);
+}
+
+void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason)
+{
+       if (!dapm_dirty_widget(w)) {
+               dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s\n",
+                        w->name, reason);
+               list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
+       }
+}
+EXPORT_SYMBOL_GPL(dapm_mark_dirty);
+
 /* create a new dapm widget */
 static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
        const struct snd_soc_dapm_widget *_widget)
@@ -124,6 +143,101 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
        return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
 }
 
+/* get snd_card from DAPM context */
+static inline struct snd_card *dapm_get_snd_card(
+       struct snd_soc_dapm_context *dapm)
+{
+       if (dapm->codec)
+               return dapm->codec->card->snd_card;
+       else if (dapm->platform)
+               return dapm->platform->card->snd_card;
+       else
+               BUG();
+
+       /* unreachable */
+       return NULL;
+}
+
+/* get soc_card from DAPM context */
+static inline struct snd_soc_card *dapm_get_soc_card(
+               struct snd_soc_dapm_context *dapm)
+{
+       if (dapm->codec)
+               return dapm->codec->card;
+       else if (dapm->platform)
+               return dapm->platform->card;
+       else
+               BUG();
+
+       /* unreachable */
+       return NULL;
+}
+
+static void dapm_reset(struct snd_soc_card *card)
+{
+       struct snd_soc_dapm_widget *w;
+
+       memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
+
+       list_for_each_entry(w, &card->widgets, list) {
+               w->power_checked = false;
+               w->inputs = -1;
+               w->outputs = -1;
+       }
+}
+
+static int soc_widget_read(struct snd_soc_dapm_widget *w, int reg)
+{
+       if (w->codec)
+               return snd_soc_read(w->codec, reg);
+       else if (w->platform)
+               return snd_soc_platform_read(w->platform, reg);
+
+       dev_err(w->dapm->dev, "no valid widget read method\n");
+       return -1;
+}
+
+static int soc_widget_write(struct snd_soc_dapm_widget *w, int reg, int val)
+{
+       if (w->codec)
+               return snd_soc_write(w->codec, reg, val);
+       else if (w->platform)
+               return snd_soc_platform_write(w->platform, reg, val);
+
+       dev_err(w->dapm->dev, "no valid widget write method\n");
+       return -1;
+}
+
+static int soc_widget_update_bits(struct snd_soc_dapm_widget *w,
+       unsigned short reg, unsigned int mask, unsigned int value)
+{
+       bool change;
+       unsigned int old, new;
+       int ret;
+
+       if (w->codec && w->codec->using_regmap) {
+               ret = regmap_update_bits_check(w->codec->control_data,
+                                              reg, mask, value, &change);
+               if (ret != 0)
+                       return ret;
+       } else {
+               ret = soc_widget_read(w, reg);
+               if (ret < 0)
+                       return ret;
+
+               old = ret;
+               new = (old & ~mask) | (value & mask);
+               change = old != new;
+               if (change) {
+                       ret = soc_widget_write(w, reg, new);
+                       if (ret < 0)
+                               return ret;
+               }
+       }
+
+       return change;
+}
+
 /**
  * snd_soc_dapm_set_bias_level - set the bias level for the system
  * @dapm: DAPM context
@@ -139,39 +253,26 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm,
        struct snd_soc_card *card = dapm->card;
        int ret = 0;
 
-       switch (level) {
-       case SND_SOC_BIAS_ON:
-               dev_dbg(dapm->dev, "Setting full bias\n");
-               break;
-       case SND_SOC_BIAS_PREPARE:
-               dev_dbg(dapm->dev, "Setting bias prepare\n");
-               break;
-       case SND_SOC_BIAS_STANDBY:
-               dev_dbg(dapm->dev, "Setting standby bias\n");
-               break;
-       case SND_SOC_BIAS_OFF:
-               dev_dbg(dapm->dev, "Setting bias off\n");
-               break;
-       default:
-               dev_err(dapm->dev, "Setting invalid bias %d\n", level);
-               return -EINVAL;
-       }
-
        trace_snd_soc_bias_level_start(card, level);
 
        if (card && card->set_bias_level)
-               ret = card->set_bias_level(card, level);
-       if (ret == 0) {
-               if (dapm->codec && dapm->codec->driver->set_bias_level)
-                       ret = dapm->codec->driver->set_bias_level(dapm->codec, level);
+               ret = card->set_bias_level(card, dapm, level);
+       if (ret != 0)
+               goto out;
+
+       if (dapm->codec) {
+               if (dapm->codec->driver->set_bias_level)
+                       ret = dapm->codec->driver->set_bias_level(dapm->codec,
+                                                                 level);
                else
                        dapm->bias_level = level;
        }
-       if (ret == 0) {
-               if (card && card->set_bias_level_post)
-                       ret = card->set_bias_level_post(card, level);
-       }
+       if (ret != 0)
+               goto out;
 
+       if (card && card->set_bias_level_post)
+               ret = card->set_bias_level_post(card, dapm, level);
+out:
        trace_snd_soc_bias_level_done(card, level);
 
        return ret;
@@ -194,7 +295,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
                unsigned int mask = (1 << fls(max)) - 1;
                unsigned int invert = mc->invert;
 
-               val = snd_soc_read(w->codec, reg);
+               val = soc_widget_read(w, reg);
                val = (val >> shift) & mask;
 
                if ((invert && !val) || (!invert && val))
@@ -209,8 +310,8 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
                int val, item, bitmask;
 
                for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
-               ;
-               val = snd_soc_read(w->codec, e->reg);
+                       ;
+               val = soc_widget_read(w, e->reg);
                item = (val >> e->shift_l) & (bitmask - 1);
 
                p->connect = 0;
@@ -240,7 +341,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
                        w->kcontrol_news[i].private_value;
                int val, item;
 
-               val = snd_soc_read(w->codec, e->reg);
+               val = soc_widget_read(w, e->reg);
                val = (val >> e->shift_l) & e->mask;
                for (item = 0; item < e->max; item++) {
                        if (val == e->values[item])
@@ -254,25 +355,28 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
                }
        }
        break;
-       /* does not effect routing - always connected */
+       /* does not affect routing - always connected */
        case snd_soc_dapm_pga:
        case snd_soc_dapm_out_drv:
        case snd_soc_dapm_output:
        case snd_soc_dapm_adc:
        case snd_soc_dapm_input:
+       case snd_soc_dapm_siggen:
        case snd_soc_dapm_dac:
        case snd_soc_dapm_micbias:
        case snd_soc_dapm_vmid:
        case snd_soc_dapm_supply:
+       case snd_soc_dapm_regulator_supply:
        case snd_soc_dapm_aif_in:
        case snd_soc_dapm_aif_out:
-               p->connect = 1;
-       break;
-       /* does effect routing - dynamically connected */
+       case snd_soc_dapm_dai:
        case snd_soc_dapm_hp:
        case snd_soc_dapm_mic:
        case snd_soc_dapm_spk:
        case snd_soc_dapm_line:
+               p->connect = 1;
+       break;
+       /* does affect routing - dynamically connected */
        case snd_soc_dapm_pre:
        case snd_soc_dapm_post:
                p->connect = 0;
@@ -325,6 +429,7 @@ static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
 }
 
 static int dapm_is_shared_kcontrol(struct snd_soc_dapm_context *dapm,
+       struct snd_soc_dapm_widget *kcontrolw,
        const struct snd_kcontrol_new *kcontrol_new,
        struct snd_kcontrol **kcontrol)
 {
@@ -334,6 +439,8 @@ static int dapm_is_shared_kcontrol(struct snd_soc_dapm_context *dapm,
        *kcontrol = NULL;
 
        list_for_each_entry(w, &dapm->card->widgets, list) {
+               if (w == kcontrolw || w->dapm != kcontrolw->dapm)
+                       continue;
                for (i = 0; i < w->num_kcontrols; i++) {
                        if (&w->kcontrol_news[i] == kcontrol_new) {
                                if (w->kcontrols)
@@ -347,9 +454,9 @@ static int dapm_is_shared_kcontrol(struct snd_soc_dapm_context *dapm,
 }
 
 /* create new dapm mixer control */
-static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
-       struct snd_soc_dapm_widget *w)
+static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
 {
+       struct snd_soc_dapm_context *dapm = w->dapm;
        int i, ret = 0;
        size_t name_len, prefix_len;
        struct snd_soc_dapm_path *path;
@@ -378,6 +485,11 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
                        if (path->name != (char *)w->kcontrol_news[i].name)
                                continue;
 
+                       if (w->kcontrols[i]) {
+                               path->kcontrol = w->kcontrols[i];
+                               continue;
+                       }
+
                        wlistsize = sizeof(struct snd_soc_dapm_widget_list) +
                                    sizeof(struct snd_soc_dapm_widget *),
                        wlist = kzalloc(wlistsize, GFP_KERNEL);
@@ -415,17 +527,17 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
                                 * for widgets so cut the prefix off
                                 * the front of the widget name.
                                 */
-                               snprintf(path->long_name, name_len, "%s %s",
-                                        w->name + prefix_len,
+                               snprintf((char *)path->long_name, name_len,
+                                        "%s %s", w->name + prefix_len,
                                         w->kcontrol_news[i].name);
                                break;
                        case snd_soc_dapm_mixer_named_ctl:
-                               snprintf(path->long_name, name_len, "%s",
-                                        w->kcontrol_news[i].name);
+                               snprintf((char *)path->long_name, name_len,
+                                        "%s", w->kcontrol_news[i].name);
                                break;
                        }
 
-                       path->long_name[name_len - 1] = '\0';
+                       ((char *)path->long_name)[name_len - 1] = '\0';
 
                        path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i],
                                                      wlist, path->long_name,
@@ -447,9 +559,9 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
 }
 
 /* create new dapm mux control */
-static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
-       struct snd_soc_dapm_widget *w)
+static int dapm_new_mux(struct snd_soc_dapm_widget *w)
 {
+       struct snd_soc_dapm_context *dapm = w->dapm;
        struct snd_soc_dapm_path *path = NULL;
        struct snd_kcontrol *kcontrol;
        struct snd_card *card = dapm->card->snd_card;
@@ -459,7 +571,7 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_widget_list *wlist;
        int shared, wlistentries;
        size_t wlistsize;
-       char *name;
+       const char *name;
 
        if (w->num_kcontrols != 1) {
                dev_err(dapm->dev,
@@ -468,7 +580,7 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
                return -EINVAL;
        }
 
-       shared = dapm_is_shared_kcontrol(dapm, &w->kcontrol_news[0],
+       shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[0],
                                         &kcontrol);
        if (kcontrol) {
                wlist = kcontrol->private_data;
@@ -514,8 +626,8 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
                                        name + prefix_len, prefix);
                ret = snd_ctl_add(card, kcontrol);
                if (ret < 0) {
-                       dev_err(dapm->dev,
-                               "asoc: failed to add kcontrol %s\n", w->name);
+                       dev_err(dapm->dev, "failed to add kcontrol %s: %d\n",
+                               w->name, ret);
                        kfree(wlist);
                        return ret;
                }
@@ -532,8 +644,7 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
 }
 
 /* create new dapm volume control */
-static int dapm_new_pga(struct snd_soc_dapm_context *dapm,
-       struct snd_soc_dapm_widget *w)
+static int dapm_new_pga(struct snd_soc_dapm_widget *w)
 {
        if (w->num_kcontrols)
                dev_err(w->dapm->dev,
@@ -580,30 +691,54 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
        struct snd_soc_dapm_path *path;
        int con = 0;
 
-       if (widget->id == snd_soc_dapm_supply)
+       if (widget->outputs >= 0)
+               return widget->outputs;
+
+       DAPM_UPDATE_STAT(widget, path_checks);
+
+       switch (widget->id) {
+       case snd_soc_dapm_supply:
+       case snd_soc_dapm_regulator_supply:
                return 0;
+       default:
+               break;
+       }
 
        switch (widget->id) {
        case snd_soc_dapm_adc:
        case snd_soc_dapm_aif_out:
-               if (widget->active)
-                       return snd_soc_dapm_suspend_check(widget);
+       case snd_soc_dapm_dai:
+               if (widget->active) {
+                       widget->outputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->outputs;
+               }
        default:
                break;
        }
 
        if (widget->connected) {
                /* connected pin ? */
-               if (widget->id == snd_soc_dapm_output && !widget->ext)
-                       return snd_soc_dapm_suspend_check(widget);
+               if (widget->id == snd_soc_dapm_output && !widget->ext) {
+                       widget->outputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->outputs;
+               }
 
                /* connected jack or spk ? */
-               if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
-                   (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))
-                       return snd_soc_dapm_suspend_check(widget);
+               if (widget->id == snd_soc_dapm_hp ||
+                   widget->id == snd_soc_dapm_spk ||
+                   (widget->id == snd_soc_dapm_line &&
+                    !list_empty(&widget->sources))) {
+                       widget->outputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->outputs;
+               }
        }
 
        list_for_each_entry(path, &widget->sinks, list_source) {
+               DAPM_UPDATE_STAT(widget, neighbour_checks);
+
+               if (path->weak)
+                       continue;
+
                if (path->walked)
                        continue;
 
@@ -613,6 +748,8 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
                }
        }
 
+       widget->outputs = con;
+
        return con;
 }
 
@@ -625,35 +762,66 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
        struct snd_soc_dapm_path *path;
        int con = 0;
 
-       if (widget->id == snd_soc_dapm_supply)
+       if (widget->inputs >= 0)
+               return widget->inputs;
+
+       DAPM_UPDATE_STAT(widget, path_checks);
+
+       switch (widget->id) {
+       case snd_soc_dapm_supply:
+       case snd_soc_dapm_regulator_supply:
                return 0;
+       default:
+               break;
+       }
 
        /* active stream ? */
        switch (widget->id) {
        case snd_soc_dapm_dac:
        case snd_soc_dapm_aif_in:
-               if (widget->active)
-                       return snd_soc_dapm_suspend_check(widget);
+       case snd_soc_dapm_dai:
+               if (widget->active) {
+                       widget->inputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->inputs;
+               }
        default:
                break;
        }
 
        if (widget->connected) {
                /* connected pin ? */
-               if (widget->id == snd_soc_dapm_input && !widget->ext)
-                       return snd_soc_dapm_suspend_check(widget);
+               if (widget->id == snd_soc_dapm_input && !widget->ext) {
+                       widget->inputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->inputs;
+               }
 
                /* connected VMID/Bias for lower pops */
-               if (widget->id == snd_soc_dapm_vmid)
-                       return snd_soc_dapm_suspend_check(widget);
+               if (widget->id == snd_soc_dapm_vmid) {
+                       widget->inputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->inputs;
+               }
 
                /* connected jack ? */
                if (widget->id == snd_soc_dapm_mic ||
-                   (widget->id == snd_soc_dapm_line && !list_empty(&widget->sinks)))
-                       return snd_soc_dapm_suspend_check(widget);
+                   (widget->id == snd_soc_dapm_line &&
+                    !list_empty(&widget->sinks))) {
+                       widget->inputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->inputs;
+               }
+
+               /* signal generator */
+               if (widget->id == snd_soc_dapm_siggen) {
+                       widget->inputs = snd_soc_dapm_suspend_check(widget);
+                       return widget->inputs;
+               }
        }
 
        list_for_each_entry(path, &widget->sources, list_sink) {
+               DAPM_UPDATE_STAT(widget, neighbour_checks);
+
+               if (path->weak)
+                       continue;
+
                if (path->walked)
                        continue;
 
@@ -663,6 +831,8 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
                }
        }
 
+       widget->inputs = con;
+
        return con;
 }
 
@@ -679,19 +849,49 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w,
        else
                val = w->off_val;
 
-       snd_soc_update_bits(w->codec, -(w->reg + 1),
+       soc_widget_update_bits(w, -(w->reg + 1),
                            w->mask << w->shift, val << w->shift);
 
        return 0;
 }
 EXPORT_SYMBOL_GPL(dapm_reg_event);
 
+/*
+ * Handler for regulator supply widget.
+ */
+int dapm_regulator_event(struct snd_soc_dapm_widget *w,
+                  struct snd_kcontrol *kcontrol, int event)
+{
+       if (SND_SOC_DAPM_EVENT_ON(event))
+               return regulator_enable(w->priv);
+       else
+               return regulator_disable_deferred(w->priv, w->shift);
+}
+EXPORT_SYMBOL_GPL(dapm_regulator_event);
+
+static int dapm_widget_power_check(struct snd_soc_dapm_widget *w)
+{
+       if (w->power_checked)
+               return w->new_power;
+
+       if (w->force)
+               w->new_power = 1;
+       else
+               w->new_power = w->power_check(w);
+
+       w->power_checked = true;
+
+       return w->new_power;
+}
+
 /* Generic check to see if a widget should be powered.
  */
 static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
 {
        int in, out;
 
+       DAPM_UPDATE_STAT(w, power_checks);
+
        in = is_connected_input_ep(w);
        dapm_clear_walk(w->dapm);
        out = is_connected_output_ep(w);
@@ -699,11 +899,20 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
        return out != 0 && in != 0;
 }
 
+static int dapm_dai_check_power(struct snd_soc_dapm_widget *w)
+{
+       DAPM_UPDATE_STAT(w, power_checks);
+
+       return w->active;
+}
+
 /* Check to see if an ADC has power */
 static int dapm_adc_check_power(struct snd_soc_dapm_widget *w)
 {
        int in;
 
+       DAPM_UPDATE_STAT(w, power_checks);
+
        if (w->active) {
                in = is_connected_input_ep(w);
                dapm_clear_walk(w->dapm);
@@ -718,6 +927,8 @@ static int dapm_dac_check_power(struct snd_soc_dapm_widget *w)
 {
        int out;
 
+       DAPM_UPDATE_STAT(w, power_checks);
+
        if (w->active) {
                out = is_connected_output_ep(w);
                dapm_clear_walk(w->dapm);
@@ -731,10 +942,16 @@ static int dapm_dac_check_power(struct snd_soc_dapm_widget *w)
 static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
 {
        struct snd_soc_dapm_path *path;
-       int power = 0;
+
+       DAPM_UPDATE_STAT(w, power_checks);
 
        /* Check if one of our outputs is connected */
        list_for_each_entry(path, &w->sinks, list_source) {
+               DAPM_UPDATE_STAT(w, neighbour_checks);
+
+               if (path->weak)
+                       continue;
+
                if (path->connected &&
                    !path->connected(path->source, path->sink))
                        continue;
@@ -742,21 +959,18 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
                if (!path->sink)
                        continue;
 
-               if (path->sink->force) {
-                       power = 1;
-                       break;
-               }
-
-               if (path->sink->power_check &&
-                   path->sink->power_check(path->sink)) {
-                       power = 1;
-                       break;
-               }
+               if (dapm_widget_power_check(path->sink))
+                       return 1;
        }
 
        dapm_clear_walk(w->dapm);
 
-       return power;
+       return 0;
+}
+
+static int dapm_always_on_check_power(struct snd_soc_dapm_widget *w)
+{
+       return 1;
 }
 
 static int dapm_seq_compare(struct snd_soc_dapm_widget *a,
@@ -883,11 +1097,17 @@ static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm,
        }
 
        if (reg >= 0) {
+               /* Any widget will do, they should all be updating the
+                * same register.
+                */
+               w = list_first_entry(pending, struct snd_soc_dapm_widget,
+                                    power_list);
+
                pop_dbg(dapm->dev, card->pop_time,
                        "pop test : Applying 0x%x/0x%x to %x in %dms\n",
                        value, mask, reg, card->pop_time);
                pop_wait(card->pop_time);
-               snd_soc_update_bits(dapm->codec, reg, mask, value);
+               soc_widget_update_bits(w, reg, mask, value);
        }
 
        list_for_each_entry(w, pending, power_list) {
@@ -940,7 +1160,7 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm,
 
                        INIT_LIST_HEAD(&pending);
                        cur_sort = -1;
-                       cur_subseq = -1;
+                       cur_subseq = INT_MIN;
                        cur_reg = SND_SOC_NOPM;
                        cur_dapm = NULL;
                }
@@ -1039,16 +1259,20 @@ static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
        struct snd_soc_dapm_context *d = data;
        int ret;
 
-       if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) {
+       /* If we're off and we're not supposed to be go into STANDBY */
+       if (d->bias_level == SND_SOC_BIAS_OFF &&
+           d->target_bias_level != SND_SOC_BIAS_OFF) {
+               if (d->dev)
+                       pm_runtime_get_sync(d->dev);
+
                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
                if (ret != 0)
                        dev_err(d->dev,
                                "Failed to turn on bias: %d\n", ret);
        }
 
-       /* If we're changing to all on or all off then prepare */
-       if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) ||
-           (!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) {
+       /* Prepare for a STADDBY->ON or ON->STANDBY transition */
+       if (d->bias_level != d->target_bias_level) {
                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
                if (ret != 0)
                        dev_err(d->dev,
@@ -1065,7 +1289,9 @@ static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
        int ret;
 
        /* If we just powered the last thing off drop to standby bias */
-       if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) {
+       if (d->bias_level == SND_SOC_BIAS_PREPARE &&
+           (d->target_bias_level == SND_SOC_BIAS_STANDBY ||
+            d->target_bias_level == SND_SOC_BIAS_OFF)) {
                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
                if (ret != 0)
                        dev_err(d->dev, "Failed to apply standby bias: %d\n",
@@ -1073,14 +1299,19 @@ static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
        }
 
        /* If we're in standby and can support bias off then do that */
-       if (d->bias_level == SND_SOC_BIAS_STANDBY && d->idle_bias_off) {
+       if (d->bias_level == SND_SOC_BIAS_STANDBY &&
+           d->target_bias_level == SND_SOC_BIAS_OFF) {
                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF);
                if (ret != 0)
                        dev_err(d->dev, "Failed to turn off bias: %d\n", ret);
+
+               if (d->dev)
+                       pm_runtime_put(d->dev);
        }
 
        /* If we just powered up then move to active bias */
-       if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) {
+       if (d->bias_level == SND_SOC_BIAS_PREPARE &&
+           d->target_bias_level == SND_SOC_BIAS_ON) {
                ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);
                if (ret != 0)
                        dev_err(d->dev, "Failed to apply active bias: %d\n",
@@ -1088,6 +1319,86 @@ static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
        }
 }
 
+static void dapm_widget_set_peer_power(struct snd_soc_dapm_widget *peer,
+                                      bool power, bool connect)
+{
+       /* If a connection is being made or broken then that update
+        * will have marked the peer dirty, otherwise the widgets are
+        * not connected and this update has no impact. */
+       if (!connect)
+               return;
+
+       /* If the peer is already in the state we're moving to then we
+        * won't have an impact on it. */
+       if (power != peer->power)
+               dapm_mark_dirty(peer, "peer state change");
+}
+
+static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power,
+                                 struct list_head *up_list,
+                                 struct list_head *down_list)
+{
+       struct snd_soc_dapm_path *path;
+
+       if (w->power == power)
+               return;
+
+       trace_snd_soc_dapm_widget_power(w, power);
+
+       /* If we changed our power state perhaps our neigbours changed
+        * also.
+        */
+       list_for_each_entry(path, &w->sources, list_sink) {
+               if (path->source) {
+                       dapm_widget_set_peer_power(path->source, power,
+                                                  path->connect);
+               }
+       }
+       switch (w->id) {
+       case snd_soc_dapm_supply:
+       case snd_soc_dapm_regulator_supply:
+               /* Supplies can't affect their outputs, only their inputs */
+               break;
+       default:
+               list_for_each_entry(path, &w->sinks, list_source) {
+                       if (path->sink) {
+                               dapm_widget_set_peer_power(path->sink, power,
+                                                          path->connect);
+                       }
+               }
+               break;
+       }
+
+       if (power)
+               dapm_seq_insert(w, up_list, true);
+       else
+               dapm_seq_insert(w, down_list, false);
+
+       w->power = power;
+}
+
+static void dapm_power_one_widget(struct snd_soc_dapm_widget *w,
+                                 struct list_head *up_list,
+                                 struct list_head *down_list)
+{
+       int power;
+
+       switch (w->id) {
+       case snd_soc_dapm_pre:
+               dapm_seq_insert(w, down_list, false);
+               break;
+       case snd_soc_dapm_post:
+               dapm_seq_insert(w, up_list, true);
+               break;
+
+       default:
+               power = dapm_widget_power_check(w);
+
+               dapm_widget_set_power(w, power, up_list, down_list);
+               break;
+       }
+}
+
 /*
  * Scan each dapm widget for complete audio path.
  * A complete path is a route that has valid endpoints i.e.:-
@@ -1105,50 +1416,67 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
        LIST_HEAD(up_list);
        LIST_HEAD(down_list);
        LIST_HEAD(async_domain);
-       int power;
+       enum snd_soc_bias_level bias;
 
        trace_snd_soc_dapm_start(card);
 
-       list_for_each_entry(d, &card->dapm_list, list)
-               if (d->n_widgets || d->codec == NULL)
-                       d->dev_power = 0;
+       list_for_each_entry(d, &card->dapm_list, list) {
+               if (d->n_widgets || d->codec == NULL) {
+                       if (d->idle_bias_off)
+                               d->target_bias_level = SND_SOC_BIAS_OFF;
+                       else
+                               d->target_bias_level = SND_SOC_BIAS_STANDBY;
+               }
+       }
+
+       dapm_reset(card);
 
        /* Check which widgets we need to power and store them in
-        * lists indicating if they should be powered up or down.
+        * lists indicating if they should be powered up or down.  We
+        * only check widgets that have been flagged as dirty but note
+        * that new widgets may be added to the dirty list while we
+        * iterate.
         */
+       list_for_each_entry(w, &card->dapm_dirty, dirty) {
+               dapm_power_one_widget(w, &up_list, &down_list);
+       }
+
        list_for_each_entry(w, &card->widgets, list) {
                switch (w->id) {
                case snd_soc_dapm_pre:
-                       dapm_seq_insert(w, &down_list, false);
-                       break;
                case snd_soc_dapm_post:
-                       dapm_seq_insert(w, &up_list, true);
+                       /* These widgets always need to be powered */
                        break;
-
                default:
-                       if (!w->power_check)
-                               continue;
-
-                       if (!w->force)
-                               power = w->power_check(w);
-                       else
-                               power = 1;
-                       if (power)
-                               w->dapm->dev_power = 1;
-
-                       if (w->power == power)
-                               continue;
-
-                       trace_snd_soc_dapm_widget_power(w, power);
-
-                       if (power)
-                               dapm_seq_insert(w, &up_list, true);
-                       else
-                               dapm_seq_insert(w, &down_list, false);
-
-                       w->power = power;
+                       list_del_init(&w->dirty);
                        break;
                }
+
+               if (w->power) {
+                       d = w->dapm;
+
+                       /* Supplies and micbiases only bring the
+                        * context up to STANDBY as unless something
+                        * else is active and passing audio they
+                        * generally don't require full power.  Signal
+                        * generators are virtual pins and have no
+                        * power impact themselves.
+                        */
+                       switch (w->id) {
+                       case snd_soc_dapm_siggen:
+                               break;
+                       case snd_soc_dapm_supply:
+                       case snd_soc_dapm_regulator_supply:
+                       case snd_soc_dapm_micbias:
+                               if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
+                                       d->target_bias_level = SND_SOC_BIAS_STANDBY;
+                               break;
+                       default:
+                               d->target_bias_level = SND_SOC_BIAS_ON;
+                               break;
+                       }
+               }
+
        }
 
        /* If there are no DAPM widgets then try to figure out power from the
@@ -1158,38 +1486,37 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
                switch (event) {
                case SND_SOC_DAPM_STREAM_START:
                case SND_SOC_DAPM_STREAM_RESUME:
-                       dapm->dev_power = 1;
+                       dapm->target_bias_level = SND_SOC_BIAS_ON;
                        break;
                case SND_SOC_DAPM_STREAM_STOP:
-                       dapm->dev_power = !!dapm->codec->active;
+                       if (dapm->codec && dapm->codec->active)
+                               dapm->target_bias_level = SND_SOC_BIAS_ON;
+                       else
+                               dapm->target_bias_level = SND_SOC_BIAS_STANDBY;
                        break;
                case SND_SOC_DAPM_STREAM_SUSPEND:
-                       dapm->dev_power = 0;
+                       dapm->target_bias_level = SND_SOC_BIAS_STANDBY;
                        break;
                case SND_SOC_DAPM_STREAM_NOP:
-                       switch (dapm->bias_level) {
-                               case SND_SOC_BIAS_STANDBY:
-                               case SND_SOC_BIAS_OFF:
-                                       dapm->dev_power = 0;
-                                       break;
-                               default:
-                                       dapm->dev_power = 1;
-                                       break;
-                       }
+                       dapm->target_bias_level = dapm->bias_level;
                        break;
                default:
                        break;
                }
        }
 
-       /* Force all contexts in the card to the same bias state */
-       power = 0;
+       /* Force all contexts in the card to the same bias state if
+        * they're not ground referenced.
+        */
+       bias = SND_SOC_BIAS_OFF;
        list_for_each_entry(d, &card->dapm_list, list)
-               if (d->dev_power)
-                       power = 1;
+               if (d->target_bias_level > bias)
+                       bias = d->target_bias_level;
        list_for_each_entry(d, &card->dapm_list, list)
-               d->dev_power = power;
+               if (!d->idle_bias_off)
+                       d->target_bias_level = bias;
 
+       trace_snd_soc_dapm_walk_done(card);
 
        /* Run all the bias changes in parallel */
        list_for_each_entry(d, &dapm->card->dapm_list, list)
@@ -1211,6 +1538,12 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
                                        &async_domain);
        async_synchronize_full_domain(&async_domain);
 
+       /* do we need to notify any clients that DAPM event is complete */
+       list_for_each_entry(d, &card->dapm_list, list) {
+               if (d->stream_event)
+                       d->stream_event(d, event);
+       }
+
        pop_dbg(dapm->dev, card->pop_time,
                "DAPM sequencing finished, waiting %dms\n", card->pop_time);
        pop_wait(card->pop_time);
@@ -1221,12 +1554,6 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
 }
 
 #ifdef CONFIG_DEBUG_FS
-static int dapm_widget_power_open_file(struct inode *inode, struct file *file)
-{
-       file->private_data = inode->i_private;
-       return 0;
-}
-
 static ssize_t dapm_widget_power_read_file(struct file *file,
                                           char __user *user_buf,
                                           size_t count, loff_t *ppos)
@@ -1246,8 +1573,9 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
        out = is_connected_output_ep(w);
        dapm_clear_walk(w->dapm);
 
-       ret = snprintf(buf, PAGE_SIZE, "%s: %s  in %d out %d",
-                      w->name, w->power ? "On" : "Off", in, out);
+       ret = snprintf(buf, PAGE_SIZE, "%s: %s%s  in %d out %d",
+                      w->name, w->power ? "On" : "Off",
+                      w->force ? " (forced)" : "", in, out);
 
        if (w->reg >= 0)
                ret += snprintf(buf + ret, PAGE_SIZE - ret,
@@ -1289,17 +1617,11 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
 }
 
 static const struct file_operations dapm_widget_power_fops = {
-       .open = dapm_widget_power_open_file,
+       .open = simple_open,
        .read = dapm_widget_power_read_file,
        .llseek = default_llseek,
 };
 
-static int dapm_bias_open_file(struct inode *inode, struct file *file)
-{
-       file->private_data = inode->i_private;
-       return 0;
-}
-
 static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf,
                                   size_t count, loff_t *ppos)
 {
@@ -1330,7 +1652,7 @@ static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf,
 }
 
 static const struct file_operations dapm_bias_fops = {
-       .open = dapm_bias_open_file,
+       .open = simple_open,
        .read = dapm_bias_read_file,
        .llseek = default_llseek,
 };
@@ -1343,7 +1665,7 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
        dapm->debugfs_dapm = debugfs_create_dir("dapm", parent);
 
        if (!dapm->debugfs_dapm) {
-               printk(KERN_WARNING
+               dev_warn(dapm->dev,
                       "Failed to create DAPM debugfs directory\n");
                return;
        }
@@ -1395,9 +1717,8 @@ static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm)
 #endif
 
 /* test and update the power status of a mux widget */
-static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
-                                struct snd_kcontrol *kcontrol, int change,
-                                int mux, struct soc_enum *e)
+int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
+                                struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e)
 {
        struct snd_soc_dapm_path *path;
        int found = 0;
@@ -1407,9 +1728,6 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
            widget->id != snd_soc_dapm_value_mux)
                return -ENODEV;
 
-       if (!change)
-               return 0;
-
        /* find dapm widget path assoc with kcontrol */
        list_for_each_entry(path, &widget->dapm->card->paths, list) {
                if (path->kcontrol != kcontrol)
@@ -1420,20 +1738,28 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
 
                found = 1;
                /* we now need to match the string in the enum to the path */
-               if (!(strcmp(path->name, e->texts[mux])))
+               if (!(strcmp(path->name, e->texts[mux]))) {
                        path->connect = 1; /* new connection */
-               else
+                       dapm_mark_dirty(path->source, "mux connection");
+               } else {
+                       if (path->connect)
+                               dapm_mark_dirty(path->source,
+                                               "mux disconnection");
                        path->connect = 0; /* old connection must be powered down */
+               }
        }
 
-       if (found)
+       if (found) {
+               dapm_mark_dirty(widget, "mux change");
                dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
+       }
 
        return 0;
 }
+EXPORT_SYMBOL_GPL(snd_soc_dapm_mux_update_power);
 
 /* test and update the power status of a mixer or switch widget */
-static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
+int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
                                   struct snd_kcontrol *kcontrol, int connect)
 {
        struct snd_soc_dapm_path *path;
@@ -1452,21 +1778,23 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
                /* found, now check type */
                found = 1;
                path->connect = connect;
-               break;
+               dapm_mark_dirty(path->source, "mixer connection");
        }
 
-       if (found)
+       if (found) {
+               dapm_mark_dirty(widget, "mixer update");
                dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
+       }
 
        return 0;
 }
+EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power);
 
 /* show dapm widget status in sys fs */
 static ssize_t dapm_widget_show(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
-       struct snd_soc_pcm_runtime *rtd =
-                       container_of(dev, struct snd_soc_pcm_runtime, dev);
+       struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
        struct snd_soc_codec *codec =rtd->codec;
        struct snd_soc_dapm_widget *w;
        int count = 0;
@@ -1490,6 +1818,7 @@ static ssize_t dapm_widget_show(struct device *dev,
                case snd_soc_dapm_mixer:
                case snd_soc_dapm_mixer_named_ctl:
                case snd_soc_dapm_supply:
+               case snd_soc_dapm_regulator_supply:
                        if (w->name)
                                count += sprintf(buf + count, "%s: %s\n",
                                        w->name, w->power ? "On":"Off");
@@ -1597,6 +1926,9 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
                return -EINVAL;
        }
 
+       if (w->connected != status)
+               dapm_mark_dirty(w, "pin configuration");
+
        w->connected = status;
        if (status == 0)
                w->force = 0;
@@ -1615,7 +1947,19 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
  */
 int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm)
 {
-       return dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);
+       int ret;
+
+       /*
+        * Suppress early reports (eg, jacks syncing their state) to avoid
+        * silly DAPM runs during card startup.
+        */
+       if (!dapm->card || !dapm->card->instantiated)
+               return 0;
+
+       mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
+       ret = dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);
+       mutex_unlock(&dapm->card->dapm_mutex);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
 
@@ -1714,13 +2058,16 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
        case snd_soc_dapm_out_drv:
        case snd_soc_dapm_input:
        case snd_soc_dapm_output:
+       case snd_soc_dapm_siggen:
        case snd_soc_dapm_micbias:
        case snd_soc_dapm_vmid:
        case snd_soc_dapm_pre:
        case snd_soc_dapm_post:
        case snd_soc_dapm_supply:
+       case snd_soc_dapm_regulator_supply:
        case snd_soc_dapm_aif_in:
        case snd_soc_dapm_aif_out:
+       case snd_soc_dapm_dai:
                list_add(&path->list, &dapm->card->paths);
                list_add(&path->list_sink, &wsink->sources);
                list_add(&path->list_source, &wsource->sinks);
@@ -1776,22 +2123,104 @@ err:
 int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
                            const struct snd_soc_dapm_route *route, int num)
 {
-       int i, ret;
+       int i, ret = 0;
 
+       mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
        for (i = 0; i < num; i++) {
                ret = snd_soc_dapm_add_route(dapm, route);
                if (ret < 0) {
                        dev_err(dapm->dev, "Failed to add route %s->%s\n",
                                route->source, route->sink);
-                       return ret;
+                       break;
                }
                route++;
        }
+       mutex_unlock(&dapm->card->dapm_mutex);
 
-       return 0;
+       return ret;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);
 
+static int snd_soc_dapm_weak_route(struct snd_soc_dapm_context *dapm,
+                                  const struct snd_soc_dapm_route *route)
+{
+       struct snd_soc_dapm_widget *source = dapm_find_widget(dapm,
+                                                             route->source,
+                                                             true);
+       struct snd_soc_dapm_widget *sink = dapm_find_widget(dapm,
+                                                           route->sink,
+                                                           true);
+       struct snd_soc_dapm_path *path;
+       int count = 0;
+
+       if (!source) {
+               dev_err(dapm->dev, "Unable to find source %s for weak route\n",
+                       route->source);
+               return -ENODEV;
+       }
+
+       if (!sink) {
+               dev_err(dapm->dev, "Unable to find sink %s for weak route\n",
+                       route->sink);
+               return -ENODEV;
+       }
+
+       if (route->control || route->connected)
+               dev_warn(dapm->dev, "Ignoring control for weak route %s->%s\n",
+                        route->source, route->sink);
+
+       list_for_each_entry(path, &source->sinks, list_source) {
+               if (path->sink == sink) {
+                       path->weak = 1;
+                       count++;
+               }
+       }
+
+       if (count == 0)
+               dev_err(dapm->dev, "No path found for weak route %s->%s\n",
+                       route->source, route->sink);
+       if (count > 1)
+               dev_warn(dapm->dev, "%d paths found for weak route %s->%s\n",
+                        count, route->source, route->sink);
+
+       return 0;
+}
+
+/**
+ * snd_soc_dapm_weak_routes - Mark routes between DAPM widgets as weak
+ * @dapm: DAPM context
+ * @route: audio routes
+ * @num: number of routes
+ *
+ * Mark existing routes matching those specified in the passed array
+ * as being weak, meaning that they are ignored for the purpose of
+ * power decisions.  The main intended use case is for sidetone paths
+ * which couple audio between other independent paths if they are both
+ * active in order to make the combination work better at the user
+ * level but which aren't intended to be "used".
+ *
+ * Note that CODEC drivers should not use this as sidetone type paths
+ * can frequently also be used as bypass paths.
+ */
+int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm,
+                            const struct snd_soc_dapm_route *route, int num)
+{
+       int i, err;
+       int ret = 0;
+
+       mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
+       for (i = 0; i < num; i++) {
+               err = snd_soc_dapm_weak_route(dapm, route);
+               if (err)
+                       ret = err;
+               route++;
+       }
+       mutex_unlock(&dapm->card->dapm_mutex);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_weak_routes);
+
 /**
  * snd_soc_dapm_new_widgets - add new dapm widgets
  * @dapm: DAPM context
@@ -1805,6 +2234,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
        struct snd_soc_dapm_widget *w;
        unsigned int val;
 
+       mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
+
        list_for_each_entry(w, &dapm->card->widgets, list)
        {
                if (w->new)
@@ -1814,56 +2245,34 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
                        w->kcontrols = kzalloc(w->num_kcontrols *
                                                sizeof(struct snd_kcontrol *),
                                                GFP_KERNEL);
-                       if (!w->kcontrols)
+                       if (!w->kcontrols) {
+                               mutex_unlock(&dapm->card->dapm_mutex);
                                return -ENOMEM;
+                       }
                }
 
                switch(w->id) {
                case snd_soc_dapm_switch:
                case snd_soc_dapm_mixer:
                case snd_soc_dapm_mixer_named_ctl:
-                       w->power_check = dapm_generic_check_power;
-                       dapm_new_mixer(dapm, w);
+                       dapm_new_mixer(w);
                        break;
                case snd_soc_dapm_mux:
                case snd_soc_dapm_virt_mux:
                case snd_soc_dapm_value_mux:
-                       w->power_check = dapm_generic_check_power;
-                       dapm_new_mux(dapm, w);
-                       break;
-               case snd_soc_dapm_adc:
-               case snd_soc_dapm_aif_out:
-                       w->power_check = dapm_adc_check_power;
-                       break;
-               case snd_soc_dapm_dac:
-               case snd_soc_dapm_aif_in:
-                       w->power_check = dapm_dac_check_power;
+                       dapm_new_mux(w);
                        break;
                case snd_soc_dapm_pga:
                case snd_soc_dapm_out_drv:
-                       w->power_check = dapm_generic_check_power;
-                       dapm_new_pga(dapm, w);
-                       break;
-               case snd_soc_dapm_input:
-               case snd_soc_dapm_output:
-               case snd_soc_dapm_micbias:
-               case snd_soc_dapm_spk:
-               case snd_soc_dapm_hp:
-               case snd_soc_dapm_mic:
-               case snd_soc_dapm_line:
-                       w->power_check = dapm_generic_check_power;
+                       dapm_new_pga(w);
                        break;
-               case snd_soc_dapm_supply:
-                       w->power_check = dapm_supply_check_power;
-               case snd_soc_dapm_vmid:
-               case snd_soc_dapm_pre:
-               case snd_soc_dapm_post:
+               default:
                        break;
                }
 
                /* Read the initial power state from the device */
                if (w->reg >= 0) {
-                       val = snd_soc_read(w->codec, w->reg);
+                       val = soc_widget_read(w, w->reg);
                        val &= 1 << w->shift;
                        if (w->invert)
                                val = !val;
@@ -1874,10 +2283,12 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
 
                w->new = 1;
 
+               dapm_mark_dirty(w, "new widget");
                dapm_debugfs_add_widget(w);
        }
 
        dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);
+       mutex_unlock(&dapm->card->dapm_mutex);
        return 0;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets);
@@ -1937,6 +2348,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
        struct snd_soc_dapm_widget *widget = wlist->widgets[0];
        struct snd_soc_codec *codec = widget->codec;
+       struct snd_soc_card *card = codec->card;
        struct soc_mixer_control *mc =
                (struct soc_mixer_control *)kcontrol->private_value;
        unsigned int reg = mc->reg;
@@ -1950,20 +2362,22 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
        int wi;
 
        val = (ucontrol->value.integer.value[0] & mask);
+       connect = !!val;
 
        if (invert)
                val = max - val;
        mask = mask << shift;
        val = val << shift;
-
+#if 0
        if (val)
                /* new connection */
                connect = invert ? 0 : 1;
        else
                /* old connection must be powered down */
                connect = invert ? 1 : 0;
+#endif
 
-       mutex_lock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
 
        change = snd_soc_test_bits(widget->codec, reg, mask, val);
        if (change) {
@@ -1979,13 +2393,13 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
                        update.val = val;
                        widget->dapm->update = &update;
 
-                       dapm_mixer_update_power(widget, kcontrol, connect);
+                       snd_soc_dapm_mixer_update_power(widget, kcontrol, connect);
 
                        widget->dapm->update = NULL;
                }
        }
 
-       mutex_unlock(&codec->mutex);
+       mutex_unlock(&card->dapm_mutex);
        return 0;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw);
@@ -2034,6 +2448,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
        struct snd_soc_dapm_widget *widget = wlist->widgets[0];
        struct snd_soc_codec *codec = widget->codec;
+       struct snd_soc_card *card = codec->card;
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
        unsigned int val, mux, change;
        unsigned int mask, bitmask;
@@ -2054,7 +2469,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
                mask |= (bitmask - 1) << e->shift_r;
        }
 
-       mutex_lock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
 
        change = snd_soc_test_bits(widget->codec, e->reg, mask, val);
        if (change) {
@@ -2070,13 +2485,13 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
                        update.val = val;
                        widget->dapm->update = &update;
 
-                       dapm_mux_update_power(widget, kcontrol, change, mux, e);
+                       snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e);
 
                        widget->dapm->update = NULL;
                }
        }
 
-       mutex_unlock(&codec->mutex);
+       mutex_unlock(&card->dapm_mutex);
        return change;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double);
@@ -2113,6 +2528,7 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
        struct snd_soc_dapm_widget *widget = wlist->widgets[0];
        struct snd_soc_codec *codec = widget->codec;
+       struct snd_soc_card *card = codec->card;
        struct soc_enum *e =
                (struct soc_enum *)kcontrol->private_value;
        int change;
@@ -2122,7 +2538,7 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
        if (ucontrol->value.enumerated.item[0] >= e->max)
                return -EINVAL;
 
-       mutex_lock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
 
        change = widget->value != ucontrol->value.enumerated.item[0];
        if (change) {
@@ -2131,12 +2547,11 @@ int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
 
                        widget->value = ucontrol->value.enumerated.item[0];
 
-                       dapm_mux_update_power(widget, kcontrol, change,
-                                             widget->value, e);
+                       snd_soc_dapm_mux_update_power(widget, kcontrol, widget->value, e);
                }
        }
 
-       mutex_unlock(&codec->mutex);
+       mutex_unlock(&card->dapm_mutex);
        return ret;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt);
@@ -2201,6 +2616,7 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,
        struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
        struct snd_soc_dapm_widget *widget = wlist->widgets[0];
        struct snd_soc_codec *codec = widget->codec;
+       struct snd_soc_card *card = codec->card;
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
        unsigned int val, mux, change;
        unsigned int mask;
@@ -2219,7 +2635,7 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,
                mask |= e->mask << e->shift_r;
        }
 
-       mutex_lock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
 
        change = snd_soc_test_bits(widget->codec, e->reg, mask, val);
        if (change) {
@@ -2235,13 +2651,13 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,
                        update.val = val;
                        widget->dapm->update = &update;
 
-                       dapm_mux_update_power(widget, kcontrol, change, mux, e);
+                       snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e);
 
                        widget->dapm->update = NULL;
                }
        }
 
-       mutex_unlock(&codec->mutex);
+       mutex_unlock(&card->dapm_mutex);
        return change;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_put_value_enum_double);
@@ -2275,15 +2691,15 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_info_pin_switch);
 int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol,
                                struct snd_ctl_elem_value *ucontrol)
 {
-       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
        const char *pin = (const char *)kcontrol->private_value;
 
-       mutex_lock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
 
        ucontrol->value.integer.value[0] =
-               snd_soc_dapm_get_pin_status(&codec->dapm, pin);
+               snd_soc_dapm_get_pin_status(&card->dapm, pin);
 
-       mutex_unlock(&codec->mutex);
+       mutex_unlock(&card->dapm_mutex);
 
        return 0;
 }
@@ -2298,41 +2714,47 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_switch);
 int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol,
                                struct snd_ctl_elem_value *ucontrol)
 {
-       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
        const char *pin = (const char *)kcontrol->private_value;
 
-       mutex_lock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
 
        if (ucontrol->value.integer.value[0])
-               snd_soc_dapm_enable_pin(&codec->dapm, pin);
+               snd_soc_dapm_enable_pin(&card->dapm, pin);
        else
-               snd_soc_dapm_disable_pin(&codec->dapm, pin);
-
-       snd_soc_dapm_sync(&codec->dapm);
+               snd_soc_dapm_disable_pin(&card->dapm, pin);
 
-       mutex_unlock(&codec->mutex);
+       mutex_unlock(&card->dapm_mutex);
 
+       snd_soc_dapm_sync(&card->dapm);
        return 0;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch);
 
-/**
- * snd_soc_dapm_new_control - create new dapm control
- * @dapm: DAPM context
- * @widget: widget template
- *
- * Creates a new dapm control based upon the template.
- *
- * Returns 0 for success else error.
- */
-int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
-       const struct snd_soc_dapm_widget *widget)
+static struct snd_soc_dapm_widget *
+snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
+                        const struct snd_soc_dapm_widget *widget)
 {
        struct snd_soc_dapm_widget *w;
        size_t name_len;
+       int ret;
 
        if ((w = dapm_cnew_widget(widget)) == NULL)
-               return -ENOMEM;
+               return NULL;
+
+       switch (w->id) {
+       case snd_soc_dapm_regulator_supply:
+               w->priv = devm_regulator_get(dapm->dev, w->name);
+               if (IS_ERR(w->priv)) {
+                       ret = PTR_ERR(w->priv);
+                       dev_err(dapm->dev, "Failed to request %s: %d\n",
+                               w->name, ret);
+                       return NULL;
+               }
+               break;
+       default:
+               break;
+       }
 
        name_len = strlen(widget->name) + 1;
        if (dapm->codec && dapm->codec->name_prefix)
@@ -2340,27 +2762,70 @@ int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
        w->name = kmalloc(name_len, GFP_KERNEL);
        if (w->name == NULL) {
                kfree(w);
-               return -ENOMEM;
+               return NULL;
        }
        if (dapm->codec && dapm->codec->name_prefix)
-               snprintf(w->name, name_len, "%s %s",
+               snprintf((char *)w->name, name_len, "%s %s",
                        dapm->codec->name_prefix, widget->name);
        else
-               snprintf(w->name, name_len, "%s", widget->name);
+               snprintf((char *)w->name, name_len, "%s", widget->name);
+
+       switch (w->id) {
+       case snd_soc_dapm_switch:
+       case snd_soc_dapm_mixer:
+       case snd_soc_dapm_mixer_named_ctl:
+               w->power_check = dapm_generic_check_power;
+               break;
+       case snd_soc_dapm_mux:
+       case snd_soc_dapm_virt_mux:
+       case snd_soc_dapm_value_mux:
+               w->power_check = dapm_generic_check_power;
+               break;
+       case snd_soc_dapm_adc:
+       case snd_soc_dapm_aif_out:
+               w->power_check = dapm_adc_check_power;
+               break;
+       case snd_soc_dapm_dac:
+       case snd_soc_dapm_aif_in:
+               w->power_check = dapm_dac_check_power;
+               break;
+       case snd_soc_dapm_pga:
+       case snd_soc_dapm_out_drv:
+       case snd_soc_dapm_input:
+       case snd_soc_dapm_output:
+       case snd_soc_dapm_micbias:
+       case snd_soc_dapm_spk:
+       case snd_soc_dapm_hp:
+       case snd_soc_dapm_mic:
+       case snd_soc_dapm_line:
+               w->power_check = dapm_generic_check_power;
+               break;
+       case snd_soc_dapm_supply:
+       case snd_soc_dapm_regulator_supply:
+               w->power_check = dapm_supply_check_power;
+               break;
+       case snd_soc_dapm_dai:
+               w->power_check = dapm_dai_check_power;
+               break;
+       default:
+               w->power_check = dapm_always_on_check_power;
+               break;
+       }
 
        dapm->n_widgets++;
        w->dapm = dapm;
        w->codec = dapm->codec;
+       w->platform = dapm->platform;
        INIT_LIST_HEAD(&w->sources);
        INIT_LIST_HEAD(&w->sinks);
        INIT_LIST_HEAD(&w->list);
+       INIT_LIST_HEAD(&w->dirty);
        list_add(&w->list, &dapm->card->widgets);
 
        /* machine layer set ups unconnected pins and insertions */
        w->connected = 1;
-       return 0;
+       return w;
 }
-EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);
 
 /**
  * snd_soc_dapm_new_controls - create new dapm controls
@@ -2376,50 +2841,160 @@ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
        const struct snd_soc_dapm_widget *widget,
        int num)
 {
-       int i, ret;
+       struct snd_soc_dapm_widget *w;
+       int i;
+       int ret = 0;
 
+       mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
        for (i = 0; i < num; i++) {
-               ret = snd_soc_dapm_new_control(dapm, widget);
-               if (ret < 0) {
+               w = snd_soc_dapm_new_control(dapm, widget);
+               if (!w) {
                        dev_err(dapm->dev,
-                               "ASoC: Failed to create DAPM control %s: %d\n",
-                               widget->name, ret);
-                       return ret;
+                               "ASoC: Failed to create DAPM control %s\n",
+                               widget->name);
+                       ret = -ENOMEM;
+                       break;
                }
                widget++;
        }
-       return 0;
+       mutex_unlock(&dapm->card->dapm_mutex);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
 
-static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,
-       const char *stream, int event)
+int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
+                                struct snd_soc_dai *dai)
 {
+       struct snd_soc_dapm_widget template;
        struct snd_soc_dapm_widget *w;
 
-       list_for_each_entry(w, &dapm->card->widgets, list)
-       {
-               if (!w->sname || w->dapm != dapm)
+       WARN_ON(dapm->dev != dai->dev);
+
+       memset(&template, 0, sizeof(template));
+       template.reg = SND_SOC_NOPM;
+
+       if (dai->driver->playback.stream_name) {
+               template.id = snd_soc_dapm_dai;
+               template.name = dai->driver->playback.stream_name;
+               template.sname = dai->driver->playback.stream_name;
+
+               dev_dbg(dai->dev, "adding %s widget\n",
+                       template.name);
+
+               w = snd_soc_dapm_new_control(dapm, &template);
+               if (!w) {
+                       dev_err(dapm->dev, "Failed to create %s widget\n",
+                               dai->driver->playback.stream_name);
+               }
+
+               w->priv = dai;
+               dai->playback_widget = w;
+       }
+
+       if (dai->driver->capture.stream_name) {
+               template.id = snd_soc_dapm_dai;
+               template.name = dai->driver->capture.stream_name;
+               template.sname = dai->driver->capture.stream_name;
+
+               dev_dbg(dai->dev, "adding %s widget\n",
+                       template.name);
+
+               w = snd_soc_dapm_new_control(dapm, &template);
+               if (!w) {
+                       dev_err(dapm->dev, "Failed to create %s widget\n",
+                               dai->driver->capture.stream_name);
+               }
+
+               w->priv = dai;
+               dai->capture_widget = w;
+       }
+
+       return 0;
+}
+
+int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
+{
+       struct snd_soc_dapm_widget *dai_w, *w;
+       struct snd_soc_dai *dai;
+       struct snd_soc_dapm_route r;
+
+       memset(&r, 0, sizeof(r));
+
+       /* For each DAI widget... */
+       list_for_each_entry(dai_w, &card->widgets, list) {
+               if (dai_w->id != snd_soc_dapm_dai)
                        continue;
-               dev_dbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n",
-                       w->name, w->sname, stream, event);
-               if (strstr(w->sname, stream)) {
-                       switch(event) {
-                       case SND_SOC_DAPM_STREAM_START:
-                               w->active = 1;
-                               break;
-                       case SND_SOC_DAPM_STREAM_STOP:
-                               w->active = 0;
-                               break;
-                       case SND_SOC_DAPM_STREAM_SUSPEND:
-                       case SND_SOC_DAPM_STREAM_RESUME:
-                       case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
-                       case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
-                               break;
+
+               dai = dai_w->priv;
+
+               /* ...find all widgets with the same stream and link them */
+               list_for_each_entry(w, &card->widgets, list) {
+                       if (w->dapm != dai_w->dapm)
+                               continue;
+
+                       if (w->id == snd_soc_dapm_dai)
+                               continue;
+
+                       if (!w->sname)
+                               continue;
+
+                       if (dai->driver->playback.stream_name &&
+                           strstr(w->sname,
+                                  dai->driver->playback.stream_name)) {
+                               r.source = dai->playback_widget->name;
+                               r.sink = w->name;
+                               dev_dbg(dai->dev, "%s -> %s\n",
+                                        r.source, r.sink);
+
+                               snd_soc_dapm_add_route(w->dapm, &r);
+                       }
+
+                       if (dai->driver->capture.stream_name &&
+                           strstr(w->sname,
+                                  dai->driver->capture.stream_name)) {
+                               r.source = w->name;
+                               r.sink = dai->capture_widget->name;
+                               dev_dbg(dai->dev, "%s -> %s\n",
+                                       r.source, r.sink);
+
+                               snd_soc_dapm_add_route(w->dapm, &r);
                        }
                }
        }
 
+       return 0;
+}
+
+static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,
+                                 int stream, struct snd_soc_dai *dai,
+                                 int event)
+{
+       struct snd_soc_dapm_widget *w;
+
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+               w = dai->playback_widget;
+       else
+               w = dai->capture_widget;
+
+       if (!w)
+               return;
+
+       dapm_mark_dirty(w, "stream event");
+
+       switch (event) {
+       case SND_SOC_DAPM_STREAM_START:
+               w->active = 1;
+               break;
+       case SND_SOC_DAPM_STREAM_STOP:
+               w->active = 0;
+               break;
+       case SND_SOC_DAPM_STREAM_SUSPEND:
+       case SND_SOC_DAPM_STREAM_RESUME:
+       case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
+       case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
+               break;
+       }
+
        dapm_power_widgets(dapm, event);
 }
 
@@ -2434,17 +3009,14 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,
  *
  * Returns 0 for success else error.
  */
-int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
-       const char *stream, int event)
+int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
+                             struct snd_soc_dai *dai, int event)
 {
-       struct snd_soc_codec *codec = rtd->codec;
-
-       if (stream == NULL)
-               return 0;
+       struct snd_soc_card *card = rtd->card;
 
-       mutex_lock(&codec->mutex);
-       soc_dapm_stream_event(&codec->dapm, stream, event);
-       mutex_unlock(&codec->mutex);
+       mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_PCM);
+       soc_dapm_stream_event(&card->dapm, stream, dai, event);
+       mutex_unlock(&card->dapm_mutex);
        return 0;
 }
 
@@ -2489,6 +3061,7 @@ int snd_soc_dapm_force_enable_pin(struct snd_soc_dapm_context *dapm,
        dev_dbg(w->dapm->dev, "dapm: force enable pin %s\n", pin);
        w->connected = 1;
        w->force = 1;
+       dapm_mark_dirty(w, "force enable");
 
        return 0;
 }
@@ -2578,9 +3151,82 @@ int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm,
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend);
 
+static bool snd_soc_dapm_widget_in_card_paths(struct snd_soc_card *card,
+                                             struct snd_soc_dapm_widget *w)
+{
+       struct snd_soc_dapm_path *p;
+
+       list_for_each_entry(p, &card->paths, list) {
+               if ((p->source == w) || (p->sink == w)) {
+                       dev_dbg(card->dev,
+                           "... Path %s(id:%d dapm:%p) - %s(id:%d dapm:%p)\n",
+                           p->source->name, p->source->id, p->source->dapm,
+                           p->sink->name, p->sink->id, p->sink->dapm);
+
+                       /* Connected to something other than the codec */
+                       if (p->source->dapm != p->sink->dapm)
+                               return true;
+                       /*
+                        * Loopback connection from codec external pin to
+                        * codec external pin
+                        */
+                       if (p->sink->id == snd_soc_dapm_input) {
+                               switch (p->source->id) {
+                               case snd_soc_dapm_output:
+                               case snd_soc_dapm_micbias:
+                                       return true;
+                               default:
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       return false;
+}
+
+/**
+ * snd_soc_dapm_auto_nc_codec_pins - call snd_soc_dapm_nc_pin for unused pins
+ * @codec: The codec whose pins should be processed
+ *
+ * Automatically call snd_soc_dapm_nc_pin() for any external pins in the codec
+ * which are unused. Pins are used if they are connected externally to the
+ * codec, whether that be to some other device, or a loop-back connection to
+ * the codec itself.
+ */
+void snd_soc_dapm_auto_nc_codec_pins(struct snd_soc_codec *codec)
+{
+       struct snd_soc_card *card = codec->card;
+       struct snd_soc_dapm_context *dapm = &codec->dapm;
+       struct snd_soc_dapm_widget *w;
+
+       dev_dbg(codec->dev, "Auto NC: DAPMs: card:%p codec:%p\n",
+               &card->dapm, &codec->dapm);
+
+       list_for_each_entry(w, &card->widgets, list) {
+               if (w->dapm != dapm)
+                       continue;
+               switch (w->id) {
+               case snd_soc_dapm_input:
+               case snd_soc_dapm_output:
+               case snd_soc_dapm_micbias:
+                       dev_dbg(codec->dev, "Auto NC: Checking widget %s\n",
+                               w->name);
+                       if (!snd_soc_dapm_widget_in_card_paths(card, w)) {
+                               dev_dbg(codec->dev,
+                                       "... Not in map; disabling\n");
+                               snd_soc_dapm_nc_pin(dapm, w->name);
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+
 /**
  * snd_soc_dapm_free - free dapm resources
- * @card: SoC device
+ * @dapm: DAPM context
  *
  * Free all dapm widgets and resources.
  */
@@ -2613,9 +3259,13 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm)
         * standby.
         */
        if (powerdown) {
-               snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_PREPARE);
+               if (dapm->bias_level == SND_SOC_BIAS_ON)
+                       snd_soc_dapm_set_bias_level(dapm,
+                                                   SND_SOC_BIAS_PREPARE);
                dapm_seq_run(dapm, &down_list, 0, false);
-               snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_STANDBY);
+               if (dapm->bias_level == SND_SOC_BIAS_PREPARE)
+                       snd_soc_dapm_set_bias_level(dapm,
+                                                   SND_SOC_BIAS_STANDBY);
        }
 }
 
@@ -2626,9 +3276,11 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card)
 {
        struct snd_soc_codec *codec;
 
-       list_for_each_entry(codec, &card->codec_dev_list, list) {
+       list_for_each_entry(codec, &card->codec_dev_list, card_list) {
                soc_dapm_shutdown_codec(&codec->dapm);
-               snd_soc_dapm_set_bias_level(&codec->dapm, SND_SOC_BIAS_OFF);
+               if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY)
+                       snd_soc_dapm_set_bias_level(&codec->dapm,
+                                                   SND_SOC_BIAS_OFF);
        }
 }