一、Linux I2C驱动整体架构
Linux I2C驱动有三层架构:
- 核心层(i2c-core) :内核提供的通用框架,负责管理
I2C适配器(Adapter)和I2C设备(Client),提供注册、匹配、传输等通用接口。 - 适配器驱动(Adapter Driver):对应硬件的I2C控制器(如RK3399的I2C控制器),实现控制器的硬件初始化、数据收发、中断处理等,向上对接核心层。
- 设备驱动(Client Driver):对应挂在I2C总线上的外设(如温湿度传感器、EEPROM),向下通过核心层调用适配器驱动的接口完成数据交互。
RK3399的I2C驱动核心代码位于内核源码的 drivers/i2c/busses/i2c-rockchip.c(不同内核版本路径可能略有差异),以下围绕该文件结合I2C驱动核心知识拆解。
二、RK3399 I2C适配器驱动核心实现
1. 驱动注册:平台驱动框架
RK3399的I2C控制器属于平台设备(Platform Device) (由设备树描述硬件信息),因此驱动通过platform_driver框架注册,核心结构体和注册逻辑如下:
c
// 1. 定义设备树匹配表(匹配RK3399的I2C控制器节点)
static const struct of_device_id rockchip_i2c_of_match[] = {
{ .compatible = "rockchip,rk3399-i2c", }, // 匹配设备树中compatible属性
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, rockchip_i2c_of_match);
// 2. 定义平台驱动结构体
static struct platform_driver rockchip_i2c_driver = {
.probe = rockchip_i2c_probe, // 设备匹配成功后执行的初始化函数
.remove = rockchip_i2c_remove, // 驱动卸载时执行的清理函数
.driver = {
.name = "rockchip-i2c",
.of_match_table = rockchip_i2c_of_match, // 设备树匹配表
.pm = &rockchip_i2c_pm_ops, // 电源管理(可选)
},
};
// 3. 驱动入口/出口函数
module_platform_driver(rockchip_i2c_driver);
MODULE_LICENSE("GPL");
关键说明:
- 设备树匹配是RK3399(ARM64架构)驱动的核心方式,设备树中会定义I2C控制器的基地址、中断号、时钟等信息,驱动通过
compatible属性匹配对应的硬件节点。 platform_driver是Linux针对片上外设的标准驱动框架,probe函数是适配器初始化的核心入口。
2. Probe函数:I2C适配器初始化
rockchip_i2c_probe是驱动的核心初始化函数,完成硬件资源申请、控制器初始化、I2C适配器注册,核心逻辑拆解:
c
static int rockchip_i2c_probe(struct platform_device *pdev)
{
struct rockchip_i2c *i2c;
struct i2c_adapter *adap;
struct resource *res;
int ret;
// 1. 分配私有数据结构体(rockchip_i2c是RK3399 I2C的私有结构体)
i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c) return -ENOMEM;
// 2. 申请IO内存(I2C控制器的寄存器基地址)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c->base)) return PTR_ERR(i2c->base);
// 3. 申请中断(I2C控制器的中断号)
i2c->irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, i2c->irq, rockchip_i2c_irq_handler,
IRQF_SHARED, dev_name(&pdev->dev), i2c);
if (ret) return ret;
// 4. 初始化硬件(时钟、控制器寄存器)
i2c->clk = devm_clk_get(&pdev->dev, "i2c_clk"); // 获取I2C时钟
clk_prepare_enable(i2c->clk); // 使能时钟
rockchip_i2c_hw_init(i2c); // 初始化I2C控制器寄存器(如速率、模式)
// 5. 初始化I2C适配器结构体(对接i2c-core)
adap = &i2c->adap;
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // 适配器分类
adap->algo = &rockchip_i2c_algo; // 核心:I2C传输算法(数据收发逻辑)
adap->dev.parent = &pdev->dev;
strscpy(adap->name, "rockchip-i2c", sizeof(adap->name));
i2c_set_adapdata(adap, i2c); // 绑定私有数据
// 6. 注册适配器到I2C核心层
ret = i2c_add_adapter(adap);
if (ret) goto err_clk_disable;
platform_set_drvdata(pdev, i2c); // 保存私有数据
return 0;
err_clk_disable:
clk_disable_unprepare(i2c->clk);
return ret;
}
核心知识点:
rockchip_i2c:RK3399 I2C的私有结构体,包含控制器基地址、中断号、时钟、I2C适配器结构体、传输状态等,是驱动中贯穿硬件操作的核心载体。i2c_adapter:Linux I2C核心层定义的适配器结构体,驱动需填充该结构体并通过i2c_add_adapter注册到核心层,完成"硬件控制器"到"内核I2C总线"的映射。- 硬件初始化(
rockchip_i2c_hw_init):会配置I2C控制器的工作模式(主模式)、通信速率(如100KHz/400KHz)、ACK使能、清空FIFO等寄存器。
3. 核心:I2C传输算法(master_xfer)
i2c_adapter的algo成员(rockchip_i2c_algo)是适配器的核心,实现master_xfer(I2C主模式数据传输),这是I2C核心层调用的核心接口,RK3399的实现逻辑如下(简化版):
c
// I2C算法结构体(对接i2c-core)
static const struct i2c_algorithm rockchip_i2c_algo = {
.master_xfer = rockchip_i2c_xfer, // 主模式数据传输函数
.functionality = rockchip_i2c_func, // 适配器功能(如支持的速率、模式)
};
// 核心传输函数:处理I2C消息收发
static int rockchip_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct rockchip_i2c *i2c = i2c_get_adapdata(adap);
int ret;
// 1. 加锁(防止多线程并发访问控制器)
mutex_lock(&i2c->lock);
// 2. 初始化传输状态(清空待传输消息、长度、状态)
i2c->msgs = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->status = 0;
// 3. 触发硬件传输(启动I2C控制器,发送起始位、设备地址)
rockchip_i2c_start_xfer(i2c);
// 4. 等待传输完成(中断驱动方式:等待中断处理完成)
ret = wait_event_timeout(i2c->wait, (i2c->status != 0), HZ);
if (ret == 0) {
ret = -ETIMEDOUT; // 超时
} else {
ret = (i2c->status == 1) ? num : -EIO; // 传输成功返回消息数,失败返回错误
}
// 5. 解锁并返回
mutex_unlock(&i2c->lock);
return ret;
}
关键说明:
i2c_msg:Linux I2C核心层定义的消息结构体,包含外设地址、读写方向、数据长度、数据缓冲区等,是I2C数据传输的基本单元。- RK3399的I2C传输采用中断驱动 :
rockchip_i2c_start_xfer启动硬件传输后,驱动通过wait_event_timeout等待中断触发,中断处理函数(rockchip_i2c_irq_handler)完成实际的数据收发、ACK检测、停止位发送等。
4. 中断处理:rockchip_i2c_irq_handler
中断是I2C传输的核心,RK3399的I2C控制器会在"地址发送完成、数据收发完成、ACK错误、传输完成"等场景触发中断,中断处理函数逻辑如下(简化版):
c
static irqreturn_t rockchip_i2c_irq_handler(int irq, void *dev_id)
{
struct rockchip_i2c *i2c = dev_id;
u32 stat, ctrl;
// 1. 读取中断状态寄存器
stat = readl(i2c->base + ROCKCHIP_I2C_INT_STATUS);
if (!stat) return IRQ_NONE;
// 2. 处理不同中断类型
if (stat & ROCKCHIP_I2C_INT_NACK) { // NACK错误
i2c->status = -EIO;
rockchip_i2c_stop_xfer(i2c); // 发送停止位
} else if (stat & ROCKCHIP_I2C_INT_COMPLETE) { // 单条消息传输完成
i2c->msg_ptr++;
if (i2c->msg_ptr >= i2c->msgs[i2c->msg_idx].len) { // 当前消息传输完
i2c->msg_ptr = 0;
i2c->msg_idx++;
if (i2c->msg_idx >= i2c->msg_num) { // 所有消息传输完
i2c->status = 1; // 标记传输成功
rockchip_i2c_stop_xfer(i2c);
} else {
rockchip_i2c_start_xfer(i2c); // 继续传输下一条消息
}
} else {
rockchip_i2c_send_byte(i2c); // 发送下一个字节(写)/读取下一个字节(读)
}
}
// 3. 清除中断状态
writel(stat, i2c->base + ROCKCHIP_I2C_INT_CLEAR);
// 4. 唤醒等待队列(通知xfer函数传输完成)
wake_up(&i2c->wait);
return IRQ_HANDLED;
}
核心逻辑:
- 中断处理函数负责逐字节收发数据、检测错误(如NACK)、切换消息、最终触发"传输完成"的状态更新,并唤醒
rockchip_i2c_xfer中的等待队列。 - 硬件寄存器操作(
readl/writel)是直接操作RK3399 I2C控制器的物理寄存器,需严格匹配芯片手册的寄存器定义。
三、RK3399 I2C设备驱动(Client)示例
适配器驱动完成后,挂在I2C总线上的外设(如AHT20温湿度传感器)需要编写Client驱动,核心是通过i2c_driver注册,并调用i2c_transfer(核心层接口)完成数据交互:
c
// 1. 设备匹配表(匹配I2C外设地址/名字)
static const struct i2c_device_id aht20_id[] = {
{ "aht20", 0 },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(i2c, aht20_id);
// 2. I2C设备驱动结构体
static struct i2c_driver aht20_driver = {
.driver = {
.name = "aht20",
.owner = THIS_MODULE,
},
.probe = aht20_probe,
.remove = aht20_remove,
.id_table = aht20_id,
};
// 3. Probe函数:外设初始化+数据读取
static int aht20_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct i2c_msg msgs[2];
u8 tx_buf[3] = {0xAC, 0x33, 0x00}; // AHT20触发测量指令
u8 rx_buf[6];
// 构造I2C消息:先写指令,再读数据
msgs[0].addr = client->addr; // 外设地址(如0x38)
msgs[0].flags = 0; // 写操作
msgs[0].len = 3;
msgs[0].buf = tx_buf;
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; // 读操作
msgs[1].len = 6;
msgs[1].buf = rx_buf;
// 调用核心层接口,最终触发适配器的master_xfer
if (i2c_transfer(client->adapter, msgs, 2) != 2) {
return -EIO;
}
// 解析rx_buf中的温湿度数据(略)
return 0;
}
module_i2c_driver(aht20_driver);
关键说明:
i2c_client:由I2C核心层根据设备树/板级信息创建,包含外设地址、对应的适配器指针等。i2c_transfer:核心层接口,会遍历适配器的master_xfer函数完成实际硬件传输,是Client驱动和Adapter驱动的桥梁。
四、核心总结
- RK3399的I2C驱动分为适配器驱动(控制器) 和设备驱动(外设),适配器驱动是硬件层实现,设备驱动是业务层实现,通过I2C核心层解耦。
- 适配器驱动基于
platform_driver,通过设备树匹配硬件,核心是实现i2c_algorithm的master_xfer(中断驱动的数收发)。 - 中断是RK3399 I2C高效传输的核心,负责逐字节处理、错误检测、传输状态更新。
- Client驱动通过
i2c_driver注册,调用i2c_transfer对接核心层,无需关心底层硬件细节,实现跨平台复用。