linux设备驱动程序-i2c(2)-adapter和设备树的解析
linux設(shè)備驅(qū)動程序-i2c(2)-adapter和設(shè)備樹的解析
(注: 基于beagle bone green開發(fā)板,linux4.14內(nèi)核版本)
在本系列l(wèi)inux內(nèi)核i2c框架的前兩篇,分別講了:
linux設(shè)備驅(qū)動程序-i2c(0)-i2c設(shè)備驅(qū)動源碼實現(xiàn)
linux設(shè)備驅(qū)動程序-i2c(1):i2c總線的添加與實現(xiàn)
而在linux設(shè)備驅(qū)動程序--串行通信驅(qū)動框架分析中,講到linux內(nèi)核中串行通信驅(qū)動框架大體分為三層:
應(yīng)用層(用戶空間接口操作)
驅(qū)動層(包含總線、i2c-core的實現(xiàn)、以及總線的device和driver部分)
i2c硬件讀寫層
在上一章節(jié)我們講了整個總線的實現(xiàn)以及device和driver的匹配機制,這一章節(jié)我們要來講講i2c硬件讀寫層的實現(xiàn)。
i2c的適配器
我們來回顧一下,在本系列文章的第一章linux設(shè)備驅(qū)動程序-i2c(0)-i2c設(shè)備驅(qū)動源碼實現(xiàn)源碼中是怎么使用i2c和設(shè)備進行通信的呢?
1、首先,在總線的device部分,使用
struct i2c_adapter *adap = i2c_get_adapter(2)
這個接口,獲取一個struct i2c_adapter結(jié)構(gòu)體指針,并關(guān)聯(lián)到i2c_client中。
2、然后,在總線driver的probe部分,在/dev目錄下創(chuàng)建文件,并關(guān)聯(lián)對應(yīng)的file_operations結(jié)構(gòu)體。
3、在file_operations結(jié)構(gòu)體的write函數(shù)中,使用
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,u8 command,u8 value);
這個接口,直接向i2c設(shè)備中寫數(shù)據(jù)(command和value)。
4、 而第三點中i2c_client就是device源碼部分注冊到bus中的i2c_client,且包含對應(yīng)的adapter,同時包含i2c地址,設(shè)備名等信息。
如果再往深挖一層,會發(fā)現(xiàn)i2c_smbus_write_byte_data()的源碼實現(xiàn)是這樣的:
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控制器是一個可編程器件,用于生成i2c時序,實現(xiàn)數(shù)據(jù)收發(fā),且維護收發(fā)buf,對外提供寄存器接口。
硬件控制器這一類外設(shè)一般直接掛在CPU總線上,CPU可直接尋址訪問。
當主機需要通過i2c接口收發(fā)數(shù)據(jù)時,直接通過讀寫硬件i2c控制器寄存器即可,硬件控制器會將主機傳送過來的數(shù)據(jù)自動完成發(fā)送,接收到的數(shù)據(jù)直接放在buf中供主機讀取。
i2c_adapter的使用方式
(注:在源碼示例中,博主使用的i2c smbus的方式收發(fā)數(shù)據(jù),為了講解與理解的方便,這里i2c收發(fā)數(shù)據(jù)方式使用i2c_transfer接口,數(shù)據(jù)傳輸原理是一樣的)。
在linux設(shè)備驅(qū)動程序-i2c(0)-i2c設(shè)備驅(qū)動源碼實現(xiàn)源碼中,用戶只需要在驅(qū)動的device部分調(diào)用:
struct i2c_adapter *adap = i2c_get_adapter(2)
獲取一個i2c硬件控制器的描述結(jié)構(gòu)體,然后在通信時以這個結(jié)構(gòu)體為參數(shù)即可。
而i2c_get_adapter()接口的參數(shù)為硬件i2c控制器的num,通常,一個單板上不止一個i2c控制器,這個num指定了i2c控制器的序號。
在驅(qū)動程序源碼實現(xiàn)中,并不需要i2c_adapter的相關(guān)實現(xiàn),那么,可以確定的是,i2c底層數(shù)據(jù)收發(fā)已經(jīng)集成到了系統(tǒng)中,只需要用戶去選擇使用哪一個adapter即可。
那么,它到底是怎么工作的呢?
辦法很簡單,繼續(xù)跟蹤源碼即可,先看一下i2c數(shù)據(jù)發(fā)送函數(shù):
數(shù)據(jù)的收發(fā)都基于同一個操作:先填充一個i2c_msg結(jié)構(gòu)體,然后再使用i2c_tranfer函數(shù)發(fā)送數(shù)據(jù)。
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()的實現(xiàn):
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);
    ...
}
可以清楚地從源碼中看到,事實上,真正的發(fā)送數(shù)據(jù)的函數(shù)是這一個:adapter->algo->master_xfer(),那么這個adapter->algo->master_xfer函數(shù)指針是怎么被初始化的呢?
要了解這個,我們必須先了解一個硬件i2c控制器對應(yīng)的i2c_adapter是怎么被添加到系統(tǒng)中的。
從設(shè)備樹開始
(linux內(nèi)核版本:4.14,基于beagle bone開發(fā)板)
首先,系統(tǒng)在開始啟動時,bootloader將設(shè)備樹在內(nèi)存中的開始地址傳遞給內(nèi)核,內(nèi)核開始對設(shè)備樹進行解析,將設(shè)備樹中的子節(jié)點(不包括子節(jié)點的子節(jié)點)轉(zhuǎn)換成struct device_node節(jié)點,再由struct device_node節(jié)點轉(zhuǎn)換成struct platform_device節(jié)點,如果此時在系統(tǒng)中存在對應(yīng)的struct platform_driver節(jié)點,則調(diào)用driver驅(qū)動程序中的probe函數(shù),在probe函數(shù)中進行一系列的初始化。
struct i2c_adapter的注冊
正如前文所說,每一個struct i2c_adapter描述一個硬件i2c控制器,其中包含了對應(yīng)的硬件i2c控制器的數(shù)據(jù)收發(fā),同時,每一個struct i2c_adapter都直接集成在系統(tǒng)中,而不需要驅(qū)動開發(fā)者去實現(xiàn)(除非做芯片的驅(qū)動移植),那么,這個i2c adapter是怎樣被注冊到系統(tǒng)中的呢?
在beagle bone green這塊開發(fā)板中,有三個i2c控制器:i2c0~i2c2,我們以i2c0為例,查看系統(tǒng)的設(shè)備樹文件,可以找到對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對應(yīng)的compatible為"ti,omap4-i2c",如果你有了解過linux總線的匹配規(guī)則,就知道總線在對driver和device進行匹配時依據(jù)compatible字段進行匹配(當然會有其他匹配方式,但是設(shè)備樹主要使用這一種方式)。
依據(jù)這個規(guī)則,在整個linux源代碼中搜索"ti,omap4-i2c"這個字段就可以找到i2c0對應(yīng)的driver文件實現(xiàn)了。
在i2c-omap.c(不同平臺可能文件名不一樣,但是按照上面從設(shè)備樹開始找的方法可以找到對應(yīng)的源文件)中找到了這個compatible的定義:
static const struct of_device_id omap_i2c_of_match[] = {
    {
        .compatible = "ti,omap4-i2c",
        .data = &omap4_pdata,
    },
    ...
}
同時,根據(jù)platform driver驅(qū)動的規(guī)則,需要填充一個struct platform_driver結(jié)構(gòu)體,然后注冊到platform總線中,這樣才能完成platfrom bus的匹配,因此,我們也可以在同文件下找到相應(yīng)的初始化部分:
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匹配上,就會調(diào)用相應(yīng)的probe函數(shù),根據(jù).probe = omap_i2c_probe,我們再查看omap_i2c_probe函數(shù):
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函數(shù)中我們找到一個i2c_add_numbered_adapter()函數(shù),再跟蹤代碼到i2c_add_numbered_adapter():
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    ...  //assert part
    return __i2c_add_numbered_adapter(adap);
}
根據(jù)名稱可以隱約猜到了,這個函數(shù)的作用是添加一個i2c adapter到系統(tǒng)中,接著看:
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    ...
    return i2c_register_adapter(adap);
}
看到這里,整個i2c adapter的注冊就已經(jīng)清晰了,首先在設(shè)備樹中會有對應(yīng)的硬件i2c控制器子節(jié)點,在系統(tǒng)啟動時,系統(tǒng)將設(shè)備節(jié)點轉(zhuǎn)換成struct platform_device節(jié)點。
然后系統(tǒng)中注冊好的struct platform_driver相匹配,調(diào)用struct platform_driver驅(qū)動部分的probe函數(shù),完成一系列的初始化和設(shè)置,生成一個i2c adapter,注冊到系統(tǒng)中。
adapter->algo->master_xfer的初始化
整個流程adapter的添加流程已經(jīng)梳理完成,回到我們之前的問題:
用于實際通信中的adapter->algo->master_xfer函數(shù)指針是怎么被初始化的?
答案就在i2c適配器對應(yīng)的platform driver驅(qū)動部分,i2c-omap.c文件中:
在platform driver對應(yīng)的probe函數(shù)中:
static int omap_i2c_probe(struct platform_device *pdev)
{
    struct i2c_adapter	*adap;
    ...
    adap->algo = &omap_i2c_algo;
    r = i2c_add_numbered_adapter(adap);
    ...
}
在這個函數(shù)中對adapter的algo元素進行賦值,接著看omap_i2c_algo:
static const struct i2c_algorithm omap_i2c_algo = {
    .master_xfer	= omap_i2c_xfer,
    .functionality	= omap_i2c_func,
};
找到了相應(yīng)的.master_xfer成員,基本可以確定omap_i2c_xfer就是主機真正控制i2c收發(fā)數(shù)據(jù)的函數(shù),adapter->algo->master_xfer指針就是指向這個函數(shù):
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)));
    ...
}
繼續(xù)跟蹤omap_i2c_xfer_msg函數(shù):
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指針指向函數(shù)的實現(xiàn)就是操作i2c硬件控制器實現(xiàn)i2c的讀寫,這一部分不再細究,對應(yīng)芯片手冊的部分。
到這里,adapter的初始化與注冊到系統(tǒng)的流程就完成了。
好了,關(guān)于linux i2c總線的adapter注冊的討論就到此為止啦,如果朋友們對于這個有什么疑問或者發(fā)現(xiàn)有文章中有什么錯誤,歡迎留言
原創(chuàng)博客,轉(zhuǎn)載請注明出處!
祝各位早日實現(xiàn)項目叢中過,bug不沾身.
總結(jié)
以上是生活随笔為你收集整理的linux设备驱动程序-i2c(2)-adapter和设备树的解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Python10/22--面向对象编程/
- 下一篇: 全文搜索引擎 ElasticSearch
