S3C2410驱动分析之触摸屏驱动

作者:刘昊昱 

博客:http://blog.csdn.net/liuhaoyutz

内核版本:2.6.36

源码路径:drivers/input/touchscreen/s3c2410_ts.c

 
在Linux-2.6.36中,S3C2410对应的触摸屏驱动程序是drivers/input/touchscreen/s3c2410_ts.c,本文对这个文件进行分析,详细介绍相关知识点。S3C2410的触摸屏驱动和ADC驱动紧密联系在一起,在读本文之前,请大家先看我的上一篇博客《S3C2410驱动分析之ADC通用驱动》。
首先我们看模块初始化函数:
436static int __init s3c2410ts_init(void)
437{
438    return platform_driver_register(&s3c_ts_driver);
439}

该驱动程序是以platform driver的方式注册的,这里注册的platform_driver是s3c_ts_driver,其定义如下:
423static struct platform_driver s3c_ts_driver = {
424    .driver         = {
425        .name   = "samsung-ts",
426        .owner  = THIS_MODULE,
427#ifdef CONFIG_PM
428        .pm = &s3c_ts_pmops,
429#endif
430    },
431    .id_table   = s3cts_driver_ids,
432    .probe      = s3c2410ts_probe,
433    .remove     = __devexit_p(s3c2410ts_remove),
434};

这里需要关注的是platform_driver结构的id_table和probe两个成员。s3cts_driver_ids定义如下:
415static struct platform_device_id s3cts_driver_ids[] = {
416    { "s3c2410-ts", 0 },
417    { "s3c2440-ts", 0 },
418    { "s3c64xx-ts", FEAT_PEN_IRQ },
419    { }
420};

421MODULE_DEVICE_TABLE(platform, s3cts_driver_ids);
probe成员函数s3c2410ts_probe当模块被加载时就会执行,其代码如下:
234/**
235 * s3c2410ts_probe - device core probe entry point
236 * @pdev: The device we are being bound to.
237 *
238 * Initialise, find and allocate any resources we need to run and then
239 * register with the ADC and input systems.
240 */
241static int __devinit s3c2410ts_probe(struct platform_device *pdev)
242{
243    struct s3c2410_ts_mach_info *info;
244    struct device *dev = &pdev->dev;
245    struct input_dev *input_dev;
246    struct resource *res;
247    int ret = -EINVAL;
248
249    /* Initialise input stuff */
250    memset(&ts, 0, sizeof(struct s3c2410ts));
251
252    ts.dev = dev;
253
254    info = pdev->dev.platform_data;
255    if (!info) {
256        dev_err(dev, "no platform data, cannot attach\n");
257        return -EINVAL;
258    }
259
260    dev_dbg(dev, "initialising touchscreen\n");
261
262    ts.clock = clk_get(dev, "adc");
263    if (IS_ERR(ts.clock)) {
264        dev_err(dev, "cannot get adc clock source\n");
265        return -ENOENT;
266    }
267
268    clk_enable(ts.clock);
269    dev_dbg(dev, "got and enabled clocks\n");
270
271    ts.irq_tc = ret = platform_get_irq(pdev, 0);
272    if (ret < 0) {
273        dev_err(dev, "no resource for interrupt\n");
274        goto err_clk;
275    }
276
277    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
278    if (!res) {
279        dev_err(dev, "no resource for registers\n");
280        ret = -ENOENT;
281        goto err_clk;
282    }
283
284    ts.io = ioremap(res->start, resource_size(res));
285    if (ts.io == NULL) {
286        dev_err(dev, "cannot map registers\n");
287        ret = -ENOMEM;
288        goto err_clk;
289    }
290
291    /* inititalise the gpio */
292    if (info->cfg_gpio)
293        info->cfg_gpio(to_platform_device(ts.dev));
294
295    ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
296                     s3c24xx_ts_conversion, 1);
297    if (IS_ERR(ts.client)) {
298        dev_err(dev, "failed to register adc client\n");
299        ret = PTR_ERR(ts.client);
300        goto err_iomap;
301    }
302
303    /* Initialise registers */
304    if ((info->delay & 0xffff) > 0)
305        writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);
306
307    writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
308
309    input_dev = input_allocate_device();
310    if (!input_dev) {
311        dev_err(dev, "Unable to allocate the input device !!\n");
312        ret = -ENOMEM;
313        goto err_iomap;
314    }
315
316    ts.input = input_dev;
317    ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
318    ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
319    input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
320    input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
321
322    ts.input->name = "S3C24XX TouchScreen";
323    ts.input->id.bustype = BUS_HOST;
324    ts.input->id.vendor = 0xDEAD;
325    ts.input->id.product = 0xBEEF;
326    ts.input->id.version = 0x0102;
327
328    ts.shift = info->oversampling_shift;
329    ts.features = platform_get_device_id(pdev)->driver_data;
330
331    ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
332              "s3c2410_ts_pen", ts.input);
333    if (ret) {
334        dev_err(dev, "cannot get TC interrupt\n");
335        goto err_inputdev;
336    }
337
338    dev_info(dev, "driver attached, registering input device\n");
339
340    /* All went ok, so register to the input system */
341    ret = input_register_device(ts.input);
342    if (ret < 0) {
343        dev_err(dev, "failed to register input device\n");
344        ret = -EIO;
345        goto err_tcirq;
346    }
347
348    return 0;
349
350 err_tcirq:
351    free_irq(ts.irq_tc, ts.input);
352 err_inputdev:
353    input_unregister_device(ts.input);
354 err_iomap:
355    iounmap(ts.io);
356 err_clk:
357    del_timer_sync(&touch_timer);
358    clk_put(ts.clock);
359    return ret;
360}

243行,定义s3c2410_ts_mach_info结构体变量info,该结构体定义在arch/arm/plat-samsung/include/plat/ts.h文件中:
13struct s3c2410_ts_mach_info {
14       int             delay;
15       int             presc;
16       int             oversampling_shift;
17    void    (*cfg_gpio)(struct platform_device *dev);
18};

delay保存ADC的延迟时间。
presc保存ADC的预分频系数。
oversampling_shift保存采样次数log2值。
cfg_gpio函数用于设置gpio。
250行,将ts清0,ts是s3c2410ts结构体类型变量,其定义如下:
62/**
63 * struct s3c2410ts - driver touchscreen state.
64 * @client: The ADC client we registered with the core driver.
65 * @dev: The device we are bound to.
66 * @input: The input device we registered with the input subsystem.
67 * @clock: The clock for the adc.
68 * @io: Pointer to the IO base.
69 * @xp: The accumulated X position data.
70 * @yp: The accumulated Y position data.
71 * @irq_tc: The interrupt number for pen up/down interrupt
72 * @count: The number of samples collected.
73 * @shift: The log2 of the maximum count to read in one go.
74 * @features: The features supported by the TSADC MOdule.
75 */
76struct s3c2410ts {
77    struct s3c_adc_client *client;
78    struct device *dev;
79    struct input_dev *input;
80    struct clk *clock;
81    void __iomem *io;
82    unsigned long xp;
83    unsigned long yp;
84    int irq_tc;
85    int count;
86    int shift;
87    int features;
88};
89
90static struct s3c2410ts ts;

s3c2410ts结构体的各个成员的含义在注册中已经说的很清楚,这里不再解释。
回到s3c2410ts_probe函数,接着往下看
262 - 268行,获取并使能触摸屏adc时钟,因为AD转换频率与时钟有关。
271行,取得中断号保存在ts.irq_tc中。
277行,取得触摸屏设备的I/O内存保存在res中。
284行,调用ioremap得到触摸屏的I/O内存对应的虚拟地址,保存在ts.io中。
292 - 293行,如果设备定义了cfg_gpio函数,则调用cfg_gpio函数初始化GPIO。
295 - 296行,调用s3c_adc_register函数注册ADC客户(client)。
s3c_adc_register函数定义在arch/arm/plat-samsung/adc.c文件中:
207struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,
208                    void (*select)(struct s3c_adc_client *client,
209                               unsigned int selected),
210                    void (*conv)(struct s3c_adc_client *client,
211                             unsigned d0, unsigned d1,
212                             unsigned *samples_left),
213                    unsigned int is_ts)
214{
215    struct s3c_adc_client *client;
216
217    WARN_ON(!pdev);
218
219    if (!select)
220        select = s3c_adc_default_select;
221
222    if (!pdev)
223        return ERR_PTR(-EINVAL);
224
225    client = kzalloc(sizeof(struct s3c_adc_client), GFP_KERNEL);
226    if (!client) {
227        dev_err(&pdev->dev, "no memory for adc client\n");
228        return ERR_PTR(-ENOMEM);
229    }
230
231    client->pdev = pdev;
232    client->is_ts = is_ts;
233    client->select_cb = select;
234    client->convert_cb = conv;
235
236    return client;
237}

s3c_adc_client结构体定义在arch/arm/plat-samsung/adc.c中:
46struct s3c_adc_client {
47    struct platform_device  *pdev;
48    struct list_head     pend;
49    wait_queue_head_t   *wait;
50
51    unsigned int         nr_samples;
52    int          result;
53    unsigned char        is_ts;
54    unsigned char        channel;
55
56    void    (*select_cb)(struct s3c_adc_client *c, unsigned selected);
57    void    (*convert_cb)(struct s3c_adc_client *c,
58                  unsigned val1, unsigned val2,
59                  unsigned *samples_left);
60};

再回到s3c2410ts_probe函数:
304 - 305行,设定ADC延迟时间,
304    if ((info->delay & 0xffff) > 0)
305        writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);

info->delay & 0xffff表示取延迟时间的低32位值。
在arch/arm/plat-samsung/include/plat/regs-adc.h文件中,S3C2410_ADCDLY定义如下:
#define S3C2410_ADCREG(x) (x)
#define S3C2410_ADCDLY     S3C2410_ADCREG(0x08)

所以S3C2410_ADCDLY的值就是0x08。
查S3C2410 Datasheet,可以发现ADC相关寄存器的起始地址是0x58000000,而ADC start delay(ADCDLY)寄存器的地址是0x58000008。ADC相关寄存器的虚拟地址保存在ts.io中,所以ADCDLY寄存器的虚拟地址是ts.io + 0x08。
再回到s3c2410ts_probe函数,307行,设置ADC touch screen control(ADCTSC)寄存器,进入等待中断模式。
307    writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);

S3C2410_ADCTSC宏定义如下:
#define S3C2410_ADCREG(x) (x)
#define S3C2410_ADCTSC     S3C2410_ADCREG(0x04)

INT_DOWN宏定义如下:
44#define INT_DOWN    (0)

WAIT4INT宏定义如下:
47#define WAIT4INT    (S3C2410_ADCTSC_YM_SEN | \
48             S3C2410_ADCTSC_YP_SEN | \
49             S3C2410_ADCTSC_XP_SEN | \
50             S3C2410_ADCTSC_XY_PST(3))

在arch/arm/plat-samsung/include/plat/regs-adc.h文件中,有如下宏定义:
41/* ADCTSC Register Bits */
42#define S3C2410_ADCTSC_YM_SEN       (1<<7)
43#define S3C2410_ADCTSC_YP_SEN       (1<<6)
44#define S3C2410_ADCTSC_XM_SEN       (1<<5)
45#define S3C2410_ADCTSC_XP_SEN       (1<<4)
46#define S3C2410_ADCTSC_PULL_UP_DISABLE  (1<<3)
47#define S3C2410_ADCTSC_AUTO_PST     (1<<2)
48#define S3C2410_ADCTSC_XY_PST(x)    (((x)&0x3)<<0)

