Qualcomm MSM GPIO-KEY device tree

本文介绍如何在Linux内核中配置GPIO按键驱动,包括添加GPIO按键设备到设备树、定义pin控制配置、检查GPIO按键节点等步骤。通过示例展示了如何为特定GPIO设置属性如标签、GPIO编号、输入类型、键值及唤醒功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、add gpio-keys.c to build 
2、add gpio-keys device in your main dtsi like that: 
&soc { 
gpio_keys { 
compatible = "gpio-keys"; 
input-name = "gpio-keys"; 
pinctrl-names = "tlmm_gpio_key_active","tlmm_gpio_key_suspend"; 
pinctrl-0 = <&gpio_key_active>; 
pinctrl-1 = <&gpio_key_suspend>; 


vol_up { 
label = "volume_up";//key label name 
gpios = <&msm_gpio 107 0x1>;//gpio num 
linux,input-type = <1>;//don't need modify that 
linux,code = <115>;//the key value that you want to use 
gpio-key,wakeup;//could wakeup system 
debounce-interval = <15>;//the delay time for debounce 
}; 
camera_focus { 
label = "camera_focus"; 
gpios = <&msm_gpio 108 0x1>; 
linux,input-type = <1>; 
linux,code = <0x210>; 
gpio-key,wakeup; 
debounce-interval = <15>; 
}; 


}; 
}; 
3、add pinctrl in pinctrl.dtsi 
tlmm_gpio_key { 
qcom,pins = <&gp 107>, <&gp 108>; 
qcom,pin-func = <0>; 
qcom,num-grp-pins = <2>; 
label = "tlmm_gpio_key"; 
gpio_key_active: gpio_key_active { 
drive-strength = <2>; 
bias-pull-up; 
}; 
gpio_key_suspend: gpio_key_suspend { 
drive-strength = <2>; 
bias-pull-up; 
}; 
}; 
4、than check gpio-keys node. 
/sys/devices/soc.0/gpio_keys.* 
cat key 
and getevent to look the event. 
<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消抖 }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值