Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1265574
  • 博文数量: 404
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 5382
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-03 16:29
文章存档

2010年(40)

2009年(140)

2008年(224)

我的朋友

分类: LINUX

2008-09-26 10:41:13

触摸屏驱动在/kernel/drivers/char/s3c2410-ts.c 文件中。


该驱动总要有以下重要数据结构:


 1.
触摸屏的file_operations
static struct file_operations s3c2410_fops={
    owner: THIS_MODULE,
    open: s3c2410_ts_open,
    read: s3c2410_ts_read,
    release: s3c2410_ts_release,
#ifdef USE_ASYNC
    fasync: s3c2410_ts_fasync,
#endif
    poll: s3c2410_ts_poll,
};


 2.
全局变量TS_DEV结构体,用来保存触摸屏的相关参数、等待处理的消息队列、当前采样数据、上一次采样数据等信息


typedef struct {
    unsigned int penStatus;
    TS_RET buf[MAX_TS_BUF];
    unsigned int head, tail;
    wait_queue_head_t wq; /
 wait_down_int();

     函数request_irq Linux 系统中驱动程序注册中断的方法。irq 为所要申请的硬件中断号,handler 为系统所注册的中断处理子程序,irq_flags 为申请时的选项,devname 为指向设备名称的字符指针,dev_id 为申请时告诉系统的设备标识。若中断申请成功则返回0,失败则返回负值。


ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, SA_INTERRUPT,
           DEVICE_NAME, s3c2410_isr_adc);
   
调用该函数来进行A/D转换的中断注册,所要申请的硬件中断号为IRQ_ADC_DONE62),在arch/irq s.h中定义;系统所注册的中断处理子程序为s3c2410_isr_adc 函数;申请中断选项为SA_INTERRUPT,表示中断处理程序是快速处理程序,即快速处理程序运行时,所有中断都被屏蔽;设备名称定义为DEVICE_NAME,即"s3c2410-ts";而设备标识仍然用中断处理子程序代替。

ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT,
          DEVICE_NAME, s3c2410_isr_tc);
   
接着继续调用该函数来进行触摸屏触摸的中断注册,所要申请的硬件中断号为IRQ_TC61);系统所注册的中断处理子程序为s3c2410_isr_tc 函数;申请中断选项为SA_INTERRUPT,表示中断处理程序是快速处理程序,即快速处理程序运行时,所有中断都被屏蔽;设备名称定义为DEVICE_NAME,即"s3c2410-ts";而设备标识仍然用中断处理子程序代替。

wait_down_int();
   
调用该宏函数来设置触摸屏为等待中断模式【笔按下产生中断】,具体定义如下:


#define wait_down_int() { ADCTSC = DOWN_INT | XP_PULL_UP_EN | \
    XP_AIN | XM_HIZ | YP_AIN | YM_GND | \
    XP_PST(WAIT_INT_MODE); }
   
用该宏函数来设置ADC 触摸屏控制寄存器,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:


DOWN_INT = 1<<8 * 0 
该位保留且应该设为0 【笔按下或笔抬起中断信号控制位,设为0 表示笔按下产生中断信号】
XP_PULL_UP_EN = 1<<3 * 0 
上拉开关使能,设为0 表示XP 引脚上拉使能
XP_AIN = 1<<4 * 1 
选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚
XM_HIZ = 1<<5 * 0 
选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态
YP_AIN = 1<<6 * 1 
选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚
YM_GND = 1<<7 * 1 
选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地
XP_PST(WAIT_INT_MODE); = 3  X
坐标Y坐标手动测量设置,设为3 表示等待中断模式

#ifdef CONFIG_DEVFS_FS
devfs_ts_dir = devfs_mk_dir(NULL, "touchscreen", NULL);
devfs_tsraw = devfs_register(devfs_ts_dir, "0raw", DEVFS_FL_DEFAULT,
       tsMajor, TSRAW_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,
       &s3c2410_fops, NULL);
#endif


  
这里调用了devfs_mk_dir 函数,在设备文件系统中创建了一个名为touchscreen 的目录,并返回一个带有目录结构的数据结构变量devfs_ts_dir。将该变量作为下一步devfs_register 函数的参数,该参数在调用设备文件系统注册清除函数devfs_unregister 时也要作为参数传入。


   
调用devfs_register 函数后,会在刚才创建的touchscreen 目录下再创建一个名为0raw 的设备文件节点。该函数的参数中,DEVFS_FL_DEFAULT 为该函数的标志选项,tsMajor 为注册字符设备时系统自动分配的主设备号,TSRAW_MINOR1)为次设备号,S_IFCHR | S_IRUSR | S_IWUSR 为默认的文件模式,&s3c2410_fops 为传入内核的触摸屏file_operations 结构中的函数接口,私有数据指针为空。返回一个devfs_handle_t 数据结构的变量devfs_tsraw,这会在调用设备文件系统注册清除函数devfs_unregister 时作为参数传入。

   模块的退出函数为s3c2410_ts_exit,该函数的工作就是清除已注册的字符设备,中断以及设备文件系统。
static void __exit s3c2410_ts_exit(void)
{
#ifdef CONFIG_DEVFS_FS
   devfs_unregister(devfs_tsraw);
   devfs_unregister(devfs_ts_dir);
#endif
   unregister_chrdev(tsMajor, DEVICE_NAME);
#ifdef CONFIG_PM
   pm_unregister(tsdev.pm_dev);
#endif
   free_irq(IRQ_ADC_DONE, s3c2410_isr_adc);
   free_irq(IRQ_TC, s3c2410_isr_tc);
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_tsraw);
devfs_unregister(devfs_ts_dir);
#endif


   
这里首先清除原先后一步创建设备文件节点0raw 的结构变量devfs_tsraw,然后再清除创建touchscreen 目录的结构变量devfs_ts_dir
unregister_chrdev(tsMajor, DEVICE_NAME);


  
接下来删除字符设备的注册信息。
void free_irq(unsigned int irq, void *dev_id)
   
函数free_irq 与函数request_irq 相对应,通常在模块被卸载时调用,负责注销一个已经申请的中断。
free_irq(IRQ_ADC_DONE, s3c2410_isr_adc);
free_irq(IRQ_TC, s3c2410_isr_tc);
   
最后依次注销A/D转换和定时器这两个已经申请的中断。

接下来看一下A/D转换的中断处理函数:
static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg)
   
其中参数irq 为中断号,dev_id 为申请中断时告诉系统的设备标识,regs 为中断发生时寄存器内容。该函数在中断产生时由系统来调用,调用时以上参数已经由系统传入。

/kernel/include/linux/spinlock.h 文件中:

#define spin_lock_irq(lock) do{local_irq_disable();spin_lock(lock);}while (0)
#define spin_unlock_irq(lock) do{spin_unlock(lock);local_irq_enable();}while(0)

#define DEBUG_SPINLOCKS 0
#if (DEBUG_SPINLOCKS < 1)
  typedef struct { } spinlock_t;
  #define SPIN_LOCK_UNLOCKED (spinlock_t) { }
#define spin_lock_init(lock) do { } while(0)
#define spin_lock(lock)  (void)(lock)
#define spin_unlock_wait(lock) do { } while(0)
#define spin_unlock(lock) do { } while(0)
可见上面这四个宏函数都是空函数,这样的话spin_lock_irq(lock)spin_unlock_irq(lock)这两个宏函数就相当于分别只调用了local_irq_disable();local_irq_enable();两个宏函数。关于自旋锁的作用和概念可以参考一篇《Linux内核的同步机制》文章的相关章节。

/kernel/include/asm-arm/system.h 文件中:
#define local_irq_disable() __cli()
#define local_irq_enable() __sti()
/kernel/include/asm-arm/proc-armo/system.h 文件中:

#define __sti()     \
do {     \
   unsigned long temp;   \
   __asm__ __volatile__(   \
" mov %0, pc  @ sti\n" \
" bic %0, %0, #0x08000000\n"  \
" teqp %0, #0\n"   \
   : "=r" (temp)    \
   :     \
   : "memory");    \
} while(0)

#define __cli()     \
do {     \
   unsigned long temp;   \
   __asm__ __volatile__(   \
" mov %0, pc  @ cli\n" \
" orr %0, %0, #0x08000000\n"  \
" teqp %0, #0\n"   \
   : "=r" (temp)    \
   :     \
   : "memory");    \
} while(0)

最后用ARM 汇编指令实现了对IRQ 的使能和禁止。
spin_lock_irq(&(tsdev.lock));
   
这样调用spin_lock_irq 宏函数,实际上只是做了local_irq_disable();一步,就是禁止IRQ 中断。

if (tsdev.penStatus == PEN_UP)
   s3c2410_get_XY();
   
然后根据变量tsdev.penStatus 所处的状态,若为笔抬起则调用s3c2410_get_XY 函数来取得A/D转换得到的坐标值,该函数会在后面说明。
#ifdef HOOK_FOR_DRAG
else
   s3c2410_get_XY();
#endif
   
这里表示如果定义了笔拖曳,且在笔没有抬起的情况下,继续调用s3c2410_get_XY 函数来得到最新的坐标值。
spin_unlock_irq(&(tsdev.lock));
   
最后调用spin_unlock_irq 宏函数,相当于只做了local_irq_enable();一步,来重新使能IRQ 中断。最后退出这个中断服务子程序。


   
继续来看一下另一个中断处理函数,即触摸屏触摸中断处理函数:
static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg)
   
该函数的参数和上面A/D转换中断处理函数的定义一样,不再累赘。
spin_lock_irq(&(tsdev.lock));

    也同上面的意思一样,首先禁止IRQ 中断。
if (tsdev.penStatus == PEN_UP) {
   start_ts_adc();
}
   
接着根据变量tsdev.penStatus 的状态值判断是否进行A/D转换。若笔抬起,则调用函数start_ts_adc 来进行A/D转换,该函数会在后面说明。
else {
   tsdev.penStatus = PEN_UP;
   DPRINTK("PEN UP: x: d, y: d\n", x, y);
   wait_down_int();
   tsEvent();
}
   
如果变量tsdev.penStatus 的状态值不是笔抬起,则先将该变量状态设为笔抬起,然后调用宏函数wait_down_int()。该宏函数已在前面说明,用来设置触摸屏为等待中断模式。最后调用tsEvent 函数指针所指的函数,在模块初始化函数s3c2410_ts_init 中,tsEvent 指向的是一个空函数tsEvent_dummy,而在打开设备函数s3c2410_ts_open 中,tsEvent 会指向tsEvent_raw 函数,该函数负责填充触摸屏缓冲区,并唤醒等待的进程。该函数也会在后面加以说明。
spin_unlock_irq(&(tsdev.lock));
   
中断处理函数的最后一步都一样,重新使能IRQ 中断。退出中断服务子程序。

    下面先来看启动A/D转换的函数:
static inline void start_ts_adc(void)
adc_state = 0;
mode_x_axis();
start_adc_x();
   
简简单单的3步。
   
第一步,对A/D转换的状态变量清零。
   
第二步,调用mode_x_axis 宏函数,具体定义如下:
#define mode_x_axis() { ADCTSC = XP_EXTVLT | XM_GND | YP_AIN | YM_HIZ | \
    XP_PULL_UP_DIS | XP_PST(X_AXIS_MODE); }


   
该宏函数用来设置ADC触摸屏控制寄存器为测量X坐标模式,参考S3C2410 datasheet 中关于触摸屏的章节,具体设置参数如下:
XP_EXTVLT = 1<<4 * 0 
选择nXPON 引脚输出值,设为0 表示nXPON 引脚输出0,则XP 引脚为接外部电压
XM_GND = 1<<5 * 1 
选择XMON 引脚输出值,设为1 表示XMON 引脚输出1,则XM 引脚为接地
YP_AIN = 1<<6 * 1 
选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚
YM_HIZ = 1<<7 * 0 
选择YMON 引脚输出值,设为0 表示YMON 引脚输出0,则YM 引脚为高阻态
XP_PULL_UP_DIS = 1<<3 * 1 
上拉开关使能,设为1 表示XP 引脚上拉禁止
XP_PST(X_AXIS_MODE); = 1  X
坐标Y坐标手动测量设置,设为1 表示X坐标测量模式
   
第三步,调用start_adc_x 宏函数,具体定义如下:
#define start_adc_x() { ADCCON = PRESCALE_EN | PRSCVL(49) | \
    ADC_INPUT(ADC_IN5) | ADC_START_BY_RD_EN | \
    ADC_NORMAL_MODE; \
     ADCDAT0; }
   
该宏函数用来设置ADC控制寄存器启动X坐标的A/D转换,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:
PRESCALE_EN = 1<<14 * 1  A/D
转换器使能,设为1 表示使能A/D转换器
PRSCVL(49) = 49<<6  A/D
转换器值,设为49
ADC_INPUT(ADC_IN5) = 5<<3 
选择模拟输入通道,设为5 表示AIN[5] 引脚作为模拟输入通道
ADC_START_BY_RD_EN = 1<<1 * 1  A/D
转换通过读启动,设为1 表示通过读操作启动A/D转换使能
ADC_NORMAL_MODE; = 1<<2 * 0 
选择待命模式,设为0 表示正常操作模式
ADCDAT0; 
读取X坐标的ADC转换数据寄存器
   
由于设置了A/D转换通过读启动,则该ADCCON 寄存器的最低位ENABLE_START 启动A/D转换位就无效了。在最后一步读取ADCDAT0 寄存器这一操作时就启动了A/D转换。


static inline void s3c2410_get_XY(void)
   
这就是获取A/D转换所得到的坐标值的函数。
if (adc_state == 0)
{
  adc_state = 1;
  disable_ts_adc();
  y = (ADCDAT0 & 0x3ff);
  mode_y_axis();
  start_adc_y();
}
   
这里首先查看A/D转换的状态变量,若为0 表示进行过X坐标的A/D转换,将该变量设为1。然后调用宏函数disable_ts_adc,该宏函数定义如下:
#define disable_ts_adc() { ADCCON &= ~(ADCCON_READ_START); }
   
这个宏函数主要工作就是禁止通过读操作启动A/D转换,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:
ADCCON_READ_START = 1<<1  A/D
转换通过读启动,设为0 表示通过读操作启动A/D转换禁止
   
然后y = (ADCDAT0 & 0x3ff); 这一步将X坐标的ADC转换数据寄存器的D9D0 10为读出到变量y(这里由于是竖屏,参考原理图后知道,硬件连线有过改动,将XPXM YPYM 进行了对换,这样ADCDAT0 里读出的是YPYM 方向电阻导通的值,也就是y轴坐标值)。这个mode_y_axis 宏函数定义如下:
#define mode_y_axis() { ADCTSC = XP_AIN | XM_HIZ | YP_EXTVLT | YM_GND | \
    XP_PULL_UP_DIS | XP_PST(Y_AXIS_MODE); }
   
该宏函数用来设置ADC触摸屏控制寄存器为测量Y坐标模式,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:
XP_AIN = 1<<4 * 1 
选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚
XM_HIZ = 1<<5 * 0 
选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态
YP_EXTVLT = 1<<6 * 0 
选择nYPON 引脚输出值,设为0 表示nYPON 引脚输出0,则YP 引脚为接外部电压
YM_GND = 1<<7 * 1 
选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地
XP_PULL_UP_DIS = 1<<3 * 1 
上拉开关使能,设为1 表示XP 引脚上拉禁止
XP_PST(Y_AXIS_MODE); = 2  X
坐标Y坐标手动测量设置,设为2 表示Y坐标测量模式
   
最后调用start_adc_y 宏函数,具体定义如下:
#define start_adc_y() { ADCCON = PRESCALE_EN | PRSCVL(49) | \
    ADC_INPUT(ADC_IN7) | ADC_START_BY_RD_EN | \
    ADC_NORMAL_MODE; \
     ADCDAT1; }
  
该宏函数用来设置ADC控制寄存器启动Y坐标的A/D转换,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:
PRESCALE_EN = 1<<14 * 1  A/D
转换器使能,设为1 表示使能A/D转换器
PRSCVL(49) = 49<<6  A/D
转换器值,设为49
ADC_INPUT(ADC_IN7) = 7<<3 
选择模拟输入通道,设为7 表示AIN[7] 引脚作为模拟输入通道
ADC_START_BY_RD_EN = 1<<1 * 1  A/D
转换通过读启动,设为1 表示通过读操作启动A/D转换使能
ADC_NORMAL_MODE; = 1<<2 * 0 
选择待命模式,设为0 表示正常操作模式
ADCDAT1; 
读取Y坐标的ADC转换数据寄存器

else if (adc_state == 1)
{
  adc_state = 0;
  disable_ts_adc();
  x = (ADCDAT1 & 0x3ff);
  tsdev.penStatus = PEN_DOWN;
  DPRINTK("PEN DOWN: x: d, y: d\n", x, y);
  wait_up_int();
  tsEvent();
}
   
若查看A/D转换的状态变量,若为1 表示进行过Y坐标的A/D转换,将该变量设为0。然后调用宏函数disable_ts_adc 来禁止通过读操作启动A/D转换。
   
接着将x = (ADCDAT1 & 0x3ff); 这一步将Y坐标的ADC转换数据寄存器的D9D0 10为读出到变量x(这里由于是竖屏,参考原理图后知道,硬件连线有过改动,将XPXM YPYM 进行了对换,这样ADCDAT1 里读出的是XPXM 方向电阻导通的值,也就是x轴坐标值)。
   
随后将变量tsdev.penStatus 的状态值改为笔按下,并调用wait_up_int 宏函数来设置触摸屏为等待中断模式【笔抬起产生中断】,具体定义如下:
#define wait_up_int() { ADCTSC = UP_INT | XP_PULL_UP_EN | XP_AIN | XM_HIZ | \
    YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE); }
   
用该宏函数来设置ADC 触摸屏控制寄存器,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:
UP_INT = 1<<8 * 1 
该位保留且应该设为0,这里设为1 不知道为什么 【笔按下或笔抬起中断信号控制位,设为1 表示笔抬起产生中断信号】
XP_PULL_UP_EN = 1<<3 * 0 
上拉开关使能,设为0 表示XP 引脚上拉使能
XP_AIN = 1<<4 * 1 
选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚
XM_HIZ = 1<<5 * 0 
选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态
YP_AIN = 1<<6 * 1 
选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚
YM_GND = 1<<7 * 1 
选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地
XP_PST(WAIT_INT_MODE); = 3  X
坐标Y坐标手动测量设置,设为3 表示等待中断模式
   
最后调用函数指针tsEvent 所指向的函数。在s3c2410_get_XY 函数里面,应该表示这个驱动的设备文件已经打开,在打开设备文件函数中,tsEvent 函数指针就指向了tsEvent_raw 这个函数,也就是说,下面执行的是tsEvent_raw 函数。tsEvent_raw 函数负责填充触摸屏缓冲区,并唤醒等待的进程,该函数会在后面说明。

 
 
###################################################################################
static void tsEvent_raw(void)
   
来看一下tsEvent_raw 这个函数。
if (tsdev.penStatus == PEN_DOWN)
{
  BUF_HEAD.x = x;
  BUF_HEAD.y = y;
  BUF_HEAD.pressure = PEN_DOWN;

一上来就根据变量tsdev.penStatus 的状态值进行判断,若为笔按下,将从A/D转换器中采集的xy轴坐标以及笔按下的状态存入变量tsdev 中的buf 成员中的相应变量中。

#ifdef HOOK_FOR_DRAG
  ts_timer.expires = jiffies + TS_TIMER_DELAY;
  add_timer(&ts_timer);
#endif
   
如果定义了笔拖曳,先将定时器的定时溢出值更新,然后调用add_timer 函数重新增加定时器计时,变量ts_timer struct timer_list 数据结构。
}
else
{
#ifdef HOOK_FOR_DRAG
  del_timer(&ts_timer);
#endif

    如果定义了笔拖曳,调用del_timer 函数来删除定时器,变量ts_timer struct timer_list 数据结构。

  BUF_HEAD.x = 0;
  BUF_HEAD.y = 0;
  BUF_HEAD.pressure = PEN_UP;
}
   
若变量tsdev.penStatus 的状态值不是笔按下,则xy轴坐标写为0和笔抬起的状态一起存入变量tsdev 中的buf 成员中的相应变量中。
tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF);
   
其中INCBUF 宏函数定义如下:
#define INCBUF(x,mod)  ((++(x)) & ((mod) - 1))
由于这里MAX_TS_BUF=16,这样(mod) - 1)就为150x0F),所以这个宏函数相当于就是将变量tsdev.head 这个表示buf 头位置的值加1,然后取模16,即指向下一个buf ,形成一个在环形缓冲区上绕环的头指针。

/kernel/include/linux/sched.h 文件中:
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
该宏函数定义为__wake_up 函数,参数TASK_INTERRUPTIBLE 1,表示要唤醒的任务的状态为中断模式,参数1 表示要唤醒的互斥进程数目为1
对应的唤醒操作包括wake_up_interruptiblewake_upwake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。关于interruptible_sleep_on wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。
/kernel/kernel/sched.c 文件中:
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{

if (q) {
  unsigned long flags;
  wq_read_lock_irqsave(&q->lock, flags);
  __wake_up_common(q, mode, nr, 0);
  wq_read_unlock_irqrestore(&q->lock, flags);
}
}
宏函数wq_read_lock_irqsave 的作用主要就是保存IRQ FIQ 的中断使能状态,并禁止IRQ 中断;而宏函数wq_read_unlock_irqrestore 的作用就是恢复IRQ FIQ 的中断使能状态。现在可以得知__wake_up 这个函数的作用,它首先保存IRQ FIQ 的中断使能状态,并禁止IRQ 中断,接着调用__wake_up_common 函数来唤醒等待q 队列的进程,最后再恢复IRQ FIQ 的中断使能状态。

static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode,
          int nr_exclusive, const int sync)
该函数的作用是唤醒在等待当前等待队列的进程。参数q 表示要操作的等待队列,mode 表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE TASK_INTERRUPTIBLE 等。nr_exclusive 是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???

/kernel/include/linux/wait.h 文件中:
struct __wait_queue_head {
wq_lock_t lock;
struct list_head task_list;
#if WAITQUEUE_DEBUG
long __magic;
long __creator;
#endif
};
typedef struct __wait_queue_head wait_queue_head_t;
这是等待队列数据结构。
# define wq_read_lock_irqsave spin_lock_irqsave
# define wq_read_unlock_irqrestore spin_unlock_irqrestore
看到这里可以知道其实宏函数wq_read_lock_irqsave wq_read_unlock_irqrestore 等价于宏函数spin_lock_irqsave spin_unlock_irqrestore,并直接将自己的参数传了下去。

/kernel/include/linux/spinlock.h 文件中:

#define spin_lock_irqsave(lock, flags)  do {local_irq_save(flags);spin_lock(lock);}while (0)
#define spin_unlock_irqrestore(lock, flags)  do {spin_unlock(lock);  local_irq_restore(flags); } while (0)
在这两个宏函数中,前面已经提到spin_lock spin_unlock 其实都为空函数,那么实际只执行了local_irq_save local_irq_restore 这两个宏函数。

/kernel/include/asm-arm/system.h 文件中:

#define local_irq_save(x) __save_flags_cli(x)
#define local_irq_restore(x) __restore_flags(x)
这里local_irq_save local_irq_restore 这两个宏函数又分别等价于__save_flags_cli __restore_flags 这两个宏函数。

/kernel/include/asm-arm/proc-armo/system.h 文件中:


#define __save_flags_cli(x)    \
do {      \
   unsigned long temp;    \
   __asm__ __volatile__(    \
" mov %0, pc  @ save_flags_cli\n" \
" orr %1, %0, #0x08000000\n"   \
" and %0, %0, #0x0c000000\n"   \
" teqp %1, #0\n"    \
   : "=r" (x), "=r" (temp)   \
   :      \
   : "memory");     \
} while (0)


#define __restore_flags(x)    \
do {      \
   unsigned long temp;    \
   __asm__ __volatile__(    \
" mov %0, pc  @ restore_flags\n" \
" bic %0, %0, #0x0c000000\n"   \
" orr %0, %0, %1\n"    \
" teqp %0, #0\n"    \
   : "=&r" (temp)    \
   : "r" (x)     \
   : "memory");     \
} while (0)
最后用ARM 汇编指令实现了对当前程序状态寄存器CPSR 中的IRQ FIQ 中断使能状态的保存和恢复。而且在__save_flags_cli 宏函数中,除了对IRQ FIQ 中断使能状态的保存外,还禁止了IRQ 中断。

wake_up_interruptible(&(tsdev.wq));
   
在这个tsEvent_raw 函数最后,调用wake_up_interruptible 函数来以中断模式唤醒等待tsdev.wq 队列的进程。

由于上面这个wake_up_interruptible 函数的作用还不是很明确,所以需要分析一下打开设备文件这个函数。

触摸屏打开设备文件函数定义如下:

static int s3c2410_ts_open(struct inode *inode, struct file *filp) tsdev.head = tsdev.tail = 0;

tsdev.penStatus = PEN_UP;

首先是最简单的将变量tsdev.head tsdev.tail 这两个表示buf 头尾位置的值清零,然后将变量tsdev.penStatus 的状态值初始化为笔抬起。

 #ifdef HOOK_FOR_DRAG init_timer(&ts_timer); ts_timer.function = ts_timer_handler;

#endif

如果定义了笔拖曳,先调用init_timer 函数来初始化一个定时器,变量ts_timer struct timer_list 数据结构,然后将定时调用的函数指针指向ts_timer_handler 函数。 tsEvent = tsEvent_raw; 将函数指针tsEvent 指向tsEvent_raw 函数。

/kernel/include/linux/wait.h 文件中:

static inline void init_waitqueue_head(wait_queue_head_t *q) {

#if WAITQUEUE_DEBUG if (!q) WQ_BUG();

#end

if q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;

INIT_LIST_HEAD(&q->task_list);

#if WAITQUEUE_DEBUG q->__magic = (long)&q->__magic;

q->__creator = (long)current_text_addr();

#endif     }

 该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prevnext指向它自身。

init_waitqueue_head(&(tsdev.wq));

在这个s3c2410_ts_open 函数中,调用init_waitqueue_head 函数来初始化一个定义在变量tsdev 中的等待队列头的成员结构。

MOD_INC_USE_COUNT;

return 0;

最后调用MOD_INC_USE_COUNT; 来对设备文件计数器加一计数,并返回再来分析一下用户层要调用的读取设备文件的接口函数:

static ssize_t s3c2410_ts_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)

 这个函数实现的任务是将事件队列从设备缓存中读到用户空间的数据缓存中。实现的过程主要是通过一个循环,只有在事件队列的头、尾指针不重合时,才能成功的从tsdev.tail指向的队列尾部读取到一组触摸信息数据,并退出循环。否则调用读取函数的进程就要进入睡眠。

TS_RET ts_ret;

retry:

if (tsdev.head != tsdev.tail) {

 int count;

count = tsRead(&ts_ret);

if (count) copy_to_user(buffer, (char *)&ts_ret, count);

return count;    }

这个函数中,首先通过变量tsdev.head tsdev.tail 是否相等来判断环形缓冲区是否为空。若不相等则表示环形缓冲区中有触摸屏数据,调用tsRead 函数将触摸屏数据读入TS_RET 数据结构的ts_ret 变量中,该函数会在后面说明。 接下来调用copy_to_user 函数来把内核空间的数据复制到用户空间,在这里就是把驱动程序里的变量ts_ret 中的数据复制到用户程序的buffer 中,然后返回复制的数据长度。

else {

 if (filp->f_flags & O_NONBLOCK) return -EAGAIN;

interruptible_sleep_on(&(tsdev.wq));

if (signal_pending(current)) return -ERESTARTSYS;

goto retry; }

若变量tsdev.head tsdev.tail 相等,则表示环形缓冲区为空,首先根据file->f_flags 与上O_NONBLOCK 值来进行判断,若为O_NONBLOCK 值,则表示采用非阻塞的文件IO方法,立即返回,否则才会调用interruptible_sleep_on 函数,调用该函数的进程将会进入睡眠,直到被唤醒。关于O_NONBLOCK 值的含义在我总结的《IIS音频驱动程序分析》一文中有详细说明。

