简介:
本文主要介绍i2c总线框架,即对i2c的各个层次(i2c总线,i2c核心,i2c设备)进行分析。同时我也会结合程序对框架进行说明。所以本文将分为两部分,第一部分对i2c的框架进行介绍,而第二部分就是结合代码分析。
内核:linux-2.6.22.6
开发板:JZ2440
CPU:S3C2440
i2c从机设备:AT24C08
声明:
在大家看本文之前希望大家能对i2c的基础概念有所了解,因为在本文中你如果对i2c概念还不了解将会很难对后面的代码进行分析。同时本文主要是对韦东山老师上课时的程序进行分析,即对AT24C08这个EEPROM设备进行分析。不过在第一部分关于i2c框架介绍中,对此不需要了解。但第二部分对代码进行分析时有时会用到对S3C2440相关的寄存器使用,如果你对这方面的知识还不了解可以看一下我在前面对这部分内容的翻译:
S3C2440A 第二十章IIC总线接口。
第一部分:i2c的框架
分析i2c驱动程序其实就是对Linux中i2c层次进行分析。因为我们都知道在内核中是面向对象编程的,而对总线——设备——驱动的分析,其实就是各个结构体以及他们所属的层次的分析。而通过分析他们所属地层次进而了解各个层的关系是我们分析驱动框架的一个很好的方法。而很多时候我们就是这样做的。而在i2c内核驱动中,我们是从不同的文件中找到不同的层次关系。我们先了解一下i2c的层次关系。
图片
1 层次关系图来自:
Linux内核I2C子系统驱动(一)
图片
2 层次关系图来自:
Linux i2c子系统(一) _动手写一个i2c设备驱动
上面两幅图是我在网上找的比较好的对i2c驱动介绍的图。而其他的图有些是比较简单,而有些又太过复杂,对于刚学习一段时间这方面知识的同学来说太多的层次反而使他们觉得i2c驱动太过复杂,而失去信心。所以我选择了这两幅图,因为他们都很清楚的说明了在i2c驱动中,主要分三层,即:
i2c总线驱动层,i2c总线核心层以及i2c设备驱动层。而上面图片
1 是对这个层次说明的很好。而相对于图片1,图片2则更能体现出在整个i2c驱动框架中,各个层中的结构体之间的关系。
现在大家就要多看看上面的两幅图来对整个i2c驱动体系有一个整体的认识,因为只有你对这个框架有一个整体认识后,在后面我们对各个部分介绍时,你才能像往框架上放零件,最后对这个体系有一个全面的了解。
现在我们就来介绍搭起这个框架所必不可少的三个分层。
i2c总线驱动层:
该层在通信中起
主机的作用通过该层对下面的从机进行
开始,结束,应答等相关的控制。而该层要根据不同的SOC(片上系统)有不同的总线驱动,而这些SOC的驱动由各个生产厂家提供。而在本文中我们所用到的是
i2c-s3c2410.c(
driversi2cusses)该文件专门为
S3C2410所提供(
而因为我们的S3C2440与S3C2410十分相似,而可以直接使用)。在该层中,我们主要做的是:
1. 填充i2c的适配器(adapter),并将其使用i2c核心层的函数注册。
2. 做片上系统上与i2c主机相关的初始化。
3. 将该适配器通过i2c总线核心层的函数注册到内核中。
i2c总线核心层:
该层在整个i2c驱动框架中起到了
中间层的作用,通过该层连接i2c总线驱动层和i2c设备驱动层。而在该层中
所有的操作是与芯片或者说与设备无关的,所以在该层中的文件是不用改动的我们直接使用就好。而他主要提供的功能为:
1. 为i2c总线驱动层提供注册函数,使适配器注册到内核中的适配器链表中
2. 使用i2c_probe实现主机对从机检测,检测存在调用处理函数
3. 使用i2c_transfer函数实现,驱动程序中实现主从数据传输
4. 使用i2c_register_driver实现将i2c设备驱动层注册到内核中
i2c设备驱动层:
该层在通信中起
从机作用,而该层也是我们要自己
根据不同的i2c从机设备所写层。而在本文中我们使用的是AT24C08芯片所以我们主要写与其相关的设置。而在该层中我们的主要工作是:
1. 实现主机对从机的检测
2. 检测到后注册i2c_client结构体,并做相应的设置,
3. 实现i2c从机通信相关的设置
4. 使用i2c总线核心层的函数将自己添加到内核驱动链表中
上面就是对i2c框架中三个层次的介绍。而我们在上面也说了我们i2c框架使用的是
i2c_type_bus,而他也属于
总线——设备——驱动类型,下面我们在从这一方面对其进行分析:
图片
3 i2c_type_bus图
上面就是i2c_type_bus的图,在上面我们可以很清楚的看到各个部分都做了什么工作,而同时我们也应该
将上面的i2c驱动层次图和i2c_type_bus图相结合。因为他们是从不同的方面对i2c驱动框架进行的介绍。同时他们两幅图又是相互对应,各个部分相关的。例如,在i2c_type_bus图中总线驱动和设备驱动分别对应于i2c层次关系图的总线驱动层和设备驱动层,而i2c_type_bus图中的i2c_add_adapter函数和i2c_add_driver函数则由i2c层次关系图的总线核心层提供。
而介绍完上面的i2c框架的层次和总线类型,下面我们就慢慢对这些层次进行分析。我们先分开分析各个层次中所使用到的结构体,然后我将通过一幅图总的来介绍他们的关系。同时我们知道i2c总线核心层是内核已经帮我们做好的,并且他主要起传输或者说连接总线驱动层和设备驱动层的作用,所以他里面使用的是连接总线驱动层和设备驱动层结构体的方法或函数,而不是去填充结构体。所以我们主要介绍总线驱动层和设备驱动层的结构体。
i2c总线驱动层中的结构体:
我们的i2c总线驱动层主要是S3C2440对于主机i2c的设置。
所以我们先讲对于主机总体设置的这个机构体:
s3c24xx_i2c
struct s3c24xx_i2c {
spinlock_t lock; /* 自旋锁 */
wait_queue_head_t wait; /* 等待序列 */
struct i2c_msg *msg; /* 信息结构体,用于后面适配器与从机通信 */
unsigned int msg_num;
unsigned int msg_idx;
unsigned int msg_ptr;
unsigned int tx_setup; /* 等待数据发送到总线上的一个建立时间 */
enum s3c24xx_i2c_state state; /* i2c状态 */
void __iomem *regs; /* 在S3C2440中所注册的寄存器 */
struct clk *clk; /* 在S3C2440中i2c的时钟 */
struct device *dev; /* 设备 */
struct resource *irq; /* 在S3C2440中所用到的中断信息 */
struct resource *ioarea; /* 在S3C2440中所注册的资源,如寄存器,中断等 */
struct i2c_adapter adap; /* 在S3C2440中所用到的适配器 */
};
从上面我们可以看出他很多都是对主机S3C2440的设置,其中包括
中断,时钟,寄存器,以及适配器。同时我们也可以看出上面包含了两个很重要的结构体,一个是
i2c_msg而另一个是
i2c_adapter。他们的作用分别是,用于传输信息和适配器。下面我们先说结构体:
i2c_adapter。
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
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; /* the adapter device */
int nr;
struct list_head clients;
struct list_head list;
char name[48];
struct completion dev_released;
};
而要说i2c_adapter其实我们更重要的是要介绍他包含的另一个结构体
algo(i2c_algorithm),因为只有使用
i2c_algorithm我们才能实现对信息i2c_msg的传输。所以我们先介绍
i2c_algorithm结构体:
struct i2c_algorithm {
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 *);
};
而从中我们可以发现这个结构体包含的都是
回调函数。当我们使用不同的CPU时会调用不同的函数。而在这其中最主要的就是
master_xfer函数,通过这个函数我们就可以向主机发送各种i2c_msg信息。
上面我们就说到了i2c_msg结构体,现在 又说到,看来他在i2c中的作用还是很大的。
那么我们现在就看看他到底是由什么构成的,同时他又有什么作用?
i2c_msg:
/*
* I2C Message - used for pure i2c transaction, also from /dev interface
*/
struct i2c_msg {
__u16 addr; /* 从设备地址 */
__u16 flags; /* 标志位,下面的define就是各种标志,其中0表示写,而1表示读 */
#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
#define I2C_M_RD 0x01
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* 数据长度 */
__u8 *buf; /* 指向数据的指针 */
};
我们知道
数据传输的三要素:源,目的和长度。
而当标志为0时表示写,那么addr为目的(向从机写)而buf为源(从主机出)。而当标志为1时表示读,那么addr为源(从从机读)而buf为目的(读往主机)。
通过上面的介绍我们就知道了,i2c_msg主要用于
主从机间的数据传输。而同时我们也知道i2c总线的主要作用就是主从机间的数据传输,所以我们知道这个结构体很重要了吧。
上面主要就是总线驱动层的结构体,下面我们介绍设备驱动层的结构体。
设备驱动层的结构体:
在设备驱动层中我们主要用到的有两个,他们分别为
i2c_driver和
i2c_client,下面我们先介绍
i2c_driver。
/*
*一个驱动可以适用于处理一个或者多个像I2C适配器这样的物理设备。
*/
struct i2c_driver {
int id;
unsigned int class;
/*
*告诉驱动有一个新的总线出现,这个程序可以被驱动用来测试新总线是否满足他的条件
*并在其中寻找它支持的芯片,如果找到,
*他将通过i2c_attach_client函数注册一个客户在i2c总线中。
*/
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/*
*告诉驱动一个客户将要被删除并要移除他的私有数据,
*同时,如果客户结构体已经被上面的驱动函数动态分配,
*他在这里必须释放。
*/
int (*detach_client)(struct i2c_client *);
int (*probe)(struct i2c_client *);
int (*remove)(struct i2c_client *);
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);
struct device_driver driver;
struct list_head list;
};
而在我们的i2c_driver中有两个很重要的回调函数他们分别是:
attach_adapter和
detach_client。而
attach_adapter直接调用i2c_probe函数用于确认从机是否存在,如果存在就调用相应的处理函数。相对于attach_adapter,detach_client则是卸载这个驱动后如果之前发现能够支持的设备则调用它来清除。
下面我们介绍:
i2c_client
就像这个结构体介绍的一样,这个结构体
表示一个从机设备,当我们调用i2c_driver的attach_adapter函数确定有从机设备存在时,我们就要使用这个结构体来记录从机设备的信息了。
/**
* struct i2c_client 表示i2c从机设备
*/
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* 从机地址,注意,这里的地址为7位*/
char name[I2C_NAME_SIZE]; /* 从机名 */
struct i2c_adapter *adapter; /* 从机所适用的适配器 */
struct i2c_driver *driver; /* 从机的要调用的驱动 */
int usage_count; /* 从机使用次数 */
struct device dev; /* 设备 */
int irq; /* 中断 */
char driver_name[KOBJ_NAME_LEN];
struct list_head list;
struct completion released;
};
上面就是我们所要介绍的结构体了,而在很多文章中他们会在下面介绍这些结构体的使用方法,而我则不想这么做。我觉现在应该把这些结构体的关系梳理一下。这样有助于我们对这些结构体的理解,而关于结构体的使用方法我会在后面的程序中详细介绍。
下面我们看这些结构体的关系,而我也用一幅图来说明它:
从图中我们可以看出他们都是相互关联的。而具体的关系为:
【i2c_adapter和i2c_client】
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即
i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
【i2c_adapter与i2c_algorithm】
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。
缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
【i2c_driver和i2c_client】
i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client(),i2c_client对应真实的i2c物理设备device,
每个i2c设备都需要一个i2c_client来描述,i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
而在介绍下一部分之前我们总是想使用什么方法将上面所说到的关系都关联起来那,
如何让我的读者对他们内部的联系有更好的理解。我觉得还是要用到上面这幅图:
上面这幅图向我们展示了如何将总线驱动和设备驱动通过i2c_add_adapter和i2c_add_driver函数注册到内核中,并将他们通过总线——设备——驱动模型关联到一起。而下面我将通过代码为大家分析他们内部的结构关系,我想只有知道了他们的这个内部关系,才会对我们后面的工作有帮助。下面我先分析
i2c_add_adapter函数所做的工作(
在分析中我会省略一些没用的代码):
int i2c_add_adapter(struct i2c_adapter *adapter)
{
······················
adapter->nr = id;
return i2c_register_adapter(adapter);
}
从中看他主要是调用了
i2c_register_adapter(adapter)函数:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
···········
mutex_lock(&core_lists);
list_add_tail(&adap->list, &adapters);
················
res = device_register(&adap->dev);
···············
/* 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);
}
}
从上面看在i2c_register_adapter函数中主要做了三件事情:
1. 将适配器放入链表:list_add_tail(&adap->list, &adapters);
2. 将适配器的设备注册到内核中:device_register(&adap->dev);
3. 从drv(设备驱动)链表中找到drv,并调用他的attach_adapter。
通过上面我们可以看到他们的一些关系。我们的总线驱动要通过总线获得设备驱动并调用他的函数。在这里就有了他们三者的关系。即在总线上有总线驱动的适配器链表同时也有设备驱动的驱动链表,而要确定这个适配器是否可以使用要调用设备驱动中的attach_adapter函数来确定。
下面我们分析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)
{
····················
res = driver_register(&driver->driver);
························
list_add_tail(&driver->list,&drivers);
/* 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);
}
}
}
从上面看在i2c_register_driver函数中主要做了三件事情:
1. 将驱动注册到内核中: driver_register(&driver->driver);
2. 将驱动放入链表:list_add_tail(&driver->list,&drivers);
3. 从适配器链表中找到适配器,然后调用驱动的attach_adapter。
通过上面的分析我们同样看到了他和上面i2c_register_adapter一样的关系。
他们都是从对方的链表中一个一个的取出对方的结构体,并使用attach_adapter函数确定适配器与驱动是否合适。
第二部分:通过代码详细分析i2c驱动框架。
通过上面这些的介绍我想大家对i2c驱动框架有了一个大致的认识,现在我们要做我们的第二部分的工作,即通过代码详细分析i2c驱动框架。
而这部分我们又要分为两部分,
一部分讲总线驱动层的主要代码,而
另一部分讲设备驱动层的代码。当然在介绍这两部分代码的时候肯定会有中间层——
i2c核心层。同时总线驱动和设备驱动也有关联就像我上面介绍的总线——设备——驱动模式一样。所以有时候可能要各个方面都涉及,但我会努力将他们将清楚的。我们先讲总线驱动层代码,后讲设备驱动层代码。
而在开始讲解总线驱动层代码之前我要先讲一下在我们的CPU在
archarmplat-s3c24xxdevs.c对i2c相关的资源和设备的设置。而这些设置将在总线驱动层中使用。
/* I2C */
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
EXPORT_SYMBOL(s3c_device_i2c);
从上面我们可以看出在S3C2440中i2c设备的
名称,寄存器的起始地址,以及中断号。而上面的这些设置将在我们后面的分析中用到。
现在我们开始讲总线驱动层代码(driversi2cussesi2c-s3c2410.c):
我们直接从
probe函数开始分析:
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = &s3c24xx_i2c; /* 设置s3c24xx_i2c结构体 */
struct resource *res;
int ret;
/* 找到i2c时钟,并使能他 */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
clk_enable(i2c->clk);
/* 申请资源,映射寄存器 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
pdev->name);
i2c->regs = ioremap(res->start, (res->end-res->start)+1);
/* 设置适配器 */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* 初始化S3C2440的i2c*/
ret = s3c24xx_i2c_init(i2c);
/* 申请资源,初始化中断 */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
pdev->name, i2c);
i2c->irq = res;
/* 将适配器注册进内核 */
ret = i2c_add_adapter(&i2c->adap);
}
从上面我们可以看出在总线驱动层中主要做了七件事:
1. 设置s3c24xx_i2c结构体
2. 找到i2c时钟,并使能他
3. 申请资源,映射寄存器
4. 设置适配器
5. 初始化S3C2440的i2c
6. 申请资源,初始化中断
7. 将适配器注册进内核
我们首先讲第一件事,在上面程序中是一笔带过的struct s3c24xx_i2c *i2c = &s3c24xx_i2c;但是
s3c24xx_i2c却是早有定义的:
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
.adap = {
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm, /* 主机传输算法 */
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};
我们在上面已经对s3c24xx_i2c这个结构体中的各项进行了介绍,现在就不细说了。而我想说的就是
.algo= &s3c24xx_i2c_algorithm。因为我们在前面说过在适配器中最主要的就是这个算法,通过他发送开始信号开始i2c系统。我们在这里分析一下他的层次关系。
s3c24xx_i2c_algorithm:
static const struct i2c_algorithm s3c24xx_i2c_algorithm
.master_xfer= s3c24xx_i2c_xfer,
s3c24xx_i2c_doxfer(i2c, msgs, num);
i2c->msg = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START;
s3c24xx_i2c_enable_irq(i2c);
s3c24xx_i2c_message_start(i2c, msgs);
iiccon = readl(i2c->regs + S3C2410_IICCON);
writel(stat, i2c->regs + S3C2410_IICSTAT);
writeb(addr, i2c->regs + S3C2410_IICDS);
上面就是他们的层次关系,下一级是上级函数里的内容。从上面我们可以看出他最后做的就是发开始信号并将从机地址写入IICDS寄存器。
下面我们继续分析总线驱动层代码,上面其他的几项都讲的很清楚,而只有 第五项:
初始化S3C2440的i2c的函数不是很清楚,我们现在看他:
s3c24xx_i2c_init:
/* s3c24xx_i2c_init
*初始化控制器,设置IO和频率
*/
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
struct s3c2410_platform_i2c *pdata;
unsigned int freq;
/* 获得平台数据 */
pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent);
/* 初始化i2c的IO口 */
s3c2410_gpio_cfgpin(S3C2410_GPE15, S3C2410_GPE15_IICSDA);
s3c2410_gpio_cfgpin(S3C2410_GPE14, S3C2410_GPE14_IICSCL);
/* 将从机地址写入IICADD寄存器 */
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
/* 算出i2c的时钟分配 */
if (s3c24xx_i2c_getdivisor(i2c, pdata, &iicon, &freq) != 0) {
dev_err(i2c->dev, "cannot meet bus frequency required
");
return -EINVAL;
}
/* 使能时钟,使能中断 */
writel(iicon, i2c->regs + S3C2410_IICCON);
/* 如果是2440,设置线控制器 */
if (s3c24xx_i2c_is2440(i2c)) {
dev_dbg(i2c->dev, "S3C2440_IICLC=%08x
", pdata->sda_delay);
writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
}
return 0;
}
从上面可以看出,他们主要做了:
1. 获得平台数据
2. 初始化i2c的IO口
3. 将从机地址写入IICADD寄存器
4. 算出i2c的时钟分配
5. 使能时钟,使能中断
6. 如果是2440,设置线控制器
而从上面我们可以看出他主要做了主机硬件方面的初始化。
我想有人会问,
上面讲了很多中断,那么我们在中断处理函数中做了什么那?
现在我们就来分析
中断处理函数:s3c24xx_i2c_irq
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
struct s3c24xx_i2c *i2c = dev_id;
unsigned long status;
unsigned long tmp;
/* 获得中断所处的状态 */
status = readl(i2c->regs + S3C2410_IICSTAT);
if (status & S3C2410_IICSTAT_ARBITR) {
// deal with arbitration loss
dev_err(i2c->dev, "deal with arbitration loss
");
}
if (i2c->state == STATE_IDLE) {
dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE
");
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;
writel(tmp, i2c->regs + S3C2410_IICCON);
goto out;
}
/* pretty much this leaves us with the fact that we've
* transmitted or received whatever byte we last sent */
i2s_s3c_irq_nextbyte(i2c, status); /* 中断传输 */
out:
return IRQ_HANDLED;
}
其实我们会发现真正重要的就是
i2s_s3c_irq_nextbyte(i2c, status)函数,我们现在分析他:
static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
{
switch (i2c->state) {
case STATE_IDLE:
·······
case STATE_STOP:
········
case STATE_START:
········
case STATE_WRITE:
·······
case STATE_READ:
··········
}
}
虽然代码很长,我做了一下省略,但是从上面代码我们还是很清楚他要做什么,而且代码不难主要是针对i2c相关寄存器的操作。
下面我们分析设备驱动层代码:
而在本例中我们直接以韦东山老师上课讲的
AT24C08的设备驱动代码来讲解。而分析韦东山老师的程序要先从入口程序开始分析:
static int s3c_i2c_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
而在入口函数中第一件事就是调用i2c_add_driver函数
将i2c_driver结构体注册到内核中。接下来我们就分析i2c_driver结构体做了什么?
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx" ,
},
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
};
从上面代码我们看出了它使用了回调函数attach_adapter,而我们在上面使用总线——设备——驱动模型分析总线驱动和设备驱动关系时,说到不管是总线驱动还是设备驱动他们都会调用attach_adapter函数来确定连接到适配器上的从机与驱动是否匹配。所以我们可以知道这个函数很重要,我们现在分析他们:
at24cxx_attach:
static int at24cxx_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
而从中可以看出他直接调用了i2c_probe函数,那么我们分析一下这个函数,我们先分析一下他的参数然后在进入他的内部进行分析,他的第一个参数是
这个从机的适配器,而第二个参数是
从机地址(该从机地址为7位从机地址),而第三个参数是
处理函数,即当通过i2c_probe函数检测到从机与驱动匹配时,调用该处理函数。
下面我们进入
i2c_probe函数
int i2c_probe(struct i2c_adapter *adapter,
struct i2c_client_address_data *address_data,
int (*found_proc) (struct i2c_adapter *, int, int))
{
int i, err;
int adap_id = i2c_adapter_id(adapter);
/* 强制进入第一个检测,并且不会随ignore的影响 */
if (address_data->forces)
···············
/* 如果我们没有用SMBUS_QUICK到这里就停止了 */
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK))
·················
/* 探测进入第二个被做,并且也不受ignore的影响 */
for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2)
···············
/* 正常进入最后做,否则进入ignore */
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);
}
而要说清楚上面的函数我们我就要分析第二个参数了,只有分析清楚第二个参数含义我们才知道下一步该怎么走。
static unsigned short ignore[] = {I2C_CLIENT_END};
static unsigned short normal_addr[] = {0x50,I2C_CLIENT_END}; /* 地址值为7位,如果将0x50改为0x60,
*由于不存在设备地址为0x60的设备
*所以at24cxx_detect不会被调用
*/
static struct i2c_client_address_data * addr_data = {
.normal_i2c = normal_addr, /* 要发出S信号和地址信号并得到ACK信号才能确定是否存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
};
从上面我们知道我们并没有使用forces而是使用正常模式,并且我们的7为从机地址为0x50;
有了现在的分析我们就知道该看哪个函数了吧,是的我们该看
i2c_probe_address函数:
static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,
int (*found_proc) (struct i2c_adapter *, int, int))
{
int err;
/* 确定从机地址匹配 */
if (kind < 0) {
if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL) < 0)
return 0;
/* prevent 24RF08 corruption */
if ((addr & ~0x0f) == 0x50)
i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL);
}
/* 调用处理函数 */
err = found_proc(adapter, addr, kind);
/* -ENODEV can be returned if there is a chip at the given address
but it isn't supported by this chip driver. We catch it here as
this isn't an error. */
if (err == -ENODEV)
err = 0;
if (err)
dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)
",
addr, err);
return err;
}
上面就是我们主要做的工作,即
1. 确定从机地址匹配
2. 在从机地址匹配后,调用处理函数
我们先讲第一个,确定从机地址匹配,我这里只是将他们的调用关系列出:
i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
switch(size) {
case I2C_SMBUS_QUICK:
case I2C_SMBUS_BYTE:
case I2C_SMBUS_BYTE_DATA:
case I2C_SMBUS_WORD_DATA:
case I2C_SMBUS_PROC_CALL:
case I2C_SMBUS_BLOCK_DATA:
case I2C_SMBUS_BLOCK_PROC_CALL:
case I2C_SMBUS_I2C_BLOCK_DATA:
i2c_transfer(adapter, msg, num) < 0)
adap->algo->master_xfer(adap,msgs,num);
我们会发现他最后还是调用总线驱动中的adap->algo->master_xfer(adap,msgs,num)来发送信息以确认从机是否匹配。
下面我们讲在从机地址匹配后,
调用处理函数:at24cxx_detect
static int at24cxx_detect (struct i2c_adapter *adapter, int address, int kind)
{
/* 构造一个i2c_client结构体:以后收发数据时会用到它 */
new_client = kzalloc(sizeof(struct i2c_client),GFP_KERNEL);
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &at24cxx_driver;
strcpy(new_client->name,"at24cxx");
i2c_attach_client(new_client);
/* 注册字符设备驱动 */
auto_major = register_chrdev(0,"at24cxx",&at24cxx_fops);
cls = class_create(THIS_MODULE,""at24cxx"");
class_device_create(cls,NULL,MKDEV(auto_major,0),NULL,"at24cxx");
return 0;
}
从上面我们知道我们做了:
1. 构造一个i2c_client结构体:以后收发数据时会用到它
2. 注册字符设备驱动
我们知道当适配器和驱动匹配后就会建立一个i2c_client结构体来记录这个设备。而上面我们做的就是注册填充这个结构体,并注册他。而做完这个我们后面就可以用他这个从机与主机收发信息了。而我们做的字符设备就是用来做AT24C08收发信息用的。我们现在看看
字符设备驱动做了什么:
static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};
我们可以看到他有一个读函数一个写函数,其实这就是对应的对AT24C08的收发函数了,我们先看
写函数中做了什么:
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
/* address = buf[0],data = buf[1] */
struct i2c_msg msg[1];
unsigned char val[2];
int ret;
if(size != 2)
return -EINVAL;
copy_from_user(val,buf,2);
/* 数据传输三要素:源,目的,长度 */
msg[0].addr = new_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 长度为两byte = 数据 + 地址 */
msg[0].flags= 0; /* 0表示写 */
ret = i2c_transfer(new_client->adapter,msg,1);
if(ret == 1)
return 2;
else
return -EIO;
}
从上面看我们知道他用了i2c_msg结构体确定了数据传输的三要素:源,目的,长度。并通过i2c_transfer(new_client->adapter,msg,1);函数将数据传出。而i2c_transfer函数我们在前面已经分析了,他主要就是调用adap->algo->master_xfer(adap,msgs,num)来传递信息。
而读函数at24cxx_read与写函数相似,只不过在它使用了两个i2c_msg结构体,
第一个用来发送数据,而
第二个用来就是数据。
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
/* address = buf[0],data = buf[1] */
struct i2c_msg msg[2];
unsigned char address,data;
int ret;
if(size != 1)
return -EINVAL;
copy_from_user(&address,buf,1);
/* 数据传输三要素:源,目的,长度 */
msg[0].addr = new_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 1; /* 长度为两byte = 数据 + 地址 */
msg[0].flags= 0; /* 0表示写 */
msg[1].addr = new_client->addr; /* 源 */
msg[1].buf = val; /* 目的 */
msg[1].len = 1; /* 长度为两byte = 数据 + 地址 */
msg[1].flags= I2C_M_RD; /* 1表示写 */
ret = i2c_transfer(new_client->adapter,msg,2);
if(ret == 2)
return 1;
else
return -EIO;
}
而讲到这里我们对i2c框架程序就讲完了,希望对你有所帮助,同时我也会在下一篇文章中讲到使用写OLED的驱动程序。
参考文章:
Linux i2c子系统(一) _动手写一个i2c设备驱动linux驱动学习(八) i2c驱动架构(史上最全) davinc dm368 i2c驱动分析I2C驱动架构详解Linux-I2C驱动Linux驱动子系统之I2C子系统Linux内核I2C子系统驱动(一)转:Linux I2C 驱动分析