Linux device driver (5) -- device tree

Code learning materials come from:

Chapter 6.1 Detailed Explanation of Linux Device Tree - What is a Device Tree? _哔哩哔哩_bilibili

Only for personal study/review, intrusion deleted

1. Device tree

After the 3.x version of the linux kernel, the linux kernel starts to use the device tree, which describes the hardware information on the development board .

As shown in the figure above, the backbone of the tree is the system bus. The IIC controller, GPIO controller, and SPI controller are all branches connected to the system main line. The IIC controller is divided into IIC1 and IIC2. The DTS file describes the device Information has corresponding grammatical rules.

The content describing the board-level hardware information is separated from linux and described in a dedicated file format. This dedicated file is called "device tree", and the file extension is .dts. A SOC can make many different boards, these different boards must have common information, extract these common information as a common file, other .dts files can directly refer to this common file, this common The file is .dtsi, which is similar to the header file in c language. Generally, .dts describes board-level information (which IIC devices and SPI devices are on the development board), and .dtsi describes SOC-level information (that is, how many CPUs the SOC has, what is the main frequency, and information about each peripheral controller, etc.).

2. The relationship between DTS, DTB and DTC

.dts is equivalent to .c, which is the DTS source code file.

The DTS tool is equivalent to the device tree source code file, DTB is the binary file obtained after compiling DTS, and DTC is the compiling tool for DTS->DTB.

The source code of the DTC tool is in the srcipts/dtc directory, and the content of the scripts/dtc/Makefile file is as follows:

# 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

It can be seen that the DTC tool depends on dtc.c, flattree.c, fstree.c and other files, and finally compiles and links the DTC host file. If you want to compile DTS files, you only need to go to the root directory of the linux source code, and then execute the following command: make all or make dtbs

How to confirm which DTS file to use? Check arch/arm/boot/dts/Makefile,

3. Use of DTS

1) .dtsi header file

Like the C language, the device tree also supports header files. The header file extension of the device tree is .dtsi. In the imx6ul-isiot.dtsi file:

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include "imx6ul.dtsi"

The include/dt-bindings/input/input.h file is as follows:

#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 also starts from /

The same name and the same reference in dts mean additional. There are some statements like & outside the root node called "append", and the appended one replaces the previous one.

/ {    // 根节点
        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 = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;    // 中断号,共享中断
                                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) The name of the node

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

4) label

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

The purpose of introducing labels is to facilitate access to nodes. You can directly access this node through &label. For example, you can access the node cpu@0 through &cpu0 without entering the complete node name.

5) View the device tree through the device

After the system starts, you can see the node information of the device tree in the root file system, and the device tree information is stored in the /proc/device-tree directory

When the kernel starts, it will parse the device tree and present it in the /proc/device-tree directory

Four, DTS related grammar

1) aliases node

The aliases node is mainly used to define an alias, and the purpose of defining an alias is to access the node conveniently. However, we generally add labels when naming nodes, and then access nodes through &label, which is also very convenient, and a large number of nodes are accessed in the form of &label in the device tree.

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

2) chosen node

The chosen node is not a real device. The chosen node mainly transmits data to the linux kernel for uboot, focusing on the bootargs parameter. Generally, the chosen node in the .dts file is usually empty or has very little content, such as the following chosen node:

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

3) compatible attribute

Usage 1 , as follows, you can see that there is compatible = "gpio-leds" under the leds device node, the value of the compatible attribute is a string list, and the compatible attribute is used to bind the device and the driver. The string list is used to select the driver to be used by the device, and can correspond to multiple string values.

        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";
                };
        };

Usage 2. Compatible under the root node /, when the kernel starts, it will check whether this platform or device is supported.

4) model attribute

The value of the model attribute is also a string. Generally, the module attribute describes the device module information, such as the name:

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

5) status attribute

The status attribute is related to the status of the device. The value of the status attribute is also a string, which is the status information of the device. The optional status is as follows:

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

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

The values ​​of these two attributes are unsigned 32-bit integers. The two attributes #address-cells and #size-cells can be used in any device with child nodes to describe the address information of child nodes. address-cells The attribute value determines the word length (32 bits) occupied by the address information in the reg attribute of the child node, and the size-cells attribute value determines the word length (32 bits) occupied by the length information in the reg attribute value of the child node, #address-cells and #size-cells indicate how the child node should write the reg attribute value. Generally, the reg attribute is related to the address. There are two types of information related to the address: the starting address and the length of the address. The format of the reg attribute is generally:

reg = <address1 length1 address2 length2 ... ...>

For example:

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

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

The reg of a node is determined by the address-cells and size-cells of the parent node .

In most cases, reg is used to describe the memory, generally it is a pair of <address, length>, which represents the device address of the i2c peripheral on the i2c, as follows:

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

7) ranges attribute

The ranges attribute can be empty or a numeric matrix written in the format of (child-bus-address, parent-bus-address, length), ranges is an address mapping/conversion table, and each item of the ranges attribute consists of child address, parent address and address space The length consists of three parts:

child-bus-address: The physical address of the sub-bus address space, the word length occupied by this physical address is determined by the #address-cells of the parent node

parent-bus-address: The physical address of the parent bus address space, and the word length occupied by this physical address is also determined by #address-cells of the parent node

length: the length of the sub-address space, the word length occupied by the address length is determined by the #size-cells of the parent node

If the value of the ranges attribute is empty, it means that the child address space is exactly the same as the parent address space, and address conversion is not required

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

8)device-type

Indicates the device type, rarely used

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

Five, linux kernel OF operation function

How does the driver get the node information in the device tree?

For example:

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>;
};

The backlight node represents the backlight chip of the display. The backlight chip has a brightness level, a default level, which pwm is useful, and so on.

Related structures:

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
};

Use the OF function in the driver to get the content of the property in the device tree:

There are five OF functions related to finding nodes:

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查找节点

For more detailed Linux kernel of function, please refer to the following blog:

Linux kernel driver learning--OF function of device tree search node

The linux kernel OF function uses the demo:

#include <dt-bindings/gpio/gpio.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/workqueue.h>

/* 解析的节点 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);

Guess you like

Origin blog.csdn.net/qq_58550520/article/details/129460214