上一节 我们学习了:
IIC接口下的AT24C02驱动分析:/zixunimg/eeworldimg/blog.csdn.net/xiaodingqq/article/details/81808875
接下来本节,学习Linux下如何利用linux下I2C驱动体系结构来操作AT24C02
1、I2C体系结构分析
1.1 首先进入linux内核的driver/i2c目录下,如下图所示:
其中重要的文件介绍如下:
1)algos文件夹(algorithms)
里面保存I2C的通信方面的算法
2)busses文件夹
里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。
3)chips文件夹
里面保存I2C设备驱动相关的文件夹,如下图所示,比如mt41t00,就是RTC实时时钟
4) i2c-core.c
这个文件实现了I2C核心的功能(I2C总线的初始化、注册和适配器添加和注销等相关工作)以及/proc/bus/i2c*接口。
5)i2c-dev.c
提供了通用的read()、write()和ioctl()等接口,实现了适配器设备文件的功能,其中I2C设备的主设备号都为89,次设备号为0~255。
应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
显然,它和前几次驱动类似,I2C也分为总线驱动和设备驱动,总线就是协议相关的,它知道如何收发数据,但不知道数据含义,设备驱动却知道数据含义
1.2 I2C驱动架构,如下图所示:
如上图所示,每一条I2C对应一个adapter适配器,在Kernel中,adapter适配器是通过struct adapter结构体定义,主要是通过i2c core层将i2c设备与i2c adapter关联起来。
在kernel中,提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter()。由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号。这个总线号的PCI中的总线号不同。它和硬件无关,只是软件上便于区分而已。
对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败。
2、接下来便分析I2C总线驱动
参考drivers/i2c/busses/i2c-s3c2410.c
在init函数中,注册一个"s3c2440-i2c"的platform平台驱动,我们来看看probe函数做了些什么。
3、进入s3c24xx_i2c_probe函数
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);
}
其中i2c_adapter结构体是放在s3c24xx_i2c->adap下,如下图所示:
4、接下来我们进入i2c_add_adapter()函数看看,到底如何注册的
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) //调用idr_pre_get()为i2c_adapter预留内存空间
return -ENOMEM;
mutex_lock(&core_lists);
/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);
//调用idr_get_new_above()将结构插入i2c_adapter_idr中,并将插入的位置赋给id,以后可以通过id在i2c_adapter_idr中找到相应的i2c_adapter结构体
mutex_unlock(&core_lists);
if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter); //调用i2c_register_adapter()函数进一步来注册.
}
其中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
}
}
在i2c_register_adapter()函数里主要执行以下几步:
1 将adapter放入i2c_bus_type的adapter链表
2 将所有的i2c设备调出来,执行i2c_driver设备的attach_adapter函数来匹配
其中,i2c_driver结构体会在后面讲述到
而i2c_adapter适配器结构体的成员结构,如下所示:
struct i2c_adapter {
struct module *owner; //所属模块
unsigned int id; //algorithm的类型,定义于i2c-id.h,
unsigned int class;
const struct i2c_algorithm *algo; //总线通信方法结构体指针
void *algo_data; //algorithm数据
struct mutex bus_lock; //控制并发访问的自旋锁
struct mutex clist_lock;
int timeout;
int retries; //重试次数
struct device dev; //适配器设备
int nr; //存放在i2c_adapter_idr里的位置号
struct list_head clients;
struct list_head list;
char name[48]; //适配器名称
struct completion dev_released;
};
i2c_adapter表示物理上的一个i2c设备(适配器),在i2c-s3c2410.c中,是存放在s3c24xx_i2c结构体下的(struct i2c_adapter adap)成员中
5、其中s3c24xx_i2c的结构体成员如下所示
/* i2c bus registration info */
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结构体,所以在s3c24xx_i2c_probe()函数中没有分配i2c_adapter适配器结构体
其中,i2c_adapter结构体的名称等于“s3c2410-i2c”,它的通信方式等于s3c24xx_i2c_algorithm,重新次数等于2
PS:如果缺少i2c_algorithm的i2c_adapter什么也做不了,就只是个i2c设备,而没有通信方式
s3c24xx_i2c_algorithm中的关键函数master_xfer()就是用于产生i2c访问周期需要的start stop ack信号
比如,在s3c24xx_i2c_algotithm中的关键函数mater_xfer()里,调用了:
s3c24xx_i2c_xfer->s3c24xx_i2c_doxfer()->s3c24xx_i2c_message_start()
来启动传输message信息,其中s3c24xx_i2c_message_start()函数代码如下:
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;//I2C从设备地址的最低位为读写标志位
... ...
stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN;//设置标志位启动IIC收发使能
if (msg->flags & I2C_M_RD) { //判断是读,还是写
stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1; //设置从IIC设备地址为读标志
} else
stat |= S3C2410_IICSTAT_MASTER_TX;
... ...
s3c24xx_i2c_enable_ack(i2c); //使能ACK信号
iiccon = readl(i2c->regs + S3C2410_IICCON); //读出IICCON寄存器
writel(stat, i2c->regs + S3C2410_IICSTA
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』