linux Regmap API 笔记


Regmap API介绍

引入Regmap 子系统来解决大量的重复、冗余对寄存器的操作的代码,比如各种i2c,spi驱动

regmap 将寄存器访问的共同逻辑抽象出来,驱动开发不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用regmap API函数。这样的好处就是统一使用 regmap,降低了代码冗余, 提高了驱动的可以移植性。

regmap重点在于

  1. 通过regmap 模型提供的统一接口函数来访问寄存器, SOC 内部的寄存器也可以使用 regmap 接口函数来访问
  2. regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。
  3. regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低

以下情况会使用regmap

  1. 硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器
  2. 提高代码复用性和驱动一致性,简化驱动开发过程
  3. 减少底层 I/O 操作次数,提高访问效率

一、Regmap 驱动框架

regmap 框架结构

regmap 框架分为三层:

  1. 底层物理总线:regmap 就是对不同的物理总线进行封装,支持的物理总线有 i2c、 i3c、 spi、 mmio、 sccb、 sdw、 slimbus、 irq、 spmi 和 w1。
  2. regmap 核心层,用于实现 regmap,不用关心具体实现。
  3. regmap API 抽象层, regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。

regmap 结构体

Linux内核将regmap框架抽象为regmap 结构体 ,这个结构体定义在文件drivers/base/regmap/internal.h 中

struct regmap {
	union {
		struct mutex mutex;
		struct {
			spinlock_t spinlock;
			unsigned long spinlock_flags;
		};
	};
	regmap_lock lock;
	regmap_unlock unlock;
	void *lock_arg; /* This is passed to lock/unlock functions */
	struct device *dev; /* Device we do I/O on */
	void *work_buf; /* Scratch buffer used to format I/O */
	struct regmap_format format; /* Buffer format */
	const struct regmap_bus *bus;
	void *bus_context;
	const char *name;
	
	bool async;
	spinlock_t async_lock;
	wait_queue_head_t async_waitq;
	struct list_head async_list;
	struct list_head async_free;
	int async_ret;
	.....
	unsigned int max_register;
	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);
	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;
	int (*reg_read)(void *context, unsigned int reg,
	unsigned int *val);
	int (*reg_write)(void *context, unsigned int reg,
	unsigned int val);
	.....
	struct rb_root range_tree;
	void *selector_work_buf; /* Scratch buffer used for selector */
};

使用 regmap,要先给驱动分配一个具体的 regmap 结构体实例

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);
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;

int (*reg_read)(void *context, unsigned int reg,
								unsigned int *val);
int (*reg_write)(void *context, unsigned int reg,
								unsigned int val);

上面函数以及 table,这些需要驱动编写人员根据实际情况选择性的初始化, regmap 的初始化通过结构体 regmap_config 来完成。

regmap_config 结构体

regmap_config 结构体就是用来初始化 regmap 的,这个结构体也定义在include/linux/regmap.h 文件中

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);
	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;
	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;
	enum regmap_endian val_format_endian;
	
	const struct regmap_range_cfg *ranges;
	unsigned int num_ranges;
};

const char *name 名字
reg_bits:寄存器地址位数,必填字段
reg_stride:寄存器地址步长
pad_bits:寄存器和值之间的填充位数。
val_bits:寄存器值位数,必填字段
writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用,并返回 true
readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并返回 true。
volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调用,并返回 true。
precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部的值
reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行
reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行
fast_io:快速 I/O,使用 spinlock 替代 mutex 来提升锁性能
max_register:有效的最大寄存器地址,可选
wr_table:可写的地址范围,为 regmap_access_table 结构体类型。后面的 rd_table、volatile_table、 precious_table、wr_noinc_table 和 rd_noinc_table 同理
reg_defaults:寄存器模式值,为 reg_default 结构体类型,此结构体有两个成员变量: reg 和 def, reg 是寄存器地址, def 是默认值。
num_reg_defaults:默认寄存器表中的元素个数。
read_flag_mask:读标志掩码
write_flag_mask:写标志掩码

Regmap 操作函数

Regmap 申请与初始化

regmap 支持多种物理总线,根据所使用的接口来选择 regmap 初始化函数。
SPI 接口初始化函数为 regmap_init_spi

struct regmap * regmap_init_spi(struct spi_device *spi,const struct regmap_config *config)
spi: 需要使用 regmap 的 spi_device。
config: regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将其地址赋值给此参数。

I2C 接口初始化函数为 regmap_init_i2c

struct regmap * regmap_init_i2c(struct i2c_client *i2c,const struct regmap_config *config)
i2c: 需要使用 regmap 的 i2c_client。
config: regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将其地址赋值给此参数。

释放 regmap 使用 regmap_exit 这个函数来释放 regmap,函数原型如下

void regmap_exit(struct regmap *map)
map: 需要释放的 regmap

regmap 设备访问 API 函数

regmap_read 和 regmap_write 读/写寄存器

int regmap_read(struct regmap *map,
				unsigned int reg,
				unsigned int *val)
map: 要操作的 regmap
reg: 要读的寄存器
val:读到的寄存器值
返回值: 0,读取成功;其他值,读取失败
int regmap_write(struct regmap *map,
				unsigned int reg,
				unsigned int val)
map: 要操作的 regmap
reg: 要写的寄存器
val:要写的寄存器值
返回值: 0,写成功;其他值,写失败

regmap_update_bits 函数,修改寄存器指定的 bit

int regmap_update_bits (struct regmap *map,
						unsigned int reg,
						unsigned int mask,
						unsigned int val,
map: 要操作的 regmap。
reg: 要操作的寄存器。
mask: 掩码,需要更新的位必须在掩码中设置为 1。
val:需要更新的位值。

例如
将寄存器bit1 和 bit2 置 1,那么 mask 应该设置为 0X00000011,此时 val 的 bit1和 bit2 应该设置为 1,也就是0Xxxxxxx11
要清除寄存器的 bit4 和 bit7,那么 mask 应该设置为 0X10010000, val 的 bit4 和 bit7 设置为 0,也就是 0X0xx0xxxx

regmap_bulk_read 读取多个寄存器的值

int regmap_bulk_read(struct regmap *map,
					unsigned int reg,
					void *val,
					size_t val_count)
map: 要操作的 regmap
reg: 要读取的第一个寄存器
val: 读取到的数据缓冲区
val_count:要读取的寄存器数量

regmap_bulk_write 多个寄存器写函数

int regmap_bulk_write(struct regmap *map,
					unsigned int reg,
					const void *val,
					size_t val_count)
map: 要操作的 regmap。
reg: 要写的第一个寄存器。
val: 要写的寄存器数据缓冲区。
val_count:要写的寄存器数量

regmap_config 掩码设置

结构体 regmap_config 里面有三个关于掩码的成员变量: read_flag_maskwrite_flag_mask,这二个掩码非常重要

比如 当使用 spi 接口的时候,读取 icm20608 寄存器的时候地址最高位必须置 1,写内部寄存器的是时候地址最高位要设置为 0

使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要我们来操作,全部由 regmap 框架来完成的。
同理 write_flag_mask 用法也一样的,只是 write_flag_mask 用于写寄存器操作。

程序编写

修改设备结构体,添加 regmap 和 regmap_config

struct icm20608_dev {
	struct spi_device* spi; /* spi 设备 */
	dev_t devid; /* 设备号 */
	struct cdev cdev; /* cdev */
	struct class* class; /* 类 */
	struct device* device; /* 设备 */
	struct device_node* nd; /* 设备节点 */
	.......
	struct regmap* regmap;
	struct regmap_config regmap_config;
};

regmap需要使用 regmap_init_spi 函数来申请和初始化
regmap_config 结构体成员变量,从来配置 regmap

初始化 regmap

一般在 probe 函数中初始化 regmap

static int icm20608_probe(struct spi_device* spi)
{
	int ret;
	struct icm20608_dev* icm20608dev;
	/* 分配 icm20608dev 对象的空间 */
	icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev),
											GFP_KERNEL);
	if (!icm20608dev)
		return -ENOMEM;
	/* 初始化 regmap_config 设置 */
	icm20608dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
	icm20608dev->regmap_config.val_bits = 8; /* 值长度 8bit */
	icm20608dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */
	/* 初始化 SPI 接口的 regmap */
	icm20608dev->regmap = regmap_init_spi(spi,
											&icm20608dev->regmap_config);
	if (IS_ERR(icm20608dev->regmap)) {
		return PTR_ERR(icm20608dev->regmap);
	}
	/* 注册字符设备驱动 */
/* 1、创建设备号 */
	ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT,
							ICM20608_NAME);
	if (ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",ICM20608_NAME, ret);
		goto del_regmap;
	}
	.......
	return 0;
destroy_class:
	device_destroy(icm20608dev->class, icm20608dev->devid);
del_cdev:
	cdev_del(&icm20608dev->cdev);
del_unregister:
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
del_regmap:
	regmap_exit(icm20608dev->regmap);
	return -EIO;
}

regmap_config 的初始化, icm20608 的寄存器地址地址长度为 8bit,寄存器值也是 8bit,因此 reg_bits 和 val_bits 都设置为 8。由于 icm20608 通过 SPI 接口读取的时候地址寄存器最高位要设置为 1,因此 read_flag_mask 设置为 0X80。

删除 regmap 就使用 regmap_exit 函数

static int icm20608_remove(struct spi_device *spi)
{
	struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
	........
	/* 4、注销类 */
	class_destroy(icm20608dev->class);
	/* 5、删除 regmap */
	regmap_exit(icm20608dev->regmap);
	return 0;
}

读写函数

static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 ret;
	unsigned int data;
	ret = regmap_read(dev->regmap, reg, &data);
	return (u8)data;
}
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
	regmap_write(dev->regmap, reg, value);
}

使用regmap_readregmap_write来代替传统spi驱动spi_sync函数

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值