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

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

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

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

典型的设备树

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

// arch/arm/boot/dts/zynq-7000.dts
/ {
    // 无效属性
    compatible = "xx";

    // /amba 节点, 会被转换为platform_device,因为它兼容"simple-bus",
    // 同时,在内核中有对应的platform_driver来完成probe
    amba {
        compatible = "simple-bus";
        #address-cells = ;
        #size-cells = ;

        /*  /amba/i2c@e0004000 也会被转换为platform_device
            因为 父节点 /amba 具备 simple-bus;
            而 自己 /amba/i2c@e0004000 也有 compatible 属性
        */
        i2c@e0004000 {
            compatible = "cdns,i2c-r1p10";
            status = "okay";
            // ...

            #address-cells = ;
            #size-cells = ;
        };
    };

    // ...

    // 类似的也有 /usb_phy0 节点, 它一般也是用来表示USB控制器, 它会被转换为platform_device,
    // 同时,在内核中有对应的platform_driver 来完成probe
    usb_phy@0 {
        compatible = "ulpi-phy";
        #phy-cells = ;
        // ...

    };

    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 = ;
    pinctrl-names = "default";
    pinctrl-0 = ;
};

&i2c0 {
    status = "okay";
    clock-frequency = ;
    pinctrl-names = "default", "gpio";
    pinctrl-0 = ;
    pinctrl-1 = ;
    scl-gpios = ;
    sda-gpios = ;

    /*
        /i2c@0/si570 节点不会被转换为platform_device,
        si570被如何处理完全由父节点的platform_driver决定,
        在这里,si570会被创建为 i2c_device。
    */
    si570: clock-generator@5d {
        #clock-cells = ;
        compatible = "silabs,si570";
        temperature-stability = ;
        reg = ;
        factory-fout = ;
        clock-frequency = ;
    };
};

&soc {
    #address-cells = ;
    #size-cells = ;
    ranges = ;
    compatible = "simple-bus";

    mygpio-leds {
        compatible = "gpio-leds";

        led-blue{
            label = "red";
            default-state = "off";
            //gpios = ;
        };
        led-green{
            label = "green";
            default-state = "off";
            //gpios = ;
        };
    };
};

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

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

/usb

/ {
    // ...

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

由 内核解析的时候,将 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_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\n");
        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 = ;
        #size-cells = ;
        // ...

        // 子节点/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);

既然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.\n");
        return PTR_ERR(id->clk);
    }
    ret = clk_prepare_enable(id->clk);
    if (ret)
        dev_err(&pdev->dev, "Unable to enable clock.\n");

    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.\n");
    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\n", 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\n", 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\n",
         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;
}
// 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);
// 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。

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];
};
// 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控制器而生成的一个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来的。

为每一个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\n",
                    node);
            of_node_clear_flag(node, OF_POPULATED);
        }
    }
    // ...

}

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

// 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\n",
                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结构并返回。
&i2c0 {
    status = "okay";
    clock-frequency = ;
    // ...

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

根据刚刚的分析,父节点已经在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 = ;
    #size-cells = ;
    ranges = ;
    compatible = "simple-bus";

    // /soc/mygpio-leds 会称为 platform device
    mygpio-leds {
        compatible = "gpio-leds";

        led-blue{
            label = "red";
            default-state = "off";
            //gpios = ;
        };
        led-green{
            label = "green";
            default-state = "off";
            //gpios = ;
        };
    };
};

我们看看下面的 /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);

实际上,类似刚刚提到的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;
}
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)\n",
                    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去处理。这样子其实也是从另外的角度体现出整个驱动框架的分层思想。

Original: https://www.cnblogs.com/schips/p/linux_driver_device_node_about_parent_node.html
Author: schips
Title: Linux 内核:设备树(4)设备树中各个节点是谁转换的

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/8529/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

免费咨询
免费咨询
扫码关注
扫码关注
联系站长

站长Johngo!

大数据和算法重度研究者!

持续产出大数据、算法、LeetCode干货,以及业界好资源!

2022012703491714

微信来撩,免费咨询:xiaozhu_tec

分享本页
返回顶部