Linux驱动-设备树基础


在最新版本的Linux中,ARM相关的驱动全部都采用了设备数,所以我们来重点学习一下设备树的语法。

1.何为设备树

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(DeviceTree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。
在这里插入图片描述
其中树的主干就是系统总线,IIC控制器,GPIO控制器,SPI控制器都是接到系统主线上的分支。DTS文件描述设备信息是有相应的语法规则的。

2.DTC,DTB,DTC

设备树源文件的扩展名为.dts,dtb是将dts编译得到的二进制文件,将dts编译成dtb的工具叫做dtc,DTC 工具源码在 Linux 内核的 scripts/dtc/Makefile 文件内容如下:
在这里插入图片描述
可以看出dtc工具依赖于dtc.c,flattree.c,fstree.c,最终编译并链接出 DTC 这个主机文件。如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令,

make all 或者 make dtbs

make all命令是编译 Linux 源码中的所有东西,包括 zImage, .ko 驱动模块以及设备树,如果只是编译设备树的话建议使用“make dtbs”命令。

基于ARM架构的SOC有很多种,一种SOC又可以制作出许多种板子,每个板子都有对应的DTS文件,那么如何确定编译哪一个 DTS 文件呢?以 I.MX6ULL 这款芯片对应的板子为例,在arch/arm/boot/dts/Makefile,有如下内容:
在这里插入图片描述
可以看出,当选中 I.MX6ULL 这个 SOC以后(CONFIG_SOC_IMX6ULL=y)所有使用到I.MX6ULL 这个 SOC 的板子对应的.dts 文件都会被编译为.dtb。如果我们使用 I.MX6ULL 新做了一个板子,只需要新建一个此板子对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb文件。

3.设备树编译与反编译

./scripts/dtc/dtc -I dts -O dtb -o xxx.dtb arch/arm/boot/dts/xxx.dts // 编译 dts 为 dtb
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb // 反编译 dtb 为 dts
  • -I:指定输入格式
  • -O:指定输出格式
  • -o:指定输出文件

4.DTS语法

4.1 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从imx6ull.dtsi 文件中缩减出来的设备树文件内容:
在这里插入图片描述
第 1 行,“/”是根节点,每个设备树文件只有一个根节点。细心的同学应该会发现, imx6ull.dtsi和 imx6ull-alientek-emmc.dts 这两个文件都有一个“/”根节点,这样不会出错吗?不会的,因为这两个“/”根节点的内容会合并成一个根节点。第 2、 6 和 17 行, aliases、 cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:
node-name@unit-address
其中“node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是 UART1 外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。
但是我们在示例代码 43.3.2.1 中我们看到的节点命名却如下所示:

label:node-name@uint-address

为什么要引入label呢,这是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 “intc:interrupt-controller@00a01000”,节点 label 是 intc,而节点名字就很长了,为“ interruptcontroller@00a01000”。很明显通过&intc 来访问“interrupt-controller@00a01000”这个节点要方便很多!

设备树源码中的几种常用的数据形式为:

5.设备树框架

  • 从上到下

    • 头文件
    • 主体
    • 子节点追加内容
  • 从外到内

    • 属性
    • 其他子节点
      • 属性
      • 其他子节点
      • .
5.1 头文件
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
5.2 主体
/ {  
    model = "Seeed i.MX6 ULL NPi Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    aliases {
            pwm0 = &pwm1;
            pwm1 = &pwm2;
            pwm2 = &pwm3;
            pwm3 = &pwm4;
    };
    chosen {
            stdout-path = &uart1;
    };

    memory {
            reg = <0x80000000 0x20000000>;
    };

    reserved-memory {
            #address-cells = <1>;
            #size-cells = <1>;
            ranges;

            linux,cma {
                    compatible = "shared-dma-pool";
                    reusable;
                    size = <0x14000000>;
                    linux,cma-default;
            };
    };
    ...
};
  • 多个根结点合并
  • 根结点下面包括多个子结点
5.3 子结点追加内容
&cpu0 {
    dc-supply = <&reg_gpio_dvfs>;
    clock-frequency = <800000000>;
};

&clks {
    assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
    assigned-clock-rates = <786432000>;
};


