2019-06-10
关键字:rk3128按键适配、rk Linux层按键适配、按键驱动
笔者手里有一块运行着 Android4.4 的 rk3128 开发板。这两天接到一个要添加外部按键的需求,稍微研究了一番以后将过程记录下来。
1、概述
Android 默认都是支持添加外部按键功能的,预留了有一系列完善的接口流程。在笔者的整个适配过程中,可以说很是轻松简便。
不过与其说是 Android 默认支持外部按键,倒不如说是 Linux 系统默认支持添加外部键盘功能。毕竟 Android 是基于 Linux 的,且按键事件也确实是先由 Linux 捕捉到再传递到 Android 层的。
下面笔者简单梳理一下自己在适配过程中的一些比较关键的步骤,当然不可能将整个流程讲的很详细的,一个是笔者也不清楚整个流程的详细,还有一个是没必要。
2、原理图
首先我们需要来关注一下我们的原理图,要看看按键都是接在哪一个引脚上的。笔者这块开发板需要适配 1 个按键。由原理图上可以看到这个按键直接连接到 rk3128 芯片的 ADCIN1 功能引脚上,如下图所示
图1 按键的原理图
这里面的 ADCIN0 ~ ADCIN2 是干嘛用的呢?由名字就可以知道它们是用于模数转换电平输入用的,说白了就是专门预留用来接外置输入设备的。在这里,我们就简单把它理解成是用来接键盘的就好了,只不过笔者这里的键盘只有一个按键而已。
记住我们的按键是接在 ADCIN1 上,就可以开始下一步了。
3、dts 配置
这里谈到的 dts 配置文件,指的是下面目录内的相关 dts 配置文件
.\kernel\arch\arm\boot\dts
因为 dts 配置文件一般分为系统原生 dts 与客户客制化 dts 两种,这里我们首先来看一下 rk 原生的 dts 配置文件
.\kernel\arch\arm\boot\dts\rk312x-sdk.dtsi
在这个文件里,我们可以找到如下所示的配置节点信息
&adc { status = "okay"; key: key { compatible = "rockchip,key"; io-channels = <&adc 1>; vol-up-key { linux,code = <115>; label = "volume up"; rockchip,adc_value = <523>; }; vol-down-key { linux,code = <114>; label = "volume down"; rockchip,adc_value = <727>; }; power-key { gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>; linux,code = <116>; label = "power"; gpio-key,wakeup; }; menu-key { linux,code = <59>; label = "menu"; rockchip,adc_value = <1>; }; home-key { linux,code = <102>; label = "home"; rockchip,adc_value = <318>; }; back-key { linux,code = <158>; label = "back"; rockchip,adc_value = <146>; }; camera-key { linux,code = <212>; label = "camera"; rockchip,adc_value = <450>; }; }; };
这个,就是和我们的外部按键相关的配置信息了。可以看到,Android 系统默认就支持了几个手机上常见的外部按键功能。我们想要客制化这些按键信息的话,直接在我们的客制化 dts 里改写这个节点信息即可,因为一般是不推荐直接修改原生 dts 配置信息的。
改写后的内容如下
&adc{ status = "okay"; key: key { compatible = "rockchip,key"; io-channels = <&adc 1>; reset-key { linux,code = <116>; label = "my reset key"; rockchip,adc_value = <1>; }; // The keys that in below is no use in rk3128. power-key { gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>; linux,code = <116>; label = "power"; gpio-key,wakeup; }; vol-up-key { linux,code = <115>; label = "volume up"; rockchip,adc_value = <400>; }; vol-down-key { linux,code = <114>; label = "volume down"; rockchip,adc_value = <401>; }; menu-key { linux,code = <59>; label = "menu"; rockchip,adc_value = <402>; }; home-key { linux,code = <102>; label = "home"; rockchip,adc_value = <403>; }; back-key { linux,code = <158>; label = "back"; rockchip,adc_value = <404>; }; camera-key { linux,code = <212>; label = "camera"; rockchip,adc_value = <405>; }; }; };
这里我们可以注意到上面的一条配置信息 io-channels = <&adc 1>; 这条就是指我们在这里配置的按键功能的事件输入来自哪一个 ADCIN 脚,这在上一节我们就已经确定了是 ADCIN1 脚,因此直接填 1 就好。
另一个就是我们可以任意新增自己的按键信息,像上面的 reset-key 信息就是笔者新增的。每个按键信息里有 3 条子配置项
1、linux,code
它是配置 Linux 上按键码的,这个值最好不要乱填。有需要的可以参考 .\kernel\include\uapi\linux\input.h 里定义的键值码信息。
2、label
描述值,可以简单说明一下这个按键的功能。没啥太大意义,一般用于在打印上打印一些易于区分的标识符。
3、rockchip,adc_value
adc 的转化值咯,这里的换算方式老实说笔者也不清楚。笔者这里由于只有一个按键,当按键按下时这里读取到的值就是 1 ,因此笔者这就直接填了个 1。
在客制化好自己的按键信息后,强烈建议将原生信息中其它没有使用到的按键信息也一并改写在这里,目的是要防止原生的配置与自己的配置有冲突,导致按下按键时同时产生两种按键事件。像笔者上面就是,其它不用的按键,统一改成与笔者需要使用的 adc_value 不冲突的值。
dts 配置弄完以后,就可以上代码了。
4、驱动
驱动层的软件一般都是好的,可以编译的。怎么确定它好不好呢?下面所讲的每一个源码文件,都看一下在它的下面是否还有一个和它同名的 .o 文件被编译出来。如果驱动功能不正常,即不能编译出 o 文件,那就得您自个去查找原因了。
首先我们来看到这个源码文件
.\kernel\drivers\input\keyboard\rk_keys.c
这个文件中,值得我们去关心的函数就一个
1 static void keys_timer(unsigned long _data) 2 { 3 struct rk_keys_drvdata *pdata = rk_key_get_drvdata(); 4 struct rk_keys_button *button = (struct rk_keys_button *)_data; 5 struct input_dev *input = pdata->input; 6 int state; 7 8 if(button->type == TYPE_GPIO) 9 state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low); 10 else 11 state = !!button->adc_state; 12 13 if(button->state != state) { 14 button->state = state; 15 key_dbg(pdata, "%s key[%s]: report event[%d] state[%d], adc_value:%d, adc_state:%d\n", 16 (button->type == TYPE_ADC)?"adc":"gpio", button->desc, button->code, button->state, button->adc_value, button->adc_state); 17 input_event(input, EV_KEY, button->code, button->state); 18 input_sync(input); 19 } 20 21 if(state) 22 { 23 mod_timer(&button->timer, jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL)); 24 } 25 26 }
当我们按下按键产生了 ADC 事件时,这个函数就会被调用。如果我们需要做什么逻辑,直接在这里做就好了。笔者的开发板在按下按键会,可以在串口上看到如下内核打印内容
[ 1277.062118] input input2: adc key[my reset key]: report event[116] state[1], adc_value:1, adc_state:1 [ 1277.258915] input input2: adc key[my reset key]: report event[116] state[0], adc_value:1, adc_state:0
它只会产生两个事件,一个是按下的事件,另一个是抬起的事件,中间按住的过程它不会有打印,但是它会处理,由上面代码第 13 行的 if 条件即可得知。
这里再额外提一下上面代码第 23 行的 mod_timer 函数。这个函数是一个 Linux 系统函数,它的作用简单理解成控制这个 key_timer 函数可以被调用的时间间隔而已。因为我们知道,芯片的处理速度是非常快的,可以达到纳秒级别。我们人去按一下按键,对于芯片来说可以产生大量事件,这些事件中大部分都是中间被按住的过程事件。为了避免这个函数被调用次数过多而消耗了过多的资源,就在这里设了一个延时间隔。DEFAULT_DEBOUNCE_INTERVAL 的值就在这个源码文件中有定义,默认值是 10ms。
上述代码第 17 行的 input_event() 函数在下面的源码文件中实现
.\kernel\drivers\input\input.c
这个代码才是 Linux 原生代码。不过其实这个文件没什么好跟的。
基本上驱动层到这里就可以结束了,再下面就是直接关注被传递到 Android 层的按键事件了。
5、Android 层
按键事件到了 Android 层,那就好办了。网上关于按键事件在 Android 层的传递流程也有大量博文可供参考。关于这点,笔者也不打算怎么去讲解了,事实上,笔者自己也没有花时间去跟踪过 Android 上的事件传递流程。总之笔者图方便,知道这个事件最终会被传递到 PhoneWindowManager.java 里去等待处理就好了,有什么逻辑直接在这上面做就好。
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
interceptKeyBeforeQueueing()
interceptKeyBeforeDispatching()
就这样了。