DAPM之八:stream domain触发过程分析

这两天在查一个bug,结果bug没有完美解决,关于stream domain和stream event的触发过程倒是跟了个遍。记于此,也好慰告《DAPM之四:dapm widget events》大坑的在天之灵。

另外,以前的DAPM系列均基于Linux-2.6.32来分析的,目前我们使用Linux-3.4.5,dapm改动很大了。列举一点:

Linux-2.6.32时代,无论codec处在什么状态,系统休眠/唤醒时,codec驱动都可以进入其suspend/resume流程;

Linux-3.4.5时代,只要dapm模块发现codec内部还打开一条complete path(不知道complete path是什么东东的,请补习《DAPM之五:dapm机制深入分析(上)》第4节),那么系统休眠/唤醒时,codec驱动不会跑其suspend/resume流程。

这告诉我们:你丫不按标准来设计音频路径,乱开乱关的,就不让进suspen/resume的大门了!(呃,,,和某猥琐男在外乱搞结果被老婆撞见的情形好像)。大致就这样,具体原因还没有深究,有空再看。

注:这里所有的分析主要由Rambo童鞋进行,感谢!如下分析基于Linux-3.0.8,但基本流程都一样。


1.stream domain


我们先看看内核文档dapm.txt的描述:

4. Stream domain - DACs and ADCs.
      Enabled and disabled when stream playback/capture is started and
      stopped respectively. e.g. aplay, arecord.

2.1 Stream Domain Widgets
-------------------------

Stream Widgets relate to the stream power domain and only consist of ADCs
(analog to digital converters) and DACs (digital to analog converters).

Stream widgets have the following format:-

SND_SOC_DAPM_DAC(name, stream name, reg, shift, invert),

NOTE: the stream name must match the corresponding stream name in your codec
snd_soc_codec_dai.

e.g. stream widgets for HiFi playback and capture

SND_SOC_DAPM_DAC("HiFi DAC", "HiFi Playback", REG, 3, 1),
SND_SOC_DAPM_ADC("HiFi ADC", "HiFi Capture", REG, 2, 1),

从中我们可以读到的信息有:

1、stream domain的触发依据有回放子流或录音子流的开始/停止;

2、stream domain widgets的编写,注意一点:widget的sname必须和snd_soc_codec_dai结构体定义的stream name一致。如:

SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),

static struct snd_soc_dai_driver wm8994_dai[] = {
	{
		.name = "wm8994-aif1",
		.id = 1,
		.playback = {
			.stream_name = "AIF1 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
		},
		.capture = {
			.stream_name = "AIF1 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
		 },
		.ops = &wm8994_aif1_dai_ops,
	},

那么当tinyplay启动一个回放子流的播放时,那么名字为AIF1DACDAT的widget就会响应,需要设置的寄存器就会被设置。其实不止那么简单,在dapm机制深入分析中,我已经说了:发生stream事件时,会触发snd_soc_dapm_stream_even()处理,然后会遍历试图找到一条complete path,如果找到,则这条complete path上面的所有widgets都会响应,widgets定义的寄存器会被设置,定义的event会被执行。


接着会实例分析下这个过程,同时不可避免的会有一些代码分析。其实我很讨厌代码分析,看别人博客如果有大量代码分析,我一般不会往下看了。但我阐述水平还没达到那种信手拈来飞花摘叶的境界,所以先说声见谅了。


2.stream domain触发流程


这次其实缘由于一个dapm widget event的跟踪,这个widget的代码如下:

static int late_enable_ev(struct snd_soc_dapm_widget *w,
			  struct snd_kcontrol *kcontrol, int event)
{
	struct snd_soc_codec *codec = w->codec;
	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
dump_stack(); //打印调用stack,跟踪流程的利器
	switch (event) {
	case SND_SOC_DAPM_PRE_PMU:
		if (wm8994->aif1clk_enable) {
			snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
					    WM8994_AIF1CLK_ENA_MASK,
					    WM8994_AIF1CLK_ENA);
			wm8994->aif1clk_enable = 0;
		}
		if (wm8994->aif2clk_enable) {
			snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
					    WM8994_AIF2CLK_ENA_MASK,
					    WM8994_AIF2CLK_ENA);
			wm8994->aif2clk_enable = 0;
		}
		break;
	}

	/* We may also have postponed startup of DSP, handle that. */
	wm8958_aif_ev(w, kcontrol, event);

	return 0;
}

