4、设备树(DT)

一、概述

​ 在传统 Linux 内核中,ARM 架构的板极硬件细节过多地被硬编码在 arch/arm/plat-xxx 和arch/arm/mach-xxx,比如板上的 platform 设备、resource、i2c_board_info、spi_board_info 以及各种硬件的 platform_data,这些板级细节代码对内核来讲只不过是垃圾代码。而采用Device Tree 后,许多硬件的细节可以直接透过它传递给 Linux,而不再需要在 kernel 中进行大量的冗余编码。导致 ARM 的 merge 工作量较大。之后经过一些讨论,对 ARM 平台的相关 code 做出如下相关规范调整,这个也正是引入DTS 的原因。

1、ARM 的核心代码仍然保存在 arch/arm 目录下

2、ARM SoC core architecture code 保存在 arch/arm 目录下

3、ARM SOC 的周边外设模块的驱动保存在 drivers 目录下

4、ARM SOC 的特定代码在 arch/arm/mach-xxx 目录下

5、ARM SOC board specific 的代码被移除,由 DeviceTree 机制来负责传递硬件拓扑和硬件资源信息。

​ 本质上,Device Tree改变了原来用 code方式将硬件配置信息嵌入到内核代码的方法,改用 bootloader 传递一个 DB 的形式。对于嵌入式系统,在系统启动阶段,bootloader 会加载内核并将控制权转交给内核,此外,还需要把上述的三个参数信息传递给 kernel,以便 kernel可以有较大的灵活性。在linux kernel 中,Device Tree 的设计目标就是如此。

在 devie tree 中,可描述的信息包括:

1、CPU 的数量和类别

2、内存基地址和大小

3、总线和桥

4、外设连接

5、中断控制器和中断的使用情况

6、GPIO 控制器和 GPIO 使用情况

7、clock 控制器和 clock 使用情况

​ 它基本就是一棵电路板上的 CPU、总线、设备组成的树,Bootloader 会将这棵树传递给内核,然后内核来识别这棵树,并根据它展开出 Linux 内核中的 platform_device、i2c_client、spi_device 等设备,而这些设备用到的内存、IRQ 等资源,也被传递给内核,内核会将这些资源绑定给展开的相应设备。Linux 内核从 3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。比如在ARM Linux内,一个.dts(device treesource)文件对应一个 ARM 的 machine,一般放置在内核的"arch/arm/boot/dts/“目录内,比如stmp1a-dk1 参考板的板级设备树文件就是"arch/arm/boot/dts/ stm32mp157a-dk1.dts”。这个文件可以通过 make dtbs 命令编译成二进制的.dtb 文件供内核驱动使用。基于同样的软件分层设计的思想,由于一个 SoC 可能对应多个 machine,如果每个 machine 的设备树都写成一个完全独立的.dts 文件,那么势必相当一些.dts 文件有重复的部分,为了解决这个问题,Linux 设备树目录把一个 SoC 公用的部分或者多个 machine 共同的部分提炼为相应的.dtsi文件。这样每个.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi 文件, 这样就是整个设备树的管理更加有序。我这里用 Linux4.19.94 源码自带的 goodixtouchscreen 触摸芯片为例来分析设备树的使用和移植。这个触摸芯片的设备树节点信息在"Documentation/devicetree/bindings/input/touchscreen/goodix.txt"有详细说明,其驱动源码是" drivers/input/touchscreen/goodix.c"。

二、基本知识

dts

硬件的相应信息都会写在.dts 为后缀的文件中,每一款硬件可以单独写一份例如stm32mp157a-dk1.dts,一般在 Linux 源码中存在大量的 dts 文件,对于 arm 架构可以在 arch/arm/boot/dts 找到相应的 dts,一个 dts 文件对应一个 ARM 的 machie。

dtsi

值得一提的是,对于一些相同的 dts配置可以抽象到 dtsi文件中,然后类似于 C语言的方式可以 include 到 dts 文件中,对于同一个节点的设置情况,dts 中的配置会覆盖dtsi 中的配置。

dtc

dtc 是编译 dts 的工具,可以在 Ubuntu 系统上通过指令 apt-get install device-tree-compiler 安装 dtc 工具,不过在内核源码 scripts/dtc 路径下已经包含了 dtc 工具;

dtb

dtb(Device Tree Blob),dts 经过 dtc 编译之后会得到 dtb 文件,dtb 通过 Bootloader 引导程序加载到内核。所以 Bootloader 需要支持设备树才行;Kernel 也需要加入设备树的支持;

