首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

具体芯片的IIC控制器驱动程序分析:i2c-imx.c

  • 25-03-04 13:41
  • 4762
  • 8916
blog.csdn.net

#1024程序员节 | 征文#
在这里插入图片描述
1

往期内容

I2C子系统专栏:

  1. I2C(IIC)协议讲解-CSDN博客
  2. SMBus 协议详解-CSDN博客
  3. I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
  4. 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
  5. 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
  6. 设备驱动与设备树匹配机制详解
  7. 编写一个通用的i2c设备驱动框架
  8. 编写一个通用的i2c控制器驱动框架
  9. consumer 角度讲一下i2c外设-CSDN博客
  10. 使用GPIO模拟I2C的驱动程序分析:i2c-gpio.c-CSDN博客
    IIC专栏到这里就暂时完结啦,后续如果遇到新的知识内容还会继续更新,以上是本专栏往期的内容,可以看顺序观看

总线和设备树专栏:

  1. 总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客

前言

参考资料:

  • Linux内核真正的I2C控制器驱动程序

    • IMX6ULL: Linux-4.9.88\drivers\i2c\busses\i2c-imx.c?i2c-imx.c

芯片手册

  • IMXX6ULL:IMX6ULLRM.pdf

    • Chapter 31: I2C Controller (I2C)
    • I2C 资料 (yuque.com) — 手册存放位置

在之前开了章节讲解了关于如何编写一个通用的IIC控制器驱动程序框架:编写一个通用的i2c控制器驱动框架-CSDN博客,接下来再讲讲内核中提供的具体芯片的IIC控制器驱动程序:i2c-imx.c

1. I2C控制器内部结构

1.1 通用的简化结构

img

从时序可以看出,传输数据过程中所在的驱动程序会调用udelay,占用CPU资源,这段时间其它程序无法占用CPU资源进行运行,如果传输的数据太多,就会造成给人一种卡顿的现象,因此得I2C控制器中提供中断寄存器,这样可以在控制器读取或输出数据期间设置为休眠状态,将CPU资源让给其它程序如果数据传输完成,就可以发送中断唤醒休眠的驱动程序继续处理其他数据

1.2 I2C控制器内部结构体

img
一个 I2C(Inter-Integrated Circuit) 控制器的模块架构图,它包括了处理 I2C 通信的各个关键组件。

1.外围总线时钟域 (Peripheral Bus Clock Domain)

  • 这是图中上半部分,它处理与外围设备总线的接口,用来访问寄存器和传输数据。

2.I2C 频率分频寄存器 (I2C Frequency Divider Register - I2C_IFDR)

  • 用来设置 I2C 的时钟频率。通过对外围总线时钟进行分频,生成适合 I2C 总线的 SCL 时钟信号。

3.I2C 控制寄存器 (I2C Control Register - I2C_I2CR)

  • 控制整个 I2C 模块的行为。可以启动、停止 I2C 通信、启用中断、设置主/从模式等。

4.I2C 状态寄存器 (I2C Status Register - I2C_I2SR)

  • 该寄存器提供 I2C 模块的状态信息。例如,它可以显示传输是否完成、是否发生中断、总线是否空闲等。

5.I2C 数据 I/O 寄存器 (I2C Data I/O Register - I2C_I2DR)

  • 用于传输数据。当设备是主设备时,数据通过该寄存器写入或读取到总线。当设备是从设备时,用于接收主设备发送的数据。

6.I2C 地址寄存器 (I2C Address Register - I2C_IADR)

  • 用于存储 I2C 从设备的地址。主设备在通信中会将地址与从设备的地址寄存器进行比对,来决定是否与该从设备进行通信。

7.地址解码 (Address Decode)

  • 该模块负责解码主设备发来的地址,并判断 I2C 模块是否需要响应通信。

8.数据多路复用器 (Data MUX)

  • 负责在总线上进行数据的选择和传输。根据不同的地址选择合适的数据路径。

9.时钟控制 (Clock Control)

  • 该模块生成并控制 I2C 时钟信号(SCL),确保数据传输的同步。I2C 的时钟由此生成。

10.输入同步 (Input Sync)

  • 将外部引脚输入的信号同步到模块内部的时钟信号。这样可以消除不同时钟域之间的时钟不同步问题。

11.起始、停止和仲裁控制 (Start, Stop, and Arbitration Control)

  • 控制 I2C 的开始和停止条件,以及在多主设备情况下的仲裁(即解决多个主设备试图同时访问总线的冲突)。

12.输入/输出数据移位寄存器 (In/Out Data Shift Register)

  • 这是用于将发送或接收的数据按位移位处理的寄存器。它负责将数据逐位发送到 SDA(数据线)上,或逐位从 SDA 接收数据。

13.地址比较 (Address Compare)

  • 用来将 I2C 从设备的地址与主设备发送的地址进行比较,从而确定通信的目标从设备是否匹配。

14.I2Cn_SCL 和 I2Cn_SDA

  • 这两条线是 I2C 总线的物理信号线。SCL 是时钟信号线,SDA 是数据信号线。通过这两条线,I2C 设备可以进行通信。

15.模块时钟域 (Module Clock Domain)

  • 图中的下半部分是模块内部的时钟域,所有的控制和数据传输都依赖于这个域中的时钟信号。

展示了一个 I2C 控制器的架构,它通过多个寄存器来控制和管理 I2C 总线的通信,包含了时钟控制、数据传输、仲裁、地址匹配等功能。

数据传输的关键流程包括通过 SDA 和 SCL 引脚发送/接收数据,仲裁模块负责协调多个主设备的总线访问。

外设总线时钟域负责通过寄存器与系统总线进行通信,而模块时钟域则处理 I2C 的低级操作和信号传输。

这个架构允许 I2C 模块支持多种功能,例如主从模式、地址解码、数据传输、仲裁和中断等。

2. I2C控制器操作方法

img

  • 使能时钟、设置时钟

  • 发送数据:

    • 把数据写入tx_register,等待中断发生
    • 中断发生后,判断状态:是否发生错误、是否得到回应信号(ACK)
    • 把下一个数据写入tx_register,等待中断:如此循环
  • 接收数据:

    • 设置controller_register,进入接收模式,启动接收,等待中断发生
    • 中断发生后,判断状态,读取rx_register得到数据
    • 如此循环

3. 分析代码

3.1 设备树

  • IMX6ULL: arch/arm/boot/dts/imx6ull.dtsi
i2c1: i2c@021a0000 {
        #address-cells = <1>;
        #size-cells = <0>;
        compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
        reg = <0x021a0000 0x4000>;
        interrupts = ;
        clocks = <&clks IMX6UL_CLK_I2C1>;
        status = "disabled";   // 在100ask_imx6ull-14x14.dts把它改为了"okay"
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.2 驱动程序分析

probe

static int i2c_imx_probe(struct platform_device *pdev)
{
    //通过设备树匹配设备数据,以便为特定的硬件获取配置
	const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
							   &pdev->dev);
	struct imx_i2c_struct *i2c_imx;
	struct resource *res;
	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
	void __iomem *base;
	int irq, ret;
	dma_addr_t phy_addr;

	dev_dbg(&pdev->dev, "<%s>\n", __func__);

    //获取 I2C 设备的中断号
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "can't get irq number\n");
		return irq;
	}

    //通过平台设备资源分配函数 platform_get_resource 来获取 I2C 控制器寄存器的内存映射,
    //并使用 devm_ioremap_resource 对寄存器进行映射,以便后续访问。
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);

	phy_addr = (dma_addr_t)res->start;
    //动态分配 i2c_imx 结构体,该结构体用于保存 I2C 控制器的配置信息和状态数据。
	i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
	if (!i2c_imx)
		return -ENOMEM;

	if (of_id)
		i2c_imx->hwdata = of_id->data;
	else
		i2c_imx->hwdata = (struct imx_i2c_hwdata *)
				platform_get_device_id(pdev)->driver_data;

	/* Setup i2c_imx driver structure */
	strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
	i2c_imx->adapter.owner		= THIS_MODULE;
    //i2c_algorithm
	i2c_imx->adapter.algo		= &i2c_imx_algo;  ------- (1)
	i2c_imx->adapter.dev.parent	= &pdev->dev;
	i2c_imx->adapter.nr		= pdev->id;
	i2c_imx->adapter.dev.of_node	= pdev->dev.of_node;
	i2c_imx->base			= base;

	/* Get I2C clock */
    //获取与 I2C 控制器关联的时钟,并使用 clk_prepare_enable 启用它
	i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(i2c_imx->clk)) {
		dev_err(&pdev->dev, "can't get I2C clock\n");
		return PTR_ERR(i2c_imx->clk);
	}

	ret = clk_prepare_enable(i2c_imx->clk);
	if (ret) {
		dev_err(&pdev->dev, "can't enable I2C clock, ret=%d\n", ret);
		return ret;
	}

	/* Request IRQ */
    //请求中断,将 i2c_imx_isr 函数注册为中断服务例程处理函数。
	ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
			       IRQF_NO_SUSPEND, pdev->name, i2c_imx);
	if (ret) {
		dev_err(&pdev->dev, "can't claim irq %d\n", irq);
		goto clk_disable;
	}

	/* Init queue */
    //初始化等待队列,用于在读写操作中进行同步。
	init_waitqueue_head(&i2c_imx->queue);

	/* Set up adapter data */
	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

	/* Set up platform driver data */
	platform_set_drvdata(pdev, i2c_imx);

	pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);
	pm_runtime_use_autosuspend(&pdev->dev);
	pm_runtime_set_active(&pdev->dev);
	pm_runtime_enable(&pdev->dev);

	ret = pm_runtime_get_sync(&pdev->dev);
	if (ret < 0)
		goto rpm_disable;

	/* Set up clock divider */
    //设置 I2C 总线时钟频率,首先尝试从设备树读取属性 "clock-frequency",如果读取失败则使用默认值。
	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "clock-frequency", &i2c_imx->bitrate);
	if (ret < 0 && pdata && pdata->bitrate)
		i2c_imx->bitrate = pdata->bitrate;

	/* Set up chip registers to defaults */
    //设置 I2C 控制器的寄存器,使其恢复到默认状态。
	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
			i2c_imx, IMX_I2C_I2CR);
	imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);

	/* Init optional bus recovery function */
	ret = i2c_imx_init_recovery_info(i2c_imx, pdev);
	/* Give it another chance if pinctrl used is not ready yet */
	if (ret == -EPROBE_DEFER)
		goto rpm_disable;

	/* Add I2C adapter */
    //将 I2C 适配器添加到 I2C 树中,以便系统能够识别并与其进行 I2C 通信。
	ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
	if (ret < 0)
		goto rpm_disable;

	pm_runtime_mark_last_busy(&pdev->dev);
	pm_runtime_put_autosuspend(&pdev->dev);

	dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
	dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
	dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
		i2c_imx->adapter.name);
	dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");

	/* Init DMA config if supported */
    //为 I2C 控制器初始化 DMA 传输配置。
	i2c_imx_dma_request(i2c_imx, phy_addr);

	return 0;   /* Return OK */

rpm_disable:
	pm_runtime_put_noidle(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
	pm_runtime_set_suspended(&pdev->dev);
	pm_runtime_dont_use_autosuspend(&pdev->dev);

clk_disable:
	clk_disable_unprepare(i2c_imx->clk);
	return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143

(1)i2c_imx->adapter.algo = &i2c_imx_algo;其中赋值了i2c_algorithm:

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer	= i2c_imx_xfer,
	.functionality	= i2c_imx_func,
};
  • 1
  • 2
  • 3
  • 4

master_xfer 是控制器执行 I2C 传输的核心函数,里面有一个关键的参数就是 struct i2c_msg *msgs, 驱动程序会通过该函数与 I2C 设备通信,具体的讲解下文会单独讲;参三num是指该msgs数据的长度。

传输函数

读I2C数据时,要先发出设备地址,这是写操作,然后再发起读操作,涉及写、读操作。以写I2C数据为例讲解核心代码。

  • IMX6ULL:函数i2c_imx_xfer分析:
  • 在编写一个通用的i2c控制器驱动框架-CSDN博客中也有稍微提到过该函数,这里具体展开讲讲。
static int i2c_imx_xfer(struct i2c_adapter *adapter,
                        struct i2c_msg *msgs, int num)
{
    unsigned int i, temp;
    int result;
    bool is_lastmsg = false;          // 标志变量,表示当前消息是否是最后一个消息
    bool enable_runtime_pm = false;   // 用于标记是否启用了运行时电源管理
    struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);  // 获取适配器的私有数据结构

    // 输出调试信息,显示函数名
    dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);

    // 检查运行时电源管理是否已启用,如果没有则启用它
    if (!pm_runtime_enabled(i2c_imx->adapter.dev.parent)) {
        pm_runtime_enable(i2c_imx->adapter.dev.parent);
        enable_runtime_pm = true;  // 标记为启用了电源管理
    }

    // 增加运行时电源的引用计数,确保设备处于活动状态
    result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent);
    if (result < 0)
        goto out;  // 如果获取电源失败,则跳转到 out 标签处理

    // 开始I2C传输,调用底层驱动启动传输过程
    /* Start I2C transfer */
    result = i2c_imx_start(i2c_imx);
    if (result) {
        // 如果启动失败且存在总线恢复功能,则尝试恢复总线
        if (i2c_imx->adapter.bus_recovery_info) {
            i2c_recover_bus(&i2c_imx->adapter);
            result = i2c_imx_start(i2c_imx);  // 尝试重新启动传输
        }
    }

    if (result)  // 如果传输启动仍然失败,跳转到失败处理
        goto fail0;

    // 开始处理消息队列
    /* read/write data */
    for (i = 0; i < num; i++) {
        if (i == num - 1)
            is_lastmsg = true;  // 如果是最后一条消息,标记为true

        if (i) {
            // 如果不是第一条消息,则发送重复开始信号 (Repeated Start)
            dev_dbg(&i2c_imx->adapter.dev, "<%s> repeated start\n", __func__);
            temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);  // 读取I2C控制寄存器
            temp |= I2CR_RSTA;  // 设置重复开始位
            imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);  // 写回控制寄存器
            result = i2c_imx_bus_busy(i2c_imx, 1);  // 等待总线忙完成
            if (result)
                goto fail0;  // 如果总线忙碌超时,跳转到失败处理
        }
        
        // 输出调试信息,显示当前正在传输的消息编号
        dev_dbg(&i2c_imx->adapter.dev, "<%s> transfer message: %d\n", __func__, i);

#ifdef CONFIG_I2C_DEBUG_BUS
        // 如果启用了I2C调试,输出控制寄存器和状态寄存器的调试信息
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> CONTROL: IEN=%d, IIEN=%d, MSTA=%d, MTX=%d, TXAK=%d, RSTA=%d\n",
            __func__,
            (temp & I2CR_IEN ? 1 : 0), (temp & I2CR_IIEN ? 1 : 0),
            (temp & I2CR_MSTA ? 1 : 0), (temp & I2CR_MTX ? 1 : 0),
            (temp & I2CR_TXAK ? 1 : 0), (temp & I2CR_RSTA ? 1 : 0));
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> STATUS: ICF=%d, IAAS=%d, IBB=%d, IAL=%d, SRW=%d, IIF=%d, RXAK=%d\n",
            __func__,
            (temp & I2SR_ICF ? 1 : 0), (temp & I2SR_IAAS ? 1 : 0),
            (temp & I2SR_IBB ? 1 : 0), (temp & I2SR_IAL ? 1 : 0),
            (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),
            (temp & I2SR_RXAK ? 1 : 0));
#endif

        // 根据消息类型执行读或写操作
        if (msgs[i].flags & I2C_M_RD)  // 如果是读操作
            result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);  // 执行读操作
        else {  // 如果是写操作
            if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)  // 如果支持DMA且消息长度大于阈值
                result = i2c_imx_dma_write(i2c_imx, &msgs[i]);  // 使用DMA执行写操作
            else
                result = i2c_imx_write(i2c_imx, &msgs[i]);  // 否则使用常规方式写
        }

        if (result)  // 如果读写操作失败,跳转到失败处理
            goto fail0;
    }

fail0:
    // 停止I2C传输
    /* Stop I2C transfer */
    i2c_imx_stop(i2c_imx);

    // 更新运行时电源状态并减少引用计数
    pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);
    pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);

out:
    // 如果启用了运行时电源管理,则在结束时禁用它
    if (enable_runtime_pm)
        pm_runtime_disable(i2c_imx->adapter.dev.parent);

    // 输出调试信息,显示函数退出时的状态
    dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
            (result < 0) ? "error" : "success msg",
            (result < 0) ? result : num);
    
    // 返回最终的结果,成功则返回消息数量,失败则返回错误码
    return (result < 0) ? result : num;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

抽出重要的部分,以写为例子:

img

static int i2c_imx_xfer(struct i2c_adapter *adapter,
						struct i2c_msg *msgs, int num)
{
	unsigned int i, temp;
	int result;
	bool is_lastmsg = false;
	bool enable_runtime_pm = false;
	struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);

    // 开始I2C传输,调用底层驱动启动传输过程
    /* Start I2C transfer */
    result = i2c_imx_start(i2c_imx); -------(1)

    /* read/write data */
	for (i = 0; i < num; i++) {
        if (msgs[i].flags & I2C_M_RD)
			//....
		else {
			if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
				//.....
			else
				result = i2c_imx_write(i2c_imx, &msgs[i]); //开始写
                -------(2)
		}
    }
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
(1)

result = i2c_imx_start(i2c_imx)发出开始信号,来看看发的是什么:

static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
{
	unsigned int temp = 0;
	int result;

	dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);

    //调用 i2c_imx_set_clk 来配置 I2C 的时钟分频寄存器,以设置合适的 I2C 时钟频率,确保总线上的设备可以正确通信。
	i2c_imx_set_clk(i2c_imx);

    //i2c_imx->ifdr 保存了计算出的时钟分频值,这里通过 imx_i2c_write_reg 将其写入到 I2C 的时钟分频寄存器(IMX_I2C_IFDR),用于控制 I2C 总线时钟。
	imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
	/* Enable I2C controller */
    //清除 I2C 状态寄存器(IMX_I2C_I2SR)中的状态位,这里使用了 i2sr_clr_opcode,清除硬件特定的状态位。
	imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
	//将 i2cr_ien_opcode 写入到 I2C 控制寄存器(IMX_I2C_I2CR)中,启用 I2C 控制器。
    imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR);

	/* Wait controller to be stable */
    //控制器启用后,短暂延迟 50 到 150 微秒,确保硬件状态稳定。
	usleep_range(50, 150);

	/* Start I2C transaction */
    //读取 I2C 控制寄存器(IMX_I2C_I2CR)的当前值,将其存储在 temp 变量中。
	temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
    //设置主模式启动位 (I2CR_MSTA),表示该控制器将作为 I2C 总线的主设备并开始传输。
	temp |= I2CR_MSTA;
    //将修改后的值写回 I2C 控制寄存器,以开始 I2C 通信
	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);

    //调用 i2c_imx_bus_busy 检查 I2C 总线是否已经被占用。如果返回非 0,表示总线忙,函数会返回错误代码并退出。
	result = i2c_imx_bus_busy(i2c_imx, 1);
	if (result)
		return result;
    //将 i2c_imx->stopped 标志设为 0,表示 I2C 控制器尚未停止传输,表明 I2C 事务已经启动。
	i2c_imx->stopped = 0;

    //配置控制寄存器以启动传输
	temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK; //使能中断、设置为传输模式、发送 NACK(No Acknowledge)信号。
	temp &= ~I2CR_DMAEN; //清除 I2CR_DMAEN,禁用 DMA 模式(如果之前启用了)
	imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);//开始实际的数据传输
	return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

temp |= I2CR_MSTA;这是比较重要的,其值是0x20,为什么是设置成这个值,看芯片手册中关于iic控制器的章节,该值会写入I2C Control Register(上文介绍内部结构的时候有提到过),其芯片手册是这样定义的:img

(2)

result = i2c_imx_write(i2c_imx, &msgs[i]); //开始写。接下来看看传输函数中你的开始写函数:

static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
{
	int i, result;

	dev_dbg(&i2c_imx->adapter.dev, "<%s> write slave address: addr=0x%x\n",
		__func__, msgs->addr << 1);

	/* write slave address */
    //address and direction
	imx_i2c_write_reg(msgs->addr << 1, i2c_imx, IMX_I2C_I2DR);
	result = i2c_imx_trx_complete(i2c_imx);
	if (result)
		return result;
    //设备的回应信号
	result = i2c_imx_acked(i2c_imx);
	if (result)
		return result;
	dev_dbg(&i2c_imx->adapter.dev, "<%s> write data\n", __func__);

	/* write data */
	for (i = 0; i < msgs->len; i++) {
        //开始逐字节写入数据,数据存储在 msgs->buf 中,msgs->len 表示要发送的数据长度。
		dev_dbg(&i2c_imx->adapter.dev,
			"<%s> write byte: B%d=0x%X\n",
			__func__, i, msgs->buf[i]);
        
        //发送字节数据:data
		imx_i2c_write_reg(msgs->buf[i], i2c_imx, IMX_I2C_I2DR);
		result = i2c_imx_trx_complete(i2c_imx);
		if (result)
			return result;
        设备的回应信号:ack
		result = i2c_imx_acked(i2c_imx); 
		if (result)
			return result;
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

嗯???start、slave address、【】、ack、data、P结束有了,是不是还缺点啥,没错,【】缺了个方向direction,这个在哪里体现???

在imx_i2c_write_reg(msgs->addr << 1, i2c_imx, IMX_I2C_I2DR);就有体现了

为什么要左移一位,因为 将从设备地址左移一位,留下 LSB 位用来表示读/写方向:

  • 如果你要 写入数据,则 W/R 位应该是 0,所以不会显式地修改它,直接发送地址即可。
  • 如果你要 读取数据,你需要将 W/R 位设置为 1。

我个人觉得改成这样比较合理:

// 如果这是一个读操作,将地址最低位设为1
if (msgs->flags & I2C_M_RD)
    imx_i2c_write_reg((msgs->addr << 1) | 1, i2c_imx, IMX_I2C_I2DR);  // 读操作
else
    imx_i2c_write_reg(msgs->addr << 1, i2c_imx, IMX_I2C_I2DR);  // 写操作
  • 1
  • 2
  • 3
  • 4
  • 5

是不是了然一点,写还是读一下子就看得出来了。

img


下面是关于读方面的:感兴趣的可以看看:

img

注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/143170658"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top