uboot/drivers/mmc/msm_sdhci.c
<<
>>
Prefs
   1/*
   2 * Qualcomm SDHCI driver - SD/eMMC controller
   3 *
   4 * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
   5 *
   6 * Based on Linux driver
   7 *
   8 * SPDX-License-Identifier:     GPL-2.0+
   9 */
  10
  11#include <common.h>
  12#include <clk.h>
  13#include <dm.h>
  14#include <sdhci.h>
  15#include <wait_bit.h>
  16#include <asm/io.h>
  17#include <linux/bitops.h>
  18
  19/* Non-standard registers needed for SDHCI startup */
  20#define SDCC_MCI_POWER   0x0
  21#define SDCC_MCI_POWER_SW_RST BIT(7)
  22
  23/* This is undocumented register */
  24#define SDCC_MCI_VERSION             0x50
  25#define SDCC_MCI_VERSION_MAJOR_SHIFT 28
  26#define SDCC_MCI_VERSION_MAJOR_MASK  (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT)
  27#define SDCC_MCI_VERSION_MINOR_MASK  0xff
  28
  29#define SDCC_MCI_STATUS2 0x6C
  30#define SDCC_MCI_STATUS2_MCI_ACT 0x1
  31#define SDCC_MCI_HC_MODE 0x78
  32
  33/* Offset to SDHCI registers */
  34#define SDCC_SDHCI_OFFSET 0x900
  35
  36/* Non standard (?) SDHCI register */
  37#define SDHCI_VENDOR_SPEC_CAPABILITIES0  0x11c
  38
  39struct msm_sdhc {
  40        struct sdhci_host host;
  41        void *base;
  42};
  43
  44DECLARE_GLOBAL_DATA_PTR;
  45
  46static int msm_sdc_clk_init(struct udevice *dev)
  47{
  48        uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
  49                                        "clock-frequency", 400000);
  50        uint clkd[2]; /* clk_id and clk_no */
  51        int clk_offset;
  52        struct udevice *clk;
  53        int ret;
  54
  55        ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd,
  56                                   2);
  57        if (ret)
  58                return ret;
  59
  60        clk_offset = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]);
  61        if (clk_offset < 0)
  62                return clk_offset;
  63
  64        ret = uclass_get_device_by_of_offset(UCLASS_CLK, clk_offset, &clk);
  65        if (ret)
  66                return ret;
  67
  68        ret = clk_set_periph_rate(clk, clkd[1], clk_rate);
  69        if (ret < 0)
  70                return ret;
  71
  72        return 0;
  73}
  74
  75static int msm_sdc_probe(struct udevice *dev)
  76{
  77        struct msm_sdhc *prv = dev_get_priv(dev);
  78        struct sdhci_host *host = &prv->host;
  79        u32 core_version, core_minor, core_major;
  80        int ret;
  81
  82        host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B;
  83
  84        /* Init clocks */
  85        ret = msm_sdc_clk_init(dev);
  86        if (ret)
  87                return ret;
  88
  89        /* Reset the core and Enable SDHC mode */
  90        writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST,
  91               prv->base + SDCC_MCI_POWER);
  92
  93
  94        /* Wait for reset to be written to register */
  95        if (wait_for_bit(__func__, prv->base + SDCC_MCI_STATUS2,
  96                         SDCC_MCI_STATUS2_MCI_ACT, false, 10, false)) {
  97                printf("msm_sdhci: reset request failed\n");
  98                return -EIO;
  99        }
 100
 101        /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
 102        if (wait_for_bit(__func__, prv->base + SDCC_MCI_POWER,
 103                         SDCC_MCI_POWER_SW_RST, false, 2, false)) {
 104                printf("msm_sdhci: stuck in reset\n");
 105                return -ETIMEDOUT;
 106        }
 107
 108        /* Enable host-controller mode */
 109        writel(1, prv->base + SDCC_MCI_HC_MODE);
 110
 111        core_version = readl(prv->base + SDCC_MCI_VERSION);
 112
 113        core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK);
 114        core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT;
 115
 116        core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK;
 117
 118        /*
 119         * Support for some capabilities is not advertised by newer
 120         * controller versions and must be explicitly enabled.
 121         */
 122        if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) {
 123                u32 caps = readl(host->ioaddr + SDHCI_CAPABILITIES);
 124                caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT;
 125                writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0);
 126        }
 127
 128        /* Set host controller version */
 129        host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
 130
 131        /* automatically detect max and min speed */
 132        return add_sdhci(host, 0, 0);
 133}
 134
 135static int msm_sdc_remove(struct udevice *dev)
 136{
 137        struct msm_sdhc *priv = dev_get_priv(dev);
 138
 139         /* Disable host-controller mode */
 140        writel(0, priv->base + SDCC_MCI_HC_MODE);
 141
 142        return 0;
 143}
 144
 145static int msm_ofdata_to_platdata(struct udevice *dev)
 146{
 147        struct udevice *parent = dev->parent;
 148        struct msm_sdhc *priv = dev_get_priv(dev);
 149        struct sdhci_host *host = &priv->host;
 150
 151        host->name = strdup(dev->name);
 152        host->ioaddr = (void *)dev_get_addr(dev);
 153        host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
 154                                         "bus-width", 4);
 155        host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0);
 156        priv->base = (void *)fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
 157                                                              parent->of_offset,
 158                                                              dev->of_offset,
 159                                                              "reg", 1, NULL);
 160        if (priv->base == (void *)FDT_ADDR_T_NONE ||
 161            host->ioaddr == (void *)FDT_ADDR_T_NONE)
 162                return -EINVAL;
 163
 164        return 0;
 165}
 166
 167static const struct udevice_id msm_mmc_ids[] = {
 168        { .compatible = "qcom,sdhci-msm-v4" },
 169        { }
 170};
 171
 172U_BOOT_DRIVER(msm_sdc_drv) = {
 173        .name           = "msm_sdc",
 174        .id             = UCLASS_MMC,
 175        .of_match       = msm_mmc_ids,
 176        .ofdata_to_platdata = msm_ofdata_to_platdata,
 177        .probe          = msm_sdc_probe,
 178        .remove         = msm_sdc_remove,
 179        .priv_auto_alloc_size = sizeof(struct msm_sdhc),
 180};
 181