Merge remote-tracking branches 'asoc/fix/atmel', 'asoc/fix/intel', 'asoc/fix/rt5645...
[firefly-linux-kernel-4.4.55.git] / sound / soc / soc-dapm.c
index 39f992bc2b6adf1d36dd9647b860c839edf4087b..c5136bb1f9821afe43bae8a2f9bece210e699952 100644 (file)
@@ -159,27 +159,135 @@ static void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason)
        }
 }
 
-void dapm_mark_io_dirty(struct snd_soc_dapm_context *dapm)
+/*
+ * dapm_widget_invalidate_input_paths() - Invalidate the cached number of input
+ *  paths
+ * @w: The widget for which to invalidate the cached number of input paths
+ *
+ * The function resets the cached number of inputs for the specified widget and
+ * all widgets that can be reached via outgoing paths from the widget.
+ *
+ * This function must be called if the number of input paths for a widget might
+ * have changed. E.g. if the source state of a widget changes or a path is added
+ * or activated with the widget as the sink.
+ */
+static void dapm_widget_invalidate_input_paths(struct snd_soc_dapm_widget *w)
+{
+       struct snd_soc_dapm_widget *sink;
+       struct snd_soc_dapm_path *p;
+       LIST_HEAD(list);
+
+       dapm_assert_locked(w->dapm);
+
+       if (w->inputs == -1)
+               return;
+
+       w->inputs = -1;
+       list_add_tail(&w->work_list, &list);
+
+       list_for_each_entry(w, &list, work_list) {
+               list_for_each_entry(p, &w->sinks, list_source) {
+                       if (p->is_supply || p->weak || !p->connect)
+                               continue;
+                       sink = p->sink;
+                       if (sink->inputs != -1) {
+                               sink->inputs = -1;
+                               list_add_tail(&sink->work_list, &list);
+                       }
+               }
+       }
+}
+
+/*
+ * dapm_widget_invalidate_output_paths() - Invalidate the cached number of
+ *  output paths
+ * @w: The widget for which to invalidate the cached number of output paths
+ *
+ * Resets the cached number of outputs for the specified widget and all widgets
+ * that can be reached via incoming paths from the widget.
+ *
+ * This function must be called if the number of output paths for a widget might
+ * have changed. E.g. if the sink state of a widget changes or a path is added
+ * or activated with the widget as the source.
+ */
+static void dapm_widget_invalidate_output_paths(struct snd_soc_dapm_widget *w)
+{
+       struct snd_soc_dapm_widget *source;
+       struct snd_soc_dapm_path *p;
+       LIST_HEAD(list);
+
+       dapm_assert_locked(w->dapm);
+
+       if (w->outputs == -1)
+               return;
+
+       w->outputs = -1;
+       list_add_tail(&w->work_list, &list);
+
+       list_for_each_entry(w, &list, work_list) {
+               list_for_each_entry(p, &w->sources, list_sink) {
+                       if (p->is_supply || p->weak || !p->connect)
+                               continue;
+                       source = p->source;
+                       if (source->outputs != -1) {
+                               source->outputs = -1;
+                               list_add_tail(&source->work_list, &list);
+                       }
+               }
+       }
+}
+
+/*
+ * dapm_path_invalidate() - Invalidates the cached number of inputs and outputs
+ *  for the widgets connected to a path
+ * @p: The path to invalidate
+ *
+ * Resets the cached number of inputs for the sink of the path and the cached
+ * number of outputs for the source of the path.
+ *
+ * This function must be called when a path is added, removed or the connected
+ * state changes.
+ */
+static void dapm_path_invalidate(struct snd_soc_dapm_path *p)
+{
+       /*
+        * Weak paths or supply paths do not influence the number of input or
+        * output paths of their neighbors.
+        */
+       if (p->weak || p->is_supply)
+               return;
+
+       /*
+        * The number of connected endpoints is the sum of the number of
+        * connected endpoints of all neighbors. If a node with 0 connected
+        * endpoints is either connected or disconnected that sum won't change,
+        * so there is no need to re-check the path.
+        */
+       if (p->source->inputs != 0)
+               dapm_widget_invalidate_input_paths(p->sink);
+       if (p->sink->outputs != 0)
+               dapm_widget_invalidate_output_paths(p->source);
+}
+
+void dapm_mark_endpoints_dirty(struct snd_soc_card *card)
 {
-       struct snd_soc_card *card = dapm->card;
        struct snd_soc_dapm_widget *w;
 
        mutex_lock(&card->dapm_mutex);
 
        list_for_each_entry(w, &card->widgets, list) {
-               switch (w->id) {
-               case snd_soc_dapm_input:
-               case snd_soc_dapm_output:
-                       dapm_mark_dirty(w, "Rechecking inputs and outputs");
-                       break;
-               default:
-                       break;
+               if (w->is_sink || w->is_source) {
+                       dapm_mark_dirty(w, "Rechecking endpoints");
+                       if (w->is_sink)
+                               dapm_widget_invalidate_output_paths(w);
+                       if (w->is_source)
+                               dapm_widget_invalidate_input_paths(w);
                }
        }
 
        mutex_unlock(&card->dapm_mutex);
 }
-EXPORT_SYMBOL_GPL(dapm_mark_io_dirty);
+EXPORT_SYMBOL_GPL(dapm_mark_endpoints_dirty);
 
 /* create a new dapm widget */
 static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
@@ -386,8 +494,6 @@ static void dapm_reset(struct snd_soc_card *card)
        list_for_each_entry(w, &card->widgets, list) {
                w->new_power = w->power;
                w->power_checked = false;
-               w->inputs = -1;
-               w->outputs = -1;
        }
 }
 
