RK3399—驱动访问设备树API

  设备树是为驱动服务的,驱动如何匹根据设备树的描述信息匹配驱动源码,及C语言如何获取设备树的描述属性,是驱动工程师关心的。编写驱动时,通常情况下都需要获取驱动对应的设备树描述属性,如GPIO属性、内存地址范围、中断地址等。linux提供了驱动和设备树交互的API接口,一般以“of_”开头,接口声明位于“kernel/include/linux”的头文件下。


1. 设备节点

  设备树中的设备是以“节点”形式存在,因此要获取设备节点的属性信息,必须先获取到这个设备的节点(device_node),获取节点属性的函数接口第一个传入的参数就是“节点”地址。关于“device_node”的声明,位于“kernel/include/linux/of.h”中。

   struct device_node {
          const char *name;		/* 节点名称 */
          const char *type;		/* 设备类型 */
          phandle phandle;	
          const char *full_name;/* 节点完整名称,包括路径 */
  
          struct  property *properties;   /* 匹配属性 */
          struct  property *deadprops;    /* removed properties */
          struct  device_node *parent;    /* 父节点 */
          struct  device_node *child;	  /* 子节点 */
          struct  device_node *sibling;   
          struct  device_node *next;      /* 链表节点 */
          struct  device_node *allnext;   
          struct  proc_dir_entry *pde;    
          struct  kref kref;
          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
  };

2. 常用函数接口

2.1 驱动匹配

2.1.1 匹配属性兼容

of_match_ptr(int_demo_dt_ids)

#ifdef CONFIG_OF
#define of_match_ptr(_ptr)    (_ptr)
#else
#define of_match_ptr(_ptr)    (null)
#endif

#ifdef CONFIG_ACPI
#define ACPI_PTR(_ptr)    (_ptr)
#else
#define of_match_ptr(_ptr)    (null)
#endif

  然而,阅读linux驱动目录下的源码,一部分驱动属性表并未通过“of_match_ptr”进行转换,也是能正常使用的。从该宏的原型可知,该宏只是针对不同类型驱动匹配的宏选择,不通过该宏处理,并不影响。保持良好的习惯,保证驱动兼容性,同时兼容dt和acpi匹配,建议增加“of_match_ptr”进行转换。

例子:

static struct of_device_id of_bmp180_ids[] = {
   {.compatible = "bosch,bmp180"},
   { }   
 };
 
static struct i2c_driver bmp180_driver = { 
	.driver   = { 
	.owner    = THIS_MODULE, 
	.name     = BMP180_DEV_NAME, 
	.of_match_table = of_match_ptr(of_bmp180_ids),
	}, 
	.id_table = bmp180_id, 
	.probe 	  = bmp180_probe, 
	.remove   = bmp180_remove, 
};

2.2 获取节点

  要获取一个设备节点的具体属性信息,必须先获取到这个设备的节点,可以通过多种方式获取设备节点。


2.2.1 通过节点名称获取

struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
参数 含义/说明
引用 #include<linux/of.h>
from 查找节点路径地址,传入NULL表示从根节点开始查找整个设备树
name 节点名称
返回 成功返回节点首地址,失败返回NULL

2.2.2 通过设备类型获取

struct device_node *of_find_node_by_type(struct device_node *from,const char *type);
参数 含义/说明
引用 #include<linux/of.h>
from 查找节点路径地址,传入NULL表示从根节点开始查找整个设备树
type 设备类型(device_type)
返回 成功返回节点首地址,失败返回NULL

2.2.3 通过“compatible”属性获取

struct device_node *of_find_compatible_node(struct device_node *from,
	const char *type, const char *compat);
参数 含义/说明
引用 #include<linux/of.h>
from 查找节点路径地址,传入NULL表示从根节点开始查找整个设备树
type 查找节点类型(device_type),传入NULL表示忽略节点类型
compat compatible属性(字符串)
返回 成功返回节点首地址,失败返回NULL

2.2.4 通过驱动匹配表属性获取

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);
参数 含义/说明
引用 #include<linux/of.h>
from 查找节点路径地址,传入NULL表示从根节点开始查找
matches 驱动匹配表,编写驱动时自定义
match 返回匹配到的驱动表
返回 成功返回节点首地址,失败返回NULL

注:
通过匹配表属性获取,本质也是通过“compatible”属性获取节点。匹配表一般定义如下:
static struct of_device_id of_bmp180_ids[] = { {.compatible = "bosch,bmp180"}, { } };

例子:

struct device_node *p;
p =of_find_matching_node(NULL, &of_bmp180_ids, &&pf);

2.2.5 通过“full_name”获取

struct device_node *of_find_node_by_path(const char *path);
参数 含义/说明
引用 #include<linux/of.h>
path 节点带全路径的名称
返回 成功返回节点首地址,失败返回NULL

例子:

struct device_node *p
p = of_find_node_by_path("/gpio-leds");

2.2.6 获取父子点

struct device_node *of_get_parent(const struct device_node *node);
参数 含义/说明
引用 #include<linux/of.h>
node 待查找的父节点
返回 成功返回父节点首地址,失败返回NULL

2.2.7 获取下一子节点

struct device_node *of_get_next_child(const struct device_node *node,
					     struct device_node *prev);
参数 含义/说明
引用 #include<linux/of.h>
node 父节点
prev 当前子节点,传入NULL表示从第一个子节点开始查找
返回 成功返回父节点首地址,失败返回NULL

2.3 获取属性

  一个节点(设备)属性包括了名称、长度、值等,这些都是驱动使用的必备属性,linux用一个结构体“strcut property”描述节点属性,位于“kernel/include/of.h”中。

struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};

2.3.1 获取指定属性

struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
参数 含义/说明
引用 #include<linux/of.h>
np 设备节点
name 属性名称
lenp 属性值长度(字节)
返回 成功返回属性首地址,失败返回NULL

2.3.2 获取指定属性元素数量

int of_property_count_elems_of_size(const struct device_node *np,
				const char *propname, int elem_size);
参数 含义/说明
引用 #include<linux/of.h>
np 设备节点
propname 属性名称
lenp 属性值长度(字节)
返回 成功返回属性元素数量,失败返回负数

例子:

