summaryrefslogtreecommitdiff
path: root/sound/soc/soc-dapm.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/soc-dapm.c')
-rw-r--r--sound/soc/soc-dapm.c275
1 files changed, 213 insertions, 62 deletions
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 32ab7fc4579a..fbfcda062839 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -124,6 +124,51 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
}
+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)
+{
+ int change;
+ unsigned int old, new;
+ int ret;
+
+ 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 +184,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 +226,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 +241,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 +272,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])
@@ -606,6 +638,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;
@@ -656,6 +691,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;
@@ -681,7 +719,7 @@ 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;
@@ -737,6 +775,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;
@@ -885,11 +926,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) {
@@ -942,7 +989,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;
}
@@ -1041,16 +1088,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,
@@ -1067,7 +1115,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",
@@ -1075,14 +1125,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",
@@ -1107,13 +1159,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.
@@ -1135,8 +1193,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;
@@ -1160,24 +1237,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;
@@ -1185,12 +1257,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 */
@@ -1794,6 +1866,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
@@ -1865,7 +2015,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
/* 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;
@@ -2353,6 +2503,7 @@ int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
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);