文章目录
Regmap API介绍
引入Regmap 子系统来解决大量的重复、冗余对寄存器的操作的代码,比如各种i2c,spi驱动
regmap 将寄存器访问的共同逻辑抽象出来,驱动开发不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用regmap API函数。这样的好处就是统一使用 regmap,降低了代码冗余, 提高了驱动的可以移植性。
regmap重点在于
- 通过regmap 模型提供的统一接口函数来访问寄存器, SOC 内部的寄存器也可以使用 regmap 接口函数来访问
- regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。
- regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低
以下情况会使用regmap
- 硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器
- 提高代码复用性和驱动一致性,简化驱动开发过程
- 减少底层 I/O 操作次数,提高访问效率
一、Regmap 驱动框架
regmap 框架结构
regmap 框架分为三层:
- 底层物理总线:regmap 就是对不同的物理总线进行封装,支持的物理总线有 i2c、 i3c、 spi、 mmio、 sccb、 sdw、 slimbus、 irq、 spmi 和 w1。
- regmap 核心层,用于实现 regmap,不用关心具体实现。
- 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_mask
和 write_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_read
和regmap_write
来代替传统spi驱动spi_sync
函数