1913|2

6111

帖子

4

TA的资源

版主

楼主
 

【Luckfox幸狐 RV1106 Linux 开发板测评】05 驱动、设备和设备树 [复制链接]

 
本帖最后由 damiaa 于 2024-2-29 17:10 编辑
        【Luckfox幸狐 RV1106 Linux 开发板测评】05 驱动、设备和设备树
 
 
就这样有了设备树
Linux诞生后就有了设备驱动程序,也就是直接获取设备的信息然后操作和调用接口实现,用户方便的在应用层调用这些接口访问设备。
后来又出了个platform-device驱动模型,一个程序分成两部分device.c driver.c 一个注册设备的资源,一个负责设备的操作和调用接口实现。这样当设备的资源变化的时候不一定要改动驱动程序driver.c。
再后来就出现了设备树,就是用设备树文件描述设备的硬件资源,由系统bootloader把资源给加载进去,然后传给内核,驱动程序从内核里面去获取设备的硬件资源。这样就更加方便了。
 
设备树文件介绍及编译和反编译
设备树(Device Tree),“设备”和“树”,描述设备树的文件叫做DTS(DeviceTree Source),DTS 采用树形结构描述板级设备(开发板上的设备信息),如CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等。目的就是为了驱动代码和设备信息分离。
一个 SOC 可以做很多不同的板子,不同的板子有共同的信息,将共同的信息提取出来作为一个通用的文件,其他的.dts 文件引用这个文件,这个文件就是.dtsi 文件,类似 C 中的头文件。.dts 描述板级信息(开发板上有哪些 IIC 设备、SPI 设备等),.dtsi 描述 SOC 级信息(SOC 有几个 CPU、主频是多少、各个外设控制器信息等)。
DTS、DTB 和 DTC
DTS 是设备树源码文件,DTB 是将 DTS 编译以后得到的二进制文件。需要用到的工具是 DTC!DTC工具源码在 Linux 内核的 scripts/dtc 目录下
DTC 把DTS编译成DTB
      DTSI
         +       ===》 DTC ===》 DTB
      DTS
DTC还可以把DTB给反编译成DTS
      DTB ===》 DTC ===》 DTS
DTC 工具依赖于 dtc.c、flattree.c、fstree.c 等文件,最终编译并链接出 DTC 文件。
 
如果要编译 DTS 文件的话
方法一:只需要进入到 Linux 源码根目录下,然后执行如下命令:make all 或者 make dtbs。
方法二:直接用dtc命令
dtc [-I input-format] [-O output-format][-o output-filename] [-V output_version] input_filename
input-format: //输入格式 反编译的把dtb编译成dts 
- “dtb”: “blob” format  //dtb格式
- “dts”: “source” format. //dts格式
- “fs” format.
output-format: //输出格式
- “dtb”: “blob” format 
- “dts”: “source” format 
- “asm”: assembly language file
output_version: 
定义”blob”的版本,在dtb文件的字段中有表示,支持1 2 3和16,默认是3,在16版本上有许多特性改变
 
dts编译成dtb
./dtc -I dts -O dtb -o B_dtb.dtb A_dts.dts
把A_dts.dts编译生成B_dtb.dtb
 
dtb编译成dts
./dtc -I dtb -O dts -o A_dts.dts A_dtb.dtb
把A_dtb.dtb反编译生成为A_dts.dts
 
设备树的基本语法
设备树语法规则请参考《Devicetree SpecificationV0.2.pdf》和《Power_ePAPR_APPROVED_v1.12.pdf》文档。
基本语法:
 
/{
	node1{
			key0 = value0; /*键值对*/
			key1 = value1;
			...
			child_node{
				key0 = value0; /*键值对*/
				key1 = value1;
				...
			}
		}
		node2{
			...
		}
}

 

“/”是根节点,每个设备树文件只有一个根节点。
node1、node2是子节点,子节点里面还可以有子节点。
 
节点的命名:
引入标号:节点名@节点地址
引用标号可以没有,有的好处就是其他地方可以代表节点使用,使用&引用标号。
节点地址可以有可以没有,主要是为了区分有相同节点名的不同节点。
比如gpio有n个 都只是叫goio 这个时候跟个地址就区分了。而且直观的知道这个gpio的硬件地址。
gpio3pa1:gpio3pa1 flash@0 spidev@0
 
