之前存在的触摸屏问题 :
触摸屏时常会失去响应,但是此时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;
}