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可能初始化失败。
注意:WM8960的I2C地址不要跟总线上其他设备冲突。我遇到过I2C地址0x1a同时被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。你写反了,数据流就断了。

避坑指南:我曾经在移植BSP时,把SAI的DMA请求号配成了SPI的。结果音频驱动加载正常,但一播放声音就死机。查了两天才发现是DMA请求号冲突。记住:每个外设的DMA请求号是唯一的,不能共用。

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 整体配置关系图

下面这张图展示了设备树中各节点之间的依赖关系。我建议你把它打印出来贴在工位上,调试时对照着看。

设备树音频节点依赖关系 根节点 / I2C 控制器 SAI3 (I2S) SDMA2 控制器 WM8960 CODEC pinctrl 管脚复用 DMA 通道 时钟系统 (MCLK / BCLK / LRCLK) 音频数据流 (I2S 总线) 每个节点都依赖正确的时钟、引脚复用和DMA通道。一个环节出错,整个音频链路就断了。

4.6 调试检查清单

每次配完设备树,我建议按这个清单逐项检查:

  1. I2C地址:用i2cdetect -y <bus>确认WM8960在预期地址上响应。
  2. 时钟频率:用示波器量MCLK引脚,确认频率正确(通常是12.288MHz或24.576MHz)。
  3. 引脚复用:检查/sys/kernel/debug/pinctrl/<pinctrl-device>/pinmux-pins,确认I2S引脚被正确配置。
  4. DMA通道:查看/proc/interrupts,确认DMA中断在音频传输时有计数。
  5. 设备树编译:确保make dtbs没有报错,生成的.dtb文件被正确加载。
重要提醒:设备树配置错误不会导致内核崩溃,但音频设备可能无法注册。用dmesg | grep -i asoc查看ALSA SoC框架的初始化日志。如果看到Failed to bind之类的错误,八成是设备树的问题。