helloyesyes 发表于 2013-2-4 14:10:41

Linux驱动修炼之道-ADC驱动

努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处
http://blog.csdn.net/woshixingaaa/archive/2011/05/18/6429002.aspx
S3C2440内部ADC结构图:
http://hi.csdn.net/attachment/201105/21/0_1305948792OiN9.gif
对于s3c2440来说,实现A/D转换比较简单,主要应用的是ADC控制寄存器ADCCON和ADC转换数据寄存器ADCDAT0。寄存器ADCDAT0的低10位用于存储A/D转换后的数据。寄存器ADCCON的第15位用于标识A/D转换是否结束。第14位用于使能是否进行预分频,而第6位到第13位则存储的是预分频数值,因为A/D转换的速度不能太快,所以要通过预分频处理才可以得到正确的A/D转换速度,如我们想要得到A/D转换频率为1MHz,则预分频的值应为49。第3位到第5位表示的是A/D转换的通道选择。第2位可以实现A/D转换的待机模式。第1位用于是否通过读取操作来使能A/D转换的开始。第0位则是在第1位被清零的情况下用于开启A/D转换。
驱动代码:
#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <mach/hardware.h>#include <asm/io.h>#include <linux/interrupt.h>#include <asm/irq.h>#include <linux/irq.h> #include <linux/miscdevice.h>#include <linux/types.h> #include <linux/clk.h>#include <linux/errno.h>#include <asm/uaccess.h>#include <mach/regs-clock.h>#include <plat/regs-adc.h> #include <linux/kernel.h>#define ADC_MINOR 100#defineADC_NAME "lwp-adc"struct clk *adc_clk;int adc_base;int adc_finish = 0;static int adc_data;DECLARE_WAIT_QUEUE_HEAD(adc_wait);static irqreturn_t adc_interrupt(int irq, void *dev_id){if(!adc_finish){adc_data = readl(adc_base + S3C2410_ADCDAT0) & 0x3ff;                  //ad转换结束会产生中断,此时读取S3C2410_ADCDAT0的0~9位,来获取数据adc_finish = 1;wake_up_interruptible(&adc_wait);                                        //唤醒等待其上的进程}return IRQ_HANDLED;}int myadc_open(struct inode *inode, struct file *file){int ret;ret = request_irq(IRQ_ADC, adc_interrupt, IRQF_SHARED, ADC_NAME, 1);         //这里注册中断,ad转换结束通知cpu有两种方式,一种靠cpu轮询标志位,一种靠中断if(ret){printk("IRQ %d can't get\n", IRQ_ADC);return -1;}return 0;}int myadc_close(struct inode *inode, struct file *file){return 0;return -ENOENT;}void start_adc(){                                       //开始ad转换int tmp;               tmp = 0xff<<6 | 1<<14;                           //设置预分频使能,值为255,使用通道AIN0writel(tmp ,adc_base + S3C2410_ADCCON);          tmp = readl(adc_base + S3C2410_ADCCON);tmp |= 1<<0;                                    //使能AD转换writel(tmp, adc_base + S3C2410_ADCCON);}ssize_t myadc_read(struct file *filp, char __user *buff, size_t count, loff_t *offp){start_adc();                                                             //要读数据开始adc转换wait_event_interruptible(adc_wait, adc_finish);                        //等待转换结束adc_finish = 0;copy_to_user(buff, (char *)&adc_data, sizeof(adc_data));               //将获得的数据拷贝到用户空间,数据会在中断程序中获得return sizeof(adc_data);}static struct file_operations adc_ops = {.owner = THIS_MODULE,.open = myadc_open,.release = myadc_close,.read = myadc_read,};static struct miscdevice adc_misc = {.name = ADC_NAME,.minor = ADC_MINOR,.fops = &adc_ops,};static int __init my_adc_init(void){ unsigned int ret;adc_clk = clk_get(NULL,"adc");                         //由于ad转换需要时钟,所以这里获取时钟if(!adc_clk){printk(KERN_ERR "fail to find adc clk resource!\n");ret = -1;goto err_clk;}clk_enable(adc_clk);                                 //使能adc的时钟adc_base = ioremap(S3C2410_PA_ADC, 20);                //获得ADC控制寄存器的虚拟地址if(adc_base == 0){printk(KERN_ERR "fail to ioremap!\n");ret = -1;goto err_nomap;}ret = misc_register(&adc_misc);                     //注册这个adc为混杂设备if(IS_ERR(ret)){goto err_register;}return 0;err_register:iounmap(adc_base);err_nomap:clk_disable(adc_clk);clk_put(adc_clk);err_clk:return ret;}static void __exit my_adc_exit(void){misc_deregister(&adc_misc);free_irq(IRQ_ADC, 1);iounmap(adc_base);clk_disable(adc_clk);clk_put(adc_clk);}module_init(my_adc_init);module_exit(my_adc_exit);MODULE_AUTHOR("liwanpeng");MODULE_LICENSE("GPL");
测试代码:
#include <stdio.h>#include <stdlib.h>#include <errno.h>int main(int argc, char **argv){    int fd;    fd = open("/dev/lwp-adc", 0);    if(fd < 0)    {      printf("Open ADC Device Faild!\n");      exit(1);    }    while(1)    {      int ret;      int data;         ret = read(fd, &data, sizeof(data));      if(ret != sizeof(data))      {            if(errno != EAGAIN)            {                printf("Read ADC Device Faild!\n");            }            continue;      }      else      {            printf("Read ADC value is: %d\n", data);      }    }    close(fd);    return 0;}
实验效果:
http://hi.csdn.net/attachment/201105/21/0_1305948855Qkpt.gif
页: [1]
查看完整版本: Linux驱动修炼之道-ADC驱动