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原始數據讀取示例
數據采集的過程中,旋轉電位器的旋鈕,改變電位器的電阻分壓,就會改變轉換后的結果。