之前存在的觸控螢幕問題
:
觸控螢幕時常會失去響應,但是此時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;
/*printk("/n~~~~~index_limit = %d/n", index_limit);
for(i=0; i<(sizeof(key_regs_index)/sizeof(u16)); i++) {
index = key_regs_index[i];
wm9712_codec->codec_write(wm9712_codec, wm9712_regs[index][0], wm9712_regs[index][1]);
}
for( i=0; i<index_limit; i++ ) {
index = index_array[i];
wm9712_codec->codec_write(wm9712_codec, wm9712_regs[index][0], wm9712_regs[index][1]);
printk("Having written 0x%x to Register %xh/n", wm9712_regs[index][1], wm9712_regs[index][0] );
}*/ ——本意是只更新發生了變化的那些寄存器,但是測試出來的效果有問題
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;
}