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
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