linux2.6.30.4中,系统已经自带有了ADC通用驱动文件---arch/arm/plat-s3c24xx/adc.c,它是以平台驱动设备模型的架构来编写的,里面是一些比较通用稳定的代码,但是linux2.6.30.4版本的ADC通用驱动文件并不完善,居然没有读函数。后来去看了linux3.8版本的ADC通用文件----arch/arm/plat-samsung/adc.c才是比较完善的。

但是本节并不是分析这个文件,而是以另外一种架构来编写ADC驱动,因为ADC驱动实在是比较简单,就没有使用平台驱动设备模型为架构来编写了,这次我们使用的是混杂(misc)设备驱动。

问:什么是misc设备驱动?

答:miscdevice共享一个主设备号MISC_MAJOR(10),但次设备号不同。所有的miscdevice设备形成一条链表,对设备访问时内核根据设备号来查找对应的miscdevice设备,然后调用其file_operations结构体中注册的文件操作接口进行操作。

[cpp] view
plain
?
  1. struct miscdevice  {
  2. int minor;              //次设备号,如果设置为MISC_DYNAMIC_MINOR则系统自动分配
  3. const char *name;       //设备名
  4. const struct file_operations *fops;     //操作函数
  5. struct list_head list;
  6. struct device *parent;
  7. struct device *this_device;
  8. };

dev_init入口函数分析:

[cpp] view
plain
?
  1. static int __init dev_init(void)
  2. {
  3. int ret;
  4. base_addr=ioremap(S3C2410_PA_ADC,0x20);
  5. if (base_addr == NULL)
  6. {
  7. printk(KERN_ERR "failed to remap register block\n");
  8. return -ENOMEM;
  9. }
  10. adc_clock = clk_get(NULL, "adc");
  11. if (!adc_clock)
  12. {
  13. printk(KERN_ERR "failed to get adc clock source\n");
  14. return -ENOENT;
  15. }
  16. clk_enable(adc_clock);
  17. ADCTSC = 0;
  18. ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
  19. if (ret)
  20. {
  21. iounmap(base_addr);
  22. return ret;
  23. }
  24. ret = misc_register(&misc);
  25. printk (DEVICE_NAME" initialized\n");
  26. return ret;
  27. }

首先是映射ADC寄存器地址将其转换为虚拟地址,然后获得ADC时钟并使能ADC时钟,接着申请ADC中断,其中断处理函数为

adcdone_int_handler,而flags为IRQF_SHARED,即共享中断,因为触摸屏里也要申请ADC中断,最后注册一个混杂设备。

当应用程序open ("/dev/adc",...)时,就会调用到驱动里面的open函数,那么我们来看看open函数做了什么?

[cpp] view
plain
?
  1. static int tq2440_adc_open(struct inode *inode, struct file *filp)
  2. {
  3. /* 初始化等待队列头 */
  4. init_waitqueue_head(&(adcdev.wait));
  5. /* 开发板上ADC的通道2连接着一个电位器 */
  6. adcdev.channel=2;   //设置ADC的通道
  7. adcdev.prescale=0xff;
  8. DPRINTK( "ADC opened\n");
  9. return 0;
  10. }

很简单,先初始化一个等待队列头,因为入口函数里既然有申请ADC中断,那么肯定要使用等待队列,接着设置ADC通道,因为TQ2440的ADC输入通道默认是2,设置预分频值为0xff。

当应用程序read时,就会调用到驱动里面的read函数,那么我们来看看read函数做了些什么?

[cpp] view
plain
?
  1. static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
  2. {
  3. char str[20];
  4. int value;
  5. size_t len;
  6. /* 尝试获得ADC_LOCK信号量,如果能够立刻获得,它就获得信号量并返回0
  7. * 否则,返回非零,它不会导致调用者睡眠,可以在中断上下文使用
  8. */
  9. if (down_trylock(&ADC_LOCK) == 0)
  10. {
  11. /* 表示A/D转换器资源可用 */
  12. ADC_enable = 1;
  13. /* 使能预分频,选择ADC通道,最后启动ADC转换*/
  14. START_ADC_AIN(adcdev.channel, adcdev.prescale);
  15. /* 等待事件,当ev_adc = 0时,进程被阻塞,直到ev_adc>0 */
  16. wait_event_interruptible(adcdev.wait, ev_adc);
  17. ev_adc = 0;
  18. DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ((ADCCON & 0x80) ? 1:0));
  19. /* 将在ADC中断处理函数读取的ADC转换结果赋值给value */
  20. value = adc_data;
  21. sprintf(str,"%5d", adc_data);
  22. copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));
  23. ADC_enable = 0;
  24. up(&ADC_LOCK);
  25. }
  26. else
  27. {
  28. /* 如果A/D转换器资源不可用,将value赋值为-1 */
  29. value = -1;
  30. }
  31. /* 将ADC转换结果输出到str数组里,以便传给应用空间 */
  32. len = sprintf(str, "%d\n", value);
  33. if (count >= len)
  34. {
  35. /* 从str数组里拷贝len字节的数据到buffer,即将ADC转换数据传给应用空间 */
  36. int r = copy_to_user(buffer, str, len);
  37. return r ? r : len;
  38. }
  39. else
  40. {
  41. return -EINVAL;
  42. }
  43. }

tq2440_adc_read函数首先尝试获得ADC_LOCK信号量,因为触摸屏驱动也有使用ADC资源,两者互有竞争关系,获得ADC资源后,使能预分频,选择ADC通道,最后启动ADC转换,接着就调用wait_event_interruptible
函数进行等待,直到ev_adc>0进程才会继续往下跑,往下跑就会将adc_data数据读出来,调用copy_to_user函数将ADC数据传给应用空间,最后释放ADC_LOCK信号量。

问:什么时候ev_adc>0?默认ev_adc = 0

答:在adcdone_int_handler中断处理函数里,等数据读出后,ev_adc被设置为1。

ADC中断处理函数adcdone_int_handler

[cpp] view
plain
?
  1. /* ADC中断处理函数 */
  2. static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
  3. {
  4. /* A/D转换器资源可用 */
  5. if (ADC_enable)
  6. {
  7. /* 读ADC转换结果数据 */
  8. adc_data = ADCDAT0 & 0x3ff;
  9. /* 唤醒标志位,作为wait_event_interruptible的唤醒条件 */
  10. ev_adc = 1;
  11. wake_up_interruptible(&adcdev.wait);
  12. }
  13. return IRQ_HANDLED;
  14. }

当AD转换完成后就会触发ADC中断,就会进入adcdone_int_handler,这个函数就会讲AD转换数据读到adc_data,接着将唤醒标志位ev_adc置1,最后调用wake_up_interruptible函数唤醒adcdev.wait等待队列。

总结一下ADC的工作流程:

一、open函数里,设置模拟输入通道,设置预分频值

二、read函数里,启动AD转换,进程休眠

三、adc_irq函数里,AD转换结束后触发ADC中断,在ADC中断处理函数将数据读出,唤醒进程

四、read函数里,进程被唤醒后,将adc转换数据传给应用程序

ADC驱动参考源码:

