设备树配置: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":别忘了打开,很多人漏掉这一步。
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 = <®_3p3v>;
DVDD-supply = <®_1p8v>;
};
};
我个人习惯把codec的电源节点也配好,虽然有些板子不配也能工作,但稳定性会差很多。曾经有个项目,WM8960偶尔不出声,查了两天才发现是DVDD纹波太大,设备树里加上电源约束后问题就解决了。
| 属性 | 说明 | 常见取值 |
|---|---|---|
| compatible | 驱动匹配字符串 | wlf,wm8960 |
| reg | I2C设备地址 | 0x1a |
| clocks | 主时钟句柄 | 参考SoC时钟树 |
| wlf,shared-lrclk | 左右声道共用LRCLK | 无值属性 |
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控制器。
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,问题就解决了。嗯,有时候降频比升频更管用。
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 = <®_3p3v>;
DVDD-supply = <®_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
>;
};
};
这张图展示了设备树各节点的层级关系。从上到下依次是:sound-card作为顶层容器,下面挂dai-link,dai-link再分出CPU侧和Codec侧。最底层是具体的硬件节点和配置属性。
dtc -I dtb -O dts反编译,检查节点是否被正确覆盖。我经常用ls /proc/device-tree/sound/查看内核实际解析的设备树内容,比看源码快多了。
设备树配置说难不难,说简单也不简单。关键是把每个属性的含义搞清楚,再结合原理图一一对应。你按照这个模板去配,WM8960的音频设备树基本不会出大问题。