基于等待队列poll机制和input子系统的按键驱动开发
gpio读管脚和poll机制
设备树编写
- 添加按键节点,注意活跃电平与实际电路对应,要和后面驱动代码对应
my_key {
compatible = "my_key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
status = "okay";
key0 {
gpios = <&gpio4 14 GPIO_ACTIVE_HIGH>; //按下 实际电平为低, high即高电平为1,低为0
interrupt-parent = <&gpio4>;
interrupts = <14 IRQ_TYPE_EDGE_BOTH>; //FALLING RISING
};
};
- 在iomux中添加
pinctrl_gpio_keys: gpio-keys {
fsl,pins = <
MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0x17059 /* gpio key */
>;
};
- 这种方式用于,gpio的电平读取,硬件电路中按键按下为低电平,令活跃电平为HIHG,即驱动中读到的gpiod_get到的value为0,在按键按下时候,和一般习惯也相同。此处不明白,看到驱动部分也会有相应理解的源码。
设备树信息获取
中断引入
- 按键的触发除去使用最占用cpu资源的轮询读取,另外一种便是中断方式,中断的原理此处不详细展开,很多资料有讲解。可以简要说的就是,有一块硬件可以替你把管脚的电平变化告诉cpu,并且让cpu去处理开发者自己预先设计好的处理程序。
- linux驱动中的中断驱动,第一步当然是获取中断号,这是不同中断的唯一编号,以示区别。
- 然后就是中断的初始化和申请中断,告诉cpu这个中断发生该干嘛。
- 直接贴上代码:
//从gpio中获取中断号
priv->keys[i].irq = gpiod_to_irq(gpiod);
dev_info(dev, "key%d:irqnum=%d\n", i, priv->keys[i].irq);
priv->keys[0].handler = key0_handler;
//申请中断
rv = request_irq(priv->keys[i].irq, priv->keys[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, priv->keys[i].name, priv);
- linux驱动提供了一个gpiod转成其中断号的API,gpiod_to_irq(),返回即是该gpio的中断号。
- 申请中断函数,request_irq(),中断号、中断处理程序的句柄、中断触发方式、等信息
定时器引入
- 如果有单片机按键开发的朋友,知道按键触发的中断是不稳的,按一次会有很多次中断触发,此时就需要引入延时进行消抖。
- 在linux内核中时间是很宝贵的,使用轮询等待,很浪费时间的,此时定时器的作用就体现出来。发生一次中断,就开启一次定时器,10ms定时器到时再进行电平读取。
- 定时器基本操作
注意: 我使用的5.10版本的内核,原本的timer初始化方法init_timer不能使用,提供了timer_setup进行初始化。还有定时器回调函数发生了变化,参数发生了变化。
//初始定时器
timer_setup(&priv->timer, key_timer_function, 0);
...
void key_timer_function(struct timer_list *t)
{
...
key_value = gpiod_get_value(priv->keys[i].key_gpiod);
if(key_value == 0) //按下按键
{
priv->keys[i].value = 0;
//printk("key[%d] had been pressed!\n", i);
}
else //松开按键
{
priv->keys[i].value = 1;
priv->keys[i].releasekey = 1;
//printk("key[%d] didn't be pressed!\n", i);
//唤醒队列
wake_up_interruptible(&priv->r_wait);
}
- 在定时器中进行gpio的电平读取,和前面的设备树对应起来,关于gpiod的用法可以参考我的led驱动,使用的也是gpiod的方式写的驱动。
等待队列和阻塞
- 等待队列的引入,最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。这就是和轮询相比的优势所在。
- 等待队列把进程挂起,使用的方式我用思维逻辑图简单绘制
- 主要理解了等待队列的作用,就知道为何需要使用他。在应用空间的read系统调用的阻塞进入内核空间阻塞住,很多也是使用的等待队列机制。
poll机制
- poll机制的引入,在linux的APUE学习中,引入过单个进程多个read阻塞的情况,使用多路复用的系统调用select、poll、epoll,可以监听多个文件描述符的变化,由阻塞到唤醒的变化。
- 并且poll最底层也是把进行进行添加到等待队列中,直到被外部事件唤醒
- poll机制的详细的分析可以单独开一篇研究。
- 不过在poll的驱动接口代码中需要做的事情不多,只需要调用poll_wait,把自己定义的等待队列添加进去,然后满足自己的返回条件时候,把掩码返回。
static unsigned int key_poll(struct file *filp, struct poll_table_struct *poll_table)
{
unsigned int mask = 0;
struct miscdevice *miscdev = filp->private_data;
struct gpio_keys_priv *priv = dev_get_drvdata(miscdev->this_device);
unsigned char key_release = priv->keys[0].releasekey;
if(!key_release) //可以直接poll_Wait,但是内核poll会进行两次遍历,调用驱动poll
{
poll_wait(filp, &priv->r_wait, poll_table);
}
else
{
priv->keys[0].releasekey = 0;
mask = POLLIN | POLLRDNORM;
}
return mask;
}
测试代码
- 应用空间测试代码,肯定是要使用poll系统调用进行的。
- 为了效果明显,就和led的驱动进行的联合测试。
/*********************************************************************************
* Copyright: (C) 2022 weihh
* All rights reserved.
*
* Filename: key_led_app_test.c
* Description: This file is key app test code.
*
* Version: 1.0.0(2022年03月31日)
* Author: Wei Huihong <[email protected]>
* ChangeLog: 1, Release initial version on "2022年03月31日 15时21分29秒"
*
********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define KEY_DEV "/dev/my_key0"
#define DEVNAME_LEN 30
#define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
int main(int argc, char *argv[])
{
int fd_key = -1;
int fd_led0 = -1;
int fd_led1 = -1;
char dev_name[DEVNAME_LEN];
int val = 0;
int rv = -1;
int cnt_key = 0;
struct pollfd fds;
//打开led设备
memset(dev_name, 0, sizeof(dev_name));
snprintf(dev_name, sizeof(dev_name), "/dev/my_led0");
fd_led0 = open(dev_name, O_RDWR, 0755);
if(fd_led0 < 0)
{
printf("file %s open failure!\n", dev_name);
goto err_led0;
}
memset(dev_name, 0, sizeof(dev_name));
snprintf(dev_name, sizeof(dev_name), "/dev/my_led1");
fd_led1 = open(dev_name, O_RDWR, 0755);
if(fd_led1 < 0)
{
printf("file %s open failure!\n", dev_name);
goto err_led1;
}
printf("open fd_led0 : [%d] and fd_led1 : [%d] successfully.\n", fd_led0, fd_led1);
//打开按键设备
fd_key = open(KEY_DEV, O_RDONLY);
if(fd_key < 0)
{
printf("open %s failure .\n", KEY_DEV);
goto err_key;
}
printf("open dev [fd_key: %d] successfully.\n", fd_key);
fds.fd = fd_key;
fds.events = POLLIN;
while(1)
{
printf("start poll.\n");
rv = poll(&fds, 1, -1);
if(rv < 0)
{
printf("poll failure.\n");
}
else if(0 == rv)
{
printf("time out.\n");
}
else if(rv > 0)
{
rv = read(fd_key, &val, sizeof(val));
if(rv < 0)
{
printf("read val failure.\n");
}
else
{
printf("key press read [%d]byte, val = [%d] .\n",rv , val);
if(0 == (cnt_key % 2))
{
ioctl(fd_led0, LED_ON, 0);
ioctl(fd_led1, LED_OFF, 1);
}
else
{
ioctl(fd_led0, LED_OFF, 0);
ioctl(fd_led1, LED_ON, 1);
}
cnt_key++;
}
}
}
err_key:
close(fd_key);
err_led1:
close(fd_led1);
err_led0:
close(fd_led0);
return 0;
}
部分源码
- 因为代码和比较完美的版本有一定差距,只贴部分代码作为参考学习。
- 需要完整代码可私聊或留言邮箱。
#define MISCKEY0_NAME "my_key0"
#define MISCKEY0_MINOR 57
#define TIMER_TIMEOUT 10
struct gpio_key_data {
char name[16]; /* key name */
struct gpio_desc *key_gpiod; /* gpio description */
unsigned int irq; /* irq num */
unsigned char value; /* key value 0 - press 1 - no press */
unsigned char releasekey; /* a vaild pressed */
irqreturn_t (*handler)(int, void *); /* irq handler */
};
struct gpio_keys_priv {
int num_keys; //keys num
struct timer_list timer; /* a timer */
wait_queue_head_t r_wait; /* wair queue head */
struct gpio_key_data keys[];
};
/* @description: key0 中断处理函数
*
* @parm : irq - 中断号
* @parm : dev_id - 设备结构,请求时传入
* @return: 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct gpio_keys_priv *priv = (struct gpio_keys_priv *)dev_id;
int found = 0;
if(irq == priv->keys[0].irq)
{
found = 1;
}
if(!found)
return IRQ_RETVAL(IRQ_NONE);
//启动定时器,10ms定时
mod_timer(&priv->timer, jiffies + msecs_to_jiffies(TIMER_TIMEOUT));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description: 定时器服务函数,用于按键消抖,定时器到达后再次读取按键值
*
* @parm : t - timer_list 结构体指针,即使用timer的地址
* @parm :
* @return: void
*/
void key_timer_function(struct timer_list *t)
{
unsigned char key_value = 0;
struct gpio_keys_priv *priv = from_timer(priv, t, timer);
int num_keys = priv->num_keys;
int i = 0;
//printk("num_keys : %d\n", priv->num_keys);
for (i = 0; i < num_keys; i++)
{
key_value = gpiod_get_value(priv->keys[i].key_gpiod);
if(key_value == 0) //按下按键
{
priv->keys[i].value = 0;
//printk("key[%d] had been pressed!\n", i);
}
else //松开按键
{
priv->keys[i].value = 1;
priv->keys[i].releasekey = 1;
//printk("key[%d] didn't be pressed!\n", i);
//唤醒队列
wake_up_interruptible(&priv->r_wait);
}
}
}
/* @description: 从设备读取文件
*
* @parm : filp - 设备文件,文件描述符
* @parm : buf - 返回给用户空间的数据缓冲区
* @parm : cnt - 要读取的数据长度
* @parm : offt - 相对于文件首地址的偏移
* @return: 读取的字节数,负数 - 读取失败
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
struct miscdevice *miscdev;
struct gpio_keys_priv *priv;
unsigned char value_key = 0;
int ret = 0;
miscdev = filp->private_data;
priv = dev_get_drvdata(miscdev->this_device); //取出私有数据
value_key = priv->keys[0].value;
// dev_info(miscdev->this_device, "value_key : [%d]\n", value_key);
ret = copy_to_user(buf, &value_key, sizeof(value_key));
return sizeof(value_key);
}
/* @description: poll函数
*
* @parm : filp - 要打开的设备文件 fd
* @parm : poll_table - 等待列表
* @return: 设备或者资源状态,mask掩码相关
*/
static unsigned int key_poll(struct file *filp, struct poll_table_struct *poll_table)
{
unsigned int mask = 0;
struct miscdevice *miscdev = filp->private_data;
struct gpio_keys_priv *priv = dev_get_drvdata(miscdev->this_device);
unsigned char key_release = priv->keys[0].releasekey;
if(!key_release) //可以直接poll_Wait,但是内核poll会进行两次遍历,调用驱动poll
{
poll_wait(filp, &priv->r_wait, poll_table);
}
else
{
priv->keys[0].releasekey = 0;
mask = POLLIN | POLLRDNORM;
}
return mask;
}
//设备操作函数
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.poll = key_poll,
.release = key_release,
};
//misc 设备结构体
static struct miscdevice key0_miscdev = {
.minor = MISCKEY0_MINOR,
.name = MISCKEY0_NAME,
.fops = &key_fops,
};
/* @description: platform驱动的probe函数,安装platform驱动时候此函数执行
*
* @parm : - dev platform设备
* @parm :
* @return: 0 successfully , !0 failure
*/
static int imx_key_probe(struct platform_device *pdev)
{
struct gpio_keys_priv *priv = NULL;
int rv = 0;
//1. 初始化IO,初始化中断状态
rv = paser_dt_init_key(pdev);
if(rv < 0)
{
return rv;
}
priv = platform_get_drvdata(pdev);
//2. 注册misc设备
rv = misc_register(&key0_miscdev);
if(rv < 0)
{
dev_err(&pdev->dev, "misc key0 device register failure.\n");
return -EFAULT;
}
dev_set_drvdata(key0_miscdev.this_device, priv);
dev_info(&pdev->dev, "misc gpio keys driver probe okay.\n");
return 0;
}
/* @description: platform驱动的remove函数,移除platform驱动时候此函数执行
*
* @parm : - dev platform设备
* @parm :
* @return: 0 successfully , !0 failure
*/
static int imx_key_remove(struct platform_device *pdev)
{
struct gpio_keys_priv *priv = platform_get_drvdata(pdev);
int i = 0;
for (i = 0; i < priv->num_keys; i++)
{
free_irq(priv->keys[i].irq, priv); //释放中断号
devm_gpiod_put(&pdev->dev, priv->keys[i].key_gpiod);//释放gpio
}
del_timer(&priv->timer);
//kfree(priv);//释放堆 TODO: 释放的函数需要注意位置,会导致系统崩溃
misc_deregister(&key0_miscdev);//注销misc设备
devm_kfree(&pdev->dev, priv); //使用的devm_kzalloc,建议使用devm_kfree释放堆
dev_info(&pdev->dev, "misc gpio keys driver remove.\n");
return 0;
}
//匹配列表
static const struct of_device_id of_gpio_key_match[] = {
{.compatible = "my_key"},
{},
};
MODULE_DEVICE_TABLE(of, of_gpio_key_match);
//platform驱动结构体
static struct platform_driver gpio_key_driver = {
.probe = imx_key_probe,
.remove = imx_key_remove,
.driver = {
.name = "imx_key",
.of_match_table = of_gpio_key_match,
},
};
/* @description:platform key 模块加载函数
*
* @parm : void
* @parm :
* @return: 0 successfully , !0 failure
*/
static int __init platdrv_key_init(void)
{
int rv = 0;
rv = platform_driver_register(&gpio_key_driver);
if(rv)
{
printk(KERN_ERR "%s():%d: Can't register platform driver %d \n", __FUNCTION__, __LINE__, rv);
return rv;
}
printk("Regist imx key Platform Driver successfully!\n");
return 0;
}
/* @description: platdrv_key 模块卸载函数
*
* @parm : void
* @parm :
* @return: void
*/
static void __exit platdrv_key_exit(void)
{
printk(KERN_ERR "%s():%d: remove Key platform driver\n", __FUNCTION__, __LINE__);
platform_driver_unregister(&gpio_key_driver);
}
module_init(platdrv_key_init);
module_exit(platdrv_key_exit);
MODULE_AUTHOR("Wei Huihong <[email protected]>");
MODULE_DESCRIPTION("i.MX6ULL key driver platform driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx_platdrv_key");
input子系统的使用
input_dev的使用
- 前面的输入按键电平的读取,其实需要花费很多功夫去编写驱动,其实内核提供了一个input子系统,可以对输入事件进行管理
- 主要API和注册input设备的流程
input_event的上报
-
结构体相关信息,最后与应用空间的交互,都是以该结构体呈现出来,详情可见测试代码。
-
事件的上报,关键事件事件常用函数
关于input子系统的其他操作,可见源码部分
源码
/*********************************************************************************
* Copyright: (C) 2022 weihh
* All rights reserved.
*
* Filename: platdrv_key_input.c
* Description: This file is platform key with input system driver code.
*
* Version: 1.0.0(2022年03月08日)
* Author: Wei Huihong <[email protected]>
* ChangeLog: 1, Release initial version on "2022年03月08日 20时12分12秒"
*
********************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/version.h>
#include <linux/ide.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/input.h>
#define KEYINPUT_NAME "keyinput"
#define TIMER_TIMEOUT 10
struct gpio_key_data {
char name[16]; /* key name */
struct gpio_desc *key_gpiod; /* gpio description */
unsigned short code; /* key code */
unsigned int irq; /* irq num */
irqreturn_t (*handler)(int, void *); /* irq handler */
};
struct gpio_keys_priv {
int num_keys; //keys num
struct timer_list timer; /* a timer */
struct input_dev *input;
struct gpio_key_data keys[];
};
/* @description: key0 中断处理函数
*
* @parm : irq - 中断号
* @parm : dev_id - 设备结构,请求时传入
* @return: 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct gpio_keys_priv *priv = (struct gpio_keys_priv *)dev_id;
int found = 0;
if(irq == priv->keys[0].irq)
{
found = 1;
}
if(!found)
return IRQ_RETVAL(IRQ_NONE);
//启动定时器,10ms定时
mod_timer(&priv->timer, jiffies + msecs_to_jiffies(TIMER_TIMEOUT));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description: 计算私有数据大小
*
* @parm : num_keys - key 数量
* @parm :
* @return: size 大小
*/
static inline int sizeof_gpio_keys_priv(int num_keys)
{
return sizeof(struct gpio_keys_priv) + (sizeof(struct gpio_key_data) * num_keys);
}
/* @description: 定时器服务函数,用于按键消抖,定时器到达后再次读取按键值
*
* @parm : t - timer_list 结构体指针,即使用timer的地址
* @parm :
* @return: void
*/
void key_timer_function(struct timer_list *t)
{
unsigned char key_value = 0;
struct gpio_keys_priv *priv = from_timer(priv, t, timer);
int num_keys = priv->num_keys;
int i = 0;
//printk("num_keys : %d\n", priv->num_keys);
for (i = 0; i < num_keys; i++)
{
key_value = gpiod_get_value(priv->keys[i].key_gpiod);
if(key_value == 0) //按下按键
{
input_event(priv->input, EV_KEY, priv->keys[0].code, 1);
}
else //松开按键
{
input_event(priv->input, EV_KEY, priv->keys[0].code, 0);
}
input_sync(priv->input);
}
}
/* @description: 解析设备树,初始化key硬件io
*
* @parm : pdev - platform 设备指针
* @parm :
* @return: 0 successfully , !0 failure
*/
static int paser_dt_init_key(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *fw_child = NULL;//固件字节点
struct gpio_keys_priv *priv = NULL;
struct gpio_desc *gpiod;
struct input_dev *input = NULL;
int num_keys = 0;
int i = 0;
int rv = 0;
num_keys = device_get_child_node_count(dev);
if(num_keys <= 0)
{
dev_err(dev, "No keys gpio assign.\n");
return -ENODEV;
}
priv = devm_kzalloc(dev, sizeof_gpio_keys_priv(num_keys), GFP_KERNEL);
if(!priv)
{
return -ENOMEM;
}
device_for_each_child_node(dev, fw_child){
gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, fw_child,
GPIOD_ASIS,
NULL);
if(IS_ERR(gpiod))
{
dev_err(dev, "get gpiod from fw_child failure.\n");
fwnode_handle_put(fw_child);
return -ENOMEM;
}
strncpy(priv->keys[i].name, fwnode_get_name(fw_child), sizeof(priv->keys[i].name));
priv->keys[i].key_gpiod = gpiod;
//初始化key使用的IO,设置为中断模式
if(gpiod_direction_input(priv->keys[i].key_gpiod))
{
devm_gpiod_put(dev, priv->keys[i].key_gpiod);
dev_err(dev, "gpiod [%d] name [%s] set input failure.\n", i, priv->keys[i].name);
continue;
}
//从gpio中获取中断号
priv->keys[i].irq = gpiod_to_irq(gpiod);
dev_info(dev, "key%d:irqnum=%d\n", i, priv->keys[i].irq);
//初始化 中断处理函数 和 按键事件码
priv->keys[0].handler = key0_handler;
priv->keys[0].code = KEY_0;
//申请中断
rv = request_irq(priv->keys[i].irq, priv->keys[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, priv->keys[i].name, priv);
if(rv != 0)
{
dev_err(dev, "key:%s irq[%d] request failure", priv->keys[i].name, priv->keys[i].irq);
return -EFAULT;
}
i++;
}
priv->num_keys = i;
//申请input_dev
input = devm_input_allocate_device(dev);
if(!input)
{
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
//初始化input_dev
input->name = KEYINPUT_NAME;
input->dev.parent = dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
__set_bit(EV_KEY, input->evbit);//设置产生按键事件
__set_bit(EV_REP, input->evbit);//重复事件
//按键0 设置
__set_bit(priv->keys[0].code, input->keybit);
//注册 input_dev
rv = input_register_device(input);
if(rv)
{
dev_err(dev, "Unable to register input device, error: %d\n", rv);
return rv;
}
priv->input = input;
//初始定时器
timer_setup(&priv->timer, key_timer_function, 0);
dev_info(dev, "paser dts got %d vaild keys.\n", i);
platform_set_drvdata(pdev, priv);
return 0;
}
/* @description: platform驱动的probe函数,安装platform驱动时候此函数执行
*
* @parm : - dev platform设备
* @parm :
* @return: 0 successfully , !0 failure
*/
static int imx_key_probe(struct platform_device *pdev)
{
// struct gpio_keys_priv *priv = NULL;
int rv = 0;
//1. 初始化IO,初始化中断状态
rv = paser_dt_init_key(pdev);
if(rv < 0)
{
return rv;
}
dev_info(&pdev->dev, "input gpio keys driver probe okay.\n");
return 0;
}
/* @description: platform驱动的remove函数,移除platform驱动时候此函数执行
*
* @parm : - dev platform设备
* @parm :
* @return: 0 successfully , !0 failure
*/
static int imx_key_remove(struct platform_device *pdev)
{
struct gpio_keys_priv *priv = platform_get_drvdata(pdev);
int i = 0;
for (i = 0; i < priv->num_keys; i++)
{
free_irq(priv->keys[i].irq, priv); //释放中断号
devm_gpiod_put(&pdev->dev, priv->keys[i].key_gpiod);//释放gpio
}
del_timer(&priv->timer);
//注销 input_dev 并释放 input_dev
input_unregister_device(priv->input);
input_free_device(priv->input);
devm_kfree(&pdev->dev, priv); //使用的devm_kzalloc,建议使用devm_kfree释放堆
dev_info(&pdev->dev, "input gpio keys driver remove.\n");
return 0;
}
//匹配列表
static const struct of_device_id of_gpio_key_match[] = {
{.compatible = "my_key"},
{},
};
MODULE_DEVICE_TABLE(of, of_gpio_key_match);
//platform驱动结构体
static struct platform_driver gpio_key_driver = {
.probe = imx_key_probe,
.remove = imx_key_remove,
.driver = {
.name = "imx_key",
.of_match_table = of_gpio_key_match,
},
};
/* @description:platform key 模块加载函数
*
* @parm : void
* @parm :
* @return: 0 successfully , !0 failure
*/
static int __init platdrv_key_init(void)
{
int rv = 0;
rv = platform_driver_register(&gpio_key_driver);
if(rv)
{
printk(KERN_ERR "%s():%d: Can't register platform driver %d \n", __FUNCTION__, __LINE__, rv);
return rv;
}
printk("Regist imx key Platform Driver successfully!\n");
return 0;
}
/* @description: platdrv_key 模块卸载函数
*
* @parm : void
* @parm :
* @return: void
*/
static void __exit platdrv_key_exit(void)
{
printk(KERN_ERR "%s():%d: remove Key platform driver\n", __FUNCTION__, __LINE__);
platform_driver_unregister(&gpio_key_driver);
}
module_init(platdrv_key_init);
module_exit(platdrv_key_exit);
MODULE_AUTHOR("Wei Huihong <[email protected]>");
MODULE_DESCRIPTION("i.MX6ULL key driver platform driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx_platdrv_key_input");
测试代码
- 源码
/*********************************************************************************
* Copyright: (C) 2022 weihh
* All rights reserved.
*
* Filename: key_led_input_app.c
* Description: This file is input app test.
*
* Version: 1.0.0(2022年04月09日)
* Author: Wei Huihong <[email protected]>
* ChangeLog: 1, Release initial version on "2022年04月09日 22时36分30秒"
*
********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
static struct input_event inputevent;
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
int err = 0;
char *filename;
filename = argv[1];
if(argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while (1) {
err = read(fd, &inputevent, sizeof(inputevent));
if (err > 0) { /* 读取数据成功 */
switch (inputevent.type) {
case EV_KEY:
if (inputevent.code < BTN_MISC) { /* 键盘键值 */
printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
} else {
printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
}
break;
/* 其他类型的事件,自行处理 */
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
}
} else {
printf("读取数据失败\r\n");
}
}
return 0;
}
- 代码里面就是就可以看到前面提到的input_event,可以看到先判断type、后判断code、最后根据value进行数据的处理,该value就是内核驱动自己定义的含义。
完整代码可以留言或私发邮箱。