0
点赞
收藏
分享

微信扫一扫

一本书掌握数字化运维方法,构建数字化运维体系

爱上流星雨 4小时前 阅读 1
linuxarm

工业场合里面也有大量的模拟量和数字量之间的转换,也就是我们常说的 ADC 和 DAC。而且随着手机、物联网、工业物联网和可穿戴设备的爆发,传感器的需求只持续增强。比如手机或者手环里面的加速度计、光传感器、陀螺仪、气压计、磁力计等,这些传感器本质上都是ADC,大家注意查看这些传感器的手册,会发现他们内部都会有个 ADC,传感器对外提供 IIC或者 SPI 接口, SOC 可以通过 IIC 或者 SPI 接口来获取到传感器内部的 ADC 数值,从而得到想要测量的结果。Linux 内核为了管理这些日益增多的 ADC 类传感器,特地推出了 IIO 子系统,本章我们就来学习如何使用 IIO 子系统来编写 ADC 类传感器驱动。

IIO 子系统简介

IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,大家不要看到“工业”两个字就觉得 IIO是只用于工业领域的。大家一般在搜索 IIO 子系统的时候,会发现大多数讲的都是 ADC,这是因为 IIO 就是为 ADC 类传感器准备的,当然了 DAC 也是可以的。大家常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC,内部 ADC 将原始的模拟数据转换为数字量,然后通过其他的通信接口,比如 IIC、 SPI 等传输给 SOC。因此,当你使用的传感器本质是 ADC 或 DAC 器件的时候,可以优先考虑使用 IIO 驱动框架。

iio_dev

IIO 子系统使用结构体 iio_dev 来描述一个具体 IIO 设备,此设备结构体定义在include/linux/iio/iio.h 文件中,结构体内容如下(有省略):

474 struct iio_dev {
475     int id;
476
477     int modes;
478     int 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;
485     int scan_bytes;
486     struct mutex mlock;
487
488     const unsigned long *available_scan_masks;
489     unsigned masklength;
490     const unsigned long *active_scan_mask;
491     bool scan_timestamp;
492     unsigned scan_index_timestamp;
493     struct iio_trigger *trig;
494     struct iio_poll_func *pollfunc;
495
496     struct iio_chan_spec const *channels;
497     int num_channels;
498
499     struct list_head channel_attr_list;
500     struct attribute_group chan_attr_group;
501     const char *name;
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;
......
515 };

我们来看一下 iio_dev 结构体中几个比较重要的成员变量:
第 477 行, modes 为设备支持的模式,可选择的模如表所示:

模式描述
INDIO_DIRECT_MODE提供 sysfs 接口。
INDIO_BUFFER_TRIGGERED支持硬件缓冲触发。
INDIO_BUFFER_SOFTWARE支持软件缓冲触发。
INDIO_BUFFER_HARDWARE支持硬件缓冲区。

第 478 行, currentmode 为当前模式。
第 483 行, buffer 为缓冲区。
第 484 行, buffer_list 为当前匹配的缓冲区列表。
第 485 行, scan_bytes 为捕获到,并且提供给缓冲区的字节数。
第 488 行, available_scan_masks 为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区。
第 490 行, active_scan_mask 为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区。
第 491 行, scan_timestamp 为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。
第 493 行, trig 为 IIO 设备当前触发器,当使用缓冲模式的时候。
第 494 行, pollfunc 为一个函数,在接收到的触发器上运行。
第 496 行, channels 为 IIO 设备通道,为 iio_chan_spec 结构体类型,稍后会详细讲解 IIO通道。
第 497 行, num_channels 为 IIO 设备的通道数。
第 501 行, name 为 IIO 设备名字。
第 502 行, info 为 iio_info 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要!我们从用户空间读取 IIO 设备内部数据,最终调用的就是 iio_info 里面的函数。稍后会详细讲解 iio_info 结构体。
第 504 行, setup_ops 为 iio_buffer_setup_ops 结构体类型,内容如下:

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_buffer_setup_ops 里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。如果未指定的话就默认使用 iio_triggered_buffer_setup_ops。
继续回到示例代码中第 505 行, chrdev 为字符设备,由 IIO 内核创建。

iio_dev 申请与释放

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

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 struct icm20608_dev *dev;
2 struct iio_dev *indio_dev;
3 
4 /* 1、申请 iio_dev 内存 */
5 indio_dev = iio_device_alloc(sizeof(*dev));
6 if (!indio_dev)
7 return -ENOMEM;
8 
9 /* 2、获取设备结构体变量地址 */
10 dev = iio_priv(indio_dev);

第 1 行, icm20608_dev 是自定义的设备结构体。
第 2 行, indio_dev 是 iio_dev 结构体变量指针。
第 5 行,使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了 icm2060_dev 的内存。
第 10 行,使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址。
如果要释放 iio_dev,需要使用 iio_device_free 函数,函数原型如下:

void iio_device_free(struct iio_dev *indio_dev)

函数参数和返回值含义如下:
indio_dev: 需要释放的 iio_dev。
返回值:无。
也 可 以 使 用 devm_iio_device_alloc 来 分 配 iio_dev , 这 样 就 不 需 要 我 们 手 动 调 用iio_device_free 函数完成 iio_dev 的释放工作。

iio_dev 注册与注销

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

int iio_device_register(struct iio_dev *indio_dev)

函数参数和返回值含义如下:
indio_dev: 需要注册的 iio_dev。
返回值: 0,成功;其他值,失败。
如果要注销 iio_dev 使用 iio_device_unregister 函数,函数原型如下:

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 中,结构体定义如下(有省略):

352 struct iio_info {
353     struct module *driver_module;
354     struct attribute_group *event_attrs;
355     const struct attribute_group *attrs;
356
357     int (*read_raw)(struct iio_dev *indio_dev,
358                     struct iio_chan_spec const *chan,
359                     int *val,
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 };

第 355 行, attrs 是通用的设备属性。
第 357 和 370 行,分别为 read_raw 和 write_raw 函数,这两个函数就是最终读写设备内部数据的操作函数,需要程序编写人员去实现的。比如应用读取一个陀螺仪传感器的原始数据,那么最终完成工作的就是 read_raw 函数,我们需要在 read_raw 函数里面实现对陀螺仪芯片的读取操作。同理, write_raw 是应用程序向陀螺仪芯片写数据,一般用于配置芯片,比如量程、数据速率等。这两个函数的参数都是一样的,我们依次来看一下:
indio_dev: 需要读写的 IIO 设备。
chan:需要读取的通道。
val, val2:对于 read_raw 函数来说 val 和 val2 这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw 来说就是应用程序向设备写入的数据。 val 和 val2 共同组成具体值, val 是整数部分, val2 是小数部分。但是val2 也是对具体的小数部分扩大 N 倍后的整数值,因为不能直接从内核向应用程序返回一个小数值。比如现在有个值为 1.00236,那么 val 就是 1, vla2 理论上来讲是 0.00236,但是我们需要对 0.00236 扩大 N 倍,使其变为整数,这里我们扩大 1000000 倍,那么 val2 就是 2360。因此val=1, val2=2360。扩大的倍数我们不能随便设置,而是要使用 Linux 定义的倍数, Linux 内核里面定义的数据扩大倍数,或者说数据组合形式如表所示:

组合宏描述
IIO_VAL_INT整数值,没有小数。比如 5000,那么就是 val=5000,不
需要设置 val2
IIO_VAL_INT_PLUS_MICRO小数部分扩大 1000000 倍,比如 1.00236,此时 val=1,
val2=2360。
IIO_VAL_INT_PLUS_NANO小数部分扩大 1000000000 倍,同样是 1.00236,此时
val=1, val2=2360000。
IIO_VAL_INT_PLUS_MICRO_DBdB 数据,和 IIO_VAL_INT_PLUS_MICRO 数据形式一
样,只是在后面添加 db。
IIO_VAL_INT_MULTIPLE多个整数值,比如一次要传回 6 个整数值,那么 val 和
val2就不够用了。此宏主要用于iio_info的read_raw_multi
函数。
IIO_VAL_FRACTIONAL分数值,也就是 val/val2。比如 val=1, val2=4,那么实际
值就是 1/4。
IIO_VAL_FRACTIONAL_LOG2值为 val>>val2,也就是 val 右移 val2 位。比如 val=25600,
val2=4 , 那 么 真 正 的 值 就 是 25600 右 移 4 位 ,
25600>>4=1600.

mask: 掩码,用于指定我们读取的是什么数据,比如 ICM20608 这样的传感器,他既有原始的测量数据,比如 X,Y,Z 轴的陀螺仪、加速度计等,也有测量范围值,或者分辨率。比如加速度计测量范围设置为±16g,那么分辨率就是 32/65536≈0.000488,我们只有读出原始值以及对应的分辨率(量程),才能计算出真实的重力加速度。此时就有两种数据值:传感器原始值、分辨率。 Linux 内核使用 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_SCALE 这两个宏来表示原始值以及分辨率,这两个宏就是掩码。至于每个通道可以采用哪几种掩码,这个在我们初始化通道的时候需要驱动编写人员设置好。掩码有很多种,稍后讲解 IIO 通道的时候详细讲解!
第 376 行的 write_raw_get_fmt 用于设置用户空间向内核空间写入的数据格式,write_raw_get_fmt 函数决定了 wtite_raw 函数中 val 和 val2 的意义,也就是表中的组合形式。比如我们需要在应用程序中设置 ICM20608 加速度计的量程为± 8g,那么分辨率就是16/65536 ≈ 0.000244 ,我们 在 write_raw_get_fmt 函数 里面设置 加速度计的数 据格式 为IIO_VAL_INT_PLUS_MICRO。那么我们在应用程序里面向指定的文件写入 0.000244 以后,最终传递给内核驱动的就是 0.000244*1000000=244。也就是 write_raw 函数的 val 参数为 0, val2参数为 244。

iio_chan_spec

IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那么这个 ADC 就有 8 个通道。我们本章实验用到的 ICM20608,这是一个六轴传感器,可以输出三轴陀螺仪(X、 Y、 Z)、三轴加速度计(X、 Y、 Z)和一路温度,也就是一共有 7 路数据,因此就有 7 个通道。注意,三轴陀螺仪或加速度计的 X、 Y、 Z 这三个轴,每个轴都算一个通道。
Linux 内核使用 iio_chan_spec 结构体来描述通道,定义在 include/linux/iio/iio.h 文件中,内容如下:

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;
239     long 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;
247     unsigned indexed:1;
248     unsigned output:1;
249     unsigned differential:1;
250 };

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

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。
继续来看 iio_chan_spec 结构体,第 225 行,当成员变量 indexed 为 1时候, channel 为通道索引。
第 226 行,当成员变量 modified 为 1 的时候, channel2 为通道修饰符。 Linux 内核给出了可用的通道修饰符,定义在 include/uapi/linux/iio/types.h 文件里面,内容如下(有省略)

40 enum iio_modifier {
41     IIO_NO_MOD,
42     IIO_MOD_X, /* X 轴 */
43     IIO_MOD_Y, /* Y 轴 */
44     IIO_MOD_Z, /* Z 轴 */
......
73 };

比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCEL, X、Y、 Z 这三个轴就用 channel2的通道修饰符来区分。 IIO_MOD_X、 IIO_MOD_Y、 IIO_MOD_Z 就分别对应 X、 Y、 Z 这三个轴。通道修饰符主要是影响 sysfs 下的通道文件名字,后面我们会讲解 sysfs 下通道文件名字组成形式。
继续回到iio_chan_spec ,第 227 行的 address 成员变量用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。比如 ICM20608 的加速度计 X 轴这个通道,它的数据首地址就是 0X3B。 address 也可以用作其他功能,自行选择,也可以不使用 address,一切以实际情况为准。
第 228 行,当使用触发缓冲区的时候, scan_index 是扫描索引。
第 229~236, scan_type 是一个结构体,描述了扫描数据在缓冲区中的存储格式。我们依次来看一下 scan_type 各个成员变量的涵义:
scan_type.sign:如果为‘u’表示数据为无符号类型,为‘s’的话为有符号类型。
scan_type.realbits:数据真实的有效位数,比如很多传感器说的 10 位 ADC,其真实有效数据就是 10 位。
scan_type.storagebits:存储位数,有效位数+填充位。比如有些传感器 ADC 是 12 位的,那么我们存储的话肯定要用到 2 个字节,也就是 16 位,这 16 位就是存储位数。
scan_type.shift:右移位数,也就是存储位数和有效位数不一致的时候,需要右移的位数,这个参数不总是需要,一切以实际芯片的数据手册位数。
scan_type.repeat:实际或存储位的重复数量。
scan_type.endianness:数据的大小端模式,可设置为 IIO_CPU、 IIO_BE(大端)或 IIO_LE(小端)。
第 237 行, info_mask_separate 标记某些属性专属于此通道, include/linux/iio/types.h 文件中的 iio_chan_info_enum 枚举类型描述了可选的属性值,如下所示:

23 enum iio_chan_info_enum {
24     IIO_CHAN_INFO_RAW = 0,
25     IIO_CHAN_INFO_PROCESSED,
26     IIO_CHAN_INFO_SCALE,
27     IIO_CHAN_INFO_OFFSET,
......
45     IIO_CHAN_INFO_DEBOUNCE_TIME,
46 };

比如 ICM20608 加速度计的 X、 Y、 Z 这三个轴,在 sysfs 下这三个轴肯定是对应三个不同的文件,我们通过读取这三个文件就能得到每个轴的原始数据。 IIO_CHAN_INFO_RAW 这个属
性表示原始数据,当我们在配置 X、 Y、 Z 这三个通道的时候,在 info_mask_separate 中使能IIO_CHAN_INFO_RAW 这个属性,那么就表示在 sysfs 下生成三个不同的文件分别对应 X、 Y、
Z 轴,这三个轴的 IIO_CHAN_INFO_RAW 属性是相互独立的。
第 238 行, info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。也就是iio_chan_spec.type 成员变量相同的通道。比如 ICM20608 加速度计的 X、 Y、 Z 轴他们的 type 都
是 IIO_ACCEL,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么在配置这三个通道的时候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 这个属性,表示
这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。
第 239 行, info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。
第 240 行, info_mask_shared_by_all 表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。
第 246 行, modified 为 1 的时候, channel2 为通道修饰符。
第 247 行, indexed 为 1 的时候, channel 为通道索引。
第 248 行, output 表示为输出通道。
第 249 行, differential 表示为差分通道。

IIO 驱动框架创建

前面我们已经对 IIO 设备、 IIO 通道进行了详细的讲解,本节我们就来学习如何搭建 IIO 驱动框架。在上一小节分析 IIO 子系统的时候大家应该看出了, IIO 框架主要用于 ADC 类的传感器,比如陀螺仪、加速度计、磁力计、光强度计等,这些传感器基本都是 IIC 或者 SPI 接口的。因此 IIO 驱动的基础框架就是 IIC 或者 SPI,我们可以在 IIC 或 SPI 驱动里面在加上上一章讲解的 regmap。当然了,有些 SOC 内部的 ADC 也会使用 IIO 框架,那么这个时候驱动的基础框架就是 platfrom。

我们以 SPI 接口为例,首先是 SPI 驱动框架,如下所示:

1 /*
2 * @description : spi 驱动的 probe 函数,当驱动与
3 * 设备匹配以后此函数就会执行
4 * @param - spi : spi 设备
5 * @return : 0,成功;其他值,失败
6 */
7 static int xxx_probe(struct spi_device *spi)
8 {
9     return 0;
10 }
11
12 /*
13 * @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
14 * @param - spi : spi 设备
15 * @return : 0,成功;其他负值,失败
16 */
17 static int xxx_remove(struct spi_device *spi)
18 {
19     return 0;
20 }
21
22 /* 传统匹配方式 ID 列表 */
23 static const struct spi_device_id xxx_id[] = {
24     {"alientek,xxx", 0},
25     {}
26 };
27
28 /* 设备树匹配列表 */
29 static const struct of_device_id xxx_of_match[] = {
30     { .compatible = "alientek,xxx" },
31     { /* Sentinel */ }
32 };
33
34 /* SPI 驱动结构体 */
35 static struct spi_driver xxx_driver = {
36     .probe = xxx_probe,
37     .remove = xxx_remove,
38     .driver = {
39         .owner = THIS_MODULE,
40         .name = "xxx",
41         .of_match_table = xxx_of_match,
42     },
43     .id_table = xxx_id,
44 };
45
46 /*
47 * @description : 驱动入口函数
48 * @param : 无
49 * @return : 无
50 */
51 static int __init xxx_init(void)
52 {
53     return spi_register_driver(&xxx_driver);
54 }
55
56 /*
57 * @description : 驱动出口函数
58 * @param : 无
59 * @return : 无
60 */
61 static void __exit xxx_exit(void)
62 {
63     spi_unregister_driver(&xxx_driver);
64 }
65
66 module_init(xxx_init);
67 module_exit(xxx_exit);
68 MODULE_LICENSE("GPL");
69 MODULE_AUTHOR("ALIENTEK");

IIO 设备申请与初始化

IIO 设备的申请、初始化以及注册在 probe 函数中完成,在注销驱动的时候还需要在 remove函数中注销掉 IIO 设备、释放掉申请的一些内存。添加完 IIO 框架以后的 probe 和 remove 函数如下所示:

1 /* 自定义设备结构体 */
2     struct xxx_dev {
3         struct spi_device *spi; /* spi 设备 */
4         struct regmap *regmap; /* regmap */
5         struct regmap_config regmap_config;
6         struct mutex lock;
7     };
8 
9 /*
10 * 通道数组
11 */
12 static const struct iio_chan_spec xxx_channels[] = {
13
14 };
15
16 /*
17 * @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,
18 * :此函数里面会从传感器里面读取各种数据,然后上传给应用。
19 * @param - indio_dev : IIO 设备
20 * @param - chan : 通道
21 * @param - val : 读取的值,如果是小数值的话, val 是整数部分。
22 * @param - val2 : 读取的值,如果是小数值的话, val2 是小数部分。
23 * @param - mask : 掩码。
24 * @return : 0,成功;其他值,错误
25 */
26     static int xxx_read_raw(struct iio_dev *indio_dev,
27                             struct iio_chan_spec const *chan,
28                             int *val, int *val2, long mask)
29     {
30         return 0;
31     }
32
33 /*
34 * @description : 写函数,当向 sysfs 中的文件写数据的时候最终此函数
35 * :会执行,一般在此函数里面设置传感器,比如量程等。
36 * @param - indio_dev : IIO 设备
37 * @param - chan : 通道
38 * @param - val : 应用程序写入值,如果是小数的话, val 是整数部分。
39 * @param - val2 : 应用程序写入值,如果是小数的话, val2 是小数部分。
40 * @return : 0,成功;其他值,错误
41 */
42     static int xxx_write_raw(struct iio_dev *indio_dev,
43                             struct iio_chan_spec const *chan,
44                             int val, int val2, long mask)
45     {
46         return 0;
47     }
48
49 /*
50 * @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设
51 * :置传感器的分辨率,如果分辨率带小数,那么这个小数传递到
52 * : 内核空间应该扩大多少倍,此函数就是用来设置这个的。
53 * @param - indio_dev : iio_dev
54 * @param - chan : 通道
55 * @param - mask : 掩码
56 * @return : 0,成功;其他值,错误
57 */
58     static int xxx_write_raw_get_fmt(struct iio_dev *indio_dev,
59                             struct iio_chan_spec const *chan, long mask)
60     {
61         return 0;
62     }
63
64 /*
65 * iio_info 结构体变量
66 */
67     static const struct iio_info xxx_info = {
68         .read_raw = xxx_read_raw,
69         .write_raw = xxx_write_raw,
70         .write_raw_get_fmt = &xxx_write_raw_get_fmt,
71     };
72
73 /*
74 * @description : spi 驱动的 probe 函数,当驱动与
75 * 设备匹配以后此函数就会执行
76 * @param - spi : spi 设备
77 *
78 */
79     static int xxx_probe(struct spi_device *spi)
80     {
81         int ret;
82         struct xxx_dev *data;
83         struct iio_dev *indio_dev;
84
85         /* 1、申请 iio_dev 内存 */
86         indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
87         if (!indio_dev)
88             return -ENOMEM;
89
90         /* 2、获取 xxx_dev 结构体地址 */
91         data = iio_priv(indio_dev);
92         data->spi = spi;
93         spi_set_drvdata(spi, indio_dev);
94         mutex_init(&data->lock);
95
96         /* 3、初始化 iio_dev 成员变量 */
97         indio_dev->dev.parent = &spi->dev;
98         indio_dev->info = &xxx_info;
99         indio_dev->name = "xxx";
100        indio_dev->modes = INDIO_DIRECT_MODE; /* 直接模式 /
101        indio_dev->channels = xxx_channels;
102        indio_dev->num_channels = ARRAY_SIZE(xxx_channels);
103
104        iio_device_register(indio_dev);
105
106        /* 4、 regmap 相关设置 */
107
108        /* 5、 SPI 相关设置*/
109
110        /* 6、芯片初始化 */
111
112        return 0;
113
114 }
115
116 /*
117 * @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
118 * @param - spi : spi 设备
119 * @return : 0,成功;其他负值,失败
120 */
121 static int xxx_remove(struct spi_device *spi)
122 {
123     struct iio_dev *indio_dev = spi_get_drvdata(spi);
124     struct xxx_dev *data;
125
126     data = iio_priv(indio_dev); ;
127
128     /* 1、其他资源的注销以及释放 */
129
130     /* 2、注销 IIO */
131     iio_device_unregister(indio_dev);
132
133     return 0;
134 }

