Linux学习第60天:Linux ADC驱动

Linux版本号4.1.15   芯片I.MX6ULL                                 大叔学Linux    品人间百味  思文短情长


本章的思维导图如下:

一、ADC简介

纯粹的 ADC 驱动也是 IIO 驱动框架的。


二、ADC驱动源码简析

1、设备树下的ADC节点

        2个ADC节点,对应一个ADC控制器。

        adc1 节点信息如下:

1 adc1: adc@02198000 {
2 compatible = "fsl,imx6ul-adc", "fsl,vf610-adc";
3 reg = <0x02198000 0x4000>;
4 interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
5 clocks = <&clks IMX6UL_CLK_ADC1>;
6 num-channels = <2>;
7 clock-names = "adc";
8 status = "disabled";
9 };

ADC 相关属性有:

- compatible: 兼容性属性,必须的,可以设置为“fsl,vf610-adc”。

- reg: ADC 控制器寄存器信息。

- interrupts: 中断属性, ADC1 和 ADC2 各对应一个中断信息。

- clocks: 时钟属性。

- clock-names: 时钟名字,可选“adc”。

- vref-supply: 此属性对应 vref 参考电压句柄。 

2、ADC驱动源码分析

        ADC 驱动文件就一个 vf610_adc.c, vf610_adc.c 主体框架是 platform,配合IIO 驱动框架实现 ADC 驱动。

1)vf610_adc 结构体

1 struct vf610_adc {
2 struct device *dev;
3 void __iomem *regs;
4 struct clk *clk;
5 6
u32 vref_uv;
7 u32 value;
8 struct regulator *vref;
9 struct vf610_adc_feature adc_feature;
10
11 u32 sample_freq_avail[5];
12
13 struct completion completion;
14 };

2) vf610_adc_probe 函数

1 static int vf610_adc_probe(struct platform_device *pdev)
2 {
3 struct vf610_adc *info;
4 struct iio_dev *indio_dev;
5 struct resource *mem;
6 int irq;
7 int ret;
8 u32 channels;
9
10 indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct vf610_adc));/*第 10 行,调用 devm_iio_device_alloc 函数申请 iio_dev,这里也连 vf610_adc 内存一起申请了*/
11 if (!indio_dev) {
12 dev_err(&pdev->dev, "Failed allocating iio device\n");
13 return -ENOMEM;
14 }
15
16 info = iio_priv(indio_dev);/*第 16 行,调用 iio_priv 函数从 iio_dev 里面得到 vf610_adc 首地址。*/
17 info->dev = &pdev->dev;
18
19 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
20 info->regs = devm_ioremap_resource(&pdev->dev, mem);
21 if (IS_ERR(info->regs))
22 return PTR_ERR(info->regs);
23
24 irq = platform_get_irq(pdev, 0);/*第 24 行,调用 platform_get_irq 获取中断号。*/
25 if (irq < 0) {
26 dev_err(&pdev->dev, "no irq resource?\n");
27 return irq;
28 }
29
/*
第 30 行,调用 devm_request_irq 函数申请中断,中断服务函数为 vf610_adc_isr。
*/
30 ret = devm_request_irq(info->dev, irq,
31 vf610_adc_isr, 0,
32 dev_name(&pdev->dev), info);
33 if (ret < 0) {
34 dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq);
35 return ret;
36 }
37
38 info->clk = devm_clk_get(&pdev->dev, "adc");
39 if (IS_ERR(info->clk)) {
40 dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
41 PTR_ERR(info->clk));
42 return PTR_ERR(info->clk);
43 }
44
45 info->vref = devm_regulator_get(&pdev->dev, "vref");
46 if (IS_ERR(info->vref))
47 return PTR_ERR(info->vref);
48
49 ret = regulator_enable(info->vref);
50 if (ret)
51 return ret;
52
53 info->vref_uv = regulator_get_voltage(info->vref);
54
55 platform_set_drvdata(pdev, indio_dev);
56
57 init_completion(&info->completion);
58
59 ret = of_property_read_u32(pdev->dev.of_node,
60 "num-channels", &channels);
61 if (ret)
62 channels = ARRAY_SIZE(vf610_adc_iio_channels);
63

/*
第 64~70 行,初始化 iio_dev,重点是第 67 行的 vf610_adc_iio_info,因为用户空间读取 ADC
数据最终就是由 vf610_adc_iio_info 来完成的。
*/
64 indio_dev->name = dev_name(&pdev->dev);
65 indio_dev->dev.parent = &pdev->dev;
66 indio_dev->dev.of_node = pdev->dev.of_node;
67 indio_dev->info = &vf610_adc_iio_info;
68 indio_dev->modes = INDIO_DIRECT_MODE;
69 indio_dev->channels = vf610_adc_iio_channels;
70 indio_dev->num_channels = (int)channels;
71
72 ret = clk_prepare_enable(info->clk);
73 if (ret) {
74 dev_err(&pdev->dev,
75 "Could not prepare or enable the clock.\n");
76 goto error_adc_clk_enable;
77 }
78

