I.MX6ULL_Linux_驱动篇(57)linux Regmap API驱动

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

Regmap API 简介

什么是 Regmap

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

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:
①、硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。
②、提高代码复用性和驱动一致性,简化驱动开发过程。
③、减少底层 I/O 操作次数,提高访问效率。
本章教程我们就来重点学习一下如何将《 Linux SPI 驱动实验》中编写的 SPI接口的 icm20608 驱动改为使用 regmap API。

Regmap 驱动框架

regmap 驱动框架如下图所示:

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

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

51 struct regmap {
52     union {
53         struct mutex mutex;
54         struct {
55             spinlock_t spinlock;
56             unsigned long spinlock_flags;
57         };
58     };
59     regmap_lock lock;
60     regmap_unlock unlock;
61     void *lock_arg; /* This is passed to lock/unlock functions */
62
63     struct device *dev; /* Device we do I/O on */
64     void *work_buf; /* Scratch buffer used to format I/O */
65     struct regmap_format format; /* Buffer format */
66     const struct regmap_bus *bus;
67     void *bus_context;
68     const char *name;
69
70     bool async;
71     spinlock_t async_lock;
72     wait_queue_head_t async_waitq;
73     struct list_head async_list;
74     struct list_head async_free;
75     int async_ret;
......
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     const struct regmap_access_table *wr_table;
95     const struct regmap_access_table *rd_table;
96     const struct regmap_access_table *volatile_table;
97     const struct regmap_access_table *precious_table;
98
99     int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
100    int (*reg_write)(void *context, unsigned int reg, unsigned int val);
......
147    struct rb_root range_tree;
148    void *selector_work_buf; /* Scratch buffer used for selector */
149 };

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

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

186 struct regmap_config {
187     const char *name;
188
189     int reg_bits;
190     int reg_stride;
191     int pad_bits;
192     int val_bits;
193
194     bool (*writeable_reg)(struct device *dev, unsigned int reg);
195     bool (*readable_reg)(struct device *dev, unsigned int reg);
196     bool (*volatile_reg)(struct device *dev, unsigned int reg);
197     bool (*precious_reg)(struct device *dev, unsigned int reg);
198     regmap_lock lock;
199     regmap_unlock unlock;
200     void *lock_arg;
201
202     int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
203     int (*reg_write)(void *context, unsigned int reg, unsigned int val);
204
205     bool fast_io;
206
207     unsigned int max_register;
208     const struct regmap_access_table *wr_table;
209     const struct regmap_access_table *rd_table;
210     const struct regmap_access_table *volatile_table;
211     const struct regmap_access_table *precious_table;
212     const struct reg_default *reg_defaults;
213     unsigned int num_reg_defaults;
214     enum regcache_type cache_type;
215     const void *reg_defaults_raw;
216     unsigned int num_reg_defaults_raw;
217
218     u8 read_flag_mask;
219     u8 write_flag_mask;
220
221     bool use_single_rw;
222     bool can_multi_write;
223
224     enum regmap_endian reg_format_endian;
225     enum regmap_endian val_format_endian;
226
227     const struct regmap_range_cfg *ranges;
228     unsigned int num_ranges;
229 };

Linux 内核里面已经对 regmap_config 各个成员变量进行了详细的讲解,这里我们只看一些比较重要的:
第 187 行 name:名字。
第 189 行 reg_bits:寄存器地址位数,必填字段。
第 190 行 reg_stride:寄存器地址步长。
第 191 行 pad_bits:寄存器和值之间的填充位数。
第 192 行 val_bits:寄存器值位数,必填字段。
第 194 行 writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用,并返回 true。
第 195 行 readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并返回 true。
第 196 行 volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调用,并返回 true。
第 197 行 precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部的值。
第 202 行 reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行。
第 203 行 reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行。
第 205 行 fast_io:快速 I/O,使用 spinlock 替代 mutex 来提升锁性能。
第 207 行 max_register:有效的最大寄存器地址,可选。
第 208 行 wr_table:可写的地址范围,为 regmap_access_table 结构体类型。后面的 rd_table、volatile_table、 precious_table、 wr_noinc_table 和 rd_noinc_table 同理。
第 212 行 reg_defaults:寄存器模式值,为 reg_default 结构体类型,此结构体有两个成员变量: reg 和 def, reg 是寄存器地址, def 是默认值。
第 216 行 num_reg_defaults:默认寄存器表中的元素个数。
第 218 行 read_flag_mask:读标志掩码。
第 219 行 write_flag_mask:写标志掩码。
关于 regmap_config 结构体成员变量就介绍这些,其他没有介绍的自行查阅 Linux 内核中的相关描述。

Regmap 操作函数

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
返回值:无。
我们一般会在 probe 函数中初始化 regmap_config,然后申请并初始化 regmap。

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,写成功;其他值,读失败。
关于 regmap 常用到 API 函数就讲解到这里,还有很多其他功能的 API 函数,大家自行查阅 Linux 内核即可,内核里面对每个 API 函数都有详细的讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/491572.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

当当狸智能激光雕刻机 多种材质自由雕刻,轻松打造独一无二的作品

提及“激光雕刻”,大多数人的印象一般都是:笨重巨大、价格昂贵、操作复杂、使用门槛较高、调试难度大...不是普通人能够随意操作的,让人望尘莫及。 而小米有品上新的这台「当当狸桌面智能激光雕刻机L1」,将超乎你的想象&#xff…

ABAP - 上传文件模板到SMW0,并从SMW0上下载模板

