Skip to content

设备树配置 —— sound-wm8960 / codec / sai2 节点逐行解读 ​

前面三节我们拆了 machine(fsl-asoc-card.c)和 codec(wm8960.c)驱动。这一节把这一切落回设备树——看看 alpha 板上那几个节点,是怎么把前面讲的 audio-cpu/audio-codec/audio-asrc/MCLK/audio-routing 这些概念,用 DTS 语法描述出来的。

学习目标

逐行读懂 sound-wm8960(machine)、codec: wm8960@1a(codec)、&sai2(cpu_dai + MCLK 时钟源)、pinctrl_sai2(JTAG→SAI2 引脚复用)四个节点;理清 audio-routing 那 12 条连线对应 04 节 哪些 DAPM widget;知道 alpha 板这些节点都在共享 imx6ull-aes.dtsi 里、本章分析型只解读不新增,板级改动看章节级 dts。

先说清楚:这些节点 alpha 板早配好了 ​

和 RTC 章 的 snvs-rtc-lp 一样,alpha 板的整条音频链路硬件描述早就在共享 imx6ull-aes.dtsi 里配齐了——sound-wm8960、codec、&sai2、pinctrl_sai2 四块。本章是分析型、复用主线驱动,不用新增任何硬件配置,这一节纯解读。

别去改共享 dtsi

按项目规范,共享 imx6ull-aes.dtsi 的改动会被回退(它是 alpha 板的基础配置,多章节共用)。板级音频改动要进每章自己的 .dts——本章的章节级 dts 在 driver/device_tree/alpha-board/24_tutorial_wm8960_audio/,见本节末尾。

一、sound-wm8960(machine 节点) ​

这是 fsl-asoc-card.c 的入口节点(03 节 probe 解析的就是它):

展开代码收起代码共 25 行
c
/* imx6ull-aes.dtsi:104 —— machine 节点 */
sound-wm8960 {
    compatible = "fsl,imx-audio-wm8960";   /* ← 命中 fsl-asoc-card 的 dt_ids */
    model = "wm8960-audio";                 /* ← card 名,/proc/asound/cards 里看到它 */

    audio-cpu = <&sai2>;                    /* cpu_dai:SAI2 */
    audio-codec = <&codec>;                 /* codec:下面那个 wm8960@1a */
    audio-asrc = <&asrc>;                   /* 可选:ASRC(注释掉也能出声) */

    hp-det-gpio = <&gpio5 4 0>;             /* 耳机检测引脚 GPIO5_IO04 */

    audio-routing =                         /* 12 条模拟通路连线,见下 */
        "Headphone Jack", "HP_L",
        "Headphone Jack", "HP_R",
        "Ext Spk", "SPK_LP",
        "Ext Spk", "SPK_LN",
        "Ext Spk", "SPK_RP",
        "Ext Spk", "SPK_RN",
        "LINPUT2", "Mic Jack",
        "LINPUT3", "Mic Jack",
        "RINPUT1", "AMIC",
        "RINPUT2", "AMIC",
        "Mic Jack", "MICB",
        "AMIC", "MICB";
};

逐属性对应 03 节:

属性对应 probe 里的含义
compatibleof_device_id 表选中 fsl-asoc-card,走 WM8960 分支(:782)
modelsnd_soc_of_parse_card_name(:929)card 名,cat /proc/asound/cards 第一列
audio-cpuof_parse_phandle(np,"audio-cpu",0)(:640)cpu_dai 节点 → SAI2
audio-codecof_parse_phandle(np,"audio-codec",0)(:659)codec 节点 → wm8960@1a
audio-asrcof_parse_phandle(np,"audio-asrc",0)(:682)可选 ASRC,去掉则 num_links=1
hp-det-gpio:1048 的 jack 检测插拔耳机自动切换输出

audio-routing 那 12 条线 ​

