Mentioned in the trigger Process Analysis of stream domain:
In the era of Linux-3.4.5, as long as the dapm module finds that codec still opens a complete path internally (do not know what the complete path is, please tutorial "dapm five: dapm mechanism in-depth analysis (on) 4th), the codec driver will not run its suspend/resume process when the system is sleeping/awake.
At that time, because other tasks were not analyzed in detail, such a bug happened again in the past few days. Although the problem was solved by adjusting the audio path, the reasons for the problem were roughly tracked and recorded as follows.
When the system goes to sleep, it calls the snd_soc_suspend () function, and then calls the suspend callback functions of cpu_dai/platform_dai/codec_dai.
The suspend call of codec is a bit special. It checks the status of bias. When codec-> dapm is found. when bias_level is on, the system jumps out and does not run suspend; only codec-> dapm. when the bias_level is standby or off, suspend processing is enabled.
Why? Because codec may still be powered on or even used during system sleep. Imagine this scenario: for voice calls, the modem is directly connected to codec, and the audio data does not pass through the CPU. Therefore, in this case, the CPU can enter sleep, just to keep codec working properly. Therefore, codec-> dapm. bias_level is used to determine whether codec is still working, and does not enter suspend of codec for processing. The measurement of "whether it is still working" is whether there is still a complete path in the codec mentioned above.
/* powers down audio subsystem for suspend */int snd_soc_suspend(struct device *dev){struct snd_soc_card *card = dev_get_drvdata(dev);struct snd_soc_codec *codec;int i;//.../* suspend all CODECs */list_for_each_entry(codec, &card->codec_dev_list, card_list) {/* If there are paths active then the CODEC will be held with * bias _ON and should not be suspended. */if (!codec->suspended && codec->driver->suspend) {switch (codec->dapm.bias_level) {case SND_SOC_BIAS_STANDBY:/* * If the CODEC is capable of idle * bias off then being in STANDBY * means it's doing something, * otherwise fall through. */if (codec->dapm.idle_bias_off) {dev_dbg(codec->dev,"idle_bias_off CODEC on over suspend\n");break;}case SND_SOC_BIAS_OFF:codec->driver->suspend(codec);codec->suspended = 1;codec->cache_sync = 1;break;default:dev_dbg(codec->dev, "CODEC is on over suspend\n");break;}}}//...}
We can see from this: it must be the codec-> dapm. bias_level flag is wrong, which leads to this problem. After some printing, we found that the bias status was on when something went wrong.
So where is the status of bias changed? We find these two functions in the soc-dapm.c:
/* Async callback run prior to DAPM sequences - brings to _PREPARE if * they're changing state. */static void dapm_pre_sequence_async(void *data, async_cookie_t cookie){struct snd_soc_dapm_context *d = data;int ret;/* 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) {if (d->dev)pm_runtime_get_sync(d->dev);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);}/* 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,"Failed to prepare bias: %d\n", ret);}}/* Async callback run prior to DAPM sequences - brings to their final * state. */static void dapm_post_sequence_async(void *data, async_cookie_t cookie){struct snd_soc_dapm_context *d = data;int ret;/* If we just powered the last thing off drop to standby bias */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",ret);}/* If we're in standby and can support bias off then do that */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 (d->dev)pm_runtime_put(d->dev);}/* If we just powered up then move to active bias */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",ret);}}
From the code comment, we should pay more attention to dapm_post_sequence_async, because it determines the final state of bias.
Then we found that both functions were called for dapm_power_widgets (). We have previously stressed that this function is the most core function in dapm. In this chapter, we have to continue to analyze it.
For the moment, let's look back at the dapm_post_sequence_async function to see what conditions are required when the bias status is set to on:
/* If we just powered up then move to active bias */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",ret);}
It requires that the current bias status be prepare and the target bias status be on to set codec-> dapm. bias_level to on.
Next we will analyze in detail the dapm_power_widgets to see under what conditions it meets this condition. The related code is as follows:
static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event){struct snd_soc_card *card = dapm->card;struct snd_soc_dapm_widget *w;struct snd_soc_dapm_context *d;LIST_HEAD(up_list);LIST_HEAD(down_list);LIST_HEAD(async_domain);enum snd_soc_bias_level bias;trace_snd_soc_dapm_start(card);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;elsed->target_bias_level = SND_SOC_BIAS_STANDBY;}}dapm_reset(card);/* 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);}list_for_each_entry(w, &card->widgets, list) {list_del_init(&w->dirty);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. Signal * generators are virtual pins and have no * power impact themselves. */switch (w->id) {case snd_soc_dapm_siggen:break;case snd_soc_dapm_supply:case snd_soc_dapm_regulator_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;}}}
Let's take a look at this section:
/* 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);}
This is explained in detail in section 4th of dapm: in-depth analysis of dapm mechanism (I): it mainly traverses every widget and looks for the complete path. If it is found, insert all widgets in the complete path to the up_list linked list, which requires power-on. Then, insert the remaining widgets to the down_list linked list, which requires power-off.
After this step, the target status of all widgets on codec is determined and saved in the W-> power flag.
See the following code snippet:
list_for_each_entry(w, &card->widgets, list) {switch (w->id) {case snd_soc_dapm_pre:case snd_soc_dapm_post:/* These widgets always need to be powered */break;default:list_del_init(&w->dirty);break;}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. Signal * generators are virtual pins and have no * power impact themselves. */switch (w->id) {case snd_soc_dapm_siggen:break;case snd_soc_dapm_supply:case snd_soc_dapm_regulator_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;}}}
It is learned from this that when it is detected that at least one widget on codec needs power-on, the target_bias_level state is on. The precondition for powering on any widget is that it must be in a complete path.
Based on the above code analysis, the conclusion is drawn: when the system checks that codec still has the complete path, all widgets on the complete path need to be powered on and the bias status is on, marking codec is still working, and the suspend/resume process cannot be disturbed.
If codec suspend/resume is not called in the future, you need to check whether there is a complete path in codec before hibernation.
Over