设备树配置:I2S控制器节点、WM8960 codec节点、dai-link与sound-card节点、GPIO与时钟配置

设备树配置,说白了就是告诉内核你的硬件长什么样。我刚开始接触音频驱动时,总觉得设备树就是填几个寄存器地址,后来踩了不少坑才明白——设备树写不对,驱动跑起来全是玄学。

今天咱们就把WM8960相关的设备树节点拆开揉碎了讲。你跟着我过一遍,以后遇到其他codec也能举一反三。

4.1 I2S控制器节点配置

I2S控制器节点,通常在你的SoC dtsi文件里已经定义好了。我们只需要在板级dts里把状态打开,配好引脚和时钟。

以i.MX6ULL为例,I2S控制器叫sai2:

&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_PLL3_PFD3;
    assigned-clock-rates = <0>, <12288000>;
    status = "okay";
};

这里有几个关键点:

  • pinctrl:引脚复用,必须和原理图对得上。我遇到过好几次因为引脚复用冲突导致I2S无时钟输出的问题。
  • assigned-clocks:指定时钟源和频率。WM8960主时钟通常需要12.288MHz或24.576MHz。
  • status = "okay":别忘了打开,很多人漏掉这一步。
注意:I2S控制器的时钟频率必须与采样率成整数倍关系。比如44.1kHz采样率,MCLK通常是256fs = 11.2896MHz。如果你配成12MHz,codec内部PLL会锁不住,声音就变调了。

4.2 WM8960 codec节点配置

codec节点挂在I2C总线下。WM8960的I2C地址是0x1A(7位地址)。

&i2c2 {
    wm8960: wm8960@1a {
        compatible = "wlf,wm8960";
        reg = <0x1a>;
        clocks = <&clks IMX6UL_CLK_SAI2>;
        clock-names = "mclk";
        wlf,shared-lrclk;
        
        AVDD-supply = <&reg_3p3v>;
        DVDD-supply = <&reg_1p8v>;
    };
};

我个人习惯把codec的电源节点也配好,虽然有些板子不配也能工作,但稳定性会差很多。曾经有个项目,WM8960偶尔不出声,查了两天才发现是DVDD纹波太大,设备树里加上电源约束后问题就解决了。

属性 说明 常见取值
compatible 驱动匹配字符串 wlf,wm8960
reg I2C设备地址 0x1a
clocks 主时钟句柄 参考SoC时钟树
wlf,shared-lrclk 左右声道共用LRCLK 无值属性
小技巧:WM8960的寄存器默认值不一定适合你的板子。我建议在驱动probe之后,先读一遍寄存器确认硬件状态。设备树里可以加wlf,hp-cfg等自定义属性来配置耳机检测。

4.3 dai-link与sound-card节点

这是音频设备树里最绕的部分。dai-link把CPU侧的I2S控制器和codec连接起来,sound-card则是整个音频子系统的顶层容器。

/ {
    sound {
        compatible = "simple-audio-card";
        simple-audio-card,name = "wm8960-audio";
        simple-audio-card,format = "i2s";
        simple-audio-card,bitclock-master = <&cpu_dai>;
        simple-audio-card,frame-master = <&cpu_dai>;
        simple-audio-card,widgets =
            "Headphone", "Headphone Jack",
            "Speaker", "External Speaker";
        simple-audio-card,routing =
            "Headphone Jack", "HP_L",
            "Headphone Jack", "HP_R",
            "External Speaker", "SPK_LN",
            "External Speaker", "SPK_RP";

        cpu_dai: simple-audio-card,cpu {
            sound-dai = <&sai2>;
        };

        codec_dai: simple-audio-card,codec {
            sound-dai = <&wm8960>;
        };
    };
};

这里我重点说几个容易出错的地方:

  • bitclock-master和frame-master:必须指定谁做主控。通常让CPU做master,codec做slave。如果配反了,I2S总线上的BCLK和LRCLK就乱套了。
  • routing:音频路径映射。很多人不配这个,结果耳机没声音。WM8960内部有多个混音器,必须把外部插孔和内部通路连起来。
  • sound-dai:引用的是I2S控制器和codec节点,不是I2C控制器。
核心要点:sound-card节点里的simple-audio-card,format必须和codec配置一致。WM8960支持I2S、左对齐、右对齐、DSP模式。默认用I2S格式,如果改成其他格式,两边都要改。

