----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板eMMC :16GBLPDDR3 :4GB显示屏 :15.6英寸HDMI接口显示屏u-boot :2023.04linux :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中的使用