在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,只需修改配置信息即可,寄存器訪問過程無需更改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM