Linux i2c system

Linux i2c system_第1张图片
题图:i2c时序

Linux i2c system

I2C总线是由PHILIPS公司开发的两线式串行总线,每个连接到总线的器件都可以通过唯一的地址和主机主机进行通讯,主机可以作为主机发送器或主机接收器;串行的8位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。

1.Linux下I2c的驱动架构


如下:

Linux i2c system_第2张图片
I2C框架

从图中可以观察到I2C系统的整个框架,从下往上;

  • Hardware:
    所连接的外界设备i2c-device,可以连接多个device,但挂载在同一条总线上的设备其地址必须不一样。

  • Kernel:
    Kernel主要分为三个模块,adapter、core、client,driver;

  • Adapter:
    CPU与I2C的接口,简单解释就是CPU要控制一个i2c-device,那你CPU自身要能支持i2c总线功能,所以adapter主要的工作就是初始化i2c,向cpu申请i2c总线,一条i2c总线对于一个adapter;adapter会根据cpu的变化有一些变化,所以需要用户根据cpu体现自行实现,一般放在driver/i2c/buses/文件夹下面(例如:i2c-s3c2410.c)。由于adapter属于片上资源,所以驱动的实现采用platform驱动模型。

  • Core:
    core为i2c驱动的核心,位于driver/i2c/i2c-core.c中;core上要跟client打好关系,为其提供client的注册申请,下要为adapter提供总线的申请注册,不过这一部分kernel基本已经为我们所实现,我们要做的就是有选择性的调用其中的函数。

  • Client:
    client即为外界设备i2c-device,一个device即是一个client,不同的i2c-device注册client方式基本一致,一般在平台设备arch/arm/mach-xxx里面添加info信息,也可以通过dts来进行设置。需要注意的是在同一个总线上的多个设备,其设备地址肯定不一样,设备reg的读写等也需要根据设备来定制。

  • Driver:
    driver可以看成是client的驱动,一个client对应一个driver,这部分的驱动由用户来实现,一般放在driver/i2c/或driver/i2c/chips/文件夹下实现。

  • User-space:
    即应用层,我们做这么多驱动的工作,主要就是要用它,所以要有特定的应用程序来控制。

上面将i2c driver的整体框架做了一个介绍,熟悉i2cdriver的框架,下面我们就将需要由用户自行实现的adapter、client和driver进行简单说明,里面会交叉讲解一些i2c-core中的内容。

2.I2c adapter and client


adapter的主要目的就是通过platform总线将片上i2c设备和i2c驱动绑定在一起,当i2c的platform驱动成功后,会执行platform_driver结构下对应的probe函数,对于如何实现i2c的platform设备驱动,查看Linux platform system。

probe函件里面会调用adapter的添加,在i2c-core.c中,为我们提供了i2c_add_adapter()i2c_add_numbered_adapter()两个函数来实现adapter接口注册。

这两个函数有什么区别呢,来具体探究下,下面为这两个函数的源码,位于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;
}

可以观察到,不管两个函数上面在执行什么操作,都是为了得到最终的i2c_adapter结构体,最后通过调用i2c_register_adapter()函数来实现adapter的注册的。

其实函数的上部分是为了处理i2c的总线号,对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其自动分配一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败。

既然有动态总线和指定总线,那就去寻找总线号的指定在哪边实现,总线号的执行是在平台设备里面指定的,在平台设备arch/arm/mach-xxx里面我们会调用i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)函数来提供总线信息和client信息。

  • 第一个参数busnum即总线号。

  • 第二个参数info为client的name和id,这边client的name和id要和driver下面的id、name一致。

上面所提到的使用i2c_register_board_info来提供总线号和注册client,在Linux3.14以后的版本也可以有dts来实现。

3.I2c client driver


一个client一般对应一个driver,driver比较简单,一般就是通过i2c_add_driver来进行添加,i2c_del_driver进行卸载,当i2c_driver.id_table信息与i2c_board_info所指向的设备(或者设备树中的节点)匹配成功,则执行i2c_driver.probe(),从而获得对应的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后,就是进行实现client的读写函数了,在i2c-core.c里面有提供例如i2c_smbus_read_byte_datai2c_smbus_write_byte_data这类的函数,需要查看芯片手册的时序找对应的实现函数即可。

调试驱动的时候最常用的方法就是使用printk来进行交互,进行定位、验证,但是要在哪边进行printk呢,个人觉得调试i2c驱动有一个地方一定要进行printk,那就是位于i2c-core.c下的i2c_match_id()函数,如下:

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

看函数名称就知道,该函数用来匹配device和client的name是否一致。通过在此处打印信息,我们可以观察到,这个函数会被调用到两次,一次是进行注册adapter时,另一次就是寻找驱动的时候,如果遇到问题是,我们只有查看打印的信息大概就能发现问题的存在点了。

Linux i2c system的分析就到这边,有感悟时会持续会更新。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

你可能感兴趣的:(Linux i2c system)