2. 设备树基础:语法、节点、属性与核心概念

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用节点(node)和属性(property)来描述系统中的设备、总线、中断控制器、时钟、GPIO 等硬件信息。在嵌入式 Linux 中,设备树是驱动与硬件之间的“桥梁”,尤其对于 V4L2 子系统,pinctrl 和时钟配置的正确性直接依赖于设备树的描述。

2.1 设备树语法概述

设备树源文件(.dts)采用树形结构,以“/”为根节点,每个节点代表一个硬件设备或总线。基本语法规则如下:

  • 节点(node):以 node-name@unit-address 形式定义,unit-address 通常为寄存器或总线地址。
  • 属性(property):键值对,值可以是字符串、整数、数组、二进制数据等。
  • 标签(label):为节点起别名,便于引用,如 i2c0: i2c@ff000000。
  • 包含文件:使用 #include 引入 .dtsi 文件,实现 SoC 级描述复用。
关键点:设备树不是可执行代码,而是静态描述。内核在启动时解析设备树,生成 platform_device 等设备模型。

2.2 节点与属性详解

每个节点可以包含子节点和属性。以下是 V4L2 摄像头设备树中常见的节点与属性示例:

/ {
    model = "Embedded Camera Board";
    compatible = "vendor,board-rev1";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        i2c2: i2c@ff130000 {
            compatible = "snps,designware-i2c";
            reg = <0xff130000 0x1000>;
            interrupts = <0 38 4>;
            clocks = <&clk_i2c2>;
            #address-cells = <1>;
            #size-cells = <0>;

            camera_sensor: camera@10 {
                compatible = "sony,imx219";
                reg = <0x10>;
                clocks = <&clk_cam_mclk>;
                clock-names = "xvclk";
                pinctrl-names = "default", "sleep";
                pinctrl-0 = <&cam_pins_default>;
                pinctrl-1 = <&cam_pins_sleep>;
                reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
                port {
                    sensor_ep: endpoint {
                        remote-endpoint = <&csi_ep>;
                        data-lanes = <1 2>;
                        clock-noncontinuous;
                    };
                };
            };
        };
    };
};

2.3 核心属性:reg、interrupts、clocks、pinctrl

对于 V4L2 驱动,以下四个属性最为关键:

属性 作用 V4L2 相关说明
reg 描述设备地址空间(寄存器基址+长度) 用于 i2c 设备地址或 CSI 控制器寄存器映射
interrupts 指定中断号、触发类型 CSI 接收完成中断、传感器帧同步中断
clocks / clock-names 引用时钟源并命名 MCLK(主时钟)频率直接影响传感器帧率
pinctrl-names / pinctrl-0 配置引脚复用状态 确保 I2C、CSI 数据线、时钟线引脚功能正确
实战提示:在调试 V4L2 摄像头时,如果设备无法识别,首先检查 reg 中的 I2C 地址是否与传感器数据手册一致;其次检查 clocks 是否已使能且频率正确。

2.4 设备树层级与 V4L2 设备关系

下图展示了设备树中摄像头传感器、CSI 控制器、时钟和 pinctrl 之间的层级关系:

设备树层级与 V4L2 设备关系图 / (根节点) soc i2c@ff130000 csi@ff150000 camera@10 (IMX219) port (endpoint) clk_cam_mclk pinctrl (cam_pins) 图例说明 实线:父子节点关系(总线/设备层级) 虚线:引用关系(时钟、pinctrl、remote-endpoint)

2.5 属性值格式与编码

设备树属性值有多种编码方式,驱动解析时必须匹配:

属性值类型 DTS 写法 内核解析 API 示例
32 位整数 <0x10> of_property_read_u32() I2C 设备地址
64 位整数 <0x00000000 0x10000000> of_property_read_u64() 64 位寄存器地址
字符串 "sony,imx219" of_property_read_string() compatible 匹配
字符串数组 "default", "sleep" of_property_read_string_index() pinctrl-names
二进制数据 [01 02 03 04] of_get_property() 校准数据、MAC 地址
phandle + 参数 <&clk_i2c2 0> of_parse_phandle_with_args() 时钟、中断、GPIO 引用

2.6 #address-cells 与 #size-cells

这两个属性决定了 reg 属性中地址和长度的编码单元数:

  • #address-cells = <1>:地址用 1 个 32 位单元表示。
  • #size-cells = <1>:长度用 1 个 32 位单元表示。
  • 对于 64 位地址空间,使用 <2>。
常见错误:如果父节点的 #address-cells 与子节点 reg 的单元数不匹配,内核将无法正确解析地址,导致设备 probe 失败。例如,父节点设置 #address-cells = <2>,但子节点 reg = <0x10> 只给了一个单元,内核会报错。

2.7 中断属性详解

中断属性 interrupts 的格式依赖于父中断控制器的 #interrupt-cells:

// GIC(通用中断控制器)典型配置
interrupt-parent = <&gic>;
interrupts = <0 38 4>;
// 含义:SPI 类型(0),中断号 38,高电平触发(4)

// 简单中断控制器
interrupts = <10 2>;
// 含义:中断号 10,下降沿触发(2)

对于 V4L2 摄像头,CSI 控制器通常使用 SPI 中断,而传感器本身通过 I2C 通信,不直接使用中断线(但部分传感器支持帧同步中断)。

2.8 设备树编译与调试

设备树源文件需要编译为二进制 .dtb 文件供内核使用:

# 编译设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 反编译查看
dtc -I dtb -O dts -o myboard.dts myboard.dtb

# 检查设备树是否被正确加载
ls /proc/device-tree/
cat /proc/device-tree/model
调试技巧:在 V4L2 驱动 probe 函数中添加 dev_info() 打印 of_node 的 name、compatible、reg 值,与设备树对比,快速定位属性解析问题。

2.9 设备树与 V4L2 驱动的绑定流程

下图展示了从设备树描述到 V4L2 设备注册的完整流程:

设备树到 V4L2 设备绑定流程 步骤 1 内核解析 .dtb 步骤 2 创建 device_node 步骤 3 匹配 compatible 步骤 4 probe() 调用 步骤 5 解析 reg/clocks/pinctrl 步骤 6 注册 V4L2 设备 关键依赖 pinctrl 配置引脚 时钟使能 MCLK

2.10 常见设备树错误与排查

错误现象 可能原因 排查方法
设备不 probe compatible 不匹配或 reg 地址错误 检查 /sys/bus/platform/devices/ 或 /sys/bus/i2c/devices/
中断不触发 interrupts 属性格式错误或中断号不对 查看 /proc/interrupts 确认中断号
时钟频率异常 clocks 引用错误或时钟未使能 使用 clk_summary 调试文件
引脚功能异常 pinctrl 配置错误或引脚复用冲突 检查 /sys/kernel/debug/pinctrl/
最佳实践:在开发阶段,建议在设备树中添加 status = "okay"; 显式使能节点;对于调试,可以使用 of_platform_populate() 手动触发设备创建,验证设备树解析逻辑。