在Linux驱动中使用regmap
背景
在学习SPI的时候,看到了某个rtc驱动中用到了regmap
,在学习了对应的原理以后,也记录一下如何使用。
介绍
在Linu 3.1开始,Linux引入了regmap来统一管理内核的I2C, SPI等总线,将I2C, SPI驱动做了一次重构,把I/O读写的重复逻辑在regmap中实现。只需初始化时指定总线类型、寄存器位宽等关键参数,即可通过regmap模型接口来操作器件寄存器。
当然,regmap同样适用于操作cpu自身的寄存器。将i2c、spi、mmio、irq都抽象出统一的接口regmap_read、regmap_write、regmap_update_bits等接口 ,从而提高代码的可重用性,并且使得在使用如上内核基础组件时变得更为简单易用。
regmap是在 linux 内核为减少慢速 I/O 驱动上的重复逻辑,提供一种通用的接口来操作底层硬件寄存器的模型框架。
此外,由于regmap在驱动和硬件寄存器之间增加了cache,如果使用了cache,能够减少底层低速 I/O 的操作次数,提高访问效率;但降低了实时性会有所降低。
配置map_config
可以仅对自己需要的部分赋值
struct regmap_config {
const char *name;
int reg_bits;// 寄存器地址的位数,必须配置,例如I2C寄存器地址位数为 8
int reg_stride;
int pad_bits;// 寄存器值的位数,必须配置
int val_bits;
bool (*writeable_reg)(struct device *dev, unsigned int reg);// 可写寄存器回调,maintain一个可写寄存器表
bool (*readable_reg)(struct device *dev, unsigned int reg); // 可读寄存器回调, maintain一个可读寄存器表
bool (*volatile_reg)(struct device *dev, unsigned int reg); // 可要求读写立即生效的寄存器回调,不可以被cache,maintain一个可立即生效寄存器表
bool (*precious_reg)(struct device *dev, unsigned int reg); // 要求寄存器数值维持在一个数值范围才正确,maintain一个数值准确表
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg;
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);//读寄存器
int (*reg_write)(void *context, unsigned int reg, unsigned int val);//写寄存器
bool fast_io;
unsigned int max_register; // 最大寄存器地址,防止访问越界
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct reg_default *reg_defaults;
unsigned int num_reg_defaults;
enum regcache_type cache_type; // cache数据类型,支持三种:flat、rbtree、Izo
const void *reg_defaults_raw;
unsigned int num_reg_defaults_raw;
u8 read_flag_mask;// 读寄存器掩码
u8 write_flag_mask;// 写寄存器掩码
bool use_single_rw;
bool can_multi_write;
enum regmap_endian reg_format_endian;// 寄存器地址大小端,大于8位时需设置
enum regmap_endian val_format_endian;// 寄存器值大小端,大于8位时需设置
const struct regmap_range_cfg *ranges;
unsigned int num_ranges;
};
关于bit位数的设置我就不再多说了;看看一些比较需要注意的。
大小端
enum regmap_endian reg_format_endian;// 寄存器地址大小端,大于8位时需设置
enum regmap_endian val_format_endian;// 寄存器值大小端,大于8位时需设置
regmap支持的大小端序格式为3种,需要根据设备的传输类型来设置。
enum regmap_endian {
/* Unspecified -> 0 -> Backwards compatible default */
REGMAP_ENDIAN_DEFAULT = 0,
REGMAP_ENDIAN_BIG,
REGMAP_ENDIAN_LITTLE,
REGMAP_ENDIAN_NATIVE,
};
cache类型
关于缓冲,需要解释的是,在regmap中加入了一层缓存,减少IO操作次数,提供硬件操作效率。
/* An enum of all the supported cache types */
enum regcache_type {
REGCACHE_NONE, // 不使用
REGCACHE_RBTREE, //红黑树类型
REGCACHE_COMPRESSED,//压缩类型
REGCACHE_FLAT, //普通数据类型
};
在Linux 4.0 版本中,已经有 3 种缓存类型,分别是数据(flat)、LZO 压缩和红黑树(rbtree)。
- 数据好理解,是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。
- LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。
- 红黑树,它的特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。
注册并初始化regmap
regmap_init_i2c(struct i2c_client *i2c, struct regmap_config *config);
regmap_init_spi(struct spi_device *spi, strcut regmap_config *config);
regmap_init_mmio(struct device *dev, struct regmap_config *config);
regmap_init_spmi_base(struct spmi_device *dev, strcut regmap_config *config);
regmap_init_spmi_ext(struct spmi_device *dev, strcut regmap_config *config);
regmap_init_ac97(struct snd_ac97 *ac97, strcut regmap_config *config);
regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, int irq_base, struct regmap_irq_chip *chip, struct regmap_irq_chip_data **data);
注:
regmap_add_irq_chip
:关联后的regmap上注册 irq
使用regmap
配置和注册regmap实例后,我们就可以使用抽象接口来访问寄存器,摈弃之前那套繁琐的数据结构和函数api。
接口比较通俗,根据函数名称和入口参数即可知道函数功能。
接口分为2大类,设置类(与初始化配置信息不同)和访问类;
访问类根据访问过程又分为两种:
- 经过regmap cache,提高访问效率,对于写操作,待cache存在一定数据量或者超出时间后写入物理寄存器;但降低实时性
- 不经过regmap cache,对于写操作,立即写入物理寄存器,实时性好;对于读操作,则经过cache,减少拷贝时间
在初始化好regmap之后,就可以调用regmap提供的read/write/update等操作了。
int regmap_write(struct regmap *map, int reg, int val); //向单个reg写入val
int regmap_raw_write(struct regmap *map, int reg, void *val, size_t val_len); //向单个reg写入指定长度的数据,数据存放在val中
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val,size_t val_count); // 写多个reg
int regmap_multi_reg_write_bypassed(struct regmap *map, const struct reg_sequence *regs,int num_regs);// 直接写入reg,不经过regmap cache
int regmap_raw_write_async(struct regmap *map, unsigned int reg,const void *val, size_t val_len);//写多个reg,并立即刷新cache写入
int regmap_read(struct regmap *map, int reg, int *val); // 读取单个reg的数据到val中/
int regmap_raw_read(struct regmap *map, int reg, void *val, size_t val_len); // 读取单个reg中指定长度的数据
int regmap_bulk_read(struct regmap *map, int reg, void *val, size_t val_count); // 读取从reg开始之后val_count个寄存器的数据到val中
int regmap_update_bits(struct regmap *map, int reg, int mask, int val); // 更新reg寄存器中mask指定的位
int regmap_write_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);//写入寄存器值指定bit *
void regcache_cache_bypass(arizona->regmap, true); // 设置读写寄存器不通过cache模式而是bypass模式,读写立即生效,一般在audio等确保时序性驱动中用到
释放regmap
在驱动注销时一定要释放已注册的regmap。
void regmap_exit(struct regmap *map);
例子
/* 第一步配置信息 */
static const struct regmap_config regmap_config =
{
.reg_bits = 8,
.val_bits = 8,
.max_register = 255,
.cache_type = REGCACHE_NONE,
.volatile_reg = false,
};
/* 第二步,注册regmap实例 */
regmap = regmap_init_i2c(i2c_client, ®map_config);
/* 第三步,访问操作 */
regmap_raw_read(regmap, reg, &data, size);
总结
regmap方式将i2c的数据结构、传输api隐藏,使用者无需关心i2c内部实现,简化驱动开发过程,提高代码的复用性。
如果将该器件物理接口更换为spi,只需修改配置信息即可,寄存器访问过程无需更改。
在Linux驱动中使用regmap的更多相关文章
- Linux驱动中的EPROBE_DEFER是个啥
Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...
- linux驱动中printk的使用注意事项
今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】
转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)
completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion: DECLARE_CO ...
- 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)
static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .
- Linux驱动中常用的宏
.module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...
- Linux驱动中的platform总线分析
copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...
- Linux驱动中获取系统时间
最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...
- linux内核中的regmap是如何初始化的?
1. 内核版本 5.2.0 2. 请看devm_regmap_init_i2c (include/linux/regmap.h) /** * devm_regmap_init_i2c() - Init ...
- linux内核驱动中对字符串的操作【转】
转自:http://www.360doc.com/content/12/1224/10/3478092_255969530.shtml Linux内核中关于字符串的相关操作,首先包含头文件: #inc ...
随机推荐
- OLAP系列之分析型数据库clickhouse集群扩缩容(四)
一.集群缩容 1.1 下线节点 步骤:1.对外停止服务2.转移数据3.修改剩余节点配置4.通知客户端修改节点列表 # 修改90,91服务器配置文件 vim /etc/clickhouse-server ...
- K8s控制器---DaemonSet控制器(12)
一.DaemonSet控制器:概念.原理解读 1.1 DaemonSet概述 DaemonSet 控制器能够确保 k8s 集群所有的节点都运行一个相同的 pod 副本,当向 k8s 集群中增加 nod ...
- 鸿蒙HarmonyOS实战-ArkUI事件(键鼠事件)
前言 键鼠事件是指在计算机操作中,用户通过键盘和鼠标来与计算机进行交互的行为.常见的键鼠事件包括按下键盘上的键.移动鼠标.点击鼠标左键或右键等等.键鼠事件可以触发许多不同的操作,比如在文本编辑器中输入 ...
- 函数编程:强大的 Stream API
函数编程:强大的 Stream API 每博一文案 只要有人的地方,世界就不会是冰冷的,我们可以平凡,但绝对不可以平庸. ------ <平凡的世界> 人活着,就得随时准备经受磨难.他已经 ...
- InternLM2 Demo实操-书生浦语大模型实战营第二期第2节作业&大语言模型3
大语言模型-3.InternLM2 Demo实操 书生浦语大模型实战营第二期第二节作业 本文包括第二期实战营的第2课作业的相关内容.本来是想在学习笔记中给InetrnLM官方教程做做补充的,没想到官方 ...
- lodash中的深拷贝方法cloneDeep()
- ansible(15)--ansible的mount模块
1. mount模块 功能:管理被控端设备挂载: 主要参数如下: 参数 说明 src 本地或远程设备的路径 path 设备挂载至本地的路径 fstype 挂载的文件系统类型,xfs.nfs... op ...
- 题解:CF1956A Nene's Game
这道题其实挺有意思,多测里面还套了个多测. 思路就是用向量模拟删除过程,具体请看代码里的注释. #include <bits/stdc++.h> using namespace std; ...
- 抽丝剥茧:详述一次DevServer Proxy配置无效问题的细致排查过程
事情的起因是这样的,在一个已上线的项目中,其中一个包含登录和获取菜单的接口因响应时间较长,后端让我尝试未经服务转发的另一域名下的新接口,旧接口允许跨域请求,但新接口不允许本地访问(只允许发布测试/生产 ...
- 用 C 语言开发一门编程语言 — Q-表达式
目录 文章目录 目录 前文列表 Q-表达式 读取并存储输入 实现 Q-Expression 语法解析器 读取 Q-Expression 实现 Q-Expression 的函数 Head & T ...