STM32MP157驱动开发——Linux IIO驱动(上)


0.前言

  工业场合中有大量的模拟量和数字量之间的转换,也就是常说的 ADC 和 DAC,生活中常见的加速度计、光传感器、陀螺仪、气压计、磁力计等,这些传感器本质上都是 ADC。这些传感器的内部通常存在一个 ADC,然后对外提供 IIC 或者 SPI 接口, SoC 可以通过 IIC 或者 SPI 接口来获取到传感器内部的 ADC 数值,从而得到想要测量的结果。Linux 内核为了管理这些日益增多的 ADC 类传感器,特地推出了 IIO 子系统,更加快捷方便的开发这类传感器的驱动。

一、IIO 子系统简介

  IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,一般在搜索 IIO 子系统的时候,会发现大多数讲的都是 ADC,不过一般来说 DAC 类设备也适用。因此,当需要开发 ADC 或 DAC 类设备驱动时,可以优先考虑使用 IIO 驱动框架。

1.iio_dev 结构体

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

525 struct iio_dev {
    
    
526 	int id;
527 	struct module *driver_module;
528
529 	int modes;
530 	int currentmode;
531 	struct device dev;
532
533 	struct iio_event_interface *event_interface;
534
535 	struct iio_buffer *buffer;
536 	struct list_head buffer_list;
537 	int scan_bytes;
538 	struct mutex mlock;
539
540 	const unsigned long *available_scan_masks;
541 	unsigned masklength;
542 	const unsigned long *active_scan_mask;
543 	bool scan_timestamp;
544 	unsigned scan_index_timestamp;
545 	struct iio_trigger *trig;
546 	bool trig_readonly;
547 	struct iio_poll_func *pollfunc;
548 	struct iio_poll_func *pollfunc_event;
549
550 	struct iio_chan_spec const *channels;
551 	int num_channels;
552
553 	struct list_head channel_attr_list;
554 	struct attribute_group chan_attr_group;
555 	const char *name;
556 	const struct iio_info *info;
557 	clockid_t clock_id;
558 	struct mutex info_exist_lock;
559 	const struct iio_buffer_setup_ops *setup_ops;
560 	struct cdev chrdev;
561 };

其中有几个重要的成员变量:
第 529 行,modes 为设备支持的模式,可选择的模式如下:

模式 描述
INDIO_DIRECT_MODE 提供 sysfs 接口
INDIO_BUFFER_TRIGGERED 支持硬件缓冲触发
INDIO_BUFFER_SOFTWARE 支持软件缓冲触发
INDIO_BUFFER_HARDWARE 支持硬件缓冲区
INDIO_EVENT_TRIGGERED 支持事件触发
INDIO_HARDWARE_TRIGGERED 支持硬件触发
INDIO_ALL_BUFFER_MODES 支持所有缓冲模式
INDIO_ALL_TRIGGERED_MODES 支持所有触发模式

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

474 struct iio_buffer_setup_ops {
    
    
475 	int (*preenable)(struct iio_dev *); /* 缓冲区使能之前调用 */
476 	int (*postenable)(struct iio_dev *); /* 缓冲区使能之后调用 */
477 	int (*predisable)(struct iio_dev *); /* 缓冲区禁用之前调用 */
478 	int (*postdisable)(struct iio_dev *); /* 缓冲区禁用之后调用 */
479 	bool (*validate_scan_mask)(struct iio_dev *indio_dev,
480 	const unsigned long *scan_mask); /* 检查扫描掩码是否有效 */
481 };

可以看出 iio_buffer_setup_ops 中都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。如果未指定就默认使用 iio_triggered_buffer_setup_ops。
第 560 行,chrdev 为字符设备,由 IIO 内核创建。

2.iio_dev 申请与释放

在使用 iio 设备之前需要申请 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

使用示例:

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);
......

第 5 行,使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了 icm2060_dev 的内存。
第 10 行,使用 iio_priv 函数从 iio_dev 中提取出私有数据,也就是 icm2608_dev 这个自定义结构体变量首地址。

iio 设备的释放
如果要释放 iio_dev,需要使用 iio_device_free 函数:
原型

void iio_device_free(struct iio_dev *indio_dev)

参数
indio_dev:需要释放的 iio_dev
返回值:无
注:也可以使用 devm_iio_device_alloc 来分配 iio_dev,就由内核自己进行释放工作。

3.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:成功
其他:失败

4.iio_info

