The method of adding button driver to the device tree of the driver in the Linux system

​Hi everyone, a simple driver every day, as time goes on, I become more and more familiar with Linux drivers, and it becomes easier to learn to write drivers. Today I will do a simple button drive.

1. Button driving principle under Linux

The principle of button driver and LED driver are basically the same. They both operate GPIO, but one is to read the high and low levels of GPIO, and the other is to output high and low levels from GPIO. This time to implement key input, an integer variable is used in the driver to represent the key value. The application reads the key value through the read function to determine whether the key is pressed.

Here, the variable storing the key value is a shared resource, the driver program needs to write the key value into it, and the application program needs to read the key value. So we need to protect it. For integer variables, our first choice is atomic operations. We use atomic operations to assign and read variables. The principle of key driver under Linux is very simple, and then start to write the driver.

Note that the routine in this chapter is only to demonstrate the writing of GPIO input driver under Linux. The actual button driver will not use the method explained in this chapter. The input subsystem under Linux is specially used for input devices!

2. Hardware schematic analysis

picture

As can be seen from the picture above, the button KEY0 is connected to the UART1_CTS IO of I.MX6U. KEY0 is connected to a 10K pull-up resistor, so when KEY0 is not pressed, UART1_CTS should be high level. When KEY0 is pressed, After this, UART1_CTS is low level.

3. Development environment

  • CPU:IMX6ULL

  • Kernel version: Linux-5.19

4. Modify the device tree file

1. Add pinctrl node

The KEY on the I.MX6U-ALPHA development board uses the PIN UART1_CTS_B. Open imx6ul-14x14-evk.dtsi and create a sub-node named "pinctrl_key" under the imx6ul-evk sub-node of the iomuxc node. The node content is as follows Show:

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

In line 3, the PIN of GPIO_IO18 is multiplexed as GPIO1_IO18.

2. Add KEY device node

Create a KEY node under the root node "/", the node name is "key", and the content of the node is as follows:

key {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "imx6ull-key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
    status = "okay";
};
  • In line 6, the pinctrl-0 property sets the pinctrl node corresponding to the PIN used by KEY.

  • On line 7, the key-gpio attribute specifies the GPIO used by KEY.

3. Check whether the PIN is used by other peripherals

The PIN used by the button in this experiment is UART1_CTS_B, so first check whether the PIN UART1_CTS_B is used by other pinctrl nodes. If it is used, it must be blocked, and then check whether the GPIO GPIO1_IO18 is used by other peripherals. , if there is any, it should also be blocked.

After the device tree is written, use the "make dtbs" command to recompile the device tree, and then use the newly compiled imx6ull-toto.dtb file to start the Linux system. After successful startup, go to the "/proc/device-tree" directory to check whether the "key" node exists. If it exists, it means that the device tree has been basically modified successfully (specifically, driver verification is required). The results are as follows:

/ # ls /proc/device-tree/
#address-cells      clock-osc           pmu
#size-cells         compatible          regulator-can-3v3
aliases             cpus                regulator-peri-3v3
backlight-display   dts_led             regulator-sd1-vmmc
beep                key                 soc
chosen              memory@80000000     sound-wm8960
clock-cli           model               spi4
clock-di0           name                timer
clock-di1           panel

5. Writing button driver program

After the device tree is ready, you can write the driver. Enter the following content in key.c:

/************************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
************************************************************/

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define KEY_CNT         1       /* 设备号数量 */
#define KEY_NAME        "key"   /* 设备名字 */

/*定义按键值*/
#define KEY0_VALUE      0xF0    /* 按键值 */
#define INVAKEY         0x00    /* 无效按键值 */

/* key 设备结构体 */
struct key_dev
{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int key_gpio;           /* key 所使用的GPIO编号 */
    atomic_t keyvalue;      /* 按键值 */
};

struct key_dev keydev;      /* key 设备 */

/*
 * @Brief   初始化按键使用的GPIO管脚
 * @Call    Internal or External
 * @Param   
 * @Note    NOne
 * @RetVal  无
 */
int keyio_init(void)
{
    keydev.nd = of_find_node_by_path("/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 key-gpio\n");
        return -EINVAL;
    }
    printk("key_gpio:%d\n", keydev.key_gpio);

    /* c初始化 key 使用的IO */
    gpio_request(keydev.key_gpio, "key0");  /* 请求IO */
    gpio_direction_input(keydev.key_gpio);  /* 设置为输入 */

    return 0;
}

/*
 * @Brief   打开设备
 * @Call    Internal or External
 * @Param   inode:
 * @Param   filp:设备文件
 * @Note    NOne
 * @RetVal  0:成功 其他值:失败
 */
static int beep_open(struct inode *inode, struct file *filp)
{
    int ret = 0;

    /* 设置私有数据 */
    filp->private_data = &keydev;

    /* 初始化按键IO */
    ret = keyio_init();
    if(ret < 0)
    {
        return ret;
    }

    return 0;
}

/*
 * @Brief   从设备读数据
 * @Call    Internal or External
 * @Param   filp:要打开的设备文件描述符
 * @Param   buf:返回给用户空间的数据地址
 * @Param   cnt:要读取的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  读取的字节数,若为负值,表示读失败
 */
static ssize_t beep_read(struct file *filp, char __user *buf,
                        size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char value;
    struct key_dev *dev = filp->private_data;

    /* key0 按下 */
    if(gpio_get_value(dev->key_gpio) == 0)
    {
        /* 等待按键释放 */
        while(!gpio_get_value(dev->key_gpio));
        atomic_set(&dev->keyvalue, KEY0_VALUE);
    }
    else
    {
        /* 无效的按键值 */
        atomic_set(&dev->keyvalue, INVAKEY);
    }

    /* 保存按键值 */
    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf, &value, sizeof(value));
    
    return ret;
}

/*
 * @Brief   写数据到设备
 * @Call    Internal or External
 * @Param   filp:要打开的设备文件描述符
 * @Param   buf:要写入设备的数据地址
 * @Param   cnt:要写入的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  写入的字节数,若为负值,表示写失败
 */
static ssize_t beep_write(struct file *filp, const char __user *buf,
                        size_t cnt, loff_t *offt)
{
    return 0;
}


/*
 * @Brief   关闭/释放设备
 * @Call    Internal or External
 * @Param   inode:
 * @Param   filp:要关闭的设备文件描述符
 * @Note    NOne
 * @RetVal  NOne
 */
static int beep_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open  = beep_open,
    .read  = beep_read,
    .write = beep_write,
    .release = beep_release,
};

/*
 * @Brief   驱动入口函数
 * @Call    Internal or External
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init mykey_init(void)
{
    /* 初始化原子变量 */
    atomic_set(&keydev.keyvalue, INVAKEY);

    /* 注册字符设备驱动 */
    /* 创建设备号 */
    if(keydev.major) /* 定义了设备号 */
    {
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
    }
    else
    {
        /* 申请设备号 */
        alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);
        /* 获取主设备号 */
        keydev.major = MAJOR(keydev.devid);
        /* 获取次设备号 */
        keydev.minor = MINOR(keydev.devid);
    }
    printk("%s new_chrdev major:%d minor:%d\n", __func__,
            keydev.major, keydev.minor);

    /* 初始化cdev */
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);

    /* 添加一个cdev */
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);

    /* 创建类 */
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(keydev.class))
    {
        return PTR_ERR(keydev.class);
    }

    /* 创建设备 */
    keydev.device = device_create(keydev.class, NULL,
                        keydev.devid, NULL, KEY_NAME);
    if(IS_ERR(keydev.device))
    {
        return PTR_ERR(keydev.device);
    }

    return 0;
}

/*
 * @Brief   驱动出口函数
 * @Call    Internal or External
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit mykey_exit(void)
{
    /* 注销字符设备驱动 */
    gpio_free(keydev.key_gpio);
    /* 注销字符设备 */
    /* 删除cdev */
    cdev_del(&keydev.cdev);
    /* 释放分配的设备号 */
    unregister_chrdev_region(keydev.devid, KEY_CNT);

    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");

6. Write test APP

The specific code of key_app.c test program is as follows:

/********************************************
 *Description: 
 *Version: 1.0
 *Autor: toto
 *Date: Do not edit
 *LastEditors: Seven
 *LastEditTime: Do not edit
********************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#define KEY0VALUE   0xF0
#define INVAKEY     0x00

/*
 * @Brief   main 主程序
 * @Call    Internal or External
 * @Param   argc:
 * @Param   argv:
 * @Note    NOne
 * @RetVal  0-成功;其他-失败
 */
int main(int argc, char *argv[])
{
    int fd, retval;
    char *filename;
    unsigned char keyvalue;

    if(argc != 2)
    {
        printf("argc != 2\n");
        return -1;
    }

    filename = argv[1];

    /*打开驱动文件*/
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("open filename:%d failed\n", filename);
        return -1;
    }

    /* 要执行的操作:打开或关闭 */
    while(1)
    {
        read(fd, &keyvalue, sizeof(keyvalue));
        if(keyvalue == KEY0VALUE)
        {
            /* 按下 */
            printf("KEY0 down, value:0x%x\n", keyvalue);
        }
    }

    /*关闭文件*/
    close(fd);

    return 0;
}

7. Operation verification

Power on the development board, copy the two files key.ko and key_app to the /lib/modules/5.19.0-g794a2f7be62d-dirty/ directory, and enter the following command to load the key.ko driver module:

/ # insmod /lib/modules/5.19.0-g794a2f7be62d-dirty/key.ko 
[  108.608375] key_driver: loading out-of-tree module taints kernel.
[  108.619185] mykey_init new_chrdev major:242 minor:0

After the driver is loaded successfully, you can use the following command to test:

./key_app /dev/key

Press the KEY0 button on the development board, key_app will obtain and output the key information, as shown below:

/home/app # ./key_app /dev/key 
[  155.043576] key_gpio:18
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0

As can be seen from the above, when we press KEY0, it will print out "KEY0 down, value = 0XF0", indicating that the key is pressed. You will find that sometimes when you press KEY0 once, several lines of "KEY0 down, value = 0XF0" will be output. This is because our code does not perform key debounce processing. If you want to uninstall the driver, enter the following command:

rmmod key.ko

Guess you like

Origin blog.csdn.net/weixin_41114301/article/details/132776511