设备树:
/ {
compatible = “rockchip,rk3399”;
interrupt-parent = <&gic>;
#address-cells = <2>;
#size-cells = <2>;
… … … …
spi4: spi@ff1f0000 {
compatible = “rockchip,rk3399-spi”, “rockchip,rk3066-spi”;
reg = <0x0 0xff1f0000 0x0 0x1000>;
clocks = <&cru SCLK_SPI4>, <&cru PCLK_SPI4>;
clock-names = “spiclk”, “apb_pclk”;
interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = “default”;
pinctrl-0 = <&spi4_clk &spi4_tx &spi4_rx &spi4_cs0>;
#address-cells = <1>;
#size-cells = <0>;
status = “disabled”;
};
… … … …

获取节点spi4的reg属性元素数量(伪代码)

struct device_node *p = NULL
int num = 0;
p = of_find_compatible_node(NULL, NULL, "rockchip,rk3399-spi");	
num = of_property_count_elems_of_size(p, "reg", 8/*64位处理器*/); /* 执行成功num==4 */

2.3.3 获取指定属性指定标号的u32类型值

extern int of_property_read_u32_index(const struct device_node *np,
				       const char *propname, u32 index, u32 *out_value);
参数 含义/说明
引用 #include<linux/of.h>
np 设备节点
propname 属性名称
index 读取标号
*out_value 返回读取值
返回 成功返回0,失败返回负数

例子

假设2.3.2中的例子是一个32位机的CPU,地址字长为1(32位),则spi节点中的reg属性值为u32
/ {
compatible = “rockchip,rkxxx”;
interrupt-parent = <&gic>;
#address-cells = <1>;
#size-cells = <1>;

struct device_node *p = NULL
uint32 out= 0;
p = of_find_compatible_node(NULL, NULL, "rockchip,rk3399-spi");	
of_property_read_u32_index(p, "reg", 1, &out); 

2.3.4 获取数值

int of_property_read_variable_u8_array(const struct device_node *np,
					const char *propname, u8 *out_values,
					size_t sz_min, size_t sz_max);
 int of_property_read_variable_u16_array(const struct device_node *np,
					const char *propname, u16 *out_values,
					size_t sz_min, size_t sz_max);
int of_property_read_variable_u32_array(const struct device_node *np,
					const char *propname,	u32 *out_values,
					size_t sz_min, size_t sz_max);
 int of_property_read_variable_u64_array(const struct device_node *np,
					const char *propname,u64 *out_values,
					size_t sz_min,size_t sz_max);
参数 含义/说明
引用 #include<linux/of.h>
np 设备节点
propname 属性名称
index 读取标号
out_value 返回读取值
sz_min 读取数组元素最小数量
sz_max 读取数组元素最大数量,可以为0
返回 成功返回0,失败返回负数

  四个函数分别可以读取节点属性值为u8、u16、u32、u64的数组数据中的变化的数据。当“sz_max”为0时,表示节点属性值是固定的数组数据,此时linux内核单独封装出几个函数,“sz_min”则表示读取元素数目。

int of_property_read_u8_array(const struct device_node *np, const char *propname,u8 *out_values, size_t sz);
int of_property_read_u16_array(const struct device_node *np, const char *propname,u16 *out_values, size_t sz);
int of_property_read_u32_array(const struct device_node *np, const char *propname,u32 *out_values, size_t sz);
int of_property_read_u64_array(const struct device_node *np, const char *propname,u64 *out_values, size_t sz);

int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
{
	int ret = of_property_read_variable_u64_array(np, propname, out_values,
						      sz, 0);
	if (ret >= 0)
		return 0;
	else
		return ret;
}

  2.3.2例子中,reg为u64类型数组,可以用"of_property_read_u64_array"一次性获取数组所有元素。


  当获取数组元素数目“sz”为1时,可以表示获取单个u8、u16、u32、u64的值,因此进一步又封装理论获取数值的函数。

static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value);
static inline int of_property_read_u16(const struct device_node *np,const char *propname,u8 *out_value); 
static inline int of_property_read_u32(const struct device_node *np,const char *propname,u8 *out_value); 
int of_property_read_u32(const struct device_node *np,const char *propname, u32 *out_value)
{
	return of_property_read_u32_array(np, propname, out_value, 1);
}

2.3.5 获取string

int of_property_read_string_helper(struct device_node *np, const char *propname,
						 const char **out_strs, size_t sz, int index)
参数 含义/说明
引用 #include<linux/of.h>
np 设备节点
propname 属性名称
out_strs 返回读取值数值地址
sz 读取数量
index 读取标号
返回 成功返回0,失败返回负数

  “of_property_read_string_helper”函数是获取节点属性为一组string数值的多个数据,而且可以指定标号开始获取。与2.3.4中获取数值函数类似,函数进一步封装为“获取string数组所有数据”和“获取属性为string的值(sz=1)”

int of_property_read_string_array(struct device_node *np,const char *propname, const char **out_strs,size_t sz)
{
	return of_property_read_string_helper(np, propname, out_strs, sz, 0);
}

int of_property_read_string(struct device_node *np,const char *propname, const char **out_string);

2.3.6 获取地址

【1】获取地址和地址范围的字长

int of_n_addr_cells(struct device_node *np);
int of_n_size_cells(struct device_node *np);
参数 含义/说明
引用 #include<linux/of.h>
np 设备节点
返回 成功返回地址字长,失败返回负数

【2】获取地址

  获取地址相关属性,一般是“reg”或者“assigned-address”的值。

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);
参数 含义/说明
引用 #include<linux/of_address.h>
dev 设备节点
index 待读取地址标号
size 待读取地址长度
flags 读取标识,IORESOURCE_IO、IORESOURCE_MEM
返回 成功返回首地址,失败返回NULL