@@ -469,10 +575,9 @@ out:
 
 /* connect mux widget to its interconnecting audio paths */
 static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
-       struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
-       struct snd_soc_dapm_path *path, const char *control_name,
-       const struct snd_kcontrol_new *kcontrol)
+       struct snd_soc_dapm_path *path, const char *control_name)
 {
+       const struct snd_kcontrol_new *kcontrol = &path->sink->kcontrol_news[0];
        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
        unsigned int val, item;
        int i;
@@ -493,9 +598,6 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
 
        for (i = 0; i < e->items; i++) {
                if (!(strcmp(control_name, e->texts[i]))) {
-                       list_add(&path->list, &dapm->card->paths);
-                       list_add(&path->list_sink, &dest->sources);
-                       list_add(&path->list_source, &src->sinks);
                        path->name = e->texts[i];
                        if (i == item)
                                path->connect = 1;
@@ -509,11 +611,10 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
 }
 
 /* set up initial codec paths */
-static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w,
-       struct snd_soc_dapm_path *p, int i)
+static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i)
 {
        struct soc_mixer_control *mc = (struct soc_mixer_control *)
-               w->kcontrol_news[i].private_value;
+               p->sink->kcontrol_news[i].private_value;
        unsigned int reg = mc->reg;
        unsigned int shift = mc->shift;
        unsigned int max = mc->max;
@@ -522,7 +623,7 @@ static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w,
        unsigned int val;
 
        if (reg != SND_SOC_NOPM) {
-               soc_dapm_read(w->dapm, reg, &val);
+               soc_dapm_read(p->sink->dapm, reg, &val);
                val = (val >> shift) & mask;
                if (invert)
                        val = max - val;
@@ -534,19 +635,15 @@ static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w,
 
 /* connect mixer widget to its interconnecting audio paths */
 static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
-       struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
        struct snd_soc_dapm_path *path, const char *control_name)
 {
        int i;
 
        /* search for mixer kcontrol */
-       for (i = 0; i < dest->num_kcontrols; i++) {
-               if (!strcmp(control_name, dest->kcontrol_news[i].name)) {
-                       list_add(&path->list, &dapm->card->paths);
-                       list_add(&path->list_sink, &dest->sources);
-                       list_add(&path->list_source, &src->sinks);
-                       path->name = dest->kcontrol_news[i].name;
-                       dapm_set_mixer_path_status(dest, path, i);
+       for (i = 0; i < path->sink->num_kcontrols; i++) {
+               if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) {
+                       path->name = path->sink->kcontrol_news[i].name;
+                       dapm_set_mixer_path_status(path, i);
                        return 0;
                }
        }
@@ -738,8 +835,10 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w)
        if (ret < 0)
                return ret;
 
-       list_for_each_entry(path, &w->sources, list_sink)
-               dapm_kcontrol_add_path(w->kcontrols[0], path);
+       list_for_each_entry(path, &w->sources, list_sink) {
+               if (path->name)
+                       dapm_kcontrol_add_path(w->kcontrols[0], path);
+       }
 
        return 0;
 }
@@ -754,34 +853,6 @@ static int dapm_new_pga(struct snd_soc_dapm_widget *w)
        return 0;
 }
 
