Linux下驱动开发之二(LED驱动)-------Tiny6410

裸机下控制LED灯非常方便,只需要配置好GPIO引脚功能,然后向GPIO引脚映射的内存地址处写入数据即可,但linux下驱动就不那么简单了,需要结合字符设备驱动的架构,然后将功能实现添加进去,笔者参考linux设备驱动程序(第三版)中介绍的新的接口来实现驱动。友善之臂官网提供的源码是基于miscdevice的驱动,而且接口似乎有点老,比如在linux设备驱动程序(第三版)中强调需要使用新的内存I/O接口来访问映射内存,建议使用ioread32,iowrite32等,但是它依然使用writel,readl等函数。另外笔者使用了两种方法:一种是修改友善之臂的驱动;另一种是使用标准的字符设备驱动

第一种方法实现:

混杂设备驱动中需要实现的是填充file_operations结构,然后设置miscdevices的各个字段值,然后注册到内核即可。详细流程如下:

1.编写ioctl接口函数

2.填充file_operations结构,主要是将ioctl函数注册进去

3.填充miscdevice结构,主要是次设备号、fops和设备名称。

4.编写要提供给module_init宏使用的初始化代码,设置LED端口为输出,注册到内核

5.编写要提供给module_exit宏使用的注销代码,主要是注销掉初始化中注册到内核的miscdevice设备

6.本源码修改了arg参数的值,0表示LED1~LED4全选中,1~4分别控制对应的LED灯

详细代码如下:

/*
 *	Tiny6410开发板-led驱动(linux)使用miscdevice实现
 *	使用方法:
 *	1.编译内核2.6.38
 *	2.编写Makefile文件
 *	3.编译运行
 *	4.运行app/led测试(有关具体的测试细节清参考led_app.c)
 *	本程序中读写GPIO使用了新的访问I/O内存的函数ioread32,iowrite32
 *	Author:jefby
 *	Emai:jef199006@gmail.com
 */ 
#include <linux/module.h>//MODULE_AUTHOR,MODULE_LICENSE
#include <linux/init.h>//module_init,module_exit
#include <linux/fs.h>//file_operations
#include <linux/miscdevice.h>//misdevice 
#include <asm/io.h>//ioread32,iowrite32
#include <mach/gpio-bank-k.h>//定义了GPKCON
#include <mach/regs-gpio.h>//定义了gpio-bank-k中使用的S3C64XX_GPK_BASE
#include <mach/map.h>//定义了S3C64XX_VA_GPIO

/*设备名称*/
#define DEVICE_NAME "leds"

/*	ioctl接口函数,cmd=0,表示关闭参数arg指定的LED灯;arg的值不能大于4;
 *	其中0表示所有的LED,LED1~LED4
 *	1~4分别表示LED1~LED4
 *	返回0或者错误-EINVAL
 * */
static int s3c6410_leds_ioctl(
				 struct file*filp,
				 unsigned int cmd,
				 unsigned long arg
			      )
{
	switch(cmd){
		unsigned tmp;
	case 0://close
	case 1://open
		if(arg > 4){//参数错误
			return -EINVAL;		
		}
		else if(arg == 0){//全亮或者全灭
			tmp = ioread32(S3C64XX_GPKDAT);//读出LED1~LED4所在寄存器的值
			tmp &= ~(0xF<<4);//打开LED1~LED4
			if(cmd == 0)//若是关闭,则关闭掉LED1~LED4
				tmp |= (0xF<<4);//if close,then write 0xF to GPK4~GPK7
			iowrite32(tmp,S3C64XX_GPKDAT);
		}else{	//参数为1~4范围内
			tmp = ioread32(S3C64XX_GPKDAT);
			tmp &= ~(1<<(4+arg-1));//清除arg所指示的那一位值
			tmp |= ((!cmd)<<(4+arg-1));//写入新值
			iowrite32(tmp,S3C64XX_GPKDAT);
		}
		return 0;
	default:
		return -EINVAL;
	}//switch(cmd)
	return 0;//应该不会执行
}

static struct file_operations dev_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = s3c6410_leds_ioctl,//定义的ioctl函数
};
static struct miscdevice misc  = {
	.minor = MISC_DYNAMIC_MINOR,//动态分配次设备号
	.name = DEVICE_NAME,//设备名称
	.fops = &dev_fops,
};

/*模块初始化*/
static int __init dev_init(void)
{
	int ret;
/*
 *	在设备驱动程序注册的时候初始化LED1~LED4所对应的GPIO管脚为输出
 *	并关闭LED1~LED4
 */ 
	unsigned tmp;
	tmp = ioread32(S3C64XX_GPKCON);
	tmp = (tmp & ~(0xFFFFU<<16)) | (0x1111U<<16);//先清除然后再设置其为输出
	iowrite32(tmp,S3C64XX_GPKCON);//写入GPKCON
	
	tmp = readl(S3C64XX_GPKDAT);
	tmp |= (0xF<<4);//关闭LED灯
	iowrite32(tmp,S3C64XX_GPKDAT);
//注册misc
	ret = misc_register(&misc);
	printk(DEVICE_NAME"\tinitialized.\n");

	return ret;
}

static void __exit dev_exit(void)
{
//卸载
	misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jefby");

第2种方法使用了标准的字符设备驱动的编写方法,具体流程如下:

1.编写在file_operations中使用的ioctl和open函数,其中open函数初始化GPIO接口,ioctl函数实现LED灯的亮灭

2.填充file_operations结构

3.编写module_init宏使用的初始化代码;在这段代码中主要完成

a. 申请主设备号,默认使用动态分配的方法

b. 动态分配cdev结构,cdev表示一个字符设备

c. 初始化cdev结构

d. 注册到内核

4.编写module_exit宏使用的注销代码,主要是注销cdev结构和动态申请的主设备号

部分代码如下:

static int __init leds_init(void)
{
	/*
	 *使用linux设备驱动中介绍的新方法来写,而不是用老的接口
	 *申请主设备号
	 *register_chrdev
	 *新方法:
	 *	0.获得一个或者多个设备编号(register_chrdev_region,或者alloc_chrdev_region)
	 *	1.分配cdev结构
	 *	2.初始化该cdev结构
	 *	3.注册到内核
	 *
	 */
	int result;

	printk("Tiny 6410 leds module init.\n");
	if(leds_major){
		dev = MKDEV(leds_major,0);
		result = register_chrdev_region(dev,1,"leds");
	}else{
		result = alloc_chrdev_region(&dev,0,1,"leds");
		leds_major = MAJOR(dev);//获得主设备号
		printk(KERN_ALERT "leds major = %d.\n",leds_major);
	}
	if(result < 0){
		printk(KERN_WARNING "leds:can't get major %d\n",leds_major);
		return result;
	}
	leds_cdev  = cdev_alloc();
	leds_cdev->ops = &leds_fops;
	//void cdev_init(struct cdev*cdev,struct file_operations *fops)
	cdev_init(leds_cdev,&leds_fops);
	//int cdev_add(struct cdev *dev,dev_t num,unsigned int count)
	cdev_add(leds_cdev,dev,1);
	printk("cdev add ok.\n");
	

	return 0;
}

int leds_ioctl(
		struct file * filp,
		unsigned int cmd,
		unsigned long arg
	     	)
{
	if(arg > 4)//确定参数必须为0,此时ON和OFF都对应的是LED1~LED4
		return -EINVAL;
	switch(cmd){
		unsigned long tmp;
		case 0:
		case 1:
			if(arg == 0){
				tmp = ioread32(S3C64XX_GPKDAT);
				tmp &= ~(0xF<<4);
				if(cmd == 0)
					tmp |= 0xF<<4;
				iowrite32(tmp,S3C64XX_GPKDAT);
					
			}else{
				tmp = ioread32(S3C64XX_GPKDAT);
				tmp &= ~(0x1<<(4+arg-1));//打开arg对应的LED灯
				if(cmd == 0)
					tmp |= 0x1<<(4+arg-1);
				iowrite32(tmp,S3C64XX_GPKDAT);
			}
			return 0;
		default:
			return -EINVAL;
	}
	return 0;
}

编写Makefile如下:

ifneq ($(KERNELRELEASE),)
	obj-m := leds.o
else
	KERNELDIR ?= /opt/FriendlyARM/mini6410/linux/linux-2.6.38
	PWD := $(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.o *.order *~ *symvers *.mod.c
endif

完成后,接着编写应用程序测试它:

/*
 *
 *	控制LED的应用程序,格式 ./led arg cmd
 *	arg=[0~4],cmd=[0.1]
 *	cmd=0表示关闭LED,cmd=1表示打开LED
 *	arg=0,全关或者全开LED灯
 *	arg=1~4表示打开或关闭指定的LED灯	
 *	Author:jefby
 *	Email:jef199006@gmail.com
 *
 **/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
	int on;
	int led_no;
	int fd;
	/* 检查led 控制的两个参数,如果没有参数输入则退出。*/
	if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||\
	on < 0 || on > 1 || led_no < 0 || led_no > 4) {
		fprintf(stderr, "Usage: leds led_no 0|1\n");
		exit(1);
	}
	/*打开/dev/leds 设备文件*/
	fd = open("/dev/leds0", 0);
	if (fd < 0) {
	fd = open("/dev/leds", 0);
	}
	if (fd < 0) {
	perror("open device leds");
	exit(1);
	}
	/*通过系统调用ioctl 和输入的参数控制led*/
	ioctl(fd, on, led_no);
	/*关闭设备句柄*/
	close(fd);
	return 0;
}

编译,然后使用NFS挂载到开发板上,加载模块然后运行应用程序即可,注意标准字符设备驱动程序还需要创建设备,如下图所示:


运行led程序可以看到LED灯已经可以成功驱动了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值