【3】IO地址转换为物理地址

u64 of_translate_address(struct device_node *np, const __be32 *addr)
参数 含义/说明
引用 #include<linux/of_address.h>
np 设备节点
addr 待转换地址
返回 成功返回物理地址,失败返回OF_BAD_ADDR

【4】获取虚拟地址

void __iomem *of_iomap(struct device_node *device, int index);
参数 含义/说明
引用 #include<linux/of_address.h>
np 设备节点
index 属性标号;如果reg属性只有一个单位地址,传入0
返回 成功返回虚拟地址首地址,失败返回NULL

  “of_iomap”用于把物理地址转为为虚拟地址。在linux未引入设备树前,采用的是“ioremap”函数实现物理地址转换虚拟地址的过程,引入设备树后,可以直接调用“of_iomap”函数获取虚拟地址。“of_iomap”本质也是将“reg”属性的的地址信息转换为虚拟地址。如果“reg”属性有多个单位地址,可以通过函数形参“index”指定某一段内存。


2.3.7 获取GPIO

int of_get_named_gpio(struct device_node *np, const char *propname, int index);
参数 含义/说明
引用 #include<linux/of_gpio.h>
np 设备节点
propname GPIO属性名称
index GPIO属性标号;只有1个GPIO,则传入0
返回 成功返回GPIO序号,失败返回负数

2.3.8 获取中断

【1】获取中断数量

int of_irq_count(struct device_node *dev);
参数 含义/说明
引用 #include<linux/of_irq.h>
np 设备节点
返回 返回中断数量,返回0表示无中断属性

【2】获取中断号

int of_irq_get(struct device_node *dev, int index);	/*通过中断标号获取*/
int of_irq_get_byname(struct device_node *dev, const char *name);/*通过中断名称获取*/
参数 含义/说明
引用 #include<linux/of_gpio.h>
dev 设备节点
index 中断标号;只有1个中断,则传入0
name 中断名称
返回 成功返回中断号,失败返回负数

2.3.9 提取资源空间

  CPU的一系列外设,包括GPIO、Timer、I2C、SPI等都有自己的寄存器,也就是它们自己特有的“资源空间”。linux内核采用“struct resource”结构体来描述一个节点的资源空间。“struct resource”位于“linux/linux/ioport.h”

/*
 * Resources are tree-like, allowing
 * nesting etc..
 */
struct resource {
	resource_size_t start;	/* 起始地址 */
	resource_size_t end;	/* 结束地址 */
	const char *name;		/* 资源名称 */
	unsigned long flags;	/* 资源类型 */
	struct resource *parent, *sibling, *child;
};

  关于资源类型“flags”,常用的资源类型,位于“linux/linux/ioport.h”中定义了常用的资源类型。我们见的是内存资源“IORESOURCE_MEM”、GPIO资源“IORESOURCE_IO”、寄存器资源“IORESOURCE_REG”、中断资源“IORESOURCE_IRQ”等。

/*
 * IO resources have these defined flags.
 */
#define IORESOURCE_BITS		0x000000ff	/* Bus-specific bits */
#define IORESOURCE_TYPE_BITS	0x00001f00	/* Resource type */
#define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_REG		0x00000300	/* Register offsets */
#define IORESOURCE_IRQ		0x00000400
#define IORESOURCE_DMA		0x00000800
#define IORESOURCE_BUS		0x00001000
......

“of_address_to_resource”函数是将“reg”属性转换为“struct resource”类型。

int of_address_to_resource(struct device_node *dev, int index, struct resource *r);
参数 含义/说明
引用 #include<linux/of_address.h>
dev 设备节点
index 地址资源标号
r 资源返回
返回 成功返回0,失败返回负数

3. 参考

【1】https://blog.csdn.net/qq_33160790/article/details/77803873

【2】https://www.cnblogs.com/xiaojiang1025/p/6368260.html

原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/103871390