i2c子系统之write()

本文详细剖析了I2C通信机制,从应用层调用write()函数开始,深入介绍了i2cdev_fops中的write方法及其实现流程。涵盖了设备打开、ioctl设置地址、数据写入等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

应用层调用write()函数后首先进入的是i2c类设备的write函数,即i2cdev_fops中的write方法。

此处的i2cdev_fops对应的是系统中所有i2c类设备的操作。也就是说系统中所有i2c adapter 的read()

write() open() close() ioctl()等操作,首先调用的是i2c类i2cdev_fops中的方法,通过i2c类中的方法

再去寻找adapter 对应的算法i2c_algorithm,此处s3c2440对应的为s3c24xx_i2c_algorithm。

对i2c的操作方法

1.首先open

2.ioctl设置at24c02的地址

3.write()


1.open设备/dev/i2c-0

open通过系统调用最后调用到fops的i2cdev_open函数。

static int i2cdev_open(struct inode *inode, struct file *file)
{
	。。。 。。。
	adap = i2c_get_adapter(i2c_dev->adap->nr);
	if (!adap)
		return -ENODEV;

	。。。 。。。
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
	client->driver = &i2cdev_driver;

	client->adapter = adap;
	file->private_data = client;

	return 0;
}

可以发现此函数的作用是根据/dev/i2c-0的设备号找到对应的adapter,然后将其保存到新建的client中。

需要注意的是,此处的client与驱动中的client不同,这里的client并不会注册到总线上,和i2c驱动模型的代码无关。

此处的client只是用来保存client地址信息等。

最后将这个clietn保存到file->private_data中,供ioctl() write() open()等操作使用。

2. ioctl

应用层调用ioctl后会调用到i2cdev_ioctl()函数,此处使用的是I2C_SLAVE_FORCE,用于设置at24c02的地址。

3.write

write通过系统调用最后执行fops这中的i2cdev_write函数

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

可以发现,在write函数中首先做的就是将在open操作中保存到file->private_data中的client取出

然后通过memdup_user函数将用户空间的缓冲区拷贝到内核空间。最后调用函数i2c_master_send()

int i2c_master_send(struct i2c_client *client, const char *buf, int count)
{
	。。。 。。。	
	ret = i2c_transfer(adap, &msg, 1);
        。。。 。。。
}

在i2c_mastr_send函数中首先初始化msg结构体,将client的地址、当前需要拷贝的数据长度等信息

填充到msg中。最后将此msg作为形参传递给i2c_transfer函数。i2c的读写过程中,发送的信息都是

通过msg来完成的,除了device address之外。device address信息单独发送,其余的通过msg.buf来完成

并且可以发现,此处i2c_transfer中的第三个参数为1,这个参数是告诉驱动每次发送的msg个数,这里设置为1

表示每次只能发送一则msg。

i2c_transfer()函数如下:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
                 。。。 。。。
               orig_jiffies = jiffies;
               for (ret = 0, try = 0; try <= adap->retries; try++) {
			ret = adap->algo->master_xfer(adap, msgs, num);
			if (ret != -EAGAIN)
				break;
			if (time_after(jiffies, orig_jiffies + adap->timeout))
				break;
		}
		 。。。 。。。
}
在此函数做完相关处理后直接调用adapter的algorithm来发送数据,此处即i2c-s3c2440文件中

s3c24xx_i2c_probe总注册的算法

i2c->adap.algo    = &s3c24xx_i2c_algorithm;

s3c24xx_i2c_algorithm算法具体如下:

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};

因此相当于直接调用了函数s3c24xx_i2c_xfer

s3c24xx_i2c_xfer只是对s3c24xx_i2c_doxfer的简单封装,实际的处理

都在函数s3c24xx_i2c_doxfer中。下面重点分析这个s3c24xx_i2c_doxfer函数

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
			      struct i2c_msg *msgs, int num)
{
	。。。 。。。
	ret = s3c24xx_i2c_set_master(i2c);
	。。。 。。。
        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);
        。。。 。。。

}

在此函数中首先调用的是s3c24xx_i2c_set_master函数,查询master(即adapter)是否

处于忙的状态。忙则休眠1ms后再次查询,总共查询400次,相当于在400ms之后i2c还处于忙状态则放弃。

master空闲后,做些相关初始化的操作。初始化操作中需要注意的是i2c->state = STATE_START,通过这个状态

位来标记i2c当前是起始状态、写状态还是读状态。

接着通关函数

	s3c24xx_i2c_enable_irq(i2c);
打开中断。然后调用函数

	s3c24xx_i2c_message_start(i2c, msgs);

来发送第一个字节,即device address。当第一个字节发送完毕后,s3c2440的i2c控制器会产生中断。

s3c2440的i2c中断发生在1.完成1字节的发送或者接收2.广播呼叫或者从地址匹配时3.总线仲裁失败。

并且当第一个字节device address发送完毕后,函数通过

        timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
将当前进程进入等待状态,等待处理器将msg.buf发送完毕(即i2c-msg_num==0表示发送完毕),假如在HZ*5(5秒钟)内没有没有将msg发送完毕,则发送超时。

中断处理函数在s3c24xx_i2c_probe函数注册,为s3c24xx_i2c_irq()。

s3c24xx_i2c_irq的操作很简单,最重要的一步就是调用函数i2s_s3c_irq_nextbyte。在

i2s_s3c_irq_nextbyte函数中

static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
{
	。。。 。。。
	switch (i2c->state) {

	。。。 。。。

	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) {
			dev_dbg(i2c->dev, "last msg sended\n");
			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;

	 。。。 。。。
}

可以发现此处的函数先进入STATE_START,然后将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);
写到s3c2440的IICDS寄存器发送,接着又会产生中断,接着就又执行i2s_s3c_irq_nextbyte->i2s_s3c_irq_nextbyte

直到msg.buf中所有的数据全部发送出去。

当所有数据发送完毕后,在i2s_s3c_irq_nextbyte函数中执行s3c24xx_i2c_stop()函数

static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret)
{
	unsigned long iicstat = readl(i2c->regs + S3C2410_IICSTAT);

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

	/* stop the transfer */
	iicstat &= ~S3C2410_IICSTAT_START;
	writel(iicstat, i2c->regs + S3C2410_IICSTAT);

	i2c->state = STATE_STOP;

	s3c24xx_i2c_master_complete(i2c, ret);
	s3c24xx_i2c_disable_irq(i2c);
}

在此函数中执行一些扫尾工作,并最后调用函数s3c24xx_i2c_master_complete()

static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)
{
	dev_dbg(i2c->dev, "master_complete %d\n", ret);

	i2c->msg_ptr = 0;
	i2c->msg = NULL;
	i2c->msg_idx++;
	i2c->msg_num = 0;
	if (ret)
		i2c->msg_idx = ret;

	wake_up(&i2c->wait);
}

然后通过wake_up函数来唤醒等待在i2c队列上的其他操作。

到此数据写完毕。读过程类似。

下一篇附上at24c02读写测试代码




### I2C 子系统的数据传输机制 #### 数据传输原理 I2C 总线采用串行通信方式,在主设备和从设备间通过两条信号线完成数据交换:一条用于发送时钟脉冲(SCL),另一条负责传送数据(SDA)[^1]。当进行数据传输时,由主控端发起操作并向目标器件发出特定地址码;一旦确认应答,则可按照既定方向(读取或写入)传递字节流。 #### 主要特点 - **同步特性**:每次数据位的变化都发生在SCL上升沿时刻,确保接收方能稳定采样到正确的电平状态。 - **起始条件与停止条件**:为了区分不同的事务处理过程以及防止误触发,规定了特殊的高低变化序列来表示开始(Start Condition) 和结束 (Stop Condition) 的标志[^2]。 #### 实现细节 对于一次完整的数据帧而言,通常遵循如下格式: 1. 起始条件启动; 2. 发送7-bit 或者 10-bit 地址加上读/写指示位(R/W#) 组成的8 bits 字符; 3. 接收来自被选中的从机回应ACK/NACK; 4. 若为写入则继续输送后续的数据包直到所有预期的信息都被成功递交完毕; 5. 如需读回信息,则等待对方反馈相应数量的有效载荷之后再给出终止命令。 ```c // C语言伪代码展示简单的I2C写入函数实现 void i2c_write(uint8_t address, uint8_t* data, size_t length){ // Generate start condition generate_start_condition(); // Send slave address with write bit set and check ACK send_byte(address << 1); if (!check_ack()) { // Handle error... } // Write all bytes of data for(size_t i = 0; i < length; ++i){ send_byte(data[i]); if(!check_ack()){ // Handle error... } } // Finish transaction by generating stop condition generate_stop_condition(); } ``` 在实际应用环境中,可能还会涉及到更复杂的交互流程比如重复起始(repeated start), 多次读写组合等情形下的编程接口设计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值