rockchip: clk: Add support for clocks needed by the displays

The displays need to use NPLL and also select some new peripheral clocks.
Add support for these to the clock driver.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/clk/clk_rk3288.c b/drivers/clk/clk_rk3288.c
index ed39b93..ad7c34f 100644
--- a/drivers/clk/clk_rk3288.c
+++ b/drivers/clk/clk_rk3288.c
@@ -235,6 +235,124 @@
 	return 0;
 }
 
+#ifndef CONFIG_SPL_BUILD
+#define VCO_MAX_KHZ	2200000
+#define VCO_MIN_KHZ	440000
+#define FREF_MAX_KHZ	2200000
+#define FREF_MIN_KHZ	269
+
+static int pll_para_config(ulong freq_hz, struct pll_div *div, uint *ext_div)
+{
+	uint ref_khz = OSC_HZ / 1000, nr, nf = 0;
+	uint fref_khz;
+	uint diff_khz, best_diff_khz;
+	const uint max_nr = 1 << 6, max_nf = 1 << 12, max_no = 1 << 4;
+	uint vco_khz;
+	uint no = 1;
+	uint freq_khz = freq_hz / 1000;
+
+	if (!freq_hz) {
+		printf("%s: the frequency can not be 0 Hz\n", __func__);
+		return -EINVAL;
+	}
+
+	no = DIV_ROUND_UP(VCO_MIN_KHZ, freq_khz);
+	if (ext_div) {
+		*ext_div = DIV_ROUND_UP(no, max_no);
+		no = DIV_ROUND_UP(no, *ext_div);
+	}
+
+	/* only even divisors (and 1) are supported */
+	if (no > 1)
+		no = DIV_ROUND_UP(no, 2) * 2;
+
+	vco_khz = freq_khz * no;
+	if (ext_div)
+		vco_khz *= *ext_div;
+
+	if (vco_khz < VCO_MIN_KHZ || vco_khz > VCO_MAX_KHZ || no > max_no) {
+		printf("%s: Cannot find out a supported VCO for Frequency (%luHz).\n",
+		       __func__, freq_hz);
+		return -1;
+	}
+
+	div->no = no;
+
+	best_diff_khz = vco_khz;
+	for (nr = 1; nr < max_nr && best_diff_khz; nr++) {
+		fref_khz = ref_khz / nr;
+		if (fref_khz < FREF_MIN_KHZ)
+			break;
+		if (fref_khz > FREF_MAX_KHZ)
+			continue;
+
+		nf = vco_khz / fref_khz;
+		if (nf >= max_nf)
+			continue;
+		diff_khz = vco_khz - nf * fref_khz;
+		if (nf + 1 < max_nf && diff_khz > fref_khz / 2) {
+			nf++;
+			diff_khz = fref_khz - diff_khz;
+		}
+
+		if (diff_khz >= best_diff_khz)
+			continue;
+
+		best_diff_khz = diff_khz;
+		div->nr = nr;
+		div->nf = nf;
+	}
+
+	if (best_diff_khz > 4 * 1000) {
+		printf("%s: Failed to match output frequency %lu, difference is %u Hz, exceed 4MHZ\n",
+		       __func__, freq_hz, best_diff_khz * 1000);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rockchip_vop_set_clk(struct rk3288_cru *cru, struct rk3288_grf *grf,
+				int periph, unsigned int rate_hz)
+{
+	struct pll_div npll_config = {0};
+	u32 lcdc_div;
+	int ret;
+
+	ret = pll_para_config(rate_hz, &npll_config, &lcdc_div);
+	if (ret)
+		return ret;
+
+	rk_clrsetreg(&cru->cru_mode_con, NPLL_MODE_MASK << NPLL_MODE_SHIFT,
+		     NPLL_MODE_SLOW << NPLL_MODE_SHIFT);
+	rkclk_set_pll(cru, CLK_NEW, &npll_config);
+
+	/* waiting for pll lock */
+	while (1) {
+		if (readl(&grf->soc_status[1]) & SOCSTS_NPLL_LOCK)
+			break;
+		udelay(1);
+	}
+
+	rk_clrsetreg(&cru->cru_mode_con, NPLL_MODE_MASK << NPLL_MODE_SHIFT,
+		     NPLL_MODE_NORMAL << NPLL_MODE_SHIFT);
+
+	/* vop dclk source clk: npll,dclk_div: 1 */
+	switch (periph) {
+	case DCLK_VOP0:
+		rk_clrsetreg(&cru->cru_clksel_con[27], 0xff << 8 | 3 << 0,
+			     (lcdc_div - 1) << 8 | 2 << 0);
+		break;
+	case DCLK_VOP1:
+		rk_clrsetreg(&cru->cru_clksel_con[29], 0xff << 8 | 3 << 6,
+			     (lcdc_div - 1) << 8 | 2 << 6);
+		break;
+	}
+
+	return 0;
+}
+#endif
+
 #ifdef CONFIG_SPL_BUILD
 static void rkclk_init(struct rk3288_cru *cru, struct rk3288_grf *grf)
 {
@@ -559,6 +677,7 @@
 static ulong rk3288_set_periph_rate(struct udevice *dev, int periph, ulong rate)
 {
 	struct rk3288_clk_priv *priv = dev_get_priv(dev);
+	struct rk3288_cru *cru = priv->cru;
 	struct udevice *gclk;
 	ulong new_rate, gclk_rate;
 	int ret;
@@ -571,15 +690,62 @@
 	case HCLK_EMMC:
 	case HCLK_SDMMC:
 	case HCLK_SDIO0:
-		new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate, periph,
-						rate);
+		new_rate = rockchip_mmc_set_clk(cru, gclk_rate, periph, rate);
 		break;
 	case SCLK_SPI0:
 	case SCLK_SPI1:
 	case SCLK_SPI2:
-		new_rate = rockchip_spi_set_clk(priv->cru, gclk_rate, periph,
-						rate);
+		new_rate = rockchip_spi_set_clk(cru, gclk_rate, periph, rate);
 		break;
+#ifndef CONFIG_SPL_BUILD
+	case DCLK_VOP0:
+	case DCLK_VOP1:
+		new_rate = rockchip_vop_set_clk(cru, priv->grf, periph, rate);
+		break;
+	case SCLK_EDP_24M:
+		/* clk_edp_24M source: 24M */
+		rk_setreg(&cru->cru_clksel_con[28], 1 << 15);
+
+		/* rst edp */
+		rk_setreg(&cru->cru_clksel_con[6], 1 << 15);
+		udelay(1);
+		rk_clrreg(&cru->cru_clksel_con[6], 1 << 15);
+		new_rate = rate;
+		break;
+	case ACLK_VOP0:
+	case ACLK_VOP1: {
+		u32 div;
+
+		/* vop aclk source clk: cpll */
+		div = CPLL_HZ / rate;
+		assert((div - 1 < 64) && (div * rate == CPLL_HZ));
+
+		switch (periph) {
+		case ACLK_VOP0:
+			rk_clrsetreg(&cru->cru_clksel_con[31],
+				     3 << 6 | 0x1f << 0,
+				     0 << 6 | (div - 1) << 0);
+			break;
+		case ACLK_VOP1:
+			rk_clrsetreg(&cru->cru_clksel_con[31],
+				     3 << 14 | 0x1f << 8,
+				     0 << 14 | (div - 1) << 8);
+			break;
+		}
+		new_rate = rate;
+		break;
+	}
+	case PCLK_HDMI_CTRL:
+		/* enable pclk hdmi ctrl */
+		rk_clrreg(&cru->cru_clkgate_con[16], 1 << 9);
+
+		/* software reset hdmi */
+		rk_setreg(&cru->cru_clkgate_con[7], 1 << 9);
+		udelay(1);
+		rk_clrreg(&cru->cru_clkgate_con[7], 1 << 9);
+		new_rate = rate;
+		break;
+#endif
 	default:
 		return -ENOENT;
 	}