Re: [RFC PATCH v2 2/3] clk: qcom: implement RCG2 'parked' clock support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Quoting Dmitry Baryshkov (2023-10-03 18:23:07)
> clk_rcg2_shared_ops implements support for the case of the RCG which
> must not be completely turned off. However its design has one major
> drawback: it doesn't allow us to properly implement the is_enabled
> callback, which causes different kinds of misbehaviour from the CCF.
> 
> Follow the idea behind clk_regmap_phy_mux_ops and implement the new
> clk_rcg2_parked_ops. It also targets the clocks which must not be fully
> switched off (and shared most of the implementation with
> clk_rcg2_shared_ops). The major difference is that it requires that the
> parent map doesn't conain the safe (parked) clock source. Instead if the
> CFG_REG register points to the safe source, the clock is considered to
> be disabled.
> 
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx>
> ---
>  drivers/clk/qcom/clk-rcg.h  |  1 +
>  drivers/clk/qcom/clk-rcg2.c | 56 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 57 insertions(+)
> 
> diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h
> index e6d84c8c7989..9fbbf1251564 100644
> --- a/drivers/clk/qcom/clk-rcg.h
> +++ b/drivers/clk/qcom/clk-rcg.h
> @@ -176,6 +176,7 @@ extern const struct clk_ops clk_byte2_ops;
>  extern const struct clk_ops clk_pixel_ops;
>  extern const struct clk_ops clk_gfx3d_ops;
>  extern const struct clk_ops clk_rcg2_shared_ops;
> +extern const struct clk_ops clk_rcg2_parked_ops;
>  extern const struct clk_ops clk_dp_ops;
>  
>  struct clk_rcg_dfs_data {
> diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c
> index 5183c74b074f..fc75e2bc2d70 100644
> --- a/drivers/clk/qcom/clk-rcg2.c
> +++ b/drivers/clk/qcom/clk-rcg2.c
> @@ -5,6 +5,7 @@
>  
>  #include <linux/kernel.h>
>  #include <linux/bitops.h>
> +#include <linux/bitfield.h>
>  #include <linux/err.h>
>  #include <linux/bug.h>
>  #include <linux/export.h>
> @@ -1150,6 +1151,61 @@ const struct clk_ops clk_rcg2_shared_ops = {
>  };
>  EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops);
>  
> +static int clk_rcg2_parked_is_enabled(struct clk_hw *hw)
> +{
> +       struct clk_rcg2 *rcg = to_clk_rcg2(hw);
> +       u32 cmd, cfg;
> +       int ret;
> +
> +       ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, &cmd);
> +       if (ret)
> +               return ret;
> +
> +       if ((cmd & CMD_ROOT_EN) == 0)
> +               return false;

return 0?

CMD_ROOT_OFF can be 0 and CMD_ROOT_EN can also be 0 at the same time.
When that happens, some branch child clk is enabled and the rcg is
actually enabled. There's a hardware feedback mechanism from the
branches to the rcg so the rcg is guaranteed enabled. I'm trying to say
that this bit is unreliable on its own, so we need to take care here. In
fact, this bit is only used as a software override to make sure the
branches don't turn off the rcg inadvertently.

What if a branch is enabled, but the rcg root_en bit isn't set, and XO
is used? In that case, this will report the clk as disabled when it's
really enabled. That will look confusing to the clk framework because a
child will be enabled without the parent being enabled. Things will
probably still work though, because this only matters during disabling
unused clks.

Maybe it's better to not implement an is_enabled() callback for this clk
and simply call a function to see which parent the hardware is using (XO
or not). Basically don't go through clk_hw_is_enabled() and just call
clk_rcg2_parked_is_enabled() directly wherever the clk_hw API is used.
Then the framework doesn't get confused about enabled children with
disabled parents, but the downside is that the framework doesn't know if
the rcg is enabled. This is most likely fine though because an enabled
rcg doesn't really make a difference. The important thing is knowing
which branches are enabled at the framework level. Furthermore, the
framework doesn't currently handle propagating up the enable state at
boot to parents, so if say we have a child branch that is enabled, the
enable state of the parent _must_ be enabled as well, or the branch is
wedged and the only way to unwedge that is to enable the parent. It's
quite a mess!

