Linux Regmap分析

举例说明:
- devm_regmap_init_mmio_clk
- regmap_update_bits(dsi->regmap, DSI_PHY_TST_CTRL1, PHY_TESTEN, 0);
- regmap_read(dsi->regmap, DSI_PHY_TST_CTRL1, &data);
- regmap_write(dsi->regmap, DSI_GEN_VCID, GEN_VCID_RX(vcid));
先逐个说明


devm_regmap_init_mmio_clk

#define devm_regmap_init_mmio_clk(dev, clk_id, regs, config)        \
    __regmap_lockdep_wrapper(__devm_regmap_init_mmio_clk, #config,  \
                dev, clk_id, regs, config)
struct regmap *__devm_regmap_init_mmio_clk(struct device *dev,
                       const char *clk_id,
                       void __iomem *regs,
                       const struct regmap_config *config,
                       struct lock_class_key *lock_key,
                       const char *lock_name)
{
    struct regmap_mmio_context *ctx;

    ctx = regmap_mmio_gen_context(dev, clk_id, regs, config);
    if (IS_ERR(ctx))
        return ERR_CAST(ctx);

    return __devm_regmap_init(dev, &regmap_mmio, ctx, config,
                  lock_key, lock_name);
}

先看regmap_mmio_gen_context,这个函数主要是完成struct regmap_mmio_context *ctx这个指针的赋值,例如寄存器地址和数据的位宽,步长设置。同时如果clk_id不为空时,还需要请求时钟,并prepare。
这里重点关注第二个函数

参数说明:
dev:最开始传入的设备指针;
bus:上一级传入的regmap_mmio的指针,包含寄存器读写的实现;
bus_context:上一级中regmap_mmio_gen_context返回的指针;
config:最初devm_regmap_init_mmio_clk传入的config指针;
lock_key和lock_name:均为NULLstruct regmap *__devm_regmap_init(struct device *dev,
                  const struct regmap_bus *bus,
                  void *bus_context,
                  const struct regmap_config *config,
                  struct lock_class_key *lock_key,
                  const char *lock_name)
{
    struct regmap **ptr, *regmap;

    ptr = devres_alloc(devm_regmap_release, sizeof(*ptr), GFP_KERNEL);
    if (!ptr)
        return ERR_PTR(-ENOMEM);

    regmap = __regmap_init(dev, bus, bus_context, config,
                   lock_key, lock_name);
    if (!IS_ERR(regmap)) {
        *ptr = regmap;
        devres_add(dev, ptr);
    } else {
        devres_free(ptr);
    }

    return regmap;
}

直接看__regmap_init这个函数
__regmap_init

    map = kzalloc(sizeof(*map), GFP_KERNEL);
    map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8);
    map->format.pad_bytes = config->pad_bits / 8;
    map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8);
    map->format.buf_size = DIV_ROUND_UP(config->reg_bits +
            config->val_bits + config->pad_bits, 8);
    map->reg_shift = config->pad_bits % 8;
    if (config->reg_stride)
        map->reg_stride = config->reg_stride;
    else
        map->reg_stride = 1;

以上通过最初devm_regmap_init_mmio_clk传入的config指针完成regmap格式的初始化;

    map->dev = dev;
    map->bus = bus;
    map->bus_context = bus_context;
    ....
    1.如果regmap_mmio指针为空,则使用config传入的读写指针作为map的寄存器读写指针
    2.如果regmap_mmio提供的读写指针不完整,则使用默认的读写寄存器操作
    3.如果regmap_mmio的读写指针完整,则map->reg_read被赋值为_regmap_bus_read;
    map->reg_update_bits被赋值为bus->reg_update_bits
    if (!bus) {
        map->reg_read  = config->reg_read;
        map->reg_write = config->reg_write;

        map->defer_caching = false;
        goto skip_format_initialization;
    } else if (!bus->read || !bus->write) {
        map->reg_read = _regmap_bus_reg_read;
        map->reg_write = _regmap_bus_reg_write;

        map->defer_caching = false;
        goto skip_format_initialization;
    } else {
        map->reg_read  = _regmap_bus_read;
        map->reg_update_bits = bus->reg_update_bits;
    }

regmap_update_bits

regmap_update_bits(struct regmap *map, unsigned int reg,unsigned int mask, unsigned int val)
 _regmap_update_bits(map, reg, mask, val, NULL, false)
  _regmap_read(map, reg, &orig); 先读取原始的寄存器的值到orig中
  做一下运算,得到需要重新写入寄存器的值
  tmp = orig & ~mask;
  tmp |= val & mask;
  再次写入寄存器中
  _regmap_write(map, reg, tmp);
下面分解来看_regmap_read_regmap_write


_regmap_read

 map->reg_read(context, reg, val);
 在我们初始化的时候map->reg_read被初始化为了regmap_bus_read,原因在于regmap_mmio实现了read和write的函数指针
  _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes);
   map->format.format_reg(map->work_buf, reg, map->reg_shift);
   map->bus->read(map->bus_context,map->work_buf,map->format.reg_bytes +map->format.pad_bytes,val, val_len);
下面的32对应的config->reg_bits+config->pad_bits%8;我们当前传入的寄存器bit位数为32同时没有偏移。

    case 32:
        switch (reg_endian) {
        case REGMAP_ENDIAN_BIG:
            map->format.format_reg = regmap_format_32_be;
            break;
        case REGMAP_ENDIAN_NATIVE:
            map->format.format_reg = regmap_format_32_native;
            break;
        default:
            goto err_map;
        }
        break;

这儿需要研究下的是format.format_reg这个函数指针的指向:原则如下:
1.如果devm_regmap_init_mmio_clk传入的config指针中含有reg_format_endian不为0,那么依据该值参照上面的switch返回对应的指针;

2.如果devm_regmap_init_mmio_clk中的config指针没有配置reg_format_endian这个指针,或者配置了该项,但是该项的值为0.那么依据bus也就是regmap_mmio中的reg_format_endian_default成员,如果该成员存在,且其值不为0,那么同样参照上面的switch中对于的分支即可;

3.如果1-2都不满足那么返回REGMAP_ENDIAN_BIG,map->format.format_reg 被赋值为regmap_format_32_be.


这个例子中bus也就是regmap_mmio中的reg_format_endian_default被设置为了REGMAP_ENDIAN_NATIVE,
因此map->format.format_reg = regmap_format_32_native;

static void regmap_format_32_native(void *buf, unsigned int val,
                    unsigned int shift)
{
    *(u32 *)buf = val << shift;
}

上面的三个参数分析下:
buf: map->work_buf
val:reg
shift: map->reg_shift

map->work_buf是在regmap_init的时候通过kzalloc分配的内存,这块内存的大小为:
config->reg_bits+config->val_bits + config->pad_bits,之后再对齐到8字节。
当前这个workbuf为32+32总共,64个字节。
那么,workbuf的前32个字节存放的就是寄存器的地址(可能含有偏移)
_regmap_read最终调用的就是map->bus->read,即对应的就是

参数说明:
context:对应的是regmap_mmio_gen_context返回的指针;
reg:map->work_buf,其中的第一个字节含有寄存器的地址;
reg_size:32个bit也就是4字节               ====   4
val:上级传入的,等待返回的寄存器值;
val_size: 寄存器值的位数32位;也就是4个字节  ====  4

static int regmap_mmio_read(void *context,
                const void *reg, size_t reg_size,
                void *val, size_t val_size)
{
    struct regmap_mmio_context *ctx = context;
    unsigned int offset;
    int ret;

    regmap_mmio_regsize_check(reg_size);
    如果时钟域不为空,那么需要在真正读的时候使能时钟
    if (!IS_ERR(ctx->clk)) {
        ret = clk_enable(ctx->clk);
        if (ret < 0)
            return ret;
    }

    先取出偏移量,针对当前的场景,寄存器的位数是32,那么就取出map->work_buf的前面32个字节也就是传入的
    寄存器地址(可能含有偏移量),将其作为读取的偏移
    offset = regmap_mmio_get_offset(reg, reg_size);

    while (val_size) {
        switch (ctx->val_bytes) {
        case 1:
            *(u8 *)val = readb(ctx->regs + offset);
            break;
        case 2:
            *(u16 *)val = readw(ctx->regs + offset);
            break;
        case 4:
            寄存器中的数据是32位的,因此读取内存中的具体值,存放到val中
            *(u32 *)val = readl(ctx->regs + offset);
            break;
#ifdef CONFIG_64BIT
        case 8:
            *(u64 *)val = readq(ctx->regs + offset);
            break;
#endif
        default:
            /* Should be caught by regmap_mmio_check_config */
            BUG();
        }
        针对32bit也就是4个字节的情况,读取一次后也就结束退出了.
        val_size -= ctx->val_bytes;
        val += ctx->val_bytes;
        offset += ctx->val_bytes;
    }


    if (!IS_ERR(ctx->clk))
        clk_disable(ctx->clk);

    return 0;
}

_regmap_write

  map->reg_write(context, reg, val);
这儿的reg_write指针指向的是哪儿呢?
我们留意到__regmap_init中
这里写图片描述
上面并没有实现map->reg_write,那在哪儿呢

    reg_endian = regmap_get_reg_endian(bus, config);
    val_endian = regmap_get_val_endian(dev, bus, config);

我们之前得到reg_endian为REGMAP_ENDIAN_NATIVE,同时val_endian的获取逻辑和之前的reg_endian基本是一致的,至少额外增加了在DTS中获取的判断:

        if (of_property_read_bool(np, "big-endian"))
            endian = REGMAP_ENDIAN_BIG;
        else if (of_property_read_bool(np, "little-endian"))
            endian = REGMAP_ENDIAN_LITTLE;

如上,如果DTS中有如上的配置,则按照具体情况返回,当前的情况下DTS并没有配置
返回的是REGMAP_ENDIAN_NATIVE,如下:

static struct regmap_bus regmap_mmio = {
    .fast_io = true,
    .write = regmap_mmio_write,
    .gather_write = regmap_mmio_gather_write,
    .read = regmap_mmio_read,
    .free_context = regmap_mmio_free_context,
    .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
    .val_format_endian_default = REGMAP_ENDIAN_NATIVE,
};

回到最初的问题map->reg_write指针的来源:
情况一:
说明: config->reg_bits + map->reg_shift之后简称为寄存器地址BIT数
      config->val_bits:简称为寄存器值BIT数
a) 如果寄存器地址的BIT数为2,但是寄存器值的BIT数为6;
b) 如果寄存器地址的BIT数为4,但是寄存器值的BIT数为12;
c) 如果寄存器地址的BIT数为7,但是寄存器值的BIT数为9;
d) 如果寄存器地址的BIT数为10,但是寄存器值的BIT数为14;
结论一:
map->reg_write = _regmap_bus_formatted_write;

