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 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。

  1. 使用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子系统:I.MX6U 嵌入式 Qt 开发指南V1.1

      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事件 */
    
  2. 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,
        };
        
  3. 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时,重复值被省略。

  4. 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

AP3216C设备驱动程序

  • a p 3216 c . h ap3216c.h ap3216c.h头文件
/* * 文件名   : ap3216c.h* 作者     : glen  * 描述     : ap3216c头文件*/#ifndef __AP3216C_H__
#define __AP3216C_H__#define AP3216C_ADDR       0x1E    /* AP3216C器件地址  *//* AP3316C寄存器 */
/* System Register Table */
#define AP3216C_SYS_CFG     0x00    /* 配置寄存器 */
#define AP3216C_INT_STAT    0X01    /* 中断状态寄存器 */
#define AP3216C_INT_CLR     0X02    /* 中断清除寄存器 */
#define AP3216C_IR_DAT_L    0x0A    /* IR红外 数据低字节 */
#define AP3216C_IR_DAT_H    0x0B    /* IR红外 数据高字节 */
#define AP3216C_ALS_DAT_L   0x0C    /* ALS光线传感器 数据低字节 */
#define AP3216C_ALS_DAT_H   0X0D    /* ALS光线传感器 数据高字节 */
#define AP3216C_PS_DAT_L    0X0E    /* PS接近传感器 数据低字节 */
#define AP3216C_PS_DAT_H    0X0F    /* PS接近传感器 数据高字节 */#define SYS_CFG_MODE_MASK   (7 << 0)
/* 该设备将停止操作。寄存器将保留以前的设置,尽管设备处于休眠状态。ALS、PS和IR将被清除。 */
#define SYS_CFG_MODE_PWRDOWN    0x00 /* System Mode: Power down */
/* 该设备仅支持ALS功能。ALS的典型转换时间为100ms。PS数据在这种模式下将无法工作。 */
#define SYS_CFG_MODE_ALS        0x01 /* System Mode: ALS function active */
/* 该设备将仅用于PS+IR功能。IR的典型转换时间为12.5ms,PS由PS的等待时间决定。ALS数据* 在此模式下将无法工作。操作时间如下所示(PS等待时间=0) */
#define SYS_CFG_MODE_PS_IR      0x02 /* System Mode: PS+IR function acitve */
/* 该设备将交替操作ALS和PS+IR功能。在此模式下,转换时间将会翻倍。 */
#define SYS_CFG_MODE_ALS_PS_IR  0x03 /* System Mode: ALS+PS+IR function active */
/* 当主机写入此设置时,设备的所有寄存器将在10ms之后成为默认值。在这10ms的期间,请不要强制指挥,以避免异常操作 */
#define SYS_CFG_MODE_SW_RESET   0x04 /* System Mode: SW reset */
/* 当主机写入此设置时,该设备将在短时间内以ALS模式工作。然后,在该设备获得ALS数据后,* 该设备将自动断电。这个时间通常是2.5个转换时间。如果设备被ALS命令休眠,ALS数据将* 被保留而不清除。 */
#define SYS_CFG_MODE_ALS_ONCE   0x05 /* System Mode: ALS function once */
/* 当主机写入此设置时,该设备将在短时间内以PS模式工作。然后,在设备获得PS和IR数据后,* 设备将自动断电。这个时间通常为2.5个转换时间,不受PS等待的影响。如果设备被PS命令* 睡眠,PS和IR数据将被保存而不清除。 */
#define SYS_CFG_MODE_PS_IR_ONCE 0x06 /* System Mode: PS+IR function once */
/* 当主机写入此设置时,设备将在短时间内交替操作ALS和PS+IR功能。然后,在设备获得ALS、* PS和IR数据后,设备将自动断电。这个时间通常为232ms,不受PS等待的影响。如果设备被* 这个命令睡眠,ALS,PS和IR数据将被保存而不清除。*/
#define SYS_CFG_MODE_ALS_PS_IR_ONCE 0x07 /* System Mode: ALS and PS+IR function once *//* ALS INT位寄存器用于指示触发ALS中断(设置为1)或不是1(设置为0)。在读取0x0D寄存器或* 写入0x01以清除后,它将被清除(取决于INT清除方式标志)。*/
#define INT_STAT_ALS    (1 << 0)    /* 0: Interrupt is cleared or not triggered yet */
/* PS INT位寄存器用于指示PS中断被触发(设置为1)或不是1(设置为0)。在0x0F寄存器被读* 取或写入0x02以清除后,它将被清除(取决于INT清除方式标志)。*/
#define INT_STAT_PS     (1 << 1)    /* 1: Interrupt is triggered */
/* 为了提供具有中断标志处理的多个控制流,CLR_MNR位用于分配两种方式的中断状态标志解除声明。* 设置为0,通过读取数据寄存器(0x0C、0x0D、0x0E、0x0F)自动清除中断标志。另一方面,通过* 写1清除中断标志。例如,如果PS_INT断言,则可以在I2C写地址0x01和0x02后清除。*/
#define INT_CLR_MANNER  (1 << 0)    /* 0: INT is automatically cleared by reading data registers *//* 1: Software clear after writing 1 into address 0x01 each bit */
/*   IR的ADC信道数据表示为10位数据,跨越两个寄存器,IR数据低和ID数据高。这两个将分别提* 供ADC值的较低和较高字节。红外辐射数据可以显示环境红外辐射的强度。所有的通道数据寄存器* 都是只读的。*   如果红外强度高,会影响PS数据,导致PS数据无效。有一个溢出标志(IR_OF)来指示PS数据,* 以查看它在高红外光谱下是否有效。如果IR_OF设置为1,则设备将强制PS对象状态为离开状态。*   读取下字节寄存器后,可以读取较高字节寄存器。当读取低字节寄存器时,高字节存储在一个* 临时寄存器中,随后读取到高字节。高字节寄存器将读取正确的值,即使额外的集成周期结束之* 间,高字节寄存器将读取正确的值。*/
#define IR_DAT_L_OVF    (1 << 7)    /* 0: Valid IR and PS data 1: Invalid IR and PS data */
#define IR_DAT_L_B1_0   (3 << 0)    /* IR lower byte of ADC output */
#define IR_DAT_H_B9_2   (0xff << 0) /* IR higher byte of ADC output */#define ALS_DAT_L_B7_0  (0xff << 0) /* ALS lower byte of ADC output */
#define ALS_DAT_L_B15_8 (0xff << 0) /* ALS higher byte of ADC output */#define PS_DAT_L_B3_0    (0xf << 0) /* PS lower byte of ADC output */
#define PS_DAT_L_IR_OVF  (1 << 6)   /* 0: Valid IR, PS data and object detected  1: Invalid IR,PS data and object detected */
#define PS_DAT_L_OBJ_DET (1 << 7)   /* 0: The object leaving 1: The object closed */#define PS_DAT_H_B9_4    (0x3F << 0) /* PS higher byte of ADC output */
#define PS_DAT_H_IR_OVF  (1 << 6)   /*  0: Valid IR, PS data and object detected  1: Invalid IR,PS data and object detected */
#define PS_DAT_H_OBJ_DET (1 << 7)   /* 0: The object leaving 1: The object closed *//* ALS Register Table */
#define AP3216C_ALS_CFG     0x10    /* Control of gain, conversion time of persist for ALS */
#define AP3216C_ALS_CAL     0x19    /* ALS window loss calibration */
#define AP3216C_ALS_THR_LLB 0x1A    /* Lower byte of ALS low threshold */
#define AP3216C_ALS_THR_LHB 0x1B    /* Higher byte of ALS low threshold */
#define AP3216C_ALS_THR_HLB 0x1C    /* Lower byte of ALS high threshold */
#define AP3216C_ALS_THR_HHB 0x1D    /* Higher byte of ALS high threshold */#define ALS_CFG_ALS_PERSIST  (0xf << 0) /* ALS interrupt is triggered after N conversion time 0000: 1 conversion time */
#define ALS_CFG_ALS_DYNA_RNG   (3 << 4) /* 00: 20661 lux 01: 5162 lux 10: 1291 lux 11: 323 lux *//* PS Register Table */
#define AP3216C_PS_CFG      0x20    /* Control of gain, integrated time and persist for PS */
#define AP3216C_PS_LED_DRV  0x21    /* Control of LED pulses number and driver current */
#define AP3216C_PS_INT_FORM 0x22    /* Interrupt algorithms style select of PS */
#define AP3216C_PS_MEAN_TIM 0x23    /* PS average time selector */
#define AP3216C_PS_LED_WAIT 0x24    /* Control PS LED waiting time */
#define AP3216C_PS_CAL_L    0x28    /* Offset value to eliminate cross talk */
#define AP3216C_PS_CLA_H    0x29    /* Offset value to eliminate cross talk */
#define AP3216C_PS_LLB      0x2A    /* Lower byte of PS low threshold */
#define AP3216C_PS_LHB      0x2B    /* Higher byte of PS low threshold */
#define AP3216C_PS_HLB      0x2C    /* Lower byte of PS high threshold */
#define AP3216C_PS_HHB      0x2D    /* Higher byte of PS high threshold */#define PS_CFG_PERSIST    (3 << 0)  /* PS interrupt is triggered after N conversion time */
#define PS_CFG_GAIN       (3 << 2)  /* PS gain */
#define PS_CFG_PSIR_INTE_TIME (0xf << 4) /* PS/IR Integrated time select */#define PS_LED_DRV_RATIO  (3 << 0)  /* 00: 16.7%  01: 33.3% 10: 66.7% 11: 100% */
#define PS_LED_PULSE      (3 << 4)  /* 00: 0 pulse 01: 1 pulse 10: 2 pulse 11: 3 pulse */#define PS_INT_MODE_ALGO  (1 << 0)  /* 0: PS INT Mode 1(Zone type) 1: PS INT Mode 2(Hysteresis type, default) */#define PS_MEAN_TIME      (3 << 0)  /* 00: mean time=12.5ms 01: mean time=25ms */#define PS_LED_WAIT       (0x3f << 0) /* 0: no waiting 1: 1 mean time 2: 2 mean time */#define PS_CAL_L_B0       (1 << 0)  /* Lower byte of PS calibration */
#define PS_CAL_L_B8_1     (0xff << 0) /* Higher byte of PS calibration */#endif
  • a p 3216 c . c ap3216c.c ap3216c.c程序文件
