rockchip: clk: Add support for clocks needed by the displays
authorSimon Glass <sjg@chromium.org>
Fri, 22 Jan 2016 02:45:02 +0000 (19:45 -0700)
committerSimon Glass <sjg@chromium.org>
Fri, 22 Jan 2016 03:42:36 +0000 (20:42 -0700)
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>
drivers/clk/clk_rk3288.c

index ed39b934b1012b352c9ddcaebfc405e05a362283..ad7c34f445b30a644e4c2e9cc9f39cd4880239b0 100644 (file)
@@ -235,6 +235,124 @@ static int rkclk_configure_ddr(struct rk3288_cru *cru, struct rk3288_grf *grf,
        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_get_periph_rate(struct udevice *dev, int periph)
 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 @@ static ulong rk3288_set_periph_rate(struct udevice *dev, int periph, ulong rate)
        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;
        }