如果寄存器值的BIT数为8,16,24,32.则
map->reg_write = _regmap_bus_raw_write;
情况二:

    switch (config->val_bits) {
    .......
    case 32:
        switch (val_endian) {
        case REGMAP_ENDIAN_BIG:
            map->format.format_val = regmap_format_32_be;
            map->format.parse_val = regmap_parse_32_be;
            map->format.parse_inplace = regmap_parse_32_be_inplace;
            break;
        case REGMAP_ENDIAN_LITTLE:
            map->format.format_val = regmap_format_32_le;
            map->format.parse_val = regmap_parse_32_le;
            map->format.parse_inplace = regmap_parse_32_le_inplace;
            break;
        case REGMAP_ENDIAN_NATIVE:
            map->format.format_val = regmap_format_32_native;
            map->format.parse_val = regmap_parse_32_native;
            break;
        default:
            goto err_map;
        }
        break;
    if (map->format.format_write) {   //这条分支不满足
        map->defer_caching = false;
        map->reg_write = _regmap_bus_formatted_write;
    } else if (map->format.format_val) {  //满足分支条件
        map->defer_caching = true;
        map->reg_write = _regmap_bus_raw_write;
    }

如上

得到当前情况的最终结论:
map->reg_write = _regmap_bus_raw_write;

static int _regmap_bus_raw_write(void *context, unsigned int reg,
                 unsigned int val)
{
    struct regmap *map = context;

    WARN_ON(!map->bus || !map->format.format_val);

    map->format.format_val(map->work_buf + map->format.reg_bytes
                   + map->format.pad_bytes, val, 0);
    return _regmap_raw_write(map, reg,
                 map->work_buf +
                 map->format.reg_bytes +
                 map->format.pad_bytes,
                 map->format.val_bytes);
}

分2步走:
通过之前的代码,知道map->format.format_val指向的是regmap_format_32_native

static void regmap_format_32_native(void *buf, unsigned int val,
                    unsigned int shift)
{
    *(u32 *)buf = val << shift;
}

因此map->format.format_val的意思就是把即将写入寄存器的值先写入map->work_buf的后32位BIT中。
再看最后一步_regmap_raw_write

int _regmap_raw_write(struct regmap *map, unsigned int reg,
              const void *val, size_t val_len)
{
    void *work_val = map->work_buf + map->format.reg_bytes + map->format.pad_bytes;

    ....
    //先将寄存器的地址写入前32 BIT,至此map->work_buf的前32个BIT存放的是寄存器的地址偏移,后32个BIT存放的是即将写入该寄存器中的值。
    map->format.format_reg(map->work_buf, reg, map->reg_shift);
    if (val != work_val && val_len == map->format.val_bytes) {
        memcpy(work_val, val, map->format.val_bytes);
        val = work_val;
    }
    ....
    //对于我们当前的情况val和work_val是相等的,在regmap_range的情况下会不相等,这里先不做考虑
    if (val == work_val)
        ret = map->bus->write(map->bus_context, map->work_buf,
                      map->format.reg_bytes +
                      map->format.pad_bytes +
                      val_len);
    else if (map->bus->gather_write)
        ret = map->bus->gather_write(map->bus_context, map->work_buf,
                         map->format.reg_bytes +
                         map->format.pad_bytes,
                         val, val_len); 

}    

函数最终还是走向了regmap_mmio_write

/*
context: 对应的是regmap_mmio_gen_context返回的指针;
data: 寄存器地址偏移的指针地址;
count:地址长度+数据长度
*/
static int regmap_mmio_write(void *context, const void *data, size_t count)
{
    struct regmap_mmio_context *ctx = context;
    //这里的offset为: 4
    unsigned int offset = ctx->reg_bytes + ctx->pad_bytes;

    regmap_mmio_count_check(count, offset);

    return regmap_mmio_gather_write(context, data, ctx->reg_bytes,
                    data + offset, count - offset);
}

函数走向regmap_mmio_gather_write

/*
参数说明:
context:对应的是regmap_mmio_gen_context返回的指针;
reg:对应的是寄存器的地址指针
reg_size:对应的是寄存器的地址大小32BIT   =====  4 (字节)
val:work_buf中即将写入寄存器地址中的数据指针的位置
val_size:需要写入寄存器地址中数据的长度  =====  4(字节)
*/
static int regmap_mmio_gather_write(void *context,
                    const void *reg, size_t reg_size,
                    const void *val, size_t val_size)
{
    struct regmap_mmio_context *ctx = context;
    unsigned int offset;
    int ret;

    regmap_mmio_regsize_check(reg_size);

    if (!IS_ERR(ctx->clk)) {
        ret = clk_enable(ctx->clk);
        if (ret < 0)
            return ret;
    }

    //取出的offset对应的是寄存器地址的偏移,同读取的时候一致,也是由最开始_regmap_write传入的值
    offset = regmap_mmio_get_offset(reg, reg_size);

    while (val_size) {
        switch (ctx->val_bytes) {
        case 1:
            writeb(*(u8 *)val, ctx->regs + offset);
            break;
        case 2:
            writew(*(u16 *)val, ctx->regs + offset);
            break;
        case 4:
            //将数值写入到寄存器中
            writel(*(u32 *)val, ctx->regs + offset);
            break;
#ifdef CONFIG_64BIT
        case 8:
            writeq(*(u64 *)val, ctx->regs + offset);
            break;
#endif
        default:
            /* Should be caught by regmap_mmio_check_config */
            BUG();
        }
        val_size -= ctx->val_bytes;
        val += ctx->val_bytes;
        offset += ctx->val_bytes;
    }

    if (!IS_ERR(ctx->clk))
        clk_disable(ctx->clk);

    return 0;
}

同时regmap_write以及regmap_read最终调用的也是_regmap_write和_regmap_read,如下:

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)
{
    int ret;

    if (reg % map->reg_stride)
        return -EINVAL;

    map->lock(map->lock_arg);

    ret = _regmap_write(map, reg, val);

    map->unlock(map->lock_arg);

    return ret;
}

