-
包含头文件
- .h .dts .dtsi , 建议是自己写头文件,使用后缀 .dtsi
- .dtsi用于描述SOC 的内部外设的消息 ,CPU架构 ,主频,外设寄存器地址范围等
-
.dtsi文件
- / 根节点,在被包含的dtsi文件中,/目录下的节点同样能在系统中体现,和自己建立的.dts文件中的/目录是并行的关系
- 节点命名
label : node-name@unit-address ==> 标签 :节点名字@设备地址或寄存器首地址 ( label: 可要可不要 节点没有寄存器地址或设备地址,unit-address可不要 )demo: cpu0:cpu@0
- 节点属性
属性都是键值对 ,值可为空,或为任意的字节流
- 字符串
compatible = "arm , cortex-a7";
- 32位无符号整数
reg=<0>; reg=<0 0x0123 100>;
- 字符串列表
compatible = "arm , cortex-a7","fsl , imx6ull-gpmi-emmc";
- 字符串
- 标准属性
- compatible 属性
兼容性属性, 值是字符串。该属性用于将设备和驱动绑定起来的。
demo:
一般驱动程序文件 .c 都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动,demo
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960"; fsl表示厂商,"imx6ul-evk-wm8960" 和 "imx-audio-wm8960"表示驱动模块名字。 该设备在linux内核先查找第一个兼容值"imx6ul-evk-wm8960",找不到再找第二兼容值"imx-audio-wm8960"
如果在设备树中有哪个节点的 compatible 属性值与此相等,那么这个节点就会使用此驱动.c文件static const struct of_device_id imx_wm8960_dt_ids[] = { { .compatible = "fsl,imx-audio-wm8960", } , { .compatible = "fsl,imx6ul-evk-wm8960", } , };
- model 属性
属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的。
model = "wm8960-audio";
- status 属性
设备状态有关的, status 属性值也是 字符串 ,字符串是设备的状态信息
值 描述 “okay” 设备是可操作 “disable” 设备当前是不可操作 “fail” 备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作 “fail-sss” 含义和“fail”相同 - #address-cells 和 #size-cells属性
用于描述子节点的地址信息
reg = < address1 length1 address2 length2 .... > 即: reg = < address-cells1 size-cells1 address-cells2 size-cells2 .... > address1是起始地址 ,length1表示地址长度字长(32b) ,假如lengthx设置为0 ,相当于只设置了起始地址
- 描述设备地址空间资源信息
reg = <address length>
- ranges 属性 ranges 是一个地址映射/转换表。ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵
- name 属性 记录节点名字,已弃用。
- compatible 属性
兼容性属性, 值是字符串。该属性用于将设备和驱动绑定起来的。
demo:
-
属性设置的套路
- 第一种是抄类似的dts
- 第二种是查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法
-
linux内核通过根节点 compatible 属性找到的对应设备函数调用过程 I.MX6UL嵌入式linux驱动开发指南pdf ==> 1089页
-
内核设备树的路径 arch/arm/boot/dts
- 文件的包含关系:imx6ull芯片平台的原始 头文件: imx6ul.dtsi
- 官方出的demo板配的设备树: imx6ull-14x14-evk.dts ==> #include <imx6ull.dtsi>
- 正点原子根据官方demo的设备树文件:追加自己的设备树文件 imx6ull_emmc_cxn.dts ==> #include<imx6ull-14x14-evk.dts>
追加设备节点的格式
&i2c1{ //&i2c1 表示要访问i2c1这个label所对应的节点 /*追加或修改的内容*/ }; demo: @i2c1{ //向i2c1这个节点添加内容 clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; mag3110@0e { //i2c1总线上挂载的设备器件1 compatible = "fsl,mag3110"; reg = <0x0e>; position = <2>; }; fxls8471@1e { //i2c1总线上挂载的设备器件2 compatible = "fsl,fxls8471"; reg = <0x1e>; position = <0>; interrupt-parent = <&gpio5>; interrupts = <0 8>; }
-
设备树在系统中的体现
- Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device/tree目录下根据节点名字创建不同文件夹
- 进入不同节点的文件夹,又可以查看节点内的节点
-
特殊节点
- aliases
别名 , 格式: 别名 = &节点label
demo: can0 = &flexcan1; can1 = &flexcan2;
- chosen 子节点 并非一个真实的设备,,是uboot向linux内核传递的数据。进入 /proc/device-tree/chosen 文件夹里面,ls,可以看到 bootargs 节点。该节点是uboot在启动内核的时候,在chosen节点创建的
-
内核查找节点的OF函数
- 数据结构
- 描述设备节点的数据结构体 device_node , 文件路径: include/linux/of.h
struct device_node { const char *name; const char *type; phandle phandle; const char *full_name; struct fwnode_handle fwnode; struct property *properties; struct property *deadprops; /* removed properties */ struct device_node *parent; struct device_node *child; struct device_node *sibling; struct kobject kobj; 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 };
- 描述设备资源信息的 数据结构体 resource ,文件路径: include/linux/ioport.h
struct resource { resource_size_t start; //开始地址 resource_size_t end; //结束地址 const char *name; //资源的名字 unsigned long flags; //资源的标志位,资源的类型(宏表示,相关宏在include/linux/ioport.h的头文件中) struct resource *parent, *sibling, *child; };
- 查找节点的of函数
- 通过节点名字查找指定的节点
struct device_node * of_find_node_by_name(struct device_node *from , const char *name)
from 是开始查找的节点,如果为NULL,表示从根节点开始查找整个设备树 , name 要查找的节点名字 返回: 找到的节点。如果为NULL,表示查找失败 - 通过device_type属性查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
from 是开始查找的节点,如果为NULL,表示从根节点开始查找整个设备树 , type 要查找的节点device_type属性值,字符串 返回: 找到的节点。如果为NULL,表示查找失败 - 通过device_type和compatible这两个属性查找指定的节点
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_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,表示查找失败 - 通过路径来查找指定的节点
inline struct device_node *of_find_node_by_path(const char *path)
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。 返回: 找到的节点。如果为NULL,表示查找失败。
- 通过节点名字查找指定的节点
- 查找属性值得of函数
- 查找指定的属性
property *of_find_property(const struct device_node *np,const char *name,int *lenp)
np:设备节点。 name: 属性名字。 lenp[out]:属性值的字节数 返回值: 找到的属性。 - 获取属性中元素的数量
int of_property_count_elems_of_size(const struct device_node *np,const char *propname,int elem_size)
np: 设备节点。 propname: 需要统计元素数量的属性名字 elem_size: 元素的长度 返回值: 得到的属性元素数量。 - 从属性中获取指定标号的u32类型数据值
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index,u32 *out_value)
np: 设备节点 propname: 要读属性的名字 elem_size: 要读取的值标号 out_value[out]: 读取到的值 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。 - 读取属性中 u8、 u16、 u32 和 u64 类型的数组数据
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)
np:设备节点。 proname: 要读取的属性名字。 out_value:读取到的数组值,分别为 u8、 u16、 u32 和 u64。 sz: 要读取的数组元素数量。 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。 - 读取只有一个整型值得属性
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)
np:设备节点。 proname: 要读取的属性名字。 out_value:读取到的数组值。 返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。 - 读取属性中的字符串值
int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
np:设备节点。 proname: 要读取的属性名字。 out_string:读取到的字符串值。 返回值: 0,读取成功,负值,读取失败。 - 用于获取#address-cells
int of_n_addr_cells(struct device_node *np)
np:设备节点。 返回值: 获取到的#address-cells 属性值 - 用于获取#size-cells 属性值
int of_n_size_cells(struct device_node *np)
np:设备节点。 返回值: 获取到的#size-cells 属性值。
- 查找指定的属性
- 其他常用的OF函数
- 函数用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device,const char *compat)
device:设备节点。 compat:要查看的字符串。 返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串; 正数,节点的 compatible属性中包含 compat 指定的字符串 - 用于获取地址相关属性。主要是“reg”和“assigned-addresser”
const __be32 *of_get_address(struct device_node *dev,int index,u64 *size,unsigned int *flags)
dev:设备节点 index:要读取的地址标号。 size:地址长度 flags:参数,比如 IORESOURCE_IO、 IORESOURCE_MEM 等 返回值: 读取到的地址数据首地址,为 NULL 的话表示读取失败。 - 负责将从设备树读取到的地址转换为物理地址
u64 of_translate_address(struct device_node *dev,const __be32 *in_addr)
dev:设备节点。 in_addr:要转换的地址。 返回值: 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。 - IIC、 SPI、 GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux内核使用 resource 结构体来描述一段内存空间
int of_address_to_resource(struct device_node *dev,int index,struct resource *r)
dev:设备节点。 index:地址资源标号。 r:得到的 resource 类型的资源值。 返回值: 0,成功;负值,失败。 - 用于直接内存映射.采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段
void __iomem *of_iomap(struct device_node *np,int index)
np:设备节点。 index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。 返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。若设备结点的reg
属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况, index为0。采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap。demo 设备树: cxnled { #address-cells = <1>; #size-cells = <1>; name = "cxnled"; compatible = "atkalpha-led","cxnled"; status = "okay"; reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */ 0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */ 0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */ 0X0209C000 0X04 /* GPIO1_DR_BASE */ 0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */ }; 驱动: virtual_ccm_ccgr1 = of_iomap(m_dts_chr_dev.node , 0); virtual_sw_mux_gpio1_io03 = of_iomap(m_dts_chr_dev.node , 1); virtual_sw_pad_gpio1_io03 = of_iomap(m_dts_chr_dev.node , 2); virtual_gpio1_dr = of_iomap(m_dts_chr_dev.node , 3); virtual_gpio1_gdir = of_iomap(m_dts_chr_dev.node , 4);
- 函数用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性
- 数据结构
-
模板分析
#include <dt-bindings/clock/imx6ul-clock.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "imx6ul-pinfunc.h"
#include "skeleton.dtsi"
/ { //根节点
compatible="fsl,imx6ull-alientek-evk" , "fsl,imx6ull";
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
ethernet0 = &fec1;
ethernet1 = &fec2;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
i2c3 = &i2c4;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
serial3 = &uart4;
serial4 = &uart5;
serial5 = &uart6;
serial6 = &uart7;
serial7 = &uart8;
spi0 = &ecspi1;
spi1 = &ecspi2;
spi2 = &ecspi3;
spi3 = &ecspi4;
usbphy0 = &usbphy1;
usbphy1 = &usbphy2;
};
cpus { //imx6ull是cortex-A7架构,只有一个cpu
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
};
};
soc { //管理 SOC内部外设
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
ocrams: sram@00900000 { //内部RAM
compatible = "fsl,lpm-sram";
reg = <0x00900000 0x4000>;
};
aips1: aips-bus@02000000 { //外设控制寄存器1
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
ranges;
};
aips2: aips-bus@02100000 { //外设控制寄存器2
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02100000 0x100000>;
ranges;
};
aips3: aips-bus@02200000 { //外设控制寄存器3
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02200000 0x100000>;
ranges;
};
};
};
/*imx6dl.dtsi中gpio1控制器的定义节点*/
gpio1: gpio@0209c000 {
compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
<0 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/*imx6qdl-sabreauto.dtsi中某个设备节点*/
max7310_reset: max7310-reset {
compatible = "gpio-reset";
reset-gpios = <&gpio1 15 1>; //&gpio1 15 表示引用了gpio1节点 的15号引脚 ,最后一个参数 1 ,表示常规状态 ,其有效状态是低电平有效 ; 因为为gpio1节点中设置了#gpio-cells = <2>,所以带有引脚号15,常规电平1共两个参数,假如#gpio-cells = <1>,则最后一个常规电平参数1可以不要
reset-delay-us = <1>;
#reset-cells = <0>;
};
应用:
gpio = of_get_named_gpio(node, "reset-gpios", index);
node : 节点
"reset-gpios" : gpio属性的名字,一定要和节点属性中的xxx-gpios相同
index : 最后一个是编号index,当节点中有n个同名的xxx-gpios时,可以通过它来获取特定的那个gpio,同一节点中gpio同名情况很少存在,所以我们都把index设为0
- 在设备树.dts文件中,添加了自己设定的节点,make dtbs编译生成.dtb之后,在系统加载设备树的时候,自己设定的节点已挂载到设备树上(但未挂载在/dev/目录下)
- 替换掉原网络挂载的设备树文件,重启开发板
- 写驱动设备,因为自己在设备树节点上带有名称,寄存器等参数,可以通过一些列的of函数,在设备树中提取节点的相关信息
- 提取了寄存器等参数,利用
of_iomap
函数,把设备树节点上的物理寄存器地址映射到虚拟地址 - 接下来的操作和标准字符设备一样
-
配置信息:形式 宏 + 值
=> 展开 <mux_reg conf_reg input_reg mux_reg_value input_reg_value> conf_reg_valdemo: &iomuxc { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; ---------------------表示引用pinctrl的子pin_XX imx6ul-evk { pinctrl_hog_1: hoggrp-1 { fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */ ---------------------pinctrl子系统的表达形式 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */ ---------------------pinctrl子系统的表达形式 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */ ---------------------pinctrl子系统的表达形式 >; }; #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0 mux_reg偏移地址 conf_reg偏移地址 input_reg偏移地址,00表示无输入寄存器 mux_reg_value input_reg_value 在参考手册里找到 : IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 对应的是 mux_reg 的地址 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 对应的是 conf_reg 的地址 IOMUXC_UART1_RTS_B_SELECT_INPUT 对应的是 input_reg 的地址
-
在设备树中添加pinctrl节点
- 标签名称一定要“pinctrl_”作为前缀
- 添加顾定属性,用来获取PIN的配置信息。 I.MX系列的SOC,属性名字一定为“fsl,pins”,不同厂商都不同
- 在顾定属性中添加具体的PIN配置信息
- demo
pinctrl_test: testgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/ >; };
-
API
-
int gpio_request(unsigned gpio, const char *label)
,申请一个IO, gpio: 要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。 label: 给申请的gpio口设置名字 返回值: 0:申请成功 其他:失败 -
void gpio_free(unsigned gpio)
,释放gpio gpio: 要释放的 gpio 标号 -
int gpio_direction_input(unsigned gpio)
,函数用于设置某个 GPIO 为输入 gpio:要设置为输入的 GPIO 标号。 返回值: 0,设置成功;负值,设置失败 -
int gpio_direction_output(unsigned gpio, int value)
,此函数用于设置某个 GPIO 为输出,并且设置默认输出值 gpio:要设置为输出的 GPIO 标号。 value: GPIO 默认输出值。 返回值: 0,设置成功;负值,设置失败。 -
int gpio_get_value(unsigned int gpio)
获取某个GPIO的值 -
void gpio_set_value(unsigned gpio, int value)
,设置某个GPIO的输出值设备树中有节点, &usdhc1 { pinctrl-names = "default", "state_100mhz", "state_200mhz"; pinctrl-0 = <&pinctrl_usdhc1>; pinctrl-1 = <&pinctrl_usdhc1_100mhz>; pinctrl-2 = <&pinctrl_usdhc1_200mhz>; cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; //在这绑定了引脚号和功能引脚 keep-power-in-suspend; enable-sdio-wakeup; vmmc-supply = <®_sd1_vmmc>; status = "okay"; };
-
-
与gpio相关的OF函数
-
int of_gpio_named_count(struct device_node *np, const char *propname)
,设备树某个属性里面定义了几个GPIO信息,空的GPIO也会被统计 np: 设备节点 propname: 要统计的GPIO属性 返回值:正值,统计到的 GPIO 数量;负值,失败。demo: 某个节点里面: gpios = < 0 &gpio1 1 2 0 & gpio2 3 4>; 使用该函数时 gpio_cnt = of_gpio_named_count(&dev_np, "gpios" );
-
int of_gpio_count(struct device_node *np)
,与上面的函数int of_gpio_named_count(struct device_node *np, const char *propname)功能类似,但该函数只针对"gpios"属性 -
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
,此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,即获取7这个编号 np: 设备节点 propname: 要统计的GPIO属性 index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。 返回值:正值,统计到的 GPIO 编号;负值,失败。 -
包含的头文件 #include <linux/of_gpio.h>
-
-
gpio 扩展
-
pinctrl && gpio 混合使用的操作流程
-
先在自己的设备树源代码(.dts)文件中,在非 /根目录节点下的 pinctrl 节点中(即&xx节点),添加自己的 pinctrl 信息
-
在根节点(或根节点下的成员节点中添加自定义的节点)
-
在官方(因为我们的设备树源代码是拷贝官方的)设备树源代码中,查找IO是有有重复使用的情况。
-
最后 make dtbs 命令重新编译设备树
-