因实际项目资料涉密,在此将项目中Linux common的部分和学习心得总结于此,以备时习之。(部分示例代码为脱敏经过删减和简化处理,仅供参考,请见谅!)
参考图来源于网络,侵删。
keyword:IIO、Industrial I/O、ADC、DAC
一.background
工业及消费电子(手机、物联网、可穿戴设备)中大量使用ADC将模拟物理量转为数字量。加速度计、光传感器、陀螺仪、气压计、磁力计等许多传感器内部均集成ADC,并通过IIC/SPI接口对外提供数据。同时,某些SoC也提供on chip的ADC pin。为统一管理这类ADC传感器,Linux内核推出了IIO子系统,开发者需基于IIO子系统编写驱动。
二.简介
IIO(Industrial I/O,工业输入输出)是Linux内核中专用于管理模数转换器(ADC)和数模转换器(DAC)类设备的子系统。它于2009年由Jonathan Cameron加入Linux内核,旨在为各类传感器和数据转换设备提供统一的驱动框架。当你使用的传感器本质是 ADC 或 DAC 器件时,可以优先考虑使用 IIO 驱动框架。
1.iio_dev 结构体
IIO 子系统使用结构体 iio_dev 来描述一个具体 IIO 设备,此设备结构体定义在include/linux/iio/iio.h 文件中,使用IIO子系统的外设需要根据该结构体的成员进行注册。
struct iio_dev { int modes; /* 设备支持的工作模式 */ int currentmode; /* 当前使用的工作模式 */ struct device dev; /* Linux 设备对象 */ struct iio_buffer *buffer; /* 数据缓冲区 */ int scan_bytes; /* 单次采集的总字节数 */ const unsigned long *available_scan_masks; /* 允许的通道组合掩码列表 */ const unsigned long *active_scan_mask; /* 当前启用的通道掩码 */ bool scan_timestamp; /* 是否在数据包末尾附加时间戳 */ struct iio_trigger *trig; /* 当前绑定的触发器 */ struct iio_poll_func *pollfunc; /* 触发信号到来时的回调函数 */ struct iio_chan_spec const *channels; /* 通道配置数组 */ int num_channels; /* 通道总数 */ const char *name; /* 设备名称(用户空间可见) */ const struct iio_info *info; /* 驱动功能回调函数集 */ const struct iio_buffer_setup_ops *setup_ops; /* 缓冲启用/禁用钩子 */ struct cdev chrdev; /* 字符设备 */ };2.iio_dev 申请与释放
在使用之前要先申请 iio_dev,申请函数为 iio_device_alloc,函数原型如下:
struct iio_dev *iio_device_alloc(int sizeof_priv)sizeof_priv:私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为 iio_dev 的私有数据,这样可以直接通过 iio_device_alloc 函数同时完成 iio_dev 和设备结构体变量的内存申请。申请成功以后使用 iio_priv 函数来得到自定义的设备结构体变量首地址。
原生IIO使用的是iio_device_alloc,这种方式方式分配的设备对象,需要手动释放(调用iio_device_free()),通常在驱动remove或probe出错时进行释放。我们也可以使用devm(device managed)机制,设备驱动无需手动释放,资源会在设备销毁时自动清理,可有效防止内存泄漏。
struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv);此函数采用“一次分配,两块空间”的设计,在连续的内存中同时分配了struct iio_dev 和驱动私有数据(private data)的空间,这种设计能提升 CPU 缓存的利用率。
返回值:如果申请成功就返回一个指向已分配 iio_dev结构体的指针,如果失败就返回 NULL。
3.iio_info
在 iio_dev 结构体中,有一个关键成员指针 info,它指向 iio_info 结构体。iio_info 是 IIO 驱动开发中需要重点实现的部分,因为它定义了设备的具体操作接口。用户空间发起的各种访问请求,最终都会通过 VFS 层回调到 iio_info 所注册的函数中,从而实现对硬件设备的控制与数据采集。iio_info 结构体的定义位于内核源码 include/linux/iio/iio.h中。
struct iio_info { struct module *driver_module; const struct attribute_group *attrs; const struct attribute_group *event_attrs; 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, u64 event_code); int (*write_event_config)(struct iio_dev *indio_dev, u64 event_code, int state); int (*read_event_value)(struct iio_dev *indio_dev, u64 event_code, int *val); int (*write_event_value)(struct iio_dev *indio_dev, u64 event_code, int val); 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 (*fwnode_xlate)(struct iio_dev *indio_dev, const struct fwnode_reference_args *args); int (*hwfifo_set_watermark)(struct iio_dev *indio_dev, unsigned int val); };4.iio_chan_spec
IIO 子系统的核心抽象是**通道**,它代表传感器设备的一个独立数据采集维度。一个物理传感器可能包含多个功能单元,产生多路相互独立但有内在关联的数据。例如:
- 三轴加速度计包含 X、Y、Z 三个通道;
- 8 通道 ADC 芯片提供 8 个模拟输入通道;
- 温湿度传感器通常包含温度(temperature)和湿度(humidity)两个通道。
在 Linux 内核中,每个通道由 `struct iio_chan_spec` 结构体描述。该结构体定义了通道的类型、编号、数据类型、是否可读写以及数值转换关系等关键属性。其定义位于内核源码 `include/linux/iio/iio.h` 文件中。
驱动开发者在实现 IIO 驱动时,需要为设备的所有通道构造一个 `iio_chan_spec` 数组,并将其赋值给 `iio_dev` 结构体的 `channels` 成员,同时通过 `num_channels` 指定通道数量。内核和用户空间将依据这些通道定义来解析、展示和访问多路传感器数据。
三.IIO framework (platform)
IIO 框架主要用于 ADC 类的传感器,比如陀螺仪、加速度计、磁力计、光强度计等,这些传感器基本都是 IIC 或者 SPI 接口的。因此IIO 驱动的基础框架就是 IIC 或者 SPI,我们可以在 IIC 或 SPI 驱动里面在加上 regmap。当然了,有些 SOC 内部的 ADC 也会使用 IIO 框架,那么这个时候驱动的基础框架就是 platfrom(为使用dev-bus-drv模型,MTK的某些STB SoC就是如此)。
1.iio_dev 结构体
/* 自定义设备结构体 */ struct xxx_dev { struct device *dev; struct iio_dev *indio_dev; void __iomem *xxx int irq; ... };2.通道数组
static const struct iio_chan_spec xxx_channels[] = { };3.读函数
当读取 sysfs 中的文件的时候最终此函数会执行,此函数里面会从传感器里面读取各种数据,然后上传给应用。
static int xxx_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { return 0; }4.写函数
static int xxx_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask) { return 0; }5.iio_info 结构体变量
static const struct iio_info xxx_info = { .read_raw = xxx_read_raw, .write_raw = xxx_write_raw, .write_raw_get_fmt = xxx_write_raw_get_fmt, .write_event_value = xxx_write_event_value, .read_event_value = xxx_read_event_value, .write_event_config = xxx_write_event_config, .read_event_config = xxx_read_event_config, };6.driver probe func
按需实现,在dev与drv match成功后调用
static int xxx_probe(struct platform_device *pdev) { }7.driver remove func
按需实现,在设备卸载时调用
static int xxx_remove(struct platform_device *pdev) { }