如上图。I2C驱动框架分为三个部分:I2CCore层、I2C总线驱动层、I2C设备驱动层。
下面我们逐个展开
I2C Core层源代码位于drivers/i2c/i2c-core.c中。
I2C Core层是系统实现的,与硬件无关的。它起着承上启下的作用,用来为设备驱动层和总线驱动层提供注册adapter、driver、client到系统中来的接口。这些接口如下:
int i2c_add_adapter(struct i2c_adapter *);
int i2c_del_adapter(struct i2c_adapter *);
int i2c_attach_client(struct module *owner,struct i2c_ client *);
void i2c_detach_client(struct i2c_ client *);
int i2c_register_driver(struct module *owner,struct i2c_ driver *);
void i2c_del_driver(struct i2c_ driver *);
int i2c_transfer(struct i2c_adapter *, structi2c_msg *msgs, int num);
int i2c_master_send(struct i2c_ client *, constchar *buf, int count);
int i2c_master_rcv (struct i2c_ client *,char*buf, int count);
send和receive分别都调用了transfer函数,而transfer也不是直接和硬件交互,而是调用 algorithm中的master_xfer()函数,所以我们要想进行数据传输,必须自己来实现这个master_xfer()函数
总线驱动层由algorithm和adapter组成。他们有各自对应的数据结构来描述。如下。
i2c_adapter结构体
struct i2c_adapter {
struct module *owner;//所属模块
unsigned int id;//algorithm的类型,定义于i2c-id.h,
unsigned int class;
const struct i2c_algorithm *algo; //总线通信方法结构体指针
void *algo_data;//algorithm数据
struct rt_mutex bus_lock;//控制并发访问的自旋锁
int timeout;
int retries;//重试次数
struct device dev; //适配器设备
int nr;
char name[48];//适配器名称
struct completion dev_released;//用于同步
struct list_head userspace_clients;//client链表头
};
I2c_algorithm结构体
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, intnum);//I2C传输函数指针
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags,char read_write,u8 command, int size, union
i2c_smbus_data *data);//smbus传输函数指针
u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能
};
// master_xfer传递的信息msg 结构
struct i2c_msg {
__u16 addr;//设备地址
__u16 flags;//标志
__u16 len;//消息长度
__u8 *buf;//消息数据
};
现在我们要实现自己的I2C 总线驱动,步骤一般如下:
1) 定义自己的I2c_algorithm,然后实现自己的master_xfer和functionality函数,然后赋值给自己定义的I2c_algorithm。原因我们再上面的I2C Core层说过了,i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)是要调用到algorithm中的master_xfer函数的。每款芯片的master_xfer都是不同的。
2) 定义自己的i2c_adapter,然后将自己定义的I2c_algorithm赋值给i2c_adapter的成员。
3) 我们最终要将总线驱动作为一个平台驱动注册到系统中,所以我们要自己定义一个平台注册函数,在平台probe函数中调用I2C Core层提供的接口i2c_add_adapter(struct i2c_adapter *)将我们的adapter添加到系统中。
4) 按照前面介绍过得平台驱动的构建方法,构建自己的platform_driver, 将probe, remove函数赋值给对应成员,然后调用platform_register_driver()将驱动注册到系统中即可。
我们来看一下DM365的i2c 总线驱动的例子吧。
//定义自己的I2c_algorithm
static struct i2c_algorithm i2c_davinci_algo = {
.master_xfer = i2c_davinci_xfer,
.functionality = i2c_davinci_func,
};
//定义自己的i2c_adapter, 在probe中调用i2c_add_adapter注册adapter
static intdavinci_i2c_probe(struct platform_device *pdev)
{
struct davinci_i2c_dev *dev;
struct i2c_adapter *adap;
….
adap= &dev->adapter;
i2c_set_adapdata(adap, dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "DaVinciI2C adapter", sizeof(adap->name));
adap->algo =&i2c_davinci_algo;
adap->dev.parent =&pdev->dev;
/* FIXME */
adap->timeout = 1;
adap->retries = 1;
adap->nr = pdev->id;
r =i2c_add_adapter(adap);
…
}
//将总线驱动作为平台驱动注册到系统中
static struct platform_driver davinci_i2c_driver = {
.probe =davinci_i2c_probe,
.remove =davinci_i2c_remove,
.driver ={
.name = "i2c_davinci",
.owner = THIS_MODULE,
},
};
/* I2C may beneeded to bring up other drivers */
static int__init davinci_i2c_init_driver(void)
{
returnplatform_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);
}
以上的代码在\diver\i2c\buses下,i2c-davinci文件中。开篇的平台驱动我们说过,有驱动就必然有设备与之匹配。既然上面把i2c总线驱动作为平台驱动注册到系统中,那么对应的i2c总线平台设备在哪里呢?我们可以在\arch\arm\mach-davinci\Devices文件中找到答案。
如我们之前分析过的平台设备的构建三个步骤:构建struct resource, 构建platform_device,然后用platform_add_devices()添加设备。I2C总线的平台设备当然也是按照这个步骤进行。我们看一下davinci平台的linux 6.18内核的I2C总线平台设备代码。
//构建struct resource
static struct resource i2c_resources[] = {
{
.start = DAVINCI_I2C_BASE,
.end = DAVINCI_I2C_BASE + 0x40,
.flags = IORESOURCE_MEM,
},
{
.start = IRQ_I2C,
.flags = IORESOURCE_IRQ,
},
};
//构建platform_device
static struct platform_device davinci_i2c_device = {
.name = "i2c_davinci",
.id = 1,
.num_resources = ARRAY_SIZE(i2c_resources),
.resource = i2c_resources,
};
static struct platform_device *devices[] __initdata = {
&i2c_device,
&watchdog_device,
&usb_device,
};
//用platform_add_devices()添加设备
static int __init davinci_init_devices(void)
{
davinci_init_cpu_i2c();
davinci_init_cpu_usb();
platform_add_devices(devices, ARRAY_SIZE(devices));
davinci_init_cpu_emac_register();
return 0;
}
可见,平台设备和平台驱动时靠名字来匹配的。这里的I2C总线驱动是使用"i2c_davinci"名字来匹配驱动和设备。
如上图,I2C设备驱动层i2c_client、i2c_driver组成,他们都有对应的结构体来描述。
i2c_driver结构体
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针
int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针
int (*probe)(struct i2c_client *, conststruct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(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表
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
//i2c_client结构体
struct i2c_client {
unsigned short flags;//标志
unsigned short addr; //低7位为芯片地址
char name[I2C_NAME_SIZE];//设备名称
struct i2c_adapter *adapter;//依附的i2c_adapter
struct i2c_driver *driver;//依附的i2c_driver
struct device dev;//设备结构体
int irq;//设备所使用的结构体
struct list_head detected;//链表头
};
实际应用我们要注册一个I2C设备到系统,我们需要做的工作的是:
1. 构建自己的attach函数,在函数中定义自己的i2c_client, 调用i2c_client_attach()来注册设备。
2. 构建自己的i2c_driver, 然后将自己定义的attach函数赋值给i2c_driver的成员。
3. 调用i2c_add_driver注册driver.
我们来看个例子吧,tvp5150的例子。在驱动中,tvp5150是通过I2C和DM365通信的,所以tvp5150是作为一个i2c设备挂载到系统中。
//用i2c_client_attach()来注册设备
staticint tvp5150_i2c_attach_client(struct i2c_client *client,
struct i2c_driver *driver,
struct i2c_adapter *adap, int addr)
{
int err = 0;
if (client->adapter)
err =-EBUSY; /* our client is alreadyattached */
else {
client->addr= addr;
client->driver= driver;
client->adapter= adap;
err =i2c_attach_client(client);
if (err)
client->adapter= NULL;
}
return err;
}
//构建自己的i2c_driver,然后将自己定义的attach函数赋值给i2c_driver的成员。
driver = &tvp5150_channel_info[i].i2c_dev.driver;
driver->driver.name= strings[i];
driver->id = I2C_DRIVERID_MISC;
if (0 == i)
driver->attach_adapter= tvp5150A_i2c_probe_adapter;
else
driver->attach_adapter= tvp5150B_i2c_probe_adapter;
driver->detach_client = tvp5150_i2c_detach_client;
…
err = i2c_add_driver(&tvp5150_channel_info[i].i2c_dev.driver);
1) i2c_adapter对应一个物理适配器,i2c_algorithm对应一种通信方法。i2c_adapter需要i2c_algorithm提供的通信方法来让I2C产生特定访问周期。所以i2c_algorithm是i2c_adapter的一个成员。
2) i2c_client对应一个设备,i2c_driver则对应操作设备一套驱动方法,例如probe, remove, suspend, resume等。每个真是的I2C设备都必须要有一个i2c_client来描述。多个设备可以使用同一套驱动方法。所以i2c_adapter 跟i2c_client是一对多得关系。
3) 在6.30的内核中,i2c_client通常在BSP中使用I2C_BOARD_INFO()来填充。I2C中线驱动会调用i2c_match_id()来将BSP中的ID和i2c_driver中的ID表匹配。