4-Linux i2c system

題圖:i2c時序

Linux i2c system

I2C總線是由PHILIPS公司開發(fā)的兩線式串行總線,每個連接到總線的器件都可以通過唯一的地址和主機主機進(jìn)行通訊,主機可以作為主機發(fā)送器或主機接收器;串行的8位雙向數(shù)據(jù)傳輸位速率在標(biāo)準(zhǔn)模式下可達(dá)100kbit/s,快速模式下可達(dá)400kbit/s,高速模式下可達(dá)3.4Mbit/s。

1.Linux下I2c的驅(qū)動架構(gòu)


如下:

I2C框架

從圖中可以觀察到I2C系統(tǒng)的整個框架,從下往上;

  • Hardware:
    所連接的外界設(shè)備i2c-device,可以連接多個device,但掛載在同一條總線上的設(shè)備其地址必須不一樣。

  • Kernel:
    Kernel主要分為三個模塊,adapter、core、client,driver;

  • Adapter:
    CPU與I2C的接口,簡單解釋就是CPU要控制一個i2c-device,那你CPU自身要能支持i2c總線功能,所以adapter主要的工作就是初始化i2c,向cpu申請i2c總線,一條i2c總線對于一個adapter;adapter會根據(jù)cpu的變化有一些變化,所以需要用戶根據(jù)cpu體現(xiàn)自行實現(xiàn),一般放在driver/i2c/buses/文件夾下面(例如:i2c-s3c2410.c)。由于adapter屬于片上資源,所以驅(qū)動的實現(xiàn)采用platform驅(qū)動模型。

  • Core:
    core為i2c驅(qū)動的核心,位于driver/i2c/i2c-core.c中;core上要跟client打好關(guān)系,為其提供client的注冊申請,下要為adapter提供總線的申請注冊,不過這一部分kernel基本已經(jīng)為我們所實現(xiàn),我們要做的就是有選擇性的調(diào)用其中的函數(shù)。

  • Client:
    client即為外界設(shè)備i2c-device,一個device即是一個client,不同的i2c-device注冊client方式基本一致,一般在平臺設(shè)備arch/arm/mach-xxx里面添加info信息,也可以通過dts來進(jìn)行設(shè)置。需要注意的是在同一個總線上的多個設(shè)備,其設(shè)備地址肯定不一樣,設(shè)備reg的讀寫等也需要根據(jù)設(shè)備來定制。

  • Driver:
    driver可以看成是client的驅(qū)動,一個client對應(yīng)一個driver,這部分的驅(qū)動由用戶來實現(xiàn),一般放在driver/i2c/或driver/i2c/chips/文件夾下實現(xiàn)。

  • User-space:
    即應(yīng)用層,我們做這么多驅(qū)動的工作,主要就是要用它,所以要有特定的應(yīng)用程序來控制。

上面將i2c driver的整體框架做了一個介紹,熟悉i2cdriver的框架,下面我們就將需要由用戶自行實現(xiàn)的adapter、client和driver進(jìn)行簡單說明,里面會交叉講解一些i2c-core中的內(nèi)容。

2.I2c adapter and client


adapter的主要目的就是通過platform總線將片上i2c設(shè)備和i2c驅(qū)動綁定在一起,當(dāng)i2c的platform驅(qū)動成功后,會執(zhí)行platform_driver結(jié)構(gòu)下對應(yīng)的probe函數(shù),對于如何實現(xiàn)i2c的platform設(shè)備驅(qū)動,查看Linux platform system

probe函件里面會調(diào)用adapter的添加,在i2c-core.c中,為我們提供了i2c_add_adapter()i2c_add_numbered_adapter()兩個函數(shù)來實現(xiàn)adapter接口注冊。

這兩個函數(shù)有什么區(qū)別呢,來具體探究下,下面為這兩個函數(shù)的源碼,位于i2c-core.c中:

int i2c_add_adapter(struct i2c_adapter *adapter)
{
    int id, res = 0;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh */
    res = idr_get_new_above(&i2c_adapter_idr, adapter,
                __i2c_first_dynamic_bus_num, &id);
    mutex_unlock(&core_lock);

    if (res < 0) {
        if (res == -EAGAIN)
            goto retry;
        return res;
    }

    adapter->nr = id;
    return i2c_register_adapter(adapter);
}
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    int id;
    int status;

    if (adap->nr == -1) /* -1 means dynamically assign bus id */
        return i2c_add_adapter(adap);
    if (adap->nr & ~MAX_IDR_MASK)
        return -EINVAL;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh;
     * we need the "equal to" result to force the result
     */
    status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
    if (status == 0 && id != adap->nr) {
        status = -EBUSY;
        idr_remove(&i2c_adapter_idr, id);
    }
    mutex_unlock(&core_lock);
    if (status == -EAGAIN)
        goto retry;

    if (status == 0)
        status = i2c_register_adapter(adap);
    return status;
}

