2. 设备树基础:设备树(DTS)语法、节点与属性
设备树(Device Tree,DT)是嵌入式 Linux 系统中用于描述硬件拓扑结构的一种数据结构。它采用树形节点层次结构,将 CPU、内存、外设、中断控制器、GPIO 控制器等硬件资源以节点和属性的形式呈现给内核。在摄像头驱动开发中,设备树是连接硬件寄存器映射、电源管理(Regulator)和 GPIO 控制的关键桥梁。
compatible 属性完成绑定。
2.1 设备树语法基础
设备树使用一种类似于 C 语言的结构化文本格式,以分号结尾,支持宏定义和包含文件(#include)。其基本单元是节点(node)和属性(property)。
/dts-v1/;
/ {
model = "Embedded Camera Board v1.0";
compatible = "vendor,camera-board";
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1GB RAM */
};
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
i2c@ffd00000 {
compatible = "vendor,i2c-controller";
reg = <0xffd00000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
camera_sensor: camera@3c {
compatible = "vendor,camera-sensor-v1";
reg = <0x3c>;
clocks = <&clkc 15>;
reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
power-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>;
avdd-supply = <®_camera_avdd>;
dvdd-supply = <®_camera_dvdd>;
};
};
};
};
- 根节点:
/表示设备树的根,所有设备节点都挂载在根下。 - 节点名称:格式为
node-name@unit-address,其中unit-address通常对应设备基地址或 I2C 从机地址。 - 属性值:可以是字符串、32 位整数数组、64 位整数数组、二进制数据或字符串列表。
- 标签(label):如
camera_sensor:,用于在其他节点中通过&camera_sensor引用。
2.2 节点与属性详解
设备树中的每个节点代表一个硬件设备或总线,属性则描述设备的配置参数。以下表格总结了摄像头驱动开发中最常用的属性及其用途。
| 属性名称 | 格式 | 用途说明 | 摄像头驱动示例 |
|---|---|---|---|
compatible |
字符串列表 | 驱动匹配标识,内核通过该属性查找对应的 platform_driver | "vendor,camera-sensor-v1" |
reg |
整数数组 | 描述设备地址(内存基地址、I2C 从机地址等) | <0x3c> (I2C 地址) |
interrupts |
整数数组 | 描述中断号、触发类型 | <GIC_SPI 55 IRQ_TYPE_EDGE_RISING> |
gpios / *-gpios |
GPIO 描述符 | 描述 GPIO 引脚、标志(高/低有效) | reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW> |
clocks |
时钟句柄数组 | 引用时钟控制器和时钟 ID | <&clkc 15> |
*-supply |
电源句柄 | 引用 Regulator 节点,描述电源域 | avdd-supply = <®_camera_avdd> |
status |
字符串 | 设备状态:"okay"、"disabled"、"fail" |
status = "okay"; |
#address-cells / #size-cells |
整数 | 定义子节点中 reg 属性的地址和长度字段宽度 | #address-cells = <1>; #size-cells = <0>; |
- compatible 顺序:内核优先匹配列表中的第一个字符串,建议将最具体的型号放在前面,通用型号放在后面。
- reg 宽度:
#address-cells和#size-cells必须在父节点中定义,否则子节点的 reg 解析会出错。 - GPIO 标志:
GPIO_ACTIVE_HIGH(0) 和GPIO_ACTIVE_LOW(1) 定义在<dt-bindings/gpio/gpio.h>中,务必包含头文件。
2.3 compatible 属性:驱动匹配的核心
compatible 属性是设备树与驱动之间建立联系的唯一标识。当内核遍历设备树节点时,会将该节点的 compatible 值与已注册的 platform_driver 中的 of_match_table 进行匹配。
// 驱动端匹配表示例
static const struct of_device_id camera_sensor_of_match[] = {
{ .compatible = "vendor,camera-sensor-v1", .data = &sensor_v1_config },
{ .compatible = "vendor,camera-sensor-v2", .data = &sensor_v2_config },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, camera_sensor_of_match);
static struct platform_driver camera_sensor_driver = {
.probe = camera_sensor_probe,
.remove = camera_sensor_remove,
.driver = {
.name = "camera_sensor",
.of_match_table = camera_sensor_of_match,
},
};
module_platform_driver(camera_sensor_driver);
- 内核解析 .dtb,为每个节点创建
struct device_node。 - 总线(platform_bus)注册驱动时,遍历所有节点,调用
of_match_node()比较compatible字符串。 - 匹配成功后,内核调用驱动的
probe()函数,并将struct device_node传递给驱动。 - 驱动通过
of_property_read_*系列 API 读取节点中的其他属性(reg、gpios、supply 等)。
2.4 reg 属性:地址空间描述
reg 属性用于描述设备在总线上的地址范围。对于内存映射设备,它表示基地址和长度;对于 I2C/SPI 设备,它表示从机地址。其格式由父节点的 #address-cells 和 #size-cells 决定。
| 总线类型 | #address-cells | #size-cells | reg 示例 | 含义 |
|---|---|---|---|---|
| 内存映射 (Memory-mapped) | 1 | 1 | <0xffd00000 0x1000> |
基地址 0xffd00000,长度 0x1000 (4KB) |
| I2C | 1 | 0 | <0x3c> |
I2C 从机地址 0x3c |
| SPI | 1 | 0 | <0x0> |
SPI 片选编号 0 |
| 64 位地址 | 2 | 1 | <0x0 0xffd00000 0x0 0x1000> |
高 32 位 + 低 32 位地址,长度 64 位 |
// 驱动中读取 reg 属性的典型方式
struct resource *res;
void __iomem *base;
// 方式1:使用 platform_get_resource (推荐)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
// 方式2:使用 of 函数直接读取
u32 addr, size;
of_property_read_u32_index(np, "reg", 0, &addr);
of_property_read_u32_index(np, "reg", 1, &size);
2.5 中断描述
设备树使用 interrupts 属性描述设备的中断资源,同时需要 interrupt-parent 属性指定中断控制器。对于摄像头驱动,通常使用 interrupts-extended 来显式指定中断控制器,避免依赖父节点继承。
// 标准中断描述
camera_sensor: camera@3c {
compatible = "vendor,camera-sensor-v1";
reg = <0x3c>;
interrupt-parent = <&gic>; // 中断控制器
interrupts = <GIC_SPI 55 IRQ_TYPE_EDGE_RISING>;
// GIC_SPI: 共享外设中断, 55: 中断号, IRQ_TYPE_EDGE_RISING: 上升沿触发
};
// 使用 interrupts-extended 显式指定
camera_sensor: camera@3c {
compatible = "vendor,camera-sensor-v1";
reg = <0x3c>;
interrupts-extended = <&gic GIC_SPI 55 IRQ_TYPE_EDGE_RISING>;
};
<dt-bindings/interrupt-controller/irq.h>):
IRQ_TYPE_EDGE_RISING(1) — 上升沿触发IRQ_TYPE_EDGE_FALLING(2) — 下降沿触发IRQ_TYPE_LEVEL_HIGH(4) — 高电平触发IRQ_TYPE_LEVEL_LOW(8) — 低电平触发
IRQ_TYPE_EDGE_RISING 或 IRQ_TYPE_LEVEL_HIGH 来报告帧同步信号。
2.6 GPIO 描述
GPIO 在设备树中使用 *-gpios 属性描述,格式为 <&gpio_controller pin_number flags>。摄像头驱动中常见的 GPIO 包括复位引脚、电源使能引脚、帧同步中断引脚等。
// 摄像头传感器 GPIO 描述
camera_sensor: camera@3c {
compatible = "vendor,camera-sensor-v1";
reg = <0x3c>;
// 复位引脚:GPIO1 第7脚,低电平有效
reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
// 电源使能引脚:GPIO2 第5脚,高电平有效
power-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>;
// 帧同步中断引脚:GPIO3 第2脚,上升沿触发
vsync-gpios = <&gpio3 2 GPIO_ACTIVE_HIGH>;
};
// 驱动中获取 GPIO 并操作
#include <linux/gpio/consumer.h>
struct gpio_desc *reset_gpio, *power_gpio;
// 获取 GPIO 描述符
reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
power_gpio = devm_gpiod_get(&pdev->dev, "power", GPIOD_OUT_LOW);
if (IS_ERR(reset_gpio) || IS_ERR(power_gpio)) {
dev_err(&pdev->dev, "Failed to get GPIOs\n");
return PTR_ERR(reset_gpio);
}
// 操作 GPIO:拉低复位引脚(低有效)
gpiod_set_value_cansleep(reset_gpio, 1); // 输出低电平(复位)
msleep(10);
gpiod_set_value_cansleep(reset_gpio, 0); // 输出高电平(释放复位)
// 操作 GPIO:使能电源
gpiod_set_value_cansleep(power_gpio, 1); // 输出高电平(使能)
GPIO_ACTIVE_HIGH(0):物理高电平表示“有效”状态。GPIO_ACTIVE_LOW(1):物理低电平表示“有效”状态。- 驱动中使用
gpiod_set_value()时,传入 1 表示“有效”状态,0 表示“无效”状态,GPIO 子系统会自动根据标志反转电平。 - 例如:
reset-gpios为GPIO_ACTIVE_LOW,调用gpiod_set_value(reset_gpio, 1)会输出物理低电平。
2.7 设备树包含与复用
实际项目中,设备树通常通过 #include 机制复用公共定义,避免重复编写。摄像头驱动开发中常用的包含文件包括 GPIO 标志、中断类型、时钟定义等。
// camera-sensor.dtsi (设备树包含文件)
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/clock/sun8i-a33-ccu.h>
/ {
reg_camera_avdd: regulator-camera-avdd {
compatible = "regulator-fixed";
regulator-name = "camera_avdd";
regulator-min-microvolt = <2800000>;
regulator-max-microvolt = <2800000>;
regulator-always-on;
};
reg_camera_dvdd: regulator-camera-dvdd {
compatible = "regulator-fixed";
regulator-name = "camera_dvdd";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1200000>;
};
};
// 主设备树文件 board.dts
#include "camera-sensor.dtsi"
&i2c0 {
status = "okay";
camera_sensor: camera@3c {
compatible = "vendor,camera-sensor-v1";
reg = <0x3c>;
reset-gpios = <&pio 7 0 GPIO_ACTIVE_LOW>; // PH7
avdd-supply = <®_camera_avdd>;
dvdd-supply = <®_camera_dvdd>;
};
};
- 将通用的 Regulator 节点和传感器节点定义放在
.dtsi文件中,便于多板复用。 - 板级特定的 GPIO 引脚和电源配置放在主
.dts文件中,通过&label引用覆盖。 - 使用
status = "disabled"默认禁用设备,在板级文件中通过status = "okay"启用。 - 所有 GPIO 和中断相关的宏定义必须通过
#include引入,不要使用魔数。