Linux 驱动开发 四十九:Linux 自带按键驱动程序
一、自带按键驱动程序源码简析Linux 内核也自带了 KEY 驱动,如果要使用内核自带的 KEY 驱动的话需要配置 Linux 内核,不过 Linux 内核一般默认已经使能了KEY驱动,但是我们还是要检查一下。按照如下路径 找到相应的配置选项:-> Device Drivers-> Input device support-> Generic input layer (neede
一、自带按键驱动程序源码简析
Linux 内核也自带了 KEY 驱动,如果要使用内核自带的 KEY 驱动的话需要配置 Linux 内核,不过 Linux 内核一般默认已经使能了KEY驱动,但是我们还是要检查一下。按照如下路径 找到相应的配置选项:
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons
选中“GPIO Buttons”选项,将其编译进 Linux 内核中,如下图所示:
选中以后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIO=y”这一行,Linux 内核就会根据这一行来将 KEY 驱动文件编译进 Linux 内核。
Linux 内核自带的 KEY 驱动文件为 drivers/input/keyboard/gpio_keys.c,gpio_keys.c 采用了 platform 驱动框架,在 KEY 驱动上使用 了 input 子系统实现。
在 gpio_keys.c 文件中找到如下所示内容:
static const struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = of_match_ptr(gpio_keys_of_match),
}
};
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
static void __exit gpio_keys_exit(void)
{
platform_driver_unregister(&gpio_keys_device_driver);
}
从以上代码可以看出,这就是一个标准的 platform 驱动框架,如果要使用设备树来描述 KEY 设备信息的话,设备节点的 compatible 属性值要设置为“gpio-keys”。当设备和驱 动匹配以后 gpio_keys_probe 函数就会执行。gpio_keys_probe 函数内容如下:
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
size_t size;
int i, error;
int wakeup = 0;
if (!pdata) {
/* 从设备树中获取到 KEY 相关的设备节点 */
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
size = sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data);
ddata = devm_kzalloc(dev, size, GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n");
return -ENOMEM;
}
/* 申请 input_dev */
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
ddata->pdata = pdata;
ddata->input = input;
mutex_init(&ddata->disable_lock);
platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata);
/* 初始化 input_dev */
input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->open = gpio_keys_open;
input->close = gpio_keys_close;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit); // 设置了EV_REP 事件
for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
/* 设置 input_dev 的 EV_KEY 事件码 (设置EV_KEY事件以及KEY的按键类型) */
error = gpio_keys_setup_key(pdev, input, bdata, button);
if (error)
return error;
if (button->wakeup)
wakeup = 1;
}
error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
return error;
}
/* 向 Linux 系统注册 input_dev */
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto err_remove_group;
}
device_init_wakeup(&pdev->dev, wakeup);
return 0;
err_remove_group:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error;
}
一切都准备就绪以后剩下的就是等待按键按下,然后向 Linux 内核上报事件,事件上报是在 gpio_keys_irq_isr 函数中完成的,此函数内容如下:
// gpio_keys_irq_isr 是按键中断处理函数
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned long flags;
BUG_ON(irq != bdata->irq);
spin_lock_irqsave(&bdata->lock, flags);
if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);
input_event(input, EV_KEY, button->code, 1);
input_sync(input);
if (!bdata->release_delay) {
/* 向 Linux 系统上报 EV_KEY 事件,表示按键按下。 */
input_event(input, EV_KEY, button->code, 0);
/* 使用 input_sync 函数向系统上报EV_REP 同步事件 */
input_sync(input);
goto out;
}
bdata->key_pressed = true;
}
if (bdata->release_delay)
mod_timer(&bdata->release_timer,
jiffies + msecs_to_jiffies(bdata->release_delay));
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
二、自带按键驱动程序的使用
要使用 Linux 内核自带的按键驱动程序很简单,只需要根据 Documentation/devicetree/bindings/input/gpio-keys.txt 这个文件在设备树中添加指定的设备节点即可,节点要求如下:
1、节点名字为“gpio-keys”。
2、gpio-keys 节点的 compatible 属性值一定要设置为“gpio-keys”。
3、所有的 KEY 都是 gpio-keys 的子节点,每个子节点可以用如下属性描述自己:
- gpios:KEY 所连接的GPIO信息。
- interrupts:KEY所使用GPIO 中断信息,不是必须的,可以不写。
- label:KEY 名字
- linux,code:KEY要模拟的按键
4、如果按键要支持连按的话要加入 autorepeat。
三、示例
gpio_keys: gpio_keys@0 {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key1@1 {
label = "USER-KEY1";
linux,code = <114>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
};
};
更多推荐
所有评论(0)