S3C2410 I2C 总线驱动实例

1.S3C2410I2C控制器硬件描述


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


1 IICCON:I2C控制寄存器。


2 IICSTAT:I2C状态寄存器。


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


4 IICADD:I2C地址寄存器。


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


2.S3C2410I2C总线驱动总体分析

S3C2410I2C总线驱动设计主要要完成以下工作。

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总线驱动的多个函数。

S3C2410 I2C 总线驱动实例_第1张图片

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_driverremove指针方式被调用。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.6S3C2410i2c_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()首先将S3C2410I2C适配器设置为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++

推进到下一条消息。


你可能感兴趣的:(S3C2410 I2C 总线驱动实例)