(注: 基於beagle bone green開發板,linux4.14內核版本)html
在本系列linux內核i2c框架的前兩篇,分別講了:
linux設備驅動程序-i2c(0)-i2c設備驅動源碼實現
linux設備驅動程序-i2c(1):i2c總線的添加與實現node
而在linux設備驅動程序--串行通訊驅動框架分析中,講到linux內核中串行通訊驅動框架大致分爲三層:linux
在上一章節咱們講了整個總線的實現以及device和driver的匹配機制,這一章節咱們要來說講i2c硬件讀寫層的實現。編程
咱們來回顧一下,在本系列文章的第一章linux設備驅動程序-i2c(0)-i2c設備驅動源碼實現源碼中是怎麼使用i2c和設備進行通訊的呢?
一、首先,在總線的device部分,使用框架
struct i2c_adapter *adap = i2c_get_adapter(2)
這個接口,獲取一個struct i2c_adapter結構體指針,並關聯到i2c_client中。函數
二、而後,在總線driver的probe部分,在/dev目錄下建立文件,並關聯對應的file_operations結構體。指針
三、在file_operations結構體的write函數中,使用code
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,u8 command,u8 value);
這個接口,直接向i2c設備中寫數據(command和value)。orm
四、 而第三點中i2c_client就是device源碼部分註冊到bus中的i2c_client,且包含對應的adapter,同時包含i2c地址,設備名等信息。htm
若是再往深挖一層,會發現i2c_smbus_write_byte_data()的源碼實現是這樣的:
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value) { union i2c_smbus_data data; data.byte = value; return i2c_smbus_xfer(client->adapter, client->addr, client->flags, I2C_SMBUS_WRITE, command, I2C_SMBUS_BYTE_DATA, &data); } EXPORT_SYMBOL(i2c_smbus_write_byte_data);
能夠看到,在i2c smbus中主導通訊的就是這個adapter。
那麼,這個i2c_adapter究竟是什麼東西呢?
事實上,一個硬件i2c控制器由i2c_adapter描述。
硬件i2c控制器是一個可編程器件,用於生成i2c時序,實現數據收發,且維護收發buf,對外提供寄存器接口。
硬件控制器這一類外設通常直接掛在CPU總線上,CPU可直接尋址訪問。
當主機須要經過i2c接口收發數據時,直接經過讀寫硬件i2c控制器寄存器便可,硬件控制器會將主機傳送過來的數據自動完成發送,接收到的數據直接放在buf中供主機讀取。
(注:在源碼示例中,博主使用的i2c smbus的方式收發數據,爲了講解與理解的方便,這裏i2c收發數據方式使用i2c_transfer接口,數據傳輸原理是同樣的)。
在linux設備驅動程序-i2c(0)-i2c設備驅動源碼實現源碼中,用戶只須要在驅動的device部分調用:
struct i2c_adapter *adap = i2c_get_adapter(2)
獲取一個i2c硬件控制器的描述結構體,而後在通訊時以這個結構體爲參數便可。
而i2c_get_adapter()接口的參數爲硬件i2c控制器的num,一般,一個單板上不止一個i2c控制器,這個num指定了i2c控制器的序號。
在驅動程序源碼實現中,並不須要i2c_adapter的相關實現,那麼,能夠肯定的是,i2c底層數據收發已經集成到了系統中,只須要用戶去選擇使用哪個adapter便可。
那麼,它究竟是怎麼工做的呢?
辦法很簡單,繼續跟蹤源碼便可,先看一下i2c數據發送函數:
數據的收發都基於同一個操做:先填充一個i2c_msg結構體,而後再使用i2c_tranfer函數發送數據。
struct i2c_msg xfer[2]; xfer[0].addr = i2c->addr; xfer[0].flags = 0; xfer[0].len = reg_size; xfer[0].buf = (void *)reg; xfer[1].addr = i2c->addr; xfer[1].flags = I2C_M_NOSTART; xfer[1].len = val_size; xfer[1].buf = (void *)val; i2c_transfer(i2c->adapter, xfer, 2);
而後跟蹤i2c_transfer()的實現:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { ... ret = __i2c_transfer(adap, msgs, num); ... } int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { ... ret = adap->algo->master_xfer(adap, msgs, num); ... }
能夠清楚地從源碼中看到,事實上,真正的發送數據的函數是這一個:adapter->algo->master_xfer(),那麼這個adapter->algo->master_xfer函數指針是怎麼被初始化的呢?
要了解這個,咱們必須先了解一個硬件i2c控制器對應的i2c_adapter是怎麼被添加到系統中的。
(linux內核版本:4.14,基於beagle bone開發板)
首先,系統在開始啓動時,bootloader將設備樹在內存中的開始地址傳遞給內核,內核開始對設備樹進行解析,將設備樹中的子節點(不包括子節點的子節點)轉換成struct device_node節點,再由struct device_node節點轉換成struct platform_device節點,若是此時在系統中存在對應的struct platform_driver節點,則調用driver驅動程序中的probe函數,在probe函數中進行一系列的初始化。
正如前文所說,每個struct i2c_adapter描述一個硬件i2c控制器,其中包含了對應的硬件i2c控制器的數據收發,同時,每個struct i2c_adapter都直接集成在系統中,而不須要驅動開發者去實現(除非作芯片的驅動移植),那麼,這個i2c adapter是怎樣被註冊到系統中的呢?
在beagle bone green這塊開發板中,有三個i2c控制器:i2c0~i2c2,咱們以i2c0爲例,查看系統的設備樹文件,能夠找到對i2c0的描述:
__symbols__ { i2c0 = "/ocp/i2c@44e0b000"; } ... i2c@44e0b000 { compatible = "ti,omap4-i2c"; ... baseboard_eeprom@50 { compatible = "atmel,24c256"; reg = <0x50>; #address-cells = <0x1>; #size-cells = <0x1>; phandle = <0x282>; baseboard_data@0 { reg = <0x0 0x100>; phandle = <0x23c>; }; }; } ...
能夠看到,i2c0對應的compatible爲"ti,omap4-i2c",若是你有了解過linux總線的匹配規則,就知道總線在對driver和device進行匹配時依據compatible字段進行匹配(固然會有其餘匹配方式,可是設備樹主要使用這一種方式)。
依據這個規則,在整個linux源代碼中搜索"ti,omap4-i2c"這個字段就能夠找到i2c0對應的driver文件實現了。
在i2c-omap.c(不一樣平臺可能文件名不同,可是按照上面從設備樹開始找的方法能夠找到對應的源文件)中找到了這個compatible的定義:
static const struct of_device_id omap_i2c_of_match[] = { { .compatible = "ti,omap4-i2c", .data = &omap4_pdata, }, ... }
同時,根據platform driver驅動的規則,須要填充一個struct platform_driver結構體,而後註冊到platform總線中,這樣才能完成platfrom bus的匹配,所以,咱們也能夠在同文件下找到相應的初始化部分:
static struct platform_driver omap_i2c_driver = { .probe = omap_i2c_probe, .remove = omap_i2c_remove, .driver = { .name = "omap_i2c", .pm = OMAP_I2C_PM_OPS, .of_match_table = of_match_ptr(omap_i2c_of_match), }, }; static int __init omap_i2c_init_driver(void) { return platform_driver_register(&omap_i2c_driver); }
既然platform總線的driver和device匹配上,就會調用相應的probe函數,根據.probe = omap_i2c_probe,咱們再查看omap_i2c_probe函數:
static int omap_i2c_probe(struct platform_device *pdev) { ... //get resource from dtb node ... //config i2c0 via set corresponding regs i2c_add_numbered_adapter(adap); ... //deinit part }
在probe函數中咱們找到一個i2c_add_numbered_adapter()函數,再跟蹤代碼到i2c_add_numbered_adapter():
int i2c_add_numbered_adapter(struct i2c_adapter *adap) { ... //assert part return __i2c_add_numbered_adapter(adap); }
根據名稱能夠隱約猜到了,這個函數的做用是添加一個i2c adapter到系統中,接着看:
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap) { ... return i2c_register_adapter(adap); }
看到這裏,整個i2c adapter的註冊就已經清晰了,首先在設備樹中會有對應的硬件i2c控制器子節點,在系統啓動時,系統將設備節點轉換成struct platform_device節點。
而後系統中註冊好的struct platform_driver相匹配,調用struct platform_driver驅動部分的probe函數,完成一系列的初始化和設置,生成一個i2c adapter,註冊到系統中。
整個流程adapter的添加流程已經梳理完成,回到咱們以前的問題:
用於實際通訊中的adapter->algo->master_xfer函數指針是怎麼被初始化的?
答案就在i2c適配器對應的platform driver驅動部分,i2c-omap.c文件中:
在platform driver對應的probe函數中:
static int omap_i2c_probe(struct platform_device *pdev) { struct i2c_adapter *adap; ... adap->algo = &omap_i2c_algo; r = i2c_add_numbered_adapter(adap); ... }
在這個函數中對adapter的algo元素進行賦值,接着看omap_i2c_algo:
static const struct i2c_algorithm omap_i2c_algo = { .master_xfer = omap_i2c_xfer, .functionality = omap_i2c_func, };
找到了相應的.master_xfer成員,基本能夠肯定omap_i2c_xfer就是主機真正控制i2c收發數據的函數,adapter->algo->master_xfer指針就是指向這個函數:
static int omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { ... omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); ... }
繼續跟蹤omap_i2c_xfer_msg函數:
static int omap_i2c_xfer_msg(struct i2c_adapter *adap,struct i2c_msg *msg, int stop) { ... omap_i2c_write_reg(omap, OMAP_I2C_CNT_REG,omap->buf_len); ... omap_i2c_write_reg(omap, OMAP_I2C_BUF_REG, w); ... }
從部分紅員能夠看出,adapter->algo->master_xfer指針指向函數的實現就是操做i2c硬件控制器實現i2c的讀寫,這一部分再也不細究,對應芯片手冊的部分。
到這裏,adapter的初始化與註冊到系統的流程就完成了。
好了,關於linux i2c總線的adapter註冊的討論就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言
原創博客,轉載請註明出處!
祝各位早日實現項目叢中過,bug不沾身.