FS4412開發板有一個4通道(0/1/2)、10/12比特精度的 ADC ,其中:node
本文主要介紹基於IIO驅動框架的ADC的簡單實現方法。linux
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"; };
ADC 的驅動源碼爲 fs4412_adc.c函數
可用的通道列表在 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"), };
根據Exynos4412處理器的官方使用手冊提供的資料,能夠總結ADC的基本使用方法以下:debug
初始化過程code
1)初始化ADC_CFG(0x0x10010118) [16] = 0 設置ADC爲普通模式 2)初始化ADCCON(0x126C0000) [16] = 1 使用12位ADC [14] = 1 容許分頻 [13:6]=0xFF 分頻係數 使ADC的工做頻率控制在5MHz之內 3)選擇輸入引腳ADCMUX(0x126C001C) [3:0] = 0x03 選擇AIN3做爲輸入引腳orm
執行採集轉換過程對象
1)開始轉換ADCCON(0x126C0000) [0] = 1 ADC開始轉換 2)判斷是否轉換完成ADCCON(0x126C0000) 讀取[15]位狀態=1表示轉換完成 3)讀取轉換結果ADCDAT(0x126C000C) 讀ADC的轉換結果開發
計算採集到的電壓
使用標準電壓將 AD 轉換的值轉換爲用戶所須要的電壓值。其計算公式以下: Vref / (2^n-1) = Vresult / raw 注:
Vref 爲標準電壓 n 爲 AD 轉換的位數 Vresult 爲用戶所須要的採集電壓 raw 爲 AD 採集的原始數據
例如,標準電壓爲 1.8V,AD 採集位數爲 10 位,AD 採集到的原始數據爲 568,則: Vresult = (1800mv * 568) / 1023; ### 驅動測試例程 如下爲完整的讀取 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 的值:
do cat /sys/devices/126c0000.adc/iio:device0/in_voltage3_raw; sleep 1; done
數據採集的過程當中,旋轉電位器的旋鈕,改變電位器的電阻分壓,就會改變轉換後的結果。