rockchip: clk: rk3368: implement MMC/SD clock reparenting
authorPhilipp Tomsich <philipp.tomsich@theobroma-systems.com>
Tue, 4 Jul 2017 12:49:38 +0000 (14:49 +0200)
committerPhilipp Tomsich <philipp.tomsich@theobroma-systems.com>
Sun, 13 Aug 2017 15:12:32 +0000 (17:12 +0200)
The original clock support for MMC/SD cards on the RK3368 suffered
from a tendency to select a divider less-or-equal to the the one
giving the requested clock-rate: this can lead to higher-than-expected
(or rather: higher than supported) clock rates for the MMC/SD
communiction.

This change rewrites the MMC/SD clock generation to:
 * always generate a clock less-than-or-equal to the requested clock
 * support reparenting among the CPLL, GPLL and OSC24M parents to
   generate the highest clock that does not exceed the requested rate

In addition to this, the Linux DTS uses HCLK_MMC/HCLK_SDMMC instead of
SCLK_MMC/SCLK_SDMMC: to match this (and to ensure that clock setup
always works), we adjust the driver appropriately.

This includes the changes from:
 - rockchip: clk: rk3368: convert MMC_PLL_SEL_* definitions to shifted-value form

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
arch/arm/include/asm/arch-rockchip/cru_rk3368.h
drivers/clk/rockchip/clk_rk3368.c

index bf09e2fa68ac69bc82daec936893027fd1dbd680..21f11e017c4ee425f65fd59e50be0c80331865dd 100644 (file)
@@ -92,10 +92,10 @@ enum {
        /* CLKSEL51_CON */
        MMC_PLL_SEL_SHIFT               = 8,
        MMC_PLL_SEL_MASK                = GENMASK(9, 8),
-       MMC_PLL_SEL_CPLL                = 0,
-       MMC_PLL_SEL_GPLL,
-       MMC_PLL_SEL_USBPHY_480M,
-       MMC_PLL_SEL_24M,
+       MMC_PLL_SEL_CPLL                = (0 << MMC_PLL_SEL_SHIFT),
+       MMC_PLL_SEL_GPLL                = (1 << MMC_PLL_SEL_SHIFT),
+       MMC_PLL_SEL_USBPHY_480M         = (2 << MMC_PLL_SEL_SHIFT),
+       MMC_PLL_SEL_24M                 = (3 << MMC_PLL_SEL_SHIFT),
        MMC_CLK_DIV_SHIFT               = 0,
        MMC_CLK_DIV_MASK                = GENMASK(6, 0),
 
index 33d29464fb651d122b83688f3a9c1059f80d0b8b..1327116f195e31cefeb78cc8ba56fd37bbc695f2 100644 (file)
@@ -59,6 +59,8 @@ static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 6);
 #endif
 #endif
 
+static ulong rk3368_clk_get_rate(struct clk *clk);
+
 /* Get pll rate by id */
 static uint32_t rkclk_pll_get_rate(struct rk3368_cru *cru,
                                   enum rk3368_pll_id pll_id)
@@ -155,16 +157,17 @@ static void rkclk_init(struct rk3368_cru *cru)
 }
 #endif
 
+#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
 static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
 {
        u32 div, con, con_id, rate;
        u32 pll_rate;
 
        switch (clk_id) {
-       case SCLK_SDMMC:
+       case HCLK_SDMMC:
                con_id = 50;
                break;
-       case SCLK_EMMC:
+       case HCLK_EMMC:
                con_id = 51;
                break;
        case SCLK_SDIO0:
@@ -175,7 +178,7 @@ static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
        }
 
        con = readl(&cru->clksel_con[con_id]);
-       switch ((con & MMC_PLL_SEL_MASK) >> MMC_PLL_SEL_SHIFT) {
+       switch (con & MMC_PLL_SEL_MASK) {
        case MMC_PLL_SEL_GPLL:
                pll_rate = rkclk_pll_get_rate(cru, GPLL);
                break;
@@ -183,6 +186,8 @@ static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
                pll_rate = OSC_HZ;
                break;
        case MMC_PLL_SEL_CPLL:
+               pll_rate = rkclk_pll_get_rate(cru, CPLL);
+               break;
        case MMC_PLL_SEL_USBPHY_480M:
        default:
                return -EINVAL;
@@ -190,23 +195,76 @@ static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
        div = (con & MMC_CLK_DIV_MASK) >> MMC_CLK_DIV_SHIFT;
        rate = DIV_TO_RATE(pll_rate, div);
 
+       debug("%s: raw rate %d (post-divide by 2)\n", __func__, rate);
        return rate >> 1;
 }
 
-static ulong rk3368_mmc_set_clk(struct rk3368_cru *cru,
-                               ulong clk_id, ulong rate)
+static ulong rk3368_mmc_find_best_rate_and_parent(struct clk *clk,
+                                                 ulong rate,
+                                                 u32 *best_mux,
+                                                 u32 *best_div)
+{
+       int i;
+       ulong best_rate = 0;
+       const ulong MHz = 1000000;
+       const struct {
+               u32 mux;
+               ulong rate;
+       } parents[] = {
+               { .mux = MMC_PLL_SEL_CPLL, .rate = CPLL_HZ },
+               { .mux = MMC_PLL_SEL_GPLL, .rate = GPLL_HZ },
+               { .mux = MMC_PLL_SEL_24M,  .rate = 24 * MHz }
+       };
+
+       debug("%s: target rate %ld\n", __func__, rate);
+       for (i = 0; i < ARRAY_SIZE(parents); ++i) {
+               /*
+                * Find the largest rate no larger than the target-rate for
+                * the current parent.
+                */
+               ulong parent_rate = parents[i].rate;
+               u32 div = DIV_ROUND_UP(parent_rate, rate);
+               u32 adj_div = div;
+               ulong new_rate = parent_rate / adj_div;
+
+               debug("%s: rate %ld, parent-mux %d, parent-rate %ld, div %d\n",
+                     __func__, rate, parents[i].mux, parents[i].rate, div);
+
+               /* Skip, if not representable */
+               if ((div - 1) > MMC_CLK_DIV_MASK)
+                       continue;
+
+               /* Skip, if we already have a better (or equal) solution */
+               if (new_rate <= best_rate)
+                       continue;
+
+               /* This is our new best rate. */
+               best_rate = new_rate;
+               *best_mux = parents[i].mux;
+               *best_div = div - 1;
+       }
+
+       debug("%s: best_mux = %x, best_div = %d, best_rate = %ld\n",
+             __func__, *best_mux, *best_div, best_rate);
+
+       return best_rate;
+}
+
+static ulong rk3368_mmc_set_clk(struct clk *clk, ulong rate)
 {
-       u32 div;
-       u32 con_id;
-       u32 gpll_rate = rkclk_pll_get_rate(cru, GPLL);
+       struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
+       struct rk3368_cru *cru = priv->cru;
+       ulong clk_id = clk->id;
+       u32 con_id, mux = 0, div = 0;
 
-       div = RATE_TO_DIV(gpll_rate, rate << 1);
+       /* Find the best parent and rate */
+       rk3368_mmc_find_best_rate_and_parent(clk, rate << 1, &mux, &div);
 
        switch (clk_id) {
-       case SCLK_SDMMC:
+       case HCLK_SDMMC:
                con_id = 50;
                break;
-       case SCLK_EMMC:
+       case HCLK_EMMC:
                con_id = 51;
                break;
        case SCLK_SDIO0:
@@ -216,33 +274,33 @@ static ulong rk3368_mmc_set_clk(struct rk3368_cru *cru,
                return -EINVAL;
        }
 
-       if (div > 0x3f) {
-               div = RATE_TO_DIV(OSC_HZ, rate);
-               rk_clrsetreg(&cru->clksel_con[con_id],
-                            MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK,
-                            (MMC_PLL_SEL_24M << MMC_PLL_SEL_SHIFT) |
-                            (div << MMC_CLK_DIV_SHIFT));
-       } else {
-               rk_clrsetreg(&cru->clksel_con[con_id],
-                            MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK,
-                            (MMC_PLL_SEL_GPLL << MMC_PLL_SEL_SHIFT) |
-                            div << MMC_CLK_DIV_SHIFT);
-       }
+       rk_clrsetreg(&cru->clksel_con[con_id],
+                    MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK,
+                    mux | div);
 
        return rk3368_mmc_get_clk(cru, clk_id);
 }
+#endif
 
 static ulong rk3368_clk_get_rate(struct clk *clk)
 {
        struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
        ulong rate = 0;
 
-       debug("%s id:%ld\n", __func__, clk->id);
+       debug("%s: id %ld\n", __func__, clk->id);
        switch (clk->id) {
+       case PLL_CPLL:
+               rate = rkclk_pll_get_rate(priv->cru, CPLL);
+               break;
+       case PLL_GPLL:
+               rate = rkclk_pll_get_rate(priv->cru, GPLL);
+               break;
+#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
        case HCLK_SDMMC:
        case HCLK_EMMC:
                rate = rk3368_mmc_get_clk(priv->cru, clk->id);
                break;
+#endif
        default:
                return -ENOENT;
        }
@@ -291,10 +349,15 @@ static ulong rk3368_clk_set_rate(struct clk *clk, ulong rate)
        case CLK_DDR:
                ret = rk3368_ddr_set_clk(priv->cru, rate);
                break;
-
-       case SCLK_SDMMC:
-       case SCLK_EMMC:
-               ret = rk3368_mmc_set_clk(priv->cru, clk->id, rate);
+#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
+       case HCLK_SDMMC:
+       case HCLK_EMMC:
+               ret = rk3368_mmc_set_clk(clk, rate);
+               break;
+#endif
+       case SCLK_MAC:
+               /* nothing to do, as this is an external clock */
+               ret = rate;
                break;
        default:
                return -ENOENT;