aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/include/asm/iproc-common/iproc_sdhci.h12
-rw-r--r--common/spl/spl_mmc.c14
-rw-r--r--drivers/mmc/Kconfig11
-rw-r--r--drivers/mmc/Makefile1
-rw-r--r--drivers/mmc/iproc_sdhci.c247
-rw-r--r--drivers/mmc/mmc-uclass.c14
-rw-r--r--drivers/mmc/mmc.c39
-rw-r--r--drivers/mmc/stm32_sdmmc2.c13
-rw-r--r--include/mmc.h14
9 files changed, 353 insertions, 12 deletions
diff --git a/arch/arm/include/asm/iproc-common/iproc_sdhci.h b/arch/arm/include/asm/iproc-common/iproc_sdhci.h
new file mode 100644
index 0000000..4e29921
--- /dev/null
+++ b/arch/arm/include/asm/iproc-common/iproc_sdhci.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: <SPDX License Expression> */
+/*
+ * Copyright 2019 Broadcom
+ *
+ */
+
+#ifndef __IPROC_SDHCI_H
+#define __IPROC_SDHCI_H
+
+int iproc_sdhci_init(int dev_index, u32 quirks);
+
+#endif
diff --git a/common/spl/spl_mmc.c b/common/spl/spl_mmc.c
index c5cae97..34e1e73 100644
--- a/common/spl/spl_mmc.c
+++ b/common/spl/spl_mmc.c
@@ -123,31 +123,25 @@ static int spl_mmc_get_device_index(u32 boot_device)
static int spl_mmc_find_device(struct mmc **mmcp, u32 boot_device)
{
-#if CONFIG_IS_ENABLED(DM_MMC)
- struct udevice *dev;
-#endif
int err, mmc_dev;
mmc_dev = spl_mmc_get_device_index(boot_device);
if (mmc_dev < 0)
return mmc_dev;
+#if CONFIG_IS_ENABLED(DM_MMC)
+ err = mmc_init_device(mmc_dev);
+#else
err = mmc_initialize(NULL);
+#endif /* DM_MMC */
if (err) {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
printf("spl: could not initialize mmc. error: %d\n", err);
#endif
return err;
}
-
-#if CONFIG_IS_ENABLED(DM_MMC)
- err = uclass_get_device(UCLASS_MMC, mmc_dev, &dev);
- if (!err)
- *mmcp = mmc_get_mmc_dev(dev);
-#else
*mmcp = find_mmc_device(mmc_dev);
err = *mmcp ? 0 : -ENODEV;
-#endif
if (err) {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
printf("spl: could not find mmc device %d. error: %d\n",
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index 2bc9d8d..18af076 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -489,6 +489,17 @@ config MMC_SDHCI_AM654
Support for Secure Digital Host Controller Interface (SDHCI)
controllers present on TI's AM654 SOCs.
+config MMC_SDHCI_IPROC
+ bool "SDHCI support for the iProc SD/MMC Controller"
+ depends on MMC_SDHCI
+ help
+ This selects the iProc SD/MMC controller.
+
+ If you have a Broadcom IPROC platform with SD or MMC devices,
+ say Y or M here.
+
+ If unsure, say N.
+
config MMC_SDHCI_KONA
bool "SDHCI support on Broadcom KONA platform"
depends on MMC_SDHCI
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 5594195..9c1f8e5 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_MMC_SDHCI_BCM2835) += bcm2835_sdhci.o
obj-$(CONFIG_MMC_SDHCI_BCMSTB) += bcmstb_sdhci.o
obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o
obj-$(CONFIG_MMC_SDHCI_AM654) += am654_sdhci.o
+obj-$(CONFIG_MMC_SDHCI_IPROC) += iproc_sdhci.o
obj-$(CONFIG_MMC_SDHCI_KONA) += kona_sdhci.o
obj-$(CONFIG_MMC_SDHCI_MSM) += msm_sdhci.o
obj-$(CONFIG_MMC_SDHCI_MV) += mv_sdhci.o
diff --git a/drivers/mmc/iproc_sdhci.c b/drivers/mmc/iproc_sdhci.c
new file mode 100644
index 0000000..831dd32
--- /dev/null
+++ b/drivers/mmc/iproc_sdhci.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 Broadcom.
+ *
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <malloc.h>
+#include <sdhci.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sdhci_iproc_host {
+ struct sdhci_host host;
+ u32 shadow_cmd;
+ u32 shadow_blk;
+};
+
+#define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18)
+
+static inline struct sdhci_iproc_host *to_iproc(struct sdhci_host *host)
+{
+ return (struct sdhci_iproc_host *)host;
+}
+
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
+static u32 sdhci_iproc_readl(struct sdhci_host *host, int reg)
+{
+ u32 val = readl(host->ioaddr + reg);
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE
+ printf("%s %d: readl [0x%02x] 0x%08x\n",
+ host->name, host->index, reg, val);
+#endif
+ return val;
+}
+
+static u16 sdhci_iproc_readw(struct sdhci_host *host, int reg)
+{
+ u32 val = sdhci_iproc_readl(host, (reg & ~3));
+ u16 word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff;
+ return word;
+}
+
+static u8 sdhci_iproc_readb(struct sdhci_host *host, int reg)
+{
+ u32 val = sdhci_iproc_readl(host, (reg & ~3));
+ u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff;
+ return byte;
+}
+
+static void sdhci_iproc_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ u32 clock = 0;
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE
+ printf("%s %d: writel [0x%02x] 0x%08x\n",
+ host->name, host->index, reg, val);
+#endif
+ writel(val, host->ioaddr + reg);
+
+ if (host->mmc)
+ clock = host->mmc->clock;
+ if (clock <= 400000) {
+ /* Round up to micro-second four SD clock delay */
+ if (clock)
+ udelay((4 * 1000000 + clock - 1) / clock);
+ else
+ udelay(10);
+ }
+}
+
+/*
+ * The Arasan has a bugette whereby it may lose the content of successive
+ * writes to the same register that are within two SD-card clock cycles of
+ * each other (a clock domain crossing problem). The data
+ * register does not have this problem, which is just as well - otherwise we'd
+ * have to nobble the DMA engine too.
+ *
+ * This wouldn't be a problem with the code except that we can only write the
+ * controller with 32-bit writes. So two different 16-bit registers are
+ * written back to back creates the problem.
+ *
+ * In reality, this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT
+ * are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND.
+ * The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so
+ * the work around can be further optimized. We can keep shadow values of
+ * BLOCK_SIZE, BLOCK_COUNT, and TRANSFER_MODE until a COMMAND is issued.
+ * Then, write the BLOCK_SIZE+BLOCK_COUNT in a single 32-bit write followed
+ * by the TRANSFER+COMMAND in another 32-bit write.
+ */
+static void sdhci_iproc_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ struct sdhci_iproc_host *iproc_host = to_iproc(host);
+ u32 word_shift = REG_OFFSET_IN_BITS(reg);
+ u32 mask = 0xffff << word_shift;
+ u32 oldval, newval;
+
+ if (reg == SDHCI_COMMAND) {
+ /* Write the block now as we are issuing a command */
+ if (iproc_host->shadow_blk != 0) {
+ sdhci_iproc_writel(host, iproc_host->shadow_blk,
+ SDHCI_BLOCK_SIZE);
+ iproc_host->shadow_blk = 0;
+ }
+ oldval = iproc_host->shadow_cmd;
+ } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
+ /* Block size and count are stored in shadow reg */
+ oldval = iproc_host->shadow_blk;
+ } else {
+ /* Read reg, all other registers are not shadowed */
+ oldval = sdhci_iproc_readl(host, (reg & ~3));
+ }
+ newval = (oldval & ~mask) | (val << word_shift);
+
+ if (reg == SDHCI_TRANSFER_MODE) {
+ /* Save the transfer mode until the command is issued */
+ iproc_host->shadow_cmd = newval;
+ } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
+ /* Save the block info until the command is issued */
+ iproc_host->shadow_blk = newval;
+ } else {
+ /* Command or other regular 32-bit write */
+ sdhci_iproc_writel(host, newval, reg & ~3);
+ }
+}
+
+static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ u32 oldval = sdhci_iproc_readl(host, (reg & ~3));
+ u32 byte_shift = REG_OFFSET_IN_BITS(reg);
+ u32 mask = 0xff << byte_shift;
+ u32 newval = (oldval & ~mask) | (val << byte_shift);
+
+ sdhci_iproc_writel(host, newval, reg & ~3);
+}
+#endif
+
+static void sdhci_iproc_set_ios_post(struct sdhci_host *host)
+{
+ u32 ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ /* Reset UHS mode bits */
+ ctrl &= ~SDHCI_CTRL_UHS_MASK;
+
+ if (host->mmc->ddr_mode)
+ ctrl |= UHS_DDR50_BUS_SPEED;
+
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+}
+
+static struct sdhci_ops sdhci_platform_ops = {
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
+ .read_l = sdhci_iproc_readl,
+ .read_w = sdhci_iproc_readw,
+ .read_b = sdhci_iproc_readb,
+ .write_l = sdhci_iproc_writel,
+ .write_w = sdhci_iproc_writew,
+ .write_b = sdhci_iproc_writeb,
+#endif
+ .set_ios_post = sdhci_iproc_set_ios_post,
+};
+
+struct iproc_sdhci_plat {
+ struct mmc_config cfg;
+ struct mmc mmc;
+};
+
+static int iproc_sdhci_probe(struct udevice *dev)
+{
+ struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+ struct iproc_sdhci_plat *plat = dev_get_platdata(dev);
+ struct sdhci_host *host = dev_get_priv(dev);
+ struct sdhci_iproc_host *iproc_host;
+ int node = dev_of_offset(dev);
+ u32 f_min_max[2];
+ int ret;
+
+ iproc_host = (struct sdhci_iproc_host *)
+ malloc(sizeof(struct sdhci_iproc_host));
+ if (!iproc_host) {
+ printf("%s: sdhci host malloc fail!\n", __func__);
+ return -ENOMEM;
+ }
+ iproc_host->shadow_cmd = 0;
+ iproc_host->shadow_blk = 0;
+
+ host->name = dev->name;
+ host->ioaddr = (void *)devfdt_get_addr(dev);
+ host->voltages = MMC_VDD_165_195 |
+ MMC_VDD_32_33 | MMC_VDD_33_34;
+ host->quirks = SDHCI_QUIRK_BROKEN_VOLTAGE;
+ host->host_caps = MMC_MODE_DDR_52MHz;
+ host->index = fdtdec_get_uint(gd->fdt_blob, node, "index", 0);
+ host->ops = &sdhci_platform_ops;
+ host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
+ ret = fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev),
+ "clock-freq-min-max", f_min_max, 2);
+ if (ret) {
+ printf("sdhci: clock-freq-min-max not found\n");
+ return ret;
+ }
+ host->max_clk = f_min_max[1];
+ host->bus_width = fdtdec_get_int(gd->fdt_blob,
+ dev_of_offset(dev), "bus-width", 4);
+
+ /* Update host_caps for 8 bit bus width */
+ if (host->bus_width == 8)
+ host->host_caps |= MMC_MODE_8BIT;
+
+ memcpy(&iproc_host->host, host, sizeof(struct sdhci_host));
+
+ ret = sdhci_setup_cfg(&plat->cfg, &iproc_host->host,
+ f_min_max[1], f_min_max[0]);
+ if (ret)
+ return ret;
+
+ iproc_host->host.mmc = &plat->mmc;
+ iproc_host->host.mmc->dev = dev;
+ iproc_host->host.mmc->priv = &iproc_host->host;
+ upriv->mmc = iproc_host->host.mmc;
+
+ return sdhci_probe(dev);
+}
+
+static int iproc_sdhci_bind(struct udevice *dev)
+{
+ struct iproc_sdhci_plat *plat = dev_get_platdata(dev);
+
+ return sdhci_bind(dev, &plat->mmc, &plat->cfg);
+}
+
+static const struct udevice_id iproc_sdhci_ids[] = {
+ { .compatible = "brcm,iproc-sdhci" },
+ { }
+};
+
+U_BOOT_DRIVER(iproc_sdhci_drv) = {
+ .name = "iproc_sdhci",
+ .id = UCLASS_MMC,
+ .of_match = iproc_sdhci_ids,
+ .ops = &sdhci_ops,
+ .bind = iproc_sdhci_bind,
+ .probe = iproc_sdhci_probe,
+ .priv_auto_alloc_size = sizeof(struct sdhci_host),
+ .platdata_auto_alloc_size = sizeof(struct iproc_sdhci_plat),
+};
diff --git a/drivers/mmc/mmc-uclass.c b/drivers/mmc/mmc-uclass.c
index 37c3843..c7a832c 100644
--- a/drivers/mmc/mmc-uclass.c
+++ b/drivers/mmc/mmc-uclass.c
@@ -122,6 +122,20 @@ int mmc_set_enhanced_strobe(struct mmc *mmc)
}
#endif
+int dm_mmc_host_power_cycle(struct udevice *dev)
+{
+ struct dm_mmc_ops *ops = mmc_get_ops(dev);
+
+ if (ops->host_power_cycle)
+ return ops->host_power_cycle(dev);
+ return 0;
+}
+
+int mmc_host_power_cycle(struct mmc *mmc)
+{
+ return dm_mmc_host_power_cycle(mmc->dev);
+}
+
int mmc_of_parse(struct udevice *dev, struct mmc_config *cfg)
{
int val;
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 6bece7f..f683b52 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -1546,6 +1546,16 @@ static int mmc_set_ios(struct mmc *mmc)
return ret;
}
+
+static int mmc_host_power_cycle(struct mmc *mmc)
+{
+ int ret = 0;
+
+ if (mmc->cfg->ops->host_power_cycle)
+ ret = mmc->cfg->ops->host_power_cycle(mmc);
+
+ return ret;
+}
#endif
int mmc_set_clock(struct mmc *mmc, uint clock, bool disable)
@@ -2715,6 +2725,11 @@ static int mmc_power_cycle(struct mmc *mmc)
ret = mmc_power_off(mmc);
if (ret)
return ret;
+
+ ret = mmc_host_power_cycle(mmc);
+ if (ret)
+ return ret;
+
/*
* SD spec recommends at least 1ms of delay. Let's wait for 2ms
* to be on the safer side.
@@ -2998,6 +3013,30 @@ int mmc_initialize(bd_t *bis)
return 0;
}
+#if CONFIG_IS_ENABLED(DM_MMC)
+int mmc_init_device(int num)
+{
+ struct udevice *dev;
+ struct mmc *m;
+ int ret;
+
+ ret = uclass_get_device(UCLASS_MMC, num, &dev);
+ if (ret)
+ return ret;
+
+ m = mmc_get_mmc_dev(dev);
+ if (!m)
+ return 0;
+#ifdef CONFIG_FSL_ESDHC_ADAPTER_IDENT
+ mmc_set_preinit(m, 1);
+#endif
+ if (m->preinit)
+ mmc_start_init(m);
+
+ return 0;
+}
+#endif
+
#ifdef CONFIG_CMD_BKOPS_ENABLE
int mmc_set_bkops_enable(struct mmc *mmc)
{
diff --git a/drivers/mmc/stm32_sdmmc2.c b/drivers/mmc/stm32_sdmmc2.c
index 32434a4..1726ed7 100644
--- a/drivers/mmc/stm32_sdmmc2.c
+++ b/drivers/mmc/stm32_sdmmc2.c
@@ -524,8 +524,6 @@ static void stm32_sdmmc2_pwrcycle(struct stm32_sdmmc2_priv *priv)
return;
stm32_sdmmc2_reset(priv);
- writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk,
- priv->base + SDMMC_POWER);
}
/*
@@ -619,10 +617,21 @@ static int stm32_sdmmc2_getcd(struct udevice *dev)
return 1;
}
+static int stm32_sdmmc2_host_power_cycle(struct udevice *dev)
+{
+ struct stm32_sdmmc2_priv *priv = dev_get_priv(dev);
+
+ writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk,
+ priv->base + SDMMC_POWER);
+
+ return 0;
+}
+
static const struct dm_mmc_ops stm32_sdmmc2_ops = {
.send_cmd = stm32_sdmmc2_send_cmd,
.set_ios = stm32_sdmmc2_set_ios,
.get_cd = stm32_sdmmc2_getcd,
+ .host_power_cycle = stm32_sdmmc2_host_power_cycle,
};
static int stm32_sdmmc2_probe(struct udevice *dev)
diff --git a/include/mmc.h b/include/mmc.h
index 686ba00..1a9efe4 100644
--- a/include/mmc.h
+++ b/include/mmc.h
@@ -466,6 +466,16 @@ struct dm_mmc_ops {
/* set_enhanced_strobe() - set HS400 enhanced strobe */
int (*set_enhanced_strobe)(struct udevice *dev);
#endif
+
+ /**
+ * host_power_cycle - host specific tasks in power cycle sequence
+ * Called between mmc_power_off() and
+ * mmc_power_on()
+ *
+ * @dev: Device to check
+ * @return 0 if not present, 1 if present, -ve on error
+ */
+ int (*host_power_cycle)(struct udevice *dev);
};
#define mmc_get_ops(dev) ((struct dm_mmc_ops *)(dev)->driver->ops)
@@ -477,6 +487,7 @@ int dm_mmc_get_cd(struct udevice *dev);
int dm_mmc_get_wp(struct udevice *dev);
int dm_mmc_execute_tuning(struct udevice *dev, uint opcode);
int dm_mmc_wait_dat0(struct udevice *dev, int state, int timeout_us);
+int dm_mmc_host_power_cycle(struct udevice *dev);
/* Transition functions for compatibility */
int mmc_set_ios(struct mmc *mmc);
@@ -485,6 +496,7 @@ int mmc_getwp(struct mmc *mmc);
int mmc_execute_tuning(struct mmc *mmc, uint opcode);
int mmc_wait_dat0(struct mmc *mmc, int state, int timeout_us);
int mmc_set_enhanced_strobe(struct mmc *mmc);
+int mmc_host_power_cycle(struct mmc *mmc);
#else
struct mmc_ops {
@@ -494,6 +506,7 @@ struct mmc_ops {
int (*init)(struct mmc *mmc);
int (*getcd)(struct mmc *mmc);
int (*getwp)(struct mmc *mmc);
+ int (*host_power_cycle)(struct mmc *mmc);
};
#endif
@@ -698,6 +711,7 @@ void mmc_destroy(struct mmc *mmc);
*/
int mmc_unbind(struct udevice *dev);
int mmc_initialize(bd_t *bis);
+int mmc_init_device(int num);
int mmc_init(struct mmc *mmc);
int mmc_send_tuning(struct mmc *mmc, u32 opcode, int *cmd_error);