#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input-event-codes.h>
#define PS_KEY0_CODE KEY_0
/* 自定义的按键设备结构体 */
struct mykey_dev {
struct input_dev *idev; // 按键对应的input_dev指针
struct timer_list timer; // 消抖定时器
int gpio; // 按键对应的gpio编号
int irq; // 按键对应的中断号
};
//定时器服务函数,用于按键消抖,定时时间到了以后再读取按键值,根据按键的状态上报相应的事件
//arg参数可以在初始化定时器的时候进行配置
static void key_timer_function(struct timer_list* arg)
{
int val=0;
//获取包含定时器结构体的首地址(这里指的是struct mykey_dev的地址,这样你才能知道是那个gpio产生的事件
//若你不获取struct mykey_dev地址,最后上报input事件的时候会出现系统异常)
struct mykey_dev *key = from_timer(key,arg,timer);
/* 读取按键值并上报按键事件 */
val = gpio_get_value(key->gpio);
input_report_key(key->idev,PS_KEY0_CODE, !val); // 上报按键事件
input_sync(key->idev); // 同步事件
/* 使能按键中断 */
enable_irq(key->irq);
}
//按键中断服务函数
//参数1:触发该中断事件对应的中断号
//参数2:arg参数可以在申请中断的时候进行配置
//返回值:中断执行结果
static irqreturn_t mykey_interrupt(int irq, void *arg)
{
struct mykey_dev *key = (struct mykey_dev *)arg;
/* 判断触发中断的中断号是否是按键对应的中断号 */
if (key->irq != irq)
return IRQ_NONE;
/* 按键防抖处理,开启定时器延时15ms */
disable_irq_nosync(irq); // 禁止按键中断 ????????????
//内核定时器并不是周期运行的,超时后就会自动关闭,因此如果需要周期性使用,
//需要在定时处理函数中重新开启
//重启定时器,修改定时值,该函数会激活定时器
//参数1:需要修改的定时器
//参数2:修改定时的时间
//jiffies/HZ为系统运行时间(秒)
//msecs_to_jiffies() 将ms/us/ns转换为jiffies类型
//注:在mykey_probe函数中初始化定时器 timer_setup
mod_timer(&key->timer, jiffies + msecs_to_jiffies(15));
return IRQ_HANDLED;
}
/*
* @description : 按键初始化函数
* @param – pdev : platform设备指针
* @return : 成功返回0,失败返回负数
*/
static int mykey_init(struct platform_device *pdev)
{
struct mykey_dev *key = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
unsigned long irq_flags=0;
int ret=0;
/* 从设备树中获取GPIO */
key->gpio = of_get_named_gpio(dev->of_node,"key-gpio",0);
if(!gpio_is_valid(key->gpio)) {
dev_err(dev, "Failed to get gpio");
return -EINVAL;
}
/* 申请使用GPIO */
ret = devm_gpio_request(dev, key->gpio, "Key Gpio");
if (ret) {
dev_err(dev, "Failed to request gpio");
return ret;
}
/* 将GPIO设置为输入模式 */
gpio_direction_input(key->gpio);
/* 获取GPIO对应的中断号 */
//key->irq = irq_of_parse_and_map(dev->of_node, 0);
key->irq=gpio_to_irq(key->gpio);
if (!key->irq)
return -EINVAL;
/* 获取设备树中指定的中断触发类型 */
irq_flags = irq_get_trigger_type(key->irq);
if (IRQF_TRIGGER_NONE == irq_flags)
irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
/* 申请中断 */
return devm_request_irq(dev, key->irq, mykey_interrupt, irq_flags, "PS_Key0 IRQ", key);
}
/*
* @description : platform驱动的probe函数,当驱动与设备
* 匹配成功以后此函数会被执行
* @param – pdev : platform设备指针
* @return : 0,成功;其他负值,失败
*/
static int mykey_probe(struct platform_device *pdev)
{
struct mykey_dev *key;
struct input_dev *idev;
int ret=0;
//启动过程、或者模块加载过程等通知类的信息等,一般只通知一次
//参数1:设备的详细信息
dev_info(&pdev->dev, "Key device and driver matched successfully!\n");
/* 为key指针分配内存 */
//和设备有关的函数,当设备被卸载时,分配的内存会被自动释放
//参数1:内存空间和绑定的dev,卸载时,内存空间正常释放
//GFP_KERNEL:内核空间中正常的内存分配
//返回值:申请成功返回内存指针,失败返回NULL
key = devm_kzalloc(&pdev->dev, sizeof(struct mykey_dev), GFP_KERNEL);
if (!key)
return -ENOMEM;
//用于将key指针存放到pdev->dev.中去
platform_set_drvdata(pdev, key);
//* 初始化按键 */
ret = mykey_init(pdev);
if (ret)
return ret;
/* 定时器初始化 */
timer_setup(&key->timer,key_timer_function,0);
/* input_dev初始化 */
//先申请一个input_dev结构体变量,该函数申请的变量在设备卸载时会自动释放
idev = devm_input_allocate_device(&pdev->dev);
if (!idev)
return -ENOMEM;
key->idev = idev;
idev->name = "mykey"; //设置input_dev名字
__set_bit(EV_KEY, idev->evbit); // 可产生按键事件
__set_bit(EV_REP, idev->evbit); // 可产生重复事件
__set_bit(PS_KEY0_CODE, idev->keybit); // 可产生KEY_0按键事件
/* 注册按键输入设备 */
return input_register_device(idev);
}
/*
* @description : platform驱动的remove函数,当platform驱动模块
* 卸载时此函数会被执行
* @param – dev : platform设备指针
* @return : 0,成功;其他负值,失败
*/
static int mykey_remove(struct platform_device *pdev)
{
//用于将pdev指针存放到key中去
struct mykey_dev *key = platform_get_drvdata(pdev);
//删除定时器:该函数会等待其他处理器使用完定时器再删除,
//该函数不能再中断上下文中使用,区别与del_timer
del_timer_sync(&key->timer);
/* 卸载按键设备 */
input_unregister_device(key->idev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id key_of_match[] = {
{ .compatible = "alientek,key" },
{ /* Sentinel */ } //最后一个元素一定要为空
};
/* platform驱动结构体 */
static struct platform_driver mykey_driver = {
.driver = {
.name = "zynq-key", // 驱动名字,用于和设备匹配
.of_match_table = key_of_match, // 设备树匹配表,用于和设备树中定义的设备匹配
},
.probe = mykey_probe, // probe函数
.remove = mykey_remove, // remove函数
};
module_platform_driver(mykey_driver);
MODULE_AUTHOR("lnb");
MODULE_DESCRIPTION("input");
MODULE_LICENSE("GPL");
ZYNQ7020 linux下input 子系统框架来处理输入事件
于 2024-01-11 00:15:26 首次发布