在内核代码里,有成千上万的以I2C / SPI为通讯接口的设备驱动。
以I2C设备为例
各种I2C接口的设备驱动都需要通过I2C子系统的API(i2c_transfer())来进行读写寄存器的操作。在对应的设备驱动中,I2C读写寄存器的操作通常会被封装成2个静态函数:
static int fsi_i2c_read_reg(struct fsi_device *fsi, unsigned int reg, u32 *data);
static int fsi_i2c_write_reg(struct fsi_device *fsi, unsigned int reg,u32 *data);
static inline unsigned char imx_i2c_read_reg(struct imx_i2c_struct *i2c_imx,unsigned int reg);
static inline void imx_i2c_write_reg(unsigned int val,struct imx_i2c_struct *i2c_imx, unsigned int reg);
没有regmap抽象层之前,内核里充斥着大量类似的代码,这些代码都是多余的,使用I2C总线来读写寄存器的操作是有共性的,应该被抽象出来,形成一份统一的代码。驱动开发人员应使用该抽象层的API来读写I2C设备的寄存器,然后把更多的精力放在驱动的逻辑设计上。
同样的,没有regmap子系统时,spi设备的寄存器读写操作也是散落在各个spi设备驱动。
i2c/spi drvier、i2c/spi device、i2c/spi subsystem之间的关系如下:
1. 同样以I2C设备为例,先定义struct regmap_config:
struct regmap_config里包含了读写芯片寄存器所需所有信息,例如寄存器数据位宽、地址位宽等。
2. 将struct regmap_config注册给regmap子系统:
struct regmap * devm_regmap_init_i2c(i2c, config);
得到一个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之间的关系如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gg98bFrk-1668320247630)(https:upload-images.jianshu.io/upload_images/13906732-7786c99038848e61?imageMogr2/auto-orient/strip|imageView2/2/w/501/format/webp)]
源码文件:
├──regmap.c
核心文件:注册/卸载总线、读写寄存器API
├── regcache.c
├── regcache-flat.c
├── regcache-lzo.c
├── regcache-rbtree.c
缓存相关
├── regmap-debugfs.c
调试接口
├── regmap-i2c.c
├── regmap-i3c.c
├── regmap-ac97.c
├── regmap-spi.c
总线相关
├── regmap-irq.c
├── regmap-mmio.c
├── regmap-sccb.c
├── regmap-sdw.c
├── regmap-slimbus.c
├── regmap-spmi.c
├── regmap-w1.c
└── trace.h
在regmap子系统里,可以选择是否使用缓存功能:
enum regcache_type {
REGCACHE_NONE,
REGCACHE_RBTREE,
REGCACHE_COMPRESSED,
REGCACHE_FLAT,
};
regmap内支持3 种缓存类型:数组(flat)、LZO 压缩和红黑树(rbtree)
数组是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。
LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。
红黑树特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。
regcache_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);
};
struct regmap_config {
const char *name;
int reg_bits;
int reg_stride;
int pad_bits;
int val_bits;
bool (*writeable_reg)(struct device *dev, unsigned int reg);
bool (*readable_reg)(struct device *dev, unsigned int reg);
bool (*volatile_reg)(struct device *dev, unsigned int reg);
bool (*precious_reg)(struct device *dev, unsigned int reg);
bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);
bool disable_locking;
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg;
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;
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct regmap_access_table *wr_noinc_table;
const struct regmap_access_table *rd_noinc_table;
const struct reg_default *reg_defaults;
unsigned int num_reg_defaults;
enum regcache_type cache_type;
const void *reg_defaults_raw;
unsigned int num_reg_defaults_raw;
unsigned long read_flag_mask;
unsigned long write_flag_mask;
bool zero_flag_mask;
bool use_single_read;
bool use_single_write;
bool can_multi_write;
enum regmap_endian reg_format_endian;
enum regmap_endian val_format_endian;
const struct regmap_range_cfg *ranges;
unsigned int num_ranges;
bool use_hwlock;
unsigned int hwlock_id;
unsigned int hwlock_mode;
};
struct regmap_config里包含了读写芯片寄存器所需的所有信息,它是regmap API里最核心的结构体。使用regmap子系统的第一步就是填充该结构体。这个结构体看着庞大,但是大多数情况下只要初始化几个成员变量就足够了,例如:
const struct regmap_config tps65912_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.cache_type = REGCACHE_RBTREE,
.volatile_table = &tps65912_volatile_table,
};
regmap_bus
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_reg_update_bits reg_update_bits;
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;
size_t max_raw_read;
size_t max_raw_write;
};
该结构体用于描述一种硬件总线的寄存器读写操作(a hardware bus for the register map infrastructure)。
regmap_bus并不是面向用户的API,也就是说使用regmap子系统并不要求一定要了解该结构体,但是理解该结构体有助于我们了解regmap是如何抽象寄存器读写操作的。无论是i2c还是spi,在调用devm_regmap_init_i2c将struct regmap_config注册给regmap子系统后,在子系统内部都会根据remap_config里的配置信息找到对应的regmap_bus。
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);
}
有了适配芯片的 regmap_config 和 regmap_bus,regmap子系统就有了读写该芯片寄存器的能力,然后就会返回一个struct regmap供设备驱动来使用了,struct regmap类似一个大管家,包含了所有信息,负责统筹一切。
regmap提供出来的读写寄存器的API非常多,最常用的3个API如下:
regmap_read
regmap_write
regmap_update_bits_base
可以猜测上述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 的操作次数。
作者:老吴的嵌入式之旅
参考链接:https://www.jianshu.com/p/600a3a4c78e9