gpio-key驱动分析

前言

Linux内核中的gpio-keys.c(driver/input/keyboard/gpio-keys.c)统一了所有关于按键的驱动实现方式。其良好的代码架构可以兼容几乎所有平台的关于按键的处理流程。如果需要在目标平台实现关于按键的驱动程序,完全可以直接使用该驱动,几乎不用自己实现任何代码。

测试平台

本文介绍的代码在以下平台进行测试:

  • Host:Ubuntu14.04
  • Target:Firefly-rk3288
  • Compiler:arm-linux-android-gcc

架构

gpio-keys驱动基于Linux内核的input子系统实现,设备驱动以platform_device的方式注册到系统中。驱动对于按键基于中断的处理方式实现,并且通过input子系统将按键事件上报到应用层,供应用程序解析使用。

DTS配置

位于 Documentation/devicetree/bindings/gpio/gpio-keys.txt介绍了对于gpio-keys驱动程序的Device-Tree bingdings。其支持的属性定义如下,关于DTS基本语法的总结可以参见

Required properties

  • - compatible = “gpio-keys”;
    该属性定义了设备的兼容性。

Optional properties

  • -autorepeat: Boolean,启动input子系统的auto repeat特性。

Subnode properties
每一个button(key)都对应为gpio-keys的一个子节点,子节点的属性包括:

  • - gpios: device-tree gpio规格属性。
  • - label: key的描述性名称。
  • - linux,code: input子系统所定义的按键代码,参见:include/dt-bindings/input/input.h关于keys和buttons的code定义。

Optional subnode-properties

  • -linux,input-type:定义该key/button所依赖的event type(input子系统定义),默认为1 == EV_KEY。
  • -debounce-interval:定义该key/button的去抖间隔,默认为5ms。
  • -gpio-key,wakeup:Boolean,标识该key可以唤醒系统,例如,Android系统的power-key。

Example nodes:

    gpio_keys_test { 
    compatible = "gpio-keys";
    #address-cells = <1>; 
    #size-cells = <0>; 
    autorepeat;

    powerkey { 
        label = "power key";
        linux,code = <116>;
        gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
        gpio-key,wakeup;
        debounce-interval = <5>; 
    };   
};    

基本数据结构

/* key/button的基本配置参数 */

struct gpio_keys_button {
	
	unsigned int code;	/* input event code (KEY_*, SW_*) */
	int gpio;		/* -1 if this key does not support gpio */
	int active_low;
	const char *desc;
	unsigned int type;	/* input event type (EV_KEY, EV_SW, EV_ABS) */
	int wakeup;		/* configure the button as a wake-up source */
	int debounce_interval;	/* debounce ticks interval in msecs */
	bool can_disable;
	int value;		/* axis value for EV_ABS */
	unsigned int irq;	/* Irq number in case of interrupt keys */
};

/*key/button控制逻辑配置参数*/

struct gpio_button_data {
		const struct gpio_keys_button *button;
		struct input_dev *input;
		struct timer_list timer;
		struct work_struct work;
		unsigned int timer_debounce;	/* in msecs */
		unsigned int irq;
		spinlock_t lock;
		bool disabled;
		bool key_pressed;
};

/*key/button platform配置参数*/

struct gpio_keys_platform_data {
	struct gpio_keys_button *buttons;
	int nbuttons;
	unsigned int poll_interval;	/* polling interval in msecs -
					   for polling driver only */
	unsigned int rep:1;		/* enable input subsystem auto repeat */
	int (*enable)(struct device *dev);
	void (*disable)(struct device *dev);
	const char *name;		/* input device name */
};

/*key/button plaform_device data配置参数,该结构作为platform data注册到platform设备总线*/

struct gpio_keys_drvdata {
	const struct gpio_keys_platform_data *pdata;
	struct input_dev *input;
	struct mutex disable_lock;
	struct gpio_button_data data[0];
};

设备注册

gpio-keys驱动是以platform_driver的身份注册到系统中的,所以其需要定义platfrom_driver结构,如下:

static struct platform_driver gpio_keys_device_driver = {
	.probe		= gpio_keys_probe,//gpio-keys驱动初始化函数
	.remove		= gpio_keys_remove,//gpio-keys驱动卸载处理函数
	.driver		= {
		.name	= "gpio-keys",
		.owner	= THIS_MODULE,
		.pm	= &gpio_keys_pm_ops,
		.of_match_table = of_match_ptr(gpio_keys_of_match),//定义驱动的兼容属性,具体定义如下:
	}
};

static struct of_device_id gpio_keys_of_match[] = {
	{ .compatible = "gpio-keys", },
	{ },
};

设备probe流程

下面主要分析一下驱动的probe主要流程,较为细节的代码请参照内核代码。

static int gpio_keys_probe(struct platform_device *pdev)
{
	... ...

	if (!pdata) {
		pdata = gpio_keys_get_devtree_pdata(dev);------------------------------------------->(1)
		if (IS_ERR(pdata))
			return PTR_ERR(pdata);
	}

	ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
			pdata->nbuttons * sizeof(struct gpio_button_data),
			GFP_KERNEL);

	input = input_allocate_device();--------------------------------------------------------(2)
	if (!ddata || !input) {
		dev_err(dev, "failed to allocate state\n");
		error = -ENOMEM;
		goto fail1;
	}

	platform_set_drvdata(pdev, ddata);
	input_set_drvdata(input, ddata);

	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;

	... ...

	/* Enable auto repeat feature of Linux input subsystem */
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);

	for (i = 0; i < pdata->nbuttons; i++) {--------------------------------------------(3)
		const struct gpio_keys_button *button = &pdata->buttons[i];
		struct gpio_button_data *bdata = &ddata->data[i];

		error = gpio_keys_setup_key(pdev, input, bdata, button);
		if (error)
			goto fail2;

		if (button->wakeup)
			wakeup = 1;
	}

	error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);----------------(4)
	if (error) {
		dev_err(dev, "Unable to export keys/switches, error: %d\n",
			error);
		goto fail2;
	}

	error = input_register_device(input);---------------------------------------------(5)
	if (error) {
		dev_err(dev, "Unable to register input device, error: %d\n",
			error);
		goto fail3;
	}

	device_init_wakeup(&pdev->dev, wakeup);

	return 0;

 	... ... 
}
  • (1)解析DTS关于gpio-keys的属性定义,创建、初始化gpio_keys_platform_data。
  • (2)分配、初始化input设备。
  • (3)遍历所有key/button,注册key/buton所需的资源(gpio、irq等)。
  • (4)注册gpio-keys在sys文件系统下的访问接口属性,gpio-keys设备在sys文件系统路径为:/sys/devices/gpio_keys_test.32,其中gpio_keys_test为DTS中设备设备节点名称。
  • (5)注册input设备。

设备资源解析

gpio_keys_get_devtree_pdata函数完成将DTS节点的设备属性翻译成gpio_keys_platform_data结构,具体执行流程如下。

gpio_keys_get_devtree_pdata(struct device *dev)
{

	... ...

	nbuttons = of_get_child_count(node);-----------------------------------------------(1)
	if (nbuttons == 0) {
		error = -ENODEV;
		goto err_out;
	}

	pdata = kzalloc(sizeof(*pdata) + nbuttons * (sizeof *button),
			GFP_KERNEL);
	if (!pdata) {
		error = -ENOMEM;
		goto err_out;
	}

	pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
	pdata->nbuttons = nbuttons;

	pdata->rep = !!of_get_property(node, "autorepeat", NULL);

	i = 0;
	for_each_child_of_node(node, pp) {------------------------------------------------(2)
		int gpio;
		enum of_gpio_flags flags;

		if (!of_find_property(pp, "gpios", NULL)) {
			pdata->nbuttons--;
			dev_warn(dev, "Found button without gpios\n");
			continue;
		}

		gpio = of_get_gpio_flags(pp, 0, &flags);
		if (gpio < 0) {
			error = gpio;
			if (error != -EPROBE_DEFER)
				dev_err(dev,
					"Failed to get gpio flags, error: %d\n",
					error);
			goto err_free_pdata;
		}

		button = &pdata->buttons[i++];

		button->gpio = gpio;
		button->active_low = flags & OF_GPIO_ACTIVE_LOW;

		if (of_property_read_u32(pp, "linux,code", &button->code)) {
			dev_err(dev, "Button without keycode: 0x%x\n",
				button->gpio);
			error = -EINVAL;
			goto err_free_pdata;
		}

		button->desc = of_get_property(pp, "label", NULL);

		if (of_property_read_u32(pp, "linux,input-type", &button->type))
			button->type = EV_KEY;

		button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);

		if (of_property_read_u32(pp, "debounce-interval",
					 &button->debounce_interval))
			button->debounce_interval = 5;
	}

	if (pdata->nbuttons == 0) {
		error = -EINVAL;
		goto err_free_pdata;
	}

	return pdata;
}
  • (1)获取keys/button的节点数量,初始化input系统的autorepeat属性。
  • (2)遍历DTS所有子节点,依次读取key/button的gpios、flags、linux,code、linux,input-type、gpio-key,wakeup、debounce-interval属性字段。

按键注册

gpio_keys_setup_key主要完成gpio的申请、配置以及gpio所关联的irq的申请、初始化配置功能,具体执行流程如下。

static int gpio_keys_setup_key(struct platform_device *pdev,
			struct input_dev *input,
			struct gpio_button_data *bdata,
			const struct gpio_keys_button *button)
{
	......

	if (gpio_is_valid(button->gpio)) {

		error = gpio_request_one(button->gpio, GPIOF_IN, desc);----------------------------->(1)
		if (error < 0) {
			dev_err(dev, "Failed to request GPIO %d, error %d\n",
				button->gpio, error);
			return error;
		}

		if (button->debounce_interval) {---------------------------------------------------->(2)
			error = gpio_set_debounce(button->gpio,
					button->debounce_interval * 1000);
			/* use timer if gpiolib doesn't provide debounce */
			if (error < 0)
				bdata->timer_debounce =
						button->debounce_interval;
		}

		irq = gpio_to_irq(button->gpio);--------------------------------------------------->(3)
		if (irq < 0) {
			error = irq;
			dev_err(dev,
				"Unable to get irq number for GPIO %d, error %d\n",
				button->gpio, error);
			goto fail;
		}
		bdata->irq = irq;

		INIT_WORK(&bdata->work, gpio_keys_gpio_work_func);------------------------------>(4)
		setup_timer(&bdata->timer,
			    gpio_keys_gpio_timer, (unsigned long)bdata);

		isr = gpio_keys_gpio_isr;
		irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

	} else {
		... ...
	}

	input_set_capability(input, button->type ?: EV_KEY, button->code);

	/*
	 * If platform has specified that the button can be disabled,
	 * we don't want it to share the interrupt line.
	 */
	if (!button->can_disable)
		irqflags |= IRQF_SHARED;

	error = request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);------------>(5)
	if (error < 0) {
		dev_err(dev, "Unable to claim irq %d; error %d\n",
			bdata->irq, error);
		goto fail;
	}

	... ...
}
  • (1)申请gpio资源,注意gpio_request_one的参数,由于key/button都为GPIO属性信号所以其第二个参数为GPIOF_IN。
  • (2)初始化key/button去抖所需要的定时器,注意gpio_set_debounce可能会失败,如果失败的话(4)的setup_timer会完成key/button的去抖功能。
  • (3)获取gpio所对应的irq,该irq为系统维护该gpio中断相关的所有操作的句柄参数。
  • (4)初始化key/button中断处理的bottom level处理workqueue,初始化key/button去抖定时器,gpio_keys_gpio_timer为定时器的超时处理函数,该函数十分的简单的,其调用schedule_work(&bdata->work);来调度中断的workqueue。初始化中断触发方式为:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,即边沿触发。
  • (5)申请中断。request/_any/_context/_irq申请中断处理所需的资源,并激活该interrupt line。注意该函数会根据中断描述的配置选择hartirq或者threaded方式的中断top level处理。

中断处理

中断处理-top level

