我是靠谱客的博主 舒心白开水,最近开发中收集的这篇文章主要介绍Regmap大杂烩Regmap在i2c中的使用1. regmap介绍2. regmap设计框架3. regmap用例(regmap-mmio)Regmap API编程Regmap初始化设备访问regmap和cache总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

我们以regmap这个结构体为例,这个地方是一个前向声明,告诉后面的代码regmap是个结构体,至于这个结构体里面有什么鬼,不知道!

Linux可以说满世界都在使用这个结构体。满世界都在使用声明在include/linux/regmap.h中的regmap_write()、regmap_read()这样的API,可以说无处不在,无处不用,比如drivers/rtc/rtc-at91sam9.c中的:

这样的东西大家随便一搜索,都可以搜索出来无数个。这样看起来,regmap这个结构体,应该是一个跨模块的API,它的整个结构体长成怎么样,应该是出现在一个include/linux/级别的顶级跨模块头文件中了,这样方便跨模块引用这个结构体。

但是,真实的情况却让你大跌眼镜,regmap结构体的具体成员长什么样子,没有出现在任何一个外部级别的头文件里面,而是完全internal(内部的、内部的、内部的,各位童鞋!!!):

drivers/base/regmap/internal.h

既然它出现在drivers/base/regmap/internal.h,那么想必除了drivers/base/regmap/本身的内部实现外,外部不可能引用drivers/base/regmap/internal.h这个头文件。

所以,我们得出一个结论,尽管Linux满世界都在使用struct regmap,但是除了drivers/base/regmap/内部以外,其实外部没有任何一个人知道regmap这个结构体长成什么样子!!

这是一种极其良好的“高内聚、低耦合”设计。因为,drivers/base/regmap/外部所有的人,其实都只是在拥有regmap这个结构体的指针,而并没有访问regmap结构体其中的任何一个成员,其实也只有drivers/base/regmap/的内部实现在访问而已。

比如,regmap_write实现于:drivers/base/regmap/regmap.c文件,它的代码如下:

这样做带来的一个极大好处是,drivers/base/regmap/外部的世界根本不需要知道regmap结构体长成什么样子,因为没人需要知道,它们都只是在访问regmap的指针!

而drivers/base/regmap/内部无论怎么修改regmap结构体的实现和成员本身,对外部的世界根本不可见,修改regmap结构体后,drivers/base/regmap/以外的模块都不需要重新编译!

相反,如果我们直接把regmap结构体的内部细节暴露在include/linux/regmap.h这个头文件中,那么由于这个头文件满世界都被引用,你只要修改regmap结构体本身,就会导致内核无数模块的增量编译!

include/linux/regmap.h中暴露了regmap_config结构体,这说明这个结构体的内容需要被regmap以外的模块知道:

...

为什么,它涉及到具体的寄存器是如何读写的callback以及具体的寄存器pattern,这肯定是一个API基本的东西,本身就应该是跨模块的东西,所以它的长相出现在了include/linux/regmap.h这个顶级头文件中。

对于一个外部模块而言,它只需要能够通过regmap.h公开暴露的小部分寄存器配置接口,来通过类似regmap_init_mmio()这个的API来填充regmap结构体的内部实现。比如drivers/rtc/rtc-at91sam9.c中的:

上述代码中,rtc->gpbr是一个struct regmap指针,regmap_init_mmio()在内部填充了regmap的本身实现。之后drivers/rtc/rtc-at91sam9.c再调用regmap_write()、regmap_read()的时候,这些API从regmap模块内部调用我们填充进去的reg_bits、val_bits、reg_stride这些寄存器pattern,帮忙完成寄存器的最终读写。

画一幅图

理清关系

永远用高内聚和低耦合的思想设计代码。Linux内核2000万行的代码,不这么设计肯定要崩盘。写代码不是得过且过。尤其做单片机写裸奔程序的童鞋要特别注意,你们往往觉得玩Linux的童鞋代码一层层套很傻逼,这是完全不正确的理解。



Regmap在i2c中的使用

regmap的背景

Linux中有I2C和SPI这样的子系统,这些子系统用来连接依附在这些总线上的子设备。这些总线都有一个特点,就是需要对子设备进行寄存器的读写,这往往会导致具有寄存器读写的子系统代码中存在冗余。

为了避免这些问题发生,Linux将通用代码抽取出来,简化了驱动程序的开发和维护。从Linux3.1开始引入了新的API,称为regmap。regmap子系统负责调用相关的i2c和spi子系统来读写寄存器,本质上是对i2c读写的一层封装系统,下面将分析一个LT9611驱动来了解regmap的使用。

regmap相关文件:regmap.c、regmap-i2c.c、regmapcache.c、regmap-flat.c、regmap-mmio.c

regmap系统配置

配置初始化函数 devm_regmap_init_i2c(struct i2c_client *client, const struct regmap_config),第一个参数传入i2c的client,此结构体从i2c_driver的probe传入,第二个参数为regmap_config结构体。

static const struct regmap_config lt9611_regmap_config = {
    .reg_bits = 8,//子设备寄存器的位数
    .val_bits = 8,//子设备寄存器中设置的值的位数
    .max_register = 0xffff,//子设备中寄存器地址的最大值
    .ranges = lt9611_ranges,//间接访问寄存器的配置
    .num_ranges = ARRAY_SIZE(lt9611_ranges
                            ),
};
static const struct regmap_range_cfg lt9611_ranges[] = {
    {
        .name = "register_range",
        .range_min =  0,//虚拟地址范围内最低寄存器地址的地址
        .range_max = 0x85ff,//虚拟地址范围内最高寄存器地址的地址
        .selector_reg = LT9611_PAGE_CONTROL,//页面选择器的
        .selector_mask = 0xff,//页面选择器的位掩码
        .selector_shift = 0,//页面选择器的位移 
        .window_start = 0,//每页数据窗口的起始
        .window_len = 0x100,//数据长度 
    },
};

regmap配置写入

devm_regmap_init_i2c将config写入regmap系统。

struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c,
                      const struct regmap_config *config,
                      struct lock_class_key *lock_key,
                      const char *lock_name)
{
    const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);

    if (IS_ERR(bus))
        return ERR_CAST(bus);

    return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config,lock_key, lock_name);
}

对于config里的信息,要对i2c总线进行检查,将最大读写值设置进regmap_bus并返回。最终会调用__regmap_init函数将配置写入regmap并返回,注册完regmap,有了regmap_bus下面就可以开始读写寄存器。

regmap读写寄存器分析

_regmap_write

int _regmap_write(struct regmap *map, unsigned int reg,
          unsigned int val)
{
    int ret;
    void *context = _regmap_map_get_context(map);

    if (!regmap_writeable(map, reg))
        return -EIO;

    if (!map->cache_bypass && !map->defer_caching) {
        ret = regcache_write(map, reg, val);
        if (ret != 0)
            return ret;
        if (map->cache_only) {
            map->cache_dirty = true;
            return 0;
        }
    }

    ret = map->reg_write(context, reg, val);
    if (ret == 0) {
        if (regmap_should_log(map))
            dev_info(map->dev, "%x <= %xn", reg, val);

        trace_regmap_reg_write(map, reg, val);
    }

    return ret;
}

如果可以使用cache的话,就可以使用regcache_write(map, reg, val)来写入寄存器,目前支持三种缓存类型:数组(flat),LZO压缩,红黑树;这里使用的reg_write(context, reg, val)是regmap-i2c中定义的regmap_smbus_byte_reg_write、regmap_smbus_byte_reg_read。最终使用i2c-smbus的读写寄存器函数。

regmap_multi_reg_write

此函数支持顺序写入多个寄存器,reg_sequence结构体保存写入的顺序。

const struct reg_sequence reg_cfg[] = {
        { 0x8106, 0x40 }, /* port A rx current */
        { 0x810a, 0xfe }, /* port A ldo voltage set */
        { 0x810b, 0xbf }, /* enable port A lprx */
        { 0x8111, 0x40 }, /* port B rx current */
        { 0x8115, 0xfe }, /* port B ldo voltage set */
        { 0x8116, 0xbf }, /* enable port B lprx */

        { 0x811c, 0x03 }, /* PortA clk lane no-LP mode */
        { 0x8120, 0x03 }, /* PortB clk lane with-LP mode */
    };

然后使用_regmap_raw_multi_reg_write顺序写入寄存器。

regmap_read

read的过程和write类似,调用regmap_bus的read函数,即regmap_smbus_byte_reg_read,走i2c读写函数。


1 简介

Regmap 机制是在 Linux 3.1 加入进来的特性。主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器。其实这就是内核做的一次重构。Regmap 除了能做到统一的 I/O 接口,还可以在驱动和硬件 IC 之间做一层缓存,从而能减少底层 I/O 的操作次数。

2 使用对比

在了解 Regmap 的实现细节前,我们先来对比一下,传统操作寄存器的方式,与 Regmap 之间的差异。

2.1 传统方式

我们以一个 I2C 设备为例。读写一个寄存器,肯定需要用到 i2c_transfer 这样的 I2C 函数。为了方便,一般的驱动中,会在这之上再写一个 Wrapper,然后通过调用这个 Wrapper 来读写寄存器。比如如下这个读取寄存器的函数:

 
  1. static int xxx_i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
  2. {
  3. struct i2c_msg msg[] = {
  4. {
  5. .addr = client->addr,
  6. .flags = 0,
  7. .len = 1,
  8. .buf = &reg,
  9. },
  10. {
  11. .addr = client->addr,
  12. .flags = I2C_M_RD,
  13. .len = 1,
  14. .buf = val,
  15. },
  16. };
  17. return i2c_transfer(client->adapter, msg, 2);
  18. }

2.2 Regmap方式

而如果 regmap 的方式来实现,对于上面这种读寄存器操作,其实现如下:

 
  1. // first step: define regmap_config
  2. static const struct regmap_config xxx_regmap_config = {
  3. .reg_bits = 10,
  4. .val_bits = 14,
  5. .max_register = 40,
  6. .cache_type = REGCACHE_RBTREE,
  7. .volatile_reg = false,
  8. .readable_reg = false,
  9. };
  10. // second step: initialize regmap in driver loading
  11. regmap = regmap_init_i2c(i2c_client, &xxx_regmap_config);
  12. // third step: register operations
  13. regmap_read(regmap, XXX_REG, &value);

代码中,做的第一步就是定义 IC 的一些寄存器信息。比如:位宽,地址位宽,寄存器总数等。然后在驱动加载的时候,初始化 Regmap,这样就可以正常调用 Regmap 的 API 了。

可以看到,为了让慢速 I/O 能够专注于自身的逻辑,内核把 SPI, I2C 等总线操作方式全部封装在 Regmap 里,这样驱动若要做 I/O 操作,直接调用 Regmap 的函数就可以了。

3 实现细节

整个 Regmap 是分为 3 层,其拓扑结构如下:

这里通过其中 3 个核心结构体来分别说明。

3.1 regmap_config

struct regmap_config 结构体代表一个设备的寄存器配置信息,在做 Regmap 初始化时,驱动就需要把这个结构体传给 Regmap。这个结构体的定义在 include/linux/regmap.h,其中包含该设备的寄存器数量,寄存器位宽,缓存类型,读写属性等。

这一层是直接和驱动对接的。Regmap 根据传进来的 regmap_config 初始化对应的缓存和总线操作接口,驱动就可以正常调用 regmap_writeregmap_read 函数。

3.2 regmap_ops

struct regmap_ops 是用来定义一个缓存类型的,具体定义如下:

 
  1. struct regcache_ops {
  2. const char *name;
  3. enum regcache_type type;
  4. int (*init)(struct regmap *map);
  5. int (*exit)(struct regmap *map);
  6. #ifdef CONFIG_DEBUG_FS
  7. void (*debugfs_init)(struct regmap *map);
  8. #endif
  9. int (*read)(struct regmap *map, unsigned int reg, unsigned int *value);
  10. int (*write)(struct regmap *map, unsigned int reg, unsigned int value);
  11. int (*sync)(struct regmap *map, unsigned int min, unsigned int max);
  12. int (*drop)(struct regmap *map, unsigned int min, unsigned int max);
  13. };

在最新 Linux 4.0 版本中,已经有 3 种缓存类型,分别是数组(flat)、LZO 压缩和红黑树(rbtree)。数组好理解,是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。而最后一类红黑树,它的特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。

缓存的类型是在 Regmap 初始化时,由 .cache_type = REGCACHE_RBTREE 来指定的。对于 regmap_read 来说,会先判断当前缓存是否有值,然后再检查是否需要 bypass,若没有,则可以直接从缓存里面取值,调用 regcache_read 来获取值,若需要从硬件上读取,则调用具体协议的读写函数,若是 I2C,调用 i2c_transfer。写的过程也是大同小异。

3.3 regmap_bus

前面说的都是 Regmap 所做的封装,而真正进行 I/O 操作就是这最后一层。struct regmap_bus 定义了一个总线上的读写函数,这一层就像之前对 i2c_transfer 所做的封装一样。其定义如下:

 
  1. struct regmap_bus {
  2. bool fast_io;
  3. regmap_hw_write write;
  4. regmap_hw_gather_write gather_write;
  5. regmap_hw_async_write async_write;
  6. regmap_hw_reg_write reg_write;
  7. regmap_hw_read read;
  8. regmap_hw_reg_read reg_read;
  9. regmap_hw_free_context free_context;
  10. regmap_hw_async_alloc async_alloc;
  11. u8 read_flag_mask;
  12. enum regmap_endian reg_format_endian_default;
  13. enum regmap_endian val_format_endian_default;
  14. };

在 Lernel 4.0 中,已经支持了 I2C、SPI、AC97、MMIO 和 SPMI 五种总线类型。相信在未来,有更多的总线会加进来。其实添加一个总线也不是很难,只需 4 个函数就可以了:xxx_readxxx_writexxx_initxxx_deinit。具体可以看源码,这里就不多说了,留个任务在这吧。

4 Reference

  1. regmap: Generic I2C and SPI register map library
  2. include/linux/regmap.h
  3. drivers/base/regmap

一、前言

regmap是在 linux 内核为减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件寄存器的模型框架。此外,regmap在驱动和硬件寄存器之间增加了cache,减少底层低速 I/O 的操作次数,提高访问效率;当然实时性会有所降低。

基于代码代码复用的原则之一,Linux内核后引入了regmap模型,将寄存器访问的共同逻辑抽象出来,只需初始化时指定总线类型、寄存器位宽等关键参数,即可通过regmap模型接口来操作器件寄存器。当然,regmap同样适用于操作cpu自身的寄存器。将i2c、spi、mmio、irq都抽象出统一的接口regmap_read、regmap_write、regmap_update_bits等接口 ,从而提高代码的可重用性,并且使得在使用如上内核基础组件时变得更为简单易用。
二、regmap 框架模型

regmap整体上分为三层,从下到上分别为物理总线、regmap核心、regmap api。

在这里插入图片描述

    底层,对接的是具体物理总线,目前regmap框架支持i2c、spi、mmio、spmi、ac97总线
    核心层,regmap 核心实现
    api,抽象通用接口

对于使用regmap框架来说,可以不用关心regmap核心的实现过程,只需根据物理总线类型,配置好相关参数信息,即可调用regmap api访问芯片寄存器。
二、怎样使用regmap

使用regmap比较简单,使用前,只需根据外设属性配置总线类型、寄存器位宽、缓存类型、读写属性等参数;接着注册一个regmap实例;然后调用抽象访问接口访问寄存器。

    第一步,配置regmap信息
    第二步,注册regmap实例
    第三步,访问寄存器
    第四步,释放regmap实例

三、regmap的结构体(可以仅对自己需要的部分赋值)

struct regmap_config {
    const char *name;

    int reg_bits;// 寄存器地址的位数,必须配置,例如I2C寄存器地址位数为 8
    int reg_stride;
    int pad_bits;// 寄存器值的位数,必须配置
    int val_bits;

    bool (*writeable_reg)(struct device *dev, unsigned int reg);// 可写寄存器回调,maintain一个可写寄存器表
    bool (*readable_reg)(struct device *dev, unsigned int reg); // 可读寄存器回调, maintain一个可读寄存器表
    bool (*volatile_reg)(struct device *dev, unsigned int reg); // 可要求读写立即生效的寄存器回调,不可以被cache,maintain一个可立即生效寄存器表
    bool (*precious_reg)(struct device *dev, unsigned int reg); // 要求寄存器数值维持在一个数值范围才正确,maintain一个数值准确表
    regmap_lock lock;
    regmap_unlock unlock;
    void *lock_arg;

    int (*reg_read)(void *context, unsigned int reg, unsigned int *val);//读寄存器
    int (*reg_write)(void *context, unsigned int reg, unsigned int val);//写寄存器

    bool fast_io;

    unsigned int max_register; // 最大寄存器地址,防止访问越界
    const struct regmap_access_table *wr_table;
    const struct regmap_access_table *rd_table;
    const struct regmap_access_table *volatile_table;
    const struct regmap_access_table *precious_table;
    const struct reg_default *reg_defaults;
    unsigned int num_reg_defaults;
    enum regcache_type cache_type;  // cache数据类型,支持三种:flat、rbtree、Izo
    const void *reg_defaults_raw;
    unsigned int num_reg_defaults_raw;

    u8 read_flag_mask;// 读寄存器掩码
    u8 write_flag_mask;// 写寄存器掩码

    bool use_single_rw;
    bool can_multi_write;

    enum regmap_endian reg_format_endian;// 寄存器地址大小端,大于8位时需设置
    enum regmap_endian val_format_endian;// 寄存器值大小端,大于8位时需设置

    const struct regmap_range_cfg *ranges;
    unsigned int num_ranges;
};

目前linux内核支持三种cache数据类型,如下:

/* An enum of all the supported cache types */
enum regcache_type {
    REGCACHE_NONE,
    REGCACHE_RBTREE,//红黑树类型
    REGCACHE_COMPRESSED,//压缩类型
    REGCACHE_FLAT,//普通数据类型
};

 

在最新 Linux 4.0 版本中,已经有 3 种缓存类型,分别是数组(flat)、LZO 压缩和红黑树(rbtree)。数组好理解,是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。而最后一类红黑树,它的特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。

缓存的类型是在 regmap 初始化时,由.cache_type = REGCACHE_RBTREE 来指定的。对于 regmap_read 来说,会先判断当前缓存是否有值,然后再检查是否需要 bypass,若没有,则可以直接从缓存里面取值,调用regcache_read来获取值,若需要从硬件上读取,则调用具体协议的读写函数,若是 I2C,调用i2c_transfer。写的过程也是大同小异。

例如我用到REGCACHE_RBTREE

 static struct regmap_config es8388_regmap = {
    .reg_bits = 8,
    .val_bits = 8,    

    .max_register = 0x34,
    .volatile_reg = es8388_volatile_register,
    .readable_reg = es8388_readable_register,

    .cache_type = REGCACHE_RBTREE,
    .reg_defaults = es8388_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(es8388_reg_defaults),

    
};

   

该数据结构主要在创建一个regmap时,实现对regmap的初始化,主要信息如下:

    寄存器的位数、寄存器值的位数;
    寄存器读写权限判断的回调函数、读写范围的定义;
    加锁、解锁函数;
    读写flag;
    字节序相关的配置;
    是否支持page读写方式,若需要定义regmap_range_cfg类型的变量,说明page select reg、page
    reg范围等内容。

四、注册并初始化regmap

regmap_init_i2c(struct i2c_client *i2c, struct regmap_config *config);   
regmap_init_spi(struct spi_device *spi, strcut regmap_config *config);
regmap_init_mmio(struct device *dev, struct regmap_config *config);   
regmap_init_spmi_base(struct spmi_device *dev, strcut regmap_config *config);
regmap_init_spmi_ext(struct spmi_device *dev, strcut regmap_config *config);
regmap_init_ac97(struct snd_ac97 *ac97, strcut regmap_config *config);
regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, int irq_base, struct regmap_irq_chip *chip, struct regmap_irq_chip_data **data);

注意:regmap_add_irq_chip:关联后的regmap上注册 irq

初始化函数声明位于kernel/include/linux/regmap.h中,原型中linux内核通过宏定义实现,展开后即是上面函数声明。具体转化过程如下


#define regmap_init_i2c(i2c, config)                    
    __regmap_lockdep_wrapper(__regmap_init_i2c, #config,i2c, config)
                
#define __regmap_lockdep_wrapper(fn, name, ...) fn(__VA_ARGS__, NULL, NULL)

struct regmap *__regmap_init_i2c(struct i2c_client *i2c,
                 const struct regmap_config *config,
                 struct lock_class_key *lock_key,
                 const char *lock_name);
转化为                                 
regmap_init_i2c(struct i2c_client *i2c, struct regmap_config *config);
 

五、 使用regmap

配置和注册regmap实例后,我们就可以使用抽象接口来访问寄存器,摈弃之前那套繁琐的数据结构和函数api。接口比较通俗,根据函数名称和入口参数即可知道函数功能。接口分为两大类,设置类(与初始化配置信息不同)和访问类,访问类根据访问过程又分为两种:

经过regmap cache,提高访问效率,对于写操作,待cache存在一定数据量或者超出时间后写入物理寄存器;但降低实时性

不经过regmap cache,对于写操作,立即写入物理寄存器,实时性好;对于读操作,则经过cache,减少拷贝时间

 

在初始化好regmap之后,就可以调用regmap提供的read/write/update等操作了。


int regmap_write(struct regmap *map, int reg, int val); //向单个reg写入val  
int regmap_raw_write(struct regmap *map, int reg, void *val, size_t val_len); //向单个reg写入指定长度的数据,数据存放在val中
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,size_t val_count); // 写多个reg
int regmap_multi_reg_write_bypassed(struct regmap *map, const struct reg_sequence *regs,int num_regs);// 直接写入reg,不经过regmap cache
int regmap_raw_write_async(struct regmap *map, unsigned int reg,const void *val, size_t val_len);//写多个reg,并立即刷新cache写入
int regmap_read(struct regmap *map, int reg, int *val); // 读取单个reg的数据到val中/
int regmap_raw_read(struct regmap *map, int reg, void *val, size_t val_len);  // 读取单个reg中指定长度的数据
int regmap_bulk_read(struct regmap *map, int reg, void *val, size_t val_count); // 读取从reg开始之后val_count个寄存器的数据到val中
int regmap_update_bits(struct regmap *map, int reg, int mask, int val);     // 更新reg寄存器中mask指定的位
int regmap_write_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);//写入寄存器值指定bit *
void regcache_cache_bypass(arizona->regmap, true); // 设置读写寄存器不通过cache模式而是bypass模式,读写立即生效,一般在audio等确保时序性驱动中用到

六、释放regmap

在驱动注销时一定要释放已注册的regmap。

void regmap_exit(struct regmap *map);

    1

七、举例

/* 第一步配置信息 */
static const struct regmap_config regmap_config =
{
.reg_bits = 8,
.val_bits = 8,
.max_register = 255,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = false,
};

/* 第二步,注册regmap实例 */
regmap = regmap_init_i2c(i2c_client, ®map_config);

/* 第三步,访问操作 */
static int read_regs(uint8_t reg, uint8_t *pdata, int size)
{
return regmap_raw_read(regmap, reg, pdata, size);
}

regmap方式将i2c的数据结构、传输api隐藏,使用者无需关心i2c内部实现,简化驱动开发过程,提高代码的复用性。如果将该器件物理接口更换为spi,只需修改配置信息即可,寄存器访问过程无需更改。
 


 

前言:

学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入了 Regmap 子系统,今天小生就给大家伙讲讲啥是regmap!

