linux下I2C驱动分析2-(1)

转自:http://blog.chinaunix.net/uid-20626696-id-77697.html

 在前一篇文章中我们分析了I2C设备驱动的写法,下面我们结合IMX27内部I2C总线驱动的例子来分析适配器驱动也就是总线驱动的具体写法。

 

1.     首先我们看看总线驱动的模块加载函数:

 

static struct platform_driver mxci2c_driver = {

       .driver = {

                 .name = "mxc_i2c",

                 .owner = THIS_MODULE,

                 },

       .probe = mxci2c_probe,

       .remove = mxci2c_remove,

};

 

static int __init mxc_i2c_init(void)

{

       /* Register the device driver structure. */

       return platform_driver_register(&mxci2c_driver);

}

 

/*!

 * This function is used to cleanup all resources before the driver exits.

 */

static void __exit mxc_i2c_exit(void)

{

       platform_driver_unregister(&mxci2c_driver);

}

 

subsys_initcall(mxc_i2c_init);

module_exit(mxc_i2c_exit);

subsys_initcall在驱动成长中相当于module_init函数。

可以看到该I2C总线驱动是用Platform虚拟平台总线来管理的,这样方便将系统资源与具体实现分开,便于驱动开发。当调用platform_driver_register函数注册平台驱动时,会引发其probe函数的执行,在这里即是mxci2c_probe函数,我们来逐条看看这个函数里做了些什么:

static int mxci2c_probe(struct platform_device *pdev)

{

//mxc_i2c_device结构是设备驱动私有结构体,主要用来存放该设备的相关信息。

       mxc_i2c_device *mxc_i2c;

       struct mxc_i2c_platform_data *i2c_plat_data = pdev->dev.platform_data;

       struct resource *res;

       int id = pdev->id;

       u32 clk_freq;

       int ret = 0;

       int i;

 

       mxc_i2c = kzalloc(sizeof(mxc_i2c_device), GFP_KERNEL);

       if (!mxc_i2c) {

              return -ENOMEM;

       }

//获得I2C控制器在ARM芯片中的地址

       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

       if (res == NULL) {

              ret = -ENODEV;

              goto err1;

       }

       mxc_i2c->membase = IO_ADDRESS(res->start);

//获得I2C中断号

       mxc_i2c->irq = platform_get_irq(pdev, 0);

       if (mxc_i2c->irq < 0) {

              ret = mxc_i2c->irq;

              goto err1;

       }

//向系统注册该中断,中断执行函数是mxc_i2c_handler

       ret = request_irq(mxc_i2c->irq, mxc_i2c_handler,

                       0, pdev->name, mxc_i2c);

       if (ret < 0) {

              goto err1;

       }

//初始化一个等待队列头,用于中断处理函数

       init_waitqueue_head(&mxc_i2c->wq);

//激活GPIO

       gpio_i2c_active(id);

//设置I2C适配器的时钟频率,注意在这个linux系统中,ARM内部各个模块的时钟由系统统一管理

       mxc_i2c->clk = clk_get(&pdev->dev, "i2c_clk");

       clk_freq = clk_get_rate(mxc_i2c->clk);

       mxc_i2c->clkdiv = -1;

       if (i2c_plat_data->i2c_clk) {

              /* Calculate divider and round up any fractional part */

              int div = (clk_freq + i2c_plat_data->i2c_clk - 1) /

                  i2c_plat_data->i2c_clk;

              for (i = 0; i2c_clk_table[i].div != 0; i++) {

                     if (i2c_clk_table[i].div >= div) {

                            mxc_i2c->clkdiv = i2c_clk_table[i].reg_value;

                            break;

                     }

              }

       }

       if (mxc_i2c->clkdiv == -1) {

              i--;

              mxc_i2c->clkdiv = 0x1F;    /* Use max divider */

       }

       dev_dbg(&pdev->dev, "i2c speed is %d/%d = %d bps, reg val = 0x%02X\n",

              clk_freq, i2c_clk_table[i].div,

              clk_freq / i2c_clk_table[i].div, mxc_i2c->clkdiv);

//设置该适配器的i2c_adapter结构体

       strcpy(mxc_i2c->adap.name, MXC_ADAPTER_NAME);

       mxc_i2c->adap.id = id;

       mxc_i2c->adap.algo = &mxc_i2c_algorithm;

       mxc_i2c->adap.timeout = 1;

       platform_set_drvdata(pdev, mxc_i2c);

//mxc_i2c设置为适配器结构体的私有数据

       i2c_set_adapdata(&mxc_i2c->adap, mxc_i2c);

//************关键部分:调用i2c_add_adapter函数向系统注册该适配器。

       if ((ret = i2c_add_adapter(&mxc_i2c->adap)) < 0) {

              goto err2;

       }

 

       printk(KERN_INFO "MXC I2C driver\n");

       return 0;

 

      err2:

       free_irq(mxc_i2c->irq, mxc_i2c);

       gpio_i2c_inactive(id);

      err1:

       dev_err(&pdev->dev, "failed to probe i2c adapter\n");

       kfree(mxc_i2c);

       return ret;

}

2.上面函数中的关键部分是 i2c_add_adapter,下面我们来跟踪该函数:

int i2c_add_adapter(struct i2c_adapter *adap)

{

       int id, res = 0;

       struct list_head   *item;

       struct i2c_driver  *driver;

 

       mutex_lock(&core_lists);

 

       if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) {

              res = -ENOMEM;

              goto out_unlock;

       }

 

       res = idr_get_new(&i2c_adapter_idr, adap, &id);

       if (res < 0) {

              if (res == -EAGAIN)

                     res = -ENOMEM;

              goto out_unlock;

       }

 

       adap->nr =  id & MAX_ID_MASK;

       mutex_init(&adap->bus_lock);

       mutex_init(&adap->clist_lock);

//其实很简单,就是将该适配器插入到链表中,由内核统一管理,adapters就是内核内部适配器链表的头。在前面一篇文章,i2c_probe函数最后通过这个链表头来逐一遍历系统中所有的适配器。

       list_add_tail(&adap->list,&adapters);

       INIT_LIST_HEAD(&adap->clients);

//下面就是属于设备管理模型的内容了,以后再详细解释

       if (adap->dev.parent == NULL)

              adap->dev.parent = &platform_bus;

       sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);

       adap->dev.driver = &i2c_adapter_driver;

       adap->dev.release = &i2c_adapter_dev_release;

       res = device_register(&adap->dev);

       if (res)

              goto out_list;

       res = device_create_file(&adap->dev, &dev_attr_name);

       if (res)

              goto out_unregister;

 

       /* Add this adapter to the i2c_adapter class */

       memset(&adap->class_dev, 0x00, sizeof(struct class_device));

       adap->class_dev.dev = &adap->dev;

       adap->class_dev.class = &i2c_adapter_class;

       strlcpy(adap->class_dev.class_id, adap->dev.bus_id, BUS_ID_SIZE);

       res = class_device_register(&adap->class_dev);

       if (res)

              goto out_remove_name;

 

       dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

 

       /* inform drivers of new adapters */

       list_for_each(item,&drivers) {

              driver = list_entry(item, struct i2c_driver, list);

              if (driver->attach_adapter)

                     /* We ignore the return code; if it fails, too bad */

                     driver->attach_adapter(adap);

       }

 

out_unlock:

       mutex_unlock(&core_lists);

       return res;

 

out_remove_name:

       device_remove_file(&adap->dev, &dev_attr_name);

out_unregister:

       init_completion(&adap->dev_released); /* Needed? */

       device_unregister(&adap->dev);

       wait_for_completion(&adap->dev_released);

out_list:

       list_del(&adap->list);

       idr_remove(&i2c_adapter_idr, adap->nr);

       goto out_unlock;

}

3. I2C中断处理函数

mxci2c_probe函数中我们注册了I2C中断,中断处理函数是:mxc_i2c_handler:

static irqreturn_t mxc_i2c_handler(int irq, void *dev_id)

{

       mxc_i2c_device *dev = dev_id;

//sr,cr分别是i2c状态寄存器和i2c控制寄存器,必须用volatile修饰

       volatile unsigned int sr, cr;

//读取两个寄存器的值

       sr = readw(dev->membase + MXC_I2SR);

       cr = readw(dev->membase + MXC_I2CR);

//清中断位

       writew(0x0, dev->membase + MXC_I2SR);

//中断处理,通过IMX27datasheet可知,有三种情况可以触发中断:1.一个字节传输完毕,包括发送完一个字节和接收到一个字节数据。2.当作为从设备的时候,接收到主设备发送过来的地址是该设备地址。3.丢失总线。所以首先要排除丢失总线的可能性。

       if (sr & MXC_I2SR_IAL) {

              printk(KERN_DEBUG "Bus Arbitration lost\n");

       } else {

              /* Interrupt due byte transfer completion */

              tx_success = false;

              /* Check if RXAK is received in Transmit mode */

//是否收到ACK确认信号

              if ((cr & MXC_I2CR_MTX) && (!(sr & MXC_I2SR_RXAK))) {

                     tx_success = true;

              }

              transfer_done = true;

//*****************关键:唤醒等待队列

              wake_up_interruptible(&dev->wq);

       }

 

       return IRQ_HANDLED;

}

4. adapter赋予具体的algorithm

mxci2c_probe函数中配置i2c_adapter结构体时,有这样一条语句:

       mxc_i2c->adap.algo = &mxc_i2c_algorithm;

即是给该adapter赋予具体的algorithm,即该适配器具体的通信方法。我们来看mxc_i2c_algorithm的具体实现:

static struct i2c_algorithm mxc_i2c_algorithm = {

       .master_xfer = mxc_i2c_xfer,

       .functionality = mxc_i2c_func

};

我们先讨论主要的mxc_i2c_xfer函数,对于 mxc_i2c_func我们最后结合前面一章的内容讨论。

static int mxc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],

                     int num)

{

//首先是过的私有结构体

       mxc_i2c_device *dev = (mxc_i2c_device *) (i2c_get_adapdata(adap));

       int i, ret = 0, addr_comp = 0;

       volatile unsigned int sr;

//使能该i2c模块

       mxc_i2c_module_en(dev, msgs[0].flags);

//读取i2c状态寄存器

       sr = readw(dev->membase + MXC_I2SR);

//是否依然拥有总线控制权

       if (sr & MXC_I2SR_IBB) {

              mxc_i2c_module_dis(dev);

              printk(KERN_DEBUG "Bus busy\n");

              return -EREMOTEIO;

       }

       //gpio_i2c_active(dev->adap.id);

       transfer_done = false;

       tx_success = false;

//处理每一天i2c消息

       for (i = 0; i < num && ret >= 0; i++) {

              addr_comp = 0;

              if (i == 0) {

       //如果是第一条消息,必须发生开始信号

                     mxc_i2c_start(dev, &msgs[0]);

                     /* Wait for the address cycle to complete */

//等待确认信号

                     if (mxc_i2c_wait_for_tc(dev, msgs[0].flags)) {

                            mxc_i2c_stop(dev);

                            //gpio_i2c_inactive(dev->adap.id);

                            mxc_i2c_module_dis(dev);

                            return -EREMOTEIO;

                     }

                     addr_comp = 1;

              } else {

                     /*

                      * Generate repeat start only if required i.e the address

                      * changed or the transfer direction changed

                      */

//如果不是第一条消息,如果从设备地址改变,或者传输方向改变,则发送repstar信号,等待确认信号

                     if ((msgs[i].addr != msgs[i - 1].addr) ||

                         ((msgs[i].flags & I2C_M_RD) !=

                          (msgs[i - 1].flags & I2C_M_RD))) {

                            mxc_i2c_repstart(dev, &msgs[i]);

                            /* Wait for the address cycle to complete */

                            if (mxc_i2c_wait_for_tc(dev, msgs[i].flags)) {

                                   mxc_i2c_stop(dev);

                                   //gpio_i2c_inactive(dev->adap.id);

                                   mxc_i2c_module_dis(dev);

                                   return -EREMOTEIO;

                            }

                            addr_comp = 1;

                     }

              }

              /* Transfer the data */

              if (msgs[i].flags & I2C_M_RD) {

                     /* Read the data */

//调用mxc_i2c_readbytes接收数据

                     ret = mxc_i2c_readbytes(dev, &msgs[i], (i + 1 == num),

                                          addr_comp);

                     if (ret < 0) {

                            printk(KERN_ERR "mxc_i2c_readbytes: fail.\n");

                            break;

                     }

              } else {

                     /* Write the data */

//调用mxc_i2c_writebytes发送数据

                     ret = mxc_i2c_writebytes(dev, &msgs[i], (i + 1 == num));

                     if (ret < 0) {

                            printk(KERN_ERR "mxc_i2c_writebytes: fail.\n");

                            break;

                     }

              }

       }

 

       //gpio_i2c_inactive(dev->adap.id);

       mxc_i2c_module_dis(dev);

       /*

        * Decrease by 1 as we do not want Start message to be included in

        * the count

        */

       return (i - 1);

}

 

5.algothm中调用到得几个子函数,也就是i2c具体信号的产生以及数据传输函数

在上述函数中调用了几个子函数,下面我们分别在看看这几个子函数的实现过程:

//首先是发送开始信号的函数:首先是获得总线控制权,将该适配器设置为主设备,并且设置发送位,将处理后的从地址放入数据寄存器。

static void mxc_i2c_start(mxc_i2c_device * dev, struct i2c_msg *msg)

{

       volatile unsigned int cr, sr;

       unsigned int addr_trans;

       int retry = 16;

 

       /*

        * Set the slave address and the requested transfer mode

        * in the data register

        */

//将从地址左移一位,并且根据读写来确定最后一个bit

       addr_trans = msg->addr << 1;

       if (msg->flags & I2C_M_RD) {

              addr_trans |= 0x01;

       }

//将该适配器设置为主设备

       /* Set the Master bit */

       cr = readw(dev->membase + MXC_I2CR);

       cr |= MXC_I2CR_MSTA;

       writew(cr, dev->membase + MXC_I2CR);

//等总线空闲

       /* Wait till the Bus Busy bit is set */

       sr = readw(dev->membase + MXC_I2SR);

       while (retry-- && (!(sr & MXC_I2SR_IBB))) {

              udelay(3);

              sr = readw(dev->membase + MXC_I2SR);

       }

       if (retry <= 0) {

              printk(KERN_DEBUG "Could not grab Bus ownership\n");

       }

//设置控制寄存器发送位

       /* Set the Transmit bit */

       cr = readw(dev->membase + MXC_I2CR);

       cr |= MXC_I2CR_MTX;

       writew(cr, dev->membase + MXC_I2CR);

//将处理过的从地址放入数据寄存器

       writew(addr_trans, dev->membase + MXC_I2DR);

}

 

 

//下面看看等待ACK信号的函数:注意这里就用到中断处理函数中的等待队列了,就是根据中断处理函数中的处理来确定是否接收到ACK信号。

static int mxc_i2c_wait_for_tc(mxc_i2c_device * dev, int trans_flag)

{

       int retry = 16;

 

       while (retry-- && !transfer_done) {

//等待时间:retry*dev->adap.timeout

              wait_event_interruptible_timeout(dev->wq,

                                           transfer_done,

                                           dev->adap.timeout);

       }

       transfer_done = false;

//下面是判断是否收到ACK信号

       if (retry <= 0) {

              /* Unable to send data */

              printk(KERN_DEBUG "Data not transmitted\n");

              return -1;

       } else if (!(trans_flag & I2C_M_RD)) {

              if (!tx_success) {

                     /* An ACK was not received for transmitted byte */

                     printk(KERN_DEBUG "ACK not received \n");

                     return -1;

              }

       }

 

       return 0;

}

 

 

 

//下面看看产生restart信号的函数:其实很简单,就是当上次传输结束后,不用发送stop信号来接收对总线的拥有权,直接通过寄存器发送restart信号,并且将处理过后的地址发送到数据寄存器

static void mxc_i2c_repstart(mxc_i2c_device * dev, struct i2c_msg *msg)

{

       volatile unsigned int cr;

       unsigned int addr_trans;

//处理从设备地址

       addr_trans = msg->addr << 1;

       if (msg->flags & I2C_M_RD) {

              addr_trans |= 0x01;

       }

//设置控制寄存器

       cr = readw(dev->membase + MXC_I2CR);

       cr |= MXC_I2CR_RSTA;

       writew(cr, dev->membase + MXC_I2CR);

       udelay(3);

//将处理后的设备从地址写入数据寄存器

       writew(addr_trans, dev->membase + MXC_I2DR);

}

 

 

//下面看看如何发出stop信号

static void mxc_i2c_stop(mxc_i2c_device * dev)

{

       volatile unsigned int cr;

       int retry = 16;

//写控制寄存器发送stop信号

       cr = readw(dev->membase + MXC_I2CR);

       cr &= ~(MXC_I2CR_MSTA | MXC_I2CR_MTX);

       writew(cr, dev->membase + MXC_I2CR);

 

//延时确保总线空闲

       for (;;) {

              unsigned int sr = readw(dev->membase + MXC_I2SR);

              if ((sr & MXC_I2SR_IBB) == 0) break;

              if (retry-- <= 0) {

                     printk(KERN_DEBUG "Bus busy\n");

                     break;

              }

              udelay(3);

       }

}

 

//最关键的读函数

 

/*!

 * Read the received data. The function waits till data is available or times

 * out. Generates a stop signal if this is the last message to be received.

 * Sends an ack for all the bytes received except the last byte.

 *

 * @param  dev       the mxc i2c structure used to get to the right i2c device

 * @param  *msg      pointer to a message structure that contains the slave

 *                   address and a pointer to the receive buffer

 * @param  last      indicates that this is the last message to be received

 * @param  addr_comp flag indicates that we just finished the address cycle

 *

 * @return  The function returns the number of bytes read or -1 on time out.

 */

static int mxc_i2c_readbytes(mxc_i2c_device * dev, struct i2c_msg *msg,

                          int last, int addr_comp)

{

       int i;

       char *buf = msg->buf;

       int len = msg->len;

       volatile unsigned int cr;

// 设置相关控制寄存器

       cr = readw(dev->membase + MXC_I2CR);

       /*

        * Clear MTX to switch to receive mode.

        */

       cr &= ~MXC_I2CR_MTX;

//当只接收1个字节数据时,不发送ACK。差点被经验欺骗了,注意这里置1的时候不发送ACK,清0的时候发送ACK

       if (len == 1) {

              cr |= MXC_I2CR_TXAK;

       } else {

              cr &= ~MXC_I2CR_TXAK;

       }

       writew(cr, dev->membase + MXC_I2CR);

       /*

        * Dummy read only at the end of an address cycle

        */

//如果刚发完地址,则要假读一次。这里不是很清楚,论坛上说照做,据说是协议要求。

       if (addr_comp > 0) {

              readw(dev->membase + MXC_I2DR);

       }

 

       for (i = 0; i < len; i++) {

              /*

               * Wait for data transmission to complete

               */

//接收到一个数据,产生一个中断

              if (mxc_i2c_wait_for_tc(dev, msg->flags)) {

                     mxc_i2c_stop(dev);

                     return -1;

              }

              /*

               * Do not generate an ACK for the last byte

               */

//接收到最后一个字节以后不发送ACK信号,如果这是最后一条i2c消息,则发送stop信号

              if (i == (len - 2)) {

                     cr = readw(dev->membase + MXC_I2CR);

                     cr |= MXC_I2CR_TXAK;

                     writew(cr, dev->membase + MXC_I2CR);

              } else if (i == (len - 1)) {

//注意这里,如果是最后一条i2c消息,则肯定会产生stop信号。如果不是,则肯定会因为从设备地址改变或者传输方向改变(比如片内寄存器地址改变,则要写寄存器地址,这个时候传输方向由读到写),则会产生restart信号。

                     if (last) {

                            mxc_i2c_stop(dev);

                     }

              }

              /* Read the data */

//将读出的数据存放到buf

              *buf++ = readw(dev->membase + MXC_I2DR);

       }

 

       return i;

}

 

 

//写函数

static int mxc_i2c_writebytes(mxc_i2c_device * dev, struct i2c_msg *msg,

                           int last)

{

       int i;

       char *buf = msg->buf;

       int len = msg->len;

       volatile unsigned int cr;

//配置I2C控制寄存器

       cr = readw(dev->membase + MXC_I2CR);

       /* Set MTX to switch to transmit mode */

       cr |= MXC_I2CR_MTX;

       writew(cr, dev->membase + MXC_I2CR);

 

       for (i = 0; i < len; i++) {

              /*

               * Write the data

               */

//写好一个字节后等中断。

              writew(*buf++, dev->membase + MXC_I2DR);

              if (mxc_i2c_wait_for_tc(dev, msg->flags)) {

                     mxc_i2c_stop(dev);

                     return -1;

              }

       }

//如果是最后一个I2C消息,则发送stop信号。

       if (last > 0) {

              mxc_i2c_stop(dev);

       }

 

       return i;

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值