6410触摸屏驱动分析(s3c-ts.c)(Linux)(分析)
原文链接:http://www.cnblogs.com/liu_xf/archive/2011/06/22/2086750.html#
摘要:
分析内核s3c-ts.c源码,看它是如何采集坐标信息及防抖动处理的。
介绍:
直接上源码吧,完全注释:
001 |
/* linux/drivers/input/touchscreen/s3c-ts.c |
002 |
* |
003 |
* This program is free software; you can redistribute it and/or modify |
004 |
* it under the terms of the GNU General Public License as published by |
005 |
* the Free Software Foundation; either version 2 of the License, or |
006 |
* (at your option) any later version. |
007 |
* |
008 |
* This program is distributed in the hope that it will be useful, |
009 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
010 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
011 |
* GNU General Public License for more details. |
012 |
* |
013 |
* You should have received a copy of the GNU General Public License |
014 |
* along with this program; if not, write to the Free Software |
015 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
016 |
* |
017 |
* a misc driver for mini6410 touch screen |
018 |
* by FriendlyARM 2010 |
019 |
* |
020 |
* Based on following software: |
021 |
* |
022 |
** Copyright (c) 2004 Arnaud Patard <arnaud.patard@rtp-net.org> |
023 |
** iPAQ H1940 touchscreen support |
024 |
** |
025 |
** ChangeLog |
026 |
** |
027 |
** 2004-09-05: Herbert Potzl <herbert@13thfloor.at> |
028 |
** - added clock (de-)allocation code |
029 |
** |
030 |
** 2005-03-06: Arnaud Patard <arnaud.patard@rtp-net.org> |
031 |
** - h1940_ -> s3c24xx (this driver is now also used on the n30 |
032 |
** machines :P) |
033 |
** - Debug messages are now enabled with the config option |
034 |
** TOUCHSCREEN_S3C_DEBUG |
035 |
** - Changed the way the value are read |
036 |
** - Input subsystem should now work |
037 |
** - Use ioremap and readl/writel |
038 |
** |
039 |
** 2005-03-23: Arnaud Patard <arnaud.patard@rtp-net.org> |
040 |
** - Make use of some undocumented features of the touchscreen |
041 |
** controller |
042 |
** |
043 |
** 2006-09-05: Ryu Euiyoul <ryu.real@gmail.com> |
044 |
** - added power management suspend and resume code |
045 |
* |
046 |
*/ |
047 |
|
048 |
#include <linux/errno.h> |
049 |
#include <linux/kernel.h> |
050 |
#include <linux/module.h> |
051 |
#include <linux/slab.h> |
052 |
#include <linux/input.h> |
053 |
#include <linux/init.h> |
054 |
#include <linux/serio.h> |
055 |
#include <linux/delay.h> |
056 |
#include <linux/platform_device.h> |
057 |
#include <linux/clk.h> |
058 |
#include <linux/fs.h> |
059 |
#include <linux/poll.h> |
060 |
#include <linux/irq.h> |
061 |
#include <linux/interrupt.h> |
062 |
#include <linux/cdev.h> |
063 |
#include <linux/miscdevice.h> |
064 |
|
065 |
#include <asm/uaccess.h> |
066 |
#include <asm/io.h> |
067 |
#include <asm/irq.h> |
068 |
#include <mach/hardware.h> |
069 |
|
070 |
#include <plat/regs-adc.h> |
071 |
#include <mach/irqs.h> |
072 |
#include <mach/map.h> |
073 |
#include <mach/regs-clock.h> |
074 |
#include <mach/regs-gpio.h> |
075 |
#include <mach/gpio-bank-a.h> |
076 |
#include <mach/ts.h> |
077 |
|
078 |
#define CONFIG_TOUCHSCREEN_S3C_DEBUG |
079 |
#undef CONFIG_TOUCHSCREEN_S3C_DEBUG |
080 |
#define DEBUG_LVL KERN_DEBUG |
081 |
|
082 |
|
083 |
#ifdef CONFIG_MINI6410_ADC |
084 |
DEFINE_SEMAPHORE(ADC_LOCK); //定义并初始化了一个信号量 |
085 |
//37内核就没有DECLARE_MUTEX了吧,功能应该是一样的 |
086 |
|
087 |
|
088 |
/* Indicate who is using the ADC controller */ |
089 |
//ADC的状态,防止触摸屏转换时,ADC正在被使用 |
090 |
#define LOCK_FREE 0 |
091 |
#define LOCK_TS 1 |
092 |
#define LOCK_ADC 2 |
093 |
static int adc_lock_id = LOCK_FREE; |
094 |
|
095 |
#define ADC_free() (adc_lock_id == LOCK_FREE) |
096 |
#define ADC_locked4TS() (adc_lock_id == LOCK_TS) |
097 |
|
098 |
//== |
099 |
static inline int s3c_ts_adc_lock(int id) { |
100 |
int ret; |
101 |
|
102 |
ret = down_trylock(&ADC_LOCK); //获取自旋锁 |
103 |
if (!ret) { |
104 |
adc_lock_id = id; |
105 |
} |
106 |
|
107 |
return ret; //返回状态 1:失败 0:成功 |
108 |
} |
109 |
//-- |
110 |
|
111 |
static inline void s3c_ts_adc_unlock(void) { |
112 |
adc_lock_id = 0; |
113 |
up(&ADC_LOCK); //释放自旋锁 |
114 |
} |
115 |
#endif |
116 |
|
117 |
|
118 |
/* Touchscreen default configuration */ |
119 |
struct s3c_ts_mach_info s3c_ts_default_cfg __initdata = { |
120 |
.delay = 10000, //转换延时 |
121 |
.presc = 49, //转换时钟分频 |
122 |
.oversampling_shift = 2, //转换次数 4次 |
123 |
.resol_bit = 12, //转换精度 |
124 |
.s3c_adc_con = ADC_TYPE_2 //6410是type2 |
125 |
}; |
126 |
/* |
127 |
struct s3c_ts_mach_info s3c_ts_default_cfg __initdata = { |
128 |
.delay = 10000, |
129 |
.presc = 49, |
130 |
.oversampling_shift = 2, |
131 |
.resol_bit = 10 |
132 |
}; |
133 |
*/ |
134 |
/* |
135 |
* Definitions & global arrays. |
136 |
*/ |
137 |
#define DEVICE_NAME "touchscreen" |
138 |
static DECLARE_WAIT_QUEUE_HEAD(ts_waitq); //定义并初始化一个等待队列 |
139 |
|
140 |
typedef unsigned TS_EVENT; |
141 |
#define NR_EVENTS 64 //触摸屏fifo大小 |
142 |
|
143 |
static TS_EVENT events[NR_EVENTS]; |
144 |
static int evt_head, evt_tail; //fifo的头的尾 |
145 |
//驱动写fifo时evt_head++,应用读fifo时 evt_tail++ |
146 |
|
147 |
#define ts_evt_pending() ((volatile u8)(evt_head != evt_tail)) //相等就表示满了 |
148 |
#define ts_evt_get() (events + evt_tail) |
149 |
#define ts_evt_pull() (evt_tail = (evt_tail + 1) & (NR_EVENTS - 1)) |
150 |
#define ts_evt_clear() (evt_head = evt_tail = 0) |
151 |
|
152 |
//将AD转换的值放入FIFO |
153 |
//这里是一个先进先出的fifo |
154 |
//只要有数据被添加进来,就会唤醒ts_waitq进程 |
155 |
static void ts_evt_add(unsigned x, unsigned y, unsigned down) { |
156 |
unsigned ts_event; |
157 |
int next_head; |
158 |
|
159 |
ts_event = ((x << 16) | (y)) | (down << 31); |
160 |
next_head = (evt_head + 1) & (NR_EVENTS - 1); |
161 |
//没满就装入 |
162 |
if (next_head != evt_tail) { |
163 |
events[evt_head] = ts_event; |
164 |
evt_head = next_head; |
165 |
//printk("====>Add ... [ %4d, %4d ]%s\n", x, y, down ? "":" ~~~"); |
166 |
|
167 |
/* wake up any read call */ |
168 |
if (waitqueue_active(&ts_waitq)) { //判斷等待隊列是否有進程睡眠 |
169 |
wake_up_interruptible(&ts_waitq); //唤醒ts_waitq等待队列中所有interruptible类型的进程 |
170 |
} |
171 |
} else { |
172 |
/* drop the event and try to wakeup readers */ |
173 |
printk(KERN_WARNING "mini6410-ts: touch event buffer full"); |
174 |
wake_up_interruptible(&ts_waitq); |
175 |
} |
176 |
} |
177 |
|
178 |
static unsigned int s3c_ts_poll( struct file *file, struct poll_table_struct *wait) |
179 |
{ |
180 |
unsigned int mask = 0; |
181 |
|
182 |
//将ts_waitq等待队列添加到poll_table里去 |
183 |
poll_wait(file, &ts_waitq, wait); |
184 |
//返回掩码 |
185 |
if (ts_evt_pending()) |
186 |
mask |= POLLIN | POLLRDNORM; //返回设备可读 |
187 |
|
188 |
return mask; |
189 |
} |
190 |
|
191 |
//读 系统调用== |
192 |
static int s3c_ts_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) |
193 |
{ |
194 |
DECLARE_WAITQUEUE(wait, current); //把当前进程加到定义的等待队列头wait中 |
195 |
char *ptr = buff; |
196 |
int err = 0; |
197 |
|
198 |
add_wait_queue(&ts_waitq, &wait); //把wait入到等待队列头中。该队列会在进程等待的条件满足时唤醒它。 |
199 |
//我们必须在其他地方写相关代码,在事件发生时,对等的队列执行wake_up()操作。 |
200 |
//这里是在ts_evt_add里wake_up |
201 |
while (count >= sizeof(TS_EVENT)) { |
202 |
err = -ERESTARTSYS; |
203 |
if (signal_pending(current)) //如果是信号唤醒 参考http://www.360doc.com/content/10/1009/17/1317564_59632874.shtml |
204 |
break; |
205 |
|
206 |
if (ts_evt_pending()) { |
207 |
TS_EVENT *evt = ts_evt_get(); |
208 |
|
209 |
err = copy_to_user(ptr, evt, sizeof(TS_EVENT)); |
210 |
ts_evt_pull(); |
211 |
|
212 |
if (err) |
213 |
break; |
214 |
|
215 |
ptr += sizeof(TS_EVENT); |
216 |
count -= sizeof(TS_EVENT); |
217 |
continue; |
218 |
} |
219 |
|
220 |
set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为可中断的睡眠 |
221 |
err = -EAGAIN; |
222 |
if (filp->f_flags & O_NONBLOCK) //如果上层调用是非阻塞方式,则不阻塞该进程,直接返回EAGAIN |
223 |
break; |
224 |
schedule(); //本进程在此处交出CPU控制权,等待被唤醒 |
225 |
//进程调度的意思侧重于把当前任务从CPU拿掉,再从就绪队列中按照调度算法取一就绪进程占用CPU |
226 |
} |
227 |
current->state = TASK_RUNNING; |
228 |
remove_wait_queue(&ts_waitq, &wait); |
229 |
|
230 |
return ptr == buff ? err : ptr - buff; |
231 |
} |
232 |
//-- |
233 |
|
234 |
static int s3c_ts_open(struct inode *inode, struct file *filp) { |
235 |
/* flush event queue */ |
236 |
ts_evt_clear(); |
237 |
|
238 |
return 0; |
239 |
} |
240 |
|
241 |
//当应用程序操作设备文件时调用的open read等函数,最终会调用这个结构体中对应的函数 |
242 |
static struct file_operations dev_fops = { |
243 |
.owner = THIS_MODULE, |
244 |
.read = s3c_ts_read, |
245 |
.poll = s3c_ts_poll, //select系统调用 |
246 |
.open = s3c_ts_open, |
247 |
}; |
248 |
|
249 |
//设备号,设备名,注册的时候用到这个数组 |
250 |
//混杂设备主设备号为10 |
251 |
static struct miscdevice misc = { |
252 |
.minor = MISC_DYNAMIC_MINOR, //自动分配次设置号 |
253 |
//.minor = 180, |
254 |
.name = DEVICE_NAME, |
255 |
.fops = &dev_fops, |
256 |
}; |
257 |
|
258 |
//x为0时为等待按下中断,x为1是为等待抬起中断 |
259 |
#define WAIT4INT(x) (((x) << 8) | \ |
260 |
S3C_ADCTSC_YM_SEN | S3C_ADCTSC_YP_SEN | S3C_ADCTSC_XP_SEN | \ |
261 |
S3C_ADCTSC_XY_PST(3)) |
262 |
|
263 |
//自动连续测量X坐标和Y坐标 |
264 |
#define AUTOPST (S3C_ADCTSC_YM_SEN | S3C_ADCTSC_YP_SEN | S3C_ADCTSC_XP_SEN | \ |
265 |
S3C_ADCTSC_AUTO_PST | S3C_ADCTSC_XY_PST(0)) |
266 |
|
267 |
static void __iomem *ts_base; |
268 |
static struct resource *ts_mem; |
269 |
static struct resource *ts_irq; |
270 |
static struct clk *ts_clock; |
271 |
static struct s3c_ts_info *ts; |
272 |
|
273 |
/** |
274 |
* get_down - return the down state of the pen |
275 |
* @data0: The data read from ADCDAT0 register. |
276 |
* @data1: The data read from ADCDAT1 register. |
277 |
* |
278 |
* Return non-zero if both readings show that the pen is down. |
279 |
*/ |
280 |
static inline bool get_down(unsigned long data0, unsigned long data1) |
281 |
{ |
282 |
/* returns true if both data values show stylus down */ |
283 |
return (!(data0 & S3C_ADCDAT0_UPDOWN) && !(data1 & S3C_ADCDAT1_UPDOWN)); //判断data0,data1最高位是否仍为"0",为“0”表示触摸笔状态保持为down |
284 |
} |
285 |
|
286 |
|
287 |
/*=========================================================================================== |
288 |
touch_timer_fire这个函数主要实现以下功能: |
289 |
|
290 |
1、 触摸笔开始点击的时候, 在中断函数stylus_updown里面被调用, |
291 |
此时缓存区没有数据,ts.count为0, 并且开启AD转换,而后进入 AD 中断 |
292 |
|
293 |
2、 ADC中断函数stylus_action把缓冲区填满的时候,作为中断后半段函数稍后被调用(由内核定时器触发中断), |
294 |
此时ts.count为4,算出其平均值后,交给事件处理层(Event Handler)处理, |
295 |
主要是填写缓冲,然后唤醒等待输入数据的进程。 |
296 |
|
297 |
3、 stylus抬起,等到缓冲区填满后(可能会包含一些无用的数据)被调用, |
298 |
这时候判断出stylus up,报告stylus up事件,重新等待stylus down。 |
299 |
============================================================================================*/ |
300 |
|
301 |
static void touch_timer_fire(unsigned long data) { |
302 |
unsigned long data0; |
303 |
unsigned long data1; |
304 |
int pendown; |
305 |
|
306 |
#ifdef CONFIG_MINI6410_ADC |
307 |
if (!ADC_locked4TS()) { |
308 |
/* Note: pen UP interrupt detected and handled, the lock is released, |
309 |
* so do nothing in the timer which started by ADC ISR. */ |
310 |
return; |
311 |
} |
312 |
#endif |
313 |
|
314 |
data0 = readl(ts_base + S3C_ADCDAT0); |
315 |
data1 = readl(ts_base + S3C_ADCDAT1);//读取AD转换数据的值 |
316 |
|
317 |
pendown = get_down(data0, data1); |
318 |
|
319 |
if (pendown) { |
320 |
if (ts->count == (1 << ts->shift)) { //定时器触发touch_timer_fire中断时执行这个括号里 |
321 |
#ifdef CONFIG_TOUCHSCREEN_S3C_DEBUG |
322 |
{ |
323 |
struct timeval tv; |
324 |
do_gettimeofday(&tv); |
325 |
printk(KERN_INFO "T: %06d, X: %03ld, Y: %03ld\n", |
326 |
(int)tv.tv_usec, ts->xp, ts->yp); |
327 |
} |
328 |
#endif |
329 |
|
330 |
ts_evt_add((ts->xp >> ts->shift), (ts->yp >> ts->shift), 1);//求平均,并写入fifo |
331 |
|
332 |
ts->xp = 0; |
333 |
ts->yp = 0; |
334 |
ts->count = 0; |
335 |
} |
336 |
|
337 |
/* start automatic sequencing A/D conversion */ |
338 |
//每次按下有四次AD转换,以下为在按下中断中触发的第一次AD转换,其余三次在AD转换中断处理函数中触发 |
339 |
//AUTOPST表示自动连续测量 以得到X位置,Y位置 |
340 |
writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base + S3C_ADCTSC); |
341 |
// 启动D转换,转换后会产生中断IRQ_ADC |
342 |
writel(readl(ts_base + S3C_ADCCON) | S3C_ADCCON_ENABLE_START, |
343 |
ts_base + S3C_ADCCON); |
344 |
|
345 |
} else { //如果是松开,报告其触摸笔状态 |
346 |
ts->xp = 0; |
347 |
ts->yp = 0; |
348 |
ts->count = 0; |
349 |
|
350 |
ts_evt_add(0, 0, 0); |
351 |
|
352 |
/* PEN is UP, Let's wait the PEN DOWN interrupt */ |
353 |
writel(WAIT4INT(0), ts_base + S3C_ADCTSC); // 设置INT 位,等待 DOWN 中断 |
354 |
|
355 |
#ifdef CONFIG_MINI6410_ADC |
356 |
if (ADC_locked4TS()) { |
357 |
s3c_ts_adc_unlock(); |
358 |
} |
359 |
#endif |
360 |
} |
361 |
} |
362 |
|
363 |
static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0); |
364 |
|
365 |
//触摸屏按下松开中断服务== |
366 |
static irqreturn_t stylus_updown(int irqno, void *param) |
367 |
{ |
368 |
#ifdef CONFIG_TOUCHSCREEN_S3C_DEBUG |
369 |
unsigned long data0; |
370 |
unsigned long data1; |
371 |
int is_waiting_up; |
372 |
int pendown; |
373 |
#endif |
374 |
|
375 |
#ifdef CONFIG_MINI6410_ADC |
376 |
if (!ADC_locked4TS()) { |
377 |
if (s3c_ts_adc_lock(LOCK_TS)) { |
378 |
/* Locking ADC controller failed */ |
379 |
printk("Lock ADC failed, %d\n", adc_lock_id); |
380 |
return IRQ_HANDLED; |
381 |
} |
382 |
} |
383 |
#endif |
384 |
|
385 |
#ifdef CONFIG_TOUCHSCREEN_S3C_DEBUG |
386 |
data0 = readl(ts_base + S3C_ADCDAT0); |
387 |
data1 = readl(ts_base + S3C_ADCDAT1); |
388 |
|
389 |
is_waiting_up = readl(ts_base + S3C_ADCTSC) & (1 << 8); |
390 |
pendown = get_down(data0, data1); |
391 |
|
392 |
printk("P: %d <--> %c\n", pendown, is_waiting_up ? 'u':'d'); |
393 |
#endif |
394 |
//执行如下语句否则不断产生中断从而把系统卡死 |
395 |
if (ts->s3c_adc_con == ADC_TYPE_2) { |
396 |
/* Clear ADC and PEN Down/UP interrupt */ |
397 |
__raw_writel(0x0, ts_base + S3C_ADCCLRWK); |
398 |
__raw_writel(0x0, ts_base + S3C_ADCCLRINT); |
399 |
} |
400 |
|
401 |
/* TODO we should never get an interrupt with pendown set while |
402 |
* the timer is running, but maybe we ought to verify that the |
403 |
* timer isn't running anyways. */ |
404 |
|
405 |
touch_timer_fire(1); |
406 |
|
407 |
return IRQ_HANDLED; |
408 |
} |
409 |
|
410 |
//ad转换结束中断服务程序== |
411 |
static irqreturn_t stylus_action(int irqno, void *param) |
412 |
{ |
413 |
unsigned long data0; |
414 |
unsigned long data1; |
415 |
|
416 |
#ifdef CONFIG_MINI6410_ADC |
417 |
if (!ADC_locked4TS()) { |
418 |
if (ADC_free()) { |
419 |
printk("Unexpected\n"); |
420 |
|
421 |
/* Clear ADC interrupt */ |
422 |
__raw_writel(0x0, ts_base + S3C_ADCCLRINT); |
423 |
} |
424 |
|
425 |
return IRQ_HANDLED; |
426 |
} |
427 |
#endif |
428 |
|
429 |
data0 = readl(ts_base + S3C_ADCDAT0); |
430 |
data1 = readl(ts_base + S3C_ADCDAT1); |
431 |
|
432 |
if (ts->resol_bit == 12) { |
433 |
#if defined(CONFIG_TOUCHSCREEN_NEW) |
434 |
ts->yp += S3C_ADCDAT0_XPDATA_MASK_12BIT - (data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT); |
435 |
ts->xp += S3C_ADCDAT1_YPDATA_MASK_12BIT - (data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT); |
436 |
#else |
437 |
ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT; |
438 |
ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT; |
439 |
#endif |
440 |
} else { |
441 |
#if defined(CONFIG_TOUCHSCREEN_NEW) |
442 |
ts->yp += S3C_ADCDAT0_XPDATA_MASK - (data0 & S3C_ADCDAT0_XPDATA_MASK); |
443 |
ts->xp += S3C_ADCDAT1_YPDATA_MASK - (data1 & S3C_ADCDAT1_YPDATA_MASK); |
444 |
#else |
445 |
ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK; |
446 |
ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK; |
447 |
#endif |
448 |
} // 转换结果累加 |
449 |
|
450 |
ts->count++; |
451 |
|
452 |
if (ts->count < (1 << ts->shift)) { // 采样未完成,继续下一次采样 ,通过 ENABLE_START 启动 AD 转换,一次一个数据 |
453 |
writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base + S3C_ADCTSC); |
454 |
writel(readl(ts_base + S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base + S3C_ADCCON); |
455 |
} else { // 采样完毕,激活下半部处理程序touch_timer_fire,处理接收数据 |
456 |
mod_timer(&touch_timer, jiffies + 1); //设置定时器超时的时间,目的是为了延时触发 touch_timer_fire 中断,如果在这段时间有抬起中断发生,则表示是抖动 |
457 |
//jiffies变量记录了系统启动以来,系统定时器已经触发的次数。内核每秒钟将jiffies变量增加HZ次。 |
458 |
//因此,对于HZ值为100的系统,1个jiffy等于10ms,而对于HZ为1000的系统,1个jiffy仅为1ms |
459 |
|
460 |
writel(WAIT4INT(1), ts_base + S3C_ADCTSC); //设置为等待抬起中断 |
461 |
} |
462 |
|
463 |
if (ts->s3c_adc_con == ADC_TYPE_2) { |
464 |
/* Clear ADC and PEN Down/UP interrupt */ |
465 |
__raw_writel(0x0, ts_base + S3C_ADCCLRWK); |
466 |
__raw_writel(0x0, ts_base + S3C_ADCCLRINT); |
467 |
} |
468 |
|
469 |
return IRQ_HANDLED; |
470 |
} |
471 |
|
472 |
|
473 |
#ifdef CONFIG_MINI6410_ADC |
474 |
static unsigned int _adccon, _adctsc, _adcdly; |
475 |
|
476 |
//其它模块要用ADC时,需要调用这个函数,来确定ADC是否可用,如果可用,则将它锁住,不让别的驱动用 |
477 |
int mini6410_adc_acquire_io(void) { |
478 |
int ret; |
479 |
|
480 |
ret = s3c_ts_adc_lock(LOCK_ADC); //锁住ADC,不让其它模块使用 |
481 |
if (!ret) { //如果ADC没有被使用,则保存ADC寄存器的值 |
482 |
_adccon = readl(ts_base + S3C_ADCCON); |
483 |
_adctsc = readl(ts_base + S3C_ADCTSC); |
484 |
_adcdly = readl(ts_base + S3C_ADCDLY); |
485 |
} |
486 |
|
487 |
return ret;// 0:操作成功 1:操作失败 |
488 |
} |
489 |
EXPORT_SYMBOL(mini6410_adc_acquire_io); //声明为外部可用 |
490 |
|
491 |
//其它模块不要用ADC了,需要调用这个函数,来解锁ADC让别的驱动用 |
492 |
void mini6410_adc_release_io(void) { |
493 |
//还原ADC寄存器的设置 |
494 |
writel(_adccon, ts_base + S3C_ADCCON); |
495 |
writel(_adctsc, ts_base + S3C_ADCTSC); |
496 |
writel(_adcdly, ts_base + S3C_ADCDLY); |
497 |
writel(WAIT4INT(0), ts_base + S3C_ADCTSC); |
498 |
|
499 |
s3c_ts_adc_unlock(); //释放ADC,其它模块可以使用 |
500 |
} |
501 |
|
502 |
EXPORT_SYMBOL(mini6410_adc_release_io); |
503 |
#endif |
504 |
|
505 |
//获得触摸屏的配置信息== |
506 |
static struct s3c_ts_mach_info *s3c_ts_get_platdata(struct device *dev) |
507 |
{ |
508 |
if (dev->platform_data != NULL) |
509 |
return (struct s3c_ts_mach_info *)dev->platform_data; //优先使用 arch/arm/mach-s3c64xx中的定义 |
510 |
|
511 |
return &s3c_ts_default_cfg; //如果前面没定义,则使用本函数的default定义 |
512 |
} |
513 |
//-- |
514 |
|
515 |
/* |
516 |
* The functions for inserting/removing us as a module. |
517 |
*/ |
518 |
static int __init s3c_ts_probe(struct platform_device *pdev) |
519 |
{ |
520 |
struct resource *res; |
521 |
struct device *dev; |
522 |
struct s3c_ts_mach_info * s3c_ts_cfg; |
523 |
int ret, size; |
524 |
|
525 |
dev = &pdev->dev; |
526 |
|
527 |
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
528 |
if (res == NULL) { |
529 |
dev_err(dev,"no memory resource specified\n"); |
530 |
return -ENOENT; |
531 |
} |
532 |
|
533 |
size = (res->end - res->start) + 1; |
534 |
ts_mem = request_mem_region(res->start, size, pdev->name); |
535 |
if (ts_mem == NULL) { |
536 |
dev_err(dev, "failed to get memory region\n"); |
537 |
ret = -ENOENT; |
538 |
goto err_req; |
539 |
} |
540 |
|
541 |
ts_base = ioremap(res->start, size); |
542 |
if (ts_base == NULL) { |
543 |
dev_err(dev, "failed to ioremap() region\n"); |
544 |
ret = -EINVAL; |
545 |
goto err_map; |
546 |
} |
547 |
|
548 |
ts_clock = clk_get(&pdev->dev, "adc"); |
549 |
if (IS_ERR(ts_clock)) { |
550 |
dev_err(dev, "failed to find watchdog clock source\n"); |
551 |
ret = PTR_ERR(ts_clock); |
552 |
goto err_clk; |
553 |
} |
554 |
|
555 |
clk_enable(ts_clock); |
556 |
|
557 |
s3c_ts_cfg = s3c_ts_get_platdata(&pdev->dev); //获取配置参数 |
558 |
|
559 |
//设置ADC分频 |
560 |
if ((s3c_ts_cfg->presc & 0xff) > 0) |
561 |
writel(S3C_ADCCON_PRSCEN | S3C_ADCCON_PRSCVL(s3c_ts_cfg->presc & 0xff), |
562 |
ts_base + S3C_ADCCON); |
563 |
else |
564 |
writel(0, ts_base + S3C_ADCCON); |
565 |
|
566 |
/* Initialise registers */ |
567 |
//设置转换延时 |
568 |
if ((s3c_ts_cfg->delay & 0xffff) > 0) |
569 |
writel(s3c_ts_cfg->delay & 0xffff, ts_base + S3C_ADCDLY); |
570 |
|
571 |
if (s3c_ts_cfg->resol_bit == 12) { |
572 |
switch(s3c_ts_cfg->s3c_adc_con) { |
573 |
case ADC_TYPE_2: |
574 |
writel(readl(ts_base + S3C_ADCCON) | S3C_ADCCON_RESSEL_12BIT, |
575 |
ts_base + S3C_ADCCON); |
576 |
break; |
577 |
|
578 |
case ADC_TYPE_1: |
579 |
writel(readl(ts_base + S3C_ADCCON) | S3C_ADCCON_RESSEL_12BIT_1, |
580 |
ts_base + S3C_ADCCON); |
581 |
break; |
582 |
|
583 |
default: |
584 |
dev_err(dev, "Touchscreen over this type of AP isn't supported !\n"); |
585 |
break; |
586 |
} |
587 |
} |
588 |
|
589 |
writel(WAIT4INT(0), ts_base + S3C_ADCTSC); |
590 |
|
591 |
ts = kzalloc(sizeof(struct s3c_ts_info), GFP_KERNEL); |
592 |
|
593 |
ts->shift = s3c_ts_cfg->oversampling_shift; |
594 |
ts->resol_bit = s3c_ts_cfg->resol_bit; |
595 |
ts->s3c_adc_con = s3c_ts_cfg->s3c_adc_con; |
596 |
|
597 |
/* For IRQ_PENDUP */ |
598 |
ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); |
599 |
if (ts_irq == NULL) { |
600 |
dev_err(dev, "no irq resource specified\n"); |
601 |
ret = -ENOENT; |
602 |
goto err_irq; |
603 |
} |
604 |
|
605 |
ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts); |
606 |
if (ret != 0) { |
607 |
dev_err(dev,"s3c_ts.c: Could not allocate ts IRQ_PENDN !\n"); |
608 |
ret = -EIO; |
609 |
goto err_irq; |
610 |
} |
611 |
|
612 |
/* For IRQ_ADC */ |
613 |
ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1); |
614 |
if (ts_irq == NULL) { |
615 |
dev_err(dev, "no irq resource specified\n"); |
616 |
ret = -ENOENT; |
617 |
goto err_irq; |
618 |
} |
619 |
|
620 |
ret = request_irq(ts_irq->start, stylus_action, IRQF_SAMPLE_RANDOM | IRQF_SHARED, |
621 |
"s3c_action", ts); |
622 |
if (ret != 0) { |
623 |
dev_err(dev, "s3c_ts.c: Could not allocate ts IRQ_ADC !\n"); |
624 |
ret = -EIO; |
625 |
goto err_irq; |
626 |
} |
627 |
|
628 |
printk(KERN_INFO "%s got loaded successfully : %d bits\n", DEVICE_NAME, s3c_ts_cfg->resol_bit); |
629 |
|
630 |
ret = misc_register(&misc); //注册这混杂字符设备 |
631 |
if (ret) { |
632 |
dev_err(dev, "s3c_ts.c: Could not register device(mini6410 touchscreen)!\n"); |
633 |
ret = -EIO; |
634 |
goto fail; |
635 |
} |
636 |
|
637 |
return 0; |
638 |
|
639 |
fail: |
640 |
free_irq(ts_irq->start, ts->dev); |
641 |
free_irq(ts_irq->end, ts->dev); |
642 |
|
643 |
err_irq: |
644 |
kfree(ts); |
645 |
|
646 |
clk_disable(ts_clock); |
647 |
clk_put(ts_clock); |
648 |
|
649 |
err_clk: |
650 |
iounmap(ts_base); |
651 |
|
652 |
err_map: |
653 |
release_resource(ts_mem); |
654 |
kfree(ts_mem); |
655 |
|
656 |
err_req: |
657 |
return ret; |
658 |
} |
659 |
|
660 |
static int s3c_ts_remove(struct platform_device *dev) |
661 |
{ |
662 |
printk(KERN_INFO "s3c_ts_remove() of TS called !\n"); |
663 |
|
664 |
disable_irq(IRQ_ADC); |
665 |
disable_irq(IRQ_PENDN); |
666 |
|
667 |
free_irq(IRQ_PENDN, ts->dev); |
668 |
free_irq(IRQ_ADC, ts->dev); |
669 |
|
670 |
if (ts_clock) { |
671 |
clk_disable(ts_clock); |
672 |
clk_put(ts_clock); |
673 |
ts_clock = NULL; |
674 |
} |
675 |
|
676 |
misc_deregister(&misc); |
677 |
iounmap(ts_base); |
678 |
|
679 |
return 0; |
680 |
} |
681 |
|
682 |
#ifdef CONFIG_PM |
683 |
static unsigned int adccon, adctsc, adcdly; |
684 |
|
685 |
static int s3c_ts_suspend(struct platform_device *dev, pm_message_t state) |
686 |
{ |
687 |
adccon = readl(ts_base + S3C_ADCCON); |
688 |
adctsc = readl(ts_base + S3C_ADCTSC); |
689 |
adcdly = readl(ts_base + S3C_ADCDLY); |
690 |
|
691 |
disable_irq(IRQ_ADC); |
692 |
disable_irq(IRQ_PENDN); |
693 |
|
694 |
clk_disable(ts_clock); |
695 |
|
696 |
return 0; |
697 |
} |
698 |
|
699 |
static int s3c_ts_resume(struct platform_device *pdev) |
700 |
{ |
701 |
clk_enable(ts_clock); |
702 |
|
703 |
writel(adccon, ts_base + S3C_ADCCON); |
704 |
writel(adctsc, ts_base + S3C_ADCTSC); |
705 |
writel(adcdly, ts_base + S3C_ADCDLY); |
706 |
writel(WAIT4INT(0), ts_base + S3C_ADCTSC); |
707 |
|
708 |
enable_irq(IRQ_ADC); |
709 |
enable_irq(IRQ_PENDN); |
710 |
return 0; |
711 |
} |
712 |
#else |
713 |
#define s3c_ts_suspend NULL |
714 |
#define s3c_ts_resume NULL |
715 |
#endif |
716 |
|
717 |
static struct platform_driver s3c_ts_driver = { |
718 |
.probe = s3c_ts_probe, |
719 |
.remove = s3c_ts_remove, |
720 |
.suspend = s3c_ts_suspend, |
721 |
.resume = s3c_ts_resume, |
722 |
.driver = { |
723 |
.owner = THIS_MODULE, |
724 |
.name = "s3c-ts", |
725 |
}, |
726 |
}; |
727 |
|
728 |
static char banner[] __initdata = KERN_INFO "S3C Touchscreen driver, (c) 2010 FriendlyARM,\n"; |
729 |
|
730 |
static int __init s3c_ts_init(void) |
731 |
{ |
732 |
printk(banner); |
733 |
return platform_driver_register(&s3c_ts_driver); |
734 |
} |
735 |
|
736 |
static void __exit s3c_ts_exit(void) |
737 |
{ |
738 |
platform_driver_unregister(&s3c_ts_driver); |
739 |
} |
740 |
|
741 |
module_init(s3c_ts_init); |
742 |
module_exit(s3c_ts_exit); |
743 |
|
744 |
MODULE_AUTHOR("FriendlyARM Inc."); |
745 |
MODULE_LICENSE("GPL"); |
746 |
|
747 |
|
748 |
/* |
749 |
* 驱动分析 |
750 |
* 1、内核是如何加载驱动的? |
751 |
* 首先要提到两个结构体:设备用Platform_device表示,驱动用Platform_driver进行注册 |
752 |
* Platform机制开发发底层驱动的大致流程为: 定义 platform_device 注册 platform_device 定义 platform_driver 注册 platform_driver |
753 |
* 首先要确认的就是设备的资源信息platform_device,例如设备的地址,中断号等 该结构体定义在kernel\include\linux\platform_device.h |
754 |
* 该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中 |
755 |
* 下面我们以本例来进行说明: |
756 |
* arch/arm/mach-s3c64xx中dev-ts-mini6410.c中定义了platform_device s3c_device_ts |
757 |
* 定义好了platform_device结构体后就可以调用函数platform_add_devices向系统中添加该设备了,之后可以调用platform_driver_register()进行设备注册。 |
758 |
* 要注意的是,这里的platform_device设备的注册过程必须在相应设备驱动加载之前被调用,即执行platform_driver_register之前,原因是因为驱动注册时需要 |
759 |
* 匹配内核中所以已注册的设备名。 |
760 |
* platform_devicerr的注册是在arch/arm/mach-s3c64xx中mach-mini6410.c中的mini6410_machine_init函数实现的。 |
761 |
* mini6410_machine_init是在启动后调用,它是在module_init之前;更具体的见MACHINE_START |
762 |
* MACHINE_START(MINI6410, "MINI6410") |
763 |
* |
764 |
* .boot_params = S3C64XX_PA_SDRAM + 0x100, //.boot_params是bootloader向内核传递的参数的位置,这要和bootloader中参数的定义要一致。 |
765 |
* |
766 |
* .init_irq = s3c6410_init_irq, //.init_irq在start_kernel() --> init_IRQ() --> init_arch_irq()中被调用 |
767 |
* .map_io = mini6410_map_io, //.map_io 在 setup_arch() --> paging_init() --> devicemaps_init()中被调用 |
768 |
* .init_machine = mini6410_machine_init, //init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用, |
769 |
* //放在 arch_initcall() 段里面,会自动按顺序被调用。 |
770 |
* .timer = &s3c24xx_timer, //.timer是定义系统时钟,定义TIMER4为系统时钟,在arch/arm/plat-s3c/time.c中体现。 |
771 |
* //在start_kernel() --> time_init()中被调用。 |
772 |
* MACHINE_END |
773 |
* 再来看看platform_driver,这个定义在本文中, |
774 |
* 在驱动初始化函数中调用函数platform_driver_register()注册platform_driver,需要注意的是s3c_device_ts结构中name元素和s3c_ts_driver结构中driver.name |
775 |
* 必须是相同的,这样在platform_driver_register()注册时会对所有已注册的所有platform_device中的name和当前注册的platform_driver的driver.name进行比较, |
776 |
* 只有找到相同的名称的platfomr_device才能注册成功,当注册成功时会调用platform_driver结构元素probe函数指针,这里就是s3c_ts_probe |
778 |
* |
779 |
* 2、timer在这里的作用 |
780 |
* timer是用来防抖的,我们知道,触摸屏处理分为两个时间段,一个是由按下中断触发的四次AD转换的时间A,一个是4次AD转换完成后将AD数据存入FIFO的时间B,在时间A,没有打开抬起中断, |
781 |
* 也就是说如果在这段时间有抬起事件,也不会触发中断,不会影响AD的转换。在时间B,打开抬起中断,打开定时器延时触发touch_timer_fire,如果在延时这段时间,有抬起事件发生 |
782 |
* 则touch_timer_fire不会将前面的数据存入到FIFO中,否则写入FIFO,表示值有效。 |
783 |
* |
784 |
* |
785 |
*/ |
浙公网安备 33010602011771号