所以用WAIT4INT宏设置ADCTSC寄存器就是把ADCTSC寄存器的第0,1,4,6,7位设置为1。查看S3C2410数据手册可知,把ADCTSC寄存器设置为0xD3表示进入“Waiting for interrupt Mode”。
再回到s3c2410ts_probe函数,309 - 326行分配input_dev并进行初始化:
309    input_dev = input_allocate_device();
310    if (!input_dev) {
311        dev_err(dev, "Unable to allocate the input device !!\n");
312        ret = -ENOMEM;
313        goto err_iomap;
314    }
315
316    ts.input = input_dev;
317    ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
318    ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
319    input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
320    input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
321
322    ts.input->name = "S3C24XX TouchScreen";
323    ts.input->id.bustype = BUS_HOST;
324    ts.input->id.vendor = 0xDEAD;
325    ts.input->id.product = 0xBEEF;
326    ts.input->id.version = 0x0102;

309行,调用input_allocate_device分配input_dev设备,并保存在input_dev变量中。
317行,设置input_dev的evbit成员,evbit代表一个input_dev设备支持的事件类型,这里支持按键事件和绝对坐标事件。
318行,设置input_dev的keybit成员,keybit代表按键的类型,这里设置为触摸屏。
319行,设置X坐标,最小值为0,最大值为0x3FF(1023)。
320行,设置Y坐标,最小值为0,最大值为1023。
322 - 326行,设置触摸屏信息,这些信息以后可以在/proc/bus/input/devices中查到。注意323行,设置总线类型为BUS_HOST,根据触摸屏不同,总线可以是I2C,SPI,RS232等各种总线类型。
328行,设置采样次数log2值。
331 - 332行,申请中断,设置中断处理函数为stylus_irq。
341行,注册input_dev设备。
至此,模块初始化及probe过程就完成了。
当触摸屏被按下时,将触发触摸屏中断IRQ_TC,下面看中断处理函数stylus_irq:
151/**
152 * stylus_irq - touchscreen stylus event interrupt
153 * @irq: The interrupt number
154 * @dev_id: The device ID.
155 *
156 * Called when the IRQ_TC is fired for a pen up or down event.
157 */
158static irqreturn_t stylus_irq(int irq, void *dev_id)
159{
160    unsigned long data0;
161    unsigned long data1;
162    bool down;
163
164    data0 = readl(ts.io + S3C2410_ADCDAT0);
165    data1 = readl(ts.io + S3C2410_ADCDAT1);
166
167    down = get_down(data0, data1);
168
169    /* TODO we should never get an interrupt with down set while
170     * the timer is running, but maybe we ought to verify that the
171     * timer isn't running anyways. */
172
173    if (down)
174        s3c_adc_start(ts.client, 0, 1 << ts.shift);
175    else
176        dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);
177
178    if (ts.features & FEAT_PEN_IRQ) {
179        /* Clear pen down/up interrupt */
180        writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
181    }
182
183    return IRQ_HANDLED;
184}

164 - 165行,读取ADCDAT0和ADCDAT1两个寄存器。注意,如果S3C2410处理器的ADCCON寄存器的READ_START位被置为1,则在读ADCDATA0和ADCDATA1时即会启动下一次AD转换。
167行,调用get_down函数获取触摸屏是否被按下,如果是被按下的,则返回1,否则返回0。get_down函数定义如下:
 92/**
 93 * get_down - return the down state of the pen
 94 * @data0: The data read from ADCDAT0 register.
 95 * @data1: The data read from ADCDAT1 register.
 96 *
 97 * Return non-zero if both readings show that the pen is down.
 98 */
 99static inline bool get_down(unsigned long data0, unsigned long data1)
100{
101    /* returns true if both data values show stylus down */
102    return (!(data0 & S3C2410_ADCDAT0_UPDOWN) &&
103        !(data1 & S3C2410_ADCDAT0_UPDOWN));
104}
#define S3C2410_ADCDAT0_UPDOWN      (1<<15)
#define S3C2410_ADCDAT1_UPDOWN      (1<<15)