/kernel/kernel/sched.c 文件中:

void interruptible_sleep_on(wait_queue_head_t *q) {

 SLEEP_ON_VAR current->state = TASK_INTERRUPTIBLE;

SLEEP_ON_HEAD schedule();

SLEEP_ON_TAIL  }

常用的睡眠操作有interruptible_sleep_onsleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal

关于interruptible_sleep_on wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。

如果进程被唤醒,则会继续跳到retry: 循环读取环形缓冲区,直到读取到一组触摸信息数据才会退出。 static int tsRead(TS_RET * ts_ret) 这个函数主要将环形缓冲区的xy轴坐标数据和笔的状态数据传入该函数形式参数所指的指针变量中。 spin_lock_irq(&(tsdev.lock)); 上文已经解释过了,

调用该宏函数来禁止IRQ 中断。

 ts_ret->x = BUF_TAIL.x;

ts_ret->y = BUF_TAIL.y;

ts_ret->pressure = BUF_TAIL.pressure;

 tsdev.tail = INCBUF(tsdev.tail, MAX_TS_BUF);

接着把变量tsdev 的环形缓冲区中相关数据赋值给该函数形式参数所指的指针变量中,并将表示环形缓冲区队列尾部的变量tsdev.tail 加一,这就意味着从环形队列中读取一组数据,尾指针加一。

spin_unlock_irq(&(tsdev.lock));

 return sizeof(TS_RET);

上文已经解释过了,调用该宏函数来重新使能IRQ 中断,然后返回。

再来看一个定时器定时调用的函数:

#ifdef  HOOK_FOR_DRAG   

static void ts_timer_handler(unsigned long data){

spin_lock_irq(&(tsdev.lock));

 if (tsdev.penStatus == PEN_DOWN) { start_ts_adc(); }

spin_unlock_irq(&(tsdev.lock)); }

#endif

这个函数需要定义过笔拖曳才有效。首先调用宏函数spin_lock_irq 来禁止IRQ 中断。 然后在变量tsdev.penStatus 状态为笔按下的时候,调用宏函数start_ts_adc 来启动A/D转换,转换的是X轴的坐标。 最后再调用宏函数spin_unlock_irq 来重新使能IRQ 中断

最后再来看一个释放设备文件的函数:

static int s3c2410_ts_release(struct inode *inode, struct file *filp)

#ifdef HOOK_FOR_DRAG del_timer(&ts_timer);

#endif MOD_DEC_USE_COUNT;

return 0;

其实也很简单,在定义了笔拖曳的情况下,调用del_timer 函数来删除定时器,变量ts_timer struct timer_list 数据结构,然后调用MOD_DEC_USE_COUNT; 将设备文件计数器减一计数,并返回。

经过对整个触摸屏驱动程序的流程,以及S3C2410 芯片数据手册里的相关章节进行分析后,下面来总结一下触摸屏驱动程序的大致流程。

首先在驱动模块初始化函数中,除了对驱动的字符设备的注册外,还要对中断进行申请。这里申请了两个触摸屏相关的中断,一个是IRQ_TC 中断,查阅了数据手册后了解到,该中断在笔按下时,由XP 管脚产生表示中断的低电平信号,而笔抬起是没有中断信号产生的。另一个是IRQ_ADC_DONE 中断,该中断是当芯片内部A/D转换结束后,通知中断控制器产生中断,这时就可以去读取转换得到的数据。当触摸屏按下后,就会出发中断,这时会调用申请中断时附带的s3c2410_isr_tc 中断回调函数,该函数中判断若为笔抬起则启动x轴坐标的A/D转换。当转换完毕后就会产生ADC中断,这时就会调用申请中断时附带的s3c2410_isr_adc 中断回调函数,在该函数中进行判断,若x轴坐标转换结束马上进行y轴坐标的A/D转换转换;若y轴坐标转换结束,则重新回到等待中断模式,然后将坐标值写入环形缓冲区,并环形等待队列中的进程。

 

阅读(1148) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~