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

    总结

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

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

    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    CAP.dll of dotnetcore
    GRPC 高性能 RPC 框架, 服务间通讯
    REST Client
    ERP Export
    ERP Update DataSource
    knockout js
    面试题目总结
    JavaScript Objiects and Prototypes
    PLS-00172: string literal too long
    字符串连接性能
  • 原文地址:https://www.cnblogs.com/schips/p/using_regmap_in_linux_kernel.html
Copyright © 2020-2023  润新知