summaryrefslogtreecommitdiffstats
path: root/drivers/mci/imx-esdhc-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mci/imx-esdhc-common.c')
-rw-r--r--drivers/mci/imx-esdhc-common.c275
1 files changed, 275 insertions, 0 deletions
diff --git a/drivers/mci/imx-esdhc-common.c b/drivers/mci/imx-esdhc-common.c
new file mode 100644
index 0000000000..d0bef9470c
--- /dev/null
+++ b/drivers/mci/imx-esdhc-common.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <common.h>
+#include <io.h>
+#include <mci.h>
+#include <pbl.h>
+
+#include "sdhci.h"
+#include "imx-esdhc.h"
+
+#define PRSSTAT_DAT0 0x01000000
+
+struct fsl_esdhc_dma_transfer {
+ dma_addr_t dma;
+ unsigned int size;
+ enum dma_data_direction dir;
+};
+
+static u32 esdhc_op_read32_le(struct sdhci *sdhci, int reg)
+{
+ struct fsl_esdhc_host *host = sdhci_to_esdhc(sdhci);
+
+ return readl(host->regs + reg);
+}
+
+static u32 esdhc_op_read32_be(struct sdhci *sdhci, int reg)
+{
+ struct fsl_esdhc_host *host = sdhci_to_esdhc(sdhci);
+
+ return in_be32(host->regs + reg);
+}
+
+static void esdhc_op_write32_le(struct sdhci *sdhci, int reg, u32 val)
+{
+ struct fsl_esdhc_host *host = sdhci_to_esdhc(sdhci);
+
+ writel(val, host->regs + reg);
+}
+
+static void esdhc_op_write32_be(struct sdhci *sdhci, int reg, u32 val)
+{
+ struct fsl_esdhc_host *host = sdhci_to_esdhc(sdhci);
+
+ out_be32(host->regs + reg, val);
+}
+
+void esdhc_populate_sdhci(struct fsl_esdhc_host *host)
+{
+ if (host->socdata->flags & ESDHC_FLAG_BIGENDIAN) {
+ host->sdhci.read32 = esdhc_op_read32_be;
+ host->sdhci.write32 = esdhc_op_write32_be;
+ } else {
+ host->sdhci.read32 = esdhc_op_read32_le;
+ host->sdhci.write32 = esdhc_op_write32_le;
+ }
+}
+
+static bool esdhc_use_pio_mode(void)
+{
+ return IN_PBL || IS_ENABLED(CONFIG_MCI_IMX_ESDHC_PIO);
+}
+static int esdhc_setup_data(struct fsl_esdhc_host *host, struct mci_data *data,
+ struct fsl_esdhc_dma_transfer *tr)
+{
+ u32 wml_value;
+ void *ptr;
+
+ if (!esdhc_use_pio_mode()) {
+ wml_value = data->blocksize/4;
+
+ if (data->flags & MMC_DATA_READ) {
+ if (wml_value > 0x10)
+ wml_value = 0x10;
+
+ esdhc_clrsetbits32(host, IMX_SDHCI_WML, WML_RD_WML_MASK, wml_value);
+ } else {
+ if (wml_value > 0x80)
+ wml_value = 0x80;
+
+ esdhc_clrsetbits32(host, IMX_SDHCI_WML, WML_WR_WML_MASK,
+ wml_value << 16);
+ }
+
+ tr->size = data->blocks * data->blocksize;
+
+ if (data->flags & MMC_DATA_WRITE) {
+ ptr = (void *)data->src;
+ tr->dir = DMA_TO_DEVICE;
+ } else {
+ ptr = data->dest;
+ tr->dir = DMA_FROM_DEVICE;
+ }
+
+ tr->dma = dma_map_single(host->dev, ptr, tr->size, tr->dir);
+ if (dma_mapping_error(host->dev, tr->dma))
+ return -EFAULT;
+
+
+ sdhci_write32(&host->sdhci, SDHCI_DMA_ADDRESS, tr->dma);
+ }
+
+ sdhci_write32(&host->sdhci, SDHCI_BLOCK_SIZE__BLOCK_COUNT, data->blocks << 16 | data->blocksize);
+
+ return 0;
+}
+
+static int esdhc_do_data(struct fsl_esdhc_host *host, struct mci_data *data,
+ struct fsl_esdhc_dma_transfer *tr)
+{
+ u32 irqstat;
+
+ if (esdhc_use_pio_mode())
+ return sdhci_transfer_data(&host->sdhci, data);
+
+ do {
+ irqstat = sdhci_read32(&host->sdhci, SDHCI_INT_STATUS);
+
+ if (irqstat & DATA_ERR)
+ return -EIO;
+
+ if (irqstat & SDHCI_INT_DATA_TIMEOUT)
+ return -ETIMEDOUT;
+ } while (!(irqstat & SDHCI_INT_XFER_COMPLETE) &&
+ (sdhci_read32(&host->sdhci, SDHCI_PRESENT_STATE) & SDHCI_DATA_LINE_ACTIVE));
+
+ dma_unmap_single(host->dev, tr->dma, tr->size, tr->dir);
+
+ return 0;
+}
+
+static bool esdhc_match32(struct fsl_esdhc_host *host, unsigned int off,
+ unsigned int mask, unsigned int val)
+{
+ const unsigned int reg = sdhci_read32(&host->sdhci, off) & mask;
+
+ return reg == val;
+}
+
+#ifdef __PBL__
+/*
+ * Stubs to make timeout logic below work in PBL
+ */
+
+#define get_time_ns() 0
+/*
+ * Use time in us (approx) as a busy counter timeout value
+ */
+#define is_timeout(s, t) ((s)++ > ((t) / 1024))
+
+static void __udelay(int us)
+{
+ volatile int i;
+
+ for (i = 0; i < us * 4; i++);
+}
+
+#define udelay(n) __udelay(n)
+#undef dev_err
+#define dev_err(d, ...) pr_err(__VA_ARGS__)
+
+#endif
+
+int esdhc_poll(struct fsl_esdhc_host *host, unsigned int off,
+ unsigned int mask, unsigned int val,
+ uint64_t timeout)
+{
+ return wait_on_timeout(timeout,
+ esdhc_match32(host, off, mask, val));
+}
+
+int __esdhc_send_cmd(struct fsl_esdhc_host *host, struct mci_cmd *cmd,
+ struct mci_data *data)
+{
+ u32 xfertyp, mixctrl, command;
+ u32 irqstat;
+ struct fsl_esdhc_dma_transfer tr = { 0 };
+ int ret;
+
+ sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, -1);
+
+ /* Wait at least 8 SD clock cycles before the next command */
+ udelay(1);
+
+ /* Set up for a data transfer if we have one */
+ if (data) {
+ ret = esdhc_setup_data(host, data, &tr);
+ if (ret)
+ return ret;
+ }
+
+ sdhci_set_cmd_xfer_mode(&host->sdhci, cmd, data,
+ !esdhc_use_pio_mode(), &command, &xfertyp);
+
+ if ((host->socdata->flags & ESDHC_FLAG_MULTIBLK_NO_INT) &&
+ (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION))
+ command |= SDHCI_COMMAND_CMDTYP_ABORT;
+
+ /* Send the command */
+ sdhci_write32(&host->sdhci, SDHCI_ARGUMENT, cmd->cmdarg);
+
+ if (esdhc_is_usdhc(host)) {
+ /* write lower-half of xfertyp to mixctrl */
+ mixctrl = xfertyp;
+ /* Keep the bits 22-25 of the register as is */
+ mixctrl |= (sdhci_read32(&host->sdhci, IMX_SDHCI_MIXCTRL) & (0xF << 22));
+ sdhci_write32(&host->sdhci, IMX_SDHCI_MIXCTRL, mixctrl);
+ }
+
+ sdhci_write32(&host->sdhci, SDHCI_TRANSFER_MODE__COMMAND,
+ command << 16 | xfertyp);
+
+ /* Wait for the command to complete */
+ ret = esdhc_poll(host, SDHCI_INT_STATUS,
+ SDHCI_INT_CMD_COMPLETE, SDHCI_INT_CMD_COMPLETE,
+ 100 * MSECOND);
+ if (ret) {
+ dev_err(host->dev, "timeout 1\n");
+ return -ETIMEDOUT;
+ }
+
+ irqstat = sdhci_read32(&host->sdhci, SDHCI_INT_STATUS);
+ sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, irqstat);
+
+ if (irqstat & CMD_ERR)
+ return -EIO;
+
+ if (irqstat & SDHCI_INT_TIMEOUT)
+ return -ETIMEDOUT;
+
+ /* Workaround for ESDHC errata ENGcm03648 / ENGcm12360 */
+ if (!data && (cmd->resp_type & MMC_RSP_BUSY)) {
+ /*
+ * Poll on DATA0 line for cmd with busy signal for
+ * timout / 10 usec since DLA polling can be insecure.
+ */
+ ret = esdhc_poll(host, SDHCI_PRESENT_STATE,
+ PRSSTAT_DAT0, PRSSTAT_DAT0,
+ 2500 * MSECOND);
+ if (ret) {
+ dev_err(host->dev, "timeout PRSSTAT_DAT0\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ sdhci_read_response(&host->sdhci, cmd);
+
+ /* Wait until all of the blocks are transferred */
+ if (data) {
+ ret = esdhc_do_data(host, data, &tr);
+ if (ret)
+ return ret;
+ }
+
+ sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, -1);
+
+ /* Wait for the bus to be idle */
+ ret = esdhc_poll(host, SDHCI_PRESENT_STATE,
+ SDHCI_CMD_INHIBIT_CMD | SDHCI_CMD_INHIBIT_DATA, 0,
+ SECOND);
+ if (ret) {
+ dev_err(host->dev, "timeout 2\n");
+ return -ETIMEDOUT;
+ }
+
+ ret = esdhc_poll(host, SDHCI_PRESENT_STATE,
+ SDHCI_DATA_LINE_ACTIVE, 0,
+ 100 * MSECOND);
+ if (ret) {
+ dev_err(host->dev, "timeout 3\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+