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

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

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

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

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

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

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

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結構體。spa

三、在file_operations結構體的write函數中,使用指針

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

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

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

若是再往深挖一層,會發現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不沾身.

相關文章
相關標籤/搜索