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

Regmap API 简介

  1. 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 操作次数,提高访问效率。

  2. regmap框架结构

    ​ regmap驱动框架如下图所示:
    在这里插入图片描述 regmap 框架分为三层:
    ① 底层物理总线: regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总线有 i2c、 i3c、 spi、 mmio、 sccb、 sdw、 slimbus、 irq、 spmi 和 w1。
    ② regmap 核心层,用于实现 regmap,我们不用关心具体实现。
    ③ regmap API 抽象层, regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。

  3. 使用regmap API编程

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

    1. 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,
      };
      
    2. 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;
      
      	...
      }
      
    3. 设备访问函数

      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,则结果如下。

        1. 如果寄存器地址在no_range内,则返回-EIO。
        2. 如果寄存器地址在yes_range内,则执行下一步。
        3. 如果寄存器地址不在no_range内,也不在yes_range内,则返回-EIO,操作中断。
        4. 如果cache_type != REGCACHE_NONE,则启用缓存。在这种情况下,首先更新缓存项,之后执行到硬件的写操作,否则不执行缓存动作。
        5. 如果提供了回调函数reg_write,则用它执行写入操作,将执行通用的regmap写功能。
      • regmap_read:从设备读取数据。其在相应数据结构(readable_reg和rd_table)上执行的功能类似于regmap_write。因此,如果提供了reg_read,则使用它执行读取操作,否则将执行通用的regmap读取函数。

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

    4. regmap使用总结

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

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

IIO子系统简介

  1. IIO子系统的引入

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

    ​ 加速度计、陀螺仪、电流/电压测量转换芯片、光传感器、压力传感器等都属于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事件 */
  1. IIO数据结构

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

    1. 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  上述两种模式的组合。
           */
      	int				modes;
      	int				currentmode;	/* 设备实际使用的模式 */
      	struct device			dev;	/* IIO设备绑定的struct device(根据Linux设备型号) */
      
      	struct iio_event_interface	*event_interface;
          
      	/* 数据缓冲区,使用触发缓冲区模式时被推送到用户空间。当使用iio_triggered_buffer_setup函数启用
           * 触发缓冲区支持时,会自动分配缓冲区并把它关联到设备。 */
      	struct iio_buffer		*buffer; 
          
      	struct list_head		buffer_list;
          
          /* 捕获并提供给缓冲区的字节数。从用户空间使用触发缓冲区时,缓冲区的大小至少应为iio_dev
           * ->scan_bytes字节 */
      	int				scan_bytes;
      	struct mutex			mlock;
          
      	/* 可选数组的位掩码。使用触发缓冲区时,可以启用通道,以捕获数据并将其反馈给IIO缓冲区。如果不想启
           * 用某些通道,则应该填写该数组,只启用允许的通道。 */    
      	const unsigned long		*available_scan_masks;
          
      	unsigned			masklength;
          
          /* 已启用通道的位掩码。只有来自这些通道的数据才应该被推入buffer。例如,对于8通道ADC转换器,如果
           * 只启用第一(0)、第三(2)和最后(7)通道,则位掩码为0b10000101(0x85)。active_scan_mask将被
           * 设置为0x85。然后驱动程序可以使用for_each_set_bit宏遍历每个设置位(set bits),根据通道获取
           * 并填充缓冲区。 */
      	const unsigned long		*active_scan_mask;
      	bool				scan_timestamp;
          
          /* 指出是否将捕获时间戳推入缓冲区。如果为true,则时间戳将作为缓冲区的最后一个元素进行推送。时间戳
           * 是8字节(64位)长。 */
      	unsigned			scan_index_timestamp;
      	struct iio_trigger		*trig; /* 当前设备的触发器(当支持缓冲模式时) */
          
      	struct iio_poll_func		*pollfunc; /* 在接收的触发器上运行的函数 */
      	struct iio_chan_spec const	*channels; /* 通道指定结构规范表,用于描述设备的每个通道。 */
      	int				num_channels; /* channels中指定的通道数量 */
      
      	struct list_head		channel_attr_list;
      	struct attribute_group		chan_attr_group;
      	const char			*name; /* 设备名称 */
      	const struct iio_info		*info; /* 来自驱动程序的回调和常量信息 */
      	struct mutex			info_exist_lock;
          
          /* 启用/禁用缓冲区之前和之后调用的一组回调函数。*/
      	const struct iio_buffer_setup_ops	*setup_ops;
      	struct cdev			chrdev; /* 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);
      
    2. 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;
      
    3. 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_type	type;
      	int			channel; /* 当.indexed设置为1时,指定通道索引。 */
      	int			channel2;/* 当.modified设置为1时,指定通道修饰符。 */
      	unsigned long		address;
          
          /* 当使用缓冲区触发器时,scan_index和scan_type成员用于标识来自缓冲区的元素。scan_index设置捕获
           * 通道在缓冲区内的位置。具有较低scan_index的通道将放置在具有较高索引的通道之前。将.scan_index      * 设置为-1将阻止通道缓冲捕获(scan_elements目录中没有条目) */
      	int			scan_index;
      	struct {
      		char	sign;
      		u8	realbits;
      		u8	storagebits;
      		u8	shift;
      		u8	repeat;
      		enum iio_endian endianness;
      	} scan_type;
      	long			info_mask_separate;
      	long			info_mask_shared_by_type;
      	long			info_mask_shared_by_dir;
      	long			info_mask_shared_by_all;
      	const struct iio_event_spec *event_spec;
      	unsigned int		num_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中的通道属性名称是否具有索引, 而不是值。 */
          unsigned		modified:1; 
          
          /* 指出通道属性名称是否具有索引。如果有,则在.channel成员中指定索引。 */
      	unsigned		indexed:1;
      	unsigned		output:1;
      	unsigned		differential: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,
        };
        
  2. 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使用单次捕获,它只执行一次转换(对于所读取的通道属性)。缓冲模式允许连续转换,从而一次捕获多个通道。

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

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

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

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

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

    2. 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时,重复值被省略。

  3. IIO数据访问

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

    1. 单次捕获

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

      # cd /sys/bus/iio/device/iio:device0
      # cat in_voltage3_raw
      6355
      # cat in_voltage_scale
      0.30517781
      

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

    2. 缓冲区数据访问

      要使触发采集正常工作,必须在驱动程序中实现触发器支持。然后,要从用户空间获取,则必须:创建触发器并进行分配,启用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设备驱动程序

  • i c m 20608. h icm20608.h icm20608.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
 */
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define	ICM20_SELF_TEST_X_GYRO		0x00
#define	ICM20_SELF_TEST_Y_GYRO		0x01
#define	ICM20_SELF_TEST_Z_GYRO		0x02
#define	ICM20_SELF_TEST_X_ACCEL		0x0D
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F

/* 陀螺仪静态偏移 */
#define	ICM20_XG_OFFS_USRH			0x13
#define	ICM20_XG_OFFS_USRL			0x14
#define	ICM20_YG_OFFS_USRH			0x15
#define	ICM20_YG_OFFS_USRL			0x16
#define	ICM20_ZG_OFFS_USRH			0x17
#define	ICM20_ZG_OFFS_USRL			0x18

#define	ICM20_SMPLRT_DIV			0x19
#define	ICM20_CONFIG				0x1A
#define	ICM20_GYRO_CONFIG			0x1B
#define	ICM20_ACCEL_CONFIG			0x1C
#define	ICM20_ACCEL_CONFIG2			0x1D
#define	ICM20_LP_MODE_CFG			0x1E
#define	ICM20_ACCEL_WOM_THR			0x1F
#define	ICM20_FIFO_EN				0x23
#define	ICM20_FSYNC_INT				0x36
#define	ICM20_INT_PIN_CFG			0x37
#define	ICM20_INT_ENABLE			0x38
#define	ICM20_INT_STATUS			0x3A

/* 加速度输出 */
#define	ICM20_ACCEL_XOUT_H			0x3B
#define	ICM20_ACCEL_XOUT_L			0x3C
#define	ICM20_ACCEL_YOUT_H			0x3D
#define	ICM20_ACCEL_YOUT_L			0x3E
#define	ICM20_ACCEL_ZOUT_H			0x3F
#define	ICM20_ACCEL_ZOUT_L			0x40

/* 温度输出 */
#define	ICM20_TEMP_OUT_H			0x41
#define	ICM20_TEMP_OUT_L			0x42

/* 陀螺仪输出 */
#define	ICM20_GYRO_XOUT_H			0x43
#define	ICM20_GYRO_XOUT_L			0x44
#define	ICM20_GYRO_YOUT_H			0x45
#define	ICM20_GYRO_YOUT_L			0x46
#define	ICM20_GYRO_ZOUT_H			0x47
#define	ICM20_GYRO_ZOUT_L			0x48

#define	ICM20_SIGNAL_PATH_RESET		0x68
#define	ICM20_ACCEL_INTEL_CTRL 		0x69
#define	ICM20_USER_CTRL				0x6A
#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_PWR_MGMT_2			0x6C
#define	ICM20_FIFO_COUNTH			0x72
#define	ICM20_FIFO_COUNTL			0x73
#define	ICM20_FIFO_R_W				0x74
#define	ICM20_WHO_AM_I 				0x75

/* 加速度静态偏移 */
#define	ICM20_XA_OFFSET_H			0x77
#define	ICM20_XA_OFFSET_L			0x78
#define	ICM20_YA_OFFSET_H			0x7A
#define	ICM20_YA_OFFSET_L			0x7B
#define	ICM20_ZA_OFFSET_H			0x7D
#define	ICM20_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 

  • i c m 20608. c icm20608.c icm20608.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);
            } else
                ret = -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);
            } else
                ret = -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 # ls
ap3216c.ko      button_drv.ko   imx6_io_drv.ko
ap3216c_test    icm20608.ko     lcd_drv.ko
btn_drv_test    icm20608_test   lcd_drv_test
/drv_module # insmod icm20608.ko
ICM20608 ID = 0xae
/drv_module # cd /sys/bus/iio/devices/iio\:device0/
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # ls
dev                     in_anglvel_scale        in_temp_raw
in_accel_scale          in_anglvel_x_calibbias  in_temp_scale
in_accel_x_calibbias    in_anglvel_x_raw        name
in_accel_x_raw          in_anglvel_y_calibbias  of_node
in_accel_y_calibbias    in_anglvel_y_raw        power
in_accel_y_raw          in_anglvel_z_calibbias  subsystem
in_accel_z_calibbias    in_anglvel_z_raw        uevent
in_accel_z_raw          in_temp_offset
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_scale
Read accel scale index value: 488281
0.000488281
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_x_calibbias
-5902
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # echo 1000 > in_accel_x_calibbias
write accel offset raw val=1000, val2=0
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_x_calibbias
1000

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IIO(Industrial I/O)子系统是Linux内核中的一个子系统,它为驱动程序提供了一种通用的接口,用于访问各种硬件设备的模拟输入/输出接口。IIO子系统支持各种类型的模拟输入/输出设备,包括ADC(模拟数字转换器)、DAC(数字模拟转换器)、压力传感器、光传感器等等。 IIO框架提供了一些基础的数据结构,用于描述IIO设备和它们的数据: 1. struct iio_dev:IIO设备的描述结构体,包括设备名称、所属总线、设备ID、通道信息、触发器信息等等。 2. struct iio_chan_spec:IIO设备通道的描述结构体,包括通道名称、数据类型、量程、采样频率等等。 3. struct iio_buffer:IIO设备数据缓冲区的描述结构体,包括缓冲区大小、数据指针、数据类型等等。 4. struct iio_poll_func:IIO设备的轮询函数,用于读取硬件设备的数据。 IIO子系统还提供了一些基础的函数,用于IIO设备的注册、数据采集、数据存储等等。这些函数包括: 1. iio_device_register():注册IIO设备。 2. iio_device_unregister():注销IIO设备。 3. iio_buffer_alloc():分配IIO设备的数据缓冲区。 4. iio_buffer_free():释放IIO设备的数据缓冲区。 5. iio_read_channel_raw():读取IIO设备通道的原始数据。 6. iio_read_channel_processed():读取IIO设备通道的处理后数据。 7. iio_push_to_buffers():将IIO设备的数据写入数据缓冲区。 总之,IIO子系统提供了一个通用的接口,使得驱动程序可以方便地访问各种类型的模拟输入/输出设备

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值