1. 程式人生 > >Linux驅動開發08:【裝置樹】MPU6050驅動和i2c驅動

Linux驅動開發08:【裝置樹】MPU6050驅動和i2c驅動

介紹

上一節在nanopi裝置樹的I2C節點下增加了一個MPU6050的子節點,並在sysfs中檢視到了該節點已經被正確解析,這一節我們來修改之前的MPU6050驅動,使之能夠匹配到我們的裝置樹節點,然後再分析裝置樹節點是如何載入到i2c總線上的。

MPU6050驅動的變更

在之前的MPU6050驅動中,為了方便測試,我們是在模組的init函式中臨時註冊了一個i2c_client到i2c總線上,該i2c_client的資訊由i2c_board_info指定,包括i2c_client的名稱和從機地址。然後我們再註冊了i2c_driver到i2c總線上,系統通過比較i2c_driver.id_table和i2c_client.name判斷裝置和驅動是否匹配,如果匹配那麼執行probe函式,整個過程如下所示:

static struct i2c_client *temp_client;
static int __init mpu6050_i2c_init(void)
{
    struct i2c_adapter *adapter;

    adapter = i2c_get_adapter(I2C_0);
    if (!adapter)
        printk(KERN_INFO "fail to get i2c-%d\n", I2C_0);

    // 臨時註冊一個i2c_client
    temp_client = i2c_new_device(adapter, &mpu6050_i2c_info);
    if
(!temp_client) printk(KERN_INFO "fail to registe %s\n", mpu6050_i2c_info.type); pr_info(KERN_INFO "mpu6050 i2c init\n"); return i2c_add_driver(&mpu6050_i2c_driver); } static void __exit mpu6050_i2c_exit(void) { i2c_unregister_device(temp_client); i2c_del_driver(&mpu6050_i2c_driver); }

而現在由於我們已經在裝置樹中添加了MPU6050子節點,系統已經正確解析,並且我們在/sys/bus/i2c/device目錄下能夠看到該節點的資訊,這說明這個子節點已經作為了一個i2c_client被載入到了i2c總線上,我們只需要修改驅動使之能夠匹配這個i2c_client即可。因此我先刪除之前的i2c_client註冊部分,只保留i2c驅動的註冊。

static int __init mpu6050_i2c_init(void)
{
    return i2c_add_driver(&mpu6050_i2c_driver);
}

static void __exit mpu6050_i2c_exit(void)
{
    i2c_del_driver(&mpu6050_i2c_driver);
}

然後在i2c_driver中加入對裝置樹節點的匹配

static const struct of_device_id mpu6050_of_match[] = {
    { .compatible = "inv,mpu6050", },
    {  }
};

static struct i2c_driver mpu6050_i2c_driver = {
    .driver = {
        .name = "mpu6050",
        .owner = THIS_MODULE,
        .of_match_table = mpu6050_of_match,
    },
    .probe = mpu6050_i2c_probe,
    .remove = mpu6050_i2c_remove,
    .id_table = mpu6050_i2c_ids,
};

之前的id_table可以不用管,因為根據i2c匯流排的i2c_device_match()函式,如果裝置樹匹配上就不會進行id_table的匹配。注意of_device_id中的compatible屬性,系統通過這個欄位來判斷是否和已經註冊的i2c_client.dev.of_node中的compatible欄位匹配,如果匹配則執行probe函式。

經過這兩項修改,該驅動已經可以支援裝置樹了,測試方法和結果跟之前一致,我們能夠正確地讀取到感測器的值,這裡就不截圖了。

我們可以看到使用了裝置樹後驅動明顯變簡單了,並且更有條理性,驅動中可以增加多個of_device_id來匹配多個裝置樹節點,從而支援多個裝置。而我們驅動的改動很小,這是因為作業系統幫我們做了大部分的事情。

到這裡我們至少要提出兩個問題:

  1. 這個MPU6050的i2c_client是何時加入到i2c匯流排的?
  2. 之前我們在i2c_board_info中明確指定了從機地址,這個裝置樹的從機地址是何時賦值到i2c_client中的?

i2c_client註冊的分析

首先說結論,裝置樹中i2c節點下的子節點是在i2c_adapter註冊的時候一同被註冊到i2c匯流排的,因為從驅動框架來看i2c_client是掛接到i2c_adapter上的,因此註冊i2c_adapter時將總線上的i2c_client新增到系統合情合理。註冊i2c_client時會找到子節點中的reg屬性並賦值到i2c_client.addr中。具體分析見以下程式碼樹(注意,對於不同的硬體平臺,目錄可能有差別)。

--- drivers --- i2c --- busses --- i2c-mv64xxx.c --- mv64xxx_i2c_probe(            --- drv_data = devm_kzalloc();
                     |                                 struct platform_device *pd)  |- drv_data->adapter.algo = &mv64xxx_i2c_algo
                     |                                                              |- drv_data->adapter.class = I2C_CLASS_DEPRECATED
                     |                                                              |- drv_data->adapter.nr = pd->id
                     |                                                              |* drv_data->adapter.dev.of_node = pd->dev.of_node
                     |                                                              |- i2c_add_numbered_adapter(&drv_data->adapter))
                     |
                     |- i2c_core.c --- i2c_add_numbered_adapter(   --- __i2c_add_numbered_adapter(adap)
                                    |    struct i2c_adapter *adap)
                                    |- __i2c_add_numbered_adapter( --- i2c_register_adapter(adap)
                                    |    struct i2c_adapter *adap)
                                    |- i2c_register_adapter(      --- *of_i2c_register_devices(adap)//新增裝置樹子節點上的裝置
                                    |    struct i2c_adapter *adap)
                                    |- of_i2c_register_devices(    --- struct device_node *bus
                                    |    struct i2c_adapter *adap)  |- struct i2c_client *client
                                    |                               |- bus = of_node_get(adap->dev.of_node)
                                    |                               |- for_each_available_child_of_node(bus, node)
                                    |                               |*     client = of_i2c_register_device(adap, node)
                                    |- of_i2c_register_device(     --- struct i2c_board_info info = {};
                                         struct i2c_adapter *adap,  |- addr_be = of_get_property(node, "reg", &len)//獲取reg屬性
                                         struct device_node *node)  |- addr = be32_to_cpup(addr_be)
                                                                    |- info.addr = addr
                                                                    |- info.of_node = of_node_get(node)
                                                                    |* i2c_new_device(adap, &info)

i2c-mv64xxx.c是針對nanopi的全志H3平臺的i2c_adapter的初始化,對於不同的平臺,初始化過程也不相同,所以需要找到所使用平臺的i2c-xxx.c檔案再分析。
該驅動是一個platform驅動,i2c_adapter是platform_device,它通過裝置樹新增到系統的platform總線上,註冊platform_driver時只要compatible匹配就會執行這裡的mv64xxx_i2c_probe函式。至於系統是如何將i2c_adapter註冊到platform匯流排的,我們下來再分析,這裡只看註冊i2c介面卡時是如何將子節點作為i2c_client新增到i2c總線上的。

以上加了星號的是重點,這個過程總結起來就是先將I2C介面卡的裝置樹節點放到i2c_adapter.dev.of_node中,然後註冊這個i2c_adapter,註冊時,遍歷了該節點下的子節點,對所有子節點執行of_i2c_register_device(adap, node),這個函式中,先對子節點進行解析,獲取其reg屬性的值,然後填充i2c_board_info結構體,最後用i2c_new_device將其註冊到i2c匯流排。

i2c_adapter註冊的分析

上面提到,這裡的i2c_adapter是一個platform_device,通過與platform_driver的匹配來執行probe函式,這個platform_driver定義如下:

static const struct of_device_id mv64xxx_i2c_of_match_table[] = {
    { .compatible = "allwinner,sun4i-a10-i2c", .data = &mv64xxx_i2c_regs_sun4i},
    { .compatible = "allwinner,sun6i-a31-i2c", .data = &mv64xxx_i2c_regs_sun4i},
    { .compatible = "marvell,mv64xxx-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
    { .compatible = "marvell,mv78230-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
    { .compatible = "marvell,mv78230-a0-i2c", .data = &mv64xxx_i2c_regs_mv64xxx},
    {}
};

static struct platform_driver mv64xxx_i2c_driver = {
    .probe  = mv64xxx_i2c_probe,
    .remove = mv64xxx_i2c_remove,
    .driver = {
        .name   = MV64XXX_I2C_CTLR_NAME,
        .pm     = mv64xxx_i2c_pm_ops,
        .of_match_table = mv64xxx_i2c_of_match_table,
    },
};

裝置樹中的i2c_adapter定義如下,這個節點將來會被系統作為一個platform_device註冊到系統

i2c0: [email protected]01c2ac00 {
    compatible = "allwinner,sun6i-a31-i2c";
    reg = <0x01c2ac00 0x400>;
    interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&ccu CLK_BUS_I2C0>;
    resets = <&ccu RST_BUS_I2C0>;
    pinctrl-names = "default";
    pinctrl-0 = <&i2c0_pins>;
    status = "disabled";
    #address-cells = <1>;
    #size-cells = <0>;
};

全志H3晶片有三個i2c控制器,這裡只列出其中的一個節點。可以看到該節點的compatible = "allwinner,sun6i-a31-i2c",再看platform_driver中也有”allwinner,sun6i-a31-i2c”這個compatible,因此它們可以匹配。

為什麼這個i2c_adapter會被系統識別為platform_device呢?因為這個i2c控制器節點是soc的子節點,而soccompatible = "simple_bus",這就是上一節提到過的:核心在解析裝置樹時遇到”simple-bus”時,會繼續解析這個節點的子節點,並將各個子節點註冊為一個platform_device放到platform_bus_type中。我們來看具體程式碼是如何寫的。

const struct of_device_id of_default_bus_match_table[] = {
    { .compatible = "simple-bus", },
    { .compatible = "simple-mfd", },
    { .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
    { .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
    {} /* Empty terminated list */
};

of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL) 

    --- drivers --- of --- platform.c --- of_platform_populate(                 --- struct device_node *child
                                    |    struct device_node *root,            |- root = root ? of_node_get(root) : 
                                    |    const struct of_device_id *matches,  |                of_find_node_by_path("/");
                                    |    const struct of_dev_auxdata *lookup, |- for_each_child_of_node(root, child) 
                                    |    struct device *parent)               |      of_platform_bus_create(child, matches,
                                    |                                         |              lookup, parent, true)
                                    |- of_platform_bus_create(               --- struct platform_device *dev
                                    |    struct device_node *bus,             |- void *platform_data = NULL
                                    |    const struct of_device_id *matches,  |- const char *bus_id = NULL
                                    |    const struct of_dev_auxdata *lookup, |- dev = of_platform_device_create_pdata(
                                    |    struct device *parent, bool strict)  |-             bus, bus_id, platform_data, parent)
                                    |                                         |  // 對該節點建立platform_device 
                                    |                                         |- if (!dev || !of_match_node(matches, bus))
                                    |                                         |      return 0;
                                    |                                         |- for_each_child_of_node(bus, child)
                                    |                                         |       of_platform_bus_create(child, matches, 
                                    |                                         |              lookup, &dev->dev, strict);
                                    |                                         |  // 如果該節點.compatible = "simple-bus"
                                    |                                         |  // 遍歷子節點,遞迴呼叫,繼續註冊子節點
                                    |- of_platform_device_create_pdata( --- struct platform_device *dev
                                    |    struct device_node *np,         |- dev = of_device_alloc(np, bus_id, parent)
                                    |    const char *bus_id,             |- dev->dev.bus = &platform_bus_type
                                    |    void *platform_data,            |- dev->dev.platform_data = platform_data
                                    |    struct device *parent)          |- of_device_add(dev) 
                                    |- of_device_alloc(           --- struct platform_device *dev
                                    |     struct device_node *np,  |- dev = platform_device_alloc("", -1)
                                    |     const char *bus_id,      |- dev->dev.of_node = of_node_get(np)
                                    |     struct device *parent)   |- dev->dev.parent = parent ? : &platform_bus
                                    |                              |- return dev
                                    |- of_device_add(                   --- device_add(&ofdev->dev)
                                         struct platform_device *ofdev)

系統通過呼叫of_platform_populate函式解析整個裝置樹,該函式在customize_machine中通過machine_desc->init_machine()間接呼叫。

static int __init customize_machine(void)
{
    /*
     * customizes platform devices, or adds new ones
     * On DT based machines, we fall back to populating the
     * machine from the device tree, if no callback is provided,
     * otherwise we would always need an init_machine callback.
     */
    if (machine_desc->init_machine)
        machine_desc->init_machine();

    return 0;
}
arch_initcall(customize_machine);

machine_desc是一個全域性變數,在setup_arch()函式中被賦值,init_machine()由平臺定義,其中會呼叫of_platform_populate函式將根節點下的裝置樹節點註冊為platform_devcie。值得注意的是of_platform_bus_create函式,它首先將節點自身註冊為platform_device,然後執行of_match_node(matches, bus)判斷該節點是否和matches匹配,這裡的matches就是of_default_bus_match_table,其中有一個欄位是.compatible = "simple-bus",就是說如果該節點的.compatible = "simple-bus",那麼就不會返回,接下來遍歷該節點下的子節點,遞迴呼叫of_platform_bus_create,將所有子節點都註冊為platform_device,這樣i2c-0的裝置樹節點就被註冊為了一個platform_device,後面的platform_driver的匹配就得以執行。

疑問

在sunxi的板級檔案中,我始終沒有找到對init_machine的賦值,這裡的machine_desc定義如下:

static const char * const sun8i_board_dt_compat[] = {
    "allwinner,sun8i-a23",
    "allwinner,sun8i-a33",
    "allwinner,sun8i-a83t",
    "allwinner,sun8i-h2-plus",
    "allwinner,sun8i-h3",
    "allwinner,sun8i-v3s",
    NULL,
};

DT_MACHINE_START(SUN8I_DT, "sun8i")
    .init_time  = sun6i_timer_init,
    .dt_compat  = sun8i_board_dt_compat,
MACHINE_END

其中沒有對init_machine屬性的賦值,如果這個屬性是空,那麼就不會執行of_platform_populate函式,也不會將裝置樹下的節點註冊為platform_device,但是實際上肯定是註冊了的,要不然無法使用cpu上所有的外設資源。這裡留下一個疑問,等學了核心除錯方法再在實際的除錯中解決該問題