static int late_disable_ev(struct snd_soc_dapm_widget *w,
			   struct snd_kcontrol *kcontrol, int event)
{
	struct snd_soc_codec *codec = w->codec;
	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
dump_stack(); //打印调用stack,跟踪流程的利器
	switch (event) {
	case SND_SOC_DAPM_POST_PMD:
		printk("-->late_disable_ev SND_SOC_DAPM_POST_PMD\n");
		if (wm8994->aif1clk_disable) {
			snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
					    WM8994_AIF1CLK_ENA_MASK, 0);
			wm8994->aif1clk_disable = 0;
		}
		if (wm8994->aif2clk_disable) {
			snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
					    WM8994_AIF2CLK_ENA_MASK, 0);
			wm8994->aif2clk_disable = 0;
		}
		break;
	}

	return 0;
}

static const struct snd_soc_dapm_widget wm8994_lateclk_revd_widgets[] = {
SND_SOC_DAPM_SUPPLY("AIF1CLK", SND_SOC_NOPM, 0, 0, aif1clk_ev,
	SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("AIF2CLK", SND_SOC_NOPM, 0, 0, aif2clk_ev,
	SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),

SND_SOC_DAPM_PGA_E("Late DAC1L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
	late_enable_ev, SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_PGA_E("Late DAC1R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
	late_enable_ev, SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
	late_enable_ev, SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
	late_enable_ev, SND_SOC_DAPM_PRE_PMU),

SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)
};

代码有点长,不要恐惧,我当时只是想了解late_enable_ev和late_disable_ev这两个event是如何被调用的。进入系统后,我播放了一个按键音,其结果如下:

[  301.866067] Backtrace: 
[  301.868445] [<c0040d94>] (dump_backtrace+0x0/0x110) from [<c0640ce0>] (dump_stack+0x18/0x1c)
[  301.876867]  r6:f2eac900 r5:f2f92200 r4:00000001 r3:f2f906a0
[  301.882473] [<c0640cc8>] (dump_stack+0x0/0x1c) from [<c04bfcd0>] (late_enable_ev+0x2c/0xd4)
[  301.890809] [<c04bfca4>] (late_enable_ev+0x0/0xd4) from [<c04b5980>] (dapm_seq_check_event.clone.14+0xcc/0x11c)
[  301.900849]  r8:c08423e8 r7:ffffffff r6:00000001 r5:c07c183c r4:f2f92200
[  301.907335] r3:f2f906a0
[  301.909943] [<c04b58b4>] (dapm_seq_check_event.clone.14+0x0/0x11c) from [<c04b5a94>] (dapm_seq_run_coalesced+0xc4/0x198)
[  301.920788]  r6:00000001 r5:f2eac994 r4:f2f92200
[  301.925369] [<c04b59d0>] (dapm_seq_run_coalesced+0x0/0x198) from [<c04b5bfc>] (dapm_seq_run.clone.15+0x94/0x478)
[  301.935527] [<c04b5b68>] (dapm_seq_run.clone.15+0x0/0x478) from [<c04b62c8>] (dapm_power_widgets+0x2e8/0x408)
[  301.945405] [<c04b5fe0>] (dapm_power_widgets+0x0/0x408) from [<c04b72e0>] (snd_soc_dapm_stream_event+0x80/0xf4)
[  301.955459] [<c04b7260>] (snd_soc_dapm_stream_event+0x0/0xf4) from [<c04b023c>] (soc_pcm_prepare+0x110/0x1cc)
[  301.965342] [<c04b012c>] (soc_pcm_prepare+0x0/0x1cc) from [<c0487c74>] (snd_pcm_do_prepare+0x1c/0x34)
[  301.974527] [<c0487c58>] (snd_pcm_do_prepare+0x0/0x34) from [<c048779c>] (snd_pcm_action_single+0x40/0x80)
[  301.984136]  r4:c083e688 r3:00000001
[  301.987683] [<c048775c>] (snd_pcm_action_single+0x0/0x80) from [<c048a394>] (snd_pcm_action_nonatomic+0x70/0x88)
[  301.997835]  r7:00020002 r6:c083e688 r5:00020002 r4:f2eacb00
[  302.003456] [<c048a324>] (snd_pcm_action_nonatomic+0x0/0x88) from [<c048b088>] (snd_pcm_common_ioctl1+0x904/0xdb8)
[  302.013776]  r6:f2f7ebd4 r5:f2eacb00 r4:00000000 r3:00000000
[  302.019403] [<c048a784>] (snd_pcm_common_ioctl1+0x0/0xdb8) from [<c048b9a4>] (snd_pcm_playback_ioctl1+0x40/0x408)
[  302.029642] [<c048b964>] (snd_pcm_playback_ioctl1+0x0/0x408) from [<c048bda4>] (snd_pcm_playback_ioctl+0x38/0x3c)
[  302.039865]  r6:f284cd28 r5:00000000 r4:00000000
[  302.044449] [<c048bd6c>] (snd_pcm_playback_ioctl+0x0/0x3c) from [<c00dd8f4>] (do_vfs_ioctl+0x88/0x50c)
[  302.053738] [<c00dd86c>] (do_vfs_ioctl+0x0/0x50c) from [<c00dddb8>] (sys_ioctl+0x40/0x68)
[  302.061875]  r9:f25bc000 r8:c003d844 r7:0000002b r6:00004140 r5:00000000
[  302.068362] r4:e5b6aa80
[  302.070969] [<c00ddd78>] (sys_ioctl+0x0/0x68) from [<c003d6c0>] (ret_fast_syscall+0x0/0x30)
[  302.079295]  r7:00000036 r6:414d7be8 r5:414d7b68 r4:4000ca78

[  302.089721] Backtrace: 
[  302.092097] [<c0040d94>] (dump_backtrace+0x0/0x110) from [<c0640ce0>] (dump_stack+0x18/0x1c)
[  302.100518]  r6:f2eac900 r5:f2f92280 r4:00000001 r3:f2f906c0
[  302.106127] [<c0640cc8>] (dump_stack+0x0/0x1c) from [<c04bfcd0>] (late_enable_ev+0x2c/0xd4)
[  302.114459] [<c04bfca4>] (late_enable_ev+0x0/0xd4) from [<c04b5980>] (dapm_seq_check_event.clone.14+0xcc/0x11c)
[  302.124506]  r8:c08423e8 r7:ffffffff r6:00000001 r5:c07c183c r4:f2f92280
[  302.130988] r3:f2f906c0
[  302.133596] [<c04b58b4>] (dapm_seq_check_event.clone.14+0x0/0x11c) from [<c04b5a94>] (dapm_seq_run_coalesced+0xc4/0x198)
[  302.144438]  r6:00000001 r5:f2eac994 r4:f2f92280
[  302.149021] [<c04b59d0>] (dapm_seq_run_coalesced+0x0/0x198) from [<c04b5bfc>] (dapm_seq_run.clone.15+0x94/0x478)
[  302.159178] [<c04b5b68>] (dapm_seq_run.clone.15+0x0/0x478) from [<c04b62c8>] (dapm_power_widgets+0x2e8/0x408)
[  302.169055] [<c04b5fe0>] (dapm_power_widgets+0x0/0x408) from [<c04b72e0>] (snd_soc_dapm_stream_event+0x80/0xf4)
[  302.179112] [<c04b7260>] (snd_soc_dapm_stream_event+0x0/0xf4) from [<c04b023c>] (soc_pcm_prepare+0x110/0x1cc)
[  302.188999] [<c04b012c>] (soc_pcm_prepare+0x0/0x1cc) from [<c0487c74>] (snd_pcm_do_prepare+0x1c/0x34)
[  302.198177] [<c0487c58>] (snd_pcm_do_prepare+0x0/0x34) from [<c048779c>] (snd_pcm_action_single+0x40/0x80)
[  302.207791]  r4:c083e688 r3:00000001
[  302.211336] [<c048775c>] (snd_pcm_action_single+0x0/0x80) from [<c048a394>] (snd_pcm_action_nonatomic+0x70/0x88)
[  302.221484]  r7:00020002 r6:c083e688 r5:00020002 r4:f2eacb00
[  302.227108] [<c048a324>] (snd_pcm_action_nonatomic+0x0/0x88) from [<c048b088>] (snd_pcm_common_ioctl1+0x904/0xdb8)
[  302.237436]  r6:f2f7ebd4 r5:f2eacb00 r4:00000000 r3:00000000
[  302.243055] [<c048a784>] (snd_pcm_common_ioctl1+0x0/0xdb8) from [<c048b9a4>] (snd_pcm_playback_ioctl1+0x40/0x408)
[  302.253297] [<c048b964>] (snd_pcm_playback_ioctl1+0x0/0x408) from [<c048bda4>] (snd_pcm_playback_ioctl+0x38/0x3c)
[  302.263514]  r6:f284cd28 r5:00000000 r4:00000000
[  302.268102] [<c048bd6c>] (snd_pcm_playback_ioctl+0x0/0x3c) from [<c00dd8f4>] (do_vfs_ioctl+0x88/0x50c)
[  302.277389] [<c00dd86c>] (do_vfs_ioctl+0x0/0x50c) from [<c00dddb8>] (sys_ioctl+0x40/0x68)
[  302.285530]  r9:f25bc000 r8:c003d844 r7:0000002b r6:00004140 r5:00000000
[  302.292014] r4:e5b6aa80
[  302.294622] [<c00ddd78>] (sys_ioctl+0x0/0x68) from [<c003d6c0>] (ret_fast_syscall+0x0/0x30)
[  302.302951]  r7:00000036 r6:414d7be8 r5:414d7b68 r4:4000ca78
[  302.486533] Backtrace: 
[  302.488908] [<c0040d94>] (dump_backtrace+0x0/0x110) from [<c0640ce0>] (dump_stack+0x18/0x1c)
[  302.497327]  r6:00000002 r5:f2f7ec00 r4:f2eac900 r3:00000013
[  302.502937] [<c0640cc8>] (dump_stack+0x0/0x1c) from [<c04bfc10>] (late_disable_ev+0x24/0xb8)
[  302.511359] [<c04bfbec>] (late_disable_ev+0x0/0xb8) from [<c04b5f8c>] (dapm_seq_run.clone.15+0x424/0x478)
[  302.520884]  r6:c0841df0 r5:ffffffff r4:ffffffff r3:00000013
[  302.526507] [<c04b5b68>] (dapm_seq_run.clone.15+0x0/0x478) from [<c04b62c8>] (dapm_power_widgets+0x2e8/0x408)
[  302.536402] [<c04b5fe0>] (dapm_power_widgets+0x0/0x408) from [<c04b72e0>] (snd_soc_dapm_stream_event+0x80/0xf4)
[  302.546456] [<c04b7260>] (snd_soc_dapm_stream_event+0x0/0xf4) from [<c04b023c>] (soc_pcm_prepare+0x110/0x1cc)
[  302.556341] [<c04b012c>] (soc_pcm_prepare+0x0/0x1cc) from [<c0487c74>] (snd_pcm_do_prepare+0x1c/0x34)
[  302.565522] [<c0487c58>] (snd_pcm_do_prepare+0x0/0x34) from [<c048779c>] (snd_pcm_action_single+0x40/0x80)
[  302.575138]  r4:c083e688 r3:00000001
[  302.578681] [<c048775c>] (snd_pcm_action_single+0x0/0x80) from [<c048a394>] (snd_pcm_action_nonatomic+0x70/0x88)
[  302.588829]  r7:00020002 r6:c083e688 r5:00020002 r4:f2eacb00
[  302.594453] [<c048a324>] (snd_pcm_action_nonatomic+0x0/0x88) from [<c048b088>] (snd_pcm_common_ioctl1+0x904/0xdb8)
[  302.604776]  r6:f2f7ebd4 r5:f2eacb00 r4:00000000 r3:00000000
[  302.610399] [<c048a784>] (snd_pcm_common_ioctl1+0x0/0xdb8) from [<c048b9a4>] (snd_pcm_playback_ioctl1+0x40/0x408)
[  302.620642] [<c048b964>] (snd_pcm_playback_ioctl1+0x0/0x408) from [<c048bda4>] (snd_pcm_playback_ioctl+0x38/0x3c)
[  302.630859]  r6:f284cd28 r5:00000000 r4:00000000
[  302.635447] [<c048bd6c>] (snd_pcm_playback_ioctl+0x0/0x3c) from [<c00dd8f4>] (do_vfs_ioctl+0x88/0x50c)
[  302.644734] [<c00dd86c>] (do_vfs_ioctl+0x0/0x50c) from [<c00dddb8>] (sys_ioctl+0x40/0x68)
[  302.652875]  r9:f25bc000 r8:c003d844 r7:0000002b r6:00004140 r5:00000000
[  302.659371] r4:e5b6aa80
[  302.661966] [<c00ddd78>] (sys_ioctl+0x0/0x68) from [<c003d6c0>] (ret_fast_syscall+0x0/0x30)
[  302.670293]  r7:00000036 r6:414d7be8 r5:414d7b68 r4:4000ca78

/ # 
/ # 
/ # 
[  310.931244] Backtrace: 
[  310.933638] [<c0040d94>] (dump_backtrace+0x0/0x110) from [<c0640ce0>] (dump_stack+0x18/0x1c)
[  310.942046]  r6:00000008 r5:f2f7ec00 r4:f2eac900 r3:00000013
[  310.947659] [<c0640cc8>] (dump_stack+0x0/0x1c) from [<c04bfc10>] (late_disable_ev+0x24/0xb8)
[  310.956083] [<c04bfbec>] (late_disable_ev+0x0/0xb8) from [<c04b5e88>] (dapm_seq_run.clone.15+0x320/0x478)
[  310.965610]  r6:c0841df0 r5:ffffffff r4:ffffffff r3:00000013
[  310.971225] [<c04b5b68>] (dapm_seq_run.clone.15+0x0/0x478) from [<c04b62c8>] (dapm_power_widgets+0x2e8/0x408)
[  310.981127] [<c04b5fe0>] (dapm_power_widgets+0x0/0x408) from [<c04b72e0>] (snd_soc_dapm_stream_event+0x80/0xf4)
[  310.991183] [<c04b7260>] (snd_soc_dapm_stream_event+0x0/0xf4) from [<c04b011c>] (close_delayed_work+0x44/0x54)
[  311.001143] [<c04b00d8>] (close_delayed_work+0x0/0x54) from [<c006c380>] (process_one_work+0x120/0x38c)
[  311.010495]  r5:f3421400 r4:f341b180
[  311.014038] [<c006c260>] (process_one_work+0x0/0x38c) from [<c006e3e8>] (worker_thread+0x160/0x34c)
[  311.023070] [<c006e288>] (worker_thread+0x0/0x34c) from [<c0072654>] (kthread+0x90/0x94)
[  311.031136] [<c00725c4>] (kthread+0x0/0x94) from [<c005b3e0>] (do_exit+0x0/0x674)
[  311.038570]  r6:c005b3e0 r5:c00725c4 r4:f3439ee0
这Log比较长,耐心点分析:

1、播放开始时,调用流程:

soc_pcm_prepare->

snd_soc_dapm_stream_event->

dapm_power_widgets->

dapm_seq_run->

dapm_seq_run_coalesced->

dapm_seq_check_event->

late_enable_ev;

2、播放结束后,调用流程:

close_delayed_work->

snd_soc_dapm_stream_event->

dapm_power_widgets->

dapm_seq_run->

late_disable_ev。

可能这里有点困惑,close_delayed_work看起来是一个delayed work的处理句柄,那么又是谁调度了这个delayed work呢?答案是soc_codec_close。

下面抽丝剥茧对每个函数进行分析。我会尽可能注明代码的用意,而不是死板的逐行注释。其实更多时候,我们更需要知道的是what/why,而不是how。


3.源码分析


3.1 soc_dapm_stream_event

static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,
	const char *stream, int event)
{
	struct snd_soc_dapm_widget *w;

	list_for_each_entry(w, &dapm->card->widgets, list)
	{
		if (!w->sname || w->dapm != dapm)
			continue;
		dev_dbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n",
			w->name, w->sname, stream, event);
		if (strstr(w->sname, stream)) {
			switch(event) {
			case SND_SOC_DAPM_STREAM_START:
				w->active = 1;
				break;
			case SND_SOC_DAPM_STREAM_STOP:
				w->active = 0;
				break;
			case SND_SOC_DAPM_STREAM_SUSPEND:
			case SND_SOC_DAPM_STREAM_RESUME:
			case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
			case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
				break;
			}
		}
	}

	dapm_power_widgets(dapm, event);
}

