IMX6ULL嵌入式Linux驱动学习笔记(五)

本文详细介绍了IMX6ULL平台的设备树概念及应用,涵盖了设备树的基础知识、DTS/DTB/DTC的关系、基本语法等内容,并通过具体实例展示了如何在驱动中使用设备树。此外,还提供了LED驱动实验的完整代码。

IMX6ULL-Linux开发学习

以下内容是我在学习正点原子IMX6ULL开发板alpha中记录的笔记,部分摘录自正点原子IMX6ULL开发手册

一、什么是设备树

  1. 设备树:设备和树。
  2. 描述设备树的文件叫做DTS(Device Tree Source),这个 DTS 文件采用树形结构描述本板级设备,也就是开发板上的设备信息。比如CPU数量、内存基地址、IIC 接口上外接了哪些设备等等。
  3. 由于以前板级信息都是写到 .c 文件里面,导致 linux 内核臃肿。因此将板级信息做成独立的格式,文件扩展名为 .dts 。一个平台或者机器对应一个 .dts

二、DTS、DTB和DTC的关系

.dts 相当于 .c ,就是DTS源码文件。DTC 工具相当于 gcc 编译器,将 .dts 编译成 .dtbdtb 相当于 bin 文件或可执行文件。

通过 make dtbs 命令来编译所有的 .dts 文件,通过 make xxxxx.dtb 来编译对应的 .dts 文件,需要在 makefile 中添加 .dts 文件所在路径。编译 dts 文件的 Makefilearch/arm/boot/dts 下。

三、DTS基本语法

这篇DTS入门知识博客对DTS进行了入门的讲解。

  1. DTS是 / 开始。

  2. / 根节点开始描述设备信息。

  3. / 根节点外有一些 &cpu0 的语句是”追加“。

  4. 节点名字的要求

    label: node-name@unit-address 标签: 节点名字@地址。

    • label :为了方便的访问节点。
    • node-name :可使用的字符 [0~9] [a~z] [A~Z] [ , . + - _ ]。约定使用小写。
    • unit-address :一般是外设寄存器的起始地址。有的是外设的设备地址或者其他含义,需要根据情况来分析。

四、设备树在系统中的体现

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

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

五、特殊节点

  1. aliases 节点,对节点进行取别名。
  2. chosen 节点,主要目的就是将 uboot 里面的 bootargs 环境变量值,传递给 linux 内核作为命令行参数 cmd lineuboot 会将 bootargs 环境变量写入 chosen 节点中,通过 fdt_chosen 函数。

六、属性

嵌入式不常用或弃用属性 实例 作用
ranges ranges = <child-bus-address,parent-bus-address,length> / ranges; ranges是一个地址映射/转换表,ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。
name name 属性用于记录节点名字,name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。
device_type device_type = “cpu”; 用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。imx6ull.dtsi 的 cpu0 节点用到了此属性。
/* ranges属性不为空时 */
soc {
   
   
	compatible = "simple-bus";
	#address-cells = <1>;
	#size-cells = <1>;
	ranges = <0x0 0xe0000000 0x00100000>;
	
	serial {
   
   
		device_type = "serial";
		compatible = "ns16550";
		reg = <0x4600 0x100>;
		clock-frequency = <0>;
		interrupts = <0xA 0x8>;
		interrupt-parent = <&ipic>;
	};
};6 行,节点 soc 定义的 ranges 属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定
了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物
理起始地址为 0xe0000000。
第 11 行, serial 是串口设备节点, reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,
寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,
0xe0004600 = 0x4600 + 0xe0000000

属性 实例/可选值 作用
compatible compatible = “fsl,imx6ul-evk-wm8960”,“fsl,imx-audio-wm8960”; 设备兼容属性
mode model = “wm8960-audio”; 描述设备模块信息
status okay/disabled/fail/fail-sss 描述设备状态
#address-cells #address-cells = <1>; 描述了子节点reg属性中地址信息所占用的字长(32 位)
#size-cells #size-cells = <1>; 描述了子节点reg属性中长度信息所占的字长(32 位)
reg reg = <address1 length1 address2 length2…> 描述设备起始地址和长度

七、特殊的属性

compatible属性,值是字符串。

根节点/下的compatible属性,内核在启动的时候会检查是否支持此平台,在以前不使用设备树的时候会通过machine id来判断内核是否支持此机器。使用设备数后不再使用机器ID,而是使用根节点/下的compatible属性。

未使用设备树的结构体

#define MACHINE_START(_type,_name)						\
static const struct machine_desc __mach_desc_##_type	\
__used													\
__attribute__((__section__(".arch.info.init"))) = {		\
	.nr = MACH_TYPE_##_type,							\
	.name = _name,										\
    
#define MACHINE_END										\
};

使用设备树的结构体

#define DT_MACHINE_START(_name, _namestr)				\
static const struct machine_desc __mach_desc_##_name	\
__used 													\
__attribute__((__section__(".arch.info.init"))) = { 	\
	.nr = ~0,											\
	.name = _namestr,									\

#define MACHINE_END										\
};

