From d7aea17d2e606249c2a3e8495f70bb4e47e11ed8 Mon Sep 17 00:00:00 2001 From: Yang Xiwen Date: Tue, 20 Jan 2026 03:07:18 +0800 Subject: [PATCH 1/6] drivers: core: device: set new parent when old parent is NULL The current logic does not update the new parent in device_reparent() if old parent is NULL. The behavior is not desired. Fix it by setting the parent in this case. Fixes: cfecbaf4e768 ("dm: core: add support for device re-parenting") Signed-off-by: Yang Xiwen --- drivers/core/device.c | 8 ++++++++ 1 file changed, 8 insertions(+) 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; From 74720cb082d48e56e5c59e078348dd61deae2b72 Mon Sep 17 00:00:00 2001 From: Yang Xiwen Date: Tue, 20 Jan 2026 03:07:19 +0800 Subject: [PATCH 2/6] test: dm: core: add some assertions for device_reparent() The original tests only assert the return value of device_reparent(), but does not check the actual relation between the new parent and the child. Add some assertions to check this behavior. It also lacks the logic to test orphan/root devices. Add tests for that. Signed-off-by: Yang Xiwen --- test/dm/core.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/test/dm/core.c b/test/dm/core.c index 53693f4f7ed..ae9415c8956 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)); From d18343651c1d49c4ffcb17356de29eac98dffb40 Mon Sep 17 00:00:00 2001 From: Yang Xiwen Date: Tue, 20 Jan 2026 03:07:20 +0800 Subject: [PATCH 3/6] clk: add uclass id check to clk_get_parent() Check the uclass id in clk_get_parent() before casting dev->priv to struct clk *. This sanity check can be also found in some other places and should be enforced. Reviewed-by: Simon Glass Signed-off-by: Yang Xiwen --- drivers/clk/clk-uclass.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index 0584429bed6..559420c8505 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -507,6 +507,10 @@ struct clk *clk_get_parent(struct clk *clk) pdev = dev_get_parent(clk->dev); if (!pdev) 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); From 5fc1388141de3660c271cf99e5fc4036fb03ae84 Mon Sep 17 00:00:00 2001 From: Yang Xiwen Date: Tue, 20 Jan 2026 03:07:21 +0800 Subject: [PATCH 4/6] clk: use clk_get_parent() helper in clk_en(dis)able() Update clk_enable() and clk_disable() to use clk_get_parent() instead of manually accessing clk->dev->parent. Reviewed-by: Simon Glass Signed-off-by: Yang Xiwen --- drivers/clk/clk-uclass.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index 559420c8505..297d4d63a57 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -663,7 +663,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"); @@ -679,9 +679,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); @@ -754,13 +755,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 { From 8f230323e42fa7dd69bcfda1e7810b3327f4b4f6 Mon Sep 17 00:00:00 2001 From: Yang Xiwen Date: Tue, 20 Jan 2026 03:07:22 +0800 Subject: [PATCH 5/6] clk: allow assigning parent lazily Don't mandate the parent device exists when registering a clock. Instead, cache the parent name in the core clk struct and resolve the parent in clk_get_parent(), which is called lazily upon real use. Disable this feature for xPLs by default to save size. Reviewed-by: Simon Glass Signed-off-by: Yang Xiwen --- drivers/clk/Kconfig | 12 +++++++++++ drivers/clk/clk-uclass.c | 44 ++++++++++++++++++++++++++++++++++++++-- drivers/clk/clk.c | 14 +++++++++++-- include/clk.h | 2 ++ 4 files changed, 68 insertions(+), 4 deletions(-) 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 297d4d63a57..a7f02d339a5 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -495,6 +495,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; @@ -505,8 +531,18 @@ struct clk *clk_get_parent(struct clk *clk) return NULL; pdev = dev_get_parent(clk->dev); - if (!pdev) - return ERR_PTR(-ENODEV); + 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); @@ -630,6 +666,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) 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/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; From 90cd316b5a0fce702525ff7980d30e2bd233078d Mon Sep 17 00:00:00 2001 From: Yang Xiwen Date: Tue, 20 Jan 2026 03:07:23 +0800 Subject: [PATCH 6/6] test: clk: add test for CLK_LAZY_REPARENT Add a test for the newly introduced CLK_LAZY_REPARENT feature. Modify clk_sandbox_ccf driver to register two clocks(i2s and i2s_root) in reversed order. The test function then try to acquire the clocks and asserts that their internal states are maintained correctly. Ensure clk->parent_name is NULL in all "normal" cases so existing drivers are not broken by this feature. Signed-off-by: Yang Xiwen --- drivers/clk/clk_sandbox_ccf.c | 10 +++++++++ include/sandbox-clk.h | 2 ++ test/dm/clk_ccf.c | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) 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/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