之前存在的觸摸屏問題 :
觸摸屏時常會失去響應,但是此時kernel及應用程式并未死掉,這種情況尤其是在音視訊檔案播放切換的時候容易發生。
WM9712晶片概述:
WM9712L 是一個适用于移動計算通信的高內建度I/O裝置。WM9712L能夠直接連接配接的裝置包括:4線/5線觸摸屏、單聲道/立體聲麥克風、立體聲耳機和單聲道喇叭,進而可以減少系統中部件的總數。WM9712L的所有功能都統一由一個單獨的AC-Link 接口進行通路和控制,另外,WM9712L還能夠針對pen-down、pen-up、觸摸屏資料、低電量、GPIO狀态而産生中斷。
WM9712的觸摸屏接口 (WM9712_Rev4.5.pdf, P38 )
WM9712L 包含針對4線或5線電阻式觸摸屏的觸摸屏驅動和數字轉換電路(digitiser),可以實作的功能包括:X坐标測量、Y坐标測量、Pen Down/Up檢測(靈敏度可程式設計)、4線觸摸屏的壓力測量,以及對來自COMP1/AUX1 (pin 29), COMP2/AUX2 (pin 30),BMON/AUX3 (pin 31), or WIPER/AUX4 (pin 12)的輔助測量。
觸摸屏的所有功能本質上就是對WM9712中的這個digitiser進行控制和通路,而所有的觸摸屏功能都是通過AC-Link接口來通路和控制的——這點非常重要,是以對于觸摸屏digitiser寄存器的通路和控制在驅動程式中也是建立在ac97驅動中建立的AC-Link接口的基礎上的。具體到程式中,AC-Link接口就是在 sound/oss/au1550_ac97.c 中完成初始化的全局變量 au1550_state 下的 codec 域,這是一個指向struct ac97_codec 結構體的指針 。
以下接合具體的驅動程式代碼來分析:
參考datasheet資料: WM9712_Rev4.5.pdf ml2010-0129.pdf(美菱項目開發闆布局圖)
核心中觸摸屏驅動程式主體為 drivers/input/touchscreen/wm9712.c,雖然在該檔案中有module_init(wm9713_init); 并且wm9713_init( ) 直接調用 wm9712_init(),但是實際上這裡并不是完成觸摸屏初始化工作的地方,因為 wm9713_init 調用的是 wm9712_init (NULL); ——根據wm9712_init()的原型 int wm9712_init( struct ac97_codec *codec); 和定義,該函數需要的是一個AC-Link接口參數,如果傳入的是NULL,什麼工作都不會做的,因為無法通路wm9712中的寄存器。
是以真正調用wm9712_init 進行觸摸屏初始化的是 au1550_ac97.c 中的 au1550_probe( ) 函數
#ifdef CONFIG_AU1200_ARGON
#ifdef CONFIG_TOUCHSCREEN_WM9712
wm9712_init (s->codec) ;
#endif
#endif
WM9712的觸摸屏相關寄存器定義和說明在 WM9712_Rev4.5.pdf 第42頁開始,從第63頁開始為所有WM9712寄存器的總結性描述。
進入wm9712_init() 函數之後,首先讀取的是裝置的Vendor資訊(P70),然後進入 wm9712_hw_init( ) 函數。
wm9712_hw_init( ) :
首先将 register 78h 的 bit [15:14] 變成 01,将觸摸屏的digitiser變為準備狀态,打開pen detect,一旦有觸摸,則 Pen digitiser and pen detect 都将開啟;
接下來對register 0x1A 的設定與觸摸屏無關,跳過;
根據 美菱項目手冊(ml2010-0129.pdf )第8頁的wm9712連接配接圖可知,WM9712晶片的46腳GPIO3/PENDOWN 将向AU1200發出 AU_PEN_IRQ 中斷信号 ,表征發生了屏觸摸,是以46腳必須設定為信号腳而非GPIO功能 ; 于是,又根據WM9712_Rev4.5.pdf 第54頁描述,寄存器 56h 的 Bit 3 必須設定為0,并且同時須設定寄存器 4Ch 的 Bit 3 為0 —— 這也正是代碼中此處的意義所在。
寄存器 14h 的設定與觸摸屏無關,跳過;
随後的工作是在MV700基礎上的完全新内容,核心就是通過建立一個核心線程來監控wm9712的狀态,防止觸摸屏失去響應帶來的假當機——因為通過反複的測試,發現了觸摸屏假死的直接原因是wm9712的相關寄存器被全部複位了,但是為什麼會導緻這樣的情況,目前還尚未找出,是以以下的方法也隻能是采用比較消耗系統資源的實時查詢方式來監測和恢複。
關于核心線程 kernel thread 機制,參考的是 http://hi.baidu.com/zkheartboy/blog/item/fa1797cbadc039fa52664f94.html
#ifdef WM9712_DETECT
wm9712_detect_task = kthread_create(wm9712_detect , NULL, "wm9712_detect_task");
——建立核心線程 wm9712_detect
if(IS_ERR(wm9712_detect_task))
{
printk("Unable to start kernel thread~~~~zhouxiao./n");
err = PTR_ERR(wm9712_detect_task);
wm9712_detect_task = NULL;
}
else
{
printk("/n/n/n/nCreate kernel_thread wm9712_detect_task OK by zhouxiao~~~~~~/n/n/n/n/n");
wake_up_process(wm9712_detect_task); ——建立成功之後就激活該線程
}
for(i=0; i<43; i++) {
wm9712_regs[i][1] = wm9712_codec->codec_read(wm9712_codec, wm9712_regs[i][0]);
printk("%xh = 0x%x ", wm9712_regs[i][0], wm9712_regs[i][1]);
} ——初始化備份wm9712的全部43個寄存器
#endif
其中 wm9712_regs 在同一c檔案中定義為 u16 wm9712_regs[43][2] 二維數組,對應于wm9712的全部43個寄存器(見 WM9712_Rev4.5.pdf P62的寄存器整體布局 )位址和值,用于wm9712出現異常寄存器全部複位後的恢複。
與此同時,程式中還定義了 u16 key_regs_index[]={ 20, 21, 30 }; 這是 三個關鍵寄存器資訊的在 wm9712_regs 數組中的 索引号,所謂的關鍵寄存器是指在核心線程監測wm9712狀态過程中,需要頻繁監測(每次線程執行都要監測)的寄存器,此處這三個寄存器其實就是 2ah、2ch和56h; 而其他的寄存器則隻需要較長時間地進行一次檢測和更新即可,更新的目的是為了在出現故障之後恢複到wm9712所有寄存器中的值是最新的内容,進而不會影響使用者對于ac97和觸摸屏的最新設定。
完成核心線程 wm9712_detect 的建立後,觸摸屏初始化工作就完成了。觸摸屏硬體開始工作,而核心監測線程也開始運作, wm9712_detect 執行的函數定義如下:
int wm9712_detect(void *data)
{
u16 wm9712_regs_buf[43]={0}; ——wm9712的全部寄存器值的緩沖區
unsigned char index[43] = {0}; ——wm9712中值改變了的寄存器在 wm9712_regs 數組中的索引
u16 i=0, j=0, itemp=0;
u32 k=0; ——while循環數,即秒數
char *argv[] = {"000000", NULL }; ——傳遞給應用程式 wm9712_recover 的參數
u16 back_interval = 10; ——進行全部寄存器檢測的時間間隔
while(1){
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop())
{
printk("/nkthread_should_stop~~~~~~/n");
break;
}
if(k%back_interval) { —— 每back_interval秒内之間隻對關鍵寄存器進行監測
for(i=0; i<(sizeof(key_regs_index)/sizeof(u16)); i++) {
itemp = key_regs_index[i];
wm9712_regs_buf[itemp] = wm9712_codec->codec_read(wm9712_codec, wm9712_regs[itemp][0]);
}
}
else ——每隔 back_interval秒進行一次全部寄存器檢測和更新備份
{
j=0;
for(i=0; i<43; i++) {
wm9712_regs_buf[i] = wm9712_codec->codec_read(wm9712_codec, wm9712_regs[i][0]);
if( wm9712_regs_buf[i] != wm9712_regs[i][1] ) {
index[j++] = i;
——當某個寄存器目前值與之前的值不等,則記錄下該寄存器在wm9712_regs數組中的的索引值 以便更新wm9712_regs
}
}
}
if( unlikely(wm9712_regs_buf[30] & (1<<3) ) ){
——unlikely用于編譯器優化,表示該情況發生機率不大,此處實際上隻對 wm9712_regs_buf[30],即 56h 寄存器進行監測,看其 bit 3 是否被置1(即GPIO3/PENDOWN引腳是否被設定成GPIO功能而不是PENDOWN檢測功能)了,如果是則以此判斷wm9712所有寄存器 都已經被複位了,能這樣判斷的依據是 56h 寄存器在初始化被設定為PENDOWN功能後,正常情況下就不會再被改變了。
wm9712_recovery(index, j); ——基于 wm9712_regs 進行wm9712寄存器的最新值恢複,函數實作見下
recovery_number ++; ——記錄系統開機以來總共進行的wm9712恢複次數
printk("/n/n~~~~~~~~~~~WM9712 Recovery~~~~~~~~~~~~~~~/n/n/n");
sprintf( argv[0], "%d", recovery_number);
call_usermodehelper("/usr/bin/wm9712_recover", argv, NULL, 1);
——在核心狀态下調用使用者态程式 wm9712_recover,并将wm9712恢複次數作為參數傳遞給它。該程式源碼見下
}
else{ ——如果沒有發生寄存器複位,則每隔 back_interval一次地對期間發生了改變的寄存器進行更新,存儲到全局的wm9712_regs數組中
if(k%back_interval == 0)
{
for( i=0; i<j; i++ ) {
itemp = index[i];
wm9712_regs[itemp][1] = wm9712_regs_buf[itemp];
if(i == j-1) {
printk("backup %d registers/n", j);
}
}
}
schedule_timeout(HZ); ——因為沒有發生寄存器全複位, 故而讓出CPU運 行其他線程,并在指定的時間内重新被排程,此處指定的時間可了解為頻率的倒數,1HZ即1秒。
}
k++;
}
return 0;
}
static void wm9712_recovery(unsigned char *index_array, u16 index_limit)
{
u16 i, index;
——本意是隻更新發生了變化的那些寄存器,但是測試出來的效果有問題
for( i=0; i<43; i++ ) {
wm9712_codec->codec_write(wm9712_codec, wm9712_regs[i][0], wm9712_regs[i][1]);
}
}
應用程式wm9712_recover.c源碼:
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
FILE *file;
char buf[256];
int i, result;
int recovery_number = 0;
file = fopen("/tmp/wm9712_recovery.txt", "w+ "); ——每次都覆寫地更新文本檔案,以使得文本不會無限增長
if(file == NULL)
{
fprintf(stderr, "Open/Create /tmp/wm9712_recovery.txt fail");
exit(-1);
}
fprintf(file, "wm9712 recovered: %s/n", argv[0]);
return 0;
}