DAPM之六:dapm机制深入分析(下)

dapm触发时的入口函数是dapm_power_widgets,稍后详细分析这个函数,这里仅说其作用:检查每个dapm widget,如果该widget处在一条complete paths中,则power up这个widget,否则power down。


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;

4、设置codec的偏置(bias)电压。

/*
 * Scan each dapm widget for complete audio path.
 * A complete path is a route that has valid endpoints i.e.:-
 *
 *  o DAC to output pin.
 *  o Input Pin to ADC.
 *  o Input pin to Output pin (bypass, sidetone)
 *  o DAC to ADC (loopback).
 */
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;
	
	//初始化两个链表up_list和down_list,up_list指向要power up的widgets,down_list指向要power down的widgets
	LIST_HEAD(up_list);
	LIST_HEAD(down_list);
	int ret = 0;
	int power;
	int sys_power = 0;

	/* Check which widgets we need to power and store them in
	 * lists indicating if they should be powered up or down.
	 */
	//遍历所有的dapm widgets,检查是否需要对widget开关;'开'则把该widget插入到up_list,'关'则插入到down_list
	list_for_each_entry(w, &codec->dapm_widgets, list) {
		switch (w->id) {
		case snd_soc_dapm_pre:
			//属machine specific pre widget,插入到down_list最前方
			dapm_seq_insert(w, &down_list, dapm_down_seq);
			break;
		case snd_soc_dapm_post:
			//属machine specific post widget,插入到up_list最后方
			dapm_seq_insert(w, &up_list, dapm_up_seq);
			break;

		default:
			//其他类型的widgets,则调用自身的power_check函数进行检查需要开关。
			//关于power_check,具体见《DAPM之五:dapm机制深入分析(上)》第四、第五小节。非常重要的一个函数。
			
			if (!w->power_check)
				continue;

			/* If we're suspending then pull down all the 
			 * power. */
			switch (event) {
			case SND_SOC_DAPM_STREAM_SUSPEND:
				//上面注释很清楚了,如果是suspend事件,则pull down所有widgets。
				power = 0;
				break;

			default:
				power = w->power_check(w);
				if (power)
					sys_power = 1;
				break;
			}

			//w->power保存widget当前的power状态,如果当前状态和设置状态一致,那么显然不用重复设置widget
			if (w->power == power)
				continue;
				
			//将widget插入到up_list或down_list中
			if (power)
				dapm_seq_insert(w, &up_list, dapm_up_seq);
			else
				dapm_seq_insert(w, &down_list, dapm_down_seq);

			//更新w->power的状态
			w->power = power;
			break;
		}
	}

	/* If there are no DAPM widgets then try to figure out power from the
	 * event type.
	 */
	if (list_empty(&codec->dapm_widgets)) {
		switch (event) {
		case SND_SOC_DAPM_STREAM_START:
		case SND_SOC_DAPM_STREAM_RESUME:
			sys_power = 1;
			break;
		case SND_SOC_DAPM_STREAM_SUSPEND:
			sys_power = 0;
			break;
		case SND_SOC_DAPM_STREAM_NOP:
			sys_power = codec->bias_level != SND_SOC_BIAS_STANDBY;
			break;
		default:
			break;
		}
	}

	/* If we're changing to all on or all off then prepare */
	if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) ||
	    (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) {
		ret = snd_soc_dapm_set_bias_level(socdev,
						  SND_SOC_BIAS_PREPARE);
		if (ret != 0)
			pr_err("Failed to prepare bias: %d\n", ret);
	}
	
	//先power down链表down_list上的widgets,接着power up链表up_list上的widgets
	//按照这样的次序,目的是避免产生pop音
	//dapm_seq_run核心函数,见其详细分析

	/* Power down widgets first; try to avoid amplifying pops. */
	dapm_seq_run(codec, &down_list, event, dapm_down_seq);

	/* Now power up. */
	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) {
		ret = snd_soc_dapm_set_bias_level(socdev,
						  SND_SOC_BIAS_STANDBY);
		if (ret != 0)
			pr_err("Failed to apply standby bias: %d\n", ret);
	}

	/* If we just powered up then move to active bias */
	if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) {
		ret = snd_soc_dapm_set_bias_level(socdev,
						  SND_SOC_BIAS_ON);
		if (ret != 0)
			pr_err("Failed to apply active bias: %d\n", ret);
	}

	pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n",
		codec->pop_time);

	return 0;
}

dapm_seq_run

/* 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;
	//创建pending链表
	LIST_HEAD(pending);
	int cur_sort = -1;
	int cur_reg = SND_SOC_NOPM;
	int ret;

	//遍历list(即up_list或down_list),根据成员power_list找到挂到list上的每一个widget
	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) {
		//为什么类型为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)
				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 */
			//这里就比较奇怪了,input/output/hp/mic/line/spk这些widgets也只有stream name和event,
			//但是仍然调用dapm_generic_apply_power试图控制widget的开关?根据这里的注释,应该是预留的。
			ret = dapm_generic_apply_power(w);
			break;

	default:
		/* Queue it up for application */
		//遇到非以上类型的widget,则插入到pending链表,进一步调用dapm_seq_run_coalesced处理。
		//这里设计很巧妙!下面详细解析这点。
		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);
}
从dapm_seq_run的分析,我们可以看出,mixer/mux类型的widgets处理是不同的。
/* 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;
}
/* Queue it up for application */
cur_sort = sort[w->id];
cur_reg = w->reg;
list_move(&w->power_list, &pending);

结合这两段代码理解:遇到操作对象是同一个reg的widgets,则把他们放入pending链表中,随后调用dapm_seq_run_coalesced进行处理。这样做的意义何在?见dapm_seq_run注释:In order to minimise the number of writes to the device required multiple widgets will be updated in a single write where possible.保证了同reg但不同widgets的一次性读写。这设计是相当巧妙高效的。

dapm_seq_run_coalesced就不累述了,比较简单,对同reg但不同widgets进行一次读写。


总结:dapm机制分析到此结束了,这篇主要简单说了下其触发过程,同时分析两个主体函数。其实原理是简单的,复杂的地方都在于前期处理,这些在上篇已详细分析了。相信了解:path的建立过程、根据path->list_source找到作为sink的widget、根据path->list_sink找到作为source的widget、endpoint的概念、complete path的概念、depop通电/断电次序,理解整个dpam机制就毫无压力了。

你可能感兴趣的:(DAPM之六:dapm机制深入分析(下))