Chinaunix首页 | 论坛 | 博客
  • 博客访问: 121611
  • 博文数量: 31
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 296
  • 用 户 组: 普通用户
  • 注册时间: 2015-01-10 21:57
文章分类

全部博文(31)

文章存档

2016年(4)

2015年(27)

我的朋友

分类: LINUX

2016-08-14 18:46:32

当我们按下触摸屏时:

1,首先进入触摸屏中断,RQ_TC

2,如果触摸屏是被按下的状态,则调用touch_timer_fire启动ADC中断adc_irq

3,如果1个时间滴答到来则进入定时器服务程序touch_timer_fire

4,判断触摸屏是否仍然处于按下状态

5,如果是,则上报事件和转换的数据,并重启ADC转换,重复第(2)

6,如果不是(松开),则上报事件和转换的数据,本次转换完成

从module_init开始分析

点击(此处)折叠或打开

  1. static int __init s3c2410ts_init(void)
  2. {
  3.     return platform_driver_register(&s3c_ts_driver);
  4. }

  5. static void __exit s3c2410ts_exit(void)
  6. {
  7.     platform_driver_unregister(&s3c_ts_driver);
  8. }

  9. module_init(s3c2410ts_init);
  10. module_exit(s3c2410ts_exit);
这里向平台总线注册一个驱动,s3c_ts_driver,看看它的定义

点击(此处)折叠或打开

  1. static struct platform_driver s3c_ts_driver = {
  2.     .driver = {
  3.         .name = "samsung-ts",//驱动的名字
  4.         .owner = THIS_MODULE,
  5. #ifdef CONFIG_PM
  6.         .pm    = &s3c_ts_pmops,//电源管理操作接口
  7. #endif
  8.     },
  9.     .id_table    = s3cts_driver_ids,//支持的设备列表
  10.     .probe        = s3c2410ts_probe,//驱动函数,加载时调用
  11.     .remove        = __devexit_p(s3c2410ts_remove),
  12. }

先看id_table

点击(此处)折叠或打开

  1. static struct dev_pm_ops s3c_ts_pmops = {
  2.     .suspend    = s3c2410ts_suspend,//挂起操作
  3.     .resume        = s3c2410ts_resume,//重启操作
  4. };
  5. #endif

  6. static struct platform_device_id s3cts_driver_ids[] = {
  7.     { "s3c2410-ts", 0 },
  8.     { "s3c2440-ts", 0 },
  9.     { "s3c64xx-ts", FEAT_PEN_IRQ },
  10.     { }
  11. };
  12. MODULE_DEVICE_TABLE(platform, s3cts_driver_ids)
接下来分析驱动函数s3c2410ts_probe

点击(此处)折叠或打开

  1. static int __devinit s3c2410ts_probe(struct platform_device *pdev)
  2. {
  3.     struct s3c2410_ts_mach_info *info;//用来接收平台设备的私有资源
  4.     struct device *dev = &pdev->dev;
  5.     struct input_dev *input_dev;//输入型设备
  6.     struct resource *res;
  7.     int ret = -EINVAL;

  8.     /* Initialise input stuff */
  9.     memset(&ts, 0, sizeof(struct s3c2410ts));//将全局变量ts清零

  10.     ts.dev = dev;

  11.     info = pdev->dev.platform_data;
  12.     if (!info) {
  13.         dev_err(dev, "no platform data, cannot attach\n");
  14.         return -EINVAL;
  15.     }

  16.     dev_dbg(dev, "initialising touchscreen\n");

  17.     ts.clock = clk_get(dev, "adc");//获取时钟
  18.     if (IS_ERR(ts.clock)) {
  19.         dev_err(dev, "cannot get adc clock source\n");
  20.         return -ENOENT;
  21.     }

  22.     clk_enable(ts.clock);//使能时钟
  23.     dev_dbg(dev, "got and enabled clocks\n");

  24.     ts.irq_tc = ret = platform_get_irq(pdev, 0);//获得触摸屏中断
  25.     if (ret < 0) {
  26.         dev_err(dev, "no resource for interrupt\n");
  27.         goto err_clk;
  28.     }

  29.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获得I/O资源
  30.     if (!res) {
  31.         dev_err(dev, "no resource for registers\n");
  32.         ret = -ENOENT;
  33.         goto err_clk;
  34.     }

  35.     ts.io = ioremap(res->start, resource_size(res));//将物理地址转化为虚拟地址
  36.     if (ts.io == NULL) {
  37.         dev_err(dev, "cannot map registers\n");
  38.         ret = -ENOMEM;
  39.         goto err_clk;
  40.     }

  41.     /* inititalise the gpio */
  42.     if (info->cfg_gpio)
  43.         info->cfg_gpio(to_platform_device(ts.dev));

  44.     ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
  45.                  s3c24xx_ts_conversion, 1);//注册申请获得ADC服务的客户, 设置好select和convert回调函数
  46.     if (IS_ERR(ts.client)) {
  47.         dev_err(dev, "failed to register adc client\n");
  48.         ret = PTR_ERR(ts.client);
  49.         goto err_iomap;
  50.     }

  51.     /* Initialise registers */
  52.     if ((info->delay & 0xffff) > 0)
  53.         writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);

  54.     writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);//硬件操作,进入等待中断模式

  55.     input_dev = input_allocate_device();//分配一个输入型设备
  56.     if (!input_dev) {
  57.         dev_err(dev, "Unable to allocate the input device !!\n");
  58.         ret = -ENOMEM;
  59.         goto err_iomap;
  60.     }

  61.     ts.input = input_dev;
  62.     ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
  63.     ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
  64.     input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
  65.     input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);

  66.     ts.input->name = "S3C24XX TouchScreen";
  67.     ts.input->id.bustype = BUS_HOST;
  68.     ts.input->id.vendor = 0xDEAD;
  69.     ts.input->id.product = 0xBEEF;
  70.     ts.input->id.version = 0x0102;

  71.     ts.shift = info->oversampling_shift;
  72.     ts.features = platform_get_device_id(pdev)->driver_data;

  73.     ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
  74.              "s3c2410_ts_pen", ts.input);
  75.     if (ret) {
  76.         dev_err(dev, "cannot get TC interrupt\n");
  77.         goto err_inputdev;
  78.     }

  79.     dev_info(dev, "driver attached, registering input device\n");

  80.     /* All went ok, so register to the input system */
  81.     ret = input_register_device(ts.input);//注册一个输入型设备
  82.     if (ret < 0) {
  83.         dev_err(dev, "failed to register input device\n");
  84.         ret = -EIO;
  85.         goto err_tcirq;
  86.     }

  87.     return 0;

  88.  err_tcirq:
  89.     free_irq(ts.irq_tc, ts.input);
  90.  err_inputdev:
  91.     input_free_device(ts.input);
  92.  err_iomap:
  93.     iounmap(ts.io);
  94.  err_clk:
  95.     del_timer_sync(&touch_timer);
  96.     clk_put(ts.clock);
  97.     return ret;
  98. }
第10行,将全局变量ts清零,我们来看ts数据类型的定义:

点击(此处)折叠或打开

  1. struct s3c2410ts {
  2.     struct s3c_adc_client *client;
  3.     struct device *dev;
  4.     struct input_dev *input;//通用输入设备结构体
  5.     struct clk *clock;
  6.     void __iomem *io;//映射后的寄存器基址
  7.     unsigned long xp;//x坐标
  8.     unsigned long yp;//y坐标
  9.     int irq_tc;//触摸屏中断号?
  10.     int count;//计采样次数
  11.     int shift;//1<
  12.     int features;
  13. };

  14. static struct s3c2410ts ts
第14行,获得了平台设备的私有数据,看info数据类型的定义,它定义在arch/arm/plat-samsung/include/plat/ts.h:

点击(此处)折叠或打开

  1. struct s3c2410_ts_mach_info {
  2.        int delay;//adc延迟
  3.        int presc;//预分频系数
  4.        int oversampling_shift;//通过它得到采用次数
  5.     void (*cfg_gpio)(struct platform_device *dev);
  6. }
应该要在mach-my2440中定义一个值,类似

点击(此处)折叠或打开

  1. static struct s3c2410_ts_mach_info s3c_ts_platform __initdata = {
  2.     .delay            = 10000,
  3.     .presc            = 49,
  4.     .oversampling_shift    = 2,
  5. }
继续s3c2410ts_probe,22行获得时钟,需要时钟的是因为触摸屏要用到ADC转换,而完成ADC转换则需要时钟
第52行,初始化gpio管脚,这个cfg_gpio是设备提供的接口,参数是一个宏,返回的是struct platform_device结构体指针,这个宏用到了大名鼎鼎的container_of宏,container_of宏的作用是根据结构体的成员返回结构体的首地址。

ts.client = s3c_adc_register(pdev, s3c24xx_ts_select, s3c24xx_ts_conversion, 1);这里s3c24xx_ts_select、s3c24xx_ts_conversion就是回调函数,先看看看这两个回调函数再继续s3c2410ts_probe:

点击(此处)折叠或打开

  1. /**
  2.  * s3c24xx_ts_select - ADC selection callback.
  3.  * @client: The client that was registered with the ADC core.
  4.  * @select: The reason for select.
  5.  *
  6.  * Called when the ADC core selects (or deslects) us as a client.
  7.  */
  8. static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
  9. {
  10.     if (select) {
  11.         writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
  12.          ts.io + S3C2410_ADCTSC);
  13.     } else {
  14.         mod_timer(&touch_timer, jiffies+1);
  15.         writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
  16.     }
  17. }
select为真,将ADCTSC的23467位置1,其他位为0,意味着禁止XP上拉,启用自动(连续)x/y轴坐标转换模式。 select为假,调用了一个mod_timer,然后设置ADCTSC为等待中断模式、探测到抬起发中断。

点击(此处)折叠或打开

  1. /**
  2.  * s3c24xx_ts_conversion - ADC conversion callback
  3.  * @client: The client that was registered with the ADC core.
  4.  * @data0: The reading from ADCDAT0.
  5.  * @data1: The reading from ADCDAT1.
  6.  * @left: The number of samples left.
  7.  *
  8.  * Called when a conversion has finished.
  9.  */
  10. static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
  11.                  unsigned data0, unsigned data1,
  12.                  unsigned *left)
  13. {
  14.     dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);

  15.     ts.xp += data0;
  16.     ts.yp += data1;

  17.     ts.count++;

  18.     /* From tests, it seems that it is unlikely to get a pen-up
  19.      * event during the conversion process which means we can
  20.      * ignore any pen-up events with less than the requisite
  21.      * count done.
  22.      *
  23.      * In several thousand conversions, no pen-ups where detected
  24.      * before count completed.
  25.      */
  26. }
看中断处理函数

点击(此处)折叠或打开

  1. /**
  2.  * stylus_irq - touchscreen stylus event interrupt
  3.  * @irq: The interrupt number
  4.  * @dev_id: The device ID.
  5.  *
  6.  * Called when the IRQ_TC is fired for a pen up or down event.
  7.  */
  8. static irqreturn_t stylus_irq(int irq, void *dev_id)
  9. {
  10.     unsigned long data0;
  11.     unsigned long data1;
  12.     bool down;

  13.     data0 = readl(ts.io + S3C2410_ADCDAT0);//读ADCDAT0
  14.     data1 = readl(ts.io + S3C2410_ADCDAT1);//读ADCDAT1

  15.     down = get_down(data0, data1);//判断有没有按下

  16.     /* TODO we should never get an interrupt with down set while
  17.      * the timer is running, but maybe we ought to verify that the
  18.      * timer isn't running anyways. */

  19.     if (down)
  20.         s3c_adc_start(ts.client, 0, 1 << ts.shift);//如果被按下
  21.     else
  22.         dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);

  23.     if (ts.features & FEAT_PEN_IRQ) {
  24.         /* Clear pen down/up interrupt */
  25.         writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
  26.     }

  27.     return IRQ_HANDLED;
  28. }
