我们以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 来读写寄存器。比如如下这个读取寄存器的函数:
- 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(reg