rk音频驱动分析之widget的上下电过程
当一个widget的状态改变后,该widget会被加入dapm_dirty链表,然后通过dapm_power_widgets函数来改变整个音频路径上的电源状态
一.dapm触发的情况
1、dapm widgets建立时,详见snd_soc_dapm_new_widgets;
2、上层通过alsa_amixer等工具改变codec音频路径时,此时与此相关的widgets状态要重置,详见dapm_mixer_update_power和dapm_mux_update_power;
- amixer-应用层[alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1]
- |->snd_ctl_ioctl-系统调用
- |->snd_ctl_elem_write_user-内核钩子函数
- |->snd_ctl_elem_wirte-
- |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl
- |->kctl->put()-调用kctl的成员函数put()
- |->snd_soc_dapm_put_volsw
- |->dapm_mixer_update_power
- |->更新path->connect状态
- |->dapm_power_widgets 触发dapm,重置相关的widgets
3、发生stream事件时,会触发snd_soc_dapm_stream_even。什么叫stream事件?准备或关闭一个pcm stream通道(snd_pcm_prepare/snd_pcm_close)这些都属于stream事件。另外suspend或resume时,也会触发snd_soc_dapm_stream_event处理。
- snd_pcm_prepare
- |->soc_pcm_prepare
- |->处理platform、codec-dai、cpu-dai的prepare回调函数
- |->snd_soc_dapm_stream_event
- |->遍历codec每个dapm widget,如果该widget的stream name与传递进来的stream参数相匹配,如果匹配则置widget->active为真
- |->dapm_power_widgets 触发dapm,重置相关的widgets
二.dapm_power_widgets分析
1、初始化两个链表up_list和down_list,如字面意思,up_list指向要power up的widgets,down_list指向要power down的widgets;
2、遍历所有widgets,检查是否需要对其进行power操作;要power up的则插入到up_list,要power down的则插入到down_list;
3、先power down down_list上widgets,再power up up_list上的widgets;
/* Scan each dapm widget for complete audio path. //搜索dapm widget得到完整的路径
* A complete path is a route that has valid endpoints i.e.:- //一个完整的路径,需要有有效的端点
* //主要有下面几种有效的路径
* o DAC to output pin. //DAC 输出
* o Input Pin to ADC. //ADC输入
* o Input pin to Output pin (bypass, sidetone) //输入直连输出
* o DAC to ADC (loopback). //DAC 直接连接ADC,回路 */
static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
struct snd_soc_dapm_widget *w;
struct snd_soc_dapm_context *d;
LIST_HEAD(up_list); //上电链表
LIST_HEAD(down_list); //下电链表
list_for_each_entry(d, &card->dapm_list, list) //遍历所有的snd_soc_dapm_context,电源域
//与SND_SOC_BIAS_OFF区别,SND_SOC_BIAS_STANDBY不能多于10ms.NOTE: The transition time between STANDBY and ON
// should be as fast as possible and no longer than 10ms.
if (d->idle_bias_off) ///* Use BIAS_OFF instead of STANDBY */
d->target_bias_level = SND_SOC_BIAS_OFF;
else
d->target_bias_level = SND_SOC_BIAS_STANDBY;
dapm_reset(card); //单独分析1,遍历所有widgets,把他们的输入输出连接数都置-1,置未检查标志
/* 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. */
//查看哪些widgets 需要改变电源,放在上下电链表。我们只检查有dirty标志位的widgets ,但是迭代的生活新建的widgets 也会在dirty链表
list_for_each_entry(w, &card->dapm_dirty, dirty) //遍历
dapm_power_one_widget(w, &up_list, &down_list); //单独分析2
list_for_each_entry(w, &card->widgets, list)
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.*/
case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_clock_supply: case snd_soc_dapm_micbias:
if (d->target_bias_level < SND_SOC_BIAS_STANDBY) //如果小于SND_SOC_BIAS_STANDBY,都是置为SND_SOC_BIAS_STANDBY
d->target_bias_level = SND_SOC_BIAS_STANDBY;
default:
d->target_bias_level = SND_SOC_BIAS_ON; //其他的类型就是SND_SOC_BIAS_ON;
/* 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) //如果电源域大于off
bias = d->target_bias_level;
list_for_each_entry(d, &card->dapm_list, list)
if (!d->idle_bias_off) //如果/* Use BIAS_OFF instead of STANDBY */,不是这个状态
d->target_bias_level = bias;
/* Run all the bias changes in parallel */
list_for_each_entry(d, &dapm->card->dapm_list, list) //每个电源域,异步执行预先的序列,单独分析6
async_schedule_domain(dapm_pre_sequence_async, d, &async_domain); //主要是设置了codec的bias_level
/* Power down widgets first; try to avoid amplifying pops. */
dapm_seq_run(dapm, &down_list, event, false); //先执行down_list,单独分析7
dapm_widget_update(dapm);
/* Now power up. */
dapm_seq_run(dapm, &up_list, event, true); //上电
/* Run all the bias changes in parallel */
list_for_each_entry(d, &dapm->card->dapm_list, list) //已经分析
async_schedule_domain(dapm_post_sequence_async, d, &async_domain);
/* do we need to notify any clients that DAPM event is complete */
list_for_each_entry(d, &card->dapm_list, list)
if (d->stream_event)
d->stream_event(d, event);
pop_wait(card->pop_time);
1.单独分析1
static void dapm_reset(struct snd_soc_card *card)
struct snd_soc_dapm_widget *w;
memset(&card->dapm_stats, 0, sizeof(card->dapm_stats)); //把card的card->dapm_stats置0
list_for_each_entry(w, &card->widgets, list) //遍历所有widgets,把他们的输入输出连接数都置-1,置未检查标志
w->power_checked = false;
w->inputs = -1;
w->outputs = -1;
2.单独分析2
static void dapm_power_one_widget(struct snd_soc_dapm_widget *w,struct list_head *up_list,struct list_head *down_list)
switch (w->id)
case snd_soc_dapm_pre: //如果是第一个执行的
////属machine specific pre widget,插入到down_list最前方
dapm_seq_insert(w, down_list, false); //单独分析3,加入到所有顺序号比它大的widget的power_list里面,还有加到down_list的末尾
case snd_soc_dapm_post: //如果是最后一个执行的,属machine specific post widget,插入到up_list最后方
dapm_seq_insert(w, up_list, true);
default:
power = dapm_widget_power_check(w); //主要是检测每个widget的状态,用回调函数
if (w->power_checked) //如果已经检测过了,就返回
return w->new_power;
if (w->force) //如果设置了强制的电源状态,返回
w->new_power = 1;
else //如果没有就调用power_check函数,这个在初始化的会根据不同的widget,设置不同的回调函数
w->new_power = w->power_check(w);
w->power_checked = true;
dapm_widget_set_power(w, power, up_list, down_list); //单独分析5,主要是找当前连接的输入输出,然后把有连接的加入到dirty链表
单独分析3. dapm_seq_insert(w, down_list, false);
加入到所有顺序号比它大的widget的power_list里面,还有加到总的list,down_list或者up_list
static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, struct list_head *list, bool power_up)
list_for_each_entry(w, list, power_list) //遍历链表
if (dapm_seq_compare(new_widget, w, power_up) < 0) //单独分析4,把它加入到所有顺序号比它大的widget的power_list里面
list_add_tail(&new_widget->power_list, &w->power_list); //把new_widget->power_list加入到w->power_list的里面的尾部
list_add_tail(&new_widget->power_list, list); //把它加到总的list的末尾
单独分析4;dapm_seq_compare(new_widget, w, power_up)
/* dapm power sequences - make this per codec in the future */
//widget就是按照这个0开始的顺序是上下电的
static int dapm_up_seq[] = { //上电队列
[snd_soc_dapm_pre] = 0,
[snd_soc_dapm_supply] = 1,
[snd_soc_dapm_regulator_supply] = 1,
[snd_soc_dapm_clock_supply] = 1,
[snd_soc_dapm_micbias] = 2,
[snd_soc_dapm_dai_link] = 2,
[snd_soc_dapm_dai_in] = 3,
[snd_soc_dapm_dai_out] = 3,
[snd_soc_dapm_aif_in] = 3,
[snd_soc_dapm_aif_out] = 3,
[snd_soc_dapm_mic] = 4,
[snd_soc_dapm_mux] = 5,
[snd_soc_dapm_virt_mux] = 5,
[snd_soc_dapm_value_mux] = 5,
[snd_soc_dapm_dac] = 6,
[snd_soc_dapm_mixer] = 7,
[snd_soc_dapm_mixer_named_ctl] = 7,
[snd_soc_dapm_pga] = 8,
[snd_soc_dapm_adc] = 9,
[snd_soc_dapm_out_drv] = 10,
[snd_soc_dapm_hp] = 10,
[snd_soc_dapm_spk] = 10,
[snd_soc_dapm_line] = 10,
[snd_soc_dapm_post] = 11,
};
static int dapm_down_seq[] = { //下电队列
[snd_soc_dapm_pre] = 0,
[snd_soc_dapm_adc] = 1,
[snd_soc_dapm_hp] = 2,
[snd_soc_dapm_spk] = 2,
[snd_soc_dapm_line] = 2,
[snd_soc_dapm_out_drv] = 2,
[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_virt_mux] = 9,
[snd_soc_dapm_value_mux] = 9,
[snd_soc_dapm_aif_in] = 10,
[snd_soc_dapm_aif_out] = 10,
[snd_soc_dapm_dai_in] = 10,
[snd_soc_dapm_dai_out] = 10,
[snd_soc_dapm_dai_link] = 11,
[snd_soc_dapm_clock_supply] = 12,
[snd_soc_dapm_regulator_supply] = 12,
[snd_soc_dapm_supply] = 12,
[snd_soc_dapm_post] = 13,
};
//主要是根据上下顺序号
static int dapm_seq_compare(struct snd_soc_dapm_widget *a, struct snd_soc_dapm_widget *b, bool power_up)
if (power_up) //选择上电队列
sort = dapm_up_seq;
else //选择下电队列
sort = dapm_down_seq;
if (sort[a->id] != sort[b->id]) //如果他们在队列里面的值不一样
return sort[a->id] - sort[b->id]; //这里需要插入的widget顺序值小于链表里面的
if (a->subseq != b->subseq) //这个下一级的排序,就是当seq相等的时候
if (power_up) //如果是上电,值越小就排在前面
return a->subseq - b->subseq;
else //如果是下电,值越小排在后面
return b->subseq - a->subseq;
if (a->reg != b->reg) //如果前面都相等,比较reg ,没有直接的dapm/* negative reg = no direct dapm */
return a->reg - b->reg;
if (a->dapm != b->dapm) //不同的/* DAPM context */
return (unsigned long)a->dapm - (unsigned long)b->dapm;
单独分析5:主要是找当前连接的输入输出,然后把有连接的加入到dirty链表
static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power, struct list_head *up_list, struct list_head *down_list)
/* If we changed our power state perhaps our neigbours changed also.*/
list_for_each_entry(path, &w->sources, list_sink) //遍历我们的source链表,找到这些path
if (path->source) //找到source
dapm_widget_set_peer_power(path->source, power, path->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. */
//如果相连接,就加入到dirty链表,没有连接就没有影响,在哪里连接的是这个问题
if (!connect) //connect是在route里面有定义,加入的会显示连接状态
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");
switch (w->id) //如果是下面这三种纯输入就跳过
case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_clock_supply:
/* Supplies can't affect their outputs, only their inputs */
break;
default:
list_for_each_entry(path, &w->sinks, list_source) //z罗列它的输出
if (path->sink) // //如果相连接,就加入到dirty链表,没有连接就没有影响,在哪里连接的是这个问题
dapm_widget_set_peer_power(path->sink, power,path->connect);
if (power) //插入到
dapm_seq_insert(w, up_list, true); //加入到所有顺序号比它大的widget的power_list里面,还有加到up_list的末尾
else
dapm_seq_insert(w, down_list, false); //加入到所有顺序号比它大的widget的power_list里面,还有加到down_list的末尾
w->power = power; //赋值状态
单独分析6:主要是设置了codec的bias_level
static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
/* 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)
// //需要使用设备时,device driver调用pm_runtime_get(或pm_runtime_get_sync)接口,增加引用计数
pm_runtime_get_sync(d->dev);
//set the bias level for the system,设置偏压
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY); //这里主要是设置codec的偏压
if (card && card->set_bias_level) //暂未发现
ret = card->set_bias_level(card, dapm, level);
if (dapm->codec)
if (dapm->codec->driver->set_bias_level) //调用rk音频驱动分析之codec的es8323_set_bias_level
ret = dapm->codec->driver->set_bias_level(dapm->codec, level);
if (card && card->set_bias_level_post) //暂时没有
ret = card->set_bias_level_post(card, dapm, level);
/* Prepare for a STADDBY->ON or ON->STANDBY transition */
if (d->bias_level != d->target_bias_level) //这个和上面一样,不过是SND_SOC_BIAS_PREPARE
ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
单独分析7:
static void dapm_seq_run(struct snd_soc_dapm_context *dapm, struct list_head *list, int event, bool power_up)
if (power_up)
sort = dapm_up_seq;
else
sort = dapm_down_seq;
list_for_each_entry_safe(w, n, list, power_list)
/* Do we need to apply any queued changes? */
if (!list_empty(&pending))
// * Apply the coalesced changes from a DAPM sequence */ 合并执行
dapm_seq_run_coalesced(cur_dapm, &pending); //这个单独分析8
//这里是在codec->dapm.seq_notifier = codec_drv->seq_notifier;,其实调用codec的seq_notifier
if (cur_dapm && cur_dapm->seq_notifier) //这里我们没有
for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
if (sort[i] == cur_sort)
cur_dapm->seq_notifier(cur_dapm, i, cur_subseq);
INIT_LIST_HEAD(&pending);
switch (w->id)
///为什么类型为pre/post的widget只执行event回调函数?看看它们的原型就明白了。
//#define SND_SOC_DAPM_PRE(wname, wevent),显然这些widget只含有stream name和event回调函数。
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) //执行 SND_SOC_DAPM_PRE(wname, wevent)传入的event函数
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);
case snd_soc_dapm_post: //这个与snd_soc_dapm_pre一样,就是顺序不同
default:
/* Queue it up for application */
//遇到非以上类型的widget,则插入到pending链表,进一步调用dapm_seq_run_coalesced处理。
//这里设计很巧妙!下面详细解析这点。
cur_sort = sort[w->id];
cur_subseq = w->subseq;
cur_reg = w->reg;
cur_dapm = w->dapm;
if (!list_empty(&pending))
dapm_seq_run_coalesced(cur_dapm, &pending);
单独分析8:/* Apply the coalesced changes from a DAPM sequence */
//这个函数的注释写得很清楚,它遍历之前已排序好(dapm_seq_insert)的链表,把1)连续的、
//2)同一个widget register的、3)同一个power sequence的widgets送到pending链表上,然后调用dapm_seq_run_coalesced对该链表上的widgets进行设置。这样做的目的是尽可能减少对widget registers的读写。
static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm, struct list_head *pending)
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;
if (w->invert)
power = !w->power;
else
power = w->power;
mask |= cur_mask;
if (power)
value |= cur_mask;
/* Check for events */
dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMU);
dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMD);
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_wait(card->pop_time); //延时
soc_widget_update_bits_locked(w, reg, mask, value);
list_for_each_entry(w, pending, power_list)
dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMU);
dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMD);