regmap

一、前言

regmap是在 linux 内核为减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件寄存器的模型框架。此外,regmap在驱动和硬件寄存器之间增加了cache,减少底层低速 I/O 的操作次数,提高访问效率;当然实时性会有所降低。

基于代码代码复用的原则之一,Linux内核后引入了regmap模型,将寄存器访问的共同逻辑抽象出来,只需初始化时指定总线类型、寄存器位宽等关键参数,即可通过regmap模型接口来操作器件寄存器。当然,regmap同样适用于操作cpu自身的寄存器。将i2c、spi、mmio、irq都抽象出统一的接口regmap_read、regmap_write、regmap_update_bits等接口 ,从而提高代码的可重用性,并且使得在使用如上内核基础组件时变得更为简单易用。

二、regmap 框架模型

regmap整体上分为三层,从下到上分别为物理总线、regmap核心、regmap api。

在这里插入图片描述

  • 底层,对接的是具体物理总线,目前regmap框架支持i2c、spi、mmio、spmi、ac97总线
  • 核心层,regmap 核心实现
  • api,抽象通用接口

对于使用regmap框架来说,可以不用关心regmap核心的实现过程,只需根据物理总线类型,配置好相关参数信息,即可调用regmap api访问芯片寄存器。

二、怎样使用regmap

使用regmap比较简单,使用前,只需根据外设属性配置总线类型、寄存器位宽、缓存类型、读写属性等参数;接着注册一个regmap实例;然后调用抽象访问接口访问寄存器。

  • 第一步,配置regmap信息
  • 第二步,注册regmap实例
  • 第三步,访问寄存器
  • 第四步,释放regmap实例

三、regmap的结构体(可以仅对自己需要的部分赋值)

struct regmap_config {
	const char *name;

	int reg_bits;// 寄存器地址的位数,必须配置,例如I2C寄存器地址位数为 8
	int reg_stride;
	int pad_bits;// 寄存器值的位数,必须配置
	int val_bits;

	bool (*writeable_reg)(struct device *dev, unsigned int reg);// 可写寄存器回调,maintain一个可写寄存器表
	bool (*readable_reg)(struct device *dev, unsigned int reg); // 可读寄存器回调, maintain一个可读寄存器表
	bool (*volatile_reg)(struct device *dev, unsigned int reg); // 可要求读写立即生效的寄存器回调,不可以被cache,maintain一个可立即生效寄存器表
	bool (*precious_reg)(struct device *dev, unsigned int reg); // 要求寄存器数值维持在一个数值范围才正确,maintain一个数值准确表
	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 reg_default *reg_defaults;
	unsigned int num_reg_defaults;
	enum regcache_type cache_type;  // cache数据类型,支持三种:flat、rbtree、Izo 
	const void *reg_defaults_raw;
	unsigned int num_reg_defaults_raw;

	u8 read_flag_mask;// 读寄存器掩码
	u8 write_flag_mask;// 写寄存器掩码

	bool use_single_rw;
	bool can_multi_write;

	enum regmap_endian reg_format_endian;// 寄存器地址大小端,大于8位时需设置
	enum regmap_endian val_format_endian;// 寄存器值大小端,大于8位时需设置 

	const struct regmap_range_cfg *ranges;
	unsigned int num_ranges;
};

目前linux内核支持三种cache数据类型,如下:

/* An enum of all the supported cache types */
enum regcache_type {
	REGCACHE_NONE,
	REGCACHE_RBTREE,//红黑树类型
	REGCACHE_COMPRESSED,//压缩类型
	REGCACHE_FLAT,//普通数据类型
};

在最新 Linux 4.0 版本中,已经有 3 种缓存类型,分别是数组(flat)、LZO 压缩和红黑树(rbtree)。数组好理解,是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。而最后一类红黑树,它的特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。

缓存的类型是在 regmap 初始化时,由.cache_type = REGCACHE_RBTREE 来指定的。对于 regmap_read 来说,会先判断当前缓存是否有值,然后再检查是否需要 bypass,若没有,则可以直接从缓存里面取值,调用regcache_read来获取值,若需要从硬件上读取,则调用具体协议的读写函数,若是 I2C,调用i2c_transfer。写的过程也是大同小异。

例如我用到REGCACHE_RBTREE

 static struct regmap_config es8388_regmap = {
	.reg_bits = 8,
	.val_bits = 8,	

    .max_register = 0x34,
	.volatile_reg = es8388_volatile_register,
    .readable_reg = es8388_readable_register,

    .cache_type = REGCACHE_RBTREE,
	.reg_defaults = es8388_reg_defaults,
	.num_reg_defaults = ARRAY_SIZE(es8388_reg_defaults),

	
};

该数据结构主要在创建一个regmap时,实现对regmap的初始化,主要信息如下:

  • 寄存器的位数、寄存器值的位数;
  • 寄存器读写权限判断的回调函数、读写范围的定义;
  • 加锁、解锁函数;
  • 读写flag;
  • 字节序相关的配置;
  • 是否支持page读写方式,若需要定义regmap_range_cfg类型的变量,说明page select reg、page
    reg范围等内容。

四、注册并初始化regmap

regmap_init_i2c(struct i2c_client *i2c, struct regmap_config *config);   
regmap_init_spi(struct spi_device *spi, strcut regmap_config *config);
regmap_init_mmio(struct device *dev, struct regmap_config *config);   
regmap_init_spmi_base(struct spmi_device *dev, strcut regmap_config *config);
regmap_init_spmi_ext(struct spmi_device *dev, strcut regmap_config *config);
regmap_init_ac97(struct snd_ac97 *ac97, strcut regmap_config *config);
regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, int irq_base, struct regmap_irq_chip *chip, struct regmap_irq_chip_data **data);

注意:regmap_add_irq_chip:关联后的regmap上注册 irq

初始化函数声明位于kernel/include/linux/regmap.h中,原型中linux内核通过宏定义实现,展开后即是上面函数声明。具体转化过程如下


#define regmap_init_i2c(i2c, config)					\
	__regmap_lockdep_wrapper(__regmap_init_i2c, #config,i2c, config)
				
#define __regmap_lockdep_wrapper(fn, name, ...) fn(__VA_ARGS__, NULL, NULL)

struct regmap *__regmap_init_i2c(struct i2c_client *i2c,
				 const struct regmap_config *config,
				 struct lock_class_key *lock_key,
				 const char *lock_name);
转化为				 				 
regmap_init_i2c(struct i2c_client *i2c, struct regmap_config *config);
 

五、 使用regmap

配置和注册regmap实例后,我们就可以使用抽象接口来访问寄存器,摈弃之前那套繁琐的数据结构和函数api。接口比较通俗,根据函数名称和入口参数即可知道函数功能。接口分为两大类,设置类(与初始化配置信息不同)和访问类,访问类根据访问过程又分为两种:

经过regmap cache,提高访问效率,对于写操作,待cache存在一定数据量或者超出时间后写入物理寄存器;但降低实时性

不经过regmap cache,对于写操作,立即写入物理寄存器,实时性好;对于读操作,则经过cache,减少拷贝时间

在初始化好regmap之后,就可以调用regmap提供的read/write/update等操作了。


int regmap_write(struct regmap *map, int reg, int val); //向单个reg写入val  
int regmap_raw_write(struct regmap *map, int reg, void *val, size_t val_len); //向单个reg写入指定长度的数据,数据存放在val中
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,size_t val_count); // 写多个reg
int regmap_multi_reg_write_bypassed(struct regmap *map, const struct reg_sequence *regs,int num_regs);// 直接写入reg,不经过regmap cache
int regmap_raw_write_async(struct regmap *map, unsigned int reg,const void *val, size_t val_len);//写多个reg,并立即刷新cache写入
int regmap_read(struct regmap *map, int reg, int *val); // 读取单个reg的数据到val中/
int regmap_raw_read(struct regmap *map, int reg, void *val, size_t val_len);  // 读取单个reg中指定长度的数据 
int regmap_bulk_read(struct regmap *map, int reg, void *val, size_t val_count); // 读取从reg开始之后val_count个寄存器的数据到val中
int regmap_update_bits(struct regmap *map, int reg, int mask, int val); 	// 更新reg寄存器中mask指定的位
int regmap_write_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);//写入寄存器值指定bit *
void regcache_cache_bypass(arizona->regmap, true); // 设置读写寄存器不通过cache模式而是bypass模式,读写立即生效,一般在audio等确保时序性驱动中用到

六、释放regmap

在驱动注销时一定要释放已注册的regmap。

void regmap_exit(struct regmap *map);

七、举例

/* 第一步配置信息 */
static const struct regmap_config regmap_config =
{
.reg_bits = 8,
.val_bits = 8,
.max_register = 255,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = false,
};

/* 第二步,注册regmap实例 */
regmap = regmap_init_i2c(i2c_client, &regmap_config);

/* 第三步,访问操作 */
static int read_regs(uint8_t reg, uint8_t *pdata, int size)
{
return regmap_raw_read(regmap, reg, pdata, size);
}

regmap方式将i2c的数据结构、传输api隐藏,使用者无需关心i2c内部实现,简化驱动开发过程,提高代码的复用性。如果将该器件物理接口更换为spi,只需修改配置信息即可,寄存器访问过程无需更改。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`regmap_write()` 是 Linux 内核中用于向设备寄存器写入数据的一个高级 API。它是 regmap 框架中的一个函数,可以自动选择适当的底层函数进行寄存器写操作。以下是 `regmap_write()` 函数的原型: ```c int regmap_write(struct regmap *map, unsigned int reg, unsigned int val); ``` 函数参数说明: - `map`:指向要操作的 regmap 实例的指针。 - `reg`:要写入的寄存器地址。 - `val`:要写入寄存器的数据。 `regmap_write()` 函数会根据 regmap 实例中的寄存器描述信息,自动选择适当的底层函数进行寄存器写操作。对于 I2C 通信,它可能会使用 `i2c_smbus_write_byte_data()`、`i2c_smbus_write_word_data()` 或 `i2c_smbus_write_i2c_block_data()` 等函数进行寄存器写操作;对于 SPI 通信,它可能会使用 `spi_write()` 函数进行寄存器写操作。 使用 `regmap_write()` 函数可以简化驱动程序中的寄存器写操作,避免手动选择底层函数的麻烦。例如,以下是一个使用 `regmap_write()` 函数向 I2C 设备 `client` 的寄存器 `0x10` 写入数据的示例: ```c #include <linux/regmap.h> struct i2c_client *client; struct regmap *regmap; // 初始化 I2C 设备并创建 regmap 实例 client = i2c_new_device(adapter, &board_info); regmap = devm_regmap_init_i2c(client, &regmap_config); // 向指定寄存器写入数据 regmap_write(regmap, 0x10, 0x1234); ``` 在这个示例中,`devm_regmap_init_i2c()` 函数用于创建 `regmap` 实例,`regmap_write()` 函数用于向寄存器写入数据。`regmap` 实例中包含了 I2C 设备的信息,因此它可以自动选择适当的函数进行寄存器写操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值