Linux i2c system
I2C总线是由PHILIPS公司开发的两线式串行总线,每个连接到总线的器件都可以通过唯一的地址和主机主机进行通讯,主机可以作为主机发送器或主机接收器;串行的8位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
1.Linux下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_data
、i2c_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相关教程,感谢您的查阅。