格式是成对的 "sink", "source",表示「音频从 source 流向 sink」。对应 04 节 的 DAPM widget:

  • 耳机("Headphone Jack" ← "HP_L/HP_R"):WM8960 的耳机输出脚 HP_L/HP_R 接到板上的耳机座。两条(左右声道)。
  • 喇叭("Ext Spk" ← "SPK_LP/SPK_LN/SPK_RP/SPK_RN"):外部喇叭走 BTL 桥接,每声道一正一负成对,所以 4 条线(左正/左负/右正/右负)。
  • 麦克风("LINPUT2/3" ← "Mic Jack"、"RINPUT1/2" ← "AMIC"):板上有两个麦——Mic Jack(3.5mm 麦克风孔)接到左声道输入 LINPUT2/3,AMIC(板载驻极体麦)接到右声道输入 RINPUT1/2。
  • 麦克风偏置("Mic Jack/AMIC" ← "MICB"):两个麦都从 WM8960 的 MICB(MICBIAS)取偏置电压,否则驻极体麦不工作。

这 12 条线在 :944 被 snd_soc_of_parse_audio_routing 解析进 card.dapm_routes,就成了 04 节 DAPM 那张图的边。

audio-routing 和 DAPM widget 的对应

audio-routing 里的 HP_L、SPK_LP、LINPUT2、MICB 这些名字,必须和 wm8960.c widget 表里的名字一字不差(SND_SOC_DAPM_OUTPUT("HP_L") 等)。写错一个字母,DAPM 路径就连不上、那路模拟通道就哑。这是设备树和 codec 驱动之间最硬的契约。

二、codec: wm8960@1a(codec 节点) ​

踩坑:WM8960 实测在 I2C1,不是原版资料的 I2C2

正点原子原版 patch(当年的 patches/linux-imx/,已随 vendor 轨退役)、linux-imx BSP imx6ull-aes.dtsi、以及本教程早期版本都把 codec 节点挂在 &i2c2——但 alpha 板实测 WM8960 接在 I2C1 @0x1a:i2cdetect -y 0(I2C1)命中 0x1a(WM8960) + 0x1e(AP3216C),而 i2cdetect -y 1(I2C2)只有 0x5d(goodix)。

挂 I2C2 时 codec probe 第一步 wm8960_reset(I2C 写 0x1a)就 NACK,dmesg 报 wm8960 1-001a: Failed to issue reset → fsl-asoc-card: snd_soc_register_card failed → /proc/asound/cards 只剩 ASRC-M2M 没声卡。把 codec 挪到 &i2c1 立刻就好(实测 card 1: wm8960audio 上线、/dev/snd/pcmC1D0p 出现)。

看到 Failed to issue reset 别急着查供电/焊接,先 i2cdetect -y 0 和 -y 1 看 0x1a 到底在哪条总线。本项目共享 imx6ull-aes.dtsi 已修正:codec 在 &i2c1。

挂在 I2C1 下的 codec 节点(wm8960.c 的 i2c_probe 解析的就是它):

c
/* imx6ull-aes.dtsi —— codec 节点,挂在 &i2c1 下(实测 I2C1,非原版资料的 I2C2)*/
codec: wm8960@1a {
    #sound-dai-cells = <0>;
    compatible = "wlf,wm8960";              /* ← 命中 wm8960.c 的 of_match */
    reg = <0x1a>;                           /* I2C1 上的 7 位从机地址 */

    wlf,shared-lrclk;                       /* LRCM:左右声道 LRCLK 共享 */
    wlf,hp-cfg = <3 2 3>;                   /* 耳机检测 3 参数 */
    wlf,gpio-cfg = <1 3>;                   /* GPIO 配置 2 参数 */

    clocks = <&clks IMX6UL_CLK_SAI2>;       /* MCLK 取自 SAI2 时钟 */
    clock-names = "mclk";                   /* ← wm8960 devm_clk_get("mclk") 找它 */
};

逐属性对应 04 节 i2c_probe:

属性对应 probe 里的含义
compatible "wlf,wm8960"of_device_id 表选中 wm8960.c 驱动
reg <0x1a>i2c_client->addrI2C1 总线上的从机地址(0x1a = 26)
wlf,shared-lrclk:1496 设 LRCM 位左右声道 LRCLK 共享一个引脚
wlf,hp-cfg = <3 2 3>:1525 写 ADDCTL4/2/1耳机检测的 3 个寄存器配置参数
wlf,gpio-cfg = <1 3>:1519 写 IFACE2/ADDCTL4WM8960 GPIO 引脚功能配置
clocks + clock-names "mclk":1441 devm_clk_get("mclk")MCLK 时钟源 = SAI2(12.288MHz)

#sound-dai-cells = <0> 表示这个 codec 节点作为 DAI 引用时不需要额外的 cell(单 DAI codec 都是这样)。clock-names 必须是 "mclk"——和 wm8960.c:1441 的 devm_clk_get(&i2c->dev, "mclk") 字符串一致,codec 才能拿到 MCLK。

三、&sai2(cpu_dai + MCLK 时钟源) ​

SAI2 节点是 cpu_dai 这端,同时是 MCLK 的「发源地」(04 节 时钟树的出口):

c
/* imx6ull-aes.dtsi:372 —— cpu_dai 节点 + MCLK 时钟配置 */
&sai2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_sai2>;            /* 引脚配置,见下一节 */

    assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>, <&clks IMX6UL_CLK_SAI2>;
    assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;   /* SAI2_SEL ← PLL4_AUDIO_DIV */
    assigned-clock-rates = <0>, <12288000>;   /* SAI2 ← 12.288 MHz(48k×256) */

    fsl,sai-mclk-direction-output;           /* MCLK 从 SAI2_MCLK 引脚输出给 codec */
    status = "okay";
};

这三行 assigned-* 就是 04 节 那张时钟树的设备树写法:

  • assigned-clock-parents:SAI2_SEL 的父时钟选 PLL4_AUDIO_DIV(786.432MHz)。
  • assigned-clock-rates = <0>, <12288000>:第一个 0 表示 SAI2_SEL 不设速率(继承父),第二个 12288000 把 SAI2_ROOT 设成 12.288MHz(=786.432MHz / 64)。
  • fsl,sai-mclk-direction-output:让 SAI2 把这个 12.288MHz 从 SAI2_MCLK 引脚送出去,物理上接到 WM8960 的 MCLK 脚。

status = "okay" 启用 SAI2(imx6ul.dtsi 里默认 disabled)。这一段是 alpha 板音频时钟链的核心,配错一个数字(比如 12288000 写成 1228800)就全哑。

四、pinctrl_sai2(JTAG 引脚复用) ​

最后是引脚配置——01 节 说过 SAI2 复用了 JTAG 那组脚,这里看实物:

c
/* imx6ull-aes.dtsi:616 —— SAI2 引脚:5 根信号 + 1 根耳机检测 */
pinctrl_sai2: sai2grp {
    fsl,pins = <
        MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK     0x17088   /* 位时钟(codec 出) */
        MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC     0x17088   /* 帧时钟/LRCLK(codec 出) */
        MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA  0x11088   /* 播放数据(CPU→codec) */
        MX6UL_PAD_JTAG_TCK__SAI2_RX_DATA     0x11088   /* 录音数据(codec→CPU) */
        MX6UL_PAD_JTAG_TMS__SAI2_MCLK        0x17088   /* 主时钟(CPU→codec) */
        MX6UL_PAD_SNVS_TAMPER4__GPIO5_IO04   0x17059   /* 耳机检测,普通 GPIO */
    >;
};

MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK 这种宏名一眼能看出来:「JTAG 的 TDI 脚,复用成 SAI2 的 TX_BCLK 功能」。五个 JTAG 脚(TDI/TDO/TRST/TCK/TMS)正好变成 SAI2 的五根信号线,外加一个 SNVS 域的 GPIO 当耳机检测。

那个 0x17088 / 0x11088 是什么?

是 i.MX IOMUX 控制寄存器的配置值,编码了引脚的驱动强度(DSE)、压摆率(SRE)、上下拉(PUS/PUE/PKE)、复用模式(MUX_MODE)等。0x17088 和 0x11088 的差异主要在上下拉/输入输出方向相关位——TX_BCLK/TX_SYNC/MCLK 是 codec 驱动的输入脚(SAI2 接收),TX_DATA/RX_DATA 是数据脚,配置略有不同。细节属于 pinctrl 子系统(02_pinctrl_gpio 章),这里知道「每根脚的电气属性都由这个值决定」即可。

五、本章的章节级 dts ​

按项目规范,每章在 driver/device_tree/alpha-board/<NN>_tutorial_<topic>/ 下有自己的章节级 dts。本章是 24_tutorial_wm8960_audio/imx6ull-aes-24_tutorial_wm8960_audio.dts:

c
/dts-v1/;
#include "imx6ull.dtsi"
#include "imx6ull-aes.dtsi"          /* ← 音频链路全在这里 */

/ {
    model = "Awesome Embedded Studio IMX6ULL Tutorial 24 - WM8960 Audio";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
};

/* 板级一行硬件配置都不用加:sound-wm8960 / codec / sai2 全在 aes.dtsi 默认启用。
 * 下面 &sai2 显式标 okay 只是风格统一,写不写都 probe。
 * 想做对比实验:改成 status = "disabled",复现 "no soundcards found"。 */
&sai2 {
    status = "okay";
};

和 RTC 章 的章节级 dts 同一个套路:分析型章节因为硬件配置已在共享 dtsi,板级 dts 只做 #include + 显式确认,留个口子给你改 disabled 做对比实验。比如把 &sai2 改成 disabled,fsl-asoc-card 找不到 cpu_dai、devm_snd_soc_register_card 失败,正好复现 06 节 要讲的「无声」现象。

六、确认节点生效 ​

部署 dtb、重启板子后,几条命令确认音频链路 probe 成功:

bash
# codec(I2C 层面)
ls /sys/bus/i2c/devices/ | grep 001a        # 0-001a 应存在(I2C1, 地址 0x1a)

# machine + codec + cpu_dai 都 probe 了?
dmesg | grep -iE "wm8960|fsl-asoc-card|sai2"
# wm8960 0-001a: ...
# fsl-asoc-card fsl-asoc-card.0: ...

# 声卡上线
cat /proc/asound/cards
#  0 [wm8960audio  ]: wm8960-audio - wm8960-audio
#                       (alpha 板的 wm8960-audio 声卡)

ls /dev/snd/                                # controlC0 pcmC0D0c pcmC0D0p timer

看到 card 0 [wm8960audio] 和 /dev/snd/pcmC0D0p(播放 PCM 设备),说明 03 节 的 dai_link 已经成功绑定、声卡上线了。接下来 06 节 就用 aplay/amixer/arecord 让它真正出声。

小结 ​

这一节我们把 alpha 板音频的四个设备树节点逐行解读了:sound-wm8960(machine,定义 cpu/codec/asrc/routing)+ codec: wm8960@1a(codec,I2C 地址 + wlf,* 属性 + MCLK)+ &sai2(cpu_dai + PLL4→12.288MHz 时钟树)+ pinctrl_sai2(JTAG 五脚复用)。这些都在共享 imx6ull-aes.dtsi、默认启用,本章分析型只解读、章节级 dts 只做显式确认。下一节 06 节 是真正的「上板时刻」——修 mainline defconfig、装 alsa-utils、aplay 出声。


v1.0.4-37-gc79a127 · c79a127 · 2026-09-29