linux设备驱动(5)--设备树

代码学习资料来源于:

第6.1讲 Linux设备树详解-什么是设备树?_哔哩哔哩_bilibili

仅用于个人学习/复习,侵联删

一、设备树

在linux内核3.x版本之后,linux内核开始使用设备树,设备树描述开发板上的硬件信息

linux设备驱动(5)--设备树_第1张图片

如上图所示,树的主干就是系统总线,IIC控制器,GPIO控制器,SPI控制器等都是接在系统主线上的分支,IIC控制器又分为IIC1和IIC2两种,DTS文件描述设备信息是有相应的语法规则的。

描述板级硬件信息的内容都从linux内中分离开来,用一个专属的文件格式来描述,这个专属的文件就称为“设备树”,文件扩展名为.dts。一个SOC可以做出很多不同的板子,这些不同的板子肯定是有共同信息的,将这些共同的信息提取出来作为一个通用的文件,其他的.dts文件直接引用这个这个通用文件即可,这个通用文件就是.dtsi,类似于c语言中的头文件。一般.dts描述板级信息(开发板上有哪些IIC设备、SPI设备),.dtsi描述SOC级信息(也就是SOC有几个CPU,主频是多少,各个外设控制器信息等)。

二、DTS、DTB、DTC的关系

.dts相当于.c,也就是DTS源码文件。

DTS工具相当于设备树源码文件,DTB是将DTS编译以后得到的二进制文件,DTC则是将DTS->DTB的编译工具。

DTC工具源码在在srcipts/dtc目录下,scripts/dtc/Makefile文件内容如下:

# SPDX-License-Identifier: GPL-2.0
# scripts/dtc makefile

hostprogs-y     := dtc 
ifeq ($(DTC_EXT),)
always          := $(hostprogs-y)
endif

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

# Source files need to get at the userspace version of libfdt_env.h to compile
HOST_EXTRACFLAGS := -I$(src)/libfdt

# Generated files need one more search path to include headers in source tree
HOSTCFLAGS_dtc-lexer.lex.o := -I$(src)
HOSTCFLAGS_dtc-parser.tab.o := -I$(src)

# dependencies on generated files need to be listed explicitly
$(obj)/dtc-lexer.lex.o: $(obj)/dtc-parser.tab.h

可以看出,DTC工具依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出DTC这个主机文件。如果要编译DTS文件的话只需要到linux源码根目录,然后执行如下命令:make all或者make dtbs

如何确认使用哪一个DTS文件呢?查看arch/arm/boot/dts/Makefile,

三、DTS相关使用

1).dtsi头文件

和c语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi,在imx6ul-isiot.dtsi文件中有:

#include 
#include 
#include "imx6ul.dtsi"

include/dt-bindings/input/input.h 文件如下:

#ifndef _DT_BINDINGS_INPUT_INPUT_H
#define _DT_BINDINGS_INPUT_INPUT_H

#include "linux-event-codes.h"

#define MATRIX_KEY(row, col, code)      \
        ((((row) & 0xFF) << 24) | (((col) & 0xFF) << 16) | ((code) & 0xFFFF))

#endif /* _DT_BINDINGS_INPUT_INPUT_H */

2)dts也是从 / 开始

同样的名称在dts里面相同的引用是追加的意思。根节点外有一些像 & 这样的语句称作“追加”,追加进来的将前面一个给替换掉了。

/ {    // 根节点
        model = "Engicam Is.IoT MX6UL eMMC Starter kit";    // 属性及属性值,开机的时候会打印该句
        compatible = "engicam,imx6ul-isiot", "fsl,imx6ul";    // 根级的compatible匹配
        
        memory@80000000 {
                reg = <0x80000000 0x20000000>;    // 起始地址和长度
        };
          
        aliases {
                ethernet0 = &fec1;
                ethernet1 = &fec2;
                ... ...
        };


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

                cpu0: cpu@0 {
                
                }
         }
         
         intc: interrupt-controller@a01000 {    // 一般是外设的起始地址,intc是节点标签。
         
         }
         
         timer { 
         
         }
         
         soc {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "simple-bus";
                interrupt-parent = <&gpc>;
                ranges;
                
                gpmi: gpmi-nand@1806000 {
                
                }
                 
                aips1: aips-bus@2000000 {
                
                }    
                
                pwm1: pwm@2080000 {
                
                }   
         }
         
         aips2: aips-bus@2100000 {
                        i2c1: i2c@21a0000 {
                                #address-cells = <1>;
                                #size-cells = <0>;
                                compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
                                reg = <0x021a0000 0x4000>;    // i2c1外设寄存器及其地址范围
                                interrupts = ;    // 中断号,共享中断
                                clocks = <&clks IMX6UL_CLK_I2C1>;    
                                status = "disabled";
                        };             
         }
         
         ... ...             
}

