mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-14 17:07:38 +00:00
1e94b46f73
This old patch was marked as deferred. Bring it back to life, to continue towards the removal of common.h Move this out of the common header and include it only where needed. Signed-off-by: Simon Glass <sjg@chromium.org>
260 lines
6.5 KiB
C
260 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2012 SAMSUNG Electronics
|
|
* Jaehoon Chung <jh80.chung@samsung.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dwmmc.h>
|
|
#include <fdtdec.h>
|
|
#include <asm/global_data.h>
|
|
#include <linux/libfdt.h>
|
|
#include <malloc.h>
|
|
#include <errno.h>
|
|
#include <asm/arch/dwmmc.h>
|
|
#include <asm/arch/clk.h>
|
|
#include <asm/arch/pinmux.h>
|
|
#include <asm/arch/power.h>
|
|
#include <asm/gpio.h>
|
|
#include <linux/printk.h>
|
|
|
|
#define DWMMC_MAX_CH_NUM 4
|
|
#define DWMMC_MAX_FREQ 52000000
|
|
#define DWMMC_MIN_FREQ 400000
|
|
#define DWMMC_MMC0_SDR_TIMING_VAL 0x03030001
|
|
#define DWMMC_MMC2_SDR_TIMING_VAL 0x03020001
|
|
|
|
#ifdef CONFIG_DM_MMC
|
|
#include <dm.h>
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
struct exynos_mmc_plat {
|
|
struct mmc_config cfg;
|
|
struct mmc mmc;
|
|
};
|
|
#endif
|
|
|
|
/* Exynos implmentation specific drver private data */
|
|
struct dwmci_exynos_priv_data {
|
|
#ifdef CONFIG_DM_MMC
|
|
struct dwmci_host host;
|
|
#endif
|
|
u32 sdr_timing;
|
|
};
|
|
|
|
/*
|
|
* Function used as callback function to initialise the
|
|
* CLKSEL register for every mmc channel.
|
|
*/
|
|
static int exynos_dwmci_clksel(struct dwmci_host *host)
|
|
{
|
|
#ifdef CONFIG_DM_MMC
|
|
struct dwmci_exynos_priv_data *priv =
|
|
container_of(host, struct dwmci_exynos_priv_data, host);
|
|
#else
|
|
struct dwmci_exynos_priv_data *priv = host->priv;
|
|
#endif
|
|
dwmci_writel(host, DWMCI_CLKSEL, priv->sdr_timing);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq)
|
|
{
|
|
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);
|
|
|
|
/*
|
|
* Assume to know divider value.
|
|
* When clock unit is broken, need to set "host->div"
|
|
*/
|
|
return sclk / clk_div / (host->div + 1);
|
|
}
|
|
|
|
static void exynos_dwmci_board_init(struct dwmci_host *host)
|
|
{
|
|
struct dwmci_exynos_priv_data *priv = host->priv;
|
|
|
|
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);
|
|
}
|
|
|
|
/* Set to timing value at initial time */
|
|
if (priv->sdr_timing)
|
|
exynos_dwmci_clksel(host);
|
|
}
|
|
|
|
static int exynos_dwmci_core_init(struct dwmci_host *host)
|
|
{
|
|
unsigned int div;
|
|
unsigned long freq, sclk;
|
|
|
|
if (host->bus_hz)
|
|
freq = host->bus_hz;
|
|
else
|
|
freq = DWMMC_MAX_FREQ;
|
|
|
|
/* request mmc clock vlaue of 52MHz. */
|
|
sclk = get_mmc_clk(host->dev_index);
|
|
div = DIV_ROUND_UP(sclk, freq);
|
|
/* set the clock divisor for mmc */
|
|
set_mmc_clk(host->dev_index, div);
|
|
|
|
host->name = "EXYNOS DWMMC";
|
|
#ifdef CONFIG_EXYNOS5420
|
|
host->quirks = DWMCI_QUIRK_DISABLE_SMU;
|
|
#endif
|
|
host->board_init = exynos_dwmci_board_init;
|
|
|
|
host->caps = MMC_MODE_DDR_52MHz;
|
|
host->clksel = exynos_dwmci_clksel;
|
|
host->get_mmc_clk = exynos_dwmci_get_clk;
|
|
|
|
#ifndef CONFIG_DM_MMC
|
|
/* Add the mmc channel to be registered with mmc core */
|
|
if (add_dwmci(host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ)) {
|
|
printf("DWMMC%d registration failed\n", host->dev_index);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_dwmci_init(struct dwmci_host *host)
|
|
{
|
|
int flag, err;
|
|
|
|
flag = host->buswidth == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
|
|
err = exynos_pinmux_config(host->dev_id, flag);
|
|
if (err) {
|
|
printf("DWMMC%d not configure\n", host->dev_index);
|
|
return err;
|
|
}
|
|
|
|
return exynos_dwmci_core_init(host);
|
|
}
|
|
|
|
static int exynos_dwmci_get_config(const void *blob, int node,
|
|
struct dwmci_host *host,
|
|
struct dwmci_exynos_priv_data *priv)
|
|
{
|
|
int err = 0;
|
|
u32 base, timing[3];
|
|
|
|
/* Extract device id for each mmc channel */
|
|
host->dev_id = pinmux_decode_periph_id(blob, node);
|
|
|
|
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;
|
|
|
|
if (host->dev_index > 4) {
|
|
printf("DWMMC%d: Can't get the dev index\n", host->dev_index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get the bus width from the device node (Default is 4bit buswidth) */
|
|
host->buswidth = fdtdec_get_int(blob, node, "samsung,bus-width", 4);
|
|
|
|
/* Set the base address from the device node */
|
|
base = fdtdec_get_addr(blob, node, "reg");
|
|
if (!base) {
|
|
printf("DWMMC%d: Can't get base address\n", host->dev_index);
|
|
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) {
|
|
printf("DWMMC%d: Can't get sdr-timings for devider\n",
|
|
host->dev_index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv->sdr_timing = (DWMCI_SET_SAMPLE_CLK(timing[0]) |
|
|
DWMCI_SET_DRV_CLK(timing[1]) |
|
|
DWMCI_SET_DIV_RATIO(timing[2]));
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef CONFIG_DM_MMC
|
|
static int exynos_dwmmc_probe(struct udevice *dev)
|
|
{
|
|
struct exynos_mmc_plat *plat = dev_get_plat(dev);
|
|
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;
|
|
|
|
err = exynos_dwmci_get_config(gd->fdt_blob, dev_of_offset(dev), host,
|
|
priv);
|
|
if (err)
|
|
return err;
|
|
err = do_dwmci_init(host);
|
|
if (err)
|
|
return err;
|
|
|
|
dwmci_setup_cfg(&plat->cfg, host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ);
|
|
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)
|
|
{
|
|
struct exynos_mmc_plat *plat = dev_get_plat(dev);
|
|
|
|
return dwmci_bind(dev, &plat->mmc, &plat->cfg);
|
|
}
|
|
|
|
static const struct udevice_id exynos_dwmmc_ids[] = {
|
|
{ .compatible = "samsung,exynos4412-dw-mshc" },
|
|
{ .compatible = "samsung,exynos-dwmmc" },
|
|
{ }
|
|
};
|
|
|
|
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,
|
|
.priv_auto = sizeof(struct dwmci_exynos_priv_data),
|
|
.plat_auto = sizeof(struct exynos_mmc_plat),
|
|
};
|
|
#endif
|