4.4 GPIO与时钟配置

GPIO在音频系统里通常用于复位、电源使能、中断检测。WM8960有个复位引脚,我建议在设备树里配好GPIO控制。

&gpio1 {
    audio_rst: audio-rst {
        gpio-hog;
        gpios = <5 GPIO_ACTIVE_LOW>;
        output-low;
        line-name = "WM8960-RST";
    };
};

时钟配置是音频调试的重灾区。WM8960需要MCLK,这个时钟从哪里来?

  • SoC内部PLL输出:最常见,通过assigned-clocks配置。
  • 外部晶振:有些板子用24.576MHz晶振直接给codec。
  • I2S总线BCLK恢复:从BCLK恢复MCLK,但需要codec内部PLL支持。

我记得有个项目,板子上MCLK走线太长导致信号质量差,codec偶尔失锁。后来在设备树里把MCLK频率从12.288MHz降到11.2896MHz,问题就解决了。嗯,有时候降频比升频更管用。

避坑指南:我曾经在设备树里配了MCLK,但忘了配assigned-clock-parents,结果内核用了默认的24MHz振荡器。WM8960内部PLL分频后得不到标准采样率,声音一直有杂音。查了两天才发现是时钟源选错了。

4.5 完整设备树示例

把上面几部分拼起来,就是一个完整的音频设备树配置:

/dts-v1/;
#include "imx6ull.dtsi"

/ {
    model = "WM8960 Audio Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    sound {
        compatible = "simple-audio-card";
        simple-audio-card,name = "wm8960-audio";
        simple-audio-card,format = "i2s";
        simple-audio-card,bitclock-master = <&cpu_dai>;
        simple-audio-card,frame-master = <&cpu_dai>;
        simple-audio-card,widgets =
            "Headphone", "Headphone Jack",
            "Speaker", "External Speaker";
        simple-audio-card,routing =
            "Headphone Jack", "HP_L",
            "Headphone Jack", "HP_R",
            "External Speaker", "SPK_LN",
            "External Speaker", "SPK_RP";

        cpu_dai: simple-audio-card,cpu {
            sound-dai = <&sai2>;
        };

        codec_dai: simple-audio-card,codec {
            sound-dai = <&wm8960>;
        };
    };
};

&i2c2 {
    wm8960: wm8960@1a {
        compatible = "wlf,wm8960";
        reg = <0x1a>;
        clocks = <&clks IMX6UL_CLK_SAI2>;
        clock-names = "mclk";
        wlf,shared-lrclk;
        
        AVDD-supply = <&reg_3p3v>;
        DVDD-supply = <&reg_1p8v>;
    };
};

&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_PLL3_PFD3>;
    assigned-clock-rates = <0>, <12288000>;
    status = "okay";
};

&iomuxc {
    pinctrl_sai2: sai2grp {
        fsl,pins = <
            MX6UL_PAD_CSI_DATA00__SAI2_DATA_I    0x1b0b0
            MX6UL_PAD_CSI_DATA01__SAI2_DATA_O    0x1b0b0
            MX6UL_PAD_CSI_DATA02__SAI2_TX_BCLK   0x1b0b0
            MX6UL_PAD_CSI_DATA03__SAI2_TX_SYNC   0x1b0b0
            MX6UL_PAD_CSI_DATA04__SAI2_TX_DATA   0x1b0b0
            MX6UL_PAD_CSI_DATA05__SAI2_RX_DATA   0x1b0b0
        >;
    };
};
WM8960音频设备树结构图 sound-card dai-link CPU DAI (sai2) Codec DAI (wm8960) I2S控制器节点 WM8960 codec节点 pinctrl + clocks + GPIO 共同构成完整音频设备树

这张图展示了设备树各节点的层级关系。从上到下依次是:sound-card作为顶层容器,下面挂dai-link,dai-link再分出CPU侧和Codec侧。最底层是具体的硬件节点和配置属性。

调试建议:设备树编译后用dtc -I dtb -O dts反编译,检查节点是否被正确覆盖。我经常用ls /proc/device-tree/sound/查看内核实际解析的设备树内容,比看源码快多了。

设备树配置说难不难,说简单也不简单。关键是把每个属性的含义搞清楚,再结合原理图一一对应。你按照这个模板去配,WM8960的音频设备树基本不会出大问题。