mmc: omap_hsmmc: Add support for DMA (ADMA2)
authorKishon Vijay Abraham I <kishon@ti.com>
Thu, 21 Sep 2017 14:51:34 +0000 (16:51 +0200)
committerTom Rini <trini@konsulko.com>
Fri, 19 Jan 2018 20:49:17 +0000 (15:49 -0500)
The omap hsmmc host controller can have the ADMA2 feature. It brings better
read and write throughput.
On most SOC, the capability is read from the hl_hwinfo register. On OMAP3,
DMA support is compiled out.

Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com>
Reviewed-by: Tom Rini <trini@konsulko.com>
arch/arm/include/asm/omap_mmc.h
drivers/mmc/omap_hsmmc.c

index 77278a3..128cd81 100644 (file)
 
 struct hsmmc {
 #ifndef CONFIG_OMAP34XX
-       unsigned char res0[0x100];
+       unsigned int hl_rev;
+       unsigned int hl_hwinfo;
+       unsigned int hl_sysconfig;
+       unsigned char res0[0xf4];
 #endif
        unsigned char res1[0x10];
        unsigned int sysconfig;         /* 0x10 */
@@ -52,6 +55,9 @@ struct hsmmc {
        unsigned int ie;                /* 0x134 */
        unsigned char res4[0x8];
        unsigned int capa;              /* 0x140 */
+       unsigned char res5[0x10];
+       unsigned int admaes;            /* 0x154 */
+       unsigned int admasal;           /* 0x158 */
 };
 
 struct omap_hsmmc_plat {
@@ -64,6 +70,7 @@ struct omap_hsmmc_plat {
 /*
  * OMAP HS MMC Bit definitions
  */
+#define MADMA_EN                       (0x1 << 0)
 #define MMC_SOFTRESET                  (0x1 << 1)
 #define RESETDONE                      (0x1 << 0)
 #define NOOPENDRAIN                    (0x0 << 0)
@@ -80,6 +87,7 @@ struct omap_hsmmc_plat {
 #define WPP_ACTIVEHIGH                 (0x0 << 8)
 #define RESERVED_MASK                  (0x3 << 9)
 #define CTPL_MMC_SD                    (0x0 << 11)
+#define DMA_MASTER                     (0x1 << 20)
 #define BLEN_512BYTESLEN               (0x200 << 0)
 #define NBLK_STPCNT                    (0x0 << 16)
 #define DE_DISABLE                     (0x0 << 0)
@@ -119,6 +127,7 @@ struct omap_hsmmc_plat {
 #define SDBP_PWRON                     (0x1 << 8)
 #define SDVS_1V8                       (0x5 << 9)
 #define SDVS_3V0                       (0x6 << 9)
+#define DMA_SELECT                     (0x2 << 3)
 #define ICE_MASK                       (0x1 << 0)
 #define ICE_STOP                       (0x0 << 0)
 #define ICS_MASK                       (0x1 << 1)
@@ -148,6 +157,7 @@ struct omap_hsmmc_plat {
 #define IE_DTO                         (0x01 << 20)
 #define IE_DCRC                                (0x01 << 21)
 #define IE_DEB                         (0x01 << 22)
+#define IE_ADMAE                       (0x01 << 25)
 #define IE_CERR                                (0x01 << 28)
 #define IE_BADA                                (0x01 << 29)
 
index 18ab033..462a891 100644 (file)
@@ -25,6 +25,7 @@
 #include <config.h>
 #include <common.h>
 #include <malloc.h>
+#include <memalign.h>
 #include <mmc.h>
 #include <part.h>
 #include <i2c.h>
@@ -71,11 +72,45 @@ struct omap_hsmmc_data {
        int wp_gpio;
 #endif
 #endif
+       u8 controller_flags;
+#ifndef CONFIG_OMAP34XX
+       struct omap_hsmmc_adma_desc *adma_desc_table;
+       uint desc_slot;
+#endif
+};
+
+#ifndef CONFIG_OMAP34XX
+struct omap_hsmmc_adma_desc {
+       u8 attr;
+       u8 reserved;
+       u16 len;
+       u32 addr;
 };
 
+#define ADMA_MAX_LEN   63488
+
+/* Decriptor table defines */
+#define ADMA_DESC_ATTR_VALID           BIT(0)
+#define ADMA_DESC_ATTR_END             BIT(1)
+#define ADMA_DESC_ATTR_INT             BIT(2)
+#define ADMA_DESC_ATTR_ACT1            BIT(4)
+#define ADMA_DESC_ATTR_ACT2            BIT(5)
+
+#define ADMA_DESC_TRANSFER_DATA                ADMA_DESC_ATTR_ACT2
+#define ADMA_DESC_LINK_DESC    (ADMA_DESC_ATTR_ACT1 | ADMA_DESC_ATTR_ACT2)
+#endif
+
 /* If we fail after 1 second wait, something is really bad */
 #define MAX_RETRY_MS   1000
 
+/* DMA transfers can take a long time if a lot a data is transferred.
+ * The timeout must take in account the amount of data. Let's assume
+ * that the time will never exceed 333 ms per MB (in other word we assume
+ * that the bandwidth is always above 3MB/s).
+ */
+#define DMA_TIMEOUT_PER_MB     333
+#define OMAP_HSMMC_USE_ADMA                    BIT(2)
+
 static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size);
 static int mmc_write_data(struct hsmmc *mmc_base, const char *buf,
                        unsigned int siz);
@@ -242,6 +277,11 @@ static int omap_hsmmc_init_setup(struct mmc *mmc)
                        return -ETIMEDOUT;
                }
        }
+#ifndef CONFIG_OMAP34XX
+       reg_val = readl(&mmc_base->hl_hwinfo);
+       if (reg_val & MADMA_EN)
+               priv->controller_flags |= OMAP_HSMMC_USE_ADMA;
+#endif
        writel(DTW_1_BITMODE | SDBP_PWROFF | SDVS_3V0, &mmc_base->hctl);
        writel(readl(&mmc_base->capa) | VS30_3V0SUP | VS18_1V8SUP,
                &mmc_base->capa);
@@ -269,8 +309,8 @@ static int omap_hsmmc_init_setup(struct mmc *mmc)
        writel(readl(&mmc_base->hctl) | SDBP_PWRON, &mmc_base->hctl);
 
        writel(IE_BADA | IE_CERR | IE_DEB | IE_DCRC | IE_DTO | IE_CIE |
-               IE_CEB | IE_CCRC | IE_CTO | IE_BRR | IE_BWR | IE_TC | IE_CC,
-               &mmc_base->ie);
+               IE_CEB | IE_CCRC | IE_ADMAE | IE_CTO | IE_BRR | IE_BWR | IE_TC |
+               IE_CC, &mmc_base->ie);
 
        mmc_init_stream(mmc_base);
 
@@ -322,6 +362,118 @@ static void mmc_reset_controller_fsm(struct hsmmc *mmc_base, u32 bit)
                }
        }
 }
