[ARM Linux 驱动开发] 按键输入实验

本篇文章介绍按键驱动的编写,以此巩固时隔已久的 pinctrl 和 gpio 子系统知识点(距离 [ARM Linux 驱动开发] 借助 pinctrl 和 gpio 子系统编写 LED 驱动 快一个月了)。

同时值得又写一篇文章的原因是,输入型的驱动我们之前没有接触过,在实验的过程中,我们会发现许多问题。这边先把问题抛出,后续会一一解决,目前只简单实现功能。

pinctrl 和 gpio 设备树设置

首先我们需要在设备树文件中配置按键相关的 pinctrl 和 gpio 设置,同样我们在之前移植的 arch/arm/boot/dts/imx6ull-my-emmc.dts 文件中进行修改。

如代码清单 1 所示,在 /iomuxc/imx6ul-evk 节点内的最后,添加我们的按键设置:设置 GPIO 复用和电气属性。

代码清单 1 pinctrl 设置
  • &iomuxc {
  •     pinctrl-names = "default";
  •     pinctrl-0 = <&pinctrl_hog_1>;
  •     imx6ul-evk {
  •         /* ………… */
  •         pinctrl_mykey: mykey {
  •             fsl,pins = <
  •                 MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xf080
  •             >;
  •         };
  •     };
  • };

如代码清单 2 所示,在根节点内的最后,添加我们的按键节点。其中 pinctrl-0 属性指向前面的按键 pinctrl 设置;key-gpio 属性指定 GPIO 组,后续 gpio 子系统的 gpio_request 操作函数会使用到。

代码清单 2 gpio 设置
  • / {
  •     model = "Freescale i.MX6 ULL 14x14 EVK Board";
  •     compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
  •     /* ………… */
  •     hanhankey {
  •         #address-cells = <1>;
  •         #size-cells = <1>;
  •         compatible = "hanhan-key";
  •         pinctrl-names = "default";
  •         pinctrl-0 = <&pinctrl_mykey>;
  •         key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
  •         status = "okay";        
  •     };
  • };

驱动代码

按键驱动代码如清单 3 所示,基本框架已经介绍过很多次了。我们关注到 keyio_init 函数,其中通过 of_find_node_by_path 读取到我们的按键节点信息(第 73 行);使用得到的按键节点,通过 of_get_named_gpio 得到 gpio 子系统中的 gpio 编号,之后 gpio 的操作都是基于此编号(第 80 行);gpio_request 申请此 gpio,已检查此 gpio 是否被占用,第二个参数是给此 gpio 起的名字(第 88 行);gpio_direction_input 设置 gpio 为输入(第 95 行)。

再看到 key_read 文件操作函数,注意此篇文章需要实现 read 函数了。gpio 的值通过 gpio_get_value 函数读取。这边我们检测到按键按下后,还等待其弹起。按键点击了,我们返回一个值,没有点击我们返回另一个值。