​ **那么 DTS 和 DTB 这两个文件是什么关系呢? DTS 是设备树源码文件, DTB 是将DTS 编译以后得到的二进制文件。将.c 文件编为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb需要用到 DTC 工具! DTC 工具源码在 Linux 内核的 scripts/dtc 目录下 **

scripts/dtc/Makefile 文件内容如下: (逐行解析)

# SPDX-License-Identifier: GPL-2.0
# scripts/dtc makefile
/*:= 与 = 的区别::
	:=(立即展开/简单赋值):
当你使用 := 进行赋值时,右边的值会立即计算并赋值给左边的变量。
换句话说,右边的表达式会在赋值时立即展开并计算,然后结果被固定下来,不会在后续引用时重新计算。
	=(延迟展开/递归赋值):
当你使用 = 进行赋值时,右边的表达式不会立即计算,而是被存储为原始的字符串。当这个变量在以后被引用时,Makefile 会重新展开并计算其值。因此,= 赋值的变量在每次被使用时都可能获得不同的值,具体取决于其他变量或规则的变化。*/
hostprogs-$(CONFIG_DTC) := dtc    
/*根据 CONFIG_DTC 的值来决定是否编译 dtc 工具,如果 CONFIG_DTC 被设置为 y(启用),$(CONFIG_DTC) 会被替换为 y,因此表达式变为 hostprogs-y。hostprogs-y 是一个特殊的 Makefile 变量,表示构建过程中需要生成的主机程序(host programs)。通过 :=,dtc 被赋值给 hostprogs-y,即在编译时,dtc 将作为主机程序被生成*/
always		:= $(hostprogs-y)	
/*这行代码将 hostprogs-y 的值赋给 always 变量always 是一个自定义的变量名,在这里它被用来确保 hostprogs-y 中定义的程序(即 dtc)始终被编译。这意味着只要 hostprogs-y 中有内容(在本例中就是 dtc),always 变量就会包含这些内容。*/
dtc-objs	:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
		   srcpos.o checks.o util.o
dtc-objs	+= dtc-lexer.lex.o dtc-parser.tab.o
/*可以看出, DTC 工具依赖于 dtc.c、 flattree.c、 fstree.c 等文件,最终编译并链接出 DTC 这个主机文件*/

​ 可以看出, DTC 工具依赖于 dtc.c、 flattree.c、 fstree.c 等文件,最终编译并链接出 DTC 这个主机文件。如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令:
​ make all
或者:
​ make dtbs
​ “make all”命令是编译 Linux 源码中的所有东西,包括 uImage, .ko 驱动模块以及设备树,如果只是编译设备树的话建议使用“make dtbs”命令,“make dtbs”会编译选中的所有设备树文件。如果只要编译指定的某个设备树,比如 ST 官方编写的“stm32mp157d-ed1.dts”,
可以输入如下命令:
​ make stm32mp157d-ed1.dtb

​ 基于 ARM 架构的 SOC 有很多种,一种 SOC 又可以制作出很多款板子,每个板子都有一个对应的 DTS 文件,那么如何确定编译哪一个 DTS 文件呢?我们就以 STM32MP1 这款芯片对应的板子为例来看一下,打开 arch/arm/boot/dts/Makefile,有如下内容:

在这里插入图片描述

​ 可以看出,当选中 STM32MP1 这个 SOC 以后(CONFIG_ARCH_STM32=y),所有使用到STM32MP1 这个 SOC 的板子对应的.dts 文件都会被编译为.dtb。如果我们使用 STM32MP1 新做了一个板子,只需要新建一个此板子对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb-$( CONFIG_ARCH_STM32)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb 文件。

1、dts结构

property 特性

/dts-v1/;  // /是根节点
 / {
     node1 {     // 子节点
         a-string-property = "A string";  // 节点属性
         a-string-list-property = "first string", "second string";
         // hex is implied in byte arrays. no '0x' prefix is required
         a-byte-data-property = [01 23 34 56];
         child-node1 {
             first-child-property;
             second-child-property = <1>;
             a-string-property = "Hello, world";
         };
         child-node2 {
             
         };
     };
     node2 {
         an-empty-property;
         a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
         child-node1 {
         };
     };
 };

