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 一起学的。
如果只学 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 的配置可能长这样:

&led_pin {
    pinctrl_led: ledgrp {
        fsl,pins = <
            MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x1b0b0
        >;
    };
};

也就是说:

设备在默认工作状态下,会套用 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 设备树只是“知道有这个东西”,外设相关才是主战场。


六.改写Linux源码led设备树实例

这一节我们改写Linux内核源码下的led设备树的内容,删除原有的led设备树,自己撰写一个新的。

6.1Linux内核源码设备树目录结构

你这个目录里有下面这些:

Makefile
rk3568.dtsi
rk3568-linux.dtsi
rk3568-pinctrl.dtsi
rockchip-pinconf.dtsi
topeet-rk3568-linux.dtsi
topeet-rk3568-linux.dts
topeet-rk3568-linux.dtb
topeet-screen-lcds.dts
rk3568-dram-default-timing.dtsi

它们不是平铺关系,而是一层层 include 叠上来的


1.1 rk3568.dtsi

这是 RK3568 SoC 级别的公共设备树文件

里面通常放的是:

  • CPU

  • 总线

  • GPIO 控制器

  • I2C/SPI/UART 控制器

  • 中断控制器

  • 时钟控制器

  • pinctrl 控制器

  • 各种 SoC 内部外设节点

你可以把它理解成:

“RK3568 这颗芯片本身有什么硬件资源”

这个文件一般不描述你板子上“LED 接哪根脚、按键接哪根脚”,因为这些是板级信息,不是芯片级公共信息。


1.2 rk3568-pinctrl.dtsi

这是 RK3568 的引脚复用配置文件

里面通常放的是:

  • 各类 pinctrl 分组

  • UART/I2C/SPI/PWM/GPIO 的引脚复用配置

  • 某些功能组的 pin 配置节点

你可以理解成:

“RK3568 这颗芯片的引脚复用方案库”

如果你要给 LED 选一个 GPIO 脚,往往会和这个文件里的 pinctrl 配置有关。


1.3 rockchip-pinconf.dtsi

这是 Rockchip 通用 pin configuration 宏/配置文件

里面通常不是设备节点,而是一些:

  • pin 配置宏

  • 上拉/下拉

  • 驱动能力

  • 输入使能

  • 复用参数相关定义

你可以把它理解成:

“Rockchip pinctrl 配置时会用到的公共配置库”

平时你一般不会直接在这里加 LED 节点。


1.4 rk3568-linux.dtsi

这个一般是 RK3568 Linux 平台的一个中间层公共文件

通常作用是:

  • 在 rk3568.dtsi 基础上

  • 再补一些 Linux 使用时默认打开/关闭的节点

  • 或者做一些平台级调整

你可以把它理解成:

“在 SoC 基础上,再给 Linux 系统整理出一版通用配置”


1.5 topeet-rk3568-linux.dtsi

这是 你这块 topeet 开发板的板级公共配置文件

里面通常放:

  • 板级电源配置

  • 外设使能状态

  • 某些板载设备

  • GPIO/按键/显示/音频等板级资源

  • 可能会 include 屏幕、摄像头等外设配置

你可以把它理解成:

“topeet 这块板子相比纯 RK3568 芯片,多了哪些板级连接关系”

如果你想加板载 LED,这个文件也可能是合适位置,尤其当 LED 不是只给一个具体最终产品用,而是给这块板级公共平台都用。


1.6 topeet-rk3568-linux.dts

这是 最终板子的顶层设备树文件,最重要。

它通常会:

  • include 前面的几个 .dtsi

  • 指定 model / compatible

  • 做最终板级选择

  • 补最终节点

  • 编译成 .dtb

你可以把它理解成:

“最后真正要编译成 dtb 的顶层设备树入口文件”

你自己新增一个 LED 节点,最推荐先放这里。

因为:

  • 最安全

  • 最直观

  • 不容易影响公共文件

  • 后续维护最简单


1.7 topeet-rk3568-linux.dtb

这是 编译后的二进制设备树文件

它不是源码,不能直接拿来编辑。

关系是:

.dts/.dtsi --> 编译 --> .dtb

1.8 topeet-screen-lcds.dts

这是和 屏幕/LCD 相关的设备树文件。

一般用于:

  • LCD 面板

  • 屏幕时序

  • 显示相关节点

通常和 LED 无关,除非你要加背光 LED。


1.9 rk3568-dram-default-timing.dtsi

这是和 DDR/DRAM 时序 相关的文件。

一般和 LED 没关系,不用动。

6.2找到原本的led节点

可以先在整个目录中搜索:

grep -R "gpio-leds" -n .
grep -R "led" -n .
grep -R "work-led" -n .
grep -R "user-led" -n .

找到类似于:

leds {
    compatible = "gpio-leds";
    ...
};

的结构,注释掉或者重写他。

6.3撰写 led 设备树

1.前置工作

撰写led设备树我们需要两个内容:

(1)一个是led接的GPIO口是什么,因为我们要引用GPIO;

(2)其次是这个GPIO口的复用方式有什么,我们应该将其复用方式选择为GPIO方式。

LED的gpio接口为GPIO0_B7;

然后在“Rockchip RK3568 TRM”数据手册中找到控制GPIO0_B的复用寄存器:

然后找到这个寄存器对GPIO0_B7的描述:

可以发现,当这个寄存器的12:14位为000的时候,GPIO是作为GPIO模式的。

2.撰写设备树

拿到这些信息之后,可以开始撰写led的设备树内容,首先打开开发板级的设备树文件,也就是图中的:topeet-rk3568-linux.dtsi

然后我们要写复用节点,也就是pinctrl节点,pinctrl节点一般我们也写在板机配置包里,不直接更改rk3568的原始设备树,可以在topeet-rk3568-linux.dtsi或topeet-rk3568-linux.dts中搜索pinctrl,看厂家是把pinctrl节点写在哪。

我们是在topeet-rk3568-linux.dts找到了pinctrl节点,我们仿照格式在下面撰写即可:

这里的RK_FUNC_GPIO也可以填0,就是我们上述查手册查到的0代表GPIO模式。

七,设备树传递给内核

设备树是如何传递给内核的?

(1)设备树源码 .dts 文件先被编译成 .dtb 文件

(2)dtb 文件被打包进内核镜像(boot.img中包含 dtb 文件)( boot 是内核镜像不是 uboot 镜像)

(3)uboot 将内核镜像 boot.img 加载到内存的某个地址上

(4)内核在初始化的时候将内存中的 dtb 文件展开成内核可以识别的设备树

(5)在内存中申请一块空间存放展开后的设备树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值