S3C2410 I2C 总线驱动实例

来源:互联网 发布:最好的logo设计软件 编辑:程序博客网 时间:2024/05/23 09:36

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


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++

推进到下一条消息。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 我惹朋友真的很生气了怎么办 买了保险后又犯杀人罪 保险怎么办 水瓶盖了拧歪了打不开怎么办 刚买的老干妈盖子打不开怎么办? 门冬胰岛素的盖子转不动怎么办 diy做的蝴蝶结上的歪了怎么办 电脑显示电源已接通未充电怎么办 微博账号被锁无法登录怎么办 惠头条登录过两个账号被锁了怎么办 被两个月大的小狗咬破皮了怎么办 阿根廷认证出口声明成分错了怎么办 花盆土里面有白色的虫子怎么办 刚买的月季苗叶子黄了怎么办? 月季新出的芽中间黑了怎么办 龙须的茎被折断了怎么办? 扦插的月季跟部总发黑怎么办 用过皮炎平后的黑印怎么办 兰花现在不管王莽岭了员工怎么办 家里很穷妈妈还不知道省钱怎么办 执业医师电子注册信息弄错了怎么办 开过花后的百合花根怎么办 荔枝吃起来有股保鲜剂的味道怎么办 厂停产不宣布倒闭又不赔钱怎么办 二审判决生效后发现判错了怎么办 应收账款质押后附属的担保怎么办 房子借款合同和抵押合同丢了怎么办 离婚判决不服上诉期限过了怎么办 法院判完执行厅不执行怎么办 民事判决书下来又有新的证据怎么办 民事判决书下来欠钱没钱法院怎么办 法院笔录没看清就签字了怎么办 有人模仿自己笔迹在法院签字怎么办 房屋未签合同中介定金不退怎么办 临时摊位买的东西想退怎么办 租房合同签了房东不想租了怎么办 租房合同签了不想租了怎么办 租房合同刚签一天不想租怎么办 买二手房房东不交物业费怎么办 买二手房原房主欠物业费怎么办 客人将物品遗留在餐厅时怎么办 工程签合同了又别人来干怎么办?