代码清单 3 按键驱动
  1. #include <linux/types.h>
  2. #include <linux/module.h>
  3. #include <linux/cdev.h>
  4. #include <linux/device.h>
  5. #include <linux/fs.h>
  6. #include <asm/io.h>
  7. #include <asm/uaccess.h>
  8. #include <linux/of.h>
  9. #include <linux/slab.h>
  10. #include <linux/of_address.h>
  11. #include <linux/of_gpio.h>
  12.  
  13. #define LED_CNT 1
  14. #define LED_NAME "mykey"
  15.  
  16. struct KeyDev
  17. {
  18.     /* 设备号相关 */
  19.     dev_t devId;
  20.     int major;
  21.     int minor;
  22.     /* cdev */
  23.     struct cdev cdev;
  24.     /* mdev */
  25.     struct class* class;
  26.     struct device* device;
  27.     /* GPIO 编号 */
  28.     int gpioKey;
  29. };
  30.  
  31. struct KeyDev keyDev;
  32.  
  33. static int key_open(struct inode* inode, struct file* filp)
  34. {
  35.     printk("led_open\n");
  36.     return 0;
  37. }
  38.  
  39. static int key_release(struct inode* inode, struct file* filp)
  40. {
  41.     printk("led_release\n");
  42.     return 0;
  43. }
  44.  
  45. ssize_t key_read(struct file* file, char __user* buf, size_t size, loff_t* ppos)
  46. {
  47.     int val, res;
  48.     if (gpio_get_value(keyDev.gpioKey) == 0)
  49.     {
  50.         while (gpio_get_value(keyDev.gpioKey) == 0); /* 等待按键释放 */
  51.         val = 0xFF;
  52.     }
  53.     else
  54.     {
  55.         val = 0;
  56.     }
  57.  
  58.     res = copy_to_user(buf, &val, sizeof(val));
  59.     return res;
  60. }
  61.  
  62. const struct file_operations led_fops =
  63. {
  64.     .owner = THIS_MODULE,
  65.     .open = key_open,
  66.     .release = key_release,
  67.     .read = key_read,
  68. };
  69.  
  70. static int keyio_init(void)
  71. {
  72.     int res;
  73.     struct device_node* nd = of_find_node_by_path("/hanhankey");
  74.     if (nd == NULL)
  75.     {
  76.         printk("of_find_node_by_path failed\n");
  77.         return -EINVAL;
  78.     }
  79.  
  80.     keyDev.gpioKey = of_get_named_gpio(nd, "key-gpio", 0);
  81.     if (keyDev.gpioKey < 0)
  82.     {
  83.         printk("of_get_named_gpio failed\n");
  84.         return -EINVAL;
  85.     }
  86.     printk("key gpio = %d\n", keyDev.gpioKey);
  87.  
  88.     res = gpio_request(keyDev.gpioKey, "key0");
  89.     if (res != 0)
  90.     {
  91.         printk("gpio_request failed\n");
  92.         return -EINVAL;
  93.     }
  94.  
  95.     gpio_direction_input(keyDev.gpioKey);
  96.     return 0;
  97. }
  98.  
  99. static int key_init(void)
  100. {
  101.     int res;
  102.     res = keyio_init();
  103.     if (res != 0)
  104.         return res;
  105.  
  106.     /////////////////////////////////////////////////////////////
  107.     /* 注册设备号 */
  108.     if (keyDev.major)
  109.     {
  110.         keyDev.devId = MKDEV(keyDev.major, keyDev.minor);
  111.         register_chrdev_region(keyDev.devId, LED_CNT, LED_NAME);
  112.     }
  113.     else
  114.     {
  115.         alloc_chrdev_region(&keyDev.devId, 0, LED_CNT, LED_NAME);
  116.         keyDev.major = MAJOR(keyDev.devId);
  117.         keyDev.minor = MINOR(keyDev.devId);
  118.     }
  119.     printk("major = %d, minor = %d\n", keyDev.major, keyDev.minor);
  120.  
  121.     /* 设置 cdev */
  122.     keyDev.cdev.owner = THIS_MODULE;
  123.     cdev_init(&keyDev.cdev, &led_fops);
  124.     cdev_add(&keyDev.cdev, keyDev.devId, LED_CNT);
  125.  
  126.     /* 设置 mdev */
  127.     keyDev.class = class_create(THIS_MODULE, LED_NAME);
  128.     if (IS_ERR(keyDev.class))
  129.     {
  130.         return PTR_ERR(keyDev.class);
  131.     }
  132.  
  133.     keyDev.device = device_create(keyDev.class, NULL, keyDev.devId, NULL, LED_NAME);
  134.     if (IS_ERR(keyDev.device))
  135.     {
  136.         return PTR_ERR(keyDev.device);
  137.     }
  138.  
  139.     return 0;
  140. }
  141.  
  142. static void key_exit(void)
  143. {
  144.     device_destroy(keyDev.class, keyDev.devId);
  145.     class_destroy(keyDev.class);
  146.  
  147.     cdev_del(&keyDev.cdev);
  148.     unregister_chrdev_region(keyDev.devId, LED_CNT);
  149.  
  150.     /* 释放 GPIO */
  151.     gpio_free(keyDev.gpioKey);
  152. }
  153.  
  154. module_init(key_init);
  155. module_exit(key_exit);
  156. MODULE_LICENSE("GPL");
  157. MODULE_AUTHOR("hanhan");

验证测试

代码清单 4 是我们编写的测试程序,这里我们一直循环读取按键状态,如果按下就打印出信息。

代码清单 4 测试程序
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6.  
  7. int main()
  8. {
  9.     int i;
  10.     int fd = open("/dev/mykey", O_RDWR);
  11.     if (fd < 0)
  12.     {
  13.         printf("open failed\n");
  14.         return -1;
  15.     }
  16.  
  17.     int buf = -1;
  18.  
  19.     while (1)
  20.     {
  21.         read(fd, &buf, sizeof(buf));
  22.         if (buf == 0xff)
  23.             printf("Trigger\n");
  24.     }
  25.  
  26. end_proc:
  27.     close(fd);
  28. }

图 1 是打印的信息,当按下按键,就会有提示信息产生。

图 1 打印信息

但是当前的程序存在问题,我们通过 top 命令可以看到测试应用程序的 CPU 占用率高达 99.6%。这是由于循环频繁读取所导致的,后续会介绍“新技术”来解决这一问题。

图 2 CPU 占用率