目录:
一、regmap子系统的引入
二、regmap子系统的内部实现
一、regmap子系统的引入
没有regmap子系统之前
在内核代码里,有成千上万的以I2C / SPI为通讯接口的设备驱动。
以I2C设备为例
各种I2C接口的设备驱动都需要通过I2C子系统的API(i2c_transfer())来进行读写寄存器的操作。在对应的设备驱动中,I2C读写寄存器的操作通常会被封装成2个静态函数。
xxx_i2c_read_reg()/xxx_i2c_write_reg():
在没有regmap抽象层之前,内核里充斥着大量类似的代码,这些代码都是多余的,使用I2C总线来读写寄存器的操作是有共性的,应该被抽象出来,形成一份统一的代码。驱动开发人员应使用该抽象层的API来读写I2C设备的寄存器,然后把更多的精力放在驱动的逻辑设计上。
同样的,没有regmap子系统时,spi设备的寄存器读写操作也是散落在各个spi设备驱动。
i2c/spi drvier、i2c/spi device、i2c/spi subsystem之间的关系如下:
有了regmap子系统后
1. 同样以I2C设备为例,先定义struct regmap_config:
struct regmap_config里包含了读写芯片寄存器所需所有信息,例如寄存器数据位宽、地址位宽等。
2. 将struct regmap_config注册给regmap子系统:
得到一个struct regmap对象,有了这个对象,就可以调用regmap子系统提供的用于读写寄存器的API了。
3. 使用regmap API读写寄存器:
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子系统里了,完整的API位于include/linux/regmap.h。
有了regmap后,i2c/spi drvier、i2c/spi device、i2c/spi subsystem之间的关系如下:
我在Linux-4.14内核里搜索了一下,目前大约有491个地方使用了regmap子系统,将来可能会更多。
二、regmap子系统的内部实现
regmap的拓扑结构
在Linux-4.14内核中,regmap分为3层:
源码文件:
regmap的缓存功能
在regmap子系统里,可以选择是否使用缓存功能:
regmap内支持3 种缓存类型:数组(flat)、LZO 压缩和红黑树(rbtree)
1) 数组是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。
2) LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。
3) 红黑树特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。
相关结构体:
struct regmap_config
struct regmap_config里包含了读写芯片寄存器所需的所有信息,它是regmap API里最核心的结构体。使用regmap子系统的第一步就是填充该结构体。这个结构体看着庞大,但是大多数情况下只要初始化几个成员变量就足够了,最简单和常见的情况类似这样:
struct regmap_config重要成员变量的含义如下:
struct regmap_bus
该结构体用于描述一种硬件总线的寄存器读写操作(a hardware bus for the register map infrastructure)。
regmap_bus并不是面向用户的API,也就是说使用regmap子系统并不要求一定要了解该结构体,但是理解该结构体有助于我们了解regmap是如何抽象寄存器读写操作的。无论是i2c还是spi,在调用devm_regmap_init_[i2c|spi|...]将struct regmap_config注册给regmap子系统后,在子系统内部都会根据remap_config里的配置信息找到对应的struct regmap_bus。
比如:
static struct regmap_bus regmap_i2c
[...]
static struct regmap_bus regmap_i2c_smbus_i2c_block
static struct regmap_bus regmap_spi
static struct regmap_bus regmap_spmi_base
[...]
有了适配芯片的 regmap_config 和 regmap_bus,regmap子系统就有了读写该芯片寄存器的能力,然后就会返回一个struct regmap供设备驱动来使用了,struct regmap类似一个大管家,包含了所有信息,负责统筹一切。
struct regmap_bus重要成员变量的含义如下:
定义一个regmap_bus结构体时不需要初始化其所有成员变量,例如某些 i2c 芯片的寄存器是16bit(2Byte)的,其使用的regmap_bus如下:
常用的regmap API
regmap提供出来的读写寄存器的API非常多,最常用的3个API如下:
可以猜测上述API会利用struct regmap_config + struct regmap_bus完成寄存器的读写操作。
简单看下regmap_read()的实现:
regmap_read
struct regmap *map
map->reg_read(context, reg, val);
_regmap_bus_reg_read
map->bus->reg_read
总结
regmap 是在 Linux 3.1 加入进来的特性,其最初的目的是减少i2c/spi等设备驱动里的重复逻辑,提供一种通用的接口来操作芯片内寄存器,随着版本的更迭,regmap 支持的bus越来越多,并且除了能做到统一的 寄存器I/O 接口,还可以在驱动和硬件 IC 之间做一层缓存,从而能减少底层 I/O 的操作次数。给我的感觉是:内核越来越强大了,但是学习的难度也越来越大,前路漫漫啊,求老司机带路...
你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对嵌入式系统开发有兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:ESexpert,一起来学习吧,欢迎各种收藏/转发/批评。

网友评论