/* 
 | 
 * Copyright (C) 2016 Socionext Inc. 
 | 
 *   Author: Masahiro Yamada <yamada.masahiro@socionext.com> 
 | 
 * 
 | 
 * SPDX-License-Identifier:    GPL-2.0+ 
 | 
 */ 
 | 
  
 | 
#include <common.h> 
 | 
#include <dm.h> 
 | 
#include <linux/io.h> 
 | 
#include <linux/iopoll.h> 
 | 
#include <linux/sizes.h> 
 | 
#include <linux/libfdt.h> 
 | 
#include <mmc.h> 
 | 
#include <sdhci.h> 
 | 
  
 | 
/* HRS - Host Register Set (specific to Cadence) */ 
 | 
#define SDHCI_CDNS_HRS04        0x10        /* PHY access port */ 
 | 
#define   SDHCI_CDNS_HRS04_ACK            BIT(26) 
 | 
#define   SDHCI_CDNS_HRS04_RD            BIT(25) 
 | 
#define   SDHCI_CDNS_HRS04_WR            BIT(24) 
 | 
#define   SDHCI_CDNS_HRS04_RDATA_SHIFT        16 
 | 
#define   SDHCI_CDNS_HRS04_WDATA_SHIFT        8 
 | 
#define   SDHCI_CDNS_HRS04_ADDR_SHIFT        0 
 | 
  
 | 
/* SRS - Slot Register Set (SDHCI-compatible) */ 
 | 
#define SDHCI_CDNS_SRS_BASE        0x200 
 | 
  
 | 
/* PHY */ 
 | 
#define SDHCI_CDNS_PHY_DLY_SD_HS    0x00 
 | 
#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT    0x01 
 | 
#define SDHCI_CDNS_PHY_DLY_UHS_SDR12    0x02 
 | 
#define SDHCI_CDNS_PHY_DLY_UHS_SDR25    0x03 
 | 
#define SDHCI_CDNS_PHY_DLY_UHS_SDR50    0x04 
 | 
#define SDHCI_CDNS_PHY_DLY_UHS_DDR50    0x05 
 | 
#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY    0x06 
 | 
#define SDHCI_CDNS_PHY_DLY_EMMC_SDR    0x07 
 | 
#define SDHCI_CDNS_PHY_DLY_EMMC_DDR    0x08 
 | 
#define SDHCI_CDNS_PHY_DLY_SDCLK    0x0b 
 | 
#define SDHCI_CDNS_PHY_DLY_HSMMC    0x0c 
 | 
#define SDHCI_CDNS_PHY_DLY_STROBE    0x0d 
 | 
  
 | 
struct sdhci_cdns_plat { 
 | 
    struct mmc_config cfg; 
 | 
    struct mmc mmc; 
 | 
    void __iomem *hrs_addr; 
 | 
}; 
 | 
  
 | 
struct sdhci_cdns_phy_cfg { 
 | 
    const char *property; 
 | 
    u8 addr; 
 | 
}; 
 | 
  
 | 
static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { 
 | 
    { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, 
 | 
    { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, 
 | 
    { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, 
 | 
    { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, 
 | 
    { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, 
 | 
    { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, 
 | 
    { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, 
 | 
    { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, 
 | 
    { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, 
 | 
    { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, 
 | 
    { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, 
 | 
}; 
 | 
  
 | 
static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat, 
 | 
                    u8 addr, u8 data) 
 | 
{ 
 | 
    void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS04; 
 | 
    u32 tmp; 
 | 
    int ret; 
 | 
  
 | 
    tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) | 
 | 
          (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT); 
 | 
    writel(tmp, reg); 
 | 
  
 | 
    tmp |= SDHCI_CDNS_HRS04_WR; 
 | 
    writel(tmp, reg); 
 | 
  
 | 
    ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 10); 
 | 
    if (ret) 
 | 
        return ret; 
 | 
  
 | 
    tmp &= ~SDHCI_CDNS_HRS04_WR; 
 | 
    writel(tmp, reg); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sdhci_cdns_phy_init(struct sdhci_cdns_plat *plat, 
 | 
                const void *fdt, int nodeoffset) 
 | 
{ 
 | 
    const fdt32_t *prop; 
 | 
    int ret, i; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { 
 | 
        prop = fdt_getprop(fdt, nodeoffset, 
 | 
                   sdhci_cdns_phy_cfgs[i].property, NULL); 
 | 
        if (!prop) 
 | 
            continue; 
 | 
  
 | 
        ret = sdhci_cdns_write_phy_reg(plat, 
 | 
                           sdhci_cdns_phy_cfgs[i].addr, 
 | 
                           fdt32_to_cpu(*prop)); 
 | 
        if (ret) 
 | 
            return ret; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sdhci_cdns_bind(struct udevice *dev) 
 | 
{ 
 | 
    struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 
 | 
  
 | 
    return sdhci_bind(dev, &plat->mmc, &plat->cfg); 
 | 
} 
 | 
  
 | 
static int sdhci_cdns_probe(struct udevice *dev) 
 | 
{ 
 | 
    DECLARE_GLOBAL_DATA_PTR; 
 | 
    struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); 
 | 
    struct sdhci_cdns_plat *plat = dev_get_platdata(dev); 
 | 
    struct sdhci_host *host = dev_get_priv(dev); 
 | 
    fdt_addr_t base; 
 | 
    int ret; 
 | 
  
 | 
    base = devfdt_get_addr(dev); 
 | 
    if (base == FDT_ADDR_T_NONE) 
 | 
        return -EINVAL; 
 | 
  
 | 
    plat->hrs_addr = devm_ioremap(dev, base, SZ_1K); 
 | 
    if (!plat->hrs_addr) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    host->name = dev->name; 
 | 
    host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE; 
 | 
    host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD; 
 | 
  
 | 
    ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev)); 
 | 
    if (ret) 
 | 
        return ret; 
 | 
  
 | 
    ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); 
 | 
    if (ret) 
 | 
        return ret; 
 | 
  
 | 
    upriv->mmc = &plat->mmc; 
 | 
    host->mmc = &plat->mmc; 
 | 
    host->mmc->priv = host; 
 | 
  
 | 
    return sdhci_probe(dev); 
 | 
} 
 | 
  
 | 
static const struct udevice_id sdhci_cdns_match[] = { 
 | 
    { .compatible = "socionext,uniphier-sd4hc" }, 
 | 
    { .compatible = "cdns,sd4hc" }, 
 | 
    { /* sentinel */ } 
 | 
}; 
 | 
  
 | 
U_BOOT_DRIVER(sdhci_cdns) = { 
 | 
    .name = "sdhci-cdns", 
 | 
    .id = UCLASS_MMC, 
 | 
    .of_match = sdhci_cdns_match, 
 | 
    .bind = sdhci_cdns_bind, 
 | 
    .probe = sdhci_cdns_probe, 
 | 
    .priv_auto_alloc_size = sizeof(struct sdhci_host), 
 | 
    .platdata_auto_alloc_size = sizeof(struct sdhci_cdns_plat), 
 | 
    .ops = &sdhci_ops, 
 | 
}; 
 |