​ device tree 的基本单元是 node。这些 node 被组织成树状结构,除了 root node,每个 node都只有一个 parent。一个 device tree 文件中只能有一个 root node。每个 node 中包含了若干的property/value 来描述该 node 的一些特性。每个 node 用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和 unit-address。unit-address 的具体格式是和设备挂在那个bus 上相关。例如对于 cpu,其 unit-address 就是从 0 开始编址,以此加一。而具体的设备,例如以太网控制器,其 unit-address 就是寄存器地址。root node 的 node name 是确定的,必须是“/”。也就是说设备树源文件的结构为:

  • 1 个 root 节点”/”;
  • root 节点下面含一系列子节点,“node1” and “node2”
  • 节点 node1 和下又含有一系列子节点,“child-node1” and “child-node2”
  • 各个节点都有一系列属性
  • 这些属性可能为空,如 an-empty-property
  • 可能为字符串,如 a-string-property
  • 可能为字符串树组,如 a-string-list-property
  • 可能为 Cells(由 u32 整数组成),如 second-child-property

2、dts语法

​ 在.dts 设备树文件中,可以通过“#include”来引用.h、 .dtsi 和.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后
缀 。

​ 在 linux kernel 中,扩展名是 dts 的文件就是描述硬件信息的 device tree source file,在dts 文件中,一个 node 被定义成如下格式:

 [label:] node-name[@unit-address] {
     [properties definitions]
     [child nodes]
 }
引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 “sram:sram@10000000”,节点 label 是 sram,而节点名字就很长了,为“sram@10000000”。很明显通
过&sram 来访问“sram@10000000”这个节点要方便很多!

在这里插入图片描述

gpu_reserved: 就是前面说的lable,方便其他位置引用。

在这里插入图片描述

设备树调用关系⬆

3、dts组成

compatible

每一个 dts 文件都是由一个 root 的根节点组成,内核通过根节点“/”的兼容性即可判断它启动的是什么设备,其代码结构如下:

/ {
     model = "HQYJ FS-MP1A Discovery Board";
     compatible = "st,stm32mp157a-dk1", "st,stm32mp157", "hqyj,fsmp1a";
    /*这样我们的设备就有三个属性值,这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查,以此类推,直到查找完 compatible 属性中的所有值。*/
     aliases {
         ethernet0 = &ethernet0;
         serial0 = &uart4;
         serial5 = &usart3;
     };
    
     chosen {
         stdout-path = "serial0:115200n8";
     };
     ... ...
 };

model 属性值是,它指定制造商的设备型号。推荐的格式是:“manufacturer,model”,其中 manufacturer 是一个字符串描述制造商的名称,而型号指定型号。

compatible 属性值是,指定了系统的名称,是一个字符串列表,它包含了一个“<制造商>,<型号>”形式的字符串。重要的是要指定一个确切的设备,并且包括制造商的名字,以避免命名空间冲突。

chosen 节点不代表一个真正的设备,但功能与在固件和操作系统间传递数据的地点一样,如根参数,取代以前 bootloader 的启动参数,控制台的输入输出参数等。

#address-cells 和#size-cells

​ #address-cells = <1>: 基地址、片选号等绝对起始地址所占字长,单位 uint32。

​ #size-cells = <1>: 长度所占字长,单位 uint32。

/ {
    #address-cells = <0x1>; //在 root node下使用1个u32来代表address。
    #size-cells = <0x0>; // 在root node下使用0个u32来代表size。
    ...
    ...
    memory {        // memory device
        ...
        reg = <0x90000000>;
            // 0x90000000是存取memory的address
        ...
    };
    ...
    ...
}

/ {
    #address-cells = <0x1>; //在root node下使用1个u32来代表address。
    #size-cells = <0x1>; //在root node下使用1个u32来代表size。
    ...
    ...
    memory { // memory device
        ...
        reg = <0x90000000 0x800000>;
            // 0x90000000 是存取 memory 的 address
            // 0x800000 是 memory 的 size。
        ...
    };
    ...
    ...
}

/ {
    #address-cells = <0x2>; // 在root node下使用2个u32来代表address。
    #size-cells = <0x1>; // 在root node下使用1个u32来代表size。
    ...
    ...
    memory { // memory device
        ...
        reg = <0x90000000 00000000 0x800000>;
            // 0x90000000 00000000 是存取memory的address
            // 0x800000 是memory的size。
        ...
    };
    ...
    ...
}

