⑤tiny4412 Linux驱动开发之ADC驱动程序

本次开发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

还有测试结果:


猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/80207812