Linux驱动 2026年5月8日 65 分钟

设备树2-设备树语法

本章主要探讨设备树的基本语法和使用技巧 子节点子节点是根节点的子项,用于描述具体的硬件设备或设备集合。 [label:]…

本章主要探讨设备树的基本语法和使用技巧

子节点子节点是根节点的子项,用于描述具体的硬件设备或设备集合。

[label:] node-name@[unit-address] {
[properties definitions]
[child nodes]
};

REG属性表示子节点reg 属性用几个32 位的数表示地址,#size-cells 表示子节点reg 属性
用几个32 位的数表示地址长度

/ {
#address-cells = <0x1>; //在根节点下使用1 个u32 来代表address。
#size-cells = <0x0>; //在根节点下使用0 个u32 来代表size。
node1 {
reg = <0x32>; //表示地址是0x32。
 };
}

model 属性model 是可选属性,用于描述设备的名称或型号,其值为字符串,便于识别和区分设备,

my_device {
compatible = "vendor,device";
model = "My Device XYZ";
// 其他属性和子节点的定义
};

status 属性status 属性用于描述设备或节点的状态,属性值是字符串。属性值有以下几种:okay 表示
设备或节点是可用状态;disabled 表示设备或节点是不可用状态;reserved 表示设备或节点已
被保留,暂时不可用;fail 表示设备或节点是不可用状态并且检测到了错误。

my_device {
compatible = "vendor,device";
status = "okay";
// 其他属性和子节点的定义
};

compatible 属性设备树中的设备节点使用compatible 属性的值与driver 进行匹配,如使用平台总线就要与
平台总线driver 部分中的platform_driver.driver.of_match_table 成员的值进行匹配,如果匹配成
功,即字符串值相等,则会执行平台总线driver 部分的probe()函数
/{
topeet{
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
myLed{
compatible = "my devicetree";
};
};
};
//平台总线模型的driver 部分
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
//匹配成功后执行probe 函数
static int my_platform_probe(struct platform_device *pdev)
{
printk(KERN_INFO "my_platform_probe: Probing platform device\n");
return 0;
}
const struct of_device_id of_match_table_id[] = {
{.compatible="my devicetree"},
};

aliases 节点aliases 是设备树根节点下的特殊节点,用于为设备节点定义别名,方便引用和管

aliases {
mmc0 = &sdmmc0;
mmc1 = &sdmmc1;
mmc2 = &sdhci;
serial0 = “/simple@fe000000/seria1@11c500”;
};

chosen 节点节点要位于设备树根节点下,它用于传递系统引导和配置相关信息。其中bootargs
属性用于存储引导内核时传递的命令行参数

chosen {
bootargs = “root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200”;
};

修改/追加节点内容语法格式为:&节点名,用于引用设备树中已有的节点

pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
status = "disabled";
}
&pinctrl {
status = "okay";//把pinctrl 节点中的属性值修改为okay
uart0 {//追加uart0 节点
/omit-if-no-ref/
uart0_xfer: uart0-xfer {
rockchip,pins =<0 RK_PC0 3 &pcfg_pull_up>,
<0 RK_PC1 3 &pcfg_pull_up>;
};
};

相关特定属性节点分析

中断:

gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;//性用于描述设备所使用的中断号及触发方式,分别表示中断控制器类型、中断号和中断触发类型。其中中断控制器的常见类型包括GIC (Generic Interrupt Controller)、IRQ (Basic Interrupt Handling)等,触发类型:#define IRQ_TYPE_NONE 0 // 无中断触发类型
{
#define IRQ_TYPE_EDGE_RISING 1 // 上升沿触发
#define IRQ_TYPE_EDGE_FALLING 2 // 下降沿触发
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)// 双边沿触发
#define IRQ_TYPE_LEVEL_HIGH 4 // 高电平触发
#define IRQ_TYPE_LEVEL_LOW 8 // 低电平触发
}
clocks = <&pmucru PCLK_GPI00>, <&pmucru DBCLK_GPI00>;
gpio-controller;
#gpio-cells = <2>;//表示中断控制器下的设备描述一个中断所需的单元数。例如,GIC 控制器使用#interrupt-cells = <2>表示使用这个中断控制器的设备中的interrupts
属性需2 个参数。
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;//表明这个节点也是一个中断控制器节点
#interrupt-cells = <2>;
};
ft5x06: ft5x06@38 {
status = "disabled";
compatible = "edt,edt-ft5306";
reg = <0x38>;
touch-gpio = <&gpio0 RK_PB5 IRQ_TYPE_EDGE_RISING>;
interrupt-parent = <&gpio0>;//指明中断父节点是谁
interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;//父节点要求这个中断需要两个参数进行描述
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <1280>;
touch_type = <1>;
};

