LINUX裝置驅動i2c架構分析(一)

來源:互聯網
上載者:User

I2c是philips提出的外設匯流排.I2C只有兩條線,一條串列資料線:SDA,一條是時鐘線SCL.正因為這樣,它方便了工程人員的布線.另外,I2C是一種多主機控制匯流排.它和USB匯流排不同,USB是基於master-slave機制,任何裝置的通
一:前言
I2c是philips提出的外設匯流排.I2C只有兩條線,一條串列資料線:SDA,一條是時鐘線SCL.正因為這樣,它方便了工程人員的布線.另外,I2C是一種多主機控制匯流排.它和USB匯流排不同,USB是基於master-slave機制,任何裝置的通訊必須由主機發起才可以.而I2C 是基於multi master機制.一同匯流排上可允許多個master.關於I2C協議的知識,這裡不再贅述.可自行下載spec閱讀即可.

架構如下:

如所示,每一條I2C對應一個adapter.在kernel中,每一個adapter提供了一個描述的結構(struct i2c_adapter),也定義了adapter支援的操作(struct i2c_adapter).再通過i2c core層將i2c裝置與i2c adapter關聯起來.
這個圖只是提供了一個大概的架構.在下面的程式碼分析中,從下至上的來分析這個架構圖.以下的程式碼分析是基於linux 2.6.26.分析的代碼基本位於: linux-2.6.26.3/drivers/i2c/位置

三:adapter註冊
在kernel中提供了兩個adapter註冊介面,分別為i2c_add_adapter()和 i2c_add_numbered_adapter().由於在系統中可能存在多個adapter,因為將每一條I2C匯流排對應一個編號,下文中稱為 I2C匯流排號.這個匯流排號的PCI中的匯流排號不同.它和硬體無關,只是軟體上便於區分而已.
對於i2c_add_adapter()而言,它使用的是動態匯流排號,即由系統給其分析一個匯流排號,而 i2c_add_numbered_adapter()則是自己指定匯流排號,如果這個匯流排號非法或者是被佔用,就會註冊失敗.
分別來看一下這兩個函數的代碼:
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);
}
在這裡涉及到一個idr結構.idr結構本來是為了配合page cache中的radix tree而設計的.在這裡我們只需要知道,它是一種高效的搜尋樹,且這個樹預先存放了一些記憶體.避免在記憶體不夠的時候出現問題.所在,在往idr中插入結構的時候,首先要調用idr_pre_get()為它預留足夠的空閑記憶體,然後再調用idr_get_new_above()將結構插入idr中,該函數以參數的形式返回一個id.以後憑這個id就可以在idr中找到相對應的結構了.對這個資料結構操作不太理解的可以查閱本站<< linux檔案系統之檔案的讀寫>>中有關radix tree的分析.
注意一下idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的參數的含義,它是將adapter結構插入到i2c_adapter_idr中,存放位置的id必須要大於或者等於 __i2c_first_dynamic_bus_num,
然後將對應的id號存放在adapter->nr中.調用i2c_register_adapter(adapter)對這個 adapter進行進一步註冊.

看一下另外一人註冊函數: i2c_add_numbered_adapter( ),如下所示:
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    int id;
    int status;

    if (adap->nr & ~MAX_ID_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;
}
對比一下就知道差別了,在這裡它已經指定好了adapter->nr了.如果分配的id不和指定的相等,便返回錯誤.

過一步跟蹤i2c_register_adapter().代碼如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0, dummy;

    mutex_init(&adap->bus_lock);
    mutex_init(&adap->clist_lock);
    INIT_LIST_HEAD(&adap->clients);

    mutex_lock(&core_lock);

    /* Add the adapter to the driver core.
     * If the parent pointer is not set up,
     * we add this adapter to the host bus.
     */
    if (adap->dev.parent == NULL) {
        adap->dev.parent = &platform_bus;
        pr_debug("I2C adapter driver [%s] forgot to specify "
             "physical device ", adap->name);
    }
    sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
    adap->dev.release = &i2c_adapter_dev_release;
    adap->dev.class = &i2c_adapter_class;
    res = device_register(&adap->dev);
    if (res)
        goto out_list;

    dev_dbg(&adap->dev, "adapter [%s] registered ", adap->name);

    /* create pre-declared device nodes for new-style drivers */
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    /* let legacy drivers scan this bus for matching devices */
    dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
                 i2c_do_add_adapter);

out_unlock:
    mutex_unlock(&core_lock);
    return res;

out_list:
    idr_remove(&i2c_adapter_idr, adap->nr);
    goto out_unlock;
}
首先對adapter和adapter中內嵌的struct device結構進行必須的初始化.之後將adapter內嵌的struct device註冊.
在這裡注意一下adapter->dev的初始化.它的類別為i2c_adapter_class,如果沒有父結點,則將其父結點設為 platform_bus.adapter->dev的名字為i2c + 匯流排號.
測試一下:
[eric@mochow i2c]$ cd /sys/class/i2c-adapter/
[eric@mochow i2c-adapter]$ ls
i2c-0
可以看到,在我的PC上,有一個I2C adapter,看下詳細資料:
[eric@mochow i2c-adapter]$ tree
.
`-- i2c-0
    |-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0
    |-- name
    |-- subsystem -> ../../../class/i2c-adapter
    `-- uevent

3 directories, 2 files
可以看到,該adapter是一個PCI裝置.
繼續往下看:
之後,在注釋中看到,有兩種類型的driver,一種是new-style drivers,另外一種是legacy drivers
New-style drivers是在2.6近版的kernel加入的.它們最主要的區別是在adapter和i2c driver的匹配上.

3.1: new-style 形式的adapter註冊
對於第一種,也就是new-style drivers,將相關代碼再次列出如下:
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);
如果adap->nr 小於__i2c_first_dynamic_bus_num的話,就會進入到i2c_scan_static_board_info().
結合我們之前分析的adapter的兩種註冊分式: i2c_add_adapter()所分得的匯流排號肯會不會小於__i2c_first_dynamic_bus_num.只有 i2c_add_numbered_adapter()才有可能滿足:
(adap->nr < __i2c_first_dynamic_bus_num)
而且必須要調用i2c_register_board_info()將板子上的I2C裝置資訊預先註冊時才會更改 __i2c_first_dynamic_bus_num的值.在x86上只沒有使用i2c_register_board_info()的.因此,x86平台上的分析可以忽略掉new-style driver的方式.不過,還是詳細分析這種情況下.
首先看一下i2c_register_board_info(),如下:
int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;

    mutex_lock(&__i2c_board_lock);

    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)
        __i2c_first_dynamic_bus_num = busnum + 1;

    for (status = 0; len; len--, info++) {
        struct i2c_devinfo *devinfo;

        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo! ");
            status = -ENOMEM;
            break;
        }

        devinfo->busnum = busnum;
        devinfo->board_info = *info;
        list_add_tail(&devinfo->list, &__i2c_board_list);
    }

    mutex_unlock(&__i2c_board_lock);

    return status;
}
這個函數比較簡單, struct i2c_board_info用來表示I2C裝置的一些情況,比如所在的匯流排.名稱,地址,中斷號等.最後,這些資訊會被存放到 __i2c_board_list鏈表.

跟蹤i2c_scan_static_board_info():代碼如下:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    struct i2c_devinfo *devinfo;

    mutex_lock(&__i2c_board_lock);
    list_for_each_entry(devinfo, &__i2c_board_list, list) {
        if (devinfo->busnum == adapter->nr
                && !i2c_new_device(adapter,
                        &devinfo->board_info))
            printk(KERN_ERR "i2c-core: can't create i2c%d-%04x ",
                i2c_adapter_id(adapter),
                devinfo->board_info.addr);
    }
    mutex_unlock(&__i2c_board_lock);
}

