背景

在学习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, &regmap_config); /* 第三步,访问操作 */
regmap_raw_read(regmap, reg, &data, size);

总结

regmap方式将i2c的数据结构、传输api隐藏,使用者无需关心i2c内部实现,简化驱动开发过程,提高代码的复用性。

如果将该器件物理接口更换为spi,只需修改配置信息即可,寄存器访问过程无需更改。

在Linux驱动中使用regmap的更多相关文章

  1. Linux驱动中的EPROBE_DEFER是个啥

    ​Linux kernel 驱动中,有不少驱动会引用到 EPROBE_DEFER 这个错误号.比如下面这个例子,对 devm_gpiod_get 的返回值进行判断,如果有错误且错误号不是 -EPRBO ...

  2. linux驱动中printk的使用注意事项

    今天在按键驱动中增加printk(KERN_INFO "gpio_keys_gpio_isr()\n");在驱动加载阶段可以输出调试信息,但驱动加载起来后的信息,在串口端看不到输出 ...

  3. Linux驱动中completion接口浅析(wait_for_complete例子,很好)【转】

    转自:http://blog.csdn.net/batoom/article/details/6298267 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用 ...

  4. Linux驱动中completion接口浅析(wait_for_complete例子,很好)

    completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成.可以利用下面的宏静态创建completion:                          DECLARE_CO ...

  5. 为什么linux驱动中变量或者函数都用static修饰?(知乎问题)

    static定义的全局变量 或函数也只能作用于当前的文件. 世界硬件厂商太多,定义static为了防止变量或 函数 重名,定义成static, 就算不同硬件驱动中的 变更 或函数重名了也没关系 .

  6. Linux驱动中常用的宏

    .module_i2c_driver(adxl34x_driver)展开为 static int __int adxl34x_driver_init(void) { return i2c_regist ...

  7. Linux驱动中的platform总线分析

    copy from :https://blog.csdn.net/fml1997/article/details/77622860 概述 从Linux2.6内核起,引入一套新的驱动管理和注册机制:pl ...

  8. Linux驱动中获取系统时间

    最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...

  9. linux内核中的regmap是如何初始化的?

    1. 内核版本 5.2.0 2. 请看devm_regmap_init_i2c (include/linux/regmap.h) /** * devm_regmap_init_i2c() - Init ...

  10. linux内核驱动中对字符串的操作【转】

    转自:http://www.360doc.com/content/12/1224/10/3478092_255969530.shtml Linux内核中关于字符串的相关操作,首先包含头文件: #inc ...

随机推荐

  1. K8s包管理工具Helm v3(19)

    一.Helm概述 官网:https://v3.helm.sh/zh/docs/ https://helm.sh/ helm 官方的 chart 站点: https://hub.kubeapps.com ...

  2. HouseParty原创故事全角色关系及主线剧情介绍(最新版)

    这是原创故事的主要的角色的主线及支线剧情的介绍及攻略和注意事项等. 这里的图比哔哩哔哩上的图清楚一点,哔哩哔哩同号:宅猫君007 以上是全角色的关系图 最新版本的游戏下载就在我的网站上:https:/ ...

  3. 自定义Lua解析器管理器-------演化脚本V0.5

    [3]自定义Lua解析器管理器-------演化脚本V0.5 方便我们在项目中使用Lua解析方法,我们封装管理一个lua解析器,管理LuaState的方法执行. 解析器脚本: using LuaInt ...

  4. golang 常用操作

    golang 获取切片 slice 第一个 最后一个 元素 复合数据类型切片通常用作Go中索引数据的口语结构. 该类型[]intSlice是具有类型为integer的元素的切片. len函数用于获取切 ...

  5. fastposter v2.7.1 紧急发布 电商海报编辑器

    fastposter v2.7.1 紧急发布 电商海报编辑器 fastposter海报生成器,电商海报编辑器,电商海报设计器,fast快速生成海报 海报制作 海报开发.二维码海报,图片海报,分享海报, ...

  6. 数据库—ER模型概念设计

    文章目录 ER模型的概念 如何画ER图 ER图转换为关系数据库 ER模型的概念 实体 画图时用方形表示 属性 用椭圆形表示 关系 用菱形表示 主键(主码) 在主属性下面画划线 外键(外码) 这里一般是 ...

  7. (图形界面)Dbever连接MySQL8 报错mysql8 安装The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone.

    问题原因是没有设置时区,这个在Mysql8中会有 解决方式: 在这个位置加上UTC时区就可以了. IDEA添加方式 设置Advanced的allowPublicKeyRetrieval为true 同时 ...

  8. 4G EPS 的架构模型

    目录 文章目录 目录 前文列表 EPS 的架构 EPS 的架构模型 E-UTRAN UE eNodeB EPC MME(移动性管理) SGW(本地移动性锚点) PGW(业务锚点) HSS(用户认证及鉴 ...

  9. 利用FileReader进行二进制文件传输

    一.读取本地二进制文件,上传(数据库文件为例) 二进制文件读取的时候应当直接读取成字节数组,以免在调试时造成误解.比如数据库文件里面的有些字段是utf8编码,因此,采用utf-8编码读出来也能看到一些 ...

  10. uniapp uni-number-box组件 步长为1,还能输入小数思路分享

    正常情况,输入了步长为1,是无法在输入小数的.需求是要能输入一位小数,但如果直接步长设为0.1,又不能按1这样递增,输入数量上用起来肯定很麻烦. 于是我就想了一个折中方法,步长设为:1.01,然后值改 ...