譯文: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章