linux設備驅動程序-i2c(2)-adapter和設備樹的解析


linux設備驅動程序-i2c(2)-adapter和設備樹的解析

(注: 基於beagle bone green開發板,linux4.14內核版本)

在本系列linux內核i2c框架的前兩篇,分別講了:
linux設備驅動程序-i2c(0)-i2c設備驅動源碼實現
linux設備驅動程序-i2c(1):i2c總線的添加與實現

而在linux設備驅動程序--串行通信驅動框架分析中,講到linux內核中串行通信驅動框架大體分為三層:

  • 應用層(用戶空間接口操作)
  • 驅動層(包含總線、i2c-core的實現、以及總線的device和driver部分)
  • i2c硬件讀寫層

在上一章節我們講了整個總線的實現以及device和driver的匹配機制,這一章節我們要來講講i2c硬件讀寫層的實現。

i2c的適配器

我們來回顧一下,在本系列文章的第一章linux設備驅動程序-i2c(0)-i2c設備驅動源碼實現源碼中是怎么使用i2c和設備進行通信的呢?
1、首先,在總線的device部分,使用

struct i2c_adapter *adap = i2c_get_adapter(2)

這個接口,獲取一個struct i2c_adapter結構體指針,並關聯到i2c_client中。

2、然后,在總線driver的probe部分,在/dev目錄下創建文件,並關聯對應的file_operations結構體。

3、在file_operations結構體的write函數中,使用

s32 i2c_smbus_write_byte_data(const struct i2c_client *client,u8 command,u8 value);

這個接口,直接向i2c設備中寫數據(command和value)。

4、 而第三點中i2c_client就是device源碼部分注冊到bus中的i2c_client,且包含對應的adapter,同時包含i2c地址,設備名等信息。

如果再往深挖一層,會發現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控制器是一個可編程器件,用於生成i2c時序,實現數據收發,且維護收發buf,對外提供寄存器接口。

硬件控制器這一類外設一般直接掛在CPU總線上,CPU可直接尋址訪問。

當主機需要通過i2c接口收發數據時,直接通過讀寫硬件i2c控制器寄存器即可,硬件控制器會將主機傳送過來的數據自動完成發送,接收到的數據直接放在buf中供主機讀取。

i2c_adapter的使用方式

(注:在源碼示例中,博主使用的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的注冊

正如前文所說,每一個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->algo->master_xfer的初始化

整個流程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不沾身.


免責聲明!

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



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