使用regmap使Linux驱动程序更加通用

2020-05-28 00:25:23

很少有开发人员喜欢在Linux内核树之外维护驱动程序,原因有很多,比如缺乏稳定的驱动程序API,或者驱动程序可能会复制预先存在的内核功能,例如从头开始整个网络堆栈。

将公共驱动程序基础设施分解为通用的、可重用的模块,以消除重复代码、修复常见错误并拥有统一的接口,这是更可取的。不足为奇的是,这一直是并将继续是一项持续的Linux内核开发工作。在本文中,我们将研究此过程的一个具体实例,即努力使Synopsys MIPI DSI主机控制器驱动程序更加通用,以便它能够支持更多的设备版本和SoC平台。

regmap是如上所述的公共基础设施创建过程的结果。它们是从v3.1开始添加到内核中的,最初是为了抽象非内存映射总线(如I2C或SPI)的公共驱动程序寄存器访问逻辑,随着时间的推移,它也扩展到也支持从v3.5开始的内存映射IO,这是我们感兴趣的。

regmap的定义从regmap配置开始(这是下面链接的示例中的实际代码)。头结构文档包含更多有用的字段,但我们只需要以下基本定义:以值大小表示的寄存器地址、以字节为单位的跨度(即寄存器开始之间的总长度)和名称。

#include<;linux/regmap.h>;static Const struct regmap_config dw_MIPI_DSI_regmap_cfg={.reg_bits=32,.val_bits=32,.reg_stride=4,.name=";dw-mipi-dsi";,};

这允许我们从内存映射区域创建regmap结构,如下所示。在本文的最后一节中可以找到一个完整的真实驱动程序示例。

只使用regmap可以做更多的事情,比如配置它以强制最大地址偏移量以避免越界读/写,或者附加一个在访问regmap时可以自动启用的时钟,但对于我们的目的来说,这已经足够了。regmap通过其regmap_read/write函数访问内存区域所使用的底层机制是标准的readl/write函数,因此可以将其视为内存映射IO之上的抽象。

regmap归拥有它所连接的内存区域的设备所有,因此不需要跟踪并手动释放它,它会被设备管理代码自动丢弃。

对我们来说,更有趣的是regmap之上的另一个抽象:regmap字段。regmap定义寄存器集,而regmap字段以可配置的偏移量定义这些寄存器内的特定位字段。这在以后会非常有用。字段是通过定义struct reg_field来配置的,内核提供了一个我们使用的方便的宏reg_field(_reg,_lsb,_msb)。

例如,i.MX 6双/四应用处理器参考手册版本2,06/2014定义了以下寄存器:

上面的寄存器字段可以这样定义(同样,下面示例中的真实代码):

#include<;linux/regmap.h>;#定义DSI_TMR_LINE_CFG 0x28 struct dw_MIPI_DSI_VARIANT{struct reg_field cfg_vid_hsa_time;struct reg_field cfg_vid_hbp_time;struct reg_field cfg_vid_hline_time;};struct dw_MIPI_DSI_VARIANT dw_MIPI_DSI_v101_Layout={.cfg_vid_hsa_time=REG_FIELD(DSI_TMR_LINE_CFG,0,8),.cfg_VID_HBP_TIME=REG_FIELD(DSI_TMR_LINE_CFG,9,17),.cfg_VID_HLINE_TIME=REG_FIELD(DSI_TMR。

接下来,我们只需要将每个struct reg_field配置与特定的regmap内存区域相关联,从而创建可读/写或轮询的struct regmap_field。

使用这样的寄存器字段的一个优点是代码更干净,因为字段范围是明确定义的,并且避免了容易出错的预处理器#定义比特操作黑客,因此显著降低了跟踪字段所需的脑力。另一个更大的优点是可以并行指定多个字段布局,以便例如驱动器可以基于检测到的硬件修订来选择正确的寄存器字段位置。我们接下来将看到这方面的一个具体例子。

在寻求优化铜线公里的使用,使设备更小、更强大或更便宜的过程中,硬件供应商想出了许多很酷的技术和版本,例如IS MIPI DSI。

它是一种全串行显示接口,易于硬件实现,因为它只需要一个高速时钟通道(在i.MX 6上通常设置为27 Mhz)和一个数据通道(实际上最多会遇到4个通道),这使得它在嵌入式、智能手机/平板电脑和汽车市场非常流行。

它有4个主要修订版(1.0-1.3,暂时忽略DSI 2),由恩智浦、意法半导体或RockChip等SoC供应商在各种组合中使用,这些供应商在其主板上添加了Synopsys创建的DSI主机控制器,以将数据发送到DSI兼容的显示面板。问题在于为这些版本编写驱动程序:正如可以从SoC供应商提供的参考手册中观察到的那样,不同版本之间的寄存器布局非常不同,即使核心硬件协议相似(如果不是几乎相同的话)。

这种情况导致Linux MIPI-DSI驱动程序激增,通常每个SoC供应商/DSI版本都有一个驱动程序。在主线树中合并的STM和RockChip驱动程序创建了一个公共桥接驱动程序模块来共享代码,不幸的是,目前该模块仅限于这些平台使用的DSI 1.30-1.31布局。如果不引入抽象层,就很难使这些驱动程序或公共网桥模块更通用,以共享更多代码、添加新平台或DSI修订版,因此这听起来是一个很好的用例,可以应用正则映射来使现有的上游主机控制器驱动程序更通用。

为此,在撰写本文时,已经创建了Genericize DW MIPI DSI Bridge and add i.MX 6驱动程序补丁系列,该补丁程序系列最终将在主线内核中实现,并略微减少树外驱动程序的扩散。为了方便起见,V6还被推到了GitLab分支,基于撰写本文时的最新Linux版本V5.6。

这些补丁的基本思想是将桥接模块逐步转换为正则映射,使其了解不同的寄存器布局,而无需大幅修改使用它的平台驱动程序,从而使驱动程序能够透明地处理与硬件修订相对应的多个布局。网桥regmap后端和平台驱动程序使用相同的MMIO接口。在创建regmap之前,寄存器内存只由驱动程序通过它们的plat_data基指针映射一次,或者由桥接器映射一次。

考虑到这篇文章的上下文,补丁应该是不言而喻的,并且很容易理解。简单来说,所采取的步骤是:

显式定义寄存器字段的附加好处的一个示例是,位字段读/写错误更容易发现和修复,因为它们不会隐藏在隐蔽的预处理器位操作宏之后。

可以看到,上面的MIPI-DSI补丁系列使用了与本文开头相同的regmap和字段配置,实现不同布局的逻辑应该足够通用,以使其可供更多访问MMIO公开寄存器的驱动程序重用,因此希望这也能对其他必须处理这些问题的驱动程序编写人员有用。硬件设计人员和供应商应该考虑到这一点,以尽量避免任何不必要的接口中断,并使软件开发人员的生活变得更容易。

请勾选此框以确认您已阅读并接受我们关于收集/存储和使用您的个人数据的隐私声明条款:*