diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index ae783254008..5a57adef3cc 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -51,6 +51,18 @@ config VPL_CLK setting up clocks within TPL, and allows the same drivers to be used as U-Boot proper. +config CLK_LAZY_REPARENT + bool "Enable clock lazy reparenting feature" + depends on CLK_CCF + default n if SPL_CLK || TPL_CLK || VPL_CLK + default y + help + This option allows registering clocks in a less strict order that + Parent clocks can be registered before their children. The clock subsystem + will cache the parent's name and resolve it to the real parent device "lazily". + This is the default behavior in Linux clock subsystem. Enabling this feature + should simplifies the porting of Linux clock drivers to U-Boot. + config CLK_BCM6345 bool "Clock controller driver for BCM6345" depends on CLK && ARCH_BMIPS diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index c33f415917e..009255cbdee 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -496,6 +496,32 @@ ulong clk_get_rate(struct clk *clk) return ops->get_rate(clk); } +static struct udevice *clk_reparent(struct clk *clk, const char *parent_name) +{ + struct udevice *pdev; + int ret; + + if (!clk_valid(clk)) + return NULL; + + if (!parent_name) + return NULL; + + debug("%s(clk=%p) reparenting to %s\n", __func__, clk, parent_name); + + ret = uclass_get_device_by_name(UCLASS_CLK, parent_name, &pdev); + if (ret) { + log_err("%s(clk=%p) failed to find parent \"%s\"\n", __func__, clk, parent_name); + return NULL; + } + + ret = device_reparent(clk->dev, pdev); + if (ret) + return NULL; + + return pdev; +} + struct clk *clk_get_parent(struct clk *clk) { struct udevice *pdev; @@ -506,8 +532,22 @@ struct clk *clk_get_parent(struct clk *clk) return NULL; pdev = dev_get_parent(clk->dev); - if (!pdev) + if (!pdev) { + if (CONFIG_IS_ENABLED(CLK_LAZY_REPARENT)) { + pdev = clk_reparent(clk, clk->parent_name); + free(clk->parent_name); + clk->parent_name = NULL; + + if (!pdev) + return ERR_PTR(-ENODEV); + } else { + return ERR_PTR(-ENODEV); + } + } + + if (device_get_uclass_id(pdev) != UCLASS_CLK) return ERR_PTR(-ENODEV); + pclk = dev_get_clk_ptr(pdev); if (!pclk) return ERR_PTR(-ENODEV); @@ -627,6 +667,10 @@ int clk_set_parent(struct clk *clk, struct clk *parent) debug("%s(clk=%p, parent=%p)\n", __func__, clk, parent); if (!clk_valid(clk)) return 0; + + free(clk->parent_name); + clk->parent_name = NULL; + ops = clk_dev_ops(clk->dev); if (!ops->set_parent) @@ -660,7 +704,7 @@ int clk_set_parent(struct clk *clk, struct clk *parent) int clk_enable(struct clk *clk) { const struct clk_ops *ops; - struct clk *clkp = NULL; + struct clk *clkp = NULL, *clk_parent; int ret; debug("%s(clk=%p name=%s)\n", __func__, clk, clk ? clk->dev->name : "NULL"); @@ -676,9 +720,10 @@ int clk_enable(struct clk *clk) clkp->enable_count++; return 0; } - if (clkp->dev->parent && - device_get_uclass_id(clkp->dev->parent) == UCLASS_CLK) { - ret = clk_enable(dev_get_clk_ptr(clkp->dev->parent)); + + clk_parent = clk_get_parent(clkp); + if (!IS_ERR_OR_NULL(clk_parent)) { + ret = clk_enable(clk_parent); if (ret) { printf("Enable %s failed\n", clkp->dev->parent->name); @@ -751,13 +796,16 @@ int clk_disable(struct clk *clk) return ret; } - if (clkp && clkp->dev->parent && - device_get_uclass_id(clkp->dev->parent) == UCLASS_CLK) { - ret = clk_disable(dev_get_clk_ptr(clkp->dev->parent)); - if (ret) { - printf("Disable %s failed\n", - clkp->dev->parent->name); - return ret; + if (clkp) { + struct clk *clk_parent = clk_get_parent(clkp); + + if (!IS_ERR_OR_NULL(clk_parent)) { + ret = clk_disable(clk_parent); + if (ret) { + printf("Disable %s failed\n", + clkp->dev->parent->name); + return ret; + } } } } else { diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index b8c2e8d531b..32b3c03ab09 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -24,8 +24,18 @@ int clk_register(struct clk *clk, const char *drv_name, if (parent_name) { ret = uclass_get_device_by_name(UCLASS_CLK, parent_name, &parent); if (ret) { - log_err("%s: failed to get %s device (parent of %s)\n", - __func__, parent_name, name); + log_debug("%s: failed to get %s device (parent of %s)\n", + __func__, parent_name, name); + + if (CONFIG_IS_ENABLED(CLK_LAZY_REPARENT)) { + /* + * The parent is not yet registered. + * Cache the parent name and resolve it later. + */ + clk->parent_name = strdup(parent_name); + if (!clk->parent_name) + return -ENOMEM; + } } else { log_debug("%s: name: %s parent: %s [0x%p]\n", __func__, name, parent->name, parent); diff --git a/drivers/clk/clk_sandbox_ccf.c b/drivers/clk/clk_sandbox_ccf.c index 9b8036d41aa..86f75132d54 100644 --- a/drivers/clk/clk_sandbox_ccf.c +++ b/drivers/clk/clk_sandbox_ccf.c @@ -229,6 +229,7 @@ static const struct udevice_id sandbox_clk_ccf_test_ids[] = { static const char *const usdhc_sels[] = { "pll3_60m", "pll3_80m", }; static const char *const i2c_sels[] = { "pll3_60m", "pll3_80m", }; +static const char *const i2s_sels[] = { "pll3_60m", "pll3_80m", }; static int sandbox_clk_ccf_probe(struct udevice *dev) { @@ -277,6 +278,15 @@ static int sandbox_clk_ccf_probe(struct udevice *dev) dev_clk_dm(dev, SANDBOX_CLK_I2C_ROOT, sandbox_clk_gate2("i2c_root", "i2c", base + 0x7c, 0)); + /* Register i2s_root(child) and i2s(parent) in reverse order to test CLK_LAZY_REPARENT */ + dev_clk_dm(dev, SANDBOX_CLK_I2S_ROOT, + sandbox_clk_gate2("i2s_root", "i2s", base + 0x80, 0)); + + reg = BIT(29) | BIT(25) | BIT(17); + dev_clk_dm(dev, SANDBOX_CLK_I2S, + sandbox_clk_composite("i2s", i2s_sels, ARRAY_SIZE(i2s_sels), + ®, CLK_SET_RATE_UNGATE)); + return 0; } diff --git a/drivers/core/device.c b/drivers/core/device.c index 779f371b9d5..0ae09f5a4e3 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -285,6 +285,14 @@ int device_reparent(struct udevice *dev, struct udevice *new_parent) assert(dev); assert(new_parent); + if (!dev->parent) { + assert(list_empty(&dev->sibling_node)); + + list_add_tail(&dev->sibling_node, &new_parent->child_head); + dev->parent = new_parent; + return 0; + } + device_foreach_child_safe(pos, n, dev->parent) { if (pos->driver != dev->driver) continue; diff --git a/include/clk.h b/include/clk.h index 90b42a61867..88db75c56d4 100644 --- a/include/clk.h +++ b/include/clk.h @@ -47,6 +47,7 @@ struct udevice; /** * struct clk - A handle to (allowing control of) a single clock. * @dev: The device which implements the clock signal. + * @parent_name: The name of the parent. * @rate: The clock rate (in HZ). * @flags: Flags used across common clock structure (e.g. %CLK_) * Clock IP blocks specific flags (i.e. mux, div, gate, etc) are defined @@ -72,6 +73,7 @@ struct udevice; */ struct clk { struct udevice *dev; + char *parent_name; long long rate; /* in HZ */ u32 flags; int enable_count; diff --git a/include/sandbox-clk.h b/include/sandbox-clk.h index c2616c27a44..0cd735e3ad5 100644 --- a/include/sandbox-clk.h +++ b/include/sandbox-clk.h @@ -21,6 +21,8 @@ enum { SANDBOX_CLK_USDHC2_SEL, SANDBOX_CLK_I2C, SANDBOX_CLK_I2C_ROOT, + SANDBOX_CLK_I2S, + SANDBOX_CLK_I2S_ROOT, }; enum sandbox_pllv3_type { diff --git a/test/dm/clk_ccf.c b/test/dm/clk_ccf.c index 9c06aadb7ed..8bbb79bec2a 100644 --- a/test/dm/clk_ccf.c +++ b/test/dm/clk_ccf.c @@ -35,12 +35,14 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_ECSPI_ROOT), &clk); ut_assertok(ret); ut_asserteq_str("ecspi_root", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_SET_RATE_PARENT, clk->flags); /* Test for clk_get_parent_rate() */ ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_ECSPI1), &clk); ut_assertok(ret); ut_asserteq_str("ecspi1", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_SET_RATE_PARENT, clk->flags); rate = clk_get_parent_rate(clk); @@ -50,6 +52,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_ECSPI0), &clk); ut_assertok(ret); ut_asserteq_str("ecspi0", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_SET_RATE_PARENT, clk->flags); rate = clk_get_parent_rate(clk); @@ -59,6 +62,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_USDHC1_SEL), &clk); ut_assertok(ret); ut_asserteq_str("usdhc1_sel", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_SET_RATE_NO_REPARENT, clk->flags); rate = clk_get_parent_rate(clk); @@ -71,6 +75,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ut_asserteq_64(60000000, rate); ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_PLL3_80M), &pclk); + ut_assertnull(clk->parent_name); ut_assertok(ret); ret = clk_set_parent(clk, pclk); @@ -82,6 +87,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_USDHC2_SEL), &clk); ut_assertok(ret); ut_asserteq_str("usdhc2_sel", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_SET_RATE_NO_REPARENT, clk->flags); rate = clk_get_parent_rate(clk); @@ -98,6 +104,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ut_asserteq_64(80000000, rate); ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_PLL3_60M), &pclk); + ut_assertnull(clk->parent_name); ut_assertok(ret); ret = clk_set_parent(clk, pclk); @@ -110,6 +117,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_I2C), &clk); ut_assertok(ret); ut_asserteq_str("i2c", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_SET_RATE_UNGATE, clk->flags); rate = clk_get_rate(clk); @@ -124,11 +132,13 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_index(test_dev, SANDBOX_CLK_TEST_ID_I2C_ROOT, &clk_ccf); ut_assertok(ret); ut_asserteq_str("clk-ccf", clk_ccf.dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(CLK_ID(clk_ccf.dev, SANDBOX_CLK_I2C_ROOT), clk_ccf.id); ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_I2C_ROOT), &clk); ut_assertok(ret); ut_asserteq_str("i2c_root", clk->dev->name); + ut_assertnull(clk->parent_name); ut_asserteq(SANDBOX_CLK_I2C_ROOT, clk_get_id(clk)); ret = clk_enable(&clk_ccf); @@ -138,6 +148,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ut_asserteq(1, ret); ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_I2C), &pclk); + ut_assertnull(clk->parent_name); ut_assertok(ret); ret = sandbox_clk_enable_count(pclk); @@ -156,6 +167,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_USDHC1_SEL), &clk); ut_assertok(ret); ut_asserteq_str("usdhc1_sel", clk->dev->name); + ut_assertnull(clk->parent_name); pclk = clk_get_parent(clk); ut_assertok_ptr(pclk); @@ -169,6 +181,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, clkid), &pclk); ut_assertok(ret); + ut_assertnull(clk->parent_name); ret = clk_set_parent(clk, pclk); ut_assertok(ret); pclk = clk_get_parent(clk); @@ -179,6 +192,7 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) ret = clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_I2C_ROOT), &clk); ut_assertok(ret); ut_asserteq_str("i2c_root", clk->dev->name); + ut_assertnull(clk->parent_name); /* Disable it, if any. */ ret = sandbox_clk_enable_count(clk); @@ -209,3 +223,29 @@ static int dm_test_clk_ccf(struct unit_test_state *uts) return 1; } DM_TEST(dm_test_clk_ccf, UTF_SCAN_FDT); + +#if CONFIG_IS_ENABLED(CLK_LAZY_REPARENT) +/* Test CLK_LAZY_REPARENT feature */ +static int dm_test_clk_lazy_reparent(struct unit_test_state *uts) +{ + struct udevice *dev; + struct clk *clk_i2s, *clk_i2s_root; + + /* Get the device using the clk device */ + ut_assertok(uclass_get_device_by_name(UCLASS_CLK, "clk-ccf", &dev)); + + ut_assertok(clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_I2S_ROOT), &clk_i2s_root)); + ut_assertok(clk_get_by_id(CLK_ID(dev, SANDBOX_CLK_I2S), &clk_i2s)); + + ut_asserteq_str(clk_i2s_root->parent_name, "i2s"); + ut_assertnull(clk_i2s_root->dev->parent); + + ut_assertok(clk_enable(clk_i2s_root)); + + ut_assertnull(clk_i2s_root->parent_name); + ut_asserteq_ptr(clk_i2s_root->dev->parent, clk_i2s->dev); + + return 0; +} +DM_TEST(dm_test_clk_lazy_reparent, UTF_SCAN_FDT); +#endif diff --git a/test/dm/core.c b/test/dm/core.c index 78ee14af228..d5a7954be95 100644 --- a/test/dm/core.c +++ b/test/dm/core.c @@ -696,9 +696,10 @@ DM_TEST(dm_test_children, 0); static int dm_test_device_reparent(struct unit_test_state *uts) { struct udevice *top[NODE_COUNT]; - struct udevice *child[NODE_COUNT]; + struct udevice *child[NODE_COUNT], *temp_child = NULL; struct udevice *grandchild[NODE_COUNT]; struct udevice *dev; + struct udevice *orphan; int total; int ret; int i; @@ -720,8 +721,11 @@ static int dm_test_device_reparent(struct unit_test_state *uts) ut_assertok(create_children(uts, child[i], NODE_COUNT, 50 * i, i == 2 ? grandchild : NULL)); + /* Create an orphan device */ + ut_assertok(create_children(uts, NULL, 1, 49, &orphan)); + /* Check total number of devices */ - total = NODE_COUNT * (3 + NODE_COUNT); + total = NODE_COUNT * (3 + NODE_COUNT) + 1; ut_asserteq(total, dm_testdrv_op_count[DM_TEST_OP_BIND]); /* Probe everything */ @@ -738,6 +742,14 @@ static int dm_test_device_reparent(struct unit_test_state *uts) ut_assertok(device_reparent(top[4], top[0])); + /* Ensure it's reparented */ + ut_asserteq_ptr(top[4]->parent, top[0]); + device_foreach_child(temp_child, top[0]) { + if (temp_child == top[4]) + break; + } + ut_asserteq_ptr(temp_child, top[4]); + /* try to get devices */ ret = uclass_find_first_device(UCLASS_TEST, &dev); ut_assert(!ret); @@ -773,6 +785,24 @@ static int dm_test_device_reparent(struct unit_test_state *uts) ut_assert(!ret); ut_assertnonnull(dev); + /* Re-parent orphant device */ + ut_assertok(device_reparent(orphan, top[0])); + + /* try to get the device */ + ret = uclass_find_first_device(UCLASS_TEST, &dev); + ut_assert(!ret); + ut_assertnonnull(dev); + + /* ensure it's reparented */ + ut_asserteq_ptr(orphan->parent, top[0]); + + temp_child = NULL; + device_foreach_child(temp_child, top[0]) { + if (temp_child == orphan) + break; + } + ut_asserteq_ptr(temp_child, orphan); + /* Remove re-pareneted devices. */ ut_assertok(device_remove(top[3], DM_REMOVE_NORMAL));