+
+#ifndef CONFIG_OMAP34XX
+static void omap_hsmmc_adma_desc(struct mmc *mmc, char *buf, u16 len, bool end)
+{
+       struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc);
+       struct omap_hsmmc_adma_desc *desc;
+       u8 attr;
+
+       desc = &priv->adma_desc_table[priv->desc_slot];
+
+       attr = ADMA_DESC_ATTR_VALID | ADMA_DESC_TRANSFER_DATA;
+       if (!end)
+               priv->desc_slot++;
+       else
+               attr |= ADMA_DESC_ATTR_END;
+
+       desc->len = len;
+       desc->addr = (u32)buf;
+       desc->reserved = 0;
+       desc->attr = attr;
+}
+
+static void omap_hsmmc_prepare_adma_table(struct mmc *mmc,
+                                         struct mmc_data *data)
+{
+       uint total_len = data->blocksize * data->blocks;
+       uint desc_count = DIV_ROUND_UP(total_len, ADMA_MAX_LEN);
+       struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc);
+       int i = desc_count;
+       char *buf;
+
+       priv->desc_slot = 0;
+       priv->adma_desc_table = (struct omap_hsmmc_adma_desc *)
+                               memalign(ARCH_DMA_MINALIGN, desc_count *
+                               sizeof(struct omap_hsmmc_adma_desc));
+
+       if (data->flags & MMC_DATA_READ)
+               buf = data->dest;
+       else
+               buf = (char *)data->src;
+
+       while (--i) {
+               omap_hsmmc_adma_desc(mmc, buf, ADMA_MAX_LEN, false);
+               buf += ADMA_MAX_LEN;
+               total_len -= ADMA_MAX_LEN;
+       }
+
+       omap_hsmmc_adma_desc(mmc, buf, total_len, true);
+
+       flush_dcache_range((long)priv->adma_desc_table,
+                          (long)priv->adma_desc_table +
+                          ROUND(desc_count *
+                          sizeof(struct omap_hsmmc_adma_desc),
+                          ARCH_DMA_MINALIGN));
+}
+
+static void omap_hsmmc_prepare_data(struct mmc *mmc, struct mmc_data *data)
+{
+       struct hsmmc *mmc_base;
+       struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc);
+       u32 val;
+       char *buf;
+
+       mmc_base = priv->base_addr;
+       omap_hsmmc_prepare_adma_table(mmc, data);
+
+       if (data->flags & MMC_DATA_READ)
+               buf = data->dest;
+       else
+               buf = (char *)data->src;
+
+       val = readl(&mmc_base->hctl);
+       val |= DMA_SELECT;
+       writel(val, &mmc_base->hctl);
+
+       val = readl(&mmc_base->con);
+       val |= DMA_MASTER;
+       writel(val, &mmc_base->con);
+
+       writel((u32)priv->adma_desc_table, &mmc_base->admasal);
+
+       flush_dcache_range((u32)buf,
+                          (u32)buf +
+                          ROUND(data->blocksize * data->blocks,
+                                ARCH_DMA_MINALIGN));
+}
+
+static void omap_hsmmc_dma_cleanup(struct mmc *mmc)
+{
+       struct hsmmc *mmc_base;
+       struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc);
+       u32 val;
+
+       mmc_base = priv->base_addr;
+
+       val = readl(&mmc_base->con);
+       val &= ~DMA_MASTER;
+       writel(val, &mmc_base->con);
+
+       val = readl(&mmc_base->hctl);
+       val &= ~DMA_SELECT;
+       writel(val, &mmc_base->hctl);
+
+       kfree(priv->adma_desc_table);
+}
+#else
+#define omap_hsmmc_adma_desc
+#define omap_hsmmc_prepare_adma_table
+#define omap_hsmmc_prepare_data
+#define omap_hsmmc_dma_cleanup
+#endif
+
 #if !CONFIG_IS_ENABLED(DM_MMC)
 static int omap_hsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
                        struct mmc_data *data)
