当前位置: > 嵌入式学院 > 嵌入式学习 > 讲师博文 > FS4412开发板使用Linux IIO驱动框架实现ADC驱动
FS4412开发板使用Linux IIO驱动框架实现ADC驱动
时间:2017-11-17作者:华清远见

1. 概述

FS4412开发板有一个4通道(0/1/2)、10/12比特精度的 ADC ,其中:

1) ADCIN0: 在核心板中引出 

2) ADCIN1: 在核心板中引出

3) ADCIN2: 在核心板中引出 

4) ADCIN3: 连接开发板的VR1电位器

本文主要介绍基于IIO驱动框架的ADC的简单实现方法。

2. 配置DTS节点

FS4412 ADC 的 DTS 节点在 kernel/arch/arm/boot/dts/exynos4412-fs4412.dts 文件中添加如下定义:

adc: adc@12C60000 {                                 

        compatible = "samsung,exynos-adc-fs4412";   

        reg = <0x126C0000 0x100>, <0x10020718 0x4>; 

        clocks = <&clock 303>;                      

        clock-names = "adc";                        

        #io-channel-cells = <1>;                    

        io-channel-ranges;                          

        status = "okay";                            

};                                                  

 

3. 编写驱动

ADC 的驱动源码为 fs4412_adc.c

3.1. 定义 ADC 通道

可用的通道列表在 fs4412_adc.c 中定义:

#define ADC_CHANNEL(_index, _id) {                      \

    .type = IIO_VOLTAGE,                                \

    .indexed = 1,                                       \

    .channel = _index,                          \

    .address = _index,                          \

    .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),       \

    .datasheet_name = _id,                              \

}

 

/* 通道信息 */

static const struct iio_chan_spec fs4412_adc_iio_channels[] = {

    ADC_CHANNEL(0, "adc0"),

    ADC_CHANNEL(1, "adc1"),

    ADC_CHANNEL(2, "adc2"),

    ADC_CHANNEL(3, "adc3"),

};

 

3.2. ADC采集原始数据原理

根据Exynos4412处理器的官方使用手册提供的资料,可以总结ADC的基本使用方法如下:

3.2.1. 初始化过程

5) 初始化ADC_CFG(0x0x10010118)

[16] = 0 设置ADC为普通模式

6) 初始化ADCCON(0x126C0000)

[16] = 1 使用12位ADC

[14] = 1 允许分频

[13:6]=0xFF 分频系数

使ADC的工作频率控制在5MHz以内

7) 选择输入引脚ADCMUX(0x126C001C)

[3:0] = 0x03 选择AIN3作为输入引脚

3.2.2. 执行采集转换过程

1) 开始转换ADCCON(0x126C0000)

[0] = 1 ADC开始转换

2) 判断是否转换完成ADCCON(0x126C0000)

读取[15]位状态=1表示转换完成

3) 读取转换结果ADCDAT(0x126C000C)

读ADC的转换结果

3.3. 计算采集到的电压

使用标准电压将 AD 转换的值转换为用户所需要的电压值。其计算公式如下:

Vref / (2^n-1) = Vresult / raw

注:

Vref 为标准电压 

n 为 AD 转换的位数 

Vresult 为用户所需要的采集电压 

raw 为 AD 采集的原始数据

 

例如,标准电压为 1.8V,AD 采集位数为 10 位,AD 采集到的原始数据为 568,则:

Vresult = (1800mv * 568) / 1023;

3.4. 驱动测试例程

以下为完整的读取 ADC 的驱动例程:

#include <linux/module.h>

#include <linux/platform_device.h>

#include <linux/io.h>

#include <linux/iio/iio.h>

 

MODULE_AUTHOR("LvXin <lvx_sy@farsight.com.cn>");

MODULE_DESCRIPTION("FS4412 ADC driver");

MODULE_LICENSE("GPL v2");

 

#define CON(x) ((x) + 0x00)

#define DLY(x) ((x) + 0x08)

#define DATX(x) ((x) + 0x0C)

#define INTCLR(x) ((x) + 0x18)

#define MUX(x) ((x) + 0x1c)

 

#define CON_RES (1u << 16)

#define CON_PRSCEN (1u << 14)

#define CON_PRSCLV(x) (((x) & 0xFF) << 6)

#define CON_STANDBY (1u << 2)

 

#define MAX_CHANNELS 4

 

#define ADC_CON_EN_START (1u << 0)

#define ADC_DATX_MASK 0xFFF

 

/* adc类 */

struct fs4412_adc {

    void __iomem *regs;

    u32 value;

};

 

static const struct of_device_id fs4412_adc_match[] = {

    { .compatible = "samsung,exynos-adc-fs4412"},

    {},

};

MODULE_DEVICE_TABLE(of, fs4412_adc_match);

 

/* 读取数据 */

static int exynos_read_raw(struct iio_dev *indio_dev,

        struct iio_chan_spec const *chan,

        int *val,

        int *val2,

        long mask)

{

    struct fs4412_adc *info = iio_priv(indio_dev);

    u32 con1;

 

    if (mask != IIO_CHAN_INFO_RAW)

        return -EINVAL;

 

    mutex_lock(&indio_dev->mlock);

 

    /* 选择通道 */

    writel(chan->address, MUX(info->regs));

 

    /* 启动转换 */

    con1 = readl(CON(info->regs));

    writel(con1 | ADC_CON_EN_START,

            CON(info->regs));

 

    /* 等待转换完成 */

    while((readl(CON(info->regs)) & (1<<15))==0){};

 

    /* 读取转换数据*/

    info->value = readl(DATX(info->regs)) & ADC_DATX_MASK;

 

    *val = info->value;

 

    mutex_unlock(&indio_dev->mlock);

 

    return IIO_VAL_INT;

}

 

 

static int fs4412_adc_reg_access(struct iio_dev *indio_dev,

        unsigned reg, unsigned writeval,

        unsigned *readval)

{

    struct fs4412_adc *info = iio_priv(indio_dev);

 

    if (readval == NULL)

        return -EINVAL;

 

    *readval = readl(info->regs + reg);

 

    return 0;

}

 

/* IIO信息对象 */

static const struct iio_info fs4412_adc_iio_info = {

    .read_raw = &exynos_read_raw,

    .debugfs_reg_access = &fs4412_adc_reg_access,

    .driver_module = THIS_MODULE,

};

 

#define ADC_CHANNEL(_index, _id) { \

    .type = IIO_VOLTAGE, \

    .indexed = 1, \

    .channel = _index, \

    .address = _index, \

    .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \

    .datasheet_name = _id, \

}

 

/* 通道信息 */

static const struct iio_chan_spec fs4412_adc_iio_channels[] = {

    ADC_CHANNEL(0, "adc0"),

    ADC_CHANNEL(1, "adc1"),

    ADC_CHANNEL(2, "adc2"),

    ADC_CHANNEL(3, "adc3"),

};

 

/* ADC硬件初始化 */

static void fs4412_adc_hw_init(struct fs4412_adc *info)

{

    u32 con;

 

    /* 设置预分频值 */

    con =  CON_PRSCLV(49) | CON_PRSCEN;

 

    /* 12位AD转换 */

    con |= CON_RES;

    writel(con, CON(info->regs));

}

 

/* 设备匹配函数 */

static int fs4412_adc_probe(struct platform_device *pdev)

{

    struct fs4412_adc *info = NULL;

    struct device_node *np = pdev->dev.of_node;

    struct iio_dev *indio_dev = NULL;

    struct resource *mem;

    int ret = -ENODEV;

 

    if (!np)

        return ret;

 

    /* 动态申请iio设备 */

    indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct fs4412_adc));

    if (!indio_dev) {

        dev_err(&pdev->dev, "failed allocating iio device\n");

        return -ENOMEM;

    }

 

    info = iio_priv(indio_dev);

 

    /* 获得ADC寄存器地址 */

    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    info->regs = devm_ioremap_resource(&pdev->dev, mem);

    if (IS_ERR(info->regs))

        return PTR_ERR(info->regs);

 

    /* 设置私有数据 */

    platform_set_drvdata(pdev, indio_dev);

    indio_dev->name = dev_name(&pdev->dev);

    indio_dev->dev.parent = &pdev->dev;

    indio_dev->dev.of_node = pdev->dev.of_node;

    indio_dev->info = &fs4412_adc_iio_info;

    indio_dev->modes = INDIO_DIRECT_MODE;

    indio_dev->channels = fs4412_adc_iio_channels;  /* 通道数据 */

    indio_dev->num_channels = MAX_CHANNELS;

 

    /* 注册iio设备 */

    ret = iio_device_register(indio_dev);

    if (ret)

        return ret;

 

    /* ADC硬件初始化 */

    fs4412_adc_hw_init(info);

 

    return 0;

}

 

/* 设备移除 */

static int fs4412_adc_remove(struct platform_device *pdev)

{

    struct iio_dev *indio_dev = platform_get_drvdata(pdev);

 

    /* 注销iio设备 */

    iio_device_unregister(indio_dev);

    return 0;

}

 

/* 平台设备对象 */

static struct platform_driver fs4412_adc_driver = {

    .probe = fs4412_adc_probe,

    .remove = fs4412_adc_remove,

    .driver = {

        .name = "exynos-adc",

        .owner = THIS_MODULE,

        .of_match_table = fs4412_adc_match,

    },

};

 

/* 平台设备模块 */

module_platform_driver(fs4412_adc_driver);

 

将以上源码保存为 drivers/iio/adc/fs4412_adc.c ,并在 drivers/iio/adc/Makefile 后加入:

obj-$(CONFIG_FS4412_ADC) += fs4412_adc.o 

编译并烧写内核,启动后即可在终端下运行以下命令来读取 ADC3 的值:

# while true; 

> do cat /sys/devices/126c0000.adc/iio\:device0/in_voltage3_raw;

> sleep 1; 

> done

 

运行结果如下:

ADC原始数据读取示例

数据采集的过程中,旋转电位器的旋钮,改变电位器的电阻分压,就会改变转换后的结果。


发表评论

全国咨询电话:400-611-6270,双休日及节假日请致电值班手机:15010390966

在线咨询: 曹老师QQ(3337544669), 徐老师QQ(1462495461), 刘老师 QQ(3108687497)

企业培训洽谈专线:010-82600901,院校合作洽谈专线:010-82600350,在线咨询:QQ(248856300)

Copyright 2004-2018 华清远见教育集团 版权所有 ,京ICP备16055225号,京公海网安备11010802025203号