这里补充两个小知识点:1.一个节点可以提示描述两个功能,2.设备树在多层包含和引用的情况下以那个文件为准。


一、GPIO 节点同时作为中断控制器和 GPIO 控制器
可以的,在设备树中这是常见设计。

原因
GPIO 引脚既可以作为普通输出/输入使用,也可以触发中断。例如:

按键按下 → GPIO 引脚电平变化 → 触发中断
触摸屏触摸 → GPIO 中断通知 CPU
你的文件中的体现

gpio0: gpio@fdd60000 {
    gpio-controller;              // ✅ 是 GPIO 控制器
    #gpio-cells = <2>;            //    每个 GPIO 引用需要 2 个 cell

    interrupt-controller;          // ✅ 也是中断控制器
    #interrupt-cells = <2>;       //    每个中断引用需要 2 个 cell
};
这表示 gpio0 节点同时承担两种角色,这是完全正确的设计。

二、interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW> 详解
2.1 完整参数结构

interrupt-parent = <&gpio0>;           // 中断父控制器
interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;  // 中断定义
interrupts 是一个数组,这里有 2 个 cell(#interrupt-cells = <2>)。

2.2 两个参数的含义
参数	含义	示例值
第1个 cell	中断号 (interrupt specifier)	GPIO 引脚编号
第2个 cell	触发类型 (interrupt type)	电平/边沿触发

< 第1个cell  第2个cell >
<RK_PB5  IRQ_TYPE_LEVEL_LOW>
2.3 第一参数 RK_PB5 的含义
RK_PB5 是 GPIO 引脚标识符,表示:


RK_PB5
 ├── RK   = Rockchip 芯片前缀
 ├── P    = Port (端口/GPIO组)  A=0, B=1, C=2, D=3
 └── B5   = Pin 5 (第5号引脚,在P组中)

换算: P组第5脚 → GPIO Bank 1, Pin 5
命名规则:


RK_XXn
  ├── XX = 端口名 (PA, PB, PC, PD, PE, PF...)
  └── n  = 引脚号  (0-31)
标识	GPIO Bank	Pin 序号	GPIO 编号
RK_PA0	0	0	GPIO0_A0
RK_PB5	1	5	GPIO1_B5
RK_PC2	2	2	GPIO2_C2
RK_PD15	3	15	GPIO3_D15
2.4 第二参数触发类型

IRQ_TYPE_NONE           = 0x00000000   // 无触发
IRQ_TYPE_EDGE_RISING   = 0x00000001   // 上升沿触发
IRQ_TYPE_EDGE_FALLING  = 0x00000002   // 下降沿触发
IRQ_TYPE_EDGE_BOTH     = 0x00000003   // 双边沿触发
IRQ_TYPE_LEVEL_HIGH    = 0x00000004   // 高电平触发
IRQ_TYPE_LEVEL_LOW     = 0x00000008   // 低电平触发  ← 你用的是这个
2.5 如何知道第一参数填什么
步骤1:确定使用的 GPIO 引脚

根据硬件原理图确定 GPIO 引脚,例如:


触摸芯片 IRQ ----→ GPIO1_B5 (RK_PB5)
触摸芯片 RST ----→ GPIO1_B6 (RK_PB6)
步骤2:查找 GPIO 编号


// pinctrl 中定义引脚复用
touch: touch-pins {
    rockchip,pins = <
        RK_PB5 RK_FUNC_GPIO &pcfg_pull_none   // 作为 GPIO 使用
        RK_PB6 RK_FUNC_GPIO &pcfg_pull_none
    >;
};
步骤3:填写到 interrupts


touch@38 {
    interrupt-parent = <&gpio1>;       // GPIO1 控制器
    interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>;  // PB5 上升沿触发
};
2.6 实际中断流程图

应用程序
    ↑
    │ 中断信号
    │
触摸芯片 IRQ 引脚
    │
    ↓
┌──────────────────────────────────────┐
│         gpio0 控制器 (GPIO1)          │
│                                       │
│  #interrupt-cells = <2>              │
│  interrupt-controller;                │
│                                       │
│  接收: <RK_PB5 IRQ_TYPE_LEVEL_LOW>   │
│  识别: Bank 1, Pin 5 的中断           │
└──────────────────────────────────────┘
    │ (转换为 GIC 中断号)
    ↓
┌──────────────────────────────────────┐
│         GIC (通用中断控制器)            │
│                                       │
│  分发中断到 CPU                        │
└──────────────────────────────────────┘
    │
    ↓
CPU (Cortex-A)
三、设备树多层引用与覆盖规则(详细版)
3.1 include 加载顺序
dts/dtsi 文件通过 #include 包含,后加载的覆盖先加载的。

示例:Rockchip 典型加载顺序


rk3399.dts
  ├── #include "rk3399.dtsi"           ← 第1层:SoC 基础定义
  │       ├── #include "skeleton.dtsi"
  │       ├── #include "dt-bindingings/xxx.dtsi"
  │       └── 定义所有外设的默认状态
  │
  ├── #include "rk3399-disvr.dtsi"     ← 第2层:中间层
  │       └── 可能覆盖部分外设
  │
  └── #include "rk3399-evb.dtsi"       ← 第3层:板级(最后加载,生效)
          └── 板级特定配置,覆盖前面的
3.2 覆盖规则详解
规则1:同路径节点 → 合并属性,冲突以后者为准
文件1: imx6ull.dtsi


/ {
    uart1: serial@02020000 {
        compatible = "fsl,imx6ul-uart";
        reg = <0x02020000 0x4000>;
        status = "okay";           // ← 属性A
        pinctrl-names = "default";
    };
};
文件2: board.dtsi


/ {
    &uart1 {
        status = "disabled";       // ← 覆盖!冲突,以后者为准
        pinctrl-names = "sleep";  // ← 覆盖!
    };
};
最终结果:


uart1: serial@02020000 {
    compatible = "fsl,imx6ul-uart";
    reg = <0x02020000 0x4000>;       // ← 保留(无冲突)
    status = "disabled";              // ← 被覆盖为 disabled
    pinctrl-names = "sleep";         // ← 被覆盖为 sleep
};
规则2:同路径全新属性 → 追加
文件1: imx6ull.dtsi


/ {
    uart1: serial@02020000 {
        compatible = "fsl,imx6ul-uart";
        reg = <0x02020000 0x4000>;
    };
};
文件2: board.dtsi


/ {
    &uart1 {
        interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;  // 全新属性,追加
        pinctrl-0 = <&pinctrl_uart1>;                   // 全新属性,追加
    };
};
最终结果:


uart1: serial@02020000 {
    compatible = "fsl,imx6ul-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;  // ← 新增
    pinctrl-0 = <&pinctrl_uart1>;                   // ← 新增
};
规则3:同路径同属性数组 → 完全覆盖
文件1


interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
文件2


interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>,   // 覆盖!
             <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;   // 新的
最终: 整个数组被替换为新的 2 个中断。

3.3 完整示例:多层覆盖
第1层: imx6ull.dtsi (SoC层)


/ {
    compatible = "fsl,imx6ull";
    #address-cells = <1>;
    #size-cells = <1>;

    uart1: serial@02020000 {
        compatible = "fsl,imx6ul-uart";
        reg = <0x02020000 0x4000>;
        interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
        status = "okay";           // 默认开启
    };

    i2c1: i2c@021a0000 {
        compatible = "fsl,imx6ul-i2c";
        reg = <0x021a0000 0x4000>;
        status = "okay";
    };
};
第2层: imx6ull-alientek-emmc.dtsi (板级层)


/ {
    // 覆盖 uart1 的状态
    &uart1 {
        status = "okay";           // 显式确认开启
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart1>;
    };

    // 覆盖 i2c1 的状态
    &i2c1 {
        status = "okay";           // 确认开启
        clock-frequency = <100000>; // 100KHz I2C 速度
    };
};
第3层: imx6ull-alientek-emmc.dts (机器层)


/ {
    model = "ALIENTEK i.MX6ULL EMMC Board";
    compatible = "alientek,imx6ull-alientek-emmc", "fsl,imx6ull";

    // 调试时临时关闭 uart1
    &uart1 {
        status = "disabled";       // 覆盖!最终关闭
    };
};
最终结果:


uart1:  status = "disabled"   ← 由 imx6ull-alientek-emmc.dts 决定
i2c1:   status = "okay"      ← 由 imx6ull-alientek-emmc.dtsi 决定
model:  "ALIENTEK..."        ← 由 imx6ull-alientek-emmc.dts 决定
3.4 优先级总结

include 加载顺序(从第1个到最后一个)
    ↑
    │
1. skeleton.dtsi       (最底层)  ← 基础骨架
2. imx6ull.dtsi       (SoC层)  ← 外设默认定义
3. board.dtsi         (板级层)  ← 板级配置
4. board-custom.dtsi  (自定义)  ← 用户定制
5. board.dts          (机器层)  ← 最终生效
    │
    └──→ 最后的文件具有最终决定权
3.5 如何查看最终生效的配置

# 1. 编译 DTB
cd /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx6ull-alientek-emmc.dtb -j16

# 2. 反编译查看最终结果
./scripts/dtc/dtc -I dtb -O dts \
    arch/arm/boot/dts/imx6ull-alientek-emmc.dtb > /tmp/final.dts

# 3. 查看特定节点的最终状态
grep -A 30 "uart1:" /tmp/final.dts
3.6 调试技巧
技巧1:在某文件中加一个特殊标记,方便追踪


// 在你的 board.dtsi 中加注释
&uart1 {
    // 2024-01-01: 覆盖为 115200
    status = "okay";
};
技巧2:用 ! 安全覆盖

dtsi 中的节点可以被完全替换:


// 完全替换(不安全)
uart1: serial@02020000 {
    ...
};

// 安全追加(推荐)
&uart1 {
    ...
};
技巧3:检查是否有多个定义冲突


# 在 .dts/.dtsi 文件中搜索同名节点
grep -r "uart1:" arch/arm/boot/dts/*.dtsi
好问题!让我逐一详细解答。

1. GPIO 节点的双重身份
GPIO 控制器 + 中断控制器的实现原理
在设备树中,同一个硬件节点可以同时声明两种功能:


gpio0: gpio@fdd60000 {
    compatible = "rockchip,gpio-bank";
    // ① GPIO 控制器功能
    gpio-controller;
    #gpio-cells = <2>;          // gpio 使用 2 个 cell
    
    // ② 中断控制器功能
    interrupt-controller;
    #interrupt-cells = <2>;      // 中断使用 2 个 cell
};
硬件层面的原理:

GPIO 控制器内部有中断状态寄存器和中断屏蔽寄存器
当 GPIO 引脚配置为输入模式时,引脚电平变化可以触发外设中断
内核为同一个 GPIO 控制器实例注册两个子系统:
gpio_chip (用于 GPIO 操作)
irq_chip (用于中断处理)
你当前文件中的问题

gpio0: gpio@fdd60000 {
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;         // ✓ 声明自己是中断控制器
    #interrupt-cells = <2>;      // 中断需要 2 个 cell
};
你的配置是正确的,可以同时作为 GPIO 和中断控制器使用。

2. interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>; 参数详解
参数结构

interrupts = <第一参数 第二参数 第三参数>;
你当前的配置:


interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;
你的配置只有 2 个参数,但 #interrupt-cells = <2>
这意味着每个中断需要 2 个 cell:

参数	含义	说明
第 1 个 cell	中断号 / GPIO 引脚	哪个引脚触发中断
第 2 个 cell	中断触发类型	上升沿/下降沿/电平等
第一个参数(RK_PB5)的含义

RK_PB5 是一个宏,代表 GPIO 引脚编号
  ├─ RK_  = Rockchip 前缀
  ├─ P    = Port (端口号, A~H)
  ├─ B    = Bank (组号)
  └─ 5    = 引脚号 (0~31)
标准格式:

格式	示例	含义
RK_PXnn	RK_PB5	Port B, Pin 5
<&gpioX port pin>	<&gpio0 1 5>	gpio0, Port B, Pin 5
数字直接表示	<33>	直接是中断号 (GIC 中断号)
如何确定第一个参数
方法1:从原理图/芯片手册查找


例如:
  RK_PB5 = GPIO0_B5 = (Bank B = 1) * 32 + Pin 5 = 37
  RK_PC3 = GPIO0_C3 = (Bank C = 2) * 32 + Pin 3 = 67
方法2:查看芯片 dtsi 文件

在 rk3399.dtsi 或 imx6ull.dtsi 中查看宏定义:


// 常见的 GPIO 宏定义格式
#define RK_PA0  0
#define RK_PA1  1
#define RK_PA2  2
// ...
#define RK_PB0  32
#define RK_PB1  33
// ...
#define RK_PB5  37   // 1*32 + 5 = 37
方法3:参考其他已配置的 dts 文件

你的配置修正

interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;
// 应该改为:
interrupts = <37 IRQ_TYPE_LEVEL_LOW>;   // RK_PB5 = 1*32 + 5 = 37
// 或者使用 GPIO phandle:
interrupts = <&gpio0 1 5 IRQ_TYPE_LEVEL_LOW>;  // &gpio0, Port B, Pin 5
⚠️ 注意:你的 #interrupt-cells = <2> 表示需要 2 个 cell(中断号 + 触发类型)。但如果中断控制器要求第一个参数是 GPIO 的 (port, pin) 对,那可能需要 3 个 cell。具体要看芯片手册和驱动代码。

触发类型宏说明
宏	含义
IRQ_TYPE_NONE	无触发
IRQ_TYPE_EDGE_RISING	上升沿触发
IRQ_TYPE_EDGE_FALLING	下降沿触发
IRQ_TYPE_EDGE_BOTH	双边沿触发
IRQ_TYPE_LEVEL_HIGH	高电平触发
IRQ_TYPE_LEVEL_LOW	低电平触发
3. 多层节点引用与状态决定机制(详细整理)
3.1 节点引用的三种方式
方式1:完整节点定义

/dts-v1/;
/ {
    nodeA {                          // 完整定义一个节点
        prop = "value";
    };
};
方式2:节点引用 (&label)

/dts-v1/;
/ {
    nodeA {
        status = "disabled";          // 原始值
    };
};

&nodeA {
    status = "okay";                  // 覆盖值
};
方式3:子节点追加

/dts-v1/;
/ {
    parent {
        child {
            prop1 = "a";
        };
    };
};

&parent {
    child {
        prop2 = "b";                 // 追加 prop2
        prop1 = "c";                 // 覆盖 prop1
    };
};
3.2 完整的多层引用示例
文件结构

file1.dtsi  (最早加载,基础定义)
    │
    ▼
file2.dtsi  (中间层,板级定制)
    │
    ▼
file3.dts   (最后加载,最终生效)
file1.dtsi (基础层)

/dts-v1/;
/ {
    compatible = "rockchip,rk3399";
    
    i2c1: i2c@ff110000 {
        compatible = "rockchip,rk3399-i2c";
        reg = <0xff110000 0x1000>;
        clocks = <&cru CLK_I2C1>;
        status = "okay";               // ← 原始状态
        pinctrl-names = "default";
    };
    
    &i2c1 {
        pinctrl-0 = <&i2c1_xfer>;    // 追加引脚配置
    };
};
file2.dtsi (板级层)

/dts-v1/;
/ {
    // 引用并覆盖
    &i2c1 {
        status = "disabled";           // ← 覆盖为 disabled
        clock-names = "i2c";
    };
    
    // 添加新子节点
    &i2c1 {
        /* file2 追加的子节点 */
    };
};
file3.dts (最终层)

