往期内容
I2C子系统专栏:
- I2C(IIC)协议讲解-CSDN博客
- SMBus 协议详解-CSDN博客
- I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
- 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
- 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇
总线和设备树专栏:
- 总线和设备树_憧憬一下的博客-CSDN博客
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
前言
本章需要用到的内核源码:
Linux内核真正的I2C控制器驱动程序
- IMX6ULL: Linux-4.9.88\drivers\i2c\busses\i2c-imx.c📎i2c-imx.c
1.分辨
1.1 I2C总线和平台总线区别
i2c总线-设备-驱动平台的注册,i2c总线是真实存在的,而bus总线-设备-驱动平台中的bus则是虚拟的
i2c总线-设备-驱动平台管理着多条总线上的i2c_Controller And Devices
i2c控制器也有自己的驱动(I2c_Adapter_drv)
- 可以自己通过注册万能的bus总线平台,让驱动程序platform_driver来和platform_device设备节点(有控制器的name、属性设置等)匹配,来代表着一个总线,创建i2c_adapter。创建平台只是为了让驱动和设备节点相匹配,使该总线上i2c控制器功能的实现
- 一个i2c_adapter就代表着一个总线,代表着一个i2c controller
- 核心函数是master_xfer函数,这个函数是将数据传给总线上的i2c设备
i2c_client上则记录着i2c设备自己被哪一个i2c Controller(i2c_adapter)管理着,也就是在哪一条总线上,以及设备地址,一般是有设备树节点转化而来
i2c_driver上则是为i2c设备注册驱动程序、生成设备节点、设备类等,为app提供打开设备的设备节点、提供对设备的操作函数,比如读取或写入。注意的是要先用i2c_add_driver函数将驱动添加进i2c总线平台中,并且读取和写入是要先经过i2c_controller的
- 如i2c_transfer函数,会将device_driver的数据(来自app)转发i2c_client中记录的i2c_adapter,也就是i2c控制器,之后i2c控制器对应的驱动程序又会将数据转发给在总线上的I2C设备(i2c_client源)中的存储芯片等
- i2c_driver和i2c_client之间的匹配则是有i2c总线平台来进行匹配的,匹配成功后才能实现数据传给i2c_controller,进而传给i2c device
- 当然,也可以自己注册bus总线平台,使用GPIO模拟I2C来让platform_device和platform_driver进行匹配,也就是i2c设备信息client和驱动程序进行匹配,只需要将GPIO引脚模拟I2C引脚输出I2C协议就行。这一种在后面也会出文章讲解。
1.2 I2C驱动程序的层次
2. I2C_Adapter驱动框架
2.1 核心的结构体
i2c_adapter
I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客该结构体在之前的文章也有介绍过其相关成员。
i2c_algorithm
\Linux-4.9.88\include\uapi\linux\i2c.h
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */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
};
这个结构体在之前的文章也有讲过,这里简单提一下:
-
master_xfer:这是最重要的函数,它实现了一般的I2C传输,用来传输一个或多个i2c_msg
-
smbus_xfer:实现SMBus传输,如果不提供这个函数,SMBus传输会使用master_xfer来模拟
-
functionality: 数通常会返回一个由功能标志位组成的位掩码,表示适配器支持哪些 I2C 或 SMBus 操作(如
I2C_FUNC_I2C
,I2C_FUNC_SMBUS_READ_BLOCK_DATA
等等)。 -
reg_slave:
-
- 注册一个 I2C 从设备,使得该设备可以响应 I2C 主设备发起的请求。有些I2C Adapter也可工作与Slave模式,用来实现或模拟一个I2C设备
-
unreg_slave:
-
- 注销一个 I2C 从设备,使其不再响应 I2C 主设备的请求。
2.2 I2C adapter的相关的API
I2C adapter定义好之后,要把它注册到kernel中去,相关的API如下:
\Linux-4.9.88\Linux-4.9.88\include\linux\i2c.h:
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
extern int i2c_add_adapter(struct i2c_adapter *);
extern void i2c_del_adapter(struct i2c_adapter *);
extern int i2c_add_numbered_adapter(struct i2c_adapter *);
extern struct i2c_adapter *i2c_get_adapter(int nr);
extern void i2c_put_adapter(struct i2c_adapter *adap);
extern unsigned int i2c_adapter_depth(struct i2c_adapter *adapter);
/* must call put_device() when done with returned i2c_adapter device */
extern struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node);/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{return adap->algo->functionality(adap);
}
/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{return (func & i2c_get_functionality(adap)) == func;
}
/* Return the adapter number for a specific adapter */
static inline int i2c_adapter_id(struct i2c_adapter *adap)
{return adap->nr;
}
(1)i2c_add_adapter和i2c_add_numbered_adapter是I2C adapter的注册接口,它们的区别是:
- 前者将一个
i2c_adapter
注册到 I2C 核心框架中,并自动为它分配一个nr
(adapter ID)。内核会为你选择一个合适的 ID,而不需要你手动指定。适用于不需要指定特定 ID 的情况,适合大多数情况下的控制器注册。 ID 自动分配。 - 后者允许你指定 i2c_adapter 的 nr 字段,也就是适配器的 ID。你可以手动设置 adap->nr,然后注册时内核将使用你指定的这个 ID。如果 nr 不可用或者冲突,则返回错误。适用于需要在系统中保留固定编号的 I2C 适配器,比如为了与用户空间或其他设备进行明确绑定。 ID 可以手动指定。
(2)i2c_del_adapter(struct i2c_adapter *adap):
- 用于从 I2C 核心中删除适配器。它会取消所有注册到该适配器的从设备,释放与之相关的资源。在驱动卸载或适配器不再需要时使用,确保适配器从系统中安全移除。
(3)i2c_get_functionality(struct i2c_adapter *adap) 和 i2c_check_functionality:
- 前者 获取指定适配器所支持的功能。返回值是一个 32 位的位掩码,用于表示该适配器支持的特性。这些特性可以包括:标准的 I2C 功能(如 7 位地址、10 位地址等)、SMBus 功能、高级协议特性(如
I2C_FUNC_I2C
、I2C_FUNC_SMBUS_*
)。 用于查询控制器支持的功能。 - 后者 检查指定控制器是否支持某个特定的功能。传入的 func 是功能的位掩码。该函数会将传入的 func 与适配器实际支持的功能进行比较,如果适配器支持所有的请求功能,则返回 1,否则返回 0。 用于检查控制器是否支持某一特定功能。
(4)i2c_adapter_id(struct i2c_adapter *adap):
- 返回给定的 i2c_adapter 的 nr 字段,也就是适配器的 ID。这个函数很简单,通常在需要识别适配器或者调试时使用
(5)i2c_get_adapter(int nr) 和 i2c_put_adapter(struct i2c_adapter *adap):
- 前者: 根据指定的
nr
(适配器的 ID)查找并返回对应的i2c_adapter
结构指针。这个函数会增加适配器所依赖模块的引用计数(内部通过调用try_module_get
)。因此在调用这个函数后,需要确保适配器不会被卸载。 增加引用计数,获取适配器。 - 后者:当使用完 i2c_adapter 后,必须调用这个函数以减少模块的引用计数。这样可以防止模块被错误卸载,同时释放相关资源。 增加引用计数,获取适配器。
(6)of_find_i2c_adapter_by_node(struct device_node *node):
- 通过设备树节点(device_node)查找与之关联的 I2C 适配器。该函数通常用于通过设备树动态探测并找到某个 I2C 适配器。在调用该函数后,你还需要通过 put_device 来正确处理设备的引用计数,以确保资源释放。
2.3 驱动程序框架
分配、设置、注册一个i2c_adpater结构体:
- i2c_adpater的核心是i2c_algorithm
- i2c_algorithm的核心是master_xfer函数
所涉及的函数
- 分配struct i2c_adpater *adap = kzalloc(sizeof(struct i2c_adpater), GFP_KERNEL);
- 设置
adap->owner = THIS_MODULE;
adap->algo = &stm32f7_i2c_algo;
- 注册:i2c_add_adapter/i2c_add_numbered_adapter
ret = i2c_add_adapter(adap); // 不管adap->nr原来是什么,都动态设置adap->nr
ret = i2c_add_numbered_adapter(adap); // 如果adap->nr == -1 则动态分配nr; 否则使用该nr
- 反注册
i2c_del_adapter(adap);
i2c_algorithm示例
大致步骤
了解了I2C adapter有关的数据结构和API之后,编写I2C控制器驱动就简单多了,主要步骤如下:
1)定义一个struct i2c_algorithm变量,并根据I2C controller的特性,实现其中的回调函数。
2)在DTS文件(一般都放到DTSI)中,定义I2C controller相关的DTS node
3)在drives/i2c/busses目录下,以i2c-xxx.c的命名方式,编写I2C controller的platform driver,并提供match id、probe、remove等接口。
4)在platform driver的probe接口中,分配一个adapter结构,并进行必要的初始化操作后,调用i2c_add_adapter或者i2c_add_numbered_adapter接口,将其注册到kernel中即可。
2.4 编写框架
这里使用的万能的总线平台:bus+dts 实现,platfrom_driver and platform_device
📎05_i2c_adapter_framework.zip – 里面有设备树
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c-algo-bit.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_data/i2c-gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>/* Global pointer to the virtual I2C adapter */
static struct i2c_adapter *g_adapter;/* * i2c_bus_virtual_master_xfer - I2C message transfer function* @i2c_adap: Pointer to I2C adapter* @msgs: Array of I2C messages* @num: Number of messages* * This function simulates the transfer of multiple I2C messages. * Here you would normally interact with the hardware.*/
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *i2c_adap,struct i2c_msg msgs[], int num)
{int i;/* Loop through each message and simulate transfer */for (i = 0; i < num; i++) {/* Normally, the actual hardware transfer would occur here */// simulate transfer of msgs[i];}/* Return the number of messages processed */return num;
}/* * i2c_bus_virtual_func - Supported functionality of the virtual I2C bus* @adap: Pointer to I2C adapter* * This function reports the capabilities of the virtual I2C bus. * It supports standard I2C transactions and SMBus emulation.*/
static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
{/* Return a combination of supported functionalities */return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |I2C_FUNC_SMBUS_READ_BLOCK_DATA |I2C_FUNC_SMBUS_BLOCK_PROC_CALL |I2C_FUNC_PROTOCOL_MANGLING;
}/* * i2c_bus_virtual_algo - Virtual I2C bus algorithm structure* * This structure defines the transfer and functionality operations for* the virtual I2C bus. It is used to handle I2C operations for the* associated adapter.*/
const struct i2c_algorithm i2c_bus_virtual_algo = {.master_xfer = i2c_bus_virtual_master_xfer,.functionality = i2c_bus_virtual_func,
};/* * i2c_bus_virtual_probe - Platform driver probe function* @pdev: Platform device structure* * This function is called when the driver is matched with a device.* It sets up and registers the I2C adapter.*/
static int i2c_bus_virtual_probe(struct platform_device *pdev)
{/* Allocate memory for the I2C adapter structure */g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);if (!g_adapter)return -ENOMEM;/* Initialize the I2C adapter structure */g_adapter->owner = THIS_MODULE;g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // Adapter supports hardware monitor and SPD classesg_adapter->nr = -1; // Dynamically assign adapter numbersnprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual"); // Set adapter name/* Assign the algorithm to the adapter */g_adapter->algo = &i2c_bus_virtual_algo;/* Register the I2C adapter with the I2C core */i2c_add_adapter(g_adapter); // Can also use i2c_add_numbered_adapter() for fixed numbersreturn 0;
}/* * i2c_bus_virtual_remove - Platform driver remove function* @pdev: Platform device structure* * This function is called when the driver is removed.* It unregisters the I2C adapter and cleans up.*/
static int i2c_bus_virtual_remove(struct platform_device *pdev)
{/* Unregister the I2C adapter */i2c_del_adapter(g_adapter);/* Free the allocated memory */kfree(g_adapter);return 0;
}/* * i2c_bus_virtual_dt_ids - Device tree compatible entries* * This array defines the compatible strings that match the virtual I2C bus* driver to the corresponding device in the device tree.*/
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {{ .compatible = "yyy,i2c-bus-virtual", },{ /* sentinel */ }
};/* * i2c_bus_virtual_driver - Platform driver structure* * This structure defines the probe, remove, and other operations for the* virtual I2C bus driver.*/
static struct platform_driver i2c_bus_virtual_driver = {.driver = {.name = "i2c-gpio", // Name of the driver.of_match_table = of_match_ptr(i2c_bus_virtual_dt_ids), // Match device tree compatible entries},.probe = i2c_bus_virtual_probe, // Probe function.remove = i2c_bus_virtual_remove, // Remove function
};/* * i2c_bus_virtual_init - Driver initialization function* * This function is called when the module is loaded. It registers* the platform driver with the Linux kernel.*/
static int __init i2c_bus_virtual_init(void)
{int ret;/* Register the platform driver */ret = platform_driver_register(&i2c_bus_virtual_driver);if (ret)printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);return ret;
}
module_init(i2c_bus_virtual_init);/* * i2c_bus_virtual_exit - Driver exit function* * This function is called when the module is unloaded. It unregisters* the platform driver from the kernel.*/
static void __exit i2c_bus_virtual_exit(void)
{/* Unregister the platform driver */platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_exit(i2c_bus_virtual_exit);MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");
设备树
在设备树里构造控制器的设备节点:
i2c-bus-virtual {compatible = "yyy,i2c-bus-virtual";//剩下的可以是挂载在该控制器上的i2c设备的设备树节点};
platform_driver
分配、设置、注册platform_driver结构体。
核心是probe函数,
它要做这几件事:
- 根据设备树信息设置硬件(引脚、时钟等)
- 分配、设置、注册i2c_apdater
这里注册platform只是为了将i2c_adapter_driver和控制器的设备节点能够匹配起来,实现创建i2c_adapter而已
master_xfer
i2c_apdater核心是master_xfer函数,它的实现取决于硬件,在内核提供控制器驱动文件i2c-m中:
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; // 如果是最后一条消息,标记为trueif (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执行写操作elseresult = 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;
}
CPU 调用 i2c_transfe
(i2c device driver) → 内部调用了通过 i2c_imx_xfer
(adap->algo->master_xfer) 与 I2C 控制器通信 → 如果是读操作,则调用i2c_adapter中的 i2c_imx_read
→ i2c_imx_read
从 I2C 控制器接收数据并填充到 msgs
的 buf
中 → CPU 读取 buf
中的数据。
在上面讲的i2c_imx_xfer
,是不是有开始信号、停止信号、data。这些都是iic传输协议的格式中有提到的,至于addr、w/r,这些就封装在了i2c_msg中,格式:I2C(IIC)协议讲解-CSDN博客
框架例子:
static int xxx_master_xfer(struct i2c_adapter *adapter,struct i2c_msg *msgs, int num)
{for (i = 0; i < num; i++) {struct i2c_msg *msg = msgs[i];{// 1. 发出S信号: 设置寄存器发出S信号CTLREG = S;// 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号// 判断是否有ACKif (!ACK)return ERROR;else {// 3. read / writeif (read) {STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方val = DATAREG; // 这会发起I2C读操作} else if(write) {DATAREG = val; // 这会发起I2C写操作val = STATUS; // 判断是否收到ACKif (!ACK)return ERROR;} }// 4. 发出P信号CTLREG = P;}}return i;
}