ASoC: Add weak routes for sidetone style paths
[firefly-linux-kernel-4.4.55.git] / sound / soc / soc-dapm.c
index 776e6f418306fe004936692fd70dab7c89cdec6d..746349faf2db4e3f0d749f75b2b87a677883adfa 100644 (file)
@@ -139,39 +139,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;
@@ -209,7 +196,7 @@ 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);
                item = (val >> e->shift_l) & (bitmask - 1);
 
@@ -350,9 +337,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;
@@ -450,9 +437,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;
@@ -535,8 +522,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,
@@ -607,6 +593,9 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
        }
 
        list_for_each_entry(path, &widget->sinks, list_source) {
+               if (path->weak)
+                       continue;
+
                if (path->walked)
                        continue;
 
@@ -657,6 +646,9 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
        }
 
        list_for_each_entry(path, &widget->sources, list_sink) {
+               if (path->weak)
+                       continue;
+
                if (path->walked)
                        continue;
 
@@ -738,6 +730,9 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
 
        /* Check if one of our outputs is connected */
        list_for_each_entry(path, &w->sinks, list_source) {
+               if (path->weak)
+                       continue;
+
                if (path->connected &&
                    !path->connected(path->source, path->sink))
                        continue;
@@ -1042,16 +1037,17 @@ 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) {
                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,
@@ -1068,7 +1064,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",
@@ -1076,14 +1074,16 @@ 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 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",
@@ -1108,13 +1108,19 @@ 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);
+       enum snd_soc_bias_level bias;
        int power;
 
        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;
+               }
+       }
 
        /* Check which widgets we need to power and store them in
         * lists indicating if they should be powered up or down.
@@ -1136,8 +1142,27 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
                                power = w->power_check(w);
                        else
                                power = 1;
-                       if (power)
-                               w->dapm->dev_power = 1;
+
+                       if (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;
+                               }
+                       }
 
                        if (w->power == power)
                                continue;
@@ -1161,24 +1186,19 @@ 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->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;
@@ -1186,12 +1206,12 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
        }
 
        /* Force all contexts in the card to the same bias state */
-       power = 0;
+       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;
+               d->target_bias_level = bias;
 
 
        /* Run all the bias changes in parallel */
@@ -1795,6 +1815,84 @@ int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
 }
 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;
+
+       for (i = 0; i < num; i++) {
+               err = snd_soc_dapm_weak_route(dapm, route);
+               if (err)
+                       ret = err;
+               route++;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_weak_routes);
+
 /**
  * snd_soc_dapm_new_widgets - add new dapm widgets
  * @dapm: DAPM context
@@ -1826,13 +1924,13 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
                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);
+                       dapm_new_mux(w);
                        break;
                case snd_soc_dapm_adc:
                case snd_soc_dapm_aif_out:
@@ -1845,7 +1943,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
                case snd_soc_dapm_pga:
                case snd_soc_dapm_out_drv:
                        w->power_check = dapm_generic_check_power;
-                       dapm_new_pga(dapm, w);
+                       dapm_new_pga(w);
                        break;
                case snd_soc_dapm_input:
                case snd_soc_dapm_output: