Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动

         现在,我们来编写自己第一个字符设备驱动 —— 点亮LED。(不完善,后面再完善)

硬件平台:Exynos4412(FS4412)


编写驱动分下面几步:

a -- 查看原理图、数据手册,了解设备的操作方法;

b -- 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始;

c -- 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名,内核才能找到相应的驱动程序;

d -- 设计所要实现的操作,比如 open、close、read、write 等函数;

e -- 实现中断服务(中断不是每个设备驱动所必须的);

f -- 编译该驱动程序到内核中,或者用 insmod 命令加载;

g-- 测试驱动程序;


下面是一个点亮LED 的驱动:

第一步,当然是查看手册,查看原理图,找到相应寄存器;

Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动_第1张图片

查看手册,四个LED 所用寄存器为:

led2

GPX2CON    0x11000c40
GPX2DAT     0x11000c44

led3

GPX1CON    0x11000c20
GPX1DAT     0x11000c24

led4  3-4 3-5

GPF3CON   0x114001e0
GPF3DAT    0x114001e4


这里要注意:arm体系架构是io内存,必须要映射   ioremap( );  其作用是物理内存向虚拟内存的映射。 用到 writel   readl这两个函数,详细解释会在后面不上,先看一下简单用法:

以LED2为例,下面是地址映射及读写:

int *pgpx2con  ;
int *pgpx2dat;

pgpx2con = ioremap( GPX2CON, 4);
pgpx2dat = ioremap(GPX2DAT,4);
readl(pgpx2con);
writel(0x01, pgpx2dat );

下面是驱动程序,后面会更完善
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

static int major = 250;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;

#define  GPX2CON    0x11000c40
#define  GPX2DAT    0x11000c44
#define  GPX1CON    0x11000c20
#define  GPX1DAT    0x11000c24
#define  GPF3CON    0x114001e0
#define  GPF3DAT    0x114001e4

static int *pgpx2con  ;
static int *pgpx2dat;

static int *pgpx1con  ;
static int *pgpx1dat;

static int *pgpf3con  ;
static int *pgpf3dat;

void fs4412_led_off(int num);

void fs4412_led_on(int num)
{
	switch(num)
	{
		case 1:
			writel(readl(pgpx2dat) |(0x1<<7), pgpx2dat);
			break;
		case 2:
			writel(readl(pgpx1dat) |(0x1<<0), pgpx1dat);			
			break;			
		case 3:
			writel(readl(pgpf3dat) |(0x1<<4), pgpf3dat);	
			break;
		case 4:
			writel(readl(pgpf3dat) |(0x1<<5), pgpf3dat);			
			break;	
		default:
			fs4412_led_off(1);
			fs4412_led_off(2);
			fs4412_led_off(3);
			fs4412_led_off(4);
			break;
			
	}
}

void fs4412_led_off(int num)
{
	switch(num)
	{
		case 1:
			writel(readl(pgpx2dat) &(~(0x1<<7)), pgpx2dat);
			break;
		case 2:
			writel(readl(pgpx1dat)&(~(0x1<<0)), pgpx1dat);			
			break;			
		case 3:
			writel(readl(pgpf3dat) &(~(0x1<<4)), pgpf3dat);	
			break;
		case 4:
			writel(readl(pgpf3dat) &(~(0x1<<5)), pgpf3dat);			
			break;			
	}
}

static int led_open (struct inode *inode, struct file *filep)
{//open
	fs4412_led_off(1);
	fs4412_led_off(2);
	fs4412_led_off(3);
	fs4412_led_off(4);	
	return 0;
}

static int led_release(struct inode *inode, struct file *filep)
{//close
	fs4412_led_off(1);
	fs4412_led_off(2);
	fs4412_led_off(3);
	fs4412_led_off(4);	
	return 0;
}

static ssize_t led_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
	return 0;
}

static ssize_t led_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
{
	int led_num;

	if(len !=4)
	{
		return -EINVAL;
	}
	if(copy_from_user(&led_num,buf,len))
	{
		return -EFAULT;	
	}

	fs4412_led_on(led_num);
	printk("led_num =%d \n",led_num);

	return 0;
}

static struct file_operations hello_ops=
{
	.open     = led_open,
	.release = led_release,
	.read     = led_read,
	.write    = led_write,
};

static void fs4412_led_init(void)
{
	pgpx2con = ioremap(GPX2CON,4);
	pgpx2dat = ioremap(GPX2DAT,4);

	pgpx1con = ioremap(GPX1CON,4);
	pgpx1dat =ioremap(GPX1DAT,4);

	pgpf3con  = ioremap(GPF3CON,4);
	pgpf3dat =ioremap(GPF3DAT,4);

	writel((readl(pgpx2con)& ~(0xf<<28)) |(0x1<<28),pgpx2con) ;
	writel((readl(pgpx1con)& ~(0xf<<0)) |(0x1<<0),pgpx1con) ;	
	writel((readl(pgpf3con)& ~(0xff<<16)) |(0x11<<16),pgpf3con) ;	
}

static int led_init(void)
{
	int ret;	
	devno = MKDEV(major,minor);
	ret = register_chrdev(major,"led",&hello_ops);

	cls = class_create(THIS_MODULE, "myclass");
	if(IS_ERR(cls))
	{
		unregister_chrdev(major,"led");
		return -EBUSY;
	}
	test_device = device_create(cls,NULL,devno,NULL,"led");//mknod /dev/hello
	if(IS_ERR(test_device))
	{
		class_destroy(cls);
		unregister_chrdev(major,"led");
		return -EBUSY;
	}	
	fs4412_led_init();
	return 0;
}

void fs4412_led_unmap(void)
{
	iounmap(pgpx2con);
	iounmap(pgpx2dat );

	iounmap(pgpx1con);
	iounmap(pgpx1dat );

	iounmap(pgpf3con );
	iounmap(pgpf3dat );
}

static void led_exit(void)
{
	fs4412_led_unmap();
	device_destroy(cls,devno);
	class_destroy(cls);	
	unregister_chrdev(major,"led");
	printk("led_exit \n");
}

MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);

测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

main()
{
	int fd,i,lednum;

	fd = open("/dev/led",O_RDWR);
	if(fd<0)
	{
		perror("open fail \n");
		return ;
	}
	for(i=0;i<100;i++)
	{
		lednum=0;
		write(fd,&lednum,sizeof(int));
		lednum = i%4+1;
		write(fd,&lednum,sizeof(int));	
		sleep(1);
	}
	close(fd);
}

makefile:

ifneq  ($(KERNELRELEASE),)
obj-m:=hello.o
$(info "2nd")
else
#KDIR := /lib/modules/$(shell uname -r)/build
KDIR := /home/xiaoming/linux-3.14-fs4412
PWD:=$(shell pwd)
all:
	$(info "1st")
	make -C $(KDIR) M=$(PWD) modules
	arm-none-linux-gnueabi-gcc test.c
	sudo cp hello.ko a.out /rootfs/test/
clean:
	rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
endif

编译结束后,将a.out 和 hello.ko 拷贝到开发板中:

# insmod hello.ko

#mknod /dev/hello c 250 0

#./a.out

会看到跑马灯效果。

后面会对该驱动完善。



你可能感兴趣的:(linux,驱动开发,字符设备)