节点属性:
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下:
   字符串
      compatible = “arm,cortex-a7”;
     上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。
   32位无符号整数
      reg = <0>;
   字符串列表
      属性值也可以是字符串列表,字符串和字符串之间采用”,”隔开,如下 所示:
     compatible ="rockchip,spidev";
 
compatible属性
Compatible属性也叫做”兼容性“属性,用于将设备和驱动绑定起来,这个很关键,如果设备和驱动的名字不一样(设备树里面就要和驱动里面的一样,然后才能挂钩)。格式如下:”manufacturer,model”
manufacturer表示,model对应驱动的名字。
compatible = "rockchip,rv1103g-38x38-ipc-v10", "rockchip,rv1103";
“"rockchip”表示厂商是飞思卡尔,“rv1103g-38x38-ipc-v10”和“rv1103”表示驱动模块名字。设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。
 
model 属性
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的.
 
status 属性
status 属性是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息。
“okay”表示可操作,”disabled”表示不可操作,”fail”表示错误,不可操作,”fail-sss”与fail相同,后面是错误内容。
 
#address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形,#address-cells 和#size-cells用在任何拥有子节点的设备中,用于描述子节点的地址信息。
每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用的字长。

 

reg 属性
reg 属性的值一般是(address,length)对。reg 属性用于描述设备地址空间资源信息,某个外设的寄存器地址范围信息。reg属性一般和前面的#address-cells 和#size-cells 属性配套使用。
 
上图就是上面三个属性的使用 #address-cells 和#size-cells 规定了下面的子节点有一个address-cells 就是0  size-cells没有。
 
ranges 属性
ranges属性值的格式 <local地址, parent地址, size>, 表示将local地址向parent地址的转换。
 

  ranges = <0x0 0x3000000 0x3000>;

如果 ranges 属性值为空值,说明子地址空间和父地址空间相同,不需要地址转换。
 
name 属性
name 属性用于记录节点名字。
 
device_type 属性
device_type 属性值为字符串,此属性只能用于 cpu 节点或者 memory 节点。
device_type = "memory";
device_type = "cpu";
 
interrupt 属性
interrupts 的cells数几个有父节点的#interrupt-cells决定。
像这样
interrupts = <0 37 1>;
interrupts = <GIC_SPI 37 1>;
interrupts = <GIC_PPI 37 1>;
 
interrupt-parent:标识设备节点属于哪个中断控制器。如果没有设置这个属性,它会默认依附于其父节点的interrupt-parent属性。
interrupt-generating-devices:表示产生中断的设备,通常用于指定哪些设备可以触发中断。
interrupts-extended:当设备连接到多个中断控制器时使用。它与interrupts属性相互排斥,二者只能选其一。如果两者都存在,则interrupts-extended属性会优先。
interrupt-controller:这是一个空的属性,用来标识该节点是一个中断控制器节点。
interrupt-map:每个元素表示一个中断映射关系,从前向后包括:中断子设备地址,中断子设备中断源(interrupt specifier),中断父设备,中断父设备地址,中断父设备中断源(interrupt specifier)五部分。
interrupt-map-mask:与interrupt-map属性一起使用时,用于过滤特定的中断请求。
 
根节点“/”中有两个特殊的子节点:aliases 和 chosen。
aliases 的意思是“别名”,aliases 节点的主要功能就是定义别名,方便访问节点。一般节点命名的时候会加上 label,然后通过&label来访问节点,也很方便,而且设备树里面大量的使用&label 的形式来访问节点。
 
aliases {
  		ethernet0 = &gmac;
		i2c0 = &i2c0;
		...
        ...
		spi1 = &spi1;
};

 

chosen 并不是一个真实的设备,chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少。
 
设备树of函数
在Linux内核采用设备树之后,驱动程序需要获取设备树的属性。Linux内核为驱动程序提供了一系列API函数,用于获取设备树的属性值。在Linux内核中,以“of_”开头的函数是设备树API函数。
读取设备节点API
Linux内核使用device_node结构体来描述一个设备节点,在include/linux/of.h 中有定义:
struct device_node {
					const char *name; 
					phandle phandle;
					const char *full_name;
					struct fwnode_handle fwnode;
					struct property *properties; 
					struct property *deadprops; 
					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)
					   unsigned int unique_id;
					   struct of_irq_controller *irq_trans;
					#endif
};
/*通过设备节点的名获取设备节点*/
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
/*通过设备节点类型获取设备节点:*/
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
/*通过节点的compatible属性和type获取设备节点*/
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
/*通过设备节点路径名获取设备节点*/
static inline struct device_node *of_find_node_by_path(const char *path){
     return of_find_node_opts_by_path(path, NULL);
}
/*通过 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)

 

读取父子设备节点API
/*of_find_node_by_path用于获取某一节点的父节点*/
struct device_node *of_get_parent(const struct device_node *node);
/*of_get_next_child 可以遍历某一节点的子节点*/
device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);

 

读取设备树属性API
设备树中的属性以结构体的形式表示,在文件include/linux/of.h中有定义:
struct property {
       char *name;
       int length;
       void *value;
       struct property *next;
};

 

相关of函数原型:

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

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

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_u8(const struct device_node *np,const char *propname,u8 *out_value)
int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
int of_property_read_u64(const struct device_node *np,const char *propname,u64 *out_value

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

int of_n_addr_cells(struct device_node *np)

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

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

int of_property_match_string(const struct device_node *np,const char *propname,const char *string);
 
Linux内核提供了大量的API来处理设备树,除了上面的API,还有大量的API在include/linux/of_xxx头文件中声明。要想深入了解,就需要在头文件中查找相关函数的定义原型。这里就不介绍了。
 
我们再来看看rv1106的设备树
进入/home/rv-xxxx/rv1106-spinand/luckfox-pico/sysdrv/source/kernel/adrv/source/kernel/arch/arm/boot/dts$
打开下面文件:

我们在更改使用gpio,adc,pwm,串口,spi,i2c等等外设功能的时候就要和它打交道。

&标号 表示对一个节点的细化,这个节点一般在dtsi头文件中有定义,这里是进一步定义 比如下面文件里的&pinctrl &pwm0 &uart3 &spi0等等

等等

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2022 Rockchip Electronics Co., Ltd.
 */

/dts-v1/;/*这个是必须要的!!!*/

#include "rv1103.dtsi"
#include "rv1106-evb.dtsi"
#include "rv1103-luckfox-pico-plus-ipc.dtsi"  /*几个dtsi头文件*/

/ {     /*根节点*/                                      
	model = "Luckfox Pico Plus";  /*model定义名称*/
	compatible = "rockchip,rv1103g-38x38-ipc-v10", "rockchip,rv1103"; /*compatible 应用设备驱动和设备挂钩的名字*/
	gpio3pa1:gpio3pa1 {             /*gpio子节点名*/
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa1>;
		regulator-name = "gpio3_pa1";
		regulator-always-on;
	};

	gpio3pa2:gpio3pa2 {       /*gpio子节点名*/
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa2>;
		regulator-name = "gpio3_pa2";
		regulator-always-on;
	};

	gpio3pa3:gpio3pa3 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa3>;
		regulator-name = "gpio3_pa3";
		regulator-always-on;
	};

	gpio3pa4:gpio3pa4 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa4>;
		regulator-name = "gpio3_pa4";
		regulator-always-on;
	};

	gpio3pa5:gpio3pa5 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa5>;
		regulator-name = "gpio3_pa5";
		regulator-always-on;
	};

	gpio3pa6:gpio3pa6 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa6>;
		regulator-name = "gpio3_pa6";
		regulator-always-on;
	};

	gpio3pa7:gpio3pa7 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio3_pa7>;
		regulator-name = "gpio3_pa7";
		regulator-always-on;
	};
};
/**********GPIO**********/
/*下面是对pinctrl 节点的细化  pinctrl 在dtsi头文件中应该有*/
&pinctrl {
	gpio3-pa1 {
		gpio3_pa1:gpio3-pa1 {
			rockchip,pins =	<3 RK_PA1 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	gpio3-pa2 {
		gpio3_pa2:gpio3-pa2 {
			rockchip,pins =	<3 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	gpio3-pa3 {
		gpio3_pa3:gpio3-pa3 {
			rockchip,pins =	<3 RK_PA3 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	gpio3-pa4 {
		gpio3_pa4:gpio3-pa4 {
			rockchip,pins =	<3 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	gpio3-pa5 {
		gpio3_pa5:gpio3-pa5 {
			rockchip,pins =	<3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	gpio3-pa6 {
		gpio3_pa6:gpio3-pa6 {
			rockchip,pins =	<3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	gpio3-pa7 {
		gpio3_pa7:gpio3-pa7 {
			rockchip,pins =	<3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};
};
/*下面是对sfc 节点的细化  sfc 在dtsi头文件中应该有 这里主要是定义了状态 flash0的细节*/
&sfc {
	status = "okay";

	flash@0 {
		compatible = "spi-nand";
		reg = <0>;
		spi-max-frequency = <75000000>;
		spi-rx-bus-width = <4>;
		spi-tx-bus-width = <1>;
	};
};
/*下面是对gmac 节点的细化  gmac 在dtsi头文件中应该有   这里主要是定义了状态 它是否可用*/
/**********ETH**********/
&gmac {
	status = "okay";
};

/*下面的都是 在dtsi头文件中应该有   不一一细说*/
/**********USB**********/
//&usbdrd {
//	status = "disabled";
//};

//&usbdrd_dwc3 {
//	status = "disabled";
//};

//&u2phy {
//	status = "disabled";
//};

//&u2phy_otg {
//	status = "disabled";
//};

/**********I2C**********/
// &i2c0 {
// 	status = "okay";
// 	pinctrl-0 = <&i2c0m2_xfer>;
// 	clock-frequency = <100000>;
// };
&i2c3 {
	status = "okay";
	pinctrl-0 = <&i2c3m1_xfer>;
	clock-frequency = <100000>;
};

/**********SPI**********/
&spi0 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi0m0_pins>;
	cs-gpios = <&gpio1 RK_PC0 1>;
	// cs-gpios = <&gpio1 26 1>;
	#address-cells = <1>;
	#size-cells = <0>;
	spidev@0 {
		compatible = "rockchip,spidev";
		spi-max-frequency = <1000000000>;
		reg = <0>;
	};
};

/**********UART**********/
&uart3 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&uart3m1_xfer>;
};
&uart4 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&uart4m1_xfer>;
};
//&uart5 {
//	status = "okay";
//	pinctrl-names = "default";
//	pinctrl-0 = <&uart5m0_xfer>;
//};

/**********PWM**********/

&pwm0 {
	status = "okay";
	pinctrl-names = "active";
	pinctrl-0 = <&pwm0m0_pins>;
	// pinctrl-0 = <&pwm0m1_pins>;
};
&pwm1 {
	status = "okay";
	pinctrl-names = "active";
	pinctrl-0 = <&pwm1m0_pins>;
	// pinctrl-0 = <&pwm1m1_pins>;
};

//&pwm2 {
//	status = "okay";
//	pinctrl-names = "active";
//	pinctrl-0 = <&pwm2m2_pins>;
//};
//&pwm3 {
//	status = "okay";
//	pinctrl-names = "active";
//	pinctrl-0 = <&pwm3m2_pins>;
//};
//&pwm4 {
//	status = "okay";
//	pinctrl-names = "active";
//	pinctrl-0 = <&pwm4m2_pins>;
//};
//&pwm5 {
//	status = "okay";
//	pinctrl-names = "active";
//	pinctrl-0 = <&pwm5m2_pins>;
//};
//&pwm6 {
//	status = "okay";
//	pinctrl-names = "active";
//	pinctrl-0 = <&pwm6m2_pins>;
//};
//&pwm7 {
//	status = "okay";
//	pinctrl-names = "active";
//	pinctrl-0 = <&pwm7m2_pins>;
//};
//&pwm8 {
//	status = "okay";
//	pinctrl-names = "active";
//	// pinctrl-0 = <&pwm8m1_pins>;
//	pinctrl-0 = <&pwm8m0_pins>;
//};
//&pwm9 {
//	status = "okay";
//	pinctrl-names = "active";
//	// pinctrl-0 = <&pwm9m1_pins>;
//	pinctrl-0 = <&pwm9m0_pins>;
//};

&pwm10 {
	status = "okay";
	pinctrl-names = "active";
	pinctrl-0 = <&pwm10m1_pins>;
	// pinctrl-0 = <&pwm10m2_pins>;
	// pinctrl-0 = <&pwm10m0_pins>;
};
&pwm11 {
	status = "okay";
	pinctrl-names = "active";
	pinctrl-0 = <&pwm11m1_pins>;
	// pinctrl-0 = <&pwm11m2_pins>;
	// pinctrl-0 = <&pwm11m0_pins>;
};

先汇报到这里,谢谢大家。

最新回复

理解好设备树文件比较重要   详情 回复 发表于 2024-3-2 10:00
点赞 关注
 
 

回复
举报

6856

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

理解好设备树文件比较重要

点评

确实,确实。  详情 回复 发表于 2024-3-2 12:00
 
 
 

回复

6111

帖子

4

TA的资源

版主

板凳
 
Jacktang 发表于 2024-3-2 10:00 理解好设备树文件比较重要

确实,确实。

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表