regmap简介
Regmap 机制是在 Linux 3.1 加入进来的特性。主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器。其实这就是内核做的一次重构。Regmap 除了能做到统一的 I/O 接口,还可以在驱动和硬件 IC 之间做一层缓存,从而能减少底层 I/O 的操作次数。学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 A
1 简介
Regmap 机制是在 Linux 3.1 加入进来的特性。主要目的是减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件上的寄存器。其实这就是内核做的一次重构。Regmap 除了能做到统一的 I/O 接口,还可以在驱动和硬件 IC 之间做一层缓存,从而能减少底层 I/O 的操作次数。
2 使用对比
在了解 Regmap 的实现细节前,我们先来对比一下,传统操作寄存器的方式,与 Regmap 之间的差异。
2.1 传统方式
我们以一个 I2C 设备为例。读写一个寄存器,肯定需要用到 i2c_transfer
这样的 I2C 函数。为了方便,一般的驱动中,会在这之上再写一个 Wrapper,然后通过调用这个 Wrapper 来读写寄存器。比如如下这个读取寄存器的函数:
- static int xxx_i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
- {
- struct i2c_msg msg[] = {
- {
- .addr = client->addr,
- .flags = 0,
- .len = 1,
- .buf = ®,
- },
- {
- .addr = client->addr,
- .flags = I2C_M_RD,
- .len = 1,
- .buf = val,
- },
- };
- return i2c_transfer(client->adapter, msg, 2);
- }
2.2 Regmap方式
而如果 regmap 的方式来实现,对于上面这种读寄存器操作,其实现如下:
- // first step: define regmap_config
- static const struct regmap_config xxx_regmap_config = {
- .reg_bits = 10,
- .val_bits = 14,
- .max_register = 40,
- .cache_type = REGCACHE_RBTREE,
- .volatile_reg = false,
- .readable_reg = false,
- };
- // second step: initialize regmap in driver loading
- regmap = regmap_init_i2c(i2c_client, &xxx_regmap_config);
- // third step: register operations
- 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_write
和 regmap_read
函数。
3.2 regmap_ops
struct regmap_ops
是用来定义一个缓存类型的,具体定义如下:
- struct regcache_ops {
- const char *name;
- enum regcache_type type;
- int (*init)(struct regmap *map);
- int (*exit)(struct regmap *map);
- #ifdef CONFIG_DEBUG_FS
- void (*debugfs_init)(struct regmap *map);
- #endif
- int (*read)(struct regmap *map, unsigned int reg, unsigned int *value);
- int (*write)(struct regmap *map, unsigned int reg, unsigned int value);
- int (*sync)(struct regmap *map, unsigned int min, unsigned int max);
- int (*drop)(struct regmap *map, unsigned int min, unsigned int max);
- };
在最新 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
所做的封装一样。其定义如下:
- struct regmap_bus {
- bool fast_io;
- regmap_hw_write write;
- regmap_hw_gather_write gather_write;
- regmap_hw_async_write async_write;
- regmap_hw_reg_write reg_write;
- regmap_hw_read read;
- regmap_hw_reg_read reg_read;
- regmap_hw_free_context free_context;
- regmap_hw_async_alloc async_alloc;
- u8 read_flag_mask;
- enum regmap_endian reg_format_endian_default;
- enum regmap_endian val_format_endian_default;
- };
在 Kernel 4.0 中,已经支持了 I2C、SPI、AC97、MMIO 和 SPMI 五种总线类型。相信在未来,有更多的总线会加进来。其实添加一个总线也不是很难,只需 4 个函数就可以了:xxx_read
、xxx_write
、xxx_init
和 xxx_deinit
。具体可以看源码,这里就不多说了,留个任务在这吧。
4 Reference
一篇文带你详解Linux Regmap 子系统
前言:
1、什么是 Regmap
2、Regmap 驱动框架
3、Regmap 操作函数
前言:
学习 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 函数都有详细的讲解。
更多推荐
所有评论(0)