2、deviceTree
Linux 设备树完整指南
一、什么是设备树
设备树(Device Tree)是一种描述硬件的数据结构,用树形节点描述 CPU、内存、外设等硬件信息。
- 后缀:
.dts(源文件)→ 编译 →.dtb(二进制)→ 内核启动时读取
.dts (文本) → dtc 编译器 → .dtb (二进制) → 内核解析
为什么需要设备树
在 ARM Linux 早期,硬件信息写死在 arch/arm/mach-xxx 的 C 代码里(board files)。
问题:换一颗 LED 就得改内核代码,一个内核镜像只支持一种板子。
设备树将硬件描述与内核代码分离:同一份内核,只需换 .dtb 即可支持不同板卡。
二、.dtsi 和 .dts 的差异
.dtsi |
.dts |
|
|---|---|---|
| 全称 | Device Tree Source Include | Device Tree Source |
| 角色 | SoC 通用定义(被包含) | 板级具体配置(入口文件) |
| 内容 | CPU、外设 IP、时钟、GIC 等共性节点 | 板型号、内存、哪些外设启用、pinmux |
| 包含关系 | 被 #include 到 .dts 中 |
#include 所有 .dtsi |
| 编译入口 | 不能被 dtc 直接编译 | 作为 dtc 的输入文件 |
| 复用性 | 同系列 SoC 共享(如 imx6ull.dtsi) | 每块板一个 |
本质:.dtsi = 硬件 IP 声明,.dts = 板级差异化配置。
三、基本语法
节点结构
[标签:] 节点名[@单元地址] {
[属性定义;]
[子节点;]
};
完整示例
/dts-v1/; // 版本标识
#include <dt-bindings/input/input.h> // C 风格 include
#include "imx6ull.dtsi" // SoC 通用定义
/ { // 根节点
#address-cells = <1>; // 地址域宽度
#size-cells = <1>; // 大小域宽度
model = "Freescale i.MX6 ULL EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
chosen { // 启动参数
stdout-path = &uart1;
};
memory@80000000 { // 内存
device_type = "memory";
reg = <0x80000000 0x20000000>; // 起始地址 + 大小
};
};
四、属性类型
| 类型 | 写法 | 说明 |
|---|---|---|
<u32> |
<0x1000> |
32 位整数 |
<u64> |
<0x00 0x10000000> |
64 位整数(2 个 cell) |
| 数组 | <0x1000 0x2000 5> |
多个 cell |
| 字符串 | "ti,omap3-uart" |
双引号 |
| 字符串列表 | "str1", "str2" |
逗号分隔 |
| 字节序列 | [00 0f 42] |
十六进制字节 |
| 混合 | <0x1000> "string" |
空格分隔不同类型 |
五、核心属性
uart0: serial@44e09000 {
compatible = "ti,omap3-uart"; // 驱动匹配的关键
reg = <0x44e09000 0x2000>; // 基地址 + 长度
interrupts = <72>; // 中断号
interrupt-parent = <&gic>; // 中断控制器
clocks = <&clk 42>; // 时钟句柄
clock-names = "fck";
pinctrl-names = "default"; // pinmux 状态
pinctrl-0 = <&uart0_pins>;
status = "okay"; // okay / disabled / reserved
};
compatible 匹配规则
格式:"厂商,型号",可多个,从前往后匹配:
compatible = "ti,am3353-uart", "ti,omap3-uart";
驱动优先匹配 "ti,am3353-uart",失败则回退 "ti,omap3-uart"。
内核驱动中:
static const struct of_device_id ids[] = {
{ .compatible = "ti,omap3-uart" },
{ }
};
reg 与 #address-cells / #size-cells
parent {
#address-cells = <2>; // 地址用 2 个 cell(64 位)
#size-cells = <2>; // 大小用 2 个 cell(64 位)
child@10000000 {
reg = <0x0 0x10000000 0x0 0x1000>;
// 起始地址 长度
};
};
规则:父节点定义 #address-cells 和 #size-cells,子节点的 reg 按此解析。
六、中断配置
// 中断控制器节点
gic: interrupt-controller@48241000 {
compatible = "arm,gic-400";
reg = <0x48241000 0x1000>;
interrupt-controller; // 声明自己是中断控制器
#interrupt-cells = <3>; // GIC: <类型 编号 触发方式>
};
// 设备引用中断
device@xxx {
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
// GIC_SPI = 0 (共享外设中断)
// GIC_PPI = 1 (私有外设中断)
};
七、phandle 与引用
三种引用其他节点的方式:
// 1. & 标签引用(最常用)
&uart0 { status = "okay"; };
// 2. phandle 属性
interrupt-parent = <&gic>; // &gic → phandle 值
// 3. 路径引用
&{/soc/i2c@48000000} { /* ... */ };
八、特殊子节点
chosen — 启动参数
chosen {
stdout-path = &uart0;
bootargs = "console=ttyO0,115200 root=/dev/mmcblk0p2 rw";
};
aliases — 序号别名
aliases {
serial0 = &uart0;
i2c0 = &i2c0;
mmc0 = &mmc1;
};
内核通过 of_alias_get_id(np, "serial") 获取序号。
九、include 机制
*.dtsi — SoC 通用定义(arch/arm/boot/dts/)
↑ include
*.dts — 板级文件(引用 & 追加覆盖)
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include "am33xx.dtsi"
#include 和 /include/ 都支持,前者可引用头文件中的宏。
十、编译与反编译
dtc -I dts -O dtb -o board.dtb board.dts # 编译
dtc -I dtb -O dts -o board.dts board.dtb # 反编译
dtc -I dts -O dtb -@ -o board.dtb board.dts # 支持 overlay
fdtdump board.dtb # 查看结构
./scripts/checkpatch.pl -f board.dts # 语法检查
dtbs_check # 内核 DT schema 验证
十一、Platform 总线与设备树的关系
总线模型
设备(DTS 描述硬件)──→ 平台总线 ←── 驱动(C 代码)
│
├─ compatible 匹配
├─ 匹配成功 → probe()
└─ 匹配失败 → 跳过
设备树与 Platform 总线的关系
DTS C 驱动
───── ─────
设备节点 ──→ 内核解析 ──→ platform_device platform_driver
│ │ │
└─ compatible └─ compatible └─ of_match_table
"fsl,imx6ul-i2c" "fsl,imx6ul-i2c" │
↓
内核自动匹配 → probe()
简单说:设备树描述有什么硬件、地址在哪,platform 总线负责把硬件信息交给正确的驱动。
驱动如何通过 of_match_table 匹配
// 驱动注册(of_match_table)
static const struct of_device_id my_i2c_ids[] = {
{ .compatible = "nxp,lm75" }, // 第一优先
{ .compatible = "national,lm75" }, // 第二优先
{ /* sentinel */ }
};
static struct i2c_driver my_driver = {
.probe = my_probe,
.driver = {
.name = "lm75",
.of_match_table = my_i2c_ids,
},
};
// 设备节点(DTS 中定义)
temp@48 {
compatible = "nxp,lm75";
reg = <0x48>;
};
十二、IOMUXC 引脚配置
SW_PAD_CTL 寄存器位域
| 缩写 | 全称 | 位域 | 含义 |
|---|---|---|---|
| PUS | Pull Up/Down Select | bit[15:14] | 00=100kΩ 下拉,01=47kΩ 上拉,10=100kΩ 上拉,11=22kΩ 上拉 |
| PUE | Pull Up/Down Enable | bit[12] | 0=Keeper,1=Pull(上下拉) |
| PKE | Pull/Keeper Enable | bit[11] | 0=禁用,1=启用 |
| DSE | Drive Strength Enable | bit[3:1] | 驱动强度,数值越大驱动能力越强 |
| SRE | Slew Rate Enable | bit[0] | 0=慢摆率,1=快摆率 |
| SPEED | Speed | bit[5:4] | 00=50MHz,01=100MHz,11=200MHz |
| SION | Software Input On | bit[30] | 强制输入使能(I2C 开漏总线必须置 1) |
常用配置值
| 信号类型 | 值 | 说明 |
|---|---|---|
| I2C | 0x4001b8b0 |
SION=1 + 100kΩ 上拉 |
| UART | 0x1b0b1 |
上拉 + 中等驱动 |
| SDIO | 0x17059 |
强驱动 + 快摆率 |
| LCD(24bit) | 0x79 |
中等驱动 |
| 普通 GPIO 输入 | 0x000010B0 |
上下拉 |
SRE(摆率)详解
摆率 = 信号电压变化的最大斜率,单位 V/μs。
- 快摆率 → 上升沿短 → 信号稳定快 → 适合高速信号
- 慢摆率 → 上升沿长 → EMI 辐射小 → 适合低速信号
| 信号 | 速率 | 摆率选择 |
|---|---|---|
| I2C (100kHz) | 慢 | SRE=0 慢摆率 |
| UART (115200) | 慢 | SRE=0 |
| SDIO (50MHz) | 快 | SRE=1 快摆率 |
| LCD CLK (50MHz) | 快 | SRE=1 |
原则:摆率够用就行,不是越快越好。
Keeper 与开漏的区别
| Bus Keeper | 开漏 (Open-Drain) | |
|---|---|---|
| 本质 | 弱锁存器,保持最后一次驱动电平 | 输出级只接 N-MOS 到 GND,高电平靠外部上拉 |
| 工作方式 | 引脚悬空时"拉住"不浮空 | 输出低时拉低,输出高时 Hi-Z |
| 典型场景 | 双向数据总线(SDIO DATA) | I2C SCL/SDA |
十三、实操:配置 I2C1 挂传感器
需要修改的文件
| 文件 | 操作 | 说明 |
|---|---|---|
arch/arm/boot/dts/imx6ull.dtsi |
只读 | I2C1 控制器已声明 |
arch/arm/boot/dts/你的板.dts |
修改 | 启用 I2C1 + 配 pinmux + 挂从设备 |
.dts 修改示例
// 引脚配置(iomuxc 节点内)
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
// I2C1 控制器 + 从设备
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
clock-frequency = <100000>;
status = "okay";
temp: lm75@48 {
compatible = "nxp,lm75";
reg = <0x48>;
};
};
编译命令
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
关键检查项
| 检查点 | 验证方法 |
|---|---|
| 引脚是否被占用 | grep 其他 pinctrl 组中是否有相同 PAD |
| I2C 地址是否正确 | 启动后 i2cdetect -y 0 |
| 驱动是否加载成功 | `dmesg |
| 从设备是否识别 | cat /sys/bus/i2c/devices/0-0048/name |
常见翻车点
| 问题 | 正确做法 |
|---|---|
clock_frequency 拼写错误 |
必须用 clock-frequency(连字符) |
| pinctrl 忘记配 | I2C 节点必须配 pinctrl-0 |
| 从设备地址错误 | reg 必须是 7 位地址,先 i2cdetect 确认 |
| 引脚被占用 | 检查同一引脚是否出现在其他 pinctrl 组中 |
| 忘了 status = "okay" | .dtsi 默认 disabled |
十四、必看文档
内核自带:
drivers/of/README.fdtree → 语法参考
Documentation/devicetree/usage-model.txt → 设备树设计理念
Documentation/devicetree/bindings/ → 每个驱动的 DT 绑定文档
学习路径:
1. 找同芯片的官方 EVK .dts 文件
2. 改一个节点编译看效果
3. 不懂查 Documentation/devicetree/bindings/
十五、bindings 目录说明
Documentation/devicetree/bindings/i2c/ 下的每个文件对应一个 I2C 控制器驱动:
| 文件 | 对应的 I2C 控制器 |
|---|---|
i2c-imx.txt |
NXP i.MX I2C(i.MX6ULL 用这个) |
i2c-s3c2410.txt |
Samsung S3C I2C |
i2c-mt65xx.txt |
MediaTek I2C |
i2c-stm32.txt |
STM32 I2C |
不同厂商的 I2C 控制器硬件不同,需要的配置也不同。bindings 目录是按驱动分类的,不是按芯片分类。
浙公网安备 33010602011771号