RK3568驱动指南|第十五篇 I2C-第175章 i2c_transfer函数解析

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十五篇 I2C_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第175章 i2c_transfer函数解析

在上个章节中编写了I2C通信中的读函数和写函数,两个函数都是由i2c_transfer函数实现的,在本章节将对i2c_transfer函数进行详细的讲解。

i2c_transfer函数定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret; // 保存传输结果

	/*
	 * 这里存在一些故障报告模型的弱点:
	 * 1. 当我们在从设备接收N个字节后出错时,没有办法报告"N"。
	 * 2. 当我们在向从设备传输N个字节后收到NAK时,没有办法报告"N",
	 *    也没有办法让主设备继续执行剩余的消息(如果这是适当的响应)。
	 * 3. 当"num"为两个,我们成功完成第一条消息但在第二条消息中途出错时,
	 *    不清楚应该报告为一个(丢弃第二条消息的状态)还是errno(丢弃第一条消息的状态)。
	 */

	// 如果适配器支持master_xfer操作,则使用该操作传输消息
	if (adap->algo->master_xfer) {
#ifdef DEBUG
		// 打印每条消息的信息,用于调试
		for (ret = 0; ret < num; ret++) {
			dev_dbg(&adap->dev,
				"master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",
				ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',
				msgs[ret].addr, msgs[ret].len,
				(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
		}
#endif

		// 如果当前上下文不允许睡眠,则尝试锁定I2C总线
		// 如果总线已经被锁定,返回-EAGAIN
		if (in_atomic() || irqs_disabled()) {
			ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);
			if (!ret)
				return -EAGAIN;
		} else {
			// 否则,锁定I2C总线
			i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
		}

		// 调用__i2c_transfer执行实际的消息传输
		ret = __i2c_transfer(adap, msgs, num);

		// 解锁I2C总线
		i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);

		return ret;
	} else {
		// 如果适配器不支持master_xfer操作,返回不支持的错误
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}
}

第15行:检查有没有实现 master_xfer函数,i2c_transfer函数自身并没有控制硬件的能力,实际上master_xfer才是真正驱动硬件工作的函数,从而实现I2C通信,master_xfer定义在i2c_adapter结构体的i2c_algorithm结构体中,具体内容如下所示:

struct i2c_algorithm {
    /* 如果 I2C 适配器算法无法执行 I2C 级别的访问,则应将 master_xfer 设置为 NULL。
       如果 I2C 适配器算法可以执行 SMBus 访问,则应设置 smbus_xfer。
       如果设置为 NULL,则将使用通用的 I2C 消息来模拟 SMBus 协议 */
    /* master_xfer 函数应该返回成功处理的消息数量,或者在出现错误时返回一个负值 */
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
                       int num);

    /* 用于执行 SMBus 传输操作的函数指针
       adap: I2C 适配器
       addr: 从设备地址
       flags: 传输标志
       read_write: 读/写标志
       command: SMBus 命令码
       size: 传输数据的大小
       data: 用于存储传输数据的联合体 */
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
                       unsigned short flags, char read_write,
                       u8 command, int size, union i2c_smbus_data *data);

    /* 用于确定适配器支持的功能的函数指针 */
    u32 (*functionality) (struct i2c_adapter *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
    /* 用于注册和注销从设备的函数指针 */
    int (*reg_slave)(struct i2c_client *client);
    int (*unreg_slave)(struct i2c_client *client);
#endif
};

第6行和第17行的两个函数都是I2C设备驱动层控制硬件的函数,由原厂工程师进行编写,rk3568的实现函数定义在“drivers/i2c/busses/i2c-rk3x.c”文件中,一般情况下只需使用i2c_transfer函数间接调用即可。

然后继续分析i2c_transfer函数,第29-36行这段代码的目的是在保证系统的稳定性的前提下,通过适当的锁定机制来管理对 I2C 总线的访问,防止多个操作之间的竞争条件。

