[linux驱动开发] 基于等待队列poll机制和input子系统的按键驱动开发

gpio读管脚和poll机制

设备树编写

  1. 添加按键节点,注意活跃电平与实际电路对应,要和后面驱动代码对应
	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
        };
    };
  1. 在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);
  1. linux驱动提供了一个gpiod转成其中断号的API,gpiod_to_irq(),返回即是该gpio的中断号。
  2. 申请中断函数,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 <weihuihui586@gmail.com>
 *      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 <weihuihui586@gmail.com>");
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 <weihuihui586@gmail.com>
 *      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 <weihuihui586@gmail.com>");
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 <weihuihui586@gmail.com>
 *      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就是内核驱动自己定义的含义。

完整代码可以留言或私发邮箱。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值