i2c实例讲解

如果对i2c设备驱动不了解的, 请先看这里的简单的驱动i2c识别:http://blog.csdn.net/zhutoubenben/article/details/8003781

#define DRIVER_LICENSE "GPL"
#define DRIVER_AUTHOR "zhutoubenben"
#define DRIVER_DESC "i i c iic_at24c08..."
#define I2C_DEVICE  "24c08"
#define MISC_DEVICE_NAME "at24c08"

static struct i2c_at24c08_data {
	struct i2c_client *i2c_client;
	const struct i2c_device_id *i2c_id;
}T_i2c_at24c08_data;

static int i2c_at24c08_open(struct inode *inode, struct file *filp){ 
	filp->private_data = (void *)&T_i2c_at24c08_data;// 把结构体存入filp私有指针,驱动惯用手段
	return 0;
}

static ssize_t i2c_at24c08_read(struct file *filp, char __user *buf, size_t count, loff_t *loff){
	struct i2c_at24c08_data *i2c_at24c08_data = (struct i2c_at24c08_data*)filp->private_data;// 获取open中的私有指针
	struct i2c_client * i2c_client = i2c_at24c08_data->i2c_client;
	unsigned char address;
	unsigned char data;
	struct i2c_msg msg[2];
	int ret;

	if (count != 1)
	 return -EINVAL;
	
	copy_from_user(&address, buf, 1);/*应用程序需要把地址放在buf中,才能调用读*/	
	
	/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
	msg[0].addr  = i2c_client->addr;        /*i2c 器件地址 实际上就是我们的0x50*/
	msg[0].buf	 = &address;		/* 读的地址 */
	msg[0].len	 = 1;			/* 地址=1 byte */
	msg[0].flags = 0;			/* 表示写 */
	
	/* 然后启动读操作 */
	msg[1].addr  = i2c_client->addr;       /*i2c 器件地址 */
	msg[1].buf	 = &data;	       /* 读的数据放的位置 */
	msg[1].len	 = 1;			 /* 数据=1 byte */
	msg[1].flags = I2C_M_RD;		/* 表示读 */
	
	
	ret = i2c_transfer(i2c_client->adapter, msg, 2);/*启动发送, 这个是重点, 待会主要分析这一块*/
	if (ret == 2)
	{
	 copy_to_user(buf, &data, 1);
	 return 1;
	}
	else
	 return -EIO;
	
}
static ssize_t i2c_at24c08_write(struct file *filp, const char __user *buf, size_t count, loff_t *loff){
	struct i2c_at24c08_data *i2c_at24c08_data = filp->private_data;
	struct i2c_client * i2c_client = i2c_at24c08_data->i2c_client;
	unsigned char val[2];
	struct i2c_msg msg[1];
	int ret;
	
	/* address = buf[0] ,  data    = buf[1]	 */
	if (count != 2)
	 return -EINVAL;
	
	copy_from_user(val, buf, 2);
	
	msg[0].addr  = i2c_client->addr;  /*i2c 器件地址 */
	msg[0].buf	 = val; 				  /* 地址和数据 */
	msg[0].len	 = 2;					  /* 地址+数据=2 byte */
	msg[0].flags = 0;					  /* 表示写 */
	
	ret = i2c_transfer(i2c_client->adapter, msg, 1);
	if (ret == 1)
	 return 2;
	else
	 return -EIO;

	
	return count;
}

static struct  file_operations i2c_fops={
       .owner   = THIS_MODULE,
	   .open    = i2c_at24c08_open,
 	   .read    = i2c_at24c08_read,
 	   .write   = i2c_at24c08_write,       
};

static struct miscdevice i2c_misc = {// 混杂设备
    .minor = MISC_DYNAMIC_MINOR,
    .name = MISC_DEVICE_NAME,
    .fops = &i2c_fops,
};

static int i2c_at24c08_probe(struct i2c_client *i2c_client, const struct i2c_device_id *i2c_device_id){
	printk("i2c_at24c08_probe , probe the device ..\n");
	misc_register(&i2c_misc);
	T_i2c_at24c08_data.i2c_client = i2c_client;
	T_i2c_at24c08_data.i2c_id = i2c_device_id;
	return 0;
}

static int i2c_at24c08_remove(struct i2c_client *i2c_client){
	misc_deregister(&i2c_misc);
	printk("i2c_at24c08_remove , remove the device ..\n");
	return 0;
}

struct i2c_device_id at24c08_id[] = {
	{I2C_DEVICE,0},{}  
};

MODULE_DEVICE_TABLE(i2c, at24c08_id);

static struct i2c_driver i2c_at24c08_driver = {
	.driver = {
		.name = I2C_DEVICE
	},
	.probe    = i2c_at24c08_probe,
	.remove    = i2c_at24c08_remove,
	.id_table    = at24c08_id,
};

static int __init i2c_at24c08_init(void){
	int result;
	result = i2c_add_driver(&i2c_at24c08_driver);
	
	if (result)
		printk("i2c_add_driver failed. Error number %d", result);
	return result;
}

static void __exit i2c_at24c08_exit(void){
	i2c_del_driver(&i2c_at24c08_driver);
}

module_init(i2c_at24c08_init);
module_exit(i2c_at24c08_exit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);

上面的程序主要的是调用了 

i2c_transfer(i2c_client->adapter, msg, 2);/*启动发送, 这个是重点, 待会主要分析这一块*/

这个函数进行发送,  所以我们主要是分析这个函数,  看看他具体做了什么

 i2c_transfer  --> adap->algo->master_xfer(adap, msgs, num);  // 这个函数实际上就是i2c-3c2410.c里面的适配器所指向的master_xfer也就是s3c24xx_i2c_xfer函数

二话不说, 我们进入s3c24xx_i2c_xfer()中, 在直接跳过简单的代码进入s3c24xx_i2c_doxfer()中

....

	i2c->msg     = msgs; //  把msg写入i2c结构体中, 这样就可以丢掉原来的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);// 等待事件
...
接下来分析s3c24xx_i2c_message_start


static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
				      struct i2c_msg *msg)
{
	unsigned int addr = (msg->addr & 0x7f) << 1; //  这里实际上是设置i2c的bus上的从器件地址,也就是我们的高7位应该是器件地址,最后一位就是读/写
	unsigned long stat;  //  这个是用来存放状态的
	unsigned long iiccon;

	stat = 0;
	stat |=  S3C2410_IICSTAT_TXRXEN;  //  使能i2c bus的发送接收位, 具体参考2440数据手册

	if (msg->flags & I2C_M_RD) {   //  如果是读的话 
		stat |= S3C2410_IICSTAT_MASTER_RX; // 设置状态为 读
		addr |= 1;  //  把addr的最低位置1, 表示读
	} else
		stat |= S3C2410_IICSTAT_MASTER_TX;// 设置状态为 写


	if (msg->flags & I2C_M_REV_DIR_ADDR) // 为0
		addr ^= 1;

	/* todo - check for wether ack wanted or not */
	s3c24xx_i2c_enable_ack(i2c); // 使能应答

	iiccon = readl(i2c->regs + S3C2410_IICCON); 
	writel(stat, i2c->regs + S3C2410_IICSTAT); // 把上面设置的stat写入iic_stat寄存器

	dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
	writeb(addr, i2c->regs + S3C2410_IICDS);  // 把addr这个从器件地址写入iicds

	/* delay here to ensure the data byte has gotten onto the bus
	 * before the transaction is started */

	ndelay(i2c->tx_setup);// 等待一会

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

	stat |= S3C2410_IICSTAT_START;             // 设置stat的 启动传输位, 这里的启动传输, 那么他就会启动发送器件地址,当器件响应的时候就会调用中断函数	
         writel(stat, i2c->regs + S3C2410_IICSTAT); // 把stat 写入iic_stat寄存器
/*
IIC中断发生在以下三种情况:当发出地址信息或接收到一个从机地址并吻合时,当总线总裁失败时,当发送/接收完一个字节的数据(包括响应位)时。当发出地址信息或接收到一个从机地址并吻合时产生中断,在中断处理函数中要准备发送或者接收数据,即读取或设备IICDS寄存器,或者发出P信号。当总线总裁失败时产生中断,在中断处理函数中决定时候延时后再次竞争总线等。当发送/接收完一个字节的数据(包括响应位)时产生中断,在中断处理函数中要准备下次要发送或者接收数据,即读取或设备IICDS寄存器,或者发出P信号。
*/
}


那么我们接下来就是查看我们的中断函数了:

static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
	...
	status = readl(i2c->regs + S3C2410_IICSTAT);// 还记得我们之前s3c24xx_i2c_doxfer设置的state么?i2c->state = STATE_START;故下面的if先不调用

	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;
	}
	i2s_s3c_irq_nextbyte(i2c, status);// 这个函数非常重要, 下面分析

 out:
	return IRQ_HANDLED;
}
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) {// 由于i2c->state == STATE_START;故到case STATE_START:

	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:
		......
		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);

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

应用层测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
 printf("%s r addr\n", file);
 printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
 int fd;
 unsigned char buf[2];

 if ((argc != 3) && (argc != 4))
 {
  print_usage(argv[0]);
  return -1;
 }
 

 fd = open("/dev/at24c08", O_RDWR);
 if (fd < 0)
 {
  printf("can't open /dev/at24c08\n");
  return -1;
 }

 if (strcmp(argv[1], "r") == 0)
 {
  buf[0] = strtoul(argv[2], NULL, 0);
  read(fd, buf, 1);
  printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
 }
 else if (strcmp(argv[1], "w") == 0)
 {
  buf[0] = strtoul(argv[2], NULL, 0);
  buf[1] = strtoul(argv[3], NULL, 0);
  write(fd, buf, 2);
 }
 else
 {
  print_usage(argv[0]);
  return -1;
 }
 
 return 0;
}

测试方法:

./xx  w  30 50    在30的地址写入字符50

./xx  r    30         读出30的地址




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值