这两天在查一个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,但基本流程都一样。
我们先看看内核文档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会被执行。
接着会实例分析下这个过程,同时不可避免的会有一些代码分析。其实我很讨厌代码分析,看别人博客如果有大量代码分析,我一般不会往下看了。但我阐述水平还没达到那种信手拈来飞花摘叶的境界,所以先说声见谅了。
这次其实缘由于一个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。
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的遍历、响应。
/* * 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之章会详细阐述这一点。
还有一些枝叶没有一一分析。
/* 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时,则刚好相反。
/* 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设置之后执行的。
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处理句柄。
通篇说了那么多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)