Make set_rate on a mux, or on a gate in front of one, select the fastest mux input that does not exceed the requested rate, as Linux does for these muxes. Add round_rate so the result can be predicted without touching the hardware. Both paths share one helper with an apply flag, so they cannot disagree about what a request would produce.
Only a PLL can change its own frequency, so asking any other clock for a rate fails with -EINVAL today. A display driver typically sets its own PLL and then asks for its pixel clock, a gate behind a mux, for the resulting rate, which only works if the request reaches the mux. A request never retunes a PLL indirectly: it stops at a PLL or a fixed factor, which report the rate they run at. PLLs are shared, the audio PLLs in particular feed unrelated blocks, and U-Boot has no rate change notifiers to keep that safe. Linux sets CLK_SET_RATE_PARENT on these clocks, but relies on that protection. Signed-off-by: David Lechner <[email protected]> --- drivers/clk/mediatek/clk-mtk.c | 251 ++++++++++++++++++++++++++++++++++------- 1 file changed, 212 insertions(+), 39 deletions(-) diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c index 1d7a65904a1..e611d48f083 100644 --- a/drivers/clk/mediatek/clk-mtk.c +++ b/drivers/clk/mediatek/clk-mtk.c @@ -14,6 +14,7 @@ #include <linux/bitops.h> #include <linux/delay.h> #include <linux/err.h> +#include <linux/string.h> #include "clk-mtk.h" @@ -249,46 +250,51 @@ static ulong mtk_ext_clock_get_rate(const struct mtk_clk_tree *tree, int id) } /* - * In case the rate change propagation to parent clocks is undesirable, - * this function is recursively called to find the parent to calculate - * the accurate frequency. + * Build @out so that it refers to clock @parent on the provider selected by + * @flags, which lets the generic clk API be used to walk up the tree even + * when the parent lives on a different provider device (a topckgen divider + * feeding off an apmixedsys PLL, say). + * + * CLK_PARENT_EXT parents are not backed by a provider at all; callers have + * to handle them with mtk_ext_clock_get_rate() instead. */ -static ulong mtk_clk_find_parent_rate(struct clk *clk, int id, - struct udevice *pdev) +static int mtk_clk_lookup_parent(struct clk *clk, int parent, u16 flags, + struct clk *out) { struct ofnode_phandle_args args = { .args_count = 1, - .args = { id }, + .args = { parent }, }; - struct clk parent = { }; - int ret; + struct udevice *pdev; - if (pdev) - parent.dev = pdev; - else - parent.dev = clk->dev; + if ((flags & CLK_PARENT_MASK) == CLK_PARENT_EXT) + return -EINVAL; - args.node = dev_ofnode(parent.dev); - ret = ((struct clk_ops *)parent.dev->driver->ops)->of_xlate(&parent, &args); - if (ret) - return ret; + pdev = mtk_clk_parent_get_provider(flags); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + memset(out, 0, sizeof(*out)); + out->dev = pdev ?: clk->dev; + args.node = dev_ofnode(out->dev); - return clk_get_rate(&parent); + return ((struct clk_ops *)out->dev->driver->ops)->of_xlate(out, &args); } static ulong mtk_find_parent_rate(struct mtk_clk_priv *priv, struct clk *clk, const int parent, u16 flags) { - struct udevice *pdev; + struct clk pclk; + int ret; if ((flags & CLK_PARENT_MASK) == CLK_PARENT_EXT) return mtk_ext_clock_get_rate(priv->tree, parent); - pdev = mtk_clk_parent_get_provider(flags); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); + ret = mtk_clk_lookup_parent(clk, parent, flags, &pclk); + if (ret) + return ret; - return mtk_clk_find_parent_rate(clk, parent, pdev); + return clk_get_rate(&pclk); } static ulong mtk_clk_mux_get_rate(struct clk *clk, u32 off) @@ -306,21 +312,10 @@ static ulong mtk_clk_mux_get_rate(struct clk *clk, u32 off) return mtk_find_parent_rate(priv, clk, parent->id, parent->flags); } -static int mtk_clk_mux_set_parent(void __iomem *base, u32 parent, - u32 parent_type, - const struct mtk_composite *mux) +static void mtk_clk_mux_write_index(void __iomem *base, u32 index, + const struct mtk_composite *mux) { - u32 val, index = 0; - - /* - * Assume parent_type in clk_tree to be always set. If it's not, assume - * parent clk ID clash is not possible. - */ - while (mux->parent[index].id != parent || - (parent_type && (mux->parent[index].flags & CLK_PARENT_MASK) != - parent_type)) - if (++index == mux->num_parents) - return -EINVAL; + u32 val; if (mux->flags & CLK_MUX_SETCLR_UPD) { val = (mux->mux_mask << mux->mux_shift); @@ -339,6 +334,25 @@ static int mtk_clk_mux_set_parent(void __iomem *base, u32 parent, val |= index << mux->mux_shift; writel(val, base + mux->mux_reg); } +} + +static int mtk_clk_mux_set_parent(void __iomem *base, u32 parent, + u32 parent_type, + const struct mtk_composite *mux) +{ + u32 index = 0; + + /* + * Assume parent_type in clk_tree to be always set. If it's not, assume + * parent clk ID clash is not possible. + */ + while (mux->parent[index].id != parent || + (parent_type && (mux->parent[index].flags & CLK_PARENT_MASK) != + parent_type)) + if (++index == mux->num_parents) + return -EINVAL; + + mtk_clk_mux_write_index(base, index, mux); return 0; } @@ -565,16 +579,37 @@ static ulong mtk_pll_get_rate(struct clk *clk) return __mtk_pll_recalc_rate(pll, xtal_rate, pcw, postdiv); } -static ulong mtk_clk_set_rate(struct clk *clk, ulong rate) +/* + * Report the rate the PLL would actually end up at if asked for @rate, + * without touching the hardware. The requested rate is rarely hit exactly + * because it has to be expressed as a pcw/post-divider pair, and it is + * clamped to the PLL's fmax. + */ +static ulong mtk_pll_round_rate(struct clk *clk, ulong rate) { struct mtk_clk_priv *priv = dev_get_priv(clk->dev); const struct mtk_parent *parent = &priv->tree->pll_parent; + const struct mtk_pll_data *pll = &priv->tree->plls[clk->id]; ulong xtal_rate; u32 pcw = 0; u32 postdiv; - if (!mtk_clk_id_is_pll(priv->tree, clk->id)) - return -EINVAL; + xtal_rate = mtk_find_parent_rate(priv, clk, parent->id, parent->flags); + if (IS_ERR_VALUE(xtal_rate)) + return xtal_rate; + + mtk_pll_calc_values(pll, xtal_rate, &pcw, &postdiv, rate); + + return __mtk_pll_recalc_rate(pll, xtal_rate, pcw, postdiv); +} + +static ulong mtk_pll_set_rate(struct clk *clk, ulong rate) +{ + struct mtk_clk_priv *priv = dev_get_priv(clk->dev); + const struct mtk_parent *parent = &priv->tree->pll_parent; + ulong xtal_rate; + u32 pcw = 0; + u32 postdiv; xtal_rate = mtk_find_parent_rate(priv, clk, parent->id, parent->flags); if (IS_ERR_VALUE(xtal_rate)) @@ -688,6 +723,143 @@ static ulong mtk_clk_get_rate(struct clk *clk) return -ENOENT; } +/* + * Pass a rate request on to the parent of a gate. The request stops at a PLL, + * which reports the rate it already runs at: PLLs are shared, so only a + * request made directly on one may retune it. + * + * @apply selects whether to program the hardware or only report what the + * result would have been, so that the round and set paths below cannot + * disagree. Both return the resulting rate. + */ +static ulong mtk_clk_parent_rate_request(struct clk *clk, int parent, u16 flags, + ulong rate, bool apply) +{ + struct mtk_clk_priv *priv = dev_get_priv(clk->dev); + struct mtk_clk_priv *ppriv; + struct clk pclk; + int ret; + + /* an external clock runs at a fixed rate: take it or leave it */ + if ((flags & CLK_PARENT_MASK) == CLK_PARENT_EXT) + return mtk_ext_clock_get_rate(priv->tree, parent); + + ret = mtk_clk_lookup_parent(clk, parent, flags, &pclk); + if (ret) + return ret; + + ppriv = dev_get_priv(pclk.dev); + if (mtk_clk_id_is_pll(ppriv->tree, pclk.id)) + return clk_get_rate(&pclk); + + if (!apply) + return clk_round_rate(&pclk, rate); + + return clk_set_rate(&pclk, rate); +} + +/* + * Select the fastest mux input that does not exceed @rate, as the Linux + * clk_mux_determine_rate_flags() does without CLK_MUX_ROUND_CLOSEST. + * + * A mux only ever picks between its inputs: it must not retune whatever PLL + * happens to sit behind one of them, because those are shared - the audio + * PLLs in particular feed unrelated blocks, and a display driver has no + * business moving them. Linux sets CLK_SET_RATE_PARENT on these muxes, but + * relies on rate change notifiers and exclusivity that U-Boot does not + * have to keep that safe. A driver that needs a rate no input currently + * provides has to set its own PLL first, the one its "pll" clock names, and + * then ask the mux for the resulting rate. + * + * An input running at 0 Hz is never selected, as in Linux. + * + * Unlike Linux, the new input is not enabled before the mux switches to + * it, and a PLL reports the rate its dividers are set to even while it is + * powered off. The known users already set and enable the PLL they need + * before asking the mux, so this is fine for now. + */ +static ulong mtk_clk_mux_rate_request(struct clk *clk, ulong rate, bool apply) +{ + struct mtk_clk_priv *priv = dev_get_priv(clk->dev); + const struct mtk_composite *mux; + ulong best_rate = 0; + u32 best_index = 0; + u32 i; + + mux = &priv->tree->muxes[clk->id - priv->tree->muxes_offs]; + + for (i = 0; i < mux->num_parents; i++) { + const struct mtk_parent *parent = &mux->parent[i]; + ulong prate; + + prate = mtk_find_parent_rate(priv, clk, parent->id, + parent->flags); + if (IS_ERR_VALUE(prate) || !prate || prate > rate) + continue; + + if (prate > best_rate) { + best_index = i; + best_rate = prate; + } + + if (prate == rate) + break; + } + + if (!best_rate) + return -EINVAL; + + if (apply) + mtk_clk_mux_write_index(priv->base, best_index, mux); + + return best_rate; +} + +/* + * Only a PLL can change its own frequency, and only when asked directly. + * Fixed clocks and fixed factors report the rate they run at, a mux picks + * between its inputs and a gate passes the request on to its parent. + */ +static ulong mtk_clk_rate_request(struct clk *clk, ulong rate, bool apply) +{ + struct mtk_clk_priv *priv = dev_get_priv(clk->dev); + const struct mtk_clk_tree *tree = priv->tree; + + if (mtk_clk_id_is_pll(tree, clk->id)) + return apply ? mtk_pll_set_rate(clk, rate) : + mtk_pll_round_rate(clk, rate); + + if (mtk_clk_id_is_fclk(tree, clk->id)) + return tree->fclks[clk->id - tree->fclks_offs].rate; + + if (mtk_clk_id_is_fdiv(tree, clk->id)) + return mtk_factor_get_rate(clk, clk->id - tree->fdivs_offs); + + if (mtk_clk_id_is_mux(tree, clk->id)) + return mtk_clk_mux_rate_request(clk, rate, apply); + + if (mtk_clk_id_is_gate(tree, clk->id)) { + const struct mtk_gate *gate; + + gate = &tree->gates[clk->id - tree->gates_offs]; + + return mtk_clk_parent_rate_request(clk, gate->parent, + gate->flags, rate, apply); + } + + return -ENOENT; +} + +static ulong mtk_clk_round_rate(struct clk *clk, ulong rate) +{ + return mtk_clk_rate_request(clk, rate, false); +} + +static ulong mtk_clk_set_rate(struct clk *clk, ulong rate) +{ + return mtk_clk_rate_request(clk, rate, true); +} + static int mtk_clk_mux_enable(struct clk *clk) { struct mtk_clk_priv *priv = dev_get_priv(clk->dev); @@ -862,6 +1034,7 @@ const struct clk_ops mtk_clk_ops = { .enable = mtk_clk_enable, .disable = mtk_clk_disable, .set_rate = mtk_clk_set_rate, + .round_rate = mtk_clk_round_rate, .get_rate = mtk_clk_get_rate, .set_parent = mtk_common_clk_set_parent, #if CONFIG_IS_ENABLED(CMD_CLK) -- 2.53.0
