Linux 驱动之 Regmap 子系统

概述

内核版本 3.1 中引入了 Regmap API,用于统一内核开发人员访问 SPI/IIC 设备的方式,无论是 SPI 设备还是 IIC 设备,只需要初始化,配置 Regmap 就可以通过 Regmap 读写设备。Regmap 子系统主要提供如下两种功能:

  • 第一是为 IIC/SPI/MMIO 等提供统一的访问接口,linux 中大量的 iic 和 spi 设备就可以通过统一的接口进行访问尤其对于那些同时支持 iic 和 spi 接口的设备。
  • 第二是提供缓存访问机制用于加速设备访问设备,对于支持缓存的设备这将大大加快设备的当问速度。

下面看一下 Regmapz 子系统在驱动中的位置,如下:原图

数据结构

regmap 子系统的实现比较简单,数据结构也是很简单的,整体结构如下:原图

  • regmap_bus:
    regmap_bus 数据结构用于对具体的 bus 进行封装,例如对 IIC 或 SPI 总线进行封装,这些 bus 下可以挂载很多设备,他们都从属于一个总线那么就有很多共性操作,对这个结构的封装就用 regmap_bus 结构。该结构主要提供同步读写、异步读写、格式化数据等操作接口

  • regmap:
    在上面的结构中讲到 regmap_bus 用于封装具体的 bus,这些 bus 下会挂载各种不同的设备,这些设备的操作有很多不一样的,针对每一个设备进行封装就使用 regmap 结构体。该结构主要提供的功能有:缓存区和缓存操作、异步读写链表和队列操作、读写权限管理(哪些地址的寄存器是可读的、哪些地址范围是可写的以及哪些地址范围是可读写的)、读写页的支持及访问操作以及对寄存器和值的格式化操作等

  • regmap_config:
    regmap_config 结构是最接近用户的,因此上图中也对该结构做了最详细的注释,该结构主要用于用户通过该结构来初始化 regmap 结构体

  • regmap_range_cfg:
    regmap_range_cfg 数据结构用于描述页范围和页选择

  • regmap_format:
    regmap_format 数据结构就是上面提到的用户格式化数据的结构,对于某些寄存器是 12bit 或其他非常规位数的寄存器进行格式化数据的结构

  • regmap_access_table:
    regmap_access_table 数据结构用于描述对寄存器访问权限的控制,yes_ranges 是可访问地址范围而 no_range 是不可访问地址访问

  • regcache_ops:
    regcache_ops 结构提供缓存操作相关接口描述

实现

regmap_bus 的实现

这里以 spi 总线的 regmap_bus 为例,iic 等其他总线是相同的逻辑,regmap_spi 的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static const struct regmap_bus regmap_spi = {
.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,
.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,
struct lock_class_key *lock_key,
const char *lock_name)
{
return __regmap_init(&spi->dev, &regmap_spi, &spi->dev, config,
lock_key, lock_name);
}

可以看到其实实现很简单就是构造一个 regmap_bus 数据结构 regmap_spi,并提供 spi 的相关操作,这里以 write 为例看下里面的实现,其他类似:

1
2
3
4
5
6
7
static int regmap_spi_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct spi_device *spi = to_spi_device(dev);

return spi_write(spi, data, count);
}

其实就是标准的 spi 接口的调用,这也证明了 regmap 其实就是对一些底层 bus 进行进一步封装并对外提供统一的接口,同时还提供页操作缓存等非常好用的接口。

regmap 的实现

在上面介绍的__regmap_init_spi 函数就会返回一个 regmap 结构体也就是 regmap 结构体的创建过程

1
2
__devm_regmap_init
__regmap_init
__regmap_init 函数的流程比较繁琐这里就不贴代码了,大致的流程是初始化需要两个重要的数据结构 regmap_bus 和 regmap_config。每一个 regmap 都需要一个 regmap_bus 数据结构,regmap 的初始化就是设置其结构体的成员,而设置的依据就是根据 regmap_config 结构体的内容来设置。

其实到这里 regmap 的主体结构和实现就大概清楚了,至于其具体的实现细节像页的访问操作,缓存的操作权限的管理感兴趣的可以去 driver/base/regmap/目录下去看源码,我也没去详细了解具体的实现过程,只是梳理了一下 regmap 的大致实现过程。

参考文献

https://blog.csdn.net/lickylin/article/details/107595373?spm=1001.2014.3001.5501
https://blog.csdn.net/lickylin/article/details/107595340?spm=1001.2014.3001.5501
《Linux 设备驱动开发》