input输入子系统包含几个功能模块,其中就包含按键模块,在系统中常用按键功能。
配置
config内核配置
Device Drivers --->
Input device support --->
[*] Keyboards --->
-*- Generic input layer (needed for keyboard, mouse, ...)
[*] Keyboards --->
<*> GPIO Buttons
#
# Input Device Drivers
#
CONFIG_KEYBOARD_GPIO=y
drivers/input/keyboard/Kconfig
config KEYBOARD_GPIO
tristate "GPIO Buttons"
depends on GPIOLIB || COMPILE_TEST
help
This driver implements support for buttons connected
to GPIO pins of various CPUs (and some other chips).
Say Y here if your device has buttons connected
directly to such GPIO pins. Your board-specific
setup logic must also provide a platform device,
with configuration data saying which GPIOs are used.
To compile this driver as a module, choose M here: the
module will be called gpio_keys.
drivers/input/keyboard/Makefile
obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
DTS
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pcieb>;
home {
label = "Home Button";
gpios = <&gpio4 0 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
linux,code = <KEY_HOME>;
};
};
pinctrl_pcieb: pcieagrp{
fsl,pins = <
SC_P_PCIE_CTRL0_PERST_B_LSIO_GPIO4_IO00 0x06000021
>;
};
compatible:驱动兼容性名称,与驱动中.compatible字符串匹配。
pinctrl:GPIO复用配置设置,默认输入拉高。
label:子节点按键的描述名称。
gpios:按键GPIO规格属性,低有效,默认应该拉高。
gpio-key,wakeup;:标识该key可以唤醒系统。
linux,code:按键代码,定义在文件:include/uapi/linux/input-event-codes.h。#define KEY_HOME 102
gpio_keys节点中可以添加多个子节点,对应的添加描述信息。
dts描述文档路径:Documentation/devicetree/bindings/input/gpio-keys.txt
驱动
驱动文件:drivers/input/keyboard/gpio_keys.c
late_initcall(gpio_keys_init);
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = gpio_keys_of_match,
}
};
static const struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};
gpio-keys与设备中的compatible字符串匹配,加载prob探测函数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;
size_t size;
int i, error;
int wakeup = 0;
if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
1、gpio_keys_get_devtree_pdata获取dts设备数据key的节点信息,并获取节点个数,然后分配内存,子节点信息button与pdata关联, 返回pdata。
static struct gpio_keys_platform_data *
gpio_keys_get_devtree_pdata(struct device *dev)
{
struct gpio_keys_platform_data *pdata;
struct gpio_keys_button *button;
struct fwnode_handle *child;
int nbuttons;
nbuttons = device_get_child_node_count(dev);
if (nbuttons == 0)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(dev,
sizeof(*pdata) + nbuttons * sizeof(*button),
GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
button = (struct gpio_keys_button *)(pdata + 1);
pdata->buttons = button;
pdata->nbuttons = nbuttons;
pdata->rep = device_property_read_bool(dev, "autorepeat");
device_property_read_string(dev, "label", &pdata->name);
device_for_each_child_node(dev, child) {
if (is_of_node(child))
button->irq =
irq_of_parse_and_map(to_of_node(child), 0);
if (fwnode_property_read_u32(child, "linux,code",
&button->code)) {
dev_err(dev, "Button without keycode\n");
fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
fwnode_property_read_string(child, "label", &button->desc);
if (fwnode_property_read_u32(child, "linux,input-type",
&button->type))
button->type = EV_KEY;
button->wakeup =
fwnode_property_read_bool(child, "wakeup-source") ||
/* legacy name */
fwnode_property_read_bool(child, "gpio-key,wakeup");
button->can_disable =
fwnode_property_read_bool(child, "linux,can-disable");
if (fwnode_property_read_u32(child, "debounce-interval",
&button->debounce_interval))
button->debounce_interval = 5;
button++;
}
return pdata;
}
2、分配ddata内存空间,包含gpio_keys_drvdata和子节点的gpio_button_data。然后进行关联ddata->pdata = 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;
}
ddata->keymap = devm_kcalloc(dev,
pdata->nbuttons, sizeof(ddata->keymap[0]),
GFP_KERNEL);
if (!ddata->keymap)
return -ENOMEM;
3、分配和初始化input设备。
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->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;
input->keycode = ddata->keymap;
input->keycodesize = sizeof(ddata->keymap[0]);
input->keycodemax = pdata->nbuttons;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit);
4、遍历所有key/button,注册key/buton所需的资源(gpio、irq等)
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;
}
devm_fwnode_get_gpiod_from_child函数获取gpios = <&gpio4 0 GPIO_ACTIVE_LOW>;信息,赋值给bdata->gpiod。
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);
if (child) {
bdata->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL,
child,
GPIOD_IN,
desc);
设置debounce_interval延时去抖,没有设置默认5ms。dts没有配置irq信息,所以需要通过gpiod_to_irq函数根据GPIO号返回对应IRQ,然后bdata->irq = irq;。初始化button中断处理workqueue下半部分函数gpio_keys_gpio_work_func,设置isr = gpio_keys_gpio_isr;调度work,设置上升沿和下降沿触发中断模式IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;。
if (bdata->gpiod) {
if (button->debounce_interval) {
error = gpiod_set_debounce(bdata->gpiod,
button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
bdata->software_debounce =
button->debounce_interval;
}
if (button->irq) {
bdata->irq = button->irq;
} else {
irq = gpiod_to_irq(bdata->gpiod);
if (irq < 0) {
error = irq;
dev_err(dev,
"Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
return error;
}
bdata->irq = irq;
}
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
isr = gpio_keys_gpio_isr;
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
}
request_any_context_irq申请中断处理所需的资源,并激活该interrupt。
error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags,
desc, bdata);
int devm_request_any_context_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irq_devres *dr;
int rc;
dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
GFP_KERNEL);
if (!dr)
return -ENOMEM;
if (!devname)
devname = dev_name(dev);
rc = request_any_context_irq(irq, handler, irqflags, devname, dev_id);
if (rc < 0) {
devres_free(dr);
return rc;
}
dr->irq = irq;
dr->dev_id = dev_id;
devres_add(dev, dr);
return rc;
}
5、注册gpio-keys在sys文件系统下的访问接口属性,gpio-keys设备在sys文件系统路径为:/sys/devices/home.32。
error = devm_device_add_group(dev, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
return error;
}
6、注册input设备,初始化唤醒电源管理。
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);
中断
1、中断上半部分处理很简单,调用唤醒电源管理上报按键事件,延时去抖后调用workqueue的中断后半部函数,之前有初始化的,INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);。
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
BUG_ON(irq != bdata->irq);
if (bdata->button->wakeup) {
const struct gpio_keys_button *button = bdata->button;
pm_stay_awake(bdata->input->dev.parent);
if (bdata->suspended &&
(button->type == 0 || button->type == EV_KEY)) {
/*
* Simulate wakeup key press in case the key has
* already released by the time we got interrupt
* handler to run.
*/
input_report_key(bdata->input, button->code, 1);
}
}
mod_delayed_work(system_wq,
&bdata->work,
msecs_to_jiffies(bdata->software_debounce));
return IRQ_HANDLED;
}
2、中断下半部分使用workqueue方式,需要在合适的时机进行调用,gpiod_get_value_cansleep获取GPIO高低电平实时状态,并根据button的active_low状态将其转换为button的state,最后通过input子系统上报button按键事件。
static void gpio_keys_gpio_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work.work);
gpio_keys_gpio_report_event(bdata);
if (bdata->button->wakeup)
pm_relax(bdata->input->dev.parent);
}
static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned int type = button->type ?: EV_KEY;
int state;
state = gpiod_get_value_cansleep(bdata->gpiod);
if (state < 0) {
dev_err(input->dev.parent,
"failed to get gpio state: %d\n", state);
return;
}
if (type == EV_ABS) {
if (state)
input_event(input, type, button->code, button->value);
} else {
input_event(input, type, *bdata->code, state);
}
input_sync(input);
}
测试
修改dts,使用EMMC0_RESET_B管脚,TP32测试点。
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pcieb>;
home {
label = "Home Button";
gpios = <&gpio4 18 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
linux,code = <KEY_HOME>;
};
};
pinctrl_pcieb: pcieagrp{
fsl,pins = <
SC_P_EMMC0_RESET_B_LSIO_GPIO4_IO18 0x06000021
>;
};
内核启动打印:
[ 4.194693] input: sc-powerkey as /devices/platform/sc-powerkey/input/input0
[ 4.834497] input: gpio-keys as /devices/platform/gpio-keys/input/input1
root@genvict_imx8qxp:~# ls /sys/devices/platform/gpio-keys/input/input1/
capabilities event1 modalias phys properties uevent
device id name power subsystem uniq
root@genvict_imx8qxp:~# ls /dev/input/ -l
total 0
drwxr-xr-x 2 root root 80 Oct 8 2021 by-path
crw-rw---- 1 root input 13, 64 Oct 8 2021 event0
crw-rw---- 1 root input 13, 65 Oct 8 2021 event1
root@genvict_imx8qxp:~# ls /dev/input/by-path/
platform-gpio-keys-event platform-sc-powerkey-event
测试:测试点短接GND
root@genvict_imx8qxp:~# evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: sc-powerkey
/dev/input/event1: gpio-keys
Select the device event number [0-1]: 1
Input driver version is 1.0.1
Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100
Input device name: "gpio-keys"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 102 (KEY_HOME)
Properties:
Testing ... (interrupt to exit)
【TP32短接GND】
Event: time 1653383474.888850, type 1 (EV_KEY), code 102 (KEY_HOME), value 1
Event: time 1653383474.888850, -------------- SYN_REPORT ------------
Event: time 1653383475.324802, type 1 (EV_KEY), code 102 (KEY_HOME), value 0
Event: time 1653383475.324802, -------------- SYN_REPORT ------------
使用设备节点测试:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#define INPUT_DEV "/dev/input/event1"
int main(int argc, char * const argv[])
{
int fd = 0;
struct input_event event;
int ret = 0;
fd = open(INPUT_DEV, O_RDONLY);
while(1){
ret = read(fd, &event, sizeof(event));
if(ret == -1) {
perror("Failed to read.\n");
exit(1);
}
if(event.type != EV_SYN) {
printf("type:%d, code:%d, value:%d\n", event.type, event.code, event.value);
}
}
return 0;
}
root@genvict_imx8qxp:~# ./key-test
type:1, code:102, value:1
type:1, code:102, value:0
改为高电平有效:
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pcieb>;
home {
label = "Home Button";
gpios = <&gpio4 18 GPIO_ACTIVE_HIGH>;
gpio-key,wakeup;
linux,code = <KEY_HOME>;
};
};
pinctrl_pcieb: pcieagrp{
fsl,pins = <
SC_P_EMMC0_RESET_B_LSIO_GPIO4_IO18 0x06000041
>;
};
root@genvict_imx8qxp:~# evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: sc-powerkey
/dev/input/event1: gpio-keys
Select the device event number [0-1]: 1
Input driver version is 1.0.1
Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100
Input device name: "gpio-keys"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 102 (KEY_HOME)
Properties:
Testing ... (interrupt to exit)
Event: time 1653446456.074197, type 1 (EV_KEY), code 102 (KEY_HOME), value 1
Event: time 1653446456.074197, -------------- SYN_REPORT ------------
Event: time 1653446456.270220, type 1 (EV_KEY), code 102 (KEY_HOME), value 0
Event: time 1653446456.270220, -------------- SYN_REPORT ------------