我们在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入
了 Regmap 子系统,本章我们就来学习一下如何使用 RegmapAPI 函数来读写 I2C/SPI 设备寄存器。
一、Regmap API 简介
什么情况下会使用 regmap:
①、硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。
②、提高代码复用性和驱动一致性,简化驱动开发过程。
③、减少底层 I/O 操作次数,提高访问效率。
1、Regmap 驱动框架
regmap 框架分为三层:
①、底层物理总线:regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总线有 i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi 和 w1。
②、regmap 核心层,用于实现 regmap,我们不用关心具体实现。
③、regmapAPI 抽象层,regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。
2、regmap 结构体
Linux 内 核 将 regmap 框 架 抽 象 为 regmap 结 构 体 , 这 个 结 构 体 定 义 在 文 件drivers/base/regmap/internal.h 中,结构体内容如下(有缩减):
struct regmap {
union {
struct mutex mutex;
struct {
spinlock_t spinlock;
unsigned long spinlock_flags;
};
};
regmap_lock lock;
......
struct rb_root range_tree;
void *selector_work_buf; /* Scratch buffer used for selector */
};
要使用 regmap,肯定要先给驱动分配一个具体的 regmap 结构体实例,regmap 的初始化通过结构体 regmap_config 来完成。
3、regmap_config 结构体
顾名思义,regmap_config 结构体就是用来初始化 regmap 的,这个结构体也定义在include/linux/regmap.h 文件中,结构体内容如下:
struct regmap_config {
const char *name;
reg_bits; //寄存器地址位数,必填字段。
int reg_stride;
int pad_bits;
int val_bits; //寄存器值位数,必填字段
bool (*writeable_reg)(struct device *dev, unsigned int reg);
........
unsigned int num_ranges;
};
4、Regmap 操作函数
4.1、Regmap 申请与初始化
前面说了,regmap 支持多种物理总线,比如 I2C 和 SPI,我们需要根据所使用的接口来选择合适的 regmap 初始化函数。Linux 内核提供了针对不同接口的 regmap 初始化函数,SPI 接口初始化函数为 regmap_init_spi,函数原型如下:
struct regmap * regmap_init_spi(struct spi_device *spi,const struct regmap_config *config)
函数参数和返回值含义如下:
spi:需要使用 regmap 的 spi_device。
config:regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将其地址赋值给此参数。
返回值:申请到的并进过初始化的 regmap。
I2C 接口的 regmap 初始化函数为 regmap_init_i2c,函数原型如下:
struct regmap * regmap_init_i2c(struct i2c_client *i2c,const struct regmap_config *config)
函数参数和返回值含义如下:
i2c:需要使用 regmap 的 i2c_client。
config:regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将其地址赋值给此参数。
返回值:申请到的并进过初始化的 regmap。
在退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit 这个函数来释放 regmap,函数原型如下:
void regmap_exit(struct regmap *map)
函数参数和返回值含义如下:
map:需要释放的 regmap
返回值:无。
我们一般会在 probe 函数中初始化 regmap_config,然后申请并初始化 regmap。
4.2、regmap 设备访问 API 函数
不管是 I2C 还是 SPI 等接口,还是 SOC 内部的寄存器,对于寄存器的操作就两种:读和写。regmap 提供了最核心的两个读写操作:regmap_read 和 regmap_write。这两个函数分别用来读/写寄存器,regmap_read 函数原型如下:
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要读的寄存器。
val:读到的寄存器值。
返回值:0,读取成功;其他值,读取失败。
regmap_write 函数原型如下:
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要写的寄存器。
val:要写的寄存器值。
返回值:0,写成功;其他值,写失败。
在 regmap_read 和 regmap_write 的基础上还衍生出了其他一些 regmap 的 API 函数,首先是regmap_update_bits 函数,看名字就知道,此函数用来修改寄存器指定的 bit,函数原型如下:
int regmap_update_bits (struct regmap *map, unsigned int reg,unsigned int mask, unsigned int val)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要操作的寄存器。
mask:掩码,需要更新的位必须在掩码中设置为 1。
val:需要更新的位值。
返回值:0,写成功;其他值,写失败。
比如要将寄存器的 bit1 和 bit2 置 1,那么 mask 应该设置为 0X00000011,此时 val 的 bit1和 bit2 应该设置为 1,也就是 0Xxxxxxx11。如果要清除寄存器的 bit4 和 bit7,那么 mask 应该设置为 0X10010000,val 的 bit4 和 bit7 设置为 0,也就是 0X0xx0xxxx。
接下来看一下 regmap_bulk_read 函数,此函数用于读取多个寄存器的值,函数原型如下:
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,size_t val_count)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要读取的第一个寄存器。
val:读取到的数据缓冲区。
val_count:要读取的寄存器数量。
返回值:0,写成功;其他值,读失败。
另外也有多个寄存器写函数 regmap_bulk_write,函数原型如下:
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,size_t val_count)
函数参数和返回值含义如下:
map:要操作的 regmap。
reg:要写的第一个寄存器。
val:要写的寄存器数据缓冲区。
val_count:要写的寄存器数量。
返回值:0,写成功;其他值,读失败。
5、regmap_config 掩码设置
结构体 regmap_config 里面有三个关于掩码的成员变量:read_flag_mask 和 write_flag_mask,这二个掩码非常重要,本节我们来学习一下如何使用这三个掩码。我们在学习 icm20608 的时候讲过了,icm20608 支持 i2c 和 spi 接口,但是当使用 spi 接口的时候,读取 icm20608 寄存器的时候地址最高位必须置 1,写内部寄存器的是时候地址最高位要设置为 0。在初始化 regmap_config的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要我们来操作,全部由 regmap 框架来完成的。同理 write_flag_mask 用法也一样的,只是 write_flag_mask 用于写寄存器操作。
打开 regmap-spi.c 文件,这个文件就是 regmap 的 spi 总线文件,找到如下所示内容:
static struct regmap_bus regmap_spi = { //初始化了一个 regmap_bus 实例
.write = regmap_spi_write,
.gather_write = regmap_spi_gather_write,
.async_write = regmap_spi_async_write,
.async_alloc = regmap_spi_async_alloc,
.read = regmap_spi_read,
.read_flag_mask = 0x80, //这里是将 regmap_bus 的 read_flag_mask 成员变量设置为0X80
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
......
struct regmap *regmap_init_spi(struct spi_device *spi,const struct regmap_config *config)
{
return regmap_init(&spi->dev, ®map_spi, &spi->dev, config);
}
在 regmap_init 函数中找到如下所示内容:
if (config->read_flag_mask || config->write_flag_mask)
{
map->read_flag_mask = config->read_flag_mask; //用 regmap_config 中的读写掩码来初始化 regmap_bus 中的掩码
map->write_flag_mask = config->write_flag_mask;
}
else if (bus)
{
map->read_flag_mask = bus->read_flag_mask;
}
二、实验程序编写
1、修改设备结构体,添加 regmap 和 regmap_config
struct icm20608_dev {
struct spi_device *spi; /* spi 设备 */
dev_t devid; /* 设备号 */
.....
signed int temp_adc; /* 温度原始值 */
struct regmap *regmap;
struct regmap_config regmap_config;
};
2、初始化 regmap
一般在 probe 函数中初始化 regmap,本章节就是 icm20608_probe 函数,初始化内容如下:
static int icm20608_probe(struct spi_device *spi)
{
....
/* 初始化 regmap_config 设置 */
icm20608dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
icm20608dev->regmap_config.val_bits = 8; /* 值长度 8bit */
icm20608dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */
/* 初始化 IIC 接口的 regmap */
icm20608dev->regmap = regmap_init_spi(spi,&icm20608dev->regmap_config);
if (IS_ERR(icm20608dev->regmap))
{
return PTR_ERR(icm20608dev->regmap);
}
......
regmap_exit(icm20608dev->regmap);
return -EIO;
}
同理,在 remove 函数中就要删除 probe 里面申请的 regmap,icm20608_remove 函数内容如下:
static int icm20608_remove(struct spi_device *spi)
{
struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
......
/* 4、注销类 */
class_destroy(icm20608dev->class);
/* 5、删除 regmap */
regmap_exit(icm20608dev->regmap);
return 0;
}
3、读写设备内部寄存器
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,u8 reg)
{
u8 ret;
unsigned int data;
ret = regmap_read(dev->regmap, reg, &data);
return (u8)data;
}
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg,u8 value)
{
regmap_write(dev->regmap, reg, value);
}
void icm20608_readdata(struct icm20608_dev *dev)
{
u8 ret;
unsigned char data[14];
ret = regmap_bulk_read(dev->regmap, ICM20_ACCEL_XOUT_H, data,14);
dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}