程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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, &regmap_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中的使用

[3] Linux驱动开发--regmap框架模型详解

posted @ 2023-07-20 22:11  大奥特曼打小怪兽  阅读(433)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步