
imx6ull: fsl-asoc-card.c 逐段拆解 —— probe 是如何缝出 HiFi 这条声卡的这个仓库已经开源所有教程主线内核移植跑新版本imx-linux/uboot都在这里或者一起来尝试跑7.1的Linux欢迎各位大佬观摩喜欢的话点个⭐昨天刚更新仓库地址https://github.com/Awesome-Embedded-Learning-Studio/imx-forge静态网页https://awesome-embedded-learning-studio.github.io/imx-forge/上一篇ASoC 框架把四件套的理论拆完了这一篇咱们正式动手拆 machine 驱动sound/soc/fsl/fsl-asoc-card.cmainline 7.11112 行。它和 RTC 篇的 rtc-snvs.c、goodix.c 一样是标准子系统驱动只不过挂在 platform 总线上干的是缝纫机的活把 cpu_daiSAI2和 codec_daiwm8960-hifi按上一篇讲的 dai_link 契约缝成一张声卡。驱动骨架platform_driver 14 种 compatible咱们从骨架看起整颗驱动是一个platform_driversound-wm8960节点是 platform 设备不是 i2c/timer所以走 platform 总线/* fsl-asoc-card.c:1079 —— 设备树匹配表一个 machine 通吃 14 种 codec */staticconststructof_device_idfsl_asoc_card_dt_ids[]{{.compatiblefsl,imx-audio-wm8960,},/* ← alpha 板命中这条 */{.compatiblefsl,imx-audio-wm8962,},{.compatiblefsl,imx-audio-sgtl5000,},{.compatiblefsl,imx-audio-cs427x,},{.compatiblefsl,imx-audio-tlv320aic32x4,},/* ... 共 14 种 codec ... */{}};/* :1098 —— platform 驱动骨架 */staticstructplatform_driverfsl_asoc_card_driver{.probefsl_asoc_card_probe,.driver{.nameDRIVER_NAME,/* fsl-asoc-card */.pmsnd_soc_pm_ops,.of_match_tablefsl_asoc_card_dt_ids,},};module_platform_driver(fsl_asoc_card_driver);内核启动解析设备树看到 alpha 板正点原子 i.MX6ULLimx6ull-aes.dtsi里sound-wm8960节点的compatible fsl,imx-audio-wm8960就用这张表匹配、触发fsl_asoc_card_probe。一份 machine 驱动兼容 14 种 codec这就是它能把正点原子那颗只认 WM8960 的imx-wm8960.c挤掉的本钱也是咱们这一篇要拆的全部内容。probe 全景从设备树到注册 cardfsl_asoc_card_probe:614是这颗驱动的核心。它要把设备树sound-wm8960节点里那几个 phandle变成一张注册进 ASoC 核心的snd_soc_card。咱们按 probe 的执行顺序一段段看。解析设备树四件audio-cpu / audio-codec / audio-asrc / MCLK/* fsl-asoc-card.c:640 —— cpu DAI 节点SAI2 */cpu_npof_parse_phandle(np,audio-cpu,0);.../* :659 —— codec 节点支持双 codecWM8960 用第 0 个 */codec_np[0]of_parse_phandle(np,audio-codec,0);codec_np[1]of_parse_phandle(np,audio-codec,1);/* :682 —— ASRC 节点可选 */asrc_npof_parse_phandle(np,audio-asrc,0);三个of_parse_phandle把设备树的audio-cpu sai2、audio-codec codec、audio-asrc asrc解析成device_node *。alpha 板这三个都有咱们到设备树那篇05会贴完整节点。紧接着读 MCLK 频率/* :686 —— MCLK 频率由 codec 端的 mclk clk 决定留给 codec 驱动去 enable */for(codec_idx0;codec_idx2;codec_idx){structclk*codec_clkclk_get(codec_dev[codec_idx],NULL);if(!IS_ERR(codec_clk)){priv-codec_priv[codec_idx].mclk_freqclk_get_rate(codec_clk);clk_put(codec_clk);}}注释写得很明白“Get the MCLK rate only, and leave it controlled by CODEC drivers”。machine 只读MCLK 频率12288000真正的clk_prepare_enable留给 codec 驱动wm8960.c去干这是分层谁用谁管使能的那一半咱们到 late_probe 那节再看。默认采样率则直接填 44100/S16运行时hw_params会覆盖/* :698 */priv-sample_rate44100;priv-sample_formatSNDRV_PCM_FORMAT_S16_LE;dai_fmt 基础与 dai_link 模板拷贝/* :702 */priv-dai_fmtDAI_FMT_BASE;/* I2S | NB_NF —— 见 :41 *//* :705 */memcpy(priv-dai_link,fsl_asoc_card_dai,sizeof(structsnd_soc_dai_link)*ARRAY_SIZE(priv-dai_link));DAI_FMT_BASE是SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF所有 codec 共用I2S 正常极性当起点。fsl_asoc_card_dai是三组静态snd_soc_dai_link模板name 分别是HiFi、HiFi-ASRC-FE、HiFi-ASRC-BEprobe 把它们 memcpy 进 priv后面再按 codec 微调咱们马上要看的就是微调的部分。接着kcalloc10 个snd_soc_dai_link_componentdlc给三条 link 的 cpus/codecs/platforms 填指针。这部分是机械的内存布局咱们知道dai_link[0] 用 dlc[0/1/3]即可不用抠。WM8960 专属 if 分支通用 machine 的差异化点probe 的中段是一条超长 if-else if 链按设备树compatible走不同分支给每种 codec 配它的专属参数14 种 codec 的差异全在这一段。咱们从 alpha 板命中的 WM8960 分支:782看起/* fsl-asoc-card.c:782 —— WM8960 专属分支 */}elseif(of_device_is_compatible(np,fsl,imx-audio-wm8960)){codec_dai_name[0]wm8960-hifi;/* ① codec DAI 名 */priv-codec_priv[0].fll_idWM8960_SYSCLK_AUTO;/* ② sysclk 模式自动 */priv-codec_priv[0].pll_idWM8960_SYSCLK_AUTO;priv-dai_fmt|SND_SOC_DAIFMT_CBP_CFP;/* ③ codec 当时钟主 */分支里就三行赋值一行决定一件事咱们逐行看。头一行是 codec DAI 名wm8960-hifi。wm8960.c注册自己的 DAI 时就叫这个名字machine 把它填进 dai_link内核才能按名匹配到 codec 驱动。咱们要是换颗 codecWM8962 是wm8962、SGTL5000 是sgtl5000改的就是这一行。中间两行把 sysclk 模式定成自动fll_id/pll_id WM8960_SYSCLK_AUTO。它告诉 codec 驱动MCLK 够用就直驱不够才启内部 PLL。alpha 板 MCLK 12.288MHz 够 48kHz 用咱们这张卡走直驱、不启 PLL下一篇详讲。最后一行priv-dai_fmt | SND_SOC_DAIFMT_CBP_CFP定的是主从位上一篇讲过codec 产生 BCLK/LRCLK主SAI2 跟着从。咱们把DAI_FMT_BASE叠上去完整 fmt I2S | NB_NF | CBP_CFP。为什么一份 machine 驱动能通吃 14 种 codec咱们对比旁边 wm8962、cs427x、sgtl5000 的分支结构完全一样只是codec_dai_name、fll_id/pll_id、dai_fmt主从位不同。差异点全收敛在这几行其余 probe 逻辑搭 dai_link、注册 card对所有 codec 通用——通用 machine 驱动就是这么设计的per-codec 差异集中到一条 if-else 链公共流程大家共用。CPU 是 SAI 时的 sysclk 选择/* :921 */}elseif(of_node_name_eq(cpu_np,sai)){priv-cpu_priv.sysclk_id[1]FSL_SAI_CLK_MAST1;/* SAI 内部主时钟1 */priv-cpu_priv.sysclk_id[0]FSL_SAI_CLK_MAST1;}machine 还按 cpu 节点名esai/sai/spdif给 cpu_dai 选 sysclk 源。alpha 板是 SAI2走这条FSL_SAI_CLK_MAST1是 SAI 自己的主时钟分支。这个细节是fsl_sai.c内部用的咱们在这里只需要知道machine 替它选好了。组装 card 名解析 audio-routing/* :926 —— 初始化 card */priv-card.devpdev-dev;priv-card.ownerTHIS_MODULE;snd_soc_of_parse_card_name(priv-card,model);/* wm8960-audio */priv-card.dai_linkpriv-dai_link;priv-card.late_probefsl_asoc_card_late_probe;priv-card.dapm_widgetsfsl_asoc_card_dapm_widgets;/* :944 —— 解析 audio-routing那 12 条耳机/喇叭/麦克风连线 */if(of_property_present(np,audio-routing))snd_soc_of_parse_audio_routing(priv-card,audio-routing);model属性变成 card 名wm8960-audio咱们在/proc/asound/cards里看到的就是它。audio-routing 那 12 条耳机/喇叭/麦克风连线解析进card.dapm_routes也就是上一篇 DAPM 那张有向图的边。填实 dai_link[0]注册 card轮到真正缝的地方了dai_link[0]HiFi的 cpu/codec/platform 三个槽咱们把前面解析到的节点填进去/* fsl-asoc-card.c:953 —— Normal DAI LinkHiFi */priv-dai_link[0].cpus-of_nodecpu_np;/* cpu_dai SAI2 */for_each_link_codecs(((priv-dai_link[0])),codec_idx,codec_comp){codec_comp-dai_namecodec_dai_name[codec_idx];/* wm8960-hifi */}for_each_link_codecs(((priv-dai_link[0])),codec_idx,codec_comp){codec_comp-of_nodecodec_np[codec_idx];/* wm89601a */}priv-dai_link[0].platforms-of_nodecpu_np;/* DMA 走 SAI2 */priv-dai_link[0].dai_fmtpriv-dai_fmt;/* I2S|NB_NF|CBP_CFP */priv-card.num_links1;/* 暂时只 1 条 */咱们回头看上一篇那张dai_link 契约里的 cpu/codec/platform/of_node/dai_name/dai_fmt全在这几行填实。填完probe 末尾一行把 card 注册进 ASoC 核心/* :1034 */retdevm_snd_soc_register_card(pdev-dev,priv-card);这一行执行完内核就按 dai_link 的名字去找 SAI2 的snd_soc_dai、WM8960 的snd_soc_dai把俩绑成一个snd_soc_pcm_runtime在/dev/snd/下生出controlC0/pcmC0D0p/pcmC0D0c——咱们的声卡正式上线。可选ASRC额外的两条 dai_link/* :990 */if(asrc_pdev){/* DPCM DAI Links only if ASRC exists */priv-dai_link[1].cpus-of_nodeasrc_np;/* FE: 用户态 PCM → ASRC */priv-dai_link[2].cpus-of_nodecpu_np;/* BE: ASRC → SAI2 */priv-card.num_links3;...}alpha 板设备树有audio-asrc asrc所以会搭这两条。ASRC异步采样率转换是个进阶特性它让咱们能用一个采样率播放另一个采样率录的音频或把不同来源的流混到统一采样率dai_link[1/2]用 DPCMDynamic PCM把 ASRC 插在用户态和 SAI2 之间。不过即使去掉audio-asrc这行alpha 板照样出声num_links退回 1走纯 HiFi 链路。咱们初学阶段想把链路砍到最简把audio-asrc注释掉、num_links1就行好理解。本教程的上板验证06默认带 ASRC但出声只依赖dai_link[0]。late_proberuntime 设 codec sysclkdevm_snd_soc_register_card的时候 card 还没真正通电等咱们第一次 aplay、用户态open(/dev/snd/pcmC0D0p)时ASoC 核心才调late_probe在这里给 codec 设 sysclk/* :600 —— late_probe 里把 MCLK 频率告诉 codec选好 sysclk 源 */retsnd_soc_dai_set_sysclk(codec_dai,codec_priv-mclk_id,codec_priv-mclk_freq,/* 12288000 */SND_SOC_CLOCK_IN);if(!IS_ERR_OR_NULL(codec_priv-mclk))clk_prepare_enable(codec_priv-mclk);/* 真正使能 MCLK */probe 里只读了 MCLK 频率到这里才使能它clk_prepare_enable并通知 codec用这个频率当 sysclk。WM8960 收到后会按SYSCLK_AUTO决定直驱还是启 PLL咱们到下一篇拆。hp-det耳机检测可选设备树有hp-det-gpio gpio5 4 0probe 末尾解析它注册成耳机插拔的 jack 检测插耳机自动切到耳机输出拔了切回喇叭。这是个体验优化咱们不配它也照样出声设备树那篇会提一下。HiFi dai_link 拓扑图咱们把这一篇拆的东西全画出来感谢AI帮忙梳理写的时候就头大了alpha 板的 HiFidai_link[0]长这样snd_soc_card wm8960-audio (fsl-asoc-card.c) │ dai_link[0] HiFi ┌──────────┼──────────┐ ▼ ▼ ▼ cpus codecs platforms of_node of_node of_node sai2 → wm89601a → sai2(DMA) dai_name: wm8960-hifi dai_fmt I2S | NB_NF | CBP_CFP (codec 产生 BCLK/LRCLKMCLK 由 SAI2 给)一行devm_snd_soc_register_card这张图就活了。