|
@@ -52,19 +52,37 @@
|
|
|
|
|
|
/* dapm power sequences - make this per codec in the future */
|
|
|
static int dapm_up_seq[] = {
|
|
|
- snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias,
|
|
|
- snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux,
|
|
|
- snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl,
|
|
|
- snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,
|
|
|
- snd_soc_dapm_post
|
|
|
+ [snd_soc_dapm_pre] = 0,
|
|
|
+ [snd_soc_dapm_supply] = 1,
|
|
|
+ [snd_soc_dapm_micbias] = 2,
|
|
|
+ [snd_soc_dapm_mic] = 3,
|
|
|
+ [snd_soc_dapm_mux] = 4,
|
|
|
+ [snd_soc_dapm_value_mux] = 4,
|
|
|
+ [snd_soc_dapm_dac] = 5,
|
|
|
+ [snd_soc_dapm_mixer] = 6,
|
|
|
+ [snd_soc_dapm_mixer_named_ctl] = 6,
|
|
|
+ [snd_soc_dapm_pga] = 7,
|
|
|
+ [snd_soc_dapm_adc] = 8,
|
|
|
+ [snd_soc_dapm_hp] = 9,
|
|
|
+ [snd_soc_dapm_spk] = 10,
|
|
|
+ [snd_soc_dapm_post] = 11,
|
|
|
};
|
|
|
|
|
|
static int dapm_down_seq[] = {
|
|
|
- snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,
|
|
|
- snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer,
|
|
|
- snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias,
|
|
|
- snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply,
|
|
|
- snd_soc_dapm_post
|
|
|
+ [snd_soc_dapm_pre] = 0,
|
|
|
+ [snd_soc_dapm_adc] = 1,
|
|
|
+ [snd_soc_dapm_hp] = 2,
|
|
|
+ [snd_soc_dapm_spk] = 3,
|
|
|
+ [snd_soc_dapm_pga] = 4,
|
|
|
+ [snd_soc_dapm_mixer_named_ctl] = 5,
|
|
|
+ [snd_soc_dapm_mixer] = 5,
|
|
|
+ [snd_soc_dapm_dac] = 6,
|
|
|
+ [snd_soc_dapm_mic] = 7,
|
|
|
+ [snd_soc_dapm_micbias] = 8,
|
|
|
+ [snd_soc_dapm_mux] = 9,
|
|
|
+ [snd_soc_dapm_value_mux] = 9,
|
|
|
+ [snd_soc_dapm_supply] = 10,
|
|
|
+ [snd_soc_dapm_post] = 11,
|
|
|
};
|
|
|
|
|
|
static void pop_wait(u32 pop_time)
|
|
@@ -689,53 +707,211 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
|
|
|
return power;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Scan a single DAPM widget for a complete audio path and update the
|
|
|
- * power status appropriately.
|
|
|
- */
|
|
|
-static int dapm_power_widget(struct snd_soc_codec *codec, int event,
|
|
|
- struct snd_soc_dapm_widget *w)
|
|
|
+static int dapm_seq_compare(struct snd_soc_dapm_widget *a,
|
|
|
+ struct snd_soc_dapm_widget *b,
|
|
|
+ int sort[])
|
|
|
{
|
|
|
- int ret;
|
|
|
+ if (sort[a->id] != sort[b->id])
|
|
|
+ return sort[a->id] - sort[b->id];
|
|
|
+ if (a->reg != b->reg)
|
|
|
+ return a->reg - b->reg;
|
|
|
|
|
|
- switch (w->id) {
|
|
|
- case snd_soc_dapm_pre:
|
|
|
- if (!w->event)
|
|
|
- return 0;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- if (event == SND_SOC_DAPM_STREAM_START) {
|
|
|
- ret = w->event(w,
|
|
|
- NULL, SND_SOC_DAPM_PRE_PMU);
|
|
|
+/* Insert a widget in order into a DAPM power sequence. */
|
|
|
+static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
|
|
|
+ struct list_head *list,
|
|
|
+ int sort[])
|
|
|
+{
|
|
|
+ struct snd_soc_dapm_widget *w;
|
|
|
+
|
|
|
+ list_for_each_entry(w, list, power_list)
|
|
|
+ if (dapm_seq_compare(new_widget, w, sort) < 0) {
|
|
|
+ list_add_tail(&new_widget->power_list, &w->power_list);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ list_add_tail(&new_widget->power_list, list);
|
|
|
+}
|
|
|
+
|
|
|
+/* Apply the coalesced changes from a DAPM sequence */
|
|
|
+static void dapm_seq_run_coalesced(struct snd_soc_codec *codec,
|
|
|
+ struct list_head *pending)
|
|
|
+{
|
|
|
+ struct snd_soc_dapm_widget *w;
|
|
|
+ int reg, power, ret;
|
|
|
+ unsigned int value = 0;
|
|
|
+ unsigned int mask = 0;
|
|
|
+ unsigned int cur_mask;
|
|
|
+
|
|
|
+ reg = list_first_entry(pending, struct snd_soc_dapm_widget,
|
|
|
+ power_list)->reg;
|
|
|
+
|
|
|
+ list_for_each_entry(w, pending, power_list) {
|
|
|
+ cur_mask = 1 << w->shift;
|
|
|
+ BUG_ON(reg != w->reg);
|
|
|
+
|
|
|
+ if (w->invert)
|
|
|
+ power = !w->power;
|
|
|
+ else
|
|
|
+ power = w->power;
|
|
|
+
|
|
|
+ mask |= cur_mask;
|
|
|
+ if (power)
|
|
|
+ value |= cur_mask;
|
|
|
+
|
|
|
+ pop_dbg(codec->pop_time,
|
|
|
+ "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n",
|
|
|
+ w->name, reg, value, mask);
|
|
|
+
|
|
|
+ /* power up pre event */
|
|
|
+ if (w->power && w->event &&
|
|
|
+ (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
|
|
|
+ pop_dbg(codec->pop_time, "pop test : %s PRE_PMU\n",
|
|
|
+ w->name);
|
|
|
+ ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
|
|
|
if (ret < 0)
|
|
|
- return ret;
|
|
|
- } else if (event == SND_SOC_DAPM_STREAM_STOP) {
|
|
|
- ret = w->event(w,
|
|
|
- NULL, SND_SOC_DAPM_PRE_PMD);
|
|
|
+ pr_err("%s: pre event failed: %d\n",
|
|
|
+ w->name, ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* power down pre event */
|
|
|
+ if (!w->power && w->event &&
|
|
|
+ (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
|
|
|
+ pop_dbg(codec->pop_time, "pop test : %s PRE_PMD\n",
|
|
|
+ w->name);
|
|
|
+ ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
|
|
|
if (ret < 0)
|
|
|
- return ret;
|
|
|
+ pr_err("%s: pre event failed: %d\n",
|
|
|
+ w->name, ret);
|
|
|
}
|
|
|
- return 0;
|
|
|
|
|
|
- case snd_soc_dapm_post:
|
|
|
- if (!w->event)
|
|
|
- return 0;
|
|
|
+ /* Lower PGA volume to reduce pops */
|
|
|
+ if (w->id == snd_soc_dapm_pga && !w->power)
|
|
|
+ dapm_set_pga(w, w->power);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reg >= 0) {
|
|
|
+ pop_dbg(codec->pop_time,
|
|
|
+ "pop test : Applying 0x%x/0x%x to %x in %dms\n",
|
|
|
+ value, mask, reg, codec->pop_time);
|
|
|
+ pop_wait(codec->pop_time);
|
|
|
+ snd_soc_update_bits(codec, reg, mask, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ list_for_each_entry(w, pending, power_list) {
|
|
|
+ /* Raise PGA volume to reduce pops */
|
|
|
+ if (w->id == snd_soc_dapm_pga && w->power)
|
|
|
+ dapm_set_pga(w, w->power);
|
|
|
|
|
|
- if (event == SND_SOC_DAPM_STREAM_START) {
|
|
|
+ /* power up post event */
|
|
|
+ if (w->power && w->event &&
|
|
|
+ (w->event_flags & SND_SOC_DAPM_POST_PMU)) {
|
|
|
+ pop_dbg(codec->pop_time, "pop test : %s POST_PMU\n",
|
|
|
+ w->name);
|
|
|
ret = w->event(w,
|
|
|
NULL, SND_SOC_DAPM_POST_PMU);
|
|
|
if (ret < 0)
|
|
|
- return ret;
|
|
|
- } else if (event == SND_SOC_DAPM_STREAM_STOP) {
|
|
|
- ret = w->event(w,
|
|
|
- NULL, SND_SOC_DAPM_POST_PMD);
|
|
|
+ pr_err("%s: post event failed: %d\n",
|
|
|
+ w->name, ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* power down post event */
|
|
|
+ if (!w->power && w->event &&
|
|
|
+ (w->event_flags & SND_SOC_DAPM_POST_PMD)) {
|
|
|
+ pop_dbg(codec->pop_time, "pop test : %s POST_PMD\n",
|
|
|
+ w->name);
|
|
|
+ ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
|
|
|
if (ret < 0)
|
|
|
- return ret;
|
|
|
+ pr_err("%s: post event failed: %d\n",
|
|
|
+ w->name, ret);
|
|
|
}
|
|
|
- return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- default:
|
|
|
- return dapm_generic_apply_power(w);
|
|
|
+/* Apply a DAPM power sequence.
|
|
|
+ *
|
|
|
+ * We walk over a pre-sorted list of widgets to apply power to. In
|
|
|
+ * order to minimise the number of writes to the device required
|
|
|
+ * multiple widgets will be updated in a single write where possible.
|
|
|
+ * Currently anything that requires more than a single write is not
|
|
|
+ * handled.
|
|
|
+ */
|
|
|
+static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list,
|
|
|
+ int event, int sort[])
|
|
|
+{
|
|
|
+ struct snd_soc_dapm_widget *w, *n;
|
|
|
+ LIST_HEAD(pending);
|
|
|
+ int cur_sort = -1;
|
|
|
+ int cur_reg = SND_SOC_NOPM;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ list_for_each_entry_safe(w, n, list, power_list) {
|
|
|
+ ret = 0;
|
|
|
+
|
|
|
+ /* Do we need to apply any queued changes? */
|
|
|
+ if (sort[w->id] != cur_sort || w->reg != cur_reg) {
|
|
|
+ if (!list_empty(&pending))
|
|
|
+ dapm_seq_run_coalesced(codec, &pending);
|
|
|
+
|
|
|
+ INIT_LIST_HEAD(&pending);
|
|
|
+ cur_sort = -1;
|
|
|
+ cur_reg = SND_SOC_NOPM;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (w->id) {
|
|
|
+ case snd_soc_dapm_pre:
|
|
|
+ if (!w->event)
|
|
|
+ list_for_each_entry_safe_continue(w, n, list,
|
|
|
+ power_list);
|
|
|
+
|
|
|
+ if (event == SND_SOC_DAPM_STREAM_START)
|
|
|
+ ret = w->event(w,
|
|
|
+ NULL, SND_SOC_DAPM_PRE_PMU);
|
|
|
+ else if (event == SND_SOC_DAPM_STREAM_STOP)
|
|
|
+ ret = w->event(w,
|
|
|
+ NULL, SND_SOC_DAPM_PRE_PMD);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case snd_soc_dapm_post:
|
|
|
+ if (!w->event)
|
|
|
+ list_for_each_entry_safe_continue(w, n, list,
|
|
|
+ power_list);
|
|
|
+
|
|
|
+ if (event == SND_SOC_DAPM_STREAM_START)
|
|
|
+ ret = w->event(w,
|
|
|
+ NULL, SND_SOC_DAPM_POST_PMU);
|
|
|
+ else if (event == SND_SOC_DAPM_STREAM_STOP)
|
|
|
+ ret = w->event(w,
|
|
|
+ NULL, SND_SOC_DAPM_POST_PMD);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case snd_soc_dapm_input:
|
|
|
+ case snd_soc_dapm_output:
|
|
|
+ case snd_soc_dapm_hp:
|
|
|
+ case snd_soc_dapm_mic:
|
|
|
+ case snd_soc_dapm_line:
|
|
|
+ case snd_soc_dapm_spk:
|
|
|
+ /* No register support currently */
|
|
|
+ ret = dapm_generic_apply_power(w);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ /* Queue it up for application */
|
|
|
+ cur_sort = sort[w->id];
|
|
|
+ cur_reg = w->reg;
|
|
|
+ list_move(&w->power_list, &pending);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret < 0)
|
|
|
+ pr_err("Failed to apply widget power: %d\n",
|
|
|
+ ret);
|
|
|
}
|
|
|
+
|
|
|
+ if (!list_empty(&pending))
|
|
|
+ dapm_seq_run_coalesced(codec, &pending);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -751,23 +927,22 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event)
|
|
|
{
|
|
|
struct snd_soc_device *socdev = codec->socdev;
|
|
|
struct snd_soc_dapm_widget *w;
|
|
|
+ LIST_HEAD(up_list);
|
|
|
+ LIST_HEAD(down_list);
|
|
|
int ret = 0;
|
|
|
- int i, power;
|
|
|
+ int power;
|
|
|
int sys_power = 0;
|
|
|
|
|
|
- INIT_LIST_HEAD(&codec->up_list);
|
|
|
- INIT_LIST_HEAD(&codec->down_list);
|
|
|
-
|
|
|
/* Check which widgets we need to power and store them in
|
|
|
* lists indicating if they should be powered up or down.
|
|
|
*/
|
|
|
list_for_each_entry(w, &codec->dapm_widgets, list) {
|
|
|
switch (w->id) {
|
|
|
case snd_soc_dapm_pre:
|
|
|
- list_add_tail(&codec->down_list, &w->power_list);
|
|
|
+ dapm_seq_insert(w, &down_list, dapm_down_seq);
|
|
|
break;
|
|
|
case snd_soc_dapm_post:
|
|
|
- list_add_tail(&codec->up_list, &w->power_list);
|
|
|
+ dapm_seq_insert(w, &up_list, dapm_up_seq);
|
|
|
break;
|
|
|
|
|
|
default:
|
|
@@ -782,10 +957,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event)
|
|
|
continue;
|
|
|
|
|
|
if (power)
|
|
|
- list_add_tail(&w->power_list, &codec->up_list);
|
|
|
+ dapm_seq_insert(w, &up_list, dapm_up_seq);
|
|
|
else
|
|
|
- list_add_tail(&w->power_list,
|
|
|
- &codec->down_list);
|
|
|
+ dapm_seq_insert(w, &down_list, dapm_down_seq);
|
|
|
|
|
|
w->power = power;
|
|
|
break;
|
|
@@ -802,32 +976,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event)
|
|
|
}
|
|
|
|
|
|
/* Power down widgets first; try to avoid amplifying pops. */
|
|
|
- for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) {
|
|
|
- list_for_each_entry(w, &codec->down_list, power_list) {
|
|
|
- /* is widget in stream order */
|
|
|
- if (w->id != dapm_down_seq[i])
|
|
|
- continue;
|
|
|
-
|
|
|
- ret = dapm_power_widget(codec, event, w);
|
|
|
- if (ret != 0)
|
|
|
- pr_err("Failed to power down %s: %d\n",
|
|
|
- w->name, ret);
|
|
|
- }
|
|
|
- }
|
|
|
+ dapm_seq_run(codec, &down_list, event, dapm_down_seq);
|
|
|
|
|
|
/* Now power up. */
|
|
|
- for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) {
|
|
|
- list_for_each_entry(w, &codec->up_list, power_list) {
|
|
|
- /* is widget in stream order */
|
|
|
- if (w->id != dapm_up_seq[i])
|
|
|
- continue;
|
|
|
-
|
|
|
- ret = dapm_power_widget(codec, event, w);
|
|
|
- if (ret != 0)
|
|
|
- pr_err("Failed to power up %s: %d\n",
|
|
|
- w->name, ret);
|
|
|
- }
|
|
|
- }
|
|
|
+ dapm_seq_run(codec, &up_list, event, dapm_up_seq);
|
|
|
|
|
|
/* If we just powered the last thing off drop to standby bias */
|
|
|
if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) {
|