2018-05-06 21:58:06 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2012-10-15 19:10:31 +00:00
|
|
|
/*
|
|
|
|
* (C) Copyright 2012 SAMSUNG Electronics
|
|
|
|
* Jaehoon Chung <jh80.chung@samsung.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#include <dwmmc.h>
|
2013-04-27 06:12:55 +00:00
|
|
|
#include <fdtdec.h>
|
2020-10-31 03:38:53 +00:00
|
|
|
#include <asm/global_data.h>
|
2018-03-04 16:20:11 +00:00
|
|
|
#include <linux/libfdt.h>
|
2013-04-27 06:12:55 +00:00
|
|
|
#include <malloc.h>
|
2016-07-19 07:33:34 +00:00
|
|
|
#include <errno.h>
|
2012-10-15 19:10:31 +00:00
|
|
|
#include <asm/arch/dwmmc.h>
|
|
|
|
#include <asm/arch/clk.h>
|
2013-04-27 06:12:55 +00:00
|
|
|
#include <asm/arch/pinmux.h>
|
mmc: exynos dwmmc: check boot mode before init dwmmc
Before this commit, the mmc devices were always registered
in the same order. So dwmmc channel 0 was registered as mmc 0,
channel 1 as mmc 1, etc.
In case of possibility to boot from more then one device,
the CONFIG_SYS_MMC_ENV_DEV should always point to right mmc device.
This can be achieved by init boot device as first, so it will be
always registered as mmc 0. Thanks to this, the 'saveenv' command
will work fine for all mmc boot devices.
Exynos based boards usually uses mmc host channels configuration:
- 0, or 0+1 for 8 bit - as a default boot device (usually eMMC)
- 2 for 4bit - as an optional boot device (usually SD card slot)
And usually the boot order is defined by OM pin configuration,
which can be changed in a few ways, eg.
- Odroid U3 - eMMC card insertion -> first boot from eMMC
- Odroid X2/XU3 - boot priority jumper
By this commit, Exynos dwmmc driver will check the OM pin configuration,
and then try to init the boot device and register it as mmc 0.
Signed-off-by: Przemyslaw Marczak <p.marczak@samsung.com>
Cc: Minkyu Kang <mk7.kang@samsung.com>
Cc: Jaehoon Chung <jh80.chung@samsung.com>
Cc: Pantelis Antoniou <panto@antoniou-consulting.com>
Cc: Simon Glass <sjg@chromium.org>
Cc: Akshay Saraswat <akshay.s@samsung.com>
2015-02-20 11:29:26 +00:00
|
|
|
#include <asm/arch/power.h>
|
2014-05-16 04:59:52 +00:00
|
|
|
#include <asm/gpio.h>
|
2023-09-15 00:21:46 +00:00
|
|
|
#include <linux/printk.h>
|
2012-10-15 19:10:31 +00:00
|
|
|
|
2013-04-27 06:12:55 +00:00
|
|
|
#define DWMMC_MAX_CH_NUM 4
|
|
|
|
#define DWMMC_MAX_FREQ 52000000
|
|
|
|
#define DWMMC_MIN_FREQ 400000
|
2015-02-04 06:48:40 +00:00
|
|
|
#define DWMMC_MMC0_SDR_TIMING_VAL 0x03030001
|
|
|
|
#define DWMMC_MMC2_SDR_TIMING_VAL 0x03020001
|
|
|
|
|
2016-06-30 11:57:37 +00:00
|
|
|
#ifdef CONFIG_DM_MMC
|
|
|
|
#include <dm.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
|
|
|
|
struct exynos_mmc_plat {
|
|
|
|
struct mmc_config cfg;
|
|
|
|
struct mmc mmc;
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2015-02-04 06:48:40 +00:00
|
|
|
/* Exynos implmentation specific drver private data */
|
|
|
|
struct dwmci_exynos_priv_data {
|
2016-06-30 11:57:37 +00:00
|
|
|
#ifdef CONFIG_DM_MMC
|
|
|
|
struct dwmci_host host;
|
|
|
|
#endif
|
2015-02-04 06:48:40 +00:00
|
|
|
u32 sdr_timing;
|
|
|
|
};
|
2012-10-15 19:10:31 +00:00
|
|
|
|
2013-04-27 06:12:55 +00:00
|
|
|
/*
|
|
|
|
* Function used as callback function to initialise the
|
|
|
|
* CLKSEL register for every mmc channel.
|
|
|
|
*/
|
2020-12-24 10:21:03 +00:00
|
|
|
static int exynos_dwmci_clksel(struct dwmci_host *host)
|
2012-10-15 19:10:31 +00:00
|
|
|
{
|
2018-08-01 12:48:59 +00:00
|
|
|
#ifdef CONFIG_DM_MMC
|
|
|
|
struct dwmci_exynos_priv_data *priv =
|
|
|
|
container_of(host, struct dwmci_exynos_priv_data, host);
|
|
|
|
#else
|
2015-02-04 06:48:40 +00:00
|
|
|
struct dwmci_exynos_priv_data *priv = host->priv;
|
2018-08-01 12:48:59 +00:00
|
|
|
#endif
|
2015-02-04 06:48:40 +00:00
|
|
|
dwmci_writel(host, DWMCI_CLKSEL, priv->sdr_timing);
|
2020-12-24 10:21:03 +00:00
|
|
|
|
|
|
|
return 0;
|
2013-04-27 06:12:55 +00:00
|
|
|
}
|
2012-10-15 19:10:31 +00:00
|
|
|
|
2015-08-30 22:55:15 +00:00
|
|
|
unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq)
|
2013-04-27 06:12:55 +00:00
|
|
|
{
|
2014-02-05 05:18:15 +00:00
|
|
|
unsigned long sclk;
|
|
|
|
int8_t clk_div;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Since SDCLKIN is divided inside controller by the DIVRATIO
|
|
|
|
* value set in the CLKSEL register, we need to use the same output
|
|
|
|
* clock value to calculate the CLKDIV value.
|
|
|
|
* as per user manual:cclk_in = SDCLKIN / (DIVRATIO + 1)
|
|
|
|
*/
|
|
|
|
clk_div = ((dwmci_readl(host, DWMCI_CLKSEL) >> DWMCI_DIVRATIO_BIT)
|
|
|
|
& DWMCI_DIVRATIO_MASK) + 1;
|
|
|
|
sclk = get_mmc_clk(host->dev_index);
|
|
|
|
|
2014-05-16 04:59:52 +00:00
|
|
|
/*
|
|
|
|
* Assume to know divider value.
|
|
|
|
* When clock unit is broken, need to set "host->div"
|
|
|
|
*/
|
|
|
|
return sclk / clk_div / (host->div + 1);
|
2012-10-15 19:10:31 +00:00
|
|
|
}
|
|
|
|
|
2013-11-29 11:08:57 +00:00
|
|
|
static void exynos_dwmci_board_init(struct dwmci_host *host)
|
|
|
|
{
|
2015-02-04 06:48:40 +00:00
|
|
|
struct dwmci_exynos_priv_data *priv = host->priv;
|
|
|
|
|
2013-11-29 11:08:57 +00:00
|
|
|
if (host->quirks & DWMCI_QUIRK_DISABLE_SMU) {
|
|
|
|
dwmci_writel(host, EMMCP_MPSBEGIN0, 0);
|
|
|
|
dwmci_writel(host, EMMCP_SEND0, 0);
|
|
|
|
dwmci_writel(host, EMMCP_CTRL0,
|
|
|
|
MPSCTRL_SECURE_READ_BIT |
|
|
|
|
MPSCTRL_SECURE_WRITE_BIT |
|
|
|
|
MPSCTRL_NON_SECURE_READ_BIT |
|
|
|
|
MPSCTRL_NON_SECURE_WRITE_BIT | MPSCTRL_VALID);
|
|
|
|
}
|
2015-02-04 06:48:39 +00:00
|
|
|
|
2015-02-04 06:48:40 +00:00
|
|
|
/* Set to timing value at initial time */
|
|
|
|
if (priv->sdr_timing)
|
2015-02-04 06:48:39 +00:00
|
|
|
exynos_dwmci_clksel(host);
|
2013-11-29 11:08:57 +00:00
|
|
|
}
|
|
|
|
|
2016-06-29 10:46:17 +00:00
|
|
|
static int exynos_dwmci_core_init(struct dwmci_host *host)
|
2012-10-15 19:10:31 +00:00
|
|
|
{
|
2013-04-27 06:12:55 +00:00
|
|
|
unsigned int div;
|
|
|
|
unsigned long freq, sclk;
|
2014-05-16 04:59:52 +00:00
|
|
|
|
|
|
|
if (host->bus_hz)
|
|
|
|
freq = host->bus_hz;
|
|
|
|
else
|
|
|
|
freq = DWMMC_MAX_FREQ;
|
|
|
|
|
2013-04-27 06:12:55 +00:00
|
|
|
/* request mmc clock vlaue of 52MHz. */
|
2016-06-29 10:46:17 +00:00
|
|
|
sclk = get_mmc_clk(host->dev_index);
|
2013-04-27 06:12:55 +00:00
|
|
|
div = DIV_ROUND_UP(sclk, freq);
|
|
|
|
/* set the clock divisor for mmc */
|
2016-06-29 10:46:17 +00:00
|
|
|
set_mmc_clk(host->dev_index, div);
|
2012-10-15 19:10:31 +00:00
|
|
|
|
2013-04-27 06:12:55 +00:00
|
|
|
host->name = "EXYNOS DWMMC";
|
2013-10-29 07:23:13 +00:00
|
|
|
#ifdef CONFIG_EXYNOS5420
|
|
|
|
host->quirks = DWMCI_QUIRK_DISABLE_SMU;
|
|
|
|
#endif
|
2013-11-29 11:08:57 +00:00
|
|
|
host->board_init = exynos_dwmci_board_init;
|
2013-04-27 06:12:55 +00:00
|
|
|
|
2014-05-16 04:59:57 +00:00
|
|
|
host->caps = MMC_MODE_DDR_52MHz;
|
2012-10-15 19:10:31 +00:00
|
|
|
host->clksel = exynos_dwmci_clksel;
|
2013-10-06 09:59:31 +00:00
|
|
|
host->get_mmc_clk = exynos_dwmci_get_clk;
|
2016-06-30 11:57:37 +00:00
|
|
|
|
|
|
|
#ifndef CONFIG_DM_MMC
|
2013-04-27 06:12:55 +00:00
|
|
|
/* Add the mmc channel to be registered with mmc core */
|
|
|
|
if (add_dwmci(host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ)) {
|
2016-06-29 10:46:17 +00:00
|
|
|
printf("DWMMC%d registration failed\n", host->dev_index);
|
2013-04-27 06:12:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2016-06-30 11:57:37 +00:00
|
|
|
#endif
|
|
|
|
|
2013-04-27 06:12:55 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-16 04:59:52 +00:00
|
|
|
static int do_dwmci_init(struct dwmci_host *host)
|
2013-04-27 06:12:55 +00:00
|
|
|
{
|
2016-06-29 10:46:17 +00:00
|
|
|
int flag, err;
|
2013-04-27 06:12:55 +00:00
|
|
|
|
2014-05-16 04:59:52 +00:00
|
|
|
flag = host->buswidth == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
|
|
|
|
err = exynos_pinmux_config(host->dev_id, flag);
|
|
|
|
if (err) {
|
2016-06-29 10:46:17 +00:00
|
|
|
printf("DWMMC%d not configure\n", host->dev_index);
|
2014-05-16 04:59:52 +00:00
|
|
|
return err;
|
|
|
|
}
|
2013-04-27 06:12:55 +00:00
|
|
|
|
2016-06-29 10:46:17 +00:00
|
|
|
return exynos_dwmci_core_init(host);
|
2014-05-16 04:59:52 +00:00
|
|
|
}
|
2012-10-15 19:10:31 +00:00
|
|
|
|
2014-05-16 04:59:52 +00:00
|
|
|
static int exynos_dwmci_get_config(const void *blob, int node,
|
2018-08-01 12:48:53 +00:00
|
|
|
struct dwmci_host *host,
|
|
|
|
struct dwmci_exynos_priv_data *priv)
|
2014-05-16 04:59:52 +00:00
|
|
|
{
|
|
|
|
int err = 0;
|
2015-02-04 06:48:40 +00:00
|
|
|
u32 base, timing[3];
|
2012-10-15 19:10:31 +00:00
|
|
|
|
2014-05-16 04:59:52 +00:00
|
|
|
/* Extract device id for each mmc channel */
|
|
|
|
host->dev_id = pinmux_decode_periph_id(blob, node);
|
2013-04-27 06:12:55 +00:00
|
|
|
|
2014-11-28 11:42:33 +00:00
|
|
|
host->dev_index = fdtdec_get_int(blob, node, "index", host->dev_id);
|
|
|
|
if (host->dev_index == host->dev_id)
|
|
|
|
host->dev_index = host->dev_id - PERIPH_ID_SDMMC0;
|
|
|
|
|
2016-06-29 10:46:16 +00:00
|
|
|
if (host->dev_index > 4) {
|
|
|
|
printf("DWMMC%d: Can't get the dev index\n", host->dev_index);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-06-29 10:46:18 +00:00
|
|
|
/* Get the bus width from the device node (Default is 4bit buswidth) */
|
|
|
|
host->buswidth = fdtdec_get_int(blob, node, "samsung,bus-width", 4);
|
2013-04-27 06:12:55 +00:00
|
|
|
|
2014-05-16 04:59:52 +00:00
|
|
|
/* Set the base address from the device node */
|
|
|
|
base = fdtdec_get_addr(blob, node, "reg");
|
|
|
|
if (!base) {
|
2014-11-28 11:42:33 +00:00
|
|
|
printf("DWMMC%d: Can't get base address\n", host->dev_index);
|
2014-05-16 04:59:52 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
host->ioaddr = (void *)base;
|
|
|
|
|
|
|
|
/* Extract the timing info from the node */
|
|
|
|
err = fdtdec_get_int_array(blob, node, "samsung,timing", timing, 3);
|
|
|
|
if (err) {
|
2014-11-28 11:42:33 +00:00
|
|
|
printf("DWMMC%d: Can't get sdr-timings for devider\n",
|
|
|
|
host->dev_index);
|
2014-05-16 04:59:52 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-02-04 06:48:40 +00:00
|
|
|
priv->sdr_timing = (DWMCI_SET_SAMPLE_CLK(timing[0]) |
|
2014-05-16 04:59:52 +00:00
|
|
|
DWMCI_SET_DRV_CLK(timing[1]) |
|
|
|
|
DWMCI_SET_DIV_RATIO(timing[2]));
|
2015-02-04 06:48:40 +00:00
|
|
|
|
|
|
|
/* sdr_timing didn't assigned anything, use the default value */
|
|
|
|
if (!priv->sdr_timing) {
|
|
|
|
if (host->dev_index == 0)
|
|
|
|
priv->sdr_timing = DWMMC_MMC0_SDR_TIMING_VAL;
|
|
|
|
else if (host->dev_index == 2)
|
|
|
|
priv->sdr_timing = DWMMC_MMC2_SDR_TIMING_VAL;
|
|
|
|
}
|
2014-05-16 04:59:52 +00:00
|
|
|
|
|
|
|
host->fifoth_val = fdtdec_get_int(blob, node, "fifoth_val", 0);
|
|
|
|
host->bus_hz = fdtdec_get_int(blob, node, "bus_hz", 0);
|
|
|
|
host->div = fdtdec_get_int(blob, node, "div", 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-30 11:57:37 +00:00
|
|
|
#ifdef CONFIG_DM_MMC
|
|
|
|
static int exynos_dwmmc_probe(struct udevice *dev)
|
|
|
|
{
|
2020-12-03 23:55:20 +00:00
|
|
|
struct exynos_mmc_plat *plat = dev_get_plat(dev);
|
2016-06-30 11:57:37 +00:00
|
|
|
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
|
|
|
struct dwmci_exynos_priv_data *priv = dev_get_priv(dev);
|
|
|
|
struct dwmci_host *host = &priv->host;
|
|
|
|
int err;
|
|
|
|
|
2018-08-01 12:48:53 +00:00
|
|
|
err = exynos_dwmci_get_config(gd->fdt_blob, dev_of_offset(dev), host,
|
|
|
|
priv);
|
2016-06-30 11:57:37 +00:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
err = do_dwmci_init(host);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2016-09-23 10:13:16 +00:00
|
|
|
dwmci_setup_cfg(&plat->cfg, host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ);
|
2016-06-30 11:57:37 +00:00
|
|
|
host->mmc = &plat->mmc;
|
|
|
|
host->mmc->priv = &priv->host;
|
|
|
|
host->priv = dev;
|
|
|
|
upriv->mmc = host->mmc;
|
|
|
|
|
|
|
|
return dwmci_probe(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int exynos_dwmmc_bind(struct udevice *dev)
|
|
|
|
{
|
2020-12-03 23:55:20 +00:00
|
|
|
struct exynos_mmc_plat *plat = dev_get_plat(dev);
|
2016-06-30 11:57:37 +00:00
|
|
|
|
2016-09-06 13:17:32 +00:00
|
|
|
return dwmci_bind(dev, &plat->mmc, &plat->cfg);
|
2016-06-30 11:57:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct udevice_id exynos_dwmmc_ids[] = {
|
|
|
|
{ .compatible = "samsung,exynos4412-dw-mshc" },
|
2018-08-01 12:49:00 +00:00
|
|
|
{ .compatible = "samsung,exynos-dwmmc" },
|
2016-06-30 11:57:37 +00:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
U_BOOT_DRIVER(exynos_dwmmc_drv) = {
|
|
|
|
.name = "exynos_dwmmc",
|
|
|
|
.id = UCLASS_MMC,
|
|
|
|
.of_match = exynos_dwmmc_ids,
|
|
|
|
.bind = exynos_dwmmc_bind,
|
|
|
|
.ops = &dm_dwmci_ops,
|
|
|
|
.probe = exynos_dwmmc_probe,
|
2020-12-03 23:55:17 +00:00
|
|
|
.priv_auto = sizeof(struct dwmci_exynos_priv_data),
|
2020-12-03 23:55:18 +00:00
|
|
|
.plat_auto = sizeof(struct exynos_mmc_plat),
|
2016-06-30 11:57:37 +00:00
|
|
|
};
|
|
|
|
#endif
|