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. Netty原理

    1. Netty简介Netty是一个高性能.异步事件驱动的NIO框架,基于JAVA NIO提供的API实现.它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都 ...

  2. 六 web爬虫讲解2—urllib库爬虫—基础使用—超时设置—自动模拟http请求

    利用python系统自带的urllib库写简单爬虫 urlopen()获取一个URL的html源码read()读出html源码内容decode("utf-8")将字节转化成字符串 ...

  3. zabbix报警优化

    常见rpc服务介绍 ---远程过程调用协议 常用的框架:阿里巴巴 Dubbo.微博 Motan.阿帕奇 thrift.谷歌 grpc Dubbo 是阿里巴巴公司开源的一个Java高性能优秀的服务框架, ...

  4. ifconfig 修改IP

    ifconfig 查看IP sudo ifconfig eth0 192.168.1.118 sudo ifconfig eth0 down sudo ifconfig eth0 up ping (w ...

  5. LeetCode OJ:Lowest Common Ancestor of a Binary Tree(最近公共祖先)

    Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According ...

  6. Mysql中文汉字转拼音的实现(每个汉字转换全拼)

    -- 创建汉字拼音对照临时表 CREATE TABLE IF NOT EXISTS `t_base_pinyin` ( `pin_yin_` varchar(255) CHARACTER SET gb ...

  7. ROS中使用ABB Yumi IRB14000的一些资料汇总

    目前,ABB RobotStudio 已经更新到6.05.01了,可至官网下载. 使用ABB RobotStudio和ROS进行联合调试,请参考下文: http://blog.csdn.net/Zha ...

  8. centOS 7 tomcat nginx 验证码乱码

    将服务部署在centOS 7上,配置完tomcat和nginx之后,启动服务后,发现验证码这样了~~~ 一开始是以为nginx的原因,但是在Ubuntu系统相同操作发现没有问题,后发现,系统的字体库中 ...

  9. PHPCMS V9调用父栏目 顶级父栏目的代码

    一.调用父栏目 首先是列表页和二级栏目页list.html {$CATEGORYS[$top_parentid][catname]} //顶级父栏目名称 {$CATEGORYS[$CAT[parent ...

  10. WDF - CSS 书写规范

    CSS已经写了很久了,但是感觉代码还是有点乱,不够漂亮.今天抽点时间整理一下手头上正在做的网站样式,顺带做一个自己比较适应的书写规范,以供以后参考.先暂时这样吧,其他以后再完善. 逻辑:大小 → 位置 ...