17、嵌入式开发:Regmap API 与 IIO 框架深度解析

AI助手已提取文章相关产品:

嵌入式开发: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, &regmap_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 框架,开发者可以更加高效地编写复杂的传感器驱动,提升嵌入式系统的性能和稳定性。在实际开发中,开发者应根据具体需求选择合适的工具和方法,灵活运用这些技术,以实现最佳的开发效果。

您可能感兴趣的与本文相关内容

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值