imx6ul-14x14-evk.dtsi文件下有:
&i2c1 {
        clock-frequency = <100000>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_i2c1>;
        status = "okay";

        // 具体的i2c设备
        mag3110@e {    // 描述i2c,i2c地址,而不是imx6ul外设寄存器的首地址(具体问题具体分析)
                compatible = "fsl,mag3110";
                reg = <0x0e>;
        };
};

3)节点的名称

node-name@unit-address    // 单元地址

4)标签

label : node-name@unit-address // 语法规则
// cpu0 : cpu@0    举例

引入标签的目的是为了方便访问节点,可以直接通过&label这种方式来直接访问这个节点,比如通过&cpu0就可以访问cpu@0这个节点,不需要输入完整的节点名字。

5)通过设备来进行查看设备树

系统启动时以后可以在根文件系统里面看到设备树的节点信息,在/proc/device-tree目录下存放着设备树信息

内核启动时会解析设备树,然后在/proc/device-tree目录下呈现出来

四、DTS相关语法

1)aliases节点

aliases节点主要用来定义别名,定义别名的目的就是为了方便地访问节点。不过我们一般会在节点命名的时候会加上label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量地使用&label的形式来访问节点。

        aliases {
                ethernet0 = &fec1;
                ethernet1 = &fec2;
                ... ...
        };

2)chosen节点

chosen并不是一个真实的设备,chosen节点主要为了uboot向linux内核传递数据,重点是bootargs参数。一般.dts文件中chosen节点通常为空或者内容很少,例如如下的chosen节点:

// imx6ul-tx6ul.dtsi
        chosen {
                stdout-path = &uart1;    // 属性stdout-path,表示标准输出使用uart1
        };

3)compatible属性

用法1、如下,可以看到在leds设备节点下有compatible = "gpio-leds",compatible属性的值是一个字符串列表,compatible属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,可以对应多个字符串值。

        leds {
                compatible = "gpio-leds";

                user_led: user {
                        label = "Heartbeat";
                        pinctrl-names = "default";
                        pinctrl-0 = <&pinctrl_led>;
                        gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>;
                        linux,default-trigger = "heartbeat";
                };
        };

用法2、根节点 / 下面的compatible,内核启动的时候会检查是否支持此平台,或设备。

4)model属性

model属性值也是一个字符串,一般module属性描述设备模块信息,比如名字:

 model = "Engicam Is.IoT MX6UL eMMC Starter kit";

5)status属性

status属性是和设备状态有关的,status属性值也是字符串,字符串是设备的状态信息,可选的状态如下:

"okay"        // 表示设备是可操作的
"disabled"    // 表示设备是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后
"fail"        // 表示设备不可操作,设备检测到了一系列错误,而且设备也不大可能变得操作
"fail-sss"    // 含义与fail相同,后面的sss部分是检测到的错误内容

6)#address-cells、#size-cells、reg属性

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

reg = 

例如:

#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;

ocram: sram@900000 {
        compatible = "mmio-sram";
        reg = <0x00900000 0x20000>;    // 0x00900000是外设首地址,0x20000表示地址段长度
};

一个节点的reg是由父节点的address-cells和size-cells决定的

大部分情况下reg用来描述内存的,一般是对组,在i2c上就代表i2c外设的器件地址,举例如下:

sgtl5000: codec@a {
       compatible = "fsl,sgtl5000";
       reg = <0x0a>;    // 器件地址
       #sound-dai-cells = <0>;
       clocks = <&clks IMX6UL_CLK_OSC>;
       clock-names = "mclk";
       VDDA-supply = <®_3p3v>;
       VDDIO-supply = <®_3p3v>;
       VDDD-supply = <®_1p8v>;
};

7)ranges属性

ranges属性可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges是一个地址映射/转化表,ranges属性每个项目由子地址、父地址和地址空间长度这三部分组成:

child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定此物理地址所占的字长

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

length:子地址空间的长度,由父节点的#size-cells确定此地址长度所占的字长

如果ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换

vendor: vendor {
       #address-cells = <1>;
       #size-cells = <1>;
       ranges = <0 0 0 0xffffffff>;
       compatible = "simple-bus";
};

8)device-type

表示设备类型,用的很少

memory { device_type = "memory"; reg = <0 0 0 0>; };

五、linux内核OF操作函数

驱动如何获取到设备树中结点信息呢?

例如:

backlight {
        compatible = "pwm-backlight";
        pwms = <&pwm8 0 100000>;
        brightness-levels = < 0  1  2  3  4  5  6  7  8  9
                             10 11 12 13 14 15 16 17 18 19
                             20 21 22 23 24 25 26 27 28 29
                             30 31 32 33 34 35 36 37 38 39
                             40 41 42 43 44 45 46 47 48 49
                             50 51 52 53 54 55 56 57 58 59
                             60 61 62 63 64 65 66 67 68 69
                             70 71 72 73 74 75 76 77 78 79
                             80 81 82 83 84 85 86 87 88 89
                             90 91 92 93 94 95 96 97 98 99
                            100>;
        default-brightness-level = <100>;
};

backlight结点表示display的背光芯片,背光芯片有亮度等级,有默认等级,有用的哪一个pwm等等信息。

相关结构体:

struct property {
        char    *name;
        int     length;
        void    *value;
        struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
        unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
        unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
        struct bin_attribute attr;
#endif
};

struct device_node {
        const char *name;
        const char *type;
        phandle phandle;
        const char *full_name;
        struct fwnode_handle fwnode;

        struct  property *properties;
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent;
        struct  device_node *child;
        struct  device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
        struct  kobject kobj;
#endif
        unsigned long _flags;
        void    *data;
#if defined(CONFIG_SPARC)
        const char *path_component_name;
        unsigned int unique_id;
        struct of_irq_controller *irq_trans;
#endif
};

在驱动中使用OF函数获取设备树中属性的内容:

与查找节点相关的OF函数有如下5个:

include/linux/of.h

of_find_node_by_name 函数                 // 通过名字查找节点
of_find_node_by_type 函数                 // 通过类型查找节点
of_find_compatible_node 函数              // 通过兼容性查找
of_find_node_by_path 函数                // 通过路径查找节点
of_find_matching_node_and_match          // 通过of_device_id查找节点

关于更详细的Linux内核of函数可以参考下面的博客:

linux内核驱动学习--设备树查找节点的 OF 函数_of_get_address___小小酥__的博客-CSDN博客

linux内核OF函数使用demo:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 解析的节点 backlight
/ {
        backlight {
                compatible = "pwm-backlight";
                pwms = <&pwm8 0 100000>;
                brightness-levels = <0, 1, 2, 3, 4, 5, 6>;
                default-brightness-level = <6>;                // 32位
                status = "okay";
        };
}
*/

// struct device_node *of_find_node_by_path(const char *path)

static int __init dtsof_init(void)
{
        int ret = 0;

        /* 路径 /backlight */
        struct device_node *nd;
        nd = of_find_node_by_path("/backlight");
        if (nd == NULL) {
                printk("No /backlight node \n");
                return 0;
        }
        /* 获取属性 */
        struct property *prop;
        prop = of_find_property(nd, "compatible", NULL);
        if (!prop) {
                return;
        } else {
                printk("compatible = %s", (char*)prop->value);
        }

        const char *st = NULL;        
        ret = of_property_read_string(nd, "status", &st);
        if (ret) {
                printk("of_property_read_string status error \n");
        } else {
                printk("status = %s", st);
        }
        
        /* 数字属性 */
        int def_value;
        ret =  of_property_read_u32(nd, "default-brightness-level", def_value);        // ARRAY_SIZE 数组包含的最大个数
        if (ret < 0) {
                printk("Failed to parse \""default-brightness-level"\" property\n");
                return -EINVAL;
        } else {
                printk("default-brightness-level = %d \n", def_value);
        }

        // u32 level[7];
        /* 数组类型的读取 */
        int elemnum = of_property_count_elems_of_size(nd, "brightness-levels", sizeof(u32));
        if (elemnum < 0) {
                printk("Read \""level"\" property failed \n");
                return -EINVAL;
        } else {
                printk("level size = %d \n", ret);
        }

        u32 *brival;
        brival = kmalloc(elemnum * sizeof(u32), GFP_KERNEL);
        if (!brival)
                return -1;
        /* 获取数组 */
        ret = of_property_read_u32_array(nd, "brightness-levels", brival, elemnum);
        if (ret < 0) {
                printk("read bright_levels property failed \n");
                kfree(brival);
                return -1;
        } else {
                for (int i = 0; i < elemnum; i++) {
                        printk("brightness-levels[%d] = %d \n", i, *(brival + i));
                }
        }
        kfree(brival);

        return ret;
}

static void __exit dtsof_exit(void) { }

module_init(dtsof_init);
module_exit(dtsof_exit);

你可能感兴趣的:(linux驱动,驱动开发,linux)