gpio-keys驱动的上半部处理十分的简单,处理过程如下

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)------------------------------------------------>(1)
		pm_stay_awake(bdata->input->dev.parent);
	if (bdata->timer_debounce)
		mod_timer(&bdata->timer,
			jiffies + msecs_to_jiffies(bdata->timer_debounce));--------------->(2)
	else
		schedule_work(&bdata->work);

	return IRQ_HANDLED;
}

static void gpio_keys_gpio_timer(unsigned long _data)
{
	struct gpio_button_data *bdata = (struct gpio_button_data *)_data;

	schedule_work(&bdata->work);------------------------------------------->(3)
}
  • (1)如果key/button具有系统唤醒功能,调用电源相关的处理过程。
  • (2)key/button的timer_debounce肯定为大于0,所以,调用mod_timer启动去抖处理定时器。
  • (3)去抖定时器超时后会调用gpio_keys_gpio_timer定时器超时处理函数,该函数的实现十分的简单,其就做一件事,即调度key/button的workqueue。

中断处理-bottom level

上文提到过gpio-keys中断下半部的处理方式为workqueue,中断上半部的去抖定时器如果超时的话,会触发workqueue调度,workqueue会在合适的时间点执行。下面为workqueue的处理流程。

static void gpio_keys_gpio_work_func(struct work_struct *work)
{
	struct gpio_button_data *bdata =
		container_of(work, struct gpio_button_data, work);

	gpio_keys_gpio_report_event(bdata);-------------------------------------------------->(1)

	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 = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;--->(2)

	if (type == EV_ABS) {
		if (state)
			input_event(input, type, button->code, button->value);
	} else {
		input_event(input, type, button->code, !!state);--------------------------------->(3)
	}
	input_sync(input);
}
  • (1)上报key/button的gpio状态。
  • (2)读取gpio的I/O状态,并根据key/button的active_low状态将其转换为key/button的state。
  • (3)通过input子系统上报key/button的按键事件。

应用测试

下面举一个例子,讲解如何通过DTS配置gpio-keys驱动,以及如何通过应用程序监测key/button的按键事件。

设备DTS配置

` gpio_keys_test { 
    compatible = "gpio-keys";
    #address-cells = <1>; 
    #size-cells = <0>; 
    autorepeat;

    powerkey { 
        label = "power key";
        linux,code = <116>;
        gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
        gpio-key,wakeup;
        debounce-interval = <5>; 
    };   
};`

上面的DTS配置一个gpio0 GPIO_A5为一个按键,配置按键事件,启用wake-up功能。

gpio-keys驱动使能

使能gpio-keys驱动的驱动配置路径如下:

Device Driver--->
	Input device support--->
		Keyboards------------->
          GPIO buttons

保存内核配置,重新编译内核,将DTB和zImage文件下载到开发板。

按键事件应用测试

经过上述的配置之后,系统启动之后,我们会在/dev/input目录下看到对应于设备的设备文件,本例为event2。通过下面的应用程序就可以读取设备的按键事件了,应用程序如下:

#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/event2"

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

程序的输出结果如下:

type:1, code:116, value:1//type:EV_KEY, code:116:power key, value:1,按键按下
type:1, code:116, value:0
type:1, code:116, value:1
type:1, code:116, value:0
type:1, code:116, value:1
type:1, code:116, value:0

总结

gpio-keys驱动基本统一了Linux系统所有按键相关的驱动模式,我们开发按键驱动时可以直接配置使用该驱动。另外,该驱动借助input子系统与用户空间的应用程序进行交互,省去了编写文件系统相关的接口(省去了file_operations结构的配置,input子系统已经做了这部分工作)的工作。可以使驱动专注于key/button按键事件的处理,简化了驱动的处理流程。