可以觀察到,不管兩個函數(shù)上面在執(zhí)行什么操作,都是為了得到最終的i2c_adapter結(jié)構(gòu)體,最后通過調(diào)用i2c_register_adapter()函數(shù)來實現(xiàn)adapter的注冊的。

其實函數(shù)的上部分是為了處理i2c的總線號,對于i2c_add_adapter()而言,它使用的是動態(tài)總線號,即由系統(tǒng)給其自動分配一個總線號,而i2c_add_numbered_adapter()則是自己指定總線號,如果這個總線號非法或者是被占用,就會注冊失敗。

既然有動態(tài)總線和指定總線,那就去尋找總線號的指定在哪邊實現(xiàn),總線號的執(zhí)行是在平臺設(shè)備里面指定的,在平臺設(shè)備arch/arm/mach-xxx里面我們會調(diào)用i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)函數(shù)來提供總線信息和client信息。

  • 第一個參數(shù)busnum即總線號。

  • 第二個參數(shù)info為client的name和id,這邊client的name和id要和driver下面的id、name一致。

上面所提到的使用i2c_register_board_info來提供總線號和注冊client,在Linux3.14以后的版本也可以有dts來實現(xiàn)。

3.I2c client driver


一個client一般對應(yīng)一個driver,driver比較簡單,一般就是通過i2c_add_driver來進(jìn)行添加,i2c_del_driver進(jìn)行卸載,當(dāng)i2c_driver.id_table信息與i2c_board_info所指向的設(shè)備(或者設(shè)備樹中的節(jié)點)匹配成功,則執(zhí)行i2c_driver.probe(),從而獲得對應(yīng)的i2c client。

如下例子:

static const struct i2c_device_id pca9555_id[] = {
    {"pca9555", 0x27},
    {}
};
static struct i2c_driver pca9555_driver = {
    .driver = {
        .name = "pca9555",
    },
    .probe = pca9555_probe,
    .remove = pca9555_remove,
    .id_table = pca9555_id,
    .suspend = pca9555_suspend,
    .resume = pca9555_resume,
    .shutdown = pca9555_shutdown,
};

static int __init pca9555_init(void)
{
    return i2c_add_driver(&pca9555_driver);
}

static void __exit pca9555_exit(void)
{
    i2c_del_driver(&pca9555_driver);
}

module_init(pca9555_init);
module_exit(pca9555_exit);

獲得i2c client后,就是進(jìn)行實現(xiàn)client的讀寫函數(shù)了,在i2c-core.c里面有提供例如i2c_smbus_read_byte_datai2c_smbus_write_byte_data這類的函數(shù),需要查看芯片手冊的時序找對應(yīng)的實現(xiàn)函數(shù)即可。

調(diào)試驅(qū)動的時候最常用的方法就是使用printk來進(jìn)行交互,進(jìn)行定位、驗證,但是要在哪邊進(jìn)行printk呢,個人覺得調(diào)試i2c驅(qū)動有一個地方一定要進(jìn)行printk,那就是位于i2c-core.c下的i2c_match_id()函數(shù),如下:

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        printk("client->name:%s\n",client->name);
        printk("id->name:%s\n",id->name);
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}

看函數(shù)名稱就知道,該函數(shù)用來匹配device和client的name是否一致。通過在此處打印信息,我們可以觀察到,這個函數(shù)會被調(diào)用到兩次,一次是進(jìn)行注冊adapter時,另一次就是尋找驅(qū)動的時候,如果遇到問題是,我們只有查看打印的信息大概就能發(fā)現(xiàn)問題的存在點了。

Linux i2c system的分析就到這邊,有感悟時會持續(xù)會更新。

注:以上內(nèi)容都是本人在學(xué)習(xí)過程積累的一些心得,難免會有參考到其他文章的一些知識,如有侵權(quán),請及時通知我,我將及時刪除或標(biāo)注內(nèi)容出處,如有錯誤之處也請指出,進(jìn)行探討學(xué)習(xí)。文章只是起一個引導(dǎo)作用,詳細(xì)的數(shù)據(jù)解析內(nèi)容還請查看Linux相關(guān)教程,感謝您的查閱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容