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

全部博文(31)

文章存档

2016年(4)

2015年(27)

我的朋友

分类: LINUX

2016-08-14 15:43:09


adc驱动文件路径:arch/arm/plat-samsung/adc.c 
adc设备文件路径:arch/arm/plat-samsung/devs.c

A/D转换器和触摸屏接口的功能框图

这里写图片描述 
s3c2416有10路adc通道,其中XP、XM、YP、YM分别与AIN9、AIN8、AIN7、AIN6复用。由于只有1个A/D转换器,需要通过一个10选1开关MUX,来选通哪一路A/D通道进行转换。 
触摸屏的中断产生单元会产生2个中断,INT_ADC和INT_TC。触摸屏工作在中断等待模式会等待中断INT_TC,INT_TC发生后,立刻激活相应的A/D转换,INT_ADC是A/D转换完成的中断信号,得到触摸点的坐标后,返回等待中断等待模式。

ADCCON和ADCMUX寄存器

ADC控制寄存 
模拟通道选择寄存器

看驱动代码当然从module_init开始


点击(此处)折叠或打开

  1. static int __init adc_init(void)
  2. {
  3.     int ret;

  4.     ret = platform_driver_register(&s3c_adc_driver);//注册平台驱动,s3c_adc_driver
  5.     if (ret)
  6.         printk(KERN_ERR "%s: failed to add adc driver\n", __func__);

  7.     return ret;
  8. }

这段代码很简单,想platform注册了s3c_adc_driver,它的定义如下

点击(此处)折叠或打开

  1. static struct platform_driver s3c_adc_driver = {
  2.     .id_table    = s3c_adc_driver_ids,//支持的设备列表
  3.     .driver        = {
  4.         .name    = "s3c-adc",
  5.         .owner    = THIS_MODULE,
  6.     },
  7.     .probe        = s3c_adc_probe,//注册设备时执行
  8.     .remove        = __devexit_p(s3c_adc_remove),
  9.     .suspend    = s3c_adc_suspend,
  10.     .resume        = s3c_adc_resume,
  11. };
注意,当id_table不为NULL时,是先拿id_talbe保存的名字和设备的名字匹配,如果没有匹配,再去拿驱动的名字和设备的名字匹配! 


看看id_talbe

点击(此处)折叠或打开

  1. static struct platform_device_id s3c_adc_driver_ids[] = {
  2.     {
  3.         .name = "s3c24xx-adc",
  4.         .driver_data = TYPE_S3C24XX,
  5.     }, {
  6.         .name = "s3c64xx-adc",
  7.         .driver_data = TYPE_S3C64XX,
  8.     },
  9.     { }
  10. };
  11. MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);
最后一行的MODULE_DEVICE_TABLE是啥?为什么结构体最后要加个空的{}呢?


MODULE_DEVICE_TABLE一般用在热插拔的设备驱动中。 
上述xxx_driver_ids结构,是此驱动所支持的设备列表。 
作用是:将xxx_driver_ids结构输出到用户空间,这样模块加载系统在加载模块时,就知道了什么模块对应什么硬件设备。 
用法是:MODULE_DEVICE_TABLE(设备类型,设备表),其中,设备类型,包括USB,PCI等,也可以自己起名字,上述代码中是针对不同的平台分的类;设备表也是自己定义的,它的最后一项必须是空,用来标识结束。

这个MODULE_DEVICE_TABLE功能的解释是别人文章里拷贝过来的。。。

platform bus做到了设备与总线的分离,由Linux内核进行统一管理

宋宝华:隔离BSP和驱动。在BSP中定义设备和设备使用的资源(IO资源、中断资源等)、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动有更好的可扩展性和跨平台性。

从宋老师的话中,我们知道,驱动中是不涉及具体硬件的配置的,用通用API去获取,怎么获取呢?看s3c_adc_driver中的s3c_adc_probe吧

s3c_adc_probe


点击(此处)折叠或打开

  1. static int s3c_adc_probe(struct platform_device *pdev)
  2. {
  3.     struct device *dev = &pdev->dev;
  4.     struct adc_device *adc;//定义一个 struct adc_device代表一个adc设备
  5.     struct resource *regs;//获得资源,IO与中断
  6.     int ret;
  7.     unsigned tmp;

  8.     adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL);//动态分配ADC空间
  9.     if (adc == NULL) {
  10.         dev_err(dev, "failed to allocate adc_device\n");
  11.         return -ENOMEM;
  12.     }

  13.     spin_lock_init(&adc->lock);//初始化自旋锁

  14.     adc->pdev = pdev;
  15.     adc->prescale = S3C2410_ADCCON_PRSCVL(49);//设置预分频系数

  16.     adc->irq = platform_get_irq(pdev, 1);//获得ADC中断号
  17.     if (adc->irq <= 0) {
  18.         dev_err(dev, "failed to get adc irq\n");
  19.         ret = -ENOENT;
  20.         goto err_alloc;
  21.     }

  22.     ret = request_irq(adc->irq, s3c_adc_irq, 0, dev_name(dev), adc);//注册中断
  23.     if (ret < 0) {
  24.         dev_err(dev, "failed to attach adc irq\n");
  25.         goto err_alloc;
  26.     }

  27.     adc->clk = clk_get(dev, "adc");//获取ADC时钟
  28.     if (IS_ERR(adc->clk)) {
  29.         dev_err(dev, "failed to get adc clock\n");
  30.         ret = PTR_ERR(adc->clk);
  31.         goto err_irq;
  32.     }

  33.     regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获得设备的IO资源
  34.     if (!regs) {
  35.         dev_err(dev, "failed to find registers\n");
  36.         ret = -ENXIO;
  37.         goto err_clk;
  38.     }

  39.     adc->regs = ioremap(regs->start, resource_size(regs));//映射IO到虚拟地址
  40.     if (!adc->regs) {
  41.         dev_err(dev, "failed to map registers\n");
  42.         ret = -ENXIO;
  43.         goto err_clk;
  44.     }

  45.     clk_enable(adc->clk);//使能时钟

  46.     tmp = adc->prescale | S3C2410_ADCCON_PRSCEN;//设置使用预分频
  47.     if (platform_get_device_id(pdev)->driver_data == TYPE_S3C64XX) {
  48.         /* Enable 12-bit ADC resolution */
  49.         tmp |= S3C64XX_ADCCON_RESSEL;
  50.     }
  51.     writel(tmp, adc->regs + S3C2410_ADCCON);

  52.     dev_info(dev, "attached adc driver\n");

  53.     platform_set_drvdata(pdev, adc);
  54.     adc_dev = adc;

  55.     return 0;

  56.  err_clk:
  57.     clk_put(adc->clk);

  58.  err_irq:
  59.     free_irq(adc->irq, adc);

  60.  err_alloc:
  61.     kfree(adc);
  62.     return ret;
  63. }

先看s3c_adc_probe的参数,struct platform_device *pdev,一个结构体直针,驱动注册时会匹配设备,struct platform_device就是用来描述设备的结构体。 
adc设备的描述定义在arch/arm/plat-samsung/dev-adc.c。我们转移阵地,去看看里面的定义。

点击(此处)折叠或打开

  1. static struct resource s3c_adc_resource[] = {
  2.     [0] = {
  3.         .start = SAMSUNG_PA_ADC,
  4.         .end = SAMSUNG_PA_ADC + SZ_256 - 1,
  5.         .flags = IORESOURCE_MEM,
  6.     },
  7.     [1] = {
  8.         .start = IRQ_TC,
  9.         .end = IRQ_TC,
  10.         .flags = IORESOURCE_IRQ,
  11.     },
  12.     [2] = {
  13.         .start = IRQ_ADC,
  14.         .end = IRQ_ADC,
  15.         .flags = IORESOURCE_IRQ,
  16.     },
  17. };

  18. struct platform_device s3c_device_adc = {
  19.     .name        = "samsung-adc",
  20.     .id        = -1,
  21.     .num_resources    = ARRAY_SIZE(s3c_adc_resource),
  22.     .resource    = s3c_adc_resource,
  23. };


回到s3c_adc_probe 
设置预分频系数使用了一个宏,它将49左移6位,因为ADCCON寄存器的6到13为代表预分频系数; 
使用tmp写ADCCON寄存器,先将14位置1,使能预分频功能,将第3位置1,设置分辨率为12位。

kzalloc为adc动态分配空间。看看struct adc_device的定义:

点击(此处)折叠或打开

  1. struct adc_device {
  2.     struct platform_device    *pdev;//平台设备信息
  3.     struct platform_device    *owner;
  4.     struct clk        *clk;//时钟
  5.     struct s3c_adc_client    *cur;//当前正在处理的客户
  6.     struct s3c_adc_client    *ts_pend;//代表触摸屏
  7.     void __iomem        *regs;//ADC的I/O寄存器
  8.     spinlock_t         lock;//自旋锁

  9.     unsigned int         prescale;//预分频

  10.     int             irq;//adc中断号
  11. };
这里有个struct s3c_adc_client结构,ts_pend代表触摸屏,这里ADC把客户分为触摸屏和非触摸屏两大类,专门用ts_pend代表触摸屏,struct s3c_adc_client定义如下:

点击(此处)折叠或打开

  1. struct s3c_adc_client {//代表了一个请求ADC服务的客户
  2.     struct platform_device *pdev;//平台设备结构体
  3.     struct list_head pend;//链表,将客户插入等待链表
  4.     wait_queue_head_t *wait;//等待队列头

  5.     unsigned int nr_samples;//记录客户指定的采样次数
  6.     int result;//记录采样结构
  7.     unsigned char is_ts;//是不是触摸屏
  8.     unsigned char channel;//客户要使用的ADC通道
  9.    
  10.     void (*select_cb)(struct s3c_adc_client *c, unsigned selected);//select_cb回调函数,用来选择客户和取消客户
  11.     void (*convert_cb)(struct s3c_adc_client *c,
  12.              unsigned val1, unsigned val2,
  13.              unsigned *samples_left);//convert_cb回调函数,用于处理A/D转换结果,
  14. }


s3c_adc_probe就到这里

客户注册ADC服务的接口函数s3c_adc_register


点击(此处)折叠或打开

  1. struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,
  2.                     void (*select)(struct s3c_adc_client *client,
  3.                          unsigned int selected),
  4.                     void (*conv)(struct s3c_adc_client *client,
  5.                          unsigned d0, unsigned d1,
  6.                          unsigned *samples_left),
  7.                     unsigned int is_ts)
  8. {
  9.     struct s3c_adc_client *client;

  10.     WARN_ON(!pdev);

  11.     if (!select)//如果没有传select回调函数,则使用默认的回调函数
  12.         select = s3c_adc_default_select;

  13.     if (!pdev)
  14.         return ERR_PTR(-EINVAL);

  15.     client = kzalloc(sizeof(struct s3c_adc_client), GFP_KERNEL);//动态分配空间
  16.     if (!client) {
  17.         dev_err(&pdev->dev, "no memory for adc client\n");
  18.         return ERR_PTR(-ENOMEM);
  19.     }

  20.     client->pdev = pdev;//初始化相关成员
  21.     client->is_ts = is_ts;
  22.     client->select_cb = select;
  23.     client->convert_cb = conv;

  24.     return client;
  25. }
  26. EXPORT_SYMBOL_GPL(s3c_adc_register);


WARN_ON调用dump_stack,打印堆栈信息

客户读取A/D转换结果,s3c_adc_read


点击(此处)折叠或打开

  1. int s3c_adc_read(struct s3c_adc_client *client, unsigned int ch)
  2. {
  3.     DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
  4.     int ret;

  5.     client->convert_cb = s3c_convert_done;//convert回调函数,用于处理A/D转换结果
  6.     client->wait = &wake;
  7.     client->result = -1;

  8.     ret = s3c_adc_start(client, ch, 1);//设置采用参数,通道为ch,次数均为1
  9.     if (ret < 0)
  10.         goto err;

  11.     ret = wait_event_timeout(wake, client->result >= 0, HZ / 2);//休眠,条件client->result >= 0为真,唤醒,最长睡眠时间为HZ/2
  12.     if (client->result < 0) {
  13.         ret = -ETIMEDOUT;
  14.         goto err;
  15.     }

  16.     client->convert_cb = NULL;
  17.     return client->result;

  18. err:
  19.     return ret;
  20. }
  21. EXPORT_SYMBOL_GPL(s3c_adc_read);

A/D转换完成函数s3c_convert_done

点击(此处)折叠或打开

  1. static void s3c_convert_done(struct s3c_adc_client *client,
  2.              unsigned v, unsigned u, unsigned *left)
  3. {
  4.     client->result = v;//记录采用结果
  5.     wake_up(client->wait);//唤醒等待队列中休眠的进程
  6. }

设置采样参数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.     if (client->is_ts && adc->ts_pend)
  11.         return -EAGAIN;

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

  13.     client->channel = channel;//设置通道
  14.     client->nr_samples = nr_samples;//设置采用是次数

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

  19.     if (!adc->cur)//如果没有正在处理的其他客户请求,则调用s3c_adc_try
  20.         s3c_adc_try(adc);

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

  22.     return 0;
  23. }

spin_lock_irqsave()是一个很便利的接口,它有以下动作:1、保存本地中断状态;2、关闭本地中断;3、获取自旋锁 

spin_lock_irqsave()适用于在中断处理函数中需要用到自旋锁。 
解锁时通过 spin_unlock_irqrestore完成释放锁、恢复本地中断到之前的状态等工作。 
还有一对 spin_lock_irq 和 spin_unlock_irq,如果你确定在获取锁之前本地中断是开启的,那么就不需要保存中断状态,解锁的时候直接将本地中断启用就可以啦。

处理当前客户请求s3c_adc_try


点击(此处)折叠或打开

  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.         next = list_first_entry(&adc_pending,
  6.                     struct s3c_adc_client, pend);
  7.         list_del(&next->pend);
  8.     } else
  9.         adc->ts_pend = NULL;

  10.     if (next) {
  11.         adc_dbg(adc, "new client is %p\n", next);
  12.         adc->cur = next;
  13.         s3c_adc_select(adc, next);
  14.         s3c_adc_convert(adc);
  15.         s3c_adc_dbgshow(adc);
  16.     }
  17. }


这段代码很简单,没有给注释,大体的功能是先:检查有没有触摸屏在等待,如果有,if后的条件为假,直接执行140行,然后设置当前客户为触摸屏,调用select、convert、dbgshow处理客户的AD请求;如果没有触摸屏在等待,就去链表中检查有没有非触摸屏客户,链表为空,则结束。 
下面就是select、convert、dbgshow的分析

s3c_adc_select

点击(此处)折叠或打开

  1. static inline void s3c_adc_select(struct adc_device *adc,
  2.                  struct s3c_adc_client *client)
  3. {
  4.     unsigned con = readl(adc->regs + S3C2410_ADCCON);

  5.     client->select_cb(client, 1);//执行客户自定义的回调函数

  6.     con &= ~S3C2410_ADCCON_MUXMASK;//清零操作
  7.     con &= ~S3C2410_ADCCON_STDBM;
  8.     con &= ~S3C2410_ADCCON_STARTMASK;

  9.     if (!client->is_ts)//如果是非触摸屏
  10.         con |= S3C2410_ADCCON_SELMUX(client->channel);

  11.     writel(con, adc->regs + S3C2410_ADCCON);//写ADCCON寄存器
  12. }

s3c_adc_convert

点击(此处)折叠或打开

  1. static inline void s3c_adc_convert(struct adc_device *adc)
  2. {
  3.     unsigned con = readl(adc->regs + S3C2410_ADCCON);//读ADCCON寄存器

  4.     con |= S3C2410_ADCCON_ENABLE_START;//将ADCCON的第0位置1,开启ADC转换
  5.     writel(con, adc->regs + S3C2410_ADCCON);//写ADCCON寄存器
  6. }

对照前面的寄存器功能表,很容易就知道它就是开启了AD转换。。

s3c_adc_dbgshow

点击(此处)折叠或打开

  1. static void s3c_adc_dbgshow(struct adc_device *adc)
  2. {
  3.     //打印三个寄存器的值
  4.     adc_dbg(adc, "CON=%08x, TSC=%08x, DLY=%08x\n",
  5.         readl(adc->regs + S3C2410_ADCCON),
  6.         readl(adc->regs + S3C2410_ADCTSC),
  7.         readl(adc->regs + S3C2410_ADCDLY));
  8. }

s3c_adc_convert函数启动AD转换,转换结束后,会触发ADC中断,下面看ADC中断处理函数

s3c_adc_irq

点击(此处)折叠或打开

  1. static irqreturn_t s3c_adc_irq(int irq, void *pw)//中断处理函数
  2. {
  3.     struct adc_device *adc = pw;
  4.     struct s3c_adc_client *client = adc->cur;//获得当前请求ADC服务的客户
  5.     enum s3c_cpu_type cpu = platform_get_device_id(adc->pdev)->driver_data;
  6.     unsigned data0, data1;

  7.     if (!client) {
  8.         dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__);
  9.         goto exit;
  10.     }

  11.     data0 = readl(adc->regs + S3C2410_ADCDAT0);//读ADCDAT0
  12.     data1 = readl(adc->regs + S3C2410_ADCDAT1);//读ADCDAT1
  13.     adc_dbg(adc, "read %d: 0x%04x, 0x%04x\n", client->nr_samples, data0, data1);

  14.     client->nr_samples--;//采样次数-1

  15.     if (cpu == TYPE_S3C64XX) {
  16.         /* S3C64XX ADC resolution is 12-bit */
  17.         data0 &= 0xfff;
  18.         data1 &= 0xfff;
  19.     } else {
  20.         data0 &= 0x3ff;
  21.         data1 &= 0x3ff;
  22.     }

  23.     if (client->convert_cb)//如果指定了convert_cb回调函数,执行它处理A/D转换结果
  24.         (client->convert_cb)(client, data0, data1, &client->nr_samples);

  25.     if (client->nr_samples > 0)//再采样一次 
  26.     {
  27.         /* fire another conversion for this */

  28.         client->select_cb(client, 1);
  29.         s3c_adc_convert(adc);//开启转换
  30.     } else {
  31.         spin_lock(&adc->lock);
  32.         (client->select_cb)(client, 0);//如果不需要采样,就取消对用户的选择
  33.         adc->cur = NULL;//设置当前客户为空

  34.         s3c_adc_try(adc);//检查是否还有客户在等待ADC服务,如果有,则其设置为当前客户,
            并启动AD转换。*/

  35.         spin_unlock(&adc->lock);
  36.     }

  37. exit:
  38.     if (cpu == TYPE_S3C64XX) {
  39.         /* Clear ADC interrupt */
  40.         writel(0, adc->regs + S3C64XX_ADCCLRINT);
  41.     }
  42.     return IRQ_HANDLED;
  43. }

到此,ADC驱动分析结束

阅读(1764) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~