4、设备树配置:I2S控制器节点、WM8960节点、DMA通道绑定、时钟与GPIO管脚复用
设备树,说白了就是Linux内核的“硬件说明书”。内核启动时通过它才知道你的板子上挂了什么外设、怎么访问它们。音频驱动调试的第一步,往往就是检查设备树配对了没有。我见过太多“驱动不工作”的案例,最后发现是设备树里一个引脚号写错了。
这一章我们聚焦I2S音频路径上的四个关键配置块:I2S控制器节点、CODEC节点、DMA通道绑定、时钟与GPIO复用。任何一个配错,音频流都出不来。
4.1 I2S控制器节点
先看I2S控制器。在i.MX平台上,它通常叫sai或ssi。以i.MX8M Plus为例,它的SAI3节点长这样:
sai3: sai@308a0000 {
compatible = "fsl,imx8mm-sai", "fsl,imx8mq-sai";
reg = <0x308a0000 0x10000>;
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MP_CLK_SAI3_ROOT>,
<&clk IMX8MP_CLK_SAI3>,
<&clk IMX8MP_CLK_AUDIO_AHB>;
clock-names = "bus", "mclk", "ahb";
dmas = <&sdma2 0 2 0>, <&sdma2 1 2 0>;
dma-names = "rx", "tx";
status = "disabled";
};
这里有几个关键字段:
- compatible:驱动匹配的关键。内核通过它找到对应的驱动文件。
- reg:控制器的寄存器基地址和大小。配错了内核直接panic。
- interrupts:中断号。我遇到过中断号跟原理图对不上,导致音频传输时CPU一直轮询,性能惨不忍睹。
- clocks:时钟源。后面会细说。
- dmas:DMA通道。这是音频数据搬运的“高速公路”。
reg地址和interrupts号。拿原理图和芯片手册一行一行对,别偷懒。我曾经因为一个地址偏移配错了,浪费了整整两天。
4.2 WM8960 CODEC节点
WM8960是I2C从设备,所以它的节点挂在I2C总线下。下面是一个典型配置:
&i2c3 {
status = "okay";
wm8960: audio-codec@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clk IMX8MP_CLK_SAI3_ROOT>;
clock-names = "mclk";
#sound-dai-cells = <0>;
wlf,shared-lrclk;
AVDD-supply = <®_audio_3v3>;
DBVDD-supply = <®_audio_3v3>;
DCVDD-supply = <®_audio_1v8>;
};
};
注意几个容易踩坑的地方:
- reg = <0x1a>:这是WM8960的I2C地址。默认是0x1a,但如果你的板子上ADDR引脚接了高电平,地址会变成0x1b。查原理图确认。
- clocks:WM8960需要MCLK。这个时钟通常来自I2S控制器的输出,或者外部晶振。频率一般是12.288MHz或24.576MHz。
- wlf,shared-lrclk:这个属性告诉驱动左右声道共用LRCLK。有些板子不配这个,音频会变调。
- 供电节点:WM8960有多个电源域,必须都配好。少一个,CODEC可能初始化失败。
i2cdetect扫一下总线,确认地址唯一。
4.3 DMA通道绑定
DMA是音频数据流的“搬运工”。没有DMA,CPU得亲自搬数据,音频卡顿是必然的。设备树里DMA通道绑定通常这样写:
dmas = <&sdma2 0 2 0>, <&sdma2 1 2 0>;
dma-names = "rx", "tx";
每个<&sdma2 0 2 0>这样的元组,含义如下:
| 字段 | 含义 | 说明 |
|---|---|---|
| sdma2 | DMA控制器句柄 | 指向设备树中定义的DMA控制器节点 |
| 0 | 通道号 | 0表示接收通道,1表示发送通道 |
| 2 | 请求号 | 对应SAI外设的DMA请求线编号 |
| 0 | 优先级 | 0为默认优先级 |
请求号这个参数特别容易配错。每个外设的DMA请求号在芯片手册里有明确表格。比如SAI3的发送请求号是2,接收请求号是3。你写反了,数据流就断了。
4.4 时钟与GPIO管脚复用
时钟配置是音频驱动里最绕的部分。一个典型的I2S音频路径涉及多个时钟:
- MCLK:主时钟,CODEC和I2S控制器共用。频率通常是采样率的256倍或384倍。
- BCLK:位时钟,每个音频数据位一个脉冲。频率 = 采样率 × 位宽 × 通道数。
- LRCLK:帧时钟,左右声道切换。频率 = 采样率。
设备树里时钟配置示例:
clocks = <&clk IMX8MP_CLK_SAI3_ROOT>,
<&clk IMX8MP_CLK_SAI3>,
<&clk IMX8MP_CLK_AUDIO_AHB>;
clock-names = "bus", "mclk", "ahb";
这里bus时钟用于SAI控制器的寄存器访问,mclk是音频主时钟,ahb是DMA访问用的总线时钟。三个时钟缺一不可。
GPIO管脚复用,说白了就是把芯片的物理引脚配置成I2S功能。在设备树里通过pinctrl实现:
&iomuxc {
pinctrl_sai3: sai3grp {
fsl,pins = <
MX8MP_IOMUXC_SAI3_TXFS__AUDIOMIX_SAI3_TX_SYNC 0xd6
MX8MP_IOMUXC_SAI3_TXC__AUDIOMIX_SAI3_TX_BCLK 0xd6
MX8MP_IOMUXC_SAI3_TXD__AUDIOMIX_SAI3_TX_DATA0 0xd6
MX8MP_IOMUXC_SAI3_RXD__AUDIOMIX_SAI3_RX_DATA0 0xd6
MX8MP_IOMUXC_SAI3_MCLK__AUDIOMIX_SAI3_MCLK 0xd6
>;
};
};
每个引脚配置的最后一个数字(如0xd6)是电气特性配置,包括驱动能力、上下拉、速度等。这个值配错了,可能导致信号质量差,音频出现爆音。
cat /sys/kernel/debug/gpio查看引脚状态,确认引脚确实被配置成了I2S功能。再用示波器量MCLK和BCLK波形。如果波形不对,八成是pinctrl配置有问题。
4.5 整体配置关系图
下面这张图展示了设备树中各节点之间的依赖关系。我建议你把它打印出来贴在工位上,调试时对照着看。
4.6 调试检查清单
每次配完设备树,我建议按这个清单逐项检查:
- I2C地址:用
i2cdetect -y <bus>确认WM8960在预期地址上响应。 - 时钟频率:用示波器量MCLK引脚,确认频率正确(通常是12.288MHz或24.576MHz)。
- 引脚复用:检查
/sys/kernel/debug/pinctrl/<pinctrl-device>/pinmux-pins,确认I2S引脚被正确配置。 - DMA通道:查看
/proc/interrupts,确认DMA中断在音频传输时有计数。 - 设备树编译:确保
make dtbs没有报错,生成的.dtb文件被正确加载。
dmesg | grep -i asoc查看ALSA SoC框架的初始化日志。如果看到Failed to bind之类的错误,八成是设备树的问题。