9.1 IIC驱动源码分析
学习目标:分析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
static struct platform_driver s3c2440_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2440-i2c",
},
};
static int __init i2c_adap_s3c_init(void)
{
int ret;
ret = platform_driver_register(&s3c2410_i2c_driver);//注册平台platform_driver
if (ret == ) {
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
}
return ret;
}
进入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函数:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{ .....................
list_add_tail(&driver->list,&drivers);
pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
/* legacy drivers scan i2c busses directly */
if (driver->attach_adapter) {
struct i2c_adapter *adapter;
list_for_each_entry(adapter, &adapters, list) {
driver->attach_adapter(adapter);
}
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结构体:
struct i2c_client_address_data {
unsigned short *normal_i2c; //存放正常的设备高7位地址数据
unsigned short *probe; //存放不受*ignore影响的高7位设备地址数据
unsigned short *ignore; //存放*ignore的高7位设备地址数据
unsigned short **forces; //forces表示适配器匹配不了该设备,也要将其放入适配器中
};
例如: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函数中,
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data * data)
{
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+];
int num = read_write == I2C_SMBUS_READ?:;
struct i2c_msg msg[] = { { addr, flags, , msgbuf0 }, ///定义2个i2c_msg结构体,
{ addr, flags | I2C_M_RD, , msgbuf1 }
};
int i;
u8 partial_pec = ;
msgbuf0[] = command;
switch(size) {
case I2C_SMBUS_QUICK:
msg[].len = ;
/* Special case: The read/write field is used as data */
msg[].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:;
num = ;
break;
case I2C_SMBUS_BYTE:
if (read_write == I2C_SMBUS_READ) {
/* Special case: only a read! */
msg[].flags = I2C_M_RD | flags;
num = ;
}
32 break;
....
if (i2c_transfer(adapter, msg, num) < ) //将 i2c_msg结构体的内容发送给I2C设备
return -;
/* Check PEC if last message is a read */
if (i && (msg[num-].flags & I2C_M_RD)) {
if (i2c_smbus_check_pec(partial_pec, &msg[num-]) < )
return -;
}
.....
}
其中,其中i2c_msg结构体为:
struct i2c_msg {
__u16 addr; //I2C从机的设备地址
__u16 flags; //当flags=0表示写, flags= I2C_M_RD表示读
__u16 len; //传输的数据长度,等于buf数组里的字节数
__u8 *buf; //存放数据的数组
};
进入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函数的代码如下:
static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *new_client; //分配i2c_client结构体
struct eeprom_data *data;
int err = ;
....
new_client = &data->client; //设置i2c_client结构体
memset(data->data, 0xff, EEPROM_SIZE);
i2c_set_clientdata(new_client, data);
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &eeprom_driver;
new_client->flags = ;
.....
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client))) //注册i2c_client结构体
goto exit_kfree;
.....
exit_detach:
i2c_detach_client(new_client);
exit_kfree:
kfree(data);
}
在函数中主要做的以下工作:1)分配i2c_client结构体;2)设置i2c_client结构体;3)注册i2c_client结构体。之后就可以用i2c_transfer()来传输数据了。
9.1 IIC驱动源码分析的更多相关文章
- (转)Linux设备驱动之HID驱动 源码分析
//Linux设备驱动之HID驱动 源码分析 http://blog.chinaunix.net/uid-20543183-id-1930836.html HID是Human Interface De ...
- Android USB驱动源码分析(-)
Android USB驱动中,上层应用协议里最重要的一个文件是android/kernel/drivers/usb/gadget/android.c.这个文件实现USB的上层应用协议. 首先包含了一些 ...
- imx6ul linux4.1.15 LED驱动配置及heartbeat源码分析【转】
本文转载自:https://blog.csdn.net/u010444107/article/details/78328807 1)查看内核配置wujun@wj-vBox:~/freescale/li ...
- MyBatis源码分析(5)——内置DataSource实现
@(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...
- 《深入理解Spark:核心思想与源码分析》(第2章)
<深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...
- Linux内核源码分析方法
一.内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径.我们都 ...
- YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)
YII 框架源码分析 百度联盟事业部——黄银锋 目 录 1. 引言 3 1.1.Yii 简介 3 1.2.本文内容与结构 3 2.组件化与模块化 4 2.1.框架加载和运行流程 4 ...
- 高性能网络I/O框架-netmap源码分析
from:http://blog.chinaunix.net/uid-23629988-id-3594118.html 博主这篇文章写的很好 感觉很有借签意义 值得阅读 高性能网络I/O框架-netm ...
- Monkey源码分析之事件注入
本系列的上一篇文章<Monkey源码分析之事件源>中我们描述了monkey是怎么从事件源取得命令,然后将命令转换成事件放到事件队列里面的,但是到现在位置我们还没有了解monkey里面的事件 ...
随机推荐
- 并发包java.util.concurrent.CountDownLatch
/** * * @描述: 倒计时器 . * 犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计算器为0的时候 * 则所有等待者或单个等待者开始执行 * ...
- Tcpdump usage examples
In most cases you will need root permission to be able to capture packets on an interface. Using tcp ...
- 把IDENTITY_INSERT 设置为 ON ,还不能插入数据问题
IDENTITY_INSERT 为 ON 时 , 必须把需要插入的列名列出来 不然报错 正确例子: SET IDENTITY_INSERT table(表名) ONinsert into table ...
- SpringBoot 启动参数设置环境变量、JVM参数、tomcat远程调试
java命令的模版:java [-options] -jar jarfile [args...] 先贴一下我的简单的启动命令: java -Xms128m -Xmx256m -Xdebug -Xrun ...
- 探索grep命令
grep是linux的一款搜索工具,基本啥linux版本都有自带此工具.下面部分参数若没有说明,表示功力不够,理解不了. 选择匹配器 -E 正则表达式(相当于egrep命令) -F 将pattern当 ...
- android-ndk-r7b编译环境Cygwin工具搭建及配置(转)
开发android ndk 的时候需要一个编译工具编译c程序,ndk需要linux下编译,所以win环境下提供Cygwin模拟linux编译C android-ndk 较低版本的这个工具的配置网上很多 ...
- POJ 3233 Matrix Power Series 【经典矩阵快速幂+二分】
任意门:http://poj.org/problem?id=3233 Matrix Power Series Time Limit: 3000MS Memory Limit: 131072K To ...
- fast、faster中ap值的计算
def voc_ap(rec, prec, use_07_metric=False): """ ap = voc_ap(rec, prec, [use_07_metric ...
- ConcurrentMap注意事项
java.util.concurrent.ConcurrentMap接口和ConcurrentHashMap实现类 只能在键不存在时将元素加入到map中, 只有在键存在并映射到特定值时才能从map中删 ...
- Intellij IDEA 报错java.lang.NoClassDefFoundError
Intellij IDEA 报错java.lang.NoClassDefFoundError 11-Aug-2018 23:48:24.686 严重 [http-nio-8080-exec-5] or ...