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()
就這樣了。