Linux驱动之LED驱动

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

目录

GPIO函数

IO内存映射

混杂设备驱动


GPIO函数

首先加入需要的头文件。

#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 :使用宏来选择复用功能

Linux驱动之LED驱动_第1张图片

 以上图为例,需要使用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灯状态。 Linux驱动之LED驱动_第2张图片

IO内存映射

我们使用函数去控制外设,是通过读写设备上的寄存器来进行的,每个寄存器都有物理地址在芯片手册中可以查到,但不管是在用户空间还是在内核空间,一律不能去直接访问寄存器的物理地址。内核中能够直接访问的地址是内核虚拟地址,所以需要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);

Linux驱动之LED驱动_第3张图片

通过手册得知设置复用功能的寄存器在基地址上偏移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内存映射来实现驱动,以及混杂设备怎么实现驱动的过程。有什么问题和建议欢迎在评论区中提出来哟。

 

 

你可能感兴趣的:(Linux驱动,Linux,驱动开发,linux,arm开发)