mirror of
https://xff.cz/git/u-boot/
synced 2025-09-01 00:32:04 +02:00
mmc: sunxi: Cleanup, add support for DDR
With this, we can boot from eMMC with read speeds around 47.5MiB/s. Signed-off-by: Ondrej Jirman <megous@megous.com>
This commit is contained in:
@@ -64,6 +64,7 @@ struct sunxi_mmc {
|
|||||||
SUNXI_MMC_GCTRL_FIFO_RESET|\
|
SUNXI_MMC_GCTRL_FIFO_RESET|\
|
||||||
SUNXI_MMC_GCTRL_DMA_RESET)
|
SUNXI_MMC_GCTRL_DMA_RESET)
|
||||||
#define SUNXI_MMC_GCTRL_DMA_ENABLE (0x1 << 5)
|
#define SUNXI_MMC_GCTRL_DMA_ENABLE (0x1 << 5)
|
||||||
|
#define SUNXI_MMC_GCTRL_DDR_MODE (0x1 << 10)
|
||||||
#define SUNXI_MMC_GCTRL_ACCESS_BY_AHB (0x1 << 31)
|
#define SUNXI_MMC_GCTRL_ACCESS_BY_AHB (0x1 << 31)
|
||||||
|
|
||||||
#define SUNXI_MMC_CMD_RESP_EXPIRE (0x1 << 6)
|
#define SUNXI_MMC_CMD_RESP_EXPIRE (0x1 << 6)
|
||||||
|
@@ -125,7 +125,47 @@ static int mmc_resource_init(int sdc_no)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
|
/*
|
||||||
|
* New timing modes usable on controllers:
|
||||||
|
*
|
||||||
|
* A83T - 2
|
||||||
|
* H3 - 1, 2
|
||||||
|
* H5 - all
|
||||||
|
* H6 - all
|
||||||
|
*
|
||||||
|
* SMHC_NTSR 0x005C: (new mode is default)
|
||||||
|
*
|
||||||
|
* H5, H6 yes
|
||||||
|
* A83T, H3 no
|
||||||
|
*
|
||||||
|
* Clock sources for SMHC clock:
|
||||||
|
*
|
||||||
|
* H3: PLL_PERIPH0 600MHz
|
||||||
|
* H5: PLL_PERIPH0(2x) 1200MHz
|
||||||
|
* H6: PLL_PERIPH0(2x) 1200MHz
|
||||||
|
* A83T: PLL_PERIPH 600MHz
|
||||||
|
*
|
||||||
|
* In new mode MMC clock for the module is post-divided by 2
|
||||||
|
*
|
||||||
|
* - SDMMCx SCLK = SRC_CLK (see above) / M / N / 2 (new mode)
|
||||||
|
* - SDMMCx SCLK = PLL_PERIPH / M / N (old mode)
|
||||||
|
*
|
||||||
|
* New mode needs to be also selected in CCU reg:
|
||||||
|
* H3, A83T
|
||||||
|
*
|
||||||
|
* Clk phase can be selected in CCU reg (for old mode):
|
||||||
|
* H3, A83T
|
||||||
|
*
|
||||||
|
* When using DDR, we need to double the SCLK and set post-div
|
||||||
|
* in CLKCR (in addition to previous requirements).
|
||||||
|
*
|
||||||
|
* H5, despite having new mode and double the clock incidentally
|
||||||
|
* works because new mode is the default, and u-boot thinks incorrectly
|
||||||
|
* that the source clock is half the speed, so it choses the right
|
||||||
|
* dividers.
|
||||||
|
*/
|
||||||
|
static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, bool is_ddr,
|
||||||
|
unsigned int hz)
|
||||||
{
|
{
|
||||||
unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly;
|
unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly;
|
||||||
bool new_mode = true;
|
bool new_mode = true;
|
||||||
@@ -143,8 +183,11 @@ static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
|
|||||||
calibrate = true;
|
calibrate = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* A83T in new mode requires double the clock */
|
||||||
if (new_mode)
|
if (new_mode)
|
||||||
hz = hz * 2;
|
hz *= 2;
|
||||||
|
if (is_ddr)
|
||||||
|
hz *= 2;
|
||||||
|
|
||||||
if (hz <= 24000000) {
|
if (hz <= 24000000) {
|
||||||
pll = CCM_MMC_CTRL_OSCM24;
|
pll = CCM_MMC_CTRL_OSCM24;
|
||||||
@@ -220,9 +263,15 @@ static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
|
|||||||
CCM_MMC_CTRL_SCLK_DLY(sclk_dly);
|
CCM_MMC_CTRL_SCLK_DLY(sclk_dly);
|
||||||
}
|
}
|
||||||
|
|
||||||
writel(CCM_MMC_CTRL_ENABLE| pll | CCM_MMC_CTRL_N(n) |
|
writel(CCM_MMC_CTRL_ENABLE | pll | CCM_MMC_CTRL_N(n) |
|
||||||
CCM_MMC_CTRL_M(div) | val, priv->mclkreg);
|
CCM_MMC_CTRL_M(div) | val, priv->mclkreg);
|
||||||
|
|
||||||
|
/* set internal divider if DDR */
|
||||||
|
val = readl(&priv->reg->clkcr);
|
||||||
|
val &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
|
||||||
|
val |= is_ddr ? 1 : 0;
|
||||||
|
writel(val, &priv->reg->clkcr);
|
||||||
|
|
||||||
debug("mmc %u set mod-clk req %u parent %u n %u m %u rate %u\n",
|
debug("mmc %u set mod-clk req %u parent %u n %u m %u rate %u\n",
|
||||||
priv->mmc_no, hz, pll_hz, 1u << n, div, pll_hz / (1u << n) / div);
|
priv->mmc_no, hz, pll_hz, 1u << n, div, pll_hz / (1u << n) / div);
|
||||||
|
|
||||||
@@ -253,22 +302,16 @@ static int mmc_update_clk(struct sunxi_mmc_priv *priv)
|
|||||||
|
|
||||||
static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc)
|
static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc)
|
||||||
{
|
{
|
||||||
unsigned rval = readl(&priv->reg->clkcr);
|
|
||||||
|
|
||||||
/* Disable Clock */
|
/* Disable Clock */
|
||||||
rval &= ~SUNXI_MMC_CLK_ENABLE;
|
clrbits_le32(&priv->reg->clkcr, SUNXI_MMC_CLK_ENABLE);
|
||||||
writel(rval, &priv->reg->clkcr);
|
|
||||||
if (mmc_update_clk(priv))
|
if (mmc_update_clk(priv))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Set mod_clk to new rate */
|
/* Set mod_clk to new rate */
|
||||||
if (mmc_set_mod_clk(priv, mmc->clock))
|
if (mmc_set_mod_clk(priv, mmc->selected_mode == MMC_DDR_52, mmc->clock))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Clear internal divider */
|
|
||||||
rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
|
|
||||||
writel(rval, &priv->reg->clkcr);
|
|
||||||
|
|
||||||
#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN50I_H6)
|
#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN50I_H6)
|
||||||
/* A64 supports calibration of delays on MMC controller and we
|
/* A64 supports calibration of delays on MMC controller and we
|
||||||
* have to set delay of zero before starting calibration.
|
* have to set delay of zero before starting calibration.
|
||||||
@@ -280,8 +323,8 @@ static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Re-enable Clock */
|
/* Re-enable Clock */
|
||||||
rval |= SUNXI_MMC_CLK_ENABLE;
|
setbits_le32(&priv->reg->clkcr, SUNXI_MMC_CLK_ENABLE);
|
||||||
writel(rval, &priv->reg->clkcr);
|
|
||||||
if (mmc_update_clk(priv))
|
if (mmc_update_clk(priv))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@@ -300,6 +343,12 @@ static int sunxi_mmc_set_ios_common(struct sunxi_mmc_priv *priv,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Timing */
|
||||||
|
if (mmc->selected_mode == MMC_DDR_52)
|
||||||
|
setbits_le32(&priv->reg->gctrl, SUNXI_MMC_GCTRL_DDR_MODE);
|
||||||
|
else
|
||||||
|
clrbits_le32(&priv->reg->gctrl, SUNXI_MMC_GCTRL_DDR_MODE);
|
||||||
|
|
||||||
/* Change bus width */
|
/* Change bus width */
|
||||||
if (mmc->bus_width == 8)
|
if (mmc->bus_width == 8)
|
||||||
writel(0x2, &priv->reg->width);
|
writel(0x2, &priv->reg->width);
|
||||||
@@ -707,7 +756,7 @@ struct mmc *sunxi_mmc_init(int sdc_no)
|
|||||||
/* unassert reset */
|
/* unassert reset */
|
||||||
setbits_le32(&ccm->sd_gate_reset, 1 << (RESET_SHIFT + sdc_no));
|
setbits_le32(&ccm->sd_gate_reset, 1 << (RESET_SHIFT + sdc_no));
|
||||||
#endif
|
#endif
|
||||||
ret = mmc_set_mod_clk(priv, 24000000);
|
ret = mmc_set_mod_clk(priv, false, 24000000);
|
||||||
if (ret)
|
if (ret)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@@ -800,6 +849,9 @@ static int sunxi_mmc_probe(struct udevice *dev)
|
|||||||
priv->mclkreg = (void *)ccu_reg +
|
priv->mclkreg = (void *)ccu_reg +
|
||||||
(priv->variant->mclk_offset + (priv->mmc_no * 4));
|
(priv->variant->mclk_offset + (priv->mmc_no * 4));
|
||||||
|
|
||||||
|
if (priv->mmc_no == 2)
|
||||||
|
cfg->host_caps |= MMC_MODE_DDR_52MHz;
|
||||||
|
|
||||||
ret = clk_get_by_name(dev, "ahb", &gate_clk);
|
ret = clk_get_by_name(dev, "ahb", &gate_clk);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
clk_enable(&gate_clk);
|
clk_enable(&gate_clk);
|
||||||
@@ -808,7 +860,7 @@ static int sunxi_mmc_probe(struct udevice *dev)
|
|||||||
if (!ret)
|
if (!ret)
|
||||||
reset_deassert_bulk(&reset_bulk);
|
reset_deassert_bulk(&reset_bulk);
|
||||||
|
|
||||||
ret = mmc_set_mod_clk(priv, 24000000);
|
ret = mmc_set_mod_clk(priv, false, 24000000);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user