i2c子系统学习总结

这几天在学习一下tlv320aix3106音频芯片的移植,所以接触了一下i2c子系统,做一个简单的学习总结。先来一张大图
i2c子系统学习总结_第1张图片

i2c子系统3个组成部分

1.i2c核心:主要提供i2c总线驱动(i2c_adapter)和设备驱动(i2c_driver)的注册注销方法,i2c的通信方法(algorithm)。
2.i2c总线驱动:,主要包含i2c适配器i2c_adapter,algorithm数据结构,以及产生i2c通信的函数(主要跟cpu的i2c有关)。
3.i2c设备驱动:主要包含i2c_driver跟i2c_client,具体的设备实现(主要跟具体的外设有关,比如在/sound/soc/corecs/tlv320aic3x.c 内就有i2c_driver aic3x_i2c_driver设备)。

i2c总线的实现

i2c总线主要涉及到i2c_adapter、algorithm结构以及对应的i2c通信方法的实现。一般这部分代码是存在的了,在/driver/i2c/busses对应的芯片的i2c驱动下。
以下用i2c-davinci.c作为分析。

static struct platform_driver davinci_i2c_driver = {
    .probe      = davinci_i2c_probe,
    .remove     = davinci_i2c_remove,
    .driver     = {
        .name   = "i2c_davinci",
        .owner  = THIS_MODULE,
        .pm = davinci_i2c_pm_ops,
    },
};

/* I2C may be needed to bring up other drivers */
static int __init davinci_i2c_init_driver(void)
{
    return platform_driver_register(&davinci_i2c_driver);
}
subsys_initcall(davinci_i2c_init_driver);

static void __exit davinci_i2c_exit_driver(void)
{
    platform_driver_unregister(&davinci_i2c_driver);
}
module_exit(davinci_i2c_exit_driver);

MODULE_AUTHOR("Texas Instruments India");
MODULE_DESCRIPTION("TI DaVinci I2C bus adapter");
MODULE_LICENSE("GPL");

一个很常规的platform_driver驱动代码,我们在板级文件只有注册一个名为“i2c_davinci”的设备就会调用.probe = davinci_i2c_probe,这个函数,接下来看一下davinci_i2c_probe函数的实现。

static int davinci_i2c_probe(struct platform_device *pdev)
{
    struct davinci_i2c_dev *dev;
    struct i2c_adapter *adap;
    struct resource *mem, *irq, *ioarea;
    int r;

    /* NOTE: driver uses the static register mapping */
    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!mem) {
        dev_err(&pdev->dev, "no mem resource?\n");
        return -ENODEV;
    }

    irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (!irq) {
        dev_err(&pdev->dev, "no irq resource?\n");
        return -ENODEV;
    }

    ioarea = request_mem_region(mem->start, resource_size(mem),
                    pdev->name);
    if (!ioarea) {
        dev_err(&pdev->dev, "I2C region already claimed\n");
        return -EBUSY;
    }

    dev = kzalloc(sizeof(struct davinci_i2c_dev), GFP_KERNEL);
    if (!dev) {
        r = -ENOMEM;
        goto err_release_region;
    }

    init_completion(&dev->cmd_complete);
#ifdef CONFIG_CPU_FREQ
    init_completion(&dev->xfr_complete);
#endif
    dev->dev = get_device(&pdev->dev);
    dev->irq = irq->start;
    platform_set_drvdata(pdev, dev);

    dev->clk = clk_get(&pdev->dev, NULL);
    if (IS_ERR(dev->clk)) {
        r = -ENODEV;
        goto err_free_mem;
    }
    clk_enable(dev->clk);

    dev->base = ioremap(mem->start, resource_size(mem));
    if (!dev->base) {
        r = -EBUSY;
        goto err_mem_ioremap;
    }

    i2c_davinci_init(dev);

    r = request_irq(dev->irq, i2c_davinci_isr, 0, pdev->name, dev);
    if (r) {
        dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq);
        goto err_unuse_clocks;
    }

    r = i2c_davinci_cpufreq_register(dev);
    if (r) {
        dev_err(&pdev->dev, "failed to register cpufreq\n");
        goto err_free_irq;
    }

    adap = &dev->adapter;
    i2c_set_adapdata(adap, dev);
    adap->owner = THIS_MODULE;
    adap->class = I2C_CLASS_HWMON;
    strlcpy(adap->name, "DaVinci I2C adapter", sizeof(adap->name));
    adap->algo = &i2c_davinci_algo;
    adap->dev.parent = &pdev->dev;
    adap->timeout = DAVINCI_I2C_TIMEOUT;

    adap->nr = pdev->id;
    r = i2c_add_numbered_adapter(adap);
    if (r) {
        dev_err(&pdev->dev, "failure adding adapter\n");
        goto err_free_irq;
    }

    return 0;

err_free_irq:
    free_irq(dev->irq, dev);
err_unuse_clocks:
    iounmap(dev->base);
err_mem_ioremap:
    clk_disable(dev->clk);
    clk_put(dev->clk);
    dev->clk = NULL;
err_free_mem:
    platform_set_drvdata(pdev, NULL);
    put_device(&pdev->dev);
    kfree(dev);
err_release_region:
    release_mem_region(mem->start, resource_size(mem));

    return r;
}

前面的代码跟主要跟cpu的i2c初始化有关,包括获取内存,中断号以及进行地址映射以及相对于硬件初始化i2c_davinci_init(dev);,这些我们不做太详细关注。
主要在这个函数做了i2c_adapter数据结构的初始化跟增加。
接下来我们进入i2c_adapter增加函数i2c_add_numbered_adapter(adap);

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

进入i2c_adapter的注册函数i2c_register_adapter(adap)

static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0;

    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p))) {
        res = -EAGAIN;
        goto out_list;
    }

    /* Sanity checks */
    if (unlikely(adap->name[0] == '\0')) {
        pr_err("i2c-core: Attempt to register an adapter with "
               "no name!\n");
        return -EINVAL;
    }
    if (unlikely(!adap->algo)) {
        pr_err("i2c-core: Attempt to register adapter '%s' with "
               "no algo!\n", adap->name);
        return -EINVAL;
    }

    rt_mutex_init(&adap->bus_lock);
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);

    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;

    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
        res = device_register(&adap->dev);     // adapter也作为一个设备注册,这里得到的sys路径为:/sys/devices/platform/i2c-davinci.1/i2c-2
    if (res)
        goto out_list;

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

#ifdef CONFIG_I2C_COMPAT
    res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                       adap->dev.parent);
    if (res)
        dev_warn(&adap->dev,
             "Failed to create compatibility class link\n");
#endif

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

    /* Notify drivers */
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    mutex_unlock(&core_lock);

    return 0;

out_list:
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    return res;
}

进入函数i2c_scan_static_board_info(adap);

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    struct i2c_devinfo  *devinfo;

    down_read(&__i2c_board_lock);
    list_for_each_entry(devinfo, &__i2c_board_list, list) {
        if (devinfo->busnum == adapter->nr
                && !i2c_new_device(adapter,
                        &devinfo->board_info))// 将i2c_boardinfo传入,构造一个i2c_client, 将此i2c设备与所有此i2c线上的驱动匹配
            dev_err(&adapter->dev,
                "Can't create device at 0x%02x\n",
                devinfo->board_info.addr);
    }
    up_read(&__i2c_board_lock);
}

这个函数会对我们的板级文件中I2C_BOARD_INFO匹配,匹配到生成一个i2c_client数据结构以及注册一个新的设备在节点下。

static struct i2c_board_info __initdata da850_evm_i2c_devices[] = {
    {
        I2C_BOARD_INFO("tlv320aic3x", 0x18),
    },
    {
        I2C_BOARD_INFO(EEPROM_DEVICE, 0x50),
        .platform_data  = &da850_evm_i2c_eeprom_info,
    },
#if 0
    {
        I2C_BOARD_INFO("tca6416", 0x20),
        .platform_data = &da850_evm_ui_expander_info,
    },
    {
        I2C_BOARD_INFO("tca6416", 0x21),
        .platform_data = &da850_evm_bb_expander_info,
    },
    {
        I2C_BOARD_INFO("cdce913", 0x65),
    },
    {
        I2C_BOARD_INFO("PCA9543A", 0x73),
    },
#endif
};

进行检查,然后在i2c_new_device函数里边实现i2c_client数据结构,再调用device_register函数在节点生成一个设备。至此i2c总线驱动分析结束。

i2c_new_device函数里边有这样的一条函数,值得注意一下
client->dev.platform_data = info->platform_data;

i2c_driver跟i2c_client匹配是通过i2c_driver->id_table 跟client->dev.platform_data进行名字匹配,而client->dev.platform_data来至于info->platform_data,所以我们可以知道id_table 跟 I2C_BOARD_INFO是有名字上的联系的,这个是我们接下来i2c_driver的id_table的设置的依据。

下面大概说一下i2c_adapter怎么跟i2c的通信方法algorithm关联上的,回到 davinci_i2c_probe函数,我们可以看到adap->algo = &i2c_davinci_algo;这段程序就给i2c_adapter数据结构成员algo指向一个函数,就是此i2c适配器的i2c通讯方法了。

static struct i2c_algorithm i2c_davinci_algo = {
    .master_xfer    = i2c_davinci_xfer,
    .functionality  = i2c_davinci_func,
};

主要看.master_xfer = i2c_davinci_xfer,

static int
i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
    struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
    int i;
    int ret;

    dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num);

    ret = i2c_davinci_wait_bus_not_busy(dev, 1);
    if (ret < 0) {
        dev_warn(dev->dev, "timeout waiting for bus ready\n");
        return ret;
    }

    for (i = 0; i < num; i++) {
        ret = i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1)));
        dev_dbg(dev->dev, "%s [%d/%d] ret: %d\n", __func__, i + 1, num,
            ret);
        if (ret < 0)
            return ret;
    }

#ifdef CONFIG_CPU_FREQ
    complete(&dev->xfr_complete);
#endif

    return num;
}

i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1)));这个函数就是i2c实现数据发送接收的函数了,里边主要是cpu硬件相关的操作,这里就不详细了解了。

设备驱动实现

以/sound/soc/corece/tlv320aix3x.c为例子
首先看i2c_driver这个数据结构

static struct i2c_driver aic3x_i2c_driver = {
    .driver = {
        .name = "tlv320aic3x-codec",
        .owner = THIS_MODULE,
    },
    .probe  = aic3x_i2c_probe,
    .remove = aic3x_i2c_remove,
    .id_table = aic3x_i2c_id,
};

首先承接i2c总线驱动最后的内容进行总结,我们进入.id_table = aic3x_i2c_id这个元素看一下。

static const struct i2c_device_id aic3x_i2c_id[] = {
    { "tlv320aic3x", AIC3X_MODEL_3X },
    { "tlv320aic33", AIC3X_MODEL_33 },
    { "tlv320aic3007", AIC3X_MODEL_3007 },
    { }
};

可以看到aic3x_i2c_id的{ “tlv320aic3x”, AIC3X_MODEL_3X }名字是跟我们板级文件里边,i2c_board_info的I2C_BOARD_INFO(“tlv320aic3x”, 0x18)名字是一样,这样在,i2c总线驱动i2c_bus_type的match()函数i2c_device_match(),会调用i2c_match_id(),匹配板文件中定义的ID和i2c_driver所支持的ID表。
所以我们进入/sys/devices/platform/i2c_davinci.1/i2c-1/1-0018查看
i2c子系统学习总结_第2张图片
可以看到节点下确实挂在了一个名为tlv320aic3x.-codec的设备,这个设备就是在上面i2c_new_device函数里边生成的。
接下来看一下.probe = aic3x_i2c_probe,

static int aic3x_i2c_probe(struct i2c_client *i2c,
               const struct i2c_device_id *id)
{
    struct aic3x_pdata *pdata = i2c->dev.platform_data;
    struct aic3x_priv *aic3x;
    int ret;

    aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL);
    if (aic3x == NULL) {
        dev_err(&i2c->dev, "failed to create private data\n");
        return -ENOMEM;
    }

    aic3x->control_type = SND_SOC_I2C;

    i2c_set_clientdata(i2c, aic3x);
    if (pdata) {
        aic3x->gpio_reset = pdata->gpio_reset;
        aic3x->setup = pdata->setup;
    } else {
        aic3x->gpio_reset = -1;
    }

    aic3x->model = id->driver_data;

    ret = snd_soc_register_codec(&i2c->dev,
            &soc_codec_dev_aic3x, &aic3x_dai, 1);
    return ret;
}

这里就是一些具体的挂载在i2c总线上设备的操作了,我们也不具体了解,主要看一条程序i2c_set_clientdata(i2c, aic3x);这里将i2c_driver的id匹配到的client传递给相关数据结构,给现对于的具体设备进行i2c通信的控制。到此整个的总结结束。

互相学习,有错误的地方欢迎留言纠正,有所交流也欢迎留言,或者发邮件
[email protected]
如果有荣幸被转载还希望能标明出处,谢谢!

你可能感兴趣的:(linux驱动)