3. 设备树基础:DTS 语法入门、节点与属性详解
设备树(Device Tree,DT)是嵌入式 Linux 中用于描述硬件拓扑结构的数据结构。在全志 T113 平台中,SPI 接口的引脚分配、时钟频率、DMA 通道等硬件信息均通过设备树传递给内核驱动。本章将系统讲解 DTS 语法、节点与属性的定义,以及 reg、status、compatible 等关键属性的使用规范。
3.1 设备树文件体系与基本语法
设备树源文件(.dts)通过 dtc 编译器生成二进制 .dtb 文件,由 bootloader 加载并传递给内核。全志 T113 平台通常包含以下文件层次:
| 文件类型 | 扩展名 | 作用 |
|---|---|---|
| 设备树源文件 | .dts |
描述单板级硬件,包含所有外设节点 |
| 设备树头文件 | .dtsi |
描述 SoC 级通用硬件,被 .dts 包含 |
| 设备树二进制 | .dtb |
编译后的二进制文件,供内核解析 |
| 设备树覆盖 | .dtbo |
运行时动态加载的硬件描述片段 |
DTS 语法采用树形结构,以分号结束语句,使用大括号表示层级。以下是一个典型的节点定义:
/dts-v1/;
#include "sun8iw20p1.dtsi"
/ {
model = "Allwinner T113 Evaluation Board";
compatible = "allwinner,t113-evb", "allwinner,sun8iw20p1";
soc {
spi0: spi@04026000 {
compatible = "allwinner,sun8i-spi";
reg = <0x04026000 0x1000>;
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_SPI0>, <&ccu CLK_BUS_SPI0>;
clock-names = "spi", "ahb";
resets = <&ccu RST_BUS_SPI0>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
};
};
.dtsi 文件通常位于 arch/arm/boot/dts/ 目录下,文件名以 sun8iw20p1 开头。SPI 控制器节点已在 .dtsi 中预定义,板级 .dts 只需修改 status 和添加子节点。
3.2 节点与属性基础
设备树由节点(node)和属性(property)组成。每个节点代表一个硬件设备或总线,属性描述该设备的配置参数。
3.2.1 节点命名规则
- 节点名称:格式为
node-name@unit-address,例如spi0@04026000。 - unit-address:与
reg属性的第一个地址段对应,用于区分同一层级下的相同名称节点。 - 标签(label):在节点前添加标签,如
spi0:,便于在.dts中通过&spi0引用。
3.2.2 属性类型与写法
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "allwinner,sun8i-spi"; |
用于驱动匹配,可包含多个字符串 |
| 32 位整数 | reg = <0x04026000 0x1000>; |
尖括号内为 32 位整数,多个值用空格分隔 |
| 64 位整数 | reg = <0x00000000 0x04026000 0x00000000 0x00001000>; |
使用两个 32 位整数表示一个 64 位地址 |
| 布尔值 | status = "disabled"; |
通过字符串表示,常用 okay 或 disabled |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号内为十六进制字节 |
| 字符串列表 | clock-names = "spi", "ahb"; |
逗号分隔的多个字符串 |
| phandle | clocks = <&ccu CLK_SPI0>; |
& 引用其他节点的标签 |
clocks 属性通常引用两个时钟源:SPI 模块时钟(CLK_SPI0)和 AHB 总线时钟(CLK_BUS_SPI0)。resets 属性用于复位控制,必须与时钟配对使用。
3.3 关键属性详解
3.3.1 compatible 属性——驱动匹配的钥匙
compatible 是设备树中最关键的属性,内核通过该属性查找对应的驱动程序。其值是一个字符串列表,优先级从前往后递减。
// SPI 控制器节点中的 compatible
compatible = "allwinner,sun8i-spi", "allwinner,sun6i-spi";
// 内核驱动匹配表(of_match_table)
static const struct of_device_id sun8i_spi_of_match[] = {
{ .compatible = "allwinner,sun8i-spi", .data = &sun8i_spi_variant },
{ .compatible = "allwinner,sun6i-spi", .data = &sun6i_spi_variant },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun8i_spi_of_match);
| compatible 值 | 匹配优先级 | 适用平台 |
|---|---|---|
allwinner,sun8i-spi |
最高(精确匹配) | T113、H616 等 sun8i 系列 |
allwinner,sun6i-spi |
次高(兼容匹配) | A31、H3 等 sun6i 系列 |
allwinner,sun4i-spi |
最低(通用匹配) | A10、A20 等 sun4i 系列 |
compatible 列表,找到第一个匹配的驱动即停止。因此,应将最精确的兼容字符串放在最前面。在全志 T113 中,sun8i-spi 驱动已支持 SPI 时钟极性、DMA 传输等特性,无需回退到旧版驱动。
3.3.2 reg 属性——地址空间描述
reg 属性描述设备在总线上的地址范围,由 #address-cells 和 #size-cells 决定地址和长度的编码方式。
// 父节点定义地址编码
spi0: spi@04026000 {
#address-cells = <1>; // 子节点地址使用 1 个 32 位整数
#size-cells = <0>; // 子节点无长度字段
// 控制器本身的 reg
reg = <0x04026000 0x1000>; // 基地址 0x04026000,长度 0x1000
// SPI 从设备子节点
spidev@0 {
reg = <0>; // 片选号 0,无长度字段
};
};
| 字段 | 含义 | 全志 T113 典型值 |
|---|---|---|
#address-cells |
地址字段占用的 32 位整数个数 | 1(32 位地址空间) |
#size-cells |
长度字段占用的 32 位整数个数 | 1(长度用 32 位表示) |
reg 地址段 |
基地址 | 0x04026000(SPI0) |
reg 长度段 |
地址空间大小 | 0x1000(4KB) |
3.3.3 status 属性——设备使能开关
status 属性控制设备节点的启用状态。在 .dtsi 中默认设置为 disabled,板级 .dts 中根据需要修改为 okay。
// sun8iw20p1.dtsi 中默认关闭
spi0: spi@04026000 {
status = "disabled";
};
// 板级 t113-evb.dts 中启用
&spi0 {
status = "okay";
pinctrl-0 = <&spi0_pins>;
pinctrl-names = "default";
};
| status 值 | 含义 | 内核行为 |
|---|---|---|
okay |
设备启用 | 内核会为节点创建 platform_device 并尝试绑定驱动 |
disabled |
设备禁用 | 内核跳过该节点,不创建设备 |
fail |
设备检测失败 | 内核创建设备但标记为失败状态 |
reserved |
保留 | 通常用于固件保留的设备 |
.dtsi 中保持 status = "disabled",在板级文件中通过 &spi0 引用并修改为 okay。这样可以避免未使用的 SPI 控制器占用资源。
3.4 中断与时钟属性
3.4.1 interrupts 属性
中断属性描述设备使用的中断号、触发类型等信息。全志 T113 使用 GIC(通用中断控制器),中断描述格式如下:
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
// GIC_SPI: 共享外设中断类型
// 11: 中断号(SPI 0 的中断号为 11)
// IRQ_TYPE_LEVEL_HIGH: 高电平触发
| 中断类型宏 | 值 | 说明 |
|---|---|---|
GIC_SPI |
0 | 共享外设中断,用于所有外设 |
GIC_PPI |
1 | 私有外设中断,用于 CPU 本地 |
IRQ_TYPE_LEVEL_HIGH |
4 | 高电平触发 |
IRQ_TYPE_EDGE_RISING |
1 | 上升沿触发 |
3.4.2 clocks 与 clock-names 属性
时钟属性为设备提供时钟源,clock-names 用于区分不同的时钟角色。
clocks = <&ccu CLK_SPI0>, <&ccu CLK_BUS_SPI0>;
clock-names = "spi", "ahb";
// 驱动中通过 devm_clk_get 获取时钟
struct spi_master *master = spi_alloc_master(dev, sizeof(struct sun8i_spi));
struct sun8i_spi *sspi = spi_master_get_devdata(master);
sspi->clk = devm_clk_get(dev, "spi"); // 获取 SPI 模块时钟
sspi->bus_clk = devm_clk_get(dev, "ahb"); // 获取 AHB 总线时钟
3.5 引脚复用配置(pinctrl)
全志 T113 的 SPI 引脚通过 pinctrl 子系统配置复用功能。引脚配置在 .dtsi 中预定义,板级文件通过 pinctrl-0 引用。
// sun8iw20p1.dtsi 中的引脚定义
spi0_pins: spi0-pins {
pins = "PC0", "PC1", "PC2", "PC3";
function = "spi0";
drive-strength = <10>;
bias-pull-up;
};
spi0_cs_pins: spi0-cs-pins {
pins = "PC4";
function = "spi0";
drive-strength = <10>;
bias-pull-up;
};
// 板级文件引用
&spi0 {
pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
pinctrl-names = "default";
status = "okay";
};
| pinctrl 属性 | 说明 | 全志 T113 典型值 |
|---|---|---|
pins |
引脚列表 | "PC0", "PC1", "PC2", "PC3" |
function |
复用功能 | "spi0" |
drive-strength |
驱动能力(mA) | 10 |
bias-pull-up |
内部上拉 | 无值(布尔属性) |
bias-pull-down |
内部下拉 | 无值(布尔属性) |
3.6 SPI 从设备子节点配置
在 SPI 控制器节点下,需要添加从设备子节点来描述挂载在 SPI 总线上的外设芯片。
&spi0 {
status = "okay";
pinctrl-0 = <&spi0_pins>;
pinctrl-names = "default";
spidev@0 {
compatible = "spidev";
reg = <0>; // 片选 0
spi-max-frequency = <50000000>; // 50MHz
spi-cpha; // 时钟相位模式
spi-cpol; // 时钟极性模式
};
spidev@1 {
compatible = "spidev";
reg = <1>; // 片选 1
spi-max-frequency = <10000000>; // 10MHz
};
};
| 子节点属性 | 说明 | 可选值 |
|---|---|---|
reg |
片选号(从 0 开始) | 0、1、2... |
spi-max-frequency |
最大 SPI 时钟频率(Hz) | 1000000 ~ 100000000 |
spi-cpha |
时钟相位(CPHA=1) | 无值(布尔属性) |
spi-cpol |
时钟极性(CPOL=1) | 无值(布尔属性) |
spi-cs-high |
片选高电平有效 | 无值(布尔属性) |
spi-3wire |
三线制 SPI 模式 | 无值(布尔属性) |
spidev 驱动(compatible = "spidev")进行用户空间测试。确认通信正常后,再替换为实际的设备驱动。注意内核配置需启用 CONFIG_SPI_SPIDEV。
3.7 设备树编译与反编译
掌握 dtc 工具的使用是调试设备树的必备技能。
# 编译设备树:.dts -> .dtb
dtc -I dts -O dtb -o sun8iw20p1-t113-evb.dtb sun8iw20p1-t113-evb.dts
# 反编译设备树:.dtb -> .dts
dtc -I dtb -O dts -o sun8iw20p1-t113-evb.dts sun8iw20p1-t113-evb.dtb
# 检查设备树语法错误
dtc -I dts -O dtb -o /dev/null sun8iw20p1-t113-evb.dts
# 查看内核中当前使用的设备树
# 在目标板上执行:
cat /proc/device-tree/model
ls /proc/device-tree/soc/sp