本文默认读者掌握裸机下的I2C操作,该部分只做简单介绍, 主要内容是对linux-2.6.22.6系统下I2C驱动的分析。
由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式,每个挂接在总线上的器件都有个唯一的地址。位速在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可待3.4Mbit/s。
I2C总线仅仅使用SCL、 SDA这两根信号线就实现了设备之间的数据交互, 极大地简化了对硬件资源和PCB板布线空间的占用。 因此, I2C总线非常广泛地应用在EEPROM、 实时钟、 小型LCD等设备与CPU的接口中。
在linux-2.6.22.6内核的driver/i2c目录下内有如下文件:
里面保存I2C的通信方面的算法(algorithms)。
里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。
里面保存I2C设备驱动相关的文件,比如ds1374,就是I2C时钟芯片驱动。在具体的I2C设别驱动中,调用的都是I2C核心提供的API,因此,这使得具体的I2C设备驱动不依赖于CPU类型和I2C适配器的硬件特性。
这个文件实现了I2C核心的功能(I2C总线的初始化、注册和适配器添加和注销等相关工作)以及/proc/bus/i2c*接口。
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配为一个设备。通过适配器访问设备是的主设备号都为89,次设备号为0~255。应用程序通过“i2c-%d”(i2c-0、i2c-1、i2c-2...)文件名并使用文件操作结构open()、write()、read()、ioctl()和close()等来访问这个设备。
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用read()等接口,应用程序可以借用这些接口访问适配器上的I2C设别的存储空间或寄存器,并控制I2C设别的工作方式。
I2C体系架构图如下:
主要分为3个部分,I2C核心、I2C总线驱动、I2C设备驱动。
是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2C driver结构和I2C adapter结构。I2C core提供接口函数,允许一个I2C adatper、I2C driver和I2C client初始化时在I2C core中进行注册,以及退出时进行注销。同时还提供了I2C总线读写访问的一般接口。
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的算法i2c_algorithm和控制I2C适配器产生通信信号的函数。经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
在单片机中使用I2C只需要简单地配置几个I2C寄存器,构建包含开始位、停止位、读写周期、ACK等简单概念函数即可,为什么在Linux上需要构建这么复杂的I2C体系架构呢?为了通用性及为了符合Linux内核驱动模式。
对于linux系统来说,支持各式各样的CPU类型,并且还想要满足各种I2C芯片硬件特性,就必须将一些稳定性好的部分抽离出来,这样就抽象出了i2c_client(描述具体的I2C设备)、i2c_driver(描述操作I2C设备方法的接口)、i2c_adpter(描述CPU如何控制I2C总线)、i2c_algorithm(I2C通信算法)等。前两个就是抽象出的I2C设备驱动,后两个为I2C总线驱动。
在一个SoC上可能有多条I2C总线,一条总线对应一个I2C总线驱动(I2C适配器),每一条总线上又可以接多个I2C设备。假如一个SoC上有3个I2C适配器,外接3个I2C设备,想要3个适配器分别都能驱动3个I2C设备,我们只需写3个适配器代码,3个I2C设备代码即可。
下面开始深入内核分析I2C驱动。
内核的I2C驱动围绕着以下几个数据结构进行盘根交错的展开。
struct i2c_adapter是用来描述一个I2C适配器,在SoC中的指的就是内部外设I2C控制器,当向I2C核心层注册一个I2C适配器时就需要提供这样的一个结构体变量。
struct i2c_adapter {
struct module *owner; //所有者
unsigned int id;
unsigned int class; //该适配器支持的从设备的类型
const struct i2c_algorithm *algo; //该适配器与从设备的通信算法
void *algo_data;
/* --- administration stuff. */
int (*client_register)(struct i2c_client *);
int (*client_unregister)(struct i2c_client *);
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
struct mutex clist_lock;
int timeout; //超时时间
int retries; //重试次数
struct device dev; //该适配器设备对应的device
int nr; //适配器的编号
struct list_head clients; //clients链表
struct list_head list; //适配器链表
char name[48]; //适配器的名字
struct completion dev_released; //用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
};
struct i2c_algorithm结构体代表的是适配器的通信算法,在构建i2c_adapter结构体变量的时候会去填充这个元素。
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data * data);
/* --- ioctl like call to set div. parameters. */
int (*algo_control)(struct i2c_adapter *, unsigned int, unsigned long);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
struct i2c_client描述一个I2C次设备。
struct i2c_client {
unsigned short flags; //标志
unsigned short addr; //该I2C设备的设备地址,存放地址高7位
char name[I2C_NAME_SIZE]; //设备名字
struct i2c_adapter *adapter; //依附的i2c_adapter,表示该I2C设备支持哪个适配器
struct i2c_driver *driver; //依附的i2c_driver ,表示该I2C设备的驱动是哪个
struct device dev; //设备结构体
int irq; //设备所使用的结构体
struct list_head detected; //链表头
};
struct i2c_driver描述一个I2C设备驱动。
struct i2c_driver {
int id; //驱动标识
unsigned int class; //所属类
int (*attach_adapter)(struct i2c_adapter *); //匹配适配器函数指针
int (*detach_adapter)(struct i2c_adapter *); //卸载适配器函数指针
int (*detach_client)(struct i2c_client *); //通知驱动程序一个client即将被删除,驱动程序之前动态分配的资源必须在这里被释放
int (*probe)(struct i2c_client *); //设备驱动层的probe函数
int (*remove)(struct i2c_client *); //设备驱动层卸载函数
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
int (*command)(struct i2c_client *client,unsigned int cmd, void *arg); //类似于ioctl的命令接口函数
struct device_driver driver; //该i2c设备驱动所对应的device_driver
struct list_head list; //用来挂接与该i2c_driver匹配成功的i2c_client(次设备)的一个链表头
};
内核是最好的学习工具。想要写一个linux驱动,最好的方法就是分析内核自带的驱动程序。对于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);
if (ret == 0) {
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
}
return ret;
}
首先在platform总线上注册一个platform_driver结构,当platform总线上有同名字的platform_device被注册或已经注册时调用s3c2440_i2c_driver->probe函数。也就是s3c24xx_i2c_probe()函数。
struct i2c_adapter adap;
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
... ...
/*获取,使能I2C时钟*/
i2c->clk = clk_get(&pdev->dev, "i2c"); //获取i2c时钟
clk_enable(i2c->clk); //使能i2c时钟
... ....
/*获取资源*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = ioremap(res->start, (res->end-res->start)+1);
... ....
/*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/
i2c->adap.algo_data = i2c; //i2c_adapter适配器指向s3c24xx_i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
/*初始化2440的I2C相关的寄存器*/
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
... ...
/*注册中断服务函数*/
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);
... ...
/*注册i2c_adapter适配器结构体*/
ret = i2c_add_adapter(&i2c->adap);
... ...
}
该函数作用如下:
1. 设置i2c_adapter适配器结构体。
2. 初始化2440的I2C相关的寄存器。
3. 注册中断服务函数。
4. 注册i2c_adapter适配器结构体。
最后调用i2c_add_adapter()函数添加适配器,i2c_add_adapter()函数实际调用了i2c_register_adapter()函数,函数如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
struct list_head *item; //链表头,用来存放i2c_driver结构体的表头
struct i2c_driver *driver; //i2c_driver,用来描述一个IIC设备驱动
list_add_tail(&adap->list, &adapters); //添加到内核的adapter链表中
... ...
list_for_each(item,&drivers) { //for循环,从drivers链表里找到i2c_driver结构体的表头
driver = list_entry(item, struct i2c_driver, list); //通过list_head表头,找到i2c_driver结构体
if (driver->attach_adapter)
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap);
//调用i2c_driver的attach_adapter函数来看看,这个新注册的设配器是否支持i2c_driver
}
}
该函数作用如下:
1. 将i2c_adapter放入i2c_bus_type的adapter链表。
2. 遍历drivers链表(该drivers链表的成员是i2c_add_driver()添加设备驱动函数里进行添加的)里的i2c_driver,调用i2c_driver->attach_adapter成员函数进行匹配。
其中i2c_adapter结构体是s3c24xx_i2c结构体的成员,i2c-s3c2410.c中定义了这么一个全局变量:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer, //主机传输
.functionality = s3c24xx_i2c_func, //协议支持函数
};
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50, //用来延时,等待SCL被释放
.adap = { // i2c_adapter适配器结构体
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm, //存放i2c_algorithm算法结构体
.retries = 2, //重试次数
.class = I2C_CLASS_HWMON,
},
};
i2c_adapter结构体的名称等于"s3c2410-i2c",它的通信方式就是s3c24xx_i2c_algorithm结构,retries表示重试次数等于2。s3c24xx_i2c_algorithm中的关键函数master_xfer()调用过程:
1. 传输数据时,调用s3c24xx_i2c_algorithm结构体中的数据传输函数s3c24xx_i2c_xfer()。
2. s3c24xx_i2c_xfer()中会调用s3c24xx_i2c_doxfer()进行数据的传输。
3. s3c24xx_i2c_doxfer()中向总线 发送IIC设备地址和开始信号S后,便会调用wati_event_timeout()函数进入等待状态。
4. 将数据准备好发送时,将产生中断,并调用实现注册的中断处理函数s3c24xx_i2c_irq()。
5. s3c24xx_i2c_irq()调用下一个字节传输函数i2s_s3c_irq_nextbyte()来传输数据。
6. 当数据传输完成后,会调用 s3c24xx_i2c_stop()。
7. 最后调用wake_up()唤醒等待队列,完成数据的传输过程。
在后面要讲的I2C设备驱动进行读写I2C设备时,最终就会调用到master_xfer。
i2c-s3c2410.c文件为我们实现了对2440上I2C控制器的设置、I2C通信协议相关代码等,这些就组成了一个适配器adapter(I2C总线驱动),使用这个适配器内核就知道如何与挂在I2C总线上的I2C设备通信了。
I2C总线驱动让内核知道了怎么发数据,那么I2C设备驱动就是让内核知道什么时候发数据和发什么数据。
内核自带的I2C设备驱动有很多,框架都是一样的,这里以linux-2.6.22.6/driver/i2c/chips/ds1374.c为例进行分析。
首先进入ds1374.c驱动的入口ds1374_init()函数:
static struct i2c_driver ds1374_driver = {
.driver = {
.name = DS1374_DRV_NAME, //名称
},
.id = I2C_DRIVERID_DS1374, //IIC设备标识ID
.attach_adapter = ds1374_attach, //用来与总线上的adapter链表上的adapter适配器匹配,匹配成功添加该i2c_driver到适配器adapter中
.detach_client = ds1374_detach, //与总线上的adapter适配器解绑,分离这个IIC从设备
};
... ...
static int __init ds1374_init(void)
{
return i2c_add_driver(&ds1374_driver); //向内核注册一个i2c_driver结构体
}
看看i2c_add_driver()函数做了什么,代码如下:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);
}
直接调用i2c_register_driver()函数:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
... ...
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type; //将i2c_driver放在i2c_bus_type链表中
res = driver_register(&driver->driver); //注册driver
... ...
list_add_tail(&driver->list,&drivers); //将该i2c_driver加入i2c_driver链表
if (driver->attach_adapter) {
struct i2c_adapter *adapter;
/*遍历adapters链表上的i2c_adapter适配器匹配该i2c_driver,
匹配函数为该i2c_driver的attach_adapter成员函数*/
list_for_each_entry(adapter, &adapters, list) {
driver->attach_adapter(adapter);
}
}
... ...
return 0;
}
该函数作用如下:
1. 将i2c_driver添加到i2c_bus_type链表中。
2. 取出adapters链表中所有的i2c_adapter,然后调用i2c_driver->attach_adapter()函数。
如下图可以看出,无论是先注册i2c_adapter适配器还是先注册i2c_driver,都会调用到i2c_driver->attach_adapter()函数进行适配器与驱动的匹配。
下面看看attach_adapter函数也就是ds1374_attach()函数:
static int ds1374_attach(struct i2c_adapter *adap)
{
return i2c_probe(adap, &addr_data, ds1374_probe);
}
只是调用了i2c_probe()函数,传进来3个参数:
1. i2c_adapter适配器。
2. addr_data变量,里面存放了I2C设备地址的信息。
3. 具体的设备探测回调函数ds1374_probe。
addr_data变量是一个struct i2c_client_address_data结构体,该结构体原型如下:
struct i2c_client_address_data {
unsigned short *normal_i2c; //存放正常的设备地址,适配器会去检验该设备地址是否存在总线上
unsigned short *probe; //存放探测的设备地址,适配器会去检验该设备地址是否存在总线上
unsigned short *ignore; //可以存放I2C_CLIENT_END这个宏
unsigned short **forces; //存放强制的设备地址,适配器不检验该设备地址是否存在总线上
};
当上面结构体的数组成员以I2C_CLIENT_END结尾,则表示地址已结束。看这个结构体如何定义的:
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x68, I2C_CLIENT_END };
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr,
.probe = ignore,
.ignore = ignore,
};
i2c_probe()函数通过判断addr_data结构体成员,以不同的参数调用i2c_probe_address()函数,主要有两种效果:
1. 适配器会去检测I2C总线上是否确实挂接有addr_data(probe成员或者normal_i2c成员)里指定的I2C设备地址的设备。(通过判断*normal_i2c或者*probe是否有非I2C_CLIENT_END项)
2. 强制认为I2C总线上存在addr_data(forces成员)里指定的I2C设备地址的设备。
i2c_probe()函数部分代码如下:
int i2c_probe(struct i2c_adapter *adapter,
struct i2c_client_address_data *address_data,
int (*found_proc) (struct i2c_adapter *, int, int))
{
... ...
/*如果address_data指定了强制条目forces*/
/*调用i2c_probe_address函数,第3个参数大于等于0*/
if (address_data->forces) {
unsigned short **forces = address_data->forces;
int kind;
... ...
err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);
... ...
}
... ...
/*如果address_data指定了检测条目probe*/
/*调用i2c_probe_address函数,第3个参数为-1*/
for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) {
... ...
err = i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);
... ...
}
/*如果address_data指定了正常条目normal_i2c*/
/*调用i2c_probe_address函数,第3个参数为-1*/
for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) {
... ...
err = i2c_probe_address(adapter, address_data->normal_i2c[i],-1, found_proc);
... ...
}
return 0;
}
看看i2c_probe_address()函数里具体做了什么:
static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,
int (*found_proc) (struct i2c_adapter *, int, int))
{
... ...
/* 确保有addr中的设备地址的芯片接在I2C总线上(发送该设备地址能收到ACK) */
if (kind < 0) {
if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL) < 0)
return 0;
... ...
}
... ...
/* 调用i2c_probe中传入的第三个参数(检测函数) */
/* 到这里表示I2C总线上确实存在该设备地址的I2C设备,但是可能有些芯片的设备地址是一样的,在该函数里进行区分具体的芯片 */
err = found_proc(adapter, addr, kind);
if (err == -ENODEV)
err = 0;
... ...
return err;
}
该函数作用如下:
1. 调用i2c_smbus_xfer()函数检测是否确实有addr中的设备地址的芯片接在I2C总线上(发送设备地址是否能收到ACK)。
2. 调用i2c_probe()中传入的第三个参数,也就是(到这里表示I2C总线上确实存在该设备地址的I2C设备,但是可能有些芯片的设备地址是一样的,在该函数里进行区分具体的芯片)。
接下来看看i2c_smbus_xfer()函数是如何检验i2c_probe()函数传入的addr_data中的设备地址的,i2c_smbus_xfer()函数部分代码如下:
s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data)
{
s32 res;
flags &= I2C_M_TEN | I2C_CLIENT_PEC;
if (adapter->algo->smbus_xfer) { //如果adapter适配器有smbus_xfer这个函数
mutex_lock(&adapter->bus_lock); //加互斥锁
res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);
//调用adapter适配器里的传输函数
mutex_unlock(&adapter->bus_lock); //解互斥锁
} else //否则使用默认函数传输设备地址
res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
return res;
}
该函数首先判断i2c_adapter适配器成员algo->smbus_xfer函数,可以回顾一下i2c-s3c2410.c中的i2c_adapter适配器是如何定义的:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer, //主机传输
.functionality = s3c24xx_i2c_func, //协议支持函数
};
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50, //用来延时,等待SCL被释放
.adap = { // i2c_adapter适配器结构体
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm, //存放i2c_algorithm算法结构体
.retries = 2, //重试次数
.class = I2C_CLASS_HWMON,
},
};
可见适配器并没有algo->smbus_xfer函数,所以i2c_smbus_xfer()函数实际是使用i2c_smbus_xfer_emulated()函数进行发送数据的,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+3]; //属于 msg[0]的buf成员
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; //属于 msg[1]的buf成员
int num = read_write == I2C_SMBUS_READ?2:1; //如果为读命令,就等于2,表示要执行两次数据传输
struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
{ addr, flags | I2C_M_RD, 0, msgbuf1 }}; //定义两个i2c_msg结构体,
msgbuf0[0] = command; //IIC设备地址最低位为读写命令
... ...
if (i2c_transfer(adapter, msg, num) < 0)
return -1;
/*设置i2c_msg结构体成员*/
if (read_write == I2C_SMBUS_READ)
switch(size) {
... ...
/*读操作需要两个i2c_msg,写操作需要一个i2c_msg*/
case I2C_SMBUS_BYTE_DATA:
if (read_write == I2C_SMBUS_READ) //如果是读字节
msg[1].len = 1;
else {
msg[0].len = 2;
msgbuf0[1] = data->byte;
}
break;
... ...
}
... ...
if (i2c_transfer(adapter, msg, num) < 0) //将i2c_msg结构体的内容发送给I2C设备
return -1; //返回值不等于0表示没有收到ACK
... ...
}
该函数主要作用如下:
1. 根据发送类型构造i2c_msg结构体
2. 使用i2c_transfer()函数发送i2c_msg
其中i2c_msg结构体的结构如下所示:
struct i2c_msg {
__u16 addr; //I2C从机的设备地址
__u16 flags; //当flags=0表示写, flags= I2C_M_RD表示读
__u16 len; //传输的数据长度,等于buf数组里的字节数
__u8 *buf; //存放数据的数组
};
i2c_transfer()函数部分代码如下:
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
int ret;
if (adap->algo->master_xfer) {
... ...
ret = adap->algo->master_xfer(adap,msgs,num);
... ...
return ret;
}
... ...
}
最终是调用到了I2C总线驱动里注册的adapter适配器的算法函数master_xfer发送i2c_msg。再次回顾一下3.2.2中分析的master_xfer()调用过程:
1. 传输数据时,调用s3c24xx_i2c_algorithm结构体中的数据传输函数s3c24xx_i2c_xfer()。
2. s3c24xx_i2c_xfer()中会调用s3c24xx_i2c_doxfer()进行数据的传输。
3. s3c24xx_i2c_doxfer()中向总线发送IIC设备地址和开始信号S后,便会调用wati_event_timeout()函数进入等待状态。
4. 将数据准备好发送时,将产生中断,并调用实现注册的中断处理函数s3c24xx_i2c_irq()。
5. s3c24xx_i2c_irq()调用下一个字节传输函数i2s_s3c_irq_nextbyte()来传输数据。
6. 当数据传输完成后,会调用 s3c24xx_i2c_stop()。
7. 最后调用wake_up()唤醒等待队列,完成数据的传输过程。
其中第5点调用i2s_s3c_irq_nextbyte()函数中有如下代码:
if (iicstat & S3C2410_IICSTAT_LASTBIT &&
!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
/* 如果没有收到ACK */
dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -EREMOTEIO);
goto out_ack;
}
当使用适配器发送设备地址没有收到ACK会有如下调用:
s3c24xx_i2c_stop(i2c, -EREMOTEIO);
s3c24xx_i2c_master_complete(i2c, ret); // ret=-EREMOTEIO
if (ret)
i2c->msg_idx = ret; // i2c->msg_idx=-EREMOTEIO
i2c_smbus_xfer()又通过以下调用得到返回值-EREMOTEIO:
i2c_smbus_xfer()
i2c_transfer()
s3c24xx_i2c_xfer()
s3c24xx_i2c_doxfer
ret = i2c->msg_idx; // ret=-EREMOTEIO
return ret;
i2c_smbus_xfer()返回值为0表示正确收到ACK,返回值-EREMOTEIO表示没有收到ACK。也就是说当i2c_smbus_xfer()函数的返回值为0表示I2C总线上确实存在该设备地址。存在的话i2c_probe_address()函数就继续往下执行,调用i2c_probe()中传入的第三个参数(回调函数)。对于ds1374.c这个设备驱动,回调函数即ds1374_probe()。
ds1374_probe()函数部分代码如下:
static int ds1374_probe(struct i2c_adapter *adap, int addr, int kind)
{
struct i2c_client *client; //定义一个i2c_client结构体局部变量
int rc;
client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); //分配内存
if (!client)
return -ENOMEM;
/*设置i2c_client结构体*/
strncpy(client->name, DS1374_DRV_NAME, I2C_NAME_SIZE); //设置名字
client->addr = addr; //设置设备地址
client->adapter = adap; //设置适配器i2c_adapter
client->driver = &ds1374_driver; //设置i2c_driver
ds1374_workqueue = create_singlethread_workqueue("ds1374"); //创建单线程队列
if (!ds1374_workqueue) {
kfree(client);
return -ENOMEM; /* most expected reason */
}
/*注册i2c_client*/
if ((rc = i2c_attach_client(client)) != 0) {
kfree(client); //注册失败,便释放i2c_client这个全局变量
return rc;
}
save_client = client;
ds1374_check_rtc_status(); //检验ds1374芯片状态
return 0;
}
该函数主要内容如下:
1. 定义一个i2c_client结构体
2. 分配相关内存
3. 设置i2c_client结构体
4. 注册i2c_client
当注册了i2c_client从设备后,便可以使用i2c_transfer()函数(实际调用了s3c24xx_i2c_xfer()函数)来实现与I2C设备传输数据了。
使用i2c_add_driver()注册i2c_driver,在该函数里面匹配并使用适配器(通信协议)发送设备地址检测该设备是否真实存在(使用强制地址则不用检测),然后注册i2c_client(用来描述具体的I2C设备信息),并将对应的适配器与该i2c_client关联起来,接在便可以使用i2c_transfer()函数读写I2C设备了。(想要在应用程序里读写设备,可以在注册完i2c_client后创建一个字符设备进行读写芯片。)
I2C总线驱动与I2C设备驱动的调用框图如下:
本节内容不进行I2C总线驱动的编写,只编写I2C设备驱动部分代码。由于使用的JZ2440开发板上没有接I2C设备,所以通过杜邦线外接一个DS3231时钟芯片,后面将会编写一个I2C设备驱动来使用DS3231。
DS3231是低成本、高精度I2C实时时钟RTC,RTC保存秒、分、时、星期、日期、月和年信息。少于31天的月份,将自动调整月末的日期,包括闰年的修正。时钟的工作格式可以是24小时或带/AM/PM指示的12小时格式。提供两个可设置的日历闹钟和一个可设置的方波输出。地址与数据通过I2C双向总线串行传输。详细芯片资料参考:https://html.alldatasheet.com/html-pdf/112132/DALLAS/DS3231/2425/11/DS3231.html
通过芯片手册可得以下信息:
1. 通过读写00h~06h这7个寄存器地址就能读取与设置时钟和日历了。
2. 从设备地址为7位ds3231地址1101000。
设备驱动ds3231.c完整代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define DS3231_REG_SEC 0x00 //秒
#define DS3231_REG_MIN 0x01 //分
#define DS3231_REG_HOURS 0x02 //时
#define DS3231_REG_DAY 0x03 //星期
#define DS3231_REG_DATE 0x04 //日
#define DS3231_REG_MOUNTH 0x05 //月
#define DS3231_REG_YEAR 0x06 //年
#define DS3231_DRV_ADDR 0x68 //ds3231从设备地址
#define DS3231_DRV_NAME "ds3231"
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
int major; //主设备号
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { DS3231_DRV_ADDR, I2C_CLIENT_END };
//static unsigned short force_addr[] = {ANY_I2C_BUS, DS3231_DRV_ADDR, I2C_CLIENT_END};
//static unsigned short * forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr,
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
};
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char time[7];
int reg = DS3231_REG_YEAR;
int i=sizeof(time);
if (size != sizeof(time))
return -EINVAL;
/* 实际调用了i2c_smbus_xfer函数发送数据
* 循环读取时钟与日历寄存器数据,读出来的数据是BCD码,
* 先转换成16进制并拷贝给应用程序
*/
for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
int tmp;
if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
dev_warn(&ds3231_client->dev,
"can't read from ds3231 chip\n");
return -EIO;
}
time[--i] = BCD2BIN(tmp) & 0xff;
}
return copy_to_user(buf, &time, sizeof(time));
}
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char time[7];
int reg;
int i=0;
if (size != sizeof(time))
return -EINVAL;
/* 拷贝要写入的7个16进制时钟与日历值 */
if(copy_from_user(time, buf, sizeof(time)))
return -EINVAL;
/* 实际调用了i2c_smbus_xfer函数发送数据
* 循环写入转换成BCD码的时钟与日历值
*/
for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
dev_warn(&ds3231_client->dev,
"can't write to ds3231 chip\n");
return -EINVAL;
}
i++;
}
return sizeof(time);
}
static struct file_operations ds3231_fops = {
.owner = THIS_MODULE,
.write = ds3231_write,
.read = ds3231_read,
};
static int ds3231_probe(struct i2c_adapter *adap, int addr, int kind)
{
int err;
dev_t devid;
ds3231_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); // 分配内存
if (!ds3231_client)
return -ENOMEM;
/* 设置i2c_client结构体 */
strcpy(ds3231_client->name, DS3231_DRV_NAME); //设置名字
ds3231_client->addr = addr; //设置从设备地址
ds3231_client->adapter = adap; //设置适配器i2c_adapter
ds3231_client->driver = &ds3231_driver; //设置i2c_driver
/* 注册i2c_client结构体 */
if ((err = i2c_attach_client(ds3231_client)) != 0) {
kfree(ds3231_client);
return err;
}
/* 创建字符设备 */
devid = MKDEV(major, 0); //从主设备号major,次设备号0得到dev_t类型
if (major)
{
err=register_chrdev_region(devid, 1, "ds3231"); //注册字符设备
}
else
{
err=alloc_chrdev_region(&devid, 0, 1, "ds3231"); //注册字符设备
major = MAJOR(devid); //从dev_t类型得到主设备
}
if(err < 0)
return err;
cdev_init(&ds3231_cdev, &ds3231_fops);
cdev_add(&ds3231_cdev, devid, 1);
ds3231_class = class_create(THIS_MODULE, "ds3231");
class_device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
return err;
}
/*回调函数*/
static int ds3231_attach(struct i2c_adapter *adap)
{
return i2c_probe(adap, &addr_data, ds3231_probe);
}
/*注销函数*/
static int ds3231_detach(struct i2c_client *client)
{
int rc;
printk("ds3231_detach !\n");
class_device_destroy(ds3231_class, MKDEV(major, 0));
class_destroy(ds3231_class);
cdev_del(&ds3231_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
if ((rc = i2c_detach_client(client)) == 0) {
kfree(i2c_get_clientdata(client));
}
return rc;
}
static struct i2c_driver ds3231_driver = {
.driver = {
.name = DS3231_DRV_NAME,
},
.attach_adapter = ds3231_attach, //回调函数
.detach_client = ds3231_detach, //注销函数
};
static int ds3231_init(void)
{
return i2c_add_driver(&ds3231_driver);
}
static void ds3231_exit(void)
{
i2c_del_driver(&ds3231_driver);
}
module_init(ds3231_init);
module_exit(ds3231_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
测试程序ds3231_test.c完整代码如下:
#include
#include
#include
#include
#include
#include
/* ./ds3231_test r //读时钟与日历格式
* ./ds3231_test w Year Mounth Date Day Hour Min Sec //设置时钟与日历格式
*/
void print_usage(char *file)
{
printf("%s r\n", file);
printf("%s w Year Mounth Date Day Hour Min Sec\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned char buf[7];
int i;
if ((argc != 2) && (argc != 9))
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/ds3231", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/ds3231\n");
return -1;
}
if (strcmp(argv[1], "r") == 0) //读取时钟与日历
{
read(fd, buf, sizeof(buf));
printf("Now Time= Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
}
else if (strcmp(argv[1], "w") == 0) //写时钟与日历
{
for(i=0;i<7;i++)
{
buf[6-i] = strtoul(argv[i+2], NULL, 0); //将字符转换成数值
}
printf("Set Time Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
write(fd, buf, sizeof(buf));
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
Makefile代码如下:
KERN_DIR = /work/system/linux-2.6.22.6 //内核目录
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += ds3231.o
内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10
将ds3231.c、ds3231_text.c、Makefile三个文件放入网络文件系统内,在ubuntu该目录下执行:
make
arm-linux-gcc -o ds3231_text ds3231_text.c
在挂载了网络文件系统的开发板上进入相同目录,执行“ls”查看:
执行如下命令装载驱动与测试:
insmod ds3231.ko //装载驱动
./ds3231_test w 18 12 31 1 23 59 55 //设置时间为:18年12月31日,星期一,23点59分55秒
./ds3231_test r //读取时钟如日历
可以看到如下图,正确设置了时间与读取时间。