最近一段时间,我在网上看了一些关于linux下i2c的文档,对i2c有了一些较浅层次了解。写这篇博客,主要是对现在已经掌握知识的巩固。
Linux下I2C驱动体系结构
Linux下I2C驱动体系结构由 三大部分 构成:
1. I2C核心
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法。它也提供了I2C通信方法(algorithm)上层的、与具体适配器无关的代码 i2c_transfer() 函数,并通过这个函数将总线驱动、用户层串起来。
2.I2C总线驱动
I2C总线驱动就是linux内核中I2C适配器的软件实现,它告诉I2C适配器该怎么工作,从而实现I2C适配器与从设备的通信功能。
我们可以通过这个驱动,使用i2c总线去操控挂接在总线上的设备。
I2C总线驱动由 i2c_adapter 和 i2c_algorithm 这两个结构体来描述。
3.I2C设备驱动
I2C设备驱动是对I2C从设备的软件实现,包括对设备的读、写等操作。
一个具体的I2C设备驱动包括两个部分:
- ①设备驱动代码。用于将设备注册到内核中,并提供了对设备的读写操作。
②设备硬件信息i2c_client。我们也可以通过设备驱动,跳过i2c总线,直接对设备进行操控。
I2C设备驱动由 i2c_driver 和 i2c_client 这两个结构体来描述。
从上面我们知道了,对于i2c总线上的设备,我们可以通过两种不同的方式去控制。一种是利用i2c设备驱动,另一种是利用i2c总线驱动。
所有的I2C驱动代码位于drivers/i2c目录下:
(1) i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
(2)i2c-dev.c
通用i2c设备驱动。就是这个文件实现了用户层利用i2c总线驱动去控制设备的功能,我们会在后面着重分析这个文件。
(3)chips文件夹
这个目录中包含了一些特定的I2C设备驱动,如Dallas公司的DS1337实时钟芯片、EPSON公司的RTC8564实时钟芯片和I2C接口的EEPROM驱动等。
(4)busses文件夹
这个文件中包含了一些I2C总线的驱动,如S3C2410的I2C控制器驱动为i2c-s3c2410.c
(5)algos文件夹
实现了一些I2C总线适配器的algorithm。
I2C核心(i2c-core.c)
初始化模块
static int __init i2c_init(void)/*i2c初始化*/
{
int retval;
retval = bus_register(&i2c_bus_type);/*i2c适配器驱动的注册*/
if (retval)
return retval;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver);/*i2c设备驱动的注册*/
if (retval)
goto class_err;
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
bus_unregister(&i2c_bus_type);
return retval;
}
我们可知i2c_init()函数主要完成的就是i2c总线驱动和i2c设备驱动的注册。
i2c传输,发送和接收
/*用户空间调用write()时,内核调用此函数,此函数最终调用i2c_transfer()*/
int i2c_master_send(const struct i2c_client *client, constchar *buf, int count)
/*用户空间调用read()时,内核调用此函数,此函数最终调用i2c_transfer()*/
int i2c_master_recv(const struct i2c_client *client, char*buf, int count)
/*用于传输数据,此函数是i2c适配器工作的基石,我们放在总线驱动中分析*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
由此我们i2c核心具体完成的就是:
- 实现了I2C总线驱动和设备驱动的注册;
- 提供接口函数i2c_master_send(),i2c_master_recv(),i2c_transfer()实现了用户界面通过适配器去控制i2c设备的功能。
我们接下来看看I2C设备驱动,也就是第一种操控i2c设备的方法。
I2C设备驱动
要想让内核中的i2c设备驱动使能,我们就应该去内核中的make menuconfig中添加设备的支持,但是这个设备在i2c总线上,所以我们必须要先去内核中添加i2c总线的支持,在添加设备支持。我们这里就只拿eeprom设备作为例子去分析。
在添加完i2c总线的支持后,我们再去添加eeprom,这时候,在eeprom下就会出现I2C EEPROM这个选项。
添加完i2c总线和eeprom设备支持后,在sys/devices/platform/s3c2440-i2c/i2c-0中就会出现相对应的设备。
那接下来,我们就来看看i2c设备驱动吧。
从前面我们知道i2c设备驱动是由i2c_driver和i2c_client这两个结构体描述的。
我们首先看一下i2c_driver这个结构体
struct i2c_driver
{
unsignedint class;
int(*attach_adapter)(struct i2c_adapter *) __deprecated; /*依附i2c适配器函数指针*/
int(*detach_adapter)(struct i2c_adapter *) __deprecated;/*脱离i2c适配器函数指针*/
int (*probe)(struct i2c_client*, const struct i2c_device_id *);
int (*remove)(struct i2c_client*);
int(*suspend)(struct i2c_client *, pm_message_t mesg);
int(*resume)(struct i2c_client *);
void(*alert)(struct i2c_client *, unsigned int data);
int(*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id*id_table; /* 该驱动所支持的设备ID表 */
/*Device detection callback for automatic device creation */
int(*detect)(struct i2c_client *, struct i2c_board_info *);
constunsigned short *address_list;
structlist_head clients;
};
我们可以看出i2c_driver结构对应一套具体的驱动方法。
那么i2c_driver这个结构体是怎么实现的呢,drivers/misc/eeprom/at24.c 这个文件就是驱动代码的实现,并且支持大多数I2C接口的eeprom。
i2c_driver的实现
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24", /*设备名字*/
.owner = THIS_MODULE,
},
.probe = at24_probe, /*at24_probe函数*/
.remove = __devexit_p(at24_remove),
.id_table = at24_ids, /*设备id表*/
};
在这个结构体中,我们对i2c_driver结构体中的driver,probe,remove,id_table这些成员进行了赋值。
i2c初始化函数
static int __init at24_init(void)
{
if (!io_limit) {
pr_err("at24: io_limit must not be 0!\n");
return -EINVAL;
}
io_limit = rounddown_pow_of_two(io_limit);
return i2c_add_driver(&at24_driver);
}
在前面的代码中,我们通过构建了at24_driver这个结构体,完成了对i2c_driver结构体的赋值,在初始化函数中,我们调用核心层函数,注册i2c_driver结构体。这样,我们的这个I2C设备层驱动就被作为模块加载到内核中了。
i2c_client
在linux系统下,我们习惯于将驱动代码与硬件信息分隔开,这样可以使我们的驱动代码具有可移植性。因此在驱动i2c_driver的构建之后,我们需要去完成硬件信息的构造,即i2c_client。
我们先看一下i2c_client 结构体
struct i2c_client {
unsigned short flags; /*I2C_CLIENT_TEN:使用10位从地址,I2C_CLIENT_PEC:使用SMBus包错误检测*/
unsignedshort addr; /* chipaddress - NOTE: 7bit */
charname[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* 依附的i2c_adapter */
struct i2c_driver *driver; /* 依附的i2c_driver*/
structdevice dev; /* the devicestructure */
intirq; /* irq issuedby device */
structlist_head detected;
};
我们可以看出结构体这个结构体对应于真实的物理设备,其中包含了芯片地址,设备名称,设备所依附的适配器,设备所依附的驱动,设备使用的中断号等内容。
i2c_client在开发板文件中的实现
At24c不依赖于具体的CPU和I2C控制器硬件特性,因此如果电路板包含该外设,只需要添加对应的i2c_board_info,下面是at24c02 i2c_client在板文件中的实现:
修改文件: linux/arch/arm/mach-s3c2440/mach-smdk2440.c
#include <linux/i2c/at24.h>
static struct at24_platform_data at24c02 = {
.byte_len = SZ_2K / 8, /* eeprom的存储大小,单位Byte */
.page_size = 8, /* 页大小 Byte */
.flags = 0,
};
static struct i2c_board_info __initdata smdk_i2c_devices[] = {
/* more devices can be added using expansion connectors */
{
I2C_BOARD_INFO("24c02", 0x50), /* 24c02设备名,0x50设备地址 */
.platform_data = &at24c02,
},
};
在smdk2440_machine_init函数中增加如下:
i2c_register_board_info(0, smdk_i2c_devices, ARRAY_SIZE(smdk_i2c_devices));
我们调用i2c_register_board_info()函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,但是还没有构建出一个i2c_client结构体,也没有注册进I2C总线。
当我们调用i2c_add_adapter()函数时,会遍历__i2c_board_list获得从设备信息以此来构造i2c_client。
具体过程:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。
at24_ids结构体
在驱动和硬件信息完成之后,系统是怎么把两部分联系到一块呢?其实就是通过驱动和硬件信息里的名字进行匹配。
这里我们就会有一个疑问,在驱动i2c_driver里我们设置”name=at24”,但是在i2c_client构建时,我们给的设备名是”at24c02”,那么他们两个是怎么匹配成功的呢,其实就是根据i2c_driver里的”id_table” 成员,我们是让”id_table= at24_ids”,那么我们来看看at24_ids这个结构体:
static const struct i2c_device_id at24_ids[] = {
/* needs 8 addresses as A0-A2 are ignored */
{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
/* old variants can't be handled with this generic entry! */
{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
/* spd is a 24c02 in memory DIMMs */
{ "spd", AT24_DEVICE_MAGIC(2048 / 8,AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
/* 24rf08 quirk is handled at i2c-core */
{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
{ "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
{ "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
{ "at24", 0 },
{ /* END OF LIST */ }
我们可以看出at24_ids结构体将所有的at24cx系列芯片做了一个集合,我们在匹配信息时会调用i2c bus中注册的i2c_device_match()->i2c_match_id()函数通过i2c_driver->id_table->name和client->name来匹配。
probe()函数
在驱动和硬件信息匹配成功后,系统会执行probe()函数:
static int at24_probe(struct i2c_client*client, const struct i2c_device_id *id)
{
……
/*
* Export the EEPROM bytes through sysfs, sincethat's convenient.
* By default, only root should see the data(maybe passwords etc)
*/
sysfs_bin_attr_init(&at24->bin);
at24->bin.attr.name= "eeprom";
at24->bin.attr.mode= chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
at24->bin.read= at24_bin_read;
at24->bin.size= chip.byte_len;
at24->macc.read= at24_macc_read;
writable = !(chip.flags &AT24_FLAG_READONLY);
if(writable)
{
if(!use_smbus || i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_WRITE_I2C_BLOCK))
{
unsignedwrite_max = chip.page_size;
at24->macc.write= at24_macc_write;
at24->bin.write= at24_bin_write;
at24->bin.attr.mode|= S_IWUSR;
……
}
……
err = sysfs_create_bin_file(&client->dev.kobj,&at24->bin);
if(err)
gotoerr_clients;
i2c_set_clientdata(client,at24);
……
}
probe()函数主要的工作是在sys目录下创建bin节点文件,用户可以同此节点文件来操作eeprom。
综述
其实 drivers/misc/eeprom/at24.c 这个文件,就是at24cx芯片的一个驱动文件,这个文件包括了:
- 向内核注册驱动
- 匹配驱动与硬件信息
- 提供操作at24cx芯片的方法(读,写),
- 生成一个节点文件
当我们看完i2c设备驱动时,就会发现,i2c设备eeprom就是一个普通的 字符设备 (driver,device“client”,probe)。我们在前面说到,有两种方式去控制i2c设备。在这里我们就提供一个完全可以跳过i2c总线,只通过设备驱动,直接对eeprom进行操作的代码。
具体代码如下:
/*********************************************************************************
* Copyright: (C) 2017 00
* All rights reserved.
*
* Filename: i2c_1.c
* Description: This file
*
* Version: 1.0.0(2017年04月26日)
* Author: PainEver <781189738@qq.com>
* ChangeLog: 1, Release initial version on "2017年04月26日 17时12分38秒"
*
********************************************************************************/
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/io.h>
#include<errno.h>
#define LEN 20
#define PRINT_ERROR printf("error: %d:%s",errno,strerror(errno))
/********************************************************************************
* Description:
* Input Args:
* Output Args:
* Return Value:
********************************************************************************/
int main (int argc, char **argv)
{
int ret,fd,i,j;
char read_data[LEN];
char write_data[LEN] ="The lifeline\n";
char offset[LEN];
memset(offset,0,sizeof(offset));
fd = open("/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/eeprom",O_RDWR); /*打开的这个节点文件,就是probe函数生成的*/
if(fd < 0)
{
printf("Open AT24C02 fail.\n");
return -1;
}
ret = read(fd, offset,LEN);
if(ret < 0)
{
printf("Read AT24C02 fail.\n");
return -1;
}
printf("%s\n",offset); //读出当前EEPROM中的数据
ret = lseek(fd, 0, SEEK_SET);//调整偏移量,从开始位置写入
if(-1 == ret)
{
PRINT_ERROR;
return -1;
}
ret = write(fd ,write_data, sizeof(write_data));
if(ret < 0)
{
printf("Write error\n");
return -1;
}//将指定数据写入当前EEPROM中
lseek(fd, 0, SEEK_SET);//重新设置偏移量从起始位置读取
ret = read(fd , read_data, LEN);
if(ret < 0)
{
printf("Read error\n");
return -1;
}
else if(ret < 20)
{
printf("Incomplete read\n");
return -1;
}
printf("%s\n",read_data);
return 0;
}
从上述代码中,可以看出,我们完全的跳过了i2c总线,而是直接调用设备本身的驱动去完成读写操作。
好了,设备驱动我们就分析到这里,接下来我们分析总线驱动,也就是另一种操控i2c设备的方法。
I2C总线驱动
一般来说,总线驱动内核已经帮我们完成了,我们分析总线驱动时,我们就主要对一下三部分进行分析:
- 总线驱动的作用
- 总线驱动的通信方法
- 总线驱动的注册
最后呢我们会对如何使用总线驱动控制i2c设备做重点分析。
前面说到I2C总线驱动由i2c_adapter和i2c_algorithm这两个结构体来描述,那么我们先来看看这两个结构体:
I2c_adapter结构体代表I2C总线控制器
struct i2c_adapter {
struct module *owner;
unsigned int class; /*classes to allow probing for */
const struct i2c_algorithm*algo; /* 总线上数据传输的方式*/
void *algo_data; /* algorithm 数据 */
int timeout; /* 超时时间设置 */
int retries; /* 重试次数 */
struct device dev; /* the adapter device */
int nr; /*IDR机制中ID号,有时也作次设备号*/
char name[48]; /* 适配器名字 */
struct completion dev_released; /* 用于同步 */
};
I2c_algorithm对应一套通信方法
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, intnum); /*指向 I2C总线通信协议的函数*/
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, charread_write,
u8 command, int size, unioni2c_smbus_data *data); /*实现SMBUS总线通信协议的函数,一般置为NULL*/
u32 (*functionality) (structi2c_adapter *); /*确定适配器支持的类型*/
};
总线驱动的作用
一直说总线驱动,总感觉很高端的样子,那我们就来看看总线驱动到底是干什么的:
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。
通俗说,就是总线驱动告诉适配器什么时候产生开始信号,什么时候产生结束信号,什么时候产生应答信号,然后适配器就通过自己的硬件去完成这些工作。
既然说到了适配器的硬件信息,那我们就来看看适配器的描述:
S3c2440处理器内部集成了一个I2C控制器,通过四个寄存器来进行控制:
IICCON I2C控制寄存器
IICSTAT I2C状态寄存器
IICDS I2C收发数据移位寄存器
IICADD I2C地址寄存器
通过IICCON,IICDS,IICADD寄存器操作,可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器来获取。
在弄懂总线驱动是干嘛的之后,我们就知道了,我们完全可以通过操控适配器去控制挂接在i2c总线上的设备,这就是我们所说的通过i2c总线驱动去控制设备。在分析完总线驱动之后,我们会给出一个代码,去实现这种操作。
总线驱动的通信方法
从前面我们知道了I2c_algorithm这个结构体对应一套通信方法,所以我们在分析总线驱动的通信方法事,主要就是对这个结构体进行分析。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, intnum); /*指向 I2C总线通信协议的函数*/
...
}
我们可以看到master_xfer()这个函数就是总线通信协议的函数,那么这个函数成员是由谁给它赋值的呢?
我们在i2c-s3c2410.c这个文件中发现:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
所以就是s3c24xx_i2c_xfer(),这个函数去完成的适配器与设备的通信,我们再来深追一下,打开s3c24xx_i2c_xfer():
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;
clk_enable(i2c->clk);
for (retry = 0; retry < adap->retries; retry++) {
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);/*传输到I2C设备的具体函数*/
if (ret != -EAGAIN) {
clk_disable(i2c->clk);
return ret;
}
....
}
我们发现s3c24xx_i2c_xfer()函数中又是调用了s3c24xx_i2c_doxfer()函数去实现的通信,我们再打开s3c24xx_i2c_doxfer():
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
481 struct i2c_msg *msgs, int num)
482 {
483 unsigned long iicstat, timeout;/*传输超时*/
484 int spins = 20;
485 int ret;/*返回传输的消息个数*/
486
487 if (i2c->suspended)/*如果适配器处于挂起状态,则返回*/
488 return -EIO;
489
490 ret = s3c24xx_i2c_set_master(i2c);/*将适配器设置为主机发送状态*/
491 if (ret != 0) {
492 dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
493 ret = -EAGAIN;
494 goto out;
495 }
496
497 spin_lock_irq(&i2c->lock);
498
499 i2c->msg = msgs;
500 i2c->msg_num = num;
501 i2c->msg_ptr = 0;
502 i2c->msg_idx = 0;
503 i2c->state = STATE_START;
504
505 s3c24xx_i2c_enable_irq(i2c);/*启动适配器的中断号,允许适配器发出中断*/
506 s3c24xx_i2c_message_start(i2c, msgs);/*启动适配器的消息传输*/
507 spin_unlock_irq(&i2c->lock);
508
509 timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);/*设置等待队列,直到i2c->msg_num == 0为真或5ms到来才被唤醒*/
510
511 ret = i2c->msg_idx;
512
513 /* having these next two as dev_err() makes life very
514 * noisy when doing an i2cdetect */
515
516 if (timeout == 0)
517 dev_dbg(i2c->dev, "timeout\n");
518 else if (ret != num)
519 dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
520
521 /* ensure the stop has been through the bus */
522
523 dev_dbg(i2c->dev, "waiting for bus idle\n");
524
525 /* first, try busy waiting briefly */
526 do {
527 iicstat = readl(i2c->regs + S3C2410_IICSTAT);
528 } while ((iicstat & S3C2410_IICSTAT_START) && --spins);
529
530 /* if that timed out sleep */
531 if (!spins) {
532 msleep(1);
533 iicstat = readl(i2c->regs + S3C2410_IICSTAT);
534 }
535
536 if (iicstat & S3C2410_IICSTAT_START)
537 dev_warn(i2c->dev, "timeout waiting for bus idle\n");
538
539 out:
540 return ret;
541 }
我们可以从注释里看到s3c24xx_i2c_doxfer()主要做了如下这些事:第一,将适配器设置为主机发送状态。第二,设置为中断传输方式。第三,发送启动信号,传输第一个字节。第四,等待超时或者其他函数在i2c->msg_num == 0时唤醒这里的等待队列。
从中我们可以知道s3c24xx_i2c_doxfer()这个函数就是传输到I2C设备的具体函数,其实我对于这个函数的具体是怎么操作的也不是很明白,在这里只是给大家看一下I2c_algorithm这个结构体到底是调用那些函数去完成适配器与设备的通信工作的。有兴趣的朋友可以去深入了解一下。
我们总结一下,I2c_algorithm结构体对应了一套通信方式,具体是其内部成员master_xfer()函数也就是s3c24xx_i2c_xfer()函数通过调用s3c24xx_i2c_doxfer()去实现的通信。
在i2c_adapter 结构体中:
struct i2c_adapter {
struct module *owner;
...
const struct i2c_algorithm *algo; /* 总线上数据传输的方式*/
...
}
包含了i2c_algorithm 这个结构体指针成员,指向了总线上数据传输的方式,所以当i2c_algorithm 这个结构体被填充完毕之后,整个i2c总线控制器,也就是适配器就可以工作了。具体怎么填充,我们在后面分析i2c-dev.c的时候,再做讲解。
总线驱动的注册
总线驱动是基于platform来实现的。
platform_driver
在drivers/i2c/busses/i2c-s3c2410.c下
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table= s3c24xx_i2c_match,
},
};
将platform_driver注册到内核中
static int __init i2c_adap_s3c_init(void)
{
returnplatform_driver_register(&s3c24xx_i2c_driver);
}
platform_device
在arch/arm/plat-samsung/dev-i2c0.c下
tatic 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,
};
struct s3c2410_platform_i2c default_i2c_data __initdata = {
.flags = 0,
.slave_addr = 0x10, /* I2C适配器的地址 */
.frequency = 100*1000, /* 总线频率 */
.sda_delay = 100, /* SDA边沿延迟时间ns */
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
structs3c2410_platform_i2c *npd;
if(!pd)
pd= &default_i2c_data;
npd= s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
&s3c_device_i2c0);
if(!npd->cfg_gpio)
npd->cfg_gpio= s3c_i2c0_cfg_gpio;
}
在板文件中把platform_device注册进内核:
static struct platform_device *mini2440_devices[] __initdata = {
……
&s3c_device_i2c0,
……
};
probe()函数
在驱动与硬件找到彼此之后,内核就执行probe()函数,来初始适配器硬件。
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
……
/*初始化适配器信息 */
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);
/*映射寄存器 */
res= platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->ioarea= request_mem_region(res->start, resource_size(res),
pdev->name);
i2c->regs= ioremap(res->start, resource_size(res));
/*设置I2C核心需要的信息 */
i2c->adap.algo_data= i2c;
i2c->adap.dev.parent= &pdev->dev;
/*初始化I2C控制器 */
ret= s3c24xx_i2c_init(i2c);
/*申请中断 */
i2c->irq= ret = platform_get_irq(pdev, 0);
ret= request_irq(i2c->irq, s3c24xx_i2c_irq, 0,
dev_name(&pdev->dev), i2c);
/* 注册I2C适配器 */
ret= i2c_add_numbered_adapter(&i2c->adap);
……
}
probe()函数主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。
I2c_adapter注册过程i2c_add_numbered_adapter->i2c_register_adapter
注意,就是在调用i2c_register_adapter这个函数的时候,生成了我们前面所说的i2c_client结构体
这样总线驱动就注册成功了。
在我们前面添加完内核对i2c总线的支持后,开发板中就会出现s3c2410-i2c这个适配器。
总线驱动就介绍到这里,那我们如何使用总线驱动去控制设备呢?
我们在前面说到过一个文件 :
i2c-dev.c
i2c-dev.c中针对每个适配器生成一个主设备号为89的设备节点,实现了文件操作接口,用户空间可以通过i2c设备节点访问i2c适配器。适配器的编号从0开始,和适配器的设备节点的次设备号相同。
i2c适配器的设备节点是/dev/i2c-x,其中x是数字,代表适配器的编号。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容。比如我们在总线驱动的注册中注册了s3c2410-i2c这个适配器,那我们来看看这个适配器是什么编号:
我们发现在/sys/class/i2c-dev/目录下只有一个适配器,我们看一下这个适配器的名字是不是我们之前注册的s3c2410-i2c呢:
确实就是我们之前注册的s3c2410-i2c适配器。
i2c-dev.c并没有针对特定的设备而设计,它不但提供了适配器的设备节点,而且还提供了通用的read()、write()和ioctl()等接口,应用层可以通过 打开对应的设备节点 和 使用这些通用接口 访问挂接在适配器上的I2C设备,并操控它们。
这就实现了用户层利用i2c总线驱动去控制设备的功能。
我们在内核中添加i2c总线的支持之后,内核就会编译i2c_dev.c,然后在/dev目录下就自动生成了主设备号为89,次设备号为0的设备节点。
说到这里,我们就明白了i2c-dev.c就是一个针对i2c设备的一个通用驱动代码,我们有了驱动代码,但是硬件信息从哪里来呢?
在这里,我之前就被迷惑了,我一直以为总线驱动和设备驱动共用我们之前在设备驱动中添加的i2c_client(硬件信息),所以,我就一直把总线驱动和设备驱动联系到了一起,其实是错的。
我们在使用总线驱动控制设备,也就是使用i2c_dev.c这个文件的时候,会打开适配器对应的设备节点open(“/dev/i2c-0”,…),就在这时i2c-dev为打开的线程建立一个i2c_client,但是这个i2c_client并不加到i2c_adapter的client链表当中。当用户关闭设备节点时,它自动被释放。这个i2c_client才是我们在这里用到的硬件信息。
好了,i2c总线驱动的代码,硬件信息都有了,那我么如何使用总线驱动去控制设备呢?
我们之前说过,完成i2c总线驱动也就是完成对I2c_algorithm这个结构体的填充,i2c总线驱动要想工作,必须得有一套通信方式,要不然总线哪知道自己要干什么。所以我们用户空间使用2c-dev.c去控制设备时,就是完成对I2c_algorithm这个结构体的填充。
而在决定了I2c_algorithm这个结构体通信方式的又是master_xfer()这个函数,那我们就会想,用户空间里怎么做才能和master_xfer()这个函数完成对接呢?
其实,在我们介绍i2c_core.c的时候,我们讲了i2c_core.c是把用户空间和i2c总线驱动层串起来的。怎么串起来呢,就是通过i2c_core.c中的i2c_transfer()这个函数。为什么是它呢?我们来看一下这个函数:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
1302 {
1303 unsigned long orig_jiffies;
1304 int ret, try;
1323 if (adap->algo->master_xfer) /*如果master_xfer和这个函数存在*/
{
1324 #ifdef DEBUG
1325 for (ret = 0; ret < num; ret++) {
1326 dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
1327 "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
1328 ? 'R' : 'W', msgs[ret].addr,
msgs[ret].len,
1329 (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
1330 }
...
}
可以看出i2c_transfer()就是去通过adap->algo->master_xfer与master_xfer()对接。
所以呢,这个函数与i2c总线驱动完成了对接,那我们再来看看这个函数是怎么和用户空间对接的呢?
我们所说的用户空间在使用i2c总线驱动时,都是通过i2c-dev.c提供的函数接口,也就是read(),write(),ioctl()。我们先看一下ioctl吧。打开i2c-dev.c,我们先看i2cdev_fops :
static const struct file_operations i2cdev_fops = {
512 .owner = THIS_MODULE,
513 .llseek = no_llseek,
514 .read = i2cdev_read,
515 .write = i2cdev_write,
516 .unlocked_ioctl = i2cdev_ioctl,/*用户层的ioctl()对应的函数*/
517 .open = i2cdev_open,
518 .release = i2cdev_release,
519 };
我们再看看看i2cdev_ioctl():
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
unsigned long funcs;
dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);
switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
/* NOTE: devices set up to work with "new style" drivers
* can't use I2C_SLAVE, even when the device node is not
* bound to a driver. Only I2C_SLAVE_FORCE will work.
*
* Setting the PEC flag here won't affect kernel drivers,
* which will be using the i2c_client node registered with
* the driver model core. Likewise, when that client has
* the PEC flag already set, the i2c-dev driver won't see
* (or use) this setting.
*/
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
client->addr = arg; //设置addr
return 0;
case I2C_TENBIT://设置10 bit地址模式
if (arg)
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return 0;
case I2C_PEC://设置传输后增加PEC标志
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS://获取函数支持
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);
case I2C_RDWR://读取和发送数据
return i2cdev_ioctl_rdrw(client, arg);
case I2C_SMBUS: //SMBUS协议数据传输
return i2cdev_ioctl_smbus(client, arg);
case I2C_RETRIES://设置重试次数
client->adapter->retries = arg;
break;
case I2C_TIMEOUT://设置超时时间
/* For historical reasons, user-space sets the timeout
* value in units of 10 ms.
*/
client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:
/* NOTE: returning a fault code here could cause trouble
* in buggy userspace code. Some old kernel bugs returned
* zero in this case, and userspace code might accidentally
* have depended on that bug.
*/
return -ENOTTY;
}
return 0;
}
对于我们操作i2c总线上的设备时,我们一般都是进行读写操作,所以当我们ioctl(…, I2C_RDWR)时,我们看一下i2cdev_ioctl()做了什么:
case I2C_RDWR://读取和发送数据
return i2cdev_ioctl_rdrw(client, arg);
i2cdev_ioctl()返回到i2cdev_ioctl_rdrw()这个函数,那么我们再来看看这个函数:
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
237 unsigned long arg)
238 {
239 struct i2c_rdwr_ioctl_data rdwr_arg;
240 struct i2c_msg *rdwr_pa;
241 u8 __user **data_ptrs;
242 int i, res;
...
295 res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);/*调用i2c_transfer()函数*/
296 while (i-- > 0) {
297 if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
298 if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,
299 rdwr_pa[i].len))
300 res = -EFAULT;
301 }
302 kfree(rdwr_pa[i].buf);
303 }
304 kfree(data_ptrs);
305 kfree(rdwr_pa);
306 return res;
307 }
所以到这里我们就明白了,正是i2c_core.c中的i2c_transfer()函数将用户空间和i2c总线驱动层串了起来。
read(),write()函数也是一样的,他们分别会调用i2c_core.c中的i2c_master_recv()和i2c_master_send(),而这连个函数又会去调用i2c_transfer()。这里我就不对这两个函数进行分析了,有兴趣的可以自己去看看。
我们明白了i2c_transfer()函数是我们控制i2c总线驱动需要去填充的目标之后,我们就看一下我们需要填充些什么参数。int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)第一个参数是不需要我们填充的,内核会自动填充,我们需要填充的就是 msgs, num这两个参数。而这两个参数呢,就是用户空间i2c_rdwr_ioctl_data这个结构体的两个成员。
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /*Msgs 表示单个开始信号传递的数据;*/
__u32 nmsgs; /*Nmsgs 表示有多少个msgs*/
};
我们再看一下struct i2c_msg这个结构体:
struct i2c_msg {
__u16 addr; /*设备的地址*/
__u16 flags; /* 默认为写入 0 */
__u16 len; /*需要写入的数据的长度*/
__u8 *buf; /*指向需要写入的数据*/
};
综述
通过上面的分析,我们明白了,想要通过i2c总线驱动,也就是适配器去控制设备,就需要去使用i2c-dev.c函数提供的适配器节点和通用函数接口,并且需要去填充struct i2c_rdwr_ioctl_data {}。那我们在下面就给出一个具体实现通过适配器向eeprom写数据的代码:
/*********************************************************************************
* Copyright: (C) 2017 00
* All rights reserved.
*
* Filename: i2c_dev.c
* Description: This file
*
* Version: 1.0.0(2017年05月11日)
* Author: PainEver <781189738@qq.com>
* ChangeLog: 1, Release initial version on "2017年05月11日 10时36分26秒"
*
********************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
/********************************************************************************
* Description:
* Input Args:
* Output Args:
* Return Value:
********************************************************************************/
int main(int argc, char **argv)
{
int i;
int fd;
int ret;
int length;
unsigned char rdwr_addr = 0x00; //eeprom 读写地址
unsigned char device_addr = 0x50; //eeprom 设备地址
unsigned char data[] = "the lifeline";
struct i2c_rdwr_ioctl_data e2prom_data;
e2prom_data.nmsgs = 1;
printf("open i2c device...\n");
fd = open("/dev/i2c-0",O_RDWR);
if (fd < 0)
{
printf("open faild");
return -1;
}
e2prom_data.msgs = (struct i2c_msg *)malloc(e2prom_data.nmsgs * sizeof(struct i2c_msg));
if (e2prom_data.msgs == NULL)
{
printf("malloc error");
return -1;
}
ioctl(fd, I2C_TIMEOUT, 1);// 设置超时
ioctl(fd, I2C_RETRIES, 2);// 设置重试次数
/* 向e2prom中写入数据 */
length = sizeof(data);
e2prom_data.msgs[0].len = length;
e2prom_data.msgs[0].addr = device_addr;
e2prom_data.msgs[0].buf =(unsigned char *)malloc(length);
e2prom_data.msgs[0].buf[0] = rdwr_addr;
for(i = 0;i<length; i++)
e2prom_data.msgs[0].buf[1+i] = data[i]; /* write data */
ret = ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret <0)
{
perror("write data error");
return -1;
}
printf("write data: %s to address %#x\n", data, rdwr_addr);
return 0;
}
好了,对于整个I2C我们就分析到这里,文档是很多地方都是我个人的理解,如果有不对的地方,欢迎大家指出。
最后附上一张通过两种方式控制i2c总线上的设备的架构图:
左边是第一种,通过设备驱动控制设备;
右边是第二种,通过总线驱动控制设备。
References:
http://blog.youkuaiyun.com/paul_liao/article/details/6978318/
http://blog.youkuaiyun.com/u010944778/article/details/46807737