按键由于物理特性,按下瞬间会多次置为 1/0,俗称抖动。
所以我们需要给一个合理的延时,来判断是否按下按钮,为了实现这个延时我们可以使用中断延迟工作和定时器来实现,下面是具体实现的方法。
中断延迟工作
1.相关结构体
内核使用struct delayed_work结构描述一个延迟工作,定义在include/linux/workqueue.h中:
/
/描述一个延迟工作 struct delayed_work { struct work_struct work; struct timer_list timer; /* target workqueue and CPU ->timer uses to queue ->work */ struct workqueue_struct *wq; int cpu; };
2.接口函数
初始化延迟工作
定义并初始化延迟工作函数:
#define INIT_DELAYED_WORK(_work, _func) \ __INIT_DELAYED_WORK(_work, _func, 0)
动态定义并初始化延迟工作
#define DECLARE_DELAYED_WORK(n, f) \ struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
静态定义并初始化延迟工作
调度延迟工作
static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
作用:在共享工作队列上调度延迟工作。
参数:*dwork:延迟的工作。delay:要延迟的时间。单位是节拍。
static inline bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
作用:在自定义工作队列上调度延迟工作
参数:
*wq:自定义的工作队列。
*dwork:延迟的工作。
delay:要延迟的时间。单位是节拍。
取消调度
bool cancel_delayed_work_sync(struct delayed_work *dwork)
作用:取消己经调度的延迟工作。
3.模板
..... struct delayed_work test_workqueue_work; struct workqueue_struct *test_workqueue; ..... /*创建和初始化*/ test_workqueue = create_workqueue("test_workqueue"); INIT_DELAYED_WORK(&irq_keydesc.test_workqueue_work,test_work); ...... /*上半部*/ static irqreturn_t key0_handler(int irq, void *dev_id) { ..... /*1ms延时*/ queue_delayed_work(irq_keydesc.test_workqueue, &test_workqueue_work, (1*HZ)/1000); ..... return IRQ_RETVAL(IRQ_HANDLED); } ..... /*下半部*/ void test_work(struct work_struct* work){ ...... printk("test\n"); } /*取消调度*/ cancel_delayed_work_sync(&test_workqueue_work);
4.代码实现
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/of_irq.h> #include <linux/irq.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/workqueue.h> #define IMX6UIRQ_CNT 1 /* 设备号个数 */ #define IMX6UIRQ_NAME "imx6uirq" /* 名字 */ #define KEY0VALUE 0X01 /* KEY0按键值 */ #define INVAKEY 0XFF /* 无效的按键值 */ #define KEY_NUM 1 /* 按键数量 */ /* 中断IO描述结构体 */ struct irq_keydesc { int gpio; /* gpio */ int irqnum; /* 中断号 */ unsigned char value; /* 按键对应的键值 */ char name[10]; /* 名字 */ irqreturn_t (*handler)(int, void *); /* 中断服务函数 */ struct delayed_work test_workqueue_work; struct workqueue_struct *test_workqueue; }irq_keydesc; //static struct delayed_work test_workqueue_work; //static struct workqueue_struct *test_workqueue; void test_work(struct work_struct* work){ printk("test\n"); } /* imx6uirq设备结构体 */ struct imx6uirq_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct device_node *nd; /* 设备节点 */ atomic_t keyvalue; /* 有效的按键键值 */ atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */ struct timer_list timer;/* 定义一个定时器*/ struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */ unsigned char curkeynum; /* 当前的按键号 */ }; struct imx6uirq_dev imx6uirq; /* irq设备 */ /* @description : 中断服务函数,开启定时器,延时10ms, * 定时器用于按键消抖。 * @param - irq : 中断号 * @param - dev_id : 设备结构。 * @return : 中断执行结果 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id; queue_delayed_work(irq_keydesc.test_workqueue, &irq_keydesc.test_workqueue_work, (1*HZ)/100000000); return IRQ_RETVAL(IRQ_HANDLED); } /* @description : 定时器服务函数,用于按键消抖,定时器到了以后 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。 * @param - arg : 设备结构变量 * @return : 无 */ /* * @description : 按键IO初始化 * @param : 无 * @return : 无 */ static int keyio_init(void) { unsigned char i = 0; int ret = 0; imx6uirq.nd = of_find_node_by_path("/key"); if (imx6uirq.nd== NULL){ printk("key node not find!\r\n"); return -EINVAL; } /* 提取GPIO */ for (i = 0; i < KEY_NUM; i++) { imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i); if (imx6uirq.irqkeydesc[i].gpio < 0) { printk("can't get key%d\r\n", i); } } /* 初始化key所使用的IO,并且设置成中断模式 */ for (i = 0; i < KEY_NUM; i++) { memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */ sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */ gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name); gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); #if 0 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio); #endif printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].irqnum); } /* 申请中断 */ imx6uirq.irqkeydesc[0].handler = key0_handler; imx6uirq.irqkeydesc[0].value = KEY0VALUE; for (i = 0; i < KEY_NUM; i++) { ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq); if(ret < 0){ printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } } irq_keydesc.test_workqueue = create_workqueue("test_workqueue"); INIT_DELAYED_WORK(&irq_keydesc.test_workqueue_work,test_work); return 0; } /* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = &imx6uirq; /* 设置私有数据 */ return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; unsigned char releasekey = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if (releasekey) { /* 有按键按下 */ if (keyvalue & 0x80) { keyvalue &= ~0x80; ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(&dev->releasekey, 0);/* 按下标志清零 */ } else { goto data_error; } return 0; data_error: return -EINVAL; } /* 设备操作函数 */ static struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .read = imx6uirq_read, }; /* * @description : 驱动入口函数 * @param : 无 * @return : 无 */ static int __init imx6uirq_init(void) { /* 1、构建设备号 */ if (imx6uirq.major) { imx6uirq.devid = MKDEV(imx6uirq.major, 0); register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME); } else { alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME); imx6uirq.major = MAJOR(imx6uirq.devid); imx6uirq.minor = MINOR(imx6uirq.devid); } /* 2、注册字符设备 */ cdev_init(&imx6uirq.cdev, &imx6uirq_fops); cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT); /* 3、创建类 */ imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.class)) { return PTR_ERR(imx6uirq.class); } /* 4、创建设备 */ imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.device)) { return PTR_ERR(imx6uirq.device); } /* 5、初始化按键 */ atomic_set(&imx6uirq.keyvalue, INVAKEY); atomic_set(&imx6uirq.releasekey, 0); keyio_init(); return 0; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit imx6uirq_exit(void) { unsigned int i = 0; /* 释放中断 */ for (i = 0; i < KEY_NUM; i++) { free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq); gpio_free(imx6uirq.irqkeydesc[i].gpio); } cdev_del(&imx6uirq.cdev); unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); device_destroy(imx6uirq.class, imx6uirq.devid); class_destroy(imx6uirq.class); cancel_delayed_work_sync(&irq_keydesc.test_workqueue_work); flush_workqueue(irq_keydesc.test_workqueue); } module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL");
Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR # 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量: # 2.1 ARCH, 比如: export ARCH=arm64 # 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu- # 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin # 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同, # 请参考各开发板的高级用户使用手册 # ROOTFS_DIR 根文件系统中存放 *.ko文件所在目录 # PROJECT_NAME 在存放.ko文件目录中创建对应项目的目录 # DRIVER_NAME 项目中需要编译出.ko来的驱动 # APP_NAME 项目中的应用测试文件 #make 编译项目 #make file 在存放.ko文件目录中创建对应项目的目录 #make install 将*.ko及其应用测试文件移动到根文件中 KERN_DIR = /home/alientek/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ROOTFS_DIR = /home/alientek/linux/nfs/rootfs/experiment #项目名字 PROJECT_NAME = wq_button #各驱动名字,ko DRIVER_NAME1 = imx6uirq DRIVER_NAME2 = #app名字 APP_NAME = imx6uirqApp all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order rm -f $(APP_NAME) file: mkdir $(ROOTFS_DIR)/$(PROJECT_NAME) install: cp *.ko $(ROOTFS_DIR)/$(PROJECT_NAME) # 参考内核源码drivers/char/ipmi/Makefile # 要想把a.c, b.c编译成ab.ko, 可以这样指定: # ab-y := a.o b.o # obj-m += ab.o obj-m += $(DRIVER_NAME1).o
定时器
1.相关结构体
Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下
struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ struct list_head entry;//内核使用 unsigned long expires;//超时的jiffies值 struct tvec_base *base;//内核使用,管理用 void (*function)(unsigned long);//超时处理函数 unsigned long data;//超时处理函数参数 int slack; #ifdef CONFIG_TIMER_STATS int start_pid; void *start_site; char start_comm[16]; #endif #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
2.接口函数
init_timer 函数
init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下。 init_timer 函数原型如下:
void init_timer(struct timer_list *timer)
函数参数和返回值含义如下:
timer:要初始化定时器。
返回值: 没有返回值。
add_timer 函数
add_timer 函数用于向Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,
定时器就会开始运行,函数原型如下:
void add_timer(struct timer_list *timer)
函数参数和返回值含义如下:
timer:要注册的定时器。
返回值:没有返回值。
del_timer 函数
del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时
器之前要先等待其他处理器的定时处理器函数退出。del_timer 函数原型如下:
int del_timer(struct timer_list * timer)
函数参数和返回值含义如下:
timer:要删除的定时器。
返回值:0,定时器还没被激活;1,定时器已经激活。
del_timer_sync 函数
del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
del_timer_sync 不能使用在中断上下文中。del_timer_sync 函数原型如下所示:
int del_timer_sync(struct timer_list *timer)
函数参数和返回值含义如下:
timer:要删除的定时器。
返回值:0,定时器还没被激活;1,定时器已经激活。
mod_timer 函数
mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时
器!函数原型如下:
int mod_timer(struct timer_list *timer, unsigned long expires)
函数参数和返回值含义如下:
timer:要修改超时时间(定时值)的定时器。
expires:修改后的超时时间。
返回值:0,调用 mod_timer 函数前定时器未被激活;1,调用 mod_timer 函数前定时器已
被激活。
3.模板
struct timer_list timer; /* 定义定时器 */ /* 定时器回调函数 */ void function(unsigned long arg){ /* * 定时器处理代码 */ /* 如果需要定时器周期性运行的话就使用 mod_timer * 函数重新设置超时值并且启动定时器。 */ mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000)); } /* 初始化函数 */ void init(void) { init_timer(&timer); / *初始化定时器 */ timer.function = function; /* 设置定时处理函数 * / timer.expires=jffies + msecs_to_jiffies(2000);/ *超时时间 2 秒 */ timer.data = (unsigned long)&dev; / *将设备结构体作为参数 */ add_timer(&timer); /* 启动定时器 */ } /* 退出函数* / void exit(void){ del_timer(&timer); / *删除定时器 */ / *或者使用 */ del_timer_sync(&timer); }
代码实现
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/of_irq.h> #include <linux/irq.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define IMX6UIRQ_CNT 1 /* 设备号个数 */ #define IMX6UIRQ_NAME "imx6uirq" /* 名字 */ #define KEY0VALUE 0X01 /* KEY0按键值 */ #define INVAKEY 0XFF /* 无效的按键值 */ #define KEY_NUM 1 /* 按键数量 */ /* 中断IO描述结构体 */ struct irq_keydesc { int gpio; /* gpio */ int irqnum; /* 中断号 */ unsigned char value; /* 按键对应的键值 */ char name[10]; /* 名字 */ irqreturn_t (*handler)(int, void *); /* 中断服务函数 */ }; /* imx6uirq设备结构体 */ struct imx6uirq_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct device_node *nd; /* 设备节点 */ atomic_t keyvalue; /* 有效的按键键值 */ atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */ struct timer_list timer;/* 定义一个定时器*/ struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */ unsigned char curkeynum; /* 当前的按键号 */ }; struct imx6uirq_dev imx6uirq; /* irq设备 */ /* @description : 中断服务函数,开启定时器,延时10ms, * 定时器用于按键消抖。 * @param - irq : 中断号 * @param - dev_id : 设备结构。 * @return : 中断执行结果 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id; dev->curkeynum = 0; dev->timer.data = (volatile long)dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */ return IRQ_RETVAL(IRQ_HANDLED); } /* @description : 定时器服务函数,用于按键消抖,定时器到了以后 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。 * @param - arg : 设备结构变量 * @return : 无 */ void timer_function(unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; num = dev->curkeynum; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); /* 读取IO值 */ if(value == 0){ /* 按下按键 */ atomic_set(&dev->keyvalue, keydesc->value); } else{ /* 按键松开 */ atomic_set(&dev->keyvalue, 0x80 | keydesc->value); atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */ } } /* * @description : 按键IO初始化 * @param : 无 * @return : 无 */ static int keyio_init(void) { unsigned char i = 0; int ret = 0; imx6uirq.nd = of_find_node_by_path("/key"); if (imx6uirq.nd== NULL){ printk("key node not find!\r\n"); return -EINVAL; } /* 提取GPIO */ for (i = 0; i < KEY_NUM; i++) { imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i); if (imx6uirq.irqkeydesc[i].gpio < 0) { printk("can't get key%d\r\n", i); } } /* 初始化key所使用的IO,并且设置成中断模式 */ for (i = 0; i < KEY_NUM; i++) { memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */ sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */ gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name); gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); #if 0 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio); #endif printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].irqnum); } /* 申请中断 */ imx6uirq.irqkeydesc[0].handler = key0_handler; imx6uirq.irqkeydesc[0].value = KEY0VALUE; for (i = 0; i < KEY_NUM; i++) { ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq); if(ret < 0){ printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } } /* 创建定时器 */ init_timer(&imx6uirq.timer); imx6uirq.timer.function = timer_function; return 0; } /* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = &imx6uirq; /* 设置私有数据 */ return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; unsigned char releasekey = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if (releasekey) { /* 有按键按下 */ if (keyvalue & 0x80) { keyvalue &= ~0x80; ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(&dev->releasekey, 0);/* 按下标志清零 */ } else { goto data_error; } return 0; data_error: return -EINVAL; } /* 设备操作函数 */ static struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .read = imx6uirq_read, }; /* * @description : 驱动入口函数 * @param : 无 * @return : 无 */ static int __init imx6uirq_init(void) { /* 1、构建设备号 */ if (imx6uirq.major) { imx6uirq.devid = MKDEV(imx6uirq.major, 0); register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME); } else { alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME); imx6uirq.major = MAJOR(imx6uirq.devid); imx6uirq.minor = MINOR(imx6uirq.devid); } /* 2、注册字符设备 */ cdev_init(&imx6uirq.cdev, &imx6uirq_fops); cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT); /* 3、创建类 */ imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.class)) { return PTR_ERR(imx6uirq.class); } /* 4、创建设备 */ imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.device)) { return PTR_ERR(imx6uirq.device); } /* 5、初始化按键 */ atomic_set(&imx6uirq.keyvalue, INVAKEY); atomic_set(&imx6uirq.releasekey, 0); keyio_init(); return 0; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit imx6uirq_exit(void) { unsigned int i = 0; /* 删除定时器 */ del_timer_sync(&imx6uirq.timer); /* 删除定时器 */ /* 释放中断 */ for (i = 0; i < KEY_NUM; i++) { free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq); gpio_free(imx6uirq.irqkeydesc[i].gpio); } cdev_del(&imx6uirq.cdev); unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); device_destroy(imx6uirq.class, imx6uirq.devid); class_destroy(imx6uirq.class); } module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL");