1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Linux学习笔记(22.1)——基于SPI + Regmap + IIO的ICM20608设备驱动

Linux学习笔记(22.1)——基于SPI + Regmap + IIO的ICM20608设备驱动

时间:2024-06-10 08:43:14

相关推荐

Linux学习笔记(22.1)——基于SPI + Regmap + IIO的ICM20608设备驱动

Regmap API 简介

regmap API的引入

​ Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样的,通过 I2C/SPI 接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如 I.MX6ULL的 PWM、定时器等外设初始化,最终都是要落到寄存器的设置上。

​ Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器, SPI 接口的话使用 spi_write/spi_read等。 I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余代码,再者,代码的复用性也会降低。比如 icm20608 这个芯片既支持 I2C 接口,也支持 SPI 接口。假设我们在产品设计阶段一开始将 icm20608 设计为 SPI 接口,但是后面发现 SPI 接口不够用,或者 SOC 的引脚不够用,我们需要将 icm20608 改为 I2C 接口。这个时候 icm20608 的驱动就要大改,我们需要将 SPI 接口函数换为 I2C 的,工作量比较大。

​ 基于代码复用的原则, Linux 内核引入了 regmap 模型, regmap 将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmap API函数。这样的好处就是统一使用 regmap,降低了代码冗余, 提高了驱动的可以移植性。 regmap模型的重点在于:

​ 通过 regmap 模型提供的统一接口函数来访问器件的寄存器, SOC 内部的寄存器也可以使用 regmap 接口函数来访问。

​ regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。另外, regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低。

什么情况下会使用 regmap:

① 硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。

② 提高代码复用性和驱动一致性,简化驱动开发过程。

③ 减少底层 I/O 操作次数,提高访问效率。

regmap框架结构

​ regmap驱动框架如下图所示:

regmap 框架分为三层:

① 底层物理总线: regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总线有 i2c、 i3c、 spi、 mmio、 sccb、 sdw、 slimbus、 irq、 spmi 和 w1。

② regmap 核心层,用于实现 regmap,我们不用关心具体实现。

③ regmap API 抽象层, regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。

使用regmap API编程

regmap API比较简单,只需要了解几个结构即可。此API中的两个重要结构是struct regmap_config(代表regmap配置)和struct regmap(regmap实例本身)。

regmap_config结构

struct regmap_config在驱动程序生命周期中存储regmap配置,这里的设置会影响读/写操作,它是regmap API中最重要的结构。其源代码如下:

struct regmap_config {const char *name;int reg_bits;/* 这个必填成员是寄存器地址中的位数 */int reg_stride;int pad_bits;int val_bits;/* 表示用于存储寄存器的位数, 这是一个必填成员 *//* 可选的回调函数。如果提供,则在需要写入/读取寄存器时供regmap子系统使用。在写入/读取寄存器之前,* 会自动调用该函数来检查寄存器是否可以写入/读取 */bool (*writeable_reg)(struct device *dev, unsigned int reg); bool (*readable_reg)(struct device *dev, unsigned int reg);/* 回调函数。每当需要通过regmap缓存读取或写入寄存器时调用它。如果寄存器是易失的,那么函数应该返回* true,然后对寄存器执行直接读写。如果返回false,则表示寄存器可缓存。在这种情况下,缓存将用于读取* 操作,并且在写入操作时写入缓存 */bool (*volatile_reg)(struct device *dev, unsigned int reg);bool (*precious_reg)(struct device *dev, unsigned int reg);regmap_lock lock;regmap_unlock unlock;void *lock_arg;/* 设备可能不支持简单的I2C/SPI读取操作。除了自己编写read函数外,别无选择。这时read_reg应该指向* 那个函数,大多数设备不需要这样。*/int (*reg_read)(void *context, unsigned int reg, unsigned int *val);/* 与reg_read相同,但针对写入操作 */int (*reg_write)(void *context, unsigned int reg, unsigned int val);bool fast_io;/* 可选的,它指定最大的有效寄存器地址,在该寄存器地址上不允许进行任何操作。 */unsigned int max_register;/* 不提供writeable_reg回调时,可以提供regmap_access_table,该结构体包含yes_range和* no_range成员,两者都指向struct regmap_range。任何属于yes_range项的寄存器都被认为* 是可写入的,如果属于no_range,则被认为是不可写入的。 */const struct regmap_access_table *wr_table;const struct regmap_access_table *rd_table; /* 与wr_table相同,但针对所有读操作 *//* 代替volatile_reg,可以提供volatile_table。原理与wr_table或rd_table相同,但针对缓存* 机制。 */const struct regmap_access_table *volatile_table;const struct regmap_access_table *precious_table;const struct reg_default *reg_defaults;unsigned int num_reg_defaults;enum regcache_type cache_type;const void *reg_defaults_raw;unsigned int num_reg_defaults_raw;u8 read_flag_mask;u8 write_flag_mask;bool use_single_rw;bool can_multi_write;enum regmap_endian reg_format_endian;enum regmap_endian val_format_endian;const struct regmap_range_cfg *ranges;unsigned int num_ranges;};

以下是regmap_config的一种初始化:

static const struct regmap_config bmp280_regmap_config = {.reg_bits = 8,.val_bits = 8,.max_register = BMP280_REG_TEMP_XLSB,.cache_type = REGCACHE_RBTREE,.writeable_reg = bmp280_is_writeable_reg,.volatile_reg = bmp280_is_volatile_reg,};

regmap初始化

​ regmap API支持SPI和I2C协议。根据驱动程序中需要支持的协议不同,probe函数中必须调用regmap_init_i2c()或regmap_init_spi()。要编写通用驱动程序,regmap是最佳选择。

regmap API是通过和同质的。SPI和I2C两种总线类型之间只有初始化不同,其他功能完全相同。

始终在probe函数中初始化regmap,并且在初始化regmap之前,必须填充regmap_config元素,这是一个良好的习惯。

无论分配的是I2C还是SPI寄存器映射,都用regmap_exit函数释放:

void regmap_exit(struct regmap *map)

该函数只是释放先前分配的寄存器映射。

I2C regmap初始化包括在regmap config上调用regmap_init_i2c(),这将配置regmap,以便在内部将所有设备访问转换为I2C命令:

struct regmap *regmap_init_i2c(struct i2c_client *i2c,const struct regmap_config *config)

该函数成功时返回指向分配的struct regmap的指针,错误时返回的值是EER_PTR()。

完整的例子如下:

static int bmp280_probe(struct i2c_client *client,const struct i2c_device_id *id){int ret;struct bmp280_data *data;unsigned int chip_id;...data->regmap = devm_regmap_init_i2c(client, &bmp280_regmap_config);if (IS_ERR(data->regmap)) {dev_err(&client->dev, "failed to allocate register map\n");return PTR_ERR(data->regmap);}ret = regmap_read(data->regmap, BMP280_REG_ID, &chip_id);if (ret < 0)return ret;if (chip_id != BMP280_CHIP_ID) {dev_err(&client->dev, "bad chip id. expected %x got %x\n",BMP280_CHIP_ID, chip_id);return -EINVAL;}ret = bmp280_chip_init(data);if (ret < 0)return ret;...}

设备访问函数

API处理数据解析、格式化和传输。在大多数情况下,设备访问通过regmap_read、regmap_write和regmap_update_bits来执行。这些是在向设备存储数据/从设备读取数据时应该始终记住的3个重要的函数。它们各自的原型如下:

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);

regmap_write:将数据写入设备。如果在regmap_config、max_register内设置过,将用它检查需要读取的寄存器地址是更大还是更小。如果传递的寄存器地址小于等于max_register,则写操作会执行;否则,regmap内核将返回无效I/O错误(-EIO)。之后立即调用函数writeable_reg。该回调函数在执行下一步操作前必须返回true。如果返回false,则返回-EIO,与操作停止。如果设置了wr_table,而不是writeable_reg,则结果如下。

如果寄存器地址在no_range内,则返回-EIO。如果寄存器地址在yes_range内,则执行下一步。如果寄存器地址不在no_range内,也不在yes_range内,则返回-EIO,操作中断。如果cache_type != REGCACHE_NONE,则启用缓存。在这种情况下,首先更新缓存项,之后执行到硬件的写操作,否则不执行缓存动作。如果提供了回调函数reg_write,则用它执行写入操作,将执行通用的regmap写功能。

regmap_read:从设备读取数据。其在相应数据结构(readable_reg和rd_table)上执行的功能类似于regmap_write。因此,如果提供了reg_read,则使用它执行读取操作,否则将执行通用的regmap读取函数。

regmap_update_bits:是一个三合一函数。它在寄存器映射上执行读/修改/写周期。

regmap使用总结

执行以下步骤来设置regmap子系统:

根据设备的特性设置struct regmap_config结构体。如果需要,可以设置寄存器范围,如果有的话,则设置为默认值;如果需要,还可以设置cache_type,等等。如果需要自定义读/写函数,请将它们传递给reg_read/reg_write成员。在probe函数中,根据总线是I2C还是SPI,使用regmap_init_i2c或regmap_init_spi分配regmap。需要读取/写入寄存器时,请调用regmap_[read | write]函数。完成regmap操作后,调用regmap_exit释放probe中分配的寄存器映射。

IIO子系统简介

IIO子系统的引入

​ 工业I/O(Industrial I/O,IIO)是专用于模数转换器(ADC)和数模转换器(DAC)的内核子系统。随着分散在内核源代码上由不同代码实现的传感器(具有模拟到数字或数字到模拟转换功能的测量设备)数量的增加,集中它们变得必要。这就是IIO子系统框架所要实现的功能,它以通用一致的方式来实现。乔纳森-卡梅隆Linux-IIO社区从开始开发它。

​ 加速度计、陀螺仪、电流/电压测量转换芯片、光传感器、压力传感器等都属于IIO系列设备。

​ IIO模型基于设备和通道架构。

设备代表芯片本身,它位于整个层次结构的顶层。通道表示设备的单个采集线,设备可能有一个或多个通道。例如,加速度计是具有3个通道的设备,每个轴(X、Y和Z)都有一个通道。

​ IIO芯片是物理和硬件传感器/转换器,它作为字符设备提供给用户空间(当支持触发缓冲时)和sysfs目录项,该目录中包含一组文件,其中一些代表通道。单个通道用单个sysfs文件项表示。

​ 下面是从用户空间与IIO驱动程序进行交互的两种方式。

/sys/bus/iio/iio:deviceX/: 代表传感器及其通道。/dev/iio:deviceX: 字符设备,用于输出设备事件和数据缓冲区。

​ 上图显示IIO框架在内核和用户空间的组织方式。该驱动程序使用IIO内核提供的功能和API来管理硬件,并向IIO内核报告处理情况。然后,IIO子系统通过sysfs接口和字符设备将整个底层机制抽象到用户空间,用户可以在其上执行系统调用。

​ IIO API分布在几个头文件中,如下所示:

#include <linux/iio/iio.h>/* 强制性的 */#include <linux/iio/sysfs.h>/* 因为使用了sysfs,所以是强制性的 */#include <linux/iio/buffer.h>/* 强制使用触发缓冲区 */#include <linux/iio/trigger.h>/* 仅当在驱动程序中实现触发器(很少使用)时 */#include <linux/iio/event.h>/* 对于高级用户,管理IIO事件 */

IIO数据结构

IIO设备在内核中表示为struct iio_dev的实例,并由struct iio_info结构体描述。所有重要的IIO结构都在include/linux/iio/iio.h文件中定义。

iio_dev数据结构

iio_dev代表IIO设备,描述设备和驱动程序。它提供以下信息:

设备上有多少个通道可用?设备可运行在哪些模式下:单次模式还是触发缓冲区?该驱动程序有哪些钩子可用?

struct iio_dev {....../* 表示设备支持的不同模式。支持的模式如下:* INDIO_DIRECT_MODE 设备提供sysfs类型的接口* INDIO_BUFFER_TRIGGERED 设备支持硬件触发。当使用iio_triggered_buffer_setup()函数设置* 触发缓冲区时,该模式会自动添加到设备。* INDIO_BUFFER_SOFTWARE 设备具有硬件区。* INDIO_BUFFER_HARDWARE 上述两种模式的组合。*/intmodes;intcurrentmode;/* 设备实际使用的模式 */struct devicedev;/* IIO设备绑定的struct device(根据Linux设备型号) */struct iio_event_interface*event_interface;/* 数据缓冲区,使用触发缓冲区模式时被推送到用户空间。当使用iio_triggered_buffer_setup函数启用* 触发缓冲区支持时,会自动分配缓冲区并把它关联到设备。 */struct iio_buffer*buffer; struct list_headbuffer_list;/* 捕获并提供给缓冲区的字节数。从用户空间使用触发缓冲区时,缓冲区的大小至少应为iio_dev* ->scan_bytes字节 */intscan_bytes;struct mutexmlock;/* 可选数组的位掩码。使用触发缓冲区时,可以启用通道,以捕获数据并将其反馈给IIO缓冲区。如果不想启* 用某些通道,则应该填写该数组,只启用允许的通道。 */ const unsigned long*available_scan_masks;unsignedmasklength;/* 已启用通道的位掩码。只有来自这些通道的数据才应该被推入buffer。例如,对于8通道ADC转换器,如果* 只启用第一(0)、第三(2)和最后(7)通道,则位掩码为0b10000101(0x85)。active_scan_mask将被* 设置为0x85。然后驱动程序可以使用for_each_set_bit宏遍历每个设置位(set bits),根据通道获取* 并填充缓冲区。 */const unsigned long*active_scan_mask;boolscan_timestamp;/* 指出是否将捕获时间戳推入缓冲区。如果为true,则时间戳将作为缓冲区的最后一个元素进行推送。时间戳* 是8字节(64位)长。 */unsignedscan_index_timestamp;struct iio_trigger*trig; /* 当前设备的触发器(当支持缓冲模式时) */struct iio_poll_func*pollfunc; /* 在接收的触发器上运行的函数 */struct iio_chan_spec const*channels; /* 通道指定结构规范表,用于描述设备的每个通道。 */intnum_channels; /* channels中指定的通道数量 */struct list_headchannel_attr_list;struct attribute_groupchan_attr_group;const char*name; /* 设备名称 */const struct iio_info*info; /* 来自驱动程序的回调和常量信息 */struct mutexinfo_exist_lock;/* 启用/禁用缓冲区之前和之后调用的一组回调函数。*/const struct iio_buffer_setup_ops*setup_ops;struct cdevchrdev; /* IIO内核创建的相关字符设备。 */......};

元素available_scan_masks:以下例子为加速度计(带有X、Y、Z通道)提供扫描掩码:

允许掩码0x07(0b111)和0x00(0b0000),这意味着可以不启用或全部启用。即不能只启用X和Y

static const unsigned long my_scan_masks[] = {0x7, 0};

iio_dev->available_scan_masks = my_scan_masks;

元素struct iio_buffer_setup_ops *setup_ops:这个结构在include/linux/iio/iio.h中定义如下:

struct iio_buffer_setup_ops {int (*preenable)(struct iio_dev *);int (*postenable)(struct iio_dev *);int (*predisable)(struct iio_dev *);int (*postdisable)(struct iio_dev *);bool (*validate_scan_mask)(struct iio_dev *indio_dev,const unsigned long *scan_mask);};

​ 如果setup_ops未指定,则内核使用drivers/iio/buffer/Indus-trialio-triggered-buffer-c中定义的默认iio_triggered_buffer_setup_ops。

元素chrdev

用于为IIO设备分配内存的函数是iio_device_alloc():

struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv)

dev是为其分配iio_dev的设备,sizeof_prive是为私有结构分配的内存空间。这样,传递每个设备(私有)数据结构就变得非常简单。如果分配失败,该函数返回NULL,如:

struct iio_dev *iiodev;struct my_private_data *data;iiodev = devm_iio_device_alloc(spi->dev, sizeof(*data));if (!iiodev) return -ENOMEM;/* 私有数据提供预留内存地址 */data = iio_priv(iiodev);

IIO设备内存分配后,下一步是填写不同的字段。完成后,必须使用iio_device_register函数向IIO子系统注册设备:

int iio_device_register(struct iio_dev *indio_dev)

该函数执行后,设备将准备好接收来自用户空间的请求。相反的操作(通常在释放函数中完成)是iio_device_unregister():

void iio_device_unregister(struct iio_dev *indio_dev)

一旦注销注册,iio_device_alloc分配的内存就可以通过iio_device_free释放:

void iio_device_free(struct iio_dev *dev)

以IIO设备作为参数,可以通过以下方式检索私有数据:

struct my_private_data *data = iio_priv(iiodev);

iio_info结构

struct iio_info结构用于声明IIO内核使用的钩子,以读取/写入通道/属性值:

struct iio_info {/* 模块结构,用于确保chrdevs所属模块是正确的,通常设置为THIS_MODULE */struct module*driver_module;struct attribute_group*event_attrs; const struct attribute_group*attrs; /* 设备属性 *//* 用户读取设备sysfs文件属性时运行的回调函数。mask参数是位掩码,说明请求的值是哪种类型。chan通道* 参数指出有关的通道。它可用于采样频率,用于将原始值转换为可用值或原始值自身的比例。 */int (*read_raw)(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val,int *val2,long mask);/* 用于向设备写入值的回调函数。例如,可以使用它来设置采样频率 */int (*write_raw)(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val,int val2,long mask);int (*write_raw_get_fmt)(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,long mask);int (*read_event_config)(struct iio_dev *indio_dev,const struct iio_chan_spec *chan,enum iio_event_type type,enum iio_event_direction dir);int (*write_event_config)(struct iio_dev *indio_dev,const struct iio_chan_spec *chan,enum iio_event_type type,enum iio_event_direction dir,int state);int (*read_event_value)(struct iio_dev *indio_dev,const struct iio_chan_spec *chan,enum iio_event_type type,enum iio_event_direction dir,enum iio_event_info info, int *val, int *val2);int (*write_event_value)(struct iio_dev *indio_dev,const struct iio_chan_spec *chan,enum iio_event_type type,enum iio_event_direction dir,enum iio_event_info info, int val, int val2);int (*validate_trigger)(struct iio_dev *indio_dev,struct iio_trigger *trig);int (*update_scan_mode)(struct iio_dev *indio_dev,const unsigned long *scan_mask);int (*debugfs_reg_access)(struct iio_dev *indio_dev,unsigned reg, unsigned writeval,unsigned *readval);int (*of_xlate)(struct iio_dev *indio_dev,const struct of_phandle_args *iiospec);int (*hwfifo_set_watermark)(struct iio_dev *indio_dev, unsigned val);int (*hwfifo_flush_to_buffer)(struct iio_dev *indio_dev,unsigned count);};

以下代码显示如何设置iio_info结构:

static const struct iio_info iio_dummy_info = {.driver_module = THIS_MODULE,.read_raw = &iio_dummy_read_raw,.write_raw = &iio_dummy_write_raw,.write_raw_get_fmt = &iio_dummy_write_raw_get_fmt,......};/* 提供特定设备类型的接口函数和常量数据 */iiodev->info = &iio_dummy_info;

IIO通道

通道代表单条采集线。例如,加速度计有3个通道(X、Y、Z),因为每个轴代表单个采集线。struct iio_chan_spec结构表示和描述内核中的单个通道:

struct iio_chan_spec {/* 指出通道的测量类型。对于电压测量,它应该是IIO_VOLTAGE; 对于光传感器,它是IIO_LIGHT; 对于加速* 度计,使用IIO_ACCEL。所有可用的类型在include/uapi/linux/iio/types.h中定义为enum iio_* chan_type。要为给定的转换器编写驱动程序,请查看该文件,了解每个通道所属类型。*/enum iio_chan_typetype;intchannel; /* 当.indexed设置为1时,指定通道索引。 */intchannel2;/* 当.modified设置为1时,指定通道修饰符。 */unsigned longaddress;/* 当使用缓冲区触发器时,scan_index和scan_type成员用于标识来自缓冲区的元素。scan_index设置捕获* 通道在缓冲区内的位置。具有较低scan_index的通道将放置在具有较高索引的通道之前。将.scan_index* 设置为-1将阻止通道缓冲捕获(scan_elements目录中没有条目) */intscan_index;struct {charsign;u8realbits;u8storagebits;u8shift;u8repeat;enum iio_endian endianness;} scan_type;longinfo_mask_separate;longinfo_mask_shared_by_type;longinfo_mask_shared_by_dir;longinfo_mask_shared_by_all;const struct iio_event_spec *event_spec;unsigned intnum_event_specs;const struct iio_chan_spec_ext_info *ext_info;const char*extend_name;const char*datasheet_name;/* 指出修饰符是否应用于此通道属性名称。在这种情况下,修饰符在.channel2中设置(如,IIO_MOD_X、* IIO_MOD_Y、IIO_MOD_Z是围绕xyz轴的轴向传感器的修饰符)。可用修饰符列表在内核IIO头文件中定* 义为enum iio_modifier。修饰符仅影响sysfs中的通道属性名称是否具有索引, 而不是值。 */unsignedmodified:1; /* 指出通道属性名称是否具有索引。如果有,则在.channel成员中指定索引。 */unsignedindexed:1;unsignedoutput:1;unsigneddifferential:1;};

提供给用户空间的通道sysfs属性以位掩码的形式指定。根据其共享信息,可以将属性设置为以下掩码之一。

info_mask_separate:将属性标记为专属于此通道。

info_mask_shared_by_type:将属性标记为由相同类型的所有通道共享。导出的信息由同一类型的所有通道共享。

info_mask_shared_by_dir:将属性标记为由相同方向的所有通道共享。导出的信息由同一方向的所有通道共享。

info_mask_shared_by_all:将属性标记为由所有通道共享,无论它们的类型或方向如何。导出的信息由所有通道共享。这些属性枚举的位掩码全部在include/linux/iio/iio.h中定义:

enum iio_chan_info_enum {IIO_CHAN_INFO_RAW = 0,IIO_CHAN_INFO_PROCESSED,IIO_CHAN_INFO_SCALE,IIO_CHAN_INFO_OFFSET,IIO_CHAN_INFO_CALIBSCALE,IIO_CHAN_INFO_CALIBBIAS,......IIO_CHAN_INFO_SAMP_FREQ,IIO_CHAN_INFO_FREQUENCY,IIO_CHAN_INFO_PHASE,IIO_CHAN_INFO_HARDWAREGAIN,IIO_CHAN_INFO_HYSTERESIS,......};

排序字段应该是下列之一:

enum iio_endian {IIO_CPU,IIO_BE,IIO_LE,};

IIO触发缓冲区支持

​ 在许多数据分析应用中,能够基于某些外部信号(触发器)捕获数据非常有用。这些触发器可能如下:

数据就绪信号连接到某个外部系统(GPIO或其他)的IRQ线处理器周期性中断用户空间读/写sysfs中的特定文件

​ IIO设备驱动程序与触发器完全无关。触发器可以初始化一个或多个设备上的数据捕获,这些触发器用于填充缓冲区、作为字符设备提供给用户空间。

​ 人们可以开发自己的触发器驱动程序。

iio-trig-interrupt:这为使用IRQ作为IIO触发器提供支持。启用此触发模式的内核选项是CONFIG_IIO_INTERRUPT_TRIGGER。如果构建为模块,该模块将称为iio-trig-interrupt。iio-trig-hrtimer:提供基于频率的IIO触发器,使用HRT作为中断源(自内核v4.5开始)。负责这种触发模式的内核选项是IIO_HRTIMER_TRIGGER。如果构建为模块,该模块将称为iio-trig-hrtimer。iio-trig-sysfs:这允许使用sysfs项触发数据捕获。CONFIG_IIO_SYSFS_TRIGGER是添加此触发模式支持的内核选项。iio-trig-bfin-timer:这允许使用blackfin定时器作为IIO触发器。

IIO提供API,使用它们可以进行如下操作。

声明任意数量的触发器。选择哪些通道的数据将推入缓冲区。

​ IIO设备提供触发缓冲区支持时,必须设置iio_dev.pollfunc,触发器触发时执行它,该处理程序负责通过iiiodev->active_scan_mask查找启用的通道,检索其数据,并使用iio_push_to_buffers_with_timestamp函数将它们提供给iiodev->buffer。因此,缓冲区和触发器在IIO子系统中有紧密的联系。

​ IIO内核提供了一组辅助函数来设置触发缓冲区,这些函数可以在drivers/iio/sindustrialio-triggered-buffer-c中找到。

​ 以下是驱动程序支持触发缓冲区的步骤。

(1) 如果需要,则填写iio_buffer_setup_ops结构:

static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {.preenable = &ad7266_preenable,.postenable = &iio_triggered_buffer_postenable,.predisable = &iio_triggered_buffer_predisable,.postdisable = &ad7266_postdisable,};

(2) 编写与触发器关联的上半部。在99%的情况下,必须提供与捕获相关的时间戳:

irqreturn_t iio_pollfunc_store_time(int irq, void *p){struct iio_poll_func *pf = p;pf->timestamp = iio_get_time_ns();return IRQ_WAKE_THREAD;}

(3) 编写触发器的下半部,它将从每个启用的通道读取数据,并把它们送入缓冲区:

static irqreturn_t sensor_trigger_handler(int irq, void *p){u16 buf[8];int bit, i = 0;struct iio_poll_func *pf = p;struct iio_dev *iiodev = pf->indio_dev;/* 读取每个活动通道的数据 */for_each_set_bit(bit, iiodev->active_scan_mask, iiodev->masklength)buf[i++] = sensor_get_data(bit);/* 如果iiodev->scan_timestamp = true, 则捕获时间戳将被推送和存储,在将其推送到设备缓冲区之前,* 它作为示例数据缓冲区的最后一个元素 */iio_push_to_buffers_with_timestamp(iiodev, &buf, pf->timestamp);/* 通知触发 */iio_trigger_notify_done(iiodev->trig);return IRQ_HANDLED;}

(4) 在probe函数中,必须在使用iio_device_register()函数注册设备之前先设置缓冲区本身:

iio_triggered_buffer_setup(iiodev, &iio_pollfunc_store_time,&sensor_trigger_handler, &iio_triggered_buffer_setup_ops);

​ 这里的神奇函数是iio_triggered_buffer_setup。这也将为设备提供INDIO_DIRECT_MODE功能。当(从用户空间)把触发器指定到设备时,无法知道什么时候会被触发。

在连续缓冲捕获激活时,应该防止(通过返回错误)驱动程序在各个通道上执行sysfs数据捕获(由read_raw()回调函数执行),以避免不确定的行为,因为触发器处理程序和read_raw()回调函数将尝试同时访问设备。用于检查是否实际使用缓冲模式的函数是iio_buffer_enabled()。回调函数看起来像这样:

static int ad7266_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, int *val, int *val2, long m){struct ad7266_state *st = iio_priv(indio_dev);unsigned long scale_mv;int ret;switch (m) {case IIO_CHAN_INFO_RAW:if (iio_buffer_enabled(indio_dev))return -EBUSY;ret = ad7266_read_single(st, val, chan->address);if (ret)return ret;*val = (*val >> 2) & 0xfff;if (chan->scan_type.sign == 's')*val = sign_extend32(*val, 11);return IIO_VAL_INT;case IIO_CHAN_INFO_SCALE:scale_mv = st->vref_mv;if (st->mode == AD7266_MODE_DIFF)scale_mv *= 2;if (st->range == AD7266_RANGE_2VREF)scale_mv *= 2;*val = scale_mv;*val2 = chan->scan_type.realbits;return IIO_VAL_FRACTIONAL_LOG2;case IIO_CHAN_INFO_OFFSET:if (st->range == AD7266_RANGE_2VREF &&st->mode != AD7266_MODE_DIFF)*val = 2048;else*val = 0;return IIO_VAL_INT;}return -EINVAL;}

iio_buffer_enabled()函数简单地测试给定IIO设备的缓冲区是否启用。

​ 下面总结一些重要内容。

iio_buffer_setup_ops:提供缓冲区设置函数,以在缓冲区配置一系列固定步骤(启用/禁用之前/之后)中调用。如果未指定,IIO内核则将默认的iio_triggered_buffer_setup_ops提供给设备。iio_pollfunc_store_time:触发器的上半部。与每个上半部一样,它在中断环境中运行,必须执行尽可能少的处理。在99%的情况下,只需提供与捕获相关的时间戳。再次重申,可以使用默认的IIO函数iio_pollfunc_store_time.sensor_trigger_handler:下半部,它运行在内核线程中,能够执行任何处理,甚至包括获取互斥锁或睡眠。重处理应该发生在这里。它通常从设备中读取数据,将其与上半部中记录的时间戳一起存储在内部缓冲区,并将其推送到IIO设备缓冲区。

对触发缓冲区而言,触发器是必需的。它告诉驱动程序何时从设备读取采样数据,并将其放入缓冲区。触发缓冲区对编写IIO设备驱动程序而言不是必需的。通过读取通道原始属性,也可以通过sysfs使用单次捕获,它只执行一次转换(对于所读取的通道属性)。缓冲模式允许连续转换,从而一次捕获多个通道。

IIO触发器和sysfs(用户空间)

sysfs中有两个位置与触发器相关

/sys/bus/iio/devices/triggerY/:一旦IIO触发器在IIO内核中注册并且对应于索引Y的触发器,就会创建该目录。该目录中至少有一个属性。

name:触发器名称,之后可用于与设备建立关联。

/sys/bus/iio/devices/iio:如果设备支持触发缓冲区,则会自动创建目录deviceX/trigger/*。在current_trigger文件中用触发器的名称就可以将触发器与设备相关联起来。

IIO缓冲区

IIO缓冲区提供连续的数据捕获,一次可以同时读取多个数据通道。可通过dev/iio:device字符设备节点从用户空间访问缓冲区。在触发器处理程序中,用于填充缓冲区的函数是iio_push_to_buffers_with_timestamp。负责为设备分配触发缓冲区的函数是iio_triggered_buffer_setup()。

IIO缓冲区的sysfs接口

IIO缓冲区在/sys/bus/iio/iio下有一个关联的属性目录:deviceX/buffer/*。以下是其一些属性:

length:缓冲区可存储的数据取样总数(容量)。这是缓冲区包含的扫描数量。enable:激活缓冲区捕获,启动缓冲区捕获。watermark:自内核版本v4.2以来,该属性一直可用。它是一个正数,指定阻塞读取应该等待的扫描元素数量。例如,如果使用轮询,则会阻塞直到水印为止。它只有在水印大于所请求的读数量时才有意义,不会影响非阻塞读取。可以用暂停阻止轮询,并在暂停过期后读取可用样本,从而获得最大延迟保证。

IIO缓冲区设置

数据将被读取并推入缓冲区的通道称为扫描元素。它们的配置可通过/sys/bus/iio/iio:deviceX/scan_elements/*目录从用户空间访问,其中包含以下属性:

en:实际上是属性名称的后缀,用于启用频道。当且仅当其属性不为零时,触发的捕捉将包含此通道的数据取样。例如in_voltage0_en、in_voltage1_en等。

type:描述扫描元素数据在缓冲区内的存储,因此描述从用户空间读取它的形式。例如in_voatage0_type。格式为[be | le]:[s | u]bits/storagebitsXrepeat[>>shift]。

be 或 le :指出字节顺序(大端或小端)。

s 或 u :指出符号,带符号(2的补码)或无符号。

bits:有效数据位数。

storagebits:该通道在缓冲区中占用的位数。例如,一个值可能实际编码是12位(bit),但占用缓冲区中的16位(storagebits)。因此必须将数据向右移4位才能得到实际值。该参数取决于设备,应参考设备的数据手册。

shift:表示在屏蔽掉未使用的位之前应该移位数据值的次数。这个参数并不总是需要的。如果有效位数(bit)等于存储位数,则shift将为0。在设备数据手册中也可以找到该参数。

repeat:指出位/存储重复数量。当重复元素为0或1时,重复值被省略。

IIO数据访问

​ 只有两种方法可以通过IIO框架访问数据:通过sysfs通道单次捕获,或通过IIO字符设备的连续模式(触发缓冲区)。

单次捕获

单次数据捕获通过sysfs接口完成。通过读取对应用于通道的sysfs条目,将只捕获与该频道相关的数据。对于具有两个通道的温度传感器:一个用于测量环境温度,另一个用于测量热电偶温度:

# cd /sys/bus/iio/device/iio:device0# cat in_voltage3_raw6355# cat in_voltage_scale0.30517781

将刻度乘以原始值即获得处理后的值。即,电压值 = 6355* 0.30517781 = 1939.40498255mV。

缓冲区数据访问

要使触发采集正常工作,必须在驱动程序中实现触发器支持。然后,要从用户空间获取,则必须:创建触发器并进行分配,启用ADC通道,设置缓冲区的大小并启用它。

使用sysfs触发器捕获

使用sysfs触发器捕获数据包括发送一组命令,但涉及少数几个sysfs文件。具体实现步骤如下:

(1)创建触发器。在将触发器分配给任何设备之前,应该创建它:

# echo 0 > /sys/devcies/iio_sysfs_trigger/add_trigger

​ 这里,0对应于需要分配给触发器的索引。此命令执行后,该触发器目录在/sys/bus/iio/devices/下作为trigger0提供。

(2)将触发器分配给设备。触发器由其名称唯一标识,使用名称可以将设备与触发器绑定。由于这里使用0作为索引,因此触发器将命名为sysfstrig0:

# echo sysfstrig0 > /sys/bus/iio/devcies/iio:device0/trigger/current_trigger

(3)启用一些扫描元素。此步骤包括选择哪些通道的数据值推入缓冲区。应该注意驱动程序中的available_scan_masks:

# echo 0 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage4_en# echo 0 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage5_en# echo 0 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage6_en

(4)设置缓冲区大小。这里应该设置缓冲区可以保存的样本集的数量:

# echo 100 > /sys/bus/iio/devices/iio:device0/buffer/length

(5)启用缓冲区。此步骤将缓冲区标记为准备好接收推送的数据:

# echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable

​ 要停止捕获,必须在同一个文件中写入0。

(6)触发触发器。启动获取:

# echo 1 > /sys/bus/iio/devices/iio:device0/trigger0/trigger_now

(7)禁用缓冲区

# echo 0 > /sys/bus/iio/devices/iio:device0/buffer/enable

(8)分离触发器

# echo " " > /sys/bus/iio/devices/iio:device0/trigger/current_trigger

(9)转存IIO字符设备的内容

# cat /dev/iio\:device0 | xxd -

使用hrtimer触发器捕获

下面这组命令允许使用hrtimer触发器捕获数据:

# echo /sys/kernel/config/iio/triggers/hrtimer/trigger0# echo 50 > /sys/bus/iio/devices/trigger0/sampling_frequency# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage4_en# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage5_en# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage6_en# echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable# cat /dev/iio\:device0 | xxd -......00000000: 0188 1a30 0000 0000 8312 68a8 c24f 5a14 ...0.......h...OZ.

接下来查看类型,以了解如何处理数据:

# cat /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage_typebe:s14/16>>2

电压处理:0x188 >> 2 = 98 * 250 = 24500 = 24.5V

M20608设备驱动程序

icm20608.hicm20608.hicm20608.h头文件

/* * 文件名 : icm20608.h* 作者: glen * 描述: icm20608头文件*/#ifndef ICM20608_H#define ICM20608_H#define ICM20608G_ID 0xAF /* ID值 */#define ICM20608D_ID 0xAE /* ID值 *//*** ICM20608寄存器* 复位后所有寄存器地址都为0, 除了*0x6B Power Management 1 = 0x40*0x75 WHO_AM_I = 0xAF或0xAE*//* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */#defineICM20_SELF_TEST_X_GYRO0x00#defineICM20_SELF_TEST_Y_GYRO0x01#defineICM20_SELF_TEST_Z_GYRO0x02#defineICM20_SELF_TEST_X_ACCEL0x0D#defineICM20_SELF_TEST_Y_ACCEL0x0E#defineICM20_SELF_TEST_Z_ACCEL0x0F/* 陀螺仪静态偏移 */#defineICM20_XG_OFFS_USRH0x13#defineICM20_XG_OFFS_USRL0x14#defineICM20_YG_OFFS_USRH0x15#defineICM20_YG_OFFS_USRL0x16#defineICM20_ZG_OFFS_USRH0x17#defineICM20_ZG_OFFS_USRL0x18#defineICM20_SMPLRT_DIV0x19#defineICM20_CONFIG0x1A#defineICM20_GYRO_CONFIG0x1B#defineICM20_ACCEL_CONFIG0x1C#defineICM20_ACCEL_CONFIG20x1D#defineICM20_LP_MODE_CFG0x1E#defineICM20_ACCEL_WOM_THR0x1F#defineICM20_FIFO_EN0x23#defineICM20_FSYNC_INT0x36#defineICM20_INT_PIN_CFG0x37#defineICM20_INT_ENABLE0x38#defineICM20_INT_STATUS0x3A/* 加速度输出 */#defineICM20_ACCEL_XOUT_H0x3B#defineICM20_ACCEL_XOUT_L0x3C#defineICM20_ACCEL_YOUT_H0x3D#defineICM20_ACCEL_YOUT_L0x3E#defineICM20_ACCEL_ZOUT_H0x3F#defineICM20_ACCEL_ZOUT_L0x40/* 温度输出 */#defineICM20_TEMP_OUT_H0x41#defineICM20_TEMP_OUT_L0x42/* 陀螺仪输出 */#defineICM20_GYRO_XOUT_H0x43#defineICM20_GYRO_XOUT_L0x44#defineICM20_GYRO_YOUT_H0x45#defineICM20_GYRO_YOUT_L0x46#defineICM20_GYRO_ZOUT_H0x47#defineICM20_GYRO_ZOUT_L0x48#defineICM20_SIGNAL_PATH_RESET0x68#defineICM20_ACCEL_INTEL_CTRL 0x69#defineICM20_USER_CTRL0x6A#defineICM20_PWR_MGMT_10x6B#defineICM20_PWR_MGMT_20x6C#defineICM20_FIFO_COUNTH0x72#defineICM20_FIFO_COUNTL0x73#defineICM20_FIFO_R_W0x74#defineICM20_WHO_AM_I 0x75/* 加速度静态偏移 */#defineICM20_XA_OFFSET_H0x77#defineICM20_XA_OFFSET_L0x78#defineICM20_YA_OFFSET_H0x7A#defineICM20_YA_OFFSET_L0x7B#defineICM20_ZA_OFFSET_H0x7D#defineICM20_ZA_OFFSET_L 0x7E/*** icm20608数据* @gyro_x_adc: 陀螺仪X轴原始值* @gyro_y_adc: 陀螺仪Y轴原始值* @gyro_z_adc: 陀螺仪Z轴原始值* @accel_x_adc: 加速度计X轴原始值* @accel_y_adc: 加速度计Y轴原始值* @accel_z_adc: 加速度计Z轴原始值* @temp_adc: 温度原始值*/struct icm20_data {signed short gyro_x_adc;signed short gyro_y_adc;signed short gyro_z_adc;signed short accel_x_adc;signed short accel_y_adc;signed short accel_z_adc;signed short temp_adc;float gyro_x_act;float gyro_y_act;float gyro_z_act;float accel_x_act;float accel_y_act;float accel_z_act;float temp_act;};#endif

icm20608.cicm20608.cicm20608.c程序文件

/*** 文件名 : icm20608.c* 作者: glen * 描述: icm20608驱动文件*/#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/of_gpio.h>#include <linux/spi/spi.h>#include <linux/of.h>#include <linux/platform_device.h>#include <asm/uaccess.h>#include <asm/io.h>#include <linux/of_irq.h>#include <linux/irq.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/trigger_consumer.h>#include <linux/iio/triggered_buffer.h>#include "icm20608.h"#define ICM20_CHAN(_type, _channel2, _si) \{\.type = (_type), \.modified = 1, \.channel2 = (_channel2), \.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \BIT(IIO_CHAN_INFO_CALIBBIAS), \.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \.scan_index = (_si), \.scan_type = {\.sign = 's', \.realbits = 16, \.storagebits = 16, \.shift = 0, \.endianness = IIO_BE, \}, \}#define ICM20_TEMP_CHAN(_type, _si) \{\.type = (_type), \.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \BIT(IIO_CHAN_INFO_OFFSET) | \BIT(IIO_CHAN_INFO_SCALE), \.scan_index = (_si), \.scan_type = {\.sign = 's', \.realbits = 16, \.storagebits = 16, \.shift = 0, \.endianness = IIO_BE, \}, \}/*** @brief ICM20的扫描元素* 3轴加速度计、3轴陀螺仪、1路温度传感器、1路时间戳*/enum icm20_scan {ICM20_SCAN_ACCEL_X,ICM20_SCAN_ACCEL_Y,ICM20_SCAN_ACCEL_Z,ICM20_SCAN_TEMP,ICM20_SCAN_GYRO_X,ICM20_SCAN_GYRO_Y,ICM20_SCAN_GYRO_Z,ICM20_SCAN_TIMESTAMP,};/*** @brief: struct icm20608* @spi: SPI设备* @regmap: * @regmap_cfg: * @lock:*/struct icm20608 {struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_cfg;struct mutex lock;};/* 陀螺仪分辨率 */static const int icm20_gyro_scale_tbl[] = {500U * 1000000 / 65536, 1000U * 1000000 / 65536,2000U * 1000000 / 65536,4000U * 1000000 / 65536,};/* 加速度计分辨率 */static const int icm20_accel_scale_tbl[] = {4ULL * 1000000000 / 65536,8ULL * 1000000000 / 65536,16ULL * 1000000000 / 65536,32ULL * 1000000000 / 65536,};static const struct iio_chan_spec icm20_channels[] = {ICM20_TEMP_CHAN(IIO_TEMP, ICM20_SCAN_TEMP),ICM20_CHAN(IIO_ANGL_VEL, IIO_MOD_X, ICM20_SCAN_GYRO_X),ICM20_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, ICM20_SCAN_GYRO_Y),ICM20_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, ICM20_SCAN_GYRO_Z),ICM20_CHAN(IIO_ACCEL, IIO_MOD_X, ICM20_SCAN_ACCEL_X),ICM20_CHAN(IIO_ACCEL, IIO_MOD_Y, ICM20_SCAN_ACCEL_Y),ICM20_CHAN(IIO_ACCEL, IIO_MOD_Z, ICM20_SCAN_ACCEL_Z),};/*** ICM20608内部寄存器初始化函数* @param : none* @return : noreturn*/void icm20608_reg_init(struct icm20608 *dev){int ret;regmap_write(dev->regmap, ICM20_PWR_MGMT_1, 0x80);mdelay(50);regmap_write(dev->regmap, ICM20_PWR_MGMT_1, 0x01);mdelay(50);if (regmap_read(dev->regmap, ICM20_WHO_AM_I, &ret) >= 0)printk("ICM20608 ID = %#x\r\n", ret);regmap_write(dev->regmap, ICM20_SMPLRT_DIV, 0x00);regmap_write(dev->regmap, ICM20_GYRO_CONFIG, 0x18);regmap_write(dev->regmap, ICM20_ACCEL_CONFIG, 0x18);regmap_write(dev->regmap, ICM20_CONFIG, 0x04);regmap_write(dev->regmap, ICM20_ACCEL_CONFIG2, 0x04);regmap_write(dev->regmap, ICM20_PWR_MGMT_2, 0x00);regmap_write(dev->regmap, ICM20_LP_MODE_CFG, 0x00);regmap_write(dev->regmap, ICM20_INT_ENABLE, 0x01);}/*** @brief 读取ICM20608传感器数据* * @param dev icm20608设备* @param reg 要读取通道寄存器首地址* @param ch 通道序号* @param val 保存读取的值* @return 0, 成功; 其它值, 错误*/static int icm20_sensor_read(struct icm20608 *dev, int reg, int ch, int *val){int ind, result;__be16 d;ind = (ch - 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;}/*** @brief : 设置ICM20传感器,可以用于陀螺仪、加速度计设置* @param - dev: icm20设备 * @param - reg : 要设置的通道寄存器首地址。* @param - anix : 要设置的通道,比如X,Y,Z。* @param - val : 要设置的值。* @return: 0,成功;其他值,错误*/static int icm20_sensor_write(struct icm20608 *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;}static int icm20_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2,long mask){int ret;struct icm20608 *dev = iio_priv(indio_dev);switch (mask) {case IIO_CHAN_INFO_RAW: /* 读取传感器数据 */switch (chan->type) {case IIO_ANGL_VEL: /* 读取陀螺仪数据 */mutex_lock(&dev->lock);ret = icm20_sensor_read(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);mutex_unlock(&dev->lock);break;case IIO_ACCEL: /* 读取加速度计数据 */mutex_lock(&dev->lock);ret = icm20_sensor_read(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val);mutex_unlock(&dev->lock);break;case IIO_TEMP: /* 读取温度数据 */mutex_lock(&dev->lock);ret = icm20_sensor_read(dev, ICM20_TEMP_OUT_H, chan->channel2, val);mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;case IIO_CHAN_INFO_OFFSET:switch (chan->type) {case IIO_TEMP:mutex_lock(&dev->lock);*val = 0;ret = IIO_VAL_INT;mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_TEMP:mutex_lock(&dev->lock);*val = 326800000 / 1000000;*val2 = 326800000 % 1000000;ret = IIO_VAL_INT_PLUS_MICRO;mutex_unlock(&dev->lock);break;case IIO_ANGL_VEL:mutex_lock(&dev->lock);regmap_read(dev->regmap, ICM20_GYRO_CONFIG, &ret);*val = 0;*val2 = icm20_gyro_scale_tbl[(ret & 0x18) >> 3];ret = IIO_VAL_INT_PLUS_MICRO;mutex_unlock(&dev->lock);break;case IIO_ACCEL:mutex_lock(&dev->lock);regmap_read(dev->regmap, ICM20_ACCEL_CONFIG, &ret);*val = 0;*val2 = icm20_accel_scale_tbl[(ret & 0x18) >> 3];printk("Read accel scale index value: %d\n", icm20_accel_scale_tbl[(ret & 0x18) >> 3]);ret = IIO_VAL_INT_PLUS_NANO;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 = icm20_sensor_read(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);mutex_unlock(&dev->lock);break; case IIO_ACCEL:mutex_lock(&dev->lock);ret = icm20_sensor_read(dev, ICM20_XA_OFFSET_H, chan->channel2, val);mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;default:ret = -EINVAL;break;}return ret;}static int icm20_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val,int val2,long mask){int ret = 0;int i, d, ind;struct icm20608 *dev = iio_priv(indio_dev);switch (mask) {case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_ANGL_VEL:mutex_lock(&dev->lock);for(i = 0; i < ARRAY_SIZE(icm20_gyro_scale_tbl); ++i) if (icm20_gyro_scale_tbl[i] == val2) break;if (i < ARRAY_SIZE(icm20_gyro_scale_tbl)) {d = (i << 3);ret = regmap_write(dev->regmap, ICM20_GYRO_CONFIG, d);} elseret = -EINVAL;mutex_unlock(&dev->lock);break;case IIO_ACCEL:mutex_lock(&dev->lock);for(i = 0; i < ARRAY_SIZE(icm20_accel_scale_tbl); ++i) if (icm20_accel_scale_tbl[i] == val2) break;if (i < ARRAY_SIZE(icm20_accel_scale_tbl)) {d = (i << 3);ret = regmap_write(dev->regmap, ICM20_ACCEL_CONFIG, d);} elseret = -EINVAL;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);printk("write gyro offset raw val=%d, val2=%d\n", val, val2);ret = icm20_sensor_write(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);mutex_unlock(&dev->lock);break;case IIO_ACCEL:mutex_lock(&dev->lock);printk("write accel offset raw val=%d, val2=%d\n", val, val2);ret = icm20_sensor_write(dev, ICM20_XA_OFFSET_H, chan->channel2, val);mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;default:ret = -EINVAL;break;}return ret;}static int icm20_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_TEMP:case IIO_ANGL_VEL:return IIO_VAL_INT_PLUS_MICRO;case IIO_ACCEL:return IIO_VAL_INT_PLUS_NANO;default:return IIO_VAL_INT_PLUS_MICRO;}default:return IIO_VAL_INT_PLUS_MICRO;}return -EINVAL;}static const struct iio_info icm20_info = {.read_raw = &icm20_read_raw,.write_raw = &icm20_write_raw,.write_raw_get_fmt = &icm20_write_raw_get_fmt,.driver_module = THIS_MODULE, };/*** spi驱动的probe函数, 当驱动与设备匹配会执行此函数* @param client: spi设备* @param id:spi设备ID*/static int icm20608_probe(struct spi_device *spi){int ret = 0;struct icm20608 *icm20608dev;struct iio_dev *iiodev;/* 1.向内核申请分配iio_dev内存, 包括同时分配的icm20_dev内存 */iiodev = devm_iio_device_alloc(&spi->dev, sizeof(struct icm20608));if (!iiodev) {return -ENOMEM;}spi_set_drvdata(spi, iiodev);/* 2.把已分配的indio_dev内存结构的私有数据赋给icm20_dev */icm20608dev = iio_priv(iiodev);icm20608dev->spi = spi;mutex_init(&icm20608dev->lock);/* 3.设置iio_dev的主要成员变量 */iiodev->name = "icm20608";iiodev->dev.parent = &spi->dev;iiodev->info = &icm20_info;iiodev->modes = INDIO_DIRECT_MODE;iiodev->channels = icm20_channels;iiodev->num_channels = ARRAY_SIZE(icm20_channels);/* 4.注册iio_dev */ret = iio_device_register(iiodev);if (ret < 0) {dev_err(&spi->dev, "iio_device_register failed\n");goto err_iio_register;}/* 5.初始化regmap_config配置 */icm20608dev->regmap_cfg.reg_bits = 8; /* 寄存器长度 */icm20608dev->regmap_cfg.val_bits = 8; /* 值长度 */icm20608dev->regmap_cfg.read_flag_mask = 0x80; /* 读掩码 *//* 6.初始化SPI接口的regmap */icm20608dev->regmap = regmap_init_spi(spi, &icm20608dev->regmap_cfg);if (IS_ERR(icm20608dev->regmap)) {ret = PTR_ERR(icm20608dev->regmap);goto err_regmap_init;}/* 初始化spi_device */spi->mode = SPI_MODE_0;ret = spi_setup(spi);icm20608dev->spi = spi;//spi_set_drvdata(spi, icm20608dev);/* 初始化ICM20608内部寄存器 */icm20608_reg_init(icm20608dev);return 0;err_regmap_init:iio_device_unregister(iiodev);err_iio_register://kzfree(icm20608dev);regmap_exit(icm20608dev->regmap);return ret;}/*** spi驱动的remove函数,移除spi驱动的时候此函数会执行* @param : client spi设备* @return : 0 成功; 负值 失败*/static int icm20608_remove(struct spi_device *spi){struct iio_dev *iiodev = spi_get_drvdata(spi);struct icm20608 *icm20608dev; icm20608dev = iio_priv(iiodev);/* 删除regmap */regmap_exit(icm20608dev->regmap);/* 注销IIO */iio_device_unregister(iiodev);//kzfree(icm20608dev);return 0;}/* 传统匹配方式ID列表 */static const struct spi_device_id icm20608_id[] = {{"glen,icm20608", 0},{}};MODULE_DEVICE_TABLE(spi, icm20608_id);/* 设备树匹配列表 */static const struct of_device_id icm20608_of_match[] = {{.compatible = "glen,icm20608"},{/* Sentinel */}};MODULE_DEVICE_TABLE(of, icm20608_of_match);/* 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,};/*** \brief 驱动模块加载函数* \param 无* \retval 无*/static int __init icm20608_init(void){return spi_register_driver(&icm20608_driver);}/*** \brief 驱动模块缷载函数* \param 无* \return 无*/static void __exit icm20608_exit(void){spi_unregister_driver(&icm20608_driver);}/* 设备注册入口, 展开后* static initcall_t \* __initcall_icm20608_init6 \* __used \* __attribute__((__section__(".initcall6.init"))) \* = icm20608_init;*/module_init(icm20608_init);/* 设备注册出口, 展开后* static exitcall_t \* __exitcall_icm20608_exit \* __exit_call \* = icm20608_exit;*/module_exit(icm20608_exit);/* 模块的许可证声明, 展开后* static const char __UNIQUE_ID_license__COUNTER__[] \* __used __attribute__((section(".modinfo"), unused, aligned(1))) \* = "license=GPL";*/MODULE_LICENSE("GPL");/* 模块的作者声明, 展开后* static const char __UNIQUE_ID_author__COUNTER__[] \* __used __attribute__((section(".modinfo"), unused, aligned(1))) \* = "author=glen_cao"*/MODULE_AUTHOR("glen");

试验

/ # cd drv_module//drv_module # lsap3216c.kobutton_drv.ko imx6_io_drv.koap3216c_test icm20608.kolcd_drv.kobtn_drv_test icm20608_test lcd_drv_test/drv_module # insmod icm20608.koICM20608 ID = 0xae/drv_module # cd /sys/bus/iio/devices/iio\:device0//sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/000.ecspi/spi_master/spi2/spi2.0/iio:device0 # lsdev in_anglvel_scale in_temp_rawin_accel_scalein_anglvel_x_calibbias in_temp_scalein_accel_x_calibbias in_anglvel_x_raw namein_accel_x_rawin_anglvel_y_calibbias of_nodein_accel_y_calibbias in_anglvel_y_raw powerin_accel_y_rawin_anglvel_z_calibbias subsystemin_accel_z_calibbias in_anglvel_z_raw ueventin_accel_z_rawin_temp_offset/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_scaleRead accel scale index value: 4882810.000488281/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_x_calibbias-5902/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/000.ecspi/spi_master/spi2/spi2.0/iio:device0 # echo 1000 > in_accel_x_calibbiaswrite accel offset raw val=1000, val2=0/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_x_calibbias1000

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。