Linux 驱动开发 三十三:Linux 按键输入

以正点原子 NPX 开发板进行实验。

一、原理分析

Linux 驱动开发 三十三:Linux 按键输入_第1张图片
Linux 驱动开发 三十三:Linux 按键输入_第2张图片
通过原理图分析可以得到,当按键按下KEY0低电平,当按键释放KEY0高电平。通过原理图可以确定 KEY0 连接在 UART1_CTS 引脚上。

二、修改设备树

1、查找引脚是否被使用

按键使用引脚如下:
Linux 驱动开发 三十三:Linux 按键输入_第3张图片
从设备树中查找是否 UART1_CTS 引脚:
Linux 驱动开发 三十三:Linux 按键输入_第4张图片
打开 imx6ull-lq-evk.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为 “pinctrl_key” 的子节点,节点 内容如下所示:

2、添加 pinctrl 节点

pinctrl_key: keygrp {
	fsl,pins = <
		MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
	>;
};

将引脚复用为 GPIO1_IO18,并将电气属性寄存器值设置为为 0xF080

3、添加 key 设备节点

在根节点 “/” 下创建 key 节点,节点名为 “key”,节点内容如下:

lq-key {
	compatible = "lq-key";
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
};

4、编译设备树

onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$ make dtbs
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  DTC     arch/arm/boot/dts/imx6ull-lq-evk.dtb
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$

5、测试

/sys/firmware/devicetree/base # ls
#address-cells                 lq-led
#size-cells                    memory
aliases                        model
backlight                      name
chosen                         pxp_v4l2
clocks                         regulators
compatible                     reserved-memory
cpus                           soc
gpioled                        sound
interrupt-controller@00a01000  spi4
lq-key
/sys/firmware/devicetree/base # cd lq-key/
/sys/firmware/devicetree/base/lq-key # ls
compatible     name           pinctrl-names
key-gpio       pinctrl-0      status
/sys/firmware/devicetree/base/lq-key # ls
compatible     name           pinctrl-names
key-gpio       pinctrl-0      status
/sys/firmware/devicetree/base/lq-key # cat compatible
lq-key
/sys/firmware/devicetree/base/lq-key #

三、驱动编写

1、配置工程

VSCode 中添加 Linux 源码中的头文件路径。打开 VSCode,按下 “Crtl+Shift+P” 打开 VSCode 的控制台,然后输入 “C/C++: Edit configurations(JSON) ”,打开 C/C++ 编辑配置文件,添加 Linux 源码头文件路径,修改后内容如下:

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include",
                "/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include",
                "/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

2、makefile

KERNELDIR := /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := key.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

3、驱动框架编写和测试

源码如下:

#include "linux/init.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"

#define KEYDEV_MAJOR 0  // 主设备号
#define KEYDEV_MINOR 0  // 次设备号
#define KEYDEV_CONUT 1  // 设备号个数
#define KEYDEV_NAME "keydev"    // 设备文件名

/* 驱动使用数据管理 */
typedef struct{
    int major;      // 主设备号
    int minor;      // 次设备号
    dev_t devid;    // 设备号
    struct cdev dev;
    struct class *class;
    struct device *device;
}keydev_t;

keydev_t keydev;

static const struct file_operations keydevops = {
    .owner   = THIS_MODULE,
};

/* 驱动加载函数 */
static int __init key_init(void)
{
    int ret = 0;
    /* 1、分配设备号 */
    keydev.major = KEYDEV_MAJOR;
    if(keydev.major){
        // 指定设备号
        keydev.minor = KEYDEV_MINOR;
        keydev.devid = MKDEV(keydev.major,keydev.minor);
        ret = register_chrdev_region(keydev.devid,KEYDEV_CONUT,KEYDEV_NAME);
    }else{
        // 分配设备号
        ret = alloc_chrdev_region(&keydev.devid,0,KEYDEV_CONUT,KEYDEV_NAME);
        keydev.major = MAJOR(keydev.devid);
        keydev.minor = MINOR(keydev.devid);
    }
    if(ret < 0){
        printk("keydev xxx_chrdev_region failed!\r\n");
        goto keydev_chrdev_region_failed;
    }
    printk("keydev major=%d,minor=%d\r\n",keydev.major,keydev.minor);
    
    /* 2、注册字符设备 */
    keydev.dev.owner = THIS_MODULE;
    cdev_init(&keydev.dev,&keydevops);
    ret = cdev_add(&keydev.dev,keydev.devid,KEYDEV_CONUT);
    if(ret < 0){
        printk("keydev cdev_add failed!\r\n");
        goto keydev_cdev_add_failed;
    }
    /* 3、创建类 */
    keydev.class = class_create(THIS_MODULE,KEYDEV_NAME);
    if (IS_ERR(keydev.class)) {
        printk("keydev class_create failed!\r\n");
        goto keydev_class_create_failed;
    }
    /* 4、创建设备 */
    keydev.device = device_create(keydev.class,NULL,keydev.devid,NULL,KEYDEV_NAME);
    if(IS_ERR(keydev.device)){
        printk("keydev device_create failed!\r\n");
        goto keydev_device_creat_failed;
    }

    return 0;
keydev_device_creat_failed:
    class_destroy(keydev.class);
keydev_class_create_failed:
    cdev_del(&keydev.dev);
keydev_cdev_add_failed:
    unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
keydev_chrdev_region_failed:
    return ret;
}

/* 驱动卸载函数 */
static void __exit key_exit(void)
{
    /* 4、删除设备 */
    device_destroy(keydev.class,keydev.devid);
    /* 3、删除类 */
    class_destroy(keydev.class);
    /* 2、注销字符设备 */
    cdev_del(&keydev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
}

module_init(key_init);
module_exit(key_exit);

MODULE_LICENSE("GPL");

编译:

onlylove@ubuntu:~/linux/driver/linux_driver/6_key$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/6_key modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
  CC [M]  /home/onlylove/linux/driver/linux_driver/6_key/key.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/onlylove/linux/driver/linux_driver/6_key/key.mod.o
  LD [M]  /home/onlylove/linux/driver/linux_driver/6_key/key.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/6_key$

测试:

/ # ls
bin          gpio-led.ko  led_dts.ko   mnt          sbin         usr
dev          key.ko       lib          proc         sys
etc          led          linuxrc      root         tmp
/ # lsmod
Module                  Size  Used by    Tainted: G
/ # ls /dev/keydev -l
ls: /dev/keydev: No such file or directory
/ # 
/ # 
/ # insmod key.ko
keydev major=248,minor=0
/ # lsmod
Module                  Size  Used by    Tainted: G
key                     1299  0
/ # ls /dev/keydev -l
crw-rw----    1 0        0         248,   0 Jan  1 01:06 /dev/keydev
/ #
/ #
/ # rmmod key.ko
/ # lsmod key.ko
Module                  Size  Used by    Tainted: G
/ # ls /dev/keydev -l
ls: /dev/keydev: No such file or directory
/ #

4、key驱动完善

#include "linux/init.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/of.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#include "linux/atomic.h"
#include "asm/uaccess.h"

#define KEYDEV_MAJOR 0  // 主设备号
#define KEYDEV_MINOR 0  // 次设备号
#define KEYDEV_CONUT 1  // 设备号个数
#define KEYDEV_NAME "keydev"    // 设备文件名

#define KEY0VALUE   0XF0    /* 按键值 */
#define INVAKEY     0X00    /* 无效的按键值 */

/* 驱动使用数据管理 */
typedef struct{
    int major;      // 主设备号
    int minor;      // 次设备号
    dev_t devid;    // 设备号
    struct cdev dev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int key_gpio;
    atomic_t keyvalue;
}keydev_t;

keydev_t keydev;

/* key 通过读取设备树 */
static int lq_key_init(void)
{
    keydev.nd = of_find_node_by_path("/lq-key");
    if(keydev.nd == NULL){
        return -EINVAL;
    }
    keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio", 0);
    if(keydev.key_gpio < 0){
        printk("can't get key0\r\n");
        return -EINVAL;
    }
    printk("key_gpio=%d\r\n", keydev.key_gpio);

    gpio_request(keydev.key_gpio, "key0");  // 请求 IO
    gpio_direction_input(keydev.key_gpio);  // 设置为输入 

    return 0;
}

static int key_open(struct inode *inode, struct file *filp)
{
    int ret = 0;

    filp->private_data = &keydev; 
    ret = lq_key_init();
    if (ret < 0) {
        return ret;
    }

    return 0;
}

static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char value;
    keydev_t *dev = filp->private_data;

    if (gpio_get_value(dev->key_gpio) == 0) {
        while(!gpio_get_value(dev->key_gpio));
        atomic_set(&dev->keyvalue, KEY0VALUE);
    }else{
        atomic_set(&dev->keyvalue, INVAKEY);
    }
    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf, &value, sizeof(value));

    return ret;
}

static const struct file_operations keydevops = {
    .owner   = THIS_MODULE,
    .open = key_open,
    .read = key_read,
};

/* 驱动加载函数 */
static int __init key_init(void)
{
    int ret = 0;
    /* 1、分配设备号 */
    keydev.major = KEYDEV_MAJOR;
    if(keydev.major){
        // 指定设备号
        keydev.minor = KEYDEV_MINOR;
        keydev.devid = MKDEV(keydev.major,keydev.minor);
        ret = register_chrdev_region(keydev.devid,KEYDEV_CONUT,KEYDEV_NAME);
    }else{
        // 分配设备号
        ret = alloc_chrdev_region(&keydev.devid,0,KEYDEV_CONUT,KEYDEV_NAME);
        keydev.major = MAJOR(keydev.devid);
        keydev.minor = MINOR(keydev.devid);
    }
    if(ret < 0){
        printk("keydev xxx_chrdev_region failed!\r\n");
        goto keydev_chrdev_region_failed;
    }
    printk("keydev major=%d,minor=%d\r\n",keydev.major,keydev.minor);
    
    /* 2、注册字符设备 */
    keydev.dev.owner = THIS_MODULE;
    cdev_init(&keydev.dev,&keydevops);
    ret = cdev_add(&keydev.dev,keydev.devid,KEYDEV_CONUT);
    if(ret < 0){
        printk("keydev cdev_add failed!\r\n");
        goto keydev_cdev_add_failed;
    }
    /* 3、创建类 */
    keydev.class = class_create(THIS_MODULE,KEYDEV_NAME);
    if (IS_ERR(keydev.class)) {
        printk("keydev class_create failed!\r\n");
        goto keydev_class_create_failed;
    }
    /* 4、创建设备 */
    keydev.device = device_create(keydev.class,NULL,keydev.devid,NULL,KEYDEV_NAME);
    if(IS_ERR(keydev.device)){
        printk("keydev device_create failed!\r\n");
        goto keydev_device_creat_failed;
    }

    return 0;
keydev_device_creat_failed:
    class_destroy(keydev.class);
keydev_class_create_failed:
    cdev_del(&keydev.dev);
keydev_cdev_add_failed:
    unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
keydev_chrdev_region_failed:
    return ret;
}

/* 驱动卸载函数 */
static void __exit key_exit(void)
{
    /* 4、删除设备 */
    device_destroy(keydev.class,keydev.devid);
    /* 3、删除类 */
    class_destroy(keydev.class);
    /* 2、注销字符设备 */
    cdev_del(&keydev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
}

module_init(key_init);
module_exit(key_exit);

MODULE_LICENSE("GPL");

四、应用程序编写

#include "stdio.h"
#include 
#include 
#include 

#define KEY0VALUE   0XF0

int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char keyvalue;

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }

    while(1){
        read(fd, &keyvalue, sizeof(keyvalue));
        if (keyvalue == KEY0VALUE){
            printf("KEY0 Press, value = %#X\r\n", keyvalue);
        }
    }


    return 0;
}

五、测试

/ # ls
bin      etc      key_app  linuxrc  proc     sbin     tmp
dev      key.ko   lib      mnt      root     sys      usr
/ #
/ # lsmod
Module                  Size  Used by    Not tainted
/ # ls -l /dev/keydev
ls: /dev/keydev: No such file or directory
/ #
/ # insmod key.ko
keydev major=248,minor=0
/ #
/ # lsmod
Module                  Size  Used by    Tainted: G
key                     1881  0
/ # ls -l /dev/keydev
crw-rw----    1 0        0         248,   0 Jan  1 01:59 /dev/keydev
/ #
/ # rmmod key.ko
/ #
/ # lsmod
Module                  Size  Used by    Tainted: G
/ # ls -l /dev/keydev
ls: /dev/keydev: No such file or directory
/ #
/ # insmod key.ko
keydev major=248,minor=0
/ #
/ # lsmod
Module                  Size  Used by    Tainted: G
key                     1881  0
/ # ls -l /dev/keydev
crw-rw----    1 0        0         248,   0 Jan  1 02:00 /dev/keydev
/ #
/ # ./key_app /dev/keydev
key_gpio=18
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
^C
/ #

通过以上日志可以确定,驱动可以正常运行。

六、说明

驱动仅仅为了测试功能,不够完善。在实际开发中也不会使用如此方法。在实际开发中会使用 Linuxinput 子系统完成。

你可能感兴趣的:(Linux驱动开发,linux)