STM32MP157驱动开发——Linux自带的按键驱动(补丁篇)
参考文章:STM32MP157驱动开发——Linux input 子系统
0.前言
上一节使用了 input 子系统开发了按键驱动,但是在 Linux 内核中,对于这种常见的字符设备,也提供了内置的驱动。这一节就当做一个按键驱动开发的小补丁,单列出来以后实际开发中便于查询。
一、开启内核 KEY 驱动
要使用内核自带的 KEY 驱动,需要先配置内核,默认已经开启,但还是需要检查一下。在 menuconfig 中的Device Drivers --> Input device support --> Generic input layer(needed for keyboard, mouse, ...)(INPUT [=y]) --> Keyboards (INPUT_KEYBOARD [=y]) --> GPIO Buttons
目录下,选中 GPIO Buttons 并使能,将其编译进 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", },
{ },
};
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.shutdown = gpio_keys_shutdown,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = gpio_keys_of_match,
.dev_groups = gpio_keys_groups,
}
};
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 函数就会执行。
1.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 fwnode_handle *child = NULL;
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
int i, error;
int wakeup = 0;
if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
......
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
.....
input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = 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);
for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
if (!dev_get_platdata(dev)) {
child = device_get_next_child_node(dev, child);
if (!child) {
dev_err(dev,
"missing child device node for entry %d\n", i);
return -EINVAL;
}
}
error = gpio_keys_setup_key(pdev, input, ddata, button, i, child);
if (error) {
fwnode_handle_put(child);
return error;
}
if (button->wakeup)
wakeup = 1;
}
fwnode_handle_put(child);
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n", error);
return error;
}
device_init_wakeup(dev, wakeup);
return 0;
}
①调用 gpio_keys_get_devtree_pdata 函数从设备树中获取到 KEY 相关的设备节点信息
②使用 devm_input_allocate_device 函数申请 input_dev
③初始化 input_dev
④设置 input_dev 事件,这里设置了 EV_REP 事件
⑤调用 gpio_keys_setup_key 函数继续设置 KEY,此函数会设置 input_dev 的 EV_KEY 事件以及事件码(也就是 KEY 模拟哪个按键)
⑥调用 input_register_device 函数向 Linux 系统注册 input_dev
2. gpio_keys_setup_key 函数
static int gpio_keys_setup_key(struct platform_device *pdev,
struct input_dev *input,
struct gpio_keys_drvdata *ddata,
const struct gpio_keys_button *button,
int idx,
struct fwnode_handle *child)
{
const char *desc = button->desc ? button->desc : "gpio_keys";
struct device *dev = &pdev->dev;
struct gpio_button_data *bdata = &ddata->data[idx];
irq_handler_t isr;
unsigned long irqflags;
int irq;
int error;
bdata->input = input;
bdata->button = button;
spin_lock_init(&bdata->lock);
....
bdata->code = &ddata->keymap[idx];
*bdata->code = button->code;
input_set_capability(input, button->type ?: EV_KEY,*bdata->code);
......
return 0;
}
①调用 input_set_capability 函数设置 EV_KEY 事件以及 KEY 的按键类型,也就
是 KEY 作为哪个按键。这部分在设备树里面设置,指定的 KEY 作为哪个按键
3. gpio_keys_irq_isr 函数
此函数用于向 Linux 内核上报事件
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
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, *bdata->code, 1);
input_sync(input);
if (!bdata->release_delay) {
input_event(input, EV_KEY, *bdata->code, 0);
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;
}
gpio_keys_irq_isr 是按键中断处理函数,使用 input_event 向 Linux 系统上报 EV_KEY 事件,表示按键按下。使用 input_sync 函数向系统上报 EV_REP 同步事件。
总结
Linux 内核自带的 gpio_keys.c 驱动文件思路和上一节编写的 keyinput.c 驱动文件基本一致。都是申请和初始化 input_dev,设置事件,向 Linux 内核注册 input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。
二、驱动使用
在 Documentation/devicetree/bindings/input/gpio-keys.txt 这个文件中有按键驱动程序的用法指导,只需要在设备树中添加指定的设备节点即可,节点要求如下:
①节点名字为“gpio-keys”
②gpio-keys 节点的 compatible 属性值一定要设置为“gpio-keys”
③所有的 KEY 都是 gpio-keys 的子节点,每个子节点可以用如下属性描述:
gpios:KEY 所连接的 GPIO 信息
interrupts:KEY 所使用 GPIO 中断信息,不是必须的,可以不写
label:KEY 名字
linux,code:KEY 要模拟的按键,也就是上一节 include/uapi/linux/input-event-codes.h 文件列举出的按键值
④如果按键要支持连按则要加入 autorepeat
1.设备树文件修改
本节就将开发板上的三个按键都是用起来,首先在设备树 stm32mp157d-atk.dts 文件中添加头文件:“ dt-bindings/input/input.h”,此文件包含了“linux,code”属性的按键宏定义。
再创建设备树节点:
gpio-keys {
compatible = "gpio-keys";
princtrl-names = "default";
pinctrl-0 = <&key_pins_a>;
autorepeat;
key0 {
label = "GPIO Key L";
linux,code = <KEY_L>;
gpios = <&gpiog 3 GPIO_ACTIVE_LOW>;
};
key1 {
label = "GPIO Key S";
linux,code = <KEY_S>;
gpios = <&gpioh 7 GPIO_ACTIVE_LOW>;
};
key_up {
label = "GPIO Key Enter";
linux,code = <KEY_ENTER>;
gpios = <&gpioa 0 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
};
};
①autorepeat 表示按键支持连按
②KEY0 按键,名字设置为“GPIO Key L”,这里将开发板上的 KEY0 按键设置为“EKY_L”这个按键,也就是‘L’键,效果和键盘上的‘L’键一样
③KEY1 按键设置,模拟键盘上的‘S’键
④WKUP 按键,模拟键盘上的‘回车’键,注意,WKUP 按键连接的 PA0 引脚,WKUP 按下去以后是高电平,因此这里设置高电平有效
⑤检查一下设备树看看这些引脚有没有被用到其他外设上,如果有的话要删除或注释掉相关代码
然后编译设备树文件,使用新编译出的设备树启动开发板。
2.测试
使用内置的按键驱动,系统启动以后就会在 /dev/input 目录下存在一个 event0 文件,可以使用 hexdump 命令对按键进行测试。
三个按键都可以输出相关的信息,相关分析在上一节叙述过,这里就不多赘述。