clk: mdss: shutdown 20nm PHY pll properly to fix power issue

The second DSI PLL is consuming power when it is in reset
state. Configure the needed registers to shutdown the
second DSI PLL properly even though its not been used.
Add these register configurations whenever mdss gdsc
is toggled.

Change-Id: I008bc102795ccb5991bf4b61545c2d672b453392
Signed-off-by: Chandan Uddaraju <chandanu@codeaurora.org>
Signed-off-by: Siddhartha Agrawal <agrawals@codeaurora.org>
diff --git a/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c b/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c
index e609330..fd8a39e 100644
--- a/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c
+++ b/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c
@@ -481,6 +481,19 @@
 	return pll_locked;
 }
 
+void __dsi_pll_disable(void __iomem *pll_base)
+{
+	if (!pll_base) {
+		pr_err("Invalid pll base\n");
+		return;
+	}
+	pr_debug("Disabling PHY PLL for PLL_BASE=%p\n", pll_base);
+
+	MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x042);
+	MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x02);
+	MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x02);
+}
+
 static int dsi_pll_enable(struct clk *c)
 {
 	int i, rc;
@@ -501,6 +514,9 @@
 		if (!rc)
 			break;
 	}
+	/* Disable PLL1 to avoid current leakage while toggling MDSS GDSC */
+	if (dsi_pll_res->pll_1_base)
+		__dsi_pll_disable(dsi_pll_res->pll_1_base);
 
 	if (rc) {
 		mdss_pll_resource_enable(dsi_pll_res, false);
@@ -524,8 +540,11 @@
 
 	dsi_pll_res->handoff_resources = false;
 
-	MDSS_PLL_REG_W(dsi_pll_res->pll_base,
-				MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x02);
+	__dsi_pll_disable(dsi_pll_res->pll_base);
+
+	/* Disable PLL1 to avoid current leakage while toggling MDSS GDSC */
+	if (dsi_pll_res->pll_1_base)
+		__dsi_pll_disable(dsi_pll_res->pll_1_base);
 
 	mdss_pll_resource_enable(dsi_pll_res, false);
 	dsi_pll_res->pll_on = false;
@@ -779,6 +798,8 @@
 	udelay(1000);
 	/* memory barrier */
 	wmb();
+	if (dsi_pll_res->pll_1_base)
+		__dsi_pll_disable(dsi_pll_res->pll_1_base);
 	return 0;
 }
 
diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c
index 6705602..c5ef107 100644
--- a/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c
+++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c
@@ -18,6 +18,7 @@
 #include <linux/delay.h>
 #include <linux/clk/msm-clk-provider.h>
 #include <linux/clk/msm-clk.h>
+#include <linux/workqueue.h>
 #include <linux/clk/msm-clock-generic.h>
 #include <dt-bindings/clock/msm-clocks-8994.h>
 
@@ -47,6 +48,8 @@
 		return rc;
 	}
 
+	pr_debug("Cancel pending pll off work\n");
+	cancel_work_sync(&dsi_pll_res->pll_off);
 	rc = pll_20nm_vco_set_rate(vco, rate);
 
 	mdss_pll_resource_enable(dsi_pll_res, false);
@@ -453,10 +456,58 @@
 	CLK_LIST(shadow_dsi_vco_clk_8994),
 };
 
+static void dsi_pll_off_work(struct work_struct *work)
+{
+	struct mdss_pll_resources *pll_res;
+
+	if (!work) {
+		pr_err("pll_resource is invalid\n");
+		return;
+	}
+
+	pr_debug("Starting PLL off Worker%s\n", __func__);
+
+	pll_res = container_of(work, struct
+			mdss_pll_resources, pll_off);
+
+	mdss_pll_resource_enable(pll_res, true);
+	__dsi_pll_disable(pll_res->pll_base);
+	if (pll_res->pll_1_base)
+		__dsi_pll_disable(pll_res->pll_1_base);
+	mdss_pll_resource_enable(pll_res, false);
+}
+
+static int dsi_pll_regulator_notifier_call(struct notifier_block *self,
+		unsigned long event, void *data)
+{
+
+	struct mdss_pll_resources *pll_res;
+
+	if (!self) {
+		pr_err("pll_resource is invalid\n");
+		goto error;
+	}
+
+	pll_res = container_of(self, struct
+			mdss_pll_resources, gdsc_cb);
+
+	if (event & REGULATOR_EVENT_ENABLE) {
+		pr_debug("Regulator ON event. Scheduling pll off worker\n");
+		schedule_work(&pll_res->pll_off);
+	}
+
+	if (event & REGULATOR_EVENT_DISABLE)
+		pr_debug("Regulator OFF event.\n");
+
+error:
+	return NOTIFY_OK;
+}
+
 int dsi_pll_clock_register_20nm(struct platform_device *pdev,
 				struct mdss_pll_resources *pll_res)
 {
 	int rc;
+	struct dss_vreg *pll_reg;
 
 	if (!pdev || !pdev->dev.of_node) {
 		pr_err("Invalid input parameters\n");
@@ -513,12 +564,23 @@
 	shadow_byte_clk_src_ops.prepare = dsi_pll_div_prepare;
 
 	if (pll_res->target_id == MDSS_PLL_TARGET_8994) {
+		pll_res->gdsc_cb.notifier_call =
+			dsi_pll_regulator_notifier_call;
+		INIT_WORK(&pll_res->pll_off, dsi_pll_off_work);
+
 		rc = of_msm_clock_register(pdev->dev.of_node,
 			mdss_dsi_pllcc_8994, ARRAY_SIZE(mdss_dsi_pllcc_8994));
 		if (rc) {
 			pr_err("Clock register failed\n");
 			rc = -EPROBE_DEFER;
 		}
+		pll_reg = mdss_pll_get_mp_by_reg_name(pll_res, "gdsc");
+		if (pll_reg) {
+			pr_debug("Registering for gdsc regulator events\n");
+			if (regulator_register_notifier(pll_reg->vreg,
+						&(pll_res->gdsc_cb)))
+				pr_err("Regulator notification registration failed!\n");
+		}
 	} else {
 		pr_err("Invalid target ID\n");
 		rc = -EINVAL;
diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll.h b/drivers/clk/qcom/mdss/mdss-dsi-pll.h
index 1ee2b58..1fef2fd 100644
--- a/drivers/clk/qcom/mdss/mdss-dsi-pll.h
+++ b/drivers/clk/qcom/mdss/mdss-dsi-pll.h
@@ -99,6 +99,7 @@
 int ndiv_set_div(struct div_clk *clk, int div);
 int shadow_ndiv_set_div(struct div_clk *clk, int div);
 int ndiv_get_div(struct div_clk *clk);
+void __dsi_pll_disable(void __iomem *pll_base);
 
 int set_mdss_pixel_mux_sel(struct mux_clk *clk, int sel);
 int get_mdss_pixel_mux_sel(struct mux_clk *clk);
diff --git a/drivers/clk/qcom/mdss/mdss-pll-util.c b/drivers/clk/qcom/mdss/mdss-pll-util.c
index 03b5b61..4d9cade9 100644
--- a/drivers/clk/qcom/mdss/mdss-pll-util.c
+++ b/drivers/clk/qcom/mdss/mdss-pll-util.c
@@ -47,6 +47,40 @@
 	return rc;
 }
 
+/**
+ * mdss_pll_get_mp_by_reg_name() -- Find power module by regulator name
+ *@pll_res: Pointer to the PLL resource
+ *@name: Regulator name as specified in the pll dtsi
+ *
+ * This is a helper function to retrieve the regulator information
+ * for each pll resource.
+ */
+struct dss_vreg *mdss_pll_get_mp_by_reg_name(struct mdss_pll_resources *pll_res
+		, char *name)
+{
+
+	struct dss_vreg *regulator = NULL;
+	int i;
+
+	if ((pll_res == NULL) || (pll_res->mp.vreg_config == NULL)) {
+		pr_err("%s Invalid PLL resource\n", __func__);
+		goto error;
+	}
+
+	regulator = pll_res->mp.vreg_config;
+
+	for (i = 0; i < pll_res->mp.num_vreg; i++) {
+		if (!strcmp(name, regulator->vreg_name)) {
+			pr_debug("Found regulator match for %s\n", name);
+			break;
+		}
+		regulator++;
+	}
+
+error:
+	return regulator;
+}
+
 void mdss_pll_util_resource_deinit(struct platform_device *pdev,
 					 struct mdss_pll_resources *pll_res)
 {
diff --git a/drivers/clk/qcom/mdss/mdss-pll.c b/drivers/clk/qcom/mdss/mdss-pll.c
index e434f62..115878a 100644
--- a/drivers/clk/qcom/mdss/mdss-pll.c
+++ b/drivers/clk/qcom/mdss/mdss-pll.c
@@ -249,6 +249,26 @@
 		goto res_parse_error;
 	}
 
+	/*
+	 * DSI PLL 1 is leaking current whenever MDSS GDSC is toggled. Need to
+	 * map PLL1 registers along with the PLl0 so that we can manually turn
+	 * off PLL1.
+	 */
+	if (pll_res->pll_interface_type == MDSS_DSI_PLL_20NM) {
+		struct resource *pll_1_base_reg;
+
+		pll_1_base_reg = platform_get_resource_byname(pdev,
+				IORESOURCE_MEM, "pll_1_base");
+		if (pll_1_base_reg) {
+			pll_res->pll_1_base = ioremap(pll_1_base_reg->start,
+					resource_size(pll_1_base_reg));
+			if (!pll_res->pll_1_base)
+				pr_err("Unable to remap pll 1 base resources\n");
+		} else {
+			pr_err("Unable to get the pll 1 base resource\n");
+		}
+	}
+
 	phy_base_reg = platform_get_resource_byname(pdev,
 						IORESOURCE_MEM, "phy_base");
 	if (!phy_base_reg) {
@@ -303,6 +323,8 @@
 	if (pll_res->phy_base)
 		iounmap(pll_res->phy_base);
 phy_io_error:
+	if (pll_res->pll_1_base)
+		iounmap(pll_res->pll_1_base);
 	mdss_pll_resource_release(pdev, pll_res);
 res_parse_error:
 	iounmap(pll_res->pll_base);
diff --git a/drivers/clk/qcom/mdss/mdss-pll.h b/drivers/clk/qcom/mdss/mdss-pll.h
index fe1ed8b..e90394a 100644
--- a/drivers/clk/qcom/mdss/mdss-pll.h
+++ b/drivers/clk/qcom/mdss/mdss-pll.h
@@ -54,6 +54,7 @@
 	 * register mapping
 	 */
 	void __iomem	*pll_base;
+	void __iomem	*pll_1_base;
 	void __iomem	*phy_base;
 	void __iomem	*dyn_pll_base;
 
@@ -107,6 +108,17 @@
 	 * for maintaining the status of saving trim codes
 	 */
 	bool		reg_upd;
+
+	/*
+	 * Notifier callback for MDSS gdsc regulator events
+	 */
+	struct notifier_block gdsc_cb;
+
+	/*
+	 * Worker function to call PLL off event
+	 */
+	struct work_struct pll_off;
+
 };
 
 struct mdss_pll_vco_calc {
@@ -131,4 +143,6 @@
 								bool enable);
 int mdss_pll_util_resource_parse(struct platform_device *pdev,
 				struct mdss_pll_resources *pll_res);
+struct dss_vreg *mdss_pll_get_mp_by_reg_name(struct mdss_pll_resources *pll_res
+		, char *name);
 #endif