今天写一下按键驱动,本次并没有用输入子系统,但仍然不适合新手直接学,建议先看一下其他人写的按键驱动,然后再看这个,本博文主要是为了复习一下之前的知识.
硬件平台:tiny4412(Cortex A9);
软件平台:Linux-3.5
本次用按键驱动LED灯,我用的tiny4412开发板上有4个按键,4个LED灯,所以可以灯和按键一一对应,首先先查看原理图,如下:
LED:
对用的引脚分别是GPM4_0, GPM4_1, GPM4_2, GPM4_3,如下图:
我们查阅datesheet:
从上图可以知道LED相关引脚的配置寄存器的地址是:0x110002E0,那么数据寄存器就是0x110002E0 + 4,接下来在找按键的,如下图:
查询到对应的引脚分别是GPX3_2, GPX3_3, GPX3_4, GPX3_5:
这个我们就不查datasheet了,因为三星的寄存器地址都已经封装好了,我们可以在驱动了通过宏直接调用,当然,所有按键的都是封装好了,其实上面的LED的引脚也可以不用查datasheet,不过,为了把知识都用上,就这里还是保留一部分直接操作寄存器的语句.接下来就直接上代码了,代码不是很难,关键部分是有注释的,特别地给中断增加了软件消抖,程序如下:
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/cdev.h> #include <linux/interrupt.h> #include <linux/input.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/gpio.h> #include <linux/delay.h> #include <asm/gpio.h> #include <asm/io.h> #include <asm/uaccess.h> #include <mach/gpio.h> #define GPM4COM 0x110002E0 #define KEY_DEV_MAJOR 0 #define LED_Switch1 _IOW('L', 0x1234, int) #define LED_Switch2 _IOW('L', 0x1235, int) #define LED_Switch3 _IOW('L', 0x1236, int) #define LED_Switch4 _IOW('L', 0x1237, int) volatile unsigned long *led_conf = NULL; volatile unsigned long *led_data = NULL; struct key_event{ int code; int value; }; // 设备对象属性 struct exynos4412_intkey{ dev_t devno; struct cdev *cdev; struct class *cls; struct device *dev; int irqno; struct key_event event; unsigned char have_data; wait_queue_head_t wq_head; }; // 单个按键信息的对象 struct key_desc{ char *name; int gpio; int code; int int_flag; // 中断触发方式 }; struct exynos4412_intkey *key_dev; // 定义所有按键的信息集合, GPX3_2, 3_3, 3_4, 3_5. struct key_desc all_keys[] = { [0] = { .name = "key1_button", .gpio = EXYNOS4_GPX3(2), .code = KEY_UP, .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [1] = { .name = "key2_button", .gpio = EXYNOS4_GPX3(3), .code = KEY_DOWN, .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [2] = { .name = "key3_button", .gpio = EXYNOS4_GPX3(4), .code = KEY_LEFT, .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [3] = { .name = "key4_button", .gpio = EXYNOS4_GPX3(5), .code = KEY_RIGHT, .int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, }; int key_open(struct inode *inode, struct file *filp) { printk("---------%s---------\n", __func__); // 演示inode的作用--->获取节点的主次设备号,就可以知道当前操作的是哪个设备 unsigned int major = imajor(inode); unsigned int minor = iminor(inode); printk("major : %d, minor: %d\n", major, minor); // 也可以通过filp获取到inode printk("filp...major: %d, minor: %d\n", imajor(filp->f_path.dentry->d_inode), iminor(filp->f_path.dentry->d_inode)); // 数据的共享 static int a = 155; filp->private_data = &a; /**************************以上为增值功能********************************/ // 先对LED的端口进行清0操作 *led_conf &= ~(0xffff); // 将4个IO口16位都设置为output输出状态 *led_conf |= (0x1111); // 初始化数据 memset(&key_dev->event, 0, sizeof(key_dev->event)); key_dev->have_data = 0; return 0; } ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos) { int ret = -1; // 判断当前是什么模式 if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data) return -EAGAIN; // 没有数据的时候进行休眠 wait_event_interruptible(key_dev->wq_head, key_dev->have_data); ret = copy_to_user(buf, &key_dev->event, count); if(ret > 0){ printk("copy to user failed!\n"); return -EFAULT; } // 清空数据,以备下次数据来临 memset(&key_dev->event, 0, sizeof(key_dev->event)); key_dev->have_data = 0; return count; } ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos) { int val = -1, ret = -1; ret = copy_from_user(&val, buf, count); if(ret != 0){ printk("copy from user failed!\n"); return -EINVAL; } switch(val) { case 0: printk(KERN_EMERG"led1_on\n"); *led_data &= ~0x1; break; case 1: printk(KERN_EMERG"led2_on\n"); *led_data &= ~(0x1 << 1); break; case 2: printk(KERN_EMERG"led3_on\n"); *led_data &= ~(0x1 << 2); break; case 3: printk(KERN_EMERG"led4_on\n"); *led_data &= ~0x8; break; case 4: printk(KERN_EMERG"ledall_on\n"); *led_data &= ~0xf; break; case 5: printk(KERN_EMERG"ledall_off\n"); *led_data |= 0xf; break; default: break; } return ret; } long key_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret = -1; switch(cmd) { case LED_Switch1: if(arg){ gpio_request(EXYNOS4X12_GPM4(0), "KEY1_LED"); gpio_direction_output(EXYNOS4X12_GPM4(0), 0); gpio_free(EXYNOS4X12_GPM4(0)); } else { gpio_request(EXYNOS4X12_GPM4(0), "KEY1_LED"); gpio_direction_output(EXYNOS4X12_GPM4(0), 1); gpio_free(EXYNOS4X12_GPM4(0)); } break; case LED_Switch2: if(arg){ gpio_request(EXYNOS4X12_GPM4(1), "KEY2_LED"); gpio_direction_output(EXYNOS4X12_GPM4(1), 0); gpio_free(EXYNOS4X12_GPM4(1)); } else { gpio_request(EXYNOS4X12_GPM4(1), "KEY2_LED"); gpio_direction_output(EXYNOS4X12_GPM4(1), 1); gpio_free(EXYNOS4X12_GPM4(1)); } break; case LED_Switch3: if(arg){ gpio_request(EXYNOS4X12_GPM4(2), "KEY3_LED"); gpio_direction_output(EXYNOS4X12_GPM4(2), 0); gpio_free(EXYNOS4X12_GPM4(2)); } else { gpio_request(EXYNOS4X12_GPM4(2), "KEY3_LED"); gpio_direction_output(EXYNOS4X12_GPM4(2), 1); gpio_free(EXYNOS4X12_GPM4(2)); } break; case LED_Switch4: if(arg){ gpio_request(EXYNOS4X12_GPM4(3), "KEY4_LED"); gpio_direction_output(EXYNOS4X12_GPM4(3), 0); gpio_free(EXYNOS4X12_GPM4(3)); } else { gpio_request(EXYNOS4X12_GPM4(3), "KEY4_LED"); gpio_direction_output(EXYNOS4X12_GPM4(3), 1); gpio_free(EXYNOS4X12_GPM4(3)); } break; default: *led_data |= 0xf; break; } return 0; } int key_close(struct inode *inode, struct file *filp) { printk("---------%s---------\n", __func__); int *p = (int *)filp->private_data; printk("close data: %d\n", *p); *led_data |= 0xf; return 0; } const struct file_operations key_fops = { // .owner = THIS_MODULE, .open = key_open, .read = key_read, .write = key_write, .unlocked_ioctl = key_ioctl, .release = key_close, }; irqreturn_t key_irq_handle(int irqno, void *devid) { // 区分不同的中断 struct key_desc *p = (struct key_desc *)devid; udelay(20); // delay 20us stabilization in key pressed if(gpio_get_value(p->gpio)){ // 弹起 printk("<KERNEL> %s UP!\n", p->name); key_dev->event.code = p->code; key_dev->event.value = 0; } else { // 按下 printk("<KERNEL> %s Down!\n", p->name); key_dev->event.code = p->code; key_dev->event.value = 1; } // 唤醒队列 wake_up_interruptible(&key_dev->wq_head); key_dev->have_data = 1; // 设置是有数据 return IRQ_HANDLED; } static void __exit intkey_exit(void) { int i; printk("---------%s---------\n", __func__); for(i = 0; i < ARRAY_SIZE(all_keys); i++) free_irq(gpio_to_irq(all_keys[i].gpio), &all_keys[i]); device_destroy(key_dev->cls, key_dev->devno); cdev_del(key_dev->cdev); class_destroy(key_dev->cls); unregister_chrdev_region(key_dev->devno, 1); kfree(key_dev); } static int __init intkey_init(void) { int ret = -1; printk("---------%s---------\n", __func__); // 1,实例化设备对象 key_dev = kzalloc(sizeof(struct exynos4412_intkey), GFP_KERNEL); if(NULL == key_dev){ printk("kzalloc failed\n"); return -ENOMEM; } // 2,申请设备号 if(KEY_DEV_MAJOR > 0){ // 静态申请 key_dev->devno = MKDEV(KEY_DEV_MAJOR, 0); ret = register_chrdev_region(key_dev->devno, 1, "key_drv"); } else { // 动态申请 ret = alloc_chrdev_region(&key_dev->devno, 16, 1, "key_drv"); } if(ret < 0){ printk("register failed!\n"); ret = -EINVAL; goto err1; } // 3,cdev的操作 // 3.1 分配一个cdev key_dev->cdev = cdev_alloc(); // 3.2 初始化cdev cdev_init(key_dev->cdev, &key_fops); // 3.3 注册一个cdev cdev_add(key_dev->cdev, key_dev->devno, 1); // 4,创建设备类 key_dev->cls = class_create(THIS_MODULE, "key_cls"); if(IS_ERR(key_dev->cls)){ printk("class create failed!\n"); ret = PTR_ERR(key_dev); goto err2; } // 5,创建设备节点 key_dev->dev = device_create(key_dev->cls, NULL, key_dev->devno, NULL, "key%d", 1); if(IS_ERR(key_dev->dev)){ printk("device create failed!\n"); ret = PTR_ERR(key_dev->dev); goto err3; } // 6,硬件初始化 int i = 0; for(i = 0; i < ARRAY_SIZE(all_keys); i++){ ret = request_irq(gpio_to_irq(all_keys[i].gpio), key_irq_handle, all_keys[i].int_flag, all_keys[i].name, &all_keys[i]); if(ret != 0){ printk("request irq failed!\n"); goto err4; } } led_conf = (volatile unsigned long *)ioremap(GPM4COM, 8); led_data = led_conf + 1; // 7,初始化一个队列头 init_waitqueue_head(&key_dev->wq_head); return 0; err4: device_destroy(key_dev->cls, key_dev->devno); err3: cdev_del(key_dev->cdev); class_destroy(key_dev->cls); err2: unregister_chrdev_region(key_dev->devno, 1); err1: kfree(key_dev); return ret; } module_init(intkey_init); module_exit(intkey_exit); MODULE_LICENSE("GPL");下面再写个测试程序:
#include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <linux/input.h> #define LED_Switch1 _IOW('L', 0x1234, int) #define LED_Switch2 _IOW('L', 0x1235, int) #define LED_Switch3 _IOW('L', 0x1236, int) #define LED_Switch4 _IOW('L', 0x1237, int) struct key_event{ int code; int value; }; int main(void) { int ret = -1, fd = -1, val = -1; struct key_event event; fd = open("/dev/key1", O_RDWR); if(fd < 0){ perror("open"); exit(1); } while(1) { ret = read(fd, &event, sizeof(struct key_event)); if(ret < 0){ perror("read"); exit(1); } if(event.code == KEY_UP){ if(event.value){ printf("<APP> key1 DOWN!\n"); ioctl(fd, LED_Switch1, 1); } else { printf("<APP> key1 UP!\n"); ioctl(fd, LED_Switch1, 0); } } if(event.code == KEY_DOWN){ if(event.value){ printf("<APP> key2 DOWN!\n"); val = 1; write(fd, &val, 4); } else { printf("<APP> key2 UP!\n"); ioctl(fd, LED_Switch2, 0); } } if(event.code == KEY_LEFT){ if(event.value){ printf("<APP> key3 DOWN!\n"); ioctl(fd, LED_Switch3, 1); } else { printf("<APP> key3 UP!\n"); ioctl(fd, LED_Switch3, 0); } } if(event.code == KEY_RIGHT){ if(event.value){ printf("<APP> key4 DOWN!\n"); ioctl(fd, LED_Switch4, 1); } else { printf("<APP> key4 UP!\n"); ioctl(fd, LED_Switch4, 0); } } usleep(3000); } if(close(fd) < 0){ perror("close"); exit(1); } }好了,还有Makefile:
#Linux源代码的路径 KERNEL_DIR = /home/george/1702/exynos/linux-3.5 #获取当前路径 CUR_DIR = $(shell pwd) MYAPP = key_test MODULE = intkey all: make -C $(KERNEL_DIR) M=$(CUR_DIR) modules arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c clean: make -C $(KERNEL_DIR) M=$(CUR_DIR) clean $(RM) $(MYAPP) install: cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702 #需要编译的目标程序 obj-m = $(MODULE).o其中测试程序是key_test.c,驱动程序是intkey.c.
编译验证一下,按一个键就可以亮一个LED.