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]> --- Changes in v2: - Reparent through clk_set_parent() before programming the divider, as suggested by Yixun Lan. CCF enables the new parent only when the clock itself is enabled. - Reparent also when only the hardware mux disagrees with the chosen parent, including the SPL's single-parent clock registration case. - Share parent-index lookup with ccu_mux_set_parent(). Keep the no-write case when the mux and divider already match. v1: https://lists.u-boot-project.org/pipermail/u-boot/2026-October/633805.html The parent/divider selection algorithm is unchanged from v1. This revision changes how the selected parent is applied. Retested on 2026-10-02, with the integrated board-support lane on next bd2f5794b7e2, OpenSBI v1.9, and "clk: spacemit: ccu_mix: fix inverted condition in ccu_mix_trigger_fc()" from Yixun's k3/for-next tree: - OrangePi R2S: eMMC SPL boot, SDH2 I/O clock 204.8 MHz instead of the BootROM's 409.6 MHz; U-Boot HS200 at 200 MHz/8-bit, normal EFI and FreeBSD boot, and a final unattended reboot. - OrangePi RV2: microSD SPL boot, U-Boot SD High Speed at 50 MHz, EFI and FreeBSD boot with root on NVMe; two boots checked. Both CPU clusters remain at 1600 MHz on both boards, with SSH working following boot. The R2S, RV2, generic K1 and RV2 SPI configurations build SPL and U-Boot proper without compiler warnings; checkpatch passes. Generic K1 and RV2 SPI are BUILD-ONLY for this revision. No before/after performance benchmarks were run. drivers/clk/spacemit/clk_mix.c | 79 ++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/drivers/clk/spacemit/clk_mix.c b/drivers/clk/spacemit/clk_mix.c index a1158512a9..fc8e0fd41d 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_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_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_parent_index(common, parent); + if (index < 0) + return index; + + if (clk->dev->parent != parent->dev || + ccu_mux_get_parent(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_parent_index(common, parent); + if (i < 0) + return i; ccu_update(&mix->common, ctrl, mask, i << mux->shift); -- 2.55.0
