平台设备驱动程序

片上设备

物理存在于 SoC 内部的硬件模块,其特点是焊死在芯片内部,不可插拔,不可枚举(无discovery机制)

  • UART 控制器
  • I2C 控制器
  • SPI 控制器
  • GPIO
  • Timer

平台设备

设备树

设备树解决什么问题

Device Tree 用来描述板级硬件资源,尤其适合描述 SoC 内部这类无法自动枚举的设备,例如 UART、I2C、SPI、GPIO、Timer 等。Bootloader 启动内核前把 .dtb 放到内存中,内核启动后解析 .dtb,再根据节点中的 compatiblereginterruptspinctrl 等信息完成设备创建和驱动匹配。

可以把设备树理解成“硬件描述”和“驱动代码”的分离:驱动不应该把某块板子的寄存器地址、中断号、 引脚复用写死在 C 代码里,而是从设备树读取。

文件关系和编译链路

文件/工具作用
.dts板级设备树源码,通常一个开发板对应一个 .dts
.dtsi公共设备树片段,常用于 SoC、芯片系列、公共外设配置复用。
DTCDevice Tree Compiler,把 .dts 编译成 .dtb
.dtb设备树二进制文件,由 Bootloader 加载并传给内核。

.dtsi 的用法类似 C 语言头文件,可以被 .dts 通过 #include 引入;.dtsi 本身也可以继续 include 其它 .dtsi

/dts-v1/;

#include <dt-bindings/input/input.h>
#include "a1.dtsi"
#include <dt-bindings/interrupt-controller/irq.h>

DTC 源码位于内核的 scripts/dtc 目录。内核选中 CONFIG_OF 后,编译内核时会生成主机侧 可执行程序 DTC。

基本结构

根节点

根节点 / 描述整块板子的顶层信息,常见属性是 modelcompatible

/dts-v1/;

#include "topeet-rk3588-linux.dtsi"
#include "rk3588-linux.dtsi"
#include "topeet-screen-lcds.dts"
#include "topeet-camera-config.dtsi"

/ {
    model = "TOPEET RK3588 LP4 LP5 Board";
    compatible = "rockchip,rk3588-evb3-lp5-v10", "rockchip,rk3588";
};
  • model:当前开发板名称,主要用于标识。
  • compatible:匹配 machine 或驱动,通常按“从具体到通用”的顺序写。

子节点

设备节点的一般格式如下:

[label:] node-name[@unit-address] {
    [property definitions];
    [child nodes];
};
  • **label:**节点标签,用于 &label 引用,必须全局唯一。(可选字段)

    • 提供简短的引用名称,避免写冗长的全路径,提高可读性
  • **node-name:**节点名称,表示设备类型,可以重复。(必须字段)

  • **unit-address:**节点地址,通常应与 reg 中的首地址对应。(可选字段)

    • 格式规范:节点名@地址,紧跟着节点名称后,地址值支持10/16进制,字符串,需与reg属性匹配
  • **属性定义:**节点的核心,由一系列键值对组成,用来描述设备的具体属性(如compatible属性)

image-20260605225032627

示例:

uart0: serial@0x10030000 {  //是 label,方便其他 DTS 节点用 &uart0 引用
    compatible = "ingenic,8250-uart";
    reg = <0x10030000 0x1000>;
    reg-shift = <2>;

    interrupt-parent = <&core_intc>;
    interrupts = <IRQ_UART0>;
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pc1>;
    status = "disabled";
};

特殊节点

chosen

不描述硬件,用于bootloader向内核传递运行是的参数

chosen {
    bootargs = "earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 rw rootwait rcupdate.rcu_expedited=1 rcu_nocbs=all";
};
  • bootargs: 传递内核启动参数
  • **stdout-path:**指定内核标准输出设备

aliases

节点顾名思义就是“别名”。设备树中的硬件节点路径通常非常长且复杂,为了方便人类阅读和内核驱动快速找到设备,就给长路径起一个短别名。

aliases {
    i2s0 = "/i2s@19700000";
    i2s1 = "/i2s@19800000";
    i2c0 = "/i2c@18d00000";
    i2c1 = "/i2c@18e00000";
    gpio0 = "/gpio@1B000000";
    gpio1 = "/gpio@1B100000";
    gpio2 = "/gpio@1B200000";
    efuse0 = "/efuse@1BA00000";
};

cpus

描述板子上的中央处理器(CPU)信息device_type = "cpu":这是每个 CPU 子节点必须包含的属性,用来明确告诉内核“我是一个处理器核心”。

cpus {
    #address-cells = <1>;
    #size-cells = <0>;

    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a7";
        reg = <0xf00>;
    };

    cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a7";
        reg = <0xf01>;
    };
};

memory

描述系统的物理内存(RAM)布局

memory@80000000 {
    device_type = "memory";
    reg = <0x80000000 0x10000000>;/* 物理内存的起始地址是 0x80000000(2GB 处),大小是 0x20000000(512MB) */
};

标准属性

属性作用
compatible最重要的匹配属性,驱动通常通过 of_match_table 与它匹配。
reg定义该设备的寄存器基地址长度
status控制节点是否启用,常见值是 okaydisabled
interrupt-parent指定中断所属的中断控制器。
interrupts描述中断号及触发方式,具体格式由中断控制器决定。
pinctrl-names定义 pinctrl 状态名称,例如 defaultsleep
pinctrl-0引用具体引脚复用配置,例如把 GPIO 切换成 UART 功能。

compatible

内核通过该属性的值来为设备匹配对应的驱动程序,是设备树中最关键的属性之一。

  • **格式:**采用"厂商,设备型号"格式(如"sony,imx415").值可以是单个字符串或字符串列表,也可以是多值。
	compatible = "rockchip,rk3588-evb3-lp5-v10", "rockchip,rk3588"

reg

  • **核心作用:**定义设备在父总线地址空间中的寄存器地址范围或内存映射区域,告知驱动程序硬件资源的具体位置。
  • **格式:**由一个或多个
    对组成

寻址属性

  • #address-cells
    • 定义子节点reg属性中**,地址字段占用的32位单元**
  • #size-cells
    • 定义子节点reg属性中,大小字段占用的32位单元

例:

/ {
    compatible = "rockchip,rk3399";

    #address-cells = <2>;
    #size-cells = <2>;
};

root节点的子节点 reg格式: address占2个cell,address占2个cell reg = <addr_hi addr_low size_hi size_low>

image-20260605233338163

&csi2_dphy3 {
    status = "okay";
    ports {
        #address-cells = <1>;
        #size-cells = <0>;
        port@0 {
            reg = <0>;
            #address-cells = <1>;
            #size-cells = <0>;
            mipidphy3_in_ucam0: endpoint@1 {
                reg = <1>;
                remote-endpoint = <&ov5695_out_4>;
                data-lanes = <1 2>;
            };
            mipidphy3_in_ucam0_ov13850: endpoint@2 {
                reg = <2>;
                remote-endpoint = <&ov13850_out_4>;
                data-lanes = <1 2 3 4>;
            };
            mipidphy3_in_ucam0_imx415: endpoint@3 {
                reg = <3>;
                remote-endpoint = <&imx415_out_4>;
                data-lanes = <1 2 3 4>;
            };
        };
        port@1 {
            reg = <1>;
            #address-cells = <1>;
            #size-cells = <0>;
            csidphy3_out: endpoint@0 {
                reg = <0>;
                remote-endpoint = <&mipi4_csi2_input>;
            };
        };
    };
};

特有属性

属性作用
reg-shift描述 UART 寄存器地址步进
  • reg-shift = <2> 后,驱动访问第 n 个寄存器时地址变成:base + (n << 2)
  • 也就是:
    • UART_RX/TXbase + 0x00
    • UART_IERbase + 0x04
    • UART_IIR/FCRbase + 0x08
    • UART_LCRbase + 0x0c

引用与板级覆盖

SoC 级 .dtsi 通常先定义硬件节点,并默认 status = "disabled"。板级 .dts 再通过 &label 引用该节点,按实际硬件连接启用或补充配置。

rtc: rtc@0x10003000 {
    compatible = "ingenic,rtc";
    reg = <0x10003000 0x1000>;
    interrupt-parent = <&core_intc>;
    interrupts = <IRQ_RTC>;
    status = "disabled";
};

&rtc {
    status = "okay"; /* 修改状态 */
};

&label含义不是重新创建节点,而是找到已经存在的节点,对它追加、修改或覆盖属性

地址描述

设备树用 #address-cells#size-cellsreg 描述子节点地址。它们通常出现在总线节点中, 例如 soci2cspipcisimple-bus 等。

cell

cell 是设备树中地址和长度的基本单位,1 cell = 32 bit = 4 字节。在 .dts 文件里,尖括号 < > 中的每个数字通常就是一个 cell。

  • #address-cells:子节点地址占几个 cell。
  • #size-cells:子节点长度占几个 cell。
  • reg:按父节点的 cell 规则解释地址和长度。

reg

reg 的基本形式是:

reg = <address size>;

addresssize 实际上可能由多个 cell 组成:

/* 每个元组代表设备使用的一段地址范围 */
reg = <address... size...>;

32 位地址常见写法:

#address-cells = <1>;
#size-cells = <1>;

uart@fe660000 {
    reg = <0xfe660000 0x100>;
};

64 位地址常见写法:

#address-cells = <2>;
#size-cells = <2>;

如果子节点只需要编号,不需要长度,例如某些 ports、I2C 设备、SPI 设备,#size-cells 可以为 0