一文说明白linux设备树

一文说明白linux设备树

提示:本文使用Rockchip平台来说明设备树如何在linux下面应用,别的平台某些写法有点区别,但是基本上不影响我们学习,一通百通,下面就让我们开始设备树之旅吧。



前言

为啥要引用使用设备树?一个新的功能出现的时候我们都应该有个疑问,为啥要使用设备树?使用设备树给我们带来了那些好处?如果不使用设备树会有那些不好的地方?下面就让我们一个一个的回答。

1、为啥要引用使用设备树?

其实在很早的别的平台就已经引用了设备树,只是在ARM平台迟迟没有引用而已,所以很多早期在ARM平台没有引进设备树之前的开发者一定很熟悉类似这样的文件夹mach-xxx,在没有引用设备树之前在kernel\arch\arm目录下有很多芯片和开发板相关的文件,比如三星的一颗芯片s5pv210,A公司给这个芯片开发了一个产品在上面添加一些文件,B公司也是使用这个芯片在上面开发一个产品又在上面添加一些文件,那么世界上有无数个公司开发无数种产品,这个目录下想必不久就会变的非常的臃肿和庞大。所以这时候linux的创始人Linux之父Linus Torvalds在看到这种情况的时候就发了一个邮件:“This whole ARM thing is a f*cking pain in the ass”。这句话迫使ARM Linux社区引入了设备树。

kernel\arch\arm 目录下和平台相关的文件
 在这里插入图片描述

2、使用设备树给我们带来了那些好处?

设备树是用来描述芯片和外围设备连接关系的一种编程语言,其实和C语言语法也是类似的。我们知道一个板子有一颗主控芯片CPU比如Rochchip的33xx系列,不同的CPU有很多外围控制器,比如有I2C,SPI,MIPI控制器,DDR控制器,网卡控制器等等。大家都知道不同的芯片和不同的控制器的寄存器地址肯定是不一样的,Rochchip和三星的芯片的对于I2C控制器的寄存器配置地址肯定是不一样,其实不仅的不同厂商的芯片寄存器不一样,就算是通一个厂商的不同系列的芯片的寄存器地址也有可能不一样。就算是同一个芯片如果这个芯片有两个I2C控制器我们可能只使用I2C1,其他的控制器我们需要关掉用来省电这就是设备树用来描述和芯片相关的东西。板子上不仅有主控芯片,还有许多外围设备。比如一个A公司生产了一款平板,使用的都是Rochchip的同一款芯片。但是这两款平台在硬件上有差别,比如A公司使用了一款陀螺仪硬件连接的是I2C1,但是B公司使用的陀螺仪硬件连接的是I2C2,假设linux系统都支持了这两款陀螺仪的驱动,但是因为引脚连接的I2C控制器不一样,是不是我们需要修改代码才能支持这两款平板?如果引用了设备树,其实不用,只需要修改设备树就可以了,因为设备树就是描述这些硬件设备连接的,比如A公司的平板陀螺仪挂载在I2C1下我们只需要在设备树里面描述出来有个陀螺仪是在I2C1下,在系统起来的时候会读取设备树的配置,会初始化I2C1控制器且会初始化陀螺仪相关的驱动,所以我们就可以不修改kernel的代码,不需要重新编译内核,只需要替换设备树就可以同时支持多款平板。

下图是设备树的树状结构
在这里插入图片描述


一、初步体验设备树

1、设备树文件

在RK平台设备树的路径:kernel\arch\arm64\boot\dts\rockchip
设备树编译过程中的编译过程生成的中间文件:.rk3399-tve1030g.dtb.d.dtc.tmp
一款开发板的设备树文件:rk3399-tve1030g.dts
生成给的设备树文件给内核使用的bin文件:rk3399-tve1030g.dtb

2、查看rk3399-tve1030g.dts

下面的rk3399-tve1030g.dts已经被我删除了很多东西,为了精简让大家更容易理解

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

/dts-v1/;
#include <dt-bindings/pwm/pwm.h>    //设备树包含的头文件,也是C语言的头文件,包含进来是为了使用一些宏
#include <dt-bindings/sensor-dev.h>
/* .dtsi表示为被引用的文件和上面的C语言的头文件是一样的,比如有些描述和芯片相关的我们一般情况下不会修改的芯片原厂就会写一个"rk3399.dtsi" ,用于不同的产品进行包含*/ 
#include "rk3399.dtsi"  
#include "rk3399-android.dtsi"
#include "rk3399-opp.dtsi"
#include "rk3399-vop-clk-set.dtsi"