-/* reset 'walked' bit for each dapm path */
-static void dapm_clear_walk_output(struct snd_soc_dapm_context *dapm,
-                                  struct list_head *sink)
-{
-       struct snd_soc_dapm_path *p;
-
-       list_for_each_entry(p, sink, list_source) {
-               if (p->walked) {
-                       p->walked = 0;
-                       dapm_clear_walk_output(dapm, &p->sink->sinks);
-               }
-       }
-}
-
-static void dapm_clear_walk_input(struct snd_soc_dapm_context *dapm,
-                                 struct list_head *source)
-{
-       struct snd_soc_dapm_path *p;
-
-       list_for_each_entry(p, source, list_sink) {
-               if (p->walked) {
-                       p->walked = 0;
-                       dapm_clear_walk_input(dapm, &p->source->sources);
-               }
-       }
-}
-
-
 /* We implement power down on suspend by checking the power state of
  * the ALSA card - when we are suspending the ALSA state for the card
  * is set to D3.
@@ -856,61 +927,23 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget,
 
        DAPM_UPDATE_STAT(widget, path_checks);
 
-       switch (widget->id) {
-       case snd_soc_dapm_supply:
-       case snd_soc_dapm_regulator_supply:
-       case snd_soc_dapm_clock_supply:
-       case snd_soc_dapm_kcontrol:
-               return 0;
-       default:
-               break;
-       }
-
-       switch (widget->id) {
-       case snd_soc_dapm_adc:
-       case snd_soc_dapm_aif_out:
-       case snd_soc_dapm_dai_out:
-               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) {
-                       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))) {
-                       widget->outputs = snd_soc_dapm_suspend_check(widget);
-                       return widget->outputs;
-               }
+       if (widget->is_sink && widget->connected) {
+               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)
+               if (path->weak || path->is_supply)
                        continue;
 
                if (path->walking)
                        return 1;
 
-               if (path->walked)
-                       continue;
-
                trace_snd_soc_dapm_output_path(widget, path);
 
-               if (path->sink && path->connect) {
-                       path->walked = 1;
+               if (path->connect) {
                        path->walking = 1;
 
                        /* do we need to add this widget to the list ? */
@@ -952,73 +985,23 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
 
        DAPM_UPDATE_STAT(widget, path_checks);
 
-       switch (widget->id) {
-       case snd_soc_dapm_supply:
-       case snd_soc_dapm_regulator_supply:
-       case snd_soc_dapm_clock_supply:
-       case snd_soc_dapm_kcontrol:
-               return 0;
-       default:
-               break;
-       }
-
-       /* active stream ? */
-       switch (widget->id) {
-       case snd_soc_dapm_dac:
-       case snd_soc_dapm_aif_in:
-       case snd_soc_dapm_dai_in:
-               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) {
-                       widget->inputs = snd_soc_dapm_suspend_check(widget);
-                       return widget->inputs;
-               }
-
-               /* connected VMID/Bias for lower pops */
-               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))) {
-                       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;
-               }
+       if (widget->is_source && widget->connected) {
+               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)
+               if (path->weak || path->is_supply)
                        continue;
 
                if (path->walking)
                        return 1;
 
