本文從書籍Linux Device Drivers Development: Develop customized drivers for embedded Linux第九章翻譯,翻譯水平有限,諒解!
在開發Regmap API之前,用於處理SPI核心、I2C核心或兩者的設備驅動程序都有冗餘代碼。它們都有相同的原理:訪問寄存器進行讀/寫操作。下圖顯示在將regmap引入內核之前SPI或i2c API是如何獨立的:
爲了統一內核開發人員訪問SPI/I2C設備的方式,在內核的3.1版本中引入了regmap API。引入該機制後,無論是SPI還是I2C僅僅只有一個問題,如何初始化、配置regmap和處理讀/寫/修改操作。下圖顯示引入了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, ®map_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 &®<= 0x4F)
return true;
if (reg>= 0x60 &®<= 0x7F)
return true;
return false;
}
static bool readable_reg(struct device *dev, unsigned int reg)
{
if (reg>= 0x20 &®<= 0x4F)
return true;
if (reg>= 0x60 &®<= 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;
}