/ {
    
      /* 这个大括号里面表示一个根节点,也是最大的节点,其他的小节点是它的组成成分,什么叫根节点?设备树设备树就是把设备比喻成一棵树,比如一个平板就是一棵树,树上面开枝散叶有很多子节点比如i2c节点,spi节点,下面rk_headset就是我们定义的一个节点 */
	compatible = "rockchip,rk3399-tve1030g", "rockchip,rk3399";	/* 根节点的属性成员,表示这个设备树支持那个板子,比如这个设备树优先支持"rockchip,rk3399-tve1030g"这款板子,其次支持是使用RK3399芯片的板子,内核启动的时候会去匹配这个字符串,匹配上了代表内核能匹配这个设备树*/	
	/*rk_headset 根节点下面的一个子节点*/
	rk_headset {
    
    
		compatible = "rockchip_headset";  /*compatible属性:"rockchip_headset" 后面这串字符需要和驱动匹配,驱动的probe函数才能被执行*/
		headset_gpio = <&gpio4 RK_PD4 GPIO_ACTIVE_LOW>;
		/*headset_gpio: 可以理解为一个变量,用来告诉驱动,使用那个GPIO,headset_gpio 可以修改为别的名字,但是对应的驱动也要修改为对应的名字,因为驱动是通过这个名字获取headset_gpio里面的值*/
		pinctrl-names = "default";
		pinctrl-0 = <&hp_det>;
		/*pinctrl-names和pinctrl-0 这个是pinctrl的相关配置,先不管,等说明pinctrl的时候在看*/
		io-channels = <&saradc 2>;
		/*io-channels 和headset_gpi0 是一样的,也是可以理解为一个变量,驱动会引用里面的变量*/
	};
};

3、在内核里面查看设备树文件

在内核里面查看设备树文件,设备树的路径是:/sys/firmware/devicetree/base
在这里插入图片描述
由上面的图片我们看到在base文件夹就是设备树的根节点,在根节点下又有很多子节点,比如我们可以看到上面例子中的子节点rk_headset,还有别的节点i2c@ff110000/ 、i2c@ff120000/每个节点i2c节点代表芯片的一个i2c控制器。我们还可以进入到子节点rk_headset的目录查看子节点由什么组成。
在这里插入图片描述
我们进入子节点rk_headset可以看里面有组成成员:compatible 、headset_gpio 、io-channels、name、 pinctrl-0、 pinctrl-names和上面我们rk3399-tve1030g.dts里面的rk_headset是一样的。

3、总结

我们初步的看了设备树的组成部分,设备树是由一个根节点组成,根节点下面有许多子节点,子节点下面还可以有子节点的子节点,以此类推形成一个树状的结构。


二、设备树的节点

1.设备树之i2c怎么使用

我们上面说了设备树有个根节点,根节点下面有多个子节点其中I2C是其中的一个子节点,我们知道i2c总线下面可以挂载多个设备,比如下图i2c总线下挂载了触摸屏设备和多个陀螺仪设备,他们通过不同的i2c地址去识别不同的设备,既然i2c下挂载有多个子节点,那我们怎么去描述i2c总线下挂载的数据呢?那就是i2c节点下可以在建立一个子节点。
在这里插入图片描述
下面是设备树截取i2c节点的一段代码。

&i2c5 {
    
     //表示引用i2c5这个节点,i2c5这个节点会在根节点下面定义
	status = "okay";  //使能enable i2c子节点,如果在前面status = "disable",后面会覆盖掉前面的赋值
	i2c-scl-rising-time-ns = <150>; //i2c-scl-rising-time-ns这个值是变量上面也说了,不同的平台不一样,可能三星的平台就不是这样定义的,可能别的平台根本就不用配置这个,这个是给i2c控制器使用的,我们可以不用管
	i2c-scl-falling-time-ns = <30>;
	clock-frequency = <100000>;//这个也是一个变量,表示设置i2c总线速率配置为100k,不同平台也不一样,可以

    /* 触摸屏gslx680 节点*/
	gslx680: gslx680@40 {
    
    
		compatible = "gslX680_tve"; /*和代码匹配,匹配之后调用驱动代码的probe函数注册驱动*/
		reg = <0x40>;   /* i2c 读写地址,注意是7位地址*/	
		max-x = <1200>;  /* max-x 变量值,驱动里面会使用 */
		max-y = <1920>;   /* max-y  变量值,驱动里面会使用 */
		status = "okay";  /* 使能这个触摸屏gslx680 节点*/
	};
};

触摸屏代码节选,代码路径:drivers/input/touchscreen/gslx680.c

static const struct i2c_device_id gsl_ts_id[] = {
    
    
	{
    
    "gslX680_tve", 0},   //和设备树里面的gslx680节点的 compatible = "gslX680_tve";进行匹配
	{
    
    }
};

