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\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
到底是什麼意思?為什麼和裝置樹有關?
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而言,有不同的匯流排處理方式和不同的driver
、device
的命名,自然不能將所有節點全部轉換成platform_device
。
在linux中,將一級子節點視為bus,而多級子節點則由具體的bus去處理。這樣子其實也是從另外的角度體現出整個驅動框架的分層思想。