int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)
{
    int ret;

    if (reg % map->reg_stride)
        return -EINVAL;

    map->lock(map->lock_arg);

    ret = _regmap_read(map, reg, val);

    map->unlock(map->lock_arg);

    return ret;
}

至此整个Regmap机制分析完成

Linux regmap是一个内核模块,主要用于管理嵌入式设备中硬件寄存器的访问和配置。它是基于设备树规范设计的,允许内核模块轻松、高效地与多种类型的硬件通信,特别是那些支持I2C总线协议的低级外设。 ### 主要组件 #### Device Tree `regmap`依赖于设备树描述硬件资源,如寄存器地址空间、读写权限、中断等。设备树文件提供了一个清晰的方式去描述设备及其连接结构,使得`regmap`能直接引用这些信息来设置硬件操作。 #### 数据结构 `struct regmap` `regmap`的主要数据结构定义了寄存器映射表,其中包含: - 寄存器的物理位置 - 高效的数据结构用于查找和访问特定寄存器 - 支持多种操作模式,如直接读写、中断处理等 #### 写注册表函数 `regmap`提供了一组API供用户空间程序或内核模块使用,这些API可以安全地读取和写入硬件寄存器。它们封装了底层细节,简化了硬件交互。 ### 功能特点 1. **通用性**:`regmap`的设计考虑到了各种硬件平台的需求,因此可以广泛应用于不同类型的嵌入式系统上。 2. **安全性**:通过设备树的安全属性,可以限制哪些进程能访问特定的寄存器,增强了系统的安全性。 3. **性能优化**:内部实现了高效的寻址机制,减少了不必要的内存访问延迟。 4. **易用性**:提供了一套标准的API,使得硬件驱动的编写变得更加简单和标准化。 ### 使用场景 - **I2C 设备驱动**: 对于需要通过 I2C 总线控制的硬件设备,如传感器、LED 控制板等,`regmap` 提供了一个强大的框架来进行交互。 - **GPIO 和 PWM**: 管理 GPIO 引脚的状态和 PWM 调整,对于控制 LED 或其他数字信号处理任务非常有用。 - **ADC 和 DAC**: 集成模拟转换器和数模转换器的操作,用于信号调理和数字化。 `regmap`的灵活性和高效性使其成为Linux内核管理和控制低级别硬件资源的重要组成部分之一。对于开发者来说,掌握`regmap`不仅能够更有效地开发驱动程序,还能增强对系统底层架构的理解。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值