目录
开发环境:msm8953、安卓7.1、linux3.18
一、gpio-keys使用
gpio-keys.c 是linux内核中的驱动文件,添加gpio按键不需要自己去实现驱动。
参考链接:高通平台如何添加没有定义的按键
1.修改设备树
先在设备树中添加设备节点,然后添加pinctrl。
label 是按键的名称。gpios 表明使用的是那个gpio。 linux,input-type的0x1是EV_KEY。linux,code 是按键上报的键值。wakeup 按键按下可以唤醒系统。debounce-interval 是按键的消抖时间。
// msm8953-nopmi-qrd.dtsi
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";
gpios = <&tlmm 48 0x1>;
linux,input-type = <1>;
linux,code = <0x73>;
gpio-key,wakeup;
debounce-interval = <15>;
};
volume_down {
label = "volume_down";
gpios = <&tlmm 135 0x1>;
linux,input-type = <1>;
linux,code = <0x72>;
gpio-key,wakeup;
debounce-interval = <15>;
};
...
}
添加pinctrl
tlmm_gpio_key {
gpio_key_active: gpio_key_active {
mux {
pins = "gpio48", "gpio135";
function = "gpio";
};
config {
pins = "gpio48", "gpio135";
drive-strength = <2>;
bias-pull-up;
};
};
gpio_key_suspend: gpio_key_suspend {
mux {
pins = "gpio48", "gpio135";
function = "gpio";
};
config {
pins = "gpio48", "gpio135";
drive-strength = <2>;
bias-pull-up;
};
};
};
2.修改内核配置
把驱动编译进内核。
在驱动目录下查看kconfig 和 makefile文件,确认配置选项。
obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
config KEYBOARD_GPIO
tristate "GPIO Buttons"
depends on GPIOLIB
help
This driver implements support for buttons connected
to GPIO pins of various CPUs (and some other chips).
...
kernel目录下,make menuconfig 进入图形界面,按下/输入KEYBOARD_GPIO,然后回车进行搜索,找到对应的配置,查看依赖选项。
如上,如果是linux系统,则根据搜索结果中提示的路径找到配置,把m改为y。若编译的时候根据defconfig生成.config,则修改对应的defconfig。请参考链接:保存编译内核产生的.config
我这里是安卓系统,在单独编译内核时会根据defconfig文件重新生成.config文件,所以我需要修改defconfig文件,在编译的时候才能把驱动编译进内核。
如何确认defconfig文件,请参考: 确定defconfig和dts配置文件
注意安卓系统退出menuconfig的时候需要在内核目录下执行命令make mrproper 清除图形界面生成的.config及其他的过程文件。
在defconfig 文件中修改配置项:
CONFIG_KEYBOARD_GPIO=y
注意defconfig的语法:
需要关闭某个配置项时,需要把该配置项改为# xxx is not set ,直接在前面加# 是不行的
3.安卓系统在.kl文件中修改键值映射
请参考:高通平台如何添加没有定义的按键
4.在系统中查看gpio状态
系统启动后执行命令 cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pinmux-pins,这里可以看到gpio的信息。
debugfs 参考链接:Debugfs的使用简介
5.查看io的中断信息
系统启动后执行命令 cat /proc/interrupts,这里可以看到相关的中断信息。
这里用到了procfs。procfs,其目的是反映进程的状态信息。procfs是Linux内核信息的抽象文件接口,大量内核中的信息以及可调参数都被作为常规文件映射到一个目录树中,这样我们就可以简单直接的通过echo或cat这样的文件操作命令对系统信息进行查取和调整了。参考链接:Linux proc详解
二、驱动分析
我这里添加按键之后遇到有两个按键按下在上层监测不到事件的上报的问题。设备树中都是一样的配置方法,驱动文件只有一份,且是内核中的驱动,基本可以判定是硬件的问题。
示波器查看波形,发现在按键按下时io口的低电平毛刺较大,达到700mv。在驱动中添加log,在按键按下时会连续重复进入gpio_keys_gpio_isr。硬件上飞线,直接把按键的输出连接到io上该现象消失。
参考链接:gpio-key驱动分析
上面这个链接中的文章讲解很详细。probe以及其中的设备数解析,注册io,中断处理等都有讲解,这里不在复述。
关于事件的上报与我碰到的问题有关,这里分析一下。
1.report event
在上面的链接中,中断处理中介绍了中断的顶部和底部。
中断top level
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启动去抖处理定时器。
设备树中不配置timer_debounce,该值默认为5ms。一般设备树中配置为15ms。若在设备树中配置为0,则直接执行schedule_work。
在按键一直保持按下状态,电平上的毛刺导致一直触发io中断,在这里mod_timer函数会一直更新定时器的时间,导致定时器不能超时,无法执行中断函数的底部,无法上报事件。
直到按键释放,io电平变为高电平,不再触发io中断,定时器超时后,上报一次事件。
- (3)去抖定时器超时后会调用gpio_keys_gpio_timer定时器超时处理函数,该函数的实现十分的简单,其就做一件事,即调度key/button的workqueue。
中断-bottom level
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);
}
监测事件上报
安卓系统可以直接使用getevent监测输入事件。下面的链接中有描述getevent的详细用法。
参考链接: getevent用法详解
这里直接使用getevent,打印信息如下:
msm8953_64:/ # getevent
add device 1: /dev/input/event5
name: "msm8953-sku3-snd-card Button Jack"
add device 2: /dev/input/event4
name: "msm8953-sku3-snd-card Headset Jack"
could not get driver version for /dev/input/mouse0, Not a typewriter
add device 3: /dev/input/event3
name: "hbtp_vm"
add device 4: /dev/input/event2
name: "qpnp_pon"
could not get driver version for /dev/input/mice, Not a typewriter
add device 5: /dev/input/event1
name: "gpio-keys"
/dev/input/event1: 0001 0006 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0001 0006 00000000
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0001 009e 00000001
总结
问题根因:电平信号上有毛刺,按键按下连续触发io中断,导致无法上报事件
解决方法:修改硬件,调整走线,添加电容等
gpiokeys只是input子系统的一小部分,还有鼠标、键盘、触摸屏等设备,需要继续学习。
以下内容引用:
gpio-keys驱动基本统一了Linux系统所有按键相关的驱动模式,我们开发按键驱动时可以直接配置使用该驱动。另外,该驱动借助input子系统与用户空间的应用程序进行交互,省去了编写文件系统相关的接口(省去了file_operations结构的配置,input子系统已经做了这部分工作)的工作。可以使驱动专注于key/button按键事件的处理,简化了驱动的处理流程。
————————————————
版权声明:本文为CSDN博主「飞翔de刺猬」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lhl_blog/article/details/82892809