GPIO子系统(1)keys

        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 ------------

猜你喜欢

转载自blog.csdn.net/TSZ0000/article/details/124941001
今日推荐