iio_dev 有个成员变量:info,为 iio_info 结构体指针变量,在编写驱动时需要着重实现,因为用户空间对设备的具体操作最终都会反映到 iio_info 中,该结构体定义在 include/linux/iio/iio.h 中,部分内容如下:

393 struct iio_info {
    
    
394 	const struct attribute_group *event_attrs;
395 	const struct attribute_group *attrs;
396
397 	int (*read_raw)(struct iio_dev *indio_dev,
398 		 struct iio_chan_spec const *chan,
399 		 int *val,
400 		 int *val2,
401 		 long mask);
......
416
417 	int (*write_raw)(struct iio_dev *indio_dev,
418 		 struct iio_chan_spec const *chan,
419 		 int val,
420 		 int val2,
421 		 long mask);
422
423 	int (*write_raw_get_fmt)(struct iio_dev *indio_dev, 
			 struct iio_chan_spec const *chan, 
			 long mask);
......
462 };

第 395 行,attrs 是通用的设备属性
第 397 和 417 行,分别为 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 是小数部分扩大整数倍的值,例:value = 1.0023,则 val1 = 1,val2 = 2300
数据的具体组合形式由 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_DB dB 数据,和 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

第 423 行的 write_raw_get_fmt 用于设置用户空间向内核空间写入的数据格式,write_raw_get_fmt 函数决定了 wtite_raw 函数中 val 和 val2 的意义。即上表中的数据组合形式。

mask:掩码,用于指定读取的是什么数据。例如 Linux 内核使用 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_SCALE 这两个宏来表示原始值以及分辨率,这两个宏就是掩码。

5.iio_chan_spec

  IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那么这个 ADC 就有 8 个通道。以之前开发的 ICM20608 传感器为例,它是一个六轴传感器,可以输出三轴陀螺仪(X、Y、Z)、三轴加速度计(X、Y、Z)和一路温度,也就是一共有 7 路数据,因此就有 7 个通道。
Linux 内核使用 iio_chan_spec 结构体来描述通道,定义在 include/linux/iio/iio.h 文件中:

236 struct iio_chan_spec {
    
    
237 	enum iio_chan_type type;
238 	int channel;
239 	int channel2;
240 	unsigned long address;
241 	int scan_index;
242 	struct {
    
    
243 		char sign;
244			u8 realbits;
245 		u8 storagebits;
246 		u8 shift;
247 		u8 repeat;
248 		enum iio_endian endianness;
249 	} scan_type;
250 	long info_mask_separate;
251 	long info_mask_separate_available;
252 	long info_mask_shared_by_type;
253 	long info_mask_shared_by_type_available;
254 	long info_mask_shared_by_dir;
255 	long info_mask_shared_by_dir_available;
256 	long info_mask_shared_by_all;
257 	long info_mask_shared_by_all_available;
258 	const struct iio_event_spec *event_spec;
259 	unsigned int num_event_specs;
260 	const struct iio_chan_spec_ext_info *ext_info;
261 	const char *extend_name;
262 	const char *datasheet_name;
263 	unsigned modified:1;
264 	unsigned indexed:1;
265 	unsigned output:1;
266 	unsigned differential:1;
267 };

其中比较重要的成员变量有以下几个:
第 237 行, type 为通道类型,iio_chan_type 是一个枚举类型,列举出了可以选择的通道类型,定义在 include/uapi/linux/iio/types.h 文件中,碍于篇幅,这里只列举几个:

14 enum iio_chan_type {
    
    
15 	IIO_VOLTAGE, /* 电压类型 */
16 	IIO_CURRENT, /* 电流类型 */
17 	IIO_POWER, /* 功率类型 */
18 	IIO_ACCEL, /* 加速度类型 */
19 	IIO_ANGL_VEL, /* 角度类型(陀螺仪) */
20 	IIO_MAGN, /* 电磁类型(磁力计) */
21 	IIO_LIGHT, /* 灯光类型 */
22 	IIO_INTENSITY, /* 强度类型(光强传感器) */
23 	IIO_PROXIMITY, /* 接近类型(接近传感器) */
24 	IIO_TEMP, /* 温度类型 */
......
50 }

可以看出 Linux 内核支持的传感器类型非常丰富,如果是 ADC,那就是 IIO_VOLTAGE 类型,如果是多轴传感器,那么就是复合类型了,其中的陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是IIO_ACCEL 类型,温度部分就是 IIO_TEMP。

第 238 行,当成员变量 indexed 为 1 时,channel 为通道索引。
第 239 行,当成员变量 modified 为 1 时,channel2 为通道修饰符。Linux 内核给出了可用的通道修饰符,定义在 include/uapi/linux/iio/types.h 文件中,部分内容如下:

52 enum iio_modifier {
    
    
53 		IIO_NO_MOD,
54 		IIO_MOD_X, /* X 轴 */
55 		IIO_MOD_Y, /* Y 轴 */
56 		IIO_MOD_Z, /* Z 轴 */
......
94 		IIO_MOD_PM10, /* PM10 */
95 		IIO_MOD_ETHANOL, /* 乙醇 */
96 		IIO_MOD_H2, /* H2 */
97 };

比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCEL,X、Y、Z 这三个轴就用 channel2的通道修饰符来区分。IIO_MOD_X、IIO_MOD_Y、IIO_MOD_Z 就分别对应 X、Y、Z 这三个轴。通道修饰符主要是影响 sysfs 下的通道文件名字。

第 240 行的 address 用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。比如 ICM20608 的加速度计 X 轴这个通道首地址为 0x3B。也可以不使用,以实际为准。
第 241 行,当使用触发缓冲区时,scan_index 是扫描索引。
第 242~249,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(小端)。

第 250 行,info_mask_separate 标记某些属性专属于此通道,include/linux/iio/types.h 文件中的 iio_chan_info_enum 枚举类型描述了可选的属性值:

34 enum iio_chan_info_enum {
    
    
35 		IIO_CHAN_INFO_RAW = 0,
36 		IIO_CHAN_INFO_PROCESSED,
37 		IIO_CHAN_INFO_SCALE,
38 		IIO_CHAN_INFO_OFFSET,
......
57 		IIO_CHAN_INFO_DEBOUNCE_TIME,
58 		IIO_CHAN_INFO_CALIBEMISSIVITY,
59 		IIO_CHAN_INFO_OVERSAMPLING_RATIO,
60 };

比如 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 属性是相互独立的。
第 251 行,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 下就会只生成一个描述分辨率的文件,由三个通道共用。
第 254 行,info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。
第 256 行,info_mask_shared_by_all 表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。
第 263 行,modified 为 1 的时候,channel2 为通道修饰符。
第 264 行,indexed 为 1 的时候,channel 为通道索引。
第 265 行,output 表示为输出通道。
第 266 行,differential 表示为差分通道。

二、驱动开发

  读完了上边一大串的属性解析,那么就来实操一把驱动开发,直接使用 IIO 驱动框架编写 ICM20608 驱动,ICM20608 驱动核心就是 SPI,上一节实现了如何在 SPI 总线上使用 regmap,这一节就为其套上 IIO 驱动框架。

1. ICM20608 的 IIO 驱动框架搭建

由于实际的物理接口没有修改,所以设备树文件也不需要修改。
基本驱动框架:(以 SPI 框架为例)

static int xxx_probe(struct spi_device *spi)
{
    
    
	return 0;
}

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

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

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

/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {
    
    
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
    
    
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};

/*驱动入口函数*/
static int __init xxx_init(void)
{
    
    
	return spi_register_driver(&xxx_driver);
}

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

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("amonter");

也就是标准的 SPI 驱动框架,如果使用 IIC 接口就用 IIC 的驱动框架。

2.IIO 设备申请与初始化

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

/* 自定义设备结构体 */
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[] = {
    
    
	/*channels*/
};
/*读函数,当读取 sysfs 中的文件时最终此函数会执行
 *从传感器中读取数据,上报给应用 
 */
static int xxx_read_raw(struct iio_dev *indio_dev, 
						struct iio_chan_spec const *chan, 
						int *val, int *val2, long mask)
{
    
    
	return 0;
}

/*写函数,当向 sysfs 中的文件写数据的时候最终此函数会执行
 *一般在此函数里面设置传感器,比如量程等
 */
static int xxx_write_raw(struct iio_dev *indio_dev,
						struct iio_chan_spec const *chan,
						int val, int val2, long mask)
{
    
    
	return 0;
}

/*用户空间写数据格式,用来设置 val2 小数部分的放大倍数*/
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;
}

3.基于以上驱动框架开发 ICM20608 的 IIO 驱动

Iio_icm20608.c:

#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 "icm20608.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/trigger_consumer.h>
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/unaligned/be_byteshift.h>
#include <linux/iio/trigger.h>

#define ICM20608_NAME "icm20608"
#define ICM20608_TEMP_OFFSET	     	0
#define ICM20608_TEMP_SCALE		     	326800000
#define ICM20608_OUTPUT_DATA_SIZE    	14	
#define ICM20608_BIT_FIFO_OVERFLOW_INT  0x10
#define ICM20608_BIT_RAW_DATA_RDY_INT   0x01

#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;
	struct iio_trigger *trig;
};

/*
 * 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),
    ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y,INV_ICM20608_SCAN_GYRO_Y),
    ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z,INV_ICM20608_SCAN_GYRO_Z),

    ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),
    ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),
    ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),
};

/*
 * 扫描掩码,两种情况,全启动0X1111111,或者都不启动0X0
 */
static const unsigned long icm20608_scan_masks[] = {
    
    
	BIT(INV_ICM20608_SCAN_ACCL_X)
	| BIT(INV_ICM20608_SCAN_ACCL_Y)
	| BIT(INV_ICM20608_SCAN_ACCL_Z)
	| BIT(INV_ICM20608_SCAN_GYRO_X)
	| BIT(INV_ICM20608_SCAN_GYRO_Y)
	| BIT(INV_ICM20608_SCAN_GYRO_Z)
	| BIT(INV_ICM20608_SCAN_TEMP),
	0,
};

/*读取 icm20608 指定寄存器值,读取一个寄存器*/
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;
}

/*向 icm20608 指定寄存器写入指定的值,写一个寄存器*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    
    
	regmap_write(dev->regmap,  reg, value);
}

/*ICM20608内部寄存器初始化函数*/
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溢出以及数据就绪中断	*/
}

/*设置ICM20608传感器,可以用于陀螺仪、加速度计设置*/
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;
}

/*读取ICM20608传感器数据,可以用于陀螺仪、加速度计、温度的读取*/
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;
}

/*读取ICM20608陀螺仪、加速度计、温度通道值*/
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;
}

/*设置ICM20608的陀螺仪计量程(分辨率)*/
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;
}

/*设置ICM20608的加速度计量程(分辨率)*/
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;
}

/*read_raw 函数*/
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加速度计、陀螺仪、温度传感器原始值 */
		iio_device_claim_direct_mode(indio_dev);		/* 保持direct模式 	*/
		mutex_lock(&dev->lock);								/* 上锁 			*/
		ret = icm20608_read_channel_data(indio_dev, chan, val); 	/* 读取通道值 */
		mutex_unlock(&dev->lock);							/* 释放锁 			*/
		iio_device_release_direct_mode(indio_dev);			/* 释放direct模式 	*/
		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;
	}
}

/*write_raw 函数*/
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;

	iio_device_claim_direct_mode(indio_dev);		/* 保持direct模式 	*/
	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;
	}

	iio_device_release_direct_mode(indio_dev);			/* 释放direct模式 	*/
	return ret;
}

/*设置数据格式,即小数部分的放大倍数*/
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,	/* 用户空间写数据格式 */
};

/*触发器下半部函数,从启动的通道里面读取数据,然后把这些数据送入缓冲区*/
irqreturn_t icm20608_trigger_handler(int irq, void *p)
{
    
    
	int ret = 0;
	struct iio_poll_func *pf = p;
	struct iio_dev *indio_dev = pf->indio_dev;
	struct icm20608_dev *dev = iio_priv(indio_dev);
	u8 data[ICM20608_OUTPUT_DATA_SIZE];
	int int_status = 0;

	mutex_lock(&dev->lock);

	/* 判断数据是否准备就绪 */
	ret = regmap_read(dev->regmap, ICM20_INT_STATUS, &int_status);
	if (!(int_status & ICM20608_BIT_RAW_DATA_RDY_INT)) {
    
    
		printk("spurious interrupt with status 0x%x\n", int_status);
		goto end_session;
	}

	ret = regmap_bulk_read(dev->regmap, ICM20_ACCEL_XOUT_H, data, 14); /* 一次读取14字节的数据 */
	if (ret)
			goto end_session;
	iio_push_to_buffers_with_timestamp(indio_dev, data, pf->timestamp);

end_session:
	mutex_unlock(&dev->lock);
	iio_trigger_notify_done(indio_dev->trig);
	return IRQ_HANDLED;
}

/*中断服务函数,对于iio触发器来说,一般在此处直接调用iio_trigger_poll*/
irqreturn_t iio_trigger_generic_data_rdy_poll(int irq, void *private)
{
    
    
	iio_trigger_poll(private);
	return IRQ_HANDLED;
}

/*触发器开关,一般在此函数中实现设备的打开和关闭操作*/
static int icm20608_trigger_set_state(struct iio_trigger *trig,
					      bool state)
{
    
    
	int ret;
	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
	struct icm20608_dev *dev = iio_priv(indio_dev);

	mutex_lock(&dev->lock);
	if (state) {
    
    
		ret = regmap_write(dev->regmap, ICM20_INT_ENABLE, 0x01);/* 使能数据就绪中断	*/
	} else {
    
    
		ret = regmap_write(dev->regmap, ICM20_INT_ENABLE, 0x00);/* 关闭数据就绪中断	*/
	}
	mutex_unlock(&dev->lock);
	return ret;
}

/*触发器操作函数集*/
static const struct iio_trigger_ops icm20608_trigger_ops = {
    
    
	.set_trigger_state = &icm20608_trigger_set_state,
};

/*probe函数*/
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 */

	/* 2、初始化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 */

	/* 3、初始化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;
	}	

	mutex_init(&dev->lock);

	/* 4、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);
	indio_dev->available_scan_masks = icm20608_scan_masks;

	/* 5、触发缓冲区设置 */
	ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
						 iio_pollfunc_store_time,
						 icm20608_trigger_handler,
						 NULL);

	/* 6、申请trigger,并初始化 */
	dev->trig = devm_iio_trigger_alloc(&indio_dev->dev,
					  "%s-dev%d",
					  indio_dev->name,
					  indio_dev->id);
	if (!dev->trig){
    
    
		ret = -ENOMEM;
		goto err_iio_trriger_alloc;
	}

	dev->trig->dev.parent = regmap_get_device(dev->regmap);
	dev->trig->ops = &icm20608_trigger_ops;
	iio_trigger_set_drvdata(dev->trig, indio_dev);

	/* 7、向内核注册触发器 */
	ret = devm_iio_trigger_register(&indio_dev->dev, dev->trig);
	indio_dev->trig = iio_trigger_get(dev->trig);

	/* 8、ICM20608中断初始化 */
	ret = devm_request_irq(&indio_dev->dev, spi->irq,
			       &iio_trigger_generic_data_rdy_poll,
			       IRQF_TRIGGER_HIGH,
			       "inv_mpu",
			       dev->trig);

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

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

	return 0;

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

/*remove函数(SPI框架的remove)*/
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,
};

/*驱动入口函数*/
static int __init icm20608_init(void)
{
    
    
	return spi_register_driver(&icm20608_driver);
}
/*驱动出口函数*/
static void __exit icm20608_exit(void)
{
    
    
	spi_unregister_driver(&icm20608_driver);
}

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

①通道宏定义 ICM20608_CHAN 用于陀螺仪和加速度计,modified 成员变量为 1,所以 channel2 就是通道修饰符,用来指定 X、Y、Z 轴。
②设置相同类型的通道 IIO_CHAN_INFO_SCALE 属性相同,“scale”是比例的意思,在这里就是量程(分辨率)。陀螺仪和加速度计的量程可以调整,但是每个传感器的三个轴是一起设置的。
③设置每个通道的 IIO_CHAN_INFO_RAWIIO_CHAN_INFO_CALIBBIAS,这两个属性都是独立的,IIO_CHAN_INFO_RAW 表示 ICM20608 每个通道的原始值,IIO_CHAN_INFO_CALIBBIAS 是 ICM20608 每个通道的校准值,这个是特性,以实际所使用的传感器为准。
.scan_type属性设置扫描数据类型,是 ICM20608 原始数据类型。由于陀螺仪和加速度计都是 16 位的 ADC,因此这里是通用的:为有符号类型、实际位数 16bit,存储位数 16bit,大端模式(ICM20608 数据寄存器为大端模式)。
⑤自定义的扫描索引枚举类型 inv_icm20608_scan,包括陀螺仪、加速度计的 6 个通道,温度计的 1 个通道、以及 1 个 ICM20608 时间戳通道。
⑥设备结构体,由于采用了 regmap 和 IIO 框架,因此成员变量相对 SPI 的设备来说非常简单。
icm20608_channels定义了 7 个通道,1 个温度通道,3 个陀螺仪通道(X、Y、Z),3 个加速度计通道(X、Y、Z)。温度通道有三个属性,IIO_CHAN_INFO_RAW 为温度通道的原始值,IIO_CHAN_INFO_OFFSET 是 ICM20608 温度 offset 值(需查阅手册),IIO_CHAN_INFO_SCALE 是 ICM20608 的比例,也就是一个单位的原始值为多少 ℃(需查阅手册)。三个属性即为原始值、offset 值、比例值,应用程序需要能够从 IIO 驱动框架中读取这三个值。剩下的陀螺仪和加速度计通道设置使用宏 ICM20608_CHAN 即可。
iio_info结构体,icm20608_read_raw 为读取函数,icm20608_write_raw 为写函数,用于应用程序对文件读写数据。icm20608_write_raw_get_fmt 函数用来设置应用程序向驱动写入的数据格式,icm20608_info 就是具体的 iio_info 变量,初始化 iio_dev 的时候需要用到。
icm20608_probe 函数,完成申请 iio_dev、初始化并注册,初始化 regmap、初始化 ICM20608 等步骤,然后使用 devm_iio_device_alloc 函数申请 iio_dev 以及自定义设备结构体内存,最后调用 iio_device_register 函数想内核注册 iio_dev。

三、测试

  驱动的开发流程还是比较复杂的,除了基本的 SPI 驱动框架以外,还加入了 IIO 框架,将一些对设备寄存器的操作分离出来,以便完成不同物理总线的适配。因此也引入了大量的结构体对象和操作函数、掩码等,需要仔细消化一下。

  驱动编写完成后,就按照正常的驱动编译流程编译出 .ko 文件,放入开发板进行测试。IIO 驱动框架提供了 sysfs 接口,因此加载成功以后可以在用户空间访问对应的 sysfs 目录项。在“/sys/bus/iio/devices/”目录下都是 IIO 框架设备:
在这里插入图片描述
trigger0为触发器目录,下文会介绍。
iio:device0 就是 ICM20608,进入该目录:
在这里插入图片描述
可以看出,此 iio:device0 对应 spi0.0 上的设备,此目录下存在 in_accel_scale、in_accel_x_calibias、in_accel_x_raw等文件,这些就是驱动中设置的通道。在配置通道时,设置了类型相同的所有通道共用 SCALE,所以这里只有一个 in_accel_scale,而 X、Y、Z 轴的原始值和校准值每个轴都有一个文件,陀螺仪和温度计同理。

通道文件命名方式,以 in_accel_x_raw 为例,这是加速度计的 X 轴原始值:
在这里插入图片描述

1.驱动中涉及的相关函数

在编写测试App之前,再看一下驱动程序中的相关函数:

  • icm20608_sensor_show 函数用于读取加速度计、陀螺仪、温度的原始数据
  • icm20608_read_channel_data 函数用于读取指定通道的数据,根据 type 类型的不同,给 icm20608_sensor_show 函数传递不同的参数,读取陀螺仪、加速度计或温度数据
  • icm20608_read_raw 函数,应用程序所有的读取操作,最终都会汇总到此函数,由type区分传感器类型
  • 用户空间向驱动写数据的时候icm20608_write_raw函数会执行,可以在用户空间设置陀螺仪、加速度计的量程、校准值等
  • icm20608_sensor_set 函数用于设置指定通道,也就是向 ICM20608 的指定寄存器写入数据,用于设置陀螺仪和加速度计的校准值。
  • icm20608_write_gyro_scale 函数用于设置 ICM20608 陀螺仪量程
  • icm20608_write_accel_scale 函数用于设置 ICM20608 加速度计量程
  • icm20608_write_raw_get_fmt 函数,也就是 iio_info 的 write_raw_get_fmt 函数。此函数用来指定用户空间写入的数据格式,即小数部分的放大倍数、整数和小数的组成方式等

2.linux文件流读取

  如果要连续不断的读取传感器数据,就不能用cat命令了。此外,以 in_accel_scale 文件中的内容为例,in_accel_scale 文件内容为 0.000488281,为字符串格式,需要转换为对应的数字。另外 in_accel_scale 是流文件,也叫做标准文件 I/O 流,因此打开、读写操作要使用文件流操作函数。

fopen函数

原型

FILE *fopen(const char *pathname, const char *mode)

功能:打开文件流
参数
pathname:需要打开的文件流路径
mode:打开方式,主要有以下几种

mode 描述
r 打开只读文件
r+ 打开读写文件
w 打开只写文件,如文件存在则文件长度清零,如文件不存在就自动创建,文件流指针调整到文件头部
w+ 打开可读写文件,如文件存在则文件长度清零,如文件不存在就自动创建,文件流指针调整到文件头部
a 以追加的方式打开只写文件,如果文件不存在就新建文件,如果存在就将数据追加到文件末尾
a+ 以追加的方式打开读写文件,如果文件不存在就新建文件,如果存在就将数据追加到文件末尾

返回值
NULL:打开错误
其他值:打开成功的文件流指针,为 FILE 类型

fclose函数

原型

int fclose(FILE *stream)

功能:关闭文件流
参数
stream:要关闭的文件流指针
返回值
0:关闭成功
EOF:关闭错误

fread函数

原型

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

功能:读取文件流
参数
ptr:要读取的数组中首个对象的指针
size:每个对象的大小
nmemb:要读取的对象个数
stream:要读取的文件流
返回值
返回读取成功的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值(或者 0)。

fwrite函数

原型

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

功能:向文件流写入数据
参数
ptr:要写入的数组中首个对象的指针
size:每个对象的大小
nmemb:要写入的对象个数
stream:要写入的文件流
返回值
返回成功写入的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值(或者 0)

fscanf函数

原型

int fscanf(FILE *stream, const char *format, ,[argument...])

功能
从一个文件流中格式化读取数据,遇到空格和换行符时结束
参数
stream:要操作的文件流
format:格式
argument:保存读取到的数据
返回值
成功读取到的数据个数, 如果读到文件末尾或者读取错误就返回 EOF。

3.测试App

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
#define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\
	ret = file_data_read(file_path[index], str);\
	dev->member = atof(str);\
	
/* 字符串转数字,将整数字符串转换为整数数值 */
#define SENSOR_INT_DATA_GET(ret, index, str, member)\
	ret = file_data_read(file_path[index], str);\
	dev->member = atoi(str);\

/* icm20608 iio框架对应的文件路径 */
static char *file_path[] = {
    
    
	"/sys/bus/iio/devices/iio:device0/in_accel_scale",
	"/sys/bus/iio/devices/iio:device0/in_accel_x_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_accel_x_raw",
	"/sys/bus/iio/devices/iio:device0/in_accel_y_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_accel_y_raw",
	"/sys/bus/iio/devices/iio:device0/in_accel_z_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_accel_z_raw",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_scale",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_x_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_x_raw",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_y_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_y_raw",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_z_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_z_raw",
	"/sys/bus/iio/devices/iio:device0/in_temp_offset",
	"/sys/bus/iio/devices/iio:device0/in_temp_raw",
	"/sys/bus/iio/devices/iio:device0/in_temp_scale",
};

/* 文件路径索引,要和file_path里面的文件顺序对应 */
enum path_index {
    
    
	IN_ACCEL_SCALE = 0,
	IN_ACCEL_X_CALIBBIAS,
	IN_ACCEL_X_RAW,
	IN_ACCEL_Y_CALIBBIAS,
	IN_ACCEL_Y_RAW,
	IN_ACCEL_Z_CALIBBIAS,
	IN_ACCEL_Z_RAW,
	IN_ANGLVEL_SCALE,
	IN_ANGLVEL_X_CALIBBIAS,
	IN_ANGLVEL_X_RAW,
	IN_ANGLVEL_Y_CALIBBIAS,
	IN_ANGLVEL_Y_RAW,
	IN_ANGLVEL_Z_CALIBBIAS,
	IN_ANGLVEL_Z_RAW,
	IN_TEMP_OFFSET,
	IN_TEMP_RAW,
	IN_TEMP_SCALE,
};

/*
 * icm20608数据设备结构体
 */
struct icm20608_dev{
    
    
	int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;
	int accel_x_raw, accel_y_raw, accel_z_raw;

	int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;
	int gyro_x_raw, gyro_y_raw, gyro_z_raw;

	int temp_offset, temp_raw;

	float accel_scale, gyro_scale, temp_scale;

	float gyro_x_act, gyro_y_act, gyro_z_act;
	float accel_x_act, accel_y_act, accel_z_act;
	float temp_act;
};

struct icm20608_dev icm20608;

 /*
 * @description			: 读取指定文件内容
 * @param - filename 	: 要读取的文件路径
 * @param - str 		: 读取到的文件字符串
 * @return 				: 0 成功;其他 失败
 */
static int file_data_read(char *filename, char *str)
{
    
    
	int ret = 0;
	FILE *data_stream;

    data_stream = fopen(filename, "r"); /* 只读打开 */
    if(data_stream == NULL) {
    
    
		printf("can't open file %s\r\n", filename);
		return -1;
	}

	ret = fscanf(data_stream, "%s", str);
    if(!ret) {
    
    
        printf("file read error!\r\n");
    } else if(ret == EOF) {
    
    
        /* 读到文件末尾的话将文件指针重新调整到文件头 */
        fseek(data_stream, 0, SEEK_SET);  
    }
	fclose(data_stream);	/* 关闭文件 */	
	return 0;
}

 /*
 * @description	: 获取ICM20608数据
 * @param - dev : 设备结构体
 * @return 		: 0 成功;其他 失败
 */
static int sensor_read(struct icm20608_dev *dev)
{
    
    
	int ret = 0;
	char str[50];

	/* 1、获取陀螺仪原始数据 */
	SENSOR_FLOAT_DATA_GET(ret, IN_ANGLVEL_SCALE, str, gyro_scale);
	SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_X_RAW, str, gyro_x_raw);
	SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Y_RAW, str, gyro_y_raw);
	SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Z_RAW, str, gyro_z_raw);

	/* 2、获取加速度计原始数据 */
	SENSOR_FLOAT_DATA_GET(ret, IN_ACCEL_SCALE, str, accel_scale);
	SENSOR_INT_DATA_GET(ret, IN_ACCEL_X_RAW, str, accel_x_raw);
	SENSOR_INT_DATA_GET(ret, IN_ACCEL_Y_RAW, str, accel_y_raw);
	SENSOR_INT_DATA_GET(ret, IN_ACCEL_Z_RAW, str, accel_z_raw);

	/* 3、获取温度值 */
	SENSOR_FLOAT_DATA_GET(ret, IN_TEMP_SCALE, str, temp_scale);
	SENSOR_INT_DATA_GET(ret, IN_TEMP_OFFSET, str, temp_offset);
	SENSOR_INT_DATA_GET(ret, IN_TEMP_RAW, str, temp_raw);

	/* 3、转换为实际数值 */
	dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;
	dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;
	dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;

	dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;
	dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;
	dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;

	dev->temp_act = ((dev->temp_raw - dev->temp_offset) / dev->temp_scale) + 25;
	return ret;
}

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    
    
	int ret = 0;

	if (argc != 1) {
    
    
		printf("Error Usage!\r\n");
		return -1;
	}

	while (1) {
    
    
		ret = sensor_read(&icm20608);
		if(ret == 0) {
    
     			/* 数据读取成功 */
			printf("\r\n原始值:\r\n");
			printf("gx = %d, gy = %d, gz = %d\r\n", icm20608.gyro_x_raw, icm20608.gyro_y_raw, icm20608.gyro_z_raw);
			printf("ax = %d, ay = %d, az = %d\r\n", icm20608.accel_x_raw, icm20608.accel_y_raw, icm20608.accel_z_raw);
			printf("temp = %d\r\n", icm20608.temp_raw);
			printf("实际值:");
			printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", icm20608.gyro_x_act, icm20608.gyro_y_act, icm20608.gyro_z_act);
			printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", icm20608.accel_x_act, icm20608.accel_y_act, icm20608.accel_z_act);
			printf("act temp = %.2f°C\r\n", icm20608.temp_act);
		}
		usleep(100000); /*100ms */
	}

	return 0;
}

①SENSOR_FLOAT_DATA_GET 宏用读取指定路径的文件内容,然后将读到的浮点型字符串数据转换为具体的浮点数据,调用 atof 函数。
②SENSOR_INT_DATA_GET 宏用于读取指定路径的文件内容,将读取到的整数型字符串数据转换为具体的整数值,调用 atoi 函数。
③path_index 为文件路径索引,顺序要和 file_path 里面的文件路径对应
④file_data_read 函数用于读取指定文件,第一个参数 filename 是要读取的文件路径。第二个参数 str 为读取到的文件内容,为字符串类型
⑤ fopen 函数打开指定的文件流,然后用 fscanf 函数进行格式化读取,当读取到文件末尾的时候,调用 fseek 函数将读取指针调整到文件头,以备下次重新读取。最后使用 fclose 关闭文件流。
⑥sensor_read 函数用于读取 ICM20608 传感器数据,包括陀螺仪、加速度和温度计的原始值,还有加速度计和陀螺仪的分辨率等,最后将获取到的原始值转换为具体的数值

使用交叉编译链编译出测试App程序,放入开发板中,就可以进行测试。
在这里插入图片描述

Supongo que te gusta

Origin blog.csdn.net/weixin_45682654/article/details/128571696
Recomendado
Clasificación