<think>我们参考用户提供的引用内容,并结合RV1126平台的特点,来总结GPIO按键的配置和驱动实现方法。 根据引用[1]和引用[2],我们可以总结出以下步骤: 1. 设备树配置:需要配置GPIO的引脚、中断触发方式等。 2. 驱动代码:实现中断处理函数,使用定时器进行消抖,并通过输入子系统上报按键事件。 具体步骤如下: 一、设备树配置 在设备树中,我们需要为按键配置GPIO和中断属性。参考引用[1]和引用[2]中的例子,我们可以这样写: 首先,在pinctrl节点中配置GPIO的引脚和上下拉(如果需要): &pinctrl { key_irq: key-irq { rockchip,pins = <0 RK_PB7 RK_FUNC_GPIO &pcfg_pull_up>; // 假设使用GPIO0_B7,上拉 }; }; 然后,在设备树中添加按键节点,例如: keys { compatible = "gpio-keys"; pinctrl-names = "default"; pinctrl-0 = <&key_irq>; key0 { label = "GPIO Key Power"; linux,code = <KEY_POWER>; gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_LOW>; // 低电平有效,即按下时GPIO为低 interrupt-parent = <&gpio0>; // 中断父节点 interrupts = <RK_PB7 IRQ_TYPE_EDGE_BOTH>; // 双边沿触发 }; }; 注意:这里我们使用了双边沿触发(IRQ_TYPE_EDGE_BOTH),这样按下(下降沿)和释放(上升沿)都会触发中断。 二、驱动代码实现 虽然设备树中已经指定了按键,但Linux内核中已经有gpio-keys驱动,所以我们通常不需要自己编写驱动,只需要在设备树中配置好即可。但是,如果用户有特殊需求(比如需要自定义消抖时间、特殊的上报逻辑等),也可以自己编写驱动。 如果使用内核自带的gpio-keys驱动,则只需要在设备树中配置好,然后确保内核配置了CONFIG_KEYBOARD_GPIO,系统启动后就会自动加载。 但是,如果用户需要自己编写驱动,可以参考以下框架: 1. 在驱动中,我们需要: - 获取设备树中的GPIO和中断信息 - 申请GPIO - 申请中断 - 在中断处理函数中,使用定时器进行消抖 - 在定时器回调函数中,读取GPIO状态并上报按键事件 2. 代码框架(以单个按键为例): ```c #include <linux/module.h> #include <linux/init.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/timer.h> #include <linux/input.h> #define DRIVER_NAME "my_gpio_key" struct my_gpio_key { int gpio; int irq; struct timer_list timer; struct input_dev *input_dev; }; static struct my_gpio_key *key; // 定时器回调函数 static void key_timer_callback(struct timer_list *t) { struct my_gpio_key *key = from_timer(key, t, timer); int state = gpio_get_value(key->gpio); // 因为我们的按键是低电平有效,所以按下时state为0,弹起为1 // 上报事件:KEY_POWER,状态为按下(1)或弹起(0) input_report_key(key->input_dev, KEY_POWER, !state); input_sync(key->input_dev); } // 中断处理函数 static irqreturn_t key_irq_handler(int irq, void *dev_id) { struct my_gpio_key *key = (struct my_gpio_key *)dev_id; // 修改定时器,20ms后执行 mod_timer(&key->timer, jiffies + msecs_to_jiffies(20)); return IRQ_HANDLED; } static int __init my_gpio_key_init(void) { int ret; // 分配结构体 key = kzalloc(sizeof(struct my_gpio_key), GFP_KERNEL); if (!key) return -ENOMEM; // 从设备树获取GPIO,这里简化,实际应该从设备树获取 // 假设GPIO0_B7的编号为55(需要根据实际计算) key->gpio = 55; // 实际应该使用of_get_gpio等函数获取 // 申请GPIO if (gpio_request(key->gpio, "key_gpio")) { printk(KERN_ERR "Failed to request GPIO\n"); ret = -ENODEV; goto err_request_gpio; } // 设置GPIO为输入 gpio_direction_input(key->gpio); // 获取中断号 key->irq = gpio_to_irq(key->gpio); if (key->irq < 0) { ret = key->irq; goto err_irq; } // 初始化定时器 timer_setup(&key->timer, key_timer_callback, 0); // 申请中断 ret = request_irq(key->irq, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key_irq", key); if (ret) { printk(KERN_ERR "Failed to request IRQ: %d\n", ret); goto err_request_irq; } // 初始化输入设备 key->input_dev = input_allocate_device(); if (!key->input_dev) { ret = -ENOMEM; goto err_input_alloc; } key->input_dev->name = DRIVER_NAME; key->input_dev->phys = "my_gpio_key/input0"; key->input_dev->id.bustype = BUS_HOST; key->input_dev->dev.parent = NULL; // 设置支持的事件类型 __set_bit(EV_KEY, key->input_dev->evbit); __set_bit(KEY_POWER, key->input_dev->keybit); ret = input_register_device(key->input_dev); if (ret) { printk(KERN_ERR "Failed to register input device\n"); goto err_input_register; } return 0; err_input_register: input_free_device(key->input_dev); err_input_alloc: free_irq(key->irq, key); err_request_irq: del_timer_sync(&key->timer); err_irq: gpio_free(key->gpio); err_request_gpio: kfree(key); return ret; } static void __exit my_gpio_key_exit(void) { free_irq(key->irq, key); del_timer_sync(&key->timer); input_unregister_device(key->input_dev); gpio_free(key->gpio); kfree(key); } module_init(my_gpio_key_init); module_exit(my_gpio_key_exit); MODULE_LICENSE("GPL"); ``` 三、调试方法 1. 查看中断是否注册成功:`cat /proc/interrupts` 2. 使用`evtest`工具测试按键事件 3. 查看GPIO状态:`cat /sys/kernel/debug/gpio` 四、常见问题 1. 中断不触发:检查设备树配置是否正确,GPIO是否被其他驱动占用,中断触发方式是否正确。 2. 按键连击:可能是消抖时间不够,或者中断触发方式设置不当(建议使用双边沿触发,并在定时器回调中读取状态)。 3. 输入设备未注册:检查input_register_device的返回值,以及是否设置了正确的事件类型和按键码。 五、注意事项 1. 消抖时间通常为20ms,但可根据实际按键特性调整。 2. 在中断处理函数中不要做耗时操作,所以使用定时器来处理消抖和上报。 3. 资源申请失败时,要逐级释放已申请的资源。 如果使用内核自带的gpio-keys驱动,则设备树配置后,驱动会自动处理消抖和上报。但自定义驱动可以更灵活。 根据引用[2]中提到的定时器超时中断未执行的问题,可能是定时器初始化或设置的问题,请确保定时器初始化正确,并且中断处理函数中调用了mod_timer。 以上就是在RV1126平台上配置和使用GPIO按键的方法。</think>### RV1126 GPIO按键配置及驱动实现指南 #### 一、设备树配置方法 在设备树中配置GPIO按键需要以下关键步骤: 1. **配置pinctrl节点**(定义GPIO引脚属性): ```dts &pinctrl { gpio0b7 { gpio0_b7: gpio0-b7 { rockchip,pins = <0 RK_PB7 RK_FUNC_GPIO &pcfg_pull_up>; }; }; }; ``` - `RK_FUNC_GPIO`:配置为GPIO功能 - `&pcfg_pull_up`:启用内部上拉电阻[^1] 2. **定义按键节点**: ```dts keys { compatible = "gpio-keys"; pinctrl-names = "default"; pinctrl-0 = <&gpio0_b7>; // 关联pinctrl配置 power_key { label = "Power Key"; gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_LOW>; // GPIO0_B7 linux,code = <KEY_POWER>; // 按键键值 interrupt-parent = <&gpio0>; // 中断控制器 interrupts = <RK_PB7 IRQ_TYPE_EDGE_BOTH>; // 双边沿触发 }; }; ``` - `GPIO_ACTIVE_LOW`:低电平有效(按下时GPIO为低电平) - `IRQ_TYPE_EDGE_BOTH`:支持按下(下降沿)和释放(上升沿)事件[^2] #### 二、驱动实现关键代码 ```c #include <linux/interrupt.h> #include <linux/input.h> #include <linux/timer.h> static struct timer_list debounce_timer; static struct input_dev *input_dev; static int irq_gpio = 55; // GPIO0_B7对应的编号 // 定时器回调函数(消抖处理) static void debounce_timer_cb(struct timer_list *t) { int state = gpio_get_value(irq_gpio); // 上报按键事件(低电平有效) input_report_key(input_dev, KEY_POWER, !state); input_sync(input_dev); } // 中断处理函数 static irqreturn_t key_irq_handler(int irq, void *dev_id) { // 重新设置20ms定时器进行消抖 mod_timer(&debounce_timer, jiffies + msecs_to_jiffies(20)); return IRQ_HANDLED; } // 驱动初始化 static int __init key_driver_init(void) { int ret; // 1. 申请GPIO ret = gpio_request(irq_gpio, "power_key"); if (ret) { printk("Failed to request GPIO\n"); return ret; } // 2. 配置为输入模式 gpio_direction_input(irq_gpio); // 3. 初始化定时器 timer_setup(&debounce_timer, debounce_timer_cb, 0); // 4. 申请中断 ret = request_irq(gpio_to_irq(irq_gpio), key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "power_key_irq", NULL); if (ret) { printk("Failed to request IRQ\n"); goto err_irq; } // 5. 初始化输入设备 input_dev = input_allocate_device(); input_dev->name = "RV1126_GPIO_Key"; input_dev->phys = "gpio-keys/input0"; set_bit(EV_KEY, input_dev->evbit); set_bit(KEY_POWER, input_dev->keybit); // 6. 注册输入设备 ret = input_register_device(input_dev); if (ret) { printk("Failed to register input device\n"); goto err_input; } return 0; err_input: input_free_device(input_dev); free_irq(gpio_to_irq(irq_gpio), NULL); err_irq: gpio_free(irq_gpio); return ret; } ``` #### 三、调试与验证方法 1. **检查设备树是否生效**: ```bash # 查看编译后的设备树 fdtdump rv1126.dtb | less # 确认/sys/firmware/devicetree/base中节点存在 ``` 2. **查看中断注册状态**: ```bash cat /proc/interrupts | grep power_key ``` 3. **测试按键事件**: ```bash # 安装evtest工具 apt install evtest # 检测输入设备 evtest # 选择对应设备测试 Input driver version is 1.0.1 Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100 Input device name: "RV1126_GPIO_Key" ``` 4. **手动触发GPIO测试**: ```bash # 导出GPIO echo 55 > /sys/class/gpio/export echo in > /sys/class/gpio/gpio55/direction # 读取状态 cat /sys/class/gpio/gpio55/value # 模拟按键(需硬件连接) echo 0 > /sys/class/gpio/gpio55/value # 模拟按下 echo 1 > /sys/class/gpio/gpio55/value # 模拟释放 ``` #### 四、常见问题解决 1. **中断不触发**: - 检查`pinctrl`配置是否正确应用 - 确认GPIO复用配置未冲突: ```bash cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins ``` - 测量硬件电压(按下时电压应<0.8V,释放时>2.0V) 2. **按键连击问题**: ```c // 在定时器回调中添加状态锁 static bool key_active = false; void debounce_timer_cb(...) { int state = gpio_get_value(irq_gpio); // 仅状态变化时上报 if (state != key_active) { key_active = state; input_report_key(...); } } ``` 3. **定时器未触发**: - 确保内核配置启用`CONFIG_HIGH_RES_TIMERS` - 检查jiffies计算: ```c printk("Timer set at %lu + %lu\n", jiffies, msecs_to_jiffies(20)); ``` 4. **输入设备未注册**: ```bash dmesg | grep input # 检查/sys/class/input目录 ls /sys/class/input/input*/name ``` > 完整实现参考:[RV1126 GPIO按键驱动示例](https://github.com/rockchip-linux/kernel/tree/develop-5.10/drivers/input/keyboard)[^1] #### 五、高级功能扩展 1. **唤醒系统功能**: ```dts power_key { ... wakeup-source; // 添加唤醒属性 }; ``` ```c // 驱动中启用唤醒 device_init_wakeup(dev, true); enable_irq_wake(irq_num); ``` 2. **多按键支持**: ```dts keys { ... vol_up { gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_LOW>; linux,code = <KEY_VOLUMEUP>; }; vol_down { gpios = <&gpio1 RK_PA1 GPIO_ACTIVE_LOW>; linux,code = <KEY_VOLUMEDOWN>; }; }; ``` 3. **消抖时间调整**: ```dts power_key { ... debounce-interval = <30>; // 30ms消抖 }; ```
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值