目錄:
一、regmap子系統(tǒng)的引入
二、regmap子系統(tǒng)的內部實現
一、regmap子系統(tǒng)的引入
沒有regmap子系統(tǒng)之前
在內核代碼里,有成千上萬的以I2C / SPI為通訊接口的設備驅動。
以I2C設備為例
各種I2C接口的設備驅動都需要通過I2C子系統(tǒng)的API(i2c_transfer())來進行讀寫寄存器的操作。在對應的設備驅動中,I2C讀寫寄存器的操作通常會被封裝成2個靜態(tài)函數。
xxx_i2c_read_reg()/xxx_i2c_write_reg():
在沒有regmap抽象層之前,內核里充斥著大量類似的代碼,這些代碼都是多余的,使用I2C總線來讀寫寄存器的操作是有共性的,應該被抽象出來,形成一份統(tǒng)一的代碼。驅動開發(fā)人員應使用該抽象層的API來讀寫I2C設備的寄存器,然后把更多的精力放在驅動的邏輯設計上。
同樣的,沒有regmap子系統(tǒng)時,spi設備的寄存器讀寫操作也是散落在各個spi設備驅動。
i2c/spi drvier、i2c/spi device、i2c/spi subsystem之間的關系如下:
有了regmap子系統(tǒng)后
1. 同樣以I2C設備為例,先定義struct regmap_config:
struct regmap_config里包含了讀寫芯片寄存器所需所有信息,例如寄存器數據位寬、地址位寬等。
2. 將struct regmap_config注冊給regmap子系統(tǒng):
得到一個struct regmap對象,有了這個對象,就可以調用regmap子系統(tǒng)提供的用于讀寫寄存器的API了。
3. 使用regmap API讀寫寄存器:
int regmap_read(struct regmap *map, unsigned int reg,
? ? ? ? ? ? ? ? unsigned int *val);
int regmap_write(struct regmap *map, unsigned int reg,
? ? ? ? ? ? ? ? unsigned int val);
int regmap_update_bits(struct regmap *map, unsigned int reg,
? ? ? ? ? ? ? ? unsigned int mask, unsigned int val);
讀寫寄存器的操作已經抽象到regmap子系統(tǒng)里了,完整的API位于include/linux/regmap.h。
有了regmap后,i2c/spi drvier、i2c/spi device、i2c/spi subsystem之間的關系如下:
我在Linux-4.14內核里搜索了一下,目前大約有491個地方使用了regmap子系統(tǒng),將來可能會更多。
二、regmap子系統(tǒng)的內部實現
regmap的拓撲結構
在Linux-4.14內核中,regmap分為3層:
源碼文件:
regmap的緩存功能
在regmap子系統(tǒng)里,可以選擇是否使用緩存功能:
regmap內支持3 種緩存類型:數組(flat)、LZO 壓縮和紅黑樹(rbtree)
1) 數組是最簡單的緩存類型,當設備寄存器很少時,可以用這種類型來緩存寄存器值。
2) LZO(Lempel–Ziv–Oberhumer) 是 Linux 中經常用到的一種壓縮算法,Linux 編譯后就會用這個算法來壓縮。這個算法有 3 個特性:壓縮快,解壓不需要額外內存,壓縮比可以自動調節(jié)。在這里,你可以理解為一個數組緩存,套了一層壓縮,來節(jié)約內存。當設備寄存器數量中等時,可以考慮這種緩存類型。
3) 紅黑樹特性就是索引快,所以當設備寄存器數量比較大,或者對寄存器操作延時要求低時,就可以用這種緩存類型。
相關結構體:
struct regmap_config
struct regmap_config里包含了讀寫芯片寄存器所需的所有信息,它是regmap API里最核心的結構體。使用regmap子系統(tǒng)的第一步就是填充該結構體。這個結構體看著龐大,但是大多數情況下只要初始化幾個成員變量就足夠了,最簡單和常見的情況類似這樣:
struct regmap_config重要成員變量的含義如下:
struct regmap_bus
該結構體用于描述一種硬件總線的寄存器讀寫操作(a hardware bus for the register map infrastructure)。
regmap_bus并不是面向用戶的API,也就是說使用regmap子系統(tǒng)并不要求一定要了解該結構體,但是理解該結構體有助于我們了解regmap是如何抽象寄存器讀寫操作的。無論是i2c還是spi,在調用devm_regmap_init_[i2c|spi|...]將struct regmap_config注冊給regmap子系統(tǒng)后,在子系統(tǒng)內部都會根據remap_config里的配置信息找到對應的struct regmap_bus。
比如:
static struct regmap_bus regmap_i2c
[...]
static struct regmap_bus regmap_i2c_smbus_i2c_block
static struct regmap_bus regmap_spi
static struct regmap_bus regmap_spmi_base
[...]
有了適配芯片的 regmap_config 和 regmap_bus,regmap子系統(tǒng)就有了讀寫該芯片寄存器的能力,然后就會返回一個struct regmap供設備驅動來使用了,struct regmap類似一個大管家,包含了所有信息,負責統(tǒng)籌一切。
struct regmap_bus重要成員變量的含義如下:
定義一個regmap_bus結構體時不需要初始化其所有成員變量,例如某些 i2c 芯片的寄存器是16bit(2Byte)的,其使用的regmap_bus如下:
常用的regmap API
regmap提供出來的讀寫寄存器的API非常多,最常用的3個API如下:
可以猜測上述API會利用struct regmap_config + struct regmap_bus完成寄存器的讀寫操作。
簡單看下regmap_read()的實現:
regmap_read
? ? struct regmap *map
? ? map->reg_read(context, reg, val);
? ? ? ? _regmap_bus_reg_read
? ? ? ? ? ? map->bus->reg_read
總結
regmap 是在 Linux 3.1 加入進來的特性,其最初的目的是減少i2c/spi等設備驅動里的重復邏輯,提供一種通用的接口來操作芯片內寄存器,隨著版本的更迭,regmap 支持的bus越來越多,并且除了能做到統(tǒng)一的 寄存器I/O 接口,還可以在驅動和硬件 IC 之間做一層緩存,從而能減少底層 I/O 的操作次數。給我的感覺是:內核越來越強大了,但是學習的難度也越來越大,前路漫漫啊,求老司機帶路...
你和我各有一個蘋果,如果我們交換蘋果的話,我們還是只有一個蘋果。但當你和我各有一個想法,我們交換想法的話,我們就都有兩個想法了。如果你也對嵌入式系統(tǒng)開發(fā)有興趣,并且想和更多人互相交流學習的話,請關注我的公眾號:ESexpert,一起來學習吧,歡迎各種收藏/轉發(fā)/批評。

?