]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mmc: dw_mmc: add a quirk for accessing 64-bit FIFOs in two halves
authorKaustabh Chakraborty <kauschluss@disroot.org>
Tue, 18 Feb 2025 18:47:48 +0000 (00:17 +0530)
committerUlf Hansson <ulf.hansson@linaro.org>
Tue, 11 Mar 2025 17:41:53 +0000 (18:41 +0100)
In certain DW MMC implementations (such as in some Exynos7870
controllers), 64-bit read/write is not allowed from a 64-bit FIFO.
Add a quirk which facilitates accessing the 64-bit FIFO registers in two
32-bit halves.

Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Link: https://lore.kernel.org/r/20250219-exynos7870-mmc-v2-2-b4255a3e39ed@disroot.org
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/dw_mmc.c
drivers/mmc/host/dw_mmc.h

index 3cbda98d08d28722d16ba2e855342249ac878ad3..74f224647bf1ed3c5ca83b573594f8c6843dd1e4 100644 (file)
@@ -2579,6 +2579,91 @@ static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt)
        }
 }
 
+static void dw_mci_push_data64_32(struct dw_mci *host, void *buf, int cnt)
+{
+       struct mmc_data *data = host->data;
+       int init_cnt = cnt;
+
+       /* try and push anything in the part_buf */
+       if (unlikely(host->part_buf_count)) {
+               int len = dw_mci_push_part_bytes(host, buf, cnt);
+
+               buf += len;
+               cnt -= len;
+
+               if (host->part_buf_count == 8) {
+                       mci_fifo_l_writeq(host->fifo_reg, host->part_buf);
+                       host->part_buf_count = 0;
+               }
+       }
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x7)) {
+               while (cnt >= 8) {
+                       u64 aligned_buf[16];
+                       int len = min(cnt & -8, (int)sizeof(aligned_buf));
+                       int items = len >> 3;
+                       int i;
+                       /* memcpy from input buffer into aligned buffer */
+                       memcpy(aligned_buf, buf, len);
+                       buf += len;
+                       cnt -= len;
+                       /* push data from aligned buffer into fifo */
+                       for (i = 0; i < items; ++i)
+                               mci_fifo_l_writeq(host->fifo_reg, aligned_buf[i]);
+               }
+       } else
+#endif
+       {
+               u64 *pdata = buf;
+
+               for (; cnt >= 8; cnt -= 8)
+                       mci_fifo_l_writeq(host->fifo_reg, *pdata++);
+               buf = pdata;
+       }
+       /* put anything remaining in the part_buf */
+       if (cnt) {
+               dw_mci_set_part_bytes(host, buf, cnt);
+               /* Push data if we have reached the expected data length */
+               if ((data->bytes_xfered + init_cnt) ==
+                   (data->blksz * data->blocks))
+                       mci_fifo_l_writeq(host->fifo_reg, host->part_buf);
+       }
+}
+
+static void dw_mci_pull_data64_32(struct dw_mci *host, void *buf, int cnt)
+{
+#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
+       if (unlikely((unsigned long)buf & 0x7)) {
+               while (cnt >= 8) {
+                       /* pull data from fifo into aligned buffer */
+                       u64 aligned_buf[16];
+                       int len = min(cnt & -8, (int)sizeof(aligned_buf));
+                       int items = len >> 3;
+                       int i;
+
+                       for (i = 0; i < items; ++i)
+                               aligned_buf[i] = mci_fifo_l_readq(host->fifo_reg);
+
+                       /* memcpy from aligned buffer into output buffer */
+                       memcpy(buf, aligned_buf, len);
+                       buf += len;
+                       cnt -= len;
+               }
+       } else
+#endif
+       {
+               u64 *pdata = buf;
+
+               for (; cnt >= 8; cnt -= 8)
+                       *pdata++ = mci_fifo_l_readq(host->fifo_reg);
+               buf = pdata;
+       }
+       if (cnt) {
+               host->part_buf = mci_fifo_l_readq(host->fifo_reg);
+               dw_mci_pull_final_bytes(host, buf, cnt);
+       }
+}
+
 static void dw_mci_pull_data(struct dw_mci *host, void *buf, int cnt)
 {
        int len;
@@ -3379,8 +3464,13 @@ int dw_mci_probe(struct dw_mci *host)
                width = 16;
                host->data_shift = 1;
        } else if (i == 2) {
-               host->push_data = dw_mci_push_data64;
-               host->pull_data = dw_mci_pull_data64;
+               if ((host->quirks & DW_MMC_QUIRK_FIFO64_32)) {
+                       host->push_data = dw_mci_push_data64_32;
+                       host->pull_data = dw_mci_pull_data64_32;
+               } else {
+                       host->push_data = dw_mci_push_data64;
+                       host->pull_data = dw_mci_pull_data64;
+               }
                width = 64;
                host->data_shift = 3;
        } else {
index 6447b916990dcd9ce91fca46e38985054d7e9612..5463392dc8110541c4fed74d600ab88dbd0faf7d 100644 (file)
@@ -281,6 +281,8 @@ struct dw_mci_board {
 
 /* Support for longer data read timeout */
 #define DW_MMC_QUIRK_EXTENDED_TMOUT            BIT(0)
+/* Force 32-bit access to the FIFO */
+#define DW_MMC_QUIRK_FIFO64_32                 BIT(1)
 
 #define DW_MMC_240A            0x240a
 #define DW_MMC_280A            0x280a
@@ -472,6 +474,31 @@ struct dw_mci_board {
 #define mci_fifo_writel(__value, __reg)        __raw_writel(__reg, __value)
 #define mci_fifo_writeq(__value, __reg)        __raw_writeq(__reg, __value)
 
+/*
+ * Some dw_mmc devices have 64-bit FIFOs, but expect them to be
+ * accessed using two 32-bit accesses. If such controller is used
+ * with a 64-bit kernel, this has to be done explicitly.
+ */
+static inline u64 mci_fifo_l_readq(void __iomem *addr)
+{
+       u64 ans;
+       u32 proxy[2];
+
+       proxy[0] = mci_fifo_readl(addr);
+       proxy[1] = mci_fifo_readl(addr + 4);
+       memcpy(&ans, proxy, 8);
+       return ans;
+}
+
+static inline void mci_fifo_l_writeq(void __iomem *addr, u64 value)
+{
+       u32 proxy[2];
+
+       memcpy(proxy, &value, 8);
+       mci_fifo_writel(addr, proxy[0]);
+       mci_fifo_writel(addr + 4, proxy[1]);
+}
+
 /* Register access macros */
 #define mci_readl(dev, reg)                    \
        readl_relaxed((dev)->regs + SDMMC_##reg)