3. Device Tree 基础:语法、节点与属性
Device Tree(设备树)是嵌入式 Linux 中描述硬件拓扑结构的核心机制。它使用一种树形数据结构,将 CPU、内存、外设、中断控制器、时钟和 GPIO 控制器等硬件资源以节点和属性的形式组织起来。DRM/KMS 显示驱动通过解析 Device Tree 来获取显示控制器的寄存器地址、中断号、时钟频率以及面板接口类型等关键参数,从而实现硬件无关的驱动设计。
3.1 Device Tree 语法基础
Device Tree 源文件(.dts)采用一种类似 C 语言的结构化文本格式,由节点(node)和属性(property)组成。每个节点代表一个硬件设备或总线,属性则描述该设备的配置参数。
3.1.1 基本语法规则
- 节点定义:
node-name@unit-address,其中unit-address通常为设备基地址。 - 属性赋值:
property-name = <value>;或property-name = "string";。 - 标签(label): 使用
label: node-name格式,便于在其他地方引用。 - 包含文件:
#include "filename.dtsi",用于复用通用硬件描述。
// 一个典型的 Device Tree 节点示例
/ {
model = "Embedded DRM Board v1.0";
compatible = "vendor,board-name";
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
display_controller: display-controller@1e000000 {
compatible = "vendor,display-controller";
reg = <0x1e000000 0x1000>;
interrupts = <0 42 4>;
clocks = <&clk_gate DISPLAY_CLK>;
status = "okay";
};
};
};
<0x1000>)、字符串("okay")、字节数组([01 02 03])以及 phandle 引用(&clk_gate)。
3.1.2 节点与属性的层级关系
Device Tree 的树形结构从根节点 / 开始,向下逐级展开。每个节点可以有子节点,形成硬件拓扑的层次化描述。显示驱动通常关注的节点路径如下:
3.2 核心属性详解
Device Tree 定义了一系列标准属性,用于描述设备的硬件资源。对于 DRM 显示驱动,以下四个属性最为关键:compatible、reg、interrupts、clocks 和 gpios。
3.2.1 compatible 属性——驱动匹配的钥匙
compatible 属性是一个字符串列表,用于将设备节点与内核驱动进行匹配。内核通过查找驱动中 of_match_table 定义的 compatible 字符串,找到对应的 probe 函数。
| 属性格式 | 示例 | 匹配规则 |
|---|---|---|
"vendor,device" |
"ti,am335x-lcdc" |
精确匹配,优先使用 |
"vendor,device-gen" |
"fsl,imx6q-ldb" |
带版本号的兼容匹配 |
"generic-driver" |
"simple-framebuffer" |
通用驱动,作为 fallback |
// 内核驱动中的匹配表示例
static const struct of_device_id display_of_match[] = {
{ .compatible = "vendor,display-controller-v2", .data = &v2_config },
{ .compatible = "vendor,display-controller", .data = &v1_config },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, display_of_match);
static int display_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
match = of_match_device(display_of_match, &pdev->dev);
// 根据 match->data 选择硬件配置
}
"Vendor,Device" 与 "vendor,device" 不匹配。
3.2.2 reg 属性——地址空间描述
reg 属性描述设备的寄存器地址范围,由一组 (address, length) 对组成。其解析依赖于父节点的 #address-cells 和 #size-cells 属性。
// reg 属性示例
display_controller: display-controller@1e000000 {
compatible = "vendor,display-controller";
reg = <0x1e000000 0x1000 // 寄存器基地址 0x1e000000,长度 4KB
0x1e010000 0x100>; // 第二个地址区域:控制寄存器
};
// 父节点定义地址格式
soc {
#address-cells = <1>; // 地址用 1 个 32 位单元表示
#size-cells = <1>; // 长度用 1 个 32 位单元表示
};
在驱动中,通过 platform_get_resource() 或 of_iomap() 获取 reg 属性对应的内存资源:
static int display_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 第二个地址区域
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
// ...
}
3.2.3 interrupts 属性——中断资源绑定
interrupts 属性描述设备使用的中断号、触发类型等信息。其格式依赖于中断控制器父节点的 #interrupt-cells 属性。
| 中断控制器类型 | #interrupt-cells | interrupts 格式 | 示例 |
|---|---|---|---|
| ARM GIC | 3 | <中断类型 中断号 触发类型> | <0 42 4> |
| 简单中断控制器 | 1 | <中断号> | <15> |
| 级联中断控制器 | 2 | <父中断号 子中断号> | <3 8> |
// 中断属性完整示例
display_controller: display-controller@1e000000 {
compatible = "vendor,display-controller";
reg = <0x1e000000 0x1000>;
interrupts = <0 42 4>; // GIC SPI 中断 42,高电平触发
interrupt-parent = <&gic>; // 指定中断控制器
};
// 驱动中获取中断号
static int display_probe(struct platform_device *pdev)
{
int irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, display_irq_handler,
IRQF_TRIGGER_HIGH, "display", dev);
}
3.2.4 时钟与 GPIO 绑定
显示驱动通常需要多个时钟源(像素时钟、总线时钟)和 GPIO 控制(复位、背光使能、电源控制)。Device Tree 使用 clocks 和 gpios 属性来描述这些资源。
时钟绑定
// 时钟绑定示例
display_controller: display-controller@1e000000 {
compatible = "vendor,display-controller";
reg = <0x1e000000 0x1000>;
clocks = <&clk_gate DISPLAY_CLK>, // 主时钟
<&clk_gate PIXEL_CLK>; // 像素时钟
clock-names = "core", "pixel"; // 时钟名称,驱动通过名称获取
assigned-clocks = <&clk_gate PIXEL_CLK>;
assigned-clock-rates = <148500000>; // 设置像素时钟频率为 148.5MHz
};
// 驱动中获取时钟
static int display_probe(struct platform_device *pdev)
{
struct clk *core_clk, *pixel_clk;
core_clk = devm_clk_get(&pdev->dev, "core");
pixel_clk = devm_clk_get(&pdev->dev, "pixel");
clk_prepare_enable(core_clk);
clk_set_rate(pixel_clk, 148500000);
}
GPIO 绑定
// GPIO 绑定示例
display_controller: display-controller@1e000000 {
compatible = "vendor,display-controller";
reg = <0x1e000000 0x1000>;
enable-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>; // 电源使能 GPIO
reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; // 复位 GPIO,低电平有效
backlight-gpios = <&gpio3 10 GPIO_ACTIVE_HIGH>; // 背光控制 GPIO
};
// 驱动中操作 GPIO
static int display_probe(struct platform_device *pdev)
{
struct gpio_desc *enable_gpio, *reset_gpio;
enable_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_HIGH);
reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
// 复位时序:拉低 10ms 后释放
gpiod_set_value(reset_gpio, 0);
msleep(10);
gpiod_set_value(reset_gpio, 1);
}
3.3 显示驱动中的 Device Tree 实践
在实际的 DRM 显示驱动开发中,Device Tree 的编写需要遵循内核社区定义的绑定规范。以下是一个完整的显示控制器 Device Tree 节点示例,涵盖了本节所学的所有核心属性:
// 完整的显示控制器 Device Tree 节点
&display_controller {
compatible = "vendor,display-controller-v2", "vendor,display-controller";
reg = <0x1e000000 0x4000>;
interrupts = <0 42 4>;
interrupt-parent = <&gic>;
// 时钟资源
clocks = <&clk_gate DISPLAY_CLK>,
<&clk_gate PIXEL_CLK>,
<&clk_gate LVDS_CLK>;
clock-names = "core", "pixel", "lvds";
// 时钟频率配置
assigned-clocks = <&clk_gate PIXEL_CLK>,
<&clk_gate LVDS_CLK>;
assigned-clock-rates = <148500000>,
<74250000>;
// GPIO 控制
enable-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
backlight-gpios = <&gpio3 10 GPIO_ACTIVE_HIGH>;
// 显示输出接口子节点
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
display_out_rgb: endpoint {
remote-endpoint = <&panel_in>;
bus-width = <24>;
data-order = <0x0012>;
};
};
port@1 {
reg = <1>;
display_out_lvds: endpoint {
remote-endpoint = <&lvds_bridge_in>;
};
};
};
status = "okay";
};
- compatible 属性建议提供多个兼容字符串,从具体到通用排列,便于驱动向后兼容。
- reg 属性中的地址范围应精确对应硬件手册中的寄存器区域,避免映射过大内存空间。
- clock-names 必须与驱动中
devm_clk_get()使用的名称完全一致。 - GPIO 的
GPIO_ACTIVE_HIGH/GPIO_ACTIVE_LOW标志决定了驱动中gpiod_set_value()的逻辑电平。
3.4 本章小结
Device Tree 是嵌入式 Linux 显示驱动开发的基石。通过本章的学习,您应该掌握:
| 知识点 | 关键内容 | DRM 驱动中的应用 |
|---|---|---|
| Device Tree 语法 | 节点、属性、标签、包含文件 | 定义显示控制器及其子节点 |
| compatible 属性 | 驱动匹配字符串,支持多级兼容 | of_match_table 匹配 probe 函数 |
| reg 属性 | 寄存器地址和长度,支持多段区域 | ioremap 映射显示控制器寄存器 |
| interrupts 属性 | 中断号、类型,依赖 interrupt-parent | 注册 VBLANK 和错误中断处理 |
| clocks 属性 | 时钟源和频率配置 | 设置像素时钟、总线时钟频率 |
| gpios 属性 | GPIO 控制,支持有效电平标志 | 面板电源、复位、背光控制 |
dtc 工具编译验证其语法正确性。