uboot/drivers/mmc/snps_dw_mmc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Synopsys DesignWare Multimedia Card Interface driver
   4 * extensions used in various Synopsys ARC devboards.
   5 *
   6 * Copyright (C) 2019 Synopsys
   7 * Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
   8 */
   9
  10#include <common.h>
  11#include <clk.h>
  12#include <dm.h>
  13#include <dwmmc.h>
  14#include <errno.h>
  15#include <fdtdec.h>
  16#include <dm/device_compat.h>
  17#include <linux/libfdt.h>
  18#include <linux/err.h>
  19#include <malloc.h>
  20
  21#define CLOCK_MIN               400000  /*  400 kHz */
  22#define FIFO_MIN                8
  23#define FIFO_MAX                4096
  24
  25struct snps_dwmci_plat {
  26        struct mmc_config       cfg;
  27        struct mmc              mmc;
  28};
  29
  30struct snps_dwmci_priv_data {
  31        struct dwmci_host       host;
  32        u32                     f_max;
  33};
  34
  35static int snps_dwmmc_clk_setup(struct udevice *dev)
  36{
  37        struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
  38        struct dwmci_host *host = &priv->host;
  39
  40        struct clk clk_ciu, clk_biu;
  41        int ret;
  42
  43        ret = clk_get_by_name(dev, "ciu", &clk_ciu);
  44        if (ret)
  45                goto clk_err;
  46
  47        ret = clk_enable(&clk_ciu);
  48        if (ret && ret != -ENOSYS && ret != -ENOTSUPP)
  49                goto clk_err_ciu;
  50
  51        host->bus_hz = clk_get_rate(&clk_ciu);
  52        if (host->bus_hz < CLOCK_MIN) {
  53                ret = -EINVAL;
  54                goto clk_err_ciu_dis;
  55        }
  56
  57        ret = clk_get_by_name(dev, "biu", &clk_biu);
  58        if (ret)
  59                goto clk_err_ciu_dis;
  60
  61        ret = clk_enable(&clk_biu);
  62        if (ret && ret != -ENOSYS && ret != -ENOTSUPP)
  63                goto clk_err_biu;
  64
  65        return 0;
  66
  67clk_err_biu:
  68        clk_free(&clk_biu);
  69clk_err_ciu_dis:
  70        clk_disable(&clk_ciu);
  71clk_err_ciu:
  72        clk_free(&clk_ciu);
  73clk_err:
  74        dev_err(dev, "failed to setup clocks, ret %d\n", ret);
  75
  76        return ret;
  77}
  78
  79static int snps_dwmmc_of_to_plat(struct udevice *dev)
  80{
  81        struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
  82        struct dwmci_host *host = &priv->host;
  83        u32 fifo_depth;
  84        int ret;
  85
  86        host->ioaddr = dev_read_addr_ptr(dev);
  87
  88        /*
  89         * If fifo-depth is unset don't set fifoth_val - we will try to
  90         * auto detect it.
  91         */
  92        ret = dev_read_u32(dev, "fifo-depth", &fifo_depth);
  93        if (!ret) {
  94                if (fifo_depth < FIFO_MIN || fifo_depth > FIFO_MAX)
  95                        return -EINVAL;
  96
  97                host->fifoth_val = MSIZE(0x2) |
  98                                   RX_WMARK(fifo_depth / 2 - 1) |
  99                                   TX_WMARK(fifo_depth / 2);
 100        }
 101
 102        host->buswidth = dev_read_u32_default(dev, "bus-width", 4);
 103        if (host->buswidth != 1 && host->buswidth != 4 && host->buswidth != 8)
 104                return -EINVAL;
 105
 106        /*
 107         * If max-frequency is unset don't set priv->f_max - we will use
 108         * host->bus_hz in probe() instead.
 109         */
 110        ret = dev_read_u32(dev, "max-frequency", &priv->f_max);
 111        if (!ret && priv->f_max < CLOCK_MIN)
 112                return -EINVAL;
 113
 114        host->fifo_mode = dev_read_bool(dev, "fifo-mode");
 115        host->name = dev->name;
 116        host->dev_index = 0;
 117        host->priv = priv;
 118
 119        return 0;
 120}
 121
 122int snps_dwmmc_getcd(struct udevice *dev)
 123{
 124        struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
 125        struct dwmci_host *host = &priv->host;
 126
 127        return !(dwmci_readl(host, DWMCI_CDETECT) & 1);
 128}
 129
 130struct dm_mmc_ops snps_dwmci_dm_ops;
 131
 132static int snps_dwmmc_probe(struct udevice *dev)
 133{
 134#ifdef CONFIG_BLK
 135        struct snps_dwmci_plat *plat = dev_get_plat(dev);
 136#endif
 137        struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
 138        struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
 139        struct dwmci_host *host = &priv->host;
 140        unsigned int clock_max;
 141        int ret;
 142
 143        /* Extend generic 'dm_dwmci_ops' with our 'getcd' implementation */
 144        memcpy(&snps_dwmci_dm_ops, &dm_dwmci_ops, sizeof(struct dm_mmc_ops));
 145        snps_dwmci_dm_ops.get_cd = snps_dwmmc_getcd;
 146
 147        ret = snps_dwmmc_clk_setup(dev);
 148        if (ret)
 149                return ret;
 150
 151        if (!priv->f_max)
 152                clock_max = host->bus_hz;
 153        else
 154                clock_max = min_t(unsigned int, host->bus_hz, priv->f_max);
 155
 156#ifdef CONFIG_BLK
 157        dwmci_setup_cfg(&plat->cfg, host, clock_max, CLOCK_MIN);
 158        host->mmc = &plat->mmc;
 159#else
 160        ret = add_dwmci(host, clock_max, CLOCK_MIN);
 161        if (ret)
 162                return ret;
 163#endif
 164        host->mmc->priv = &priv->host;
 165        upriv->mmc = host->mmc;
 166        host->mmc->dev = dev;
 167
 168        return dwmci_probe(dev);
 169}
 170
 171static int snps_dwmmc_bind(struct udevice *dev)
 172{
 173#ifdef CONFIG_BLK
 174        struct snps_dwmci_plat *plat = dev_get_plat(dev);
 175        int ret;
 176
 177        ret = dwmci_bind(dev, &plat->mmc, &plat->cfg);
 178        if (ret)
 179                return ret;
 180#endif
 181
 182        return 0;
 183}
 184
 185static const struct udevice_id snps_dwmmc_ids[] = {
 186        { .compatible = "snps,dw-mshc" },
 187        { }
 188};
 189
 190U_BOOT_DRIVER(snps_dwmmc_drv) = {
 191        .name                           = "snps_dw_mmc",
 192        .id                             = UCLASS_MMC,
 193        .of_match                       = snps_dwmmc_ids,
 194        .of_to_plat             = snps_dwmmc_of_to_plat,
 195        .ops                            = &snps_dwmci_dm_ops,
 196        .bind                           = snps_dwmmc_bind,
 197        .probe                          = snps_dwmmc_probe,
 198        .priv_auto              = sizeof(struct snps_dwmci_priv_data),
 199        .plat_auto      = sizeof(struct snps_dwmci_plat),
 200};
 201