&fec1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_enet1>;
    phy-mode = "rmii";
    phy-handle = <&ethphy0>;
    status = "okay";
};

5.4 节点命令

基本方法
node-name@unit-address{

属性1 = …

属性2 = …

属性3= …

子节点…

}
  • node-name:指定节点的名称
  • “unit-address”用于指定“单元地址”
节点标签
cpu0: cpu@0 {
    compatible = "arm,cortex-a7";
    device_type = "cpu";
    reg = <0>;
}
  • cpu0:为节点名称器一个别名
别名子节点
    aliases {
    can0 = &flexcan1;
    can1 = &flexcan2;
    ethernet0 = &fec1;
    ethernet1 = &fec2;
	...
}

6.结点属性

6.1 #address-cells和#size-cells属性

这两个属性的值都是无符号 32 位整形, #address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells 属性值决定了子节点 reg 属性地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中地址长度信息所占的字长。#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg 属性的格式一为:

6.2 range属性

range属性可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges是一个地址映射/转换表,ranges属性每个项目都由子地址,父地址和地址空间长度这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。

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

1 soc {
2 	compatible = "simple-bus";
3 	#address-cells = <1>;
4 	#size-cells = <1>;
5 	ranges = <0x0 0xe0000000 0x00100000>;
};

第 5 行,节点 soc 定义的 ranges 属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
第 10 行, serial 是串口设备节点, reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。

7. 向节点追加或者修改内容

在产品开发过程中面临着频繁的修改,这个时候我们需要同步修改设备树,如我们需要在i2c1接口上添加测试节点fxls8471,先看一下对应I2C1接口对应的节点

 i2c1: i2c@021a0000 {
 	#address-cells = <1>;
	 #size-cells = <0>;
	 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
	 reg = <0x021a0000 0x4000>;
	 interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
 	clocks = <&clks IMX6UL_CLK_I2C1>;
 	status = "disabled";
 };

上面的代码就是I.MX6ULL的I2C1节点,现在要在 i2c1 节点下创建一个子节点,这个子节点就是 fxls8471,最简单的方法就是在 i2c1 下直接添加一个名为 fxls8471 的子节点,最简单的方法就是在 i2c1 下直接添加一个名为fxls8471 的子节点,但是其他的SOC会调用这个设备树文件,而没有这个设备肯定不行,这里引入一个概念,如何向i2c1节点追加一个名为fxls8471的子节点,又不会影响其他的I.MX6ULL板子。
在这里插入图片描述
第 1 行, &i2c1 表示要访问 i2c1 这个 label 所对应的节点,也就是 imx6ull.dtsi 中的“i2c1:i2c@021a0000”。
第 2 行,花括号内就是要向 i2c1 这个节点添加的内容,包括修改某些属性的值。
打开 imx6ull-alientek-emmc.dts,找到如下所示内容:
在这里插入图片描述
示例代码 就是向 i2c1 节点添加/修改数据,比如第 225 行的属性“clock-frequency”就表示 i2c1 时钟为 100KHz。“clock-frequency”就是新添加的属性。

  • 第 228 行,将 status 属性的值由原来的 disabled 改为 okay。
  • 第 230~234 行, i2c1 子节点 mag3110,因为 NXP 官方开发板在 I2C1 上接了一个磁力计芯片 mag3110,正点原子的 I.MX6U-ALPHA 开发板并没有使用 mag3110。
  • 第 236~242 行, i2c1 子节点 fxls8471,同样是因为 NXP 官方开发板在 I2C1 上接了 fxls8471这颗六轴芯片。
    因为示例代码 中的内容是 imx6ull-alientek-emmc.dts 这个文件内的,所以不会对使用 I.MX6ULL 这颗 SOC 的其他板子造成任何影响。这个就是向节点追加或修改内容,重点就是通过&label 来访问节点,然后直接在里面编写要追加或者修改的内容。

修改dts文件之后,编译dtb拷贝到tftp服务器下,启动开发板,查看节点

cd /proc/device-tree
cd soc/aips-bus@02100000/i2c@021a0000/fxls8471@1e

其中soc/aips-bus@02100000/i2c@021a0000/在设备树imx6ull.dtsi中体现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值