4-Linux i2c system

题图:i2c时序

Linux i2c system

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

1.Linux下I2c的驱动架构


如下:

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相关教程,感谢您的查阅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容