-               if (path->walked)
-                       continue;
-
                trace_snd_soc_dapm_input_path(widget, path);
 
-               if (path->source && path->connect) {
-                       path->walked = 1;
+               if (path->connect) {
                        path->walking = 1;
 
                        /* do we need to add this widget to the list ? */
@@ -1060,21 +1043,25 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
 int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
        struct snd_soc_dapm_widget_list **list)
 {
-       struct snd_soc_card *card = dai->card;
+       struct snd_soc_card *card = dai->component->card;
+       struct snd_soc_dapm_widget *w;
        int paths;
 
        mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
-       dapm_reset(card);
 
-       if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+       /*
+        * For is_connected_{output,input}_ep fully discover the graph we need
+        * to reset the cached number of inputs and outputs.
+        */
+       list_for_each_entry(w, &card->widgets, list) {
+               w->inputs = -1;
+               w->outputs = -1;
+       }
+
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
                paths = is_connected_output_ep(dai->playback_widget, list);
-               dapm_clear_walk_output(&card->dapm,
-                                      &dai->playback_widget->sinks);
-       } else {
+       else
                paths = is_connected_input_ep(dai->capture_widget, list);
-               dapm_clear_walk_input(&card->dapm,
-                                     &dai->capture_widget->sources);
-       }
 
        trace_snd_soc_dapm_connected(paths, stream);
        mutex_unlock(&card->dapm_mutex);
@@ -1163,44 +1150,10 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
        DAPM_UPDATE_STAT(w, power_checks);
 
        in = is_connected_input_ep(w, NULL);
-       dapm_clear_walk_input(w->dapm, &w->sources);
        out = is_connected_output_ep(w, NULL);
-       dapm_clear_walk_output(w->dapm, &w->sinks);
        return out != 0 && in != 0;
 }
 
-/* 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, NULL);
-               dapm_clear_walk_input(w->dapm, &w->sources);
-               return in != 0;
-       } else {
-               return dapm_generic_check_power(w);
-       }
-}
-
-/* Check to see if a DAC has power */
-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, NULL);
-               dapm_clear_walk_output(w->dapm, &w->sinks);
-               return out != 0;
-       } else {
-               return dapm_generic_check_power(w);
-       }
-}
-
 /* Check to see if a power supply is needed */
 static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
 {
@@ -1219,9 +1172,6 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
                    !path->connected(path->source, path->sink))
                        continue;
 
-               if (!path->sink)
-                       continue;
-
                if (dapm_widget_power_check(path->sink))
                        return 1;
        }
@@ -1636,27 +1586,14 @@ static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool 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,
+       list_for_each_entry(path, &w->sources, list_sink)
+               dapm_widget_set_peer_power(path->source, power, path->connect);
+
+       /* Supplies can't affect their outputs, only their inputs */
+       if (!w->is_supply) {
+               list_for_each_entry(path, &w->sinks, list_source)
+                       dapm_widget_set_peer_power(path->sink, power,
                                                   path->connect);
-               }
-       }
-       switch (w->id) {
-       case snd_soc_dapm_supply:
-       case snd_soc_dapm_regulator_supply:
-       case snd_soc_dapm_clock_supply:
-       case snd_soc_dapm_kcontrol:
-               /* 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)
@@ -1863,10 +1800,14 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
        if (!buf)
                return -ENOMEM;
 
-       in = is_connected_input_ep(w, NULL);
-       dapm_clear_walk_input(w->dapm, &w->sources);
-       out = is_connected_output_ep(w, NULL);
-       dapm_clear_walk_output(w->dapm, &w->sinks);
+       /* Supply widgets are not handled by is_connected_{input,output}_ep() */
+       if (w->is_supply) {
+               in = 0;
+               out = 0;
+       } else {
+               in = is_connected_input_ep(w, NULL);
+               out = is_connected_output_ep(w, NULL);
+       }
 
        ret = snprintf(buf, PAGE_SIZE, "%s: %s%s  in %d out %d",
                       w->name, w->power ? "On" : "Off",
@@ -2011,32 +1952,45 @@ static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm)
 
 #endif
 
+/*
+ * soc_dapm_connect_path() - Connects or disconnects a path
+ * @path: The path to update
+ * @connect: The new connect state of the path. True if the path is connected,
+ *  false if it is disconneted.
+ * @reason: The reason why the path changed (for debugging only)
+ */
+static void soc_dapm_connect_path(struct snd_soc_dapm_path *path,
+       bool connect, const char *reason)
+{
+       if (path->connect == connect)
+               return;
+
+       path->connect = connect;
+       dapm_mark_dirty(path->source, reason);
+       dapm_mark_dirty(path->sink, reason);
+       dapm_path_invalidate(path);
+}
+
 /* test and update the power status of a mux widget */
 static int soc_dapm_mux_update_power(struct snd_soc_card *card,
                                 struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e)
 {
        struct snd_soc_dapm_path *path;
        int found = 0;
+       bool connect;
 
        lockdep_assert_held(&card->dapm_mutex);
 
        /* find dapm widget path assoc with kcontrol */
        dapm_kcontrol_for_each_path(path, kcontrol) {
-               if (!path->name || !e->texts[mux])
-                       continue;
-
                found = 1;
                /* we now need to match the string in the enum to the path */
-               if (!(strcmp(path->name, e->texts[mux]))) {
-                       path->connect = 1; /* new connection */
-                       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 */
-               }
-               dapm_mark_dirty(path->sink, "mux change");
+               if (!(strcmp(path->name, e->texts[mux])))
+                       connect = true;
+               else
+                       connect = false;
+
+               soc_dapm_connect_path(path, connect, "mux update");
        }
 
        if (found)
@@ -2075,9 +2029,7 @@ static int soc_dapm_mixer_update_power(struct snd_soc_card *card,
        /* find dapm widget path assoc with kcontrol */
        dapm_kcontrol_for_each_path(path, kcontrol) {
                found = 1;
-               path->connect = connect;
-               dapm_mark_dirty(path->source, "mixer connection");
-               dapm_mark_dirty(path->sink, "mixer update");
+               soc_dapm_connect_path(path, connect, "mixer update");
        }
 
        if (found)
@@ -2255,8 +2207,11 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
                return -EINVAL;
        }
 
-       if (w->connected != status)
+       if (w->connected != status) {
                dapm_mark_dirty(w, "pin configuration");
+               dapm_widget_invalidate_input_paths(w);
+               dapm_widget_invalidate_output_paths(w);
+       }
 
        w->connected = status;
        if (status == 0)
@@ -2309,6 +2264,53 @@ int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm)
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
 
+/*
+ * dapm_update_widget_flags() - Re-compute widget sink and source flags
+ * @w: The widget for which to update the flags
+ *
+ * Some widgets have a dynamic category which depends on which neighbors they
+ * are connected to. This function update the category for these widgets.
+ *
+ * This function must be called whenever a path is added or removed to a widget.
+ */
+static void dapm_update_widget_flags(struct snd_soc_dapm_widget *w)
+{
+       struct snd_soc_dapm_path *p;
+
+       switch (w->id) {
+       case snd_soc_dapm_input:
+               w->is_source = 1;
+               list_for_each_entry(p, &w->sources, list_sink) {
+                       if (p->source->id == snd_soc_dapm_micbias ||
+                               p->source->id == snd_soc_dapm_mic ||
+                               p->source->id == snd_soc_dapm_line ||
+                               p->source->id == snd_soc_dapm_output) {
+                                       w->is_source = 0;
+                                       break;
+                       }
+               }
+               break;
+       case snd_soc_dapm_output:
+               w->is_sink = 1;
+               list_for_each_entry(p, &w->sinks, list_source) {
+                       if (p->sink->id == snd_soc_dapm_spk ||
+                               p->sink->id == snd_soc_dapm_hp ||
+                               p->sink->id == snd_soc_dapm_line ||
+                               p->sink->id == snd_soc_dapm_input) {
+                                       w->is_sink = 0;
+                                       break;
+                       }
+               }
+               break;
+       case snd_soc_dapm_line:
+               w->is_sink = !list_empty(&w->sources);
+               w->is_source = !list_empty(&w->sinks);
+               break;
+       default:
+               break;
+       }
+}
+
 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
        const char *control,
@@ -2318,6 +2320,27 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
        struct snd_soc_dapm_path *path;
        int ret;
 
+       if (wsink->is_supply && !wsource->is_supply) {
+               dev_err(dapm->dev,
+                       "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n",
+                       wsource->name, wsink->name);
+               return -EINVAL;
+       }
+
+       if (connected && !wsource->is_supply) {
+               dev_err(dapm->dev,
+                       "connected() callback only supported for supply widgets (%s -> %s)\n",
+                       wsource->name, wsink->name);
+               return -EINVAL;
+       }
+
+       if (wsource->is_supply && control) {
+               dev_err(dapm->dev,
+                       "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n",
+                       wsource->name, control, wsink->name);
+               return -EINVAL;
+       }
+
        path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
        if (!path)
                return -ENOMEM;
@@ -2330,85 +2353,49 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
        INIT_LIST_HEAD(&path->list_source);
        INIT_LIST_HEAD(&path->list_sink);
 
-       /* check for external widgets */
-       if (wsink->id == snd_soc_dapm_input) {
-               if (wsource->id == snd_soc_dapm_micbias ||
-                       wsource->id == snd_soc_dapm_mic ||
-                       wsource->id == snd_soc_dapm_line ||
-                       wsource->id == snd_soc_dapm_output)
-                       wsink->ext = 1;
-       }
-       if (wsource->id == snd_soc_dapm_output) {
-               if (wsink->id == snd_soc_dapm_spk ||
-                       wsink->id == snd_soc_dapm_hp ||
-                       wsink->id == snd_soc_dapm_line ||
-                       wsink->id == snd_soc_dapm_input)
-                       wsource->ext = 1;
-       }
-
-       dapm_mark_dirty(wsource, "Route added");
-       dapm_mark_dirty(wsink, "Route added");
+       if (wsource->is_supply || wsink->is_supply)
+               path->is_supply = 1;
 
        /* connect static paths */
        if (control == NULL) {
-               list_add(&path->list, &dapm->card->paths);
-               list_add(&path->list_sink, &wsink->sources);
-               list_add(&path->list_source, &wsource->sinks);
                path->connect = 1;
-               return 0;
-       }
-
-       /* connect dynamic paths */
-       switch (wsink->id) {
-       case snd_soc_dapm_adc:
-       case snd_soc_dapm_dac:
-       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_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_clock_supply:
-       case snd_soc_dapm_aif_in:
-       case snd_soc_dapm_aif_out:
-       case snd_soc_dapm_dai_in:
-       case snd_soc_dapm_dai_out:
-       case snd_soc_dapm_dai_link:
-       case snd_soc_dapm_kcontrol:
-               list_add(&path->list, &dapm->card->paths);
-               list_add(&path->list_sink, &wsink->sources);
-               list_add(&path->list_source, &wsource->sinks);
-               path->connect = 1;
-               return 0;
-       case snd_soc_dapm_mux:
-               ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
-                       &wsink->kcontrol_news[0]);
-               if (ret != 0)
-                       goto err;
-               break;
-       case snd_soc_dapm_switch:
-       case snd_soc_dapm_mixer:
-       case snd_soc_dapm_mixer_named_ctl:
-               ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
-               if (ret != 0)
+       } else {
+               /* connect dynamic paths */
+               switch (wsink->id) {
+               case snd_soc_dapm_mux:
+                       ret = dapm_connect_mux(dapm, path, control);
+                       if (ret != 0)
+                               goto err;
+                       break;
+               case snd_soc_dapm_switch:
+               case snd_soc_dapm_mixer:
+               case snd_soc_dapm_mixer_named_ctl:
+                       ret = dapm_connect_mixer(dapm, path, control);
+                       if (ret != 0)
+                               goto err;
+                       break;
+               default:
+                       dev_err(dapm->dev,
+                               "Control not supported for path %s -> [%s] -> %s\n",
+                               wsource->name, control, wsink->name);
+                       ret = -EINVAL;
                        goto err;
-               break;
-       case snd_soc_dapm_hp:
-       case snd_soc_dapm_mic:
-       case snd_soc_dapm_line:
-       case snd_soc_dapm_spk:
-               list_add(&path->list, &dapm->card->paths);
-               list_add(&path->list_sink, &wsink->sources);
-               list_add(&path->list_source, &wsource->sinks);
-               path->connect = 0;
-               return 0;
+               }
        }
 
