17、Regmap API与IIO框架:设备驱动开发的高效工具
Regmap API与IIO框架:设备驱动开发的高效工具
1. Regmap API概述
Regmap API是一种寄存器映射抽象,支持SPI和I2C协议,对于编写通用驱动程序而言是绝佳选择。其具有通用性和同质性,不同总线类型仅初始化方式有所不同,其他功能函数相同。
1.1 regmap初始化
在驱动的探测函数中,需根据支持的协议调用
regmap_init_i2c()
或
regmap_init_spi()
来初始化regmap。在初始化之前,必须填充
regmap_config
元素。使用完regmap后,可调用
regmap_exit()
函数释放之前分配的寄存器映射。
void regmap_exit(struct regmap *map)
1.1.1 SPI初始化
SPI初始化通过
regmap_init_spi()
函数将设备访问转换为SPI命令。
struct regmap * regmap_init_spi(struct spi_device *spi, const struct regmap_config);
以下是一个完整示例:
static int foo_spi_probe(struct spi_device *client)
{
int err;
struct regmap *my_regmap;
struct regmap_config bmp085_regmap_config;
/* fill bmp085_regmap_config somewhere */
[...]
client->bits_per_word = 8;
my_regmap = regmap_init_spi(client, &bmp085_regmap_config);
if (IS_ERR(my_regmap)) {
err = PTR_ERR(my_regmap);
dev_err(&client->dev, "Failed to init regmap: %d\n", err);
return err;
}
[...]
}
1.1.2 I2C初始化
I2C初始化使用
regmap_init_i2c()
函数将设备访问转换为I2C命令。
struct regmap * regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config);
以下是一个完整示例:
static int bar_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
{
struct my_struct * bar_struct;
struct regmap_config regmap_cfg;
/* fill regmap_cfgsome where */
[...]
bar_struct = kzalloc(&i2c->dev, sizeof(*my_struct), GFP_KERNEL);
if (!bar_struct)
return -ENOMEM;
i2c_set_clientdata(i2c, bar_struct);
bar_struct->regmap = regmap_init_i2c(i2c, ®map_config);
if (IS_ERR(bar_struct->regmap))
return PTR_ERR(bar_struct->regmap);
bar_struct->dev = &i2c->dev;
bar_struct->irq = i2c->irq;
[...]
}
1.2 设备访问函数
Regmap API处理数据解析、格式化和传输,常用的设备访问函数有
regmap_read
、
regmap_write
和
regmap_update_bits
。
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_write(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);
1.2.1 regmap_write
该函数用于向设备写入数据。若
regmap_config
中设置了
max_register
,会检查寄存器地址是否有效。若地址有效,还会调用
writeable_reg
回调函数,若回调返回
true
则继续执行写入操作,否则返回
-EIO
错误。若设置了
wr_table
,则根据寄存器地址所在范围进行相应处理。若启用了缓存(
cache_type != REGCACHE_NONE
),会先更新缓存再写入硬件;若提供了
reg_write
回调函数,则使用该函数执行写入操作,否则执行通用的regmap写入函数。
1.2.2 regmap_read
该函数用于从设备读取数据,工作方式与
regmap_write
类似,使用
readable_reg
和
rd_table
进行相应检查。若提供了
reg_read
回调函数,则使用该函数执行读取操作,否则执行通用的regmap读取函数。
1.2.3 regmap_update_bits
该函数是一个三合一函数,执行寄存器映射的读/修改/写循环。
int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val)
其底层调用
_regmap_update_bits
函数:
static int _regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val, bool *change)
{
int ret;
unsigned int tmp, orig;
ret = _regmap_read(map, reg, &orig);
if (ret != 0)
return ret;
tmp = orig & ~mask;
tmp |= val & mask;
if (tmp != orig) {
ret = _regmap_write(map, reg, tmp);
*change = true;
} else {
*change = false;
}
return ret;
}
使用时,需将需要更新的位在
mask
中设置为1,在
val
中设置相应的值。
1.3 特殊的regmap_multi_reg_write函数
该函数用于向设备写入多个寄存器。
int regmap_multi_reg_write(struct regmap *map, const struct reg_sequence *regs, int num_regs)
struct reg_sequence
定义如下:
struct reg_sequence {
unsigned int reg;
unsigned int def;
unsigned int delay_us;
};
以下是使用示例:
static const struct reg_sequence foo_default_regs[] = {
{ FOO_REG1, 0xB8 },
{ BAR_REG1, 0x00 },
{ FOO_BAR_REG1, 0x10 },
{ REG_INIT, 0x00 },
{ REG_POWER, 0x00 },
{ REG_BLABLA, 0x00 },
};
static int probe ( ...)
{
[...]
ret = regmap_multi_reg_write(my_regmap, foo_default_regs, ARRAY_SIZE(foo_default_regs));
[...]
}
1.4 其他设备访问函数
regmap_bulk_read()
和
regmap_bulk_write()
用于从/向设备读取/写入多个寄存器,适用于处理大块数据。
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count);
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count);
1.5 regmap与缓存
Regmap支持缓存,是否使用缓存系统取决于
regmap_config
中
cache_type
字段的值。支持的缓存类型有:
enum regcache_type {
REGCACHE_NONE,
REGCACHE_RBTREE,
REGCACHE_COMPRESSED,
REGCACHE_FLAT,
};
默认值为
REGCACHE_NONE
,表示禁用缓存。其他值定义了缓存的存储方式。设备某些寄存器可能有预定义的上电复位值,可将这些值存储在
reg_defaults
数组中,以加快设备访问速度。
struct reg_default {
unsigned int reg;
unsigned int def;
};
若
cache_type
设置为
REGCACHE_NONE
,则
reg_defaults
将被忽略。若未设置
default_reg
但启用了缓存,会自动创建相应的缓存结构。以下是
LTC3589
调节器驱动的示例:
static const struct reg_default ltc3589_reg_defaults[] = {
{ LTC3589_SCR1, 0x00 },
{ LTC3589_OVEN, 0x00 },
{ LTC3589_SCR2, 0x00 },
{ LTC3589_VCCR, 0x00 },
{ LTC3589_B1DTV1, 0x19 },
{ LTC3589_B1DTV2, 0x19 },
{ LTC3589_VRRCR, 0xff },
{ LTC3589_B2DTV1, 0x19 },
{ LTC3589_B2DTV2, 0x19 },
{ LTC3589_B3DTV1, 0x19 },
{ LTC3589_B3DTV2, 0x19 },
{ LTC3589_L2DTV1, 0x19 },
{ LTC3589_L2DTV2, 0x19 },
};
static const struct regmap_config ltc3589_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.writeable_reg = ltc3589_writeable_reg,
.readable_reg = ltc3589_readable_reg,
.volatile_reg = ltc3589_volatile_reg,
.max_register = LTC3589_L2DTV2,
.reg_defaults = ltc3589_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(ltc3589_reg_defaults),
.use_single_rw = true,
.cache_type = REGCACHE_RBTREE,
};
1.6 搭建regmap子系统的步骤
-
根据设备特性设置
struct regmap_config,包括寄存器范围、默认值、缓存类型等。若需要自定义读写函数,可将其传递给reg_read/reg_write字段。 -
在探测函数中,根据总线类型(I2C或SPI)使用
regmap_init_i2c或regmap_init_spi分配regmap。 -
需要读写寄存器时,调用
regmap_[read|write]函数。 -
使用完regmap后,调用
regmap_exit释放探测函数中分配的寄存器映射。
1.7 regmap示例
以下是一个模拟SPI设备驱动的示例:
/* mandatory for regmap */
#include <linux/regmap.h>
/* Depending on your need you should include other files */
static struct private_struct
{
/* Feel free to add whatever you want here */
struct regmap *map;
int foo;
};
static const struct regmap_range wr_rd_range[] =
{
{
.range_min = 0x20,
.range_max = 0x4F,
},
{
.range_min = 0x60,
.range_max = 0x7F
},
};
struct regmap_access_table drv_wr_table =
{
.yes_ranges = wr_rd_range,
.n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
struct regmap_access_table drv_rd_table =
{
.yes_ranges = wr_rd_range,
.n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
static bool writeable_reg(struct device *dev, unsigned int reg)
{
if (reg >= 0x20 && reg <= 0x4F)
return true;
if (reg >= 0x60 && reg <= 0x7F)
return true;
return false;
}
static bool readable_reg(struct device *dev, unsigned int reg)
{
if (reg >= 0x20 && reg <= 0x4F)
return true;
if (reg >= 0x60 && reg <= 0x7F)
return true;
return false;
}
static int my_spi_drv_probe(struct spi_device *dev)
{
struct regmap_config config;
struct custom_drv_private_struct *priv;
unsigned char data;
/* setup the regmap configuration */
memset(&config, 0, sizeof(config));
config.reg_bits = 8;
config.val_bits = 8;
config.write_flag_mask = 0x80;
config.max_register = 0x80;
config.fast_io = true;
config.writeable_reg = drv_writeable_reg;
config.readable_reg = drv_readable_reg;
/*
* If writeable_reg and readable_reg are set,
* there is no need to provide wr_table nor rd_table.
* Uncomment below code only if you do not want to use
* writeable_reg nor readable_reg.
*/
//config.wr_table = drv_wr_table;
//config.rd_table = drv_rd_table;
/* allocate the private data structures */
/* priv = kzalloc */
/* Init the regmap spi configuration */
priv->map = regmap_init_spi(dev, &config);
/* Use regmap_init_i2c in case of i2c bus */
/*
* Let us write into some register
* Keep in mind that, below operation will remain same
* whether you use SPI or I2C. It is and advantage when
* you use regmap.
*/
regmap_read(priv->map, 0x30, &data);
[...] /* Process data */
data = 0x24;
regmap_write(priv->map, 0x23, data); /* write new value */
/* set bit 2 (starting from 0) and 6 of register 0x44 */
regmap_update_bits(priv->map, 0x44, 0b00100010, 0xFF);
[...] /* Lot of stuff */
return 0;
}
2. IIO框架概述
Industrial I/O (IIO) 是一个内核子系统,专门用于模拟 - 数字转换器 (ADC) 和数字 - 模拟转换器 (DAC)。随着具有模拟 - 数字或数字 - 模拟转换能力的传感器数量不断增加,且代码实现分散在不同的内核源文件中,IIO框架以通用和同质的方式将这些设备整合在一起。自2009年以来,Jonathan Cameron和Linux IIO社区一直在开发该框架。加速度计、陀螺仪、电流/电压测量芯片、光传感器、压力传感器等都属于IIO设备家族。
2.1 IIO模型架构
IIO模型基于设备和通道架构:
-
设备
:代表芯片本身,是层次结构的顶层。
-
通道
:代表设备的单个采集线路,一个设备可能有一个或多个通道。例如,加速度计是一个具有三个通道的设备,分别对应X、Y和Z轴。
IIO芯片作为物理和硬件传感器/转换器,在用户空间以字符设备(支持触发缓冲时)和
sysfs
目录条目的形式暴露,其中一些文件代表通道,单个通道由单个
sysfs
文件条目表示。
2.2 用户空间与IIO驱动的交互方式
-
/sys/bus/iio/iio:deviceX/:代表传感器及其通道。 -
/dev/iio:deviceX:是一个字符设备,用于导出设备的事件和数据缓冲区。
2.3 IIO框架架构和布局
IIO框架在内核和用户空间之间的组织方式如下:驱动程序管理硬件并将处理结果报告给IIO核心,使用IIO核心提供的一组工具和API。IIO子系统通过
sysfs
接口和字符设备将底层机制抽象给用户空间,用户可以在其上执行系统调用。
2.4 IIO API头文件
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)*/
2.5 IIO数据结构
IIO设备在内核中由
struct iio_dev
实例表示,并由
struct iio_info
结构描述。所有重要的IIO结构都在
include/linux/iio/iio.h
中定义。
2.5.1 iio_dev结构
该结构描述了IIO设备和驱动程序,包含以下重要信息:
struct iio_dev {
[...]
int modes;
int currentmode;
struct device dev;
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;
};
-
modes
:表示设备支持的不同模式,包括
INDIO_DIRECT_MODE(设备提供sysfs类型接口)、INDIO_BUFFER_TRIGGERED(设备支持硬件触发)、INDIO_BUFFER_HARDWARE(设备有硬件缓冲区)和INDIO_ALL_BUFFER_MODES(前两者的并集)。 - currentmode :表示设备实际使用的模式。
-
dev
:表示IIO设备关联的
struct device。 - buffer :数据缓冲区,在启用触发缓冲模式时推送到用户空间。
- scan_bytes :捕获并馈送到缓冲区的字节数。
- available_scan_masks :可选的允许位掩码数组,用于限制触发缓冲时可启用的通道。
- active_scan_mask :启用通道的位掩码,仅将这些通道的数据推送到缓冲区。
- scan_timestamp :指示是否将捕获时间戳推送到缓冲区。
- trig :当前设备的触发器(支持缓冲模式时)。
- pollfunc :触发器收到时运行的函数。
- channels :通道规范结构表,描述设备的每个通道。
- num_channels :通道数量。
- name :设备名称。
- info :驱动程序的回调和常量信息。
- setup_ops :在缓冲区启用/禁用前后调用的回调函数集。
- chrdev :IIO核心创建的关联字符设备。
2.5.2 内存分配、注册和注销
使用
iio_device_alloc()
函数为IIO设备分配内存:
struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv)
分配内存后,填充不同字段,然后使用
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)
2.6 IIO框架操作流程
以下是使用IIO框架的主要操作流程:
graph TD;
A[分配IIO设备内存] --> B[填充iio_dev字段];
B --> C[注册IIO设备];
C --> D[处理设备操作];
D --> E[注销IIO设备];
2.7 总结
Regmap API和IIO框架为设备驱动开发提供了强大而高效的工具。Regmap API简化了SPI和I2C设备的寄存器操作,通过统一的接口和缓存机制提高了开发效率和设备访问速度。IIO框架则为模拟 - 数字和数字 - 模拟转换设备提供了统一的管理和交互方式,使得不同类型的传感器能够更方便地集成到内核中。掌握这两个工具,开发者可以更轻松地开发出高质量的设备驱动程序。
3. Regmap API与IIO框架的综合应用及深入分析
3.1 Regmap API与IIO框架的协同工作
在实际的设备驱动开发中,Regmap API和IIO框架可以协同工作,以实现更复杂的功能。例如,对于一个支持SPI或I2C接口的传感器设备,我们可以使用Regmap API来进行寄存器的读写操作,同时使用IIO框架来管理设备的通道和数据采集。
以下是一个简单的示例,展示了如何将两者结合使用:
#include <linux/regmap.h>
#include <linux/iio/iio.h>
// 假设这是一个SPI设备
static struct spi_device *my_spi_device;
static struct iio_dev *my_iio_dev;
static struct regmap *my_regmap;
// 初始化Regmap
static int init_regmap(void) {
struct regmap_config config = {
.reg_bits = 8,
.val_bits = 8,
// 其他配置...
};
my_regmap = regmap_init_spi(my_spi_device, &config);
if (IS_ERR(my_regmap)) {
return PTR_ERR(my_regmap);
}
return 0;
}
// 初始化IIO设备
static int init_iio_device(void) {
my_iio_dev = iio_device_alloc(sizeof(struct my_private_data));
if (!my_iio_dev) {
return -ENOMEM;
}
// 填充iio_dev字段
my_iio_dev->name = "my_iio_device";
my_iio_dev->modes = INDIO_DIRECT_MODE;
// 其他配置...
// 注册IIO设备
return iio_device_register(my_iio_dev);
}
// 读取传感器数据
static int read_sensor_data(unsigned int reg, unsigned int *val) {
return regmap_read(my_regmap, reg, val);
}
// 主初始化函数
static int my_driver_init(void) {
int ret;
ret = init_regmap();
if (ret) {
return ret;
}
ret = init_iio_device();
if (ret) {
regmap_exit(my_regmap);
return ret;
}
return 0;
}
// 主退出函数
static void my_driver_exit(void) {
iio_device_unregister(my_iio_dev);
regmap_exit(my_regmap);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
3.2 Regmap API的性能优化
Regmap API提供了一些机制来优化设备的读写性能,特别是在使用缓存时。以下是一些性能优化的建议:
-
合理使用缓存
:根据设备的特点和使用场景,选择合适的缓存类型。例如,如果设备的寄存器值变化频繁,使用
REGCACHE_NONE
可能更合适;如果寄存器值变化较少,使用
REGCACHE_RBTREE
或其他缓存类型可以减少对硬件的访问次数。
-
批量读写操作
:使用
regmap_bulk_read()
和
regmap_bulk_write()
函数进行批量读写操作,减少通信开销。例如:
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count);
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count);
-
自定义读写函数
:如果设备有特殊的读写要求,可以提供自定义的
reg_read和reg_write回调函数,以提高读写效率。
3.3 IIO框架的高级应用
IIO框架除了基本的设备和通道管理外,还支持一些高级功能,如触发缓冲和事件管理。
3.3.1 触发缓冲支持
触发缓冲允许在特定事件触发时采集数据,提高数据采集的效率。以下是使用触发缓冲的基本步骤:
1. 分配和初始化IIO设备。
2. 启用触发缓冲功能,设置相关参数。
3. 注册触发器和轮询函数。
4. 开始数据采集。
// 启用触发缓冲
static int enable_triggered_buffer(struct iio_dev *indio_dev) {
struct iio_buffer_setup_ops setup_ops = {
.preenable = my_preenable,
.postenable = my_postenable,
.predisable = my_predisable,
.postdisable = my_postdisable,
.validate_scan_mask = my_validate_scan_mask,
};
indio_dev->setup_ops = &setup_ops;
return iio_triggered_buffer_setup(indio_dev, my_trigger, my_poll_func);
}
// 主初始化函数
static int my_driver_init(void) {
struct iio_dev *indio_dev;
int ret;
indio_dev = iio_device_alloc(sizeof(struct my_private_data));
if (!indio_dev) {
return -ENOMEM;
}
// 填充iio_dev字段
// ...
ret = iio_device_register(indio_dev);
if (ret) {
iio_device_free(indio_dev);
return ret;
}
ret = enable_triggered_buffer(indio_dev);
if (ret) {
iio_device_unregister(indio_dev);
iio_device_free(indio_dev);
return ret;
}
return 0;
}
3.3.2 事件管理
IIO框架还支持事件管理,允许设备在特定条件下触发事件。例如,当传感器数据超过某个阈值时触发事件。以下是事件管理的基本步骤:
1. 定义事件类型和处理函数。
2. 注册事件处理函数。
3. 在设备驱动中触发事件。
// 事件处理函数
static int my_event_handler(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int64_t timestamp) {
// 处理事件
return 0;
}
// 注册事件处理函数
static int register_event_handler(struct iio_dev *indio_dev) {
struct iio_event_spec event_spec = {
.type = IIO_EV_TYPE_THRESH,
.direction = IIO_EV_DIR_RISING,
.channel = 0,
.handler = my_event_handler,
};
return iio_add_event_spec(indio_dev, &event_spec);
}
3.4 常见问题及解决方案
在使用Regmap API和IIO框架时,可能会遇到一些常见问题,以下是一些解决方案:
| 问题描述 | 解决方案 |
|---|---|
| Regmap初始化失败 | 检查设备连接、配置参数是否正确,查看错误码进行调试。 |
| IIO设备注册失败 | 检查内存分配是否成功,iio_dev字段是否填充正确。 |
| 数据读写错误 | 检查寄存器地址、数据格式是否正确,使用调试工具查看通信数据。 |
| 触发缓冲不工作 | 检查触发器和轮询函数是否正确注册,触发条件是否满足。 |
3.5 总结与展望
Regmap API和IIO框架为设备驱动开发提供了强大的支持,通过统一的接口和高效的机制,简化了开发过程,提高了设备的管理和数据采集效率。在未来的开发中,随着硬件设备的不断发展,这两个工具也将不断完善和扩展,为开发者提供更多的功能和便利。
开发者可以进一步探索这两个工具的高级特性,结合实际项目需求,开发出更加高效、稳定的设备驱动程序。同时,关注社区的发展动态,学习其他开发者的经验和技巧,不断提升自己的开发水平。
通过本文的介绍,希望读者能够对Regmap API和IIO框架有更深入的理解,并能够在实际项目中灵活运用这两个工具,为设备驱动开发带来更多的可能性。
更多推荐
所有评论(0)