ccu_mix_set_rate() picks the best parent and divider but writes only the divider. U-Boot's CCF has no determine_rate step that would reparent the clock first, so the clock keeps running from whatever source the mux selects, at a rate the caller never asked for. Worse, when the hardware mux selects a source that this build's parent list does not contain (the K1 SPL lists only pll1_d6_409p6 for the SDH clocks), init registers the clock under parents[0] and the clock framework's idea of the parent no longer matches the hardware.
Reparent the clock to the chosen parent with clk_set_parent() before programming the divider, as the clock core does in Linux. Do so also when the framework already has that parent but the mux selects another source. Nothing is written when the mux and divider already match, so a clock left running at the requested rate by an earlier stage is not disturbed. Signed-off-by: Dave Klotz <[email protected]> Reviewed-by: Yixun Lan <[email protected]> --- Changes in v3: - Rename ccu_mux_get_parent() to ccu_mux_current_parent_index(), including the set-rate and clock-registration callers. - Rename ccu_mux_parent_index() to ccu_mux_wanted_parent_index(), including the set-rate and set-parent callers. - Name the hardware parent-index local variable index instead of parent. - Carry Yixun Lan's Reviewed-by. No functional changes from v2. Changes in v2: - Reparent through clk_set_parent() before programming the divider. - Reparent also when the hardware mux disagrees with the chosen parent. - Share parent-index lookup with ccu_mux_set_parent() and preserve the no-write case when mux and divider already match. v2: https://lists.u-boot-project.org/pipermail/u-boot/2026-October/633842.html Review: https://lists.u-boot-project.org/pipermail/u-boot/2026-October/633911.html Validation: exact-base replay and inverse-renaming comparison against v2 pass, with zero checkpatch errors or warnings. Both R2S and RV2 configurations build successfully. An integration containing this patch, the MMC corrections and a separate DDR firmware overlap fix passed warm-boot tests on a 2 GiB OrangePi R2S and a 4 GiB OrangePi RV2. Both reached DDR/SPL, FIT verification and automatic EFI boot to their existing FreeBSD 16 installation, with the expected memory and eight CPUs. These are combined-integration results, not isolated tests of this patch. Cold boot of the corrected firmware and clock error paths remain untested. drivers/clk/spacemit/clk_mix.c | 87 +++++++++++++++------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/drivers/clk/spacemit/clk_mix.c b/drivers/clk/spacemit/clk_mix.c index a115851..b626295 100644 --- a/drivers/clk/spacemit/clk_mix.c +++ b/drivers/clk/spacemit/clk_mix.c @@ -136,14 +136,63 @@ ccu_mix_calc_best_rate(struct clk *clk, unsigned long rate, return best_rate; } +static u8 ccu_mux_current_parent_index(struct clk *clk) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_mux_config *mux = &mix->mux; + u8 index; + + index = ccu_read(&mix->common, ctrl) >> mux->shift; + index &= (1 << mux->width) - 1; + + return index; +} + +static int ccu_mux_wanted_parent_index(struct ccu_common *common, struct clk *parent) +{ + int i; + + for (i = 0; i < common->num_parents; i++) + if (!strcmp(parent->dev->name, common->parents[i])) + return i; + + return -EINVAL; +} + +/* + * U-Boot's CCF has no determine_rate step, so the parent giving the best rate + * is chosen here. As in Linux, the clock is reparented first and the divider + * programmed after. Reparent also when the framework already has the chosen + * parent but the mux selects another source: init registers a clock under + * parents[0] when the mux selects a source this build does not list (the SPL + * lists only a subset). Nothing is written when mux and divider already match, + * so a clock left running by an earlier stage is not disturbed. + */ static unsigned long ccu_mix_set_rate(struct clk *clk, unsigned long rate) { struct ccu_mix *mix = clk_to_ccu_mix(clk); struct ccu_common *common = &mix->common; struct ccu_div_config *div = &mix->div; - u32 current_div, target_div, mask; - - ccu_mix_calc_best_rate(clk, rate, NULL, NULL, &target_div); + struct ccu_mux_config *mux = &mix->mux; + struct clk *parent = NULL; + unsigned long parent_rate; + u32 current_div, target_div = 0, mask; + int index, ret; + + ccu_mix_calc_best_rate(clk, rate, &parent, &parent_rate, &target_div); + + if (mux->width && parent) { + index = ccu_mux_wanted_parent_index(common, parent); + if (index < 0) + return index; + + if (clk->dev->parent != parent->dev || + ccu_mux_current_parent_index(clk) != index) { + ret = clk_set_parent(clk, parent); + if (ret) + return ret; + } + } current_div = ccu_read(common, ctrl) >> div->shift; current_div &= (1 << div->width) - 1; @@ -158,35 +207,19 @@ static unsigned long ccu_mix_set_rate(struct clk *clk, unsigned long rate) return ccu_mix_trigger_fc(clk); } -static u8 ccu_mux_get_parent(struct clk *clk) -{ - struct ccu_mix *mix = clk_to_ccu_mix(clk); - struct ccu_mux_config *mux = &mix->mux; - u8 parent; - - parent = ccu_read(&mix->common, ctrl) >> mux->shift; - parent &= (1 << mux->width) - 1; - - return parent; -} - static int ccu_mux_set_parent(struct clk *clk, struct clk *parent) { struct ccu_common *common = clk_to_ccu_common(clk); struct ccu_mix *mix = clk_to_ccu_mix(clk); struct ccu_mux_config *mux = &mix->mux; u32 mask; - int i = 0; + int i; mask = GENMASK(mux->width + mux->shift - 1, mux->shift); - for (i = 0; i < common->num_parents; i++) { - if (!strcmp(parent->dev->name, common->parents[i])) - break; - } - - if (i == common->num_parents) - return -EINVAL; + i = ccu_mux_wanted_parent_index(common, parent); + if (i < 0) + return i; ccu_update(&mix->common, ctrl, mask, i << mux->shift); @@ -238,7 +271,7 @@ int spacemit_mux_init(struct ccu_common *common) struct clk *clk = &common->clk; u8 index; - index = ccu_mux_get_parent(clk); + index = ccu_mux_current_parent_index(clk); if (index >= common->num_parents) index = 0; @@ -304,7 +337,7 @@ int spacemit_mux_gate_init(struct ccu_common *common) struct clk *clk = &common->clk; u8 index; - index = ccu_mux_get_parent(clk); + index = ccu_mux_current_parent_index(clk); if (index >= common->num_parents) index = 0; @@ -353,7 +386,7 @@ int spacemit_mux_div_init(struct ccu_common *common) struct clk *clk = &common->clk; u8 index; - index = ccu_mux_get_parent(clk); + index = ccu_mux_current_parent_index(clk); if (index >= common->num_parents) index = 0; @@ -379,7 +412,7 @@ int spacemit_mux_div_gate_init(struct ccu_common *common) struct clk *clk = &common->clk; u8 index; - index = ccu_mux_get_parent(clk); + index = ccu_mux_current_parent_index(clk); if (index >= common->num_parents) index = 0;
