最近一段时间,我在网上看了一些关于linux下i2c的文档,对i2c有了一些较浅层次了解。写这篇博客,主要是对现在已经掌握知识的巩固。
Linux下I2C驱动体系结构
Linux下I2C驱动体系结构由 三大部分 构成:
1. I2C核心
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法。它也提供了I2C通信方法(algorithm)上层的、与具体适配器无关的代码 i2c_transfer() 函数,并通过这个函数将总线驱动、用户层串起来。
2.I2C总线驱动
I2C总线驱动就是linux内核中I2C适配器的软件实现,它告诉I2C适配器该怎么工作,从而实现I2C适配器与从设备的通信功能。
我们可以通过这个驱动,使用i2c总线去操控挂接在总线上的设备。
I2C总线驱动由 i2c_adapter 和 i2c_algorithm 这两个结构体来描述。
3.I2C设备驱动
I2C设备驱动是对I2C从设备的软件实现,包括对设备的读、写等操作。
一个具体的I2C设备驱动包括两个部分:
- ①设备驱动代码。用于将设备注册到内核中,并提供了对设备的读写操作。
②设备硬件信息i2c_client。我们也可以通过设备驱动,跳过i2c总线,直接对设备进行操控。
I2C设备驱动由 i2c_driver 和 i2c_client 这两个结构体来描述。
从上面我们知道了,对于i2c总线上的设备,我们可以通过两种不同的方式去控制。一种是利用i2c设备驱动,另一种是利用i2c总线驱动。
所有的I2C驱动代码位于drivers/i2c目录下:
(1) i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
(2)i2c-dev.c
通用i2c设备驱动。就是这个文件实现了用户层利用i2c总线驱动去控制设备的功能,我们会在后面着重分析这个文件。
(3)chips文件夹
这个目录中包含了一些特定的I2C设备驱动,如Dallas公司的DS1337实时钟芯片、EPSON公司的RTC8564实时钟芯片和I2C接口的EEPROM驱动等。
(4)busses文件夹
这个文件中包含了一些I2C总线的驱动,如S3C2410的I2C控制器驱动为i2c-s3c2410.c
(5)algos文件夹
实现了一些I2C总线适配器的algorithm。
I2C核心(i2c-core.c)
初始化模块
static int __init i2c_init(void)/*i2c初始化*/
{
int retval;
retval = bus_register(&i2c_bus_type);/*i2c适配器驱动的注册*/
if (retval)
return retval;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver);/*i2c设备驱动的注册*/
if (retval)
goto class_err;
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
bus_unregister(&i2c_bus_type);
return retval;
}
我们可知i2c_init()函数主要完成的就是i2c总线驱动和i2c设备驱动的注册。
i2c传输,发送和接收
/*用户空间调用write()时,内核调用此函数,此函数最终调用i2c_transfer()*/
int i2c_master_send(const struct i2c_client *client, constchar *buf, int count)
/*用户空间调用read()时,内核调用此函数,此函数最终调用i2c_transfer()*/
int i2c_master_recv(const struct i2c_client *client, char*buf, int count)
/*用于传输数据,此函数是i2c适配器工作的基石,我们放在总线驱动中分析*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
由此我们i2c核心具体完成的就是:
- 实现了I2C总线驱动和设备驱动的注册;
- 提供接口函数i2c_master_send(),i2c_master_recv(),i2c_transfer()实现了用户界面通过适配器去控制i2c设备的功能。
我们接下来看看I2C设备驱动,也就是第一种操控i2c设备的方法。
I2C设备驱动
要想让内核中的i2c设备驱动使能,我们就应该去内核中的make menuconfig中添加设备的支持,但是这个设备在i2c总线上,所以我们必须要先去内核中添加i2c总线的支持,在添加设备支持。我们这里就只拿eeprom设备作为例子去分析。
在添加完i2c总线的支持后,我们再去添加eeprom,这时候,在eeprom下就会出现I2C EEPROM这个选项。
添加完i2c总线和eeprom设备支持后,在sys/devices/platform/s3c2440-i2c/i2c-0中就会出现相对应的设备。
那接下来,我们就来看看i2c设备驱动吧。
从前面我们知道i2c设备驱动是由i2c_driver和i2c_client这两个结构体描述的。
我们首先看一下i2c_driver这个结构体
struct i2c_driver
{
unsignedint class;
int(*attach_adapter)(struct i2c_adapter *) __deprecated; /*依附i2c适配器函数指针*/
int(*detach_adapter)(struct i2c_adapter *) __deprecated;/*脱离i2c适配器函数指针*/
int (*probe)(struct i2c_client*, const struct i2c_device_id *);
int (*remove)(struct i2c_client*);
int(*suspend)(struct i2c_client *, pm_message_t mesg);
int(*resume)(struct i2c_client *);
void(*alert)(struct i2c_client *, unsigned int data);
int(*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id*id_table; /* 该驱动所支持的设备ID表 */
/*Device detection callback for automatic device creation */
int(*detect)(struct i2c_client *, struct i2c_board_info *);
constunsigned short *address_list;
structlist_head clients;
};
我们可以看出i2c_driver结构对应一套具体的驱动方法。
那么i2c_driver这个结构体是怎么实现的呢,drivers/misc/eeprom/at24.c 这个文件就是驱动代码的实现,并且支持大多数I2C接口的eeprom。
i2c_driver的实现
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24", /*设备名字*/
.owner = THIS_MODULE,
},
.probe = at24_probe, /*at24_probe函数*/
.remove = __devexit_p(at24_remove),
.id_table = at24_ids, /*设备id表*/
};
在这个结构体中,我们对i2c_driver结构体中的driver,probe,remove,id_table这些成员进行了赋值。
i2c初始化函数
static int __init at24_init(void)
{
if (!io_limit) {
pr_err("at24: io_limit must not be 0!\n");
return -EINVAL;
}
io_limit = rounddown_pow_of_two(io_limit);
return i2c_add_driver(&at24_driver);
}
在前面的代码中,我们通过构建了at24_driver这个结构体,完成了对i2c_driver结构体的赋值,在初始化函数中,我们调用核心层函数,注册i2c_driver结构体。这样,我们的这个I2C设备层驱动就被作为模块加载到内核中了。
i2c_client
在linux系统下,我们习惯于将驱动代码与硬件信息分隔开,这样可以使我们的驱动代码具有可移植性。因此在驱动i2c_driver的构建之后,我们需要去完成硬件信息的构造,即i2c_client。
我们先看一下i2c_client 结构体
struct i2c_client {
unsigned short flags; /*I2C_CLIENT_TEN:使用10位从地址,I2C_CLIENT_PEC:使用SMBus包错误检测*/
unsignedshort addr; /* chipaddress - NOTE: 7bit */
charname[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* 依附的i2c_adapter */
struct i2c_driver *driver; /* 依附的i2c_driver*/
structdevice dev; /* the devicestructure */
intirq; /* irq issuedby device */
structlist_head detected;
};
我们可以看出结构体这个结构体对应于真实的物理设备,其中包含了芯片地址,设备名称,设备所依附的适配器,设备所依附的驱动,设备使用的中断号等内容。
i2c_client在开发板文件中的实现
At24c不依赖于具体的CPU和I2C控制器硬件特性,因此如果电路板包含该外设,只需要添加对应的i2c_board_info,下面是at24c02 i2c_client在板文件中的实现:
修改文件: linux/arch/arm/mach-s3c2440/mach-smdk2440.c
#include <linux/i2c/at24.h>
static struct at24_platform_data at24c02 = {
.byte_len = SZ_2K / 8, /* eeprom的存储大小,单位Byte */
.page_size =