s3c_adc_start函数分析

点击(此处)折叠或打开

  1. int s3c_adc_start(struct s3c_adc_client *client,
  2.           unsigned int channel, unsigned int nr_samples)
  3. {
  4.     struct adc_device *adc = adc_dev;
  5.     unsigned long flags;

  6.     if (!adc) {
  7.         printk(KERN_ERR "%s: failed to find adc\n", __func__);
  8.         return -EINVAL;
  9.     }

  10.     spin_lock_irqsave(&adc->lock, flags);//获得锁

  11.     if (client->is_ts && adc->ts_pend) {
  12.         spin_unlock_irqrestore(&adc->lock, flags);
  13.         return -EAGAIN;
  14.     }

  15.     client->channel = channel;//设置通道
  16.     client->nr_samples = nr_samples;//设置采样次数

  17.     if (client->is_ts)
  18.         adc->ts_pend = client;//如果是触摸屏,将client保存在ts_pend中
  19.     else
  20.         list_add_tail(&client->pend, &adc_pending);//如果不是触摸屏,挂到链表

  21.     if (!adc->cur)//如果没有正在处理其他客户请求,则调用s3c_adc_try函数处理当前客户请求
  22.         s3c_adc_try(adc);

  23.     spin_unlock_irqrestore(&adc->lock, flags);//释放锁

  24.     return 0;
  25. }
  26. EXPORT_SYMBOL_GPL(s3c_adc_start)

点击(此处)折叠或打开

  1. static void s3c_adc_try(struct adc_device *adc)
  2. {
  3.     struct s3c_adc_client *next = adc->ts_pend;

  4.     if (!next && !list_empty(&adc_pending))//如果没有触摸屏在等待, 
  5.     {
  6.         next = list_first_entry(&adc_pending,
  7.                     struct s3c_adc_client, pend);
  8.         list_del(&next->pend);
  9.     }
  10.     else//如果有
  11.         adc->ts_pend = NULL;

  12.     if (next) {
  13.         adc_dbg(adc, "new client is %p\n", next);
  14.         adc->cur = next;
  15.         s3c_adc_select(adc, next);//调用s3c_adc_select,该函数有client->select_cb回调函数的调用
  16.         s3c_adc_convert(adc);
  17.         s3c_adc_dbgshow(adc);
  18.     }
  19. }
s3c_adc_select(adc, next);
也就是在这里调用了s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)函数,调用了mod_timer(&touch_timer, jiffies+1);开启了定时器
mod_timer的功能是修改定时器的超时参数并重启,第一个参数是要修改的时钟,第二个参数是超时时间。

点击(此处)折叠或打开

  1. int mod_timer(struct timer_list *timer, unsigned long expires)
  2. {
  3.     expires = apply_slack(timer, expires);

  4.     /*
  5.      * This is a common optimization triggered by the
  6.      * networking code - if the timer is re-modified
  7.      * to be the same thing then just return:
  8.      */
  9.     if (timer_pending(timer) && timer->expires == expires)
  10.         return 1;

  11.     return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
  12. }
定时器touch_timer的初始化: 

点击(此处)折叠或打开

  1. static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0)
DEFINE_TIMER宏会定义一个名叫 touch_timer内核定时器,并初始化其 function, expires, name 和 base 字段。当jiffies 到达expires时会执行function,并传递data作为参数。这里并没有给expires和data传值

看看touch_timer_fire,定时器超时会做什么
touch_timer_fire

点击(此处)折叠或打开

  1. static void touch_timer_fire(unsigned long data)
  2. {
  3.     unsigned long data0;
  4.     unsigned long data1;
  5.     bool down;

  6.     data0 = readl(ts.io + S3C2410_ADCDAT0);
  7.     data1 = readl(ts.io + S3C2410_ADCDAT1);

  8.     down = get_down(data0, data1);

  9.     if (down) {//如果被按下
  10.         if (ts.count == (1 << ts.shift)) {//如果采样的次数到达计数值
  11.             ts.xp >>= ts.shift;
  12.             ts.yp >>= ts.shift;

  13.             dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
  14.                 __func__, ts.xp, ts.yp, ts.count);

  15.             input_report_abs(ts.input, ABS_X, ts.xp);//提交坐标
  16.             input_report_abs(ts.input, ABS_Y, ts.yp);

  17.             input_report_key(ts.input, BTN_TOUCH, 1);//提交键值,触摸屏被按下
  18.             input_sync(ts.input);

  19.             ts.xp = 0;
  20.             ts.yp = 0;
  21.             ts.count = 0;
  22.         }

  23.         s3c_adc_start(ts.client, 0, 1 << ts.shift);//初始化的时候,ts被清零,ts.count = 0,第一次进按键按下中断,启动ADC中断,ADC采集数据,再启动定时器
  24.     } else {//按键弹起
  25.         ts.xp = 0;
  26.         ts.yp = 0;
  27.         ts.count = 0;

  28.         input_report_key(ts.input, BTN_TOUCH, 0);//报告键值,触摸屏被弹起
  29.         input_sync(ts.input);

  30.         writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);//进入等待中断模式
  31.     }
  32. }
使用input子系统函数input_sync(ts.input)同步所有事件,将所有事件组成一个evdev包,并通过/dev/input/eventX发送出去。input子系统要求提交各种事件后必须执行同步。 

代码都分析完了,结合ADC驱动,总结下触摸屏驱动工作的逻辑

  • s3c2410ts_probe 
    • 获得时钟、使能时钟
    • 获得IO资源,映射到虚拟地址
    • 初始化gpio、为采样数据分配空间
    • 注册ADC服务,传递select和convert回调函数指针
    • 设置工作模式为等待中断模式
    • 获得、申请中断,中断处理函数stylus_irq
    • 注册系统输入设备
  • 等待INT_TC中断,中断到来,进入stylus_irq 
    • 判断是否被按下,被按下调用s3c_adc_start
    • 在s3c_adc_start中根据传的参数设置通道和采样次数,调用s3c_adc_try处理请求
  • 在s3c_adc_try中,先检查有没有触摸屏在等待,如果有 
    • 调用s3c_adc_select,执行触摸屏注册的select回调函数(s3c24xx_ts_select),禁止XP上拉,启用自动(连续)x/y轴坐标转换模式。
    • 调用s3c_adc_convert,开启AD转换
    • 调用s3c_adc_dbgshow,打印相关寄存器的值
  • 转换完成,产生INT_ADC中断,进入中断处理函数,s3c_adc_irq 
    • 读ADCDAT0和ADCDAT1的值,将12位以上清零,这是x、y轴的坐标
    • 执行触摸屏注册的convert回调函数(s3c24xx_ts_conversion),保存坐标到结构体数组apt
    • 这里我们假设采样完成,再次调用select回调函数,取消对触摸屏的选择,启动定时器,超时后执行touch_timer_fire
  • 定时器超时处理函数touch_timer_fire 中 
    • 采样完成,向input子系统提交处理后的坐标、触屏时间等,清空ts.x、ts.y、ts.count,等待下一次采样
    • 采样未完成,调用s3c_adc_start继续采样
  • 执行完touch_timer_fire,设置ADC_TSC寄存器,回到等待中断模式






阅读(2020) | 评论(0) | 转发(0) |
0

上一篇:s3c2410 ADC驱动

下一篇:没有了

给主人留下些什么吧!~~