第 2~7 行,用户自定义的设备结构体。
第 12 行, IIO 通道数组。
第 16~71 行, 这部分为 iio_info,当应用程序读取相应的驱动文件的时候, xxx_read_raw函数就会执行,我们在此函数中会读取传感器数据,然后返回给应用层。当应用层向相应的驱动写数据的时候, xxx_write_raw 函数就会执行。因此 xxx_read_raw 和 xxx_write_raw 这两个函数是非常重要的!需要我们根据具体的传感器来编写,这两个函数是编写 IIO 驱动的核心。
第 79~114 行, xxx_probe 函数,此函数的核心就是分配并初始化 iio_dev,最后向内核注册iio_dev。

第 86 行调用 devm_iio_device_alloc 函数分配 iio_dev 内存,这里连用户自定义的设备结构体变量内存一起申请了。

第 91 行调用 iio_priv 函数从 iio_dev 中提取出私有数据,这个私有数据就是设备结构体变量。

第 97~102 行初始化 iio_dev,重点是第 98 行设置 iio_dev 的 info成员变量。

第 101 行设置 iio_dev 的通道。初始化完成以后就要调用 iio_device_register 函数向内核注册 iio_dev。整个过程就是:申请 iio_dev、初始化、注册,和我们前面讲解的其他驱动框架步骤一样。
第 121~134 行, xxx_remove 函数里面需要做的就是释放 xxx_probe 函数申请到的 IIO 相关资源,比如第 131 行,使用 iio_device_unregister 注销掉前面注册的 iio_dev。由于前面我们使用
devm_iio_device_alloc 函数申请的 iio_dev,因此不需要在 remove 函数中手动释放 iio_dev。
IIO 框架示例就讲解到这里,剩下的就是根据所使用的具体传感器,在 IIO 驱动框架里面添加相关的处理,接下来我们就以正点原子 I.MX6ULL 开发板上的 ICM20608 为例,进行 IIO 驱动实战!
 

实验

驱动代码:

#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include "icm20608reg.h"
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/unaligned/be_byteshift.h>

#define ICM20608_NAME	"icm20608"
#define ICM20608_TEMP_OFFSET	     0
#define ICM20608_TEMP_SCALE		     326800000

#define ICM20608_CHAN(_type, _channel2, _index)                    \
	{                                                             \
		.type = _type,                                        \
		.modified = 1,                                        \
		.channel2 = _channel2,                                \
		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	      \
				      BIT(IIO_CHAN_INFO_CALIBBIAS),   \
		.scan_index = _index,                                 \
		.scan_type = {                                        \
				.sign = 's',                          \
				.realbits = 16,                       \
				.storagebits = 16,                    \
				.shift = 0,                           \
				.endianness = IIO_BE,                 \
			     },                                       \
	}

/* 
 * ICM20608的扫描元素,3轴加速度计、
 * 3轴陀螺仪、1路温度传感器,1路时间戳 
 */
enum inv_icm20608_scan {
	INV_ICM20608_SCAN_ACCL_X,
	INV_ICM20608_SCAN_ACCL_Y,
	INV_ICM20608_SCAN_ACCL_Z,
	INV_ICM20608_SCAN_TEMP,
	INV_ICM20608_SCAN_GYRO_X,
	INV_ICM20608_SCAN_GYRO_Y,
	INV_ICM20608_SCAN_GYRO_Z,
	INV_ICM20608_SCAN_TIMESTAMP,
};

struct icm20608_dev {
	struct spi_device *spi;		/* spi设备 */
	struct regmap *regmap;				/* regmap */
	struct regmap_config regmap_config;	
	struct mutex lock;
};

/*
 * icm20608陀螺仪分辨率,对应250、500、1000、2000,计算方法:
 * 以正负250度量程为例,500/2^16=0.007629,扩大1000000倍,就是7629
 */
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};

/* 
 * icm20608加速度计分辨率,对应2、4、8、16 计算方法:
 * 以正负2g量程为例,4/2^16=0.000061035,扩大1000000000倍,就是61035
 */
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};

/*
 * icm20608通道,1路温度通道,3路陀螺仪,3路加速度计
 */
static const struct iio_chan_spec icm20608_channels[] = {
	/* 温度通道 */
	{
		.type = IIO_TEMP,
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
				| BIT(IIO_CHAN_INFO_OFFSET)
				| BIT(IIO_CHAN_INFO_SCALE),
		.scan_index = INV_ICM20608_SCAN_TEMP,
		.scan_type = {
				.sign = 's',
				.realbits = 16,
				.storagebits = 16,
				.shift = 0,
				.endianness = IIO_BE,
			     },
	},

	ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X),	/* 陀螺仪X轴 */
	ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y),	/* 陀螺仪Y轴 */
	ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z),	/* 陀螺仪Z轴 */

	ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),	/* 加速度X轴 */
	ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),	/* 加速度Y轴 */
	ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),	/* 加速度Z轴 */
};

/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 ret;
	unsigned int data;

	ret = regmap_read(dev->regmap, reg, &data);
	return (u8)data;
}

/*
 * @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */	
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
	regmap_write(dev->regmap,  reg, value);
}

/*
 * @description  	: ICM20608内部寄存器初始化函数 
 * @param - spi 	: 要操作的设备
 * @return 			: 无
 */
void icm20608_reginit(struct icm20608_dev *dev)
{
	u8 value = 0;
	
	icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);
	mdelay(50);
	icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);
	mdelay(50);

	value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
	printk("ICM20608 ID = %#X\r\n", value);	

	icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率		*/
	icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 		*/
	icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 		*/
	icm20608_write_onereg(dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 	*/
	icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 	*/
	icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 	*/
	icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 				*/
	icm20608_write_onereg(dev, ICM20_INT_ENABLE, 0x01);		/* 使能FIFO溢出以及数据就绪中断	*/
}

/*
  * @description  	: 设置ICM20608传感器,可以用于陀螺仪、加速度计设置
  * @param - dev	: icm20608设备 
  * @param - reg  	: 要设置的通道寄存器首地址。
  * @param - anix  	: 要设置的通道,比如X,Y,Z。
  * @param - val  	: 要设置的值。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_sensor_set(struct icm20608_dev *dev, int reg,
				int axis, int val)
{
	int ind, result;
	__be16 d = cpu_to_be16(val);

	ind = (axis - IIO_MOD_X) * 2;
	result = regmap_bulk_write(dev->regmap, reg + ind, (u8 *)&d, 2);
	if (result)
		return -EINVAL;

	return 0;
}

/*
  * @description  	: 读取ICM20608传感器数据,可以用于陀螺仪、加速度计、温度的读取
  * @param - dev	: icm20608设备 
  * @param - reg  	: 要读取的通道寄存器首地址。
  * @param - anix  	: 需要读取的通道,比如X,Y,Z。
  * @param - val  	: 保存读取到的值。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg,
				   int axis, int *val)
{
	int ind, result;
	__be16 d;

	ind = (axis - IIO_MOD_X) * 2;
	result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);
	if (result)
		return -EINVAL;
	*val = (short)be16_to_cpup(&d);

	return IIO_VAL_INT;
}

/*
  * @description  		: 读取ICM20608陀螺仪、加速度计、温度通道值
  * @param - indio_dev	: iio设备 
  * @param - chan  		: 通道。
  * @param - val  		: 保存读取到的通道值。
  * @return				: 0,成功;其他值,错误
  */
static int icm20608_read_channel_data(struct iio_dev *indio_dev,
					 struct iio_chan_spec const *chan,
					 int *val)
{
	struct icm20608_dev *dev = iio_priv(indio_dev);
	int ret = 0;

	switch (chan->type) {
	case IIO_ANGL_VEL:	/* 读取陀螺仪数据 */
		ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);  /* channel2为X、Y、Z轴 */
		break;
	case IIO_ACCEL:		/* 读取加速度计数据 */
		ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2为X、Y、Z轴 */
		break;
	case IIO_TEMP:		/* 读取温度 */
		ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);  
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

/*
  * @description  	: 设置ICM20608的陀螺仪计量程(分辨率)
  * @param - dev	: icm20608设备
  * @param - val   	: 量程(分辨率值)。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_write_gyro_scale(struct icm20608_dev *dev, int val)
{
	int result, i;
	u8 d;

	for (i = 0; i < ARRAY_SIZE(gyro_scale_icm20608); ++i) {
		if (gyro_scale_icm20608[i] == val) {
			d = (i << 3);
			result = regmap_write(dev->regmap, ICM20_GYRO_CONFIG, d);
			if (result)
				return result;
			return 0;
		}
	}
	return -EINVAL;
}

 /*
  * @description  	: 设置ICM20608的加速度计量程(分辨率)
  * @param - dev	: icm20608设备
  * @param - val   	: 量程(分辨率值)。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_write_accel_scale(struct icm20608_dev *dev, int val)
{
	int result, i;
	u8 d;

	for (i = 0; i < ARRAY_SIZE(accel_scale_icm20608); ++i) {
		if (accel_scale_icm20608[i] == val) {
			d = (i << 3);
			result = regmap_write(dev->regmap, ICM20_ACCEL_CONFIG, d);
			if (result)
				return result;
			return 0;
		}
	}
	return -EINVAL;
}

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

	switch (mask) {
	case IIO_CHAN_INFO_RAW:								/* 读取ICM20608加速度计、陀螺仪、温度传感器原始值 */
		mutex_lock(&dev->lock);								/* 上锁 			*/
		ret = icm20608_read_channel_data(indio_dev, chan, val); 	/* 读取通道值 */
		mutex_unlock(&dev->lock);							/* 释放锁 			*/
		return ret;
	case IIO_CHAN_INFO_SCALE:
		switch (chan->type) {
		case IIO_ANGL_VEL:
			mutex_lock(&dev->lock);
			regdata = (icm20608_read_onereg(dev, ICM20_GYRO_CONFIG) & 0X18) >> 3;
			*val  = 0;
			*val2 = gyro_scale_icm20608[regdata];
			mutex_unlock(&dev->lock);
			return IIO_VAL_INT_PLUS_MICRO;	/* 值为val+val2/1000000 */
		case IIO_ACCEL:
			mutex_lock(&dev->lock);
			regdata = (icm20608_read_onereg(dev, ICM20_ACCEL_CONFIG) & 0X18) >> 3;
			*val = 0;
			*val2 = accel_scale_icm20608[regdata];;
			mutex_unlock(&dev->lock);
			return IIO_VAL_INT_PLUS_NANO;/* 值为val+val2/1000000000 */
		case IIO_TEMP:					
			*val = ICM20608_TEMP_SCALE/ 1000000;
			*val2 = ICM20608_TEMP_SCALE % 1000000;
			return IIO_VAL_INT_PLUS_MICRO;	/* 值为val+val2/1000000 */
		default:
			return -EINVAL;
		}
		return ret;
	case IIO_CHAN_INFO_OFFSET:		/* ICM20608温度传感器offset值 */
		switch (chan->type) {
		case IIO_TEMP:
			*val = ICM20608_TEMP_OFFSET;
			return IIO_VAL_INT;
		default:
			return -EINVAL;
		}
		return ret;
	case IIO_CHAN_INFO_CALIBBIAS:	/* ICM20608加速度计和陀螺仪校准值 */
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 陀螺仪的校准值 */
			mutex_lock(&dev->lock);
			ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);
			mutex_unlock(&dev->lock);
			return ret;
		case IIO_ACCEL:			/* 加速度计的校准值 */
			mutex_lock(&dev->lock);	
			ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan->channel2, val);
			mutex_unlock(&dev->lock);
			return ret;
		default:
			return -EINVAL;
		}
		
	default:
		return ret -EINVAL;
	}
}	

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

	switch (mask) {
	case IIO_CHAN_INFO_SCALE:	/* 设置陀螺仪和加速度计的分辨率 */
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 设置陀螺仪 */
			mutex_lock(&dev->lock);
			ret = icm20608_write_gyro_scale(dev, val2);
			mutex_unlock(&dev->lock);
			break;
		case IIO_ACCEL:			/* 设置加速度计 */
			mutex_lock(&dev->lock);
			ret = icm20608_write_accel_scale(dev, val2);
			mutex_unlock(&dev->lock);
			break;
		default:
			ret = -EINVAL;
			break;
		}
		break;
	case IIO_CHAN_INFO_CALIBBIAS:	/* 设置陀螺仪和加速度计的校准值*/
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 设置陀螺仪校准值 */
			mutex_lock(&dev->lock);
			ret = icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH,
									    chan->channel2, val);
			mutex_unlock(&dev->lock);
			break;
		case IIO_ACCEL:			/* 加速度计校准值 */
			mutex_lock(&dev->lock);
			ret = icm20608_sensor_set(dev, ICM20_XA_OFFSET_H,
							             chan->channel2, val);
			mutex_unlock(&dev->lock);
			break;
		default:
			ret = -EINVAL;
			break;
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

/*
  * @description     	: 用户空间写数据格式,比如我们在用户空间操作sysfs来设置传感器的分辨率,
  * 					:如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是
  *						: 用来设置这个的。
  * @param - indio_dev	: iio_dev
  * @param - chan   	: 通道
  * @param - mask   	: 掩码
  * @return				: 0,成功;其他值,错误
  */
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,
				 struct iio_chan_spec const *chan, long mask)
{
	switch (mask) {
	case IIO_CHAN_INFO_SCALE:
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 用户空间写的陀螺仪分辨率数据要乘以1000000 */
			return IIO_VAL_INT_PLUS_MICRO;
		default:				/* 用户空间写的加速度计分辨率数据要乘以1000000000 */
			return IIO_VAL_INT_PLUS_NANO;
		}
	default:
		return IIO_VAL_INT_PLUS_MICRO;
	}
	return -EINVAL;
}

/*
 * iio_info结构体变量
 */
static const struct iio_info icm20608_info = {
	.read_raw		= icm20608_read_raw,
	.write_raw		= icm20608_write_raw,
	.write_raw_get_fmt = &icm20608_write_raw_get_fmt,	/* 用户空间写数据格式 */
};

/*
  * @description    : spi驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - spi  	: spi设备
  * @return  		: 0,成功;其他值,失败
  */	
static int icm20608_probe(struct spi_device *spi)
{
	int ret;
	struct icm20608_dev *dev;
	struct iio_dev *indio_dev;

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

	/* 2、获取icm20608_dev结构体地址 */
	dev = iio_priv(indio_dev); 
	dev->spi = spi;
	spi_set_drvdata(spi, indio_dev);    		/* 将indio_de设置为spi->dev的driver_data */
	mutex_init(&dev->lock);

	/* 3、iio_dev的其他成员变量 */
	indio_dev->dev.parent = &spi->dev;
	indio_dev->info = &icm20608_info;
	indio_dev->name = ICM20608_NAME;	
	indio_dev->modes = INDIO_DIRECT_MODE;	/* 直接模式,提供sysfs接口 */
	indio_dev->channels = icm20608_channels;
	indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);

	/* 4、注册iio_dev */
	ret = iio_device_register(indio_dev);
	if (ret < 0) {
		dev_err(&spi->dev, "iio_device_register failed\n");
		goto err_iio_register;
	}

	/* 5、初始化regmap_config设置 */
	dev->regmap_config.reg_bits = 8;			/* 寄存器长度8bit */
	dev->regmap_config.val_bits = 8;			/* 值长度8bit */
	dev->regmap_config.read_flag_mask = 0x80;  /* 读掩码设置为0X80,ICM20608使用SPI接口读的时候寄存器最高位应该为1 */

	/* 6、初始化SPI接口的regmap */
	dev->regmap = regmap_init_spi(spi, &dev->regmap_config);
	if (IS_ERR(dev->regmap)) {
		ret = PTR_ERR(dev->regmap);
		goto err_regmap_init;
	}

	/* 7、初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	
	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit(dev);	
	return 0;

err_regmap_init:
    iio_device_unregister(indio_dev);
err_iio_register:
	return ret;
}

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

	/* 1、删除regmap */ 
	regmap_exit(dev->regmap);

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

/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};

/* SPI驱动结构体 */
static struct spi_driver icm20608_driver = {
	.probe = icm20608_probe,
	.remove = icm20608_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "icm20608",
		   	.of_match_table = icm20608_of_match,
		   },
	.id_table = icm20608_id,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init icm20608_init(void)
{
	return spi_register_driver(&icm20608_driver);
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit icm20608_exit(void)
{
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

测试

IIO 驱动框架提供了 sysfs 接口,因此加载成功以后我们可以在用户空间访问对应的 sysfs目录项,进入目录“/sys/bus/iio/devices/”目录里面,此目录下都是 IIO 框架设备,如图所示:

从图可以看出,此时有两个 IIO 设备“iio:device0”, iio:device0 是 I.MX6ULL 内部 ADC, iio:device1 才是 ICM20608。大家进入到对应的设备目录就可以看出对应的 IIO 设备,我们进入图中的“iio:device1”目录,此目录下的内容如图所示:

从图可以看出, iio:device1 对应 spi2.0 上的设备,也就是 ICM20608,此目录下有很多文件,比如 in_accel_scale、 in_accel_x_calibias、 in_accel_x_raw 等,这些就是我们设置的通道。 in_accel_scale 就是加速度计的比例,也就是分辨率(量程), in_accel_x_calibias 就是加速度计 X 轴的校准值, in_accel_x_raw 就是加速度计的 X 轴原始值。我们在配置通道的时候,设置
了类型相同的所有通道共用 SCALE,所以这里只有一个 in_accel_scale,而 X、 Y、 Z 轴的原始值和校准值每个轴都有一个文件,陀螺仪和温度计同理。

我们来看一下图中这些文件名字组成方式,以 in_accel_x_raw 为例,这是加速度计的 X 轴原始值,驱动代码中此通道的配置内容展开以后如下(演示代码):

1     .type = IIO_ANGL_VEL,
2     .modified = 1,
3     .channel2 = IIO_MOD_X,
4     .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
5     .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
6                         BIT(IIO_CHAN_INFO_CALIBBIAS),
7     .scan_index = INV_ICM20608_SCAN_GYRO_X,
8     .scan_type = {
9         .sign = 's',
10         .realbits = 16,
11         .storagebits = 16,
12         .shift = 0,
13         .endianness = IIO_BE,
14     },

第 5 行设置了此通道有 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_CALIBBIAS 这两个专属属性,因此才会有图中的 in_accel_x_raw 和 in_accel_x_calibias 这两个文件。
通 道 属 性 的 命 名 , 也 就 是 图中 文 件 的 命 名 模 式 为 :
[direction]_[type]_[index]_[modifier]_[info_mask],我们依次来看一下这些命名组织模块:
direction:为属性对应的方向, iio_direction 结构体定义了方向,内容如下:

48 static const char * const iio_direction[] = {
49     [0] = "in",
50     [1] = "out",
51 };

可以看出,就有两个方向: in 和 out。
type: 也就是配置通道的时候 type 值, type 对应的字符可以参考 iio_chan_type_name_spec,如下:

53 static const char * const iio_chan_type_name_spec[] = {
54     [IIO_VOLTAGE] = "voltage",
55     [IIO_CURRENT] = "current",
56     [IIO_POWER] = "power",
57     [IIO_ACCEL] = "accel",
58     [IIO_ANGL_VEL] = "anglvel",
59     [IIO_MAGN] = "magn",
......
85     [IIO_GRAVITY] = "gravity",
86     [IIO_POSITIONRELATIVE] = "positionrelative",
87     [IIO_PHASE] = "phase",
88     [IIO_MASSCONCENTRATION] = "massconcentration",
89 };

所以,当通道的 type 设置为 IIO_ACCEL 的时候,对应的名字就是“accel”。
index:索引,如果配置通道的时候设置了 indexed=1,那么就会使用通道的 channel 成员变量来替代此部分命名。比如,有个 ADC 芯片支持 8 个通道,那么就可以使用 channel 来表示对
应的通道,最终在用户空间呈现的每个通道文件名的 index 部分就是通道号。
modifier:当通道的 modified 成员变量为 1 的时候, channel2 就是修饰符,修饰符对应的字符串参考结构体 iio_modifier_names,内容如下:

91 static const char * const iio_modifier_names[] = {
92     [IIO_MOD_X] = "x",
93     [IIO_MOD_Y] = "y",
94     [IIO_MOD_Z] = "z",
......
131     [IIO_MOD_PM4] = "pm4",
132     [IIO_MOD_PM10] = "pm10",
133 };

当通道的修饰符设置为 IIO_MOD_X 的时候,对应的名字就是“x”。
info_mask: 属性掩码,也就是属性,不同属性对应的字符如下所示:

136 static const char * const iio_chan_info_postfix[] = {
137     [IIO_CHAN_INFO_RAW] = "raw",
138     [IIO_CHAN_INFO_PROCESSED] = "input",
139     [IIO_CHAN_INFO_SCALE] = "scale",
140     [IIO_CHAN_INFO_OFFSET] = "offset",
141     [IIO_CHAN_INFO_CALIBSCALE] = "calibscale",
142     [IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
......
161     [IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time",
162     [IIO_CHAN_INFO_CALIBEMISSIVITY] = "calibemissivity",
163     [IIO_CHAN_INFO_OVERSAMPLING_RATIO] = "oversampling_ratio",
164 };

可以看出, IIO_CHAN_INFO_RAW 属性对应的就是“raw”, IIO_CHAN_INFO_SCALE 属性对应的是“scale”。
综上所述, in_accel_x_raw 组成形式如图所示:

读文件测试

我们读取一下 in_accel_z_raw 这个文件,这个文件是加速度计的 Z 轴原始值,静态情况下 Z 轴应该是 1g 的重力加速度计,我们可以读取 in_accel_z_raw 这个文件的值,然后在结合上面读取到的加速度计分辨率,计算一下对应的 Z 轴重力值,看看是不是 1g 左右。

2074× 0.000488281≈1.01g,此时 Z 轴重力为 1g,结果正确。

举报

相关推荐

0 条评论