I2C總線架構(gòu) 之 I2C核心

引言

在之前的 《I2C總線架構(gòu) 之 設(shè)備驅(qū)動(dòng)》《I2C總線架構(gòu) 之 總線驅(qū)動(dòng)》 中一再提到i2c核心,本篇文章就總結(jié)一下i2c核心的主要功能。

分層思想

在之前的篇章中,大致能夠了解i2c核心功能主要是為i2c設(shè)備驅(qū)動(dòng)和i2c總線驅(qū)動(dòng)的注冊(cè)操作提供API。
這種設(shè)計(jì)的好處在于實(shí)現(xiàn):高內(nèi)聚,低耦合。i2c總線與i2c設(shè)備相互獨(dú)立,互無(wú)聯(lián)系;兩者都是通過(guò)調(diào)用i2c核心提供的API實(shí)現(xiàn)匹配。即兩者只與i2c核心有聯(lián)系,這樣也就可以實(shí)現(xiàn)一個(gè)固定i2c總線驅(qū)動(dòng)可以和任一符合條件的設(shè)備驅(qū)動(dòng)匹配。

具體API

這里對(duì)i2c總線驅(qū)動(dòng)和i2c設(shè)備驅(qū)動(dòng)進(jìn)行分析,關(guān)鍵在于分析i2c_client與i2c_driver如何實(shí)現(xiàn)匹配的。


i2c總線驅(qū)動(dòng):

  1. i2c適配器注冊(cè)
    在總線篇是利用 i2c_add_numbered_adapter注冊(cè)到內(nèi)核中:
 i2c_add_numbered_adapter --> __i2c_add_numbered_adapter --> i2c_register_adapter

static int i2c_register_adapter(struct i2c_adapter *adap) 
{
    /* 初始化鏈表節(jié)點(diǎn) */
    INIT_LIST_HEAD(&adap->userspace_clients);
……
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
    res = device_register(&adap->dev);
……
    i2c_scan_static_board_info(adap);

    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
……
}

(1) device_register注冊(cè)設(shè)備

int device_register(struct device *dev)
{
    device_initialize(dev);
    return device_add(dev);
}
int device_add(struct device *dev) 
{
    bus_add_device(dev);
    bus_probe_device(dev);
}
int bus_add_device(struct device *dev)
{
    /* 將設(shè)備添加到總線鏈表中 */
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
void bus_probe_device(struct device *dev)
{ 
    if (bus->p->drivers_autoprobe)
        device_initial_probe(dev);

    list_for_each_entry(sif, &bus->p->interfaces, node)
    if (sif->add_dev) sif->add_dev(dev, sif);
}

將設(shè)備信息的鏈表成員添加到對(duì)應(yīng)內(nèi)核鏈表中,即將device資源注冊(cè)到內(nèi)核中。

void device_initial_probe(struct device *dev)
{
    __device_attach(dev, true);
}
static int __device_attach(struct device *dev, bool allow_async)
{
    bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
}

i2c_scan_static_board_info //掃描硬件信息,生成與adapter總線號(hào)相同的i2c_client設(shè)備

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    list_for_each_entry(devinfo, &__i2c_board_list, list) { //遍歷i2c鏈表
        if (devinfo->busnum == adapter->nr
                && !i2c_new_device(adapter,
                        &devinfo->board_info))
    }
}
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    client->adapter = adap; //i2c_client綁定adapter

    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
    client->dev.of_node = info->of_node;
    client->dev.fwnode = info->fwnode;

    i2c_dev_set_name(adap, client);
    status = device_register(&client->dev);

    return client;

在生成一個(gè)新的i2c_client的同時(shí),會(huì)將adapter與i2c_client綁定。然后將i2c_client注冊(cè)到Linux。

bus_for_each_drv:匹配機(jī)制。遍歷整個(gè)driver鏈表,進(jìn)行i2c_client與i2c_driver的匹配工作

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
             void *data, int (*fn)(struct device_driver *, void *))
{
    while ((drv = next_driver(&i)) && !error)
        error = fn(drv, data);
}

fn指向的參數(shù)為_(kāi)_process_new_adapter

static int __process_new_adapter(struct device_driver *d, void *data)
{
    return i2c_do_add_adapter(to_i2c_driver(d), data);
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
                  struct i2c_adapter *adap)
{
    i2c_detect(adap, driver);
}

i2c_detect在設(shè)備驅(qū)動(dòng)注冊(cè)的時(shí)候也會(huì)使用到,這里也不過(guò)分追究,只需要知道其功能是為i2c_driver匹配合適的i2c_client。在文章后續(xù)i2c設(shè)備驅(qū)動(dòng)會(huì)詳細(xì)追一下這個(gè)函數(shù)。

