platform: msm_shared: Add support for alpha pll

Add support for alpha pll. Alpha pll is the clock source for
multimedia clocks. MMPLL0 uses alpha pll for generating required
rate for display clocks.

Change-Id: I70970b6bdc7be08d2b9305eed3f942840a426dea
diff --git a/platform/msm_shared/clock_alpha_pll.c b/platform/msm_shared/clock_alpha_pll.c
new file mode 100644
index 0000000..3a0c3e0
--- /dev/null
+++ b/platform/msm_shared/clock_alpha_pll.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of The Linux Foundation nor
+ *     the names of its contributors may be used to endorse or promote
+ *     products derived from this software without specific prior written
+ *     permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <debug.h>
+#include <reg.h>
+#include <bits.h>
+#include <limits.h>
+#include <clock.h>
+#include <clock_pll.h>
+#include <clock_alpha_pll.h>
+#include <arch/defines.h>
+#include <platform/timer.h>
+
+#define ALPHA_REG_BITWIDTH 40
+#define ALPHA_BITWIDTH 32
+
+static void set_fsm_mode(uint32_t mode_reg)
+{
+	uint32_t regval = readl_relaxed(mode_reg);
+
+	/* De-assert reset to FSM */
+	regval &= ~PLL_VOTE_FSM_RESET;
+	writel_relaxed(regval, mode_reg);
+
+	/* Program bias count */
+	regval &= ~BM(PLL_BIAS_COUNT_END, PLL_BIAS_COUNT_START);
+	regval |= BVAL(PLL_BIAS_COUNT_END, PLL_BIAS_COUNT_START, PLL_BIAS_COUNT);
+	writel_relaxed(regval, mode_reg);
+
+	/* Program lock count */
+	regval &= ~BM(PLL_LOCK_COUNT_END, PLL_LOCK_COUNT_START);
+	regval |= BVAL(PLL_LOCK_COUNT_END, PLL_LOCK_COUNT_START, 0x0);
+	writel_relaxed(regval, mode_reg);
+
+	/* Enable PLL FSM voting */
+	regval |= PLL_VOTE_FSM_ENA;
+	writel_relaxed(regval, mode_reg);
+}
+
+static inline struct alpha_pll_clk *to_alpha_pll_clk(struct clk *c)
+{
+	return container_of(c, struct alpha_pll_clk, c);
+}
+
+static void init_alpha_pll(struct clk *c)
+{
+	struct alpha_pll_clk *pll = to_alpha_pll_clk(c);
+	struct alpha_pll_masks *masks = pll->masks;
+	uint32_t output_en, userval;
+
+	if (masks->output_mask && pll->enable_config) {
+		output_en = readl_relaxed(OUTPUT_REG(pll));
+		output_en &= ~masks->output_mask;
+		output_en |= pll->enable_config;
+		writel_relaxed(output_en, OUTPUT_REG(pll));
+	}
+
+	if (masks->post_div_mask) {
+		userval = readl_relaxed(USER_CTL_LO_REG(pll));
+		userval &= ~masks->post_div_mask;
+		userval |= pll->post_div_config;
+		writel_relaxed(userval, USER_CTL_LO_REG(pll));
+	}
+
+	if (pll->fsm_en_mask)
+		set_fsm_mode(MODE_REG(pll));
+
+	pll->inited = true;
+}
+
+static bool is_active(struct alpha_pll_clk *pll)
+{
+	uint32_t reg = readl(ACTIVE_REG(pll));
+	uint32_t mask = pll->masks->active_mask;
+	return (reg & mask) == mask;
+}
+
+static bool is_locked(struct alpha_pll_clk *pll)
+{
+	uint32_t reg = readl(LOCK_REG(pll));
+	uint32_t mask = pll->masks->lock_mask;
+	return (reg & mask) == mask;
+}
+
+static bool is_fsm_mode(uint32_t mode_reg)
+{
+	return !!(readl(mode_reg) & PLL_VOTE_FSM_ENA);
+}
+
+static bool update_finish(struct alpha_pll_clk *pll)
+{
+	return is_active(pll);
+}
+
+static int wait_for_update(struct alpha_pll_clk *pll)
+{
+	uint32_t retry = 100;
+
+	while (retry)
+	{
+		if (update_finish(pll))
+			break;
+		udelay(1);
+		retry--;
+	}
+
+	if (!retry)
+	{
+		dprintf(CRITICAL, "%s didn't lock after enabling it!\n", pll->c.dbg_name);
+		return -1;
+	}
+
+	return 0;
+}
+
+static unsigned long compute_rate(struct alpha_pll_clk *pll,
+				uint32_t l_val, uint32_t a_val)
+{
+	uint64_t rate, parent_rate;
+
+	parent_rate = pll->parent->rate;
+	rate = parent_rate * l_val;
+	rate += (parent_rate * a_val) >> ALPHA_BITWIDTH;
+	return rate;
+}
+
+static uint32_t do_div(uint64_t *n, uint32_t divisor)
+{
+	uint32_t remainder = *n % divisor;
+	*n = *n / divisor;
+	return remainder;
+}
+
+static unsigned long calc_values(struct alpha_pll_clk *pll,
+		uint64_t rate, int *l_val, uint64_t *a_val, bool round_up)
+{
+	uint32_t parent_rate;
+	uint64_t remainder;
+	uint64_t quotient;
+	unsigned long freq_hz;
+
+	parent_rate = pll->parent->rate;
+	quotient = rate;
+	remainder = do_div(&quotient, parent_rate);
+
+	*l_val = quotient;
+
+	if (!remainder) {
+		*a_val = 0;
+		return rate;
+	}
+
+	/* Upper ALPHA_BITWIDTH bits of Alpha */
+	quotient = remainder << ALPHA_BITWIDTH;
+	remainder = do_div(&quotient, parent_rate);
+
+	if (remainder && round_up)
+		quotient++;
+
+	*a_val = quotient;
+	freq_hz = compute_rate(pll, *l_val, *a_val);
+	return freq_hz;
+}
+
+static unsigned long round_rate_up(struct alpha_pll_clk *pll,
+		unsigned long rate, int *l_val, uint64_t *a_val)
+{
+	return calc_values(pll, rate, l_val, a_val, true);
+}
+
+static uint32_t find_vco(struct alpha_pll_clk *pll, unsigned long rate)
+{
+	unsigned long i;
+	struct alpha_pll_vco_tbl *v = pll->vco_tbl;
+
+	for (i = 0; i < pll->vco_num; i++) {
+		if (rate >= v[i].min_freq && rate <= v[i].max_freq)
+			return v[i].vco_val;
+	}
+	return -1;
+}
+
+static int alpha_pll_set_rate(struct clk *c, unsigned long rate)
+{
+	struct alpha_pll_clk *pll = to_alpha_pll_clk(c);
+	struct alpha_pll_masks *masks = pll->masks;
+	unsigned long freq_hz;
+	int regval, l_val;
+	uint64_t a_val;
+	uint32_t vco_val;
+
+	freq_hz = round_rate_up(pll, rate, &l_val, &a_val);
+	if (freq_hz != rate) {
+		dprintf(CRITICAL, "alpha_pll: Call clk_set_rate with rounded rates!\n");
+		return -1;
+	}
+
+	vco_val = find_vco(pll, freq_hz);
+	if (!vco_val)
+	{
+		dprintf(CRITICAL, "alpha pll: VCO not valid\n");
+		return -1;
+	}
+
+	/*
+	 * Ensure PLL is off before changing rate. For optimization reasons,
+	 * assume no downstream clock is actively using it. No support
+	 * for dynamic update at the moment.
+	 */
+	if (c->count)
+		alpha_pll_disable(c);
+
+	a_val = a_val << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH);
+
+	writel_relaxed(l_val, L_REG(pll));
+	writel_relaxed(a_val, A_REG(pll));
+	a_val = (a_val >> ALPHA_BITWIDTH);
+	writel_relaxed(a_val, A_REG_U(pll));
+
+	if (masks->vco_mask) {
+		regval = readl_relaxed(VCO_REG(pll));
+		regval &= ~(masks->vco_mask << masks->vco_shift);
+		regval |= vco_val << masks->vco_shift;
+		writel_relaxed(regval, VCO_REG(pll));
+	}
+
+	regval = readl_relaxed(ALPHA_EN_REG(pll));
+	regval |= masks->alpha_en_mask;
+	writel_relaxed(regval, ALPHA_EN_REG(pll));
+
+	if (c->count)
+		alpha_pll_enable(c);
+
+	return 0;
+}
+
+int alpha_pll_enable(struct clk *c)
+{
+	struct alpha_pll_clk *pll = to_alpha_pll_clk(c);
+	uint32_t ena;
+
+	/* if PLL is not initialized already and is not in FSM state */
+	if (!pll->inited && !is_locked(pll))
+	{
+		if (c->rate && alpha_pll_set_rate(c, c->rate))
+			dprintf(INFO, "Warning: Failed to set rate for alpha pll\n");
+		init_alpha_pll(c);
+	}
+	else if (!is_fsm_mode(MODE_REG(pll)))
+	{
+		dprintf(INFO, "FSM mode is not set for: %s\n", c->dbg_name);
+	}
+
+	ena = readl_relaxed(VOTE_REG(pll));
+	ena |= pll->fsm_en_mask;
+	writel_relaxed(ena, VOTE_REG(pll));
+	dmb();
+
+	return wait_for_update(pll);
+}
+
+void alpha_pll_disable(struct clk *c)
+{
+	struct alpha_pll_clk *pll = to_alpha_pll_clk(c);
+	uint32_t ena;
+
+	ena = readl_relaxed(VOTE_REG(pll));
+	ena &= ~pll->fsm_en_mask;
+	writel_relaxed(ena, VOTE_REG(pll));
+	dmb();
+}
diff --git a/platform/msm_shared/clock_lib2.c b/platform/msm_shared/clock_lib2.c
index cc87c92..ad088cb 100644
--- a/platform/msm_shared/clock_lib2.c
+++ b/platform/msm_shared/clock_lib2.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -77,8 +77,9 @@
 	cbcr_val &= ~CBCR_BRANCH_ENABLE_BIT;
 	writel(cbcr_val, bclk->cbcr_reg);
 
-	/* wait until status shows it is disabled */
-	while(!(readl(bclk->cbcr_reg) & CBCR_BRANCH_OFF_BIT));
+	if (!bclk->no_halt_check_on_disable)
+		/* wait until status shows it is disabled */
+		while(!(readl(bclk->cbcr_reg) & CBCR_BRANCH_OFF_BIT));
 }
 
 /* Branch clock set rate */
diff --git a/platform/msm_shared/include/clock_alpha_pll.h b/platform/msm_shared/include/clock_alpha_pll.h
new file mode 100644
index 0000000..5c79089
--- /dev/null
+++ b/platform/msm_shared/include/clock_alpha_pll.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of The Linux Foundation nor
+ *     the names of its contributors may be used to endorse or promote
+ *     products derived from this software without specific prior written
+ *     permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CLK_APLHA_PLL
+#define CLK_APLHA_PLL
+
+#include <platform/iomap.h>
+#include <platform/clock.h>
+
+#define L_REG(pll) (pll->base + pll->offset + 0x4)
+#define A_REG(pll) (pll->base + pll->offset + 0x8)
+#define A_REG_U(pll) (pll->base + pll->offset + 0xC)
+#define ACTIVE_REG(pll) (pll->base + pll->offset + 0x0)
+#define VOTE_REG(pll) (pll->base + pll->fsm_reg_offset)
+#define VCO_REG(pll) (pll->base + pll->offset + 0x10)
+#define OUTPUT_REG(pll) (pll->base + pll->offset + 0x10)
+#define USER_CTL_LO_REG(pll) (pll->base + pll->offset + 0x10)
+#define MODE_REG(pll) (pll->base + pll->offset + 0x0)
+#define LOCK_REG(pll) (pll->base + pll->offset + 0x0)
+#define ALPHA_EN_REG(pll) (pll->base + pll->offset + 0x10)
+
+#define PLL_VOTE_FSM_ENA     BIT(20)
+#define PLL_VOTE_FSM_RESET   BIT(21)
+#define PLL_BIAS_COUNT_START 14
+#define PLL_BIAS_COUNT_END   19
+#define PLL_BIAS_COUNT       0x6
+#define PLL_LOCK_COUNT_START 8
+#define PLL_LOCK_COUNT_END   13
+
+#define VCO(a, b, c) { \
+	.vco_val = a,\
+	.min_freq = b,\
+	.max_freq = c,\
+}
+
+struct alpha_pll_vco_tbl {
+	uint32_t vco_val;
+	unsigned long min_freq;
+	unsigned long max_freq;
+};
+
+struct alpha_pll_masks {
+	uint32_t lock_mask;      /* lock_det bit */
+	uint32_t active_mask;    /* active_flag in FSM mode */
+	uint32_t update_mask;    /* update bit for dynamic update */
+	uint32_t vco_mask;       /* vco_sel bits */
+	uint32_t vco_shift;
+	uint32_t alpha_en_mask;  /* alpha_en bit */
+	uint32_t output_mask;    /* pllout_* bits */
+	uint32_t post_div_mask;
+};
+
+struct alpha_pll_clk {
+	struct alpha_pll_masks *masks;
+	uint32_t offset;
+	uint32_t base;
+	struct alpha_pll_vco_tbl *vco_tbl;
+	uint32_t vco_num;
+	/* if fsm_en_mask is set, config PLL to FSM mode */
+	uint32_t fsm_reg_offset;
+	uint32_t fsm_en_mask;
+	uint32_t enable_config;  /* bitmask of outputs to be enabled */
+	uint32_t post_div_config;    /* masked post divider setting */
+	bool inited;
+	struct clk *parent;
+	struct clk c;
+};
+
+void alpha_pll_disable(struct clk *c);
+int  alpha_pll_enable(struct clk *c);
+
+#endif
diff --git a/platform/msm_shared/include/clock_lib2.h b/platform/msm_shared/include/clock_lib2.h
index d353684..6b07d45 100644
--- a/platform/msm_shared/include/clock_lib2.h
+++ b/platform/msm_shared/include/clock_lib2.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2014, Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2015, Linux Foundation. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -150,6 +150,7 @@
 	uint32_t cur_div;
 	uint32_t max_div;
 	uint32_t halt_check;
+	bool no_halt_check_on_disable;
 };
 
 /* Root Clock */
diff --git a/platform/msm_shared/rules.mk b/platform/msm_shared/rules.mk
index 22427ac..a098c00 100644
--- a/platform/msm_shared/rules.mk
+++ b/platform/msm_shared/rules.mk
@@ -508,6 +508,7 @@
 			$(LOCAL_DIR)/interrupts.o \
 			$(LOCAL_DIR)/clock.o \
 			$(LOCAL_DIR)/clock_pll.o \
+			$(LOCAL_DIR)/clock_alpha_pll.o \
 			$(LOCAL_DIR)/clock_lib2.o \
 			$(LOCAL_DIR)/uart_dm.o \
 			$(LOCAL_DIR)/board.o \