5.IMX6ULL LINUX驱动之设备数详解

363 阅读6分钟

一、什么是设备树

在没有使用设备树的时候,使用的是c文件,mach-xxx文件夹内存储的c源码。

DTS是采用树形结构描述版及设备的文件。将描述扳级信息的文件与linux内核分离出来。dts描述扳级信息,dtsi描述SOC级信息。

设备树有DTS,DTB,DTC。DTS是源码文件,DTB是编译的二进制文件,DTC是编译器。

1、编译dts

编译所有的dts文件。

make dtbs

编译指定的dts文件。

make imx6ull-emmc.dtb

或者在arch/arm/boot/dts目录下的Makefile中有说明编译那些文件,宏条件在defconfig中设置过了,所以才会编译很多dts文件。

二、DTS语法

详细的语法规则请参考《 Devicetree SpecificationV0.2.pdf 》和《Power_ePAPR_APPROVED_v1.12.pdf》这两份文档。

dts文件首先是文件层,使用如下,代表一个dts文件的开始

/dts-v1/;

紧接着,dts文件会#include包含dtsi文件(soc的信息),层层包含着的dtsi文件中都有一个根节点,这些根节点都是代表同一个根节点。

其次,通过一个这样的结构,代表根节点的声明。根节点中可以放入属性,compatible与model。接着放入其他一级子节点。

/ {
        /*skelection.dtsi中包含的属性的文件*/
        #address-cells = <1>;
        #size-cells = <1>;
        chosen { };
        aliases { };
        memory { device_type = "memory"; reg = <0 0>; };
​
        /*imx6ull-14x14-emmc.dtsi文件*/
        model = "Freescale i.MX6 ULL 14x14 EVK Board";
        compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
        chosen {
        };
        memory {
        };
        backlight {
        };
        pxp_v4l2 {
        };
​
        /*Imx6ull.dtsi文件*/
        aliases{};
        cpus{};
        intc: interrupt-controller@00a01000 {};
        clocks {};
        soc{};
};

在根节点外有一些追加的结点。其中追加使用的是标签,可以通过标签找到主要的结点。

&Lable{
};

1.设备结点名称信息创建

<结点标签>:<结点名字>。Lable可以访问结点。

Label:<node-name>@<name-address>

2.结点中的信息

①字符串compatible

用于描述兼容性的,与驱动程序进行匹配,如果驱动程序中也有compatible属性一致就可以进行匹配,它可以跟多个兼容字符串属性。这个字符串在dts中有一份,在mach-xxx.c中的源码也有一份,当能匹配到的时候,就代表该设备结点开发版可以兼容的,如果不兼容,那么该结点使用不了。

compatible="abc";

②32位无符号整数reg

reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。描述外设的地址的其实位置+长度。

reg由父节点的address-cells ,size-cells决定的。一般对于我们的开发版而言,是不会使用#address-cells= <2>来表示的,这是由于虚拟内存空间大小决定的。

/*Example 2*/
/{
    #address-cells = <1>;//表示用一个32位的数来描述地址
    #size-cells = <1>;//表示用0个32位的数来描述该地址的大小
    node{
        reg=<0x02409000 , 0x4000>;
    };
};
/*Example 2*/
/ {
    #address-cells = <0x2>;    //使用2个32来描述address。
    #size-cells = <0x1>;       //使用1个32来描述size。
    memory {
        reg = <0x90000000 00000000 0x800000>;
        // 0x90000000 00000000 是存取 memory 的 address
        // 0x800000 是 memory 的 size。
    };
}

③字符串列表

compatiable="zbc","fts";

④ranges属性

是一个矩阵表,也可以不填。

矩阵表内容分别:子总线地址空间物理地址,副总线地址空间物理地址,子地址空间长度。

arm中一般不使用该属性。

⑤model , status

model也是。当使能一个外设后,status会使用ok,挂在根文件系统后,就会能看到被使能的结点。

⑥address-cells ,size-cells

该属性决定子节点的reg属性。

⑦特殊的属性

三、dtsi文件简要分析

我们在参数手册中的ARM Paltform MEMORY MAP里面有映射外设地址。

比如说下图所示,显示了系统地址内存映射信息,在根据如下的外设显示,去DTSI文件中找到相应的代码,可以确定是SOC的信息。

1_外设映射地址内存信息.png

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

		ocrams: sram@00900000 {
			compatible = "fsl,lpm-sram";
			reg = <0x00900000 0x4000>;
		};

四、设备树的体现

挂载根文件系统以后可以查看设备树结点信息。

在/proc/device-tree目录下存放着一级结点的信息。

内核启动以后会解析设备树文件,然后呈现在/proc/device-tree目录中。

五、特殊结点

alias结点:

用于使用新的名称代表标签

chosen结点:

将Uboot的bootargs环境变量传递给Linux内核作为命令行参数。

六、OF内核的操作函数

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

使用of函数获取设备树的属性。位于include/linux/of.h文件中存储。

/*找到结点函数*/
extern struct device_node *of_find_node_by_name(struct device_node *from,
	const char *name);		//根据名字
extern struct device_node *of_find_node_by_type(struct device_node *from,
	const char *type);		//根据类型
static inline struct device_node *of_find_node_by_path(const char *path);
						  //根据路径
/*获取子结点,父节点*/
extern struct device_node *of_get_parent(const struct device_node *node);
extern struct device_node *of_get_next_parent(struct device_node *node);
extern struct device_node *of_get_next_child(const struct device_node *node,
                                             
/*查找属性property*/
struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};
extern struct property *of_find_property(const struct device_node *np,
					 const char *name,int *lenp);		//查找属性
extern int of_property_count_elems_of_size(const struct device_node *np,
				const char *propname, int elem_size);	//查找属性的元素大小
 extern int of_property_read_u32_index(const struct device_node *np,
				       const char *propname,
				       u32 index, u32 *out_value);		//读取索引
extern int of_property_read_u8_array(const struct device_node *np,
			const char *propname, u8 *out_values, size_t sz);//读取数组的值
static inline int of_property_read_u8(const struct device_node *np,
				       const char *propname,
				       u8 *out_value)					//驱动属性的值
extern int of_property_read_string(struct device_node *np,
				   const char *propname,
				   const char **out_string);			//读取字符串
extern int of_n_addr_cells(struct device_node *np);		  //读取#address-cells
extern int of_n_size_cells(struct device_node *np);		  //读取#size-cells

1.读取属性例程代码

    int ret = 0;
    
/*
backlight {
    compatible = "pwm-backlight";
    pwms = <&pwm1 0 5000000>;
    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 = <50>;
    status = "okay";
};
*/
    struct device_node *backlight_nd = NULL;
    struct property *backlight_pro=NULL;
    const char *name ;
    u32 def_value = 0 , elemsize = 0;
    u32 *brival ;
    u8 i = 0;

    /*找到 “/backlight” 设备结点*/
    backlight_nd = of_find_node_by_path("/backlight");
    if (backlight_nd == NULL){
		printk("ERROR TO GET backlight_nd");
        return;
    }

    /*获取属性*/
    backlight_pro = of_find_property(backlight_nd,	"compatible", NULL);
    if (backlight_pro == NULL){
		printk("ERROR TO GET backlight_pro");
        return;
    }else{
        printk("compatible = %s\r\n" ,(char *)backlight_pro->value);
    }

    /*读取字符串属性*/
    ret = of_property_read_string(backlight_nd,   "status", &name);
    if(ret < 0){
        printk("ERROR TO GET of_property_read_string");
    }else{
        printk("status = %s\r\n" , name);
    }
    /*读取数字的属性值*/
    ret = of_property_read_u32(backlight_nd, "default-brightness-level", &def_value);
    if(ret < 0){
        printk("ERROR TO GET of_property_read_u32");
    }else{
        printk("default-brightness-level = %d\r\n" , def_value);
    }
    /*读取数组类型的属性的数量*/
    elemsize = of_property_count_elems_of_size(backlight_nd,"brightness-levels", sizeof(u32));
    if(elemsize < 0){
        printk("ERROR TO GET of_property_count_elems_of_size");
    }else{
        printk("brightness-levels = %d\r\n" , elemsize);
    }
    /*动态地申请数组的空间*/
    brival = kmalloc( elemsize * sizeof(u32), GFP_KERNEL);
    if(!brival){
        printk("FAILED KMALLOC");
        return -1;
    }
    /*读取数组数据*/
    ret = of_property_read_u32_array(backlight_nd , "brightness-levels", brival , elemsize);
    if(ret < 0){
        printk("ERROR TO GET of_property_read_u16_array");
    }else{
        *brival = 0;
        for(i = 0 ; i < elemsize ; i++)
            printk("brightness-levels[%d] = %d\r\n" ,i, *(brival+i));
    }
    /*释放Kmalloc*/
    kfree(brival);

2.驱动函数调用的打印过程

/mydrivers # insmod led_driver.ko
compatible = pwm-backlight
status = okay
default-brightness-level = 50
brightness-levels = 101
brightness-levels[0] = 0
brightness-levels[1] = 1
......
brightness-levels[98] = 98
brightness-levels[99] = 99
brightness-levels[100] = 100
/mydrivers #

*绑定信息文档

在/Document/devicetree/bindings路径下存储着各类外设的绑定属性信息的写法,我们可以参照文档进行学习。只是作为一个参考信息。

\