/*
第 79 行,调用 vf610_adc_cfg_init 函数完成 ADC 的配置初始化。
*/
79 vf610_adc_cfg_init(info);

80 vf610_adc_hw_init(info);/*第 80 行,调用 vf610_adc_hw_init 函数来初始化 ADC 硬件。*/
81
82 ret = iio_device_register(indio_dev);/*第 82 行,调用 iio_device_register 函数向内核注册 iio_dev。*/
83 if (ret) {
84 dev_err(&pdev->dev, "Couldn't register the device.\n");
85 goto error_iio_device_register;
86 }
87
88 return 0;
......
96 return ret;
97 }

3)vf610_adc_iio_info 结构体
 

1 static const struct iio_info vf610_adc_iio_info = {
2 .driver_module = THIS_MODULE,
3 .read_raw = &vf610_read_raw,
4 .write_raw = &vf610_write_raw,
5 .debugfs_reg_access = &vf610_adc_reg_access,
6 .attrs = &vf610_attribute_group,
7 };
1 static int vf610_read_raw(struct iio_dev *indio_dev,
2 struct iio_chan_spec const *chan,
3 int *val,
4 int *val2,
5 long mask)
6 {
7 struct vf610_adc *info = iio_priv(indio_dev);
8 unsigned int hc_cfg;
9 long ret;
10
11 switch (mask) {
/*
第 12~49 行, 读取 ADC 原始数据值,第 32 行 type 值为 IIO_VOLTAGE,也就是读取电压
值。这里直接读取 vf610_adc 的 value 成员变量得到 ADC 转换结果,并没有看到读取 ADC 数
据寄存器的过程。这是因为真正的 ADC 数据读取过程是在中断服务函数 vf610_adc_isr 中完成。
*/
12 case IIO_CHAN_INFO_RAW:
13 case IIO_CHAN_INFO_PROCESSED:
14 mutex_lock(&indio_dev->mlock);
15 reinit_completion(&info->completion);
16
17 hc_cfg = VF610_ADC_ADCHC(chan->channel);
18 hc_cfg |= VF610_ADC_AIEN;
19 writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);
20 ret = wait_for_completion_interruptible_timeout
21 (&info->completion, VF610_ADC_TIMEOUT);
22 if (ret == 0) {
23 mutex_unlock(&indio_dev->mlock);
24 return -ETIMEDOUT;
25 }
26 if (ret < 0) {
27 mutex_unlock(&indio_dev->mlock);
28 return ret;
29 }
30
31 switch (chan->type) {
32 case IIO_VOLTAGE:
33 *val = info->value;
34 break;
35 case IIO_TEMP:
36 /*
37 * Calculate in degree Celsius times 1000
38 * Using sensor slope of 1.84 mV/°C and
39 * V at 25°C of 696 mV
40 */
41 *val = 25000 - ((int)info->value - 864) * 1000000 /
1840;
42 break;
43 default:
44 mutex_unlock(&indio_dev->mlock);
45 return -EINVAL;
46 }
47
48 mutex_unlock(&indio_dev->mlock);
49 return IIO_VAL_INT;
50
/*第 51~54 行,返回 ADC 对应的分辨率。*/
51 case IIO_CHAN_INFO_SCALE:
52 *val = info->vref_uv / 1000;
53 *val2 = info->adc_feature.res_mode;
54 return IIO_VAL_FRACTIONAL_LOG2;
55
56 case IIO_CHAN_INFO_SAMP_FREQ:
57 *val = info->sample_freq_avail[info->
adc_feature.sample_rate];
58 *val2 = 0;
59 return IIO_VAL_INT;
60
61 default:
62 break;
63 }
64
65 return -EINVAL;
66 }

 4)vf610_adc_isr 函数

1 static irqreturn_t vf610_adc_isr(int irq, void *dev_id)
2 {
3 struct vf610_adc *info = (struct vf610_adc *)dev_id;
4 int coco;
5
6 coco = readl(info->regs + VF610_REG_ADC_HS);
7 if (coco & VF610_ADC_HS_COCO0) {
8 info->value = vf610_adc_read_data(info);/*第 8 行通过调用 vf610_adc_read_data 函
数来读取 ADC 原始值,然后将 ADC 的原始值保存在 vf610_adc 的 value 成员变量里面。*/
9 complete(&info->completion);
10 }
11
12 return IRQ_HANDLED;
13 }

三、硬件原理图分析

1)、指示灯 LED0。

2)、 RGB LCD 接口。

3)、 GPIO1_IO01 引脚
 

四、ADC驱动编写

1、修改设备树

        在 imx6ull-alientekemmc.dts 文件中添加 ADC 使用的 GPIO1_IO01 引脚配置信息:

1 pinctrl_adc1: adc1grp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
4 >;
5 };

        在 imx6ull-alientek-emmc.dts 文件中的在 regulators 节点下添加参考电源子节点:
 

1 reg_vref_adc: regulator@2 {
2 compatible = "regulator-fixed";
3 regulator-name = "VREF_3V3";
4 regulator-min-microvolt = <3300000>;
5 regulator-max-microvolt = <3300000>;
6 };

        在 imx6ull-alientek-emmc.dts 文件中向 adc1 节点追加一些内容:
 

1 &adc1 {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_adc1>;
4 num-channels = <2>;
5 vref-supply = <&reg_vref_adc>;
6 status = "okay";
7 };

2、使能ADC驱动

        配置路径如下:

-> Device Drivers

        -> Industrial I/O support

                -> Analog to digital converters

                        -> <*> Freescale vf610 ADC driver //选中

3、编写测试APP

        编译修改后的设备树,然后使用新的设备树启动系统。进入/sys/bus/iio/devices 目录下,此目录下就有 ADC 对应的 iio 设备: iio:deviceX,

in_voltage1_raw: ADC1 通道 1 原始值文件。

in_voltage_scale: ADC1 比例文件(分辨率),单位为 mV。实际电压值(mV)=in_voltage1_raw* in_voltage_scale
         编写测试 APP,新建 adcApp.c 文件:

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include <errno.h>
15
16 /* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
17 #define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\
18 ret = file_data_read(file_path[index], str);\
19 dev->member = atof(str);\
20
21 /* 字符串转数字,将整数字符串转换为整数数值 */
22 #define SENSOR_INT_DATA_GET(ret, index, str, member)\
23 ret = file_data_read(file_path[index], str);\
24 dev->member = atoi(str);\
25
26
27 /* adc iio 框架对应的文件路径 */
28 static char *file_path[] = {
29 "/sys/bus/iio/devices/iio:device0/in_voltage_scale",
30 "/sys/bus/iio/devices/iio:device0/in_voltage1_raw",
31 };
32
33 /* 文件路径索引,要和 file_path 里面的文件顺序对应 */
34 enum path_index {
35 IN_VOLTAGE_SCALE = 0,
36 IN_VOLTAGE_RAW,
37 };
38
39 /*
40 * ADC 数据设备结构体
41 */
42 struct adc_dev{
43 int raw;
44 float scale;
45 float act;
46 };
47
48 struct adc_dev imx6ulladc;
49
50 /*
51 * @description : 读取指定文件内容
52 * @param – filename : 要读取的文件路径
53 * @param - str : 读取到的文件字符串
54 * @return : 0 成功;其他 失败
55 */
56 static int file_data_read(char *filename, char *str)
57 {
58 int ret = 0;
59 FILE *data_stream;
60
61 data_stream = fopen(filename, "r"); /* 只读打开 */
62 if(data_stream == NULL) {
63 printf("can't open file %s\r\n", filename);
64 return -1;
65 }
66
67 ret = fscanf(data_stream, "%s", str);
68 if(!ret) {
69 printf("file read error!\r\n");
70 } else if(ret == EOF) {
71 /* 读到文件末尾的话将文件指针重新调整到文件头 */
72 fseek(data_stream, 0, SEEK_SET);
73 }
74 fclose(data_stream); /* 关闭文件 */
75 return 0;
76 }
77
78 /*
79 * @description : 获取 ADC 数据
80 * @param - dev : 设备结构体
81 * @return : 0 成功;其他 失败
82 */
83 static int adc_read(struct adc_dev *dev)
84 {
85 int ret = 0;
86 char str[50];
87
88 SENSOR_FLOAT_DATA_GET(ret, IN_VOLTAGE_SCALE, str, scale);
89 SENSOR_INT_DATA_GET(ret, IN_VOLTAGE_RAW, str, raw);
90
91 /* 转换得到实际电压值 mV */
92 dev->act = (dev->scale * dev->raw)/1000.f;
93 return ret;
94 }
95
96 /*
97 * @description : main 主程序
98 * @param – argc : argv 数组元素个数
99 * @param - argv : 具体参数
100 * @return : 0 成功;其他 失败
101 */
102 int main(int argc, char *argv[])
103 {
104 int ret = 0;
105
106 if (argc != 1) {
107 printf("Error Usage!\r\n");
108 return -1;
109 }
110
111 while (1) {
112 ret = adc_read(&imx6ulladc);
113 if(ret == 0) { /* 数据读取成功 */
114 printf("ADC 原始值: %d,电压值: %.3fV\r\n", imx6ulladc.raw,
imx6ulladc.act);
115 }
116 usleep(100000); /*100ms */
117 }
118 return 0;
119 }

五、运行测试

1、编译驱动程序和测试APP

        使能硬件浮点,输入如下编译 adcApp.c 这个测试程序:

arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard adcApp.c -o adcApp

        编译成功以后就会生成 adcApp 这个应用程序。
 

2、运行测试

        输入如下命令,使用 adcApp 测试程序:

        ./adcApp
        测试 APP 会不断的读取 ADC 值并输出到终端.


本笔记为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,未经允许不得用于商业用途。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值