之前学习完了字符设备驱动的大体框架,现在我们就使用这个基本的框架来对硬件进行操作,例如通过指令控制led的状态,编写LED驱动。LED驱动有多种实现方式。
目录
GPIO函数
IO内存映射
混杂设备驱动
首先加入需要的头文件。
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led.h"
#define LED_MINOR 0
struct 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) //如果命令中的序列数大于4
return -EINVAL; //返回错误代码表示无效参数
gpio_set_value(led_info[_IOC_NR(cmd)].gpio, data);
if(data)
printk("LED%d:OFF!\n",_IOC_NR(cmd));
else
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,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资源并初始化
for(i=0;i
如果对 led_ioctl函数中_IOC_TYPE和_IOC_NR两个宏不了解可以参考上一篇文章ioctl命令的统一格式,
led_test.c
#include
#include
#include
#include
#include
#include
#include
#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灯状态。
我们使用函数去控制外设,是通过读写设备上的寄存器来进行的,每个寄存器都有物理地址在芯片手册中可以查到,但不管是在用户空间还是在内核空间,一律不能去直接访问寄存器的物理地址。内核中能够直接访问的地址是内核虚拟地址,所以需要MMU(内存管理单元)将寄存器的物理地址映射到内核虚拟地址空间再进行访问,之后驱动程序访问内核虚拟地址就是在间接访问寄存器的物理地址。
先引入两个需要使用到的函数头文件。
#include
#include
使用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
#include
#include
#include
#include
#include
#include
#include
#include
#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) //如果命令中的序列数大于4
return - 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+0x20
iowrite32(ioread32(gpioe_base+0x20)&~(0x3<<26),gpioe_base+0x20);
//设置输出模式 outenb 13位 置1 addr:base+0x04
iowrite32(ioread32(gpioe_base+0x04)|(0x1<<13),gpioe_base+0x04);
//设置默认值为高电平 out 13位 置1 addr:base
iowrite32(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
混杂设备驱动相比于传统的字符设备来说,初始化很简单。字符设备需要先初始化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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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) //如果命令中的序列数大于4
return - 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;
//注册miscdevice
ret = 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+0x20
iowrite32(ioread32(gpioe_base+0x20)&~(0x3<<26),gpioe_base+0x20);
//设置输出模式 outenb 13位 置1 addr:base+0x04
iowrite32(ioread32(gpioe_base+0x04)|(0x1<<13),gpioe_base+0x04);
//设置默认值为高电平 out 13位 置1 addr:base
iowrite32(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);
//注销miscdevice
misc_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内存映射来实现驱动,以及混杂设备怎么实现驱动的过程。有什么问题和建议欢迎在评论区中提出来哟。