往期内容
I2C子系统专栏:
- I2C(IIC)协议讲解-CSDN博客
- SMBus 协议详解-CSDN博客
- I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
- 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
- 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
- 设备驱动与设备树匹配机制详解
- 编写一个通用的i2c设备驱动框架
- 编写一个通用的i2c控制器驱动框架
- consumer 角度讲一下i2c外设-CSDN博客
- 使用GPIO模拟I2C的驱动程序分析:i2c-gpio.c-CSDN博客
IIC专栏到这里就暂时完结啦,后续如果遇到新的知识内容还会继续更新,以上是本专栏往期的内容,可以看顺序观看总线和设备树专栏:
前言
参考资料:
-
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 通用的简化结构
从时序可以看出,传输数据过程中所在的驱动程序会调用udelay,占用CPU资源,这段时间其它程序无法占用CPU资源进行运行,如果传输的数据太多,就会造成给人一种卡顿的现象,因此得I2C控制器中提供中断寄存器,这样可以在控制器读取或输出数据期间设置为休眠状态,将CPU资源让给其它程序如果数据传输完成,就可以发送中断唤醒休眠的驱动程序继续处理其他数据
1.2 I2C控制器内部结构体
一个 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控制器操作方法
-
使能时钟、设置时钟
-
发送数据:
- 把数据写入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
抽出重要的部分,以写为例子:
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(上文介绍内部结构的时候有提到过),其芯片手册是这样定义的:
(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
是不是了然一点,写还是读一下子就看得出来了。
下面是关于读方面的:感兴趣的可以看看:
评论记录:
回复评论: