Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样的,通过 I2C/SPI 接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如 I.MX6ULL的 PWM、定时器等外设初始化,最终都是要落到寄存器的设置上。
Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器, SPI 接口的话使用 spi_write/spi_read等。 I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余代码,再者,代码的复用性也会降低。比如 icm20608 这个芯片既支持 I2C 接口,也支持 SPI 接口。假设我们在产品设计阶段一开始将 icm20608 设计为 SPI 接口,但是后面发现 SPI 接口不够用,或者 SOC 的引脚不够用,我们需要将 icm20608 改为 I2C 接口。这个时候 icm20608 的驱动就要大改,我们需要将 SPI 接口函数换为 I2C 的,工作量比较大。
基于代码复用的原则, Linux 内核引入了 regmap 模型, regmap 将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmap API函数。这样的好处就是统一使用 regmap,降低了代码冗余, 提高了驱动的可以移植性。 regmap模型的重点在于:
通过 regmap 模型提供的统一接口函数来访问器件的寄存器, SOC 内部的寄存器也可以使用 regmap 接口函数来访问。
regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。另外, regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低。
什么情况下会使用 regmap:
① 硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。
② 提高代码复用性和驱动一致性,简化驱动开发过程。
③ 减少底层 I/O 操作次数,提高访问效率。
regmap驱动框架如下图所示:
regmap 框架分为三层:
① 底层物理总线: regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总线有 i2c、 i3c、 spi、 mmio、 sccb、 sdw、 slimbus、 irq、 spmi 和 w1。
② regmap 核心层,用于实现 regmap,我们不用关心具体实现。
③ regmap API 抽象层, regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。
regmap API比较简单,只需要了解几个结构即可。此API中的两个重要结构是struct regmap_config(代表regmap配置)和struct regmap(regmap实例本身)。
struct regmap_config在驱动程序生命周期中存储regmap配置,这里的设置会影响读/写操作,它是regmap API中最重要的结构。其源代码如下:
struct regmap_config {
const char *name;
int reg_bits; /* 这个必填成员是寄存器地址中的位数 */
int reg_stride;
int pad_bits;
int val_bits; /* 表示用于存储寄存器的位数, 这是一个必填成员 */
/* 可选的回调函数。如果提供,则在需要写入/读取寄存器时供regmap子系统使用。在写入/读取寄存器之前,
* 会自动调用该函数来检查寄存器是否可以写入/读取 */
bool (*writeable_reg)(struct device *dev, unsigned int reg);
bool (*readable_reg)(struct device *dev, unsigned int reg);
/* 回调函数。每当需要通过regmap缓存读取或写入寄存器时调用它。如果寄存器是易失的,那么函数应该返回
* true,然后对寄存器执行直接读写。如果返回false,则表示寄存器可缓存。在这种情况下,缓存将用于读取
* 操作,并且在写入操作时写入缓存 */
bool (*volatile_reg)(struct device *dev, unsigned int reg);
bool (*precious_reg)(struct device *dev, unsigned int reg);
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg;
/* 设备可能不支持简单的I2C/SPI读取操作。除了自己编写read函数外,别无选择。这时read_reg应该指向
* 那个函数,大多数设备不需要这样。*/
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
/* 与reg_read相同,但针对写入操作 */
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
bool fast_io;
/* 可选的,它指定最大的有效寄存器地址,在该寄存器地址上不允许进行任何操作。 */
unsigned int max_register;
/* 不提供writeable_reg回调时,可以提供regmap_access_table,该结构体包含yes_range和
* no_range成员,两者都指向struct regmap_range。任何属于yes_range项的寄存器都被认为
* 是可写入的,如果属于no_range,则被认为是不可写入的。 */
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table; /* 与wr_table相同,但针对所有读操作 */
/* 代替volatile_reg,可以提供volatile_table。原理与wr_table或rd_table相同,但针对缓存
* 机制。 */
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct reg_default *reg_defaults;
unsigned int num_reg_defaults;
enum regcache_type cache_type;
const void *reg_defaults_raw;
unsigned int num_reg_defaults_raw;
u8 read_flag_mask;
u8 write_flag_mask;
bool use_single_rw;
bool can_multi_write;
enum regmap_endian reg_format_endian;
enum regmap_endian val_format_endian;
const struct regmap_range_cfg *ranges;
unsigned int num_ranges;
};
以下是regmap_config的一种初始化:
static const struct regmap_config bmp280_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = BMP280_REG_TEMP_XLSB,
.cache_type = REGCACHE_RBTREE,
.writeable_reg = bmp280_is_writeable_reg,
.volatile_reg = bmp280_is_volatile_reg,
};
regmap API支持SPI和I2C协议。根据驱动程序中需要支持的协议不同,probe函数中必须调用regmap_init_i2c()或regmap_init_spi()。要编写通用驱动程序,regmap是最佳选择。
regmap API是通过和同质的。SPI和I2C两种总线类型之间只有初始化不同,其他功能完全相同。
始终在probe函数中初始化regmap,并且在初始化regmap之前,必须填充regmap_config元素,这是一个良好的习惯。
无论分配的是I2C还是SPI寄存器映射,都用regmap_exit函数释放:
void regmap_exit(struct regmap *map)
该函数只是释放先前分配的寄存器映射。
I2C regmap初始化包括在regmap config上调用regmap_init_i2c(),这将配置regmap,以便在内部将所有设备访问转换为I2C命令:
struct regmap *regmap_init_i2c(struct i2c_client *i2c,
const struct regmap_config *config)
该函数成功时返回指向分配的struct regmap的指针,错误时返回的值是EER_PTR()。
完整的例子如下:
static int bmp280_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
struct bmp280_data *data;
unsigned int chip_id;
...
data->regmap = devm_regmap_init_i2c(client, &bmp280_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(&client->dev, "failed to allocate register map\n");
return PTR_ERR(data->regmap);
}
ret = regmap_read(data->regmap, BMP280_REG_ID, &chip_id);
if (ret < 0)
return ret;
if (chip_id != BMP280_CHIP_ID) {
dev_err(&client->dev, "bad chip id. expected %x got %x\n",
BMP280_CHIP_ID, chip_id);
return -EINVAL;
}
ret = bmp280_chip_init(data);
if (ret < 0)
return ret;
...
}
API处理数据解析、格式化和传输。在大多数情况下,设备访问通过regmap_read、regmap_write和regmap_update_bits来执行。这些是在向设备存储数据/从设备读取数据时应该始终记住的3个重要的函数。它们各自的原型如下:
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);
regmap_write:将数据写入设备。如果在regmap_config、max_register内设置过,将用它检查需要读取的寄存器地址是更大还是更小。如果传递的寄存器地址小于等于max_register,则写操作会执行;否则,regmap内核将返回无效I/O错误(-EIO)。之后立即调用函数writeable_reg。该回调函数在执行下一步操作前必须返回true。如果返回false,则返回-EIO,与操作停止。如果设置了wr_table,而不是writeable_reg,则结果如下。
- 如果寄存器地址在no_range内,则返回-EIO。
- 如果寄存器地址在yes_range内,则执行下一步。
- 如果寄存器地址不在no_range内,也不在yes_range内,则返回-EIO,操作中断。
- 如果cache_type != REGCACHE_NONE,则启用缓存。在这种情况下,首先更新缓存项,之后执行到硬件的写操作,否则不执行缓存动作。
- 如果提供了回调函数reg_write,则用它执行写入操作,将执行通用的regmap写功能。
regmap_read:从设备读取数据。其在相应数据结构(readable_reg和rd_table)上执行的功能类似于regmap_write。因此,如果提供了reg_read,则使用它执行读取操作,否则将执行通用的regmap读取函数。
regmap_update_bits:是一个三合一函数。它在寄存器映射上执行读/修改/写周期。
执行以下步骤来设置regmap子系统:I.MX6U 嵌入式 Qt 开发指南V1.1
- 根据设备的特性设置struct regmap_config结构体。如果需要,可以设置寄存器范围,如果有的话,则设置为默认值;如果需要,还可以设置cache_type,等等。如果需要自定义读/写函数,请将它们传递给reg_read/reg_write成员。
- 在probe函数中,根据总线是I2C还是SPI,使用regmap_init_i2c或regmap_init_spi分配regmap。
- 需要读取/写入寄存器时,请调用regmap_[read | write]函数。
- 完成regmap操作后,调用regmap_exit释放probe中分配的寄存器映射。
工业I/O(Industrial I/O,IIO)是专用于模数转换器(ADC)和数模转换器(DAC)的内核子系统。随着分散在内核源代码上由不同代码实现的传感器(具有模拟到数字或数字到模拟转换功能的测量设备)数量的增加,集中它们变得必要。这就是IIO子系统框架所要实现的功能,它以通用一致的方式来实现。乔纳森-卡梅隆Linux-IIO社区从2009年开始开发它。
加速度计、陀螺仪、电流/电压测量转换芯片、光传感器、压力传感器等都属于IIO系列设备。
IIO模型基于设备和通道架构。
IIO芯片是物理和硬件传感器/转换器,它作为字符设备提供给用户空间(当支持触发缓冲时)和sysfs目录项,该目录中包含一组文件,其中一些代表通道。单个通道用单个sysfs文件项表示。
下面是从用户空间与IIO驱动程序进行交互的两种方式。
/sys/bus/iio/iio:deviceX/: 代表传感器及其通道。
/dev/iio:deviceX: 字符设备,用于输出设备事件和数据缓冲区。
上图显示IIO框架在内核和用户空间的组织方式。该驱动程序使用IIO内核提供的功能和API来管理硬件,并向IIO内核报告处理情况。然后,IIO子系统通过sysfs接口和字符设备将整个底层机制抽象到用户空间,用户可以在其上执行系统调用。
IIO API分布在几个头文件中,如下所示:
#include /* 强制性的 */
#include /* 因为使用了sysfs,所以是强制性的 */
#include /* 强制使用触发缓冲区 */
#include /* 仅当在驱动程序中实现触发器(很少使用)时 */
#include /* 对于高级用户,管理IIO事件 */
IIO设备在内核中表示为struct iio_dev的实例,并由struct iio_info结构体描述。所有重要的IIO结构都在include/linux/iio/iio.h文件中定义。
iio_dev数据结构
iio_dev代表IIO设备,描述设备和驱动程序。它提供以下信息:
struct iio_dev {
......
/* 表示设备支持的不同模式。支持的模式如下:
* INDIO_DIRECT_MODE 设备提供sysfs类型的接口
* INDIO_BUFFER_TRIGGERED 设备支持硬件触发。当使用iio_triggered_buffer_setup()函数设置
* 触发缓冲区时,该模式会自动添加到设备。
* INDIO_BUFFER_SOFTWARE 设备具有硬件区。
* INDIO_BUFFER_HARDWARE 上述两种模式的组合。
*/
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内核创建的相关字符设备。 */
......
};
允许掩码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 {
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。
用于为IIO设备分配内存的函数是iio_device_alloc():
struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv)
dev是为其分配iio_dev的设备,sizeof_prive是为私有结构分配的内存空间。这样,传递每个设备(私有)数据结构就变得非常简单。如果分配失败,该函数返回NULL,如:
struct iio_dev *iiodev;
struct my_private_data *data;
iiodev = devm_iio_device_alloc(spi->dev, sizeof(*data));
if (!iiodev)
return -ENOMEM;
/* 私有数据提供预留内存地址 */
data = iio_priv(iiodev);
IIO设备内存分配后,下一步是填写不同的字段。完成后,必须使用iio_device_register函数向IIO子系统注册设备:
int iio_device_register(struct iio_dev *indio_dev)
该函数执行后,设备将准备好接收来自用户空间的请求。相反的操作(通常在释放函数中完成)是iio_device_unregister():
void iio_device_unregister(struct iio_dev *indio_dev)
一旦注销注册,iio_device_alloc分配的内存就可以通过iio_device_free释放:
void iio_device_free(struct iio_dev *dev)
以IIO设备作为参数,可以通过以下方式检索私有数据:
struct my_private_data *data = iio_priv(iiodev);
iio_info结构
struct iio_info结构用于声明IIO内核使用的钩子,以读取/写入通道/属性值:
struct iio_info {
/* 模块结构,用于确保chrdevs所属模块是正确的,通常设置为THIS_MODULE */
struct module *driver_module;
struct attribute_group *event_attrs;
const struct attribute_group *attrs; /* 设备属性 */
/* 用户读取设备sysfs文件属性时运行的回调函数。mask参数是位掩码,说明请求的值是哪种类型。chan通道 * 参数指出有关的通道。它可用于采样频率,用于将原始值转换为可用值或原始值自身的比例。 */
int (*read_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
int *val2,
long mask);
/* 用于向设备写入值的回调函数。例如,可以使用它来设置采样频率 */
int (*write_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val,
int val2,
long mask);
int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
long mask);
int (*read_event_config)(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir);
int (*write_event_config)(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
int state);
int (*read_event_value)(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info, int *val, int *val2);
int (*write_event_value)(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info, int val, int val2);
int (*validate_trigger)(struct iio_dev *indio_dev,
struct iio_trigger *trig);
int (*update_scan_mode)(struct iio_dev *indio_dev,
const unsigned long *scan_mask);
int (*debugfs_reg_access)(struct iio_dev *indio_dev,
unsigned reg, unsigned writeval,
unsigned *readval);
int (*of_xlate)(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec);
int (*hwfifo_set_watermark)(struct iio_dev *indio_dev, unsigned val);
int (*hwfifo_flush_to_buffer)(struct iio_dev *indio_dev,
unsigned count);
};
以下代码显示如何设置iio_info结构:
static const struct iio_info iio_dummy_info = {
.driver_module = THIS_MODULE,
.read_raw = &iio_dummy_read_raw,
.write_raw = &iio_dummy_write_raw,
.write_raw_get_fmt = &iio_dummy_write_raw_get_fmt,
......
};
/* 提供特定设备类型的接口函数和常量数据 */
iiodev->info = &iio_dummy_info;
IIO通道
通道代表单条采集线。例如,加速度计有3个通道(X、Y、Z),因为每个轴代表单个采集线。struct iio_chan_spec结构表示和描述内核中的单个通道:
struct iio_chan_spec {
/* 指出通道的测量类型。对于电压测量,它应该是IIO_VOLTAGE; 对于光传感器,它是IIO_LIGHT; 对于加速
* 度计,使用IIO_ACCEL。所有可用的类型在include/uapi/linux/iio/types.h中定义为enum iio_
* chan_type。要为给定的转换器编写驱动程序,请查看该文件,了解每个通道所属类型。*/
enum iio_chan_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,
};
在许多数据分析应用中,能够基于某些外部信号(触发器)捕获数据非常有用。这些触发器可能如下:
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设备驱动程序而言不是必需的。通过读取通道原始属性,也可以通过sysfs使用单次捕获,它只执行一次转换(对于所读取的通道属性)。缓冲模式允许连续转换,从而一次捕获多个通道。
IIO触发器和sysfs(用户空间)
sysfs中有两个位置与触发器相关
/sys/bus/iio/devices/triggerY/:一旦IIO触发器在IIO内核中注册并且对应于索引Y的触发器,就会创建该目录。该目录中至少有一个属性。
name:触发器名称,之后可用于与设备建立关联。
/sys/bus/iio/devices/iio:如果设备支持触发缓冲区,则会自动创建目录deviceX/trigger/*。在current_trigger文件中用触发器的名称就可以将触发器与设备相关联起来。
IIO缓冲区
IIO缓冲区提供连续的数据捕获,一次可以同时读取多个数据通道。可通过dev/iio:device字符设备节点从用户空间访问缓冲区。在触发器处理程序中,用于填充缓冲区的函数是iio_push_to_buffers_with_timestamp。负责为设备分配触发缓冲区的函数是iio_triggered_buffer_setup()。
IIO缓冲区的sysfs接口
IIO缓冲区在/sys/bus/iio/iio下有一个关联的属性目录:deviceX/buffer/*。以下是其一些属性:
IIO缓冲区设置
数据将被读取并推入缓冲区的通道称为扫描元素。它们的配置可通过/sys/bus/iio/iio:deviceX/scan_elements/*目录从用户空间访问,其中包含以下属性:
en:实际上是属性名称的后缀,用于启用频道。当且仅当其属性不为零时,触发的捕捉将包含此通道的数据取样。例如in_voltage0_en、in_voltage1_en等。
type:描述扫描元素数据在缓冲区内的存储,因此描述从用户空间读取它的形式。例如in_voatage0_type。格式为[be | le]:[s | u]bits/storagebitsXrepeat[>>shift]。
be 或 le :指出字节顺序(大端或小端)。
s 或 u :指出符号,带符号(2的补码)或无符号。
bits:有效数据位数。
storagebits:该通道在缓冲区中占用的位数。例如,一个值可能实际编码是12位(bit),但占用缓冲区中的16位(storagebits)。因此必须将数据向右移4位才能得到实际值。该参数取决于设备,应参考设备的数据手册。
shift:表示在屏蔽掉未使用的位之前应该移位数据值的次数。这个参数并不总是需要的。如果有效位数(bit)等于存储位数,则shift将为0。在设备数据手册中也可以找到该参数。
repeat:指出位/存储重复数量。当重复元素为0或1时,重复值被省略。
只有两种方法可以通过IIO框架访问数据:通过sysfs通道单次捕获,或通过IIO字符设备的连续模式(触发缓冲区)。
单次捕获
单次数据捕获通过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。
缓冲区数据访问
要使触发采集正常工作,必须在驱动程序中实现触发器支持。然后,要从用户空间获取,则必须:创建触发器并进行分配,启用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.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
/**
* 文件名 : 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