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设备驱动程序

  • 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*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#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 
  • 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 # 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

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

  1. 【Linux学习笔记22】mysql数据库的基本管理

    文章目录 1. 数据库的介绍 2. 安装mysql并启用 3. 软件基本信息 4. 数据库的安全初始化 4.1 关闭数据库开放端口 4.2 执行安全初始化脚本 5. 数据库基本管理 5.1 数据库查看 ...

  2. Linux学习笔记22——sigsuspend 让指定的信号无法唤醒进程

    1,由来 让指定的信号无法唤醒进程貌似很简单,只要先设置屏蔽字屏蔽指定的信号,再pause就可以了.以后只要接收到信号,切该信号不是指定的信号就可以结束 pause.但是,这种做法是分两步做的,而信号 ...

  3. (零)我为什么要写Linux学习笔记?

    我对Linux有强烈的好奇心,兴趣:写学习笔记会让我有成就感:我记忆力实在不好.好吧,其实真正原因是:如果我能把自己学到的知识简明扼要的讲出来,写出来,那便证明我真的学懂了我想学的知识.没时间怎么办? ...

  4. Linux学习笔记(3)- 网络编程以及范例程序

    Linux学习笔记(3)- 网络编程以及范例程序 前言 网络介绍 IP地址的介绍 端口和端口号的介绍 通信流程 socket介绍 TCP介绍 python3编码转换 TCP客户端程序开发流程 多任务版 ...

  5. LINUX学习笔记:31个常用LINUX命令和相关解释

    LINUX学习笔记三: 最近在带新同事,是一个刚入行的萌新,对于linux不是很熟悉,问我怎么来学习. 所以今天接着之前的部分,继续分享一些linux学习资料.有需要的点点保存,没有跳过呗.PS:内容 ...

  6. Linux学习笔记-随即更新-慢速学习

    Linux学习笔记 Linux系统简介 UNIX发展历史和发行版本 开源软件简介 支撑互联网的开源技术 Linux应用领域 Linux学习方法 Linux系统安装 给初学者的建议 学习linux的注意 ...

  7. 史上最牛最强的linux学习笔记 10.shell基础

    史上最牛最强的linux学习笔记 10.shell基础 写在最前面: 本文是基于某站的视频学习所得,第一个链接如下: https://www.bilibili.com/video/BV1mW411i7 ...

  8. Linux学习笔记---命令篇

    Linux学习笔记-命令篇 本笔记是根据尚硅谷的Linux视频,经过自己的总结得出来的笔记,分为两个大章节,一-四章讲的是Linux的文件目录结构.vim编辑器.网络配置和系统管理.进程和服务.第二个 ...

  9. 大数据开发工程师基本功修炼之史上最全Linux学习笔记(建议)

    我正在参加年度博客之星评选,请大家帮我投票打分,您的每一分都是对我的支持与鼓励. 2021年「博客之星」参赛博主:Maynor大数据 (感谢礼品.红包免费送!) https://bbs.csdn.ne ...

最新文章

  1. 因特尔显卡自定义分辨率_蓝宝石RX 5600XT 6G D6 白金版OC显卡评测:两千价位段好选择...
  2. 在windows下搭建React Native Android开发环境
  3. JAVA中 @Override 的作用
  4. abp 打包部署到ubuntu_如何在Ubuntu中安装Docker和运行 Docker容器
  5. 【转】[Java] HashMap使用的示例
  6. python没有这个xlwt模块_python xlwt模块简介
  7. python后台执行代码
  8. 针对Android智能电视研发的,基于Android的智能电视遥控系统研制
  9. 动画中英文对照表(09.24)
  10. 大学生心理健康调研报告
  11. kafka的全面知识点
  12. axure9总是崩_axure老是崩怎么办
  13. webpack 打包异常 unhandledRejection 错误详情查看
  14. 鸿蒙系统可以装微信,网友:让腾讯支持华为,微信专用鸿蒙系统,安卓:你说啥?...
  15. 绘画新手怎么把人物骨骼画好
  16. 视频在线发布网站HTML模板分享
  17. 【linux】循序渐进学运维-基础篇-文件的软硬链接
  18. 微信小程序开发——上课摇号系统的开发
  19. 电脑开机转到计算机状态页面,如何让电脑每次开机都恢复原来的状态?-开机启动项怎么设置...
  20. “应试教育的死穴,恰在于堵住了孩子“犯错”空间”——有感

热门文章

  1. 百度鹰眼控制台整合javaweb
  2. 哈工大C语言程序设计精髓第六周
  3. android目录结构
  4. 隔夜茶到底能不能喝?
  5. PyTorch 训练时中遇到的卡住停住等问题
  6. linux_ rpm管理(Synaptic Package Manager)
  7. Unity移动平台下的烘焙使用及优化
  8. Win之Software Installation:谷歌地球(Google Earth) 的简介、安装、使用方法之详细攻略
  9. 玩电脑玩出的 Linux 专家 - Google(谷歌)开发者日演讲者之苏哲
  10. 不要浪费那少得可怜的注意力(2)