3. 设备树基础与I2C配置

设备树,说白了就是一块「硬件描述板」。Linux 内核通过它才知道你的板子上接了哪些外设、地址是多少、用哪个中断。我刚开始接触设备树时也觉得这东西很玄乎,后来调过几块板子就明白了——它就是个结构化的配置文件。

这一章我们聚焦三个事:设备树语法怎么用、WM8960 节点怎么写、I2C 控制器怎么配。最后编译验证,确保改动生效。

3.1 设备树语法速览

设备树文件后缀是 .dts,编译后生成 .dtb。基本单元叫「节点」,每个节点描述一个硬件设备。

/dts-v1/;

/ {
    model = "My Audio Board";
    compatible = "vendor,board";

    i2c1: i2c@40012000 {
        compatible = "st,stm32-i2c1";
        reg = <0x40012000 0x400>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";

        wm8960: codec@1a {
            compatible = "wlf,wm8960";
            reg = <0x1a>;
        };
    };
};

几个关键点:

  • compatible:驱动匹配的关键字,格式通常是「厂商,设备名」。
  • reg:地址信息。对 I2C 设备来说就是从机地址。
  • status:"okay" 表示启用,"disabled" 表示关闭。
  • #address-cells 和 #size-cells:定义子节点 reg 的格式。I2C 总线一般用 <1 0>。
个人习惯:我写设备树时喜欢先确认 datasheet 里的设备地址。WM8960 的 I2C 地址是 0x1a(ADDR 引脚接地时),千万别写成 0x1b,否则内核根本找不到它。

3.2 在设备树中描述 WM8960 节点

WM8960 是一个音频编解码芯片,挂在 I2C 总线上。我们需要告诉内核:它的地址、用哪个 I2C 控制器、以及音频相关的时钟和引脚。

下面是一个完整的 WM8960 节点示例:

&i2c1 {
    status = "okay";
    clock-frequency = <100000>;

    wm8960: audio-codec@1a {
        compatible = "wlf,wm8960";
        reg = <0x1a>;
        #sound-dai-cells = <0>;

        clocks = <&clk IMX6QDL_CLK_CKO>;
        clock-names = "mclk";

        AVDD-supply = <&reg_3p3v>;
        DVDD-supply = <&reg_3p3v>;

        wlf,shared-lrclk;
    };
};

解释一下:

  • clock-frequency:I2C 总线速度,100kHz 是标准模式。
  • #sound-dai-cells:音频 DAI 接口的配置,一般设为 0。
  • clocks:主时钟 MCLK 的来源。WM8960 需要外部提供 12.288MHz 或 24.576MHz。
  • supply:电源域,告诉内核这个芯片用哪路供电。
  • wlf,shared-lrclk:厂商自定义属性,表示左右声道时钟共享。
我曾经踩过的坑:忘记配 clocks 属性,结果 WM8960 没有 MCLK,I2S 接口完全没声音。查了两天才发现是时钟树没打开。所以时钟配置一定要检查。

3.3 配置 I2C 控制器与时钟

WM8960 的 I2C 控制器节点通常在 SoC 的 .dtsi 文件里已经定义好了。我们只需要在板级 .dts 文件中启用它,并配置 pinmux。

以 i.MX6Q 为例:

&i2c1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";
};

&iomuxc {
    pinctrl_i2c1: i2c1grp {
        fsl,pins = <
            MX6QDL_PAD_CSI0_DAT8__I2C1_SDA  0x4001b8b1
            MX6QDL_PAD_CSI0_DAT9__I2C1_SCL  0x4001b8b1
        >;
    };
};

时钟部分,WM8960 的 MCLK 通常来自 SoC 的某个时钟输出引脚。比如 i.MX6Q 的 CKO 引脚:

&clks {
    assigned-clocks = <&clk IMX6QDL_CLK_CKO>;
    assigned-clock-parents = <&clk IMX6QDL_CLK_OSC>;
    assigned-clock-rates = <12288000>;
};

这里把 CKO 时钟设为 12.288MHz,正好是 WM8960 需要的 MCLK 频率。

时钟名称 典型频率 用途
MCLK 12.288 MHz / 24.576 MHz 编解码器主时钟
BCLK 1.536 MHz ~ 6.144 MHz I2S 位时钟
LRCLK 48 kHz / 96 kHz 左右声道帧时钟
关键点:MCLK 必须与采样率成整数倍关系。48kHz 采样率下,MCLK 通常是 12.288MHz(256 × 48kHz)。如果 MCLK 不对,WM8960 内部 PLL 锁不住,音频就会失真。

3.4 编译并验证设备树

设备树写完后,需要编译成 .dtb 文件。方法很简单:

# 进入内核源码目录
make ARCH=arm dtbs

# 或者单独编译某个设备树
make ARCH=arm imx6q-sabresd.dtb

编译完成后,把 .dtb 文件复制到启动分区,重启系统。

验证设备树是否生效,我一般用这几个方法:

  1. 查看 /proc/device-tree:
ls /proc/device-tree/i2c1/audio-codec@1a/

如果能看到 compatible、reg 等文件,说明节点被正确解析了。

  1. 检查内核启动日志:
dmesg | grep wm8960

正常会看到类似:

wm8960 0-001a: WM8960 audio codec initialized
  1. 使用 i2cdetect 扫描总线:
i2cdetect -y 1

如果地址 0x1a 显示为 UU,表示设备已被驱动占用。

我建议:每次修改设备树后,先用 dtc -I dtb -O dts 反编译 .dtb,检查生成的 .dts 是否符合预期。这样可以避免一些低级错误。

3.5 本章知识结构图

下面这张 SVG 图梳理了设备树配置 WM8960 的完整链路:

WM8960 设备树配置流程 WM8960 设备树配置链路 设备树源文件 .dts / .dtsi I2C 控制器节点 i2c@40012000 WM8960 节点 codec@1a 时钟配置 MCLK 12.288MHz pinmux 配置 SDA / SCL 引脚 电源配置 AVDD / DVDD 编译验证 make dtbs → dmesg | grep wm8960 → i2cdetect 提示:设备树配置完成后,务必检查时钟频率和 I2C 地址,这两处最容易出错

从图中可以看到,WM8960 的设备树配置涉及 I2C 控制器、时钟、pinmux 和电源四个维度。任何一个环节出问题,音频驱动都可能无法正常工作。

总结一下:设备树是硬件与驱动之间的桥梁。写 WM8960 节点时,compatible、reg、clocks 这三个属性是必须的。编译后用 dmesg 和 i2cdetect 验证,确保节点被正确解析。这一步做好了,后面的驱动开发才会顺利。