summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--drivers/mci/arasan-sdhci.c2
-rw-r--r--drivers/mci/atmel-sdhci-common.c2
-rw-r--r--drivers/mci/imx-esdhc-common.c2
-rw-r--r--drivers/mci/mci-bcm2835.c2
-rw-r--r--drivers/mci/sdhci.c118
-rw-r--r--drivers/mci/sdhci.h10
6 files changed, 130 insertions, 6 deletions
diff --git a/drivers/mci/arasan-sdhci.c b/drivers/mci/arasan-sdhci.c
index dd5ad9d69b..04fce62bf4 100644
--- a/drivers/mci/arasan-sdhci.c
+++ b/drivers/mci/arasan-sdhci.c
@@ -213,7 +213,7 @@ static int arasan_sdhci_send_cmd(struct mci_host *mci, struct mci_cmd *cmd,
sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, mask);
if (data)
- ret = sdhci_transfer_data(&host->sdhci, data);
+ ret = sdhci_transfer_data_pio(&host->sdhci, data);
error:
if (ret) {
diff --git a/drivers/mci/atmel-sdhci-common.c b/drivers/mci/atmel-sdhci-common.c
index 9d78c59e95..eff2a993db 100644
--- a/drivers/mci/atmel-sdhci-common.c
+++ b/drivers/mci/atmel-sdhci-common.c
@@ -166,7 +166,7 @@ int at91_sdhci_send_command(struct at91_sdhci *host, struct mci_cmd *cmd,
sdhci_write32(sdhci, SDHCI_INT_STATUS, mask);
if (data)
- sdhci_transfer_data(sdhci, data);
+ sdhci_transfer_data_pio(sdhci, data);
udelay(1000);
diff --git a/drivers/mci/imx-esdhc-common.c b/drivers/mci/imx-esdhc-common.c
index 7980278801..a85459d29c 100644
--- a/drivers/mci/imx-esdhc-common.c
+++ b/drivers/mci/imx-esdhc-common.c
@@ -109,7 +109,7 @@ static int esdhc_do_data(struct fsl_esdhc_host *host, struct mci_data *data,
u32 irqstat;
if (esdhc_use_pio_mode())
- return sdhci_transfer_data(&host->sdhci, data);
+ return sdhci_transfer_data_pio(&host->sdhci, data);
do {
irqstat = sdhci_read32(&host->sdhci, SDHCI_INT_STATUS);
diff --git a/drivers/mci/mci-bcm2835.c b/drivers/mci/mci-bcm2835.c
index 91027857be..0450f899c6 100644
--- a/drivers/mci/mci-bcm2835.c
+++ b/drivers/mci/mci-bcm2835.c
@@ -172,7 +172,7 @@ static int bcm2835_mci_request(struct mci_host *mci, struct mci_cmd *cmd,
}
if (!ret && data)
- ret = sdhci_transfer_data(&host->sdhci, data);
+ ret = sdhci_transfer_data_pio(&host->sdhci, data);
sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, 0xFFFFFFFF);
if (ret) {
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;
}
diff --git a/drivers/mci/sdhci.h b/drivers/mci/sdhci.h
index 0cdd558565..351940a511 100644
--- a/drivers/mci/sdhci.h
+++ b/drivers/mci/sdhci.h
@@ -2,6 +2,7 @@
#define __MCI_SDHCI_H
#include <pbl.h>
+#include <dma.h>
#include <linux/iopoll.h>
#define SDHCI_DMA_ADDRESS 0x00
@@ -201,6 +202,7 @@ struct sdhci {
u32 caps; /* CAPABILITY_0 */
u32 caps1; /* CAPABILITY_1 */
bool read_caps; /* Capability flags have been read */
+ u32 sdma_boundary;
struct mci_host *mci;
};
@@ -253,11 +255,17 @@ static inline void sdhci_write8(struct sdhci *host, int reg, u32 val)
writeb(val, host->base + reg);
}
+#define SDHCI_NO_DMA DMA_ERROR_CODE
void sdhci_read_response(struct sdhci *host, struct mci_cmd *cmd);
void sdhci_set_cmd_xfer_mode(struct sdhci *host, struct mci_cmd *cmd,
struct mci_data *data, bool dma, u32 *command,
u32 *xfer);
-int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data);
+void sdhci_setup_data_pio(struct sdhci *sdhci, struct mci_data *data);
+void sdhci_setup_data_dma(struct sdhci *sdhci, struct mci_data *data, dma_addr_t *dma);
+int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data, dma_addr_t dma);
+int sdhci_transfer_data_pio(struct sdhci *sdhci, struct mci_data *data);
+int sdhci_transfer_data_dma(struct sdhci *sdhci, struct mci_data *data,
+ dma_addr_t dma);
int sdhci_reset(struct sdhci *sdhci, u8 mask);
u16 sdhci_calc_clk(struct sdhci *host, unsigned int clock,
unsigned int *actual_clock, unsigned int input_clock);