这个函数是stream event处理的入口:

1、找到sname和stream name相匹配的widget--这种widget类型一般是DAC/ADC/AIF_IN/AIF_OUT,也只有这四种widgets会有定义sname,具体见soc-dapm.h宏定义;

2、如果音频子流是start状态,则置其active标志为1;如果音频子流是stop状态,则置active标志为0;这个active标志会在遍历complete path时用到,请留意这一点;

3、最后进入dapm_power_widgets处理。可见,stream event和amixer一样,都会触发对所有的dapm widgets的遍历、响应。


3.2 dapm_power_widgets

/*
 * 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_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);
	int power;

	trace_snd_soc_dapm_start(card);

	list_for_each_entry(d, &card->dapm_list, list)
		if (d->n_widgets || d->codec == NULL)
			d->dev_power = 0;

	// 遍历所有dapm widgets,寻找一条完整的音频路径complete audio path
	
	/* 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, &card->widgets, list) {
		switch (w->id) {
		case snd_soc_dapm_pre:
			dapm_seq_insert(w, &down_list, false);
			break;
		case snd_soc_dapm_post:
			dapm_seq_insert(w, &up_list, true);
			break;

		default:
			if (!w->power_check)
				continue;

			// 检查widget的force标志,当驱动对某pin强制enable时,那么该pin对应的widget会一直维持通电状态,不管是否它处在一个complete path里面
			if (!w->force)
				power = w->power_check(w); // 检查该widget是否位于complete path中,如果是,那么需要对它通电,设置通电标志power=1;否则设置通电标志power=0
			else
				power = 1;
			if (power)
				w->dapm->dev_power = 1;

			if (w->power == power)
				continue;

			trace_snd_soc_dapm_widget_power(w, power);

			if (power)
				dapm_seq_insert(w, &up_list, true); // 通电标志power=1,把该widget插入up_list链表,该链表上的所有widgets均需要power up的
			else
				dapm_seq_insert(w, &down_list, false); // 通电标志power=0,把该widget插入down_list链表,该链表上的所有widgets均需要power down的

			w->power = power;
			break;
		}
	}

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

	/* Force all contexts in the card to the same bias state */
	power = 0;
	list_for_each_entry(d, &card->dapm_list, list)
		if (d->dev_power)
			power = 1;
	list_for_each_entry(d, &card->dapm_list, list)
		d->dev_power = power;


	/* Run all the bias changes in parallel */
	list_for_each_entry(d, &dapm->card->dapm_list, list)
		async_schedule_domain(dapm_pre_sequence_async, d,
					&async_domain);
	async_synchronize_full_domain(&async_domain);
	
	//先power down链表down_list上的widgets,接着power up链表up_list上的widgets;按照这样的次序,目的是避免产生pop音    

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

	dapm_widget_update(dapm); // 这里设置widget的kcontrol,注意只有mux/mixer切换输入源才有效,普通的widgets直接返回

	/* 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);
	async_synchronize_full_domain(&async_domain);

	pop_dbg(dapm->dev, card->pop_time,
		"DAPM sequencing finished, waiting %dms\n", card->pop_time);
	pop_wait(card->pop_time);

	trace_snd_soc_dapm_done(card);

	return 0;
}

该函数是dapm模块最核心的一个函数,它的作用:

1、遍历所有dapm widgets,寻找一条完整的音频路径complete audio path;所谓complete audio path,在这个函数的注释都说得很清楚了,就是如下四种情形:

 *  o DAC to output pin.
 *  o Input Pin to ADC.
 *  o Input pin to Output pin (bypass, sidetone)
 *  o DAC to ADC (loopback).

complete path涉及到的概念,如input endpoint、output endpoint等请翻到dapm核心分析,里面有详尽的解释;还有如何查找complete path,之前也有解释,这里不累述了;

2、把complete path上面的所有widgets插入到up_list链表中,其他widgets插入到down_list链表中;

3、power down链表down_list上的widgets,然后power up链表up_list上的widgets。

注意:dapm_widget_update用于设置widget kcontrol寄存器,只在mixer/mux部件切换输入源source有效,具体见dapm_mux_update_power和dapm_mixer_update_power函数。如果有必要,在path domain之章会详细阐述这一点。

还有一些枝叶没有一一分析。


3.3 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_dapm_context *dapm,
			 struct list_head *list, int event, bool power_up)
{
	struct snd_soc_dapm_widget *w, *n;
	LIST_HEAD(pending);
	int cur_sort = -1;
	int cur_subseq = -1;
	int cur_reg = SND_SOC_NOPM;
	struct snd_soc_dapm_context *cur_dapm = NULL;
	int ret, i;
	int *sort;

	if (power_up)
		sort = dapm_up_seq;
	else
		sort = dapm_down_seq;

	list_for_each_entry_safe(w, n, list, power_list) {
		ret = 0;

		/* Do we need to apply any queued changes? */
		// 将pending链表上所有的widgets通电/断电,注意:该链表上的所有widgets要设置的寄存器(widget register)、通电次序(power sequence)均是一样的。在保证POPs有效处理基础上,实现寄存器的最少次数的读写
		if (sort[w->id] != cur_sort || w->reg != cur_reg ||
		    w->dapm != cur_dapm || w->subseq != cur_subseq) {
			if (!list_empty(&pending))
				dapm_seq_run_coalesced(cur_dapm, &pending);

			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); // 重新初始化pending链表,为下个widget准备
			cur_sort = -1;
			cur_subseq = -1;
			cur_reg = SND_SOC_NOPM;
			cur_dapm = NULL;
		}
 
		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); // widget event句柄处理
			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;

		default:
			/* Queue it up for application */
			cur_sort = sort[w->id];
			cur_subseq = w->subseq;
			cur_reg = w->reg;
			cur_dapm = w->dapm;
			list_move(&w->power_list, &pending); // 将遍历到的widget移除到pending链表
			break;
		}

		if (ret < 0)
			dev_err(w->dapm->dev,
				"Failed to apply widget power: %d\n", ret);
	}

	if (!list_empty(&pending))
		dapm_seq_run_coalesced(cur_dapm, &pending);

	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);
	}
}
这个函数的注释写得很清楚,它遍历之前已排序好(dapm_seq_insert)的链表,把1)连续的、2)同一个widget register的、3)同一个power sequence的widgets送到pending链表上,然后调用dapm_seq_run_coalesced对该链表上的widgets进行设置。这样做的目的是尽可能减少对widget registers的读写。

