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\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 = <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.\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;
}

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\n",
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\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结构并返回。

/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)\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去处理。这样子其实也是从另外的角度体现出整个驱动框架的分层思想。

Linux 内核:设备树(4)设备树中各个节点是谁转换的的更多相关文章

  1. (转)Linux内核基数树应用分析

    Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...

  2. Linux内核入门到放弃-设备驱动程序-《深入Linux内核架构》笔记

    I/O体系结构 总线系统 PCI(Peripheral Component Interconnect) ISA(Industrial Standard Architecture) SBus IEEE1 ...

  3. linux设备驱动程序-设备树(3)-设备树多级子节点的转换

    linux设备驱动程序--设备树多级子节点的转换 在上一章:设备树处理之--device_node转换成platform_device中,有提到在设备树的device_node到platform_de ...

  4. Linux 内核驱动支持什么设备

    struct usb_device_id 结构提供了这个驱动支持的一个不同类型 USB 设备的列表. 这个 列表被 USB 核心用来决定给设备哪个驱动, 并且通过热插拔脚本来决定哪个驱动自动加载, 当 ...

  5. Linux 内核PCI去除一个设备

    一个 PCI 可用多个不同的方法被从系统中去除. 所有的 card-bus 设备在一个不同的物 理因素上是真正的 PCI 设备, 并且内核 PCI 核心不区分它们. 允许在机器运行时加减 PCI 设备 ...

  6. Linux 内核驱动自动创建设备节点并挂载设备

    *注:本文来自http://blog.csdn.net/lwj103862095/article/details/17470573 一.首先需要在最开始定义两个数据结构: static struct ...

  7. Linux设备树语法详解

    概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写.引入了设备树之后,驱动代 ...

  8. linux设备树笔记__dts基本概念及语法【转】

    转自:http://www.360doc.com/content/15/1113/11/15700426_512794532.shtml 设备树手册(Device Tree Usage)原文地址:ht ...

  9. Linux设备树语法详解【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6131381.html 概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备 ...

  10. linux 设备树【转】

    转自:http://blog.csdn.net/chenqianleo/article/details/77779439 [-] linux 设备树 为什么要使用设备树Device Tree 设备树的 ...

随机推荐

  1. C语言实验1

    #include<stdio.h> #include<stdlib.h> int main() { printf(" o\n"); printf(" ...

  2. js实现一个拖拽效果(本例vue中),边界限定,获取鼠标坐标,div坐标

    有事没事搞个图: demo: <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...

  3. Java 集合类 List 的那些坑

    现在的一些高级编程语言都会提供各种开箱即用的数据结构的实现,像 Java 编程语言的集合框架中就提供了各种实现,集合类包含 Map 和 Collection 两个大类,其中 Collection 下面 ...

  4. linux 系统tar文件压缩打包命令

    打包成tar.gz格式压缩包 # tar -zcvf renwolesshel.tar.gz /renwolesshel 解压tar.gz格式压缩包 # tar zxvf renwolesshel.t ...

  5. 如何使用go module导入本地包

    go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具. 到今天Go1.14版本推出之后Go modules 功能 ...

  6. 在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)

    闲来无聊在我的Biwen.QuickApi中实现一下极简的事件总线,其实代码还是蛮简单的,对于初学者可能有些帮助 就贴出来,有什么不足的地方也欢迎板砖交流~ 首先定义一个事件约定的空接口 public ...

  7. 简易的 Linux 流量实时监控工具 watch+ifstat

    非常简单小巧的流量实时监控工具,贵在不需要安装,Linux 操作系统自带,在不出外网的环境中很是实用. watch -d ifstat eth1 如果连 ifstat 都没有的环境中也可以使用 ifc ...

  8. C 语言编程 — 函数

    目录 文章目录 目录 前文列表 函数 函数的声明 函数的定义 函数的形参与实参 值传递 引用传递 可变长形参列表 函数的调用 函数的指针 回调函数 递归函数 数的阶乘 斐波那契数列 构造函数(Cons ...

  9. AIRIOT训练营沈阳站圆满结束|手把手教你搞定物联网应用开发

    8月28日-9月1日,由航天科技控股集团有限公司(以下简称"航天科技")主办的<AIRIOT物联网平台应用与实战>训练营在沈阳圆满结束,来自上海电机学院.中渝软通信息技 ...

  10. AIRIOT物联网低代码平台如何配置db-driver驱动?

    设备接入能力包括接入驱动类型及接入数据量,性能方面需要考量数据采集的稳定性和驱动的丰富性等多个因素.用户在选择物联网平台时,往往存在一些误区,比如很关注平台支持的驱动数量,越多越好.市场上确有支持上千 ...