+       list_add(&path->list, &dapm->card->paths);
+       list_add(&path->list_sink, &wsink->sources);
+       list_add(&path->list_source, &wsource->sinks);
+
+       dapm_update_widget_flags(wsource);
+       dapm_update_widget_flags(wsink);
+
+       dapm_mark_dirty(wsource, "Route added");
+       dapm_mark_dirty(wsink, "Route added");
+
+       if (dapm->card->instantiated && path->connect)
+               dapm_path_invalidate(path);
+
        return 0;
 err:
        kfree(path);
@@ -2489,6 +2476,7 @@ err:
 static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm,
                                  const struct snd_soc_dapm_route *route)
 {
+       struct snd_soc_dapm_widget *wsource, *wsink;
        struct snd_soc_dapm_path *path, *p;
        const char *sink;
        const char *source;
@@ -2526,10 +2514,19 @@ static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm,
        }
 
        if (path) {
-               dapm_mark_dirty(path->source, "Route removed");
-               dapm_mark_dirty(path->sink, "Route removed");
+               wsource = path->source;
+               wsink = path->sink;
+
+               dapm_mark_dirty(wsource, "Route removed");
+               dapm_mark_dirty(wsink, "Route removed");
+               if (path->connect)
+                       dapm_path_invalidate(path);
 
                dapm_free_path(path);
+
+               /* Update any path related flags */
+               dapm_update_widget_flags(wsource);
+               dapm_update_widget_flags(wsink);
        } else {
                dev_warn(dapm->dev, "ASoC: Route %s->%s does not exist\n",
                         source, sink);
@@ -3087,40 +3084,44 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
        }
 
        switch (w->id) {
-       case snd_soc_dapm_switch:
-       case snd_soc_dapm_mixer:
-       case snd_soc_dapm_mixer_named_ctl:
+       case snd_soc_dapm_mic:
+       case snd_soc_dapm_input:
+               w->is_source = 1;
                w->power_check = dapm_generic_check_power;
                break;
-       case snd_soc_dapm_mux:
+       case snd_soc_dapm_spk:
+       case snd_soc_dapm_hp:
+       case snd_soc_dapm_output:
+               w->is_sink = 1;
                w->power_check = dapm_generic_check_power;
                break;
-       case snd_soc_dapm_dai_out:
-               w->power_check = dapm_adc_check_power;
-               break;
-       case snd_soc_dapm_dai_in:
-               w->power_check = dapm_dac_check_power;
+       case snd_soc_dapm_vmid:
+       case snd_soc_dapm_siggen:
+               w->is_source = 1;
+               w->power_check = dapm_always_on_check_power;
                break;
+       case snd_soc_dapm_mux:
+       case snd_soc_dapm_switch:
+       case snd_soc_dapm_mixer:
+       case snd_soc_dapm_mixer_named_ctl:
        case snd_soc_dapm_adc:
        case snd_soc_dapm_aif_out:
        case snd_soc_dapm_dac:
        case snd_soc_dapm_aif_in:
        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:
        case snd_soc_dapm_dai_link:
+       case snd_soc_dapm_dai_out:
+       case snd_soc_dapm_dai_in:
                w->power_check = dapm_generic_check_power;
                break;
        case snd_soc_dapm_supply:
        case snd_soc_dapm_regulator_supply:
        case snd_soc_dapm_clock_supply:
        case snd_soc_dapm_kcontrol:
+               w->is_supply = 1;
                w->power_check = dapm_supply_check_power;
                break;
        default:
@@ -3137,6 +3138,9 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
        INIT_LIST_HEAD(&w->dirty);
        list_add(&w->list, &dapm->card->widgets);
 
+       w->inputs = -1;
+       w->outputs = -1;
+
        /* machine layer set ups unconnected pins and insertions */
        w->connected = 1;
        return w;
@@ -3484,6 +3488,14 @@ static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream,
                case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
                        break;
                }
