您的位置:首页 > 运维架构 > Linux

S3C2410 I2C 总线驱动实例

2012-08-16 11:51 344 查看
1.S3C2410I2C
控制器硬件描述

S3C2410处理器内部集成了一个I2C控制器,通过4个寄存器就可方便地对其进行控制,这4个寄存器如下。

1 IICCON:I2C
控制寄存器。

2 IICSTAT:I2C
状态寄存器。

3 IICDS:I2C
收发数据移位寄存器。

4 IICADD:I2C
地址寄存器。

S3C2410处理器内部集成的I2C控制器可支持主、从两种模式,我们主要使用其主模式。通过对IICCON、IICDS和IICADD寄存器的操作,可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器获取。

2.S3C2410I2C
总线驱动总体分析

S3C2410的I2C总线驱动设计主要要完成以下工作。
1 设计对应于i2c_adapter_xxx_init()模板的S3C2410的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。
2设计对应于i2c_adapter_xxx_xfer()模板的S3C2410适配器的通信方法函数。针对
S3C2410, functionality()
函数 只需 简单 地返 回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING
表明其支持的功能。内核 源代 码中 的/drivers/i2c/busses/i2c-s3c2410.c

图1
给出了S3C2410驱动中的主要函数与模板函数的对应关系,由于实现通信方法的方式不一样,模板的一个函数可能对应与S3C2410I2C
总线驱动的多个函数。



图1    I2C
总线驱动模板与S3C2410I2C
总线驱动的映射

3.S3C2410I2C
适配器驱动的模块加载与卸载

I2C适配器驱动被作为一个单独的模块加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体,如代码清单1.1所示
代码清单1.1 S3C2410 I2C
总线驱动的模块加载与卸载
static  int _ _init i2c_adap_s3c_init(void)
{
     int ret;
     ret= platform_driver_register(&s3c2410_i2c_driver);
     if(ret == 0)

        {
             ret= platform_driver_register(&s3c2440_i2c_driver);
             if(ret)
             platform_driver_unregister(&s3c2410_i2c_driver);
        }
     return  ret;
}

static  void _ _exit i2c_adap_s3c_exit(void)
{
      platform_driver_unregister(&s3c2410_i2c_driver);

      platform_driver_unregister(&s3c2440_i2c_driver);

}

module_init(i2c_adap_s3c_init);

module_exit(i2c_adap_s3c_exit);

platform_driver结构体包含了具体适配器的probe()函数、remove()函数、resume()

函数指针等信息,它需要被定义和赋值,如代码清单1.2所示。

代码清单1.2platform_driver
结构体

staticstruct platform_driver s3c2410_i2c_driver = {

.probe=s3c24xx_i2c_probe,

.remove=s3c24xx_i2c_remove,

.resume=s3c24xx_i2c_resume,

.driver={
.owner=THIS_MODULE,

.name="s3c2410-i2c",
},

};

当通 过Linux内核
源代 码/drivers/base/platform.c文件 中定义platform_driver_unregister()函数注册platform_driver结构体时,
其中probe指针指向的
s3c24xx_i2c_probe()函数将被调用,以初始化适配器硬件,如代码清单1.3所示。

代码清单1.3 S3C2410 I2C
总线驱动中的s3c24xx_i2c_probe
函数

staticint s3c24xx_i2c_probe(struct platform_device *pdev)

