Rockchip RK3399 - regmap子系统
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux :6.3
----------------------------------------------------------------------------------------------------------------------------
linux驱动开发中,对于一些外设型器件驱动,如ADC、DAC、EEPROM、Sensor,这里器件通常是以uart、i2c、spi、mipi为控制接口,通过配置器件寄存器来设置芯片工作模式、运行参数、校准值等等,并通过获取寄存器值来获得有效数据。
普通的做法,我们是根据不同的控制总线接口来实现寄存器访问,这样的方式是需要根据总线类型来调整访问接口、数据结构,这样显得繁琐。比如,目前有个ADC器件,支持spi和i2c接口;在此之前采用的是spi接口;后面因cpu spi接口不够用,线需要改为i2c控制。这样,该ADC驱动程序得修改,从spi改为i2c驱动,虽然工作量不大,但是也得花费一定时间。那么大体工作量有:
- spi_write/spi_rea接口修改为 i2c_transfer;
- spi片选(cs)修改为i2c从地址寻址;
- 数据结构修改, struct spi_message修改为struct i2c_msg;
基于代码代码复用的原则之一,linux在3.1内核后引入了regmap模型,将寄存器访问的共同逻辑抽象出来,只需初始化时指定总线类型、寄存器位宽等关键参数,即可通过regmap模型接口来操作器件寄存器。当然,regmap同样适用于操作cpu自身的寄存器。
一、regmap框架
regmap是在linux内核为减少慢速I/O驱动上的重复逻辑,提供一种通用的接口来操作底层硬件寄存器的模型框架。此外,regmap在驱动和硬件寄存器之间增加了cache,减少底层低速I/O的操作次数,提高访问效率;当然实时性会有所降低。
1.1 regmap框架
regmap整体上分为三层,从下到上分别为物理总线、regmap核心、regmap api。
其中:
- physical bus:对接的是具体物理总线,目前regmap框架支持i2c、spi、mmio、spmi、ac97总线;
- regmap core:regmap核心实现;
- regmap api:抽象通用接口;
对于使用regmap框架来说,可以不用关心regmap核心的实现过程,只需根据物理总线类型,配置好相关参数信息,即可调用regmap api访问芯片寄存器。
1.2 regmap注册流程
使用regmap比较简单,使用前,只需根据外设属性配置总线类型、寄存器位宽、缓存类型、读写属性等参数;接着注册一个regmap实例;然后调用抽象访问接口访问寄存器。具体步骤如下:
- 配置regmap信息;
- 注册regmap实例;
- 访问寄存器;
- 释放regmap实例;
二、注册regmap
3.1 配置信息
配置信息,首先需了解配置信息数据结构,linux内核以struct regmap_config描述该数据结构,位于include/linux/regmap.h中声明,其中包含该设备的寄存器数量,寄存器位宽,缓存类型,读写属性等;
/** * struct regmap_config - Configuration for the register map of a device. * * @name: Optional name of the regmap. Useful when a device has multiple * register regions. * * @reg_bits: Number of bits in a register address, mandatory. * @reg_stride: The register address stride. Valid register addresses are a * multiple of this value. If set to 0, a value of 1 will be * used. * @reg_downshift: The number of bits to downshift the register before * performing any operations. * @reg_base: Value to be added to every register address before performing any * operation. * @pad_bits: Number of bits of padding between register and value. * @val_bits: Number of bits in a register value, mandatory. * * @writeable_reg: Optional callback returning true if the register * can be written to. If this field is NULL but wr_table * (see below) is not, the check is performed on such table * (a register is writeable if it belongs to one of the ranges * specified by wr_table). * @readable_reg: Optional callback returning true if the register * can be read from. If this field is NULL but rd_table * (see below) is not, the check is performed on such table * (a register is readable if it belongs to one of the ranges * specified by rd_table). * @volatile_reg: Optional callback returning true if the register * value can't be cached. If this field is NULL but * volatile_table (see below) is not, the check is performed on * such table (a register is volatile if it belongs to one of * the ranges specified by volatile_table). * @precious_reg: Optional callback returning true if the register * should not be read outside of a call from the driver * (e.g., a clear on read interrupt status register). If this * field is NULL but precious_table (see below) is not, the * check is performed on such table (a register is precious if * it belongs to one of the ranges specified by precious_table). * @writeable_noinc_reg: Optional callback returning true if the register * supports multiple write operations without incrementing * the register number. If this field is NULL but * wr_noinc_table (see below) is not, the check is * performed on such table (a register is no increment * writeable if it belongs to one of the ranges specified * by wr_noinc_table). * @readable_noinc_reg: Optional callback returning true if the register * supports multiple read operations without incrementing * the register number. If this field is NULL but * rd_noinc_table (see below) is not, the check is * performed on such table (a register is no increment * readable if it belongs to one of the ranges specified * by rd_noinc_table). * @disable_locking: This regmap is either protected by external means or * is guaranteed not to be accessed from multiple threads. * Don't use any locking mechanisms. * @lock: Optional lock callback (overrides regmap's default lock * function, based on spinlock or mutex). * @unlock: As above for unlocking. * @lock_arg: this field is passed as the only argument of lock/unlock * functions (ignored in case regular lock/unlock functions * are not overridden). * @reg_read: Optional callback that if filled will be used to perform * all the reads from the registers. Should only be provided for * devices whose read operation cannot be represented as a simple * read operation on a bus such as SPI, I2C, etc. Most of the * devices do not need this. * @reg_write: Same as above for writing. * @reg_update_bits: Optional callback that if filled will be used to perform * all the update_bits(rmw) operation. Should only be provided * if the function require special handling with lock and reg * handling and the operation cannot be represented as a simple * update_bits operation on a bus such as SPI, I2C, etc. * @read: Optional callback that if filled will be used to perform all the * bulk reads from the registers. Data is returned in the buffer used * to transmit data. * @write: Same as above for writing. * @max_raw_read: Max raw read size that can be used on the device. * @max_raw_write: Max raw write size that can be used on the device. * @fast_io: Register IO is fast. Use a spinlock instead of a mutex * to perform locking. This field is ignored if custom lock/unlock * functions are used (see fields lock/unlock of struct regmap_config). * This field is a duplicate of a similar file in * 'struct regmap_bus' and serves exact same purpose. * Use it only for "no-bus" cases. * @io_port: Support IO port accessors. Makes sense only when MMIO vs. IO port * access can be distinguished. * @max_register: Optional, specifies the maximum valid register address. * @wr_table: Optional, points to a struct regmap_access_table specifying * valid ranges for write access. * @rd_table: As above, for read access. * @volatile_table: As above, for volatile registers. * @precious_table: As above, for precious registers. * @wr_noinc_table: As above, for no increment writeable registers. * @rd_noinc_table: As above, for no increment readable registers. * @reg_defaults: Power on reset values for registers (for use with * register cache support). * @num_reg_defaults: Number of elements in reg_defaults. * * @read_flag_mask: Mask to be set in the top bytes of the register when doing * a read. * @write_flag_mask: Mask to be set in the top bytes of the register when doing * a write. If both read_flag_mask and write_flag_mask are * empty and zero_flag_mask is not set the regmap_bus default * masks are used. * @zero_flag_mask: If set, read_flag_mask and write_flag_mask are used even * if they are both empty. * @use_relaxed_mmio: If set, MMIO R/W operations will not use memory barriers. * This can avoid load on devices which don't require strict * orderings, but drivers should carefully add any explicit * memory barriers when they may require them. * @use_single_read: If set, converts the bulk read operation into a series of * single read operations. This is useful for a device that * does not support bulk read. * @use_single_write: If set, converts the bulk write operation into a series of * single write operations. This is useful for a device that * does not support bulk write. * @can_multi_write: If set, the device supports the multi write mode of bulk * write operations, if clear multi write requests will be * split into individual write operations * * @cache_type: The actual cache type. * @reg_defaults_raw: Power on reset values for registers (for use with * register cache support). * @num_reg_defaults_raw: Number of elements in reg_defaults_raw. * @reg_format_endian: Endianness for formatted register addresses. If this is * DEFAULT, the @reg_format_endian_default value from the * regmap bus is used. * @val_format_endian: Endianness for formatted register values. If this is * DEFAULT, the @reg_format_endian_default value from the * regmap bus is used. * * @ranges: Array of configuration entries for virtual address ranges. * @num_ranges: Number of range configuration entries. * @use_hwlock: Indicate if a hardware spinlock should be used. * @use_raw_spinlock: Indicate if a raw spinlock should be used. * @hwlock_id: Specify the hardware spinlock id. * @hwlock_mode: The hardware spinlock mode, should be HWLOCK_IRQSTATE, * HWLOCK_IRQ or 0. * @can_sleep: Optional, specifies whether regmap operations can sleep. */ struct regmap_config { const char *name; int reg_bits; int reg_stride; int reg_downshift; unsigned int reg_base; 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); int (*reg_update_bits)(void *context, unsigned int reg, unsigned int mask, unsigned int val); /* Bulk read/write */ int (*read)(void *context, const void *reg_buf, size_t reg_size, void *val_buf, size_t val_size); int (*write)(void *context, const void *data, size_t count); size_t max_raw_read; size_t max_raw_write; bool fast_io; bool io_port; 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 use_relaxed_mmio; 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; bool use_raw_spinlock; unsigned int hwlock_id; unsigned int hwlock_mode; bool can_sleep; };
以rt5651_regmap为例,定义在sound/soc/codecs/rt5651.c:
#define RT5651_PR_RANGE_BASE (0xff + 1) #define RT5651_PR_SPACING 0x100 #define RT5651_PR_BASE (RT5651_PR_RANGE_BASE + (0 * RT5651_PR_SPACING)) static const struct regmap_range_cfg rt5651_ranges[] = { { .name = "PR", .range_min = RT5651_PR_BASE, // 虚拟地址范围内最低寄存器地址的地址 .range_max = RT5651_PR_BASE + 0xb4, // 虚拟地址范围内最高寄存器地址的地址 .selector_reg = RT5651_PRIV_INDEX, // 页面选择器 .selector_mask = 0xff, // 页面选择器的位掩码 .selector_shift = 0x0, // 页面选择器的位移 .window_start = RT5651_PRIV_DATA, // 每页数据窗口的地址 .window_len = 0x1, // 数据长度 }, }; static const struct regmap_config rt5651_regmap = { .reg_bits = 8, //寄存器地址位宽,常见的有8位、16位、必须设置 .val_bits = 16, //寄存器值位宽,常见的有8位、16位、必须设置 .max_register = RT5651_DEVICE_ID + 1 + (ARRAY_SIZE(rt5651_ranges) * // 最大寄存器地址,防止访问越界 RT5651_PR_SPACING), .volatile_reg = rt5651_volatile_register, .readable_reg = rt5651_readable_register, .cache_type = REGCACHE_RBTREE, // cache数据类型,支持三种:flat(普通数据类型)、rbtree(红黑树类型)、Iz(压缩类型) .reg_defaults = rt5651_reg, .num_reg_defaults = ARRAY_SIZE(rt5651_reg), .ranges = rt5651_ranges, // 间接访问寄存器的配置 .num_ranges = ARRAY_SIZE(rt5651_ranges), .use_single_read = true, .use_single_write = true, };
2.2 注册regmap
regmap为每一种物理接口提供了一个注册函数;
devm_regmap_init_i2c(struct i2c_client *i2c, struct regmap_config *config); devm_regmap_init_spi(struct spi_device *spi, strcut regmap_config *config); devm_regmap_init_mmio(struct device *dev, struct regmap_config *config); devm_regmap_init_spmi_base(struct spmi_device *dev, strcut regmap_config *config); devm_regmap_init_spmi_ext(struct spmi_device *dev, strcut regmap_config *config); devm_regmap_init_ac97(struct snd_ac97 *ac97, strcut regmap_config *config);
注册函数声明位于include/linux/regmap.h中,原型中linux内核通过宏定义实现,展开后即是上面函数声明。
这里以devm_regmap_init_i2c为例介绍:
#define __regmap_lockdep_wrapper(fn, name, ...) fn(__VA_ARGS__, NULL, NULL) // __VA_ARGS__ 表示可变参数,即.... /** * devm_regmap_init_i2c() - Initialise managed register map * * @i2c: Device that will be interacted with * @config: Configuration for register map * * The return value will be an ERR_PTR() on error or a valid pointer * to a struct regmap. The regmap will be automatically freed by the * device management code. */ #define devm_regmap_init_i2c(i2c, config) \ __regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config, \ i2c, config)
__devm_regmap_init_i2c函数定义在drivers/base/regmap/regmap-i2c.c:
struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c, // 传入i2c const struct regmap_config *config, // 传入config struct lock_class_key *lock_key, //NULL const char *lock_name) // NULL { const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config); if (IS_ERR(bus)) return ERR_CAST(bus); return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config, lock_key, lock_name); }
regmap根据传进来的regmap_config初始化对应的缓存和总线操作接口,驱动就可以正常调用regmap_write和regmap_read函数。
2.3 抽象访问接口
配置和注册regmap实例后,我们就可以使用抽象接口来访问寄存器,摈弃之前那套繁琐的数据结构和函数api。
接口比较通俗,根据函数名称和入口参数即可知道函数功能。接口分为两大类,设置类(与初始化配置信息不同)和访问类,访问类根据访问过程又分为两种:
- 经过regmap cache,提高访问效率,对于写操作,待cache存在一定数据量或者超出时间后写入物理寄存器;但降低实时性;
- 不经过regmap cache,对于写操作,立即写入物理寄存器,实时性好;对于读操作,则经过cache,减少拷贝时间;
常用访问类api:
int regmap_write(struct regmap *map, int reg, int val); /* 写单个寄存器 */ int regmap_raw_write(struct regmap *map, int reg, void *val, size_t val_len); /* 单个寄存器写指定长度数据 */ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,size_t val_count); /* 写多个寄存器 */ int regmap_multi_reg_write_bypassed(struct regmap *map, const struct reg_sequence *regs,int num_regs);/* 直接写入寄存器,不经过regmap cache */ int regmap_raw_write_async(struct regmap *map, unsigned int reg,const void *val, size_t val_len);/* 写多个寄存器,并立即刷新cache写入 */ int regmap_read(struct regmap *map, int reg, int *val); /* 读单个寄存器 */ int regmap_raw_read(struct regmap *map, int reg, void *val, size_t val_len); /* 单个寄存器读指定长度数据 */ int regmap_bulk_read(struct regmap *map, int reg, void *val, size_t val_count); /* 读多个寄存器 */ int regmap_update_bits(struct regmap *map, int reg, int mask, int val); /* 更新寄存器值指定bit */ int regmap_write_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);/* 写入寄存器值指定bit */
更多的操作api参考include/linux/regmap.h中的声明。
2.4 释放接口
在驱动注销函数里应调用regmap_exit释放已注册的regmap实例;
void regmap_exit(struct regmap *map);
三、示例
以i2c为例,以伪代码访问寄存器比较传统方式和通过regmap访问方式。
3.1 传统方式
我们以一个 I2C 设备为例。读写一个寄存器,肯定需要用到i2c_transfer这样的I2C函数。
为了方便,一般的驱动中,会在这之上再写一个wrapper,然后通过调用这个wrapper来读写寄存器。比如如下这个读取寄存器的函数:
static int xxx_i2c_read_reg(struct i2c_client *client, uint8_t reg, uint8_t *pdata, int size) { int ret = 0; struct i2c_msg msg[2]; if(size == 0) { return 0; }
msg[0].addr = client->addr; // 第一个数据包 msg[0].buf = ® msg[0].len = 1; msg[0].flags = 0; msg[1].addr = client->addr; // 第二个数据包,flags未设置I2C_M_NOSTART,会重新发送起始信号(即进行新的数据传输流程) msg[1].buf = pdata; msg[1].len = size; msg[1].flags = I2C_M_RD; // 读 if(i2c_transfer(client->adapter, msg, 2) != 2) { ret =-1; } return ret; }
3.2 regmap方式
如果regmap的方式来实现,对于上面这种读寄存器操作,其实现如下。
3.2.1 配置信息
static const struct regmap_config regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = 255, .cache_type = REGCACHE_RBTREE, .volatile_reg = false, };
3.2.2 注册regmap
regmap = regmap_init_i2c(i2c_client, ®map_config);
3.2.3 访问操作
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] 设备驱动中的regmap
[2] Regmap在i2c中的使用