Long story short, I question why we need to implement is_enabled() for
this clk. What's the benefit? The branches being off is more important
if we're concerned about saving power. There's the problem of handing
off enable state from when the driver probes, but that's not so easy to
solve given that a branch could be enabled (or a branch could be enabled
that isn't even known to linux). And it also sort of doesn't matter
because we know XO is practically always enabled and what really matters
is making sure the driver can't wedge the RCG by changing the source to
something that isn't enabled if it thinks the RCG is disabled when it is
really enabled.

That's sort of the only rule here, don't write the hardware when the
current parent isn't enabled or the new parent isn't enabled. We don't
know if the rcg is ever enabled, so we can only write the "go bit"
(CMD_UPDATE) when we're 100% certain that the parent (or next parent
when switching) is enabled. XO we know is always enabled, but otherwise
we don't know unless the framework has enabled the clk (and therefore
implicitly enabled the parent). The set_rate op could be called from
either enabled or disabled state, same for the set_parent op. And we
want the other clk APIs to report the state of the clk (like the parent
or rate) even if the hardware hasn't been changed.

> +
> +       ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg);
> +       if (ret)
> +               return ret;
> +
> +       return FIELD_GET(CFG_SRC_SEL_MASK, cfg) != rcg->safe_src_index;
> +}
> +
> +static int clk_rcg2_parked_init(struct clk_hw *hw)
> +{
> +       struct clk_rcg2 *rcg = to_clk_rcg2(hw);
> +       const struct freq_tbl *f = rcg->freq_tbl;
> +
> +       regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &rcg->parked_cfg);

I need this part today to fix a stuck clk problem I see on trogdor.lazor
where during registration a call to clk_ops::get_parent() sees the clk
isn't enabled at boot (because there isn't a clk_ops::is_enabled()
function) so clk_rcg2_shared_get_parent() reads the parent from the
'parked_cfg' value, which is zero. If the hardware actually has non-zero
for the parent then the framework will get the wrong parent, which is
what happens on trogdor when the devmode screen is shown. The parent is
the display PLL instead of XO. I haven't dug far enough to understand
why disabling unused clks wedges the branch when we try to enable it
again, but not disabling unused clks fixes the problem or reading the
config register at registration to get the proper parent also fixes it.
I guess the problem is that we're switching the RCG value when we
shouldn't be doing that.

> +
> +       if (FIELD_GET(CFG_SRC_SEL_MASK, rcg->parked_cfg) != rcg->safe_src_index)
> +               return 0;
> +
> +       if (WARN_ON(!f) ||
> +           WARN_ON(qcom_find_src_cfg(hw, rcg->parent_map, f->src) == rcg->safe_src_index))
> +               return -EINVAL;
> +
> +       return __clk_rcg2_configure(rcg, f, &rcg->parked_cfg);

It would be good to have a comment above this like

	/*
	 * Dirty the rcg registers to point at the first frequency table
	 * entry which is guaranteed to not use the safe_src_index.
	 * Setting the rate of the clk with rcg registers containing the
	 * safe_src_index will confuse clk_rcg2_parked_is_enabled() as
	 * to the enable state and lead to actually changing the rate of
	 * the clk when it isn't enabled.
	 */

> +}
> +
> +/*
> + * Unlike clk_rcg2_shared_ops, the safe_src_index aka XO must NOT be present in
> + * parent_map. This allows us to implement proper is_enabled callback.

We could also modify clk_ops::set_rate() and clk_ops::determine_rate()
to ignore frequency table entries with the safe_src_index, so that no
driver can change the frequency to be XO. Then XO is still "reserved",
and it still means the clk is disabled when the parent is XO, but we
don't have to change the RCG registers during clk_rcg2_parked_init() to
move off the safe_src/XO parent. We also have to prevent the parent from
being set to XO with clk_set_parent(). That should be doable by failing
the clk_ops::set_parent() op when the parent is XO.

I'd actually prefer that approach if it's workable, so that we don't
dirty the RCG registers during clk registration. I think qcom folks were
unhappy with the rcg registers being dirty for a long time
(CMD_DIRTY_CFG), because the other entity (the gdsc?) was triggering the
rcg switch (CMD_UPDATE) and that was causing the wrong parent to be
used.

I still come back to the why question though. What are we gaining by
implementing is_enabled for this clk?

> + */
> +const struct clk_ops clk_rcg2_parked_ops = {
> +       .init = clk_rcg2_parked_init,
> +       .is_enabled = clk_rcg2_parked_is_enabled,
> +       .enable = clk_rcg2_shared_enable,
> +       .disable = clk_rcg2_shared_disable,
> +       .get_parent = clk_rcg2_shared_get_parent,
> +       .set_parent = clk_rcg2_shared_set_parent,
> +       .recalc_rate = clk_rcg2_shared_recalc_rate,
> +       .determine_rate = clk_rcg2_determine_rate,
> +       .set_rate = clk_rcg2_shared_set_rate,
> +       .set_rate_and_parent = clk_rcg2_shared_set_rate_and_parent,
> +};
> +EXPORT_SYMBOL_GPL(clk_rcg2_parked_ops);




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux