• Linux 内核:设备树(4)设备树中各个节点是谁转换的


    Linux 内核:设备树(4)设备树中各个节点是谁转换的

    背景

    之前,我们在《把device_node转换成platfrom_device》中提到在设备树的device_node到platform_device转换中,必须满足以下条件:

    • 一般情况下,只对设备树中根的一级子节点进行转换,也就是多级子节点(子节点的子节点)并不处理。但是存在一种特殊情况,就是当某个根子节点的compatible属性为"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"时,当前节点中的一级子节点将会被转换成platform_device节点。
    • 节点中必须有compatible属性。

    那么,设备树中的一级节点的处理已经完成了,但是现在,我们还有一些子节点的读取不太了解。实际上,没有被处理称为platform_device的设备树节点,都被各个platform_device作为子节点进行处理了。

    我们通过解析一棵比较经典的设备树,展示设备树中i2c子系统以及gpio-leds中是如何使用的。

    参考:https://www.cnblogs.com/downey-blog/p/10519317.html

    典型的设备树

    事实上,在设备树中,通常会存在将描述设备驱动的设备树节点被放置在多级子节点的情况:

    // arch/arm/boot/dts/zynq-7000.dts
    / {
        // 无效属性
        compatible = "xx"; 
        
        // /amba 节点, 会被转换为`platform_device`,因为它兼容"simple-bus",
        // 同时,在内核中有对应的platform_driver来完成probe
        amba {
            compatible = "simple-bus";
            #address-cells = <0x1>;
            #size-cells = <0x1>;
    
            /*  /amba/i2c@e0004000 也会被转换为platform_device
                因为 父节点 /amba 具备 simple-bus;
                而 自己 /amba/i2c@e0004000 也有 compatible 属性
            */
            i2c@e0004000 {
                compatible = "cdns,i2c-r1p10";
                status = "okay";
                // ...
                #address-cells = <0x1>;
                #size-cells = <0x0>;
            };
        };
    
        // ...
        // 类似的也有 /usb_phy0 节点, 它一般也是用来表示USB控制器, 它会被转换为platform_device, 
        // 同时,在内核中有对应的platform_driver 来完成probe
        usb_phy@0 {
            compatible = "ulpi-phy";
            #phy-cells = <0x0>;
            // ...
        };
        
        aliases {
            // i2c0 代表了 /amba/i2c@e0004000
            i2c0 = "/amba/i2c@e0004000";
        };
        soc: soc { };
    };
    #######################################################################
    // arch/arm/boot/dts/zynq-zc702.dts
    #include "zynq-7000.dtsi"
    / {
        model = "Zynq ZC702 Development Board";
        compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000";
    
        aliases {
            ethernet0 = &gem0;
            i2c0 = &i2c0;
            serial0 = &uart1;
            spi0 = &qspi;
            mmc0 = &sdhci0;
            usb0 = &usb0;
        };
    };
    
    &usb0 {
        status = "okay";
        dr_mode = "host";
        usb-phy = <&usb_phy0>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_usb0_default>;
    };
    
    &i2c0 {
        status = "okay";
        clock-frequency = <400000>;
        pinctrl-names = "default", "gpio";
        pinctrl-0 = <&pinctrl_i2c0_default>;
        pinctrl-1 = <&pinctrl_i2c0_gpio>;
        scl-gpios = <&gpio0 50 0>;
        sda-gpios = <&gpio0 51 0>;
    
        /* 
            /i2c@0/si570 节点不会被转换为platform_device, 
            si570被如何处理完全由父节点的platform_driver决定, 
            在这里,si570会被创建为 i2c_device。
        */
        si570: clock-generator@5d {
            #clock-cells = <0>;
            compatible = "silabs,si570";
            temperature-stability = <50>;
            reg = <0x5d>;
            factory-fout = <156250000>;
            clock-frequency = <148500000>;
        };
    };
    
    &soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges = <0 0 0 0xffffffff>;
        compatible = "simple-bus";
        
        mygpio-leds {
            compatible = "gpio-leds";
    
            led-blue{
                label = "red";
                default-state = "off";
                //gpios = <&msm_gpio 17 0x00>;
            };
            led-green{
                label = "green";
                default-state = "off";
                //gpios = <&msm_gpio 34 0x00>;
            };
        };
    };
    

    显然,i2c@e0004000会被转换成platform_device,而si570则不会,至少在设备树初始化阶段不会被转换,仍旧以device_node的形式存在在内存中。

    显而易见,这些设备并非是无意义的设备,接下来我们来看看,驱动中是如何处理的。

    /usb

    / {
        // ...
        // 类似的也有 /usb_phy0 节点, 它一般也是用来表示USB控制器, 它会被转换为platform_device, 
        // 同时,在内核中有对应的platform_driver 来完成probe
        usb_phy@0 {
            compatible = "ulpi-phy";
            #phy-cells = <0x0>;
            reg = <0xe0002000 0x1000>;
            view-port = <0x170>;
            drv-vbus;
            linux,phandle = <0x7>;
            phandle = <0x7>;
        };
    };
    

    由 内核解析的时候,将usb_phy@0转换为platform_device

    // drivers/usb/phy/phy-ulpi.c
    static const struct of_device_id ulpi_phy_table[] = {
        { .compatible = "ulpi-phy" },
        { },
    };
    MODULE_DEVICE_TABLE(of, ulpi_phy_table);
    
    static struct platform_driver ulpi_phy_driver = {
        .probe      = ulpi_phy_probe,
        .remove     = ulpi_phy_remove,
        .driver     = {
            .name   = "ulpi-phy",
            .of_match_table = ulpi_phy_table,
        },
    };
    module_platform_driver(ulpi_phy_driver);
    

    ulpi_phy_probe

    加载这个驱动(ulpi_phy_driver)以后,因为设备树中有上述的platform_device,所以会执行对应的probe。

    static int ulpi_phy_probe(struct platform_device *pdev)
    {
        struct device_node *np = pdev->dev.of_node;
        struct resource *res;
        struct ulpi_phy *uphy;
        bool flag;
        int ret;
    
        uphy = devm_kzalloc(&pdev->dev, sizeof(*uphy), GFP_KERNEL);
        if (!uphy)
            return -ENOMEM;
    
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        uphy->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
        if (IS_ERR(uphy->regs))
            return PTR_ERR(uphy->regs);
    
        ret = of_property_read_u32(np, "view-port", &uphy->vp_offset);
        if (IS_ERR(uphy->regs)) {
            dev_err(&pdev->dev, "view-port register not specified
    ");
            return PTR_ERR(uphy->regs);
        }
    
        flag = of_property_read_bool(np, "drv-vbus");
        if (flag)
            uphy->flags |= ULPI_OTG_DRVVBUS | ULPI_OTG_DRVVBUS_EXT;
    
        uphy->usb_phy = otg_ulpi_create(&ulpi_viewport_access_ops, uphy->flags);
    
        uphy->usb_phy->dev = &pdev->dev;
    
        uphy->usb_phy->io_priv = uphy->regs + uphy->vp_offset;
    
        ret = usb_add_phy_dev(uphy->usb_phy);
        if (ret < 0)
            return ret;
    
        return 0;
    }
    

    /amba

    还记得我们之前说的,因为 /amba节点的compatible属性符合"simple-bus"规则,所以,把/amba/中的所有子节点当作对应的总线来对待。

    即,将符合条件的子节点转换为platform_device结构。

    事实上,/amba/i2c@e0004000对应一个i2c硬件控制器,控制器的起始地址是0x44e04000,这个节点的作用就是生成一个i2c硬件控制器的platform_device,与同样被加载到内存中的platform_driver相匹配。此后,在内存中构建一个i2c硬件控制器的描述节点,负责对应i2c控制器的数据收发。

    / {
        // /amba 节点, 会被转换为`platform_device`,因为它兼容"simple-bus",
        // amba 作为 一个soc总线,同时,在内核中有对应的platform_driver;
        amba {
            compatible = "simple-bus";
            #address-cells = <0x1>;
            #size-cells = <0x1>;
            // ...
    
            // 子节点/amba/i2c@e0004000 也会被转换为platform_device
            i2c@e0004000 {
                compatible = "cdns,i2c-r1p10";
                // ...
            };
        };
    

    因此,i2c@e0004000会被注册成为platform_device,那么我们来看看对应的platform_driver

    /amba/i2c@e0004000

    基本上,只有原厂商以及比较老旧的平台需要自行开发总线控制器。但是我们作为一种学习了解也是不错的。

    根据platform driver驱动的规则,需要填充一个struct platform_driver结构体,然后注册到platform总线中,这样才能完成platfrom bus的匹配,因此,我们也可以在同文件下找到相应的初始化部分:

    // drivers/i2c/busses/i2c-cadence.c
    static const struct of_device_id cdns_i2c_of_match[] = {
        { .compatible = "cdns,i2c-r1p10", // ... },
        { .compatible = "cdns,i2c-r1p14",},
        { /* end of table */ }
    };
    MODULE_DEVICE_TABLE(of, cdns_i2c_of_match);
    
    static struct platform_driver cdns_i2c_drv = {
        .driver = {
            .name  = DRIVER_NAME,
            .of_match_table = cdns_i2c_of_match,
            .pm = &cdns_i2c_dev_pm_ops,
        },
        .probe  = cdns_i2c_probe,
        .remove = cdns_i2c_remove,
    };
    
    module_platform_driver(cdns_i2c_drv);
    

    cdns_i2c_probe

    既然platform总线的driver和device匹配上,就会调用相应的probe函数,根据.probe = cdns_i2c_probe,,我们再查看cdns_i2c_probe函数:

    /**
     * cdns_i2c_probe - Platform registration call
     * @pdev:   Handle to the platform device structure
     *
     * This function does all the memory allocation and registration for the i2c
     * device. User can modify the address mode to 10 bit address mode using the
     * ioctl call with option I2C_TENBIT.
     *
     * Return: 0 on success, negative error otherwise
     */
    static int cdns_i2c_probe(struct platform_device *pdev)
    {
        struct resource *r_mem;
        struct cdns_i2c *id;
        int ret;
        const struct of_device_id *match;
    
        id = devm_kzalloc(&pdev->dev, sizeof(*id), GFP_KERNEL);
        if (!id)
            return -ENOMEM;
    
        id->dev = &pdev->dev;
        platform_set_drvdata(pdev, id);
    
        match = of_match_node(cdns_i2c_of_match, pdev->dev.of_node);
        if (match && match->data) {
            const struct cdns_platform_data *data = match->data;
            id->quirks = data->quirks;
        }
    
        id->pinctrl = devm_pinctrl_get(&pdev->dev);
        if (!IS_ERR(id->pinctrl)) {
            ret = cdns_i2c_init_recovery_info(id, pdev);
            if (ret)
                return ret;
        }
    
        r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        id->membase = devm_ioremap_resource(&pdev->dev, r_mem);
        if (IS_ERR(id->membase))
            return PTR_ERR(id->membase);
    
        id->irq = platform_get_irq(pdev, 0);
    
        // 设置 adapter 一些属性
        id->adap.owner = THIS_MODULE;
        id->adap.dev.of_node = pdev->dev.of_node;
        id->adap.algo = &cdns_i2c_algo;
        id->adap.timeout = CDNS_I2C_TIMEOUT;
        id->adap.retries = 3;       /* Default retry value. */
        id->adap.algo_data = id;
        id->adap.dev.parent = &pdev->dev;
        init_completion(&id->xfer_done);
        snprintf(id->adap.name, sizeof(id->adap.name),
             "Cadence I2C at %08lx", (unsigned long)r_mem->start);
    
        id->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(id->clk)) {
            dev_err(&pdev->dev, "input clock not found.
    ");
            return PTR_ERR(id->clk);
        }
        ret = clk_prepare_enable(id->clk);
        if (ret)
            dev_err(&pdev->dev, "Unable to enable clock.
    ");
    
        pm_runtime_set_autosuspend_delay(id->dev, CNDS_I2C_PM_TIMEOUT);
        pm_runtime_use_autosuspend(id->dev);
        pm_runtime_set_active(id->dev);
        pm_runtime_enable(id->dev);
    
        id->clk_rate_change_nb.notifier_call = cdns_i2c_clk_notifier_cb;
        if (clk_notifier_register(id->clk, &id->clk_rate_change_nb))
            dev_warn(&pdev->dev, "Unable to register clock notifier.
    ");
        id->input_clk = clk_get_rate(id->clk);
    
        ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency",
                &id->i2c_clk);
        if (ret || (id->i2c_clk > CDNS_I2C_SPEED_MAX))
            id->i2c_clk = CDNS_I2C_SPEED_DEFAULT;
    
    #if IS_ENABLED(CONFIG_I2C_SLAVE)
        /* Set initial mode to master */
        id->dev_mode = CDNS_I2C_MODE_MASTER;
        id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
    #endif
        id->ctrl_reg = CDNS_I2C_CR_ACK_EN | CDNS_I2C_CR_NEA | CDNS_I2C_CR_MS;
    
        ret = cdns_i2c_setclk(id->input_clk, id);
        if (ret) {
            dev_err(&pdev->dev, "invalid SCL clock: %u Hz
    ", id->i2c_clk);
            ret = -EINVAL;
            goto err_clk_dis;
        }
    
        ret = devm_request_irq(&pdev->dev, id->irq, cdns_i2c_isr, 0,
                     DRIVER_NAME, id);
        if (ret) {
            dev_err(&pdev->dev, "cannot get irq %d
    ", id->irq);
            goto err_clk_dis;
        }
    
        cdns_i2c_init(id);
    
        // 添加成为 adapter
        ret = i2c_add_adapter(&id->adap);
        if (ret < 0)
            goto err_clk_dis;
    
        dev_info(&pdev->dev, "%u kHz mmio %08lx irq %d
    ",
             id->i2c_clk / 1000, (unsigned long)r_mem->start, id->irq);
    
        return 0;
    
    err_clk_dis:
        clk_disable_unprepare(id->clk);
        pm_runtime_disable(&pdev->dev);
        pm_runtime_set_suspended(&pdev->dev);
        return ret;
    }
    

    i2c_add_adapter

    // drivers/i2c/i2c-core-base.c
    /**
     * i2c_add_adapter - declare i2c adapter, use dynamic bus number
     * @adapter: the adapter to add
     * Context: can sleep
     *
     * This routine is used to declare an I2C adapter when its bus number
     * doesn't matter or when its bus number is specified by an dt alias.
     * Examples of bases when the bus number doesn't matter: I2C adapters
     * dynamically added by USB links or PCI plugin cards.
     *
     * When this returns zero, a new bus number was allocated and stored
     * in adap->nr, and the specified adapter became available for clients.
     * Otherwise, a negative errno value is returned.
     */
    int i2c_add_adapter(struct i2c_adapter *adapter)
    {
        struct device *dev = &adapter->dev;
        int id;
    
        if (dev->of_node) {
            // 获取 i2c 设备树节点
            id = of_alias_get_id(dev->of_node, "i2c");
            if (id >= 0) {
                adapter->nr = id;
                return __i2c_add_numbered_adapter(adapter);
            }
        }
    
        // ...
    }
    EXPORT_SYMBOL(i2c_add_adapter);
    
    of_alias_get_id
    // drivers/of/base.c
    /**
     * of_alias_get_id - Get alias id for the given device_node
     * @np:     Pointer to the given device_node
     * @stem:   Alias stem of the given device_node
     *
     * The function travels the lookup table to get alias id for the given
     * device_node and alias stem.  It returns the alias id if find it.
     */
    int of_alias_get_id(struct device_node *np, const char *stem)
    {
        struct alias_prop *app;
        int id = -ENODEV;
    
        mutex_lock(&of_aliases_mutex);
        list_for_each_entry(app, &aliases_lookup, link) {
            if (strcmp(app->stem, stem) != 0)
                continue;
    
            if (np == app->np) {
                id = app->id;
                break;
            }
        }
        mutex_unlock(&of_aliases_mutex);
    
        return id;
    }
    EXPORT_SYMBOL_GPL(of_alias_get_id);
    

    之前解析过的aliases节点都会加入到aliases_lookup中。

    因此,现在根据名字,获取到对应的i2c节点的id。

    alias_prop原型

    id实际上就是一个索引,有了id就可以找到这个项目。

    // drivers/of/of_private.h
    /**
     * struct alias_prop - Alias property in 'aliases' node
     * @link:   List node to link the structure in aliases_lookup list
     * @alias:  Alias property name
     * @np:     Pointer to device_node that the alias stands for
     * @id:     Index value from end of alias name
     * @stem:   Alias string without the index
     *
     * The structure represents one alias property of 'aliases' node as
     * an entry in aliases_lookup list.
     */
    struct alias_prop {
        struct list_head link;
        const char *alias;
        struct device_node *np;
        int id;
        char stem[0];
    };
    
    __i2c_add_numbered_adapter
    // drivers/i2c/i2c-core-base.c
    /**
     * __i2c_add_numbered_adapter - i2c_add_numbered_adapter where nr is never -1
     * @adap: the adapter to register (with adap->nr initialized)
     * Context: can sleep
     *
     * See i2c_add_numbered_adapter() for details.
     */
    static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
    {
        // ...
        return i2c_register_adapter(adap);
    }
    
    i2c_register_adapter

    根据这个名称可以看出这是根据设备树描述的硬件i2c控制器而生成的一个i2c_adapter,并注册到系统中,这个i2c_adapter负责i2c底层数据收发。

    // drivers/i2c/i2c-core-base.c
    static int i2c_register_adapter(struct i2c_adapter *adap)
    {
    	// ...
    	of_i2c_register_devices(adap);
    	// ...
    }
    

    注意到一个of前缀的函数,看到of就能想到这肯定是设备树相关的函数。

    of 代表 openFramework,设备树就是从of来的。

    of_i2c_register_devices

    为每一个i2c下的节点注册对应为对应的i2c device(即,i2c_client

    // drivers/i2c/i2c-core-of.c
    void of_i2c_register_devices(struct i2c_adapter *adap)
    {
    	// ...
        
        // 轮询每个子节点
    	for_each_available_child_of_node(bus, node) {
    		if (of_node_test_and_set_flag(node, OF_POPULATED))
    			continue;
    
    		client = of_i2c_register_device(adap, node);
    		if (IS_ERR(client)) {
    			dev_warn(&adap->dev,
    					"Failed to create I2C device for %pOF
    ",
    					node);
    			of_node_clear_flag(node, OF_POPULATED);
    		}
    	}
    	// ...
    }
    

    所以可以看出,of_i2c_register_device()这个函数的作用就是解析设备树中当前i2c中的子节点,并将其转换成相应的i2c_client描述结构。

    of_i2c_register_device
    // drivers/i2c/i2c-core-of.c
    static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,struct device_node *node)
    {
    	// ...
    	struct i2c_board_info info = {};
    	of_modalias_node(node, info.type, sizeof(info.type);
    	of_get_property(node, "reg", &len);
    	info.addr = addr;
    	info.of_node = of_node_get(node);
    	info.archdata = &dev_ad;
    
    	if (of_property_read_bool(node, "host-notify"))
    		info.flags |= I2C_CLIENT_HOST_NOTIFY;
    
    	if (of_get_property(node, "wakeup-source", NULL))
    		info.flags |= I2C_CLIENT_WAKE;
    
    	result = i2c_new_device(adap, &info);
    	// ...
    }
    
    struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
    {
    	// ...
    	struct i2c_client	*client;
    	client = kzalloc(sizeof *client, GFP_KERNEL);
    	client->adapter = adap;
    	client->dev.platform_data = info->platform_data;
    
    	if (info->archdata)
    		client->dev.archdata = *info->archdata;
    	client->flags = info->flags;
    	client->addr = info->addr;
    	client->irq = info->irq;
    	client->dev.parent = &client->adapter->dev;
    	client->dev.bus = &i2c_bus_type;
    	client->dev.type = &i2c_client_type;
    	client->dev.of_node = info->of_node;
    	client->dev.fwnode = info->fwnode;
    
    	if (info->properties) {
    		status = device_add_properties(&client->dev, info->properties);
    		if (status) {
    			dev_err(&adap->dev,
    				"Failed to add properties to client %s: %d
    ",
    				client->name, status);
    			goto out_err;
    		}
    	}
    	device_register(&client->dev);
    	return client;
    	// ...
    }
    

    确实如此,从device_node到i2c_client的转换主要是在这两个函数中了:

    • of_i2c_register_device()函数中,从device_node节点中获取各种属性的值记录在info结构体中
    • 然后将info传递给i2c_new_device(),生成一个对应的i2c_client结构并返回。

    /amba/i2c@e0004000/si570

    &i2c0 {
        status = "okay";
        clock-frequency = <400000>;
        // ...
    
        /* 
            /i2c@0/si570 节点不会被转换为platform_device, 
            si570被如何处理完全由父节点的platform_driver决定, 
            在这里,si570会被创建为 i2c_client(device)。
        */
        si570: clock-generator@5d {
            #clock-cells = <0>;
            compatible = "silabs,si570";
            temperature-stability = <50>;
            reg = <0x5d>;
            factory-fout = <156250000>;
            clock-frequency = <148500000>;
        };
    };
    

    根据刚刚的分析,父节点已经在probe的时候,就帮所有的子节点注册为i2c_client

    在i2c子系统中,device称为client

    我们看看对应驱动的写法;注意到,这里是 i2c_driver 类型的驱动:

    // drivers/clk/clk-si570.c
    static const struct of_device_id clk_si570_of_match[] = {
        { .compatible = "silabs,si570" },
        { .compatible = "silabs,si571" },
        { .compatible = "silabs,si598" },
        { .compatible = "silabs,si599" },
        { },
    };
    MODULE_DEVICE_TABLE(of, clk_si570_of_match);
    
    // 注意到,这里是 `i2c_driver` 类型的驱动
    static struct i2c_driver si570_driver = {
        .driver = {
            .name = "si570",
            .of_match_table = clk_si570_of_match,
        },
        .probe      = si570_probe,
        .remove     = si570_remove,
        .id_table   = si570_id,
    };
    module_i2c_driver(si570_driver);
    

    因此,就会在si570_driver中的probe匹配对应的devrice_driver,我们具体就不再细看了。

    /soc/mygpio-leds

    /soc同样具备compatible = "simple-bus";,就不再解释了。

    &soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges = <0 0 0 0xffffffff>;
        compatible = "simple-bus";
        
        // /soc/mygpio-leds 会称为 platform device
        mygpio-leds {
            compatible = "gpio-leds";
    
            led-blue{
                label = "red";
                default-state = "off";
                //gpios = <&msm_gpio 17 0x00>;
            };
            led-green{
                label = "green";
                default-state = "off";
                //gpios = <&msm_gpio 34 0x00>;
            };
        };
    };
    

    我们看看下面的/soc/mygpio-leds节点,以及下面的子节点/soc/mygpio-leds/{led-blue,led-green}

    // drievers/leds/leds-gpio.c
    static const struct of_device_id of_gpio_leds_match[] = {
        { .compatible = "gpio-leds", },
        {},
    };
    
    MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
    static struct platform_driver gpio_led_driver = {
        .probe      = gpio_led_probe,
        .shutdown   = gpio_led_shutdown,
        .driver     = {
            .name   = "leds-gpio",
            .of_match_table = of_gpio_leds_match,
        },
    };
    module_platform_driver(gpio_led_driver);
    

    gpio_led_probe

    实际上,类似刚刚提到的i2c,/soc/mygpio-leds下的子节点会被gpio_led_driver处理,实际上就是在gpio_led_probe中进行的。

    // drivers/leds/leds-gpio.c
    static int gpio_led_probe(struct platform_device *pdev)
    {
        struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
        struct gpio_leds_priv *priv;
        int i, ret = 0;
    
        if (pdata && pdata->num_leds) {
            priv = devm_kzalloc(&pdev->dev,
                    sizeof_gpio_leds_priv(pdata->num_leds),
                        GFP_KERNEL);
            if (!priv)
                return -ENOMEM;
    
            priv->num_leds = pdata->num_leds;
            for (i = 0; i < priv->num_leds; i++) {
                // 填充 为 gpio led 对应的属性
                ret = create_gpio_led(&pdata->leds[i], &priv->leds[i],
                              &pdev->dev, NULL,
                              pdata->gpio_blink_set);
                if (ret < 0)
                    return ret;
            }
        } else {
            // 寻找子节点,并解析,创建设备
            priv = gpio_leds_create(pdev);
            if (IS_ERR(priv))
                return PTR_ERR(priv);
        }
    
        platform_set_drvdata(pdev, priv);
    
        return 0;
    }
    

    创建led

    static int create_gpio_led(const struct gpio_led *template,
        struct gpio_led_data *led_dat, struct device *parent,
        struct device_node *np, gpio_blink_set_t blink_set)
    {
        int ret, state;
    
        led_dat->gpiod = template->gpiod;
        if (!led_dat->gpiod) {
            /*
             * This is the legacy code path for platform code that
             * still uses GPIO numbers. Ultimately we would like to get
             * rid of this block completely.
             */
            unsigned long flags = GPIOF_OUT_INIT_LOW;
    
            /* skip leds that aren't available */
            if (!gpio_is_valid(template->gpio)) {
                dev_info(parent, "Skipping unavailable LED gpio %d (%s)
    ",
                        template->gpio, template->name);
                return 0;
            }
    
            if (template->active_low)
                flags |= GPIOF_ACTIVE_LOW;
    
            ret = devm_gpio_request_one(parent, template->gpio, flags,
                            template->name);
            if (ret < 0)
                return ret;
    
            led_dat->gpiod = gpio_to_desc(template->gpio);
            if (!led_dat->gpiod)
                return -EINVAL;
        }
    
        led_dat->cdev.name = template->name;
        led_dat->cdev.default_trigger = template->default_trigger;
        led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
        if (!led_dat->can_sleep)
            led_dat->cdev.brightness_set = gpio_led_set;
        else
            led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
        led_dat->blinking = 0;
        if (blink_set) {
            led_dat->platform_gpio_blink_set = blink_set;
            led_dat->cdev.blink_set = gpio_blink_set;
        }
        if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
            state = gpiod_get_value_cansleep(led_dat->gpiod);
            if (state < 0)
                return state;
        } else {
            state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
        }
        led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
        if (!template->retain_state_suspended)
            led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
        if (template->panic_indicator)
            led_dat->cdev.flags |= LED_PANIC_INDICATOR;
        if (template->retain_state_shutdown)
            led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
    
        ret = gpiod_direction_output(led_dat->gpiod, state);
        if (ret < 0)
            return ret;
    
        return devm_of_led_classdev_register(parent, np, &led_dat->cdev);
    }
    

    根据子节点创建的

    static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
    {
        struct device *dev = &pdev->dev;
        struct fwnode_handle *child;
        struct gpio_leds_priv *priv;
        int count, ret;
    
        count = device_get_child_node_count(dev);
        if (!count)
            return ERR_PTR(-ENODEV);
    
        priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
        if (!priv)
            return ERR_PTR(-ENOMEM);
    
        device_for_each_child_node(dev, child) {
            struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
            struct gpio_led led = {};
            const char *state = NULL;
            struct device_node *np = to_of_node(child);
    
            ret = fwnode_property_read_string(child, "label", &led.name);
            if (ret && IS_ENABLED(CONFIG_OF) && np)
                led.name = np->name;
            if (!led.name) {
                fwnode_handle_put(child);
                return ERR_PTR(-EINVAL);
            }
    
            led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
                                     GPIOD_ASIS,
                                     led.name);
            if (IS_ERR(led.gpiod)) {
                fwnode_handle_put(child);
                return ERR_CAST(led.gpiod);
            }
    
            fwnode_property_read_string(child, "linux,default-trigger",
                            &led.default_trigger);
    
            if (!fwnode_property_read_string(child, "default-state",
                             &state)) {
                if (!strcmp(state, "keep"))
                    led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
                else if (!strcmp(state, "on"))
                    led.default_state = LEDS_GPIO_DEFSTATE_ON;
                else
                    led.default_state = LEDS_GPIO_DEFSTATE_OFF;
            }
    
            if (fwnode_property_present(child, "retain-state-suspended"))
                led.retain_state_suspended = 1;
            if (fwnode_property_present(child, "retain-state-shutdown"))
                led.retain_state_shutdown = 1;
            if (fwnode_property_present(child, "panic-indicator"))
                led.panic_indicator = 1;
    
            ret = create_gpio_led(&led, led_dat, dev, np, NULL);
            if (ret < 0) {
                fwnode_handle_put(child);
                return ERR_PTR(ret);
            }
            led_dat->cdev.dev->of_node = np;
            priv->num_leds++;
        }
    
        return priv;
    }
    

    就这样子,用上了。

    总结

    可以看到,i2c下面的子节点并没有被处理为platform_device

    为什么在内核初始化时只将一级子目录节点(compatible属性中含有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"的向下递归一级)转换成platform_device?

    对于bus而言,有不同的总线处理方式和不同的driverdevice的命名,自然不能将所有节点全部转换成platform_device

    在linux中,将一级子节点视为bus,而多级子节点则由具体的bus去处理。这样子其实也是从另外的角度体现出整个驱动框架的分层思想。

    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    mysql查的用法
    js和jquery的基本应用
    JS实现html页面点击下载文件
    十二、PHP变量以及变量间传值的用法
    十一、jQuery的基本用法
    十、MySQL的应用大全
    九、使用盒子布局 margin 与padding如何使用
    在 Apex 中使用合并统计查询
    Salesforce 的 package.xml 文件
    Salesforce 应用生命周期管理
  • 原文地址:https://www.cnblogs.com/schips/p/linux_driver_device_node_about_parent_node.html
Copyright © 2020-2023  润新知