6410触摸屏驱动分析(s3c-ts.c)(Linux)(分析)

来源:互联网 发布:安卓软件开发入门 编辑:程序博客网 时间:2024/05/21 00:19

http://www.cnblogs.com/liu_xf/archive/2011/06/22/2086750.html

摘要:

    分析内核s3c-ts.c源码,看它是如何采集坐标信息及防抖动处理的。

介绍:

    直接上源码吧,完全注释:

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

 

原创粉丝点击