總結(jié):

  • 總線驅(qū)動(dòng)最終會(huì)調(diào)用i2c-core接口i2c_register_adapter,生成i2c_client設(shè)備,并且綁定總線號(hào)相同的adapter。
  • 歸根到底并不是對(duì)adapter的注冊(cè),而是對(duì)綁定過(guò)adapter的i2c_client的注冊(cè)。由于adapter已經(jīng)依附給i2c_client,所以可以通過(guò)注冊(cè)過(guò)的i2c_client找到對(duì)應(yīng)的adapter。
  • i2c_client的數(shù)據(jù)信息就是i2c硬件設(shè)備的抽象,其數(shù)據(jù)主要記錄在設(shè)備樹(shù)中:
&i2c2 {
    clock_frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c2>;
    status = "okay";

    sii902x: sii902x@39 {
          compatible = "SiI,sii902x";
          pinctrl-names = "default";
          reset-names="sii902x";
          pinctrl-0 = <&pinctrl_sii902x>;
          reg = <0x39>;
          status = "okay";    
         };

    gt9xx@5d {
         compatible = "goodix,gt9xx";
         reg = <0x5d>;
         status = "okay";
         interrupt-parent = <&gpio1>;
         interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_tsc_reset &pinctrl_touchscreen_int>;
    };
};

以上設(shè)備樹(shù)代碼記錄i2c2總線下掛接的2個(gè)硬件設(shè)備信息;
linux在啟動(dòng)uboot的時(shí)候,會(huì)解析設(shè)備樹(shù),自動(dòng)調(diào)用i2c_register_board_info函數(shù)將硬件設(shè)備注冊(cè)進(jìn)設(shè)備鏈表中。故在總線驅(qū)動(dòng)注冊(cè)時(shí),會(huì)掃描設(shè)備鏈表域,并獲取鏈表中的數(shù)據(jù)生成對(duì)應(yīng)的與adapter總線相同的i2c_client。

  • 在i2c_client生成完畢之后,就會(huì)開(kāi)始遍歷i2c_driver鏈表,查找是否與i2c_client相匹配的i2c_driver。如果有匹配成功,則最終會(huì)進(jìn)入driver->probe。

i2c設(shè)備驅(qū)動(dòng):

  1. 設(shè)備驅(qū)動(dòng)注冊(cè):
    通過(guò)i2c_add_driver注冊(cè):
#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;
      /* 初始化鏈表節(jié)點(diǎn) */
    INIT_LIST_HEAD(&driver->clients);

    res = driver_register(&driver->driver);
    i2c_for_each_dev(driver, __process_new_driver);
}

看一下driver_registe如何注冊(cè)驅(qū)動(dòng):

int driver_register(struct device_driver *drv)
{
    /* 檢查總線上是否已經(jīng)存在需要注冊(cè)的驅(qū)動(dòng)名,如果已經(jīng)被注冊(cè),就會(huì)報(bào)錯(cuò) */
    other = driver_find(drv->name, drv->bus);
    if (other) {
        printk(KERN_ERR "Error: Driver '%s' is already registered, "
            "aborting...\n", drv->name);
        return -EBUSY;
    }

    ret = bus_add_driver(drv);
    //ret = driver_add_groups(drv, drv->groups);
}
int bus_add_driver(struct device_driver *drv)
{
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

    error = driver_attach(drv);
   /* 在 /sys 添加節(jié)點(diǎn) */
    error = driver_create_file(drv, &driver_attr_uevent);
    error = driver_add_groups(drv, bus->drv_groups);
}
int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,
             void *data, int (*fn)(struct device *, void *))
{
    while ((dev = next_device(&i)) && !error)
        error = fn(dev, data);
}

fn指向__driver_attach

static int __driver_attach(struct device *dev, void *data)
{
    /* 匹配driver與device */
    ret = driver_match_device(drv, dev);
    /* 匹配成功后,進(jìn)入driver_probe_device */
    if (!dev->driver)
        driver_probe_device(drv, dev);
}
static inline int driver_match_device(struct device_driver *drv,
                      struct device *dev)
{
    /* 調(diào)用macth函數(shù) */
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

這里調(diào)用i2c-core封裝的接口

struct bus_type i2c_bus_type = {
    .name       = "i2c",
    .match      = i2c_device_match,
    .probe      = i2c_device_probe,
    .remove     = i2c_device_remove,
    .shutdown   = i2c_device_shutdown,
};

看下匹配函數(shù):

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    /* 通過(guò)id_table名進(jìn)行匹配 */
    return i2c_match_id(driver->id_table, client) != NULL;
}
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    if (strcmp(client->name, id->name) == 0)
}

發(fā)現(xiàn)是通過(guò)client->name與id->name匹配。匹配成功后,再追driver_probe_device(drv, dev)

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    ret = really_probe(dev, drv);
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
    } else if (drv->probe) {
        ret = drv->probe(dev);
    }
}
struct bus_type i2c_bus_type = {
    .name       = "i2c",
    .match      = i2c_device_match,
    .probe      = i2c_device_probe,
    .remove     = i2c_device_remove,
    .shutdown   = i2c_device_shutdown,
};
static int i2c_device_probe(struct device *dev)
{
    status = driver->probe(client, i2c_match_id(driver->id_table, client));
}

小結(jié): 通過(guò)以上分析,基本上可以了解到i2c_driver與i2c_client匹配機(jī)制的流程。在i2c_driver驅(qū)動(dòng)注冊(cè)時(shí),會(huì)先將過(guò)of_device_id與client->name進(jìn)行匹配,當(dāng)發(fā)現(xiàn)匹配成功后,會(huì)進(jìn)入driver->probe。

回到第一步,再看i2c_for_each_dev

int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{
    res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,
             void *data, int (*fn)(struct device *, void *))
{
    while ((dev = next_device(&i)) && !error)
        error = fn(dev, data);
}

fn指向__process_new_driver

static int __process_new_driver(struct device *dev, void *data)
{
    return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
                  struct i2c_adapter *adap)
{
    i2c_detect(adap, driver);
}
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
    address_list = driver->address_list;
    temp_client->adapter = adapter;

    for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
        err = i2c_detect_address(temp_client, driver);
    }
}
static int i2c_detect_address(struct i2c_client *temp_client,
                  struct i2c_driver *driver)
{
    /* 檢測(cè)是否忙狀態(tài) */
    i2c_check_addr_busy(adapter, addr);

    /* 說(shuō)明i2c總線存在, 調(diào)用detect再次確認(rèn) */
    err = driver->detect(temp_client, &info);

    /* 確認(rèn)完畢,生成i2c設(shè)備i2c_client ,并添加到鏈表中*/
    client = i2c_new_device(adapter, &info);
    list_add_tail(&client->detected, &driver->clients);
}

看似driver_register已經(jīng)把注冊(cè)的操作實(shí)現(xiàn)完了,i2c_for_each_dev似乎有點(diǎn)多余。其實(shí)i2c_for_each_dev是另一種注冊(cè)方式,也可稱為動(dòng)態(tài)注冊(cè),在driver_register注冊(cè)失敗,就會(huì)啟動(dòng)此函數(shù)進(jìn)行注冊(cè)。其實(shí)現(xiàn)功能與driver_register相同,不同的是i2c_client的數(shù)據(jù)是存在驅(qū)動(dòng)文件中,因此子函數(shù)的注冊(cè)也需要i2c_driver的配合。

傳輸數(shù)據(jù): i2c_transfer
此函數(shù)是設(shè)備驅(qū)動(dòng)文件用來(lái)與i2c器件傳輸i2c協(xié)議數(shù)據(jù)的,封裝好的i2c數(shù)據(jù)傳輸API。

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)
{
    unsigned long orig_jiffies;
    int ret, try;

    if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
        return -EOPNOTSUPP;

    /* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets
     * enabled.  This is an efficient way of keeping the for-loop from
     * being executed when not needed.
     */
    if (static_key_false(&i2c_trace_msg)) {
        int i;
        for (i = 0; i < num; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_read(adap, &msgs[i], i);
            else
                trace_i2c_write(adap, &msgs[i], i);
    }

    /* Retry automatically on arbitration loss */
    orig_jiffies = jiffies;
    for (ret = 0, try = 0; try <= adap->retries; try++) {
        /*調(diào)用adapter下的i2c數(shù)據(jù)傳輸函數(shù)*/
        ret = adap->algo->master_xfer(adap, msgs, num); 
        if (ret != -EAGAIN)
            break;
        if (time_after(jiffies, orig_jiffies + adap->timeout))
            break;
    }

    if (static_key_false(&i2c_trace_msg)) {
        int i;
        for (i = 0; i < ret; i++)
            if (msgs[i].flags & I2C_M_RD)   
                trace_i2c_reply(adap, &msgs[i], i);
        trace_i2c_result(adap, i, ret);
    }

    return ret;
}

在之前i2c總線驅(qū)動(dòng)中,能發(fā)現(xiàn)master_xfer指向的傳輸函數(shù)

static int i2c_imx_probe(struct platform_device *pdev)
{
    i2c_imx->adapter.algo       = &i2c_imx_algo;
}
static struct i2c_algorithm i2c_imx_algo = {
    .master_xfer    = i2c_imx_xfer,
    .functionality  = i2c_imx_func,
};

i2c_imx_xfer具體實(shí)現(xiàn)i2c數(shù)據(jù)傳輸?shù)募?xì)節(jié)。這個(gè)就屬于i2c字節(jié)傳輸?shù)倪壿媽?shí)現(xiàn),不過(guò)多分析,代碼很容易理解。

static int i2c_imx_xfer(struct i2c_adapter *adapter,
                        struct i2c_msg *msgs, int num)
{
    unsigned int i, temp;
    int result;
    bool is_lastmsg = false;
    bool enable_runtime_pm = false;
    struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);

    dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);


    if (!pm_runtime_enabled(i2c_imx->adapter.dev.parent)) {
        pm_runtime_enable(i2c_imx->adapter.dev.parent);
        enable_runtime_pm = true;
    }

    result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent);
    if (result < 0)
        goto out;

    /* Start I2C transfer */
    result = i2c_imx_start(i2c_imx);
    if (result) {
        if (i2c_imx->adapter.bus_recovery_info) {
            i2c_recover_bus(&i2c_imx->adapter);
            result = i2c_imx_start(i2c_imx);
        }
    }

    if (result)
        goto fail0;

    /* read/write data */
    for (i = 0; i < num; i++) {
        if (i == num - 1)
            is_lastmsg = true;

        if (i) {
            dev_dbg(&i2c_imx->adapter.dev,
                "<%s> repeated start\n", __func__);
            temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
            temp |= I2CR_RSTA;
            imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
            result = i2c_imx_bus_busy(i2c_imx, 1);
            if (result)
                goto fail0;
        }
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> transfer message: %d\n", __func__, i);
        /* write/read data */
#ifdef CONFIG_I2C_DEBUG_BUS
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> CONTROL: IEN=%d, IIEN=%d, MSTA=%d, MTX=%d, TXAK=%d, RSTA=%d\n",
            __func__,
            (temp & I2CR_IEN ? 1 : 0), (temp & I2CR_IIEN ? 1 : 0),
            (temp & I2CR_MSTA ? 1 : 0), (temp & I2CR_MTX ? 1 : 0),
            (temp & I2CR_TXAK ? 1 : 0), (temp & I2CR_RSTA ? 1 : 0));
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> STATUS: ICF=%d, IAAS=%d, IBB=%d, IAL=%d, SRW=%d, IIF=%d, RXAK=%d\n",
            __func__,
            (temp & I2SR_ICF ? 1 : 0), (temp & I2SR_IAAS ? 1 : 0),
            (temp & I2SR_IBB ? 1 : 0), (temp & I2SR_IAL ? 1 : 0),
            (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),
            (temp & I2SR_RXAK ? 1 : 0));
#endif
        if (msgs[i].flags & I2C_M_RD)
            result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
        else {
            if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
                result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
            else
                result = i2c_imx_write(i2c_imx, &msgs[i]);
        }
        if (result)
            goto fail0;
    }

fail0:
    /* Stop I2C transfer */
    i2c_imx_stop(i2c_imx);

    pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);
    pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);

out:
    if (enable_runtime_pm)
        pm_runtime_disable(i2c_imx->adapter.dev.parent);

    dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
        (result < 0) ? "error" : "success msg",
            (result < 0) ? result : num);
    return (result < 0) ? result : num;
}

由以上邏輯可以發(fā)現(xiàn),不同平臺(tái)提供的i2c總線驅(qū)動(dòng)不一樣,所以其具體的i2c數(shù)據(jù)傳輸?shù)膶?shí)現(xiàn)也會(huì)不一樣,但是對(duì)于具體的i2c設(shè)備驅(qū)動(dòng)是沒(méi)有任何影響的。

總結(jié):

對(duì)于i2c驅(qū)動(dòng)核心的整個(gè)分析,就主要是這些。期間查閱了很多優(yōu)質(zhì)的博客以及內(nèi)核代碼,才有了本篇文章。此篇文章整理的還有些瑕疵,但是整體的邏輯流程基本上是完整的,如有錯(cuò)誤,歡迎指出!

參考:
https://blog.csdn.net/ordinaryjoe/article/details/6678871
https://blog.csdn.net/chihunqi5879/article/details/79971034
https://blog.csdn.net/weixin_42462202/article/details/91128914
代碼路徑:
linux/include/linux/i2c.h
linux/driver/i2c

如需技術(shù)交流,可關(guān)注公眾號(hào)“開(kāi)源519”。

開(kāi)源519.jpg

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容