本次开发ADC没有使用iio子系统,也没有用hwmon子系统的方式开发,而是使用字符设备开发的,原因是友善之臂官方提供了一个线程的ADC驱动,是基于杂项设备写的,为了降低难度,这里也是这样写,等到后边比较深入之后,再用相关子系统的方式做规范开发.
在看本文之前可以先看<<S3C2410驱动分析之ADC通用驱动>>先了解一下三星平台ADC使用的大致情况.本例程ADC相关操作使用的是三星远程提供的\linux-3.5\arch\arm\plat-samsung\adc.c里的一些函数.具体看代码即可知道.
首先是驱动代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/platform_device.h> #include <asm/uaccess.h> #include <plat/adc.h> #define ADC_SET_CHANNEL _IOW('L', 0x1234, int) #define ADC_SET_ADCTSC _IOW('L', 0x1235, int) struct ADC_OBJ{ struct mutex mutex_lock; struct s3c_adc_client *client; int channel; }; struct ADC_OBJ *exynos4412_adcdev = NULL; static inline int exynos_adc_read_byte(void) { int ret = -1; ret = mutex_lock_interruptible(&exynos4412_adcdev->mutex_lock); if(0 > ret) return ret; ret = s3c_adc_read(exynos4412_adcdev->client, exynos4412_adcdev->channel); mutex_unlock(&exynos4412_adcdev->mutex_lock); return ret; } static inline void exynos_adc_set_channel(int channel) { if(0 > channel || 3 < channel) return; exynos4412_adcdev->channel = channel; } int exynos_adc_open(struct inode *inode, struct file *filp) { // 设置ADC通道,1506底板channel 0引出,其他3个没有引出 exynos_adc_set_channel(0); return 0; } ssize_t exynos_adc_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos) { int value; size_t len; char str[20] = {0}; value = exynos_adc_read_byte(); len = sprintf(str, "%d\n", value); if(count >= len){ if(copy_to_user(buf, str, len)) return len; } return -EINVAL; } long exynos_adc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch(cmd) { case ADC_SET_CHANNEL: exynos_adc_set_channel(arg); break; case ADC_SET_ADCTSC: // do nothing break; default: break; } return 0; } int exynos_adc_release(struct inode *inode, struct file *filp) { // do nothing return 0; } struct file_operations adc_dev_fops = { open : exynos_adc_open, read : exynos_adc_read, unlocked_ioctl : exynos_adc_ioctl, release : exynos_adc_release, }; struct miscdevice exynos4412_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "adc1", .fops = &adc_dev_fops, }; int exynos4412_adc_probe(struct platform_device *dev) { int ret = -1; printk("-----%s-----\n", __func__); // 1,申请设备对象空间 exynos4412_adcdev = kzalloc(sizeof(struct ADC_OBJ), GFP_KERNEL); if(NULL == exynos4412_adcdev){ printk("kzalloc failed !\n"); return -ENOMEM; } // 2,初始化互斥锁 mutex_init(&exynos4412_adcdev->mutex_lock); // 3,通过三星通用驱动注册ADC exynos4412_adcdev->client = s3c_adc_register(dev, NULL, NULL, 0); if(IS_ERR(exynos4412_adcdev->client)){ printk("s3c adc register failed !\n"); ret = PTR_ERR(exynos4412_adcdev->client); goto err1; } // 4,通过混杂设备的形式注册驱动程序,可以减少代码上的操作,4步变1步即可完成 ret = misc_register(&exynos4412_misc); if(0 > ret){ printk("misc register failed !\n"); goto err2; } return 0; err2: s3c_adc_release(exynos4412_adcdev->client); err1: kfree(exynos4412_adcdev); return ret; } int __devexit exynos4412_adc_remove(struct platform_device *dev) { misc_deregister(&exynos4412_misc); s3c_adc_release(exynos4412_adcdev->client); kfree(exynos4412_adcdev); return 0; } struct platform_device_id adc_id_table[] = { {"tiny4412_adc", 0x2}, {"s5pv210_adc1", 0x4}, }; struct platform_driver exynos4412_adc_drv = { .driver = { .name = "Samsung_adc", }, .probe = exynos4412_adc_probe, .remove = __devexit_p(exynos4412_adc_remove), .id_table = adc_id_table, }; static void __exit exynos4412_adc_exit(void) { platform_driver_unregister(&exynos4412_adc_drv); } static int __init exynos4412_adc_init(void) { return platform_driver_register(&exynos4412_adc_drv); } module_init(exynos4412_adc_init); module_exit(exynos4412_adc_exit); MODULE_LICENSE("GPL");
然后是应用层测试代码:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main(void) { int fd, val = 0; char buf[32] = {0}; fd = open("/dev/adc1", O_RDWR); if(fd < 0){ perror("open failed"); exit(1); } while(1) { if(read(fd, buf, 32)){ val = atoi(buf); val = ((int)val * 10000) >> 12; printf("ADC_APP adc val: %d\n", val); } sleep(1); } if(close(fd) < 0){ perror("close failed"); exit(1); } return 0; }
之后还有Makefile:
#指定内核源码路径 KERNEL_DIR = /home/george/1702/exynos/linux-3.5 #指定当前路径 CUR_DIR = $(shell pwd) MYAPP = adc_app MODULE = exynos4412_adc all: make -C $(KERNEL_DIR) M=$(CUR_DIR) modules arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c clean: make -C $(KERNEL_DIR) M=$(CUR_DIR) clean $(RM) $(MYAPP) install: cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702 #指定编译当前目录下哪个源文件 obj-m = $(MODULE).o
还有测试结果: