水平有限,描述不当之处还请之处,转载请注明出处http://blog.csdn.net/vanbreaker/article/details/7743184        

       本节结合i2cdev,来阐述Linux下的IIC是如何进行数据传输的。和spidev类似,i2cdev也是一个通用的设备驱动,但是又有些不同。在spidev中,spidev驱动注册会和相应的从设备绑定,也就是说spidev对应的是一个实际的从设备,而i2cdev只是一个虚拟的从设备,因为它并不对应一个实际的从设备,而是和IIC控制器adapter绑定,只有当用户调用open()打开一个设备文件时,才会创建一个虚拟的client。那么怎么知道该i2cdev对应哪个从设备呢?其实很简单,只需要用户在用户空间多做一个工作,就是通过Iocntl()函数来设定从设备的地址,当然这个从设备必须是挂接在对应的adapter下的。所以i2cdev作为一个虚拟设备,本身并不对应任何设备,但又可以对应任意一个挂接在其依附的adapter下的设备。

先来看下i2cdev的定义

struct i2c_dev {
	struct list_head list;//用于链入i2c_dev_list
	struct i2c_adapter *adap;//依附的IIC主控制器
	struct device *dev;
};


 

第一步,当然是初始化i2cdev模块,将i2cdev作为字符设备注册,创建一个i2cdev类,并且注册i2cdev的驱动

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

	/*注册i2cdev字符设备*/
	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

         /*创建i2cdev类*/
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}

	/*添加i2cdev驱动*/
	res = i2c_add_driver(&i2cdev_driver);
	if (res)
		goto out_unreg_class;

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}


i2c_add_driver()只是i2c_register_driver()的一个封装,进入i2c_register_driver()

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	...
         ...
	...
	driver->driver.bus = &i2c_bus_type;
         ...
 	res = driver_register(&driver->driver);
         ...
	/*遍历i2c_adapter_class下的i2c_adapter,并调用__attach_adapter*/
	class_for_each_device(&i2c_adapter_class, NULL, driver,
			      __attach_adapter);
         ...
	return 0;
}

我们可以看到主要操作有两个,一个是driver_register(),还有一个就是遍历adapter,并调用__attach_adapter()

实际上这里的driver_register()并没有太多实际的作用,因为它无法完成i2cdev_driver和从设备的匹配,为什么呢?我们来看一下i2c_bus_type中定义的match函数

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = to_i2c_client(dev);
	struct i2c_driver	*driver = to_i2c_driver(drv);

	/* make legacy i2c drivers bypass driver model probing entirely;
	 * such drivers scan each i2c adapter/bus themselves.
	 */
	if (!is_newstyle_driver(driver))
		return 0;

	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}


 

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)
			return id;
		id++;
	}
	return NULL;
}


 

可以看到,只有当driver->id_table不为空时,才会调用实际的匹配函数i2c_match_id(),而i2cdev_driver中并没有定义id_table,因此是无法和任何从设备匹配的。

static struct i2c_driver i2cdev_driver = {
	.driver = {
		.name	= "dev_driver",
	},
	.attach_adapter	= i2cdev_attach_adapter,
	.detach_adapter	= i2cdev_detach_adapter,
};


再来看__attach_adapter()

static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
	struct i2c_dev *i2c_dev;
	
         ...
	i2c_dev = get_free_i2c_dev(adap);
	/*注册i2c_dev设备*/
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
         ...
	res = device_create_file(i2c_dev->dev, &dev_attr_name);
	...
         ...	
}

跟踪get_free_i2c_dev()

static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap)
{
	struct i2c_dev *i2c_dev;

	if (adap->nr >= I2C_MINORS) {
		printk(KERN_ERR "i2c-dev: Out of device minors (%d)\n",
		       adap->nr);
		return ERR_PTR(-ENODEV);
	}

	/*为i2c_dev申请空间*/
	i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL);
	if (!i2c_dev)
		return ERR_PTR(-ENOMEM);
	i2c_dev->adap = adap;//设置i2c_dev依附的i2c_adapter

	spin_lock(&i2c_dev_list_lock);
	list_add_tail(&i2c_dev->list, &i2c_dev_list);//将i2c_dev添加到i2c_dev_list
	spin_unlock(&i2c_dev_list_lock);
	return i2c_dev;
}


到这里就完成了i2cdev_driver的注册,主要任务就是在每个i2c_adapter下创建一个绑定的i2c_dev。

然后就是通过open来打开一个i2cdev

 

static int i2cdev_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int ret = 0;

	lock_kernel();
	/*遍历i2c_dev_list,通过次设备号来定位i2c_dev*/
	i2c_dev = i2c_dev_get_by_minor(minor);
	if (!i2c_dev) {
		ret = -ENODEV;
		goto out;
	}

	/*获取i2c_dev对应的i2c_adapter*/
	adap = i2c_get_adapter(i2c_dev->adap->nr);
	if (!adap) {
		ret = -ENODEV;
		goto out;
	}

	/* This creates an anonymous i2c_client, which may later be
	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
	 *
	 * This client is ** NEVER REGISTERED ** with the driver model
	 * or I2C core code!!  It just holds private copies of addressing
	 * information and maybe a PEC flag.
	 */
	 /*创建一个client虚拟从设备*/
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		ret = -ENOMEM;
		goto out;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
	client->driver = &i2cdev_driver;//设定client所依附的driver

	client->adapter = adap;	    //设定client所依附的i2c控制器
	file->private_data = client;

out:
	unlock_kernel();
	return ret;
}

此时已经建立了一个虚拟的client,下一步就是要让其对应一个实际的从设备,那么就要在用户空间通过iocntl()指定从设备的地址

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	...
	switch ( cmd ) {
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
		client->addr = arg;
		return 0;
	...
}


然后还需要设置超时等待的时间,和重试次数等等……就不赘述了。

然后就可以通过read()/write()函数来进行一个message的读写操作,也可以通过iocntl()函数控制进行一个消息组的传输,我们通过write()来分析消息传输的机制

static ssize_t i2cdev_write (struct file *file, const char __user *buf, size_t count,
                             loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = (struct i2c_client *)file->private_data;

	if (count > 8192)
		count = 8192;

	tmp = kmalloc(count,GFP_KERNEL);
	if (tmp==NULL)
		return -ENOMEM;
	if (copy_from_user(tmp,buf,count)) {//从用户空间获取数据
		kfree(tmp);
		return -EFAULT;
	}

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
		iminor(file->f_path.dentry->d_inode), count);

	ret = i2c_master_send(client,tmp,count);//调用IIC核心层函数进行发送处理
	kfree(tmp);
	return ret;
}



 

int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
{
	int ret;
	struct i2c_adapter *adap=client->adapter;
	struct i2c_msg msg;

	msg.addr = client->addr;//从设备地址
	msg.flags = client->flags & I2C_M_TEN;
	msg.len = count;      //数据传输长度
	msg.buf = (char *)buf;//数据缓冲区

	ret = i2c_transfer(adap, &msg, 1);

	/* If everything went ok (i.e. 1 msg transmitted), return #bytes
	   transmitted, else error code. */
	return (ret == 1) ? count : ret;
}


IIC核心层函数i2c_transfer会调用相应的adap->algo中定义的传输函数master_xfer()进行传输,s3c24xx中的master_xfer()函数为s3c24xx_i2c_xfer(),s3c24xx_i2c_xfer()会调用s3c24xx_i2c_doxfer()进行实际的传输处理

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
			      struct i2c_msg *msgs, int num)
{
	unsigned long timeout;
	int ret;

	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;//保存msg起始地址
	i2c->msg_num = num; //保存msg的数量
	i2c->msg_ptr = 0;   //已传输的msg数量为0
	i2c->msg_idx = 0;   //单个msg中已传输的字节为0
	i2c->state   = STATE_START;//设置状态为start

	s3c24xx_i2c_enable_irq(i2c);//使能i2c中断
	/*确定是读还是写,并设定相关寄存器,准备开始传输数据*/
	s3c24xx_i2c_message_start(i2c, msgs);
	spin_unlock_irq(&i2c->lock);

	/*阻塞进程,直到i2c中断完成传输任务或超时*/
	timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);

	ret = i2c->msg_idx;

	/* having these next two as dev_err() makes life very
	 * noisy when doing an i2cdetect */

	if (timeout == 0)
		dev_dbg(i2c->dev, "timeout\n");
	else if (ret != num)
		dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

	/* ensure the stop has been through the bus */

	msleep(1);

 out:
	return ret;
}


到这里,传输工作就交给IIC中断函数进行处理了

static irqreturn_t s3c24xx_i2c_irq(int irqno, 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) {
		/* deal with arbitration loss */
		dev_err(i2c->dev, "deal with 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;
	}

	/* pretty much this leaves us with the fact that we've
	 * transmitted or received whatever byte we last sent */

	i2s_s3c_irq_nextbyte(i2c, status);

 out:
	return IRQ_HANDLED;
}


我们来看中断的实际处理函数i2s_s3c_irq_nextbyte(),不知道这里为什么是i2s-.-,难道是内核开发者的笔误?

 

static int i2s_s3c_irq_nextbyte(struct s3c24xx_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:  //停止传输,禁止中断
		dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
		s3c24xx_i2c_disable_irq(i2c);
		goto out_ack;

	case STATE_START:
		/* last thing we did was send a start condition on the
		 * bus, or started a new i2c message
		 */

		if (iicstat & S3C2410_IICSTAT_LASTBIT &&
		    !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
			/* ack was not received... */

			dev_dbg(i2c->dev, "ack was not received\n");
			s3c24xx_i2c_stop(i2c, -ENXIO);
			goto out_ack;
		}

		/*确定读写状态*/
		if (i2c->msg->flags & I2C_M_RD)
			i2c->state = STATE_READ;
		else
			i2c->state = STATE_WRITE;

		/* terminate the transfer if there is nothing to do
		 * as this is used by the i2c probe to find devices. */

		/*如果已经是最后一条消息并且该消息已经发送完,则停止传输*/
		if (is_lastmsg(i2c) && i2c->msg->len == 0) {
			s3c24xx_i2c_stop(i2c, 0);
			goto out_ack;
		}

		if (i2c->state == STATE_READ)
			goto prepare_read;

		/* fall through to the write state, as we will need to
		 * send a byte as well */

	case STATE_WRITE:
		/* we are writing data to the device... check for the
		 * end of the message, and if so, work out what to do
		 */

		if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
			if (iicstat & S3C2410_IICSTAT_LASTBIT) {
				dev_dbg(i2c->dev, "WRITE: No Ack\n");

				s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
				goto out_ack;
			}
		}

 retry_write:

		if (!is_msgend(i2c)) {//该消息还未发送完毕
			byte = i2c->msg->buf[i2c->msg_ptr++];
			writeb(byte, i2c->regs + S3C2410_IICDS);//将数据写入寄存器发送

			/* delay after writing the byte to allow the
			 * data setup time on the bus, as writing the
			 * data to the register causes the first bit
			 * to appear on SDA, and SCL will change as
			 * soon as the interrupt is acknowledged */

			ndelay(i2c->tx_setup);

		} else if (!is_lastmsg(i2c)) {//不是最后一条消息,则取下一条消息
			/* we need to go to the next i2c message */

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

			i2c->msg_ptr = 0;
			i2c->msg_idx++;
			i2c->msg++; //取下一条消息进行发送

			/* check to see if we need to do another message */
			if (i2c->msg->flags & I2C_M_NOSTART) {

				if (i2c->msg->flags & I2C_M_RD) {
					/* cannot do this, the controller
					 * forces us to send a new START
					 * when we change direction */

					s3c24xx_i2c_stop(i2c, -EINVAL);
				}

				goto retry_write;
			} else {/*开始新一轮的发送*/
				/* send the new start */
				s3c24xx_i2c_message_start(i2c, i2c->msg);
				i2c->state = STATE_START;
			}

		} else {
			/* send stop */

			s3c24xx_i2c_stop(i2c, 0);
		}
		break;

	case STATE_READ:
		/* we have a byte of data in the data register, do
		 * something with it, and then work out wether we are
		 * going to do any more read/write
		 */

		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);//失能ack

		} else if (is_msgend(i2c)) {//消息读取完毕
			/* ok, we've read the entire buffer, see if there
			 * is anything else we need to do */

			if (is_lastmsg(i2c)) {//如果是最后一条消息则停止传输
				/* last message, send stop and complete */
				dev_dbg(i2c->dev, "READ: Send Stop\n");

				s3c24xx_i2c_stop(i2c, 0);
			} else {//否则取下一个消息存放读取的数据
				/* go to the next transfer */
				dev_dbg(i2c->dev, "READ: Next Transfer\n");

				i2c->msg_ptr = 0;
				i2c->msg_idx++;
				i2c->msg++;
			}
		}

		break;
	}

	/* acknowlegde the IRQ and get back on with the work */

 out_ack:
	tmp = readl(i2c->regs + S3C2410_IICCON);
	tmp &= ~S3C2410_IICCON_IRQPEND;
	writel(tmp, i2c->regs + S3C2410_IICCON);
 out:
	return ret;
}


IIC的框架介绍到此为止





 

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