基于mpc体系架构的I2C体系结构(适配器与core层交互)
[本文导读]:I2C总线作为系统总线,广泛用于e2prom,rtc等设备接口中。本文以mpc8309为架构,linux-2.26.34源码为例分析I2C总线的实现过程
1. I2C硬件时序
I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,它由串行数据线SDA和串行时钟SCL构成的总线,可发送和接收数据。I2C总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC(设备)在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
2. I2C驱动层次划分
I2C代码位于/driver/I2c目录下,I2c-core.h, I2c-core.c是整个框架的核心,定义了中间数据结构,总线驱动和设备驱动注册方,销毁方法。以及通信方法。因此,这个层面看作是一个在总线与设备之间的适配层。
Buses文件目录下定义了总线适配器驱动。
I2c-dev.c实现了通用设备驱动方法。
如下图所示,它描述了各文件在架构中的具体位置和各块之间联系:
下面开始分析,同USB子系统,介绍I2c模块在内核中的启动到具体实现。
3.I2c核心在内核中的初始化
postcore_initcall(i2c_init);
#define postcore_initcall(fn) __define_initcall("2",fn,2)
在USB子系统实现一文中,描述了这些宏定义的函数初始化顺序,它在arch_initcall,subsys_initcall等宏定义的函数之前执行。
如下图,i2c_init函数执行过程,主要包括总线注册bus_register,类注册函数class_compat_register,驱动注册函数I2c_add_driver.
初始化完成之后,在/sys/bus/i2c目录下显示如下结构:
/sys/bus/i2c/
|-- devices
|-- drivers
| |-- dummy
| |-- bind
| |-- uevent
| |`-- unbind
|-- drivers_autoprobe
|-- drivers_probe
|-- uevent
在USB子系统中说明了bus_register函数完成的任务,现结合sysfs来说明这三个函数分别完成哪些事情。
Bus_register中完成创造哪些文件:
函数名 | 文件名 | |
bus_create_file | uevent | |
kset_create_and_add("devices"…) | Devices目录 | |
kset_create_and_add("drivers"…) | Drivers 目录 | |
add_probe_files | bus_create_file(bus, &bus_attr_drivers_probe) | drivers_probe |
bus_create_file(bus, &bus_attr_drivers_autoprobe) | drivers_autoprobe | |
|
| |
|
|
上面我们又从sysfs角度来了解了bus_register所做的事情
class_compat_register函数所做的事情就是在/sys/class目录下创建i2c-adapter目录。
I2c_add_driver主要为适配器添加注册驱动程序。
而dummy_driver驱动中的dummy_probe,dummy_remove函数为空。系统为什么要这样做呢?
首先,为了支持各种不同的适配器,在此不同将代码固定化,这个core层只是一个适配层, 也就是连接client与适配器的中间层,具体适配器驱动的初始化延迟到内核配置完成之后,也就是确定了某类的适配器之后这个驱动才会初始化。下面以mpc架构来分析适配器驱动的实现。
4. I2c适配器的实现(mpc构架)
适配器代码实现在/driver/i2c/busses/目录下,首先,熟悉一下适配器的定义:
/***********************kernel/include/linux/I2c.h*****************************/
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr; //描述了总线号
char name[48];
struct completion dev_released;
struct list_head userspace_clients;
};
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
/***********************kernel/include/linux/I2c.h*****************************/
在I2c.h文件中,主要定义
struct i2c_msg;
struct i2c_algorithm;
struct i2c_adapter;
struct i2c_client;
struct i2c_driver;
union i2c_smbus_data;
struct i2c_board_info;
上面的几个数据结构
Device_register函数主要是设备的初始化和添加过程,Device_add主要是向核心注册过程,向总线匹配驱动,创建设备属性文件,与class创建链接过程。在USB子系统一节关于驱动模型已经说明白,设备向总线注册的过程同时会与总线下已经注册的驱动相匹配的过程。
I2c_scan_static_board_info函数主要是在板内寻找i2c设备的过程。
Bus_for_each_dri对总线内的每个i2c_driver驱动调用一次i2c_detect函数。
从上图中可以看出,对于每种适配器都有自己的通信算法,core层屏蔽了底层硬件的差异。而向适配器提供了一系列方法,这些方法包括:
1. core集中处理adapter结构,adapter是对硬件层的抽象描述
2. 为了让操作系统识别,必须向系统注册设备,包括注册总线自身,向用户空间信息的导出
3. 由于本层不会涉及到i2c的客户层。由于适配器可能不止一种,因此,在core层有设备和驱动的匹配过程.