平台:MX53_QSB開發板
MX53_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,即一旦調度相應隊列名,該函數將得到執行。