ASoC: Only run power_check() on a widget once per run
[deliverable/linux.git] / sound / soc / soc-dapm.c
index 170c4ffa609ff2f9df87da7af186f47d12cee977..c39146d435e227217ce12cb68376332230863cc1 100644 (file)
@@ -48,6 +48,8 @@
 
 #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,
@@ -117,6 +119,20 @@ 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);
+}
+
+static 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);
+       }
+}
+
 /* create a new dapm widget */
 static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
        const struct snd_soc_dapm_widget *_widget)
@@ -316,7 +332,7 @@ 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:
@@ -328,13 +344,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
        case snd_soc_dapm_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_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;
@@ -584,8 +600,8 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w)
                                        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;
                }
@@ -649,6 +665,8 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
        struct snd_soc_dapm_path *path;
        int con = 0;
 
+       DAPM_UPDATE_STAT(widget, path_checks);
+
        if (widget->id == snd_soc_dapm_supply)
                return 0;
 
@@ -673,6 +691,8 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
        }
 
        list_for_each_entry(path, &widget->sinks, list_source) {
+               DAPM_UPDATE_STAT(widget, neighbour_checks);
+
                if (path->weak)
                        continue;
 
@@ -697,6 +717,8 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
        struct snd_soc_dapm_path *path;
        int con = 0;
 
+       DAPM_UPDATE_STAT(widget, path_checks);
+
        if (widget->id == snd_soc_dapm_supply)
                return 0;
 
@@ -726,6 +748,8 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
        }
 
        list_for_each_entry(path, &widget->sources, list_sink) {
+               DAPM_UPDATE_STAT(widget, neighbour_checks);
+
                if (path->weak)
                        continue;
 
@@ -761,12 +785,29 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w,
 }
 EXPORT_SYMBOL_GPL(dapm_reg_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);
@@ -779,6 +820,8 @@ 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);
@@ -793,6 +836,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);
@@ -808,8 +853,12 @@ 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;
 
@@ -820,13 +869,7 @@ 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)) {
+               if (dapm_widget_power_check(path->sink)) {
                        power = 1;
                        break;
                }
@@ -837,6 +880,11 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
        return power;
 }
 
+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,
                            struct snd_soc_dapm_widget *b,
                            bool power_up)
@@ -1177,6 +1225,78 @@ 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);
+               }
+       }
+       list_for_each_entry(path, &w->sinks, list_source) {
+               if (path->sink) {
+                       dapm_widget_set_peer_power(path->sink, power,
+                                                  path->connect);
+               }
+       }
+
+       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.:-
@@ -1195,7 +1315,6 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
        LIST_HEAD(down_list);
        LIST_HEAD(async_domain);
        enum snd_soc_bias_level bias;
-       int power;
 
        trace_snd_soc_dapm_start(card);
 
@@ -1208,61 +1327,45 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
                }
        }
 
-       /* Check which widgets we need to power and store them in
-        * lists indicating if they should be powered up or down.
-        */
+       memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
+
        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);
-                       break;
+               w->power_checked = false;
+       }
 
-               default:
-                       if (!w->power_check)
-                               continue;
+       /* Check which widgets we need to power and store them in
+        * 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);
+       }
 
-                       if (!w->force)
-                               power = w->power_check(w);
-                       else
-                               power = 1;
+       list_for_each_entry(w, &card->widgets, list) {
+               list_del_init(&w->dirty);
 
-                       if (power) {
-                               d = w->dapm;
+               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.
-                                */
-                               switch (w->id) {
-                               case snd_soc_dapm_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;
-                               }
+                       /* 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.
+                        */
+                       switch (w->id) {
+                       case snd_soc_dapm_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 (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;
-                       break;
                }
+
        }
 
        /* If there are no DAPM widgets then try to figure out power from the
@@ -1291,14 +1394,18 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
                }
        }
 
-       /* Force all contexts in the card to the same bias state */
+       /* 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->target_bias_level > bias)
                        bias = d->target_bias_level;
        list_for_each_entry(d, &card->dapm_list, list)
-               d->target_bias_level = bias;
+               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)
@@ -1529,14 +1636,21 @@ 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;
 }
@@ -1561,10 +1675,13 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
                /* found, now check type */
                found = 1;
                path->connect = connect;
+               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;
 }
@@ -1708,6 +1825,7 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
        w->connected = status;
        if (status == 0)
                w->force = 0;
+       dapm_mark_dirty(w, "pin configuration");
 
        return 0;
 }
@@ -2047,6 +2165,9 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
                        break;
                }
 
+               if (!w->power_check)
+                       w->power_check = dapm_always_on_check_power;
+
                /* Read the initial power state from the device */
                if (w->reg >= 0) {
                        val = soc_widget_read(w, w->reg);
@@ -2060,6 +2181,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
 
                w->new = 1;
 
+               list_add(&w->dirty, &(w->dapm->card->dapm_dirty));
                dapm_debugfs_add_widget(w);
        }
 
@@ -2541,6 +2663,7 @@ int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
        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 */
@@ -2591,6 +2714,7 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,
                dev_vdbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n",
                        w->name, w->sname, stream, event);
                if (strstr(w->sname, stream)) {
+                       dapm_mark_dirty(w, "stream event");
                        switch(event) {
                        case SND_SOC_DAPM_STREAM_START:
                                w->active = 1;
@@ -2680,6 +2804,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;
 }
@@ -2771,7 +2896,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend);
 
 /**
  * snd_soc_dapm_free - free dapm resources
- * @card: SoC device
+ * @dapm: DAPM context
  *
  * Free all dapm widgets and resources.
  */
This page took 0.029999 seconds and 5 git commands to generate.