• regmap


    一、regmap的引入

    没有regmap子系统之前

    在内核代码里,有成千上万的以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);
    
    • 1
    • 2
    • 3
    • 4

    没有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 * 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);
    
    • 1
    • 2
    • 3

    读写寄存器的操作已经抽象到regmap子系统里了,完整的API位于include/linux/regmap.h。

    有了regmap后,i2c/spi drvier、i2c/spi device、i2c/spi subsystem之间的关系如下:

    在这里插入图片描述

    二、regmap子系统的内部实现

    regmap的拓扑结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    regmap的缓存功能

    在regmap子系统里,可以选择是否使用缓存功能:

    enum regcache_type {
    	REGCACHE_NONE,
    	REGCACHE_RBTREE,
    	REGCACHE_COMPRESSED,
    	REGCACHE_FLAT,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    regmap内支持3 种缓存类型:数组(flat)、LZO 压缩和红黑树(rbtree)

    1. 数组是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。

    2. LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。

    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);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    该结构体用于描述一种硬件总线的寄存器读写操作(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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    有了适配芯片的 regmap_config 和 regmap_bus,regmap子系统就有了读写该芯片寄存器的能力,然后就会返回一个struct regmap供设备驱动来使用了,struct regmap类似一个大管家,包含了所有信息,负责统筹一切。

    常用的regmap API

    regmap提供出来的读写寄存器的API非常多,最常用的3个API如下:

    regmap_read
    regmap_write
    regmap_update_bits_base
    
    • 1
    • 2
    • 3

    可以猜测上述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

  • 相关阅读:
    深眸科技以需求定制AI视觉解决方案,全面赋能产品外观缺陷检测
    神经网络中的优化方法
    【React 源码】(十五)React Context 原理
    华为鸿蒙技术官带你庖丁解牛般的理解Linux操作系统内核分析
    kubernetes CNI(Container Network Inferface)
    vue小技能:通过插槽`<slot>`分发内容,使用 插槽 prop向父级组件传递数据。
    【区块链技术与应用】(六)
    C++流程控制总结,看这一篇就够了
    Ubuntu 22 LTS 搭建SpringBoot服务器
    学JAVA好?还是学Python好?
  • 原文地址:https://blog.csdn.net/qq_37935909/article/details/127831727