嵌入式开发:Regmap API 与 IIO 框架深度解析
在嵌入式系统开发中,设备驱动的编写是一项关键任务,尤其是对于SPI和I2C等常见总线设备。Regmap API和IIO框架为开发者提供了强大而便捷的工具,能够简化驱动开发过程,提高开发效率。本文将深入探讨Regmap API和IIO框架的相关知识,包括其初始化、设备访问、缓存机制以及数据结构等方面。
1. Regmap API 概述
Regmap API是一种寄存器映射抽象层,它支持SPI和I2C两种协议,为开发者提供了一种通用且统一的方式来访问设备寄存器。无论使用哪种总线,大部分功能函数都是相同的,仅初始化过程会因总线类型而异。
1.1 Regmap 初始化
在驱动的probe函数中,根据需要支持的协议,调用
regmap_init_i2c()
或
regmap_init_spi()
来初始化regmap。在初始化之前,需要填充
regmap_config
结构体的各个元素。以下是SPI和I2C初始化的示例代码:
// SPI 初始化示例
static int foo_spi_probe(struct spi_device *client)
{
int err;
struct regmap *my_regmap;
struct regmap_config bmp085_regmap_config;
// 填充 bmp085_regmap_config
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;
}
// 其他操作
return 0;
}
// I2C 初始化示例
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;
// 填充 regmap_cfg
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;
// 其他操作
return 0;
}
当不再使用regmap时,调用
regmap_exit()
函数释放之前分配的寄存器映射:
void regmap_exit(struct regmap *map)
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);
-
regmap_write
:向设备写入数据。在写入之前,会检查寄存器地址是否在有效范围内,同时会调用
writeable_reg回调函数进行进一步验证。如果启用了缓存,会先更新缓存,再进行硬件写入操作。 -
regmap_read
:从设备读取数据,工作方式与
regmap_write类似,会调用readable_reg回调函数进行验证。 -
regmap_update_bits
:这是一个三合一的函数,执行读 - 修改 - 写循环。通过设置
mask和val参数,可以方便地更新寄存器的特定位。
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;
}
此外,还有
regmap_multi_reg_write
、
regmap_bulk_read
和
regmap_bulk_write
等函数,用于一次性写入多个寄存器或读写大块数据。
1.3 Regmap 缓存机制
Regmap支持缓存功能,缓存系统的使用与否取决于
regmap_config
结构体中的
cache_type
字段。支持的缓存类型包括
REGCACHE_NONE
、
REGCACHE_RBTREE
、
REGCACHE_COMPRESSED
和
REGCACHE_FLAT
,默认值为
REGCACHE_NONE
,表示禁用缓存。
设备的某些寄存器可能有预定义的上电复位值,可以将这些值存储在
reg_defaults
数组中。当进行读操作时,会优先返回数组中的值;写操作会更新设备寄存器和数组中的对应值。以下是一个使用缓存的示例:
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,
};
下图展示了Regmap的初始化和使用流程:
graph TD;
A[设置 regmap_config] --> B[在 probe 函数中初始化 regmap];
B --> C{选择总线类型};
C -- SPI --> D[调用 regmap_init_spi];
C -- I2C --> E[调用 regmap_init_i2c];
D --> F[使用 regmap 进行读写操作];
E --> F;
F --> G[完成后调用 regmap_exit 释放资源];
1.4 Regmap 示例
为了更好地理解Regmap的使用,下面给出一个假的SPI设备驱动示例。该设备具有8位寄存器地址和8位寄存器值,最大寄存器地址为
0x80
,有效地址范围为
0x20
到
0x4F
和
0x60
到
0x7F
。
#include <linux/regmap.h>
static struct private_struct
{
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;
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;
// 分配私有数据结构
// priv = kzalloc
priv->map = regmap_init_spi(dev, &config);
regmap_read(priv->map, 0x30, &data);
// 处理数据
data = 0x24;
regmap_write(priv->map, 0x23, data);
regmap_update_bits(priv->map, 0x44, 0b00100010, 0xFF);
return 0;
}
2. IIO 框架概述
IIO(Industrial I/O)框架是一个内核子系统,专门用于处理模拟 - 数字转换器(ADC)和数字 - 模拟转换器(DAC)等设备。随着传感器数量的不断增加,IIO框架提供了一种通用且统一的方式来管理这些设备,简化了驱动开发过程。
2.1 IIO 框架基本概念
IIO模型基于设备和通道架构。设备代表芯片本身,是层次结构的顶层;通道代表设备的单个采集线路,一个设备可以有一个或多个通道。例如,加速度计是一个具有三个通道的设备,分别对应X、Y和Z轴。
IIO芯片通过字符设备(当支持触发缓冲时)和sysfs目录项暴露给用户空间。用户可以通过以下两种方式与IIO驱动进行交互:
-
/sys/bus/iio/iio:deviceX/
:表示传感器及其通道。
-
/dev/iio:deviceX
:这是一个字符设备,用于导出设备的事件和数据缓冲区。
2.2 IIO 框架架构
IIO框架在内核和用户空间之间进行了有效的组织。驱动程序负责管理硬件,并将处理结果报告给IIO核心,使用IIO核心提供的一系列工具和API。IIO子系统通过sysfs接口和字符设备将底层机制抽象给用户空间,用户可以在其上执行系统调用。
IIO API分布在多个头文件中,开发者需要根据需求包含相应的头文件:
#include <linux/iio/iio.h> // 必需
#include <linux/iio/sysfs.h> // 由于使用了sysfs,必需
#include <linux/iio/events.h> // 高级用户用于管理IIO事件
#include <linux/iio/buffer.h> // 使用触发缓冲区时必需
#include <linux/iio/trigger.h>// 仅在驱动中实现触发功能时使用(很少使用)
2.3 IIO 数据结构
IIO设备在内核中由
struct iio_dev
结构体表示,并由
struct iio_info
结构体进行描述。以下是
iio_dev
结构体的主要成员:
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 :表示设备支持的不同模式,如直接模式、触发缓冲模式等。
- currentmode :表示设备当前使用的模式。
- buffer :数据缓冲区,在触发缓冲模式下将数据推送给用户空间。
- available_scan_masks :可选的允许位掩码数组,用于限制可以启用的通道。
- active_scan_mask :已启用通道的位掩码,只有这些通道的数据会被推送到缓冲区。
- scan_timestamp :指示是否将捕获时间戳推送到缓冲区。
以下是一个设置
available_scan_masks
的示例:
static const unsigned long my_scan_masks[] = {0x7, 0};
indio_dev->available_scan_masks = my_scan_masks;
2.4 IIO 设备的分配、注册和注销
在使用IIO设备之前,需要使用
iio_device_alloc()
函数为其分配内存,并填充相应的字段。然后使用
iio_device_register()
函数将设备注册到IIO子系统中,使其可以接受用户空间的请求。在不再使用设备时,使用
iio_device_unregister()
函数注销设备。
struct iio_dev *indio_dev;
struct my_private_data *data;
indio_dev = iio_device_alloc(sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
// 填充字段
int ret = iio_device_register(indio_dev);
if (ret < 0) {
// 处理注册失败
}
// 使用设备
iio_device_unregister(indio_dev);
下图展示了IIO设备的分配、注册和注销流程:
graph TD;
A[使用 iio_device_alloc 分配内存] --> B[填充 iio_dev 字段];
B --> C[使用 iio_device_register 注册设备];
C --> D[设备接受用户空间请求];
D --> E[使用 iio_device_unregister 注销设备];
总结
Regmap API和IIO框架为嵌入式系统开发中的设备驱动编写提供了强大而便捷的工具。Regmap API通过寄存器映射抽象层,简化了SPI和I2C设备的访问过程,同时支持缓存机制,提高了设备访问效率。IIO框架则为模拟 - 数字转换器和数字 - 模拟转换器等设备提供了统一的管理方式,基于设备和通道架构,方便开发者进行驱动开发。通过深入理解和掌握这些工具的使用,开发者可以更加高效地编写设备驱动,提升嵌入式系统的性能和稳定性。
3. IIO 框架的更多特性
3.1 触发缓冲支持
IIO 框架支持触发缓冲功能,允许设备在特定触发条件下连续捕获数据。当启用触发缓冲模式时,需要使用
iio_triggered_buffer_setup()
函数进行设置。该函数会自动为设备分配并关联数据缓冲区。
int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
const struct iio_buffer_setup_ops *setup_ops,
iio_poll_func_t *pollfunc,
const char *pollfunc_name);
以下是一个简单的使用触发缓冲的示例:
static int my_iio_triggered_buffer_setup(struct iio_dev *indio_dev)
{
int ret;
struct iio_buffer_setup_ops *setup_ops;
setup_ops = kzalloc(sizeof(*setup_ops), GFP_KERNEL);
if (!setup_ops)
return -ENOMEM;
// 设置 setup_ops 的回调函数
setup_ops->preenable = my_preenable_callback;
setup_ops->postenable = my_postenable_callback;
setup_ops->predisable = my_predisable_callback;
setup_ops->postdisable = my_postdisable_callback;
setup_ops->validate_scan_mask = my_validate_scan_mask;
ret = iio_triggered_buffer_setup(indio_dev, setup_ops, my_pollfunc, "my_pollfunc");
if (ret < 0) {
kfree(setup_ops);
return ret;
}
return 0;
}
在触发缓冲模式下,数据会在触发事件发生时自动填充到缓冲区中。用户可以通过
/dev/iio:deviceX
字符设备读取缓冲区中的数据。
3.2 连续捕获
连续捕获是 IIO 框架的一个重要特性,允许设备持续采集数据。在连续捕获模式下,设备会不断地将采集到的数据填充到缓冲区中,直到停止捕获。
要实现连续捕获,需要在
iio_dev
结构体中设置相应的模式,并在
pollfunc
中实现数据采集逻辑。以下是一个简单的连续捕获示例:
static int my_pollfunc(struct iio_dev *indio_dev, int index)
{
// 采集数据
int ret = my_iio_read_data(indio_dev);
if (ret < 0)
return ret;
// 将数据写入缓冲区
ret = iio_buffer_push_to_user(indio_dev);
if (ret < 0)
return ret;
return 0;
}
3.3 探索现有 IIO 触发器
IIO 框架支持多种触发器,开发者可以根据需求选择合适的触发器。常见的触发器包括定时器触发器、GPIO 触发器等。
以下是一个使用定时器触发器的示例:
#include <linux/timer.h>
static struct timer_list my_timer;
static void my_timer_callback(unsigned long data)
{
struct iio_dev *indio_dev = (struct iio_dev *)data;
// 触发数据采集
iio_trigger_poll(indio_dev->trig);
// 重新设置定时器
mod_timer(&my_timer, jiffies + msecs_to_jiffies(100));
}
static int my_iio_setup_timer_trigger(struct iio_dev *indio_dev)
{
init_timer(&my_timer);
my_timer.function = my_timer_callback;
my_timer.data = (unsigned long)indio_dev;
// 设置定时器初始时间
mod_timer(&my_timer, jiffies + msecs_to_jiffies(100));
return 0;
}
3.4 数据捕获模式
IIO 框架支持两种数据捕获模式:一次性模式和连续模式。
-
一次性模式
:在一次性模式下,设备只采集一次数据。开发者可以通过调用
iio_read_channel_raw()等函数来触发数据采集。
int iio_read_channel_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val);
- 连续模式 :如前面所述,连续模式允许设备持续采集数据,直到停止捕获。
3.5 测试工具
为了帮助开发者测试 IIO 设备,有一些可用的工具。例如,
iio_info
工具可以显示 IIO 设备的信息,包括设备名称、通道信息等。
iio_info
iio_readdev
工具可以读取 IIO 设备的寄存器值。
iio_readdev iio:deviceX
4. 综合应用示例
下面给出一个综合应用示例,结合 Regmap API 和 IIO 框架,实现一个简单的传感器驱动。
#include <linux/regmap.h>
#include <linux/iio/iio.h>
// 假设的传感器寄存器地址
#define SENSOR_REG_DATA 0x10
// Regmap 配置
static const struct regmap_config sensor_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.writeable_reg = sensor_writeable_reg,
.readable_reg = sensor_readable_reg,
.cache_type = REGCACHE_RBTREE,
};
// IIO 设备信息
static const struct iio_info sensor_iio_info = {
.read_raw = sensor_read_raw,
};
// IIO 通道规格
static const struct iio_chan_spec sensor_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
};
// 读取原始数据回调函数
static int sensor_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct regmap *map = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = regmap_read(map, SENSOR_REG_DATA, (unsigned int *)val);
if (ret < 0)
return ret;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
// SPI 设备探测函数
static int sensor_spi_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct regmap *map;
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(struct regmap *));
if (!indio_dev)
return -ENOMEM;
map = regmap_init_spi(spi, &sensor_regmap_config);
if (IS_ERR(map))
return PTR_ERR(map);
iio_priv(indio_dev) = map;
indio_dev->info = &sensor_iio_info;
indio_dev->channels = sensor_channels;
indio_dev->num_channels = ARRAY_SIZE(sensor_channels);
indio_dev->name = "sensor";
ret = iio_device_register(indio_dev);
if (ret < 0) {
regmap_exit(map);
return ret;
}
return 0;
}
// SPI 设备驱动结构体
static struct spi_driver sensor_spi_driver = {
.driver = {
.name = "sensor",
},
.probe = sensor_spi_probe,
};
module_spi_driver(sensor_spi_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple sensor driver using Regmap and IIO");
总结
本文详细介绍了 Regmap API 和 IIO 框架的相关知识。Regmap API 为 SPI 和 I2C 设备的寄存器访问提供了统一的抽象层,简化了设备驱动的开发过程,同时支持缓存机制,提高了设备访问效率。IIO 框架则专注于模拟 - 数字转换器和数字 - 模拟转换器等设备的管理,基于设备和通道架构,支持触发缓冲、连续捕获等功能,为开发者提供了强大的工具。
通过综合应用 Regmap API 和 IIO 框架,开发者可以更加高效地编写复杂的传感器驱动,提升嵌入式系统的性能和稳定性。在实际开发中,开发者应根据具体需求选择合适的工具和方法,灵活运用这些技术,以实现最佳的开发效果。
67

被折叠的 条评论
为什么被折叠?



