linux驱动基础系列--Linux I2c驱动分析
前言
主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动、设备模型、sysfs等也不进行详细说明原理,涉及到i2c协议部分也只会简单带过,因为linux内核里面已经实现了该协议,我们以后并不需要重新实现这些,只需要对协议有个简单的了解。如果有任何错误地方,请指出,谢谢!
注:图片来自互联网
内核版本:2.6.35.6
I2c介绍
I2C是“Inter Integrated Circuit Bus”的缩写,中文译成“内部集成电路。
它是Philips 公司于20 世纪80 年代研发成功的一种具有多端控制功能的双线双向串行数据总线标准,其具有模块化、电路结构简单等优点。在嵌入式系统中,I2C总线已经成为器件接口的标准之一,常用于连接RAM、EEPROM 以及LCD 控制器等设备。另外,总线的数据传输是以字节为单位的。
目前,标准的I2C的传输速率可以达到100kbit/s,能支持128 个设备,增强型I2C传输速率可达400kbit/s,能支持多达1024 个设备,高速模式下的I2C传输速率更高达3.4Mbit/s。
参考:http://zh.wikipedia.org/wiki/I²C
I2C总线是由数据线SDA和时钟SCL构成的串行总线,各种i2c接口的设备均并联在这条总线上,每个器件都有一个唯一的地址识别,可以作为总线上的一个发送器件或接收器件(具体由器件的功能决定)
I2C总线协议规定,各主机进行通信时都要有起始、结束、发送数据和应答信号。这些信号都是通信过程中的基本单元。主器件产生串行时钟(SCL),同时控制总线的传输方向,并产生开始和停止条件。空闲状态时SDA和SCL都为高电平;起始信号就是在SCL线为高时SDA线从高变化到低;停止信号就是在SCL线为高时SDA线从低变化到高;应答信号是在SCL为高时SDA为低;非应答信号相反,是在SCL为高时SDA为高。
总线传送的每1帧数据均是1个字节。协议规定,在启动总线后的第1个字节的高7位是对从机的寻址地址,第8位为方向位(“0”表示主机对从机的写操作;“1”表示主机对从机的读操作),其余的字节为操作数据。 数据传送过程是:在I2C总线发送起始信号后,发送从机的7位寻址地址和1位表示这次操作性质的读写位,在有应答信号后开始传送数据,直到发送停止信号。主机每发送1个字节就要检测SDA线上有没有收到应答信号,有则继续发送,否则将停止发送数据。
I2C总线的接口电路结构如下图所示:
I2c数据传输图:
I2C传输详细介绍
- I2C位传输
数据传输:SCL为高电平时,SDA线若保持稳定,那么SDA上是在传输数据bit;
若SDA发生跳变,则用来表示一个会话的开始或结束
数据改变:SCL为低电平时,SDA线才能改变传输的bit
- I2C开始和结束信号
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
- I2C应答信号
Master每发送完8bit数据后等待Slave的ACK。即在第9个clock,若从IC发ACK,SDA会被拉低。若没有ACK,SDA会被置高,这会引起Master发生RESTART或STOP流程,如下所示:
- I2C写流程
写寄存器的标准流程为:
1. Master发起START
2. Master发送I2Caddr(7bit)和w操作0(1bit),等待ACK
3. Slave发送ACK
4. Master发送reg addr(8bit),等待ACK
5. Slave发送ACK
6. Master发送data(8bit),即要写入寄存器中的数据,等待ACK
7. Slave发送ACK
8. 第6步和第7步可以重复多次,即顺序写多个寄存器
9. Master发起STOP
写一个寄存器
写多个寄存器
5. I2C读流程
读寄存器的标准流程为:
1. Master发送I2Caddr(7bit)和w操作1(1bit),等待ACK
2. Slave发送ACK
3. Master发送reg addr(8bit),等待ACK
4. Slave发送ACK
5. Master发起START
6. Master发送I2Caddr(7bit)和r操作1(1bit),等待ACK
7. Slave发送ACK
8. Slave发送data(8bit),即寄存器里的值
9. Master发送ACK
10. 第8步和第9步可以重复多次,即顺序读多个寄存器
读一个寄存器
读多个寄存器
linux里i2c驱动的实现
终于到这里了,前面的讲解大部分都是摘抄自网上的,拿来主义是也!不过我是有选择性的拿啦。 Linux I2C驱动目录linux-2.6.35\drivers\i2c如下:
algos目录:I2c算法的实现
busses目录:linux里面一个i2c控制器被称为一条总线,因为它确实引出了一条总线
其他文件:I2c核心层的实现,里面定义了总线的描述对象和设备的描述对象以及总线驱动、设备驱动需要用到的接口,使用这些接口,设备的驱动实现不需要关心底层i2c控制器的部分,I2c控制器(总线驱动)的实现也不需要关心具体设备驱动的部分,由核心层完成这些。下面再上一个图,该图描述了linux下I2C驱动框架。每一条I2C对应一个adapter。在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter
),也定义了adapter支持的操作(struct i2c_adapter
)。再通过i2c core层将i2c设备与i2c adapter关联起来。
驱动框架图:
Linux下的I2c的驱动分成两部分:
- 总线的驱动(其实就是I2C控制器的驱动)
- 设备的驱动(具体的I2C接口的设备驱动实现)
我们更多的是在写设备的驱动,因此下面分两部分来讲述
总线的驱动即I2C控制器的驱动
很容易想到,I2C控制器的驱动应该是采用平台设备来实现,我以s3c为例。文件i2c-s3c2410.c
(它实际包含了2410和2440的驱动,这个看s3c24xx_driver_ids
就能够想到)
和我们想的一样,模块加载就会(如果是编译成模块)调用i2c_adap_s3c_init
,它里面就是注册平台驱动s3c24xx_i2c_driver
,平台驱动的细节就略过,直接看s3c24xx_i2c_probe
还是将I2C控制器平台设备相关的代码贴出来(以2410为例):
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,
},
};
在arch/arm/plat-samsung
的dev-i2c0.c
中:
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,
};
static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
.flags = 0,
.slave_addr = 0x10,
.frequency = 100*1000,
.sda_delay = 100,
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd)
pd = &default_i2c_data0;
npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform data\n", __func__);
else if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;
s3c_device_i2c0.dev.platform_data = npd;
}
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
};
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
继续看s3c24xx_i2c_probe
:
和其他类似驱动一样,进来应该是分配一个驱动上下文对象,也就是实现该控制器驱动需要的一些数据类型的结合,在s3c里用struct s3c24xx_i2c
来描述该上下文对象,它封装了I2C核心层定义的struct i2c_adapter
(核心层以它来描述一个I2C控制器)。
Probe主要的工作有:
指定I2c算法:
i2c->adap.algo = &s3c24xx_i2c_algorithm;
使能I2C控制器时钟:
i2c->clk = clk_get(&pdev->dev, "i2c");
clk_enable(i2c->clk);
根据平台设备指定的数据申请I2c控制器使用的寄存器空间并进行映射为以后所用
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控制器,这个是具体平台相关的,s3c上对应的函数:s3c24xx_i2c_init
,它主要完成如下工作:
根据平台设备指定的数据(s3c_device_i2c0
、default_i2c_data0
)来操作s3c的控制器寄存器,最终达到初始化控制器的目地。
unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
pdata = i2c->dev->platform_data;
if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
writel(iicon, i2c->regs + S3C2410_IICCON);
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
这里需要理解I2c控制器的四种模式:
主发送、主接收、从发送、从接收。在这里主要配置了I2c控制器作为从设备时的地址、使能了发送/接收中断及应答、并计算配置了时钟。配置的方式是参考i2c控制器设备端指定的数据和动态检测,原话如下:
/* s3c24xx_i2c_clockrate
*
* work out a divisor for the user requested frequency setting,
* either by the requested frequency, or scanning the acceptable
* range of frequencies until something is found
*/
根据平台设备指定的数据注册I2c控制器使用的中断
i2c->irq = ret = platform_get_irq(pdev, 0);
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
调用核心层提供的接口,注册到I2C核心
ret = i2c_add_numbered_adapter(&i2c->adap);
看吧,I2C核心只认它指定的控制器对象struct i2c_adapter
,所以s3c控制器的驱动实现就是将附属于自己对象上的控制器对象struct i2c_adapter
作为参数传过去了。当然,在probe里面需要对它进行一些必要的初始化:
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //这个上面说过了的,指定I2c通讯的算法
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->adap.algo_data = i2c; //保存我们自己的对象,以后平台函数里会提取出来
i2c->adap.dev.parent = &pdev->dev; //这会让控制器在设备模型中属于i2c控制器设备
i2c->adap.nr = pdata->bus_num;//指定总线号,一个I2C控制器代表一条I2C总线,申明自己总线为该总线号的设备都会由该控制器驱动管理
暂时先不跟进核心层,分析控制器驱动的时候,以实现控制器驱动的思路来分析,不然会越来越复杂。
那i2c_add_numbered_adapter
之后发生了什么呢?
控制器驱动需要关心的两个主要问题:
s3c24xx_i2c_algorithm
函数集的实现,因为核心层会将驱动的读写通过该接口完成s3c24xx_i2c_irq
的实现,这个是I2c控制器接收中断后的处理函数
还有一个需要注意,那就是设备是怎么进入本控制器驱动的管理之下的,这个看核心层代码就会情况,后面分析核心层的时候再说,这里暂时不去关心这个问题。
分析s3c24xx_i2c_algorithm
:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
/* declare our i2c functionality */
static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}
该接口的实现比较简单,表明本控制器支持的功能
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;//提取出我们上下文数据,在probe里面分配并初始化的
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:
它主要做的工作是:
将核心层规定的消息对象struct i2c_msg *msg
发送出去,消息个数由参数num指定
看一下msg对象的定义:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#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 */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
很简单吧! Addr表示发给谁,flag表示一些发送的指示(看后面的宏定义就知道有哪些指示了),len指定数据的长度,buf是数据指针
将num个消息发送出去的函数是s3c24xx_i2c_message_start
它的实现也很简单:
通过操作s3c I2c控制/状态寄存器来使能发送接收,使能应答,并配置成四种模式中的一种,
stat |= S3C2410_IICSTAT_TXRXEN;
if (msg->flags & I2C_M_RD) {
stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1;
} else
stat |= S3C2410_IICSTAT_MASTER_TX;
s3c24xx_i2c_enable_ack(i2c);
writel(stat, i2c->regs + S3C2410_IICSTAT);
将地址写入地址寄存器
writeb(addr, i2c->regs + S3C2410_IICDS);
发出起始信号传输
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT);
这样之后,就等待设备的响应了,响应的时候会伴随中断处理函数的调用 s3c24xx_i2c_message_start
返回之后,s3c24xx_i2c_doxfer
会阻塞最多5秒等待响应,
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
下面进入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\n");
}
if (i2c->state == STATE_IDLE) {//我们空闲的时候,收到了irq,直接无视掉
dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");
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
主要完成的工作:
如果状态为STATE_START,表示我们之前发送了start,
case STATE_START:
/* last thing we did was send a start condition on the
* bus, or started a new i2c message
*/
if (iicstat & S3C2410_IICSTAT_LASTBIT &&
!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {//这里表示我们期望收到ack,但是实际没有收到
/* ack was not received... */
dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -ENXIO);
goto out_ack;
}
if (i2c->msg->flags & I2C_M_RD)//更新状态
i2c->state = STATE_READ;
else
i2c->state = STATE_WRITE;
/* terminate the transfer if there is nothing to do
* as this is used by the i2c probe to find devices. */
if (is_lastmsg(i2c) && i2c->msg->len == 0) {//如果是最后一个消息,那么发送停止信号
s3c24xx_i2c_stop(i2c, 0);
goto out_ack;
}
if (i2c->state == STATE_READ)//如果是读操作,跳到准备读那去
goto prepare_read;
//否则进入写
/* fall through to the write state, as we will need to
* send a byte as well */
case STATE_WRITE:
/* we are writing data to the device... check for the
* end of the message, and if so, work out what to do
*/
if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {//如果我们期望ack
if (iicstat & S3C2410_IICSTAT_LASTBIT) {//而我们没有收到ack
dev_dbg(i2c->dev, "WRITE: No Ack\n");
s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
goto out_ack;
}
}
retry_write:
if (!is_msgend(i2c)) {//如果当前消息还没发完
byte = i2c->msg->buf[i2c->msg_ptr++];
writeb(byte, i2c->regs + S3C2410_IICDS);
/* delay after writing the byte to allow the
* data setup time on the bus, as writing the
* data to the register causes the first bit
* to appear on SDA, and SCL will change as
* soon as the interrupt is acknowledged */
ndelay(i2c->tx_setup);
} else if (!is_lastmsg(i2c)) {//如果还不是最后一个消息
/* we need to go to the next i2c message */
dev_dbg(i2c->dev, "WRITE: Next Message\n");
i2c->msg_ptr = 0;//复位统计值
i2c->msg_idx++; //消息idx指向下一个消息
i2c->msg++;//消息指针指向下一个
/* check to see if we need to do another message */
if (i2c->msg->flags & I2C_M_NOSTART) {//是否需要在切换下一个消息的时候发送重新start信号,这里是不需要
if (i2c->msg->flags & I2C_M_RD) {//如果新消息是读,而我们开始还是写,这种不允许,发送停止,然后重新开始write流程
/* cannot do this, the controller
* forces us to send a new START
* when we change direction */
s3c24xx_i2c_stop(i2c, -EINVAL);
}
goto retry_write;
} else {//否则发送一个start信号
/* send the new start */
s3c24xx_i2c_message_start(i2c, i2c->msg);
i2c->state = STATE_START;//切换状态为start
}
} else {//如果所有消息的所有内容都发完了,那么就停止
/* send stop */
s3c24xx_i2c_stop(i2c, 0);
}
break;
//下面看一下读操作的处理,前面会直接进入到准备读位置:
case STATE_READ:
/* we have a byte of data in the data register, do
* something with it, and then work out wether we are
* going to do any more read/write
*/
byte = readb(i2c->regs + S3C2410_IICDS);
i2c->msg->buf[i2c->msg_ptr++] = byte;
prepare_read:
if (is_msglast(i2c)) {//如果接收的是倒数第二个字节(从start状态那直接跳过来的一般不会进入到这里面)
/* last byte of buffer */
if (is_lastmsg(i2c)) //如果是最后一个消息,那么就需要告诉控制器下次接收的时候no ack
s3c24xx_i2c_disable_ack(i2c);
} else if (is_msgend(i2c)) {//如果当前消息接收完成(从start状态那直接跳过来的一般也不会进入到这里面,会在数据到来时,即下一个中断读)
/* ok, we've read the entire buffer, see if there
* is anything else we need to do */
if (is_lastmsg(i2c)) {//如果是最后一个消息
/* last message, send stop and complete */
dev_dbg(i2c->dev, "READ: Send Stop\n");
s3c24xx_i2c_stop(i2c, 0);
} else {//否则切换下一个待装载的消息
/* go to the next transfer */
dev_dbg(i2c->dev, "READ: Next Transfer\n");
i2c->msg_ptr = 0;
i2c->msg_idx++;
i2c->msg++;
}
}
break;
s3c24xx_i2c_irq
就分析完毕了,它里面主要实现了消息接收,发送等。这里还需要关心一下s3c24xx_i2c_stop
它里面调用了s3c24xx_i2c_master_complete(i2c, ret);
唤醒阻塞在上文中说过的那个线程,不然它的5秒就会timeout了,呵呵
I2C控制器的驱动实现就分析完了。
I2C核心层实现
在分析设备的驱动前先分析下I2C核心层,这样可以进一步增进对I2c控制器驱动的了解和I2C设备驱动的了解。
I2C核心层的初始化在i2c-core.c里面i2c_init:
主要是注册了I2C总线:
retval = bus_register(&i2c_bus_type);
这条总线是I2C设备驱动和I2C控制器及它上面的设备的总线
前面看到在I2C控制器驱动的probe里调用了i2c_add_numbered_adapter
,现在分析它的内部实现(它就是核心层实现的接口):
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
return -ENOMEM;
mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh;
* we need the "equal to" result to force the result
*/
status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
通过idr机制获取一个唯一号,因为支持多条总线,所以为了防止总线冲突,用总线号来区分开来
调用i2c_register_adapter
进一步完成I2C控制器的注册工作:
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
通过设备模型,将I2C控制器加入到i2c_bus_type
总线
注:I2C控制器也是一个设备!只不过它的类型是i2c_adapter_type
,因此不会和I2C设备驱动匹配(I2C总线的匹配函数里过滤了,呵呵)
然后进入到重要两步的第一步:
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
它会扫描之前添加到系统中的设备信息,并根据它们实例化一个I2C设备的对象:
i2c_scan_static_board_info
list_for_each_entry(devinfo, &__i2c_board_list, list) {//扫描
if (devinfo->busnum == adapter->nr//如果总线号和本控制器一样
&& !i2c_new_device(adapter, //执行添加一个设备
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
i2c_new_device
(注意:I2C核心里面定义了一个设备由struct i2c_client表示,里面封装了device对象,因此可以方便的添加到设备模型中)
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
根据之前注册时的信息初始化client
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
初始化device,注意它的类型为i2c_client_type
,和I2C控制器是不同的
status = device_register(&client->dev);
添加到设备模型,至于添加的过程,了解设备模型的应该都清楚,扫描总线,与总线上的每一个驱动尝试着匹配…
到这里第一步就分析完了,只是还剩一个问题没有分析,那就是这些静态注册的I2c设备信息是在哪里添加的呢?
我们以s3c mini2440为例,文件arch/arm/mach-s3c2440/mach-mini2440.c中:
mini2440_init里面调用了
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));//添加到0号总线
/*
* I2C devices
*/
static struct at24_platform_data at24c08 = {
.byte_len = SZ_8K / 8,//指定容量
.page_size = 16,//指定page 大小,这些都是具体设备相关的,由最终的I2C设备驱动来解析
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50), //指定I2C设备名字及设备的地址
.platform_data = &at24c08, //指向具体设备相关的指针
},
};
这里添加的是一个eeprom芯片信息,它是I2C接口的。
通过i2c_register_board_info
,将设备的信息添加到了全局链表__i2c_board_list
中,开始讲到的i2c_scan_static_board_info
里面就是遍历该全局链表,于是一切都清楚了,呵呵。
到这里I2C总线上已经有I2C适配器和静态注册的I2c设备了,接着进入到重要两步的第二步:
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
__process_new_adapter);
遍历I2C总线,对每一个驱动调用__process_new_adapter
,这里主要的目的是防止某些驱动在I2C控制器驱动加载前就加载了,那个时候因为没有总线,当然驱动就找不到任何它能驱动的I2C设备,所以这里在I2C控制器驱动加载的时候补上。
里面核心的实现是在
i2c_do_add_adapter
里的i2c_detect中,这个过程我就懒得分析了,不难,虽然有点长。
I2C设备的驱动
I2C设备的驱动方式可以通过两种方式实现
第一种:在应用层实现I2c设备的驱动,这个是基于I2C里面通用的设备驱动实现的,通用驱动的文件:i2c-dev.c,我不对这种进行分析,网上也有很多这方面的资料,本人不建议这种方式,太多的弊端
第二种:基于内核里面I2C核心层实现特定的I2C设备驱动,我以at24.c为例子分析(以eeprom at24为例,最简单,不会牵涉出太多额外的技术)
编写设备驱动一般需要对照芯片手册,我们这里的at24.c实现的驱动是支持多个类似芯片的:
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.c怎么实现的:
at24_init负责加载驱动后的初始化,主要就是将该芯片的驱动添加到I2C总线,该总线是在I2C核心层中注册的,前文有说明。
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.owner = THIS_MODULE,
},
.probe = at24_probe,
.remove = __devexit_p(at24_remove)
.id_table = at24_ids
};
i2c_add_driver(&at24_driver);
at24.c里面实现的I2C EEPROM芯片驱动是用at24_driver
来记录上下文数据的,它实际上就是I2C核心层定义的I2C驱动对象类型struct i2c_driver
。
i2c_add_driver
里面调用了i2c_register_driver
,主要工作:
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
res = driver_register(&driver->driver);
这里面如果有I2C设备挂在I2C总线上(设备可以通过静态方式让I2C控制器驱动加载的时候将它们实例化成I2C设备对象并加入到I2C总线)且和当前驱动是匹配的,那么当前驱动的probe会被调用,即at24_probe
,下面主要分析它:
if (client->dev.platform_data) {//如果有平台数据,就提取平台数据
chip = *(struct at24_platform_data *)client->dev.platform_data;
} else {//没有的话
if (!id->driver_data) {//如果驱动的i2c_device_id里填充了数据,那么根据它来初始化平台芯片数据
err = -ENODEV;
goto err_out;
}
magic = id->driver_data;
chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
magic >>= AT24_SIZE_BYTELEN;
chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
/*
* This is slow, but we can't know all eeproms, so we better
* play safe. Specifying custom eeprom-types via platform_data
* is recommended anyhow.
*/
chip.page_size = 1;
chip.setup = NULL;
chip.context = NULL;
}
分配并初始化代表一个at24芯片的对象struct at24_data用来在驱动中表示这个I2C设备
at24 = kzalloc(sizeof(struct at24_data) +
num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);
at24->use_smbus = use_smbus;
at24->chip = chip;
at24->num_addresses = num_addresses;
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;
…
…
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
这里是需要关心的,一般像这里简单的设备是以字符设备节点方式提供给应用层,但是这里它的实现是通过sysfs到处文件到sysfs中,应用层直接通过文件来操作,有一个很明显的好处就是调试方便,直接echo或者cat对应的文件即可实现控制器操作对应的I2C设备。这里指定的kobj为client,它是设备对象内嵌的kobject,由I2C控制器分配、初始化并加入到I2C总线上的,所以它对应的目录应该是在/sys/bus/i2c/devices/xxxx,这里sysfs_create_bin_file
导致的属性文件应该是在xxxx下。
2. bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver);
遍历I2C总线上所有的I2C控制器,然后调用__process_new_driver
,这里的作用主要是想借助I2C控制器扫描一下当前驱动支持的所有地址,如果支持的设备地址上有设备存在,那么就将其添加到I2C总线上 我们这里的at24驱动没有地址列表,所以实际这里可以看出空操作啦。这里也不再跟进去分析了,里面的流程和__process_new_adapter
类似
情景分析
应用层open sysfs下的文件导致vfs里面的open被调用,最终导致sysfs的open被调用(fs/sysfs/bin.c)bin_fops
应用层读I2C设备
应用层read sysfs下的文件导致vfs里面的read被调用,导致sysfs的read被调用,最终导致at24_bin_read
被调用,这个就是I2C设备驱动里实现的函数
static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct at24_data *at24;
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
return at24_read(at24, buf, off, count);
}
最终的读操作在at24_read 中:
status = at24_eeprom_read(at24, buf, off, count);
它通过将读请求封装成I2C核心层指定的消息格式struct i2c_msg,最终调用I2C核心层实现的接口中的一个:
status = i2c_smbus_read_i2c_block_data(client, offset, count, buf);
status = i2c_smbus_read_word_data(client, offset);
status = i2c_smbus_read_byte_data(client, offset);
status = i2c_transfer(client->adapter, msg, 2);
将请求发送出去,这后面的过程在I2C核心层和I2C控制器驱动部分已经分析了
应用层写I2C设备
应用层的写和读的流程类似:
应用层write sysfs下的文件导致vfs里面的write被调用,导致sysfs的write被调用,最终导致at24_bin_write
被调用,这个就是I2C设备驱动里实现的函数
static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct at24_data *at24;
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
return at24_write(at24, buf, off, count);
}
最终的读操作在at24_write 中:
status = at24_eeprom_write(at24, buf, off, count);
它通过将写请求封装成I2C核心层指定的消息格式struct i2c_msg,最终调用I2C核心层实现的接口中的一个:
status = i2c_smbus_write_i2c_block_data(client,
offset, count, buf);
status = i2c_transfer(client->adapter, &msg, 1);
将请求发送出去,这后面的过程在I2C核心层和I2C控制器驱动部分已经分析了。
注: 本来不打算写I2C驱动博文的,网上一大堆了,但个人认为它是入门稍微复杂驱动的一个较好的点,看到下面的链接
http://blog.csdn.net/paul_liao/article/details/6972305
也是I2C驱动的文章,我只是偶然的发现了他里面的贴图,觉得图很明了,我想文章也不会差到哪去吧,建议大家也看看那篇
-----------------------完!
2014年5月
linux驱动基础系列--Linux I2c驱动分析的更多相关文章
- linux驱动基础系列--linux spi驱动框架分析
前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...
- linux驱动基础系列--linux spi驱动框架分析(续)
前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...
- linux驱动基础系列--Linux mmc sd sdio驱动分析
前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...
- linux驱动基础系列--Linux 串口、usb转串口驱动分析
前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...
- linux驱动基础系列--Linux下Spi接口Wifi驱动分析
前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...
- linux驱动基础系列--linux rtc子系统
前言 linux驱动子系统太多了,连时钟也搞了个子系统,这导致一般的时钟芯片的驱动也会涉及到至少2个子系统,一个是时钟芯片接口子系统(比如I2c接口的时钟芯片),一个是内核给所有时钟芯片提供的rtc子 ...
- Linux基础系列—Linux体系结构和Linux内核结构
/** ****************************************************************************** * @author 暴走的小 ...
- Linux入门基础(一):Linux基本操作
命令行BASH基本操作 Shell 用户不能直接操作内核,所以用户操作通过shell传递给内核 shell分为两种 : GUI 图形界面 (linux一般是GNOME) CLI 命令行界面 (linu ...
- Spring基础系列-AOP源码分析
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...
随机推荐
- Python 3基础教程22-单个列表操作
本文来介绍列表的操作,先看看单个列表的操作,列表有多个方法.以下多行代码,建议你写一个方法,测试运行一个方法,不然看起来很乱. # 元组操作 x = [5,6,2,1,6,7,2,7,9] # app ...
- jmeter+ant的使用
1.安装ant 下载ant,解压到某盘 2.配置环境变量: 变量名称 变量值 备注 ANT_HOME F:\apache-ant-1.10.3 Ant的解压路径 Path %ANT_HOME%\bin ...
- 孤荷凌寒自学python第七十六天开始写Python的第一个爬虫6
孤荷凌寒自学python第七十六天开始写Python的第一个爬虫6 (完整学习过程屏幕记录视频地址在文末) 今天在上一天的基础上继续完成对我的第一个代码程序的书写. 不过由于对python-docx模 ...
- Captcha 验证码Example
maven依赖 防止和spring中的servlet冲突 <dependency> <groupId>com.github.penggle</groupId> &l ...
- [leetcode-652-Find Duplicate Subtrees]
Given a binary tree, return all duplicate subtrees. For each kind of duplicate subtrees, you only ne ...
- BZOJ 4592 SHOI2015 脑洞治疗仪 线段树
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4592 题意概述:需要维护一个01序列A,一开始A全部都是1.支持如下操作: 1.将区间[l ...
- DFS(6)——hdu1342Lotto
一.题目回顾 题目链接:Lotto Sample Input 7 1 2 3 4 5 6 7 8 1 2 3 5 8 13 21 34 0 Sample Output 1 2 3 4 5 6 1 2 ...
- C#的internal访问修饰符
文章:C# 访问修饰符internal的访问范围误区释疑 internal访问修饰符限定的类,只能在本程序集中访问.
- linux cfs 负载均衡
确定新的负载的时候,代码中给出的公式是: (old×(2^i-1) + new))/2^i 整理下来是: old + (new-old)/2^i i的范围是[1, 4],也就是说,i的层级越高,那么n ...
- NIO--2-代码
package com.study.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.ni ...