[cpp] view
plain
?
  1. /*************************************
  2. NAME:EmbedSky_adc.c
  3. COPYRIGHT:www.embedsky.net
  4. *************************************/
  5. #include <linux/errno.h>
  6. #include <linux/kernel.h>
  7. #include <linux/module.h>
  8. #include <linux/slab.h>
  9. #include <linux/input.h>
  10. #include <linux/init.h>
  11. #include <linux/serio.h>
  12. #include <linux/delay.h>
  13. #include <linux/clk.h>
  14. #include <asm/io.h>
  15. #include <asm/irq.h>
  16. #include <asm/uaccess.h>
  17. #include <mach/regs-clock.h>
  18. #include <plat/regs-timer.h>
  19. #include <plat/regs-adc.h>
  20. #include <mach/regs-gpio.h>
  21. #include <linux/cdev.h>
  22. #include <linux/miscdevice.h>
  23. #include "tq2440_adc.h"
  24. #undef DEBUG
  25. //#define DEBUG
  26. #ifdef DEBUG
  27. #define DPRINTK(x...) {printk(KERN_DEBUG "EmbedSky_adc: " x);}
  28. #else
  29. #define DPRINTK(x...) (void)(0)
  30. #endif
  31. #define DEVICE_NAME "adc"       /* 设备节点: /dev/adc */
  32. static void __iomem *base_addr;
  33. typedef struct
  34. {
  35. wait_queue_head_t wait;     /* 定义等待队列头 */
  36. int channel;
  37. int prescale;
  38. }ADC_DEV;
  39. DECLARE_MUTEX(ADC_LOCK);    /* 定义并初始化信号量,并初始化为1 */
  40. static int ADC_enable = 0;          /* A/D转换器资是否可用标志位 */
  41. static ADC_DEV adcdev;              /* 用于表示ADC设备 */
  42. static volatile int ev_adc = 0;     /* 作为wait_event_interruptible的唤醒条件 */
  43. static int adc_data;
  44. static struct clk   *adc_clock;
  45. #define ADCCON      (*(volatile unsigned long *)(base_addr + S3C2410_ADCCON))   //ADC control
  46. #define ADCTSC      (*(volatile unsigned long *)(base_addr + S3C2410_ADCTSC))   //ADC touch screen control
  47. #define ADCDLY      (*(volatile unsigned long *)(base_addr + S3C2410_ADCDLY))   //ADC start or Interval Delay
  48. #define ADCDAT0     (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT0))  //ADC conversion data 0
  49. #define ADCDAT1     (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT1))  //ADC conversion data 1
  50. #define ADCUPDN     (*(volatile unsigned long *)(base_addr + 0x14))         //Stylus Up/Down interrupt status
  51. #define PRESCALE_DIS    (0 << 14)
  52. #define PRESCALE_EN     (1 << 14)
  53. #define PRSCVL(x)       ((x) << 6)
  54. #define ADC_INPUT(x)    ((x) << 3)
  55. #define ADC_START       (1 << 0)
  56. #define ADC_ENDCVT      (1 << 15)
  57. /* 使能预分频,选择ADC通道,最后启动ADC转换*/
  58. #define START_ADC_AIN(ch, prescale) \
  59. do{     ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
  60. ADCCON |= ADC_START; \
  61. }while(0)
  62. /* ADC中断处理函数 */
  63. static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
  64. {
  65. /* A/D转换器资源可用 */
  66. if (ADC_enable)
  67. {
  68. /* 读ADC转换结果数据 */
  69. adc_data = ADCDAT0 & 0x3ff;
  70. /* 唤醒标志位,作为wait_event_interruptible的唤醒条件 */
  71. ev_adc = 1;
  72. wake_up_interruptible(&adcdev.wait);
  73. }
  74. return IRQ_HANDLED;
  75. }
  76. static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
  77. {
  78. char str[20];
  79. int value;
  80. size_t len;
  81. /* 尝试获得ADC_LOCK信号量,如果能够立刻获得,它就获得信号量并返回0
  82. * 否则,返回非零,它不会导致调用者睡眠,可以在中断上下文使用
  83. */
  84. if (down_trylock(&ADC_LOCK) == 0)
  85. {
  86. /* 表示A/D转换器资源可用 */
  87. ADC_enable = 1;
  88. /* 使能预分频,选择ADC通道,最后启动ADC转换*/
  89. START_ADC_AIN(adcdev.channel, adcdev.prescale);
  90. /* 等待事件,当ev_adc = 0时,进程被阻塞,直到ev_adc>0 */
  91. wait_event_interruptible(adcdev.wait, ev_adc);
  92. ev_adc = 0;
  93. DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ((ADCCON & 0x80) ? 1:0));
  94. /* 将在ADC中断处理函数读取的ADC转换结果赋值给value */
  95. value = adc_data;
  96. sprintf(str,"%5d", adc_data);
  97. copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));
  98. ADC_enable = 0;
  99. up(&ADC_LOCK);
  100. }
  101. else
  102. {
  103. /* 如果A/D转换器资源不可用,将value赋值为-1 */
  104. value = -1;
  105. }
  106. /* 将ADC转换结果输出到str数组里,以便传给应用空间 */
  107. len = sprintf(str, "%d\n", value);
  108. if (count >= len)
  109. {
  110. /* 从str数组里拷贝len字节的数据到buffer,即将ADC转换数据传给应用空间 */
  111. int r = copy_to_user(buffer, str, len);
  112. return r ? r : len;
  113. }
  114. else
  115. {
  116. return -EINVAL;
  117. }
  118. }
  119. static int tq2440_adc_open(struct inode *inode, struct file *filp)
  120. {
  121. /* 初始化等待队列头 */
  122. init_waitqueue_head(&(adcdev.wait));
  123. /* 开发板上ADC的通道2连接着一个电位器 */
  124. adcdev.channel=2;   //设置ADC的通道
  125. adcdev.prescale=0xff;
  126. DPRINTK( "ADC opened\n");
  127. return 0;
  128. }
  129. static int tq2440_adc_release(struct inode *inode, struct file *filp)
  130. {
  131. DPRINTK( "ADC closed\n");
  132. return 0;
  133. }
  134. static struct file_operations dev_fops = {
  135. owner:  THIS_MODULE,
  136. open:   tq2440_adc_open,
  137. read:   tq2440_adc_read,
  138. release:    tq2440_adc_release,
  139. };
  140. static struct miscdevice misc = {
  141. .minor = MISC_DYNAMIC_MINOR,
  142. .name = DEVICE_NAME,
  143. .fops = &dev_fops,
  144. };
  145. static int __init dev_init(void)
  146. {
  147. int ret;
  148. base_addr=ioremap(S3C2410_PA_ADC,0x20);
  149. if (base_addr == NULL)
  150. {
  151. printk(KERN_ERR "failed to remap register block\n");
  152. return -ENOMEM;
  153. }
  154. adc_clock = clk_get(NULL, "adc");
  155. if (!adc_clock)
  156. {
  157. printk(KERN_ERR "failed to get adc clock source\n");
  158. return -ENOENT;
  159. }
  160. clk_enable(adc_clock);
  161. ADCTSC = 0;
  162. ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
  163. if (ret)
  164. {
  165. iounmap(base_addr);
  166. return ret;
  167. }
  168. ret = misc_register(&misc);
  169. printk (DEVICE_NAME" initialized\n");
  170. return ret;
  171. }
  172. static void __exit dev_exit(void)
  173. {
  174. free_irq(IRQ_ADC, &adcdev);
  175. iounmap(base_addr);
  176. if (adc_clock)
  177. {
  178. clk_disable(adc_clock);
  179. clk_put(adc_clock);
  180. adc_clock = NULL;
  181. }
  182. misc_deregister(&misc);
  183. }
  184. EXPORT_SYMBOL(ADC_LOCK);
  185. module_init(dev_init);
  186. module_exit(dev_exit);
  187. MODULE_LICENSE("GPL");
  188. MODULE_AUTHOR("www.embedsky.net");
  189. MODULE_DESCRIPTION("ADC Drivers for EmbedSky SKY2440/TQ2440 Board and support touch");

ADC应用测试参考源码:

[cpp] view
plain
?
  1. /*************************************
  2. NAME:EmbedSky_adc.c
  3. COPYRIGHT:www.embedsky.net
  4. *************************************/
  5. #include <stdio.h>
  6. #include <unistd.h>
  7. #include <stdlib.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <sys/ioctl.h>
  11. #include <fcntl.h>
  12. #include <linux/fs.h>
  13. #include <errno.h>
  14. #include <string.h>
  15. int main(void)
  16. {
  17. int fd ;
  18. char temp = 1;
  19. fd = open("/dev/adc", 0);
  20. if (fd < 0)
  21. {
  22. perror("open ADC device !");
  23. exit(1);
  24. }
  25. for( ; ; )
  26. {
  27. char buffer[30];
  28. int len ;
  29. len = read(fd, buffer, sizeof buffer -1);
  30. if (len > 0)
  31. {
  32. buffer[len] = '\0';
  33. int value;
  34. sscanf(buffer, "%d", &value);
  35. printf("ADC Value: %d\n", value);
  36. }
  37. else
  38. {
  39. perror("read ADC device !");
  40. exit(1);
  41. }
  42. sleep(1);
  43. }
  44. adcstop:
  45. close(fd);
  46. }

测试结果:

[cpp] view
plain
?
  1. [WJ2440]# ./adc_test
  2. ADC Value: 693
  3. ADC Value: 695
  4. ADC Value: 694
  5. ADC Value: 695
  6. ADC Value: 702
  7. ADC Value: 740
  8. ADC Value: 768
  9. ADC Value: 775
  10. ADC Value: 820
  11. ADC Value: 844
  12. ADC Value: 887
  13. ADC Value: 937
  14. ADC Value: 978
  15. ADC Value: 1000
  16. ADC Value: 1023
  17. ADC Value: 1023
  18. ADC Value: 1023

