android系統移植之按鍵驅動篇

android系統移植之按鍵驅動篇


X53_QSB開發板上一起有四個按鍵,分別爲RESET,POWER,USER1,USER2。其中RESET爲純硬件復位按鍵,無須軟件控制。POWER,USER1,USER2三個按鍵均需要程序控制。默認BSP包中將三個按鈕全設置爲上升和下降沿觸發,當系統起來後,按下POWER鍵,進入睡眠狀態,這時再按下POWER鍵喚醒時,系統系統被喚醒,但是一旦手鬆下,又觸發了POWER鍵的中斷,系統又睡下去了。在進入睡眠狀態後,只有按USER1和USER2這兩個鍵,才能正常喚醒。因此,這裏有BUG需修復。
按鍵驅動有兩個,一個爲矩陣鍵盤驅動,路徑爲:
\drivers\input\keyboard\mxc_keyb.c
一個爲GPIO接口的鍵盤驅動,路徑爲:
\drivers\input\keyboard\gpio_keys.c
前者用於多按鍵的情況,如果按鍵比較少,後者就可以了,一般情況下,android系統只需幾個按鍵就可以了,所以大多數情況下,都是使用的gpio_keys.c。下面我們將詳細分析該驅動的工作流程。
在module_init函數中,在總線上註冊名爲gpio-keys的驅動,這時將夫在總線上查找是否存在同名的設備。系統初始化時,mx53_loco.c中,mxc_board_init函數已調用了按鍵初始化函數loco_add_device_buttons(),同時pdev的數據結構體中定義了按鍵的相關信息如下:
#define GPIO_BUTTON(gpio_num, ev_code, act_low,descr, wake)   \
{                                                                         \
         .gpio           = gpio_num,                                    \
         .type           = EV_KEY,                                     \
         .code          = ev_code,                             \
         .active_low = act_low,                              \
         .desc           = "btn " descr,                                 \
         .wakeup               = wake,                                           \
}
 
static struct gpio_keys_button loco_buttons[] = {
         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),
         GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),
         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),
};
 
static struct gpio_keys_platform_dataloco_button_data = {
         .buttons      = loco_buttons,
         .nbuttons    = ARRAY_SIZE(loco_buttons),
};
可見,結構體定義了三個GPIO,分別爲power,back以及home。注意GPIO_BUTTON函數中的實參,第一個爲對應的GPIO,純硬件特性,第二個爲按鍵的鍵值,在linux/input.h中定義:
#defineKEY_POWER         116
#defineKEY_HOME           102
#defineKEY_BACK            158
第三個參數爲1,表明按下去爲1,擡起爲0;第四個參數爲按鍵名稱描述,無關緊要;第5個參數爲wakeup,看名稱好像與休眠喚醒有關,實際測試修改爲1後,沒有發現有什麼異常。這幾個參數是後續添加新的按鍵,或者更改按鍵功能的關鍵。
再回到gpio-keys.c中,找到同名設備後,探測函數gpio_keys_probe得到執行,調用input_allocate_device函數創建一個input設備,再通過一個for循環,調用gpio_keys_setup_key和input_set_capability函數,設置上表中列出的三個IO口的中斷函數以及按鍵功能。後面用到了sysfs_create_group函數創建了基於sys系統的文件屬性組,具體可以在?sys/devices/platform/gpio-keys目錄下找到gpio_keys_attr_group結構體中attrs組對應的gpio_keys_attrs結構體中的幾個屬性文件,gpio_keys_attrs結構體描述如下:
static struct attribute *gpio_keys_attrs[] = {
         &dev_attr_keys.attr,
         &dev_attr_switches.attr,
         &dev_attr_disabled_keys.attr,
         &dev_attr_disabled_switches.attr,
         NULL,
};
dev_attr_disabled_keys和dev_attr_disabled_switches在前面做了如下聲明:
static DEVICE_ATTR(disabled_keys, S_IWUSR |S_IRUGO,
                      gpio_keys_show_disabled_keys,
                      gpio_keys_store_disabled_keys);
static DEVICE_ATTR(disabled_switches, S_IWUSR |S_IRUGO,
                      gpio_keys_show_disabled_switches,
                      gpio_keys_store_disabled_switches);
在android系統終端,我們可以進入該路徑查看是否存在,以及他們的文件屬性如下:


注意上面四個文件的讀寫屬性。
可見,這裏留有在系統中操作按鍵的後門,具體以後再分析。接下來,調用input_register_device函數向輸入子系統註冊input_dev,結束探測函數初始化。
探測函數的關鍵點在gpio_keys_setup_key函數中,相關代碼如下:
static int __devinit gpio_keys_setup_key(structplatform_device *pdev,
                                                struct gpio_button_data *bdata,
                                                struct gpio_keys_button *button)
{
         char *desc= button->desc ? button->desc : "gpio_keys";//從loco_buttons數組中獲得按鍵的描述名稱
         structdevice *dev = &pdev->dev;
         unsignedlong irqflags;
         intirq, error;
 
