基于exynos4412的按键驱动编程

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

基于exynos4412的按键驱动编程_第1张图片

这里我们实现K3和K2的按键驱动。从底板的原理图中可以看到K3按下之后SIM_DET引脚被拉成低电平(0),K3抬起时引脚又被拉高,变成高电平(1),K2同理。再看核心板的原理图。

基于exynos4412的按键驱动编程_第2张图片

可以看到K3按键对应芯片4412的外部中断10,也就是XEINT10,K2同理。接下来我们看4412的芯片手册。

基于exynos4412的按键驱动编程_第3张图片

上图中26是外部中断所属的总线中断的序列号,这里EINT[10]属于SPI(shared peripheral interrupt)中断,对应26号中断,EINT[9]同理,对应25号中断。

既然按键对应的是CPU的中断线,那么我们就要获取按键对应的中断号。我们可以从linux内核的设备树中获取。首先,我们要自己在设备树中定义两个设备节点,如下图所示。

基于exynos4412的按键驱动编程_第4张图片

这个节点我们定义为key2_node和key3_node,它们有三个属性,compatibe是节点的名称,interrupt-parent说明该节点继承于某某父节点,interrupts定义具体哪个中断和中断触发方式。首先我们看一下gpx1节点。

基于exynos4412的按键驱动编程_第5张图片

可以看到gpx1继承于节点gic,gic就是中断控制器,另外,我们也可以看到interrupts属性,它定义的就是中断号,共有8个中断号,其中26那个就是EINT[10]所对应的中断号,所以key3_node的interrupt属性的2就来自这里(从0开始数第三个),key2_node同理,后面的2指的是触发方式是下降沿触发(看实际需求自定义触发方式)。我们可以从内核的说明文档下找到这个定义(Documentation/devicetree/bindings/arm/gic.txt)

基于exynos4412的按键驱动编程_第6张图片

好了,设备树定义好之后我们在内核中make dtbs一下,就可以看到在/proc/device-tree/下看到key_node这个文件夹了。

基于exynos4412的按键驱动编程_第7张图片

可以看到设备树中多了key_node2和key3_node这两个节点,相应地,会在proc/devicetree/下生成对应的文件,这样我们就可以通过访问节点文件来获取终端号了,我们先看看整个驱动代码。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define GPX1CON 0x11000c20
#define KEY2_ENTER 28
#define KEY3_ENTER 29

struct key_event{
	int code;
	int value;
};
struct dev_desc{
	void *reg_addr;
	int key_major;	
	unsigned int irqno1;
	unsigned int irqno2;
	struct class *cls;
	struct device *dev;
	struct key_event event;
};

struct dev_desc *key_dev;

//中断处理函数
irqreturn_t key2_handler(int irqno, void *dev)
{
	unsigned int value;
	printk("-----------------%s--------------------\n",__FUNCTION__);
	//读取key2的状态
	value = readl(key_dev->reg_addr+4) & (0x1<<1);
	if(value){
		key_dev->event.code = KEY2_ENTER;
		key_dev->event.value = 1;
		printk("key2 up\n");
	}else{
		key_dev->event.code = KEY2_ENTER;
		key_dev->event.value = 0;
		printk("key2 down\n");
	}
	
	return IRQ_HANDLED;
}

irqreturn_t key3_handler(int irqno, void *dev)
{

	unsigned int value;
	printk("-----------------%s--------------------\n",__FUNCTION__);
	//读取key3的状态
	value = readl(key_dev->reg_addr+4) & (0x1<<2);
	if(value){
		key_dev->event.code = KEY3_ENTER;
		key_dev->event.value = 1;
		printk("key3 up\n");
	}else{
		key_dev->event.code = KEY3_ENTER;
		key_dev->event.value = 0;
		printk("key3 down\n");
	}
	
	return IRQ_HANDLED;
}

ssize_t key_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	int ret;
	
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret){
		printk("copy_from_user failed\n");
		return -EFAULT;
	}

	memset(&key_dev->event, 0, sizeof(key_dev->event));
	
	return count;
}

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

	return 0;
}

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

	return 0;
}

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

	return 0;
}

const struct file_operations fops = {
	.open = key_open,
	.read = key_read,
	.write = key_write,
	.release = key_close,
};