該函數遍曆掛在__i2c_board_list鏈表上面的i2c裝置的資訊,也就是我們在啟動的時候指出的i2c裝置的資訊.
如果指定裝置是位於adapter所在的I2C匯流排上,那麼,就調用i2c_new_device().代碼如下:
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client   *client;
    int         status;

    client = kzalloc(sizeof *client, GFP_KERNEL);
    if (!client)
        return NULL;

    client->adapter = adap;

    client->dev.platform_data = info->platform_data;
    device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);

    client->flags = info->flags & ~I2C_CLIENT_WAKE;
    client->addr = info->addr;
    client->irq = info->irq;

    strlcpy(client->name, info->type, sizeof(client->name));

    /* a new style driver may be bound to this device when we
     * return from this function, or any later moment (e.g. maybe
     * hotplugging will load the driver module). and the device
     * refcount model is the standard driver model one.
     */
    status = i2c_attach_client(client);
    if (status < 0) {
        kfree(client);
        client = NULL;
    }
    return client;
}

我們又遇到了一個新的結構:struct i2c_client,不要被這個結構嚇倒了,其實它就是一個嵌入struct device的I2C裝置的封裝.它和我們之前遇到的struct usb_device結構的作用是一樣的.
首先,在clinet裡儲存該裝置的相關訊息.特別的, client->adapter指向了它所在的adapter.
特別的,clinet->name為info->name.也是指定好了的.
一切初始化完成之後,便會調用i2c_attach_client( ).看這個函數的字面意思,是將clinet關聯起來.到底怎麼樣關聯呢?繼續往下看:
int i2c_attach_client(struct i2c_client *client)
{
    struct i2c_adapter *adapter = client->adapter;
    int res = 0;

    //初始化client內嵌的dev結構
    //父結點為所在的adapter,所在bus為i2c_bus_type
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;

    //如果client已經指定了driver,將driver和內嵌的dev關聯起來
    if (client->driver)
        client->dev.driver = &client->driver->driver;
    //指定了driver, 但不是newstyle的
    if (client->driver && !is_newstyle_driver(client->driver)) {
        client->dev.release = i2c_client_release;
        client->dev.uevent_suppress = 1;
    } else
        client->dev.release = i2c_client_dev_release;

    //clinet->dev的名稱
    snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),
        "%d-%04x", i2c_adapter_id(adapter), client->addr);
    //將內嵌的dev註冊
    res = device_register(&client->dev);
    if (res)
        goto out_err;

    //將clinet鏈到adapter->clients中
    mutex_lock(&adapter->clist_lock);
    list_add_tail(&client->list, &adapter->clients);
    mutex_unlock(&adapter->clist_lock);

    dev_dbg(&adapter->dev, "client [%s] registered with bus id %s ",
        client->name, client->dev.bus_id);
    //如果adapter->cleinet_reqister存在,就調用它
    if (adapter->client_register) {
        if (adapter->client_register(client)) {
            dev_dbg(&adapter->dev, "client_register "
                "failed for client [%s] at 0x%02x ",
                client->name, client->addr);
        }
    }

    return 0;

out_err:
    dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "
        "(%d) ", client->name, client->addr, res);
    return res;
}

參考上面添加的注釋,應該很容易理解這段代碼了,就不加詳細分析了.這個函數的名字不是i2c_attach_client()麼?怎麼沒看到它的關係過程呢?
這是因為:在代碼中設定了client->dev所在的bus為i2c_bus_type .以為只需要有bus為i2c_bus_type的driver註冊,就會產生probe了.這個過程呆後面分析i2c driver的時候再來詳細分析.

3.2: legacy形式的adapter註冊
Legacy形式的adapter註冊程式碼片段如下:
    dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
                 i2c_do_add_adapter);
這段代碼遍曆掛在i2c_bus_type上的驅動,然後對每一個驅動和adapter調用i2c_do_add_adapter().
代碼如下:
static int i2c_do_add_adapter(struct device_driver *d, void *data)
{
    struct i2c_driver *driver = to_i2c_driver(d);
    struct i2c_adapter *adap = data;

    if (driver->attach_adapter) {
        /* We ignore the return code; if it fails, too bad */
        driver->attach_adapter(adap);
    }
    return 0;
}
該函數很簡單,就是調用driver的attach_adapter()介面.
到此為止,adapter的註冊已經分析完了.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.