[经验] I2C驱动程序框架源码分析(1)

宋元浩   2017-9-18 15:09 楼主

微信图片_20170918141712.jpg

a. 硬件部分
在此只提供mini2440I2C接口通信协议,S3C2440I2C控制器主要由4个寄存器完成所有的I2C操作的,这4个寄存器是IICONIICSTATIICADDIICCDS。(请参见Mini2440手册)
b. I2C总线驱动
首先我们要明白总线层驱动编写好是放在/drivers/i2c/buses目录下的。那下面让我们一起分析下I2c_s3c2410.c这个总线驱动吧。前面说过,编写I2C总线驱动层主要是填充i2c_adapteri2c_algorithm结构体,那么可以开始了,让我们先来填充i2c_algorithm结构体吧,代码如下
static conststruct i2c_algorithms3c24xx_i2c_algorithm = {
.master_xfer=s3c24xx_i2c_xfer,
.functionality= s3c24xx_i2c_func,
};
我们先看i2c_algorithm中的master_xfer成员,刚才说过,s3c24xx_i2c_xfer是用来确定适配器支持的类型,用于返回总线支持的协议,具体到代码如下
static u32s3c24xx_i2c_func(structi2c_adapter *adap)
{
returnI2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |I2C_FUNC_PROTOCOL_MANGLING;
}
好了,接下来我们把重点放在i2c_algorithm中的functionality成员上,s3c24xx_i2c_func函数用于实现I2C通信协议,将i2c_msg消息传给I2C设备。
static ints3c24xx_i2c_xfer(structi2c_adapter *adap, struct i2c_msg *msgs,int num)
{

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

int retry;
int ret;

structs3c2410_platform_i2c *pdata= i2c->dev->platform_data;

if(pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));

for (retry = 0;retry retries; retry++) {

ret = s3c24xx_i2c_doxfer(i2c, msgs,num); //传输到I2C设备的具体函数
if (ret != -EAGAIN)
return ret;
dev_dbg(i2c->dev,"Retryingtransmission (%d)\n", retry);
udelay(100);
}
return-EREMOTEIO;
}
其实,s3c24xx_i2c_xfer主要就是调用s3c24xx_i2c_doxfer完成具体数据的传输任务。
static ints3c24xx_i2c_doxfer(structs3c24xx_i2c *i2c, struct i2c_msg *msgs,int num)
{
unsigned long timeout; //传输超时
intret; //返回传输的消息个数
if(i2c->suspended) //如果适配器处于挂起状态,则返回
return -EIO;

ret =s3c24xx_i2c_set_master(i2c);//将适配器设置为主机发送状态

if (ret != 0) {
dev_err(i2c->dev, "cannot get bus(error%d)\n", ret);
ret = -EAGAIN;
goto out;
}
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);

//设置等待队列,直到i2c->msg_num==0为真或5ms到来才被唤醒

ret =i2c->msg_idx;
if (timeout ==0)
dev_dbg(i2c->dev,"timeout\n");
else if (ret !=num)
dev_dbg(i2c->dev, "incompletexfer(%d)\n", ret);
msleep(1);
out:
return ret;
}
总结一下,s3c24xx_i2c_doxfer主要做了如下几件事:第一,将适配器设置为主机发送状态。第二,设置为中断传输方式。第三,发送启动信号,传输第一个字节。第四,等待超时或者其他函数在i2c->msg_num == 0时唤醒这里的等待队列。
到此为止我们会带来几个疑问:第一,s3c24xx_i2c_enable_irq和s3c24xx_i2c_message_start具体怎么实现的?第二,等待队列在何时被唤醒呢?
首先我们先来研究第一个问题,s3c24xx_i2c_enable_irq实现开中断如下
static inline voids3c24xx_i2c_enable_irq(structs3c24xx_i2c *i2c)
{
unsigned long tmp;

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

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

//将IICCON的D5位置1表示总线在接收或发送一个字节数据后会产生一个中断。
}
下面看看s3c24xx_i2c_message_start实现启动适配器的消息传输的实现吧。
static voids3c24xx_i2c_message_start(structs3c24xx_i2c *i2c, struct i2c_msg *msg)
{

unsigned int addr = (msg->addr& 0x7f) << 1; //取从设备地址低7位,并前移1位

unsigned long stat; //缓存IICSTAT
unsigned long iiccon; //缓存IICCON
stat = 0;

stat |= S3C2410_IICSTAT_TXRXEN;//使能发送接收功能,为写地址到IICDS

if (msg->flags & I2C_M_RD){ //如果读,则主机接收,地址位D0=1

stat |=S3C2410_IICSTAT_MASTER_RX;
addr |= 1;
}else //如果写,则主机发送,地址位D0=0
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);

dev_dbg(i2c->dev, "START:%08lx to IICSTAT, %02x toDS\n", stat, addr);

writeb(addr, i2c->regs +S3C2410_IICDS); //写地址到IICDS寄存器

ndelay(i2c->tx_setup);

dev_dbg(i2c->dev,"iiccon, %08lx\n", iiccon);

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

stat |= S3C2410_IICSTAT_START; //发送S信号,IICDS寄存器中数据自动发出

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

}
总结下我们这个3c24xx_i2c_message_start函数吧,这个函数主要做了两件事,第一使能ACK信号。第二,将从机地址和读写方式控制字写入待IICDS中,由IICSTAT发送S信号,启动发送从机地址。
嗯,到现在为止我们已经把前面提出的第一个问题解决了,该轮到解决第二个问题了,s3c24xx_i2c_doxfer中的等待队列何时被唤醒呢?其实分析到现在我们发现s3c24xx_i2c_doxfer调用3c24xx_i2c_message_start只是发送了一个从机的地址。真正的数据传输在哪里呢?其实真正的数据传输我们放在了中断处理函数中实现的。当数据准备好发送时,将产生中断,并调用事先注册的中断处理函数s3c24xx_i2c_irq进行数据传输。中断的产生其实有3种情况,第一,总线仲裁失败。第二,当总线还处于空闲状态,因为一些错误操作等原因,意外进入了中断处理函数。第三,收发完一个字节的数据,或者当收发到的I2C设备地址信息吻合。行,那我们先来看看s3c24xx_i2c_irq到底怎么来传输数据的吧。
static irqreturn_ts3c24xx_i2c_irq(intirqno, 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) { //仲裁失败下的处理

dev_err(i2c->dev, "dealwith arbitration loss\n");

}
//当总线为空闲状态,突然进入中断,我们需要清除中断信号,继续传输数据

if (i2c->state == STATE_IDLE){

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;
}

i2s_s3c_irq_nextbyte(i2c,status); //传输或接收下一个字节