/ {
    #address-cells = <0x2>; // 在root node下使用2个u32来代表address。
    #size-cells = <0x2>; // 在root node下使用2个u32来代表size。
    ...
    ...
    memory { // memory device
        ...
        reg = <0x90000000 00000000 0x800000 00000000>;
            // 0x90000000 00000000 是存取memory的address
            // 0x800000 00000000 是memory的size。
        ...
    };
    ...
    ...
}
reg

​ reg 属性前面已经提到过了, reg 属性的值一般是(address, length)对。 reg 属性一般用于描述设备地址空间资源信息或者设备地址信息,比如某个外设的寄存器地址范围信息,或者 IIC器件的设备地址等,比如在 stm32mp151.dtsi 中有如下内容:

uart5: serial@40011000 {
    compatible = "st,stm32h7-uart";
    reg = <0x40011000 0x400>;
	interrupts-extended = <&exti 31 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&rcc UART5_K>;
	resets = <&rcc UART5_R>;
	wakeup-source;
	power-domains = <&pd_core>;
	dmas = <&dmamux1 65 0x400 0x5>,
	<&dmamux1 66 0x400 0x1>;
    dma-names = "rx", "tx";
	status = "disabled";
};

​ uart5 节点描述了 stm32mp1 系列芯片的 UART5 相关信息,重点是第 578 行的 reg 属性。由于 uart5 的父节点“ soc”设置了#address-cells = <1>、 #size-cells = <1>,因此 reg 属性中address=0x40011000, length=0x400。查阅《STM32MP157 参考手册》可知, stm32mp157 芯片的UART5 寄存器首地址为 0x40011000,但是 UART5 的地址长度(范围)并没有 0x400 这么多,这里我们重点是获取 UART5 寄存器首地址 。

CPU addressing

​ 寻址时,CPU 节点代表了最简单的情况。 每个 CPU 都分配有一个唯一的 ID,并且没有与 CPU ID 相关联的大小。

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

     cpu0: cpu@0 {
         compatible = "arm,cortex-a7";
         device_type = "cpu";
         reg = <0>;
         clocks = <&rcc CK_MPU>;
         clock-names = "cpu";
         operating-points-v2 = <&cpu0_opp_table>;
         nvmem-cells = <&part_number_otp>;
         nvmem-cell-names = "part_number";
     };

     cpu1: cpu@1 {
         compatible = "arm,cortex-a7";
         device_type = "cpu";
         reg = <1>;
         clocks = <&rcc CK_MPU>;
         clock-names = "cpu";
         operating-points-v2 = <&cpu0_opp_table>;
     };
 };

​ 在 cpus 节点,#address-cells 被设置成了 1,#size-cells 被设置成了 0。这是说子 reg 值是单独的 uint32,它用无大小字段表示地址。在此情况下,这两个 cpu 分配到的地址为 0和 1。cpu 节点的#size-cells 是 0 因为每个 cpu 只分配到了一个单独的地址。你仍然需要注意 reg 值需要与节点名的值相匹配。按照惯例,如果一个节点有一个 reg属性,那么这个节点名称必须包括 unit-address,这是 reg 属性的第一个 address 值。

Memory Mapped Devices

​ 与在 cpu节点中单独的 address 值不同,内存映射设备被分配了一系列将要响应的地址,因此不仅需要包含内存的基地址而且还需要映射地址的长度,因此需要使用#size-cells 用来表示在每个子 reg 元组中长度字段的大小。在以下示例中,每个 address 值为 1 cell(32 bits),每个长度值也是 1 cell,这在 32 bit 系统是比较典型的。64 bit 设备也许会为#address-cells 和#sizecells 使用数值 2,在 device tree 中获取 64 bit addressing。

注:寄存器地址表见 ./download/STM32MP157-datasheet.pdf

/dts-v1/;
 / {
     #address-cells = <1>;
     #size-cells = <1>;
     ...
     timers2: timer@40000000 { /*设备的地址或寄存器首地址*/
         compatible = "st,stm32-timers";
         reg = <0x40000000 0x400>;
     };
 
    timers3: timer@40001000 {
         compatible = "st,stm32-timers";
         reg = <0x40001000 0x400>;
     };

     spi2: spi@4000b000 {
         compatible = "st,stm32h7-spi";
         reg = <0x4000b000 0x400>;
     };

     uart7: serial@40018000 {
         compatible = "st,stm32h7-uart";
         reg = <0x40018000 0x400>;
     };

     sai1: sai@4400a000 {
         compatible = "st,stm32h7-sai";
         reg = <0x4400a000 0x4>;
     };
     ...    
 };