1、什么是 Regmap

Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样的,通过 I2C/SPI 接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如 I.MX6ULL的 PWM、定时器等外设初始化,最终都是要落到寄存器的设置上。

Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器,SPI 接口的话使用 spi_write/spi_read等。I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余代码,再者,代码的复用性也会降低。比如 icm20608 这个芯片既支持 I2C 接口,也支持 SPI 接口。假设我们在产品设计阶段一开始将 icm20608 设计为 SPI 接口,但是后面发现 SPI 接口不够用,或者 SOC 的引脚不够用,我们需要将 icm20608 改为 I2C 接口。这个时候 icm20608 的驱动就要大改,我们需要将 SPI 接口函数换为 I2C 的,工作量比较大。

基于代码复用的原则,Linux 内核引入了 regmap 模型,regmap 将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmapAPI 函数。这样的好处就是统一使用 regmap,降低了代码冗余,提高了驱动的可以移植性。regmap 模型的重点在于:

通过 regmap 模型提供的统一接口函数来访问器件的寄存器,SOC 内部的寄存器也可以使用 regmap 接口函数来访问。

regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。另外,regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低。

什么情况下会使用 regmap:

①、硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。

②、提高代码复用性和驱动一致性,简化驱动开发过程。

③、减少底层 I/O 操作次数,提高访问效率。

2、Regmap 驱动框架

1、regmap 框架结构

regmap 驱动框架如下图所示:

regmap 框架分为三层:

①、底层物理总线:regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总线有 i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi 和 w1。

②、regmap 核心层,用于实现 regmap,我们不用关心具体实现。

③、regmapAPI 抽象层,regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。

2、regmap 结构体

Linux 内 核 将 regmap 框 架 抽 象 为 regmap 结 构 体 , 这 个 结 构 体 定 义 在 文 件 drivers/base/regmap/internal.h 中。

3、regmap_config 结构体

顾名思义,regmap_config 结构体就是用来初始化 regmap 的,这个结构体也定义在include/linux/regmap.h 文件中。

3、Regmap 操作函数

1、Regmap 申请与初始化

regmap 支持多种物理总线,比如 I2C 和 SPI,我们需要根据所使用的接口来选择合适的 regmap 初始化函数。Linux 内核提供了针对不同接口的 regmap 初始化函数。

SPI 接口初始化函数为 regmap_init_spi

I2C 接口的初始化函数为 regmap_init_i2c

不管是什么接口,全部使用 regmap_exit 这个函数来释放 regmap

我们一般会在 probe 函数中初始化 regmap_config,然后申请并初始化 regmap。

2、regmap 设备访问 API 函数

不管是 I2C 还是 SPI 等接口,还是 SOC 内部的寄存器,对于寄存器的操作就两种:读和写。regmap 提供了最核心的两个读写操作:regmap_read 和 regmap_write。这两个函数分别用来读/写寄存器。

在 regmap_read 和 regmap_write 的基础上还衍生出了其他一些 regmap 的 API 函数,首先是regmap_update_bits 函数。看名字就知道,此函数用来修改寄存器指定的 bit,函数原型如下:

int regmap_update_bits (struct regmap *map, 
    unsigned int reg,
    unsigned int mask,
    unsigned int val,

函数参数和返回值含义如下:

map:要操作的 regmap。

reg:要操作的寄存器。

mask:掩码,需要更新的位必须在掩码中设置为 1。

val:需要更新的位值。

返回值:0,写成功;其他值,写失败。

比如要将寄存器的 bit1 和 bit2 置 1,那么 mask 应该设置为 0X00000011,此时 val 的 bit1 和 bit2 应该设置为 1,也就是 0Xxxxxxx11。

如果要清除寄存器的 bit4 和 bit7,那么 mask 应该设置为 0X10010000,val 的 bit4 和 bit7 设置为 0,也就是 0X0xx0xxxx。

接下来看一下 regmap_bulk_read 函数,此函数用于读取多个寄存器的值,函数原型如下:

int regmap_bulk_read(struct regmap *map, 
     unsigned int reg, 
     void *val,
     size_t val_count)

函数参数和返回值含义如下:

map:要操作的 regmap。

reg:要读取的第一个寄存器。

val:读取到的数据缓冲区。

val_count:要读取的寄存器数量。

返回值:0,写成功;其他值,读失败。

另外也有多个寄存器写函数 regmap_bulk_write,函数原型如下:

int regmap_bulk_write(struct regmap *map, 
     unsigned int reg, 
     const void *val,
     size_t val_count)

函数参数和返回值含义如下:

map:要操作的 regmap。

reg:要写的第一个寄存器。

val:要写的寄存器数据缓冲区。

val_count:要写的寄存器数量。

返回值:0,写成功;其他值,读失败。

关于 regmap 常用到 API 函数就讲解到这里,还有很多其他功能的 API 函数,大家自行查 阅 Linux 内核即可,内核里面对每个 API 函数都有详细的讲解



linux设备驱动模型 - regmap

1. regmap介绍

regmap主要是为了方便操作寄存器而设计的,它将所有模块的寄存器(包括soc上模块的寄存器和外围设备的寄存器等)
抽象出来,用一套统一接口来操作寄存器

比如,如果要操作i2c设备的寄存器,那么就要调用i2c_transfer接口,要操作spi设备的寄存器,就要调用spi_write/spi_read等接口,
如果把它们都抽象为regmap结构,那么只要调用regmap_read/regmap_write就可以了

regmap的代码在目录:drivers/base/regmap

目前regmap抽象的设备主要分两种类型,一种是cache类型的,这种设备就是把寄存器值写入到内存中,
另一种是实际的硬件设备,寄存器的值要写入实际的模块中

在内核版本4.14上,cache的类型有3中:
- flat:普通数组类型
- rb-tree:红黑树类型
- lzo:压缩类型

 enum regcache_type {
    REGCACHE_NONE,
    REGCACHE_RBTREE,
    REGCACHE_COMPRESSED,
    REGCACHE_FLAT,
 };

实际的硬件设备实现regmap的有:I2C/SPI/spmi/mmio等,后面会举例介绍一下mmio

2. regmap设计框架

regmap的整个框架如下:

 

2.1 regmap结构体

简单介绍下regmap结构体

struct regmap {
    。。。。。。
    struct device *dev; /* Device we do I/O on */
    void *work_buf;     /* Scratch buffer used to format I/O */
    struct regmap_format format;  /* Buffer format */
    const struct regmap_bus *bus;-------------regmap设备总线
    void *bus_context;------------------------总线私有数据
    const char *name;

    unsigned int max_register;
    /×判断是否可读写的函数×/
    bool (*writeable_reg)(struct device *dev, unsigned int reg);
    bool (*readable_reg)(struct device *dev, unsigned int reg);
    bool (*volatile_reg)(struct device *dev, unsigned int reg);
    bool (*precious_reg)(struct device *dev, unsigned int reg);
    /×读写寄存器函数×/
    int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
    int (*reg_write)(void *context, unsigned int reg, unsigned int val);
    int (*reg_update_bits)(void *context, unsigned int reg,
                   unsigned int mask, unsigned int val);
    /×读写掩码×/
    unsigned long read_flag_mask;
    unsigned long write_flag_mask;

    /* number of bits to (left) shift the reg value when formatting*/
    int reg_shift;------------寄存器偏移
    int reg_stride;-----------寄存器对齐位
    int reg_stride_order;

    /×regcache相关的函数×/
    /* regcache specific members */
    const struct regcache_ops *cache_ops;
    enum regcache_type cache_type;

    /* number of bytes in reg_defaults_raw */
    unsigned int cache_size_raw;
    /* number of bytes per word in reg_defaults_raw */
    unsigned int cache_word_size;
    /* number of entries in reg_defaults */
    unsigned int num_reg_defaults;
    /* number of entries in reg_defaults_raw */
    unsigned int num_reg_defaults_raw;

    /* if set, only the cache is modified not the HW */
    bool cache_only;
    /* if set, only the HW is modified not the cache */
    bool cache_bypass;
    /* if set, remember to free reg_defaults_raw */
    bool cache_free;

    /×多寄存器读写相关字段×/
    /* if set, converts bulk read to single read */
    bool use_single_read;
    /* if set, converts bulk read to single read */
    bool use_single_write;
    /* if set, the device supports multi write mode */
    bool can_multi_write;

    /* if set, raw reads/writes are limited to this size */
    size_t max_raw_read;---------能读的寄存器范围
    size_t max_raw_write;--------能写的寄存器范围
。。。。。。
};

2.2 regmap_config结构体

我们要创建自己的regmap的时候,一般会先初始化regmap_config结构体,然后进行regmap的创建

struct regmap_config {
    int reg_bits;-------------------寄存器地址位数
    int reg_stride;-----------------寄存器地址对齐
    int pad_bits;-------------------填充位数
    int val_bits;-------------------寄存器值位数

    bool (*writeable_reg)(struct device *dev, unsigned int reg);---判断寄存器是否可写
    bool (*readable_reg)(struct device *dev, unsigned int reg);----判断寄存器是否可读
    bool (*volatile_reg)(struct device *dev, unsigned int reg);
    bool (*precious_reg)(struct device *dev, unsigned int reg);

    int (*reg_read)(void *context, unsigned int reg, unsigned int *val);---寄存器读函数
    int (*reg_write)(void *context, unsigned int reg, unsigned int val);---寄存器写函数

    unsigned int max_register;------------最大寄存器地址

    enum regcache_type cache_type;--------regmap类型

    unsigned long read_flag_mask;---------读掩码
    unsigned long write_flag_mask;--------写掩码

    enum regmap_endian reg_format_endian;-----寄存器地址大小端
    enum regmap_endian val_format_endian;-----寄存器值大小端
};

2.3 regmap创建

2.3.1 __devm_regmap_init

调用devm接口方便驱动卸载的时候释放资源

2.3.2 __regmap_init

init的过程如下:

  1. config的lock配置给regmap
  2. config的reg-bit/val-bit等配置给regmap
  3. 把regmap_bus给regmap
  4. 把config的读写判断函数(writeable_reg/readable_reg)等相关配置给regmap
  5. 如果regmap_bus不为空,那么把regmap_bus的读写函数给regmap,否则把config的配置给regmap
  6. 给regmap提供数据格式化format的相关函数

2.4 常用寄存器操作接口

使用regmap读写寄存器的接口有:

static inline int regmap_write(struct regmap *map, unsigned int reg,
               unsigned int val)
static inline int regmap_read(struct regmap *map, unsigned int reg,
              unsigned int *val)
static inline int regmap_bulk_read(struct regmap *map, unsigned int reg,
               void *val, size_t val_count)
static inline int regmap_update_bits_base(struct regmap *map, unsigned int reg,
                  unsigned int mask, unsigned int val,
                  bool *change, bool async, bool force)

3. regmap用例(regmap-mmio)

regmap-mmio是用来映射soc上的模块寄存器,方便驱动操作模块而设计的

3.1 regmap-mmio的创建

可以调用函数:

struct regmap *__regmap_init_mmio_clk(...)

struct regmap *__devm_regmap_init_mmio_clk(...)

创建过程:
1. 先初始化regmap-mmio的私有结构体:regmap_mmio_context
主要是分配读写函数和clock
2. 然后进行regmap的初始化,使用regmap_bus总线为regmap_mmio




在开发Regmap API之前,用于处理SPI核心、I2C核心或两者的设备驱动程序都有冗余代码。它们都有相同的原理:访问寄存器进行读/写操作。下图显示在将regmap引入内核之前SPI或i2c API是如何独立的:

为了统一内核开发人员访问SPI/I2C设备的方式,在内核的3.1版本中引入了regmap API。引入该机制后,无论是SPI还是I2C仅仅只有一个问题,如何初始化、配置regmap和处理读/写/修改操作。下图显示引入了regmap后SPI或者I2C是如何交互的:

本章将通过以下方式介绍regmap框架:

  • 介绍regmap框架使用的主要数据结构
  • 介绍regmap配置
  • 使用regmap API访问设备
  • 介绍regmap缓存系统
  • 提供一个总结所有概念的完整驱动程序

Regmap API编程

regmap API非常简单,这个API的两个最重要的结构是struct regmap_config,它表示regmap的配置,struct regmap,它是regmap实例本身。所有regmap数据结构都是在include/linux/regmap.h中定义的。

regmap_config结构体

struct regmap_config保存了驱动程序的regmap配置,这里设置的内容会影响读/写操作。它是regmap api中最重要的结构。源代码如下所示:

struct regmap_config {
    const char *name;
    int reg_bits;
    int reg_stride;
    int pad_bits;
    int val_bits;
    bool (*writeable_reg)(struct device *dev, unsigned int reg);
    bool (*readable_reg)(struct device *dev, unsigned int reg);
    bool (*volatile_reg)(struct device *dev, unsigned int reg);
    bool (*precious_reg)(struct device *dev, unsigned int reg);
    regmap_lock lock;
    regmap_unlock unlock;
    void *lock_arg;
    int (*reg_read)(void *context, unsigned int reg,unsigned int *val);
    int (*reg_write)(void *context, unsigned int reg,unsigned int val);
    bool fast_io;
    unsigned int max_register;
    const struct regmap_access_table *wr_table;
    const struct regmap_access_table *rd_table;
    const struct regmap_access_table *volatile_table;
    const struct regmap_access_table *precious_table;
    const struct reg_default *reg_defaults;
    unsigned int num_reg_defaults;
    enum regcache_type cache_type;
    const void *reg_defaults_raw;
    unsigned int num_reg_defaults_raw;
    u8 read_flag_mask;
    u8 write_flag_mask;
    bool use_single_rw;
    bool can_multi_write;
    enum regmap_endian reg_format_endian;
    enum regmap_endian val_format_endian;
    const struct regmap_range_cfg *ranges;
    unsigned int num_ranges;
}

reg_bits:寄存器地址中的位数,强制的必须配置
val_bits:寄存器值的位数,强制的必须配置
writeable_reg:这是一个可选的回调函数。如果提供了,则在需要写入寄存器时由regmap子系统调用。在写入寄存器之前,将自动调用此函数以检查是否将值写入寄存器。如下所示:

static bool foo_writeable_register(struct device *dev,
unsigned int reg)
{
    switch (reg) {
        case 0x30 ... 0x38:
        case 0x40 ... 0x45:
        case 0x50 ... 0x57:
        case 0x60 ... 0x6e:
        case 0x70 ... 0x75:
        case 0x80 ... 0x85:
        case 0x90 ... 0x95:
        case 0xa0 ... 0xa5:
        case 0xb0 ... 0xb2:
            return true;
        default:
            return false;
    }
}

readable_reg:与writeable_reg相同。在读取寄存器之前,将自动调用此函数检查是否读取寄存器值
volatile_reg:通过regmap缓存读取或写入寄存器时调用的回调函数(如果寄存器是易失性的,则该函数应该返回true),并对寄存器执行直接读/写操作。如果返回false,则表示寄存器可缓存。在这种情况下,缓存将用于读取操作,而在写操作的情况下,缓存将被写入:

static bool foo_volatile_register(struct device *dev,unsigned int reg)
{
    switch (reg) {
        case 0x24 ... 0x29:
        case 0xb6 ... 0xb8:
            return true;
        default:
            return false;
    }
}

max_register: 这是可选的,它指定最大有效寄存器地址。
reg_read: 您的设备可能不支持简单的i2c/spi读取操作(意思就是需要填充该函数实现SPI/I2C自定义读功能)。然后,您将别无选择,只能编写您自己的自定义读取功能。reg_read应该指向该功能。也就是说,大多数设备都不需要该功能。
reg_write:和reg_read相同,但适用于写操作。

我强烈建议您查看include/linux/regmap.h,以了解每个元素的更多细节。

下面是一个regmap_config的初始化:

static const struct regmap_config regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = LM3533_REG_MAX,
    .readable_reg = lm3533_readable_register,
    .volatile_reg = lm3533_volatile_register,
    .precious_reg = lm3533_precious_register,
};

Regmap初始化

如前所述,regmap API支持SPI和i2c协议。根据驱动程序中需要支持的协议,必须在probe()函数中调用regmap_init_i2c()或regmap_init_spi()。要编写通用驱动程序,remap是最好的选择。
regmap API是通用的,只需要在初始化时更改不同的总线类型,而其他函数是相同的。
注意:在probe()中初始化regmap是一种很好的做法,在初始化regmap之前,必须首先填充regmap_config字段。

无论是分配i2c还是SPI寄存器映射,都会使用regmap_exit函数释放它:

void regmap_exit(struct regmap *map)
  • 1

此函数简单的释放之前分配的regmap map

SPI初始化

regmap SPI初始化包括设置regmap,这样设备的读写都会在内部转换为spi命令。例如函数regmap_init_spi():

struct regmap * regmap_init_spi(struct spi_device *spi, const struct regmap_config);
  • 1

提供一个struct spi_device类型的spi设备作为第一个参数,以及一个struct regmap_config,它表示regmap的配置。此函数返回一个指向已分配的struct regmap的指针,失败时返回一个ERR_PTR()值。

示例如下:

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: %dn", err);
        return err;
    }

    [...]

}

I2C初始化

i2c regmap调用regmap_init_i2c()初始化regmap配置,设备读写都会在内部转化为I2C命令。例如:

struct regmap * regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config);
  • 1

struct i2c_client类型的i2c设备作为第一个参数,以及一个指向struct regmap_config的指针,该指针表示regmap的配置。该函数在成功时返回一个指向已分配的struct regmap指针,失败时返回ERR_PTR()值。

示例如下:

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, &regmap_config);
    if (IS_ERR(bar_struct->regmap))
        return PTR_ERR(bar_struct->regmap);
    bar_struct->dev = &i2c->dev;
    bar_struct->irq = i2c->irq;

    [...]

}

设备访问

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:写数据到设备。如果在regmap_config中设置了max_register,该函数将会检查需要写入的寄存器地址。如果寄存器地址小于或等于max_register,写操作才会被执行;否则,regmap core将返回无效的I/O错误(-EIO)。紧接着,执行writeable_reg回调,writeable_reg回调必须在进行下一步之前返回true。如果返回false,写操作停止并返回-EIO。如果设置了wr_table而不是writeable_reg,有下面几种情况:

  • 如果寄存器地址在no_range中,返回-EIO
  • 如果寄存器地址在yes_range,执行下一步
  • 如果寄存器地址不在no_range或yes_range中,写操作结束并返回-EIO
  • 如果cache_type不是REGCACHE_NONE,则启用缓存。在这种情况下,首先更新缓存条目,然后对硬件执行写操作;否则,执行NO缓存操作
  • 如果提供了reg_write回调,可用于执行写操作;否则,将执行泛型regmap写函数

regmap_read:从设备读取数据。使用方式和regmap_write相同。因此,如果提供了reg_read,则调用reg_read执行读操作;否则,调用泛型regmap读函数

regmap_update_bits

regmap_update_bits有三个功能,其原型如下:

int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val)
  • 1

该函数在注册的regmap上执行读/写、修改操作,是由_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;
}

需要更新的bit掩码mask必须设置为1,相应的bit才会被赋值为val
例如,要将第一位和第三位设置为1,掩码mask应该是0b00000101,值应该是0bxxxxx1x1。要清除第七位,掩码必须是0b01000000,值应该是0bx0xxxxxx,以此类推。

regmap_multi_reg_write

函数功能是写入设备的多个寄存器,其原型如下:

int regmap_multi_reg_write(struct regmap *map, const struct reg_sequence *regs, int num_regs)
  • 1

要了解如何使用该函数,首先需要了解结构体struct reg_sequence:

/**
* Register/value pairs for sequences of writes with an optional delay in
* microseconds to be applied after each write.
*
* @reg: Register address.
* @def: Register value.
* @delay_us: Delay to be applied after the register write in microseconds
*/
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 },
};
staticint probe ( ...)
{
    [...]

    ret = regmap_multi_reg_write(my_regmap, foo_default_regs,ARRAY_SIZE(foo_default_regs));

    [...]
}

其他设备访问功能

regmap_bulk_read()和regmap_bulk_write()用于从设备中读取/写入多个寄存器,通常与大量数据块一起使用:

int regmap_bulk_read(struct regmap *map, unsigned int reg, void*val, size_tval_count);
int regmap_bulk_write(struct regmap *map, unsigned int reg,const void *val, size_t val_count);

regmap和cache

regmap支持缓存,是否使用缓存取决于regmap_config中cache_type字段的值。查看include/linux/regmap.h,cache_type支持的类型有:

/* Anenum of all the supported cache types */
enum regcache_type {
    REGCACHE_NONE,
    REGCACHE_RBTREE,
    REGCACHE_COMPRESSED,
    REGCACHE_FLAT,
};

默认情况下,cache_type被设置为regcache_NONE,这意味着缓存被禁用。其他值定义了如何存储缓存值。

您的设备可能在某些寄存器中具有预定义的上电复位值,这些值可以存储在数组中,这样任何读操作都会返回数组中包含的值。但是,写操作都会影响设备的真实寄存器,并更新数组中的内容。这是一种缓存,我们可以用它来加速对设备的访问。该数组是reg_defaults,它的结构在源码中如下:

/**
* Default value for a register. We use an array of structs rather
* than a simple array as many modern devices have very sparse
* register maps.
*
* @reg: Register address.
* @def: Register default value.
*/
struct reg_default {
    unsigned int reg;
    unsigned int def;
};

注意:如果cache_type设置为none,reg_defaults将被忽略。如果没有设置default reg,但仍然启用缓存,则将创建相应的缓存结构。

使用起来非常简单,只需要声明它并将其作为参数传递给regmap_config结构。让我们看看位于drivers/regulator/ltc3589.c的LTC3589 regulator驱动:

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,
};

reg_default中的任何一个寄存器的读操作都会立刻返回其在数组中的值。但是,对reg_default中的寄存器执行写操作,都会更新reg_default中的相对应的寄存器的值。例如,读取ltc3589_vrrcr寄存器将立刻返回0 xff;在该寄存器中写入任何值,它将更新数组中的条目,以便任何新的读操作都将直接从缓存返回最后的写入值。

总结

构建regmap子系统步骤:

  • 根据设备特性创建结构体regmap_config,如果需要,设置一个寄存器范围,默认值(如果有的话),缓存类型(如果需要)等等。如果需要自定义读/写函数,将他们传递给reg_read/reg_write字段
  • 在probe()函数中,根据总线类型(I2C或SPI),调用regmap_init_i2c()或者regmap_init_spi()分配regmap
  • 每当需要从寄存器读取/写入值时,调用remap_read/ remap_write函数
  • 结束时,调用regmap_exit()来释放在probe()中分配的regmap

Regmap示例

为了实现我们的目标,让我们首先描述一个假冒的SPI设备,我们可以为它编写一个驱动程序:

  • 8bit寄存器地址
  • 8bit寄存器值
  • 最大寄存器地址0x80
  • 写入掩码0x80
  • 有效地址范围
    0x20 to 0x4F
    0x60 to 0x7F
  • 不需要自定义读写功能

骨架示例程序:

/* 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;
}

最后

以上就是舒心白开水为你收集整理的Regmap大杂烩Regmap在i2c中的使用1. regmap介绍2. regmap设计框架3. regmap用例(regmap-mmio)Regmap API编程Regmap初始化设备访问regmap和cache总结的全部内容,希望文章能够帮你解决Regmap大杂烩Regmap在i2c中的使用1. regmap介绍2. regmap设计框架3. regmap用例(regmap-mmio)Regmap API编程Regmap初始化设备访问regmap和cache总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部