Regmap API 实验

我们在前面学习 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, &regmap_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]);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式学习者。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值