正点原子嵌入式linux驱动开发——Linux Regmap驱动

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

Regmap API简介

Regmap

Linux下大部分设备的驱动开发都是操作其内部寄存器,比如I2C/SPI设备的本质都是一样的,通过I2C/SPI接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如STM32MP157的PWM、TIM等外设初始化,最终都是要落到寄存器的设置上。

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函数,统一使用regmap API函数。这样的好处就是统一使用regmap,降低了代码冗余,提高了驱动的可以移植性。regmap模型的重点在于:通过regmap模型提供的统一接口函数来访问器件的寄存器,SOC内部的寄存器也可以使用regmap接口函数来访问。

regmap是Linux内核为了减少慢速I/O在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。另外,regmap在驱动和硬件之间添加了cache,降低了低速I/O的操作次数,提高了访问效率,缺点是实时性会降低。

regmap的应用场景有如下所示:

  1. 硬件寄存器操作,比如选用通过I2C/SPI接口来读写设备的内部寄存器,或者需要读写SOC内部的硬件寄存器。
  2. 提高代码复用性和驱动一致性,简化驱动开发过程。
  3. 减少底层 I/O 操作次数,提高访问效率。

本章教程就来重点学习一下如何将SPI接口的icm20608驱动改为使用regmap API。

Regmap驱动框架

regmap框架结构

regmap驱动框架如下图所示:

regmap框架分为三层:

  1. 底层物理总线:regmap就是对不同的物理总线进行封装,目前regmap支持的物理总线有i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi和w1。
  2. regmap核心层,用于实现regmap,不用关心具体实现。
  3. regmapAPI抽象层,regmap向驱动编写人员提供的API接口,驱动编写人员使用这些API接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的

regmap结构体

Linux内核将regmap框架抽象为regmap结构体,这个结构体定义在文件include/linux/regmap.h 中,结构体内容如下(有缩减):

c 复制代码
示例代码 55.1.2.1 regmap 结构体
49  struct regmap {
50      union {
51          struct mutex mutex;
52          struct {
53              spinlock_t spinlock;
54              unsigned long spinlock_flags;
55          };
56      };
57      regmap_lock lock;
58      regmap_unlock unlock;
59      void *lock_arg; /* This is passed to lock/unlock functions */
60      gfp_t alloc_flags;
......
89      unsigned int max_register;
90      bool (*writeable_reg)(struct device *dev, unsigned int reg);
91      bool (*readable_reg)(struct device *dev, unsigned int reg);
92      bool (*volatile_reg)(struct device *dev, unsigned int reg);
93      bool (*precious_reg)(struct device *dev, unsigned int reg);
94      bool (*writeable_noinc_reg)(struct device *dev,
unsigned int reg);
95      bool (*readable_noinc_reg)(struct device *dev,
unsigned int reg);
96      const struct regmap_access_table *wr_table;
97      const struct regmap_access_table *rd_table;
98      const struct regmap_access_table *volatile_table;
99      const struct regmap_access_table *precious_table;
100     const struct regmap_access_table *wr_noinc_table;
101     const struct regmap_access_table *rd_noinc_table;
102
103     int (*reg_read)(void *context, unsigned int reg,
unsigned int *val);
104     int (*reg_write)(void *context, unsigned int reg,
unsigned int val);
105     int (*reg_update_bits)(void *context, unsigned int reg,
106     unsigned int mask, unsigned int val);
......
159
160     struct rb_root range_tree;
161     void *selector_work_buf; /* Scratch buffer used for selector */
162
163     struct hwspinlock *hwlock;
164 };

要使用regmap,肯定要先给驱动分配一个具体的regmap结构体实例,一会讲解如何分配regmap实例。可以看到示例代码55.1.2.1中第90-101行有很多的函数以及table,这些需要驱动编写人员根据实际情况选择性的初始化,regmap的初始化通过结构体regmap_config来完成

regmap_config结构体

regmap_config结构体就是用来初始化regmap的,这个结构体也定义在include/linux/regmap.h 文件中,结构体内容如下:

c 复制代码
示例代码 55.1.2.2 regmap_config 结构体
352 struct regmap_config {
353     const char *name;
354
355     int reg_bits;
356     int reg_stride;
357     int pad_bits;
358     int val_bits;
359
360     bool (*writeable_reg)(struct device *dev, unsigned int reg);
361     bool (*readable_reg)(struct device *dev, unsigned int reg);
362     bool (*volatile_reg)(struct device *dev, unsigned int reg);
363     bool (*precious_reg)(struct device *dev, unsigned int reg);
364     bool (*writeable_noinc_reg)(struct device *dev,
unsigned int reg);
365     bool (*readable_noinc_reg)(struct device *dev,
unsigned int reg);
366
367     bool disable_locking;
368     regmap_lock lock;
369     regmap_unlock unlock;
370     void *lock_arg;
371
372     int (*reg_read)(void *context, unsigned int reg,
unsigned int *val);
373     int (*reg_write)(void *context, unsigned int reg,
unsigned int val);
374
375     bool fast_io;
376
377     unsigned int max_register;
378     const struct regmap_access_table *wr_table;
379     const struct regmap_access_table *rd_table;
380     const struct regmap_access_table *volatile_table;
381     const struct regmap_access_table *precious_table;
382     const struct regmap_access_table *wr_noinc_table;
383     const struct regmap_access_table *rd_noinc_table;
384     const struct reg_default *reg_defaults;
385     unsigned int num_reg_defaults;
386     enum regcache_type cache_type;
387     const void *reg_defaults_raw;
388     unsigned int num_reg_defaults_raw;
389
390     unsigned long read_flag_mask;
391     unsigned long write_flag_mask;
392     bool zero_flag_mask;
393
394     bool use_single_read;
395     bool use_single_write;
396     bool can_multi_write;
397
398     enum regmap_endian reg_format_endian;
399     enum regmap_endian val_format_endian;
400
401     const struct regmap_range_cfg *ranges;
402     unsigned int num_ranges;
403
404     bool use_hwlock;
405     unsigned int hwlock_id;
406     unsigned int hwlock_mode;
407 };

Linux内核里面已经对regmap_config各个成员变量进行了详细的讲解,这里只看一些比较重要的:

第353行name:名字。

第355行reg_bits:寄存器地址位数,必填字段。

第356行reg_stride:寄存器地址步长。

第357行pad_bits:寄存器和值之间的填充位数。

第358行val_bits:寄存器值位数,必填字段。

第360行writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用,并返回true。

第361行readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并返回true。

第362行volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调用,并返回 true。

第363行precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部的值。

第372行reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行。

第373行reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行。

第375行fast_io:快速I/O,使用spinlock替代mutex来提升锁性能。

第377行max_register:有效的最大寄存器地址,可选。

第378行wr_table:可写的地址范围,为regmap_access_table结构体类型。后面的rd_table、volatile_table、precious_table、wr_noinc_table和rd_noinc_table同理。

第384行reg_defaults:寄存器模式值,为reg_default结构体类型,此结构体有两个成员变

量:reg和def,reg是寄存器地址,def是默认值。

第385行num_reg_defaults:默认寄存器表中的元素个数。

第390行read_flag_mask:读标志掩码。

第391行write_flag_mask:写标志掩码。

关于regmap_config 结构体成员变量就介绍这些,其他没有介绍的自行查阅Linux内核中的相关描述。

Regmap操作函数

Regmap申请与初始化

regmap支持多种物理总线,比如I2C和SPI,需要根据所使用的接口来选择合适的regmap初始化函数 。Linux内核提供了针对不同接口的regmap初始化函数,SPI接口初始化函数为regmap_init_spi,函数原型如下:

c 复制代码
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,函数原型如下:

c 复制代码
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,函数原型如下:

c 复制代码
void regmap_exit(struct regmap *map)

函数参数和返回值含义如下:

  • map:需要释放的regmap。
  • 返回值:无。

一般会在probe函数中初始化regmap_config,然后申请并初始化regmap

regmap设备访问API函数

不管是I2C还是SPI等接口,还是SOC内部的寄存器,对于寄存器的操作就两种:读和写。regmap提供了最核心的两个读写操作:regmap_read和regmap_write。这两个函数分别用来读/写寄存器,regmap_read函数原型如下:

c 复制代码
int regmap_read(struct regmap *map, 
				unsigned int reg, 
				unsigned int *val)

函数参数和返回值含义如下:

  • map:要操作的regmap。
  • reg:要读的寄存器。
  • val:读到的寄存器值。
  • 返回值:0,读取成功;其他值,读取失败。

regmap_write函数原型如下:

c 复制代码
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,函数原型如下:

c 复制代码
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函数,此函数用于读取多个寄存器的值,函数原型如下:

c 复制代码
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,函数原型如下:

c 复制代码
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常用到API函数就讲解到这里,还有很多其他功能的API函数,可以自行查阅Linux内核即可,内核里面对每个API函数都有详细的讲解。

regmap_config掩码设置

结构体regmap_config里面有三个关于掩码的成员变量:read_flag_mask、write_flag_mask和zero_flag_mask,这三个掩码非常重要 ,本节来学习一下如何使用这三个掩码。在学习icm20608的时候讲过了,icm20608支持i2c和spi接口,但是当使用spi接口的时候,读取icm20608寄存器的时候地址最高位必须置1,写内部寄存器的是时候地址最高位要设置为0 。因此这里就涉及到对寄存器地址最高位的操作,在之前的SPI驱动实验中在使用SPI接口函数读取icm20608内部寄存器的时候手动将寄存器地址的最高位置1,代码如下所示:

c 复制代码
示例代码 55.1.4.1 icm20608 驱动
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 }

示例代码55.1.4.1就是标准的SPI驱动,其中第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用于写寄存器操作。

打开regmap-spi.c文件,这个文件就是regmap的spi总线文件,找到如下所示内容:

第101-110行初始化了一个regmap_bus实例:regmap_spi,重点看一下第107行中read_flag_mask默认为0X80 。注意,这里是将regmap_bus的read_flag_mask成员变量设置为0X80。regmap_bus结构体大家自行查看一下,这里就不讲了。

第112-119行__regmap_init_spi函数,前面说了要想在spi总线中使用regmap框架,首先要使用regmap_init_spi函数用于并申请一个SPI总线的regmapregmap_init_spi函数会调用这里的__regmap_init_spi函数 ,从第117行可以看出__regmap_init_spi函数只是对__regmap_init的简单封装,因此最终完成regmap申请并初始化的是__regmap_init函数。在__regmap_init函数中找到如下所示内容:

第812-817行就是用regmap_config中的读写掩码来初始化regmap_bus中的掩码。由于regmap_spi默认将read_flag_mask设置为0X80,当所使用的SPI设备不需要读掩码,在初始化regmap_config的时候一定要将read_flag_mask设置为0X00,并且也要将zero_flag_mask设置为true

regmap框架就讲解到这里,接下来学习如何之前SPI驱动实验中编写的icm20608驱动改为regmap框架。

实验程序编写

修改设备结构体,添加regmap和regmap_config

regmap框架的核心就是regmap和regmap_config结构体,一般都是在自定义的设备结构体里面添加这两个类型的成员变量,所以首先在icm20608_dev结构体里面添加regmap和regmap_config。

只需要在icm20608_dev设备结构体中,添加regmap结构体变量指针regmap,以及regmap_config结构体变量regmap_config就可以了。

初始化regmap

一般在probe函数中初始化regmap,本章节就是icm20608_probe函数。

在原先的基础上,添加regmap_config的初始化,配置icm20608dev这个设备结构体的regmap_config成员变量就好了,设置reg_bits=8,val_bits=8,read_flag_mask=0x80。

然后配置icm20608dev的regmap变量,由regmap_init_spi来进行初始化配置SPI总线的regmap。

同理,需要注销的话就是在remove函数中,添加regmap_exit来注销就可以了。

读写设备内部寄存器

在read的操作函数中,可以直接通过regmap_read来进行寄存器读取;在write的函数中,通过regmap_write来写入。

在最后的readdata函数中,可以通过regmap_buld_read来进行多个寄存器的读取。

运行测试

测试APP直接用之前SPI驱动编写的icm20608App.c即可。测试方法也和之前一样,输入如下命令:

|---------------------------------------------------------------------------------------------------|
| depmod //第一次加载驱动的时候需要运行此命令 modprobe icm20608.ko //加载驱动模块 ./icm20608App /dev/icm20608 //app 读取内部数据 |

如果regmap API工作正常,那么就会正确的初始化icm20608,并且读出传感器数据,结果和SPI驱动是一样的,如下图所示:

IIC总线的regmap框架基本和SPI一样,只是需要使用regmap_init_i2c来申请并初始化对应的regmap,同样都是使用regmap_read和regmap_write来读写I2C设备内部寄存器

总结

Regmap就是对IIC、SPI总线的再次封装,可以通过regmap_init_xxx(xxx就是你的具体总线名称),然后通过regmap_read和regmap_write来进行单个寄存器的读写操作就可以了。

也就是说,之前的IIC和SPI总线的实验,需要自己去翻手册,然后对寄存器进行读写,如果使用Regmap,就可以用一样的函数来进行读写了,相当于代码复用不用重新编写了。

相关推荐
偶尔微微一笑3 分钟前
AI网络渗透kali应用(gptshell)
linux·人工智能·python·自然语言处理·编辑器
Run1.13 分钟前
深入解析 Linux 中动静态库的加载机制:从原理到实践
linux·运维·服务器
The Mr.Nobody25 分钟前
STM32MPU开发之旅:从零开始构建嵌入式Linux镜像
linux·stm32·嵌入式硬件
晓数35 分钟前
【硬核干货】JetBrains AI Assistant 干货笔记
人工智能·笔记·jetbrains·ai assistant
老兵发新帖42 分钟前
Ubuntu 上安装 Conda
linux·ubuntu·conda
秋秋秋秋秋雨1 小时前
linux安装单节点Elasticsearch(es),安装可视化工具kibana
linux·elasticsearch·jenkins
我的golang之路果然有问题1 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
genggeng不会代码1 小时前
用于协同显著目标检测的小组协作学习 2021 GCoNet(总结)
学习
lwewan1 小时前
26考研——存储系统(3)
c语言·笔记·考研
阿川!1 小时前
嵌入式软件--stm32 DAY 3
stm32·单片机·嵌入式硬件