美文网首页Linux驱动
I2C总线架构 之 I2C核心

I2C总线架构 之 I2C核心

作者: 拂去尘世尘 | 来源:发表于2020-08-21 08:47 被阅读0次

    引言

    在之前的 《I2C总线架构 之 设备驱动》《I2C总线架构 之 总线驱动》 中一再提到i2c核心,本篇文章就总结一下i2c核心的主要功能。

    分层思想

    在之前的篇章中,大致能够了解i2c核心功能主要是为i2c设备驱动和i2c总线驱动的注册操作提供API。
    这种设计的好处在于实现:高内聚,低耦合。i2c总线与i2c设备相互独立,互无联系;两者都是通过调用i2c核心提供的API实现匹配。即两者只与i2c核心有联系,这样也就可以实现一个固定i2c总线驱动可以和任一符合条件的设备驱动匹配。

    具体API

    这里对i2c总线驱动和i2c设备驱动进行分析,关键在于分析i2c_client与i2c_driver如何实现匹配的。


    i2c总线驱动:

    1. i2c适配器注册
      在总线篇是利用 i2c_add_numbered_adapter注册到内核中:
     i2c_add_numbered_adapter --> __i2c_add_numbered_adapter --> i2c_register_adapter
    
    static int i2c_register_adapter(struct i2c_adapter *adap) 
    {
        /* 初始化链表节点 */
        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注册设备

    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)
    {
        /* 将设备添加到总线链表中 */
        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);
    }
    

    将设备信息的链表成员添加到对应内核链表中,即将device资源注册到内核中。

    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总线号相同的i2c_client设备

    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;
    

    在生成一个新的i2c_client的同时,会将adapter与i2c_client绑定。然后将i2c_client注册到Linux。

    bus_for_each_drv:匹配机制。遍历整个driver链表,进行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指向的参数为__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在设备驱动注册的时候也会使用到,这里也不过分追究,只需要知道其功能是为i2c_driver匹配合适的i2c_client。在文章后续i2c设备驱动会详细追一下这个函数。

    总结:

    • 总线驱动最终会调用i2c-core接口i2c_register_adapter,生成i2c_client设备,并且绑定总线号相同的adapter。
    • 归根到底并不是对adapter的注册,而是对绑定过adapter的i2c_client的注册。由于adapter已经依附给i2c_client,所以可以通过注册过的i2c_client找到对应的adapter。
    • i2c_client的数据信息就是i2c硬件设备的抽象,其数据主要记录在设备树中:
    &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>;
        };
    };
    

    以上设备树代码记录i2c2总线下挂接的2个硬件设备信息;
    linux在启动uboot的时候,会解析设备树,自动调用i2c_register_board_info函数将硬件设备注册进设备链表中。故在总线驱动注册时,会扫描设备链表域,并获取链表中的数据生成对应的与adapter总线相同的i2c_client。

    • 在i2c_client生成完毕之后,就会开始遍历i2c_driver链表,查找是否与i2c_client相匹配的i2c_driver。如果有匹配成功,则最终会进入driver->probe。

    i2c设备驱动:

    1. 设备驱动注册:
      通过i2c_add_driver注册:
    #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;
          /* 初始化链表节点 */
        INIT_LIST_HEAD(&driver->clients);
    
        res = driver_register(&driver->driver);
        i2c_for_each_dev(driver, __process_new_driver);
    }
    

    看一下driver_registe如何注册驱动:

    int driver_register(struct device_driver *drv)
    {
        /* 检查总线上是否已经存在需要注册的驱动名,如果已经被注册,就会报错 */
        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 添加节点 */
        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);
        /* 匹配成功后,进入driver_probe_device */
        if (!dev->driver)
            driver_probe_device(drv, dev);
    }
    
    static inline int driver_match_device(struct device_driver *drv,
                          struct device *dev)
    {
        /* 调用macth函数 */
        return drv->bus->match ? drv->bus->match(dev, drv) : 1;
    }
    

    这里调用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,
    };
    

    看下匹配函数:

    static int i2c_device_match(struct device *dev, struct device_driver *drv)
    {
        /* 通过id_table名进行匹配 */
        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)
    }
    

    发现是通过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));
    }
    

    小结: 通过以上分析,基本上可以了解到i2c_driver与i2c_client匹配机制的流程。在i2c_driver驱动注册时,会先将过of_device_id与client->name进行匹配,当发现匹配成功后,会进入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)
    {
        /* 检测是否忙状态 */
        i2c_check_addr_busy(adapter, addr);
    
        /* 说明i2c总线存在, 调用detect再次确认 */
        err = driver->detect(temp_client, &info);
    
        /* 确认完毕,生成i2c设备i2c_client ,并添加到链表中*/
        client = i2c_new_device(adapter, &info);
        list_add_tail(&client->detected, &driver->clients);
    }
    

    看似driver_register已经把注册的操作实现完了,i2c_for_each_dev似乎有点多余。其实i2c_for_each_dev是另一种注册方式,也可称为动态注册,在driver_register注册失败,就会启动此函数进行注册。其实现功能与driver_register相同,不同的是i2c_client的数据是存在驱动文件中,因此子函数的注册也需要i2c_driver的配合。

    传输数据: i2c_transfer
    此函数是设备驱动文件用来与i2c器件传输i2c协议数据的,封装好的i2c数据传输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++) {
            /*调用adapter下的i2c数据传输函数*/
            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总线驱动中,能发现master_xfer指向的传输函数

    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具体实现i2c数据传输的细节。这个就属于i2c字节传输的逻辑实现,不过多分析,代码很容易理解。

    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;
    }
    

    由以上逻辑可以发现,不同平台提供的i2c总线驱动不一样,所以其具体的i2c数据传输的实现也会不一样,但是对于具体的i2c设备驱动是没有任何影响的。

    总结:

    对于i2c驱动核心的整个分析,就主要是这些。期间查阅了很多优质的博客以及内核代码,才有了本篇文章。此篇文章整理的还有些瑕疵,但是整体的逻辑流程基本上是完整的,如有错误,欢迎指出!

    参考:
    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

    如需技术交流,可关注公众号“开源519”

    开源519.jpg

    相关文章

      网友评论

        本文标题:I2C总线架构 之 I2C核心

        本文链接:https://www.haomeiwen.com/subject/bjjjdktx.html