summaryrefslogtreecommitdiffstats
path: root/drivers/mci/sdhci.c
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2021-06-10 16:47:18 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2021-06-11 09:38:44 +0200
commit60b608b2714472aa22862a20d04f267cbbac0863 (patch)
tree90d51bf5aa566a22ae3a375a14d823261e04c444 /drivers/mci/sdhci.c
parent56ca2c197e7165add9a75c9f1d2ef42e1487b8be (diff)
downloadbarebox-60b608b2714472aa22862a20d04f267cbbac0863.tar.gz
barebox-60b608b2714472aa22862a20d04f267cbbac0863.tar.xz
mci: sdhci: Add DMA transfer helpers
The SDHCI helpers only use PIO mode so far. This patch implements SDMA mode helpers. The helpers with _pio suffix explicitly do PIO while the DMA helpers have a _dma suffix and first try to do DMA, but fall back to PIO when DMA is not possible. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Link: https://lore.barebox.org/20210610144720.25620-2-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mci/sdhci.c')
-rw-r--r--drivers/mci/sdhci.c118
1 files changed, 117 insertions, 1 deletions
diff --git a/drivers/mci/sdhci.c b/drivers/mci/sdhci.c
index 0783f6d420..aca4a5a6f9 100644
--- a/drivers/mci/sdhci.c
+++ b/drivers/mci/sdhci.c
@@ -4,6 +4,7 @@
#include <driver.h>
#include <mci.h>
#include <io.h>
+#include <dma.h>
#include <linux/bitfield.h>
#include "sdhci.h"
@@ -123,12 +124,112 @@ void sdhci_set_bus_width(struct sdhci *host, int width)
#endif
-int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data)
+void sdhci_setup_data_pio(struct sdhci *sdhci, struct mci_data *data)
+{
+ if (!data)
+ return;
+
+ sdhci_write16(sdhci, SDHCI_BLOCK_SIZE, sdhci->sdma_boundary |
+ SDHCI_TRANSFER_BLOCK_SIZE(data->blocksize));
+ sdhci_write16(sdhci, SDHCI_BLOCK_COUNT, data->blocks);
+}
+
+void sdhci_setup_data_dma(struct sdhci *sdhci, struct mci_data *data,
+ dma_addr_t *dma)
+{
+ struct device_d *dev = sdhci->mci->hw_dev;
+ int nbytes;
+
+ if (!data)
+ return;
+
+ sdhci_setup_data_pio(sdhci, data);
+
+ if (!dma)
+ return;
+
+ nbytes = data->blocks * data->blocksize;
+
+ if (data->flags & MMC_DATA_READ)
+ *dma = dma_map_single(dev, (void *)data->src, nbytes,
+ DMA_FROM_DEVICE);
+ else
+ *dma = dma_map_single(dev, data->dest, nbytes,
+ DMA_TO_DEVICE);
+
+ if (dma_mapping_error(dev, *dma)) {
+ *dma = SDHCI_NO_DMA;
+ return;
+ }
+
+ sdhci_write32(sdhci, SDHCI_DMA_ADDRESS, *dma);
+}
+
+int sdhci_transfer_data_dma(struct sdhci *sdhci, struct mci_data *data,
+ dma_addr_t dma)
+{
+ struct device_d *dev = sdhci->mci->hw_dev;
+ int nbytes;
+ u32 irqstat;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ nbytes = data->blocks * data->blocksize;
+
+ do {
+ irqstat = sdhci_read32(sdhci, SDHCI_INT_STATUS);
+
+ if (irqstat & SDHCI_INT_DATA_END_BIT) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (irqstat & SDHCI_INT_DATA_CRC) {
+ ret = -EBADMSG;
+ goto out;
+ }
+
+ if (irqstat & SDHCI_INT_DATA_TIMEOUT) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ if (irqstat & SDHCI_INT_DMA) {
+ u32 addr = sdhci_read32(sdhci, SDHCI_DMA_ADDRESS);
+
+ /*
+ * DMA engine has stopped on buffer boundary. Acknowledge
+ * the interrupt and kick the DMA engine again.
+ */
+ sdhci_write32(sdhci, SDHCI_INT_STATUS, SDHCI_INT_DMA);
+ sdhci_write32(sdhci, SDHCI_DMA_ADDRESS, addr);
+ }
+
+ if (irqstat & SDHCI_INT_XFER_COMPLETE)
+ break;
+ } while (1);
+
+ ret = 0;
+out:
+ if (data->flags & MMC_DATA_READ)
+ dma_unmap_single(dev, dma, nbytes, DMA_FROM_DEVICE);
+ else
+ dma_unmap_single(dev, dma, nbytes, DMA_TO_DEVICE);
+
+ return 0;
+}
+
+int sdhci_transfer_data_pio(struct sdhci *sdhci, struct mci_data *data)
{
unsigned int block = 0;
u32 stat, prs;
uint64_t start = get_time_ns();
+ if (!data)
+ return 0;
+
do {
stat = sdhci_read32(sdhci, SDHCI_INT_STATUS);
if (stat & SDHCI_INT_ERROR)
@@ -161,6 +262,19 @@ int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data)
return 0;
}
+int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data, dma_addr_t dma)
+{
+ struct device_d *dev = sdhci->mci->hw_dev;
+
+ if (!data)
+ return 0;
+
+ if (dma_mapping_error(dev, dma))
+ return sdhci_transfer_data_pio(sdhci, data);
+ else
+ return sdhci_transfer_data_dma(sdhci, data, dma);
+}
+
int sdhci_reset(struct sdhci *sdhci, u8 mask)
{
u8 val;
@@ -428,5 +542,7 @@ int sdhci_setup_host(struct sdhci *host)
if (host->caps & SDHCI_CAN_DO_HISPD)
mci->host_caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
+ host->sdma_boundary = SDHCI_DMA_BOUNDARY_512K;
+
return 0;
}