> 文章列表 > Linux驱动之GPIO函数、IO内存映射、混杂设备驱动

Linux驱动之GPIO函数、IO内存映射、混杂设备驱动

Linux驱动之GPIO函数、IO内存映射、混杂设备驱动

之前学习完了字符设备驱动的大体框架,现在我们就使用这个基本的框架来对硬件进行操作,例如通过指令控制led的状态,编写LED驱动。LED驱动有多种实现方式。

目录

GPIO函数

IO内存映射

混杂设备驱动


GPIO函数

首先加入需要的头文件。

#include <asm/gpio.h>

#include <mach/soc.h>

#include <mach/platform.h> 

GPIO属于资源,在内核中属于资源使用前就需要先申请,使用完就需要释放。 

使用gpio_request函数向内核申请需要的GPIO引脚。

int gpio_request(unsigned gpio, const char *label);

参数:

    gpio :GPIO引脚号

    本人使用的s5p6818,每组GPIO都有宏,然后加上组内编号。例如GPIOE13表示为              PAD_GPIO_E+13

    label:自定义标签,也可以说是名字吧,可以是NULL

返回0成功,返回小于0失败   

 使用gpio_free函数向内核释放使用的GPIO引脚。

void gpio_free(unsigned gpio);

//传入要释放的GPIO的端口号

根据s5p6818对GPIO接口操作的说明,对GPIO的操作分为:

选择复用功能    //每个引脚最多有4个复用功能,看原理图或手册选择

选择输入/输出模式

设置输出值/获取输入的值

有些引脚有不同的功能,使用前需要选择作用为GPIO,不同的板子可能对应的操作有些许差别。

我们把上面三个操作的函数介绍下,就写个小测试。

选择复用功能使用nxp_soc_gpio_set_io_func函数

void nxp_soc_gpio_set_io_func(unsigned int io, unsigned int func);

参数:

    io :GPIO的端口号

    func :使用宏来选择复用功能

 以上图为例,需要使用GPIOC17引脚作为GPIO,根据原理图GPIO功能在第二个,所以使用宏NX_GPIO_PADFUNC_1。

 选择输出输入模式使用gpio_direction_output,gpio_direction_input函数。

int gpio_direction_output(unsigned gpio, int value);

参数:

    gpio:GPIO端口号

    value:默认输出值

   

int gpio_direction_input(unsigned gpio):

//传入要设置的gpio端口号  

 设置输出值/获取输入的值使用gpio_set_value,gpio_get_value函数。

void gpio_set_value(unsigned gpio, int value);

参数:

    gpio:GPIO端口号

    value:输出的电平值

   

int gpio_get_value(unsigned gpio);

参数:

    gpio:GPIO端口号

//返回GPIO引脚的电平值

介绍完了,写一个LED的小测试程序吧。

led.h

#ifndef __LED_H
#define __LED_H//定义命令的最大序数
#define IOC_MAX_NR 4 
//定义设备类型(幻数)
#define IOC_MAGIC 'L'#define LED0 _IO(IOC_MAGIC,0)
#define LED1 _IO(IOC_MAGIC,1)
#define LED2 _IO(IOC_MAGIC,2)
#define LED3 _IO(IOC_MAGIC,3)#endif

led_drv.c 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <asm/gpio.h>
#include <mach/soc.h>
#include <mach/platform.h>
#include "led.h"#define LED_MINOR 0struct led_dest{int gpio;//gpio端口号char *name;//名称
};//定义led的硬件信息
struct led_dest led_info[] = {[0] = {.gpio = PAD_GPIO_E+13,.name = "LED0",},[1] = {.gpio = PAD_GPIO_C+17,.name = "LED1",},[2] = {.gpio = PAD_GPIO_C+8,.name = "LED2",},[3] = {.gpio = PAD_GPIO_C+7,.name = "LED3",}
};//设备号
dev_t dev;
//声明cdev
struct cdev led_cdev;
//设备类指针
struct class *led_class;
//设备指针
struct device *led_device;/*
inode是文件的节点结构,用来存储文件静态信息
文件创建时,内核中就会有一个inode结构
file结构记录的是文件打开的信息
文件被打开时内核就会创建一个file结构
*/
int led_open(struct inode *inode, struct file *filp)
{printk("enter led_open!\\n");return 0;
}long led_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{printk("enter led_ioctl!\\n");if(_IOC_TYPE(cmd) != IOC_MAGIC)   //如果命令中的幻数不是‘x’return -EINVAL;               //返回错误代码表示无效参数if(_IOC_NR(cmd) >= IOC_MAX_NR)    //如果命令中的序列数大于4return -EINVAL;               //返回错误代码表示无效参数gpio_set_value(led_info[_IOC_NR(cmd)].gpio, data);if(data)printk("LED%d:OFF!\\n",_IOC_NR(cmd));elseprintk("LED%d:ON!\\n",_IOC_NR(cmd));return 0;
}int led_release(struct inode *inode, struct file *filp)
{printk("enter led_release!\\n");return 0;
}//声明操作函数集合
struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.unlocked_ioctl = led_ioctl,//ioctl接口.release = led_release,//对应用户close接口
};//加载函数
int led_init(void)
{int ret,i;// 1.注册字符设备驱动ret = register_chrdev(0, "led_demo", &led_fops);if(ret<0){printk("register_chrdev failed!\\n");goto failure_register_chrdev;}//构建设备号dev = MKDEV(ret,LED_MINOR);printk("register_chrdev success!\\n");// 2.注册设备类/*成功会在/sys/class目录下出现led_class子目录*/led_class = class_create(THIS_MODULE, "led_class");if(IS_ERR(led_class)){printk("class_create failed!\\n");ret = PTR_ERR(led_class);goto failure_class_create;}// 3.创建设备文件led_device = device_create(led_class, NULL, dev,NULL, "led");if(IS_ERR(led_device)){printk("device_create failed!\\n");ret = PTR_ERR(led_device);goto failure_device_create;}// 4.申请gpio资源并初始化//ARRAY_SIZE(arr)求数组大小的宏,原型为sizeof(arr)/sizeof(arr[0])for(i=0;i<ARRAY_SIZE(led_info);i++){  //申请gpioret = gpio_request(led_info[i].gpio, led_info[i].name);//设置复用功能if(i==0)nxp_soc_gpio_set_io_func(led_info[i].gpio,NX_GPIO_PADFUNC_0);elsenxp_soc_gpio_set_io_func(led_info[i].gpio,NX_GPIO_PADFUNC_1);//设置输出模式,默认高电平---灭gpio_direction_output(led_info[i].gpio, 1);}return 0;failure_device_create:class_destroy(led_class);// 3
failure_class_create:unregister_chrdev(MAJOR(dev), "led_demo");// 2
failure_register_chrdev:return ret;
}//卸载函数
void led_exit(void)
{int i;//释放GPIOfor(i=0;i<ARRAY_SIZE(led_info);i++){//LED熄灭gpio_set_value(led_info[i].gpio,1);gpio_free(led_info[i].gpio);}//销毁设备文件device_destroy(led_class, dev);//注销设备类class_destroy(led_class);//注销字符设备驱动unregister_chrdev(MAJOR(dev), "led_demo");
}//声明为模块的入口和出口
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("led driver!");//描述信息

如果对 led_ioctl函数中_IOC_TYPE_IOC_NR两个宏不了解可以参考上一篇文章ioctl命令的统一格式,

led_test.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "led.h"int main()
{char ch = 0;int fd = open("/dev/led",O_RDWR);if(fd==-1){perror("open");exit(-1);}printf("open successed!fd = %d\\n",fd);while(1){ch = getchar();if(ch=='q')break;switch(ch){case '1': ioctl(fd,LED0 ,0);//1灯亮break;case '2': ioctl(fd,LED0,1);//1灯灭break;case '3': ioctl(fd,LED1,0);//2灯亮break;case '4': ioctl(fd,LED1,1);//2灯灭break;case '5': ioctl(fd,LED2,0);//3灯亮break;case '6': ioctl(fd,LED2,1);//3灯灭break;case '7': ioctl(fd,LED3,0);//4灯亮break;case '8': ioctl(fd,LED3,1);//4灯灭break;default:printf("error input!\\n");break;}while(ch=getchar()!='\\n' && ch!=EOF)sleep(1);}close(fd);return 0;
}

 这样我们就可以通过应用程序输入字符1到8来控制LED灯状态。 

IO内存映射

我们使用函数去控制外设,是通过读写设备上的寄存器来进行的,每个寄存器都有物理地址在芯片手册中可以查到,但不管是在用户空间还是在内核空间,一律不能去直接访问寄存器的物理地址。内核中能够直接访问的地址是内核虚拟地址,所以需要MMU(内存管理单元)将寄存器的物理地址映射到内核虚拟地址空间再进行访问,之后驱动程序访问内核虚拟地址就是在间接访问寄存器的物理地址。

先引入两个需要使用到的函数头文件。

#include <linux/io.h>

#include <mach/platform.h>

使用ioremap建立映射。

#define ioremap(cookie,size)           __ioremap(cookie,size,0)

__ioremap函数原型为(arm/mm/ioremap.c):

void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);

参数:

phys_addr:需要映射的起始IO地址

size:要映射的空间的大小

flags:要映射的IO空间和权限有关的标志

该函数返回映射后的内核虚拟地址(3G-4G),接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源

使用iounmap函数解除映射。

 void iounmap(void * addr);

参数:

addr:虚拟起始地址

 访问io内存的读写函数

读I/O内存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);

