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

来源:互联网 发布:淘宝现货是什么意思 编辑:程序博客网 时间:2024/06/01 08:44

这两天在查一个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 codecsnd_soc_codec_dai.e.g. stream widgets for HiFi playback and captureSND_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=0elsepower = 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的elsedapm_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;elsesort = 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;elsepower = 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 bepowered 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_PMU0x1 /* before widget power up */#define SND_SOC_DAPM_POST_PMU0x2/* after widget power up */#define SND_SOC_DAPM_PRE_PMD0x4 /* before widget power down */#define SND_SOC_DAPM_POST_PMD0x8/* after widget power down */#define SND_SOC_DAPM_PRE_REG0x10/* before audio path setup */#define SND_SOC_DAPM_POST_REG0x20/* after audio path setup */#define SND_SOC_DAPM_PRE_POST_PMD \(SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)