Non Memory Mapped Devices

​ 处理器总线的其它设备为非内存映射设备。他们有地址范围,但不能被 CPU 直接寻址。母设备的驱动程序将代替 CPU 进行间接访问。以 i2c 设备为例,每个设备都分配了一个地址,但没有长度或范围与之相匹配。这与 CPU 地址分配很相似。

etf: etf@50092000 {
     compatible = "arm,coresight-tmc", "arm,primecell";
     reg = <0x50092000 0x1000>;
     clocks = <&rcc CK_TRACE>;
     clock-names = "apb_pclk";

     ports {
         #address-cells = <1>;
         #size-cells = <0>;
        
         port@0 {
             reg = <0>;
             etf_in_port: endpoint {
                 slave-mode;
                 remote-endpoint = <&funnel_out_port0>;
             };
         };

         port@1 {
         	reg = <0>;
             etf_out_port: endpoint {
                 remote-endpoint = <&tpiu_in_port>;
             };
         };
     };
 };

​ 前面已经讨论过如何向设备分配地址,但此时这些地址只是本地设备节点,还没有说明如何从那些地址里映射到 cpu 可以使用的地址。根节点经常描述地址空间的 CPU 视图。根节点的子节点已经使用了 CPU 的 address domain,所以不需要任何明确的映射。例如,serial@101f0000 设备被直接分配了地址 0x101f0000。根节点的非直接子节点是无法使用 CPU 的 address domain 的。

ranges

为了在 deivce tree 获取内存映射地址必须指定如何从一个域名将地址转换到另一个。Ranges 属性就用于此目的。以下是添加了 ranges 属性的 device tree 示例。

m4_rproc: m4@0 {
    compatible = "st,stm32mp1-rproc";
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x00000000 0x38000000 0x10000>,
            <0x30000000 0x30000000 0x60000>,
            <0x10000000 0x10000000 0x60000>;
    resets = <&rcc MCU_R>;
    reset-names = "mcu_rst";
    st,syscfg-pdds = <&pwr 0x014 0x1>;
    st,syscfg-holdboot = <&rcc 0x10C 0x1>;
    st,syscfg-tz = <&rcc 0x000 0x1>;
    st,syscfg-rsc-tbl = <&tamp 0x144 0xFFFFFFFF>;
    status = "disabled";
    m4_system_resources {
        compatible = "rproc-srm-core";
        status = "disabled";
    };
};

ranges 是一个地址转换列表。每个输入 ranges 表格的是包含子地址的元组,母地址和子地址空间的范围大小。每个字段的大小都由获取的子地址的#address-cells 值,母地址的#address-cell 值和子地址的#size-cells 值而定。

child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。

以外部总线为例,子地址是 2 cells,母地址是 1 cell,大小也为 1 cell。

​ 如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的 stm32mp157 来说,子地址空间和父地址空间完全相同,因此会在stm32mp151.dtsi 中找到大量的值为空的 ranges 属性,如下所示:

soc {
	compatible = "simple-bus";
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;
	ranges;
......
};

ranges 属性不为空的示例代码如下所示

soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    interrupt-parent = <&intc>;
    ranges = <0 0x10000000 0x100000>;
   
    sram: sram@10000000 {
        compatible = "mmio-sram";
        reg = <0x0 0x60000>;
        #address-cells = <1>;
        #size-cells = <1>;
        ranges = <0 0x10000000 0x60000>;
    };
    serial {
        device_type = "serial";
        compatible = "ns16550";
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};

第 6 行,节点 soc 定义的 ranges 属性,值为<0 0x10000000 0x100000>,此属性值指定了一个 1024KB(0x100000)的地址范围,子地址空间的物理起始地址为 0,父地址空间的物理起始地址为 0x10000000

第 8 行, sram 是 STM32MP1 内部 RAM 节点, reg 属性定义了 sram 设备寄存器的起始地址为 0,寄存器长度为 0x60000。经过地址转换, sram 设备可以从 0x10000000 开始进行读写操作,0x10000000=0x0 + 0x10000000。

serial 是串口设备节点,reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。

status

​ device tree 中的 status 标识了设备的状态,使用 status 可以去禁止设备或者启用设备,看下设备树规范中的 status 可选值。

在这里插入图片描述

中断映射

