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

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驱动框架如下图所示:

Linux学习笔记(22.2)——基于IIC + Regmap + IIO的AP3216C的设备驱动_第1张图片

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: 字符设备,用于输出设备事件和数据缓冲区。
    

    Linux学习笔记(22.2)——基于IIC + Regmap + IIO的AP3216C的设备驱动_第2张图片

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

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

    #include 					/* 强制性的 */
    #include 				/* 因为使用了sysfs,所以是强制性的 */
    #include 				/* 强制使用触发缓冲区 */
    #include 				/* 仅当在驱动程序中实现触发器(很少使用)时 */
    #include 				/* 对于高级用户,管理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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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);
            } else
                ret = -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,linux,学习,驱动开发)