/*** 文件名   : ap3216c.c* 作者     : glen  * 描述     : ap3216c驱动文件*/
#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/i2c.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 "ap3216c.h"
#include "asm-generic/int-ll64.h"
#include "linux/byteorder/little_endian.h"
#include "linux/iio/types.h"
#include "linux/mutex.h"
#include "linux/types.h"/*** struct ap3216c* @client:    I2C客户端* @regmap:    * @regmap_cfg: * @lock:*/
struct ap3216c {struct i2c_client   *client;struct regmap       *regmap;struct regmap_config regmap_cfg;struct mutex         lock;
};/*** AP3216C的扫描元素, IR、ALS、PS*/
enum ap3216c_scan {AP3216C_IR,AP3216C_ALS,AP3216C_PS,
};/* ap3216c环境光传感器分辨率,扩大1000000倍,* 量程依次为0~20661,0~5162,0~1291,0~323。单位:lux */
static const int ap3216c_als_scale_tbl[] = {315000, 78800, 19700, 4900
};static const struct iio_chan_spec ap3216c_channels[] = {/* IR红外传感器 */{ .type = (IIO_LIGHT), .modified = 1, .address = AP3216C_IR_DAT_L,.channel2 = (IIO_MOD_LIGHT_IR), .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), .scan_index = AP3216C_IR,.scan_type = { .sign = 'u', .realbits = 10, .storagebits = 16, .endianness = IIO_LE, }, },/* ALS光线传感器 */{ .type = (IIO_INTENSITY), .address = AP3216C_ALS_DAT_L,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \BIT(IIO_CHAN_INFO_SCALE), .scan_index = AP3216C_ALS,.scan_type = { .sign = 'u', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, }, },/* PS接近传感器 */{ .type = (IIO_PROXIMITY), .address = AP3216C_PS_DAT_L,.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),.scan_index = AP3216C_PS,.scan_type = { .sign = 'u', .realbits = 10, .storagebits = 16, .endianness = IIO_LE, }, },
};/*** 扫描掩码,两种情况,全启动0x111,或者都不启动0x0*/
static const unsigned long ap3216c_scan_masks[] = {BIT(AP3216C_IR) | BIT(AP3216C_ALS) | BIT(AP3216C_PS),0,
};static int ap3216c_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val, int *val2,long mask)
{int ret;u8 d[2];struct ap3216c *dev = iio_priv(indio_dev);switch (mask) {case IIO_CHAN_INFO_RAW: /* 读取传感器数据 */switch (chan->type) {case IIO_LIGHT:     /* 读取红外传感器原始数据 */mutex_lock(&dev->lock);regmap_bulk_read(dev->regmap, chan->address, &d, 2);*val = ((u16)d[1] << 2) | (d[0] & 0x03);mutex_unlock(&dev->lock);ret = IIO_VAL_INT;break;case IIO_INTENSITY: /* 读取光线传感器原始数据 */mutex_lock(&dev->lock);regmap_bulk_read(dev->regmap, chan->address, &d, 2);*val = ((u16)d[1] << 8) | d[0];mutex_unlock(&dev->lock);ret = IIO_VAL_INT;break;case IIO_PROXIMITY: /* 读取接近传感器原始数据 */mutex_lock(&dev->lock);regmap_bulk_read(dev->regmap, chan->address, &d, 2);*val = ((u16)(d[1] & 0x3f) << 4) | (d[0] & 0x0f);mutex_unlock(&dev->lock);ret = IIO_VAL_INT;break;default:ret = -EINVAL;break;}break;case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_INTENSITY:mutex_lock(&dev->lock);regmap_read(dev->regmap, AP3216C_ALS_CFG, &ret);*val = 0;*val2 = ap3216c_als_scale_tbl[(ret & 0x30) >> 4];ret = IIO_VAL_INT_PLUS_MICRO;mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;default:ret = -EINVAL;break;}return ret;
}static int ap3216c_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 ap3216c *dev = iio_priv(indio_dev);switch (mask) {case IIO_CHAN_INFO_SCALE:switch (chan->type) {case IIO_INTENSITY:mutex_lock(&dev->lock);for(i = 0; i < ARRAY_SIZE(ap3216c_als_scale_tbl); ++i) if (ap3216c_als_scale_tbl[i] == val) break;if (i < ARRAY_SIZE(ap3216c_als_scale_tbl)) {d = (i << 4);ret = regmap_write(dev->regmap, AP3216C_ALS_CAL, d);} elseret = -EINVAL;mutex_unlock(&dev->lock);break;default:ret = -EINVAL;break;}break;default:ret = -EINVAL;break;}return ret;
}static int ap3216c_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_INTENSITY:return IIO_VAL_INT_PLUS_MICRO;default:return IIO_VAL_INT_PLUS_MICRO;}default:return IIO_VAL_INT_PLUS_MICRO;}return -EINVAL;
}static const struct iio_info ap3216c_info = {.read_raw = &ap3216c_read_raw,.write_raw = &ap3216c_write_raw,.write_raw_get_fmt = &ap3216c_write_raw_get_fmt,.driver_module = THIS_MODULE,
};/*** spi驱动的probe函数, 当驱动与设备匹配会执行此函数* @param   client: spi设备* @param   id:     spi设备ID*/
static int ap3216c_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
{int ret = 0;struct ap3216c *ap3216cdev;struct iio_dev *iiodev;/* 1.向内核申请分配iio_dev内存, 包括同时分配的ap3216cdev内存 */iiodev = devm_iio_device_alloc(&i2c->dev, sizeof(struct ap3216c));if (!iiodev) {return -ENOMEM;}i2c_set_clientdata(i2c, iiodev);/* 2.把已分配的indio_dev内存结构的私有数据赋给ap3216cdev */ap3216cdev = iio_priv(iiodev);ap3216cdev->client = i2c;mutex_init(&ap3216cdev->lock);/* 3.设置iio_dev的主要成员变量 */iiodev->name = "ap3216c";iiodev->dev.parent = &i2c->dev;iiodev->info = &ap3216c_info;iiodev->modes = INDIO_DIRECT_MODE;iiodev->channels = ap3216c_channels;iiodev->num_channels = ARRAY_SIZE(ap3216c_channels);iiodev->available_scan_masks = ap3216c_scan_masks;/* 4.注册iio_dev */ret = iio_device_register(iiodev);if (ret < 0) {dev_err(&i2c->dev, "iio_device_register failed\n");goto err_iio_register;}/* 5.初始化regmap_config配置 */ap3216cdev->regmap_cfg.reg_bits = 8;           /* 寄存器长度 */ap3216cdev->regmap_cfg.val_bits = 8;           /* 值长度 *///ap3216cdev->regmap_cfg.read_flag_mask = I2C_M_RD;  /* 读掩码 *//* 6.初始化SPI接口的regmap */ap3216cdev->regmap = regmap_init_i2c(i2c, &ap3216cdev->regmap_cfg);if (IS_ERR(ap3216cdev->regmap)) {ret = PTR_ERR(ap3216cdev->regmap);goto err_regmap_init;}regmap_write(ap3216cdev->regmap, AP3216C_SYS_CFG, SYS_CFG_MODE_SW_RESET);mdelay(50);regmap_write(ap3216cdev->regmap, AP3216C_SYS_CFG, SYS_CFG_MODE_ALS_PS_IR);regmap_write(ap3216cdev->regmap, AP3216C_ALS_CFG, 0x00);       /* ALS单次转换触发,量程为0~20661 lux */regmap_write(ap3216cdev->regmap, AP3216C_PS_LED_DRV,  0x13);     /* IR LED 1脉冲,驱动电流100%*/ap3216cdev->client = i2c;return 0;err_regmap_init:iio_device_unregister(iiodev);
err_iio_register://kzfree(ap3216cdev);regmap_exit(ap3216cdev->regmap);return ret;
}/*** spi驱动的remove函数,移除spi驱动的时候此函数会执行* @param   : client spi设备* @return  : 0 成功; 负值 失败*/
static int ap3216c_remove(struct i2c_client *i2c)
{    struct iio_dev *iiodev = i2c_get_clientdata(i2c);struct ap3216c *ap3216cdev; ap3216cdev = iio_priv(iiodev);/* 删除regmap */regmap_exit(ap3216cdev->regmap);/* 注销IIO */iio_device_unregister(iiodev);//kzfree(ap3216cdev);return 0;
}/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {{"glen, ap3216c", 0},{}
};
MODULE_DEVICE_TABLE(i2c, ap3216c_id);/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {{.compatible = "glen, ap3216c"},{ /* Sentinel */}
};
MODULE_DEVICE_TABLE(of, ap3216c_of_match);/* SPI驱动结构体 */
static struct i2c_driver ap3216c_driver = {.probe  = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name  = "ap3216c",.of_match_table = ap3216c_of_match,},.id_table = ap3216c_id,
};/*** \brief   驱动模块加载函数* \param   无* \retval  无*/
static int __init ap3216c_init(void)
{return i2c_add_driver(&ap3216c_driver);
}/*** \brief   驱动模块缷载函数* \param   无* \return  无*/
static void __exit ap3216c_exit(void)
{i2c_del_driver(&ap3216c_driver);
}/* 设备注册入口, 展开后* static initcall_t \*        __initcall_ap3216c_init6 \*        __used \*        __attribute__((__section__(".initcall6.init"))) \*        = ap3216c_init;*/
module_init(ap3216c_init);/* 设备注册出口, 展开后* static exitcall_t \*        __exitcall_ap3216c_exit \*        __exit_call \*        = ap3216c_exit;*/
module_exit(ap3216c_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");
  • 试验
/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/iio:device0 # ls
dev                    in_proximity_raw       subsystem
in_illuminance_ir_raw  name                   uevent
in_intensity_raw       of_node
in_intensity_scale     power
/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/iio:device0 # cat in_intensity_scale
0.315000
/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/iio:device0 # cat in_intensity_raw
162
/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/iio:device0 # cat in_proximity_raw
469
/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/iio:device0 # cat in_illuminance_ir_raw
13

Linux学习笔记(22.2)——基于IIC + Regmap + IIO的AP3216C的设备驱动相关推荐

  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 学习笔记 (四)Ubuntu14.04 解决上网问题安装无线网卡驱动

    闲来无事有时候又想熟悉熟悉Linux各种指令.写写shell 脚本,虽然现在各种云主机硬件以及软件非常的火爆,各个大的厂商都在推出自己的硬件服务,比如百度.阿里.360.IBM等等都推出了自己的云服务 ...

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. Go 学习笔记(13)— 指针定义、指针特点、空指针、指针数组、指向指针的指针、指针作为函数入参
  2. CentOS 5.5 编译安装apache+php+mysql,利用CMS快速建立论坛
  3. 有多少状元能够成才?(zz)
  4. boost::filesystem目录相关的测试程序
  5. java移动端接口测试_使用java如何进行接口测试
  6. 信息安全系统设计基础第四次实验报告 201352012013530620135307
  7. 书籍:Python游戏开发 Game Development Using Python - 2019.pdf
  8. [刷题]算法竞赛入门经典(第2版) 6-7/UVa804 - Petri Net Simulation
  9. 惠斯通电桥信号调理芯片_变频器通电后无反应,如何检查维修?
  10. STP中各算法接口开销(COST)计算方式
  11. 线性规划 整数规划 01规划
  12. 【实习日报】2019年5月 前端开发实习工作日报汇总
  13. Welcome home, Chtholly [Ynoi2018]五彩斑斓的世界
  14. Groups CodeForces - 1598B
  15. 80老翁谈人生(187):老翁为北大排名而自豪
  16. 红帽 linux 安装gns3,在Arch Linux和Manjaro系统上安装GNS3的方法
  17. Enterprise Architect 中文版 注册码 破解 亲测有效!
  18. html下拉菜单换背景颜色,如何在select框下拉菜单中更改蓝色突出显示的颜色
  19. app中jeDate日期控件的使用
  20. java里getter和setter的作用和区别是什么?

热门文章

  1. 国产服务器Kylin(aarch64)安装mysql8.0.27
  2. 《Java SE实战指南》09-01:访问级别修饰符概述
  3. C++题目:因数最多
  4. Anaconda,tensorflow,keras安装过程以及遇到的问题和解决方案
  5. 数据结构考研:数据、数据元素、数据项、数据对象、数据结构的区别/详细解释(计算机/软件工程/王道论坛)
  6. 第21章、系统设定工具(网络与打印机)与硬件侦测
  7. 续 winform 打包-集合dotnetfx文件夹到一个exe
  8. Intel汇编-带符号乘法
  9. 自动化设备数据采集系统优势
  10. 华为如何在开发者选项观察错误日志_爬虫scrapy框架--log日志输出配置及使用