         //傳入參數: 過期時間,回調函數,上下文
         //當計時器過期時,回調函數gpio_keys_timer將得到運行
         setup_timer(&bdata->timer,gpio_keys_timer, (unsigned long)bdata);//初始化計時器
         INIT_WORK(&bdata->work,gpio_keys_work_func);
 
         error= gpio_request(button->gpio, desc);//請求使用GPIO
         if(error < 0)
         {
                   dev_err(dev,"failed to request GPIO %d, error %d\n",button->gpio, error);
                   gotofail2;
         }
 
         error= gpio_direction_input(button->gpio);//設置指定的GPIO爲輸入模式
         if(error < 0)
         {
                   dev_err(dev,"failed to configure direction for GPIO %d, error %d\n",
                            button->gpio,error);
                   gotofail3;
         }
 
         irq =gpio_to_irq(button->gpio);//獲得GPIO對應的中斷號
         if (irq< 0)
         {
                   error= irq;
                   dev_err(dev,"Unable to get irq number for GPIO %d, error %d\n",button->gpio,error);
                   gotofail3;
         }
 
         irqflags= IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
         //irqflags= IRQF_TRIGGER_FALLING;//lqm changed.
         /*
          * If platform has specified that the buttoncan be disabled,
          * we don't want it to share the interruptline.
          */
         if(!button->can_disable)
                   irqflags|= IRQF_SHARED;
 
         error= request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);//請求按鍵中斷
     ……
}
該函數使用了帶定時器延時的中斷機制,用於按鍵去抖動。即中斷的執行在定時器到來之後執行,這樣能夠有效的去除按鍵抖動。同時,中斷使用工作隊列的機制。
整個按鍵中斷的工作調用有點複雜,下面逐步解析:
首先,上面函數中setup_timer函數初始化定時器,第二個實參爲一個名爲gpio_keys_timer的函數,一旦計時器過期,該函數將得到運行。setup_timer函數需和mod_timer函數配合使用,在按鍵中斷的頂半部,即gpio_keys_isr函數,會判斷debounce_interval是否爲0,若爲非0,則調用mod_timer函數延時debounce_interval ms,再觸發定時器中斷,即gpio_keys_timer函數得到執行。否則,直接調度工作隊列,執行中斷底半部。
回到gpio_keys_setup_key函數,在初始化完計時器後,再調用INIT_WORK函數初始化工作隊列,初始化函數有一個實參函數gpio_keys_work_func,即一旦調度相應隊列名,該函數將得到執行。
接下來是IO口的中斷初始化,調用gpio_request函數申請相應的GPIO,調用gpio_direction_input函數將對應的GPIO設置爲輸入,gpio_to_irq函數通過指定GPIO口映射到指定的IRQ中斷號,request_irq函數申請中斷。
值得注意的是,三個按鍵通過request_irq申請中斷時,共用了同一個中斷函數gpio_keys_isr,那麼程序是怎麼判斷具體是哪個按鍵觸發的呢?
帶着這個問題,我們將整個中斷的執行過程疏理一遍:
第一步:硬件板按下按鍵,電平由高變低,中斷被觸發,中斷函數gpio_keys_isr被調用。代碼如下:
static irqreturn_t gpio_keys_isr(int irq, void*dev_id)
{
         structgpio_button_data *bdata = dev_id;
         structgpio_keys_button *button = bdata->button;
 
         BUG_ON(irq!= gpio_to_irq(button->gpio));
 
         if(button->debounce_interval)//產生按鍵中斷後,用計時器延時button->debounce_interval ms之後,再執行按鍵處理
                  mod_timer(&bdata->timer,jiffies+ msecs_to_jiffies(button->debounce_interval));
         else
                   schedule_work(&bdata->work);
 
         returnIRQ_HANDLED;
}
中斷函數會判斷debounce_interval是否爲0,debounce_interval代表計時器需要延時的時間,單位爲毫秒。爲了確定程序具體如何執行,我們首先需分析出它的值。以下是推理邏輯:
button->debounce_interval  à   找到button結構體來源
*bdata = dev_id&& *button = bdata->button  à  *button = dev_id->button
staticirqreturn_t gpio_keys_isr(int irq, void *dev_id);
error =request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);
上面兩個函數,gpio_keys_isr爲request_irq的一個實參,根到request_irq代碼中,最終會調用kernel\irq\manage.c中的request_threaded_irq函數,部分代碼如下:
int request_threaded_irq(unsigned int irq,irq_handler_t handler,
                             irq_handler_t thread_fn, unsigned longirqflags,
                             const char *devname, void *dev_id)
{
     ……
         action= kzalloc(sizeof(struct irqaction), GFP_KERNEL);
         if(!action)
                   return-ENOMEM;
 
         action->handler= handler;
         action->thread_fn= thread_fn;
         action->flags= irqflags;
         action->name= devname;
         action->dev_id= dev_id;
 
         chip_bus_lock(irq,desc);
         retval= __setup_irq(irq, desc, action);
         chip_bus_sync_unlock(irq,desc);
 
         if(retval)
                   kfree(action);
 
#ifdef CONFIG_DEBUG_SHIRQ
         if(!retval && (irqflags & IRQF_SHARED)) {
                   /*
                    * It's a shared IRQ -- the driver ought to beprepared for it
                    * to happen immediately, so let's makesure....
                    * We disable the irq to make sure that a'real' IRQ doesn't
                    * run in parallel with our fake.
                    */
                   unsignedlong flags;
 
                   disable_irq(irq);
                   local_irq_save(flags);
 
                   handler(irq,dev_id);
 
                   local_irq_restore(flags);
                   enable_irq(irq);
         }
#endif
         returnretval;
}
繼續根到__setup_irq函數,後面不繼續分析了,最終會將request_irq中的兩個實參irq和bdata賦給中斷服務函數gpio_keys_isr中的兩個實參,也就是說,*button =dev_id->button等價於*button=bdata->button。
在gpio_keys_probe函數中,bdata->button = button,*button = &pdata->buttons[i];可以推斷出*button= pdata->buttons[i],而*pdata = pdev->dev.platform_data,那麼*button= pdev->dev.platform_data-> buttons[i],也就是mx53_loco.c中的如下結構體數組中的一組:
static struct gpio_keys_button loco_buttons[] = {
         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),
         //GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),
         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),
         GPIO_BUTTON(USER_UI1,KEY_RIGHT, 1, "right", 0),//test by lqm.
};
由此可以推斷出,前面的button->debounce_interval即loco_buttons[i]-> debounce_interval。數組loco_buttons的結構體如下:
struct gpio_keys_button {
         /*Configuration parameters */
         intcode;               /* input event code(KEY_*, SW_*) */
         intgpio;
         intactive_low;
         char*desc;
         inttype;               /* input event type(EV_KEY, EV_SW) */
         intwakeup;                   /* configure thebutton as a wake-up source */
         intdebounce_interval;   /* debounce ticksinterval in msecs */
         boolcan_disable;
};
由於程序中並沒有對debounce_interval賦值,因此默認debounce_interval爲0。回到gpio_keys_isr函數,由於button->debounce_interval爲0,那麼計時器機制沒有啓動,直接執行調度函數schedule_work。
第二步:中斷隊列gpio_keys_work_func函數得到執行。它又會調用gpio_keys_report_event函數,代碼如下:
static void gpio_keys_report_event(structgpio_button_data *bdata)
{
         structgpio_keys_button *button = bdata->button;
         structinput_dev *input = bdata->input;
         unsignedint type = button->type ?: EV_KEY;
         intstate = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;//獲得按鍵信息,同時與1異或?
 
         input_event(input,type, button->code, !!state);
         input_sync(input);//事件同步,它告知事件的接收者驅動已經發出了一個完整的報告
}
這裏是中斷底半部,變量state經gpio_get_value函數獲得當前IO口的電平狀態,再通過input_event函數將當前電平狀態以及button->code上傳給輸入子系統。button->code即loco_buttons數組裏面GPIO_BUTTON中的第二個參數,它定義了按鍵的作用。最後調用input_sync函數同事事件,結束一次按鍵的操作。
由於前面分析的debounce_interval值爲0,因此執行流程比較簡單,如果它不會0,將會啓用計時器機制,流程會複雜一些。
默認三個按鍵的功能爲開關機,主頁和返回三個功能,如果我們需要修改對應按鍵的功能,是否修改上面的GPIO_BUTTON->ev_code就可以了呢?比如,我們需要將back鍵改爲right鍵,做如下修改:
static struct gpio_keys_button loco_buttons[] = {
         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),
         //GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),
         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),
         GPIO_BUTTON(USER_UI1,KEY_RIGHT, 1, "right", 0),//test by lqm.
};
編譯內核,這樣按鍵功能就改變了嗎?答案是否定的。因爲android並沒有直接使用映射後的鍵值,而且對其再進行了一次映射,從內核標準鍵值到android所用鍵值的映射表定義在android文件系統的/system/usr/keylayout目錄下。標準的映射文件爲qwerty.kl,定義如下:
key 399  GRAVE
key 2     1
key 3     2
key 4     3
key 5     4
key 6     5
key 7     6
key 8     7
key 9     8
key 10    9
key 11    0
key 158  BACK              WAKE_DROPPED
key 230  SOFT_RIGHT        WAKE
key 60   SOFT_RIGHT        WAKE
key 107  ENDCALL           WAKE_DROPPED
key 62   ENDCALL           WAKE_DROPPED
key 229  MENU              WAKE_DROPPED
key 139  MENU              WAKE_DROPPED
key 59   MENU              WAKE_DROPPED
key 127  SEARCH            WAKE_DROPPED
key 217  SEARCH            WAKE_DROPPED
key 228  POUND
key 227  STAR
key 231  CALL              WAKE_DROPPED
key 61   CALL              WAKE_DROPPED
key 232  DPAD_CENTER       WAKE_DROPPED
key 108  DPAD_DOWN         WAKE_DROPPED
key 103  DPAD_UP           WAKE_DROPPED
key 102  HOME              WAKE
key 105  DPAD_LEFT         WAKE_DROPPED
key 106  DPAD_RIGHT        WAKE_DROPPED
key 115  VOLUME_UP
key 114  VOLUME_DOWN
key 116  POWER             WAKE
key 212  CAMERA
 
key 16    Q
key 17    W
key 18    E
key 19    R
key 20    T
key 21    Y
key 22    U
key 23    I
key 24    O
key 25    P
key 26   LEFT_BRACKET
key 27   RIGHT_BRACKET
key 43   BACKSLASH
 
key 30    A
key 31    S
key 32    D
key 33    F
key 34    G
key 35    H
key 36    J
key 37    K
key 38    L
key 39   SEMICOLON
key 40   APOSTROPHE
key 14   DEL
       
key 44    Z
key 45    X
key 46    C
key 47    V
key 48    B
key 49    N
key 50    M
key 51   COMMA
key 52   PERIOD
key 53   SLASH
key 28   ENTER
       
key 56   ALT_LEFT
key 100  ALT_RIGHT
key 42   SHIFT_LEFT
key 54   SHIFT_RIGHT
key 15   TAB
key 57   SPACE
key 150  EXPLORER
key 155  ENVELOPE       
 
key 12   MINUS
key 13   EQUALS
key 215   AT
android按鍵的處理是Window Manager負責,主要的映射轉換實現在android源代碼frameworks/base/libs/ui/EventHub.cpp此文件處理來自底層的所有輸入事件,並根據來源對事件進行分類處理,對於按鍵事件,它首先記錄驅動名稱,再獲取環境變量ANDROID_ROOT爲系統路徑,默認是/system,定義在android源代碼/system/core/rootdir/init.rc文件中。然後查找路徑爲"系統路徑/usr/keylayout/驅動名稱.kl"的按鍵映射文件,如果不存在則默認用路徑爲"系統路徑/usr/keylayout/qwerty.kl"。這個默認的按鍵映射文件,映射完成後再把經映射得到的android按鍵碼值發給上層應用程序。所以我們可以在內核中定義多個按鍵設備,然後爲每個設備設定不同的按鍵映射文件,不定義則會默認用qwerty.kl。
有了上面的分析,我們不難發現,上述更改是不可能有效果的,只能越改越不能用。相反,如果僅僅只需要更改某個按鍵的功能,根本就不用改內核,只需重新定義android系統的gpio-keys.kl即可。
進android系統後找到該文件,


可以發現,在在gpio-keys.kl和qwerty.kl兩個文件,因此a ndroid系統會從gpio-keys.kl中查找鍵值進行映射。裏面內容如下:
key 102  HOME              WAKE
key 158   BACK              WAKE
這不正是開發板上android的兩個功能鍵嗎?對於第三個POWER鍵,是用於開關機的,不屬android功能鍵範疇,因此無須映射。
修改按鍵功能有兩種方法:
方法一:直接修改gpio-keys.kl文件的內容,比如我們想將BACK鍵改成右鍵,我們只需做如下修改:
key 102  HOME              WAKE
key 106  DPAD_RIGHT       WAKE
注意上面的106以及按鍵名稱,都是從qwerty.kl裏面找的,千萬不要在linux內核的input.h中查找。DPAD_RIGHT不能更改爲RIGHT,否則android無法識別。
方法二:修改內核中mx53_loco.c中的數組如下:
static struct gpio_keys_button loco_buttons[] = {
         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),
         //GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),
         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),
         GPIO_BUTTON(USER_UI1,KEY_RIGHT, 1, "right", 0),//test by lqm.
};
再修改上面的gpio-keys.kl文件,內容如下:
key 102  HOME              WAKE
key 106  DPAD_RIGHT       WAKE
方法二相比方法一,多了一個步驟,但是推薦採用方法二,這樣更容易理解,也不易混淆。






按鍵映射的路徑
/system/usr/keylayout/qwerty.kl
Y:\ap\android_4.4\device\softwinner\astar-y3\config\sunxi-keyboard.kl
發佈了101 篇原創文章 · 獲贊 23 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章