一、原理图
二、中断表
三、设备树
四、说明
本次实验是在按键中断的基础上修改,原理图和设备树不变,使用中断下半部机制,介绍work和tasklet两种实现下半部的方式。
方式一:tasklet
方式二:
五、tasklet方式实现下半部的驱动程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/string.h> #include <linux/irq.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/interrupt.h> #define DeviceName "devicetree-key0-interrupt-tasklet" //设备驱动名称 #define DeviceNodes 1 //设备节点个数 #define KEY_NUM 0X01 #define KEY0VALUE 0X01 #define INVAKEY 0XFF struct irq_keydesc{ int gpio_num; /* io编号 */ int irq_num; /* 中断号 */ unsigned char value; /* 键值 */ char name[25]; /* 名字 */ irqreturn_t (*handler) (int, void *); /* 中断处理函数 */ struct tasklet_struct tasklet; }; /* 定义一个设备结构体 */ struct mod_struct { dev_t devid; /* 设备号 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ struct device_node *nd; /* 设备节点 */ struct irq_keydesc irqkey0; /* 按键中断 */ struct timer_list timer; /* 内核定时器 */ atomic_t keyvalue; /* 原子变量1->整形*/ atomic_t releasekey; /* 原子变量2->整形*/ }; struct mod_struct mod_device; /* 定义一个设备 */ /* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int key0_interrupt_open(struct inode *inode, struct file *filp) { filp->private_data = &mod_device; /* 设置私有数据 */ return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t key0_interrupt_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue; unsigned char releasekey; struct mod_struct *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 ret; data_error: return -EINVAL; } /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t key0_interrupt_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt) { return 0; } /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ static int key0_interrupt_release(struct inode *inode, struct file *filp) { return 0; } /* 设备操作函数 */ static struct file_operations beep_fops = { .owner = THIS_MODULE, .open = key0_interrupt_open, .read = key0_interrupt_read, .write = key0_interrupt_write, .release = key0_interrupt_release, }; /* 按键中断处理函数 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct mod_struct *dev = dev_id; #if 0 dev->timer.data = (volatile long)dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */ #endif tasklet_schedule(&dev->irqkey0.tasklet); return IRQ_HANDLED; } /* tasklet (下半部)*/ static void key_tasklet(unsigned long data) { struct mod_struct *dev = (struct mod_struct*)data; dev->timer.data = data; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */ } /* 定时器处理函数 */ static void timer_func(unsigned long arg) { int value = 0; struct mod_struct *dev = (struct mod_struct*)arg; value = gpio_get_value(dev->irqkey0.gpio_num); if(value == 0) { /* 按下 */ atomic_set(&dev->keyvalue, dev->irqkey0.value);/*=0x01*/ } else if(value == 1) { /* 释放 */ atomic_set(&dev->keyvalue, 0X80 | (dev->irqkey0.value)); atomic_set(&dev->releasekey, 1); /* 完成的按键过程 */ } } /* * @description : 按键初始化函数 * @param : dev * @return : 无 */ static int key_init(struct mod_struct *dev) { int ret = 0; /* 1、获取设备节点:/devicetree-leds-pincrl */ dev->nd = of_find_node_by_path("/devicetree-key0-interrupt"); if(dev->nd == NULL) { printk("devicetree-key0-interrupt node not find!\r\n"); ret = -EINVAL; goto fail_fd; } else { printk("devicetree-key0-interrupt node find!\r\n"); } /* 2、从设备节点下的led-gpio属性里获取gpio编号 */ dev->irqkey0.gpio_num = of_get_named_gpio(dev->nd,"key-gpio", 0); if(dev->irqkey0.gpio_num < 0) { printk("can't get key-gpio\r\n"); ret = -EINVAL; goto fail_gpio; } printk("key-gpio num = %d\r\n", dev->irqkey0.gpio_num); /*3、申请gpio(检查是否被占用)*/ sprintf(dev->irqkey0.name,"%s","key0-gpio-interrupt"); ret = gpio_request(dev->irqkey0.gpio_num,dev->irqkey0.name); if(ret) { ret = -EBUSY; printk("IO %d busy,can't request!\r\n",dev->irqkey0.gpio_num); goto fail_ioreq; } /*4、设置key0为输入 */ ret = gpio_direction_input(dev->irqkey0.gpio_num); if(ret < 0) { printk("can't set gpio!\r\n"); goto fail_ioset; } /*5、根据IO编号获取其中断号*/ dev->irqkey0.irq_num=gpio_to_irq(dev->irqkey0.gpio_num); #if 0 dev->irqkey0.irq_num = irq_of_parse_and_map(dev->nd,0); #endif /*6、设置中断函数*/ dev->irqkey0.handler = key0_handler; dev->irqkey0.value = KEY0VALUE; /*7、注册中断*/ ret = request_irq(dev->irqkey0.irq_num, dev->irqkey0.handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, dev->irqkey0.name, &mod_device); if(ret){ printk("irq %d request failed!\r\n", dev->irqkey0.irq_num); goto fail_irq; } tasklet_init(&dev->irqkey0.tasklet, key_tasklet, (unsigned long)dev); /*8、初始化定时器*/ init_timer(&mod_device.timer); mod_device.timer.function = timer_func; return 0; fail_irq: fail_ioset: gpio_free(dev->irqkey0.gpio_num); fail_ioreq: fail_gpio: fail_fd: return ret; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static int __init key0_interrupt_init(void) { int ret = 0; /*一、 注册字符设备驱动 */ /* 1、创建设备号 */ if (mod_device.major) { mod_device.devid = MKDEV(mod_device.major,0);//设备号起始点(major,0) //设备号起始点开始,申请DeviceNodes个设备号 ret = register_chrdev_region(mod_device.devid,DeviceNodes,DeviceName); } else { ret = alloc_chrdev_region(&mod_device.devid,0,DeviceNodes,DeviceName); /* 申请设备号 */ mod_device.major = MAJOR(mod_device.devid); /* 获取分配号的主设备号 */ mod_device.minor = MINOR(mod_device.devid); /* 获取分配号的次设备号 */ } if(ret < 0){ printk("设备号申请失败\r\n");goto fail_chrdev; } printk("mod_device major=%d,minor=%d\r\n",mod_device.major,mod_device.minor); /* 2、初始化cdev并添加cdev */ mod_device.cdev.owner = THIS_MODULE; cdev_init(&mod_device.cdev,&beep_fops); ret = cdev_add(&mod_device.cdev,mod_device.devid,DeviceNodes); if(ret < 0){ printk("添加cdev失败!\r\n");goto fail_cdev; } /* 3、创建类 */ mod_device.class = class_create(THIS_MODULE,DeviceName); if (IS_ERR(mod_device.class)) { ret = PTR_ERR(mod_device.class);goto fail_class; } /* 4、创建设备节点*/ mod_device.device = device_create(mod_device.class,NULL,mod_device.devid,NULL,"DeviceName"); if (IS_ERR(mod_device.device)) { ret = PTR_ERR(mod_device.device);goto fail_device; } /* 5、初始化IO */ ret = key_init(&mod_device); if(ret < 0) { goto fail_keyinit; } /* 6、初始化原子变量 */ atomic_set(&mod_device.keyvalue, INVAKEY);/*mod_device.keyvalue=INVAKEY*/ atomic_set(&mod_device.releasekey, 0);/*mod_device.releasekey=0*/ /*X、模块加载失败处理部分*/ fail_keyinit: device_destroy(mod_device.class,mod_device.devid); fail_device: class_destroy(mod_device.class); fail_class: cdev_del(&mod_device.cdev); fail_cdev: unregister_chrdev_region(mod_device.devid,DeviceNodes); fail_chrdev: return ret; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit key0_interrupt_exit(void)/*按照相反顺序注销*/ { /*1、释放中断*/ free_irq(mod_device.irqkey0.irq_num, &mod_device); /*2、内核定定时器注销部分*/ del_timer_sync(&mod_device.timer);/* 删除timer */ /*3、LED设备注销部分*/ gpio_free(mod_device.irqkey0.gpio_num);/*注销gpio*/ /*4、设备驱动注销部分*/ device_destroy(mod_device.class,mod_device.devid);/*注销设备节点*/ class_destroy(mod_device.class);/*注销类*/ cdev_del(&mod_device.cdev);/*注销cdev*/ unregister_chrdev_region(mod_device.devid,DeviceNodes);/*注销chrdev*/ } module_init(key0_interrupt_init); module_exit(key0_interrupt_exit); MODULE_LICENSE("GPL"); |
六、work方式实现下半部的驱动程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/string.h> #include <linux/irq.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/interrupt.h> #define DeviceName "devicetree-key0-interrupt-work" //设备驱动名称 #define DeviceNodes 1 //设备节点个数 #define KEY_NUM 0X01 #define KEY0VALUE 0X01 #define INVAKEY 0XFF struct irq_keydesc{ int gpio_num; /* io编号 */ int irq_num; /* 中断号 */ unsigned char value; /* 键值 */ char name[25]; /* 名字 */ irqreturn_t (*handler) (int, void *); /* 中断处理函数 */ struct tasklet_struct tasklet; }; /* 定义一个设备结构体 */ struct mod_struct { dev_t devid; /* 设备号 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ struct device_node *nd; /* 设备节点 */ struct irq_keydesc irqkey0; /* 按键中断 */ struct timer_list timer; /* 内核定时器 */ struct work_struct work; /* 工作队列 */ atomic_t keyvalue; /* 原子变量1->整形*/ atomic_t releasekey; /* 原子变量2->整形*/ }; struct mod_struct mod_device; /* 定义一个设备 */ /* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */ static int key0_interrupt_open(struct inode *inode, struct file *filp) { filp->private_data = &mod_device; /* 设置私有数据 */ return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t key0_interrupt_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue; unsigned char releasekey; struct mod_struct *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 ret; data_error: return -EINVAL; } /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t key0_interrupt_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt) { return 0; } /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ static int key0_interrupt_release(struct inode *inode, struct file *filp) { return 0; } /* 设备操作函数 */ static struct file_operations beep_fops = { .owner = THIS_MODULE, .open = key0_interrupt_open, .read = key0_interrupt_read, .write = key0_interrupt_write, .release = key0_interrupt_release, }; /* 按键中断处理函数 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct mod_struct *dev = dev_id; #if 0 dev->timer.data = (volatile long)dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */ #endif //tasklet_schedule(&dev->irqkey0.tasklet); schedule_work(&dev->work); return IRQ_HANDLED; } /* tasklet (下半部方法1)*/ static void key_tasklet(unsigned long data) { struct mod_struct *dev = (struct mod_struct*)data; dev->timer.data = data; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */ } /* work (下半部方法2)*/ static void key_work(struct work_struct *work) { struct mod_struct *dev = container_of(work, struct mod_struct, work); dev->timer.data = (unsigned long)dev; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */ } /* 定时器处理函数 */ static void timer_func(unsigned long arg) { int value = 0; struct mod_struct *dev = (struct mod_struct*)arg; value = gpio_get_value(dev->irqkey0.gpio_num); if(value == 0) { /* 按下 */ atomic_set(&dev->keyvalue, dev->irqkey0.value);/*=0x01*/ } else if(value == 1) { /* 释放 */ atomic_set(&dev->keyvalue, 0X80 | (dev->irqkey0.value)); atomic_set(&dev->releasekey, 1); /* 完成的按键过程 */ } } /* * @description : 按键初始化函数 * @param : dev * @return : 无 */ static int key_init(struct mod_struct *dev) { int ret = 0; /* 1、获取设备节点:/devicetree-leds-pincrl */ dev->nd = of_find_node_by_path("/devicetree-key0-interrupt"); if(dev->nd == NULL) { printk("devicetree-key0-interrupt node not find!\r\n"); ret = -EINVAL; goto fail_fd; } else { printk("devicetree-key0-interrupt node find!\r\n"); } /* 2、从设备节点下的led-gpio属性里获取gpio编号 */ dev->irqkey0.gpio_num = of_get_named_gpio(dev->nd,"key-gpio", 0); if(dev->irqkey0.gpio_num < 0) { printk("can't get key-gpio\r\n"); ret = -EINVAL; goto fail_gpio; } printk("key-gpio num = %d\r\n", dev->irqkey0.gpio_num); /*3、申请gpio(检查是否被占用)*/ sprintf(dev->irqkey0.name,"%s","key0-gpio-interrupt"); ret = gpio_request(dev->irqkey0.gpio_num,dev->irqkey0.name); if(ret) { ret = -EBUSY; printk("IO %d busy,can't request!\r\n",dev->irqkey0.gpio_num); goto fail_ioreq; } /*4、设置key0为输入 */ ret = gpio_direction_input(dev->irqkey0.gpio_num); if(ret < 0) { printk("can't set gpio!\r\n"); goto fail_ioset; } /*5、根据IO编号获取其中断号*/ dev->irqkey0.irq_num=gpio_to_irq(dev->irqkey0.gpio_num); #if 0 dev->irqkey0.irq_num = irq_of_parse_and_map(dev->nd,0); #endif /*6、设置中断函数*/ dev->irqkey0.handler = key0_handler; dev->irqkey0.value = KEY0VALUE; /*7、注册中断*/ ret = request_irq(dev->irqkey0.irq_num, dev->irqkey0.handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, dev->irqkey0.name, &mod_device); if(ret){ printk("irq %d request failed!\r\n", dev->irqkey0.irq_num); goto fail_irq; } INIT_WORK(&dev->work,key_work); //tasklet_init(&dev->irqkey0.tasklet, key_tasklet, (unsigned long)dev); /*8、初始化定时器*/ init_timer(&mod_device.timer); mod_device.timer.function = timer_func; return 0; fail_irq: fail_ioset: gpio_free(dev->irqkey0.gpio_num); fail_ioreq: fail_gpio: fail_fd: return ret; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static int __init key0_interrupt_init(void) { int ret = 0; /*一、 注册字符设备驱动 */ /* 1、创建设备号 */ if (mod_device.major) { mod_device.devid = MKDEV(mod_device.major,0);//设备号起始点(major,0) //设备号起始点开始,申请DeviceNodes个设备号 ret = register_chrdev_region(mod_device.devid,DeviceNodes,DeviceName); } else { ret = alloc_chrdev_region(&mod_device.devid,0,DeviceNodes,DeviceName); /* 申请设备号 */ mod_device.major = MAJOR(mod_device.devid); /* 获取分配号的主设备号 */ mod_device.minor = MINOR(mod_device.devid); /* 获取分配号的次设备号 */ } if(ret < 0){ printk("设备号申请失败\r\n");goto fail_chrdev; } printk("mod_device major=%d,minor=%d\r\n",mod_device.major,mod_device.minor); /* 2、初始化cdev并添加cdev */ mod_device.cdev.owner = THIS_MODULE; cdev_init(&mod_device.cdev,&beep_fops); ret = cdev_add(&mod_device.cdev,mod_device.devid,DeviceNodes); if(ret < 0){ printk("添加cdev失败!\r\n");goto fail_cdev; } /* 3、创建类 */ mod_device.class = class_create(THIS_MODULE,DeviceName); if (IS_ERR(mod_device.class)) { ret = PTR_ERR(mod_device.class);goto fail_class; } /* 4、创建设备节点*/ mod_device.device = device_create(mod_device.class,NULL,mod_device.devid,NULL,"DeviceName"); if (IS_ERR(mod_device.device)) { ret = PTR_ERR(mod_device.device);goto fail_device; } /* 5、初始化IO */ ret = key_init(&mod_device); if(ret < 0) { goto fail_keyinit; } /* 6、初始化原子变量 */ atomic_set(&mod_device.keyvalue, INVAKEY);/*mod_device.keyvalue=INVAKEY*/ atomic_set(&mod_device.releasekey, 0);/*mod_device.releasekey=0*/ /*X、模块加载失败处理部分*/ fail_keyinit: device_destroy(mod_device.class,mod_device.devid); fail_device: class_destroy(mod_device.class); fail_class: cdev_del(&mod_device.cdev); fail_cdev: unregister_chrdev_region(mod_device.devid,DeviceNodes); fail_chrdev: return ret; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit key0_interrupt_exit(void)/*按照相反顺序注销*/ { /*1、释放中断*/ free_irq(mod_device.irqkey0.irq_num, &mod_device); /*2、内核定定时器注销部分*/ del_timer_sync(&mod_device.timer);/* 删除timer */ /*3、LED设备注销部分*/ gpio_free(mod_device.irqkey0.gpio_num);/*注销gpio*/ /*4、设备驱动注销部分*/ device_destroy(mod_device.class,mod_device.devid);/*注销设备节点*/ class_destroy(mod_device.class);/*注销类*/ cdev_del(&mod_device.cdev);/*注销cdev*/ unregister_chrdev_region(mod_device.devid,DeviceNodes);/*注销chrdev*/ } module_init(key0_interrupt_init); module_exit(key0_interrupt_exit); MODULE_LICENSE("GPL"); |
七、APP测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> /* *argc:应用程序参数个数 *argv[]:具体的参数内容,字符串形式 *./imx6uirqAPP <filename> * ./imx6uirqAPP /dev/mx6uirq */ int main(int argc, char *argv[]) { int fd, ret; char *filename; unsigned char data; if(argc != 2) { printf("Error Usage!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("file %s open failed!\r\n", filename); return -1; } /* 循环读取 */ while(1) { ret = read(fd, &data, sizeof(data)); if(ret < 0) { } else { if(data) printf("key value = %#x\r\n", data); } } close(fd); return 0; } |
八、看完别走,你还有两件事没做
1、欢迎加入全国大学生电子交流群下载源码历程
2、点个赞呗