Linux regmap机制
在Linux下开发WDT驱动时候参考某源代码时候发现devm_regmap_init_mmio_clk()函数的使用,故深入探究一下。
什么是 Regmap
Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 器件的本质都是一样的,通过 I2C/SPI 接口读写器件内部寄存器。芯片内部寄存器也是同样的道理,比如STM32的 PWM、TIM 等外设初始化,最终都是要落到寄存器的设置上。
例如,Linux 下使用 i2c_transfer 来读写 I2C 器件中的寄存器,SPI 接口的话使用 spi_write/spi_read等。I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余代码。
再者,代码的复用性也会降低,如果一个器件同时支持SPI和IIC接口的话,在产品开发初期将器件设计为 SPI 接口,但是后面发现 SPI 接口不够用,或者 SOC 的引脚不够用,我们需要将器件改为 I2C 接口。这个时候器件的驱动就要大改,我们需要将 SPI 接口函数换为 I2C 的,工作量比较大。
基于代码复用的原则,Linux 内核引入了 regmap 模型,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 框架分为三层:
- 底层物理总线: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 */
gfp_t alloc_flags;
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);
bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
bool (*readable_noinc_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;
const struct regmap_access_table *wr_noinc_table;
const struct regmap_access_table *rd_noinc_table;
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
int (*reg_update_bits)(void *context, unsigned int reg,
unsigned int mask, unsigned int val);
......
struct rb_root range_tree;
void *selector_work_buf; /* Scratch buffer used for selector */
struct hwspinlock *hwlock;
};
要使用 regmap,肯定要先给驱动分配一个具体的 regmap 结构体实例。
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);
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;
};
Linux 内核里面已经对 regmap_config 各个成员变量进行了详细的讲解(函数上面有注释),这里我们只看一些比较重要的成员:
- reg_bits:寄存器地址位数,必填字段。
- reg_stride:寄存器地址步长,相邻寄存器的距离。
- val_bits:寄存器值位数,必填字段。
- max_register:有效的最大寄存器地址,可选。
关于 regmap_config 结构体成员变量就介绍这些,其他没有介绍的自行查阅 Linux 内核中的注释描述。