译文:Regmap API -A Register Map Abstraction


本文从书籍Linux Device Drivers Development: Develop customized drivers for embedded Linux第九章翻译,翻译水平有限,谅解!


在开发Regmap API之前,用于处理SPI核心、I2C核心或两者的设备驱动程序都有冗余代码。它们都有相同的原理:访问寄存器进行读/写操作。下图显示在将regmap引入内核之前SPI或i2c API是如何独立的:
REGMAP之前的SPI和I2C子系统

为了统一内核开发人员访问SPI/I2C设备的方式,在内核的3.1版本中引入了regmap API。引入该机制后,无论是SPI还是I2C仅仅只有一个问题,如何初始化、配置regmap和处理读/写/修改操作。下图显示引入了regmap后SPI或者I2C是如何交互的:
REGMAP之后的SPI和I2C子系统

本章将通过以下方式介绍regmap框架:

  • 介绍regmap框架使用的主要数据结构
  • 介绍regmap配置
  • 使用regmap API访问设备
  • 介绍regmap缓存系统
  • 提供一个总结所有概念的完整驱动程序

Regmap API编程

regmap API非常简单,这个API的两个最重要的结构是struct regmap_config,它表示regmap的配置,struct regmap,它是regmap实例本身。所有regmap数据结构都是在include/linux/regmap.h中定义的。

regmap_config结构体

struct regmap_config保存了驱动程序的regmap配置,这里设置的内容会影响读/写操作。它是regmap api中最重要的结构。源代码如下所示:

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;
}

reg_bits:寄存器地址中的位数,强制的必须配置
val_bits:寄存器值的位数,强制的必须配置
writeable_reg:这是一个可选的回调函数。如果提供了,则在需要写入寄存器时由regmap子系统调用。在写入寄存器之前,将自动调用此函数以检查是否将值写入寄存器。如下所示:

static bool foo_writeable_register(struct device *dev,
unsigned int reg)
{
    switch (reg) {
        case 0x30 ... 0x38:
        case 0x40 ... 0x45:
        case 0x50 ... 0x57:
        case 0x60 ... 0x6e:
        case 0x70 ... 0x75:
        case 0x80 ... 0x85:
        case 0x90 ... 0x95:
        case 0xa0 ... 0xa5:
        case 0xb0 ... 0xb2:
            return true;
        default:
            return false;
    }
}

readable_reg:与writeable_reg相同。在读取寄存器之前,将自动调用此函数检查是否读取寄存器值
volatile_reg:通过regmap缓存读取或写入寄存器时调用的回调函数(如果寄存器是易失性的,则该函数应该返回true),并对寄存器执行直接读/写操作。如果返回false,则表示寄存器可缓存。在这种情况下,缓存将用于读取操作,而在写操作的情况下,缓存将被写入:

static bool foo_volatile_register(struct device *dev,unsigned int reg)
{
    switch (reg) {
        case 0x24 ... 0x29:
        case 0xb6 ... 0xb8:
            return true;
        default:
            return false;
    }
}

max_register: 这是可选的,它指定最大有效寄存器地址。
reg_read: 您的设备可能不支持简单的i2c/spi读取操作(意思就是需要填充该函数实现SPI/I2C自定义读功能)。然后,您将别无选择,只能编写您自己的自定义读取功能。reg_read应该指向该功能。也就是说,大多数设备都不需要该功能。
reg_write:和reg_read相同,但适用于写操作。

我强烈建议您查看include/linux/regmap.h,以了解每个元素的更多细节。

下面是一个regmap_config的初始化:

static const struct regmap_config regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = LM3533_REG_MAX,
    .readable_reg = lm3533_readable_register,
    .volatile_reg = lm3533_volatile_register,
    .precious_reg = lm3533_precious_register,
};

Regmap初始化

如前所述,regmap API支持SPI和i2c协议。根据驱动程序中需要支持的协议,必须在probe()函数中调用regmap_init_i2c()或regmap_init_spi()。要编写通用驱动程序,remap是最好的选择。
regmap API是通用的,只需要在初始化时更改不同的总线类型,而其他函数是相同的。
注意:在probe()中初始化regmap是一种很好的做法,在初始化regmap之前,必须首先填充regmap_config字段。

无论是分配i2c还是SPI寄存器映射,都会使用regmap_exit函数释放它:

void regmap_exit(struct regmap *map)

此函数简单的释放之前分配的regmap map

SPI初始化

regmap SPI初始化包括设置regmap,这样设备的读写都会在内部转换为spi命令。例如函数regmap_init_spi():

struct regmap * regmap_init_spi(struct spi_device *spi, const struct regmap_config);

提供一个struct spi_device类型的spi设备作为第一个参数,以及一个struct regmap_config,它表示regmap的配置。此函数返回一个指向已分配的struct regmap的指针,失败时返回一个ERR_PTR()值。

示例如下:

static int foo_spi_probe(struct spi_device *client)
{
    int err;
    struct regmap *my_regmap;
    struct regmap_config bmp085_regmap_config;
    /* fill bmp085_regmap_config somewhere */

    [...]

    client->bits_per_word = 8;
    my_regmap = regmap_init_spi(client,&bmp085_regmap_config);
    if (IS_ERR(my_regmap)) {
        err = PTR_ERR(my_regmap);
        dev_err(&client->dev, "Failed to init regmap: %d\n", err);
        return err;
    }

    [...]

}

I2C初始化

i2c regmap调用regmap_init_i2c()初始化regmap配置,设备读写都会在内部转化为I2C命令。例如:

struct regmap * regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config);

struct i2c_client类型的i2c设备作为第一个参数,以及一个指向struct regmap_config的指针,该指针表示regmap的配置。该函数在成功时返回一个指向已分配的struct regmap指针,失败时返回ERR_PTR()值。

示例如下:

static int bar_i2c_probe(struct i2c_client *i2c,const struct i2c_device_id *id)
{
    struct my_struct * bar_struct;
    struct regmap_config regmap_cfg;
    /* fill regmap_cfgsome where */

    [...]

    bar_struct = kzalloc(&i2c->dev,
    sizeof(*my_struct), GFP_KERNEL);
    if (!bar_struct)
        return -ENOMEM;
    i2c_set_clientdata(i2c, bar_struct);
    bar_struct->regmap = regmap_init_i2c(i2c, &regmap_config);
    if (IS_ERR(bar_struct->regmap))
        return PTR_ERR(bar_struct->regmap);
    bar_struct->dev = &i2c->dev;
    bar_struct->irq = i2c->irq;

    [...]

}

设备访问

API处理数据的解析,格式化和传输。大多数情况下,设备读写使用regmap_read, regmap_write和regmap_update_bits。这是设备主从间数据交互时有三个最重要的功能,它们各自的原型是:

int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);

regmap_write:写数据到设备。如果在regmap_config中设置了max_register,该函数将会检查需要写入的寄存器地址。如果寄存器地址小于或等于max_register,写操作才会被执行;否则,regmap core将返回无效的I/O错误(-EIO)。紧接着,执行writeable_reg回调,writeable_reg回调必须在进行下一步之前返回true。如果返回false,写操作停止并返回-EIO。如果设置了wr_table而不是writeable_reg,有下面几种情况:

  • 如果寄存器地址在no_range中,返回-EIO
  • 如果寄存器地址在yes_range,执行下一步
  • 如果寄存器地址不在no_range或yes_range中,写操作结束并返回-EIO
  • 如果cache_type不是REGCACHE_NONE,则启用缓存。在这种情况下,首先更新缓存条目,然后对硬件执行写操作;否则,执行NO缓存操作
  • 如果提供了reg_write回调,可用于执行写操作;否则,将执行泛型regmap写函数

regmap_read:从设备读取数据。使用方式和regmap_write相同。因此,如果提供了reg_read,则调用reg_read执行读操作;否则,调用泛型regmap读函数

regmap_update_bits

regmap_update_bits有三个功能,其原型如下:

int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val)

该函数在注册的regmap上执行读/写、修改操作,是由_regmap_update_bits封装,如下:

static int _regmap_update_bits(struct regmap *map,unsigned int reg, unsigned int mask,
    unsigned int val, bool *change)
{
    int ret;
    unsigned int tmp, orig;
    ret = _regmap_read(map, reg, &orig);
    if (ret != 0)
        return ret;
    tmp = orig& ~mask;
    tmp |= val & mask;
    if (tmp != orig) {
        ret = _regmap_write(map, reg, tmp);
        *change = true;
    } else {
        *change = false;
    }
    return ret;
}

需要更新的bit掩码mask必须设置为1,相应的bit才会被赋值为val
例如,要将第一位和第三位设置为1,掩码mask应该是0b00000101,值应该是0bxxxxx1x1。要清除第七位,掩码必须是0b01000000,值应该是0bx0xxxxxx,以此类推。

regmap_multi_reg_write

函数功能是写入设备的多个寄存器,其原型如下:

int regmap_multi_reg_write(struct regmap *map, const struct reg_sequence *regs, int num_regs)

要了解如何使用该函数,首先需要了解结构体struct reg_sequence:

/**
* Register/value pairs for sequences of writes with an optional delay in
* microseconds to be applied after each write.
*
* @reg: Register address.
* @def: Register value.
* @delay_us: Delay to be applied after the register write in microseconds
*/
struct reg_sequence {
    unsigned int reg;
    unsigned int def;
    unsigned int delay_us;
};

它的用法如下:

static const struct reg_sequence foo_default_regs[] = {
    { FOO_REG1, 0xB8 },
    { BAR_REG1, 0x00 },
    { FOO_BAR_REG1, 0x10 },
    { REG_INIT, 0x00 },
    { REG_POWER, 0x00 },
    { REG_BLABLA, 0x00 },
};
staticint probe ( ...)
{
    [...]

    ret = regmap_multi_reg_write(my_regmap, foo_default_regs,ARRAY_SIZE(foo_default_regs));

    [...]
}

其他设备访问功能

regmap_bulk_read()和regmap_bulk_write()用于从设备中读取/写入多个寄存器,通常与大量数据块一起使用:

int regmap_bulk_read(struct regmap *map, unsigned int reg, void*val, size_tval_count);
int regmap_bulk_write(struct regmap *map, unsigned int reg,const void *val, size_t val_count);

regmap和cache

regmap支持缓存,是否使用缓存取决于regmap_config中cache_type字段的值。查看include/linux/regmap.h,cache_type支持的类型有:

/* Anenum of all the supported cache types */
enum regcache_type {
    REGCACHE_NONE,
    REGCACHE_RBTREE,
    REGCACHE_COMPRESSED,
    REGCACHE_FLAT,
};

默认情况下,cache_type被设置为regcache_NONE,这意味着缓存被禁用。其他值定义了如何存储缓存值。

您的设备可能在某些寄存器中具有预定义的上电复位值,这些值可以存储在数组中,这样任何读操作都会返回数组中包含的值。但是,写操作都会影响设备的真实寄存器,并更新数组中的内容。这是一种缓存,我们可以用它来加速对设备的访问。该数组是reg_defaults,它的结构在源码中如下:

/**
* Default value for a register. We use an array of structs rather
* than a simple array as many modern devices have very sparse
* register maps.
*
* @reg: Register address.
* @def: Register default value.
*/
struct reg_default {
    unsigned int reg;
    unsigned int def;
};

注意:如果cache_type设置为none,reg_defaults将被忽略。如果没有设置default reg,但仍然启用缓存,则将创建相应的缓存结构。

使用起来非常简单,只需要声明它并将其作为参数传递给regmap_config结构。让我们看看位于drivers/regulator/ltc3589.c的LTC3589 regulator驱动:

static const struct reg_default ltc3589_reg_defaults[] = {
    { LTC3589_SCR1, 0x00 },
    { LTC3589_OVEN, 0x00 },
    { LTC3589_SCR2, 0x00 },
    { LTC3589_VCCR, 0x00 },
    { LTC3589_B1DTV1, 0x19 },
    { LTC3589_B1DTV2, 0x19 },
    { LTC3589_VRRCR, 0xff },
    { LTC3589_B2DTV1, 0x19 },
    { LTC3589_B2DTV2, 0x19 },
    { LTC3589_B3DTV1, 0x19 },
    { LTC3589_B3DTV2, 0x19 },
    { LTC3589_L2DTV1, 0x19 },
    { LTC3589_L2DTV2, 0x19 },
};
static const struct regmap_config ltc3589_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .writeable_reg = ltc3589_writeable_reg,
    .readable_reg = ltc3589_readable_reg,
    .volatile_reg = ltc3589_volatile_reg,
    .max_register = LTC3589_L2DTV2,
    .reg_defaults = ltc3589_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(ltc3589_reg_defaults),
    .use_single_rw = true,
    .cache_type = REGCACHE_RBTREE,
};

