学习目标:分析linux内核源码下的i2c总线驱动 drivers/i2c/busses/i2c-s3c2410.c 和 driver/i2c/chips/eeprom.c 设备驱动;
一、i2c驱动框架
在drivers/i2c/目录下查看文件结构可看到:
其中,
1)Busses: I2C总线驱动相关的文件。例如i2c-s3c2410.c.
2)Chips: I2C设备驱动相关文件。例如:eeprom.c、m41t00.c.
3)Algos:i2c通信相关,使能中断、启动传输、i2c寄存器操作等。
4)i2c-core.c:实现了I2C核心的功能,例如:I2C总线的初始化、注册和适配器添加和注销等相关工作以及/proc/bus/i2c*接口。
5)i2c-dev.c:提供了通用的read、 write和ioctl等接口,实现了I2C适配器设备文件的功能。
二、源码分析
简单的结构图:
第一部分: i2c总线驱动程序 drivers/i2c/busses/i2c-s3c2410.c
1 static struct platform_driver s3c2440_i2c_driver = { 2 .probe = s3c24xx_i2c_probe, 3 .remove = s3c24xx_i2c_remove, 4 .resume = s3c24xx_i2c_resume, 5 .driver = { 6 .owner = THIS_MODULE, 7 .name = "s3c2440-i2c", 8 }, 9 }; 10 11 static int __init i2c_adap_s3c_init(void) 12 { 13 int ret; 15 ret = platform_driver_register(&s3c2410_i2c_driver);//注册平台platform_driver 16 if (ret == 0) { 17 ret = platform_driver_register(&s3c2440_i2c_driver); 18 if (ret) 19 platform_driver_unregister(&s3c2410_i2c_driver); 20 } 22 return ret; 23 }
进入probe函数可以看到:
(1)*设置i2c_adapter适配器结构体,i2c_adapter适配器指向s3c24xx_i2c;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
其中,s3c24xx_i2c结构体为:
这里.master_xfer = s3c24xx_i2c_xfe函数:与i2c通信相关,使能中断、启动传输、i2c寄存器操作等功能。==》发送i2c信号函数。
(2)i2c_add_adapter(&i2c->adap);注册adapter适配器。
进入其中的i2c_register_adapter函数:
static int i2c_register_adapter(struct i2c_adapter *adap)
{ .......
list_add_tail(&adap->list, &adapters);//添加adap到链表中
/* let legacy drivers scan this bus for matching devices */ list_for_each(item,&drivers) { driver = list_entry(item, struct i2c_driver, list); if (driver->attach_adapter) /* We ignore the return code; if it fails, too bad */ driver->attach_adapter(adap); }
}
1)首先将adapter放入i2c_bus_type的adapter链表中
2)然后将所有的i2c设备调出来,执行i2c_driver设备的attach_adapter函数进行匹配;
第二部分:i2c设备驱动,以driver/i2c/chips/eeprom.c 设备驱动为例
利用i2c_add_driver分配eeprom_driver结构体:
进入i2c_add_driver函数,可看到调用的int i2c_register_driver函数:
1 int i2c_register_driver(struct module *owner, struct i2c_driver *driver) 2 { ..................... 3 list_add_tail(&driver->list,&drivers); 4 pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name); 5 /* legacy drivers scan i2c busses directly */ 6 if (driver->attach_adapter) { 7 struct i2c_adapter *adapter; 8 list_for_each_entry(adapter, &adapters, list) { 9 driver->attach_adapter(adapter); 10 }
1)首先添加driver到driver链表中
2)然后取出adapters链表中所有的i2c_adapter, 然后调用了i2c_driver->attach_adapter()函数,进行匹配。
接下来会进入eeprom_driver结构体的eeprom_attach_adapter函数:
其中,第1个参数就是i2c_adapter适配器, 参数addr_data存放了I2C设备地址的信息, 调用i2c_probe_address 发出S信号,发出设备地址(来自addr_data),最终会调用到adap->algo->master_xfer函数发信号,如果如果收到ACK信号,就调用eeprom_detect()回调函数来注册i2c_client结构体,该结构体对应真实的物理从设备。
这里的addr_data为 i2c_client_address_data结构体:
1 struct i2c_client_address_data { 2 unsigned short *normal_i2c; //存放正常的设备高7位地址数据 3 unsigned short *probe; //存放不受*ignore影响的高7位设备地址数据 4 unsigned short *ignore; //存放*ignore的高7位设备地址数据 5 unsigned short **forces; //forces表示适配器匹配不了该设备,也要将其放入适配器中 6 7 };
例如:DS1374.c设备驱动程序中:
接下来的i2c_probe函数的调用顺序为:
i2c_probe(adapter, &addr_data, eeprom_detect);
-->i2c_probe_address // 发出S信号,发出设备地址(来自addr_data)
-->i2c_smbus_xfer
--> i2c_smbus_xfer_emulated
--> i2c_transfer
-->adap->algo->master_xfer(adap,msgs,num);// s3c24xx_i2c_xfer
其中,在i2c_smbus_xfer_emulated函数中,
1 static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr, 2 unsigned short flags, 3 char read_write, u8 command, int size, 4 union i2c_smbus_data * data) 5 { 10 unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; 11 unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; 12 int num = read_write == I2C_SMBUS_READ?2:1; 13 struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 }, ///定义2个i2c_msg结构体, 14 { addr, flags | I2C_M_RD, 0, msgbuf1 } 15 }; 16 int i; 17 u8 partial_pec = 0; 18 msgbuf0[0] = command; 19 switch(size) { 20 case I2C_SMBUS_QUICK: 21 msg[0].len = 0; 22 /* Special case: The read/write field is used as data */ 23 msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0; 24 num = 1; 25 break; 26 case I2C_SMBUS_BYTE: 27 if (read_write == I2C_SMBUS_READ) { 28 /* Special case: only a read! */ 29 msg[0].flags = I2C_M_RD | flags; 30 num = 1; 31 } 32 break; 33 .... 34 if (i2c_transfer(adapter, msg, num) < 0) //将 i2c_msg结构体的内容发送给I2C设备 35 return -1; 36 /* Check PEC if last message is a read */ 37 if (i && (msg[num-1].flags & I2C_M_RD)) { 38 if (i2c_smbus_check_pec(partial_pec, &msg[num-1]) < 0) 39 return -1; 40 } 41 ..... 42 }
其中,其中i2c_msg结构体为:
1 struct i2c_msg { 2 __u16 addr; //I2C从机的设备地址 3 __u16 flags; //当flags=0表示写, flags= I2C_M_RD表示读 4 __u16 len; //传输的数据长度,等于buf数组里的字节数 5 __u8 *buf; //存放数据的数组 6 };
进入i2c_transfer函数的master_xfer(adap,msgs,num);即总线驱动适配器指向结构体的s3c24xx_i2c_xfer函数;其中,参数*adap表示通过哪个适配器传输出去,msgs表示I2C消息,num表示msgs的数目。
总而言之,根据以上分析,i2c_driver ->attach_adapter(adapter)函数里主要执行以下几步:
1) 调用 i2c_probe(adap, addr_data 设备地址结构体, 回调函数);
2) 将要发的设备地址结构体打包成i2c_msg,
3) 然后执行i2c_transfer()来调用i2c_adapter->algo->master_xfer()将i2c_msg发出去
4) 若收到ACK回应,便进入回调函数,注册i2c_client从设备,使该设备与适配器联系在一起。
第三部分:通过回调函数eeprom_detect注册和设置i2c_client从设备(即结构体),使用i2c_transfer()来实现与设备进行后续的传输数据了。
eeprom_detect函数的代码如下:
1 static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind) 2 { 3 struct i2c_client *new_client; //分配i2c_client结构体 4 struct eeprom_data *data; 5 int err = 0;
.... 6 new_client = &data->client; //设置i2c_client结构体 7 memset(data->data, 0xff, EEPROM_SIZE); 8 i2c_set_clientdata(new_client, data); 9 new_client->addr = address; 10 new_client->adapter = adapter; 11 new_client->driver = &eeprom_driver; 12 new_client->flags = 0; 13 ..... 14 /* Tell the I2C layer a new client has arrived */ 15 if ((err = i2c_attach_client(new_client))) //注册i2c_client结构体 16 goto exit_kfree; 17 .....
exit_detach:
i2c_detach_client(new_client);
exit_kfree:
kfree(data); 18 }
在函数中主要做的以下工作:1)分配i2c_client结构体;2)设置i2c_client结构体;3)注册i2c_client结构体。之后就可以用i2c_transfer()来传输数据了。