diff mbox series

[6/7] clk: renesas: rcar-gen3: Add custom clock for PLLs

Message ID 20210326120100.1577596-7-geert+renesas@glider.be (mailing list archive)
State Accepted
Delegated to: Geert Uytterhoeven
Headers show
Series clk: renesas: rcar-gen3: Add support for CPU core clock boost modes | expand

Commit Message

Geert Uytterhoeven March 26, 2021, noon UTC
Currently the PLLs are modeled as fixed factor clocks, based on initial
settings.  However, enabling CPU boost clock rates requires increasing
the PLL clock rates.

Add a custom clock driver to model the PLL clocks.  This will allow the
Z (CPU) clock driver to request changing the PLL clock rate.

Based on a patch in the BSP by Takeshi Kihara
<takeshi.kihara.df@renesas.com>.

Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
---
 drivers/clk/renesas/rcar-gen3-cpg.c | 147 ++++++++++++++++++++++++----
 1 file changed, 128 insertions(+), 19 deletions(-)

Comments

Stephen Boyd March 30, 2021, 2:22 a.m. UTC | #1
Quoting Geert Uytterhoeven (2021-03-26 05:00:59)
> +}
> +
> +static int cpg_pll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long parent_rate)
> +{
> +       struct cpg_pll_clk *pll_clk = to_pll_clk(hw);
> +       unsigned int mult, i;
> +       u32 val;
> +
> +       mult = DIV_ROUND_CLOSEST_ULL(rate, parent_rate * pll_clk->fixed_mult);
> +       mult = clamp(mult, 1U, 128U);
> +
> +       val = readl(pll_clk->pllcr_reg);
> +       val &= ~CPG_PLLnCR_STC_MASK;
> +       val |= (mult - 1) << __ffs(CPG_PLLnCR_STC_MASK);
> +       writel(val, pll_clk->pllcr_reg);
> +
> +       for (i = 1000; i; i--) {
> +               if (readl(pll_clk->pllecr_reg) & pll_clk->pllecr_pllst_mask)
> +                       return 0;
> +
> +               cpu_relax();
> +       }

Why not readl_poll_timeout()?

> +
> +       return -ETIMEDOUT;
> +}
> +
Geert Uytterhoeven March 30, 2021, 7:02 a.m. UTC | #2
Hi Stephen,

On Tue, Mar 30, 2021 at 4:22 AM Stephen Boyd <sboyd@kernel.org> wrote:
> Quoting Geert Uytterhoeven (2021-03-26 05:00:59)
> > +}
> > +
> > +static int cpg_pll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> > +                               unsigned long parent_rate)
> > +{
> > +       struct cpg_pll_clk *pll_clk = to_pll_clk(hw);
> > +       unsigned int mult, i;
> > +       u32 val;
> > +
> > +       mult = DIV_ROUND_CLOSEST_ULL(rate, parent_rate * pll_clk->fixed_mult);
> > +       mult = clamp(mult, 1U, 128U);
> > +
> > +       val = readl(pll_clk->pllcr_reg);
> > +       val &= ~CPG_PLLnCR_STC_MASK;
> > +       val |= (mult - 1) << __ffs(CPG_PLLnCR_STC_MASK);
> > +       writel(val, pll_clk->pllcr_reg);
> > +
> > +       for (i = 1000; i; i--) {
> > +               if (readl(pll_clk->pllecr_reg) & pll_clk->pllecr_pllst_mask)
> > +                       return 0;
> > +
> > +               cpu_relax();
> > +       }
>
> Why not readl_poll_timeout()?

I had considered that.  But then I noticed readl_poll_timeout() does not
call cpu_relax() if sleep_us == 0.  Perhaps such a call should be added
(at the risk of introducing a subtle regression elsewhere)?

Gr{oetje,eeting}s,

                        Geert
Stephen Boyd April 1, 2021, 1:40 a.m. UTC | #3
Quoting Geert Uytterhoeven (2021-03-30 00:02:03)
> Hi Stephen,
> 
> On Tue, Mar 30, 2021 at 4:22 AM Stephen Boyd <sboyd@kernel.org> wrote:
> > Quoting Geert Uytterhoeven (2021-03-26 05:00:59)
> > > +}
> > > +
> > > +static int cpg_pll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> > > +                               unsigned long parent_rate)
> > > +{
> > > +       struct cpg_pll_clk *pll_clk = to_pll_clk(hw);
> > > +       unsigned int mult, i;
> > > +       u32 val;
> > > +
> > > +       mult = DIV_ROUND_CLOSEST_ULL(rate, parent_rate * pll_clk->fixed_mult);
> > > +       mult = clamp(mult, 1U, 128U);
> > > +
> > > +       val = readl(pll_clk->pllcr_reg);
> > > +       val &= ~CPG_PLLnCR_STC_MASK;
> > > +       val |= (mult - 1) << __ffs(CPG_PLLnCR_STC_MASK);
> > > +       writel(val, pll_clk->pllcr_reg);
> > > +
> > > +       for (i = 1000; i; i--) {
> > > +               if (readl(pll_clk->pllecr_reg) & pll_clk->pllecr_pllst_mask)
> > > +                       return 0;
> > > +
> > > +               cpu_relax();
> > > +       }
> >
> > Why not readl_poll_timeout()?
> 
> I had considered that.  But then I noticed readl_poll_timeout() does not
> call cpu_relax() if sleep_us == 0.  Perhaps such a call should be added
> (at the risk of introducing a subtle regression elsewhere)?
> 

Risk seems low. Maybe introduce a readl_poll_timeout_relaxed() if it's a
concern. At the least, a patch to document why it doesn't use
cpu_relax() right now would be good to have.
diff mbox series

Patch

diff --git a/drivers/clk/renesas/rcar-gen3-cpg.c b/drivers/clk/renesas/rcar-gen3-cpg.c
index 6b389c1caca76f07..cc9a116d7d093ce7 100644
--- a/drivers/clk/renesas/rcar-gen3-cpg.c
+++ b/drivers/clk/renesas/rcar-gen3-cpg.c
@@ -26,12 +26,127 @@ 
 #include "rcar-cpg-lib.h"
 #include "rcar-gen3-cpg.h"
 
-#define CPG_PLL0CR		0x00d8
+#define CPG_PLLECR		0x00d0	/* PLL Enable Control Register */
+
+#define CPG_PLLECR_PLLST(n)	BIT(8 + (n))	/* PLLn Circuit Status */
+
+#define CPG_PLL0CR		0x00d8	/* PLLn Control Registers */
 #define CPG_PLL2CR		0x002c
 #define CPG_PLL4CR		0x01f4
 
+#define CPG_PLLnCR_STC_MASK	GENMASK(30, 24)	/* PLL Circuit Mult. Ratio */
+
 #define CPG_RCKCR_CKSEL	BIT(15)	/* RCLK Clock Source Select */
 
+/* PLL Clocks */
+struct cpg_pll_clk {
+	struct clk_hw hw;
+	void __iomem *pllcr_reg;
+	void __iomem *pllecr_reg;
+	unsigned int fixed_mult;
+	u32 pllecr_pllst_mask;
+};
+
+#define to_pll_clk(_hw)   container_of(_hw, struct cpg_pll_clk, hw)
+
+static unsigned long cpg_pll_clk_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	struct cpg_pll_clk *pll_clk = to_pll_clk(hw);
+	unsigned int mult;
+	u32 val;
+
+	val = readl(pll_clk->pllcr_reg) & CPG_PLLnCR_STC_MASK;
+	mult = (val >> __ffs(CPG_PLLnCR_STC_MASK)) + 1;
+
+	return parent_rate * mult * pll_clk->fixed_mult;
+}
+
+static int cpg_pll_clk_determine_rate(struct clk_hw *hw,
+				      struct clk_rate_request *req)
+{
+	struct cpg_pll_clk *pll_clk = to_pll_clk(hw);
+	unsigned int min_mult, max_mult, mult;
+	unsigned long prate;
+
+	prate = req->best_parent_rate * pll_clk->fixed_mult;
+	min_mult = max(div64_ul(req->min_rate, prate), 1ULL);
+	max_mult = min(div64_ul(req->max_rate, prate), 128ULL);
+	if (max_mult < min_mult)
+		return -EINVAL;
+
+	mult = DIV_ROUND_CLOSEST_ULL(req->rate, prate);
+	mult = clamp(mult, min_mult, max_mult);
+
+	req->rate = prate * mult;
+	return 0;
+}
+
+static int cpg_pll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct cpg_pll_clk *pll_clk = to_pll_clk(hw);
+	unsigned int mult, i;
+	u32 val;
+
+	mult = DIV_ROUND_CLOSEST_ULL(rate, parent_rate * pll_clk->fixed_mult);
+	mult = clamp(mult, 1U, 128U);
+
+	val = readl(pll_clk->pllcr_reg);
+	val &= ~CPG_PLLnCR_STC_MASK;
+	val |= (mult - 1) << __ffs(CPG_PLLnCR_STC_MASK);
+	writel(val, pll_clk->pllcr_reg);
+
+	for (i = 1000; i; i--) {
+		if (readl(pll_clk->pllecr_reg) & pll_clk->pllecr_pllst_mask)
+			return 0;
+
+		cpu_relax();
+	}
+
+	return -ETIMEDOUT;
+}
+
+static const struct clk_ops cpg_pll_clk_ops = {
+	.recalc_rate = cpg_pll_clk_recalc_rate,
+	.determine_rate = cpg_pll_clk_determine_rate,
+	.set_rate = cpg_pll_clk_set_rate,
+};
+
+static struct clk * __init cpg_pll_clk_register(const char *name,
+						const char *parent_name,
+						void __iomem *base,
+						unsigned int mult,
+						unsigned int offset,
+						unsigned int index)
+
+{
+	struct cpg_pll_clk *pll_clk;
+	struct clk_init_data init = {};
+	struct clk *clk;
+
+	pll_clk = kzalloc(sizeof(*pll_clk), GFP_KERNEL);
+	if (!pll_clk)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = name;
+	init.ops = &cpg_pll_clk_ops;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	pll_clk->hw.init = &init;
+	pll_clk->pllcr_reg = base + offset;
+	pll_clk->pllecr_reg = base + CPG_PLLECR;
+	pll_clk->fixed_mult = mult;	/* PLL refclk x (setting + 1) x mult */
+	pll_clk->pllecr_pllst_mask = CPG_PLLECR_PLLST(index);
+
+	clk = clk_register(NULL, &pll_clk->hw);
+	if (IS_ERR(clk))
+		kfree(pll_clk);
+
+	return clk;
+}
+
 /*
  * Z Clock & Z2 Clock
  *
@@ -314,16 +429,13 @@  struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev,
 
 	case CLK_TYPE_GEN3_PLL0:
 		/*
-		 * PLL0 is a configurable multiplier clock. Register it as a
-		 * fixed factor clock for now as there's no generic multiplier
-		 * clock implementation and we currently have no need to change
-		 * the multiplier value.
+		 * PLL0 is implemented as a custom clock, to change the
+		 * multiplier when cpufreq changes between normal and boost
+		 * modes.
 		 */
-		value = readl(base + CPG_PLL0CR);
-		mult = (((value >> 24) & 0x7f) + 1) * 2;
-		if (cpg_quirks & PLL_ERRATA)
-			mult *= 2;
-		break;
+		mult = (cpg_quirks & PLL_ERRATA) ? 4 : 2;
+		return cpg_pll_clk_register(core->name, __clk_get_name(parent),
+					    base, mult, CPG_PLL0CR, 0);
 
 	case CLK_TYPE_GEN3_PLL1:
 		mult = cpg_pll_config->pll1_mult;
@@ -332,16 +444,13 @@  struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev,
 
 	case CLK_TYPE_GEN3_PLL2:
 		/*
-		 * PLL2 is a configurable multiplier clock. Register it as a
-		 * fixed factor clock for now as there's no generic multiplier
-		 * clock implementation and we currently have no need to change
-		 * the multiplier value.
+		 * PLL2 is implemented as a custom clock, to change the
+		 * multiplier when cpufreq changes between normal and boost
+		 * modes.
 		 */
-		value = readl(base + CPG_PLL2CR);
-		mult = (((value >> 24) & 0x7f) + 1) * 2;
-		if (cpg_quirks & PLL_ERRATA)
-			mult *= 2;
-		break;
+		mult = (cpg_quirks & PLL_ERRATA) ? 4 : 2;
+		return cpg_pll_clk_register(core->name, __clk_get_name(parent),
+					    base, mult, CPG_PLL2CR, 2);
 
 	case CLK_TYPE_GEN3_PLL3:
 		mult = cpg_pll_config->pll3_mult;