@@ -332,6 +484,10 @@ static int omap_hsmmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
                        struct mmc_data *data)
 {
        struct omap_hsmmc_data *priv = dev_get_priv(dev);
+#ifndef CONFIG_OMAP34XX
+       struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+       struct mmc *mmc = upriv->mmc;
+#endif
 #endif
        struct hsmmc *mmc_base;
        unsigned int flags, mmc_stat;
@@ -405,6 +561,14 @@ static int omap_hsmmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
                        flags |= (DP_DATA | DDIR_READ);
                else
                        flags |= (DP_DATA | DDIR_WRITE);
+
+#ifndef CONFIG_OMAP34XX
+               if ((priv->controller_flags & OMAP_HSMMC_USE_ADMA) &&
+                   !mmc_is_tuning_cmd(cmd->cmdidx)) {
+                       omap_hsmmc_prepare_data(mmc, data);
+                       flags |= DE_ENABLE;
+               }
+#endif
        }
 
        writel(cmd->cmdarg, &mmc_base->arg);
@@ -414,7 +578,7 @@ static int omap_hsmmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
        start = get_timer(0);
        do {
                mmc_stat = readl(&mmc_base->stat);
-               if (get_timer(0) - start > MAX_RETRY_MS) {
+               if (get_timer(start) > MAX_RETRY_MS) {
                        printf("%s : timeout: No status update\n", __func__);
                        return -ETIMEDOUT;
                }
@@ -441,6 +605,41 @@ static int omap_hsmmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
                }
        }
 
+#ifndef CONFIG_OMAP34XX
+       if ((priv->controller_flags & OMAP_HSMMC_USE_ADMA) && data &&
+           !mmc_is_tuning_cmd(cmd->cmdidx)) {
+               u32 sz_mb, timeout;
+
+               if (mmc_stat & IE_ADMAE) {
+                       omap_hsmmc_dma_cleanup(mmc);
+                       return -EIO;
+               }
+
+               sz_mb = DIV_ROUND_UP(data->blocksize *  data->blocks, 1 << 20);
+               timeout = sz_mb * DMA_TIMEOUT_PER_MB;
+               if (timeout < MAX_RETRY_MS)
+                       timeout = MAX_RETRY_MS;
+
+               start = get_timer(0);
+               do {
+                       mmc_stat = readl(&mmc_base->stat);
+                       if (mmc_stat & TC_MASK) {
+                               writel(readl(&mmc_base->stat) | TC_MASK,
+                                      &mmc_base->stat);
+                               break;
+                       }
+                       if (get_timer(start) > timeout) {
+                               printf("%s : DMA timeout: No status update\n",
+                                      __func__);
+                               return -ETIMEDOUT;
+                       }
+               } while (1);
+
+               omap_hsmmc_dma_cleanup(mmc);
+               return 0;
+       }
+#endif
+
        if (data && (data->flags & MMC_DATA_READ)) {
                mmc_read_data(mmc_base, data->dest,
                                data->blocksize * data->blocks);