本文从书籍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 API非常简单,这个API的两个最重要的结构是struct regmap_config,它表示regmap的配置,struct regmap,它是regmap实例本身。所有regmap数据结构都是在include/linux/regmap.h中定义的。
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 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
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 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,有下面几种情况:
regmap_read:从设备读取数据。使用方式和regmap_write相同。因此,如果提供了reg_read,则调用reg_read执行读操作;否则,调用泛型regmap读函数
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,以此类推。
函数功能是写入设备的多个寄存器,其原型如下:
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支持缓存,是否使用缓存取决于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子系统步骤:
为了实现我们的目标,让我们首先描述一个假冒的SPI设备,我们可以为它编写一个驱动程序:
骨架示例程序:
/* mandatory for regmap */
#include
/* 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;
}