ADC的驱动也采用platform设备驱动的方式进行编写,platform_device为platform_driver提供ADC控制器/ADC数据存储器/ADC延时器的地址,在platform_driver的probe函数在进行ioremap的映射,进而操作相应的寄存器。
对于ADC寄存器的操作,可分为三步:
1.使用clk_get获取adc时钟,接着使用clk_enable使能adc时钟
2.设置ADCCON的工作方式,预分频比之类的
3.当开始读取数据值时,开启ADC转换,判断是否开始转换,判断是否转换完成,最终读取数据,返回给用户空间
硬件电路图:
ADCIN1的模拟量输入端接一个滑动变阻器,通过滑动变阻器,提供不同的模拟量输入端。
adc平台设备代码
adc_under_device.c
#include <linux/init.h> #include <linux/module.h> #include <linux/printk.h> #include <linux/platform_device.h> static struct resource adc_under_resource[]= //adc的资源 { [0]= //adc_con0 { .start=0xE1700000, .end=0xE1700000+0x3, .flags=IORESOURCE_MEM, }, [1]=//adc_delay0 { .start=0xE1700000+0x8, .end=0xE1700000+0xB, .flags=IORESOURCE_MEM, }, [2]=//adc_data0 { .start=0xE1700000+0xC, .end=0xE1700000+0xF, .flags=IORESOURCE_MEM, }, [3]=//adc_con1 { .start=0xE1701000, .end=0xE1701000+0x3, .flags=IORESOURCE_MEM, }, [4]=//adc_delay1 { .start=0xE1701000+0x8, .end=0xE1701000+0xB, .flags=IORESOURCE_MEM, }, [5]=//adc_data1 { .start=0xE1701000+0xC, .end=0xE1701000+0xF, .flags=IORESOURCE_MEM, }, }; static void adc_under_release(struct device *dev) //释放adc设备函数 { } static struct platform_device adc_under_platform_device= //adc的平台函数 { .name="adc_under", //用于与驱动进行匹配的名称 .id=-1, .num_resources=sizeof(adc_under_resource)/sizeof(adc_under_resource[0]), .resource=adc_under_resource, .dev= { .release=adc_under_release, }, }; static int __init adc_under_init(void) //adc模块初始化函数 { int ret=platform_device_register(&adc_under_platform_device); //注册adc平台设备,为adc驱动提供资源 if(ret==0) printk(KERN_INFO "adc_under_platform_device register success.\n"); else printk(KERN_INFO "adc_under_platform_device register failed.\n"); return ret; } static void __exit adc_under_exit(void) //adc的模块卸载函数 { platform_device_unregister(&adc_under_platform_device); //卸载adc平台设备 printk(KERN_INFO "adc_under_platform_device unregister success.\n"); } module_init(adc_under_init); module_exit(adc_under_exit); MODULE_LICENSE("GPL");
编译文件 Makefile
obj-m :=adc_under_device.o KERNELDIR :=~/java/Kernel_3.0.8_TQ210_for_Android_v1.0/ PWD :=$(shell pwd) build:kernel_module kernel_module: make -C $(KERNELDIR) M=$(PWD) modules clean: make -C $(KERNELDIR) M=$(PWD) clean
adc平台驱动文件
adc_under_driver.c
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/printk.h> #include <linux/clk.h> #include <asm/io.h> #include <asm/uaccess.h> //包含copy_to_user... #include <linux/delay.h> //包含mdelay... #include <linux/platform_device.h> #define ADC_DEVICE_NAME "adc_unders" //adc的设备名称 static struct class *cls; //类指针 static void __iomem *adccon1,*adcdata1,*adcdly1; //adc控制器/数据存储器/延时器 //__iomem表示指向一个I/O的内存空间,目的为了开发通用的驱动,编译器不会对其进行检查 static struct clk *adc_clk;//adc时钟指针 #define ADCCON (*(volatile unsigned long *)(adccon1)) //adc控制器宏 #define ADCDATA (*(volatile unsigned long *)(adcdata1))//将__iomem指针转为unsigned long *型指针 #define ADCDLY (*(volatile unsigned long *)(adcdly1)) //volatile表示变量是易变的,总是需要从内存地址中读取,而不是从寄存器中读取,对该变量不进行优化 int major; //存储设备主设备号 static int adc_under_open(struct inode *inode,struct file *file) //adc打开函数 { printk(KERN_INFO "adc_under_open.\n"); return 0; } static int adc_under_close(struct platform_device *pdev) //adc关闭函数,将申请的资源释放 { unregister_chrdev(major, ADC_DEVICE_NAME); //从chrdevs指针数组中删掉adc字符设备 device_destroy(cls, MKDEV(major,0)); //从subsystem中将其删除,并将其引用减一 class_destroy(cls); //删除cls类 iounmap(adccon1); //取消物理地址到虚拟地址的映射 iounmap(adcdata1); iounmap(adcdly1); printk(KERN_INFO "adc_under_close success.\n"); return 0; } static int adc_under_read(struct file *file,char __user *buf,size_t count,loff_t *fops) //adc读取转换后的数字量 { int adc_val,n,con; ADCCON |=0x01; //开启ADC的转换 while(ADCCON&0x1); //判断是否开始转换 while(!ADCCON&0x8000); //判断是否转换完成 con=ADCCON&0xffff; adc_val=(ADCDATA&0x03ff); //获取到转换后的结果值 mdelay(100); n=copy_to_user(buf, &adc_val, sizeof(adc_val)); //将adc_val的值从内核空间拷到用户空间 printk(KERN_INFO "adc_under_read,adc_val=%d,con=0x%x\n",adc_val,con); return n; } struct file_operations adc_under_fops= //adc文件操作 { .owner=THIS_MODULE, .open=adc_under_open, .read=adc_under_read, }; static void adc_reg_init(void) //adc的初始化函数 { int con; adc_clk=clk_get(NULL, "adc"); //获取到adc的时钟,在S5PV210中,进行ADC转换的时候,使用的是PCLK(Max 66MHz) is used clk_enable(adc_clk); //使能clk时钟 ADCDLY=100; //adc采样转换的延迟时间,影响stylus down,x-conversion,y-conversion时间 ADCCON=(0<<0) | //没有操作 (0<<1) | //disable start by read operation (0<<2) | //正常操作模式 (65<<6) | //分频值为65 ,因为PCLK=66 所以prscvl=65 即 a/d转换频率为1MHz ,Conversion time=5us (1<<14) ;// 使能预分频值 con=ADCCON&0xffff; //调试信息 printk(KERN_INFO "adc_reg_init.con=0x%x\n",con); } static int adc_under_probe(struct platform_device *pdev) //adc的探针函数,与platform_device匹配后调用,用于初始化platform_driver { struct resource *rescon1,*resdata1,*resdly1; //资源指针 rescon1=platform_get_resource(pdev,IORESOURCE_MEM, 3); //获取到ADCCON1的资源,详情见adc_under_device.c if(rescon1==NULL) { printk(KERN_INFO "can not find rescon1.\n"); return -1;} //判断是否获得资源 adccon1=ioremap(rescon1->start, rescon1->end-rescon1->start+1); //物理地址映射为虚拟地址 if(adccon1==NULL) { printk(KERN_INFO "no mem to adccon1.\n"); return -1;} //判断映射是否成功 resdata1=platform_get_resource(pdev,IORESOURCE_MEM, 5);//获取到ADCDATA1的资源,详情见adc_under_device.c if(resdata1==NULL) { printk(KERN_INFO "cann not find resdata1->\n"); return -1;} //判断是否获得资源 adcdata1=ioremap(resdata1->start, resdata1->end-resdata1->start+1);//物理地址转为虚拟地址 if(adcdata1==NULL) { printk(KERN_INFO "no mem to adcdata1.\n"); return -1;}//判断映射是否成功 resdly1=platform_get_resource(pdev,IORESOURCE_MEM, 4); //获取到ADCDLY1的资源,详情见adc_under_device.c if(resdly1==NULL) { printk(KERN_INFO "can not find resdly1.\n"); return -1; } //判断是否获取资源 adcdly1=ioremap(resdly1->start, resdly1->end-resdly1->start+1); //物理地址映射为虚拟地址 if(adcdly1==NULL) { printk(KERN_INFO "no mem to adcdly1.\n"); return -1;}//判断映射是否成功 adc_reg_init(); //adc寄存器的初始化 major=register_chrdev(0, ADC_DEVICE_NAME,&adc_under_fops); //创建并注册字符设备 cls=class_create(THIS_MODULE, ADC_DEVICE_NAME); //创建类 device_create(cls,NULL, MKDEV(major,0), NULL, ADC_DEVICE_NAME); //建立/dev/adc_unders设备文件,利用udev策略 printk(KERN_INFO "adc_under_probe success.\n"); return 0; } static struct platform_driver adc_under_platform_driver= //adc的平台驱动 { .probe=adc_under_probe, .remove=adc_under_close, .driver= { .name="adc_under", //用于与platform_device匹配的名称 }, }; static int __init adc_driver_init(void) //adc模块初始化函数 { int ret=platform_driver_register(&adc_under_platform_driver); //注册平台驱动 if(ret==0) printk(KERN_INFO "adc_under_platform_driver success.\n"); else printk(KERN_INFO "adc_under_platform_driver failed.\n"); return ret; } static void __exit adc_driver_exit(void) //模块卸载函数 { platform_driver_unregister(&adc_under_platform_driver); //卸载平台驱动 } module_init(adc_driver_init); module_exit(adc_driver_exit); MODULE_LICENSE("GPL");
编译文件 Makefile
obj-m :=adc_under_driver.o KERNELDIR :=~/java/Kernel_3.0.8_TQ210_for_Android_v1.0/ PWD :=$(shell pwd) build:kernel_module kernel_module: make -C $(KERNELDIR) M=$(PWD) modules clean: make -C $(KERNELDIR) M=$(PWD) clean
最后的测试文件
adc_under_test.c
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #define DEVICE_NAME "/dev/adc_unders" int main(void) { int fd,ret=0,data,count=0; fd=open(DEVICE_NAME, O_RDWR); if(fd<0) {printf("can not open fd=%s\n",DEVICE_NAME); return -1; } while(count<10){ ret=read(fd,&data,sizeof(data)); if(ret<0) printf("read data is error \n"); printf("adc is %f V\n",(float)data*3.3/1024); sleep(2); count++; } close(fd); return ret; }
测试文件的Android.mk
LOCAL_PATH :=$(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS :=eng LOCAL_SRC_FILES :=adc_under_test.c LOCAL_MODULE :=adc_test LOCAL_MODULE_PATH :=$(LOCAL_PATH) include $(BUILD_EXECUTABLE)
对于这个adc的驱动,我在测试的时候出现了一个很奇怪的现象,我在adc驱动测试代码中总共是读取了10次adc的值,偶合会有一两次的值是错的,就是3,3V,即adc没开始转换的值,嗯,对于这个问题暂时还不清楚,是不是我的adc硬件上有问题,待后续研究。