Regmap大杂烩

我们以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 <= %x\n", 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(
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值