Regmap API (IIC,SPI)

Regmap API

在学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入了 Regmap 子系统。

什么是 Regmap ?

Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样的,通过 I2C/SPI 接口读写芯片内部寄存器。
Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器,SPI 接口的话使用 spi_write/spi_read等。I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余代码,再者,代码的复用性也会降低。比如 icm20608 这个芯片既支持 I2C 接口,也支持 SPI 接口。假设我们在产品设计阶段一开始将 icm20608 设计为 SPI 接口,但是后面发现 SPI 接口不够用,或者 SOC 的引脚不够用,我们要将 icm20608 改为 I2C 接口。这个时候 icm20608 驱动就要大改,我们要将 SPI 接口换为 I2C 的,工作量比较大。
基于代码复用的原则,Linux 内核引入了 regmap 模型,regmap 将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmapAPI函数。这样的好处就是统一使用 regmap,降低了代码冗余,提高了驱动的可以移植性。
通过 regmap 模型提供的统一接口函数来访问器件的寄存器,SOC 内部的寄存器也可以使用 regmap 接口函数来访问。
接下来说说regmap 模型的优缺点:
1.减少慢速 I/O 在驱动上的冗余开销
2.降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低。

Regmap 操作函数

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 初始化函数,这里就不介绍了,大家直接查阅 Linux内核即可,基本和 SPI/I2C 的初始化函数相同。
在退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit 这个函数来释放 regmap,函数原型如下:

void regmap_exit(struct regmap *map)

map:需要释放的 regmap
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:需要更新的位值。
比如要将寄存器的 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,写成功;其他值,读失败。

regmap_config 掩码设置

结构体 regmap_config 里面有三个关于掩码的成员变量:其中read_flag_mask 和 write_flag_mask,这二个掩码非常重要,接下来我们来学习一下如何使用这三个掩码。在使用 spi 接口的时候,读取外设器件寄存器的时候地址最高位必须置 1,写内部寄存器的是时候地址最高位要设置为 0。因此这里就涉及到对寄存器地址最高位的操作。
比如我们在使用 SPI 接口函数读取icm20608 内部寄存器的时候手动将寄存器地址的最高位置 1,代码如下所示:

1 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg,
void *buf, int len) 
2 { 
3 
......
21 txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit7 要置 1 */ 
22 t->tx_buf = txdata; /* 要发送的数据 */
23 t->rx_buf = rxdata; /* 要读取的数据 */
24 t->len = len+1; /* t->len=发送的长度+读取的长度 */
25 spi_message_init(&m); /* 初始化 spi_message */
26 spi_message_add_tail(t, &m);
27 ret = spi_sync(spi, &m); /* 同步发送 */
......
39 return ret;
40 }

在代码其中第 21 行将寄存器的地址 bit7 置 1,表示这是一个读操作。
当我们使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要我们来操作,全部由 regmap 框架来完成的。
同理 write_flag_mask 用法也一样,只是 write_flag_mask 用于写寄存器的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340