+
+               if (w->id == snd_soc_dapm_dai_in) {
+                       w->is_source = w->active;
+                       dapm_widget_invalidate_input_paths(w);
+               } else {
+                       w->is_sink = w->active;
+                       dapm_widget_invalidate_output_paths(w);
+               }
        }
 }
 
@@ -3610,7 +3622,15 @@ int snd_soc_dapm_force_enable_pin_unlocked(struct snd_soc_dapm_context *dapm,
        }
 
        dev_dbg(w->dapm->dev, "ASoC: force enable pin %s\n", pin);
-       w->connected = 1;
+       if (!w->connected) {
+               /*
+                * w->force does not affect the number of input or output paths,
+                * so we only have to recheck if w->connected is changed
+                */
+               dapm_widget_invalidate_input_paths(w);
+               dapm_widget_invalidate_output_paths(w);
+               w->connected = 1;
+       }
        w->force = 1;
        dapm_mark_dirty(w, "force enable");
 
@@ -3788,35 +3808,54 @@ int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm,
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend);
 
+/**
+ * dapm_is_external_path() - Checks if a path is a external path
+ * @card: The card the path belongs to
+ * @path: The path to check
+ *
+ * Returns true if the path is either between two different DAPM contexts or
+ * between two external pins of the same DAPM context. Otherwise returns
+ * false.
+ */
+static bool dapm_is_external_path(struct snd_soc_card *card,
+       struct snd_soc_dapm_path *path)
+{
+       dev_dbg(card->dev,
+               "... Path %s(id:%d dapm:%p) - %s(id:%d dapm:%p)\n",
+               path->source->name, path->source->id, path->source->dapm,
+               path->sink->name, path->sink->id, path->sink->dapm);
+
+       /* Connection between two different DAPM contexts */
+       if (path->source->dapm != path->sink->dapm)
+               return true;
+
+       /* Loopback connection from external pin to external pin */
+       if (path->sink->id == snd_soc_dapm_input) {
+               switch (path->source->id) {
+               case snd_soc_dapm_output:
+               case snd_soc_dapm_micbias:
+                       return true;
+               default:
+                       break;
+               }
+       }
+
+       return false;
+}
+
 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);
+       list_for_each_entry(p, &w->sources, list_sink) {
+               if (dapm_is_external_path(card, p))
+                       return true;
+       }
 
-                       /* 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;
-                               }
-                       }
-               }
+       list_for_each_entry(p, &w->sinks, list_source) {
+               if (dapm_is_external_path(card, p))
+                       return true;
        }
 
        return false;