该小节我们讲解DAPM的情景分析的构造过程,我们先回顾一下widget上电的过程,如下是一条上电路线:
从LINPUT1经过两个Mixer到达ADC,从图上可以看出,控制接口共六个部分,只要其中有一个部分没有打开(connect),则该线路的所有开关都不会打开。如果其上所有接口都开看,并且有应用程序使用这个声卡,那么图中的四个widget全部都会打开。这样就是comlete path,即满足3个条件:
1.线路所有涉及的path->connect全部为连接(等于1)
2.有产生数据和处理数据两个端点(图示的LINPUT1为产生数据,ADC为处理数据)
3.有应用程序使用声卡
其上的LINPUT1我们称为input ed,ADC称为output ed,如下面的下路:
即使把开关合上也不是一条comlete path,因为为mixer不是output ed端点。
那么其是如何找出所有的comlete path的呢?其实不需要找,如下面一个widger:
其左右两边分别调用is_connected_input_ep与is_connected_output_ep两个函数,判断两边是否分别连接了input_ep与output_ep,如果两边都连接,则这个widger位于comlete path上面,即可以上电(有应用程序使用声卡)。
is_connected_input_ep与is_connected_output_ep函数是在power_check函数中被调用的,下面是widger设置power_check函数的流程,之前的小节已经讲解过:
static int snd_soc_instantiate_card(struct snd_soc_card *card)
static int soc_probe_link_components(struct snd_soc_card *card, int num,int order)
static int soc_probe_component(struct snd_soc_card *card,struct snd_soc_component *component)
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
snd_soc_dapm_new_control_unlocked(dapm, &template);
w->power_check = dapm_generic_check_power;
我们先来看看snd_soc_dapm_new_control_unlocked函数:
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,const struct snd_soc_dapm_widget *widget)
switch (w->id) {
case snd_soc_dapm_mic:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_input:
if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_spk:
case snd_soc_dapm_hp:
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_output:
if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_vmid:
case snd_soc_dapm_siggen:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_always_on_check_power;
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_demux:
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
case snd_soc_dapm_adc:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_dac:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
case snd_soc_dapm_micbias:
case snd_soc_dapm_line:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_dai_out:
case snd_soc_dapm_dai_in:
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_clock_supply:
case snd_soc_dapm_kcontrol:
w->is_supply = 1;
w->power_check = dapm_supply_check_power;
break;
default:
w->power_check = dapm_always_on_check_power;
break;
}
可以知道其会根据w->id的不同,设置不同的w->power_check函数,那么他到底是怎么判断widger两边是否连接input_ep与output_ep的呢?下面我们分析一个例子,即dapm_generic_check_power函数:
/* Generic check to see if a widget should be powered */
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
{
/*其为一个递归的过程
首先判断自己是否为input_ep端点,如果不是通过path找到下一个widger,
继续判断,知道找到末尾,找到则返回1,没有找到返回0*/
in = is_connected_input_ep(w, NULL);
/*同上*/
out = is_connected_output_ep(w, NULL);
return out != 0 && in != 0;
}
其原理以在上面进行注释,不再此处重复,对于任意widger,先通过path,找到path中的src widger与 sink widger。利用这种递归的方法。
那么w->power_check函数时在哪里被调用的呢?我们需要回答以下问题。
1.每个widger->power_check由谁启动
a. APP使用声卡之前如:aplay/crecord命令之前,无论播放还是录制声音,在这之前我们都需要设置好一条comlete path。
b. APP使用声卡过程中,axmier可以打开或者关闭某条path,如在录音的时候,是可以更换线路的。其回去设置某个kcontrl,导致重新判断每一个widger
2.path->connect合适被设置
a. 注册route,route转换为path,根据对应kcontrol_new的寄存器值设置(该为初始值)
b. 后期使用mixer设置:设置kcontrol对应的寄存器,与所在的path的connect。
c. 会遍历所有的widger,决定是否上电。
上面是我们的提问与总结,下面我们通过源代码去进行验证。
mixer调用过程:
以SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),为例:
snd_soc_dapm_put_volsw
connect = xxx // 根据传入的val确定
// 把kcontrol要设置的reg,val写入update
update.kcontrol = kcontrol;
update.widget = widget;
update.reg = reg;
update.mask = mask;
update.val = val;
widget->dapm->update = &update;
soc_dapm_mixer_update_power(widget, kcontrol, connect);
// 找到path并设置connect
path->connect = connect;
// 调用此函数逐个widget进行判断、上电/关闭
dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
widget->dapm->update = NULL;
aplay调用过程:
播放/录音前都会调用 soc_pcm_prepare
soc_pcm_prepare
// stream's name = Playback or Capture
snd_soc_dapm_stream_event(rtd, stream's name, SND_SOC_DAPM_STREAM_START)
soc_dapm_stream_event
// 找出每一个widget
// 如果strstr(w->sname, stream) // w->sname中含有Playback or Capture
// w->active = 1
dapm_power_widgets
大家都会调用dapm_power_widgets
dapm_power_widgets
// 对于每一个widget
// power = w->power_check(w); // 确定是否要上电
// 放入不同的链表, 以后统一上是或关闭
if (power)
dapm_seq_insert(w, &up_list, true);
else
dapm_seq_insert(w, &down_list, false);
// 关闭down_list上的所有widget
dapm_seq_run(dapm, &down_list, event, false);
// 根据dapm->update设置kcontrol, // update来自tinymix的调用
dapm_widget_update(dapm);
// 打开up_list上的所有widget
dapm_seq_run(dapm, &up_list, event, true);
// 给链表中的widget上电或关闭
dapm_seq_run
dapm_seq_run_coalesced
snd_soc_update_bits