Linux I2C驱动完全分析(一)
2015-12-18 10:50
579 查看
版权声明: 可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息。 ----http://blog.csdn.net/ypoflyer/article/details/6376545
博主按:其实老早就想写这个I2C的了,期间有各种各样的事情给耽误了。借着五一放假的时间把这个写出来,供同志们参考。以后会花一些时间深入研究下内核,虽然以前对内核也有所了解,但是还不系统。I2C的硬件结构并不复杂,一个适配器加几个设备而已。Linux下驱动的体系结构看着挺复杂,实际也是比较简单的。在本文中我还是使用实际的例子,结合硬件和软件两个方面来介绍。希望能给初学的同志们一些帮助,另外抛砖引玉,希望高手能给一些指点。话不多说,开整!~
本文用到的一些资源:
1. Source Insight软件
2. mini2440原理图。 下载地址http://wenku.baidu.com/view/0521ab8da0116c175f0e48fe.html
3. S3C2440 datasheet
4. AT24C08 datasheet
5. Bq27200 datasheet
6. kernel 2.6.31中的At24.c ,Bq27x00_battery.c和i2c-s3c2410.c
7. mini2440的板文件mach-mini2440.c
8. 参考资料:《linux设备驱动开发详解(第2版)》 by 宋宝华
本文的结构:
第一部分:At24C08驱动
1. mini2440中at24c08的电气连接
2. Linux中I2C驱动框架分析
3. I2C总线驱动代码分析
4. at24c08驱动代码分析
第二部分:Bq27200驱动
1. Bq27200的典型应用电路
2. 主要分析一下ba27x00的代码,对比at24c08来加深理解。
---------------------我是分割线----------------------
第一部分
1. mini2440中at24c08的电气连接及其板文件
如下图。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/3e2c0c182c0c665937e3b0ae8f6bdc52.gif)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/058be0f16c75eca3dcee2bea8af54aaa.gif)
24C08的I2C接口是与2440的IICSCL/IICSDA直接相连的。在2440内部集成了一个I2C控制器,可以通过寄存器来控制它。先来和这四个寄存器混个脸熟吧,后面分析时还会经常用到这四个寄存器。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/f8dbff6bf905aad617f97f7490553a86.gif)
在mini2440的板文件中可以找到关于at24c08的内容,如下:
[c-sharp] view
plaincopy
/*
* I2C devices
*/
static struct at24_platform_data at24c08 = {
.byte_len = SZ_8K / 8,
.page_size = 16,
};
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50),
.platform_data = &at24c08,
},
};
static void __init mini2440_init(void)
{
... ...
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
... ...
}
可以看出,在mini2440的init函数中注册了一个i2c的设备,这个设备我们使用了一个结构体i2c_board_info来描述。这个结构体定义在i2c.h文件中。如下:
[c-sharp] view
plaincopy
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
int irq;
};
其中的platform_data又指向一个at24_platform_data结构体。
以上只是at24c08的部分,在板文件中还可以看到关于2440内部i2c控制器的部分,如下:
[c-sharp] view
plaincopy
static struct platform_device *mini2440_devices[] __initdata = {
... ...
&s3c_device_i2c0,
... ...
};
static void __init mini2440_init(void)
{
... ...
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
... ...
}
其中s2c_device_i2c0定义在arch/arm/plat-s3c/Dev-i2c0.c中(在同一目录下还可以看到很多Dev-开头的c文件,都是2440内部集成的各种设备),仔细看下面的代码再对比2440的datasheet就可以很清楚的知道:
* 控制器的IO起始地址为S3C_PA_IIC =0x54000000,大小是4K,中断号是43 = IRQ_IIC S3C2410_IRQ(27)
* 控制器名是"s3c2410-i2c"
[c-sharp] view
plaincopy
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
2. Linux中I2C驱动框架分析
这部分是本文的重点部分。根据上面的电气连接关系我们可以看出,我们要想操作24c08,必须要做两方面的驱动。
第一方面: 2440中I2C控制器的驱动,有了这部分驱动,我们才可以操作控制器来产生I2C的时序信号,来发送数据和接收数据。
第二方面: 24C08的驱动,有了这部分驱动,才能使用控制器正确操作芯片,来读取和存放数据。
在Linux系统中,对上边第一方面的实现叫做I2C总线驱动,对第二方面的实现叫做I2C设备驱动。一般来说,如果CPU中集成了I2C控制器并且Linux内核支持这个CPU,那么总线驱动方面就不用我们操心了,内核已经做好了。但如果CPU中没有I2C控制器,而是外接的话,那么就要我们自己实现总线驱动了。对于设备驱动来说,一般常用的驱动也都包含在内核中了,如果我们用了一个内核中没有的芯片,那么就要自己来写了。
Linux中I2C体系结构如下图所示(图片来源于网络)。图中用分割线分成了三个层次:用户空间(也就是应用程序),内核(也就是驱动部分)和硬件(也就是实际物理设备,这里就是2440中的i2c控制器和at24c08)。这个够清晰了吧?我们现在就是要研究中间那一层。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/6f952426d2519488d5440bc7ee301fbc.gif)
由上图我们还可以看出哪些信息呢?
1). 可以看到几个重要的组成部分,它们是:Driver,Client,i2c-dev,i2c-core,Algorithm,Adapter。这几个部分在内核中都有相应的数据结构,定义在i2c.h文件中,尽量避免粘贴打断代码来凑数,就不贴出来了。简要概括一下每个结构体的意义。
Driver --> struct i2c_driver
这个结构体对应了驱动方法,重要成员函数有probe,remove,suspend,resume。
还包括一个重要的数据结构: struct i2c_device_id *id_table; 如果驱动可以支持好几个设备,那么这里面就要包含这些设备的ID
Client --> struct i2c_client
应用程序是选择性失明的,它只能看到抽象的设备文件,其他部分都是看不见的。图中只有Client与应用程序有联系,所以我们可以大胆得出结论:这个Client是对应于真实的物理设备,在本文就是at24c08。 所以很显然这个结构体中的内容应该是描述设备的。包含了芯片地址,设备名称,设备使用的中断号,设备所依附的控制器,设备所依附的驱动等内容。
Algorithm -->struct i2c_algorithm
Algorithm就是算法的意思。在这个结构体中定义了一套控制器使用的通信方法。其中关键函数是master_xfer()。我们实际工作中的重要一点就是要实现这个函数。
[c-sharp] view
plaincopy
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
Adapter --> struct i2c_adapter
这个结构体对应一个控制器。其中包含了控制器名称,algorithm数据,控制器设备等。
2). 可以看出,i2c-core起到了关键的承上启下的作用。事实上也是这样,我们将从这里展开来分析。源代码位于drivers/i2c/i2c-core.c中。在这个文件中可以看到几个重要的函数。
*增加/删除i2c控制器的函数
[c-sharp] view
plaincopy
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_del_adapter(struct i2c_adapter *adap)
*增加/删除设备驱动的函数
[c-sharp] view
plaincopy
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
*增加/删除i2c设备的函数
[c-sharp] view
plaincopy
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
void i2c_unregister_device(struct i2c_client *client)
注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函数。之后attach被merge到了i2c_new_device中,而detach直接被unresister取代。实际上这两个函数内部都是调用了device_register()和device_unregister()。源码如下:
*I2C传输、发送和接收函数
[c-sharp] view
plaincopy
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
其中send和receive分别都调用了transfer函数,而transfer也不是直接和硬件交互,而是调用algorithm中的master_xfer()函数,所以我们要想进行数据传输,必须自己来实现这个master_xfer()函数,这是总线驱动开发的重点之一。下面以read()系统调用的流程来简单梳理一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/27c8cdb0e116ebebde60433c022380e5.gif)
博主按:大热的天,刚刚负重从五道口走到石板房,大约4公里吧。终于让我找了一个咖啡屋休息一下,继续写这篇驱动分析。单身的生活就是这样无聊啊。 不发牢骚了,活出个样儿来给自己看!千难万险脚下踩,啥也难不倒咱!继续整!~
先说一下,本文中有个疑惑,一直没有搞懂,写在这里,望高人指点一二,不胜感激!
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
这里I2C_FUNC_PROTOCOL_MANGLING 是什么意思?为什么定义这些东东?看了注释也不太理解。求解释!
3. I2C总线驱动代码分析
s3c2440的总线驱动代码在i2c-s3c2410.c中。照例先从init看起。
[c-sharp] view
plaincopy
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
在init中只是调用了平台驱动注册函数注册了一个i2c的平台驱动s3c24xx_i2c_driver。这个驱动是一个platform_driver的结构体变量。注意这里不是i2c_driver结构体,因为i2c_driver是对设备的驱动,而这里对控制器的驱动要使用platform_driver
[c-sharp] view
plaincopy
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.suspend_late = s3c24xx_i2c_suspend_late,
.resume = s3c24xx_i2c_resume,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
},
};
同样的,重要的函数还是那几个:probe,remove,suspend_late,resume。再加上一个id_table和device_driver结构体变量。
下面逐个分析:
* probe函数
当调用platform_driver_register函数注册platform_driver结构体时,probe指针指向的s3c24xx_i2c_probe函数将会被调用。这部分详细解释参考本博客另一篇文章《S3C2410看门狗驱动分析》。细心的朋友可能会发现,在s3c24xx_i2c_driver中,驱动的名字是"s3c-i2c",而在板文件中可以看到,设备的名字是"s3c2410-i2c",这两个名字不一样,那驱动和设备是如何match的呢?答案就在于id_table。这个id_table包含了驱动所支持的设备ID表。在match的时候,判断这个表中的名字是不是和设备一致,一致则match成功。这也是为什么一个驱动可以同时match成功多个设备的原因。如果只是靠platform_driver-->driver中的名字来匹配的话,那么驱动和设备只能是一对一的关系了。
[c-sharp] view
plaincopy
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
扯远了,还是看看probe的代码吧~
[c-sharp] view
plaincopy
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata;
struct resource *res;
int ret;
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "no platform data/n");
return -EINVAL;
}
//给s3c24xx_i2c结构体申请空间
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state/n");
return -ENOMEM;
}
//填充s3c24xx_i2c结构体中各项,包括名称、所有者、算法、所属class等等
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //这个下面会重点介绍
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
// 找到i2c始终并且使能它
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock/n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p/n", i2c->clk);
clk_enable(i2c->clk);
/* map the registers */
/*映射寄存器*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource/n");
ret = -ENOENT;
goto err_clk;
}
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO/n");
ret = -ENXIO;
goto err_clk;
}
i2c->regs = ioremap(res->start, resource_size(res));
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO/n");
ret = -ENXIO;
goto err_ioarea;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)/n",
i2c->regs, i2c->ioarea, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
/*s3c24xx_i2c结构体变量i2c的必要的信息都填充完了以后,开始进行初始化*/
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
//接下来申请中断
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ/n");
goto err_iomap;
}
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ %d/n", i2c->irq);
goto err_iomap;
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier/n");
goto err_irq;
}
/* Note, previous versions of the driver used i2c_add_adapter()
* to add the bus at any number. We now pass the bus number via
* the platform data, so if unset it will now default to always
* being bus 0.
*/
i2c->adap.nr = pdata->bus_num;
//看到了吧?下面调用了i2c-core中的i2c_add_adapter函数来添加一个i2c控制器
//i2c_add_numbered_adapter和i2c_add_adapter的区别在于前者用来添加一个在CPU内
//部集成的适配器,而后者用来添加一个CPU外部的适配器。显然这里应该用前者。
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core/n");
goto err_cpufreq;
}
platform_set_drvdata(pdev, i2c);
dev_info(&pdev->dev, "%s: S3C I2C adapter/n", dev_name(&i2c->adap.dev));
return 0;
err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
err_irq:
free_irq(i2c->irq, i2c);
err_iomap:
iounmap(i2c->regs);
err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
err_noclk:
kfree(i2c);
return ret;
}
*remove函数
这是和probe相反的一个函数,在i2c_adap_s3c_exit时调用。主要功能是注销适配器,释放中断,释放内存区域,禁止始终等等。看到上边代码中的err_的各个部分了吧? remove是它们的汇总。
*suspend函数和resume函数
把这两个放一起说吧,挂起和恢复函数。挂起时保存状态并置标志位,恢复时重新初始化i2c适配器并置标志位。
Algorithm
哎呀我去,终于到这了。憋得我难受啊。这里要重点介绍一下,不仅要知其然,还要知其所以然,这样我们以后自己写驱动的时候就有把握了。
[c-sharp] view
plaincopy
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
这里实现的就是这个s3c24xx_i2c_xfer。这个是控制器能不能动作的关键,缺了这个,控制器就是废铜烂铁。
[c-sharp] view
plaincopy
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret;
for (retry = 0; retry < adap->retries; retry++) {
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
if (ret != -EAGAIN)
return ret;
dev_dbg(i2c->dev, "Retrying transmission (%d)/n", retry);
udelay(100);
}
return -EREMOTEIO;
}
完成任务的函数是s3c24xx_i2c_doxfer(),源码清单如下,
[c-sharp] view
plaincopy
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;
if (i2c->suspended)
return -EIO;
ret = s3c24xx_i2c_set_master(i2c);
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)/n", ret);
ret = -EAGAIN;
goto out;
}
spin_lock_irq(&i2c->lock);
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);
spin_unlock_irq(&i2c->lock);
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
ret = i2c->msg_idx;
/* having these next two as dev_err() makes life very
* noisy when doing an i2cdetect */
if (timeout == 0)
dev_dbg(i2c->dev, "timeout/n");
else if (ret != num)
dev_dbg(i2c->dev, "incomplete xfer (%d)/n", ret);
/* ensure the stop has been through the bus */
msleep(1);
out:
return ret;
}
上面代码可以分成几个部分来看:
* s3c24xx_i2c_set_master() 这个函数每隔1ms查看一次i2c总线状态,timeout是400ms,如果在这期间总线状态不忙,则返回零。否则返回-ETIMEDOUT
* 将要发送的消息和其他信息付给i2c->msg和其他变量,并将状态设置为STATE_START
* s3c24xx_i2c_enable_irq() 使能中断
* s3c24xx_i2c_message_start() 重中之重啊。在看代码之前先来看看2440的datasheet上是怎么说的吧。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/1745f2315f86ccc821073e4f99d14011.gif)
代码清单如下:
[c-sharp] view
plaincopy
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;
unsigned long stat;
unsigned long iiccon;
stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN;
if (msg->flags & I2C_M_RD) {//如果是read data, from slave to master stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1;
} else
stat |= S3C2410_IICSTAT_MASTER_TX;
if (msg->flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
/* todo - check for wether ack wanted or not */
s3c24xx_i2c_enable_ack(i2c);
iiccon = readl(i2c->regs + S3C2410_IICCON);
writel(stat, i2c->regs + S3C2410_IICSTAT);
dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS/n", stat, addr);
writeb(addr, i2c->regs + S3C2410_IICDS);
/* delay here to ensure the data byte has gotten onto the bus
* before the transaction is started */
ndelay(i2c->tx_setup);
dev_dbg(i2c->dev, "iiccon, %08lx/n", iiccon);
writel(iiccon, i2c->regs + S3C2410_IICCON);
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT);
}
(今天没写完啊,明天继续~)
博主按:其实老早就想写这个I2C的了,期间有各种各样的事情给耽误了。借着五一放假的时间把这个写出来,供同志们参考。以后会花一些时间深入研究下内核,虽然以前对内核也有所了解,但是还不系统。I2C的硬件结构并不复杂,一个适配器加几个设备而已。Linux下驱动的体系结构看着挺复杂,实际也是比较简单的。在本文中我还是使用实际的例子,结合硬件和软件两个方面来介绍。希望能给初学的同志们一些帮助,另外抛砖引玉,希望高手能给一些指点。话不多说,开整!~
本文用到的一些资源:
1. Source Insight软件
2. mini2440原理图。 下载地址http://wenku.baidu.com/view/0521ab8da0116c175f0e48fe.html
3. S3C2440 datasheet
4. AT24C08 datasheet
5. Bq27200 datasheet
6. kernel 2.6.31中的At24.c ,Bq27x00_battery.c和i2c-s3c2410.c
7. mini2440的板文件mach-mini2440.c
8. 参考资料:《linux设备驱动开发详解(第2版)》 by 宋宝华
本文的结构:
第一部分:At24C08驱动
1. mini2440中at24c08的电气连接
2. Linux中I2C驱动框架分析
3. I2C总线驱动代码分析
4. at24c08驱动代码分析
第二部分:Bq27200驱动
1. Bq27200的典型应用电路
2. 主要分析一下ba27x00的代码,对比at24c08来加深理解。
---------------------我是分割线----------------------
第一部分
1. mini2440中at24c08的电气连接及其板文件
如下图。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/3e2c0c182c0c665937e3b0ae8f6bdc52.gif)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/058be0f16c75eca3dcee2bea8af54aaa.gif)
24C08的I2C接口是与2440的IICSCL/IICSDA直接相连的。在2440内部集成了一个I2C控制器,可以通过寄存器来控制它。先来和这四个寄存器混个脸熟吧,后面分析时还会经常用到这四个寄存器。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/f8dbff6bf905aad617f97f7490553a86.gif)
在mini2440的板文件中可以找到关于at24c08的内容,如下:
[c-sharp] view
plaincopy
/*
* I2C devices
*/
static struct at24_platform_data at24c08 = {
.byte_len = SZ_8K / 8,
.page_size = 16,
};
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50),
.platform_data = &at24c08,
},
};
static void __init mini2440_init(void)
{
... ...
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
... ...
}
可以看出,在mini2440的init函数中注册了一个i2c的设备,这个设备我们使用了一个结构体i2c_board_info来描述。这个结构体定义在i2c.h文件中。如下:
[c-sharp] view
plaincopy
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
int irq;
};
其中的platform_data又指向一个at24_platform_data结构体。
以上只是at24c08的部分,在板文件中还可以看到关于2440内部i2c控制器的部分,如下:
[c-sharp] view
plaincopy
static struct platform_device *mini2440_devices[] __initdata = {
... ...
&s3c_device_i2c0,
... ...
};
static void __init mini2440_init(void)
{
... ...
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
... ...
}
其中s2c_device_i2c0定义在arch/arm/plat-s3c/Dev-i2c0.c中(在同一目录下还可以看到很多Dev-开头的c文件,都是2440内部集成的各种设备),仔细看下面的代码再对比2440的datasheet就可以很清楚的知道:
* 控制器的IO起始地址为S3C_PA_IIC =0x54000000,大小是4K,中断号是43 = IRQ_IIC S3C2410_IRQ(27)
* 控制器名是"s3c2410-i2c"
[c-sharp] view
plaincopy
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
2. Linux中I2C驱动框架分析
这部分是本文的重点部分。根据上面的电气连接关系我们可以看出,我们要想操作24c08,必须要做两方面的驱动。
第一方面: 2440中I2C控制器的驱动,有了这部分驱动,我们才可以操作控制器来产生I2C的时序信号,来发送数据和接收数据。
第二方面: 24C08的驱动,有了这部分驱动,才能使用控制器正确操作芯片,来读取和存放数据。
在Linux系统中,对上边第一方面的实现叫做I2C总线驱动,对第二方面的实现叫做I2C设备驱动。一般来说,如果CPU中集成了I2C控制器并且Linux内核支持这个CPU,那么总线驱动方面就不用我们操心了,内核已经做好了。但如果CPU中没有I2C控制器,而是外接的话,那么就要我们自己实现总线驱动了。对于设备驱动来说,一般常用的驱动也都包含在内核中了,如果我们用了一个内核中没有的芯片,那么就要自己来写了。
Linux中I2C体系结构如下图所示(图片来源于网络)。图中用分割线分成了三个层次:用户空间(也就是应用程序),内核(也就是驱动部分)和硬件(也就是实际物理设备,这里就是2440中的i2c控制器和at24c08)。这个够清晰了吧?我们现在就是要研究中间那一层。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/6f952426d2519488d5440bc7ee301fbc.gif)
由上图我们还可以看出哪些信息呢?
1). 可以看到几个重要的组成部分,它们是:Driver,Client,i2c-dev,i2c-core,Algorithm,Adapter。这几个部分在内核中都有相应的数据结构,定义在i2c.h文件中,尽量避免粘贴打断代码来凑数,就不贴出来了。简要概括一下每个结构体的意义。
Driver --> struct i2c_driver
这个结构体对应了驱动方法,重要成员函数有probe,remove,suspend,resume。
还包括一个重要的数据结构: struct i2c_device_id *id_table; 如果驱动可以支持好几个设备,那么这里面就要包含这些设备的ID
Client --> struct i2c_client
应用程序是选择性失明的,它只能看到抽象的设备文件,其他部分都是看不见的。图中只有Client与应用程序有联系,所以我们可以大胆得出结论:这个Client是对应于真实的物理设备,在本文就是at24c08。 所以很显然这个结构体中的内容应该是描述设备的。包含了芯片地址,设备名称,设备使用的中断号,设备所依附的控制器,设备所依附的驱动等内容。
Algorithm -->struct i2c_algorithm
Algorithm就是算法的意思。在这个结构体中定义了一套控制器使用的通信方法。其中关键函数是master_xfer()。我们实际工作中的重要一点就是要实现这个函数。
[c-sharp] view
plaincopy
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
Adapter --> struct i2c_adapter
这个结构体对应一个控制器。其中包含了控制器名称,algorithm数据,控制器设备等。
2). 可以看出,i2c-core起到了关键的承上启下的作用。事实上也是这样,我们将从这里展开来分析。源代码位于drivers/i2c/i2c-core.c中。在这个文件中可以看到几个重要的函数。
*增加/删除i2c控制器的函数
[c-sharp] view
plaincopy
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_del_adapter(struct i2c_adapter *adap)
*增加/删除设备驱动的函数
[c-sharp] view
plaincopy
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
*增加/删除i2c设备的函数
[c-sharp] view
plaincopy
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
void i2c_unregister_device(struct i2c_client *client)
注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函数。之后attach被merge到了i2c_new_device中,而detach直接被unresister取代。实际上这两个函数内部都是调用了device_register()和device_unregister()。源码如下:
*I2C传输、发送和接收函数
[c-sharp] view
plaincopy
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
其中send和receive分别都调用了transfer函数,而transfer也不是直接和硬件交互,而是调用algorithm中的master_xfer()函数,所以我们要想进行数据传输,必须自己来实现这个master_xfer()函数,这是总线驱动开发的重点之一。下面以read()系统调用的流程来简单梳理一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/27c8cdb0e116ebebde60433c022380e5.gif)
博主按:大热的天,刚刚负重从五道口走到石板房,大约4公里吧。终于让我找了一个咖啡屋休息一下,继续写这篇驱动分析。单身的生活就是这样无聊啊。 不发牢骚了,活出个样儿来给自己看!千难万险脚下踩,啥也难不倒咱!继续整!~
先说一下,本文中有个疑惑,一直没有搞懂,写在这里,望高人指点一二,不胜感激!
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
这里I2C_FUNC_PROTOCOL_MANGLING 是什么意思?为什么定义这些东东?看了注释也不太理解。求解释!
3. I2C总线驱动代码分析
s3c2440的总线驱动代码在i2c-s3c2410.c中。照例先从init看起。
[c-sharp] view
plaincopy
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
在init中只是调用了平台驱动注册函数注册了一个i2c的平台驱动s3c24xx_i2c_driver。这个驱动是一个platform_driver的结构体变量。注意这里不是i2c_driver结构体,因为i2c_driver是对设备的驱动,而这里对控制器的驱动要使用platform_driver
[c-sharp] view
plaincopy
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.suspend_late = s3c24xx_i2c_suspend_late,
.resume = s3c24xx_i2c_resume,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
},
};
同样的,重要的函数还是那几个:probe,remove,suspend_late,resume。再加上一个id_table和device_driver结构体变量。
下面逐个分析:
* probe函数
当调用platform_driver_register函数注册platform_driver结构体时,probe指针指向的s3c24xx_i2c_probe函数将会被调用。这部分详细解释参考本博客另一篇文章《S3C2410看门狗驱动分析》。细心的朋友可能会发现,在s3c24xx_i2c_driver中,驱动的名字是"s3c-i2c",而在板文件中可以看到,设备的名字是"s3c2410-i2c",这两个名字不一样,那驱动和设备是如何match的呢?答案就在于id_table。这个id_table包含了驱动所支持的设备ID表。在match的时候,判断这个表中的名字是不是和设备一致,一致则match成功。这也是为什么一个驱动可以同时match成功多个设备的原因。如果只是靠platform_driver-->driver中的名字来匹配的话,那么驱动和设备只能是一对一的关系了。
[c-sharp] view
plaincopy
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
扯远了,还是看看probe的代码吧~
[c-sharp] view
plaincopy
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata;
struct resource *res;
int ret;
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "no platform data/n");
return -EINVAL;
}
//给s3c24xx_i2c结构体申请空间
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state/n");
return -ENOMEM;
}
//填充s3c24xx_i2c结构体中各项,包括名称、所有者、算法、所属class等等
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //这个下面会重点介绍
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
// 找到i2c始终并且使能它
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock/n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p/n", i2c->clk);
clk_enable(i2c->clk);
/* map the registers */
/*映射寄存器*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource/n");
ret = -ENOENT;
goto err_clk;
}
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO/n");
ret = -ENXIO;
goto err_clk;
}
i2c->regs = ioremap(res->start, resource_size(res));
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO/n");
ret = -ENXIO;
goto err_ioarea;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)/n",
i2c->regs, i2c->ioarea, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
/*s3c24xx_i2c结构体变量i2c的必要的信息都填充完了以后,开始进行初始化*/
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
//接下来申请中断
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ/n");
goto err_iomap;
}
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ %d/n", i2c->irq);
goto err_iomap;
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier/n");
goto err_irq;
}
/* Note, previous versions of the driver used i2c_add_adapter()
* to add the bus at any number. We now pass the bus number via
* the platform data, so if unset it will now default to always
* being bus 0.
*/
i2c->adap.nr = pdata->bus_num;
//看到了吧?下面调用了i2c-core中的i2c_add_adapter函数来添加一个i2c控制器
//i2c_add_numbered_adapter和i2c_add_adapter的区别在于前者用来添加一个在CPU内
//部集成的适配器,而后者用来添加一个CPU外部的适配器。显然这里应该用前者。
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core/n");
goto err_cpufreq;
}
platform_set_drvdata(pdev, i2c);
dev_info(&pdev->dev, "%s: S3C I2C adapter/n", dev_name(&i2c->adap.dev));
return 0;
err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
err_irq:
free_irq(i2c->irq, i2c);
err_iomap:
iounmap(i2c->regs);
err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
err_noclk:
kfree(i2c);
return ret;
}
*remove函数
这是和probe相反的一个函数,在i2c_adap_s3c_exit时调用。主要功能是注销适配器,释放中断,释放内存区域,禁止始终等等。看到上边代码中的err_的各个部分了吧? remove是它们的汇总。
*suspend函数和resume函数
把这两个放一起说吧,挂起和恢复函数。挂起时保存状态并置标志位,恢复时重新初始化i2c适配器并置标志位。
Algorithm
哎呀我去,终于到这了。憋得我难受啊。这里要重点介绍一下,不仅要知其然,还要知其所以然,这样我们以后自己写驱动的时候就有把握了。
[c-sharp] view
plaincopy
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
这里实现的就是这个s3c24xx_i2c_xfer。这个是控制器能不能动作的关键,缺了这个,控制器就是废铜烂铁。
[c-sharp] view
plaincopy
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret;
for (retry = 0; retry < adap->retries; retry++) {
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
if (ret != -EAGAIN)
return ret;
dev_dbg(i2c->dev, "Retrying transmission (%d)/n", retry);
udelay(100);
}
return -EREMOTEIO;
}
完成任务的函数是s3c24xx_i2c_doxfer(),源码清单如下,
[c-sharp] view
plaincopy
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;
if (i2c->suspended)
return -EIO;
ret = s3c24xx_i2c_set_master(i2c);
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)/n", ret);
ret = -EAGAIN;
goto out;
}
spin_lock_irq(&i2c->lock);
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);
spin_unlock_irq(&i2c->lock);
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
ret = i2c->msg_idx;
/* having these next two as dev_err() makes life very
* noisy when doing an i2cdetect */
if (timeout == 0)
dev_dbg(i2c->dev, "timeout/n");
else if (ret != num)
dev_dbg(i2c->dev, "incomplete xfer (%d)/n", ret);
/* ensure the stop has been through the bus */
msleep(1);
out:
return ret;
}
上面代码可以分成几个部分来看:
* s3c24xx_i2c_set_master() 这个函数每隔1ms查看一次i2c总线状态,timeout是400ms,如果在这期间总线状态不忙,则返回零。否则返回-ETIMEDOUT
* 将要发送的消息和其他信息付给i2c->msg和其他变量,并将状态设置为STATE_START
* s3c24xx_i2c_enable_irq() 使能中断
* s3c24xx_i2c_message_start() 重中之重啊。在看代码之前先来看看2440的datasheet上是怎么说的吧。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/15/1745f2315f86ccc821073e4f99d14011.gif)
代码清单如下:
[c-sharp] view
plaincopy
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;
unsigned long stat;
unsigned long iiccon;
stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN;
if (msg->flags & I2C_M_RD) {//如果是read data, from slave to master stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1;
} else
stat |= S3C2410_IICSTAT_MASTER_TX;
if (msg->flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
/* todo - check for wether ack wanted or not */
s3c24xx_i2c_enable_ack(i2c);
iiccon = readl(i2c->regs + S3C2410_IICCON);
writel(stat, i2c->regs + S3C2410_IICSTAT);
dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS/n", stat, addr);
writeb(addr, i2c->regs + S3C2410_IICDS);
/* delay here to ensure the data byte has gotten onto the bus
* before the transaction is started */
ndelay(i2c->tx_setup);
dev_dbg(i2c->dev, "iiccon, %08lx/n", iiccon);
writel(iiccon, i2c->regs + S3C2410_IICCON);
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT);
}
(今天没写完啊,明天继续~)
相关文章推荐
- linux镜像下载
- linux里查看文件大小
- Linux 系统挂载数据盘
- Linux中“Disk /dev/sdb doesn't contain a valid partition table”解决方案
- Linux 自虐之路(二): 基本操作命令
- linux C socket函数介绍和使用实例
- <Android> 使用Runtime运行linux命令
- 关于ubuntu Linux 系统用startx进入X-windows然后重启无限登陆的解决方式
- CentOS 6 时间,时区,设置修改及时间同步
- pthread_join函数及linux线程
- Linux 之定时任务crontab
- Linux指令--head,tail
- Linux指令--head,tail
- 推荐一篇非常不错的关于Linux系统硬盘、分区及文件系统的基本概念介绍文章
- linux的用户操作
- linux下常用FTP命令 1. 连接ftp服务器
- CentOS6.5 系统挂载 NTFS 分区的移动硬盘
- LINUX基础命令
- Linux指令--more,less
- Linux指令--more,less