努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处
http://blog.youkuaiyun.com/woshixingaaa/archive/2011/05/18/6429002.aspx
S3C2440内部ADC结构图:
对于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 #define ADC_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,使用通道AIN0 writel(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; }
实验效果: