rockchip: clk: rk3368: implement MMC/SD clock reparenting
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>
This commit is contained in:
parent
05c57e12d1
commit
f5a432959a
@ -92,10 +92,10 @@ enum {
|
|||||||
/* CLKSEL51_CON */
|
/* CLKSEL51_CON */
|
||||||
MMC_PLL_SEL_SHIFT = 8,
|
MMC_PLL_SEL_SHIFT = 8,
|
||||||
MMC_PLL_SEL_MASK = GENMASK(9, 8),
|
MMC_PLL_SEL_MASK = GENMASK(9, 8),
|
||||||
MMC_PLL_SEL_CPLL = 0,
|
MMC_PLL_SEL_CPLL = (0 << MMC_PLL_SEL_SHIFT),
|
||||||
MMC_PLL_SEL_GPLL,
|
MMC_PLL_SEL_GPLL = (1 << MMC_PLL_SEL_SHIFT),
|
||||||
MMC_PLL_SEL_USBPHY_480M,
|
MMC_PLL_SEL_USBPHY_480M = (2 << MMC_PLL_SEL_SHIFT),
|
||||||
MMC_PLL_SEL_24M,
|
MMC_PLL_SEL_24M = (3 << MMC_PLL_SEL_SHIFT),
|
||||||
MMC_CLK_DIV_SHIFT = 0,
|
MMC_CLK_DIV_SHIFT = 0,
|
||||||
MMC_CLK_DIV_MASK = GENMASK(6, 0),
|
MMC_CLK_DIV_MASK = GENMASK(6, 0),
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 6);
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static ulong rk3368_clk_get_rate(struct clk *clk);
|
||||||
|
|
||||||
/* Get pll rate by id */
|
/* Get pll rate by id */
|
||||||
static uint32_t rkclk_pll_get_rate(struct rk3368_cru *cru,
|
static uint32_t rkclk_pll_get_rate(struct rk3368_cru *cru,
|
||||||
enum rk3368_pll_id pll_id)
|
enum rk3368_pll_id pll_id)
|
||||||
@ -155,16 +157,17 @@ static void rkclk_init(struct rk3368_cru *cru)
|
|||||||
}
|
}
|
||||||
#endif
|
#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)
|
static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
|
||||||
{
|
{
|
||||||
u32 div, con, con_id, rate;
|
u32 div, con, con_id, rate;
|
||||||
u32 pll_rate;
|
u32 pll_rate;
|
||||||
|
|
||||||
switch (clk_id) {
|
switch (clk_id) {
|
||||||
case SCLK_SDMMC:
|
case HCLK_SDMMC:
|
||||||
con_id = 50;
|
con_id = 50;
|
||||||
break;
|
break;
|
||||||
case SCLK_EMMC:
|
case HCLK_EMMC:
|
||||||
con_id = 51;
|
con_id = 51;
|
||||||
break;
|
break;
|
||||||
case SCLK_SDIO0:
|
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]);
|
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:
|
case MMC_PLL_SEL_GPLL:
|
||||||
pll_rate = rkclk_pll_get_rate(cru, GPLL);
|
pll_rate = rkclk_pll_get_rate(cru, GPLL);
|
||||||
break;
|
break;
|
||||||
@ -183,6 +186,8 @@ static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
|
|||||||
pll_rate = OSC_HZ;
|
pll_rate = OSC_HZ;
|
||||||
break;
|
break;
|
||||||
case MMC_PLL_SEL_CPLL:
|
case MMC_PLL_SEL_CPLL:
|
||||||
|
pll_rate = rkclk_pll_get_rate(cru, CPLL);
|
||||||
|
break;
|
||||||
case MMC_PLL_SEL_USBPHY_480M:
|
case MMC_PLL_SEL_USBPHY_480M:
|
||||||
default:
|
default:
|
||||||
return -EINVAL;
|
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;
|
div = (con & MMC_CLK_DIV_MASK) >> MMC_CLK_DIV_SHIFT;
|
||||||
rate = DIV_TO_RATE(pll_rate, div);
|
rate = DIV_TO_RATE(pll_rate, div);
|
||||||
|
|
||||||
|
debug("%s: raw rate %d (post-divide by 2)\n", __func__, rate);
|
||||||
return rate >> 1;
|
return rate >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ulong rk3368_mmc_set_clk(struct rk3368_cru *cru,
|
static ulong rk3368_mmc_find_best_rate_and_parent(struct clk *clk,
|
||||||
ulong clk_id, ulong rate)
|
ulong rate,
|
||||||
|
u32 *best_mux,
|
||||||
|
u32 *best_div)
|
||||||
{
|
{
|
||||||
u32 div;
|
int i;
|
||||||
u32 con_id;
|
ulong best_rate = 0;
|
||||||
u32 gpll_rate = rkclk_pll_get_rate(cru, GPLL);
|
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 }
|
||||||
|
};
|
||||||
|
|
||||||
div = RATE_TO_DIV(gpll_rate, rate << 1);
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* Find the best parent and rate */
|
||||||
|
rk3368_mmc_find_best_rate_and_parent(clk, rate << 1, &mux, &div);
|
||||||
|
|
||||||
switch (clk_id) {
|
switch (clk_id) {
|
||||||
case SCLK_SDMMC:
|
case HCLK_SDMMC:
|
||||||
con_id = 50;
|
con_id = 50;
|
||||||
break;
|
break;
|
||||||
case SCLK_EMMC:
|
case HCLK_EMMC:
|
||||||
con_id = 51;
|
con_id = 51;
|
||||||
break;
|
break;
|
||||||
case SCLK_SDIO0:
|
case SCLK_SDIO0:
|
||||||
@ -216,33 +274,33 @@ static ulong rk3368_mmc_set_clk(struct rk3368_cru *cru,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (div > 0x3f) {
|
|
||||||
div = RATE_TO_DIV(OSC_HZ, rate);
|
|
||||||
rk_clrsetreg(&cru->clksel_con[con_id],
|
rk_clrsetreg(&cru->clksel_con[con_id],
|
||||||
MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK,
|
MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK,
|
||||||
(MMC_PLL_SEL_24M << MMC_PLL_SEL_SHIFT) |
|
mux | div);
|
||||||
(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rk3368_mmc_get_clk(cru, clk_id);
|
return rk3368_mmc_get_clk(cru, clk_id);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static ulong rk3368_clk_get_rate(struct clk *clk)
|
static ulong rk3368_clk_get_rate(struct clk *clk)
|
||||||
{
|
{
|
||||||
struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
|
struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
|
||||||
ulong rate = 0;
|
ulong rate = 0;
|
||||||
|
|
||||||
debug("%s id:%ld\n", __func__, clk->id);
|
debug("%s: id %ld\n", __func__, clk->id);
|
||||||
switch (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_SDMMC:
|
||||||
case HCLK_EMMC:
|
case HCLK_EMMC:
|
||||||
rate = rk3368_mmc_get_clk(priv->cru, clk->id);
|
rate = rk3368_mmc_get_clk(priv->cru, clk->id);
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
@ -291,10 +349,15 @@ static ulong rk3368_clk_set_rate(struct clk *clk, ulong rate)
|
|||||||
case CLK_DDR:
|
case CLK_DDR:
|
||||||
ret = rk3368_ddr_set_clk(priv->cru, rate);
|
ret = rk3368_ddr_set_clk(priv->cru, rate);
|
||||||
break;
|
break;
|
||||||
|
#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
|
||||||
case SCLK_SDMMC:
|
case HCLK_SDMMC:
|
||||||
case SCLK_EMMC:
|
case HCLK_EMMC:
|
||||||
ret = rk3368_mmc_set_clk(priv->cru, clk->id, rate);
|
ret = rk3368_mmc_set_clk(clk, rate);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case SCLK_MAC:
|
||||||
|
/* nothing to do, as this is an external clock */
|
||||||
|
ret = rate;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
Loading…
Reference in New Issue
Block a user