zoukankan      html  css  js  c++  java
  • 在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/
  • 相关阅读:
    Hdu 1257 最少拦截系统
    Hdu 1404 Digital Deletions
    Hdu 1079 Calendar Game
    Hdu 1158 Employment Planning(DP)
    Hdu 1116 Play on Words
    Hdu 1258 Sum It Up
    Hdu 1175 连连看(DFS)
    Hdu 3635 Dragon Balls (并查集)
    Hdu 1829 A Bug's Life
    Hdu 1181 变形课
  • 原文地址:https://www.cnblogs.com/schips/p/using_regmap_in_linux_kernel.html
Copyright © 2011-2022 走看看