免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 726 | 回复: 0
打印 上一主题 下一主题

s3c2410触摸屏在linux下的驱动分析 二 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-02-28 14:09 |只看该作者 |倒序浏览
接下来看一下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 文件中:
/*
* These are the generic versions of the spinlocks and read-write
* locks..
*/
#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 /* 0 == no debugging, 1 == maintain lock state, 2 == full debug */
#if (DEBUG_SPINLOCKS
    下面先来看启动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 = 1lock, 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 的中断使能状态。
/*
* The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just wake everything
* up.  If it's an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the
* non-exclusive tasks and one exclusive task.
*
* There are circumstances in which we can try to wake a task which has already
* started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns zero
* in this (rare) case, and we handle it by contonuing to scan the queue.
*/
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 文件中:
/*
* These are the generic versions of the spinlocks and read-write
* locks..
*/
#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 文件中:
/* For spinlocks etc */
#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 文件中:
/*
* A couple of speedups for the ARM
*/
/*
* Save the current interrupt enable state & disable IRQs
*/
#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)
/*
* restore saved IRQ & FIQ state
*/
#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();
#endif
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
}
该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。
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_on和sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。关于interruptible_sleep_on 和wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。
    如果进程被唤醒,则会继续跳到retry: 循环读取环形缓冲区,直到读取到一组触摸信息数据才会退出。
static int tsRead(TS_RET * ts_ret)
    这个函数主要将环形缓冲区的x轴y轴坐标数据和笔的状态数据传入该函数形式参数所指的指针变量中。
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轴坐标转换结束,则重新回到等待中断模式,然后将坐标值写入环形缓冲区,并环形等待队列中的进程。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/55409/showart_485818.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP