Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux I2C 子系統 Platform 驅動模型深度解析:以 S3C2410 控制器為例

編輯:Linux 教學

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

關鍵流程詳解

1. Platform 驅動註冊入口


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()。

2. Probe 階段:從 Platform Device 到 I2C Adapter


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;
}

3. I2C Algorithm (算法操作集) 實現

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,       // 返回支援的功能標誌位
};
  • master_xfer:接收 struct i2c_msg 數組,實現 Start → Addr+R/W → Ack → Data... → Stop 完整協議邏輯,處理重試、超時、仲裁失敗。
  • functionality:返回 I2C_FUNC_I2C | I2C_FUNC_SMBUS_READ_BYTE | ... 告知核心支援哪些操作。

4. 平台數據與設備樹綁定

傳統平台數據 (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。

Remove 階段:資源釋放逆序


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 控制器驅動。

Copyright © Windows教程網 All Rights Reserved