留意power sequence,这是为了有效消除POPs而设置的,我们分别看看power up和power down情况下的次序是怎样的:

/* dapm power sequences - make this per codec in the future */
static int dapm_up_seq[] = {
	[snd_soc_dapm_pre] = 0,
	[snd_soc_dapm_supply] = 1,
	[snd_soc_dapm_micbias] = 2,
	[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_supply] = 11,
	[snd_soc_dapm_post] = 12,
};
可知:power up时,通电次序是从input endpoint到output endpoint的,power down时,则刚好相反。

3.4 dapm_seq_run_coalesced

/* Apply the coalesced changes from a DAPM sequence */
static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm,
				   struct list_head *pending)
{
	struct snd_soc_card *card = dapm->card;
	struct snd_soc_dapm_widget *w;
	int reg, power;
	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;

	// 从之前的分析可知,pending链表上的widgets均是对同一个register操作的,所以在这里遍历链表,整理每个widget要设置的值和掩码
	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(dapm->dev, card->pop_time,
			"pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n",
			w->name, reg, value, mask);

		/* Check for events */
		dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMU); // widget pre event句柄处理
		dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMD);
	}

	if (reg >= 0) {
		pop_dbg(dapm->dev, card->pop_time,
			"pop test : Applying 0x%x/0x%x to %x in %dms\n",
			value, mask, reg, card->pop_time);
		pop_wait(card->pop_time);
		snd_soc_update_bits(dapm->codec, reg, mask, value); // 设置widget register
	}

	list_for_each_entry(w, pending, power_list) {
		dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMU); // widget post event句柄处理
		dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMD);
	}
}
因为pending链表上的widgets均是对同一个regiser进行操作的,所以整理每个widget需要设置的register bits,然后一次性设置widget register。留意:widget pre event是在widget register设置之前执行的,widget post event是在widget register设置之后执行的。


3.5 dapm_seq_check_event

static void dapm_seq_check_event(struct snd_soc_dapm_context *dapm,
				 struct snd_soc_dapm_widget *w, int event)
{
	struct snd_soc_card *card = dapm->card;
	const char *ev_name;
	int power, ret;

	switch (event) {
	case SND_SOC_DAPM_PRE_PMU:
		ev_name = "PRE_PMU";
		power = 1;
		break;
	case SND_SOC_DAPM_POST_PMU:
		ev_name = "POST_PMU";
		power = 1;
		break;
	case SND_SOC_DAPM_PRE_PMD:
		ev_name = "PRE_PMD";
		power = 0;
		break;
	case SND_SOC_DAPM_POST_PMD:
		ev_name = "POST_PMD";
		power = 0;
		break;
	default:
		BUG();
		return;
	}

	if (w->power != power)
		return;

	if (w->event && (w->event_flags & event)) {
		pop_dbg(dapm->dev, card->pop_time, "pop test : %s %s\n",
			w->name, ev_name);
		trace_snd_soc_dapm_widget_event_start(w, event);
		ret = w->event(w, NULL, event);
		trace_snd_soc_dapm_widget_event_done(w, event);
		if (ret < 0)
			pr_err("%s: %s event failed: %d\n",
			       ev_name, w->name, ret);
	}
}
这个真没什么可说的,进入widget event处理句柄。