ADCDAT0和ADCDAT1寄存器的第15位为0,表示触摸屏被按下,如果为1,表示触摸屏抬起。
回到触摸屏中断处理函数stylus_irq,
173 - 174行,如果触摸屏是被按下的,则调用s3c_adc_start函数,该函数在arch/arm/plat-samsung/adc.c文件中定义,我在《S3C2410驱动分析之ADC通用驱动》中对s3c_adc_start函数已经进行过详细分析,具体函数内容,这里不再重复,这里列出其函数调用逻辑:
s3c_adc_start -> 
s3c_adc_try -> 
s3c_adc_select -> 
client->select_cb(client, 1) 
s3c_adc_convert -> 
s3c_adc_irq -> 
(client->convert_cb)(client, data0, data1, &client->nr_samples)
(client->select_cb)(client, 0)
首先s3c_adc_start完成一些初始化工作后,调用s3c_adc_try,该函数检测等待处理的客户(client),然后调用s3c_adc_select,该函数会调用客户定义的select回调函数选择客户(client)。s3c_adc_try执行完s3c_adc_select后,又会调用s3c_adc_convert,该函数启动AD转换,转换结束后,触发中断,ADC中断处理函数s3c_adc_irq执行,在s3c_adc_irq函数中,会调用客户定义的convert回调函数对转换结果进行处理,然后调用select回调函数取消对客户的选择。
根据上面的调用逻辑,下面我们要分析的就是触摸屏的select和convert回调函数。先看convert回调函数,因为它比较简单,其定义如下:
186/**
187 * s3c24xx_ts_conversion - ADC conversion callback
188 * @client: The client that was registered with the ADC core.
189 * @data0: The reading from ADCDAT0.
190 * @data1: The reading from ADCDAT1.
191 * @left: The number of samples left.
192 *
193 * Called when a conversion has finished.
194 */
195static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
196                  unsigned data0, unsigned data1,
197                  unsigned *left)
198{
199    dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
200
201    ts.xp += data0;
202    ts.yp += data1;
203
204    ts.count++;
205
206    /* From tests, it seems that it is unlikely to get a pen-up
207     * event during the conversion process which means we can
208     * ignore any pen-up events with less than the requisite
209     * count done.
210     *
211     * In several thousand conversions, no pen-ups where detected
212     * before count completed.
213     */
214}

201行,将从ADCDAT0寄存器中读得的x坐标值累加到ts.xp上。
202行,将从ADCDAT1寄存器中读得的y坐标值累加到ts.yp上。
204行,ts.count累加1。
所以,convert回调函数s3c24xx_ts_conversion的作用就是累加每次采样后得到的x,y坐标值,并记录采样次数,在其它函数中,可以取ts.xp和ts.yp的平均值,得到传递给用户空间的x,y坐标值。
下面看触摸屏的select回调函数,其定义如下:
216/**
217 * s3c24xx_ts_select - ADC selection callback.
218 * @client: The client that was registered with the ADC core.
219 * @select: The reason for select.
220 *
221 * Called when the ADC core selects (or deslects) us as a client.
222 */
223static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
224{
225    if (select) {
226        writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
227               ts.io + S3C2410_ADCTSC);
228    } else {
229        mod_timer(&touch_timer, jiffies+1);
230        writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
231    }
232}

S3C2410_ADCTSC_PULL_UP_DISABLE宏表示禁止XP上拉,其定义如下:
#define S3C2410_ADCTSC_PULL_UP_DISABLE  (1<<3)

AUTOPST宏表示启用自动(连续)x/y轴坐标转换模式,其定义如下:
52#define AUTOPST     (S3C2410_ADCTSC_YM_SEN | \
53             S3C2410_ADCTSC_YP_SEN | \
54             S3C2410_ADCTSC_XP_SEN | \
55             S3C2410_ADCTSC_AUTO_PST | \
56             S3C2410_ADCTSC_XY_PST(0))

我们来看select回调函数s3c24xx_ts_select,
225 - 227行,如果select参数为1,则启用自动(连续)x/y轴坐标转换模式。
228 - 231行,如果select参数为0,将touch_timer定时器的到时时间设置为此后一个时钟滴答,并进入中断等待模式。
再次回忆一下前面介绍的s3c_adc_start执行流程:
s3c_adc_start -> 
s3c_adc_try -> 
s3c_adc_select -> 
client->select_cb(client, 1) 
s3c_adc_convert -> 
s3c_adc_irq -> 
(client->convert_cb)(client, data0, data1, &client->nr_samples)
(client->select_cb)(client, 0)
重点看select和convert回调函数的调用。s3c_adc_try首先是调用client->select_cb(client, 1),即启用自动(连续)x/y轴坐标转换模式,然后调用s3c_adc_convert进行AD转换,AD转换结束后,触发AD中断,中断处理函数s3c_adc_irq会首先调用s3c24xx_ts_conversion,累加x,y坐标值及采样次数,然后再调用client->select_cb(client, 0)设置定时器和重新进入等待中断模式。
下面要分析的是定时器touch_timer,其定义如下:
149static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0);

定时器函数touch_timer_fire定义如下:
106static void touch_timer_fire(unsigned long data)
107{
108    unsigned long data0;
109    unsigned long data1;
110    bool down;
111
112    data0 = readl(ts.io + S3C2410_ADCDAT0);
113    data1 = readl(ts.io + S3C2410_ADCDAT1);
114
115    down = get_down(data0, data1);
116
117    if (down) {
118        if (ts.count == (1 << ts.shift)) {
119            ts.xp >>= ts.shift;
120            ts.yp >>= ts.shift;
121
122            dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
123                __func__, ts.xp, ts.yp, ts.count);
124
125            input_report_abs(ts.input, ABS_X, ts.xp);
126            input_report_abs(ts.input, ABS_Y, ts.yp);
127
128            input_report_key(ts.input, BTN_TOUCH, 1);
129            input_sync(ts.input);
130
131            ts.xp = 0;
132            ts.yp = 0;
133            ts.count = 0;
134        }
135
136        s3c_adc_start(ts.client, 0, 1 << ts.shift);
137    } else {
138        ts.xp = 0;
139        ts.yp = 0;
140        ts.count = 0;
141
142        input_report_key(ts.input, BTN_TOUCH, 0);
143        input_sync(ts.input);
144
145        writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
146    }
147}

212 - 215行,取得ADCDAT0和ADCDAT1的值保存在data0和data1中,并由get_down函数判断触摸屏是按下还是松开,如果是按下的,down为1,否则down为0。
117 - 118行,如果触摸屏是按下的,再判断采样次数是否是用户指定的次数。
119行,取ts.xp的平均值,即x坐标平均值。
120行,取ts.yp的平均值,即y坐标平均值。
125行,使用input子系统函数input_report_abs提交x绝对坐标值。
126行,使用input子系统函数input_report_abs提交y绝对坐标值。
128行, 使用input子系统函数input_report_key提交按键事件为触摸屏按下。
129行,使用input子系统函数input_sync(ts.input)同步所有事件,将所有事件组成一个evdev包,并通过/dev/input/eventX发送出去。input子系统要求提交各种事件后必须执行同步。
131 - 133行,将ts.xp,ts.yp和ts.count清0,准备一下次采样。
136行,如果采样次数与用户指定的采样次数不同,则重新执行s3c_adc_start进行采样。但是这里我有个疑问,再次采样,ts.count并没有清0,那么采样计数可以会超过用户指定的次数,这是怎么回事呢?
137 - 146行,如果触摸屏是抬起的,则将ts.xp,ts.yp和ts.count清0,准备一下次采样。然后提交触摸屏抬起事件,并将触摸屏设置为等待中断模式。
至此,整个S3C2410的触摸屏驱动我们就分析完了。

你可能感兴趣的:(c,timer,struct,report,input,features)