/dts-v1/;
#include "file1.dtsi"
#include "file2.dtsi"

/ {
    &i2c1 {
        status = "okay";               // ← 覆盖为 okay,最终生效
        pinctrl-0 = <&i2c1_xfer &i2c1_pull>;  // 合并引脚配置
    };
};
3.3 include 顺序与覆盖规则
dts 文件 include 顺序:


// imx6ull-alientek-emmc.dts
/dts-v1/;

#include "imx6ull.dtsi"                    // ① 第1个加载
#include "imx6ull-alientek-emmc.dtsi"      // ② 第2个加载 (覆盖)
dtc 编译器的处理逻辑:


dts 文件按行顺序处理,后出现的定义覆盖先出现的

加载顺序:
  ↓
┌─────────────────────────┐
│  imx6ull.dtsi           │  最早加载,设置默认值
│  ├── i2c1 status=okay   │
│  └── pinctrl-0=xxx       │
└──────────┬────────────────┘
           ↓ 覆盖
┌─────────────────────────┐
│  imx6ull-alientek-emmc  │  后加载,板级定制
│  ├── i2c1 status=disabled│  → 覆盖
│  └── pinctrl-1=yyy       │  → 追加
└──────────┬────────────────┘
           ↓ 覆盖
┌─────────────────────────┐
│  最终 .dtb 结果          │
│  ├── i2c1 status=disabled│
│  ├── pinctrl-0=xxx      │  (来自 dtsi1)
│  └── pinctrl-1=yyy      │  (来自 dtsi2)
└─────────────────────────┘
3.4 属性覆盖规则表
情况	处理方式	示例
同节点同名属性	后者覆盖前者	status = "okay" → status = "disabled"
同节点不同属性	合并	reg 保留 + interrupts 追加
子节点引用	合并子节点属性	子节点属性冲突时后者覆盖
同名子节点	合并到同一子节点	子节点内容被合并
全新节点/属性	追加	新内容被添加到结果
3.5 调试方法

# 查看最终 DTB 的内容,确认哪个文件生效
cd /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

# 1. 反编译 DTB 为 DTS
./scripts/dtc/dtc -I dtb -O dts \
    arch/arm/boot/dts/imx6ull-alientek-emmc.dtb > /tmp/final.dts

# 2. 查看特定节点
grep -A 20 "i2c1" /tmp/final.dts

# 3. 或者用 dtx_diff 查看差异
#    (需要在不同阶段反编译然后对比)

# 4. 查看 include 了哪些文件
head -20 arch/arm/boot/dts/imx6ull-alientek-emmc.dts
3.6 常见错误

/* 错误1:引用不存在的节点 */
&nonexistent {        // ← 节点不存在会报错
    status = "okay";
};

/* 错误2:状态拼写错误 */
status = "ok";        // ← 不是 "okay",不会生效
status = "enable";    // ← 不是 "okay",不会生效

/* 正确值 */
status = "okay";      // 启用
status = "disabled";  // 禁用
status = "reserved";  // 保留
总结
问题	答案
GPIO 可否同时是中断控制器?	可以,硬件上 GPIO 控制器有中断能力,设备树中声明两个属性即可
第一个参数是什么?	中断号或 GPIO 引脚编号,如 RK_PB5 = 37
如何确定第一个参数?	从芯片手册、原理图、或参考现有 dts 文件
节点状态由谁决定?	最后 include 的文件中对该节点的定义
属性冲突怎么办?	后加载的文件覆盖先加载的

设备树中关于时钟的节点

目前这个知识点还不太了解,后续用到了再来这边进行补充。

CPU节点

单核处理器CPU描述方法

cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu"; //指示设备类型为处理器
// 其他属性...
};
}

多核处理器CPU描述方法

cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu"; //指示设备类型为处理器
compatible = "arm,cortex-a9";
// 其他属性...
};
cpu1: cpu@1 {
device_type = "cpu"; //指示设备类型为处理器
compatible = "arm,cortex-a9";
// 其他属性...
};
}

cpu-map 节点主要用来进行大小核的描述

cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu-map {
cluster0 {
core0 {
cpu = <&cpu_l0>;
};
core1 {
cpu = <&cpu_l1>;
};
core2 {
cpu = <&cpu_l2>;
};
core3 {
cpu = <&cpu_l3>;
};
};
cluster1 {
core0 {
cpu = <&cpu_b0>;
};
core1 {
cpu = <&cpu_b1>;
};
};
};
上述的节点用的都不是很多,用到的时候再进行详细的解释

GPIO属性

gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru PCLK_GPI00>, <&pmucru DBCLK_GPI00>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
ft5x06: ft5x06@38 {
status = "disabled";
compatible = "edt,edt-ft5306";
reg = <0x38>;
touch-gpio = <&gpio0 RK_PB5 IRQ_TYPE_EDGE_RISING>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <1280>;
touch_type = <1>;
};
gpio-controller 属性:gpio-controller 属性用于标识当前节点所描述的设备是一个GPIO 控制器。其本身没有特定的属性值,只需出现在节点中即可。
#gpio-cells 属性:#gpio-cells 属性值是一个整数,表示用于GPIO 引脚描述符的单元数,通常值为2
你的 GPIO 控制器节点:


gpio0: gpio@fdd60000 {
    gpio-controller;
    #gpio-cells = <2>;
};
然后触摸屏节点引用它:
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
你看到的是 3 项:
<&gpio0 RK_PB6 GPIO_ACTIVE_LOW>
但设备树解析时要分成两部分:
<&gpio0    RK_PB6    GPIO_ACTIVE_LOW>
  ↑          ↑           ↑
 phandle   cell0       cell1
其中:
部分	是否属于 #gpio-cells	含义
&gpio0	不属于	指向 GPIO 控制器节点的引用,也叫 phandle
RK_PB6	属于	第 1 个 GPIO 参数,表示 GPIO 编号
GPIO_ACTIVE_LOW	属于	第 2 个 GPIO 参数,表示有效电平/标志
所以:
#gpio-cells = <2>;
表示的是 &gpio0 后面还要跟 2 个参数,不是整个尖括号里总共 2 个。
因此:

reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;



gpio-controller@00000000 {
compatible = "foo";
reg = <0x00000000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <18>;
gpio-reserved-ranges = <0 4>, <12 2>;
gpio-line-names = "MMC-CD", "MMC-WP",
"voD eth", "RST eth", "LED R",
"LED G", "LED B", "col A",
"col B", "col C", "col D",
"NMI button", "Row A", "Row B",
"Row C", "Row D", "poweroff",
"reset";
};
其他属性:ngpios 属性:指定了GPIO 控制器所支持的GPIO 引脚数量。
gpio-reserved-ranges 属性:定义了保留的GPIO范围。
gpio-line-names 属性:定义了GPIO 引脚的名称,以逗号分隔。这里描述的就是ngpios 属
性所指的18 个GPIO 引脚对应的名称

Pinctl属性

通过pinctrl 在设备树中可以设置引脚复用关系,pinctrl 的语法由客户端和服务端两个部分
构成。
客户端代码格式是固定的,通过pinctrl-names 和pinctrl-<N>属性完成
客户端常见的三种使用模式
node {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
}
node {
pinctrl-names = "default", "wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
}
node {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;
}
服务端
服务端是设备树中定义引脚配置的部分,不同处理器其服务端代码格式是不同的


上一篇 IO模型详解(数据流本质视角)

阻塞、非阻塞、同步、异步详解(数据流本质视角) 本文档从”数据从设备到用户空间的完整旅途”这一角...

下一篇 设备树3-设备树展开过程

本章内容主要参考的又韦东山老版视频中设备树章节、迅为的驱动开发手册、以及claude生成的文章 1.是再不使用设备树的情...