一、设备树
设备树是一个描述硬件的数据结构,内核可以从设备树中获取硬件信息
设备树目录在内核中的arch/arm/boot/dts/
设备树是一个包含节点和属性的简单树状结构。属性就是键值对,而节点可以同时包含属性和子节点。设备树采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息。
其中,设备树源文件扩展名为.dts,但是我们在前面移植 Linux 的时候却一直在使用.dtb 件,DTS 是设备树源码文件,DTB 是将DTS 编译以后得到的二进制文件。将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb需要用到 DTC 工具。
1、设备树文件内容样式
/{
aliases {
can0 = &flexcan1;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
};
};
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
}
“ /”是根节点,每个设备树文件只有一个根节点,aliases、cpus 和 intc 是三个子节点
2、节点命名
在设备树中节点命名格式如下:
node-name@unit-address
其中“node-name”是节点名字,为 ASCII 字符串
unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要。
也可以采用节点标签(label)的形式进行命名:
label: node-name@unit-address
引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点
3、节点属性
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任
意的字节流。
compatible 属性
compatible 属性也叫做“兼容性”属性,用于将设备和驱动绑定起来,compatible 属性的值是一个字符串列表,格式如下所示:"manufacturer,model",其中 manufacturer 表示厂商,model 一般是模块对应的驱动名字,如下所示:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。
一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设
备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个
驱动。
model 属性
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,如下所示;
model = "wm8960-audio";
status 属性
status 属性是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息,可选的状态有:
“okay” 表明设备是可操作的。
“disabled” 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备
插入以后。至于 disabled 的具体含义还要看设备的绑定文档。
“fail” 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可
操作。
“fail-sss” 含义和“fail”相同,后面的 sss 部分是检测到的错误内容。
#address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形,#address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。
#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位)
#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
reg 属性
reg 属性的值一般是(address,length)对。reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg 属性的格式一为:
reg = <address1 length1 address2 length2 address3 length3……>
每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长度
#address-cells 表明 address 这个数据所占用的字长
#size-cells 表明 length 这个数据所占用的字长
ranges 属性
ranges 属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字
矩阵,ranges 是一个地址映射/转换表,ranges 属性每个项目由子地址、父地址和地址空间长度
这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换
4、设备树常用 OF 操作函数
我们通过OF操作函数从设备树中获取设备的节点和属性信息。
查找节点的 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_path 函数
of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:
inline struct device_node *of_find_node_by_path(const char *path)
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值:找到的节点,如果为 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_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_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 函数
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_string 函数
of_property_read_string 函数用于读取属性中字符串值,函数原型如下:
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string)
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值:0,读取成功,负值,读取失败
of_n_addr_cells 函数
of_n_addr_cells 函数用于获取#address-cells 属性值,函数原型如下:
int of_n_addr_cells(struct device_node *np)
np:设备节点。
返回值:获取到的#address-cells 属性值。
of_n_size_cells 函数
of_size_cells 函数用于获取#size-cells 属性值,函数原型如下:
int of_n_size_cells(struct device_node *np)
np:设备节点。
返回值:获取到的#size-cells 属性值。
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 函数
of_property_read_u16 函数
of_property_read_u32 函数
of_property_read_u64 函数
这四个函数就是用于读取这种只有一个整形值的属性,分别用于读取 u8、u16、u32 和 u64 类型属性值,函数原型如下:
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 表示属性值列表太小
of_property_read_u8_array 函数
of_property_read_u16_array 函数
of_property_read_u32_array 函数
of_property_read_u64_array 函数
这 4 个函数分别是读取属性中 u8、u16、u32 和 u64 类型的数组数据,比如大多数的 reg 属
性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。这四个函数的原型
如下:
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 表示属性值列表太小。
查找父/子节点的 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,表示从第一个子节点开始。
返回值:找到的下一个子节点。
其他常用的 OF 函数
of_device_is_compatible 函数
of_device_is_compatible 函数用于查看节点的 compatible 属性是否有包含 compat 指定的字
符串,也就是检查设备节点的兼容性,函数原型如下:
int of_device_is_compatible(const struct device_node *device,
const char *compat)
device:设备节点。
compat:要查看的字符串。
返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串;正数,节点的 compatible
属性中包含 compat 指定的字符串。
of_get_address 函数
of_get_address 函数用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性
值,函数原型如下:
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 的话表示读取失败。
of_translate_address 函数
of_translate_address 函数负责将从设备树读取到的地址转换为物理地址,函数原型如下:
u64 of_translate_address(struct device_node *dev,
const __be32 *in_addr)
dev:设备节点。
in_addr:要转换的地址。
返回值:得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
of_address_to_resource 函数
IIC、SPI、GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux内核使用 resource 结构体来描述一段内存空间,“ resource”翻译出来就是“资源”,因此用 resource结构体描述的都是设备资源信息,resource 结构体定义在文件 include/linux/ioport.h 中,定义如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;};
of_address_to_resource 函数本质上就是将 reg 属性值,然后将其转换为 resource 结构体类型,
函数原型如下所示:
int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r)
dev:设备节点。
index:地址资源标号。
r:得到的 resource 类型的资源值。
返回值:0,成功;负值,失败。
of_iomap 函数
of_iomap 函数用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地
址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,
不需要使用 ioremap 函数了。
void __iomem *of_iomap(struct device_node *np,
int index)
np:设备节点。
index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
二、pinctrl和gpio 子系统
1、pinctrl子系统
pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin (引脚)信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
(1)设备树设置PIN 配置信息
pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。在设备树文件中找到一个叫做 iomuxc 的节点,添加我们自定义外设的 PIN,新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。
完整的 iomuxc 节点如下
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
>;
......
};
};
};
pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息
(2)配置LED灯的pinctrl 节点
需要通过底板原理图找到LED灯对应的IO引脚,GPIO_3 就是 GPIO1_IO03
然后在imx6ull-pinfunc.h或者imx6ul-pinfunc.h中搜索MX6UL_PAD_GPIO1_IO03找到对应的引脚
#define MX6UL_PAD_GPIO1_IO03__I2C1_SDA 0x0068 0x02F4 0x05A8 0x0 0x1
#define MX6UL_PAD_GPIO1_IO03__GPT1_COMPARE3 0x0068 0x02F4 0x0000 0x1 0x0
#define MX6UL_PAD_GPIO1_IO03__USB_OTG2_OC 0x0068 0x02F4 0x0660 0x2 0x0
#define MX6UL_PAD_GPIO1_IO03__REF_CLK_32K 0x0068 0x02F4 0x0000 0x3 0x0
#define MX6UL_PAD_GPIO1_IO03__USDHC1_CD_B 0x0068 0x02F4 0x0668 0x4 0x0
#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x0068 0x02F4 0x0000 0x5 0x0
#define MX6UL_PAD_GPIO1_IO03__CCM_DI0_EXT_CLK 0x0068 0x02F4 0x0000 0x6 0x0
#define MX6UL_PAD_GPIO1_IO03__SRC_TESTER_ACK 0x0068 0x02F4 0x0000 0x7 0x0
#define MX6UL_PAD_GPIO1_IO03__UART1_DCE_RX 0x0068 0x02F4 0x0624 0x8 0x1
#define MX6UL_PAD_GPIO1_IO03__UART1_DTE_TX 0x0068 0x02F4 0x0000 0x8 0x0
我们使用 #define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 复用为gpio引脚
然后配置其电气属性,在参考手册中IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03中配置其速度设置、驱动能力设置、压摆率设置等电气属性。(二进制转16进制得到0x10B0)
所以在内核中的arch/arm/boot/dts/imx6ull-alientek-emmc.dts中的iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
表示将IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03的电气属性值设为0x10B0
2、gpio 子系统
gpio子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等,gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程。
gpio节点是添加在根节点“\”下的,模板为:
test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};
(1)配置LED灯的gpio节点
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
其中,pinctrl-0属性的值是LED灯的pinctrl节点名字
led-gpio属性值“gpio1 3”表示引脚号 GPIO1_IO03,因为LED灯是低电平有效,所以是GPIO_ACTIVE_LOW
(2)检查 PIN 是否被其他外设使用
检查原因:因为我们所使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设备树是根据自己官方开发板编写的,很多 PIN 的配置和我们所使用的开发板不一样。比如 A 这个引脚在官方开发板接的是 I2C 的 SDA,而我们所使用的硬件可能将 A 这个引脚接到了其他的外设,比如 LED 灯上,接不同的外设,A 这个引脚的配置就不同。一个引脚一次只能实现一个功能,如果 A 引脚在设备树中配置为了 I2C 的 SDA 信号,那么 A 引脚就不能再配置为 GPIO,否则的话驱动程序在申请 GPIO 的时候就会失败。
检查步骤:
①、检查 pinctrl 设置
比如LED灯使用的 PIN 为 GPIO1_IO03,因此先检查 GPIO_IO03 这个 PIN 有没有被其他的pinctrl 节点使用,有的话屏蔽掉。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用
搜索“gpio1 3”,将该行屏蔽掉。
(3)gpio 子系统 API 函数
gpio_request 函数
gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request
进行申请,函数原型如下:
int gpio_request(unsigned gpio, const char *label)
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信
息,此函数会返回这个 GPIO 的标号。
label:给 gpio 设置个名字。
返回值:0,申请成功;其他值,申请失败。
gpio_free 函数
如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:
void gpio_free(unsigned gpio)
gpio:要释放的 gpio 标号。
返回值:无。
gpio_direction_input 函数
此函数用于设置某个 GPIO 为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)
gpio:要设置为输入的 GPIO 标号。
返回值:0,设置成功;负值,设置失败。
gpio_direction_output 函数
此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:
int gpio_direction_output(unsigned gpio, int value)
gpio:要设置为输出的 GPIO 标号。
value:GPIO 默认输出值。
返回值:0,设置成功;负值,设置失败。
gpio_get_value 函数
gpio_get_value 函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
gpio:要获取的 GPIO 标号。
返回值:非负值,得到的 GPIO 值;负值,获取失败。
gpio_set_value 函数
此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
gpio:要设置的 GPIO 标号。
value:要设置的值。
返回值:无
(4)与 gpio 相关的 OF 函数
of_gpio_named_count 函数
of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的
是空的 GPIO 信息也会被统计到。此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
np:设备节点。
propname:要统计的 GPIO 属性。
返回值:正值,统计到的 GPIO 数量;负值,失败。
of_gpio_count 函数
和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属
性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下
所示:
int of_gpio_count(struct device_node *np)
np:设备节点。
返回值:正值,统计到的 GPIO 数量;负值,失败。
of_get_named_gpio 函数
此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,
此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编
号,此函数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)
np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值:正值,获取到的 GPIO 编号;负值,失败。
3、设备树编译
使用“make dtbs”命令重新编译设备树
make dtbs
随后将编译得到的.dtb文件复制到tftp目录下(内核通过tftp方式挂载在tftp目录下)
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/f3161/linux/tftp/ -f
启动Linux系统后,进入“/proc/device-tree”目录中可以查看“gpioled”节点是否存在。
三、编写驱动程序调用pinctrl和gpio子系统
以LED灯实验为例
1、修改设备结构体
在设备结构体中添加所使用的GPIO编号 int led_gpio;
struct gpioled_dev{
dev_t devid;
struct cdev cdev; /* 设备号 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led 所使用的 GPIO 编号*/
};
struct gpioled_dev gpioled;
2、获取设备节点
在驱动入口函数static int __init led_init(void)中添加如下内容获取设备节点
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
printk("gpioled node cant not found!\n");
return -EINVAL;
}
其中of_find_node_by_path()的参数是在设备树中添加的gpio节点
如果获取设备节点失败,需要将设备号、字符设备、类、设备都进行释放
3、获取设备树中的 gpio 属性
获取LED所对应的GPIO,在驱动入口函数static int __init led_init(void)中添加如下内容
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
of_get_named_gpio()函数的第一个参数是设备节点,第二个是获取所需要的GPIO信息的属性名,第三个是GPIO索引。
如果获取设备节点失败,需要将设备号、字符设备、类、设备都进行释放
4、申请GPIO管脚
得到GPIO编号后,需要进行申请
ret = request(gpioled.led_gpio, "led-gpio");
if(ret != 0){
printk("request failed!\n");
return -1;
}
注:需要在驱动出口函数static void __exit mykey_exit(void)中释放IO
gpio_free(gpioled.led_gpio);
参数为GPIO编号
5、设置GPIO输入输出
设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
注:使用IO失败时需要gpio_free()函数释放IO,同时将设备号、字符设备、类、设备都进行释放
6、设置GPIO数值
可以在其他函数中对LED灯进行操作,设置为低电平,点亮
gpio_set_value(gpioled.led_gpio,0);
7、驱动出口函数
在驱动出口函数中使用gpio_free()函数释放IO,同时将设备号、字符设备、类、设备都进行释放
gpio_free(gpioled.led_gpio);