summaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/Kconfig15
-rw-r--r--drivers/mmc/Makefile1
-rw-r--r--drivers/mmc/adi_sdhci.c148
-rw-r--r--drivers/mmc/fsl_esdhc_imx.c8
-rw-r--r--drivers/mmc/msm_sdhci.c10
-rw-r--r--drivers/mmc/sunxi_mmc.c35
-rw-r--r--drivers/mmc/sunxi_mmc.h18
7 files changed, 220 insertions, 15 deletions
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index 4827834b4aa..6740591a653 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -292,6 +292,15 @@ config MMC_DW_ROCKCHIP
SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well
as removeable SD and micro-SD cards.
+config MMC_SDHCI_ADI
+ bool "ADI SD/MMC controller support"
+ depends on ARCH_SC5XX
+ depends on DM_MMC && OF_CONTROL
+ depends on MMC_SDHCI && MMC_SDHCI_ADMA
+ help
+ This enables support for the SD/MMC controller included in some Analog
+ Devices SC5XX Socs.
+
config MMC_DW_SOCFPGA
bool "SOCFPGA specific extensions for Synopsys DW Memory Card Interface"
depends on ARCH_SOCFPGA
@@ -919,6 +928,12 @@ config ESDHC_DETECT_QUIRK
bool "QIXIS-based eSDHC quirk detection"
depends on FSL_ESDHC && FSL_QIXIS
+config ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE
+ bool
+ depends on FSL_ESDHC || FSL_ESDHC_IMX
+ def_bool y if ARCH_T1024 || ARCH_T1040 || ARCH_T1042 || ARCH_T2080 \
+ || FSL_ESDHC_IMX
+
config FSL_ESDHC_IMX
bool "Freescale/NXP i.MX eSDHC controller support"
help
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 90e76f90769..94ed28ead71 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_MMC_SDHCI_MV) += mv_sdhci.o
obj-$(CONFIG_MMC_SDHCI_NPCM) += npcm_sdhci.o
obj-$(CONFIG_MMC_SDHCI_PIC32) += pic32_sdhci.o
obj-$(CONFIG_MMC_SDHCI_ROCKCHIP) += rockchip_sdhci.o
+obj-$(CONFIG_MMC_SDHCI_ADI) += adi_sdhci.o
obj-$(CONFIG_MMC_SDHCI_S5P) += s5p_sdhci.o
obj-$(CONFIG_MMC_SDHCI_SNPS) += snps_sdhci.o
obj-$(CONFIG_MMC_SDHCI_STI) += sti_sdhci.o
diff --git a/drivers/mmc/adi_sdhci.c b/drivers/mmc/adi_sdhci.c
new file mode 100644
index 00000000000..65a22cefb71
--- /dev/null
+++ b/drivers/mmc/adi_sdhci.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * (C) Copyright 2022 - Analog Devices, Inc.
+ *
+ * Written and/or maintained by Timesys Corporation
+ *
+ * Contact: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
+ * Contact: Greg Malysa <greg.malysa@timesys.com>
+ *
+ * Based on Rockchip's sdhci.c file
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <malloc.h>
+#include <sdhci.h>
+#include <asm/cache.h>
+
+/* 400KHz is max freq for card ID etc. Use that as min */
+#define EMMC_MIN_FREQ 400000
+
+/* Check if an operation crossed a boundary of size ADMA_BOUNDARY_ALIGN */
+#define ADMA_BOUNDARY_ALGN SZ_128M
+#define BOUNDARY_OK(addr, len) \
+ (((addr) | (ADMA_BOUNDARY_ALGN - 1)) == (((addr) + (len) - 1) | \
+ (ADMA_BOUNDARY_ALGN - 1)))
+
+/* We split a descriptor for every crossing of the ADMA alignment boundary,
+ * so we need an additional descriptor for every expected crossing.
+ * As I understand it, the max expected transaction size is:
+ * CONFIG_SYS_MMC_MAX_BLK_COUNT * MMC_MAX_BLOCK_LEN
+ *
+ * With the way the SDHCI-ADMA driver is implemented, if ADMA_MAX_LEN was a
+ * clean power of two, we'd only ever need +1 descriptor as the first
+ * descriptor that got split would then bring the remaining DMA
+ * destination addresses into alignment. Unfortunately, it's currently
+ * hardcoded to a non-power-of-two value.
+ *
+ * If that ever becomes parameterized, ADMA max length can be set to
+ * 0x10000, and set this to 1.
+ */
+#define ADMA_POTENTIAL_CROSSINGS \
+ DIV_ROUND_UP((CONFIG_SYS_MMC_MAX_BLK_COUNT * MMC_MAX_BLOCK_LEN), \
+ ADMA_BOUNDARY_ALGN)
+/* +1 descriptor for each crossing.
+ */
+#define ADMA_TABLE_EXTRA_SZ (ADMA_POTENTIAL_CROSSINGS * ADMA_DESC_LEN)
+
+struct adi_sdhc_plat {
+ struct mmc_config cfg;
+ struct mmc mmc;
+};
+
+void adi_dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc,
+ dma_addr_t addr, int len, bool end)
+{
+ int tmplen, offset;
+
+ if (likely(!len || BOUNDARY_OK(addr, len))) {
+ sdhci_adma_write_desc(host, desc, addr, len, end);
+ return;
+ }
+
+ offset = addr & (ADMA_BOUNDARY_ALGN - 1);
+ tmplen = ADMA_BOUNDARY_ALGN - offset;
+ sdhci_adma_write_desc(host, desc, addr, tmplen, false);
+
+ addr += tmplen;
+ len -= tmplen;
+ sdhci_adma_write_desc(host, desc, addr, len, end);
+}
+
+struct sdhci_ops adi_dwcmshc_sdhci_ops = {
+ .adma_write_desc = adi_dwcmshc_adma_write_desc,
+};
+
+static int adi_dwcmshc_sdhci_probe(struct udevice *dev)
+{
+ struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+ struct adi_sdhc_plat *plat = dev_get_plat(dev);
+ struct sdhci_host *host = dev_get_priv(dev);
+ int max_frequency, ret;
+ struct clk clk;
+
+ max_frequency = dev_read_u32_default(dev, "max-frequency", 0);
+ ret = clk_get_by_index(dev, 0, &clk);
+
+ host->quirks = 0;
+ host->max_clk = max_frequency;
+ /*
+ * The sdhci-driver only supports 4bit and 8bit, as sdhci_setup_cfg
+ * doesn't allow us to clear MMC_MODE_4BIT. Consequently, we don't
+ * check for other bus-width values.
+ */
+ if (host->bus_width == 8)
+ host->host_caps |= MMC_MODE_8BIT;
+
+ host->mmc = &plat->mmc;
+ host->mmc->priv = host;
+ host->mmc->dev = dev;
+ upriv->mmc = host->mmc;
+
+ host->ops = &adi_dwcmshc_sdhci_ops;
+ host->adma_desc_table = memalign(ARCH_DMA_MINALIGN,
+ ADMA_TABLE_SZ + ADMA_TABLE_EXTRA_SZ);
+ host->adma_addr = virt_to_phys(host->adma_desc_table);
+
+ ret = sdhci_setup_cfg(&plat->cfg, host, 0, EMMC_MIN_FREQ);
+ if (ret)
+ return ret;
+
+ return sdhci_probe(dev);
+}
+
+static int adi_dwcmshc_sdhci_of_to_plat(struct udevice *dev)
+{
+ struct sdhci_host *host = dev_get_priv(dev);
+
+ host->name = dev->name;
+ host->ioaddr = dev_read_addr_ptr(dev);
+ host->bus_width = dev_read_u32_default(dev, "bus-width", 4);
+
+ return 0;
+}
+
+static int adi_sdhci_bind(struct udevice *dev)
+{
+ struct adi_sdhc_plat *plat = dev_get_plat(dev);
+
+ return sdhci_bind(dev, &plat->mmc, &plat->cfg);
+}
+
+static const struct udevice_id adi_dwcmshc_sdhci_ids[] = {
+ { .compatible = "adi,dwc-sdhci" },
+ { }
+};
+
+U_BOOT_DRIVER(adi_dwcmshc_sdhci_drv) = {
+ .name = "adi_sdhci",
+ .id = UCLASS_MMC,
+ .of_match = adi_dwcmshc_sdhci_ids,
+ .of_to_plat = adi_dwcmshc_sdhci_of_to_plat,
+ .ops = &sdhci_ops,
+ .bind = adi_sdhci_bind,
+ .probe = adi_dwcmshc_sdhci_probe,
+ .priv_auto = sizeof(struct sdhci_host),
+ .plat_auto = sizeof(struct adi_sdhc_plat),
+};
diff --git a/drivers/mmc/fsl_esdhc_imx.c b/drivers/mmc/fsl_esdhc_imx.c
index d7a45ef0ad0..926113f79d3 100644
--- a/drivers/mmc/fsl_esdhc_imx.c
+++ b/drivers/mmc/fsl_esdhc_imx.c
@@ -40,12 +40,6 @@
#include <linux/iopoll.h>
#include <linux/dma-mapping.h>
-#ifndef ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE
-#ifdef CONFIG_FSL_USDHC
-#define ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE 1
-#endif
-#endif
-
DECLARE_GLOBAL_DATA_PTR;
#define SDHCI_IRQ_EN_BITS (IRQSTATEN_CC | IRQSTATEN_TC | \
@@ -376,7 +370,7 @@ static int esdhc_setup_data(struct fsl_esdhc_priv *priv, struct mmc *mmc,
(timeout == 4 || timeout == 8 || timeout == 12))
timeout++;
- if (IS_ENABLED(ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE))
+ if (IS_ENABLED(CONFIG_ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE))
timeout = 0xE;
esdhc_clrsetbits32(&regs->sysctl, SYSCTL_TIMEOUT_MASK, timeout << 16);
diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c
index 27bb7052fca..ac77fb06bf7 100644
--- a/drivers/mmc/msm_sdhci.c
+++ b/drivers/mmc/msm_sdhci.c
@@ -10,6 +10,7 @@
#include <clk.h>
#include <dm.h>
#include <malloc.h>
+#include <reset.h>
#include <sdhci.h>
#include <wait_bit.h>
#include <asm/global_data.h>
@@ -153,9 +154,18 @@ static int msm_sdc_probe(struct udevice *dev)
const struct msm_sdhc_variant_info *var_info;
struct sdhci_host *host = &prv->host;
u32 core_version, core_minor, core_major;
+ struct reset_ctl bcr_rst;
u32 caps;
int ret;
+ ret = reset_get_by_index(dev, 0, &bcr_rst);
+ if (!ret) {
+ reset_assert(&bcr_rst);
+ udelay(200);
+ reset_deassert(&bcr_rst);
+ udelay(200);
+ }
+
host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B;
host->max_clk = 0;
diff --git a/drivers/mmc/sunxi_mmc.c b/drivers/mmc/sunxi_mmc.c
index 0b56d1405be..951e6acd34d 100644
--- a/drivers/mmc/sunxi_mmc.c
+++ b/drivers/mmc/sunxi_mmc.c
@@ -92,6 +92,13 @@ static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
pll = CCM_MMC_CTRL_PLL6;
pll_hz = clock_get_pll6();
#endif
+ /*
+ * On the D1/R528/T113 mux source 1 refers to PLL_PERIPH0(1x),
+ * like for the older SoCs. However we still have the hidden
+ * divider of 2x, so compensate for that here.
+ */
+ if (IS_ENABLED(CONFIG_MACH_SUN8I_R528))
+ pll_hz /= 2;
}
div = pll_hz / hz;
@@ -442,6 +449,26 @@ out:
return error;
}
+static void sunxi_mmc_reset(void *regs)
+{
+ /* Reset controller */
+ writel(SUNXI_MMC_GCTRL_RESET, regs + SUNXI_MMC_GCTRL);
+ udelay(1000);
+
+ if (IS_ENABLED(CONFIG_SUN50I_GEN_H6) || IS_ENABLED(CONFIG_SUNXI_GEN_NCAT2)) {
+ /* Reset card */
+ writel(SUNXI_MMC_HWRST_ASSERT, regs + SUNXI_MMC_HWRST);
+ udelay(10);
+ writel(SUNXI_MMC_HWRST_DEASSERT, regs + SUNXI_MMC_HWRST);
+ udelay(300);
+
+ /* Setup FIFO R/W threshold. Needed on H616. */
+ writel(SUNXI_MMC_THLDC_READ_THLD(512) |
+ SUNXI_MMC_THLDC_WRITE_EN |
+ SUNXI_MMC_THLDC_READ_EN, regs + SUNXI_MMC_THLDC);
+ }
+}
+
/* non-DM code here is used by the (ARM) SPL only */
#if !CONFIG_IS_ENABLED(DM_MMC)
@@ -489,9 +516,7 @@ static int sunxi_mmc_core_init(struct mmc *mmc)
{
struct sunxi_mmc_priv *priv = mmc->priv;
- /* Reset controller */
- writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl);
- udelay(1000);
+ sunxi_mmc_reset(priv->reg);
return 0;
}
@@ -684,9 +709,7 @@ static int sunxi_mmc_probe(struct udevice *dev)
upriv->mmc = &plat->mmc;
- /* Reset controller */
- writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl);
- udelay(1000);
+ sunxi_mmc_reset(priv->reg);
return 0;
}
diff --git a/drivers/mmc/sunxi_mmc.h b/drivers/mmc/sunxi_mmc.h
index f4ae5a790c8..71865160319 100644
--- a/drivers/mmc/sunxi_mmc.h
+++ b/drivers/mmc/sunxi_mmc.h
@@ -37,7 +37,9 @@ struct sunxi_mmc {
u32 res0; /* 0x54 reserved */
u32 a12a; /* 0x58 Auto command 12 argument */
u32 ntsr; /* 0x5c New timing set register */
- u32 res1[8];
+ u32 res1[6];
+ u32 hwrst; /* 0x78 Hardware Reset */
+ u32 res5;
u32 dmac; /* 0x80 internal DMA control */
u32 dlba; /* 0x84 internal DMA descr list base address */
u32 idst; /* 0x88 internal DMA status */
@@ -46,7 +48,8 @@ struct sunxi_mmc {
u32 cbda; /* 0x94 */
u32 res2[26];
#if defined(CONFIG_SUNXI_GEN_SUN6I) || defined(CONFIG_SUN50I_GEN_H6) || defined(CONFIG_SUNXI_GEN_NCAT2)
- u32 res3[17];
+ u32 thldc; /* 0x100 Threshold control */
+ u32 res3[16];
u32 samp_dl;
u32 res4[46];
#endif
@@ -57,6 +60,7 @@ struct sunxi_mmc {
#define SUNXI_MMC_CLK_ENABLE (0x1 << 16)
#define SUNXI_MMC_CLK_DIVIDER_MASK (0xff)
+#define SUNXI_MMC_GCTRL 0x000
#define SUNXI_MMC_GCTRL_SOFT_RESET (0x1 << 0)
#define SUNXI_MMC_GCTRL_FIFO_RESET (0x1 << 1)
#define SUNXI_MMC_GCTRL_DMA_RESET (0x1 << 2)
@@ -123,6 +127,10 @@ struct sunxi_mmc {
#define SUNXI_MMC_NTSR_MODE_SEL_NEW (0x1 << 31)
+#define SUNXI_MMC_HWRST 0x078
+#define SUNXI_MMC_HWRST_ASSERT (0x0 << 0)
+#define SUNXI_MMC_HWRST_DEASSERT (0x1 << 0)
+
#define SUNXI_MMC_IDMAC_RESET (0x1 << 0)
#define SUNXI_MMC_IDMAC_FIXBURST (0x1 << 1)
#define SUNXI_MMC_IDMAC_ENABLE (0x1 << 7)
@@ -133,6 +141,12 @@ struct sunxi_mmc {
#define SUNXI_MMC_COMMON_CLK_GATE (1 << 16)
#define SUNXI_MMC_COMMON_RESET (1 << 18)
+#define SUNXI_MMC_THLDC 0x100
+#define SUNXI_MMC_THLDC_READ_EN (0x1 << 0)
+#define SUNXI_MMC_THLDC_BSY_CLR_INT_EN (0x1 << 1)
+#define SUNXI_MMC_THLDC_WRITE_EN (0x1 << 2)
+#define SUNXI_MMC_THLDC_READ_THLD(x) (((x) & 0xfff) << 16)
+
#define SUNXI_MMC_CAL_DL_SW_EN (0x1 << 7)
#endif /* _SUNXI_MMC_H */