linux 混杂设备驱动之adc驱动的更多相关文章

  1. Linux混杂设备驱动学习

    Linux混杂设备是字符设备的一类,主要是混杂设备拥有相同的主设备号(10),但是次设备号是不同的.所有的混杂设备行程一个链表,对设备访问时内核更据次设备号查找到相应的混杂设备. 混杂设备用struc ...

  2. Linux混杂设备驱动

    1. Linux混杂设备驱动模型 ① 在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice).所有混杂设备形成一个链表, ...

  3. 芯灵思Sinlinx A64 linux 通过设备树写LED驱动(附参考代码,未测试)

    开发平台 芯灵思Sinlinx A64 内存: 1GB 存储: 4GB 详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 全志A64设备树结构体 #inc ...

  4. Linux 混杂设备、外部中断和输入子系统

    混杂设备也是一种字符设备,主设备号固定为10.相对于普通字符设备驱动,它不需要自己去生成设备文件. 1.声明使用的头文件 #include <linux/miscdevice.h> 2.定 ...

  5. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

  6. Linux块设备IO子系统(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,块设备(blockdevice)是一种具有一定结构的随机存取设备,对这种设备的读写是按块(所以叫块设备)进行的,他使用缓 ...

  7. Linux驱动之混杂设备(misc)

    字符设备之混杂设备: 定义混杂设备: struct misdevice{ int minor; //为什么这里只有次设备号,因为混杂设备是一种在 /////////////////////////Li ...

  8. 驱动之路四------adc驱动(input设备)

    开发板:smdk6410 开发环境:Linux 突然想起一点,写这些驱动,内核需要配成支持抢占才行. 前面的博客已经将其它的基本知识都解释了,这里也就不过多的阐述了,咱就直接写代码吧 这次写的是adc ...

  9. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

随机推荐

  1. 新东方雅思词汇---7.3、dioxide

    新东方雅思词汇---7.3.dioxide 一.总结 一句话总结: di(双)+oxide 英 [daɪ'ɒksaɪd]  美 [daɪ'ɑksaɪd]  n. 二氧化物 词组短语 carbon di ...

  2. BusyIndicator using MVVM 忙碌状态指示器的的实现

    ViewModel 视图模型 public abstract class ViewModelBase : INotifyPropertyChanged { private bool isbusy; p ...

  3. java 多媒体发送邮件

    import java.util.Properties; import javax.mail.Address; import javax.mail.BodyPart; import javax.mai ...

  4. sizeof结构体

    规则1:结构体的对折长度为其基本数据成员的长度的最大值. 规则2:指定边界情况下,结构体的对折长度为自身对折长度和指定对折长度中较小者. 规则3:当行内结构体的基本数据成员的起始地址必须为其长度的整数 ...

  5. MySQL数据库维护、备份、和复制

    预防性维护的基本原则:1)启动MySQL服务器提供的自动恢复功能2)有计划的开展预防心维护工作,定期对表进行检查,日常的表检查有助于及时发现各种小问题,并在问题变得更糟之前将其纠正.3)建立数据库备份 ...

  6. webpack打包图片资源找不到问题

    当我们进行前端打包时,需改成如下配置: 往常这样打包是没有问题的,可是今天进行项目打包的时候缺报图片找不到的错误,如图所示: 头部组件的图片资源找不到错误,后台发现因为头部组件的背景图片size过大, ...

  7. 【spark】连接Hbase

    0.我们有这样一个表,表名为Student 1.在Hbase中创建一个表 表明为student,列族为info 2.插入数据 我们这里采用put来插入数据 格式如下   put  ‘表命’,‘行键’, ...

  8. Django常用插件

    1 富文本编辑器--tinymce 2 分页器--pure pagination 视图中 all_orgs_list = CourseOrg.objects.all() try: page = req ...

  9. lzugis—搭建属于自己的小型的版本控制SVN

    对于不了解SVN的同志们可以参考下"mh942408056"的这篇博文,SVN简介,链接地址为:http://blog.csdn.net/mh942408056/article/d ...

  10. cursor光标类型

    今天早上在网上看到一篇关于光标类型的总结代码,很好,特定拿来: 最终结果: 代码: <!DOCTYPE html> <html lang="zh-cn"> ...