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里面的事件 ...
随机推荐
- MySQL代码备份
package com.dus.utils; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io. ...
- 框架页面jquery装载
- vue-cli3 项目从搭建优化到docker部署
项目地址 vue-cli3-project 欢迎 star 原文地址 https://www.ccode.live/lentoo/list/9?from=art 1. 创建一个vue项目 相信大部分人 ...
- 【图文详解】Zookeeper集群搭建(CentOs6.3)
Zookeeper简介: Zookeeper是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务的. A.zookeeper是为别的分布式程序服务的 B.Zookeeper本身就是一个分布式程 ...
- MyEclipse2015Stable3.0破解方法
原理大概是这样的(个人粗略分析):获取当前的日期,来设置证书失效日期,解析后生成码-->再转码,最后生成序列号. 1.新建一个Java工程,(不会安装jdk创建环境变量的,请前往传送门:链接.) ...
- linux shell中 if else以及大于、小于、等于逻辑表达式介绍
在linux shell编程中,大多数情况下,可以使用测试命令来对条件进行测试,这里简单的介绍下, 比如比较字符串.判断文件是否存在及是否可读等,通常用"[]"来表示条件测试. 注 ...
- 【[SDOI2009]晨跑】
板子 题意就是每个点只能经过一次 所以非常显然拆点,除去\(1,n\)每个点\(i\)向\(i'\)连一条容量为\(1\)费用为\(0\)的边 剩下的边按照输入给出的建就好了 代码 #include& ...
- 【[HAOI2011]Problem c】
好题啊 先考虑一些如何判掉无解的情况 我们开一个桶,存一下每个编号有多少个人必须选,之后做一个后缀和,之后我们扫一遍,如果一旦有一个后缀和\(pre[i]\)超过\(n-i+1\)就不合法了,因为我们 ...
- React中的虚拟DOM
当组件当state和props发生变化当时候,组件当render函数就会重新执行,组件就会被重新渲染,react中实现这种重新渲染,他的性能是非常高的,因为他引入了一个虚拟Dom的概念,那么什么是虚拟 ...
- solidity开发之windows下配置remix本地环境遇到的问题及解决
本人按照这个教程配置remix本地环境.[https://cloud.tencent.com/developer/article/1374376] win+R打开管理员终端,在欲配置为本地目录的路径执 ...