本篇文章介绍一些基础的设备树实例分析,包括GPIO,中断,pinmux,cpu,时钟等内容,逐一拆建这些设备常见设备树形式,做到读懂,会改。
一、GPIO 相关
GPIO 是最基础的一块,因为 LED、按键、使能脚、复位脚、方向控制脚这些全都离不开 GPIO。
但你要先建立一个正确认识:
GPIO 在设备树里不是只有"一个属性",而是分成"GPIO 控制器"和"GPIO 使用者"两类内容。
1. GPIO 控制器节点是什么
设备树里,GPIO 控制器节点表示:
谁来提供 GPIO 资源。
比如:
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
gpio-controller;
#gpio-cells = <2>;
};
你要把这段看成:
- **gpio0:**这个节点的标签,后面别人会用 &gpio0 来引用它
- **gpio@fdd60000:**这是一个 GPIO 控制器节点,它的寄存器基地址和这个地址有关
- **compatible:**说明它是什么类型的 GPIO 控制器
- **reg:**说明它的寄存器地址范围
- **gpio-controller:**说明这个节点是gpio控制器,能给别人提供 GPIO
- **#gpio-cells = <2>:**说明别的设备引用它时,要跟两个参数
2. #gpio-cells 到底是什么意思
你看到:
#gpio-cells = <2>;
不要只记"等于 2",而要理解成:
以后谁来引用这个 GPIO 控制器,就必须提供两个参数。
例如:
led-gpios = <&gpio0 5 0>;
这里:
- &gpio0 是被引用的 GPIO 控制器
- 后面 5 0 这两个参数,就对应 #gpio-cells = <2>
一般可粗略理解为:
- 第一个参数:bank 内的引脚号
- 第二个参数:标志位,比如高低电平有效性、开漏等
3. 使用 GPIO 的设备节点是什么
这类节点才是你平时最常改的。
例如 LED 节点:
myled {
compatible = "myvendor,myled";
led-gpios = <&gpio0 5 0>;
status = "okay";
};
你要会这样理解:
- myled 是一个设备节点
- 它不是 GPIO 控制器,它只是"使用 GPIO 的设备"
- led-gpios 表示这个设备要用一个 GPIO
- &gpio0 表示这个 GPIO 来自 gpio0 控制器
- 5 0 是这个控制器所需的参数
所以你以后看到 xxx-gpios,第一反应应该是:
这个设备在向某个 GPIO 控制器申请引脚资源。
4. GPIO 在驱动里是怎么用起来的
设备树里写了:
led-gpios = <&gpio0 5 0>;
驱动在 probe() 里会去读这个属性,比如通过:
- of_get_named_gpio()
- 或更推荐的 devm_gpiod_get()
然后拿到一个 GPIO 资源句柄,接着:
-
设置输入/输出方向
-
输出高低电平
-
读取输入电平
所以你要理解:
设备树只是在"声明这个设备要用 GPIO",真正拿到并控制 GPIO 的动作是在驱动里完成的。
5. GPIO 最容易混淆的地方
最容易混淆的是:
误区 1:以为 GPIO 只是一个引脚号
不是。
GPIO 在设备树里至少牵涉:
-
一个 GPIO 控制器节点
-
一个引用它的设备节点
-
若干参数
误区 2:以为引用 GPIO 就能直接用
也不一定,因为这个引脚还可能需要 pinmux 配置正确。
这就引出了下一部分。
二、pinmux 相关
pinmux 是你必须和 GPIO 一起学的。
如果只学 GPIO,不学 pinmux,后面你会经常遇到这种情况:
-
驱动里明明拿到 GPIO 了
-
输出高低电平也没报错
-
结果 LED 不亮、按键不响应
很可能不是 GPIO 错了,而是:
引脚压根没有被切到 GPIO 功能。
1. pinmux 本质是什么
pinmux 的本质是:
同一个物理引脚的功能选择。
一个脚可能可以复用成:
-
GPIO
-
UART_TX / UART_RX
-
SPI
-
I2C
-
PWM
-
SDIO
所以你不能只说"这个脚是 GPIO",更准确地说应该是:
这个脚当前被 pinmux 配成 GPIO 功能。
2. 设备树里 pinmux 最常见长什么样
设备树里,pinmux 最常见会出现在设备节点里:
myled {
compatible = "myvendor,myled";
pinctrl-names = "default";
pinctrl-0 = <&led_pin>;
led-gpios = <&gpio0 5 0>;
status = "okay";
};
你要把这两行理解透:
pinctrl-names = "default";
表示这个设备有一个 pinctrl 状态,名字叫 default
pinctrl-0 = <&led_pin>;
表示编号 0 的这个状态,使用 &led_pin 这组引脚配置
也就是说:
设备在默认工作状态下,会套用 led_pin 这组 pin 配置。
3. 为什么有 GPIO 还要有 pinctrl
这是最核心的问题。
因为:
- led-gpios = <&gpio0 5 0> 只说明"逻辑上使用这个 GPIO"
- pinctrl-0 = <&led_pin> 才说明"物理引脚要切到正确功能"
你可以这样记:
GPIO
告诉驱动:我要用这个引脚做输入/输出
pinmux
告诉硬件:请把这个物理脚切成 GPIO 模式,而不是 UART/SPI/I2C 模式
所以:
GPIO 决定"怎么用",pinmux 决定"能不能按这个方式用"。
4. pinctrl 节点你要看到什么程度
你现在不需要一开始就研究特别复杂的 pinctrl 大节点,但你至少要知道:
-
设备节点里会通过 pinctrl-0 引用某个 pin 配置
-
那个被引用的 pin 配置,通常定义在 pinctrl 控制器下面
-
它的作用就是把某些引脚设成某种复用功能
比如 LED 可能是"切成 GPIO 输出",
UART 可能是"切成 TX/RX 功能"。
5. pinmux 这一块你必须掌握到什么程度
你至少要做到:
- 理解 pinmux 不是控制电平,而是选择引脚功能
- 看懂 pinctrl-names
- 看懂 pinctrl-0
- 知道 default 是一种常见的默认引脚状态
- 知道为什么很多设备节点既有 xxx-gpios 又有 pinctrl-0
6. pinmux 最容易混淆的地方
误区 1:把 pinmux 和 GPIO 当成一回事
不是。
-
GPIO:使用引脚做输入输出
-
pinmux:决定引脚当前能不能做 GPIO
误区 2:以为所有引脚默认就是 GPIO
很多引脚默认根本不是 GPIO,而是别的外设功能。
这也是为什么 pinmux 这一块一定要学。
三、中断相关:你必须真正掌握什么
中断这一块,建议你一定要结合"按键驱动"去理解。
因为中断不是只背几个 API,而是要看懂整条链路。
1. 中断在设备树里是什么角色
设备树里中断相关,本质上是在回答两个问题:
问题一
谁能提供中断?
这就是中断控制器节点。
问题二
谁要使用中断?
这就是设备节点里的 interrupts 、interrupt -parent。
2. 中断控制器节点要怎么看
你在某些节点里会看到:
interrupt-controller;
#interrupt-cells = <2>;
这表示:
-
这个节点本身可以作为中断控制器
-
别人如果把它当作中断来源来引用,就要跟两个参数
比如 GPIO bank 很典型:
-
它既是 GPIO 控制器
-
也可以作为中断控制器
因为按键接到 GPIO 上时,GPIO 控制器可以检测边沿/电平变化,并上报中断。
所以一个节点里同时出现:
gpio-controller;
interrupt-controller;
是很正常的。
3. 使用中断的设备节点怎么看
例如:
mykey {
compatible = "myvendor,mykey";
interrupt-parent = <&gpio0>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
status = "okay";
};
你要会看懂:
interrupt-parent = <&gpio0>;
表示这个设备的中断来源是 gpio0
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
表示中断参数,比如:
-
第 5 号引脚/中断源
-
下降沿触发
所以这整段的意思是:
这个按键设备通过 GPIO0 提供中断能力,按某个触发方式触发中断。
4. 中断和 GPIO 的关系要怎么理解
按键这种场景里,GPIO 和中断经常是绑在一起的。
比如:
-
按键接在一个 GPIO 输入脚上
-
按键按下后引脚电平变化
-
GPIO 控制器检测到变化
-
GPIO 控制器向中断控制器上报
-
CPU 收到中断
-
驱动的中断处理函数执行
所以你要把中断链路理解成:
按键不是直接通知 CPU,而是先通过 GPIO 控制器,再进入中断链路。
5. 中断在驱动里怎么对应
设备树里写了:
- interrupt-parent
- interrupts
驱动在 probe() 里通常会去拿 IRQ 号,比如:
- platform_get_irq()
- 或其他 OF/IRQ 相关接口
拿到 IRQ 后,再:
- request_irq()
- 写中断处理函数
也就是说:
设备树声明"这个设备有中断资源",驱动负责"把这个中断资源申请并用起来"。
6. 中断这一块你必须掌握到什么程度
你至少要做到:
- 看懂谁是中断控制器
- 看懂谁在使用中断
- 看懂 interrupt-parent
- 看懂 interrupts
- 理解为什么 GPIO 节点可能同时是 gpio-controller 和 interrupt-controller
- 能把"按键按下 -> GPIO 电平变化 -> 中断触发 -> 驱动处理函数执行"讲清楚
7. 中断最容易混淆的地方
误区 1:以为 interrupts 就是中断号本身
不总是。
它的具体格式依赖 interrupt-parent 对应的中断控制器。
误区 2:以为设备直接和 CPU 相连
很多时候不是。
中断路径中间往往还隔着 GPIO 控制器和中断控制器。
四、时钟相关
1. 先建立一个正确认识
时钟不是只有 CPU 才有。
你要记住:
很多外设控制器本身也依赖时钟。
比如:
-
GPIO 控制器
-
UART 控制器
-
PWM
-
SPI
-
I2C
-
定时器
这些模块内部有:
-
寄存器接口逻辑
-
状态机
-
采样逻辑
-
去抖逻辑
-
波特率/时序逻辑
这些很多都要靠时钟工作。
2. 设备树里时钟最常见的属性
你最需要看懂的是:
- clocks
- clock-names
例如:
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;
};
你要这样理解:
&pmucru
表示时钟提供者
PCLK_GPIO0
表示 GPIO0 模块的 APB/总线接口时钟
DBCLK_GPIO0
表示 GPIO0 模块的 debounce 或功能参考时钟
所以这不是"GPIO 对外输出时钟",而是:
GPIO 控制器自己需要这些时钟资源。
3. clock-names 又是干什么的
有时候设备树里不仅写:
clocks = <...>, <...>;
还会写:
clock-names = "pclk", "dbclk";
作用就是:
给多个时钟资源起名字,方便驱动按名字去获取。
这样驱动里就不会只是"拿第 0 个、第 1 个时钟",而是"拿 pclk、dbclk"。
4. 为什么驱动里要先开时钟
很多外设控制器如果时钟没开,可能会出现:
-
寄存器访问不正常
-
模块不响应
-
功能根本不起作用
所以驱动在 probe() 里,常常要先:
-
获取时钟
-
使能时钟
然后再:
-
映射寄存器
-
初始化硬件
-
注册中断或字符设备
所以你要建立一个意识:
设备树里的
clocks是"设备模块工作资源"的一部分。
5. 时钟这一块你必须掌握到什么程度
你至少要做到:
-
看懂 clocks
-
看懂 clock-names
-
知道谁是时钟提供者
-
知道谁是时钟消费者
-
知道外设为什么也要时钟
-
知道驱动里通常要先使能时钟
6. 时钟最容易混淆的地方
误区 1:以为时钟就是 CPU 主频
不是。
CPU 时钟只是整个 SoC 时钟树的一部分。
误区 2:以为 GPIO 节点写时钟很奇怪
不奇怪。
因为 GPIO 不是一根线,而是一个片上控制器模块。
五、CPU 相关:你现在到底该掌握什么
CPU 这部分确实也在设备树里,但和前四部分比起来,当前优先级没那么高。
1. 设备树里 CPU 节点是什么样
通常会看到:
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0>;
};
};
你要知道:
- cpus 是 CPU 节点的容器
- cpu@0 表示第 0 个 CPU 核
- compatible 表示 CPU 类型
- reg 通常表示 CPU 的逻辑编号
2. CPU 节点在设备树里为什么存在
设备树里描述 CPU,不是为了你写 LED 驱动用,而更多是为了:
-
启动阶段识别 CPU
-
多核系统描述
-
CPU 频率、电压、功耗管理
-
SMP 拓扑
-
中断和调度相关配置
也就是说:
CPU 设备树更多偏系统级,而不是单个外设驱动级。
3. 你当前对 CPU 最该掌握到什么程度
你现在只需要做到:
- 知道设备树里有 cpus 节点
- 知道 CPU 也有 compatible
- 知道 CPU 也会和时钟、电源、频率等资源关联
- 知道 CPU 是最终执行驱动代码、访问寄存器、响应中断的主体
你现在暂时不用深入:
-
OPP
-
DVFS
-
cpu-map
-
拓扑结构
-
cache / MMU 相关设备树细节
这些更偏系统启动和电源管理。
4. CPU 这块最容易混淆的地方
误区:以为 CPU 设备树和外设设备树是一个层级的重要性
对内核整体来说都重要。
但对你当前的驱动学习来说,CPU 设备树只是"知道有这个东西",外设相关才是主战场。