static struct i2c_driver gsl_ts_driver = {
    
    
	.driver = {
    
    
		   .name = GSLX680_I2C_NAME,
		   .owner = THIS_MODULE,
		   .of_match_table = of_match_ptr(gsl_ts_ids),
		   },
#if 0 //ndef CONFIG_HAS_EARLYSUSPEND
	.suspend = gsl_ts_suspend,
	.resume = gsl_ts_resume,
#endif
	.probe = gsl_ts_probe,  //匹配上后这个probe函数会被调用,然后初始化触摸屏驱动
	.remove = gsl_ts_remove,
	.id_table = gsl_ts_id,
};

总结

i2c下面的子节点会在内核起来的时候进行解析,最后会转化为一个struct i2c_client结构体,然后注册到一个链表里面,最后struct i2c_client会和触摸屏驱动里面的struct i2c_driver进行匹配,当这两个匹配上的时候,会调用驱动里面的probe函数,进行驱动的下一步注册。


3.设备树之spi怎么使用

在芯片中spi节点和i2c节点差不多,所以暂时不进行解析。


3.设备树之pinctrl怎么使用

1、pinctrl是用来干什么的?

在设备树里面一定会有pinctrl,在说明白什么是pinctrl之前我们先说一下芯片的Pin脚功能,学过单片机的都知道,芯片的Pin脚一般有两个功能,1、可以当成GPIO进行使用。2、可以当初特殊功能的引脚使用,比如这个Pin脚可以配置为I2c功能,那么这个Pin的寄存器就需要配置为I2c功能,其实内部是通过一个MUX电路进行切换。
在这里插入图片描述

既然是配置寄存器那么谁更熟悉如何配置寄存器?当然是芯片的原厂工程师,如果我们自己配置要怎么办?是不是要查询数据手册,然后设置对应的标志位才能切换到i2c功能?这样做是不是效率太低了?专业的事情就让专业的人干吧,我们只要使用就好了,所以pinctrl的作用就是用来管理一组Pin脚的功能,比如这组pin脚想配置为i2c功能,我们在i2c节点里面写出来就好了,内核起来的时候会解析你的配置,然后把引脚配置成i2c的功能。

代码已经被精简,只截取i2c部分拿出来进行说明

/*pinctrl 这个节点是根节点下的一个普通节点*/
pinctrl: pinctrl {
    
    
    /* compatible和上面的功能一样主要是用来和芯片原厂的驱动进行匹配,匹配之后调用对于的芯片设置Pinctrl的驱动*/
	compatible = "rockchip,rk3399-pinctrl";
	rockchip,grf = <&grf>;
	rockchip,pmu = <&pmugrf>;
	#address-cells = <2>;
	#size-cells = <2>;
	ranges;
	i2c6 {
    
       /*Pin脚要设置为i2c6功能的配置 */
			i2c6_xfer: i2c6-xfer {
    
    
				rockchip,pins =
					<2 10 RK_FUNC_2 &pcfg_pull_none>,
					<2 9 RK_FUNC_2 &pcfg_pull_none>;
			};
		};
};

上面是pinctrl的要将pin脚配置为i2c的功能的配置,那么既然配置好了谁要进行使用呢?当然就是i2c节点拿过来进行使用,我们查看i2c6的节点代码

i2c6: i2c@ff150000 {
    
    
		compatible = "rockchip,rk3399-i2c";
		reg = <0x0 0xff150000 0x0 0x1000>;
		clocks = <&cru SCLK_I2C6>, <&cru PCLK_I2C6>;
		clock-names = "i2c", "pclk";
		interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH 0>;
		/*pinctrl-names = "default"是什么意思呢,内核去解析这个节点的的时候会调用下面图片里面的函数pinctrl_select_state,将pinctrl-0 = <&i2c6_xfer>;这个组Pin脚设置为i2c6_xfer配置*/
		pinctrl-names = "default"; 
		pinctrl-0 = <&i2c6_xfer>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";
	};

代码配置kernel\drivers\base\pinctrl.c
在这里插入图片描述

//有些pinctrl-names可能有两种状态,一种是"default",分别为对应的配置为pinctrl-0,"sleep"对应的配置为pinctrl-1,系统休眠的时候会调用pinctrl_select_state这个函数将这组pin脚设置为休眠配置,为了起到省电效果
        pinctrl-names = "default" "sleep"; 
		pinctrl-0 = <&i2c6_xfer>;
		pinctrl-1 = <&i2c6_sleep>

3.设备树之怎么指定中断

猜你喜欢

转载自blog.csdn.net/qq_27809619/article/details/115859079