Linux内核与驱动:12.设备树实例分析

本篇文章介绍一些基础的设备树实例分析,包括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. 中断在设备树里是什么角色

设备树里中断相关,本质上是在回答两个问题:

问题一

谁能提供中断?

这就是中断控制器节点。

问题二

谁要使用中断?

这就是设备节点里的 interruptsinterrupt -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 设备树只是"知道有这个东西",外设相关才是主战场。


相关推荐
一月千帆2 小时前
基于STM32的智能小型洗碗机控制系统设计
stm32·单片机·嵌入式硬件
cmpxr_2 小时前
【算法】ECC验签名
单片机·算法
Edward111111112 小时前
TS安装
linux·运维·服务器
ZzzZZzzzZZZzzzz…2 小时前
Docker 数据持久化:4种挂载方式 + 备份还原实战
linux·运维·docker·云原生·容器·数据持久化
弹简特2 小时前
【Linux命令饲养指南】03-Linux文件操作与编辑:从“摸鱼”到“搬砖”,这篇让你把文件玩出花
linux
LSG_Dawn2 小时前
linux 开机黑屏,/dev/nvme1n1p4:clean, xxxxx/xxxxxxx files, xxxx/xxxx blocks
linux·运维·服务器
送外卖的CV工程师2 小时前
STM32 CubeMX Makefile 工程编译 入门指南
stm32·单片机·嵌入式硬件·学习·makefile·stm32cubemx
喜欢吃燃面2 小时前
Linux 进程间通信:命名管道与 System V 共享内存深度解析
linux·运维·服务器·学习
项目題供诗2 小时前
STM32-新建工程(二)
stm32·单片机·嵌入式硬件