写I/O内存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);

参数:

addr:虚拟地址

 上面使用GPIO函数来控制LED灯,现在使用io内存映射,用虚拟地址来控制一个LED(GPIOE13)灯。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/io.h>
#include <mach/platform.h>
#include "led.h"#define LED_MINOR 0//声明IO内存映射地址
void __iomem *gpioe_base = NULL;//设备号
dev_t dev;
//声明cdev
struct cdev led_cdev;
//设备类指针
struct class *led_class;
//设备指针
struct device *led_device;/*
inode是文件的节点结构,用来存储文件静态信息
文件创建时,内核中就会有一个inode结构
file结构记录的是文件打开的信息
文件被打开时内核就会创建一个file结构
*/
int led_open(struct inode *inode, struct file *filp)
{printk("enter led_open!\\n");return 0;
}long led_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{printk("enter led_ioctl!\\n");if(_IOC_TYPE(cmd) != IOC_MAGIC)   //如果命令中的幻数不是‘x’return - EINVAL;              //就返回错误代码表示无效参数if(_IOC_NR(cmd) >= IOC_MAX_NR)    //如果命令中的序列数大于4return - EINVAL;              //就返回错误代码表示无效参数if(data){iowrite32(ioread32(gpioe_base)|(0x1<<13),gpioe_base);printk("LED%d:OFF!\\n",_IOC_NR(cmd));}else{iowrite32(ioread32(gpioe_base)&~(0x1<<13),gpioe_base);printk("LED%d:ON!\\n",_IOC_NR(cmd));}return 0;
}int led_release(struct inode *inode, struct file *filp)
{printk("enter led_release!\\n");return 0;
}//声明操作函数集合
struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.unlocked_ioctl = led_ioctl,//ioctl接口.release = led_release,//对应用户close接口
};//加载函数
int led_init(void)
{int ret;// 1.注册字符设备驱动ret = register_chrdev(0, "led_demo", &led_fops);if(ret<0){printk("register_chrdev failed!\\n");goto failure_register_chrdev;}//构建设备号dev = MKDEV(ret,LED_MINOR);printk("register_chrdev success!\\n");// 2.注册设备类/*成功会在/sys/class目录下出现led_class子目录*/led_class = class_create(THIS_MODULE, "led_class");if(IS_ERR(led_class)){printk("class_create failed!\\n");ret = PTR_ERR(led_class);goto failure_class_create;}// 3.创建设备文件led_device = device_create(led_class, NULL, dev,NULL, "led");if(IS_ERR(led_device)){printk("device_create failed!\\n");ret = PTR_ERR(led_device);goto failure_device_create;}// 4.IO内存映射gpioe_base = ioremap(PHY_BASEADDR_GPIOE, SZ_64);if(IS_ERR_OR_NULL(gpioe_base)){//失败printk("ioremap failed!\\n");ret = -ENOMEM;goto failure_ioremap;}//初始化//设置复用功能 alt0  26 27位清0   addr:base+0x20iowrite32(ioread32(gpioe_base+0x20)&~(0x3<<26),gpioe_base+0x20);//设置输出模式  outenb 13位 置1   addr:base+0x04iowrite32(ioread32(gpioe_base+0x04)|(0x1<<13),gpioe_base+0x04);//设置默认值为高电平 out 13位 置1   addr:baseiowrite32(ioread32(gpioe_base)|(0x1<<13),gpioe_base);return 0;failure_ioremap:device_destroy(led_class, dev);
failure_device_create:class_destroy(led_class);
failure_class_create:unregister_chrdev(MAJOR(dev), "led_demo");
failure_register_chrdev:return ret;
}//卸载函数
void led_exit(void)
{//解除IO映射iounmap(gpioe_base);//销毁设备文件device_destroy(led_class, dev);//注销设备类class_destroy(led_class);//注销字符设备驱动unregister_chrdev(MAJOR(dev), "led_demo");
}//声明为模块的入口和出口
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("led driver!");//描述信息

gpioe_base = ioremap(PHY_BASEADDR_GPIOE, SZ_64);

PHY_BASEADDR_GPIOE这是内核定义的GPIOE的物理地址,SZ_64为64个字节

iowrite32(ioread32(gpioe_base+0x20)&~(0x3<<26),gpioe_base+0x20);

通过手册得知设置复用功能的寄存器在基地址上偏移0x20个地址。

先使用ioread32(gpioe_base+0x20)读取整个寄存器32位的值,然后ioread32(gpioe_base+0x20)&~(0x3<<26)把26和27位位置零,设置为GPIO功能,最后再写回去。初始化另外两个也是一样的意思,只是操作的寄存器不一样。

通过GPIO操作函数和io内存映射比较发现,内核gpio操作函数是通过封装IO内部映射来实现的,使用比较方便,但是只能用于GPIO操作。IO内存映射可以用于任意寄存器操作的场合

混杂设备驱动

在Linux驱动中,会把一些无法归类的设备定义为混杂设备——misc,它们是拥有着共同的特性的简单字符设备,它们的特点是共享统一的主设备号10,但每个设备可以选择一个单独的次设备号。

使用混杂设备需要头文件。

#include <linux/miscdevice.h>

混杂设备驱动相比于传统的字符设备来说,初始化很简单。字符设备需要先初始化cdev结构体,混杂设备也需要初始化miscdevice 结构体。

struct miscdevice  {

int minor;

const char *name;//设备文件名

const struct file_operations *fops;//操作函数集合

struct list_head list;

struct device *parent;

struct device *this_device;

const char *nodename;

umode_t mode;

};

//minor是次设备号,想要系统自动生成可以配置为MISC_DYNAMIC_MINOR

初始化完了 miscdevice 结构体就向内核注册这个混杂设备。

int misc_register(struct miscdevice * misc);

注销这个混杂设备。

int misc_deregister(struct miscdevice *misc);

这样就完成了一个混杂设备的使用,其实混杂设备的注册函数只创建了设备文件,其他的步骤都是在内核初始化阶段完成,在misc_init函数中完成。

那就看看使用混杂设备驱动怎么完成一个LED(GPIOE13)灯的控制吧。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/io.h>
#include <mach/platform.h>
#include <linux/miscdevice.h>
#include "led.h"//声明IO内存映射地址
void __iomem *gpioe_base = NULL;/*
inode是文件的节点结构,用来存储文件静态信息
文件创建时,内核中就会有一个inode结构
file结构记录的是文件打开的信息
文件被打开时内核就会创建一个file结构
*/
int led_open(struct inode *inode, struct file *filp)
{printk("enter led_open!\\n");return 0;
}long led_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{printk("enter led_ioctl!\\n");if(_IOC_TYPE(cmd) != IOC_MAGIC)   //如果命令中的幻数不是‘x’return - EINVAL;              //就返回错误代码表示无效参数if(_IOC_NR(cmd) >= IOC_MAX_NR)    //如果命令中的序列数大于4return - EINVAL;              //就返回错误代码表示无效参数if(data){iowrite32(ioread32(gpioe_base)|(0x1<<13),gpioe_base);printk("LED%d:OFF!\\n",_IOC_NR(cmd));}else{iowrite32(ioread32(gpioe_base)&~(0x1<<13),gpioe_base);printk("LED%d:ON!\\n",_IOC_NR(cmd));}return 0;
}int led_release(struct inode *inode, struct file *filp)
{printk("enter led_release!\\n");return 0;
}//声明操作函数集合
struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.unlocked_ioctl = led_ioctl,//ioctl接口.release = led_release,//对应用户close接口
};//分配初始化miscdevice
struct miscdevice led_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "led",//设备文件名.fops = &led_fops,//操作函数集合
};//加载函数
int led_init(void)
{int ret;//注册miscdeviceret = misc_register(&led_dev);if(ret<0){printk("misc_register failed!\\n");goto failure_misc_register;}//IO内存映射gpioe_base = ioremap(PHY_BASEADDR_GPIOE, SZ_64);if(IS_ERR_OR_NULL(gpioe_base)){//失败printk("ioremap failed!\\n");ret = -ENOMEM;goto failure_ioremap;}//初始化//设置复用功能 alt0  26 27位清0   addr:base+0x20iowrite32(ioread32(gpioe_base+0x20)&~(0x3<<26),gpioe_base+0x20);//设置输出模式  outenb 13位 置1   addr:base+0x04iowrite32(ioread32(gpioe_base+0x04)|(0x1<<13),gpioe_base+0x04);//设置默认值为高电平 out 13位 置1   addr:baseiowrite32(ioread32(gpioe_base)|(0x1<<13),gpioe_base);return 0;failure_ioremap:misc_deregister(&led_dev);
failure_misc_register:return ret;
}//卸载函数
void led_exit(void)
{//解除IO映射iounmap(gpioe_base);//注销miscdevicemisc_deregister(&led_dev);
}//声明为模块的入口和出口
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("3.0");//版本
MODULE_DESCRIPTION("led driver!");//描述信息

可以发现通过混杂设备驱动的框架来编写LED驱动,加载函数中轻便了不少,有许多的步骤都是内核完成。

 好了,以上就介绍完了字符设备通过GPIO函数或者IO内存映射来实现驱动,以及混杂设备怎么实现驱动的过程。有什么问题和建议欢迎在评论区中提出来哟。