out:
return IRQ_HANDLED;
}
我们发现其实中断处理函数s3c24xx_i2c_irq中真正传输数据的函数是i2s_s3c_irq_nextbyte。走了这么久,其实i2s_s3c_irq_nextbyte才是真正的传输数据的核心函数,那我们赶紧来看看 i2s_s3c_irq_nextbyte吧。
static inti2s_s3c_irq_nextbyte(structs3c24xx_i2c *i2c, unsigned long iicstat)
{
unsigned long tmp;
unsigned char byte;
int ret = 0;
switch (i2c->state) {
case STATE_IDLE: //总线上没有数据传输,则立即返回

dev_err(i2c->dev,"%s:called in STATE_IDLE\n", __func__);

goto out;
break;
case STATE_STOP: //发出停止信号P

dev_err(i2c->dev,"%s:called in STATE_STOP\n", __func__);

s3c24xx_i2c_disable_irq(i2c); //关中断
goto out_ack;
case STATE_START: //发出开始信号S
//当没有接收到ACK应答信号,说明I2C设备不存在,应停止总线工作

if (iicstat&S3C2410_IICSTAT_LASTBIT &&

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

dev_dbg(i2c->dev, "ackwas not received\n");

s3c24xx_i2c_stop(i2c, -ENXIO); //完成发送P信号,唤醒,关中断三个事情
goto out_ack;
}

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

i2c->state = STATE_READ; //一个读消息
else
i2c->state = STATE_WRITE; //一个写消息
// is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1

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

s3c24xx_i2c_stop(i2c, 0);
goto out_ack;
}

if (i2c->state ==STATE_READ)//如果是读那进行跳转,注此case无break!

goto prepare_read;
case STATE_WRITE:
//当没有接收到ACK应答信号,说明I2C设备不存在,应停止总线工作

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

if (iicstat &S3C2410_IICSTAT_LASTBIT) {

dev_dbg(i2c->dev, "WRITE:NoAck\n");

s3c24xx_i2c_stop(i2c,-ECONNREFUSED);
gotoout_ack;
}
}
retry_write:
// is_msgend(0判断当前消息是否已经传输完所有字节,如果是返回1
if (!is_msgend(i2c)) {

byte =i2c->msg->buf[i2c->msg_ptr++]; //读取待传输数据

writeb(byte,i2c->regs +S3C2410_IICDS); //将待传输数据写入IICDS

ndelay(i2c->tx_setup); //延时50ms,等待发送到总线上
// is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1
} else if (!is_lastmsg(i2c)) { //当前信息传输完,还有信息要传输情况下

dev_dbg(i2c->dev,"WRITE:Next Message\n");

i2c->msg_ptr= 0; //下一条消息字符串的首地址置0
i2c->msg_idx++; //表示已经传输完1条消息
i2c->msg++; //表示准备传输下一条消息

if(i2c->msg->flags &I2C_M_NOSTART) { //不处理此新类型消息,停止

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

s3c24xx_i2c_stop(i2c, -EINVAL);
}
goto retry_write; //当本消息因类型不被处理则继续查看下面是否有消息
} else { //开始传输消息,将IICDS里的数据发送到总线上
s3c24xx_i2c_message_start(i2c,i2c->msg);
i2c->state= STATE_START;
}
} else { //当前信息传输完,没有信息要传输情况下,停止总线工作
s3c24xx_i2c_stop(i2c, 0);
}
break;
case STATE_READ:

byte = readb(i2c->regs +S3C2410_IICDS); //从IICDS读取数据

//将读取到的数据放入缓存区, msg_ptr++直到当前消息传输完毕

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

prepare_read:
// is_msglast()判断如果是消息的最后一个字节,如果是返回1
if (is_msglast(i2c)) {
// is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1
if (is_lastmsg(i2c))
s3c24xx_i2c_disable_ack(i2c); //关闭ACK应答信号
// is_msgend(0判断当前消息是否已经传输完所有字节,如果是返回1
}else if (is_msgend(i2c)) {
// is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1
if(is_lastmsg(i2c)) {

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

s3c24xx_i2c_stop(i2c, 0); //发P信号,唤醒等待队列
}else {
//当前消息传输完毕,但还有其他消息,则将相关指针指向下一条消息

dev_dbg(i2c->dev, "READ:Next Transfer\n");

i2c->msg_ptr = 0; //下一条消息字符串的首地址置0
i2c->msg_idx++; //表示已经传输完1条消息
i2c->msg++; //表示准备传输下一条消息
}
}
break;
}
out_ack:

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

tmp &=~S3C2410_IICCON_IRQPEND; //清除中断,否则会重复执行中断处理函数

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

out:
return ret;
}
我们终于把这个庞大的i2s_s3c_irq_nextbyte搞定了,在这里需要说明几点,第一,消息分为第一条消息,第二条消息,第三条消息等,总共有msg_num条消息,每发送完一个消息,会msg_idx++。每条消息发送完还需要调用s3c24xx_i2c_message_start进行发送新的起始信号S。第二,第i条消息是一个字符串,按照一个字节一个字节的形式发送,由一个指针msg_ptr指向这个字符串的待发送字节的地址。
在i2s_s3c_irq_nextbyte这个函数中,我们发现有很多s3c24xx_i2c_stop终止函数, 那么让我们来看看这个函数到底怎么终止的吧。
static inline voids3c24xx_i2c_stop(structs3c24xx_i2c *i2c, int ret)
{

unsigned long iicstat =readl(i2c->regs + S3C2410_IICSTAT);

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

iicstat &=~S3C2410_IICSTAT_START; //发送P信号

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

i2c->state = STATE_STOP; //设置适配器状态为停止

s3c24xx_i2c_master_complete(i2c,ret); //唤醒传输等待队列中的进程

s3c24xx_i2c_disable_irq(i2c); //禁止中断
}
这个s3c24xx_i2c_stop函数还是很简单的,但里面调用了s3c24xx_i2c_master_complete这个函数来唤醒传输等待队列中的进程,那我们就来看看s3c24xx_i2c_master_complete啦。
static inlinevoids3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)
{

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

i2c->msg_ptr = 0; //下一条消息字符串的首地址置0
i2c->msg = NULL; //表示没有可传输的消息
i2c->msg_idx++; //表示已经传输完1条消息
i2c->msg_num = 0; //表示没有可传输的消息
if (ret)
i2c->msg_idx =ret; //记录已经传输完的信息个数
wake_up(&i2c->wait); //唤醒等待队列中的进程
}
到此为止,我们已经完成了在I2C总线驱动层填充了i2c_adapter和i2c_algorithm结构体,剩下来我们需要把这两个结构体外包一下,来注册这个适配器,这怎么实现呢?当然我们在上面已经分析了中断处理函数s3c24xx_i2c_irq,那么这个函数什么时候被注册的呢?带着这两个问题我们需要继续往下走,go!
下面两个函数就完成了I2C总线层驱动模块的注册和注销。
static int __init i2c_adap_s3c_init(void)
{ //注册平台设备
return platform_driver_register(&s3c24xx_i2c_driver);
}
static void __exit i2c_adap_s3c_exit(void)
{ //注销平台设备
platform_driver_unregister(&s3c24xx_i2c_driver);
}
那我们来看看这个平台设备吧。
static structplatform_drivers3c24xx_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,
},
};
平台驱动的这个探测函数s3c24xx_i2c_probe就完成了整个适配器的注册和中断处理函数的注册工作了。我们再来看看这个s3c24xx_i2c_probe函数:
static int s3c24xx_i2c_probe(structplatform_device*pdev)
{
struct s3c24xx_i2c *i2c; //适配器指针

struct s3c2410_platform_i2c*pdata; //平台数据

struct resource *res; //指向资源
int ret;

pdata =pdev->dev.platform_data; //获取平台数据

if (!pdata) {

dev_err(&pdev->dev,"no platform data\n");

return -EINVAL;
}

i2c = kzalloc(sizeof(structs3c24xx_i2c), GFP_KERNEL); //分配适配器空间

if (!i2c) {

dev_err(&pdev->dev,"no memory for state\n");

return -ENOMEM;
}
//给适配器赋予名字s3c2410-i2c,这个名字会由cat /sys/class/i2c_dev/0/name看到。

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; //数据从适配器到总线的时间为50ms
spin_lock_init(&i2c->lock);

init_waitqueue_head(&i2c->wait);//初始化等待队列

i2c->dev = &pdev->dev;

i2c->clk =clk_get(&pdev->dev,"i2c"); //获取i2c时钟

if (IS_ERR(i2c->clk)) {

dev_err(&pdev->dev,"cannot get clock\n");

ret = -ENOENT;
goto err_noclk;
}

dev_dbg(&pdev->dev,"clock source %p\n",i2c->clk);

clk_enable(i2c->clk); //使能i2c时钟

res = platform_get_resource(pdev,IORESOURCE_MEM, 0);//获取适配器寄存器资源

if (res == NULL) {

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

ret = -ENOENT;
goto err_clk;
}

i2c->ioarea =request_mem_region(res->start, resource_size(res), //申请I/O内存

pdev->name);
if (i2c->ioarea == NULL) {

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

ret = -ENXIO;
goto err_clk;
}

i2c->regs =ioremap(res->start, resource_size(res)); //将内存地址映射到虚拟地址

if (i2c->regs == NULL) {

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

ret = -ENXIO;
goto err_ioarea;
}

dev_dbg(&pdev->dev,"registers %p (%p, %p)\n",

i2c->regs,i2c->ioarea,res);

i2c->adap.algo_data =i2c; //将私有数据指向适配器结构体

i2c->adap.dev.parent=&pdev->dev; //组织适配器的设备模型

ret =s3c24xx_i2c_init(i2c); //初始化I2C控制器
if (ret != 0)
goto err_iomap;

i2c->irq = ret =platform_get_irq(pdev, 0); //获取平台设备的中断号

if (ret <= 0) {

dev_err(&pdev->dev,"cannotfind IRQ\n");

goto err_iomap;
}

ret = request_irq(i2c->irq,s3c24xx_i2c_irq, IRQF_DISABLED,

dev_name(&pdev->dev),i2c); //注册中断处理函数

if (ret != 0) {

dev_err(&pdev->dev,"cannot claim IRQ %d\n", i2c->irq);

goto err_iomap;
}

ret =s3c24xx_i2c_register_cpufreq(i2c); //在内核中注册一个适配器使用的时钟

if (ret < 0) {

dev_err(&pdev->dev,"failed to register cpufreq notifier\n");

goto err_irq;
}

i2c->adap.nr =pdata->bus_num;

ret =i2c_add_numbered_adapter(&i2c->adap); //向内核中添加适配器

if (ret < 0) {

dev_err(&pdev->dev,"failed to add bus to i2c core\n");

goto err_cpufreq;
}

platform_set_drvdata(pdev, i2c);//将I2C适配器设置为平台设备的私有数据

dev_info(&pdev->dev, "%s:S3C I2C adapter\n",dev_name(&i2c->adap.dev));

return 0;
err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
err_irq:
free_irq(i2c->irq, i2c);
err_iomap:
iounmap(i2c->regs);
err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
err_noclk:
kfree(i2c);
return ret;
}
我们来回顾下这个探测函数s3c24xx_i2c_probe吧,这个函数主要干了六件事。第一,申请一个I2C适配器结构体,并对其赋值。第二,获取I2C时钟资源,并注册时钟。第三,获取资源并最终映射到物理地址。第四,申请中断处理函数。第五,初始化I2C控制器。第六,将I2C适配器添加到内核中。对于resume函数由于做的是跟探测函数相反的操作,在此就无需浪费时间了。
接下来,我们来看看上面第五步初始化I2C控制器所使用的函数s3c24xx_i2c_init。

static ints3c24xx_i2c_init(struct s3c24xx_i2c *i2c)

{

unsigned long iicon =S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;

struct s3c2410_platform_i2c*pdata;

unsigned int freq;

pdata =i2c->dev->platform_data;

if (pdata->cfg_gpio) //初始化GPIO口
pdata->cfg_gpio(to_platform_device(i2c->dev));

writeb(pdata->slave_addr,i2c->regs + S3C2410_IICADD); //写入从机地址

dev_info(i2c->dev, "slaveaddress 0x%02x\n",pdata->slave_addr);

writel(iicon, i2c->regs +S3C2410_IICCON); //开中断,ACK信号使能

if (s3c24xx_i2c_clockrate(i2c,&freq) != 0) { //设置时钟源和时钟频率

writel(0, i2c->regs+S3C2410_IICCON); //失败则设置为0

dev_err(i2c->dev,"cannotmeet bus frequency required\n");

return -EINVAL;
}

dev_info(i2c->dev, "busfrequency set to %d KHz\n",freq);

dev_dbg(i2c->dev,"S3C2410_IICCON=0x%02lx\n", iicon);

return 0;
}
在s3c24xx_i2c_init中,我们调用s3c24xx_i2c_clockrate设置了时钟源和时钟频率,继续看下去。

