[PATCH] clk: ti: Add support for basic clk_set_rate for dm814x and j5-eco ADPLL

Tony Lindgren tony at atomide.com
Fri May 13 09:40:53 PDT 2016


Here's basic clk_set_rate support for the dm814x and j5-eco ADPLL.

Note that to clk_get the ADPLL clocks without a DTS phandle, you need to
use the generated names like "pll1a0dco", "pll1a0m2" and "pll1a0clkout".
The ADPLL driver is now enabled for dm814x and j5-eco starting with
v4.6-rc6, so there are no longer other pending dependencies.

Signed-off-by: Tony Lindgren <tony at atomide.com>
---
 drivers/clk/ti/adpll.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 182 insertions(+)

diff --git a/drivers/clk/ti/adpll.c b/drivers/clk/ti/adpll.c
index 255cafb..ff8ecfa 100644
--- a/drivers/clk/ti/adpll.c
+++ b/drivers/clk/ti/adpll.c
@@ -17,6 +17,7 @@
 #include <linux/math64.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
+#include <linux/rational.h>
 #include <linux/string.h>
 
 #define ADPLL_PLLSS_MMR_LOCK_OFFSET	0x00	/* Managed by MPPULL */
@@ -54,12 +55,17 @@
 #define ADPLL_M2NDIV_M2			16
 #define ADPLL_M2NDIV_M2_ADPLL_S_WIDTH	5
 #define ADPLL_M2NDIV_M2_ADPLL_LJ_WIDTH	7
+#define TI_ADPLL_DIV_N_MAX		GENMASK(7, 0)
 
 #define ADPLL_MN2DIV_OFFSET		0x14
 #define ADPLL_MN2DIV_N2			16
+#define TI_ADPLL_MIN_MULT_M		2
+#define TI_ADPLL_MULT_M_MAX		(GENMASK(11, 0) + 1)
 
 #define ADPLL_FRACDIV_OFFSET		0x18
 #define ADPLL_FRACDIV_REGSD		24
+#define TI814X_ADPLLJ_MIN_SD_DIV	2
+#define TI814X_ADPLLJ_MAX_SD_DIV	255
 #define ADPLL_FRACDIV_FRACTIONALM	0
 #define ADPLL_FRACDIV_FRACTIONALM_MASK	0x3ffff
 
@@ -480,6 +486,180 @@ static unsigned long ti_adpll_recalc_rate(struct clk_hw *hw,
 	return rate;
 }
 
+static long ti_adpll_calc_n_m(struct ti_adpll_dco_data *dco,
+			      unsigned long rate,
+			      unsigned long parent_rate,
+			      u8 *div_n, u16 *mult_m, u16 *frac_m)
+{
+	struct ti_adpll_data *d = to_adpll(dco);
+	unsigned long dcorate, m, n;
+	u32 v;
+	u64 tmp64;
+
+	if (!rate)
+		return 0;
+
+	dcorate = parent_rate;
+	if (d->c->is_type_s) {
+		v = readl_relaxed(d->regs + ADPLL_CLKCTRL_OFFSET);
+		if (v & BIT(ADPLL_CLKCTRL_REGM4XEN_ADPLL_S))
+			dcorate /= 4;
+		dcorate /= 2;
+	}
+
+	/* TRM table "DPLLLJ Frequency Factors" sets the minimum M at 2 */
+	if (rate < TI_ADPLL_MIN_MULT_M * dcorate)
+		return 0;
+
+	/* Ratio for integer multiplier M and pre-divider N */
+	rational_best_approximation(rate, dcorate, TI_ADPLL_MULT_M_MAX,
+				    TI_ADPLL_DIV_N_MAX, &m, &n);
+	if (m < TI_ADPLL_MIN_MULT_M) {
+		m = TI_ADPLL_MIN_MULT_M;
+		n = rate / (dcorate * m);
+		if (n > TI_ADPLL_DIV_N_MAX) {
+			dev_err(d->dev, "div_n overflow for rate %li\n", rate);
+			return 0;
+		}
+	}
+
+	/* Calculate fractional part for multiplier M */
+	dcorate /= n;
+	tmp64 = rate % dcorate;
+	tmp64 *= 10000000;
+	do_div(tmp64, dcorate);
+
+	*div_n = n - 1;
+	*mult_m = m;
+	*frac_m = tmp64;
+
+	return rate;
+}
+
+/*
+ * Sets the sigma-delta divider targeting near 250Mhz. Adapted
+ * from TI 2.6.37 kernel tree adpll_ti814x.c. Maybe we can
+ * make the SD_DIV_TARGET_MHZ configurable also?
+ */
+#define SD_DIV_TARGET_MHZ	250
+static int ti_adpll_lookup_sddiv(struct ti_adpll_data *d,
+				 unsigned long parent_rate,
+				 u8 *sd_div, u16 m, u8 n)
+{
+	unsigned long clkinp, sd;
+	int mod1, mod2;
+
+	clkinp = parent_rate / 100000;
+	mod1 = (clkinp * m) % (SD_DIV_TARGET_MHZ * n);
+	sd = (clkinp * m) / (SD_DIV_TARGET_MHZ * n);
+	mod2 = sd % 10;
+	sd /= 10;
+
+	if (mod1 || mod2)
+		sd++;
+	if ((sd >= TI814X_ADPLLJ_MIN_SD_DIV) &&
+	    (sd <= TI814X_ADPLLJ_MAX_SD_DIV)) {
+		*sd_div = sd;
+		return 0;
+	}
+
+	*sd_div = 0;
+	dev_err(d->dev, "sigma-delta out of range: %li\n", sd);
+
+	return -EINVAL;
+}
+
+static long ti_adpll_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *parent_rate)
+{
+	struct ti_adpll_dco_data *dco = to_dco(hw);
+	struct ti_adpll_data *d = to_adpll(dco);
+	long sd_min_rate;
+	u16 mult_m, frac_m;
+	u8 div_n;
+
+	sd_min_rate = (SD_DIV_TARGET_MHZ + 10) * 1000000;
+	/* Limited by sigma-delta somewhere, see above */
+	if (!d->c->is_type_s && rate < sd_min_rate) {
+		dev_warn(d->dev, "unsupported rate: %lu\n", rate);
+		return sd_min_rate;
+	}
+
+	return ti_adpll_calc_n_m(dco, rate, *parent_rate, &div_n,
+				 &mult_m, &frac_m);
+}
+
+static int ti_adpll_set_rate(struct clk_hw *hw, unsigned long rate,
+			     unsigned long parent_rate)
+{
+	struct ti_adpll_dco_data *dco = to_dco(hw);
+	struct ti_adpll_data *d = to_adpll(dco);
+	u16 mult_m, frac_m = 0;
+	u8 div_n, div_sd = 0;
+	unsigned long flags;
+	long new_rate;
+	int err;
+	u32 v;
+
+	new_rate = ti_adpll_calc_n_m(dco, rate, parent_rate,
+				     &div_n, &mult_m, &frac_m);
+	ti_adpll_set_idle_bypass(d);
+
+	spin_lock_irqsave(&d->lock, flags);
+	writeb_relaxed(0, d->regs + ADPLL_CLKCTRL_OFFSET);
+
+	/* Check sigma-delta divider, otherwise PLL won't lock */
+	if (!d->c->is_type_s) {
+		err = ti_adpll_lookup_sddiv(d, parent_rate,
+					    &div_sd, mult_m, div_n + 1);
+		if (err)
+			goto fail;
+	}
+
+	/* Set integer multiplier M */
+	writew_relaxed(mult_m, d->regs + ADPLL_MN2DIV_OFFSET);
+
+	/* Set ractional multiplier M and sigma-delta divider */
+	v = frac_m;
+	v |= (div_sd << 24);
+	writel_relaxed(v, d->regs + ADPLL_FRACDIV_OFFSET);
+
+	/* Configure SELFREQDCO */
+	if (!d->c->is_type_s) {
+		if (rate < 1000000000)
+			v = 2;
+		else
+			v = 4;
+		writeb_relaxed(v << 2, d->regs + ADPLL_CLKCTRL_OFFSET + 1);
+	}
+
+	dev_info(d->dev,
+		 "clkin: dco: %lu %lu N: %i + 1 M: %i Mf: %i sddiv: %i r: %i\n",
+		 parent_rate, rate, div_n, mult_m, frac_m, div_sd, v);
+
+	/* Set pre-devider N */
+	writeb_relaxed(div_n, d->regs + ADPLL_M2NDIV_OFFSET);
+
+	writeb_relaxed(1, d->regs + ADPLL_TENABLE_OFFSET);
+	writeb_relaxed(0, d->regs + ADPLL_TENABLE_OFFSET);
+	writeb_relaxed(1, d->regs + ADPLL_TENABLEDIV_OFFSET);
+	writeb_relaxed(0, d->regs + ADPLL_TENABLEDIV_OFFSET);
+	writeb_relaxed(1, d->regs + ADPLL_CLKCTRL_OFFSET);
+	spin_unlock_irqrestore(&d->lock, flags);
+
+	ti_adpll_clear_idle_bypass(d);
+
+	return ti_adpll_wait_lock(d);
+
+fail:
+	writeb_relaxed(1, d->regs + ADPLL_CLKCTRL_OFFSET);
+	spin_unlock_irqrestore(&d->lock, flags);
+	ti_adpll_clear_idle_bypass(d);
+	ti_adpll_wait_lock(d);
+
+	return err;
+}
+
 /* PLL parent is always clkinp, bypass only affects the children */
 static u8 ti_adpll_get_parent(struct clk_hw *hw)
 {
@@ -491,6 +671,8 @@ static struct clk_ops ti_adpll_ops = {
 	.unprepare = ti_adpll_unprepare,
 	.is_prepared = ti_adpll_is_prepared,
 	.recalc_rate = ti_adpll_recalc_rate,
+	.round_rate = ti_adpll_round_rate,
+	.set_rate = ti_adpll_set_rate,
 	.get_parent = ti_adpll_get_parent,
 };
 
-- 
2.8.1




More information about the linux-arm-kernel mailing list