AIGC标识 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 目录是按驱动分类的,不是按芯片分类。

posted on 2026-07-12 04:48  三分流水  阅读(11)  评论(0)    收藏  举报