upload file template to SMW0 and download the template from it 首先上传文件到tcode SMW0 选择新建后,输入文件名和描述,再选择想要上传的文件 上传完成后: 在表WWWPARAMS, WWWDATA里就会有信息存进去 然后就可以程序里写代码了: 屏幕上的效果:

界面控件DevExpress WinForms/WPF v23.2 - 电子表格支持表单控件

DevExpress WinForm拥有180组件和UI库,能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序,无论是Office风格的界面,还是分析处理大批量的业务数据,它都能轻松胜任…

yolov9报错:AttributeError: ‘list‘ object has no attribute ‘view‘的两种解决方法

1. 报错问题 In loss_tal.py: pred_distri, pred_scores torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split( (self.reg_max * 4, self.nc), 1) The error is as follows: AttributeError: list object has no attribute vie…

5.递归分治——1.递归与函数调用

程序运行 指令 指令的划分以函数为单位,调用函数本质上是使用call指令将pc指针移动到被调用的函数函数调用完成,需要使用return返回,本质是pc移回调用函数位置 数据 函数调用时,在堆栈区域要申请一片栈帧,函数的局…

linux用户管理命令2

useradd可以创建用户,其执行具体表现为在home文件夹下创建对应文件 那么有了useradd添加用户,自然有passwd添加用户密码 userdel可以删除用户,其中-r命令删除用户及其文件,-f命令可以强制删除用户,即使用户当前正在登录…

LeetCode 面试经典150题 205.同构字符串

题目: 给定两个字符串 s 和 t ,判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字…

【MATLAB源码-第16期】基于matlab的MSK定是同步仿真,采用gardner算法和锁相环。

操作环境: MATLAB 2022a 1、算法描述 **锁相环(PLL)** 是一种控制系统,用于将一个参考信号的相位与一个输入信号的相位同步。它在许多领域中都有应用,如通信、无线电、音频、视频和计算机系统。锁相环通常由以下几个…

什么是公网IP?

公网IP,即公开网络IP地址,是指在互联网中公开可见、可访问的IP地址。每个设备在连接互联网时,都需要一个唯一的公网IP地址,以便其他设备可以定位并与之通信。 尽管公网IP在网络通信中具有重要作用,但它也带来了一些安全…

机器学习之聚类算法、随机森林

文章目录 随机森林决策树基础特征值问题? 聚类算法 随机森林 决策树 基础 概念:从根节点一步步走到叶子节点(决策); 组成:根节点第一个选择的节点;叶子节点最终的决策结果;非叶子…

汽车电子行业知识:智能汽车电子架构

文章目录 3.智能汽车电子架构3.1.汽车电子概念及发展3.2.汽车电子架构类型3.2.1.博世汽车电子架构3.2.2.联合电子未来汽车电子架构3.2.3.安波福汽车电子架构3.2.4.丰田汽车电子架构3.2.5.华为汽车电子架构 3.智能汽车电子架构 3.1.汽车电子概念及发展 汽车电子是车体汽车电子…

LeetCode146:LRU缓存

leetCode:146. LRU 缓存 题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#x…

达梦数据库自动备份(全库)+还原(全库) 控制台

一 前提 1.安装达梦数据库DB8(请参照以前文章) 我的数据库安装目录是 /app/dmDB8 2.已创建实例 (请参照上一篇文章) 二 准备测试数据 三 自动备份步骤 1.开启归档模式 开启DM管理工具管理控制台 弹不出来工具的 输入命令 xhost 第一步 将服务器转换为配置状态 右键-&g…

基于SpringBoot和Vue的车辆管理系统的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的车辆管理系统的设计与实现 !!! 有需要的小伙伴可以通过文章末尾名片咨询我哦!!! 💕💕作者:李同学 💕&#x1f…

数据结构——链表(单链表)

大家好,又是我(小锋),今天给大家带了一个比较有挑战的章节(链表),但是不用担心,小锋会陪大家一起度过。 顺序表的思考与问题 1. 中间/头部的插入删除,时间复杂度为O(N) …

java-springboot实现图片的上传

我们在resources目录下创建image目录来存放上传的图片 service层懒的写,就都写controller层了。 RestController RequestMapping("/upload") public class upload {PostMapping("/pic")public String upLoad(RequestParam("multipartFile…

EPSON推出的实时时钟模块RX8130CE功耗低至300nA、从容应对各种使用场景

随着科技的进步和消费者需求的不断变化,笔记本电脑市场继续展现出强劲的发展势头一方面移动性和轻薄性成为主流,另外一方面性能在不断提升,功能也日益丰富。实时时钟模组,作为提供时间和定时功能的单元模块,是笔记本电…

esp单片机下arduino_gfx不相干显示驱动优化对flash空间的占用对比

一般情况下,很多esp32或者esp8266下的tft模块驱动都会包含很多种,而我们只需要其中一种,那就有个疑问这些被编译进的显示驱动到底占用了多少空间,是否需要把他优化掉? 这是默认的驱动列表: 84个文件&…

“选项按钮”的妙用

背景:是否厌倦了下拉菜单?现在可以使用更好玩的选项按钮了。 操作:点击“开发工具”,插入“选项按钮”的窗体控件。 插入一个选项按钮以后,右键“设置控件格式”,设定单元格链接,比如说本次设定…

投影变形的在线查看工具

投影变形的在线查看工具 地图投影是将地球椭球面转换到平面上的过程。不同的地图投影方式会导致不同类型和程度的变形。如何去了解这种变形? ESRI开发过一个投影变换工具,可以在线展示各个投影坐标系的变形情况。通过选择data-wkid,可以在网…