​ 与遵循树的自然结构而进行的地址转换不同,机器上的任何设备都可以发起和终止中断信号。另外地址的编址也不同于中断信号,前者是设备树的自然表示,而后者者表现为独立于设备树结构的节点之间的链接。

在这里插入图片描述

下面显示了 ADC 控制器中断设备片段

adc: adc@48003000 {
     compatible = "st,stm32mp1-adc-core";
     reg = <0x48003000 0x400>;
     interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>,
     <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>;
     clocks = <&rcc ADC12>, <&rcc ADC12_K>;
     clock-names = "bus", "adc";
     interrupt-controller;
     st,syscfg-vbooster = <&syscfg 0x4 0x100>;
     st,syscfg-vbooster-clr = <&syscfg 0x44 0x100>;
     st,syscfg-anaswvdd = <&syscfg 0x4 0x200>;
     st,syscfg-anaswvdd-clr = <&syscfg 0x44 0x200>;
     #interrupt-cells = <1>;
     #address-cells = <1>;
     #size-cells = <0>;
     status = "disabled";
    
     adc1: adc@0 {
         compatible = "st,stm32mp1-adc";
         #io-channel-cells = <1>;
         reg = <0x0>;
         interrupt-parent = <&adc>;
         interrupts = <0>;
         dmas = <&dmamux1 9 0x400 0x05>;
         dma-names = "rx";
         status = "disabled";
     };
    
     adc2: adc@100 {
         compatible = "st,stm32mp1-adc";
         #io-channel-cells = <1>;
         reg = <0x100>;
         interrupt-parent = <&adc>;
         interrupts = <1>;
         dmas = <&dmamux1 10 0x400 0x05>;
         dma-names = "rx";
         /* temperature sensor */
         st,adc-channels = <12>;
         st,min-sample-time-nsecs = <10000>;
         status = "disabled";
     };
    
     jadc1: jadc@0 {
         compatible = "st,stm32mp1-adc";
         st,injected;
         #io-channel-cells = <1>;
         reg = <0x0>;
         interrupt-parent = <&adc>;
         interrupts = <3>;
         status = "disabled";
     };
    
     jadc2: jadc@100 {
         compatible = "st,stm32mp1-adc";
         st,injected;
         #io-channel-cells = <1>;
         reg = <0x100>;
         interrupt-parent = <&adc>;
         interrupts = <4>;
         /* temperature sensor */
         st,adc-channels = <12>;
         st,min-sample-time-nsecs = <10000>;
         status = "disabled";
     };
    
     adc_temp: temp {
         compatible = "st,stm32mp1-adc-temp";
         io-channels = <&adc2 12>;
         nvmem-cells = <&ts_cal1>, <&ts_cal2>;
         nvmem-cell-names = "ts_cal1", "ts_cal2";
         #io-channel-cells = <0>;
         #thermal-sensor-cells = <0>;
         status = "disabled";
     };
};

下面显示了 ethernet 控制器中断设备片段

ethernet0: ethernet@5800a000 {
    compatible = "st,stm32mp1-dwmac", "snps,dwmac-4.20a";
    reg = <0x5800a000 0x2000>;
    reg-names = "stmmaceth";
    interrupts-extended = <&intc GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>,
                            <&intc GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH>,
                            <&exti 70 1>;
    interrupt-names = "macirq",
                "eth_wake_irq",
                "stm32_pwr_wakeup";
    clock-names = "stmmaceth",
                "mac-clk-tx",
                "mac-clk-rx",
                "ethstp";
    clocks = <&rcc ETHMAC>,
            <&rcc ETHTX>,
            <&rcc ETHRX>,
            <&rcc ETHSTP>;
    st,syscon = <&syscfg 0x4>;
    snps,mixed-burst;
    snps,pbl = <2>;
    snps,en-tx-lpi-clockgating;
    snps,axi-config = <&stmmac_axi_config_0>;
    snps,tso;
    power-domains = <&pd_core>;
    status = "disabled";
};

在这里插入图片描述

特殊节点

​ aliases 节点为了解决节点路径名过长的问题,引入了节点别名的概念,可以引用到一个全路径的节点。如/external-bus/ethernet@0,0,但当用户想知道具体内容的时候显得太累赘。

aliases {
    ethernet0 = ðernet0;
    serial0 = &uart4;
    serial5 = &usart3;
}

当为设备分配一个标识符的时候,操作系统更倾向于使用 aliases。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值