一、简介
加速度计、陀螺仪、电流/电压测量芯片、光传感器、压力传感器等都属于IIO 系列设备
IIO模型基于设备和通道架构:
设备代表芯片本身,它位于整个层次结构的顶层
通道表示设备的单个采集线,设备可能有一个或多个通道。例如,加速度计是具有3个通道的设备,每个轴(X、Y和Z)都有一个通道。
用户空间与IIO驱动程序进行交互的两种方式:
/sys/bus/iio/iio:deviceX:代表传感器及其通道
/dev/iio:deviceX:字符设备,用于输出设备事件和数据缓冲区。
IIO API头文件
#include <linux/iio/iio.h> /* mandatory */
#include <linux/iio/sysfs.h> /* mandatory since sysfs is used */
#include <linux/iio/events.h> /* For advanced users, to manage iio events */
#include <linux/iio/buffer.h> /* mandatory to use triggered buffers */
#include <linux/iio/trigger.h>/* Only if you implement trigger in your driver (rarely used)*/
二、IIO数据结构
结构体
</include/linux/iio/iio.h>/* modes选项 */
#define INDIO_DIRECT_MODE 0x01 //设备提供的sysfs接口/*表示设备支持硬件触发器,使用iio_triggered_buffer_setup()函数设 *置触发缓冲区时,此模式会自动添加到设备中.*/
#define INDIO_BUFFER_TRIGGERED 0x02
#define INDIO_BUFFER_SOFTWARE 0x04
#define INDIO_BUFFER_HARDWARE 0x08//设备具有硬件缓冲区
#define INDIO_EVENT_TRIGGERED 0x10
#define INDIO_HARDWARE_TRIGGERED 0x20
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);
};
</include/linux/iio/iio.h>
struct iio_dev {
int id;
struct module *driver_module;
int modes;//表示设备支持的不同模式
int currentmode;//设备实际使用的模式
struct device dev;//IIO设备绑定的设备
struct iio_event_interface *event_interface; //数据缓冲区,使用触发缓冲区模式会被推送到用户空间
struct iio_buffer *buffer;
struct list_head buffer_list;
int scan_bytes;//捕获并提供给缓冲区的字节数
struct mutex mlock; //使用触发缓冲器时,可以启用通道捕获并将其馈入IIO缓冲区。 //如果不想允许某些通道启用,则应仅使用允许的通道填充此数组
const unsigned long *available_scan_masks;//可选数组的位掩码
unsigned masklength; /*只有来自这些通道的数据能被推入缓冲区。例如,对于8通道ADC转换器, * 如果只启用第一个(0),第三个(2)和最后一个(7)通道,则位掩码 *将为0b10000101(0x85)。active_scan_mask将设置为0x85。然后,驱动 * 程序可以使用for_each_set_bit宏遍历每个设置位,根据通道获取数据, *并填充缓冲区。*/
const unsigned long *active_scan_mask;//已启用通道的位掩码
bool scan_timestamp;//是否将捕获时间戳推入缓冲区
unsigned scan_index_timestamp;
struct iio_trigger *trig;//当前设备的触发器
bool trig_readonly;
struct iio_poll_func *pollfunc;//在接收的触发器上运行的函数
struct iio_poll_func *pollfunc_event;
struct iio_chan_spec const *channels;
int num_channels;//指定通道数量
struct list_head channel_attr_list;
struct attribute_group chan_attr_group;
const char *name;//设备名称
const struct iio_info *info;//来自驱动程序的回调和常量信息
clockid_t clock_id;
struct mutex info_exist_lock; //启用/禁用缓冲区之前和之后调用的一组回调函数
const struct iio_buffer_setup_ops *setup_ops;
struct cdev chrdev;//IIO内核创建的相关字符设备
#define IIO_MAX_GROUPS 6
const struct attribute_group *groups[IIO_MAX_GROUPS + 1];
int groupcounter;
unsigned long flags;
#if defined(CONFIG_DEBUG_FS)
struct dentry *debugfs_dentry;
unsigned cached_reg_addr;
#endif
};
API函数
</include/linux/iio/iio.h>//为IIO设备分配内存,sizeof_priv是为私有结构分配的内存空间
struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv);
int iio_device_register(struct iio_dev *indio_dev);//向IIO子系统注册设备
void iio_device_unregister(struct iio_dev *indio_dev);
int devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev);
static void *iio_priv(const struct iio_dev *indio_dev)//获取私有数据
iio_info数据结构,声明IIO内核使用的钩子函数,以读取/写入属性/通道
</include/linux/iio/iio.h>
struct iio_info {
const struct attribute_group *event_attrs;
const struct attribute_group *attrs;//设备属性
/*用户读取设备sysfs文件属性时运行的回调函数*/
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);
};
三、IIO通道
通道代表单条采集线,例如,加速度计有3个通道(X,Y,Z),因为每个轴代表单个采集线。struct iio_chan_spec结构表示和描述内核中单个通道:
</include/linux/iio/iio.h>
struct iio_chan_spec {
enum iio_chan_type type;//指出通道产生的测量类型
int channel;//当indexed设置为1时,指定通道索引
int channel2;//当.modfied设置为1时,指定通道修饰符
unsigned long address;
int scan_index;
struct {//负责确定如何将通道的值存储到缓冲区中的元素
char sign;//n表示数据的符号,并匹配模式中的[s | u]
u8 realbits;
u8 storagebits;
u8 shift;
u8 repeat;
enum iio_endian endianness; //排序
} scan_type;
long info_mask_separate;//将属性标记为专属于此通道
long info_mask_separate_available;
long info_mask_shared_by_type;//将该属性标记为由相同类型的所有通道共享
long info_mask_shared_by_type_available;
long info_mask_shared_by_dir;//将该属性标记为由相同方向的所有通道共享
long info_mask_shared_by_dir_available;
long info_mask_shared_by_all;//将该属性标记为所有类型的所有通道共享
long info_mask_shared_by_all_available;
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;
unsigned modified:1;
unsigned indexed:1;//指出通道属性名称是否具有索引
unsigned output:1;
unsigned differential:1;
};
四、通道属性和命名约定
属性的名称由IIO核心自动生成,具有以下模式:{direction} _ {type} _ {index} _ {modifier} _ {info_mask}
direction方向对应于属性方向:
static const char * const iio_direction[] = { [0] = "in", [1] = "out", };
type对应通道类型
</drivers/iio/industrialio-core.c>
static const char * const iio_chan_type_name_spec[] = {
[IIO_VOLTAGE] = "voltage",
[IIO_CURRENT] = "current",
[IIO_POWER] = "power",
[IIO_ACCEL] = "accel",
[IIO_ANGL_VEL] = "anglvel",
....
}
index取决于通道的.indexd字段是否设置,如果设置,索引将从.channel字段中获取,以替换{index}模式。
modifier 模式取决于通道所设置的.modified字段。如果设置,修饰符将从.channel2字段中获取,{modifier}模式将根据char数组struct iio_modifier_names结构替换。
static const char * const iio_modifier_names[] = {
[IIO_MOD_X] = "x",
[IIO_MOD_Y] = "y",
[IIO_MOD_Z] = "z",
[IIO_MOD_X_AND_Y] = "x&y",
[IIO_MOD_X_AND_Z] = "x&z",
[IIO_MOD_Y_AND_Z] = "y&z",
[...]
[IIO_MOD_CO2] = "co2",
[IIO_MOD_VOC] = "voc",
};
info_mask取决于char数组iio_chan_info_postfix中的通道信息掩码,私有或共享索引值
static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_RAW] = "raw",
[IIO_CHAN_INFO_PROCESSED] = "input",
[IIO_CHAN_INFO_SCALE] = "scale",
[IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
[...]
[IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency",
[IIO_CHAN_INFO_FREQUENCY] = "frequency",
[...]
};
通道区分
每种通道类型有多个数据通道时,可能会遇到麻烦。如何识别它们?有两种解决方案:索引和修饰符。
使用索引 index
给定具有一个通道线的ADC器件,不需要索引。通道定义如下:
static const struct iio_chan_spec adc_channels[] = {
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
}
//描述的通道产生的属性名称将是in_voltage_raw
///sys/bus/iio/iio:deviceX/in_voltage_raw
现在让我们看一下有4个甚至8个通道的转换器。我们如何识别它们?解决方案是使用索引。将.indexed字段设置为1将使用.channel值替换{index}模式来替换通道属性名称:
static const struct iio_chan_spec adc_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 2,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 3,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
}
//生成的通道属性为
// /sys/bus/iio/iio:deviceX/in_voltage0_raw
// /sys/bus/iio/iio:deviceX/in_voltage1_raw
// /sys/bus/iio/iio:deviceX/in_voltage2_raw
// /sys/bus/iio/iio:deviceX/in_voltage3_raw
使用修饰符 modified
static const struct iio_chan_spec mylight_channels[] = {
{
.type = IIO_INTENSITY,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_IR,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_INTENSITY,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_BOTH,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_LIGHT,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.info_mask_shared =BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
}
// /sys/bus/iio/iio:deviceX/in_intensity_ir_raw 用于测量IR强度的通道
// /sys/bus/iio/iio:deviceX/in_intensity_both_raw用于测量红外和可见光的通道
// /sys/bus/iio/iio:deviceX/in_illuminance_input用于处理后的数据
// /sys/bus/iio/iio:deviceX/sampling_frequency 用于采样频率,由所有人共享
五、触发缓冲区支持
在许多数据分析中,能够基于某些外部信号(触发器)捕获数据非常有用,这些触发器可能如下:
数据就绪信号
连接到某个外部系统(GPIO)的IRQ线。
处理器周期性中断
用户空间读写sysfs中的特定文件。
现有的触发器驱动程序:
iio-trig-interrupt:这为使用IRQ作为IO触发器提供支持。在旧的内核版本中,它曾使用的是 iio-trig-gpio。启用此触发模式的内核选项是CONFIG_IIO_INTERRUPT_TRIGGER。如果构建为模块,该模块将称为iio-trig-interrupt.
iio-trig-hrttimer:提供基于频率的IIO 触发器,使用HRT 作为中断源。在旧内核版本中,它曾使用的是 iio-trg-rtc。负责这种触发模式的内核选项是 IIO_HRTIMER_TRIGGER。如果构建为模块,则该模将称为iio-trig-hrtimer。
iio-trig-sysfs:这允许使用sysfs项触发数据捕获。内核选项CONFIG_IIO_SYSFS_TRIGGER。
iio-trig-bfin-timer:这允许使用blackfin定时器作为IIO触发器
IIO提供API,使用它们可以进行如下操作:
声明任意数量的触发器。
选择那些通道的数据将推入缓冲区。
IIO 设备提供触发缓冲区支持时,必须设置 iiodev->pollfunc,触发器触发时执行它,该处理程序负责通过 indio_dev-> active_scan_mask查找启用的通道,检索其数据,并使用 iio_push_to_buffers_with_timestamp函数将它们提供给indio_dev-> buffer。因此,缓冲区和触发器在IIO子系统中有紧密的联系。
六、触发器设置
填写iio_buffer_setup_ops结构
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);//检查扫描掩码是否有效
};
编写与触发器关联的上半部,提供与捕获相关的时间戳
irqreturn_t sensor_iio_pollfunc(int irq, void *p)
{
pf->timestamp = iio_get_time_ns((struct indio_dev *)p);
return IRQ_WAKE_THREAD;//该中断使用了中断线程化,需要唤醒中断线程执行该中断回调函数
}
编写触发器的下半部,它将从每个启动的通道读取数据,并把它们送入缓冲区
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 *indio_dev = pf->indio_dev;
/* one can use lock here to protect the buffer */
/* mutex_lock(&my_mutex); */
/* read data for each active channel */
for_each_set_bit(bit, indio_dev->active_scan_mask,
indio_dev->masklength)
buf[i++] = sensor_get_data(bit)
/*
* If iio_dev.scan_timestamp = true, the capture timestamp
* will be pushed and stored too, as the last element in the
* sample data buffer before pushing it to the device buffers.
*/
iio_push_to_buffers_with_timestamp(indio_dev, buf, timestamp);
/* Please unlock any lock */
/* mutex_unlock(&my_mutex); */
/* Notify trigger */
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
最后,在probe函数中,必须在使用iio_device_register()注册设备之前连接触发器和缓冲区
</include/linux/iio/triggered_buffer.h>
int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
irqreturn_t (*h)(int irq, void *p),
irqreturn_t (*thread)(int irq, void *p),
const struct iio_buffer_setup_ops *setup_ops);
避免触发器处理程序和read_raw()钩子将尝试同时访问设备。用于检查是否实际使用缓冲模式的函数是iio_buffer_enabled()。
触发缓冲必须使用触发器。 它告诉驱动程序何时从设备读取样本并将其放入缓冲区。 触发缓冲对于编写IIO设备驱动程序不是必需的。 通过读取通道的原始属性,也可以通过sysf使用单次捕获,这只会执行单次转换(对于正在读取的通道属性)。 缓冲模式允许连续转换,从而在单次捕获多个通道。
</include/linux/iio/iio.h>
static bool iio_buffer_enabled(struct iio_dev *indio_dev)//测试给定IIO设备的缓冲区是否启用
static int my_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
[...]
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (iio_buffer_enabled(indio_dev))
return -EBUSY;
[...]
}
七、IIO触发器和sysfs(用户空间)
sysfs中有两个与触发器相关的位置:
/sys/bus/iio/devices/triggerY/:一旦IIO触发器注册到IIO核心并且对应于索引为Y的触发器,就会创建该目录。目录中至少有一个属性:
name:这是可以在以后用于与设备关联的触发器名称
另一个可能是采样频率或其他,和触发器类型相关
/sys/bus/iio/devices/iio:deviceX/trigger/*:如果设备支持触发缓冲区,将自动创建目录。 通过在current_trigger文件中写入触发器的名称,可以将触发器与我们的设备相关联。
sysfs触发器接口
通过CONFIG_IIO_SYSFS_TRIGGER = y,config选项在内核中启用sysfs触发器,将自动创建/sys/bus/iio/devices/iio_sysfs_trigger/文件夹,并可用于sysfs触发器管理。 目录中将有两个文件add_trigger和remove_trigger。 它的驱动程序在
drivers/iio/trigger/iio-trig-sysfs.c中。
add_trigger文件和remove_trigger文件
用于创建新的sysfs触发器,将正值(将用作触发器ID)写入该文件即可创建新触发器。它会创建的sysfs触发器,可在/sys/bus/iio/devicecs/triggerX处访问,其中X是触发器编号:
//创建sysfs触发器
echo 2 > add_trigger
//将设备与给定触发器相关联包括将触发器的名称写入设备触发器目录下可用的current_trigger文件
echo sysfstrig2 > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
//要从设备分离触发器,应该将空字符串写入设备触发器目录的current_trigger文件,如下所示:
echo "" > iio:device0/trigger/current_trigger
//删除触发器
echo 2 > remove_trigger
中断触发器接口
static struct resource iio_irq_trigger_resources[] = {
[0] = {
.start = IRQ_NR_FOR_YOUR_IRQ,
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_LOWEDGE,
},
};
static struct platform_device iio_irq_trigger = {
.name = "iio_interrupt_trigger",
.num_resources = 1,
.reource = iio_irq_trigger_resources,
};
platform_device_register(&&iio_irq_trigger);
// 声明为IRQ触发器,将导致加载IRQ触发器独立模块。IRQ触发器名称的格式为irqtrigX,
//其中X为对应于刚传递的虚拟IRQ, 查看cat /sys/bus/iio/devices/trigger0/name
// 下面是在设备树节点声明IRQ触发器接口:
mylabel : my_trigger@0 {
compatible = "iio_interrupt_trigger";
interrupt-parent = <&gpio4>;
interrupts = <30 0x0>;
};
hrtimer触发器接口
hrtimer触发器依赖于configfs文件系统(请参阅内核源代码中的Documentation / iio / iio_configfs.txt),可以通过CONFIG_IIO_CONFIGFS配置选项启用它,并挂载在系统上(通常位于/ config目录下)
# mkdir /config
# mount -t configfs none /config
//创建
$ mkdir /config/iio/triggers/hrtimer/my_trigger_name
//移除
$ rmdir /config/iio/triggers/hrtimer/my_trigger_name
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: 指定阻塞读取应等待的扫描元素数。 例如,如果使用轮询,它将阻塞,直到达到watermark。 只有当watermark大于请求的读取量时才有意义。 它不会影响非阻塞读取。 可以在超时时阻止轮询并在超时到期后读取可用样本,因此具有最大延迟保证。
IIO缓冲区设置
将要读取数据并将其推入缓冲区的通道称为扫描元素(scan element )。 可以从用户空间通过/sys/bus/iio/iio:deviceX/scan_elements / *目录访问它们的配置,其中包含以下属性:
en:用于启用通道。 当且仅当其属性为非零时,触发捕获将包含此通道的数据样本。
type: 描述了缓冲区内的扫描元素数据存储,因此描述了从用户空间读取它的形式。格式为:
[s|u]bits/storagebitsXrepeat[>>shift].
be或le指定字节序(大或小)
s或u指定符号(带符号(2的补码)或无符号)
bits 是有效数据位的数量
storagebits :是此通道在缓冲区中占用的位数。 也就是说,一个值可以用12位(位)真正编码,但在缓冲区中占用16位(存储位)。 因此,必须将数据向右移动四次以获得实际值。 此参数取决于设备,应参考其数据表。
shift:表示在屏蔽掉未使用的位之前应该移位数据值的次数。 并不总是需要此参数。 如果有效位(位)的数量等于存储位的数量,则移位将为0.还可以在器件数据手册中找到该参数。
repeat 指定位/存储位重复的数量。 当repeat元素为0或1时,则省略重复值
//用于3轴加速度计的驱动程序,具有12位分辨率,其中数据存储在两个8位寄存器中,如下所示:
7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+
|D3 |D2 |D1 |D0 | X | X | X | X | (LOW byte, address 0x06)
+---+---+---+---+---+---+---+---+
7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+
|D11|D10|D9 |D8 |D7 |D6 |D5 |D4 | (HIGH byte, address 0x07)
+---+---+---+---+---+---+---+---+
//每个轴将具有以下扫描元素类型:
$ cat /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_y_type
$ le:s12/16>>4
struct struct iio_chan_spec accel_channels[] = {
{
.type = IIO_ACCEL,
.modified = 1,
.channel2 = IIO_MOD_X,
/* other stuff here */
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 12,
.storagebits = 16,
.shift = 4,
.endianness = IIO_LE,
},
}
/* similar for Y (with channel2 = IIO_MOD_Y, scan_index = 1)
* and Z (with channel2 = IIO_MOD_Z, scan_index = 2) axis
*/
}