八、Linux内核的OF操作函数

  1. 驱动如何获取到设备树中的设备信息。在驱动中使用OF函数获取设备树属性内容。

  2. 驱动要想获取到设备树节点内容,首先要找到节点。

  3. 查找节点的of函数:

    • of_find_node_by_name函数

      of_find_node_by_name 函数通过节点名字查找指定的节点,函数原型如下:

      struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
      

      函数参数和返回值含义如下:
      from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
      name:要查找的节点名字。
      返回值:找到的节点,如果为NULL表示查找失败。

    • of_find_node_by_type函数

      of_find_node_by_type 函数通过device_type属性查找指定的节点,函数原型如下:

      struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
      

      函数参数和返回值含义如下:
      from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
      type:要查找的节点对应的 type 字符串,也就是device_type属性值。
      返回值:找到的节点,如果为NULL表示查找失败。

    • of_find_compatible_node函数

      of_find_compatible_node 函数根据device_typecompatible这两个属性查找指定的节点,
      函数原型如下:

      struct device_node *of_find_compatible_node(struct device_node *from,
      							const char *type, const char *compatible)
      

      函数参数和返回值含义如下:
      from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
      type:要查找的节点对应的type字符串,也就是device_type属性值,可以为NULL,表示忽略掉device_type属性。
      compatible:要查找的节点所对应的compatible属性列表。
      返回值:找到的节点,如果为NULL表示查找失败。

    • of_find_matching_node_and_match函数

      of_find_matching_node_and_match 函数通过of_device_id匹配表来查找指定的节点,函数原
      型如下:

      struct device_node *of_find_matching_node_and_match(struct device_node *from,
                                                          const struct of_device_id *matches,
                                                          const struct of_device_id **match)
      

      函数参数和返回值含义如下:

      from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
      matches:of_device_id匹配表,也就是在此匹配表里面查找节点。
      match:找到的匹配的of_device_id

      返回值:找到的节点,如果为NULL表示查找失败。

    • of_find_node_by_path函数
      of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:

      struct device_node *of_find_node_by_path(const char *path)
      

      函数参数和返回值含义如下:
      path:带有全路径的节点名,可以使用节点的别名,比如/backlight就是backlight这个节点的全路径。
      返回值:找到的节点,如果为NULL表示查找失败。

  4. 查找父/子节点的of函数

    • of_get_parent函数

      of_get_parent 函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下:

      struct device_node *of_get_parent(const struct device_node *node)
      

      函数参数和返回值含义如下:

      node:要查找的父节点的节点。
      返回值:找到的父节点。

    • of_get_next_child函数

      of_get_next_child 函数用迭代的查找子节点,函数原型如下:

      struct device_node *of_get_next_child(const struct device_node *node,
      										struct device_node *prev)
      

      函数参数和返回值含义如下:
      node:父节点。
      prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
      返回值:找到的下一个子节点。

  5. 提取属性值的of函数

    节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要, Linux 内
    核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下:

    struct property {
         
         
    	char *name;				/* 属性名字 	*/
    	int length; 			/* 属性长度		*/
    	void *value;			/* 属性值		 */
    	struct property *next;	/* 下一个属性	*/
    	unsigned long _flags; 
    	unsigned int unique_id;
        struct bin_attribute attr; 
    };
    
    • of_find_property函数

      of_find_property 函数用于查找指定的属性,函数原型如下:

      property *of_find_property(const struct device_node *np,
      							const char *name,int *lenp)
      

      函数参数和返回值含义如下:
      np:设备节点。
      name:属性名字。
      lenp:属性值的字节数
      返回值:找到的属性。

    • of_property_count_elems_of_size函数

      of_property_count_elems_of_size 函数用于获取属性中元素的数量,比如reg属性值是一个
      数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:

      int of_property_count_elems_of_size(const struct device_node *np,
      									const char *propname, int elem_size)
      

      函数参数和返回值含义如下:
      np:设备节点。
      proname:需要统计元素数量的属性名字。
      elem_size:元素长度。
      返回值:得到的属性元素数量。

    • of_property_read_u32_index函数

      of_property_read_u32_index 函数用于从属性中获取指定标号的u32类型数据值(无符号 32
      位),比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定标号的数据值,此
      函数原型如下:

      int of_property_read_u32_index(const struct device_node *np,
      								const char *propname, u32 index,
                                     u32 *out_value)
      

      函数参数和返回值含义如下:
      np:设备节点。
      proname:要读取的属性名字。
      index:要读取的值标号。
      out_value:读取到的值
      返回值:0 读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有
      要读取的数据,-EOVERFLOW表示属性值列表太小。

    • of_property_read_u8_array 函数
      of_property_read_u16_array 函数
      of_property_read_u32_array 函数
      of_property_read_u64_array 函数

      这 4 个函数分别是读取属性中 u8u16u32u64 类型的数组数据,比如大多数的reg
      性都是数组数据,可以使用这 4 个函数一次读取出reg属性中的所有数据。这四个函数的原型
      如下:

      int of_property_read_u8_array(const struct device_node 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值