static ints3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsignedint *got)

{

struct s3c2410_platform_i2c*pdata = i2c->dev->platform_data;

unsigned long clkin =clk_get_rate(i2c->clk); //获取PLCK时钟,单位为HZ

unsigned int divs, div1;
unsigned long target_frequency;
u32 iiccon;
int freq;
i2c->clkrate = clkin;
clkin /= 1000; //时钟频率单位转为KHZ

dev_dbg(i2c->dev, "pdatadesired frequency %lu\n", pdata->frequency);

target_frequency =pdata->frequency ? pdata->frequency :100000;

target_frequency /= 1000; //目标频率,单位KHZ

freq =s3c24xx_i2c_calcdivisor(clkin, target_frequency, &div1,&divs); //获取分频值

if (freq > target_frequency) {
dev_err(i2c->dev,

"Unable to achieve desiredfrequency %luKHz." \

" Lowest achievable%dKHz\n", target_frequency, freq);

return -EINVAL;
}
*got = freq;
//将分频值写入IICCON相应位

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

iiccon &=~(S3C2410_IICCON_SCALEMASK |S3C2410_IICCON_TXDIV_512);

iiccon |= (divs-1);
if (div1 == 512)

iiccon|=S3C2410_IICCON_TXDIV_512;

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

//如果设备是2440,则执行下面代码处理
if (s3c24xx_i2c_is2440(i2c)) {
unsigned long sda_delay;
if (pdata->sda_delay){

sda_delay = (freq / 1000) *pdata->sda_delay;

sda_delay /= 1000000;

sda_delay =DIV_ROUND_UP(sda_delay, 5);

if (sda_delay > 3)
sda_delay = 3;

sda_delay |=S3C2410_IICLC_FILTER_ON;

} else
sda_delay = 0;

dev_dbg(i2c->dev,"IICLC=%08lx\n",sda_delay);

writel(sda_delay,i2c->regs +S3C2440_IICLC);

}
return 0;
}
在s3c24xx_i2c_clockratet中,我们调用s3c24xx_i2c_calcdivisor根据已知PCLK和目标频率,获取了两个分频系数,我们继续看下去。
static ints3c24xx_i2c_calcdivisor(unsignedlong clkin, unsigned int wanted,

unsigned int *div1, unsigned int*divs)

{

unsigned int calc_divs = clkin /wanted;

unsigned int calc_div1;
if (calc_divs > (16*16))
calc_div1 =512; //IICLK=PCLK/512
else
calc_div1 =16; //IICLK=PCLK/16
calc_divs += calc_div1-1;
calc_divs /= calc_div1;
if (calc_divs == 0) //控制分频量程范围
calc_divs = 1;
if (calc_divs > 17) //控制分频量程范围
calc_divs = 17;
*divs = calc_divs; //分频系数
*div1 = calc_div1; //时钟源的选择

return clkin / (calc_divs *calc_div1);

}
好了,到现在为止,我们的I2C总线层驱动就已经全部搞定了,我们总结下吧!在基于mini2440的I2C总线层驱动中,我们首先加载了一个平台设备,在平台设备的探测函数中,我们主要注册了适配器和中断处理函数。适配器结构体主要是实现通信方法的函数s3c24xx_i2c_xfer,我们在这里是使用的中断方式进行通信的,这也是大多数的情况下我们的选择,当然我们也可以采用查询的方式进行编写s3c24xx_i2c_xfer函数,只需要判断是读还是写操作就可以了。I2C总线层驱动模块加载后会在sys文件系统下产生一个适配器节点,可以供I2C设备驱动层来进行探测匹配。

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复