Linux I2C 子系統採用標準的 Platform 驅動模型 管理控制器硬體。本文以三星 S3C2410/2440/2450 系列 SoC 內建 I2C 控制器驅動 (drivers/i2c/busses/i2c-s3c2410.c) 為例,深度剖析 i2c_adapter 註冊、platform_driver 掛載、i2c_algorithm 綁定、設備樹/平台數據匹配全流程,助你掌握 I2C 控制器驅動開發核心邏輯。
struct platform_device (設備) struct platform_driver (驅動)
.name = "s3c-i2c" ◄──── 匹配 ────► .driver.name = "s3c-i2c"
.id = 0/1/2 .id_table = s3c24xx_driver_ids
.dev.platform_data = &pdata .probe = s3c24xx_i2c_probe
.remove = s3c24xx_i2c_remove
.driver.pm = S3C24XX_DEV_PM_OPS
probe() 中:
struct s3c24xx_i2c *i2c = kzalloc(...)
i2c->adap.name = "s3c2410-i2c"
i2c->adap.owner = THIS_MODULE
i2c->adap.algo = &s3c24xx_i2c_algorithm // 核心:I2C 總線操作算法
i2c->adap.algo_data = i2c // 算法私有數據指向控制器結構體
i2c->adap.dev.parent = &pdev->dev // 設備模型父子關係
i2c->adap.nr = pdata->bus_num // 總線編號 (0/1/2...)
i2c_add_numbered_adapter(&i2c->adap) // 向 I2C 核心註冊 adapter
static int __init i2c_adap_s3c_init(void) {
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init); // 內核子系統初始化階段調用
subsys_initcall 確保在設備模型就緒後、設備掃描前註冊驅動。platform_device,匹配 name 與 id_table,命中則調用 probe()。
static int s3c24xx_i2c_probe(struct platform_device *pdev) {
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = pdev->dev.platform_data;
// 1. 分配控制器私有結構體
i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
// 2. 映射寄存器物理地址
i2c->regs = ioremap(pdata->regs_start, SZ_4K);
// 3. 申請中斷
request_irq(pdev->irq, s3c24xx_i2c_irq, 0, "s3c24xx-i2c", i2c);
// 4. 時鐘使能
i2c->clk = clk_get(&pdev->dev, "i2c");
clk_enable(i2c->clk);
// 5. 初始化 i2c_adapter 核心結構體
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; // 綁定算法操作集
i2c->adap.algo_data = i2c; // 算法回調可訪問控制器上下文
i2c->adap.dev.parent = &pdev->dev; // sysfs 目錄層級
i2c->adap.nr = pdata->bus_num; // 固定總線編號
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // 自動探測類別
// 6. 註冊到 I2C 核心層
ret = i2c_add_numbered_adapter(&i2c->adap);
// 7. 保存私有指針供 remove 使用
platform_set_drvdata(pdev, i2c);
return ret;
}
struct i2c_algorithm 定義了總線層面的原子操作:
static struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer, // 核心:執行讀/寫事務 (多消息)
.smbus_xfer = s3c24xx_i2c_smbus_xfer, // SMBus 協議快捷命令 (可選)
.functionality = s3c24xx_i2c_func, // 返回支援的功能標誌位
};
struct i2c_msg 數組,實現 Start → Addr+R/W → Ack → Data... → Stop 完整協議邏輯,處理重試、超時、仲裁失敗。I2C_FUNC_I2C | I2C_FUNC_SMBUS_READ_BYTE | ... 告知核心支援哪些操作。傳統平台數據 (arch/arm/mach-s3c2410/):
static struct s3c2410_platform_i2c s3c2410_i2c0_pdata = {
.bus_num = 0,
.regs_start = S3C2410_IIC0_BASE,
.frequency = 100000, // 100kHz
.sda_pin = S3C2410_GPE(0),
.scl_pin = S3C2410_GPE(1),
.slave_addr = 0x50, // 控制器自身作為從設備時的地址 (罕用)
};
static struct platform_device s3c2410_i2c0 = {
.name = "s3c-i2c",
.id = 0,
.dev = { .platform_data = &s3c2410_i2c0_pdata },
.num_resources = 2,
.resource = s3c2410_i2c0_resource, // mem + irq
};
現代設備樹 (arch/arm/boot/dts/):
i2c0: i2c@13c00000 {
compatible = "samsung,s3c2410-i2c", "samsung,s3c2440-i2c";
reg = <0x13c00000 0x100>;
interrupts = <38>;
clocks = <&clk_i2c0>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
samsung,i2c-sda-delay = <100>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
};
驅動通過 of_match_table 匹配 compatible,probe 中用 of_property_read_u32 解析屬性,完全替代 platform_data。
static int s3c24xx_i2c_remove(struct platform_device *pdev) {
struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
i2c_del_adapter(&i2c->adap); // 1. 註銷 I2C adapter
clk_disable_unget(i2c->clk); // 2. 關閉時鐘
free_irq(pdev->irq, i2c); // 3. 釋放中斷
iounmap(i2c->regs); // 4. 取消映射寄存器
kfree(i2c); // 5. 釋放私有結構體
return 0;
}
| 現象 | 排查方向 | 關鍵命令/代碼 |
|---|---|---|
| probe 未被調用 | 名稱不匹配、id_table 缺失、設備未註冊 | grep s3c-i2c /proc/devices、ls /sys/bus/platform/devices/ |
| i2c_add_numbered_adapter 失敗 | 總線編號衝突、algorithm 為 NULL、algo_data 未設置 | 檢查 pdata->bus_num 唯一性、algo 賦值 |
| 通訊超時/NACK | 時鐘頻率過高、上拉電阻缺失、Pinmux 錯誤、從設備地址錯 | 示波器抓 SDA/SCL 波形、i2cdetect -y 0 掃描 |
| 休眠/喚醒異常 | PM ops 未實現、時鐘未關閉/使能 | 實現 .pm = S3C24XX_DEV_PM_OPS、clk_disable/enable |
S3C2410 I2C 驅動完美演示了 Linux Platform 驅動模型在 I2C 子系統中的應用:platform_driver_register → probe 分配資源/初始化 i2c_adapter → 綁定 i2c_algorithm → i2c_add_numbered_adapter 註冊到核心層。現代驅動開發應優先使用設備樹綁定,利用 devm_ 系列托管資源簡化 remove,並實現完整的 pm_runtime 支撐低功耗。掌握此模型,即可快速移植任意 SoC 的 I2C 控制器驅動。