Input子系统:按键驱动
1. 环境:
1.1 开发板:正点原子 I.MX6U ALPHA V2.2
1.2 开发PC:Ubuntu20.04
1.3 U-boot:uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2
1.4 LInux内核:linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2
1.5 rootfs:busybox-1.29.0.tar.bz2制作
1.6 交叉编译工具链:gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
2. 硬件控制说明
2.1 由GPIO1 PIN18控制,高---松开, 低---按下
3. 设备树修改
3.1 在节点iomuxc/imx6ul-evk下新增pinctrl_key节点,如下
1 pinctrl_key: keygrp { 2 fsl,pins = < 3 MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 4 >; 5 };
3.2 在根目录"/"中创建"gpiokey"子节点
1 gpiokey { 2 compatible = "imx-gpiokeys"; 3 4 #address-cells = <1>; 5 #size-cells = <1>; 6 pinctrl-names = "default"; 7 pinctrl-0 = <&pinctrl_key>; 8 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; 9 interrupt-parent = <&gpio1>; 10 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; 11 status = "okay"; 12 };
3.3 检查设备树,将本GPIO做其他用途的属性注释掉
4. 驱动代码
1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> 4 #include <linux/ide.h> 5 #include <linux/init.h> 6 #include <linux/module.h> 7 #include <linux/errno.h> 8 #include <linux/gpio.h> 9 #include <linux/cdev.h> 10 #include <linux/device.h> 11 #include <linux/of.h> 12 #include <linux/of_address.h> 13 #include <linux/of_gpio.h> 14 #include <linux/input.h> 15 #include <linux/semaphore.h> 16 #include <linux/timer.h> 17 #include <linux/of_irq.h> 18 #include <linux/irq.h> 19 #include <asm/mach/map.h> 20 #include <asm/uaccess.h> 21 #include <asm/io.h> 22 23 24 //自定义一个结构体,将一些需要的变量放在其中,以便管理 25 struct key_device 26 { 27 struct device_node *node; //key 设备节点 28 struct input_dev *key_inputdev; //输入设备结构体 29 int key_gpio; //用于保存GPIO编号 30 int key_irqnum; //用于保存中断号 31 unsigned char key_value; //用于包括按键号 32 irqreturn_t (*handler)(int, void *); //中断处理函数指针 33 struct timer_list timer; //定时器 34 }; 35 36 struct key_device key_in; 37 38 //中断处理函数 39 static irqreturn_t key_handler(int irq, void *dev_id) 40 { 41 struct key_device *key0 = (struct key_device *)dev_id; 42 key0->timer.data = (volatile long)dev_id; 43 44 //启动定时器,延时10ms,相当于是按键滤波 45 mod_timer(&key0->timer, jiffies + msecs_to_jiffies(10)); 46 47 return IRQ_RETVAL(IRQ_HANDLED); 48 } 49 50 //定时器处理函数,当按键滤波完成,并且确定产生按键动作时,此函数用于上报按键值 51 void timer_function(unsigned long arg) 52 { 53 unsigned char value; 54 55 struct key_device *key = (struct key_device *)arg; 56 57 //根据GPIO编号,获取当前读取到的值 58 value = gpio_get_value(key->key_gpio); 59 60 //上报按键值,1--松开 0--按下 61 if(0 == value) 62 { 63 input_report_key(key->key_inputdev, key->key_value, 1); 64 input_sync(key->key_inputdev); 65 } 66 else 67 { 68 input_report_key(key->key_inputdev, key->key_value, 0); 69 input_sync(key->key_inputdev); 70 } 71 72 } 73 74 75 static int __init gpiokey_init(void) 76 { 77 int ret = 0; 78 79 //获取按键节点 80 key_in.node = of_find_node_by_path("/gpiokey"); 81 if(key_in.node == NULL) 82 { 83 printk("key node find fail\n"); 84 return -EINVAL; 85 } 86 87 //获取GPIO编号 88 key_in.key_gpio = of_get_named_gpio(key_in.node, "key-gpio", 0); //key-gpio为dts下gpiokey节点的key-gpio属性 89 if(key_in.key_gpio < 0) 90 { 91 printk("gpio get fail\n"); 92 } 93 94 gpio_request(key_in.key_gpio,"key0"); //申请GPIO 95 gpio_direction_input(key_in.key_gpio); //设置GPIO为输入 96 key_in.key_irqnum = irq_of_parse_and_map(key_in.node, 0); //获取中断号 97 98 key_in.handler = key_handler; //中断函数,上半部 99 key_in.key_value = KEY_0; 100 101 //注册中断 102 ret = request_irq(key_in.key_irqnum, key_in.handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "KEY0", &key_in); 103 if(ret < 0) 104 { 105 printk("request_irq fail,the irqnum is %d\n",key_in.key_irqnum); 106 return -EFAULT; 107 } 108 109 init_timer(&key_in.timer); //初始化定时器,此时时钟不会运行,等待mod_timer函数唤醒 110 key_in.timer.function = timer_function; //定时器处理函数 111 112 //申请一个输入设备 113 key_in.key_inputdev = input_allocate_device(); 114 key_in.key_inputdev->name = "keyinputdev"; 115 116 //设置事件 117 key_in.key_inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 118 input_set_capability(key_in.key_inputdev, EV_KEY, KEY_0); //KEY_0--事件码,include\uapi\linux\input.h 119 120 ret = input_register_device(key_in.key_inputdev); 121 if(ret < 0) 122 { 123 printk("input_register_device fail\n"); 124 return ret; 125 } 126 127 return 0; 128 } 129 130 static void __exit gpiokey_exit(void) 131 { 132 gpio_free(key_in.key_gpio); 133 del_timer_sync(&key_in.timer); 134 free_irq(key_in.key_irqnum,&key_in); 135 input_unregister_device(key_in.key_inputdev); 136 input_free_device(key_in.key_inputdev); 137 138 } 139 140 module_init(gpiokey_init); 141 module_exit(gpiokey_exit); 142 MODULE_LICENSE("Dual BSD/GPL");
5. 测试代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <linux/input.h> 7 8 int main(int argc, char **argv) 9 { 10 int fd,err; 11 static struct input_event key_inputevent; 12 13 14 fd = open("/dev/input/event1", O_RDWR); 15 16 printf("fd = %d\n", fd); 17 if(fd < 0) 18 { 19 perror("open fail!\n"); 20 21 exit(1); 22 } 23 24 while(1) 25 { 26 err = read(fd, &key_inputevent, sizeof(key_inputevent)); 27 if(err < 0) 28 { 29 printf("read fail\n"); 30 return -1; 31 } 32 else 33 { 34 switch(key_inputevent.type) 35 { 36 case EV_KEY: 37 printf("key input code is %x && value is %x\n", key_inputevent.code, key_inputevent.value); 38 break; 39 default: 40 break; 41 } 42 } 43 44 } 45 46 close(fd); 47 return 0; 48 }
总结:
1. 此input之系统按键驱动,涉及到了中断、定时器
2. 本驱动中断处理放在了上半部,如果是其他比较耗时的中断处理,需要放到中断下半部
3. input子系统产生的文件为/dev/input/eventxx,具体是哪个,可以未加载ko前先查看,然后再加载查看;是否可能指定文件名?不然有多个input ko该如何快速有效区分?
4. 本驱动大概流程为:1.设备树增加pinctrl_、按键节点-->2.获取设备树中的资源-->3.申请、设置GPIO-->4.注册中断-->5.输入设备申请、设置-->6.中断处理函数