in_atomic() 和 irqs_disabled() 是内核中的两个函数,用于检查当前是否处于原子上下文或者中断是否被禁用。在这两种情况下,内核通常不允许执行可能导致上下文切换的操作。

如果当前处于原子上下文或者中断被禁用,代码使用 i2c_trylock_bus() 函数来尝试获取 I2C 总线的锁定。I2C_LOCK_SEGMENT 是用于指定锁定标志的。如果 i2c_trylock_bus() 返回失败(返回值为 false),表示 I2C 总线上正在进行活动,此时函数返回错误码 -EAGAIN,表示暂时无法获取锁。

如果不处于原子上下文或者中断被禁用,代码直接调用 i2c_lock_bus() 函数来获取 I2C 总线的锁,而不进行条件检查。这是因为在这种情况下,系统允许执行可能导致上下文切换的操作。

最后来看第39行的__i2c_transfer函数,该函数的具体内容如下所示:

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    unsigned long orig_jiffies;  // 记录初始的 jiffies 值
    int ret, try;                // 返回值和重试次数

    // 如果 msgs 为空或 num 小于 1,返回无效参数错误
    if (WARN_ON(!msgs || num < 1))
        return -EINVAL;

    // 如果适配器有特殊需求,检查是否支持当前的 I2C 消息
    if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
        return -EOPNOTSUPP;

    /*
     * 如果启用了 i2c_trace_msg_key 这个分支点(用于跟踪 I2C 传输消息),
     * 则遍历所有消息,分别记录读操作和写操作的跟踪信息
     */
    if (static_branch_unlikely(&i2c_trace_msg_key)) {
        int i;
        for (i = 0; i < num; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_read(adap, &msgs[i], i);
            else
                trace_i2c_write(adap, &msgs[i], i);
    }

    // 自动重试仲裁丢失错误
    orig_jiffies = jiffies;
    for (ret = 0, try = 0; try <= adap->retries; try++) {
        // 调用适配器的 master_xfer 函数完成 I2C 传输
        ret = adap->algo->master_xfer(adap, msgs, num);
        if (ret != -EAGAIN)  // 如果不是仲裁丢失错误,退出循环
            break;
        if (time_after(jiffies, orig_jiffies + adap->timeout))  // 如果超时,退出循环
            break;
    }

    // 如果启用了 i2c_trace_msg_key 这个分支点,记录 I2C 传输的结果
    if (static_branch_unlikely(&i2c_trace_msg_key)) {
        int i;
        for (i = 0; i < ret; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_reply(adap, &msgs[i], i);
        trace_i2c_result(adap, num, ret);
    }

    return ret;
}

第6-12行:函数参数检查,检查传入的 msgs 指针是否为空,或 num 参数是否小于 1。如果有问题,返回 -EINVAL 错误。检查 I2C 适配器是否有特殊需求,如果不支持当前的 I2C 消息,返回 -EOPNOTSUPP 错误。

第18-25行,跟踪 I2C 传输消息,如果内核启用了 i2c_trace_msg_key 这个分支点,遍历所有 I2C 消息,记录读操作和写操作的跟踪信息。

第28-36行,自动重试仲裁丢失错误,记录当前的时间戳 orig_jiffies,并循环最多 adap->retries 次重试。在每次重试时,调用适配器的 master_xfer 函数完成 I2C 传输。如果返回值不是 -EAGAIN(表示仲裁丢失错误),或者已经超时,则退出循环。

第39-45行,记录 I2C 传输结果,如果内核启用了 i2c_trace_msg_key 这个分支点,记录 I2C 传输的结果。

至此,关于i2c_transfer函数就分析完成了,现在来进行总结,i2c_transfer对于硬件控制的能力实际上是通过master_xfer函数实现的,而master_xfer函数是由原厂工程师编写的,一般情况下我们只需要通过调用i2c_transfer间接调用master_xfer从而实现硬件控制即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值