reg_default中的任何一个寄存器的读操作都会立刻返回其在数组中的值。但是,对reg_default中的寄存器执行写操作,都会更新reg_default中的相对应的寄存器的值。例如,读取ltc3589_vrrcr寄存器将立刻返回0 xff;在该寄存器中写入任何值,它将更新数组中的条目,以便任何新的读操作都将直接从缓存返回最后的写入值。

总结

构建regmap子系统步骤:

  • 根据设备特性创建结构体regmap_config,如果需要,设置一个寄存器范围,默认值(如果有的话),缓存类型(如果需要)等等。如果需要自定义读/写函数,将他们传递给reg_read/reg_write字段
  • 在probe()函数中,根据总线类型(I2C或SPI),调用regmap_init_i2c()或者regmap_init_spi()分配regmap
  • 每当需要从寄存器读取/写入值时,调用remap_read/ remap_write函数
  • 结束时,调用regmap_exit()来释放在probe()中分配的regmap

Regmap示例

为了实现我们的目标,让我们首先描述一个假冒的SPI设备,我们可以为它编写一个驱动程序:

  • 8bit寄存器地址
  • 8bit寄存器值
  • 最大寄存器地址0x80
  • 写入掩码0x80
  • 有效地址范围
    0x20 to 0x4F
    0x60 to 0x7F
  • 不需要自定义读写功能

骨架示例程序:

/* mandatory for regmap */
#include <linux/regmap.h>
/* Depending on your need you should include other files */
static struct private_struct
{
    /* Feel free to add whatever you want here */
    struct regmap *map;
    int foo;
};
static const struct regmap_range wr_rd_range[] =
{
    {
        .range_min = 0x20,
        .range_max = 0x4F,
    },{
        .range_min = 0x60,
        .range_max = 0x7F
    },
};
struct regmap_access_table drv_wr_table =
{
    .yes_ranges = wr_rd_range,
    .n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
struct regmap_access_table drv_rd_table =
{
    .yes_ranges = wr_rd_range,
    .n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
static bool writeable_reg(struct device *dev, unsigned int reg)
{
    if (reg>= 0x20 &&reg<= 0x4F)
        return true;
    if (reg>= 0x60 &&reg<= 0x7F)
        return true;
    return false;
}
static bool readable_reg(struct device *dev, unsigned int reg)
{
    if (reg>= 0x20 &&reg<= 0x4F)
        return true;
    if (reg>= 0x60 &&reg<= 0x7F)
        return true;
    return false;
}
static int my_spi_drv_probe(struct spi_device *dev)
{
    struct regmap_config config;
    struct custom_drv_private_struct *priv;
    unsigned char data;

    /* setup the regmap configuration */
    memset(&config, 0, sizeof(config));
    config.reg_bits = 8;
    config.val_bits = 8;
    config.write_flag_mask = 0x80;
    config.max_register = 0x80;
    config.fast_io = true;
    config.writeable_reg = drv_writeable_reg;
    config.readable_reg = drv_readable_reg;
    /*
    * If writeable_reg and readable_reg are set,
    * there is no need to provide wr_table nor rd_table.
    * Uncomment below code only if you do not want to use
    * writeable_reg nor readable_reg.
    */
    //config.wr_table = drv_wr_table;
    //config.rd_table = drv_rd_table;
    /* allocate the private data structures */
    /* priv = kzalloc */
    /* Init the regmap spi configuration */
    priv->map = regmap_init_spi(dev, &config);

    /* Use regmap_init_i2c in case of i2c bus */
    /*
    * Let us write into some register
    * Keep in mind that, below operation will remain same
    * whether you use SPI or I2C. It is and advantage when
    * you use regmap.
    */
    regmap_read(priv->map, 0x30, &data);
    [...] /* Process data */
    data = 0x24;
    regmap_write(priv->map, 0x23, data); /* write new value */
    /* set bit 2 (starting from 0) and 6 of register 0x44 */
    regmap_update_bits(priv->map, 0x44, 0b00100010, 0xFF);
    [...] /* Lot of stuff */
    return 0;
}

猜你喜欢

转载自blog.csdn.net/u014770862/article/details/81385481