4.个人对widget event的理解


通篇说了那么多widget event,还没有涉及一个关键问题:widget event究竟是什么,它为了什么而存在?

我们先看dapm.txt是如何描述的:

5 DAPM Widget Events
====================

Some widgets can register their interest with the DAPM core in PM events.
e.g. A Speaker with an amplifier registers a widget so the amplifier can be
powered only when the spk is in use.

/* turn speaker amplifier on/off depending on use */
static int corgi_amp_event(struct snd_soc_dapm_widget *w, int event)
{
	gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event));
	return 0;
}

/* corgi machine dapm widgets */
static const struct snd_soc_dapm_widget wm8731_dapm_widgets =
	SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event);

Please see soc-dapm.h for all other widgets that support events.
这里面有个例子,当使用spk时,会调用这个event从而控制GPIO打开功放(amplifier)。

我个人的理解是:widget event是对部件(widget)开关的一种时序控制。比如说,某些DAC通电或断电前后,可能需要对其它寄存器进行一些设置,否则DAC工作会有问题。这就是所谓的硬件通断电时序,widget event就是为了迎合这种需要而设置的。

soc-dapm.h里面定义的event类型有:

/* dapm event types */
#define SND_SOC_DAPM_PRE_PMU	0x1 	/* before widget power up */
#define SND_SOC_DAPM_POST_PMU	0x2		/* after widget power up */
#define SND_SOC_DAPM_PRE_PMD	0x4 	/* before widget power down */
#define SND_SOC_DAPM_POST_PMD	0x8		/* after widget power down */
#define SND_SOC_DAPM_PRE_REG	0x10	/* before audio path setup */
#define SND_SOC_DAPM_POST_REG	0x20	/* after audio path setup */
#define SND_SOC_DAPM_PRE_POST_PMD \
				(SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值