uboot/drivers/mmc/s5p_sdhci.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * (C) Copyright 2012 SAMSUNG Electronics
   4 * Jaehoon Chung <jh80.chung@samsung.com>
   5 */
   6
   7#include <common.h>
   8#include <dm.h>
   9#include <log.h>
  10#include <malloc.h>
  11#include <sdhci.h>
  12#include <fdtdec.h>
  13#include <asm/global_data.h>
  14#include <linux/libfdt.h>
  15#include <asm/gpio.h>
  16#include <asm/arch/mmc.h>
  17#include <asm/arch/clk.h>
  18#include <errno.h>
  19#include <asm/arch/pinmux.h>
  20
  21#ifdef CONFIG_DM_MMC
  22struct s5p_sdhci_plat {
  23        struct mmc_config cfg;
  24        struct mmc mmc;
  25};
  26
  27DECLARE_GLOBAL_DATA_PTR;
  28#endif
  29
  30static char *S5P_NAME = "SAMSUNG SDHCI";
  31static void s5p_sdhci_set_control_reg(struct sdhci_host *host)
  32{
  33        unsigned long val, ctrl;
  34        /*
  35         * SELCLKPADDS[17:16]
  36         * 00 = 2mA
  37         * 01 = 4mA
  38         * 10 = 7mA
  39         * 11 = 9mA
  40         */
  41        sdhci_writel(host, SDHCI_CTRL4_DRIVE_MASK(0x3), SDHCI_CONTROL4);
  42
  43        val = sdhci_readl(host, SDHCI_CONTROL2);
  44        val &= SDHCI_CTRL2_SELBASECLK_MASK(3);
  45
  46        val |=  SDHCI_CTRL2_ENSTAASYNCCLR |
  47                SDHCI_CTRL2_ENCMDCNFMSK |
  48                SDHCI_CTRL2_ENFBCLKRX |
  49                SDHCI_CTRL2_ENCLKOUTHOLD;
  50
  51        sdhci_writel(host, val, SDHCI_CONTROL2);
  52
  53        /*
  54         * FCSEL3[31] FCSEL2[23] FCSEL1[15] FCSEL0[7]
  55         * FCSel[1:0] : Rx Feedback Clock Delay Control
  56         *      Inverter delay means10ns delay if SDCLK 50MHz setting
  57         *      01 = Delay1 (basic delay)
  58         *      11 = Delay2 (basic delay + 2ns)
  59         *      00 = Delay3 (inverter delay)
  60         *      10 = Delay4 (inverter delay + 2ns)
  61         */
  62        val = SDHCI_CTRL3_FCSEL0 | SDHCI_CTRL3_FCSEL1;
  63        sdhci_writel(host, val, SDHCI_CONTROL3);
  64
  65        /*
  66         * SELBASECLK[5:4]
  67         * 00/01 = HCLK
  68         * 10 = EPLL
  69         * 11 = XTI or XEXTCLK
  70         */
  71        ctrl = sdhci_readl(host, SDHCI_CONTROL2);
  72        ctrl &= ~SDHCI_CTRL2_SELBASECLK_MASK(0x3);
  73        ctrl |= SDHCI_CTRL2_SELBASECLK_MASK(0x2);
  74        sdhci_writel(host, ctrl, SDHCI_CONTROL2);
  75}
  76
  77static void s5p_set_clock(struct sdhci_host *host, u32 div)
  78{
  79        /* ToDo : Use the Clock Framework */
  80        set_mmc_clk(host->index, div);
  81}
  82
  83static const struct sdhci_ops s5p_sdhci_ops = {
  84        .set_clock      = &s5p_set_clock,
  85        .set_control_reg = &s5p_sdhci_set_control_reg,
  86};
  87
  88static int s5p_sdhci_core_init(struct sdhci_host *host)
  89{
  90        host->name = S5P_NAME;
  91
  92        host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_VOLTAGE |
  93                SDHCI_QUIRK_32BIT_DMA_ADDR |
  94                SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_USE_WIDE8;
  95        host->max_clk = 52000000;
  96        host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
  97        host->ops = &s5p_sdhci_ops;
  98
  99        if (host->bus_width == 8)
 100                host->host_caps |= MMC_MODE_8BIT;
 101
 102#ifndef CONFIG_BLK
 103        return add_sdhci(host, 0, 400000);
 104#else
 105        return 0;
 106#endif
 107}
 108
 109int s5p_sdhci_init(u32 regbase, int index, int bus_width)
 110{
 111        struct sdhci_host *host = calloc(1, sizeof(struct sdhci_host));
 112        if (!host) {
 113                printf("sdhci__host allocation fail!\n");
 114                return -ENOMEM;
 115        }
 116        host->ioaddr = (void *)regbase;
 117        host->index = index;
 118        host->bus_width = bus_width;
 119
 120        return s5p_sdhci_core_init(host);
 121}
 122
 123static int do_sdhci_init(struct sdhci_host *host)
 124{
 125        int dev_id, flag, ret;
 126
 127        flag = host->bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
 128        dev_id = host->index + PERIPH_ID_SDMMC0;
 129
 130        ret = exynos_pinmux_config(dev_id, flag);
 131        if (ret) {
 132                printf("external SD not configured\n");
 133                return ret;
 134        }
 135
 136        if (dm_gpio_is_valid(&host->pwr_gpio)) {
 137                dm_gpio_set_value(&host->pwr_gpio, 1);
 138                ret = exynos_pinmux_config(dev_id, flag);
 139                if (ret) {
 140                        debug("MMC not configured\n");
 141                        return ret;
 142                }
 143        }
 144
 145        if (dm_gpio_is_valid(&host->cd_gpio)) {
 146                ret = dm_gpio_get_value(&host->cd_gpio);
 147                if (ret) {
 148                        debug("no SD card detected (%d)\n", ret);
 149                        return -ENODEV;
 150                }
 151        }
 152
 153        return s5p_sdhci_core_init(host);
 154}
 155
 156static int sdhci_get_config(const void *blob, int node, struct sdhci_host *host)
 157{
 158        int bus_width, dev_id;
 159        unsigned int base;
 160
 161        /* Get device id */
 162        dev_id = pinmux_decode_periph_id(blob, node);
 163        if (dev_id < PERIPH_ID_SDMMC0 || dev_id > PERIPH_ID_SDMMC3) {
 164                debug("MMC: Can't get device id\n");
 165                return -EINVAL;
 166        }
 167        host->index = dev_id - PERIPH_ID_SDMMC0;
 168
 169        /* Get bus width */
 170        bus_width = fdtdec_get_int(blob, node, "samsung,bus-width", 0);
 171        if (bus_width <= 0) {
 172                debug("MMC: Can't get bus-width\n");
 173                return -EINVAL;
 174        }
 175        host->bus_width = bus_width;
 176
 177        /* Get the base address from the device node */
 178        base = fdtdec_get_addr(blob, node, "reg");
 179        if (!base) {
 180                debug("MMC: Can't get base address\n");
 181                return -EINVAL;
 182        }
 183        host->ioaddr = (void *)base;
 184
 185        gpio_request_by_name_nodev(offset_to_ofnode(node), "pwr-gpios", 0,
 186                                   &host->pwr_gpio, GPIOD_IS_OUT);
 187        gpio_request_by_name_nodev(offset_to_ofnode(node), "cd-gpios", 0,
 188                                   &host->cd_gpio, GPIOD_IS_IN);
 189
 190        return 0;
 191}
 192
 193#ifdef CONFIG_DM_MMC
 194static int s5p_sdhci_probe(struct udevice *dev)
 195{
 196        struct s5p_sdhci_plat *plat = dev_get_plat(dev);
 197        struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
 198        struct sdhci_host *host = dev_get_priv(dev);
 199        int ret;
 200
 201        ret = sdhci_get_config(gd->fdt_blob, dev_of_offset(dev), host);
 202        if (ret)
 203                return ret;
 204
 205        ret = do_sdhci_init(host);
 206        if (ret)
 207                return ret;
 208
 209        ret = mmc_of_parse(dev, &plat->cfg);
 210        if (ret)
 211                return ret;
 212
 213        host->mmc = &plat->mmc;
 214        host->mmc->dev = dev;
 215
 216        ret = sdhci_setup_cfg(&plat->cfg, host, 0, 400000);
 217        if (ret)
 218                return ret;
 219
 220        host->mmc->priv = host;
 221        upriv->mmc = host->mmc;
 222
 223        return sdhci_probe(dev);
 224}
 225
 226static int s5p_sdhci_bind(struct udevice *dev)
 227{
 228        struct s5p_sdhci_plat *plat = dev_get_plat(dev);
 229        int ret;
 230
 231        ret = sdhci_bind(dev, &plat->mmc, &plat->cfg);
 232        if (ret)
 233                return ret;
 234
 235        return 0;
 236}
 237
 238static const struct udevice_id s5p_sdhci_ids[] = {
 239        { .compatible = "samsung,exynos4412-sdhci"},
 240        { }
 241};
 242
 243U_BOOT_DRIVER(s5p_sdhci_drv) = {
 244        .name           = "s5p_sdhci",
 245        .id             = UCLASS_MMC,
 246        .of_match       = s5p_sdhci_ids,
 247        .bind           = s5p_sdhci_bind,
 248        .ops            = &sdhci_ops,
 249        .probe          = s5p_sdhci_probe,
 250        .priv_auto      = sizeof(struct sdhci_host),
 251        .plat_auto      = sizeof(struct s5p_sdhci_plat),
 252};
 253#endif /* CONFIG_DM_MMC */
 254