{

structs3c24xx_i2c *i2c = &s3c24xx_i2c;

structresource *res;

intret;

/*使能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;

gotoout;

}

clk_enable(i2c->clk);

/*映射寄存器*/

res= platform_get_resource(pdev, IORESOURCE_MEM, 0);

if(res == NULL)

{

dev_err(&pdev->dev,"cannot find IO resource\n");

ret= -ENOENT;

gotoout;

}

i2c->ioarea
=
request_mem_region(res->start,
(res->end-res->start)+1,

pdev->name);

if(i2c->ioarea == NULL)

{

dev_err(&pdev->dev,"cannot request IO\n");

ret= -ENXIO;

gotoout;

}

i2c->regs= ioremap(res->start, (res->end-res->start)+1);

if(i2c->regs == NULL)

{

dev_err(&pdev->dev,"cannot map IO\n");

ret= -ENXIO;

gotoout;

}

/*设置I2C的信息块*/

i2c->adap.algo_data= i2c;

i2c->adap.dev.parent= &pdev->dev;

/*初始化I2C控制器*/

ret= s3c24xx_i2c_init(i2c);

if(ret != 0)

goto out;

/*申请中断*/

res= platform_get_resource(pdev, IORESOURCE_IRQ, 0);

if(res == NULL)
{

ret= -ENOENT;

gotoout;

}

ret= request_irq(res->start, s3c24xx_i2c_irq, SA_INTERRUPT,
pdev->name,i2c);

if(ret != 0)

{

gotoout;

}

i2c->irq= res;

ret= i2c_add_adapter(&i2c->adap); /*添加i2c_adapter*/

if(ret < 0)

{

gotoout;

}

platform_set_drvdata(pdev,i2c);

out:

if(ret < 0)

s3c24xx_i2c_free(i2c);

returnret;

}

上述代码中的主体工作是使能硬件并申请I2C适配器使用的I/O地址、
在这些工作都完成无误后,通过IC
核心提供的i2c_add_adapter()函数添加这个适配
器。 因为
S3C2410内部 集成
I2C控制 器,可以
确定 I2C适配 器一 定存 在,
s3c24xx_i2c_probe()函数虽然命名“探测”
,但实际没有也不必进行任何探测工作,之
所以这样命名完全是一种设计习惯。

与s3c24xx_i2c_probe()函数完成相反功能的函数是s3c24xx_i2c_remove()函数,


在适 配器 模块 卸载 函数 调用platform_driver_unregister()函数
时所 示通 过
platform_driver的remove指针方式被调用。xxx_i2c_remove()的设计模板如代码清单
1.4所示。

代码清单1.4S3C2410 I2C
总线驱动中的s3c24xx_i2c_remove函数

staticint s3c24xx_i2c_remove(struct platform_device *pdev)

{

structs3c24xx_i2c *i2c = platform_get_drvdata(pdev);

if(i2c != NULL)
{

s3c24xx_i2c_free(i2c);

platform_set_drvdata(pdev,NULL);

}

return 0;

}

代码清单1.3和代码清单1.4中用到的s3c24xx_i2c结构体进行适配器所有信
息的封装,类似于私有信息结构体,它与xxx_i2c结构体模板
对应。代码清单1.5所示s3c24xx_i2c结构体的定义,以及驱动模块定义的一个
s3c24xx_i2c结构体全局实例。

xxx_i2c结构体模板

structxxx_i2c

{

spinlock_t
lock;

wait_queue_head_twait;

structi2c_msg *msg;

unsignedint
msg_num;

unsignedint
msg_idx;

unsignedint
msg_ptr;

...

structi2c_adapter adap;

};

代码清单1.5s3c24xx_i2c
结构体

structs3c24xxx_i2c

{

spinlock_t
lock;

wait_queue_head_twait;

structi2c_msg *msg;

unsignedint
msg_num;

unsignedint
msg_idx;

unsignedint
msg_ptr;

enums3c24xx_i2c_state state;

void_ _iomem *regs;
structclk *clk;
structdevice *dev;
structresource *irq;

structresource *ioarea;

structi2c_adapter adap;

};

staticstruct s3c24xx_i2c s3c24xx_i2c = {

.lock= SPIN_LOCK_UNLOCKED, /*自旋锁未锁定*/

.wait= _ _WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),/*等待

队列初始化*/

.adap
={

.name = "s3c2410-i2c",

.owner = THIS_MODULE,

.algo = &s3c24xx_i2c_algorithm,

.retries = 2,
.class= I2C_CLASS_HWMON,

},
};
4.S3C2410I2C
总线通信方法

由代码清单1.5的.algo=
&s3c24xx_i2c_algorithm行可以看出,I2C适配器对应的i2c_algorithm结构体实
例为s3c24xx_i2c_algorithm,代码清单1.6所示s3c24xx_i2c_algorithm的定义。

代码清单1.6S3C2410
的i2c_algorithm结构体

staticstruct i2c_algorithm s3c24xx_i2c_algorithm = {

.master_xfer
=s3c24xx_i2c_xfer,

.functionality
=s3c24xx_i2c_func,

};

上述代码第1行指定了S3C2410I2C
总线通信传输函数s3c24xx_i2c_xfer(),这个

函数非常关键,所有I2C总线上对设备的访问最终应该由它来完成,代码清单1.7

所示 为这 个重 要函 数以 及其 依赖 的s3c24xx_i2c_doxfer()函数


s3c24xx_i2c_message_start()函数的源代码。
代码清单1.7 S3C2410 I2C
总线驱动的master_xfer函数

staticint s3c24xx_i2c_xfer(struct i2c_adapter *adap,
structi2c_msg *msgs, int num)

{

struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;

intretry;

int ret;

//最多可以重试adap->retries次。
for (retry = 0; retry < adap->retries; retry++)

{

ret = s3c24xx_i2c_doxfer(i2c, msgs, num);

if(ret != -EAGAIN)

return ret;

udelay(100);

}

return-EREMOTEIO;

}

staticint s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct
i2c_msg*msgs, int num)

{

unsignedlong timeout;

intret;

ret= s3c24xx_i2c_set_master(i2c);

if(ret != 0) {

ret= -EAGAIN;

gotoout;

}

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;

if(timeout == 0)

dev_dbg(i2c->dev,"timeout\n");

elseif (ret != num)

dev_dbg(i2c->dev,"incomplete xfer (%d)\n", ret);

msleep(1);/*确保停止位已经被传递*/

out:

returnret;

}

staticvoid s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,

structi2c_msg *msg)

{

unsignedint addr = (msg->addr & 0x7f) << 1;

unsignedlong stat;

unsignedlong iiccon;

stat= 0;

stat|= S3C2410_IICSTAT_TXRXEN;

if(msg->flags & I2C_M_RD)
{

stat|= S3C2410_IICSTAT_MASTER_RX;

addr|= 1;

}
else

stat|= S3C2410_IICSTAT_MASTER_TX;

if(msg->flags & I2C_M_REV_DIR_ADDR)

addr^= 1;

s3c24xx_i2c_enable_ack(i2c);/*如果要使能ACK,则使能*/

iiccon= readl(i2c->regs + S3C2410_IICCON);

writel(stat,i2c->regs + S3C2410_IICSTAT);

writeb(addr,i2c->regs + S3C2410_IICDS);

udelay(1);/*在发送新的开始位前延迟1位*/

writel(iiccon,i2c->regs + S3C2410_IICCON);

stat|= S3C2410_IICSTAT_START;

writel(stat,i2c->regs + S3C2410_IICSTAT);

}

s3c24xx_i2c_xfer()函数调用s3c24xx_i2c_doxfer()函数传输I2C消息,

s3c24xx_i2c_doxfer()首先将S3C2410的I2C适配器设置为I2C主设备,其后初始

化s3c24xx_i2c结构体,使能I2C中断,并调用s3c24xx_i2c_message_start()函数启动

I2C消息的传输。

s3c24xx_i2c_message_start()函数写S3C2410适配器对应的控制寄存器,向I2C从

设备传递开始位和从设备地址。

上述代码只是启动了I2C消息数组的传输周期,并没有完整实现下图中给出的

algorithmmaster_xfer
流程。这个流程的完整实现需要借助I2C适配器上的中断来步步

推进。代码清单1.8所示为S3C2410I2C
适配器中断处理函数以及其依赖的

i2s_s3c_irq_nextbyte()函数的源代码。

代码清单1.8S3C2410 I2C
适配器中断处理函数

staticirqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id,
structpt_regs *regs)

{

structs3c24xx_i2c *i2c = dev_id;

unsignedlong status;

unsignedlong tmp;

status= readl(i2c->regs + S3C2410_IICSTAT);

if(status & S3C2410_IICSTAT_ARBITR)

{

...

}

if(i2c->state == STATE_IDLE)

{

tmp= readl(i2c->regs + S3C2410_IICCON);

tmp&= ~S3C2410_IICCON_IRQPEND;

writel(tmp,i2c->regs + S3C2410_IICCON);

gotoout;

}

i2s_s3c_irq_nextbyte(i2c,status);/*
把传输工作进一步推进*/

out:

returnIRQ_HANDLED;

}

staticint i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c,

unsignedlong iicstat)

{

unsigned long tmp;

unsigned char byte;

int ret = 0;

switch (i2c->state)

{

case STATE_IDLE:

goto out;

break;

caseSTATE_STOP:

s3c24xx_i2c_disable_irq(i2c);

goto out_ack;

case STATE_START:

/*
我们最近做的一件事是启动一个新I2C消息*/

if (iicstat & S3C2410_IICSTAT_LASTBIT &&

!(i2c->msg->flags& I2C_M_IGNORE_NAK))

{

/*没有收到ACK*/

s3c24xx_i2c_stop(i2c, -EREMOTEIO);

goto out_ack;

}

if (i2c->msg->flags & I2C_M_RD)

i2c->state = STATE_READ;

else

i2c->state = STATE_WRITE;

/*
仅一条消息,而且长度为0(主要用于适配器探测),发送停止位*/

if (is_lastmsg(i2c) && i2c->msg->len == 0)

{

s3c24xx_i2c_stop(i2c, 0);

goto out_ack;

}

if(i2c->state == STATE_READ)

gotoprepare_read;

/*进入写状态*/

caseSTATE_WRITE:

retry_write:

if(!is_msgend(i2c))

{

byte= i2c->msg->buf[i2c->msg_ptr++];

writeb(byte,i2c->regs + S3C2410_IICDS);

}

elseif (!is_lastmsg(i2c))

{

/*推进到下一条消息*/

i2c->msg_ptr= 0;

i2c->msg_idx++;

i2c->msg++;

/*
检查是否要为该消息产生开始位*/

if(i2c->msg->flags & I2C_M_NOSTART)

{

if(i2c->msg->flags & I2C_M_RD)

{

s3c24xx_i2c_stop(i2c,-EINVAL);

}

gotoretry_write;

}
else
{

/*发送新的开始位*/

s3c24xx_i2c_message_start(i2c, i2c->msg);

i2c->state = STATE_START;

}

}
else
{

s3c24xx_i2c_stop(i2c,0);/* send stop */

}

break;

caseSTATE_READ:

/*有一个字节可读,看是否还有消息要处理

*/

if(!(i2c->msg->flags & I2C_M_IGNORE_NAK) &&

!(is_msglast(i2c)&& is_lastmsg(i2c))) {

if (iicstat & S3C2410_IICSTAT_LASTBIT) {

dev_dbg(i2c->dev,"READ: No Ack\n");

s3c24xx_i2c_stop(i2c,-ECONNREFUSED);

gotoout_ack;

}

}

byte= readb(i2c->regs + S3C2410_IICDS);

i2c->msg->buf[i2c->msg_ptr++]= byte;

prepare_read:

if(is_msglast(i2c)) {/* last byte of buffer */

if(is_lastmsg(i2c))

s3c24xx_i2c_disable_ack(i2c);

}else if (is_msgend(i2c)) {

/*还有消息要处理吗?*/

if(is_lastmsg(i2c)) {

s3c24xx_i2c_stop(i2c,0);/* last message, send stop and

complete*/

}else {

/*推进到下一条消息*/

i2c->msg_ptr= 0;

i2c->msg_idx++;

i2c->msg++;

}

}

break;

}

/*irq
清除*/

out_ack:

tmp= readl(i2c->regs + S3C2410_IICCON);

tmp&= ~S3C2410_IICCON_IRQPEND;

writel(tmp,i2c->regs + S3C2410_IICCON);

out:

returnret;

}

中断处理函数s3c24xx_i2c_irq()主要通过调用i2s_s3c_irq_nextbyte()函数进行传输

工作 的进 一步 推进 。i2s_s3c_irq_nextbyte()函数
通过 switch(i2c->state)语句 分成
i2c->state的不同状态进行处理,在每种状态下,先检查i2c->state的状态与硬件寄存
器应该处于的状态是否一致,如果不一致,则证明有误,直接返回。当I2C处于读状
态STATE_READ或写状态STATE_WRITE时,通过is_lastmsg()函数判断是否传输的
是最后一条I2C消息,
如果是,
则产生停止位,
否则通过i2c->msg_idx++、
i2c->msg++
推进到下一条消息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息