Compare commits

...

7 Commits

Author SHA1 Message Date
Tom Rini
fd070b0e71 Merge patch series "clk: support arbitrary clk_register() sequence"
Yang Xiwen <forbidden405@outlook.com> says:

Currently, the U-Boot clk framework mandates that clock registration
begins at the root and proceeds to children. This creates an additional
requirement that does not exist in the Linux kernel, making the porting
of clk drivers more difficult.

This series handles the dependency entirely within the clk framework,
allowing drivers the freedom to register clocks in any order.

This is achieved by assigning the parent "lazily". The framework caches
the parent name in the core clk struct and attempts to resolve the
actual parent when clk consumers call clk_get_parent(). The process is
transparent to clk consumers as long as they use standard clk framework
APIs.

I've run `ut dm clk*` and verified these commits do not break any
existing test cases. It also passes the new test case.

This feature is disabled for xPLs by default. I have not found a clean
way to enable this separately for xPLs without introducing a repetitive
Kconfig entry (e.g., xPL_CLK_LAZY_REPARENT), which looks very ugly.

Link: https://lore.kernel.org/r/20260120-clk-reparent-v3-0-0d43d4b362ac@outlook.com
2026-02-02 13:40:50 -06:00
Yang Xiwen
90cd316b5a 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 <forbidden405@outlook.com>
2026-02-02 13:40:41 -06:00
Yang Xiwen
8f230323e4 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 <simon.glass@canonical.com>
Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
2026-02-02 13:40:41 -06:00
Yang Xiwen
5fc1388141 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 <simon.glass@canonical.com>
Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
2026-02-02 13:40:41 -06:00
Yang Xiwen
d18343651c 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 <simon.glass@canonical.com>
Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
2026-02-02 13:40:41 -06:00
Yang Xiwen
74720cb082 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 <forbidden405@outlook.com>
2026-02-02 13:40:41 -06:00
Yang Xiwen
d7aea17d2e 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: cfecbaf4e7 ("dm: core: add support for device re-parenting")
Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
2026-02-02 13:40:41 -06:00
9 changed files with 178 additions and 16 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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),
&reg, CLK_SET_RATE_UNGATE));
return 0;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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

View File

@@ -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));