文章目录
环境
linux 4.9
armv8
写一个复杂设备驱动时使用了regmap子系统,资料很少,所以只能自己去研究子系统的实现,大部分分析都在注释中,当做笔记。
一、关于regmap子系统
关于该系统的作用,可以看这些文章:
简而言之,就是regmap子系统把一些低速接口的api函数给平台化了。之前如果要使用i2c,spi,中断irq等等都有不同的接口,但是现在只需要几个通用api就可以满足,比如说:
- regmap_write()
- regmap_read()
- regmap_upadte_bits()
使用方法也很简单,很多资料,教程都有,如何初始化,如何配置等等。除此之外,regmap子系统还提供了缓存机制,可以选择哪些数据可以缓存,以及缓存方式。
这样做优化,比如说你可以设置i2c/spi等设备的哪些寄存器的值启用缓存,那么当我使用write更新了该寄存器之后,再使用read,就没必要再通过协议去寄存器中读取,而是直接去缓存中读取该寄存器的值就可以了。这片缓存区是由regmap子系统提供的,可以选择用数组的方式进行缓存,也可以生成红黑树来进行缓存。
本篇文章主要分析这个框架是怎么实现的,为什么能这么使用。
二、regmap-i2c初始化
如果想使用i2c通信,那就要把regmap初始化为i2c通信模式,需要:
- 创建一个struct regmap结构体指针
- 创建一个struct regmap_config结构体并初始化其中所需要的域。
- 调用devm_regmap_init_i2c()函数,该函数根据regmap_config中配置的信息初始化regmap结构体
struct regmap = devm_regmap_init_i2c(struct i2c_client, struct regmap_config)
struct regmap结构体会保存很多regmap框架的信息,后续使用regmap_write()等函数都需要传入初始化后的结构体。
在此之前,有必要分析一下struct regmap_config这个结构体,配置regmap最主要的信息都来自这里:
include/linux/regmap.h:
struct regmap_config {
const char *name;
int reg_bits; // g, 寄存器bit数,说明一个寄存器要用8bit表示
int reg_stride;
int pad_bits;
int val_bits; // g, 一个数据要用8bit表示
bool (*writeable_reg)(struct device *dev, unsigned int reg); // g, 自己注册的可写检查函数,如果存在,则会再写入reg之前,会调用该函数检查寄存器是否可写
bool (*readable_reg)(struct device *dev, unsigned int reg); // g, 同上
bool (*volatile_reg)(struct device *dev, unsigned int reg); // g, 寄存器缓存控制函数,请看volatile_table表的注释
bool (*precious_reg)(struct device *dev, unsigned int reg); // g, 要求寄存器数值维持在一个数值范围才正确,maintain一个数值准确表
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg;
// g, 有些设备的读写很复杂,不只是单纯按照i2c/spi协议进行读写,如果存在这种情况要手写对应的读写函数,注册到下面这俩。在读写时会优先使用自己注册的读写函数
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
bool fast_io;
unsigned int max_register; // g, 最大寄存器地址,防止访问越界
// g, 如果没有注册writeable_reg函数,就要使用下面两个表来判断寄存器是否可以写,该表只有来个成员变量:start和end,地址在这个范围内的表示可以写
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
// g, 哪些寄存器可以缓存(volatile_table->yes_range域),哪些不可以(volatile_table->no_range域)。可要求读写立即生效的寄存器范围,no_range域范围中的reg不可以被cache
// g, regmap的缓存机制会把一些寄存器缓存到内存中,在读这些寄存器的时候就可以去缓存中去读。但是写的时候仍然要向物理设备中写入,同时更新缓存,所以缓存只影响读的性能
// g, 关于这个写缓存,看到一种不一样的说法:对于写操作,待cache存在一定数据量或者超出时间后写入物理寄存器,但降低实时性。具体什么情况还得有空看一下源码
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct reg_default *reg_defaults;
unsigned int num_reg_defaults;
enum regcache_type cache_type; // g, 若启用缓存,该域决定使用哪种缓存方式
const void *reg_defaults_raw;
unsigned int num_reg_defaults_raw;
unsigned long read_flag_mask; // g, 如果有需要,可以设置该掩码。那么regmap在发送读数据时就会把数据先 & mask再发送(有的spi设备只需要7位数据并且要求最高位必须为1时就有用了)
unsigned long write_flag_mask;
bool use_single_rw;
bool can_multi_write;
enum regmap_endian reg_format_endian; // g, 寄存器地址大小端,大于8位时需设置
enum regmap_endian val_format_endian; // g, 寄存器值大小端,大于8位时需设置
const struct regmap_range_cfg *ranges;
unsigned int num_ranges;
};
具体要启用哪些配置,需要按照需求来配,比如说:
// g, 配置方式:
static const struct regmap_range i2cdev_writeable_ranges[] = {
regmap_reg_range(可写寄存器起始地址, 可写寄存器结束地址),
};
static const struct regmap_range i2cdev_volatile_table[] = {
regmap_reg_range(可缓存寄存器起始地址, 可缓存寄存器结束地址),
};
static const struct regmap_access_table i2cdev_writeable_table = {
.yes_ranges = i2cdev_writeable_ranges, // g, 配置可写寄存器范围
.n_yes_ranges = ARRAY_SIZE(i2cdev_writeable_ranges), // g, 可写寄存器个数
// g, 没有配置不可写的范围,所以后续使用regmap_write时只会检查要写入的寄存器是否再可写寄存器范围内
};
static const struct regmap_access_table i2cdev_volatile_table = {
.yes_ranges = i2cdev_volatile_ranges, // g, 配置可缓存寄存器范围
.n_yes_ranges = ARRAY_SIZE(i2cdev_volatile_ranges), // g, 可缓存寄存器个数
// g, 没有配置不可写的范围
};
/* --------- 配置结构体----------------------*/
static const struct regmap_config i2cdev_regmap_config = {
.reg_bits = 8, // g, 配置寄存器地址由8bit表示
.val_bits = 8, // g, 配置一次传输的value为8bit
.wr_table = &i2cdev_writeable_table, // g, 配置可写/不可写寄存器的范围
.volatile_table = &i2cdev_volatile_table, // g, 配置可以缓存/不可以缓存的寄存器范围
.max_register = 最大寄存器地址,
.cache_type = REGCACHE_RBTREE, // g, 使用红黑树进行缓存
};
接下来就可以调用devm_regmap_init_i2c()来初始化regmap了:
struct i2c_client *i2c = i2c设备; // g, 挂在i2c bus的driver的probe中会传入匹配到的i2c设备的。
struct regmap *regmap = devm_regmap_init_i2c(i2c, i2cdev_regmap_config );
include/linux/regmap.h:
// g, 该宏__regmap_lockdep_wrapper会执行__devm_regmap_init_i2c(i2c, config, NULL, NULL)
#define devm_regmap_init_i2c(i2c, config) \
__regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config, \
i2c, config)
最终会执行__devm_regmap_init_i2c(i2c, config, NULL, NULL):
drivers/base/regmap/regmap-i2c.c:
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)
{
// g, 该函数初始化i2c_bus,bus中可以提供.read, .write等函数,后续的regmap_write()等接口都会调用到map->bus->read/write
const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);
if (IS_ERR(bus))
return ERR_CAST(bus);
// g, 该函数会初始化regmap的多个域,如 map->dev = dev; map->bus = bus;其余的域都是通过config进行初始化,与config中的各个域名几乎一模一样
// g, 其中关键函数有三个:regmap->reg_read(), regmap->reg_write(), regmao->rreg_update_bits(),但是这三个函数具体调用了什么先不看
// g, 在使用regmap时,regmap_read()会调用cache/map->reg_read, regmap_update_btits()会调用map->reg_update_bits(如果有的话) or _regmap_write->[map->reg_write]
// g, 对于map->reg_read()和map->reg_write(),并不是简单的调用map->bus->read/write等,而是会根据config提供的value_bits,reg_bits等做很多前后处理以及优化之后再调用bus提供的函数,很复杂
// g, 这里知道通过该函数可以设置i2c通信的参数,并且最终一定会调用到map->bus->read/write就可以了
return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config,
lock_key, lock_name);
}
该函数就做了两件事:
- 为regmap选择bus,我把他当做是选择通信协议(i2c也有好几种不同的协议)。
- 根据struct regmap_config配置信息和选择的bus,初始化struct regmap。
2.1 regmap_get_i2c_bus()
regmap作为一个平台化的工作,并且是一个出现在i2c、spi等子系统之后的子系统,肯定不会造轮子的。这一点从选择bus的函数regmap_get_i2c_bus()中就能看到:
drivers/base/regmap/regmap-i2c.c:
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
const struct regmap_config *config)
{
// g, 该函数比较i2c->adapter->algo->functionality(adap)与后面的宏是否相等来判断该i2c总线使用什么i2c传输算法
// g, functionality()函数由总线设备提供,要求能通过该函数获得设备使用什么i2c协议
// g, 比如说有些设备支持普通i2c协议)(I2C_FUNC_I2C),有些设备支持i2c协议子集smbus
if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
// g, 关于这个regmap_i2c,是一个静态结构体,提供了.write,.read等regmap框架的i2c读写函数
// g, 其底层实现仍然是使用i2c_transfer(),i2c_master_send()等i2c总线函数
return ®map_i2c;
// g, 也有可能i2c adapter支持的i2c的子集协议:SMBus,该协议明确了数据的传输格式,是面向命令的,我认为面向命令就是类似于CAN,指明每一帧是做什么的
else if (config->val_bits == 8 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK)) // g, 块写入/读取协议
return ®map_i2c_smbus_i2c_block;
else if (config->val_bits == 16 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_WORD_DATA))
switch (regmap_get_val_endian(&i2c->dev, NULL, config)) {
case REGMAP_ENDIAN_LITTLE:
return ®map_smbus_word;
case REGMAP_ENDIAN_BIG:
return ®map_smbus_word_swapped;
default: /* everything else is not supported */
break;
}
else if (config->val_bits == 8 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
return ®map_smbus_byte;
return ERR_PTR(-ENOTSUPP);
}
该函数根据adapter的类型,来选择合适的通信协议以及对应的通信函数。不考虑SMBus,就以一般i2c通信协议来说,最终选择的函数集合为:
drivers/base/regmap/regmap-i2c.c:
static struct regmap_bus regmap_i2c = {
.write = regmap_i2c_write,
.gather_write = regmap_i2c_gather_write,
.read = regmap_i2c_read,
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
最终实现的函数,其实就是套皮的i2c通信函数,借助了i2c子系统。以write/read函数regmap_i2c_write/regmap_i2c_read为例:
drivers/base/regmap/regmap-i2c.c:
static int regmap_i2c_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct i2c_client *i2c = to_i2c_client(dev);
int ret;
ret = i2c_master_send(i2c, data, count); // g, 套皮i2c
if (ret == count)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
...
static int regmap_i2c_read(void *context,
const void *reg, size_t reg_size,
void *val, size_t val_size)
{
struct device *dev = context;
struct i2c_client *i2c = to_i2c_client(dev);
struct i2c_msg xfer[2];
int ret;
xfer[0].addr = i2c->addr;
xfer[0].flags = 0;
xfer[0].len = reg_size;
xfer[0].buf = (void *)reg;
xfer[1].addr = i2c->addr;
xfer[1].flags = I2C_M_RD;
xfer[1].len = val_size;
xfer[1].buf = val;
ret = i2c_transfer(i2c->adapter, xfer, 2); // g, 套皮i2c
if (ret == 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
当我们使用regmap_write()/regmap_read()等regmap通信API时,最终会调用到上述绑定的通信函数中。
2.2 __devm_regmap_init()
在选择完通信协议函数后,就需要使用选择的通信协议函数和配置参数struct regmap_config来初始化struct regmap了,执行这些工作的函数为__devm_regmap_init():
drivers/base/regmap/regmap.c:
struct 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); // g, 添加的dev resource中,最终也会随dev的unregister而释放
if (!ptr)
return ERR_PTR(-ENOMEM);
regmap = __regmap_init(dev, bus, bus_context, config, // g, 初始化regmap
lock_key, lock_name);
if (!IS_ERR(regmap)) {
*ptr = regmap;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return regmap;
}
其中regmap是作为dev resource绑定到dev->devres_head链表中去的,最终会在unregister的时候释放资源。
完成初始化工作的是函数__regmap_init():
struct regmap *__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 *map;
int ret = -EINVAL;
enum regmap_endian reg_endian, val_endian;
int i, j;
if (!config)
goto err;
map = kzalloc(sizeof(*map), GFP_KERNEL);
if (map == NULL) {
ret = -ENOMEM;
goto err;
}
if (config->lock && config->unlock) {
map