基于exynos4412 led驱动编程

时间:2021-03-28 17:54:20

本文基于华清4412开发板,讲解如何从零开始编写led驱动程序和测试程序。首先介绍一下该4412开发板的led硬件原理图。

基于exynos4412 led驱动编程

从原理图上我们可以看出,让led点亮的条件是往对应端口送高电平,熄灭的条件是送低电平。

基于exynos4412 led驱动编程

从上面这幅图中可以看到对应引脚的寄存器配置,这里我们选择对LED2进行闪烁实验。我们需要把GPX2CON【7】配置寄存器设置为输出模式,也就是设置为0x1。我们还需要通过设置数据寄存器来控制LED的亮和灭,从下面这幅图可以看出我们往GPX2DAT【7】送1就能点亮LED2,送0就能使LED2熄灭。我们可以从这两幅图上得到对应寄存器的地址。基于exynos4412 led驱动编程

下面我们开始写代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>

#define GPX2CON 0x11000c40
//封装设备信息,面向对象编程思想
struct led_desc{
	struct class *class;
	struct device *device;
	unsigned long *reg_virt_addr;//声明GPX2CON的虚拟地址
};
//定义设备
struct led_desc *led_dev; 

int led_open (struct inode *inode, struct file *filp)
{
	printk("---------%s----------\n",__FUNCTION__);

	return 0;
}

ssize_t led_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	printk("---------%s----------\n",__FUNCTION__);

	return 0;
}

ssize_t led_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int value;
	int res;
	
	printk("---------%s----------\n",__FUNCTION__);
	
	res = copy_from_user(&value,buf, count);
	if(res > 0){
		printk("copy to user error\n");
		return -EFAULT;
	}
	printk("value from user is %d\n",value);
	//如果用户空间传过来的数据非0,则点亮LED2
	if(value){
		writel(readl(led_dev->reg_virt_addr+1) | (0x1<<7), led_dev->reg_virt_addr+1);
	}else{
		writel(readl(led_dev->reg_virt_addr+1) & ~(0x1<<7), led_dev->reg_virt_addr+1);
	}
	
	return 0;
}

int led_close (struct inode *inode, struct file *filp)
{
	printk("---------%s----------\n",__FUNCTION__);

	return 0;
}

const struct file_operations myfops = {
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = led_close,
};

int ret;
static int __init led_drv_init(void)
{
	int err;
	//u32 val;
	
	printk("---------%s----------\n",__FUNCTION__);

	//给设备对象申请空间
	led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
	if(led_dev == NULL){
		printk(KERN_ERR "kmalloc error\n");
		return -ENOMEM;
	}
	//动态申请设备号
	ret = register_chrdev(ret, "led_drv_test", &myfops);
	if(ret < 0){
		printk(KERN_ERR "register chr_dev error\n");
		err = -ENODEV;
		goto err_0;
	}
	//申请设备节点
	led_dev->class = class_create(THIS_MODULE, "led_drv_class");
	if(IS_ERR(led_dev->class)){
		printk(KERN_ERR "class create error\n");
		err = PTR_ERR(led_dev->class);
		goto err_1;
	}
	
	led_dev->device = device_create(led_dev->class,NULL, MKDEV(ret,0), NULL, "led%d",2);
	if(IS_ERR(led_dev->device)){
		printk(KERN_ERR "device create error\n");
		err = PTR_ERR(led_dev->device);
		goto err_2;
	}
	
	//地址映射
	led_dev->reg_virt_addr = ioremap(GPX2CON, 8);
	if(led_dev->reg_virt_addr == NULL){
		printk(KERN_ERR "ioremap error\n");
		err = -ENOMEM;
		goto err_3;
	}

	//配置GPXC2CON寄存器为输出模式
	*led_dev->reg_virt_addr &= ~(0xf<<28);
	*led_dev->reg_virt_addr |= (0x1<<28);

	/*
	val = readl(led_dev->reg_virt_addr);
	val &= ~(0xf<<28);]
	val |= (0x1<<28);
	writel(val,led_dev->reg_virt_addr);
	*/
	
	return 0;
//错误处理	
err_3:
	device_destroy(led_dev->class, MKDEV(ret, 0));
err_2:
	class_destroy(led_dev->class);
err_1:
	unregister_chrdev(ret, "led_drv_test");
err_0:
	kfree(led_dev);
	return err;
}

static void __exit led_drv_exit(void)
{
	printk("---------%s----------\n",__FUNCTION__);
	iounmap(led_dev->reg_virt_addr);	
	device_destroy(led_dev->class, MKDEV(ret, 0));
	class_destroy(led_dev->class);
	unregister_chrdev(ret, "led_drv_test");
	kfree(led_dev);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

MODULE_LICENSE("GPL");

以上就是led的驱动编写框架。分为这几个部分:

1、封装设备信息。这里把led的有关信息封装成一个结构体,然后用此结构体定义一个设备(led)。

2、入口函数里先给上一步定义的设备(led)申请空间,然后是动态申请设备号,当然也可以静态申请设备号,可以自定义设备号,只要不和已有的设备号重复就行。接下去是申请设备节点,也可以采用手动申请的方式,这里不多作介绍。最后是初始化映射地址和配置寄存器。

3、初始化完毕,我们就可以在空户空间和内核空间就是数据传递,这里我们采用从用户空间传递过来的数据来控制led2的亮和灭。

大致的思路就是这样。整个框架搭好了就好办了。下面我们编写测试程序,先上代码。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc,char **argv)
{
	int fd;
	int value = 0;
	
	fd = open("/dev/led2",O_RDWR);
	if(fd < 0){
		perror("open");
		exit(1);
	}

	while(1){
		value = 0;
		write(fd,&value,4);
		usleep(50000);

		value = 1;
		write(fd,&value,4);
		usleep(50000);
	}
	
	close(fd);
	
	return 0;
}

好了,下面编写makefile

#当前目录
CUR_DIR = `pwd`
#根文件系统所在目录
ROOTFS_DIR = /source/rootfs
#内核所在目录
KERNEL_DIR = /home/linux/linux-3.14.1
#应用程序名称
APP_NAME = led_test
#要编译的模块名称
MODULE_NAME = led_drv
#交叉编译工具链
CROSS_COMPILE = /home/linux/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE),)

all:
	#编译模块
	make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
	#编译上层APP应用程序
	$(CC) $(APP_NAME).c -o $(APP_NAME)
clean:
	#清除模块
	make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
	#清除app
	rm -rf $(APP_NAME)
install:
	#更新驱动模块和测试程序
	cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module

else
	obj-m += $(MODULE_NAME).o

endif

整个工程编译完毕。我们编译一下工程。基于exynos4412 led驱动编程

启动开发板,查看设备号。

基于exynos4412 led驱动编程

加载led模块再看设备号。

基于exynos4412 led驱动编程

设备节点也有了/dev/led2

基于exynos4412 led驱动编程

程序开始执行,输出对应信息,开发板led2每隔50ms闪烁一次。

基于exynos4412 led驱动编程

这里的测试程序只是先打开设备节点文件,所以我们可以看到先执行驱动程序的led_open函数,然后我们往内核传数据,相应地就执行驱动程序的led_write函数,由于一直在while循环中,所以并没有执行驱动程序里的led_read和led_close函数。

开发板上led2闪烁现场就不发了,图片看不出效果。好了,先写到这里。。。