第九章GPIO框架驱动
19.1 GPIO框架驱动概述
在日常的设备驱动开发中,GPIO驱动是开发中比较常用的而且是很重要的驱动,例如需要对LED灯、按键扫描以及输出端口高低电平等控制,它们都需要用到GPIO驱动来实现。
GPIO是嵌入式平台比较常见的硬件模块,因此linux内核针对GPIO的访问控制等操作进行了封装,形成了GPIO独有的标准的函数接口,这些函数接口在调用时是与硬件平台无关的,同时GPIO框架的函数中的GPIO都是使用整型的GPIO编号进行标识的,因此只需要获取到需要操作的GPIO编号,即可使用相应的框架函数对GPIO进行控制,对这样有助于提高了开发人员的开发效率。
19.2 GPIO框架的相关结构体介绍
GPIO是跟硬件平台相关性很强的,linux提供的GPIO框架函数来统一处理GPIO各种操作,其中如下为GPIO框架的处理过程:
(1) 各个板卡都是有对应的gpio_chip控制模块,其中它是包含的成员函数:request()、free()、input()和output()等,然后将控制模块注册进内核中。
(2) 当注册gpio_chip模块时,会改变全局的gpio_desc[]。
(3) 当用户对相应的gpio进行请求时,此时会先到这个gpio_desc[]中寻找并调用相应的GPIO对应的gpio_chip的处理函数,来完成相应的操作。
(4) GPIO框架对应每组的GPIO都会用一个gpio_chip对象来去实现其驱动的功能,因此gpio_chip也被称为GPIO控制器。
如下为对应上述过程涉及到的结构体的定义:
(1) struct gpio_desc结构体
struct gpio_desc { struct gpio_chip *chip; unsigned long flags; /* flag symbols are bit numbers */ #define FLAG_REQUESTED 0 //已请求资源 #define FLAG_IS_OUT 1 //输出IO #define FLAG_EXPORT 2 /* protected by sysfs_lock */ #define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */ #define FLAG_TRIG_FALL 4 /* trigger on falling edge */ #define FLAG_TRIG_RISE 5 /* trigger on rising edge */ #define FLAG_ACTIVE_LOW 6 /* value has active low */ #define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */ #define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */ #define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */ #define ID_SHIFT 16 /* add new flags before this one */ #define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1) #define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE)) #ifdef CONFIG_DEBUG_FS const char *label; #endif };
(2) struct gpio_chip结构体
结构体gpio_chip可以对所有的GPIO源进行抽象化,然后使其硬件板上的其他模块可以使用相同的函数接口来对GPIO进行操作控制。
IOS大小的gpio描述符数组来表示整个系统的gpio的信息。struct gpio_chip { const char *label; struct device *dev; struct module *owner; int (*request)(struct gpio_chip *chip,unsigned offset);//请求gpio void *free)(struct gpio_chip *chip,unsigned offset);//释放gpio int (*get_direction)(struct gpio_chip *chip,unsigned offset); int (*direction_input)(struct gpio_chip *chip,unsigned offset);//配置gpio为输入,返回当前gpio状态 int (*get)(struct gpio_chip *chip,unsigned offset);//获取gpio的状态 int (*direction_output)(struct gpio_chip *chip,unsigned offset, int value);//配置gpio为输出,并设置为value int (*set_debounce)(struct gpio_chip *chip,unsigned offset, unsigned debounce);//设置消抖动时间,尤其是gpio按键时有用 void (*set)(struct gpio_chip *chip,unsigned offset, int value);//设置gpio为value值 int (*to_irq)(struct gpio_chip *chip,unsigned offset);//把gpio号转换为中断号 void (*dbg_show)(struct seq_file *s,struct gpio_chip *chip) int base;// 这个gpio控制器的gpio开始编号 u16 ngpio;//这个gpio控制器说控制的gpio数 const char *const *names; unsigned can_sleep:1; unsigned exported:1; #if defined(CONFIG_OF_GPIO) struct device_node *of_node; int of_gpio_n_cells; int (*of_xlate)(struct gpio_chip *gc,const struct of_phandle_args *gpiospec, u32 *flags); #endif #ifdef CONFIG_PINCTRL struct list_head pin_ranges; #endif };
下面介绍一下gpio_chip的相关成员:
base:为对应该组GPIO的起始值。
ngpio:为该组GPIO的数量。
owner:为该成员的所有者,一般设置为THIS_MODULE
request:为对这组的GPIO进行调用gpio_request()函数时,该成员指向的真正需要执行的request()函数。
free:为对这组的GPIO进行调用gpio_free()函数时,该成员指向的真正需要执行的释放函数。
direction_input:在该组GPIO被调用gpio_direction_input()时,该成员指向的实现函数将被调用,主要完成设置指定的GPIO的方向为输入方向。
get:该成员主要是保存该组GPIO需要被调用的gpio_get_value()的函数具体实现,主要完成获取指定的GPIO的电平状态值。
direction_output:在该组GPIO被调用gpio_direction_output()时,该成员指向的实现函数将被调用,主要完成设置指定的GPIO的方向为输出方向。
set:该成员主要是保存该组GPIO需要被调用的gpio_set_value()的函数具体实现,主要完成设置指定的GPIO的电平状态值。
(3) GPIO口编号
在GPIO框架驱动中,每个GPIO都会有一个对应的GPIO口编号用于识别其唯一性,同时GPIO口号是与硬件相关的。
如下在s5p6818平台上是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), };
而后对应如下几个GPIO口为例则对应关系如下:
D8 ---> GPIOC17 --->PAD_GPIO_C+17 D9 ---> GPIOC8 --->PAD_GPIO_C+8 D10 ---> GPIOC7 --->PAD_GPIO_C+7 D11 ---> GPIOC12 --->PAD_GPIO_C+12
19.2 GPIO框架驱动接口讲解
GPIO框架提供了非常简洁的接口,可供对指定的GPIO进行操作控制。
(1) GPIO资源的申请和释放
在使用GPIO进行控制前,需要先调用gpio_request()函数来申请相应的GPIO资源,如下为该函数的定义:
/*函数功能:申请GPIO资源 *函数参数: *@unsigned gpio:为GPIO口编号 *@const char *label:GPIO标识字符串,可以随意设置,用于说明标识 *函数返回值: *@成功:返回0 *@失败:返回非零值 */ int gpio_request(unsigned gpio, const char *label);
当调用gpio_request()函数时需要传入GPIO的编号值,以及设置一下label的标识字符串的值,例如需要申请GPIOC12的资源时:
gpio_request(PAD_GPIO_C+12, "D11");
在使用完GPIO口资源时,需要调用释放函数gpio_free()释放资源:
/*函数功能:释放GPIO资源 *函数参数: *@unsigned gpio:为GPIO口编号 *函数返回值:无 */ void gpio_free(unsigned gpio);
(2) GPIO的输出控制设置
在申请完GPIO资源后,如果需要操作GPIO输出高低电平前,还需将该GPIO设置为输出方向,可调用gpio_direction_output()函数进行设置,如下为该函数定义:
/*函数功能:将GPIO设置为输出方向 *函数参数: *@unsigned gpio:为GPIO口编号 *@ int value:设置默认输出电平值,1为高电平,0为低电平 *函数返回值: *@成功:返回0 *@失败:返回非零值 */ int gpio_direction_output(unsigned gpio, int value);
(3) GPIO的输入控制设置
在申请完GPIO资源后,如果想获取GPIO输入电平的状态值前还需要调用gpio_direction_input()函数来将GPIO设置为输入方向,如下为该函数的定义:
/*函数功能:将GPIO设置为输入方向 *函数参数: *@unsigned gpio:为GPIO口编号 *函数返回值: *@成功:返回0 *@失败:返回非零值 */ int gpio_direction_input(unsigned gpio);
(4) 获取GPIO电平状态
在使用gpio_direction_input()函数设置为输入方向时,此时可以调用gpio_get_value()函数读取当前GPIO的输入电平状态值:
/*函数功能:获取GPIO电平状态 *函数参数: *@unsigned gpio:为GPIO口编号 *函数返回值:1为高电平,0为低电平 */ int gpio_get_value(unsigned gpio);
(5) 设置GPIO输出电平状态
在使用gpio_direction_output()函数设置为输出方向时,此时可以调用gpio_set_value()函数设置当前GPIO的输出电平状态值:
/*函数功能:设置GPIO输出电平状态 *函数参数: *@unsigned gpio:为GPIO口编号 *@ int value:输出电平状态值,1为高电平,0为低电平 *函数返回值:无 */ void gpio_set_value(unsigned gpio, int value);
(6) GPIO的中断映射设置
大部分的硬件平台的GPIO引脚在被设置为输入方向后,一般都可用于外部中断信号的输入,通常情况下,这些中断号会与GPIO编号有着对应的关系,可以调用gpio_to_irq()函数来获取对应GPIO编号的中断号:
/*函数功能:根据GPIO编号获取对应的中断号 *函数参数: *@unsigned gpio:为GPIO口编号 *函数返回值: *成功:返回中断号;失败:返回小于0的错误码 */ int gpio_to_irq(unsigned gpio);
在调用gpio_to_irq()函数后,并不是所有的GPIO口都有对应的外部中断输入信号的,需要根据不同的硬件平台的数据手册来确定的,因此成功会返回中断号,失败返回相应的错误码。
19.3 GPIO框架驱动示例讲解
在了解了上述GPIO框架驱动的接口函数后,如下以温湿度控制器的驱动程序展示如何使用在GPIO框架下编写驱动程序(gec6818_humity.c)进行对外设进行控制。
在驱动初始化部分的关键代码如下:
其中驱动使用gpio_request()函数申请了GPIOC29的GPIO资源,并将该GPIO接口设置为输出方向的模式。
#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;
}
如下为分别对该GPIOC29设置为输入和输出模式的关键代码:
在函数data_in()中,调用gpio_direction_input()函数设置为输入模式后,即可调用gpio_get_value()函数获取当前引脚的输入状态值了。
如果需要设置GPIOC29为输出模式且立刻设置当前的输出值时,可以看到函数data_out()中调用了gpio_direction_output(CFG_IO_X6818_Humity, data);设置了GPIOC29为输出模式,且通过data的值设置当前的默认输出电平状态值。
/************************************************* *设置为输入且获取当前输入状态值 *************************************************/ int data_in(void) { gpio_direction_input(CFG_IO_X6818_Humity); return gpio_get_value(CFG_IO_X6818_Humity); } /************************************************* *管教设置为输出 *************************************************/ void data_out(int data) { gpio_direction_output(CFG_IO_X6818_Humity, data); }
接下来给出完整的温湿度控制器的驱动函数以供参考学习,由于前面已经讲解过关于混杂设备驱动的相关知识,此处将不再展开讲述。本例程的温湿度控制器即是按照混杂驱动设备来进行注册进内核使用,然后根据数据手册的设备运行时序图编写程序,最后对外围设备进行控制,例如对输入端口读取数据时需要先对温湿度控制器进行重启,然后再检测温湿度控制器是否重启完成和与系统进行连接成功,如果连接成功后再跟进时序进行获取数据。
gec6818_humity.c完整代码部
/*功能描述: 温湿度驱动程序 */ /************************************************* *头文件 *************************************************/ #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <asm/mach-types.h> #include <linux/gpio.h> #include <asm/gpio.h> #include <asm/delay.h> #include <linux/clk.h> #include <mach/gpio.h> #include <mach/soc.h> #include <mach/platform.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/errno.h> #include <linux/serio.h> #include <linux/wait.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <linux/uaccess.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> #include <mach/gpio.h> #include <linux/gpio.h> #define DEVICE_NAME "humidity" //设备名字 unsigned long receive_value; unsigned long receive_jy; /* * HUMITY -> GPDC(29) */ #define CFG_IO_X6818_Humity (PAD_GPIO_C + 29) /************************************************* *管教设置为输入 *************************************************/ int data_in(void) { gpio_direction_input(CFG_IO_X6818_Humity); return gpio_get_value(CFG_IO_X6818_Humity); } /************************************************* *管教设置为输出 *************************************************/ void data_out(int data) { gpio_direction_output(CFG_IO_X6818_Humity, data); } /************************************************* *温湿度数据读取 *主机拉低,延时20ms。主机拉高, *主机拉高,延时40ms。 *设置为输入端口,等待数据位拉低 *设置为输入端口,等待数据位拉高 *数据位 *************************************************/ void humidity_read_data(void) { unsigned int u32i = 0; unsigned int flag = 0; receive_value = 0; receive_jy = 0; //重启温湿度控制器 data_out(0); mdelay(40); data_out(1); data_in(); //检测温湿度控制器是否在线 while(gpio_get_value(CFG_IO_X6818_Humity) && (flag <100)) //waiting for dht11's response { flag++; } flag = 0; //等待数据为拉低 //udelay(80); //dht11 response last about 80us while((!gpio_get_value(CFG_IO_X6818_Humity)) && (flag <100)) //waiting for dht11'response end { flag++; } flag = 0; //等待数据位拉高 //udelay(80); //dht11 pull up about 80us while(gpio_get_value(CFG_IO_X6818_Humity)&& (flag <100)) //waiting for dht11 pull up end { flag++; } flag = 0; //获取数据位数据 for (u32i=0x80000000; u32i>0; u32i>>=1) { while((!gpio_get_value(CFG_IO_X6818_Humity)) && (flag <100)) //waiting for dht11'response end { flag++; } flag = 0; udelay(50); if( data_in() == 1) { receive_value |= u32i; while(gpio_get_value(CFG_IO_X6818_Humity)&& (flag <100)) //waiting for dht11 pull up end { flag++; } flag = 0; } continue; } #if 1 for (u32i=0x80; u32i>0; u32i>>=1) { while((!gpio_get_value(CFG_IO_X6818_Humity)) && (flag <100)) //waiting for dht11'response end { flag++; } flag = 0; udelay(50); if( data_in() == 1) { receive_jy |= u32i; while(gpio_get_value(CFG_IO_X6818_Humity)&& (flag <100)) //waiting for dht11 pull up end { flag++; } flag = 0; } continue; } #endif data_out(1); } /************************************************* *温湿度数据读取 *************************************************/ static ssize_t gec6818_humidiy_read(struct file *file, char __user *buf, size_t size, loff_t *off) { unsigned char tempz = 0; unsigned char tempx = 0; unsigned char humidityz = 0; unsigned char humidityx = 0; unsigned char ecc,jy; int ret = 0; humidity_read_data(); printk("=============receive_value= %lx receive_jy= %lx \n", receive_value, receive_jy); humidityz = (receive_value & 0xff000000)>>24; humidityx = (receive_value & 0x00ff0000)>>16; tempz = (receive_value & 0x0000ff00)>>8; tempx = (receive_value & 0x000000ff); jy = receive_jy & 0xff; ecc = humidityz + humidityx + tempz + tempx; printk("=============ecc=%x jy=%x \n",ecc,jy); if(ecc != jy) return -EAGAIN; ret = copy_to_user(buf,&receive_value,sizeof (receive_value)); if (ret != 0) return -EAGAIN; return 0; } static int gec6818_humidiy_open(struct inode *inode, struct file *file) { printk("open in kernel\n"); return 0; } */ /************************************************* *文件操作集 *************************************************/ static struct file_operations gec6818_humidity_dev_fops = { .owner = THIS_MODULE, .open = gec6818_humidiy_open, .read = gec6818_humidiy_read, }; /************************************************* *杂项设备 *************************************************/ static struct miscdevice gec6818_humidity_dev = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &gec6818_humidity_dev_fops, }; /******************************************************************** *驱动的初始化函数--->从内核中申请资源(内核、中断、设备号、锁....) ********************************************************************/ 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; } /***************************************************************** *驱动退出函数 --->将申请的资源还给内核 *****************************************************************/ static void __exit gec6818_humidity_dev_exit(void) { gpio_free(CFG_IO_X6818_Humity); misc_deregister(&gec6818_humidity_dev); } module_init(gec6818_humidity_dev_init); //驱动的入口函数会调用一个用户的初始化函数 module_exit(gec6818_humidity_dev_exit); //驱动的出口函数会调用一个用户的退出函数 //驱动的描述信息: #modinfo*.ko,驱动的描述信息并不是必需的。 MODULE_AUTHOR("WHL@GEC"); //驱动的作者 MODULE_DESCRIPTION("whl of driver"); //驱动的描述 MODULE_LICENSE("GPL"); //遵循的协议
19.4本章总结
本章节讲述了关于linux设备驱动中比较重要的一个内容GPIO框架驱动的相关知识,主要讲述了相关的结构体的定义以及GPIO框架中的简洁的函数接口的使用,最后结合一个温湿度的例子来展示如何在混杂设备中完成使用GPIO框架的接口函数来操作温湿度控制器获取数据等。GPIO框架驱动是在日常开发中非常常见的以及它提供的函数接口非常简洁,因此学习本章节对后续的设备驱动工作可以提升一定的开发效率。