一、GPIO的标准接口函数
- 前面访问GPIO的方法:
- request_mem_region申请GPIO寄存器的物理内存区
- ioremap实现IO内存的动态映射,得到虚拟地址
- 访问虚拟地址进而控制硬件
- 后面的简单的GPIO访问方法:
GPIO是嵌入式平台最常见的一个硬件模块,所以linux内核将GPIO的访问过程封装成了标准的接口函数,这些接口函数在调用的时候和平台无关的,平常使用iowrite32、ioread32等这些函数有以下优缺点:
优点:能够对申请到寄存器资源进行访问,例如GPIO寄存器、串口寄存器、ADC寄存器、I2C寄存器等。
缺点:针对GPIO配置与控制有点繁琐。
因此,GPIO标准接口函数就解决了iowrite32与ioread32的缺点,操作GPIO更加的简单。
二、GPIO标准接口函数
#include <linux/gpio.h> //linux自带的
#include <cfg_type.h> //芯片厂商提供的
- 单个GPIO的申请和释放
GPIO使用之前先申请
int gpio_request(unsigned gpio, const char *label);
参数:
gpio,引脚的编号,依赖于芯片厂商提供的源代码,没有唯一的。
label:自定义引脚的名字,用于调试信息打印,gpio_request函数体里面有以下代码:
int gpio_request(unsigned gpio, const char *label)
{
//定义gpio描述结构体
struct gpio_desc *desc;
//将label添加到desc结构体中的label
desc_set_label(desc, label ? : "?");
//label辅助打印信息更详细
pr_debug("gpio_request: gpio-%d (%s) status %d\n",gpio, label ? : "?", status);
}
返回值:
成功,0
失败,错误码
2. 单个GPIO的释放(GPIO使用之后需要释放)
void gpio_free(unsigned gpio);
参数:引脚的编号
- 配置GPIO为输出模式
int gpio_direction_output(unsigned gpio, int value);
参数:
gpio,引脚的编号
value,1 输出高电平;0 输出低电平
返回值:
成功,0
失败,错误码
- 配置GPIO为输入模式
int gpio_direction_input(unsigned gpio);
参数:
gpio,引脚的编号
返回值:
成功,0
失败,错误码
- 设置GPIO的输出值
void gpio_set_value(unsigned gpio, int value);
参数:
gpio,引脚的编号
value,1 输出高电平;0 输出低电平
- 获取GPIO的输入值
int gpio_get_value(unsigned gpio);
参数:
gpio,引脚的编号
返回值:
引脚的电平
注意:上述这些函数是通用的,是与平台无关的。
三、参考源码实例
- 源码路径:drivers/char/humity/gec6818_humity.c
- 关键代码
#define CFG_IO_X6818_Humity (PAD_GPIO_C + 29)
static int __init gec6818_humidity_dev_init(void) {
int ret;
ret = gpio_request(CFG_IO_X6818_Humity, "humidity");
if (ret) {
printk("%s: request GPIO %d for humidity failed, ret = %d\n", DEVICE_NAME,
CFG_IO_X6818_Humity, ret);
return ret;
}
gpio_direction_output(CFG_IO_X6818_Humity, 1);
ret = misc_register(&gec6818_humidity_dev);
printk(DEVICE_NAME"\tinitialized\n");
return ret;
}
四、GPIO口号
- GPIO号是与硬件相关的
每个GPIO都有一个GPIO口号的,使用GPIO口号还识别/区分一个具体的GPIO。GPIO号是与硬件相关的。
- GPIO号的定义
- /kernel/arch/arm/plat-s5p6818/common/cfg_type.h
enum {
PAD_GPIO_A = (0 * 32),
PAD_GPIO_B = (1 * 32),
PAD_GPIO_C = (2 * 32),
PAD_GPIO_D = (3 * 32),
PAD_GPIO_E = (4 * 32),
PAD_GPIO_ALV = (5 * 32),
};
例子:
D8 ---> GPIOC17 --->PAD_GPIO_C+17
D9 ---> GPIOC8 --->PAD_GPIO_C+8
D10 ---> GPIOC7 --->PAD_GPIO_C+7
D11 ---> GPIOC12 --->PAD_GPIO_C+12
注意:使用设备树的方法来操作的方法如下:
关键代码:
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev 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; /* led设备 */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
五 、检查 PIN 是否被其他外设使用
这一点非常重要!!!
很多初次接触设备树的驱动开发人员很容易因为这个小问题栽了大跟头!因为我们所使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设备树是根据自己官方开发板编写的,很多 PIN 的配置和我们所使用的开发板不一样。比如 A 这个引脚在官方开发板接的是 I2C 的 SDA,而我们所使用的硬件可能将 A 这个引脚接到了其他的外设,比如 LED 灯上,接不同的外设,A 这个引脚的配置就不同。一个引脚一次只能实现一个功能,如果A引脚在设备树中配置为了I2C的SDA信号,那么A引脚就不能再配置为GPIO,否则的话驱动程序在申请 GPIO 的时候就会失败。检查 PIN 有没有被其他外设使用包括两个方面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。
在本章实验中 LED 灯使用的 PIN 为 GPIO1_IO03,因此先检查 GPIO_IO03 这个 PIN 有没有被其他的 pinctrl 节点使用,在 imx6ull-alientek-emmc.dts 中找到如下内容:
示例代码 45.4.1.3 pinctrl_tsc 节点
480 pinctrl_tsc: tscgrp {
481 fsl,pins = <
482 MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483 MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
485 MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486 >;
487 };
pinctrl_tsc 节点是 TSC(电阻触摸屏接口)的 pinctrl 节点,从第 484 行可以看出,默认情况下GPIO1_IO03 作为了 TSC 外设的 PIN。所以我们需要将第 484 行屏蔽掉!和 C 语言一样,在要屏蔽的内容前后加上“/”和“/”符号即可。其实在 I.MX6U-ALPHA 开发板上并没有用到 TSC接口,所以第 482~485 行的内容可以全部屏蔽掉。因为本章实验我们将 GPIO1_IO03 这个 PIN 配置为了 GPIO,所以还需要查找一下有没有其他的外设使用了 GPIO1_IO03,在 imx6ull-alientek-emmc.dts 中搜索“gpio1 3”,找到如下内
容:
示例代码 45.4.1.4 tsc 节点
723 &tsc {
724 pinctrl-names = "default";
725 pinctrl-0 = <&pinctrl_tsc>;
726 xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
727 measure-delay-time = <0xffff>;
728 pre-charge-time = <0xfff>;
729 status = "okay";
730 };
tsc 是 TSC 的外设节点,从 726 行可以看出,tsc 外设也使用了 GPIO1_IO03,同样我们需要将这一行屏蔽掉。然后在继续搜索“gpio1 3”,看看除了本章的 LED 灯以外还有没有其他的地方也使用了 GPIO1_IO03,找到一个屏蔽一个。
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中
查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),
图 45.4.1.1 gpio 子节点