内核版本:4.14.0
基于设备树、GPIO子系统、GPIO按键
#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>
#define DEVICE_NAME "ps_key0" /* Device name */
#define COMPAT_PROPT "navigator,ps_key0" /* Compatible property of the device matched with this driver. */
#define EVENT_CODE KEY_0 /* Event code */
/* Device information structure. */
struct input_info {
struct input_dev *p_inputdev;
struct timer_list timer;
int gpio_id;
int irq_id;
};
/* Interrupt handler */
static irqreturn_t irq_handler(int irq_id, void *arg)
{
struct input_info *p_input_info = (struct input_info *)arg;
/* Disable input interrupt */
disable_irq_nosync(p_input_info->irq_id);
mod_timer(&p_input_info->timer, jiffies+msecs_to_jiffies(15));
return IRQ_HANDLED;
}
/* Timer function */
static void timer_function(unsigned long arg)
{
struct input_info *p_input_info = (struct input_info *)arg;
int val;
val = gpio_get_value(p_input_info->gpio_id);
/* Report event */
input_report_key(p_input_info->p_inputdev, EVENT_CODE, !val);
input_sync(p_input_info->p_inputdev);
/* Enable input interrupt */
enable_irq(p_input_info->irq_id);
}
/*
* @description : Initialize the device.
* @param -pdev: Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int input_init(struct platform_device *pdev)
{
int ret;
unsigned long IntrTrigType;
struct input_info *p_input_info = platform_get_drvdata(pdev);
/* Get gpio id. */
p_input_info->gpio_id = of_get_named_gpio(pdev->dev.of_node, DEVICE_NAME"-gpio", 0);
if (!gpio_is_valid(p_input_info->gpio_id))
{
dev_err(&pdev->dev, "Failed to get gpio id!");
return -EINVAL;
}
printk(KERN_INFO "%s: gpio id = %d\n", DEVICE_NAME, p_input_info->gpio_id);
/*
* Request the use of GPIO from the gpio subsystem.
* Equivalent to gpio_request(), it is also used to request GPIO resources (pins) from the
* system. However, the function has the devm_ prefix, which means that it is a version of the function
* that includes device resource management. Therefore, when using it, the struct device pointer of the
* device needs to be specified, and there is no need to manually release GPIO when uninstalling the driver.
*/
ret = devm_gpio_request(&pdev->dev, p_input_info->gpio_id, DEVICE_NAME"-GPIO");
if (ret)
{
dev_err(&pdev->dev, "Failed to request GPIO!");
return ret;
}
/* Set GPIO as input mode */
gpio_direction_input(p_input_info->gpio_id);
/* Get intr id */
p_input_info->irq_id = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (!p_input_info->irq_id)
{
dev_err(&pdev->dev, "Failed to get intr id!\n");
return -EINVAL;
}
/* Get the trigger type from device tree. */
IntrTrigType = irq_get_trigger_type(p_input_info->irq_id);
if (IntrTrigType == IRQF_TRIGGER_NONE)
IntrTrigType = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
/* Request the intr */
return devm_request_irq(&pdev->dev, p_input_info->irq_id, irq_handler, IntrTrigType, "PS_KEY0 Intr", p_input_info);
}
/*
* @description : Probe function of the platform, it will be executed when the
* platform driver and platform device matching successfully.
* @param -pdev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int input_probe(struct platform_device *pdev)
{
int ret;
struct input_info *p_input_info;
dev_info(&pdev->dev, "Driver and device matched successfully!\n");
/* Allocate memory for struct led_info */
p_input_info = devm_kzalloc(&pdev->dev, sizeof(struct input_info), GFP_KERNEL);
if (!p_input_info)
return -ENOMEM;
/* Store the input_info pointer in pdev->dev.driver_data for later use */
platform_set_drvdata(pdev, p_input_info);
/* Input device init */
ret = input_init(pdev);
if (ret)
return ret;
/* Init timer */
init_timer(&p_input_info->timer);
p_input_info->timer.function = timer_function;
p_input_info->timer.data = (unsigned long)p_input_info;
/* Init the input_dev */
p_input_info->p_inputdev = devm_input_allocate_device(&pdev->dev);
if (!p_input_info->p_inputdev)
return -ENOMEM;
p_input_info->p_inputdev->name = DEVICE_NAME;
__set_bit(EV_KEY, p_input_info->p_inputdev->evbit); /* Key event */
__set_bit(EV_REP, p_input_info->p_inputdev->evbit); /* Repeated event */
__set_bit(EVENT_CODE, p_input_info->p_inputdev->keybit); /* Event-code */
/* Register input device */
return input_register_device(p_input_info->p_inputdev);
}
/*
* @description : Release some resources. This function will be executed when the platform
* driver module is unloaded.
* @param -dev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int input_remove(struct platform_device *pdev)
{
/* Get the struct input_info pointer which is stored in pdev->dev.driver_data before */
struct input_info *p_input_info = platform_get_drvdata(pdev);
/* Some Reset code */
/* Delete timer */
del_timer_sync(&p_input_info->timer);
/* Unregister the input device */
input_unregister_device(p_input_info->p_inputdev);
dev_info(&pdev->dev, "Driver has been removed!\n");
return 0;
}
/* Match table */
static const struct of_device_id device_of_match[] = {
{.compatible = COMPAT_PROPT},
{/* Sentinel */}
};
/*
* Declare device matching table. Note that this macro is generally used to dynamically
* load and unload drivers for hot-pluggable devices such as USB devices.
*/
MODULE_DEVICE_TABLE(of, device_of_match);
/* Platform driver struct */
static struct platform_driver input_driver = {
.driver = {
.name = DEVICE_NAME, //Drive name, used to match device who has the same name.
.of_match_table = device_of_match, //Used to match the device tree who has the same compatible property.
},
.probe = input_probe, //probe function
.remove = input_remove, //remove function
};
/*
* Register or unregister platform driver,
* and Register the entry and exit functions of the Module.
*/
module_platform_driver(input_driver);
/*
* Author, driver information and LICENSE.
*/
MODULE_AUTHOR("蒋楼丶");
MODULE_DESCRIPTION(DEVICE_NAME" Driver Based on Input Subsystem");
MODULE_LICENSE("GPL");