[学习分享]嵌入式linux字符驱动详解(三)

前面第一篇文章开头中提到过,一个led灯的驱动程序无非就是配置一下GPIO寄存器,让它具有输出功能,同时提供数据寄存器给用户来操作实现对led灯的控制。前面我们做的所有工作都是在搭建一个Linux驱动框架,现在框架搭建好了,我们只需要在这个框架的基础上添加少量的代码即可完成一个真正的led驱动。

我用的处理器是飞思卡尔(后来被恩智浦收购)的i.mx6ull,板上载的led灯使用的是GPIO1_IO04。通过查找芯片手册,找到关于GPIO1_IO04的描述。配置这个引脚的步骤大致分为:

一、使能时钟。

二、配置复用寄存器,让它工作在GPIO模式下。

三、配置GPIO属性,如上下拉,速率等。

四、配置GPIO方向,即输入或者输出。

五、操作GPIO,即操作对应的DR寄存器。

所以,需要使用到的寄存器共由5个。在芯片手册上找到这5个寄存器的描述,确定如何操作。

由于使用的处理器不同,对应的操作方式也是不一样的,这里就不讲述如何通过芯片手册确定这些寄存器的操作方式了。

上一篇中,我们定义了几个操作函数open, release, write。但里面的内容还没填写,所以还没办法使用这个驱动程序操作led灯。那么,关于GPIO操作部分,我们应该放在哪些函数里面呢。

前面四部可以把它概括为初始化操作,第五步则是设备操作。

初始化操作可以放在模块的入口函数中,对应地,在出口函数处可以添加关闭打开的时钟等操作。设备操作则可以放到write函数中。

初始化操作也可以放到open函数中,应用在打开对应的设备节点的时候就执行初始化操作,然后对应第在release函数中执行关闭对应时钟操作,但这样做有一个缺点,应用程序每打开一个设备节点文件,就会执行一次相关的GPIO操作。

简单提一下,在Linux中通过寄存器地址来配置寄存器的方法。Linux会把设备的物理地址映射为一块更大的虚拟地址,在Linux系统中使用虚拟地址才能正确地操作对应的寄存器,Linux提供的API函数是:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)

参数有两个,一个是需要映射的物理地址,另一个是大小。

另外,Linux也不推荐直接通过地址读写数据,提供了几个函数来帮我们完成读写的功能,分别是readl readw readb, 分别对应读取4个字节数据,读取2个字节数据,读取一个字节数据。

上一篇中给出的write函数原型中,有个参数是buf,表示的是应用层传过来的数据,但前面有个__user 符号修饰,在内核中,(编写的驱动程序属于内核,又称内核模块)不可以直接使用用户数据,Linux提供了两个函数:copy_to_user和copy_from_user来实现访问用户数据。

在上一篇的代码中,有许多函数是有返回值的,但我们暂时还没处理,特别是一些向内核申请资源的函数,假如失败了,则一定要释放之前已经申请得到的资源。参考别人写好的一些驱动程序,可以使用goto语句处理失败的情况。核心思想是,错误处理的顺序和申请操作的顺序相反,即先申请的后释放。详见代码。

将GPIO操作部分加入到上一篇的代码文件中,如下:

#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h>
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/device.h> 
#include <asm/mach/map.h> 
#include <asm/uaccess.h> 
#include <asm/io.h>

#define MODULE_NAME "led_driver"
#define LED_COUNT 1

#define CCM_CCGR1		(0x020C406C)
#define SW_MUX_GPIO1_IO04 	(0x020E006C)
#define SW_PAD_GPIO1_IO04	(0x020E02F8)
#define GPIO1_DIR		(0x0209C004)
#define GPIO1_DR		(0x0209C000)

static void __iomem *CCGR1;
static void __iomem *MUX;
static void __iomem *PAD;
static void __iomem *DIR;
static void __iomem *DR;

struct led_dev_t{
	int major;
	int minor;
	dev_t devid;
	struct cdev led_cdev;
	
	struct class *class;
	struct device *device;
};

struct led_dev_t led_dev;


void led_switch(u8 sta)
{
	u32 val ;
	val = readl(DR);
	if(sta){
		val &= ~(1<<4);
		printk("led on\r\n");
	}
	else{
		val |= (1<<4);
		printk("led off\r\n");
	}
	writel(val,DR);
}


static int led_open(struct inode *inode, struct file *filp)
{
	
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	
	return 0;
}

static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt)
{
	int ret;
	u8 databuf[1];
	ret = copy_from_user(databuf,buf,cnt);
	
	if(ret<0) 
		return -1;
	else
		printk("recv data:%d\r\n",databuf[0]);
	led_switch(databuf[0]);
	return 0;
}


static struct file_operations led={
	.owner = THIS_MODULE,
	.open  = led_open,
	.release = led_release,
	.write	= led_write,
};

static int __init led_init(void)
{
	u32 val;	
	int ret_val;
	CCGR1 = ioremap(CCM_CCGR1,4);
	MUX = ioremap(SW_MUX_GPIO1_IO04,4);
	PAD = ioremap(SW_PAD_GPIO1_IO04,4);
	DIR = ioremap(GPIO1_DIR,4);
	DR = ioremap(GPIO1_DR,4);

	val = readl(CCGR1);
	val &= ~(3<<26);
	val |= (3<<26);
	writel(val,CCGR1);

	writel(0x05,MUX);
	writel(0x10B0,PAD);
	
	val = readl(DIR);
	val |= 1<< 4;
	writel(val,DIR);

	val = readl(DR);
	val &=~(1<<4);
	writel(val,DR);	

	printk("led_driver_init\r\n");
	if(led_dev.major){
		led_dev.devid = MKDEV(led_dev.major,led_dev.minor);
		ret_val = register_chrdev_region(led_dev.devid,LED_COUNT,MODULE_NAME);
		if(ret_val < 0) goto fail;
	}else{
		ret_val = alloc_chrdev_region(&led_dev.devid,0,LED_COUNT,MODULE_NAME);
		if(ret_val < 0) goto fail;
		led_dev.major = MAJOR(led_dev.devid);
		led_dev.minor = MINOR(led_dev.devid);
	}
	printk("led dev: major:%d,minor:%d\r\n",led_dev.major,led_dev.minor);

	led_dev.led_cdev.owner = THIS_MODULE;
	cdev_init(&led_dev.led_cdev, &led);

	
	ret_val = cdev_add(&led_dev.led_cdev, led_dev.devid, LED_COUNT);
	if(ret_val < 0) goto cdev_fail;

	led_dev.class = class_create(THIS_MODULE, MODULE_NAME);
	if(IS_ERR(led_dev.class)){
		ret_val =PTR_ERR(led_dev.class);
		goto class_fail;	
	}	
	
	led_dev.device = device_create(led_dev.class,NULL,led_dev.devid,NULL,MODULE_NAME);
	if(IS_ERR(led_dev.device)){
		ret_val = PTR_ERR(led_dev.device);
		goto device_fail;	
	}
	return 0;

device_fail:
	class_destroy(led_dev.class);
class_fail:
	cdev_del(&led_dev.led_cdev);
cdev_fail:
	unregister_chrdev_region(led_dev.devid, LED_COUNT);
fail:

    iounmap(CCGR1);
	iounmap(MUX);
	iounmap(PAD);
	iounmap(DIR);
	iounmap(DR);

	return ret_val;
}


static void __exit led_exit(void)
{

	iounmap(CCGR1);
	iounmap(MUX);
	iounmap(PAD);
	iounmap(DIR);
	iounmap(DR);

	printk("led_driver_deinit\r\n");
	cdev_del(&led_dev.led_cdev);
	unregister_chrdev_region(led_dev.devid, LED_COUNT);

	device_destroy(led_dev.class,led_dev.devid);
	class_destroy(led_dev.class);
}


module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("weymin");

到这里一个完成的led驱动程序完成了,下面编写应用层层序,即用户程序测试一下刚刚编写的驱动。

简单通过几个系统调用来操作设备节点即可。touch test.c新建一个测试文件,填入代码如下:

#include "stdio.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main(int argc,char **argv)
{
	int fd;
	fd = open(argv[1],O_RDWR);
	if(fd<0){
		printf("cannot open file %s\r\n",argv[1]);
		return -1;
	}
	printf("input :%d",atoi(argv[2]));
	unsigned char buf[1];
	buf[0] = atoi(argv[2]);
	printf("input :%d",buf[0]);
	write(fd,buf,sizeof(buf));
	close(fd);
	return 0;
}

编译:arm-linux-gnueabihf-gcc test.c -o test

拷贝到开发板上,并加载好驱动,执行./test /dev/led_driver 0让led灯灭,./test /dev/led_driver 1让led灯亮。

(完)2020.05.03

原创文章 57 获赞 71 访问量 11万+

猜你喜欢

转载自blog.csdn.net/u013053268/article/details/105909036
今日推荐