IIO介绍

IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,大家不要看到“工业”两个字就觉得 IIO是只用于工业领域的。大家一般在搜索 IIO 子系统的时候,会发现大多数讲的都是 ADC,这是因为 IIO 就是为 ADC 类传感器准备的,当然了 DAC 也是可以的。

1、 iio_dev 结构体
IIO 子系统使用结构体 iio_dev 来描述一个具体 IIO 设备,此设备结构体定义在include/linux/iio/iio.h 文件中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
474 struct iio_dev {
475 int id;
476
477 int modes; //为设备支持模式
/*INDIO_DIRECT_MODE 提供 sysfs 接口。
INDIO_BUFFER_TRIGGERED 支持硬件缓冲触发。
INDIO_BUFFER_SOFTWARE 支持软件缓冲触发。
INDIO_BUFFER_HARDWARE 支持硬件缓冲区。*/
478 int currentmode; //currentmode 为当前模式。
479 struct device dev;
480
481 struct iio_event_interface *event_interface;
482
483 struct iio_buffer *buffer; //缓冲区
484 struct list_head buffer_list; //buffer_list 为当前匹配的缓冲区列表。
485 int scan_bytes; //scan_bytes 为捕获到,并且提供给缓冲区的字节数
486 struct mutex mlock; //available_scan_masks 为可选的扫描位掩码,使用触发缓冲区的时候可以通过设
487 //置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区。
488 const unsigned long *available_scan_masks;
489 unsigned masklength;
490 const unsigned long *active_scan_mask; //active_scan_mask 为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区
491 bool scan_timestamp; //scan_timestamp 为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。
492 unsigned scan_index_timestamp;
493 struct iio_trigger *trig; //触发器
494 struct iio_poll_func *pollfunc;//pollfunc 为一个函数,在接收到的触发器上运行
495
496 struct iio_chan_spec const *channels; // IIO 设备通道
497 int num_channels; //num_channels 为 IIO 设备的通道数
498
499 struct list_head channel_attr_list;
500 struct attribute_group chan_attr_group;
501 const char *name; //name 为 IIO 设备名字
502 const struct iio_info *info;
503 struct mutex info_exist_lock;
504 const struct iio_buffer_setup_ops *setup_ops;
505 struct cdev chrdev;// chrdev 为字符设备,由 IIO 内核创建
......
515 };

操作集合iio_buffer_setup_ops

第 504 行, 为 iio_buffer_setup_ops 结构体类型,内容如下:

1
2
3
4
5
6
7
8
427 struct iio_buffer_setup_ops {
428 int (*preenable)(struct iio_dev *); /* 缓冲区使能之前调用 */
429 int (*postenable)(struct iio_dev *); /* 缓冲区使能之后调用 */
430 int (*predisable)(struct iio_dev *); /* 缓冲区禁用之前调用 */
431 int (*postdisable)(struct iio_dev *); /* 缓冲区禁用之后调用 */
432 bool (*validate_scan_mask)(struct iio_dev *indio_dev,
433 const unsigned long *scan_mask); /* 检查扫描掩码是否有效 */
434 };

iio_dev 申请与释放

在使用之前要先申请 iio_dev,申请函数为 iio_device_alloc,函数原型如下:

1
struct iio_dev *iio_device_alloc(int sizeof_priv)

sizeof_priv: 私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为 iio_dev的私有数据,这样可以直接通过 iio_device_alloc 函数同时完成 iio_dev 和设备结构体变量的内存申请。 申请成功以后使用 iio_priv 函数来得到自定义的设备结构体变量首地址。
返回值:如果申请成功就返回 iio_dev 首地址,如果失败就返回 NULL。
一般 iio_device_alloc 和 iio_priv 之间的配合使用如下所示:

1
2
3
4
5
6
7
8
9
10
struct icm20608_dev *dev;		// icm20608_dev 是自定义的设备结构体。
struct iio_dev *indio_dev; // indio_dev 是 iio_dev 结构体变量指针。

/* 1、申请 iio_dev 内存 */
indio_dev = iio_device_alloc(sizeof(*dev));//使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了icm2060_dev 的内存。
if (!indio_dev)
return -ENOMEM;

/* 2、获取设备结构体变量地址 */c
dev = iio_priv(indio_dev);//使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址。

如果要释放 iio_dev,需要使用 iio_device_free 函数,函数原型如下:

1
void iio_device_free(struct iio_dev *indio_dev)//indio_dev: 需要释放的 iio_dev。

iio_dev 注册与注销

前面分配好 iio_dev 以后就要初始化各种成员变量,初始化完成以后就需要将 iio_dev 注册到内核中,需要用到 iio_device_register 函数,函数原型如下:

1
int iio_device_register(struct iio_dev *indio_dev)//indio_dev: 需要注册的 iio_dev。

返回值: 0,成功;其他值,失败。
如果要注销 iio_dev 使用 iio_device_unregister 函数,函数原型如下:

1
void iio_device_unregister(struct iio_dev *indio_dev)//indio_dev: 需要注销的 iio_dev。

返回值: 0,成功;其他值,失败

iio_info

iio_dev 有个成员变量: info,为 iio_info 结构体指针变量,这个是我们在编写 IIO 驱动的时候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到 iio_info 里面。 iio_info结构体定义在 include/linux/iio/iio.h 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
352 struct iio_info {
353 struct module *driver_module;
354 struct attribute_group *event_attrs;
355 const struct attribute_group *attrs; //attrs 是通用的设备属性。
356
357 int (*read_raw)(struct iio_dev *indio_dev, //读设备内部数据函数 indio_dev为读写设备
358 struct iio_chan_spec const *chan, //需要读取的通道
359 int *val, // 是数据值,val 是整数部分, val2 是小数部分
360 int *val2,
361 long mask); //掩码
......
369
370 int (*write_raw)(struct iio_dev *indio_dev, //写设备内部数据函数
371 struct iio_chan_spec const *chan,
372 int val, //应用程序从内核空间读取到数据
373 int val2,
374 long mask);
375
376 int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
377 struct iio_chan_spec const *chan,
378 long mask);
......
415 };

Linux 内核使用 iio_chan_spec 结构体来描述通道,定义在 include/linux/iio/iio.h 文件中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
223 struct iio_chan_spec {
224 enum iio_chan_type type;
225 int channel;
226 int channel2;
227 unsigned long address;
228 int scan_index;
229 struct {
230 char sign;
231 u8 realbits;
232 u8 storagebits;
233 u8 shift;
234 u8 repeat;
235 enum iio_endian endianness;
236 } scan_type;
237 long info_mask_separate;
238 long info_mask_shared_by_type; // info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。
239 long info_mask_shared_by_dir; //info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。
240 long info_mask_shared_by_all;
241 const struct iio_event_spec *event_spec;
242 unsigned int num_event_specs;
243 const struct iio_chan_spec_ext_info *ext_info;
244 const char *extend_name;
245 const char *datasheet_name;
246 unsigned modified:1; //modified 为 1 的时候, channel2 为通道修饰符。
247 unsigned indexed:1; //indexed 为 1 的时候, channel 为通道索引。
248 unsigned output:1; //
249 unsigned differential:1; //differential为差分通道
250 };

来看一下 iio_chan_spec 结构体中一些比较重要的成员变量:
第 224 行, type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类型,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
13 enum iio_chan_type {
14 IIO_VOLTAGE, /* 电压类型 */
15 IIO_CURRENT, /* 电流类型 */
16 IIO_POWER, /* 功率类型 */
17 IIO_ACCEL, /* 加速度类型 */
18 IIO_ANGL_VEL, /* 角度类型(陀螺仪) */
19 IIO_MAGN, /* 电磁类型(磁力计) */
20 IIO_LIGHT, /* 灯光类型 */
21 IIO_INTENSITY, /* 强度类型(光强传感器) */
22 IIO_PROXIMITY, /* 接近类型(接近传感器) */
23 IIO_TEMP, /* 温度类型 */
24 IIO_INCLI, /* 倾角类型(倾角测量传感器) */
25 IIO_ROT, /* 旋转角度类型 */
26 IIO_ANGL, /* 转动角度类型(电机旋转角度测量传感器) */
27 IIO_TIMESTAMP, /* 时间戳类型 */
28 IIO_CAPACITANCE, /* 电容类型 */
29 IIO_ALTVOLTAGE, /* 频率类型 */
30 IIO_CCT, /* 笔者暂时未知的类型 */
31 IIO_PRESSURE, /* 压力类型 */
32 IIO_HUMIDITYRELATIVE, /* 湿度类型 */
33 IIO_ACTIVITY, /* 活动类型(计步传感器) */
34 IIO_STEPS, /* 步数类型 */
35 IIO_ENERGY, /* 能量类型(卡路里) */
36 IIO_DISTANCE, /* 距离类型 */
37 IIO_VELOCITY, /* 速度类型 */
38 };

目前 Linux 内核支持的传感器类型非常丰富,而且支持类型也会不断的增加。如果是 ADC,那就是 IIO_VOLTAGE 类型。如果是 ICM20608 这样的多轴传感器,那么就是复合类型了,陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是IIO_ACCEL 类型,温度部分是IIO_TEMP。

设备驱动框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
 /* 自定义设备结构体 */
struct xxx_dev {
struct spi_device *spi; /* spi 设备 */
struct regmap *regmap; /* regmap */
struct regmap_config regmap_config;
struct mutex lock;
};

/*
* 通道数组
*/
static const struct iio_chan_spec xxx_channels[] = {

};

/*
* @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,
* :此函数里面会从传感器里面读取各种数据,然后上传给应用。
* @param - indio_dev : IIO 设备
* @param - chan : 通道
* @param - val : 读取的值,如果是小数值的话, val 是整数部分。
* @param - val2 : 读取的值,如果是小数值的话, val2 是小数部分。
* @param - mask : 掩码。
* @return : 0,成功;其他值,错误
*/
static int xxx_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2,
long mask)
{
return 0;
}

/*
* @description : 写函数,当向 sysfs 中的文件写数据的时候最终此函数
* :会执行,一般在此函数里面设置传感器,比如量程等。
* @param - indio_dev : IIO 设备
* @param - chan : 通道
* @param - val : 应用程序写入值,如果是小数的话, val 是整数部分。
* @param - val2 : 应用程序写入值,如果是小数的话, val2 是小数部分。
* @return : 0,成功;其他值,错误
*/
static int xxx_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
return 0;
}

/*
* @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设
* :置传感器的分辨率,如果分辨率带小数,那么这个小数传递到
* : 内核空间应该扩大多少倍,此函数就是用来设置这个的。
* @param - indio_dev : iio_dev
* @param - chan : 通道
* @param - mask : 掩码
* @return : 0,成功;其他值,错误
*/
static int xxx_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, long mask)
{
return 0;
}

/*
* iio_info 结构体变量
*/
static const struct iio_info xxx_info = {
.read_raw = xxx_read_raw,
.write_raw = xxx_write_raw,
.write_raw_get_fmt = &xxx_write_raw_get_fmt,
};

/*
* @description : spi 驱动的 probe 函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - spi : spi 设备
*
*/
static int xxx_probe(struct spi_device *spi)
{
int ret;
struct xxx_dev *data;
struct iio_dev *indio_dev;

/* 1、申请 iio_dev 内存 */
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;

/* 2、获取 xxx_dev 结构体地址 */
data = iio_priv(indio_dev);
data->spi = spi;
spi_set_drvdata(spi, indio_dev);
mutex_init(&data->lock);

/* 3、初始化 iio_dev 成员变量 */
indio_dev->dev.parent = &spi->dev;
indio_dev->info = &xxx_info;
indio_dev->name = "xxx";
indio_dev->modes = INDIO_DIRECT_MODE; /* 直接模式 /
indio_dev->channels = xxx_channels;
indio_dev->num_channels = ARRAY_SIZE(xxx_channels);

iio_device_register(indio_dev);

/* 4、 regmap 相关设置 */

/* 5、 SPI 相关设置*/

/* 6、芯片初始化 */
return 0;
}

/*
* @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
* @param - spi : spi 设备
* @return : 0,成功;其他负值,失败
*/
static int xxx_remove(struct spi_device *spi)
{
struct iio_dev *indio_dev = spi_get_drvdata(spi);
struct xxx_dev *data;
data = iio_priv(indio_dev); ;

/* 1、其他资源的注销以及释放 */

/* 2、注销 IIO */
iio_device_unregister(indio_dev);

return 0;
}