uboot/drivers/mmc/sdhci-cadence.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2016 Socionext Inc.
   4 *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
   5 */
   6
   7#include <common.h>
   8#include <dm.h>
   9#include <asm/global_data.h>
  10#include <dm/device_compat.h>
  11#include <linux/bitfield.h>
  12#include <linux/bitops.h>
  13#include <linux/bug.h>
  14#include <linux/io.h>
  15#include <linux/iopoll.h>
  16#include <linux/sizes.h>
  17#include <linux/libfdt.h>
  18#include <mmc.h>
  19#include <sdhci.h>
  20
  21/* HRS - Host Register Set (specific to Cadence) */
  22#define SDHCI_CDNS_HRS04                0x10            /* PHY access port */
  23#define   SDHCI_CDNS_HRS04_ACK                  BIT(26)
  24#define   SDHCI_CDNS_HRS04_RD                   BIT(25)
  25#define   SDHCI_CDNS_HRS04_WR                   BIT(24)
  26#define   SDHCI_CDNS_HRS04_RDATA                GENMASK(23, 16)
  27#define   SDHCI_CDNS_HRS04_WDATA                GENMASK(15, 8)
  28#define   SDHCI_CDNS_HRS04_ADDR                 GENMASK(5, 0)
  29
  30#define SDHCI_CDNS_HRS06                0x18            /* eMMC control */
  31#define   SDHCI_CDNS_HRS06_TUNE_UP              BIT(15)
  32#define   SDHCI_CDNS_HRS06_TUNE                 GENMASK(13, 8)
  33#define   SDHCI_CDNS_HRS06_MODE                 GENMASK(2, 0)
  34#define   SDHCI_CDNS_HRS06_MODE_SD              0x0
  35#define   SDHCI_CDNS_HRS06_MODE_MMC_SDR         0x2
  36#define   SDHCI_CDNS_HRS06_MODE_MMC_DDR         0x3
  37#define   SDHCI_CDNS_HRS06_MODE_MMC_HS200       0x4
  38#define   SDHCI_CDNS_HRS06_MODE_MMC_HS400       0x5
  39#define   SDHCI_CDNS_HRS06_MODE_MMC_HS400ES     0x6
  40
  41/* SRS - Slot Register Set (SDHCI-compatible) */
  42#define SDHCI_CDNS_SRS_BASE             0x200
  43
  44/* PHY */
  45#define SDHCI_CDNS_PHY_DLY_SD_HS        0x00
  46#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT   0x01
  47#define SDHCI_CDNS_PHY_DLY_UHS_SDR12    0x02
  48#define SDHCI_CDNS_PHY_DLY_UHS_SDR25    0x03
  49#define SDHCI_CDNS_PHY_DLY_UHS_SDR50    0x04
  50#define SDHCI_CDNS_PHY_DLY_UHS_DDR50    0x05
  51#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY  0x06
  52#define SDHCI_CDNS_PHY_DLY_EMMC_SDR     0x07
  53#define SDHCI_CDNS_PHY_DLY_EMMC_DDR     0x08
  54#define SDHCI_CDNS_PHY_DLY_SDCLK        0x0b
  55#define SDHCI_CDNS_PHY_DLY_HSMMC        0x0c
  56#define SDHCI_CDNS_PHY_DLY_STROBE       0x0d
  57
  58/*
  59 * The tuned val register is 6 bit-wide, but not the whole of the range is
  60 * available.  The range 0-42 seems to be available (then 43 wraps around to 0)
  61 * but I am not quite sure if it is official.  Use only 0 to 39 for safety.
  62 */
  63#define SDHCI_CDNS_MAX_TUNING_LOOP      40
  64
  65struct sdhci_cdns_plat {
  66        struct mmc_config cfg;
  67        struct mmc mmc;
  68        void __iomem *hrs_addr;
  69};
  70
  71struct sdhci_cdns_phy_cfg {
  72        const char *property;
  73        u8 addr;
  74};
  75
  76static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
  77        { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
  78        { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
  79        { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
  80        { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, },
  81        { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, },
  82        { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, },
  83        { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, },
  84        { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, },
  85        { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, },
  86        { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, },
  87        { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, },
  88};
  89
  90static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat,
  91                                    u8 addr, u8 data)
  92{
  93        void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS04;
  94        u32 tmp;
  95        int ret;
  96
  97        tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) |
  98              FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr);
  99        writel(tmp, reg);
 100
 101        tmp |= SDHCI_CDNS_HRS04_WR;
 102        writel(tmp, reg);
 103
 104        ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 10);
 105        if (ret)
 106                return ret;
 107
 108        tmp &= ~SDHCI_CDNS_HRS04_WR;
 109        writel(tmp, reg);
 110
 111        return 0;
 112}
 113
 114static int sdhci_cdns_phy_init(struct sdhci_cdns_plat *plat,
 115                                const void *fdt, int nodeoffset)
 116{
 117        const fdt32_t *prop;
 118        int ret, i;
 119
 120        for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
 121                prop = fdt_getprop(fdt, nodeoffset,
 122                                   sdhci_cdns_phy_cfgs[i].property, NULL);
 123                if (!prop)
 124                        continue;
 125
 126                ret = sdhci_cdns_write_phy_reg(plat,
 127                                               sdhci_cdns_phy_cfgs[i].addr,
 128                                               fdt32_to_cpu(*prop));
 129                if (ret)
 130                        return ret;
 131        }
 132
 133        return 0;
 134}
 135
 136static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
 137{
 138        struct mmc *mmc = host->mmc;
 139        struct sdhci_cdns_plat *plat = dev_get_plat(mmc->dev);
 140        unsigned int clock = mmc->clock;
 141        u32 mode, tmp;
 142
 143        /*
 144         * REVISIT:
 145         * The mode should be decided by MMC_TIMING_* like Linux, but
 146         * U-Boot does not support timing.  Use the clock frequency instead.
 147         */
 148        if (clock <= 26000000) {
 149                mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */
 150        } else if (clock <= 52000000) {
 151                if (mmc->ddr_mode)
 152                        mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
 153                else
 154                        mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
 155        } else {
 156                if (mmc->ddr_mode)
 157                        mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400;
 158                else
 159                        mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200;
 160        }
 161
 162        tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06);
 163        tmp &= ~SDHCI_CDNS_HRS06_MODE;
 164        tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode);
 165        writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06);
 166}
 167
 168static const struct sdhci_ops sdhci_cdns_ops = {
 169        .set_control_reg = sdhci_cdns_set_control_reg,
 170};
 171
 172static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat,
 173                                   unsigned int val)
 174{
 175        void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS06;
 176        u32 tmp;
 177        int i, ret;
 178
 179        if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val)))
 180                return -EINVAL;
 181
 182        tmp = readl(reg);
 183        tmp &= ~SDHCI_CDNS_HRS06_TUNE;
 184        tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val);
 185
 186        /*
 187         * Workaround for IP errata:
 188         * The IP6116 SD/eMMC PHY design has a timing issue on receive data
 189         * path. Send tune request twice.
 190         */
 191        for (i = 0; i < 2; i++) {
 192                tmp |= SDHCI_CDNS_HRS06_TUNE_UP;
 193                writel(tmp, reg);
 194
 195                ret = readl_poll_timeout(reg, tmp,
 196                                         !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), 1);
 197                if (ret)
 198                        return ret;
 199        }
 200
 201        return 0;
 202}
 203
 204static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev,
 205                                                    unsigned int opcode)
 206{
 207        struct sdhci_cdns_plat *plat = dev_get_plat(dev);
 208        struct mmc *mmc = &plat->mmc;
 209        int cur_streak = 0;
 210        int max_streak = 0;
 211        int end_of_streak = 0;
 212        int i;
 213
 214        /*
 215         * This handler only implements the eMMC tuning that is specific to
 216         * this controller.  The tuning for SD timing should be handled by the
 217         * SDHCI core.
 218         */
 219        if (!IS_MMC(mmc))
 220                return -ENOTSUPP;
 221
 222        if (WARN_ON(opcode != MMC_CMD_SEND_TUNING_BLOCK_HS200))
 223                return -EINVAL;
 224
 225        for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
 226                if (sdhci_cdns_set_tune_val(plat, i) ||
 227                    mmc_send_tuning(mmc, opcode, NULL)) { /* bad */
 228                        cur_streak = 0;
 229                } else { /* good */
 230                        cur_streak++;
 231                        if (cur_streak > max_streak) {
 232                                max_streak = cur_streak;
 233                                end_of_streak = i;
 234                        }
 235                }
 236        }
 237
 238        if (!max_streak) {
 239                dev_err(dev, "no tuning point found\n");
 240                return -EIO;
 241        }
 242
 243        return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2);
 244}
 245
 246static struct dm_mmc_ops sdhci_cdns_mmc_ops;
 247
 248static int sdhci_cdns_bind(struct udevice *dev)
 249{
 250        struct sdhci_cdns_plat *plat = dev_get_plat(dev);
 251
 252        return sdhci_bind(dev, &plat->mmc, &plat->cfg);
 253}
 254
 255static int sdhci_cdns_probe(struct udevice *dev)
 256{
 257        DECLARE_GLOBAL_DATA_PTR;
 258        struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
 259        struct sdhci_cdns_plat *plat = dev_get_plat(dev);
 260        struct sdhci_host *host = dev_get_priv(dev);
 261        fdt_addr_t base;
 262        int ret;
 263
 264        base = dev_read_addr(dev);
 265        if (base == FDT_ADDR_T_NONE)
 266                return -EINVAL;
 267
 268        plat->hrs_addr = devm_ioremap(dev, base, SZ_1K);
 269        if (!plat->hrs_addr)
 270                return -ENOMEM;
 271
 272        host->name = dev->name;
 273        host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE;
 274        host->ops = &sdhci_cdns_ops;
 275        host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD;
 276        sdhci_cdns_mmc_ops = sdhci_ops;
 277#ifdef MMC_SUPPORTS_TUNING
 278        sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning;
 279#endif
 280
 281        ret = mmc_of_parse(dev, &plat->cfg);
 282        if (ret)
 283                return ret;
 284
 285        ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev));
 286        if (ret)
 287                return ret;
 288
 289        host->mmc = &plat->mmc;
 290        host->mmc->dev = dev;
 291        ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0);
 292        if (ret)
 293                return ret;
 294
 295        upriv->mmc = &plat->mmc;
 296        host->mmc->priv = host;
 297
 298        return sdhci_probe(dev);
 299}
 300
 301static const struct udevice_id sdhci_cdns_match[] = {
 302        { .compatible = "socionext,uniphier-sd4hc" },
 303        { .compatible = "cdns,sd4hc" },
 304        { /* sentinel */ }
 305};
 306
 307U_BOOT_DRIVER(sdhci_cdns) = {
 308        .name = "sdhci-cdns",
 309        .id = UCLASS_MMC,
 310        .of_match = sdhci_cdns_match,
 311        .bind = sdhci_cdns_bind,
 312        .probe = sdhci_cdns_probe,
 313        .priv_auto      = sizeof(struct sdhci_host),
 314        .plat_auto      = sizeof(struct sdhci_cdns_plat),
 315        .ops = &sdhci_cdns_mmc_ops,
 316};
 317