Linux字符设备驱动 -- regmap子系统

环境

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通信模式,需要:

  1. 创建一个struct regmap结构体指针
  2. 创建一个struct regmap_config结构体并初始化其中所需要的域。
  3. 调用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);
}

该函数就做了两件事:

  1. 为regmap选择bus,我把他当做是选择通信协议(i2c也有好几种不同的协议)。
  2. 根据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 &regmap_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 &regmap_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 &regmap_smbus_word;
		case REGMAP_ENDIAN_BIG:
			return &regmap_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 &regmap_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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值