static int __init key_dev_init(void)
{
	struct device_node *np1,*np2;
	int err = 0;

	printk("-----------------%s--------------------\n",__FUNCTION__);
	//分配空间
	key_dev = kzalloc(sizeof(struct dev_desc), GFP_KERNEL);
	if(key_dev == NULL){
		printk("kzalloc failed\n");
		return -ENOMEM;
	}
	//获取key2的设备树节点
	np1 = of_find_node_by_path("/key2_node");
	if(np1 == NULL){
		printk("key2 of_find_node_by_path failed\n");
	}
	
	//获取key3的设备树节点
	np2 = of_find_node_by_path("/key3_node");
	if(np2 == NULL){
		printk("key3 of_find_node_by_path failed\n");
	}
	
	//获取key2中断号
	key_dev->irqno1 = irq_of_parse_and_map(np1,0);
	//获取key3中断号
	key_dev->irqno2 = irq_of_parse_and_map(np2,0);
	
	//打印中断号
	printk("irqno of key2 is %d\n",key_dev->irqno1);
	printk("irqno of key3 is %d\n",key_dev->irqno2);

	//申请主设备号
	key_dev->key_major = register_chrdev(0, "key_drv",&fops);
	if(key_dev->key_major < 0){
		printk("register_chrdev failed\n");
		err = -ENODEV;
		goto err_0;
	}
	//创建设备节点
	key_dev->cls = class_create(THIS_MODULE, "key_class");
	if(IS_ERR(key_dev->cls)){
		printk(KERN_ERR "class_create failed\n");
		err = PTR_ERR(key_dev->cls);
		goto err_1;
	}
	key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->key_major, 0), NULL, "key");
	if(IS_ERR(key_dev->dev)){
		printk(KERN_ERR "device_create failed\n");
		err = PTR_ERR(key_dev->dev);
		goto err_2;
	}
	//key2申请中断
	if(request_irq(key_dev->irqno1,key2_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"key2_irq",NULL)){
		printk("key2 request_irq failed\n");
	}
	//key3申请中断
	if(request_irq(key_dev->irqno2,key3_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"key3_irq",NULL)){
		printk("key3 request_irq failed\n");
	}
	//将GPX1CON物理地址映射为虚拟地址reg_addr
	key_dev->reg_addr = ioremap(GPX1CON,8);
	if(key_dev->reg_addr == NULL){
		printk("ioremap error\n");
		err = -EFAULT;
		goto err_3;
	}
	return 0;
	
err_3:
	device_destroy(key_dev->cls,MKDEV(key_dev->key_major, 0));
err_2:
	class_destroy(key_dev->cls);
err_1:
	unregister_chrdev(key_dev->key_major, "key_drv");
err_0:
	kfree(key_dev);
	return err;
}

static void __exit key_dev_exit(void)
{
	printk("-----------------%s--------------------\n",__FUNCTION__);

	//释放地址映射
	iounmap(key_dev->reg_addr);
	//释放key2中断
	free_irq(key_dev->irqno1,NULL);
	//释放key3中断
	free_irq(key_dev->irqno2,NULL);
	//释放设备节点
	device_destroy(key_dev->cls,MKDEV(key_dev->key_major, 0));
	class_destroy(key_dev->cls);
	//释放设备号
	unregister_chrdev(key_dev->key_major, "key_drv");
	//释放key_dev
	kfree(key_dev);
}

module_init(key_dev_init);
module_exit(key_dev_exit);
MODULE_LICENSE("GPL");






再看看测试代码:

#include 
#include 
#include 
#include 
#include 
#include 

#define KEY2_ENTER 28
#define KEY3_ENTER 29

struct key_event{
	int code;
	int value;
};
struct key_event event;

int main()
{
	int fd;

	fd = open("/dev/key",O_RDWR);
	if(fd < 0){
		perror("open");
		exit(-1);
	}
	
	while(1){
		read(fd,&event,sizeof(struct key_event));
		if(event.code == KEY2_ENTER){
			if(event.value == 1){
				printf("user key2 up\n");
			}else{
				printf("user key2 down\n");
			}
		}else if(event.code == KEY3_ENTER){
			if(event.value == 1){
				printf("user key3 up\n");
			}else{
				printf("user key3 down\n");
			}
		}else{

		}
	}
	close(fd);
	
	return 0;
}

我们在驱动程序和测试程序中都打印了按键状态的语句。好,接下来我们编译并加载模块进内核,cat /proc/devices

基于exynos4412的按键驱动编程_第8张图片

加载模块成功后,我们运行测试程序。

基于exynos4412的按键驱动编程_第9张图片

这时候看似程序卡在这里,其实是我们在驱动程序中封装了一个key_event结构体,测试程序是一直在读取K3和K2的状态的,只是没有显示出来而已。接下来我们按一下K3。

基于exynos4412的按键驱动编程_第10张图片

可以看到先执行中断处理函数,紧接着内核打印K3状态信息,然后内核将K3状态信息传递给用户态,就出现了用户态中的K3打印信息了。然后是按键K3抬起的打印信息,类似。K2按键也是类似的现象。

最后,有一点要注意,前面我们在说设备树节点的时候讲到触发方式,这里是下降沿触发,是我故意选择的,如果选择高电平触发的话,那么板子一上电就会直接进入内核的中断处理函数中(因为按键上电对应引脚是高电平),在驱动入口函数中我故意把映射初始化函数放在申请中断后面。此时中断申请后就立马进入中断处理函数,此时key_dev->reg_base没有被初始化,是空指针!!!

基于exynos4412的按键驱动编程_第11张图片

基于exynos4412的按键驱动编程_第12张图片

所以要么将二者顺序换一下,要么修改设备树节点的触发方式。

好了,今天先写到这~~~



你可能感兴趣的:(嵌入式)