linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
@ 2013-07-10  9:28 Jonas Jensen
  2013-07-17 12:51 ` [PATCH v2] " Jonas Jensen
  0 siblings, 1 reply; 12+ messages in thread
From: Jonas Jensen @ 2013-07-10  9:28 UTC (permalink / raw)
  To: linux-mmc; +Cc: cjb, linux-arm-kernel, linux-kernel, arm, Jonas Jensen

Add SDHCI driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    Applies to next-20130703
    
    I know this is a large chunk and there are many pitfalls with
    the controller. I give you my word it does work / seem to be stable,
    with or without DMA. Any feedback is appreciated.
    
    Tested by creating multiple directories/files and moving the card
    back and forth to different reader.
    
    IRQF_DISABLED is now pointless, remove it from request_irq?

 drivers/mmc/host/Kconfig        |   9 +
 drivers/mmc/host/Makefile       |   1 +
 drivers/mmc/host/sdhci-moxart.c | 786 ++++++++++++++++++++++++++++++++++++++++
 drivers/mmc/host/sdhci-moxart.h | 155 ++++++++
 4 files changed, 951 insertions(+)
 create mode 100644 drivers/mmc/host/sdhci-moxart.c
 create mode 100644 drivers/mmc/host/sdhci-moxart.h

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 1be2289..fb401c4 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -260,6 +260,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_SDHCI_MOXART
+	tristate "MOXART SD Host Controller Interface support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects the MOXART SD Host Controller Interface.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 67718c1..274199a 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
 obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
+obj-$(CONFIG_MMC_SDHCI_MOXART)		+= sdhci-moxart.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sdhci-moxart.c b/drivers/mmc/host/sdhci-moxart.c
new file mode 100644
index 0000000..f5aaea6
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.c
@@ -0,0 +1,786 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#include "sdhci-moxart.h"
+
+#define DMA_FIFO_LEN_FORCE		0
+#define APB_DMA_SD_REQ_NO		5
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, &host->reg->clear);
+	writel(cmd->arg, &host->reg->argument);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, &host->reg->command);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(&host->reg->status);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, &host->reg->clear);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, &host->reg->clear);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, &host->reg->clear);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(&host->reg->response0);
+					cmd->resp[2] =
+						readl(&host->reg->response1);
+					cmd->resp[1] =
+						readl(&host->reg->response2);
+					cmd->resp[0] =
+						readl(&host->reg->response3);
+				} else {
+					cmd->resp[0] =
+						readl(&host->reg->response0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, &host->reg->clear);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * this seems to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 * when this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host *host = param;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	direction_dev = (data->flags & MMC_DATA_WRITE) ?
+			DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+
+	/*
+	 * because dma_map_sg takes both sg and sg_len as arguments
+	 * (and maps the entire list of buffers) data->sg does not
+	 * have to be incremented between calls (as is required
+	 * in moxart_transfer_pio)
+	 */
+	len = dma_map_sg(host->dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		/* host->dma_active = true;*/
+		desc = dmaengine_prep_slave_sg(host->dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		pr_err("%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(host->dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* when DMA is available this is used only for SD_APP_SEND_SCR */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       &host->reg->data_window);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* byte order reversed only when reading SCR */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					&host->reg->data_window);
+			} else {
+				*(unsigned int *)buffer =
+					readl(&host->reg->data_window);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered) {
+		moxart_next_sg(host);
+		/*
+		 * used to goto "transfer_start" label here,
+		 * there is no need, this function will be
+		 * called again from interrupt.
+		 *
+		 * compare with DMA where sg increment is
+		 * redundant thanks to dma_map_sg
+		 */
+	} else {
+		complete(&host->pio_complete);
+	}
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, &host->reg->data_timer);
+	writel(host->size, &host->reg->data_length);
+	writel(datactrl, &host->reg->data_control);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(&host->reg->status);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, &host->reg->clear);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, &host->reg->clear);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, &host->reg->clear);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, &host->reg->clear);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(&host->reg->status) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE. instead, check
+	 * host->mrq != NULL in moxart_irq to avoid unnecessary calls
+	 * to moxart_transfer_pio
+	 * if this happens during transfer the mmc_request in
+	 * moxart_request should still be valid
+	 * (which is why host->mrq can be set NULL here)
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       &host->reg->clear);
+	writel(MSD_CARD_CHANGE, &host->reg->interrupt_mask);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(&host->reg->status) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* only use DMA/PIO (and wait) when there is data */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, &host->reg->interrupt_mask);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			host->dma_chan_cur = (mrq->cmd->data->flags
+					     & MMC_DATA_WRITE) ?
+					     host->dma_chan_tx :
+					     host->dma_chan_rx;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_cur->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       &host->reg->interrupt_mask);
+
+			status = readl(&host->reg->status);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer started from interrupt */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		/* removed during transfer? (interrupts were just enabled..) */
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(&host->reg->status);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       &host->reg->clear);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, &host->reg->clock_control);
+	} else if (!(readl(&host->reg->clock_control) & MSD_CLK_DIS)) {
+		writel(readl(&host->reg->clock_control) | MSD_CLK_DIS,
+		       &host->reg->clock_control);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(&host->reg->power_control) & ~MSD_SD_POWER_ON,
+		       &host->reg->power_control);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       &host->reg->power_control);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, &host->reg->bus_width);
+	else
+		writel(MSD_WIDE_BUS, &host->reg->bus_width);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	int ret;
+	struct moxart_host *host = mmc_priv(mmc);
+
+	(readl(&host->reg->status) & MSD_WRITE_PROT) ? (ret = 1) : (ret = 0);
+	return ret;
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+	unsigned int dma_chan_rx_req = 1;
+	unsigned int dma_chan_tx_req = 0;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc)) {
+		dev_err(dev, "%s: devm_ioremap_resource res_mmc failed\n",
+			__func__);
+		return PTR_ERR(reg_mmc);
+	}
+
+	mmc->ops = &moxart_ops;
+
+	/*
+	 * hardware does not support MMC_CAP_SD_HIGHSPEED
+	 * CMD6 will timeout and make things not work
+	 */
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* support 2.0v - 3.6v power */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->reg = (struct moxart_reg *)reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* disable all interrupt */
+	writel(0, &host->reg->interrupt_mask);
+
+	/* reset chip */
+	writel(MSD_SDC_RST, &host->reg->command);
+
+	/* wait for reset finished */
+	while (readl(&host->reg->command) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = dma_request_channel(mask, moxart_filter_fn,
+						(void *)&dma_chan_tx_req);
+	host->dma_chan_rx = dma_request_channel(mask, moxart_filter_fn,
+						(void *)&dma_chan_rx_req);
+	dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+		__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		cfg.slave_id = APB_DMA_SD_REQ_NO;
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.slave_id = APB_DMA_SD_REQ_NO;
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.dst_addr = 0;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+
+		/*
+		 * there seems to be a max size on transfers so
+		 * set max_blk_count low for both DMA and PIO
+		 *
+		 * sending large chunks result either in timeout
+		 * or render the MMC controller unresponsive
+		 * (status register 0 on consecutive read retries,
+		 * also see comments in moxart_send_command)
+		 *
+		 * obviously, DMA is quicker and can handle
+		 * larger chunks but setting it higher than 16
+		 * can still bug the controller
+		 */
+		mmc->max_blk_count = 16;
+	}
+
+	ret = request_irq(irq, moxart_irq,
+			  IRQF_DISABLED, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+	struct resource *res_irq;
+
+	res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+
+		writel(0, &host->reg->interrupt_mask);
+		writel(0, &host->reg->power_control);
+		writel(readl(&host->reg->clock_control) | MSD_CLK_DIS,
+		       &host->reg->clock_control);
+
+		free_irq(res_irq->start, host);
+
+		mmc_free_host(mmc);
+	}
+	return 0;
+}
+
+static const struct of_device_id moxart_mmc_match[] = {
+	{ .compatible = "moxa,moxart-mmc" },
+	{ }
+};
+
+static struct platform_driver moxart_mmc_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "sdhci-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_mmc_match,
+	},
+};
+module_platform_driver(moxart_mmc_driver);
+
+MODULE_ALIAS("platform:sdhci-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
diff --git a/drivers/mmc/host/sdhci-moxart.h b/drivers/mmc/host/sdhci-moxart.h
new file mode 100644
index 0000000..f60bed1
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.h
@@ -0,0 +1,155 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _SDHCI_MOXART_H
+#define _SDHCI_MOXART_H
+
+extern bool moxart_filter_fn(struct dma_chan *chan, void *param);
+
+#define MSD_CMD_REG		0
+#define MSD_ARG_REG		4
+#define MSD_RESP0_REG		8
+#define MSD_RESP1_REG		0x0c
+#define MSD_RESP2_REG		0x10
+#define MSD_RESP3_REG		0x14
+#define MSD_RESP_CMD_REG	0x18
+#define MSD_DATA_CTRL_REG	0x1c
+#define MSD_DATA_TIMER_REG	0x20
+#define MSD_DATA_LEN_REG	0x24
+#define MSD_STATUS_REG		0x28
+#define MSD_CLEAR_REG		0x2c
+#define MSD_INT_MASK_REG	0x30
+#define MSD_POWER_CTRL_REG	0x34
+#define MSD_CLOCK_CTRL_REG	0x38
+#define MSD_BUS_WIDTH_REG	0x3c
+#define MSD_DATA_WIN_REG	0x40
+#define MSD_FEATURE_REG		0x44
+#define MSD_REVISION_REG	0x48
+
+#define MMC_RSP_SHORT		(1 << 0)
+#define MMC_RSP_LONG		(2 << 0)
+#define MMC_RSP_MASK		(3 << 0)
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+struct moxart_reg {
+
+#define MSD_SDC_RST		(1<<10)
+#define MSD_CMD_EN		(1<<9)
+#define MSD_APP_CMD		(1<<8)
+#define MSD_LONG_RSP		(1<<7)
+#define MSD_NEED_RSP		(1<<6)
+#define MSD_CMD_IDX_MASK	0x3f
+	unsigned int	command;
+
+	unsigned int	argument;
+	unsigned int	response0;
+	unsigned int	response1;
+	unsigned int	response2;
+	unsigned int	response3;
+
+#define MSD_RSP_CMD_APP		(1<<6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+	unsigned int	response_command;
+
+#define MSD_DATA_EN		(1<<6)
+#define MSD_DMA_EN		(1<<5)
+#define MSD_DATA_WRITE		(1<<4)
+#define MSD_BLK_SIZE_MASK	0x0f
+	unsigned int	data_control;
+
+	unsigned int	data_timer;
+
+#define MSD_DATA_LEN_MASK	0xffffff
+	unsigned int	data_length;
+
+#define MSD_WRITE_PROT		(1<<12)
+#define MSD_CARD_DETECT		(1<<11)
+/* 1-10 below can be sent to interrupt or clear register */
+#define MSD_CARD_CHANGE		(1<<10)
+#define MSD_FIFO_ORUN		(1<<9)
+#define MSD_FIFO_URUN		(1<<8)
+#define MSD_DATA_END		(1<<7)
+#define MSD_CMD_SENT		(1<<6)
+#define MSD_DATA_CRC_OK		(1<<5)
+#define MSD_RSP_CRC_OK		(1<<4)
+#define MSD_DATA_TIMEOUT	(1<<3)
+#define MSD_RSP_TIMEOUT		(1<<2)
+#define MSD_DATA_CRC_FAIL	(1<<1)
+#define MSD_RSP_CRC_FAIL	(1<<0)
+	unsigned int	status;
+
+	unsigned int	clear;
+	unsigned int	interrupt_mask;
+
+#define MSD_SD_POWER_ON		(1<<4)
+#define MSD_SD_POWER_MASK	0x0f
+	unsigned int	power_control;
+
+#define MSD_CLK_DIS		(1<<8)
+#define MSD_CLK_SD		(1<<7)
+#define MSD_CLK_DIV_MASK	0x7f
+	unsigned int	clock_control;
+
+#define MSD_WIDE_BUS_SUPPORT	(1<<3)
+#define MSD_WIDE_BUS		(1<<2)	/* bus width is 4 bytes */
+#define MSD_SINGLE_BUS		(1<<0)	/* bus width is 1 byte */
+	unsigned int	bus_width;
+
+	unsigned int	data_window;
+
+#define MSD_CPRM_FUNCTION	(1<<8)
+	unsigned int	feature;
+
+	unsigned int	revision;
+};
+
+struct moxart_host {
+	spinlock_t		lock;
+	struct moxart_reg	*reg;
+	phys_addr_t		reg_phys;
+
+	struct dma_chan		*dma_chan_rx;
+	struct dma_chan		*dma_chan_tx;
+	struct dma_chan		*dma_chan_cur;
+	unsigned int		dma_direction;
+	bool			have_dma;
+	struct completion	dma_complete;
+	struct completion	pio_complete;
+
+	struct mmc_host		*mmc;
+	struct mmc_request	*mrq;
+
+	struct scatterlist	*cur_sg;
+	unsigned int		num_sg;
+	unsigned int		remain;
+	int			size;
+
+	unsigned int		wait_for;
+	unsigned int		timeout;
+	long			sysclk;
+	bool			removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16	/* 16 bytes */
+
+#endif	/* _SDHCI_MOXART_H */
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2013-07-10  9:28 [PATCH] mmc: sdhci-moxart: Add MOXA ART SDHCI driver Jonas Jensen
@ 2013-07-17 12:51 ` Jonas Jensen
  2013-07-29 10:49   ` [PATCH v3] " Jonas Jensen
  0 siblings, 1 reply; 12+ messages in thread
From: Jonas Jensen @ 2013-07-17 12:51 UTC (permalink / raw)
  To: linux-mmc; +Cc: cjb, linux-arm-kernel, linux-kernel, arm, Jonas Jensen

Add SDHCI driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    Changes since v1:
    
    1. remove IRQF_DISABLED
    2. use devm_request_irq, remove free_irq
    3. remove unnecessary dev_set_drvdata in moxart_remove
    4. add devicetree bindings document
    5. use BIT macro in header file
    
    Applies to next-20130716

 .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  17 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/sdhci-moxart.c                    | 780 +++++++++++++++++++++
 drivers/mmc/host/sdhci-moxart.h                    | 155 ++++
 5 files changed, 962 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
 create mode 100644 drivers/mmc/host/sdhci-moxart.c
 create mode 100644 drivers/mmc/host/sdhci-moxart.h

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
new file mode 100644
index 0000000..9ec8650
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
@@ -0,0 +1,17 @@
+MOXA ART SD Host Controller Interface
+
+Required properties:
+
+- compatible : Should be "moxa,moxart-mmc"
+- reg : Should contain registers location and length
+- interrupts : Should contain the interrupt number
+- clocks : Should contain phandle for APB clock "clkapb"
+
+Example:
+
+	mmc: mmc@98e00000 {
+		compatible = "moxa,moxart-mmc";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&clkapb>;
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 8a4c066..3c246390 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -271,6 +271,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_SDHCI_MOXART
+	tristate "MOXART SD Host Controller Interface support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects the MOXART SD Host Controller Interface.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index d422e21..c91317f 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
 obj-$(CONFIG_MMC_SDHCI_BCM_KONA)	+= sdhci-bcm-kona.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
+obj-$(CONFIG_MMC_SDHCI_MOXART)		+= sdhci-moxart.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sdhci-moxart.c b/drivers/mmc/host/sdhci-moxart.c
new file mode 100644
index 0000000..9d809b5
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.c
@@ -0,0 +1,780 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#include "sdhci-moxart.h"
+
+#define DMA_FIFO_LEN_FORCE		0
+#define APB_DMA_SD_REQ_NO		5
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, &host->reg->clear);
+	writel(cmd->arg, &host->reg->argument);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, &host->reg->command);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(&host->reg->status);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, &host->reg->clear);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, &host->reg->clear);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, &host->reg->clear);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(&host->reg->response0);
+					cmd->resp[2] =
+						readl(&host->reg->response1);
+					cmd->resp[1] =
+						readl(&host->reg->response2);
+					cmd->resp[0] =
+						readl(&host->reg->response3);
+				} else {
+					cmd->resp[0] =
+						readl(&host->reg->response0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, &host->reg->clear);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * this seems to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 * when this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host *host = param;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	direction_dev = (data->flags & MMC_DATA_WRITE) ?
+			DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+
+	/*
+	 * because dma_map_sg takes both sg and sg_len as arguments
+	 * (and maps the entire list of buffers) data->sg does not
+	 * have to be incremented between calls (as is required
+	 * in moxart_transfer_pio)
+	 */
+	len = dma_map_sg(host->dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		/* host->dma_active = true;*/
+		desc = dmaengine_prep_slave_sg(host->dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		pr_err("%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(host->dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* when DMA is available this is used only for SD_APP_SEND_SCR */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       &host->reg->data_window);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* byte order reversed only when reading SCR */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					&host->reg->data_window);
+			} else {
+				*(unsigned int *)buffer =
+					readl(&host->reg->data_window);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered) {
+		moxart_next_sg(host);
+		/*
+		 * used to goto "transfer_start" label here,
+		 * there is no need, this function will be
+		 * called again from interrupt.
+		 *
+		 * compare with DMA where sg increment is
+		 * redundant thanks to dma_map_sg
+		 */
+	} else {
+		complete(&host->pio_complete);
+	}
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, &host->reg->data_timer);
+	writel(host->size, &host->reg->data_length);
+	writel(datactrl, &host->reg->data_control);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(&host->reg->status);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, &host->reg->clear);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, &host->reg->clear);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, &host->reg->clear);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, &host->reg->clear);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(&host->reg->status) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE. instead, check
+	 * host->mrq != NULL in moxart_irq to avoid unnecessary calls
+	 * to moxart_transfer_pio
+	 * if this happens during transfer the mmc_request in
+	 * moxart_request should still be valid
+	 * (which is why host->mrq can be set NULL here)
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       &host->reg->clear);
+	writel(MSD_CARD_CHANGE, &host->reg->interrupt_mask);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(&host->reg->status) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* only use DMA/PIO (and wait) when there is data */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, &host->reg->interrupt_mask);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			host->dma_chan_cur = (mrq->cmd->data->flags
+					     & MMC_DATA_WRITE) ?
+					     host->dma_chan_tx :
+					     host->dma_chan_rx;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_cur->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       &host->reg->interrupt_mask);
+
+			status = readl(&host->reg->status);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer started from interrupt */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		/* removed during transfer? (interrupts were just enabled..) */
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(&host->reg->status);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       &host->reg->clear);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, &host->reg->clock_control);
+	} else if (!(readl(&host->reg->clock_control) & MSD_CLK_DIS)) {
+		writel(readl(&host->reg->clock_control) | MSD_CLK_DIS,
+		       &host->reg->clock_control);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(&host->reg->power_control) & ~MSD_SD_POWER_ON,
+		       &host->reg->power_control);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       &host->reg->power_control);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, &host->reg->bus_width);
+	else
+		writel(MSD_WIDE_BUS, &host->reg->bus_width);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	int ret;
+	struct moxart_host *host = mmc_priv(mmc);
+
+	(readl(&host->reg->status) & MSD_WRITE_PROT) ? (ret = 1) : (ret = 0);
+	return ret;
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+	unsigned int dma_chan_rx_req = 1;
+	unsigned int dma_chan_tx_req = 0;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc)) {
+		dev_err(dev, "%s: devm_ioremap_resource res_mmc failed\n",
+			__func__);
+		return PTR_ERR(reg_mmc);
+	}
+
+	mmc->ops = &moxart_ops;
+
+	/*
+	 * hardware does not support MMC_CAP_SD_HIGHSPEED
+	 * CMD6 will timeout and make things not work
+	 */
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* support 2.0v - 3.6v power */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->reg = (struct moxart_reg *)reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* disable all interrupt */
+	writel(0, &host->reg->interrupt_mask);
+
+	/* reset chip */
+	writel(MSD_SDC_RST, &host->reg->command);
+
+	/* wait for reset finished */
+	while (readl(&host->reg->command) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = dma_request_channel(mask, moxart_filter_fn,
+						(void *)&dma_chan_tx_req);
+	host->dma_chan_rx = dma_request_channel(mask, moxart_filter_fn,
+						(void *)&dma_chan_rx_req);
+	dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+		__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		cfg.slave_id = APB_DMA_SD_REQ_NO;
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.slave_id = APB_DMA_SD_REQ_NO;
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.dst_addr = 0;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+
+		/*
+		 * there seems to be a max size on transfers so
+		 * set max_blk_count low for both DMA and PIO
+		 *
+		 * sending large chunks result either in timeout
+		 * or render the MMC controller unresponsive
+		 * (status register 0 on consecutive read retries,
+		 * also see comments in moxart_send_command)
+		 *
+		 * obviously, DMA is quicker and can handle
+		 * larger chunks but setting it higher than 16
+		 * can still bug the controller
+		 */
+		mmc->max_blk_count = 16;
+	}
+
+	devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, &host->reg->interrupt_mask);
+		writel(0, &host->reg->power_control);
+		writel(readl(&host->reg->clock_control) | MSD_CLK_DIS,
+		       &host->reg->clock_control);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_mmc_match[] = {
+	{ .compatible = "moxa,moxart-mmc" },
+	{ }
+};
+
+static struct platform_driver moxart_mmc_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "sdhci-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_mmc_match,
+	},
+};
+module_platform_driver(moxart_mmc_driver);
+
+MODULE_ALIAS("platform:sdhci-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
diff --git a/drivers/mmc/host/sdhci-moxart.h b/drivers/mmc/host/sdhci-moxart.h
new file mode 100644
index 0000000..7269d40
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.h
@@ -0,0 +1,155 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _SDHCI_MOXART_H
+#define _SDHCI_MOXART_H
+
+extern bool moxart_filter_fn(struct dma_chan *chan, void *param);
+
+#define MSD_CMD_REG		0
+#define MSD_ARG_REG		4
+#define MSD_RESP0_REG		8
+#define MSD_RESP1_REG		0x0c
+#define MSD_RESP2_REG		0x10
+#define MSD_RESP3_REG		0x14
+#define MSD_RESP_CMD_REG	0x18
+#define MSD_DATA_CTRL_REG	0x1c
+#define MSD_DATA_TIMER_REG	0x20
+#define MSD_DATA_LEN_REG	0x24
+#define MSD_STATUS_REG		0x28
+#define MSD_CLEAR_REG		0x2c
+#define MSD_INT_MASK_REG	0x30
+#define MSD_POWER_CTRL_REG	0x34
+#define MSD_CLOCK_CTRL_REG	0x38
+#define MSD_BUS_WIDTH_REG	0x3c
+#define MSD_DATA_WIN_REG	0x40
+#define MSD_FEATURE_REG		0x44
+#define MSD_REVISION_REG	0x48
+
+#define MMC_RSP_SHORT		1
+#define MMC_RSP_LONG		2
+#define MMC_RSP_MASK		3
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+struct moxart_reg {
+
+#define MSD_SDC_RST		BIT(10)
+#define MSD_CMD_EN		BIT(9)
+#define MSD_APP_CMD		BIT(8)
+#define MSD_LONG_RSP		BIT(7)
+#define MSD_NEED_RSP		BIT(6)
+#define MSD_CMD_IDX_MASK	0x3f
+	unsigned int	command;
+
+	unsigned int	argument;
+	unsigned int	response0;
+	unsigned int	response1;
+	unsigned int	response2;
+	unsigned int	response3;
+
+#define MSD_RSP_CMD_APP		BIT(6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+	unsigned int	response_command;
+
+#define MSD_DATA_EN		BIT(6)
+#define MSD_DMA_EN		BIT(5)
+#define MSD_DATA_WRITE		BIT(4)
+#define MSD_BLK_SIZE_MASK	0x0f
+	unsigned int	data_control;
+
+	unsigned int	data_timer;
+
+#define MSD_DATA_LEN_MASK	0xffffff
+	unsigned int	data_length;
+
+#define MSD_WRITE_PROT		BIT(12)
+#define MSD_CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to interrupt or clear register */
+#define MSD_CARD_CHANGE		BIT(10)
+#define MSD_FIFO_ORUN		BIT(9)
+#define MSD_FIFO_URUN		BIT(8)
+#define MSD_DATA_END		BIT(7)
+#define MSD_CMD_SENT		BIT(6)
+#define MSD_DATA_CRC_OK		BIT(5)
+#define MSD_RSP_CRC_OK		BIT(4)
+#define MSD_DATA_TIMEOUT	BIT(3)
+#define MSD_RSP_TIMEOUT		BIT(2)
+#define MSD_DATA_CRC_FAIL	BIT(1)
+#define MSD_RSP_CRC_FAIL	BIT(0)
+	unsigned int	status;
+
+	unsigned int	clear;
+	unsigned int	interrupt_mask;
+
+#define MSD_SD_POWER_ON		BIT(4)
+#define MSD_SD_POWER_MASK	0x0f
+	unsigned int	power_control;
+
+#define MSD_CLK_DIS		BIT(8)
+#define MSD_CLK_SD		BIT(7)
+#define MSD_CLK_DIV_MASK	0x7f
+	unsigned int	clock_control;
+
+#define MSD_WIDE_BUS_SUPPORT	BIT(3)
+#define MSD_WIDE_BUS		BIT(2)	/* bus width is 4 bytes */
+#define MSD_SINGLE_BUS		BIT(0)	/* bus width is 1 byte */
+	unsigned int	bus_width;
+
+	unsigned int	data_window;
+
+#define MSD_CPRM_FUNCTION	BIT(8)
+	unsigned int	feature;
+
+	unsigned int	revision;
+};
+
+struct moxart_host {
+	spinlock_t		lock;
+	struct moxart_reg	*reg;
+	phys_addr_t		reg_phys;
+
+	struct dma_chan		*dma_chan_rx;
+	struct dma_chan		*dma_chan_tx;
+	struct dma_chan		*dma_chan_cur;
+	unsigned int		dma_direction;
+	bool			have_dma;
+	struct completion	dma_complete;
+	struct completion	pio_complete;
+
+	struct mmc_host		*mmc;
+	struct mmc_request	*mrq;
+
+	struct scatterlist	*cur_sg;
+	unsigned int		num_sg;
+	unsigned int		remain;
+	int			size;
+
+	unsigned int		wait_for;
+	unsigned int		timeout;
+	long			sysclk;
+	bool			removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16	/* 16 bytes */
+
+#endif	/* _SDHCI_MOXART_H */
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2013-07-17 12:51 ` [PATCH v2] " Jonas Jensen
@ 2013-07-29 10:49   ` Jonas Jensen
  2013-07-30  9:08     ` Mark Rutland
  2013-08-06 13:57     ` [PATCH v4] " Jonas Jensen
  0 siblings, 2 replies; 12+ messages in thread
From: Jonas Jensen @ 2013-07-29 10:49 UTC (permalink / raw)
  To: linux-mmc; +Cc: cjb, linux-arm-kernel, linux-kernel, arm, Jonas Jensen

Add SDHCI driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    Changes since v2:
    
    1. #include <linux/bitops.h> because BIT() comes from there
    2. reinsert dev_set_drvdata() in moxart_remove
    3. remove MODULE_VERSION()
    
    device tree bindings document:
    4. describe compatible variable "Must be" instead of "Should be".
    5. change description so it's from the point of view of the device
    
    Applies to next-20130729

 .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  17 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/sdhci-moxart.c                    | 782 +++++++++++++++++++++
 drivers/mmc/host/sdhci-moxart.h                    | 155 ++++
 5 files changed, 964 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
 create mode 100644 drivers/mmc/host/sdhci-moxart.c
 create mode 100644 drivers/mmc/host/sdhci-moxart.h

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
new file mode 100644
index 0000000..67782ad
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
@@ -0,0 +1,17 @@
+MOXA ART SD Host Controller Interface
+
+Required properties:
+
+- compatible : Must be "moxa,moxart-mmc"
+- reg : Should contain registers location and length
+- interrupts : Should contain the interrupt number
+- clocks : Should contain phandle for the clock feeding the SDHCI controller
+
+Example:
+
+	mmc: mmc@98e00000 {
+		compatible = "moxa,moxart-mmc";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&coreclk>;
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 8a4c066..3c246390 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -271,6 +271,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_SDHCI_MOXART
+	tristate "MOXART SD Host Controller Interface support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects the MOXART SD Host Controller Interface.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index d422e21..c91317f 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
 obj-$(CONFIG_MMC_SDHCI_BCM_KONA)	+= sdhci-bcm-kona.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
+obj-$(CONFIG_MMC_SDHCI_MOXART)		+= sdhci-moxart.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sdhci-moxart.c b/drivers/mmc/host/sdhci-moxart.c
new file mode 100644
index 0000000..fa9056c
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.c
@@ -0,0 +1,782 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#include "sdhci-moxart.h"
+
+#define DMA_FIFO_LEN_FORCE		0
+#define APB_DMA_SD_REQ_NO		5
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, &host->reg->clear);
+	writel(cmd->arg, &host->reg->argument);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, &host->reg->command);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(&host->reg->status);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, &host->reg->clear);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, &host->reg->clear);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, &host->reg->clear);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(&host->reg->response0);
+					cmd->resp[2] =
+						readl(&host->reg->response1);
+					cmd->resp[1] =
+						readl(&host->reg->response2);
+					cmd->resp[0] =
+						readl(&host->reg->response3);
+				} else {
+					cmd->resp[0] =
+						readl(&host->reg->response0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, &host->reg->clear);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * this seems to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 * when this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host *host = param;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	direction_dev = (data->flags & MMC_DATA_WRITE) ?
+			DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+
+	/*
+	 * because dma_map_sg takes both sg and sg_len as arguments
+	 * (and maps the entire list of buffers) data->sg does not
+	 * have to be incremented between calls (as is required
+	 * in moxart_transfer_pio)
+	 */
+	len = dma_map_sg(host->dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		/* host->dma_active = true;*/
+		desc = dmaengine_prep_slave_sg(host->dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		pr_err("%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(host->dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* when DMA is available this is used only for SD_APP_SEND_SCR */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       &host->reg->data_window);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* byte order reversed only when reading SCR */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					&host->reg->data_window);
+			} else {
+				*(unsigned int *)buffer =
+					readl(&host->reg->data_window);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered) {
+		moxart_next_sg(host);
+		/*
+		 * used to goto "transfer_start" label here,
+		 * there is no need, this function will be
+		 * called again from interrupt.
+		 *
+		 * compare with DMA where sg increment is
+		 * redundant thanks to dma_map_sg
+		 */
+	} else {
+		complete(&host->pio_complete);
+	}
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, &host->reg->data_timer);
+	writel(host->size, &host->reg->data_length);
+	writel(datactrl, &host->reg->data_control);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(&host->reg->status);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, &host->reg->clear);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, &host->reg->clear);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, &host->reg->clear);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, &host->reg->clear);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(&host->reg->status) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE. instead, check
+	 * host->mrq != NULL in moxart_irq to avoid unnecessary calls
+	 * to moxart_transfer_pio
+	 * if this happens during transfer the mmc_request in
+	 * moxart_request should still be valid
+	 * (which is why host->mrq can be set NULL here)
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       &host->reg->clear);
+	writel(MSD_CARD_CHANGE, &host->reg->interrupt_mask);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(&host->reg->status) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* only use DMA/PIO (and wait) when there is data */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, &host->reg->interrupt_mask);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			host->dma_chan_cur = (mrq->cmd->data->flags
+					     & MMC_DATA_WRITE) ?
+					     host->dma_chan_tx :
+					     host->dma_chan_rx;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_cur->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       &host->reg->interrupt_mask);
+
+			status = readl(&host->reg->status);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer started from interrupt */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		/* removed during transfer? (interrupts were just enabled..) */
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(&host->reg->status);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       &host->reg->clear);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, &host->reg->clock_control);
+	} else if (!(readl(&host->reg->clock_control) & MSD_CLK_DIS)) {
+		writel(readl(&host->reg->clock_control) | MSD_CLK_DIS,
+		       &host->reg->clock_control);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(&host->reg->power_control) & ~MSD_SD_POWER_ON,
+		       &host->reg->power_control);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       &host->reg->power_control);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, &host->reg->bus_width);
+	else
+		writel(MSD_WIDE_BUS, &host->reg->bus_width);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	int ret;
+	struct moxart_host *host = mmc_priv(mmc);
+
+	(readl(&host->reg->status) & MSD_WRITE_PROT) ? (ret = 1) : (ret = 0);
+	return ret;
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+	unsigned int dma_chan_rx_req = 1;
+	unsigned int dma_chan_tx_req = 0;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc)) {
+		dev_err(dev, "%s: devm_ioremap_resource res_mmc failed\n",
+			__func__);
+		return PTR_ERR(reg_mmc);
+	}
+
+	mmc->ops = &moxart_ops;
+
+	/*
+	 * hardware does not support MMC_CAP_SD_HIGHSPEED
+	 * CMD6 will timeout and make things not work
+	 */
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* support 2.0v - 3.6v power */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->reg = (struct moxart_reg *)reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* disable all interrupt */
+	writel(0, &host->reg->interrupt_mask);
+
+	/* reset chip */
+	writel(MSD_SDC_RST, &host->reg->command);
+
+	/* wait for reset finished */
+	while (readl(&host->reg->command) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = dma_request_channel(mask, moxart_filter_fn,
+						(void *)&dma_chan_tx_req);
+	host->dma_chan_rx = dma_request_channel(mask, moxart_filter_fn,
+						(void *)&dma_chan_rx_req);
+	dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+		__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		cfg.slave_id = APB_DMA_SD_REQ_NO;
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.slave_id = APB_DMA_SD_REQ_NO;
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.dst_addr = 0;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+
+		/*
+		 * there seems to be a max size on transfers so
+		 * set max_blk_count low for both DMA and PIO
+		 *
+		 * sending large chunks result either in timeout
+		 * or render the MMC controller unresponsive
+		 * (status register 0 on consecutive read retries,
+		 * also see comments in moxart_send_command)
+		 *
+		 * obviously, DMA is quicker and can handle
+		 * larger chunks but setting it higher than 16
+		 * can still bug the controller
+		 */
+		mmc->max_blk_count = 16;
+	}
+
+	devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, &host->reg->interrupt_mask);
+		writel(0, &host->reg->power_control);
+		writel(readl(&host->reg->clock_control) | MSD_CLK_DIS,
+		       &host->reg->clock_control);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_mmc_match[] = {
+	{ .compatible = "moxa,moxart-mmc" },
+	{ }
+};
+
+static struct platform_driver moxart_mmc_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "sdhci-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_mmc_match,
+	},
+};
+module_platform_driver(moxart_mmc_driver);
+
+MODULE_ALIAS("platform:sdhci-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
diff --git a/drivers/mmc/host/sdhci-moxart.h b/drivers/mmc/host/sdhci-moxart.h
new file mode 100644
index 0000000..7269d40
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.h
@@ -0,0 +1,155 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _SDHCI_MOXART_H
+#define _SDHCI_MOXART_H
+
+extern bool moxart_filter_fn(struct dma_chan *chan, void *param);
+
+#define MSD_CMD_REG		0
+#define MSD_ARG_REG		4
+#define MSD_RESP0_REG		8
+#define MSD_RESP1_REG		0x0c
+#define MSD_RESP2_REG		0x10
+#define MSD_RESP3_REG		0x14
+#define MSD_RESP_CMD_REG	0x18
+#define MSD_DATA_CTRL_REG	0x1c
+#define MSD_DATA_TIMER_REG	0x20
+#define MSD_DATA_LEN_REG	0x24
+#define MSD_STATUS_REG		0x28
+#define MSD_CLEAR_REG		0x2c
+#define MSD_INT_MASK_REG	0x30
+#define MSD_POWER_CTRL_REG	0x34
+#define MSD_CLOCK_CTRL_REG	0x38
+#define MSD_BUS_WIDTH_REG	0x3c
+#define MSD_DATA_WIN_REG	0x40
+#define MSD_FEATURE_REG		0x44
+#define MSD_REVISION_REG	0x48
+
+#define MMC_RSP_SHORT		1
+#define MMC_RSP_LONG		2
+#define MMC_RSP_MASK		3
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+struct moxart_reg {
+
+#define MSD_SDC_RST		BIT(10)
+#define MSD_CMD_EN		BIT(9)
+#define MSD_APP_CMD		BIT(8)
+#define MSD_LONG_RSP		BIT(7)
+#define MSD_NEED_RSP		BIT(6)
+#define MSD_CMD_IDX_MASK	0x3f
+	unsigned int	command;
+
+	unsigned int	argument;
+	unsigned int	response0;
+	unsigned int	response1;
+	unsigned int	response2;
+	unsigned int	response3;
+
+#define MSD_RSP_CMD_APP		BIT(6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+	unsigned int	response_command;
+
+#define MSD_DATA_EN		BIT(6)
+#define MSD_DMA_EN		BIT(5)
+#define MSD_DATA_WRITE		BIT(4)
+#define MSD_BLK_SIZE_MASK	0x0f
+	unsigned int	data_control;
+
+	unsigned int	data_timer;
+
+#define MSD_DATA_LEN_MASK	0xffffff
+	unsigned int	data_length;
+
+#define MSD_WRITE_PROT		BIT(12)
+#define MSD_CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to interrupt or clear register */
+#define MSD_CARD_CHANGE		BIT(10)
+#define MSD_FIFO_ORUN		BIT(9)
+#define MSD_FIFO_URUN		BIT(8)
+#define MSD_DATA_END		BIT(7)
+#define MSD_CMD_SENT		BIT(6)
+#define MSD_DATA_CRC_OK		BIT(5)
+#define MSD_RSP_CRC_OK		BIT(4)
+#define MSD_DATA_TIMEOUT	BIT(3)
+#define MSD_RSP_TIMEOUT		BIT(2)
+#define MSD_DATA_CRC_FAIL	BIT(1)
+#define MSD_RSP_CRC_FAIL	BIT(0)
+	unsigned int	status;
+
+	unsigned int	clear;
+	unsigned int	interrupt_mask;
+
+#define MSD_SD_POWER_ON		BIT(4)
+#define MSD_SD_POWER_MASK	0x0f
+	unsigned int	power_control;
+
+#define MSD_CLK_DIS		BIT(8)
+#define MSD_CLK_SD		BIT(7)
+#define MSD_CLK_DIV_MASK	0x7f
+	unsigned int	clock_control;
+
+#define MSD_WIDE_BUS_SUPPORT	BIT(3)
+#define MSD_WIDE_BUS		BIT(2)	/* bus width is 4 bytes */
+#define MSD_SINGLE_BUS		BIT(0)	/* bus width is 1 byte */
+	unsigned int	bus_width;
+
+	unsigned int	data_window;
+
+#define MSD_CPRM_FUNCTION	BIT(8)
+	unsigned int	feature;
+
+	unsigned int	revision;
+};
+
+struct moxart_host {
+	spinlock_t		lock;
+	struct moxart_reg	*reg;
+	phys_addr_t		reg_phys;
+
+	struct dma_chan		*dma_chan_rx;
+	struct dma_chan		*dma_chan_tx;
+	struct dma_chan		*dma_chan_cur;
+	unsigned int		dma_direction;
+	bool			have_dma;
+	struct completion	dma_complete;
+	struct completion	pio_complete;
+
+	struct mmc_host		*mmc;
+	struct mmc_request	*mrq;
+
+	struct scatterlist	*cur_sg;
+	unsigned int		num_sg;
+	unsigned int		remain;
+	int			size;
+
+	unsigned int		wait_for;
+	unsigned int		timeout;
+	long			sysclk;
+	bool			removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16	/* 16 bytes */
+
+#endif	/* _SDHCI_MOXART_H */
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v3] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2013-07-29 10:49   ` [PATCH v3] " Jonas Jensen
@ 2013-07-30  9:08     ` Mark Rutland
  2013-08-06 13:57     ` [PATCH v4] " Jonas Jensen
  1 sibling, 0 replies; 12+ messages in thread
From: Mark Rutland @ 2013-07-30  9:08 UTC (permalink / raw)
  To: Jonas Jensen; +Cc: linux-mmc, cjb, linux-kernel, linux-arm-kernel, arm

On Mon, Jul 29, 2013 at 11:49:34AM +0100, Jonas Jensen wrote:
> Add SDHCI driver for MOXA ART SoCs.
> 
> Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
> ---
> 
> Notes:
>     Changes since v2:
> 
>     1. #include <linux/bitops.h> because BIT() comes from there
>     2. reinsert dev_set_drvdata() in moxart_remove
>     3. remove MODULE_VERSION()
> 
>     device tree bindings document:
>     4. describe compatible variable "Must be" instead of "Should be".
>     5. change description so it's from the point of view of the device
> 
>     Applies to next-20130729
> 
>  .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  17 +
>  drivers/mmc/host/Kconfig                           |   9 +
>  drivers/mmc/host/Makefile                          |   1 +
>  drivers/mmc/host/sdhci-moxart.c                    | 782 +++++++++++++++++++++
>  drivers/mmc/host/sdhci-moxart.h                    | 155 ++++
>  5 files changed, 964 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
>  create mode 100644 drivers/mmc/host/sdhci-moxart.c
>  create mode 100644 drivers/mmc/host/sdhci-moxart.h
> 
> diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> new file mode 100644
> index 0000000..67782ad
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> @@ -0,0 +1,17 @@
> +MOXA ART SD Host Controller Interface
> +
> +Required properties:
> +
> +- compatible : Must be "moxa,moxart-mmc"
> +- reg : Should contain registers location and length
> +- interrupts : Should contain the interrupt number
> +- clocks : Should contain phandle for the clock feeding the SDHCI controller
> +
> +Example:
> +
> +       mmc: mmc@98e00000 {
> +               compatible = "moxa,moxart-mmc";
> +               reg = <0x98e00000 0x5C>;
> +               interrupts = <5 0>;
> +               clocks = <&coreclk>;
> +       };

This binding looks sensible, but I'd appreciate someone who understands
MMCs checking that this captures the relevant information, as I don't
know much about MMCs.

Most mmc bindings I see have to describe some gpio pins, pinctrl to mux
the relevant pins on from the SoC, etc. Is this a full description of
the hardware supporting all features?

[...]

> +static int moxart_get_ro(struct mmc_host *mmc)
> +{
> +       int ret;
> +       struct moxart_host *host = mmc_priv(mmc);
> +
> +       (readl(&host->reg->status) & MSD_WRITE_PROT) ? (ret = 1) : (ret = 0);
> +       return ret;

This looks a little odd, how about something like:

return !!(readl(&host->reg->status) & SD_WRITE_PROT);

> +}
> +
> +static struct mmc_host_ops moxart_ops = {
> +       .request = moxart_request,
> +       .set_ios = moxart_set_ios,
> +       .get_ro = moxart_get_ro,
> +};
> +
> +static int moxart_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +       struct resource res_mmc;
> +       struct mmc_host *mmc;
> +       struct moxart_host *host = NULL;
> +       void __iomem *reg_mmc;
> +       dma_cap_mask_t mask;
> +       int ret;
> +       struct dma_slave_config cfg;
> +       unsigned int irq;
> +       struct clk *clk;
> +       unsigned int dma_chan_rx_req = 1;
> +       unsigned int dma_chan_tx_req = 0;
> +
> +       mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
> +       if (!mmc) {
> +               dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
> +               ret = -ENOMEM;
> +               goto out;
> +       }
> +
> +       ret = of_address_to_resource(node, 0, &res_mmc);
> +       if (ret) {
> +               dev_err(dev, "%s: could not get MMC base resource\n", __func__);
> +               goto out;
> +       }
> +
> +       irq = irq_of_parse_and_map(node, 0);
> +
> +       reg_mmc = devm_ioremap_resource(dev, &res_mmc);
> +       if (IS_ERR(reg_mmc)) {
> +               dev_err(dev, "%s: devm_ioremap_resource res_mmc failed\n",
> +                       __func__);
> +               return PTR_ERR(reg_mmc);
> +       }
> +
> +       mmc->ops = &moxart_ops;
> +
> +       /*
> +        * hardware does not support MMC_CAP_SD_HIGHSPEED
> +        * CMD6 will timeout and make things not work
> +        */
> +       mmc->caps = MMC_CAP_4_BIT_DATA;
> +
> +       mmc->f_min = 400000;
> +       mmc->f_max = 25000000;
> +       mmc->ocr_avail = 0xffff00;      /* support 2.0v - 3.6v power */
> +       mmc->max_segs = 32;
> +       mmc->max_blk_size = 512;
> +       mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
> +       mmc->max_seg_size = mmc->max_req_size;
> +
> +       host = mmc_priv(mmc);
> +       host->mmc = mmc;
> +       host->reg = (struct moxart_reg *)reg_mmc;
> +       host->reg_phys = res_mmc.start;
> +       host->timeout = msecs_to_jiffies(1000);
> +
> +       dma_cap_zero(mask);
> +       dma_cap_set(DMA_SLAVE, mask);
> +
> +       clk = of_clk_get(node, 0);
> +       if (IS_ERR(clk)) {
> +               dev_err(dev, "%s: of_clk_get failed\n", __func__);
> +               return PTR_ERR(clk);
> +       }
> +       host->sysclk = clk_get_rate(clk);
> +
> +       spin_lock_init(&host->lock);
> +
> +       /* disable all interrupt */
> +       writel(0, &host->reg->interrupt_mask);
> +
> +       /* reset chip */
> +       writel(MSD_SDC_RST, &host->reg->command);
> +
> +       /* wait for reset finished */
> +       while (readl(&host->reg->command) & MSD_SDC_RST)
> +               udelay(10);
> +
> +       host->dma_chan_tx = dma_request_channel(mask, moxart_filter_fn,
> +                                               (void *)&dma_chan_tx_req);
> +       host->dma_chan_rx = dma_request_channel(mask, moxart_filter_fn,
> +                                               (void *)&dma_chan_rx_req);
> +       dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
> +               __func__, host->dma_chan_rx, host->dma_chan_tx);
> +
> +       if (!host->dma_chan_rx || !host->dma_chan_tx) {
> +               host->have_dma = false;
> +               mmc->max_blk_count = 1;
> +       } else {
> +               cfg.slave_id = APB_DMA_SD_REQ_NO;
> +               cfg.direction = DMA_MEM_TO_DEV;
> +               cfg.src_addr = 0;
> +               cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
> +               cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +               dmaengine_slave_config(host->dma_chan_tx, &cfg);
> +
> +               cfg.slave_id = APB_DMA_SD_REQ_NO;
> +               cfg.direction = DMA_DEV_TO_MEM;
> +               cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
> +               cfg.dst_addr = 0;
> +               cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +               dmaengine_slave_config(host->dma_chan_rx, &cfg);
> +
> +               host->have_dma = true;
> +
> +               /*
> +                * there seems to be a max size on transfers so
> +                * set max_blk_count low for both DMA and PIO
> +                *
> +                * sending large chunks result either in timeout
> +                * or render the MMC controller unresponsive
> +                * (status register 0 on consecutive read retries,
> +                * also see comments in moxart_send_command)
> +                *
> +                * obviously, DMA is quicker and can handle
> +                * larger chunks but setting it higher than 16
> +                * can still bug the controller
> +                */
> +               mmc->max_blk_count = 16;
> +       }
> +
> +       devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
> +
> +       if (ret)
> +               goto out;

Presumably you meant to record the return code of devm_request_irq?

> +
> +       dev_set_drvdata(dev, mmc);
> +       mmc_add_host(mmc);
> +
> +       dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
> +
> +       return 0;
> +
> +out:
> +       if (mmc)
> +               mmc_free_host(mmc);
> +       return ret;
> +}

[...]

> +struct moxart_reg {
> +
> +#define MSD_SDC_RST            BIT(10)
> +#define MSD_CMD_EN             BIT(9)
> +#define MSD_APP_CMD            BIT(8)
> +#define MSD_LONG_RSP           BIT(7)
> +#define MSD_NEED_RSP           BIT(6)
> +#define MSD_CMD_IDX_MASK       0x3f
> +       unsigned int    command;
> +
> +       unsigned int    argument;
> +       unsigned int    response0;
> +       unsigned int    response1;
> +       unsigned int    response2;
> +       unsigned int    response3;
> +
> +#define MSD_RSP_CMD_APP                BIT(6)
> +#define MSD_RSP_CMD_IDX_MASK   0x3f
> +       unsigned int    response_command;
> +
> +#define MSD_DATA_EN            BIT(6)
> +#define MSD_DMA_EN             BIT(5)
> +#define MSD_DATA_WRITE         BIT(4)
> +#define MSD_BLK_SIZE_MASK      0x0f
> +       unsigned int    data_control;
> +
> +       unsigned int    data_timer;
> +
> +#define MSD_DATA_LEN_MASK      0xffffff
> +       unsigned int    data_length;
> +
> +#define MSD_WRITE_PROT         BIT(12)
> +#define MSD_CARD_DETECT                BIT(11)
> +/* 1-10 below can be sent to interrupt or clear register */
> +#define MSD_CARD_CHANGE                BIT(10)
> +#define MSD_FIFO_ORUN          BIT(9)
> +#define MSD_FIFO_URUN          BIT(8)
> +#define MSD_DATA_END           BIT(7)
> +#define MSD_CMD_SENT           BIT(6)
> +#define MSD_DATA_CRC_OK                BIT(5)
> +#define MSD_RSP_CRC_OK         BIT(4)
> +#define MSD_DATA_TIMEOUT       BIT(3)
> +#define MSD_RSP_TIMEOUT                BIT(2)
> +#define MSD_DATA_CRC_FAIL      BIT(1)
> +#define MSD_RSP_CRC_FAIL       BIT(0)
> +       unsigned int    status;
> +
> +       unsigned int    clear;
> +       unsigned int    interrupt_mask;
> +
> +#define MSD_SD_POWER_ON                BIT(4)
> +#define MSD_SD_POWER_MASK      0x0f
> +       unsigned int    power_control;
> +
> +#define MSD_CLK_DIS            BIT(8)
> +#define MSD_CLK_SD             BIT(7)
> +#define MSD_CLK_DIV_MASK       0x7f
> +       unsigned int    clock_control;
> +
> +#define MSD_WIDE_BUS_SUPPORT   BIT(3)
> +#define MSD_WIDE_BUS           BIT(2)  /* bus width is 4 bytes */
> +#define MSD_SINGLE_BUS         BIT(0)  /* bus width is 1 byte */
> +       unsigned int    bus_width;
> +
> +       unsigned int    data_window;
> +
> +#define MSD_CPRM_FUNCTION      BIT(8)
> +       unsigned int    feature;
> +
> +       unsigned int    revision;
> +};

I see you're using this to handle your register offsets. This isn't
necessarily portable, as you're relying on the size of unsigned int
being 4 bytes, and that elements aren't padded to a larger size. At the
very least unsigned int should be replaced with u32.

Thanks,
Mark.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2013-07-29 10:49   ` [PATCH v3] " Jonas Jensen
  2013-07-30  9:08     ` Mark Rutland
@ 2013-08-06 13:57     ` Jonas Jensen
  2013-12-11 12:03       ` [PATCH v5] " Jonas Jensen
  1 sibling, 1 reply; 12+ messages in thread
From: Jonas Jensen @ 2013-08-06 13:57 UTC (permalink / raw)
  To: linux-mmc
  Cc: cjb, linux-arm-kernel, linux-kernel, arm, mark.rutland, Jonas Jensen

Add SDHCI driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    Changes since v3:
    
    1. don't rely on structs for register offsets
    2. use DT probed DMA, remove extern bool moxart_filter_fn()
    3. remove unnecessary ternary operator in moxart_get_ro
    4. check return value of devm_request_irq
    
    device tree bindings document:
    5. add description of DT DMA node
    
    Applies to next-20130806

 .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  28 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/sdhci-moxart.c                    | 917 +++++++++++++++++++++
 4 files changed, 955 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
 create mode 100644 drivers/mmc/host/sdhci-moxart.c

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
new file mode 100644
index 0000000..c9ad588
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
@@ -0,0 +1,28 @@
+MOXA ART SD Host Controller Interface
+
+Required properties:
+
+- compatible :	Must be "moxa,moxart-mmc"
+- reg :		Should contain registers location and length
+- interrupts :	Should contain the interrupt number
+- clocks :	Should contain phandle for the clock feeding the SDHCI controller
+
+Optional properties:
+
+These are optional but required to enable DMA transfer mode:
+
+- dmas :	Should contain two DMA channels, line request number must be 5 for
+		both channels
+- dma-names :	Must be "tx", "rx"
+
+Example:
+
+	mmc: mmc@98e00000 {
+		compatible = "moxa,moxart-mmc";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&coreclk>;
+		dmas =  <&dma 0 5>,
+			<&dma 1 5>;
+		dma-names = "tx", "rx";
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 8a4c066..3c246390 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -271,6 +271,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_SDHCI_MOXART
+	tristate "MOXART SD Host Controller Interface support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects the MOXART SD Host Controller Interface.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index d422e21..c91317f 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
 obj-$(CONFIG_MMC_SDHCI_BCM_KONA)	+= sdhci-bcm-kona.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
+obj-$(CONFIG_MMC_SDHCI_MOXART)		+= sdhci-moxart.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sdhci-moxart.c b/drivers/mmc/host/sdhci-moxart.c
new file mode 100644
index 0000000..c8c1807
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.c
@@ -0,0 +1,917 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technology Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/of_dma.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#define MSD_CMD_REG		0
+#define MSD_ARG_REG		4
+#define MSD_RESP0_REG		8
+#define MSD_RESP1_REG		0x0c
+#define MSD_RESP2_REG		0x10
+#define MSD_RESP3_REG		0x14
+#define MSD_RESP_CMD_REG	0x18
+#define MSD_DATA_CTRL_REG	0x1c
+#define MSD_DATA_TIMER_REG	0x20
+#define MSD_DATA_LEN_REG	0x24
+#define MSD_STATUS_REG		0x28
+#define MSD_CLEAR_REG		0x2c
+#define MSD_INT_MASK_REG	0x30
+#define MSD_POWER_CTRL_REG	0x34
+#define MSD_CLOCK_CTRL_REG	0x38
+#define MSD_BUS_WIDTH_REG	0x3c
+#define MSD_DATA_WIN_REG	0x40
+#define MSD_FEATURE_REG		0x44
+#define MSD_REVISION_REG	0x48
+
+#define MMC_RSP_SHORT		1
+#define MMC_RSP_LONG		2
+#define MMC_RSP_MASK		3
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+#define REG_COMMAND		0
+#define REG_ARGUMENT		4
+#define REG_RESPONSE0		8
+#define REG_RESPONSE1		12
+#define REG_RESPONSE2		16
+#define REG_RESPONSE3		20
+#define REG_RESPONSE_COMMAND	24
+#define REG_DATA_CONTROL	28
+#define REG_DATA_TIMER		32
+#define REG_DATA_LENGTH		36
+#define REG_STATUS		40
+#define REG_CLEAR		44
+#define REG_INTERRUPT_MASK	48
+#define REG_POWER_CONTROL	52
+#define REG_CLOCK_CONTROL	56
+#define REG_BUS_WIDTH		60
+#define REG_DATA_WINDOW		64
+#define REG_FEATURE		68
+#define REG_REVISION		72
+
+/* REG_COMMAND */
+#define MSD_SDC_RST		BIT(10)
+#define MSD_CMD_EN		BIT(9)
+#define MSD_APP_CMD		BIT(8)
+#define MSD_LONG_RSP		BIT(7)
+#define MSD_NEED_RSP		BIT(6)
+#define MSD_CMD_IDX_MASK	0x3f
+
+/* REG_RESPONSE_COMMAND */
+#define MSD_RSP_CMD_APP		BIT(6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+
+/* REG_DATA_CONTROL */
+#define MSD_DATA_EN		BIT(6)
+#define MSD_DMA_EN		BIT(5)
+#define MSD_DATA_WRITE		BIT(4)
+#define MSD_BLK_SIZE_MASK	0x0f
+
+/* REG_DATA_LENGTH */
+#define MSD_DATA_LEN_MASK	0xffffff
+
+/* REG_STATUS */
+#define MSD_WRITE_PROT		BIT(12)
+#define MSD_CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to interrupt or clear register */
+#define MSD_CARD_CHANGE		BIT(10)
+#define MSD_FIFO_ORUN		BIT(9)
+#define MSD_FIFO_URUN		BIT(8)
+#define MSD_DATA_END		BIT(7)
+#define MSD_CMD_SENT		BIT(6)
+#define MSD_DATA_CRC_OK		BIT(5)
+#define MSD_RSP_CRC_OK		BIT(4)
+#define MSD_DATA_TIMEOUT	BIT(3)
+#define MSD_RSP_TIMEOUT		BIT(2)
+#define MSD_DATA_CRC_FAIL	BIT(1)
+#define MSD_RSP_CRC_FAIL	BIT(0)
+
+/* REG_POWER_CONTROL */
+#define MSD_SD_POWER_ON		BIT(4)
+#define MSD_SD_POWER_MASK	0x0f
+
+/* REG_CLOCK_CONTROL */
+#define MSD_CLK_DIS		BIT(8)
+#define MSD_CLK_SD		BIT(7)
+#define MSD_CLK_DIV_MASK	0x7f
+
+/* REG_BUS_WIDTH */
+#define MSD_WIDE_BUS_SUPPORT	BIT(3)
+#define MSD_WIDE_BUS		BIT(2)	/* bus width is 4 bytes */
+#define MSD_SINGLE_BUS		BIT(0)	/* bus width is 1 byte */
+
+/* REG_FEATURE */
+#define MSD_CPRM_FUNCTION	BIT(8)
+
+struct moxart_host {
+	spinlock_t		lock;
+	void __iomem		*base;
+	phys_addr_t		reg_phys;
+
+	struct dma_chan		*dma_chan_rx;
+	struct dma_chan		*dma_chan_tx;
+	struct dma_chan		*dma_chan_cur;
+	unsigned int		dma_direction;
+	bool			have_dma;
+	struct completion	dma_complete;
+	struct completion	pio_complete;
+
+	struct mmc_host		*mmc;
+	struct mmc_request	*mrq;
+
+	struct scatterlist	*cur_sg;
+	unsigned int		num_sg;
+	unsigned int		remain;
+	int			size;
+
+	unsigned int		wait_for;
+	unsigned int		timeout;
+	long			sysclk;
+	bool			removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16	/* 16 bytes */
+
+#define DMA_FIFO_LEN_FORCE		0
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, host->base + REG_CLEAR);
+	writel(cmd->arg, host->base + REG_ARGUMENT);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, host->base + REG_COMMAND);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, host->base +
+				       REG_CLEAR);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, host->base + REG_CLEAR);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(host->base +
+						      REG_RESPONSE0);
+					cmd->resp[2] =
+						readl(host->base +
+						      REG_RESPONSE1);
+					cmd->resp[1] =
+						readl(host->base +
+						      REG_RESPONSE2);
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE3);
+				} else {
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * this seems to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 * when this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host *host = param;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	direction_dev = (data->flags & MMC_DATA_WRITE) ?
+			DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+
+	/*
+	 * because dma_map_sg takes both sg and sg_len as arguments
+	 * (and maps the entire list of buffers) data->sg does not
+	 * have to be incremented between calls (as is required
+	 * in moxart_transfer_pio)
+	 */
+	len = dma_map_sg(host->dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		desc = dmaengine_prep_slave_sg(host->dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		pr_err("%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(host->dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* when DMA is available this is used only for SD_APP_SEND_SCR */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       host->base + REG_DATA_WINDOW);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* byte order reversed only when reading SCR */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					host->base + REG_DATA_WINDOW);
+			} else {
+				*(unsigned int *)buffer =
+					readl(host->base + REG_DATA_WINDOW);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered) {
+		moxart_next_sg(host);
+		/*
+		 * used to goto "transfer_start" label here,
+		 * there is no need, this function will be
+		 * called again from interrupt.
+		 *
+		 * compare with DMA where sg increment is
+		 * redundant thanks to dma_map_sg
+		 */
+	} else {
+		complete(&host->pio_complete);
+	}
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, host->base + REG_DATA_TIMER);
+	writel(host->size, host->base + REG_DATA_LENGTH);
+	writel(datactrl, host->base + REG_DATA_CONTROL);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, host->base + REG_CLEAR);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, host->base + REG_CLEAR);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE. instead, check
+	 * host->mrq != NULL in moxart_irq to avoid unnecessary calls
+	 * to moxart_transfer_pio
+	 * if this happens during transfer the mmc_request in
+	 * moxart_request should still be valid
+	 * (which is why host->mrq can be set NULL here)
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       host->base + REG_CLEAR);
+	writel(MSD_CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* only use DMA/PIO (and wait) when there is data */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, host->base +
+			       REG_INTERRUPT_MASK);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			host->dma_chan_cur = (mrq->cmd->data->flags
+					     & MMC_DATA_WRITE) ?
+					     host->dma_chan_tx :
+					     host->dma_chan_rx;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_cur->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       host->base + REG_INTERRUPT_MASK);
+
+			status = readl(host->base + REG_STATUS);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer started from interrupt */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		/* removed during transfer? (interrupts were just enabled..) */
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(host->base + REG_STATUS);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       host->base + REG_CLEAR);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, host->base + REG_CLOCK_CONTROL);
+	} else if (!(readl(host->base + REG_CLOCK_CONTROL) & MSD_CLK_DIS)) {
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(host->base + REG_POWER_CONTROL) & ~MSD_SD_POWER_ON,
+		       host->base + REG_POWER_CONTROL);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       host->base + REG_POWER_CONTROL);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, host->base + REG_BUS_WIDTH);
+	else
+		writel(MSD_WIDE_BUS, host->base + REG_BUS_WIDTH);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+
+	return readl(host->base + REG_STATUS) & MSD_WRITE_PROT;
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc)) {
+		dev_err(dev, "%s: devm_ioremap_resource res_mmc failed\n",
+			__func__);
+		return PTR_ERR(reg_mmc);
+	}
+
+	mmc->ops = &moxart_ops;
+
+	/*
+	 * hardware does not support MMC_CAP_SD_HIGHSPEED
+	 * CMD6 will timeout and make things not work
+	 */
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* support 2.0v - 3.6v power */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->base = reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* disable all interrupt */
+	writel(0, host->base + REG_INTERRUPT_MASK);
+
+	/* reset chip */
+	writel(MSD_SDC_RST, host->base + REG_COMMAND);
+
+	/* wait for reset finished */
+	while (readl(host->base + REG_COMMAND) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
+	host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		dev_dbg(dev, "%s: using PIO mode transfer\n", __func__);
+
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+			__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.dst_addr = 0;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+
+		/*
+		 * there seems to be a max size on transfers so
+		 * set max_blk_count low for both DMA and PIO
+		 *
+		 * sending large chunks result either in timeout
+		 * or render the MMC controller unresponsive
+		 * (status register 0 on consecutive read retries,
+		 * also see comments in moxart_send_command)
+		 *
+		 * obviously, DMA is quicker and can handle
+		 * larger chunks but setting it higher than 16
+		 * can still bug the controller
+		 */
+		mmc->max_blk_count = 16;
+	}
+
+	ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, host->base + REG_INTERRUPT_MASK);
+		writel(0, host->base + REG_POWER_CONTROL);
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_mmc_match[] = {
+	{ .compatible = "moxa,moxart-mmc" },
+	{ }
+};
+
+static struct platform_driver moxart_mmc_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "sdhci-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_mmc_match,
+	},
+};
+module_platform_driver(moxart_mmc_driver);
+
+MODULE_ALIAS("platform:sdhci-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v5] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2013-08-06 13:57     ` [PATCH v4] " Jonas Jensen
@ 2013-12-11 12:03       ` Jonas Jensen
  2014-01-17  9:43         ` [PATCH v6] " Jonas Jensen
  0 siblings, 1 reply; 12+ messages in thread
From: Jonas Jensen @ 2013-12-11 12:03 UTC (permalink / raw)
  To: linux-mmc
  Cc: cjb, linux-arm-kernel, linux-kernel, arm, mark.rutland, Jonas Jensen

Add SDHCI driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    Changes since v4:
    
    1.  remove dma_chan_cur pointer from host structure
    2.  add local dma_chan_cur pointer in moxart_transfer_dma()
    3.  add dma_async_tx_descriptor pointer (host structure)
    4.  save descriptor pointers between transfers
    5.  use [3] to check for DMA transfer error (with dmaengine_tx_status())
    6.  use !!(condition) construct in moxart_get_ro()
    7.  set both source/destination DMA transfer width early in probe,
        this fixes the problem that dst_addr_width is left uninitialized
        for dma_chan_tx
    
        both "src_addr_width" and "dst_addr_width" (dma_slave_config)
        are now assigned DMA_SLAVE_BUSWIDTH_4_BYTES
        (before configuring channels with dmaengine_slave_config())
    
    8.  remove redundant dev_err() call in moxart_probe(),
        devm_ioremap_resource() prints a similar message
    9.  use dev_err(), not pr_err() in moxart_transfer_dma()
    10. bail on irq_of_parse_and_map() failure
    11. remove unused host variable "wait_for"
    12. change driver name and compatible strings "mmc" to "sdhci"
    
    Applies to next-20131211

 .../devicetree/bindings/mmc/moxa,moxart-sdhci.txt  |  28 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/sdhci-moxart.c                    | 929 +++++++++++++++++++++
 4 files changed, 967 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
 create mode 100644 drivers/mmc/host/sdhci-moxart.c

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
new file mode 100644
index 0000000..020b13e
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
@@ -0,0 +1,28 @@
+MOXA ART SD Host Controller Interface
+
+Required properties:
+
+- compatible :	Must be "moxa,moxart-sdhci"
+- reg :		Should contain registers location and length
+- interrupts :	Should contain the interrupt number
+- clocks :	Should contain phandle for the clock feeding the SDHCI controller
+
+Optional properties:
+
+These are optional but required to enable DMA transfer mode:
+
+- dmas :	Should contain two DMA channels, line request number must be 5 for
+		both channels
+- dma-names :	Must be "tx", "rx"
+
+Example:
+
+	sdhci: sdhci@98e00000 {
+		compatible = "moxa,moxart-sdhci";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&coreclk>;
+		dmas =  <&dma 0 5>,
+			<&dma 1 5>;
+		dma-names = "tx", "rx";
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 7fc5099..3421424 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -271,6 +271,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_SDHCI_MOXART
+	tristate "MOXART SD Host Controller Interface support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects the MOXART SD Host Controller Interface.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index c41d0c3..7f6ebc3 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
 obj-$(CONFIG_MMC_SDHCI_BCM_KONA)	+= sdhci-bcm-kona.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
+obj-$(CONFIG_MMC_SDHCI_MOXART)		+= sdhci-moxart.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sdhci-moxart.c b/drivers/mmc/host/sdhci-moxart.c
new file mode 100644
index 0000000..5fb854e
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.c
@@ -0,0 +1,929 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technologies Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/of_dma.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#define MSD_CMD_REG		0
+#define MSD_ARG_REG		4
+#define MSD_RESP0_REG		8
+#define MSD_RESP1_REG		0x0c
+#define MSD_RESP2_REG		0x10
+#define MSD_RESP3_REG		0x14
+#define MSD_RESP_CMD_REG	0x18
+#define MSD_DATA_CTRL_REG	0x1c
+#define MSD_DATA_TIMER_REG	0x20
+#define MSD_DATA_LEN_REG	0x24
+#define MSD_STATUS_REG		0x28
+#define MSD_CLEAR_REG		0x2c
+#define MSD_INT_MASK_REG	0x30
+#define MSD_POWER_CTRL_REG	0x34
+#define MSD_CLOCK_CTRL_REG	0x38
+#define MSD_BUS_WIDTH_REG	0x3c
+#define MSD_DATA_WIN_REG	0x40
+#define MSD_FEATURE_REG		0x44
+#define MSD_REVISION_REG	0x48
+
+#define MMC_RSP_SHORT		1
+#define MMC_RSP_LONG		2
+#define MMC_RSP_MASK		3
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+#define REG_COMMAND		0
+#define REG_ARGUMENT		4
+#define REG_RESPONSE0		8
+#define REG_RESPONSE1		12
+#define REG_RESPONSE2		16
+#define REG_RESPONSE3		20
+#define REG_RESPONSE_COMMAND	24
+#define REG_DATA_CONTROL	28
+#define REG_DATA_TIMER		32
+#define REG_DATA_LENGTH		36
+#define REG_STATUS		40
+#define REG_CLEAR		44
+#define REG_INTERRUPT_MASK	48
+#define REG_POWER_CONTROL	52
+#define REG_CLOCK_CONTROL	56
+#define REG_BUS_WIDTH		60
+#define REG_DATA_WINDOW		64
+#define REG_FEATURE		68
+#define REG_REVISION		72
+
+/* REG_COMMAND */
+#define MSD_SDC_RST		BIT(10)
+#define MSD_CMD_EN		BIT(9)
+#define MSD_APP_CMD		BIT(8)
+#define MSD_LONG_RSP		BIT(7)
+#define MSD_NEED_RSP		BIT(6)
+#define MSD_CMD_IDX_MASK	0x3f
+
+/* REG_RESPONSE_COMMAND */
+#define MSD_RSP_CMD_APP		BIT(6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+
+/* REG_DATA_CONTROL */
+#define MSD_DATA_EN		BIT(6)
+#define MSD_DMA_EN		BIT(5)
+#define MSD_DATA_WRITE		BIT(4)
+#define MSD_BLK_SIZE_MASK	0x0f
+
+/* REG_DATA_LENGTH */
+#define MSD_DATA_LEN_MASK	0xffffff
+
+/* REG_STATUS */
+#define MSD_WRITE_PROT		BIT(12)
+#define MSD_CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to interrupt or clear register */
+#define MSD_CARD_CHANGE		BIT(10)
+#define MSD_FIFO_ORUN		BIT(9)
+#define MSD_FIFO_URUN		BIT(8)
+#define MSD_DATA_END		BIT(7)
+#define MSD_CMD_SENT		BIT(6)
+#define MSD_DATA_CRC_OK		BIT(5)
+#define MSD_RSP_CRC_OK		BIT(4)
+#define MSD_DATA_TIMEOUT	BIT(3)
+#define MSD_RSP_TIMEOUT		BIT(2)
+#define MSD_DATA_CRC_FAIL	BIT(1)
+#define MSD_RSP_CRC_FAIL	BIT(0)
+
+/* REG_POWER_CONTROL */
+#define MSD_SD_POWER_ON		BIT(4)
+#define MSD_SD_POWER_MASK	0x0f
+
+/* REG_CLOCK_CONTROL */
+#define MSD_CLK_DIS		BIT(8)
+#define MSD_CLK_SD		BIT(7)
+#define MSD_CLK_DIV_MASK	0x7f
+
+/* REG_BUS_WIDTH */
+#define MSD_WIDE_BUS_SUPPORT	BIT(3)
+#define MSD_WIDE_BUS		BIT(2)	/* bus width is 4 bytes */
+#define MSD_SINGLE_BUS		BIT(0)	/* bus width is 1 byte */
+
+/* REG_FEATURE */
+#define MSD_CPRM_FUNCTION	BIT(8)
+
+struct moxart_host {
+	spinlock_t			lock;
+	void __iomem			*base;
+	phys_addr_t			reg_phys;
+
+	struct dma_chan			*dma_chan_rx;
+	struct dma_chan			*dma_chan_tx;
+	struct dma_async_tx_descriptor	*tx_desc;
+	unsigned int			dma_direction;
+	bool				have_dma;
+	struct completion		dma_complete;
+	struct completion		pio_complete;
+
+	struct mmc_host			*mmc;
+	struct mmc_request		*mrq;
+
+	struct scatterlist		*cur_sg;
+	unsigned int			num_sg;
+	unsigned int			remain;
+	int				size;
+
+	unsigned int			timeout;
+	long				sysclk;
+	bool				removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16	/* 16 bytes */
+
+#define DMA_FIFO_LEN_FORCE		0
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, host->base + REG_CLEAR);
+	writel(cmd->arg, host->base + REG_ARGUMENT);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, host->base + REG_COMMAND);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, host->base +
+				       REG_CLEAR);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, host->base + REG_CLEAR);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(host->base +
+						      REG_RESPONSE0);
+					cmd->resp[2] =
+						readl(host->base +
+						      REG_RESPONSE1);
+					cmd->resp[1] =
+						readl(host->base +
+						      REG_RESPONSE2);
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE3);
+				} else {
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * this seems to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 * when this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host		*host = param;
+	struct dma_chan			*chan = host->tx_desc->chan;
+	struct dma_tx_state		txs;
+
+	if (dmaengine_tx_status(chan, chan->cookie, &txs) == DMA_ERROR)
+		dev_err_ratelimited(mmc_dev(host->mmc), "%s: DMA error\n",
+				    __func__);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+	struct dma_chan *dma_chan_cur;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		direction_dev = DMA_MEM_TO_DEV;
+		dma_chan_cur = host->dma_chan_tx;
+	} else {
+		direction_dev = DMA_DEV_TO_MEM;
+		dma_chan_cur = host->dma_chan_rx;
+	}
+
+	/*
+	 * because dma_map_sg takes both sg and sg_len as arguments
+	 * (and maps the entire list of buffers) data->sg does not
+	 * have to be incremented between calls (as is required
+	 * in moxart_transfer_pio)
+	 */
+	len = dma_map_sg(dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		desc = dmaengine_prep_slave_sg(dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		dev_err(mmc_dev(host->mmc),
+			"%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		host->tx_desc = desc;
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* when DMA is available this is used only for SD_APP_SEND_SCR */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       host->base + REG_DATA_WINDOW);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* byte order reversed only when reading SCR */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					host->base + REG_DATA_WINDOW);
+			} else {
+				*(unsigned int *)buffer =
+					readl(host->base + REG_DATA_WINDOW);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered) {
+		moxart_next_sg(host);
+		/*
+		 * used to goto "transfer_start" label here,
+		 * there is no need, this function will be
+		 * called again from interrupt.
+		 *
+		 * compare with DMA where sg increment is
+		 * redundant thanks to dma_map_sg
+		 */
+	} else {
+		complete(&host->pio_complete);
+	}
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, host->base + REG_DATA_TIMER);
+	writel(host->size, host->base + REG_DATA_LENGTH);
+	writel(datactrl, host->base + REG_DATA_CONTROL);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, host->base + REG_CLEAR);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, host->base + REG_CLEAR);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE. instead, check
+	 * host->mrq != NULL in moxart_irq to avoid unnecessary calls
+	 * to moxart_transfer_pio
+	 * if this happens during transfer the mmc_request in
+	 * moxart_request should still be valid
+	 * (which is why host->mrq can be set NULL here)
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       host->base + REG_CLEAR);
+	writel(MSD_CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* only use DMA/PIO (and wait) when there is data */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, host->base +
+			       REG_INTERRUPT_MASK);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_tx->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       host->base + REG_INTERRUPT_MASK);
+
+			status = readl(host->base + REG_STATUS);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer started from interrupt */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		/* removed during transfer? (interrupts were just enabled..) */
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(host->base + REG_STATUS);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       host->base + REG_CLEAR);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, host->base + REG_CLOCK_CONTROL);
+	} else if (!(readl(host->base + REG_CLOCK_CONTROL) & MSD_CLK_DIS)) {
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(host->base + REG_POWER_CONTROL) & ~MSD_SD_POWER_ON,
+		       host->base + REG_POWER_CONTROL);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       host->base + REG_POWER_CONTROL);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, host->base + REG_BUS_WIDTH);
+	else
+		writel(MSD_WIDE_BUS, host->base + REG_BUS_WIDTH);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+
+	return !!(readl(host->base + REG_STATUS) & MSD_WRITE_PROT);
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+	if (irq <= 0) {
+		dev_err(dev, "irq_of_parse_and_map failed\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc))
+		return PTR_ERR(reg_mmc);
+
+	mmc->ops = &moxart_ops;
+
+	/*
+	 * hardware does not support MMC_CAP_SD_HIGHSPEED
+	 * CMD6 will timeout and make things not work
+	 */
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* support 2.0v - 3.6v power */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->base = reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* disable all interrupt */
+	writel(0, host->base + REG_INTERRUPT_MASK);
+
+	/* reset chip */
+	writel(MSD_SDC_RST, host->base + REG_COMMAND);
+
+	/* wait for reset finished */
+	while (readl(host->base + REG_COMMAND) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
+	host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		dev_dbg(dev, "%s: using PIO mode transfer\n", __func__);
+
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+			__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.dst_addr = 0;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+
+		/*
+		 * there seems to be a max size on transfers so
+		 * set max_blk_count low for both DMA and PIO
+		 *
+		 * sending large chunks result either in timeout
+		 * or render the MMC controller unresponsive
+		 * (status register 0 on consecutive read retries,
+		 * also see comments in moxart_send_command)
+		 *
+		 * obviously, DMA is quicker and can handle
+		 * larger chunks but setting it higher than 16
+		 * can still bug the controller
+		 */
+		mmc->max_blk_count = 16;
+	}
+
+	ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, host->base + REG_INTERRUPT_MASK);
+		writel(0, host->base + REG_POWER_CONTROL);
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_sdhci_match[] = {
+	{ .compatible = "moxa,moxart-sdhci" },
+	{ }
+};
+
+static struct platform_driver moxart_sdhci_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "sdhci-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_sdhci_match,
+	},
+};
+module_platform_driver(moxart_sdhci_driver);
+
+MODULE_ALIAS("platform:sdhci-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v6] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2013-12-11 12:03       ` [PATCH v5] " Jonas Jensen
@ 2014-01-17  9:43         ` Jonas Jensen
  2014-01-17 14:40           ` Arnd Bergmann
  2014-01-21 10:52           ` [PATCH v7] mmc: moxart: Add MOXA ART SD/MMC driver Jonas Jensen
  0 siblings, 2 replies; 12+ messages in thread
From: Jonas Jensen @ 2014-01-17  9:43 UTC (permalink / raw)
  To: chris
  Cc: linux-mmc, cjb, devicetree, linux-arm-kernel, linux-kernel, arm,
	mark.rutland, Jonas Jensen

Add SDHCI driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    Changes in v6 fixes a kernel panic in moxart_dma_complete().
    
    Panic only happens with CONFIG_SLAB or CONFIG_SLOB, the same code
    works for CONFIG_SLUB, and happens because a cookie belonging
    to the channel is passed to dmaengine_tx_status(), e.g.
    "host->tx_desc->chan->cookie".
    
    Panic log:
    [    4.130000] mmc0: new SD card at address e624
    [    4.170000] nf_conntrack version 0.5.0 (357 buckets, 1428 max)
    [    4.190000] ip_tables: (C) 2000-2006 Netfilter Core Team
    [    4.190000] TCP: cubic registered
    [    4.200000] NET: Registered protocol family 10
    [    4.240000] sit: IPv6 over IPv4 tunneling driver
    [    4.270000] mmcblk0: mmc0:e624 SD02G 1.84 GiB
    [    4.320000]  mmcblk0: p1
    [    4.370000] NET: Registered protocol family 17
    [    4.440000] console [netcon0] enabled
    [    4.440000] netconsole: network logging started
    [    4.450000] moxart-rtc rtc.0: setting system clock to 2014-01-16 13:39:54 UTC (1389879594)
    [    4.550000] kjournald starting.  Commit interval 5 seconds
    [    4.550000] EXT3-fs (mmcblk0p1): warning: maximal mount count reached, running e2fsck is recommended
    [    4.570000] EXT3-fs (mmcblk0p1): using internal journal
    [    4.580000] EXT3-fs (mmcblk0p1): mounted filesystem with ordered data mode
    [    4.590000] VFS: Mounted root (ext3 filesystem) on device 179:1.
    [    4.610000] devtmpfs: mounted
    [    4.630000] Freeing unused kernel memory: 156K (c0339000 - c0360000)
    [    4.640000] Unable to handle kernel paging request at virtual address aaaaaaaa
    [    4.640000] pgd = c0004000
    [    4.640000] [aaaaaaaa] *pgd=00000000
    [    4.640000] Internal error: Oops: 1 [#1] ARM
    [    4.640000] CPU: 0 PID: 0 Comm: swapper Not tainted 3.13.0-rc8-next-20140115+ #1577
    [    4.640000] task: c03676a8 ti: c0362000 task.ti: c0362000
    [    4.640000] PC is at moxart_dma_complete+0x14/0x68
    [    4.640000] LR is at vchan_complete+0xcc/0xf4
    [    4.640000] pc : [<c01a6724>]    lr : [<c0154a28>]    psr: a0000013
    [    4.640000] sp : c0363e98  ip : c037ffcc  fp : c0380f00
    [    4.640000] r10: c1a867e0  r9 : c0380f38  r8 : c0363eb0
    [    4.640000] r7 : 00100100  r6 : 00200200  r5 : c01a6710  r4 : c1a867e0
    [    4.640000] r3 : c1ac0030  r2 : c0363e9c  r1 : c03676a8  r0 : aaaaaaaa
    [    4.640000] Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment kernel
    [    4.640000] Control: 0000397f  Table: 00004000  DAC: 00000017
    [    4.640000] Process swapper (pid: 0, stack limit = 0xc03621c0)
    [    4.640000] Stack: (0xc0363e98 to 0xc0364000)
    [    4.640000] 3e80:                                                       c1a867e0 c03676a8
    [    4.640000] 3ea0: 00000000 c03676a8 c1871158 c0154a28 c0363eb0 c0363eb0 60000013 c1871184
    [    4.640000] 3ec0: c03694d4 00000000 00000000 00000006 00000100 c0018fc4 c036bc60 00000007
    [    4.640000] 3ee0: 00000040 c0362000 c0380f20 c0018950 c185b894 c185b894 c185b840 00000006
    [    4.640000] 3f00: 0000000a 00200000 00000001 ffff8ca1 00000000 00000011 00000000 c0934d40
    [    4.640000] 3f20: 00000001 c0363f68 66015261 c0352624 00000000 c0018d38 00000011 c0009a14
    [    4.640000] 3f40: c18020e4 01000000 00000018 c0008538 c0009b94 20000013 ffffffff c0363f9c
    [    4.640000] 3f60: c0998200 c000bfb8 00000001 c03676a8 00000000 20000013 c0362000 c0364020
    [    4.640000] 3f80: c0364110 ffffffff c0998200 66015261 c0352624 00000000 f6aa4ecf c0363fb0
    [    4.640000] 3fa0: c003da20 c0009b94 20000013 ffffffff c03676a8 c0047548 c03676a8 c0339988
    [    4.640000] 3fc0: ffffffff ffffffff c03394d4 00000000 00000000 c0352624 00000000 0000397d
    [    4.640000] 3fe0: c03640a4 c0352620 c03689ec 00004000 003517b4 00008040 00000000 00000000
    [    4.640000] [<c01a6724>] (moxart_dma_complete) from [<c0154a28>] (vchan_complete+0xcc/0xf4)
    [    4.640000] [<c0154a28>] (vchan_complete) from [<c0018fc4>] (tasklet_action+0x84/0xd4)
    [    4.640000] [<c0018fc4>] (tasklet_action) from [<c0018950>] (__do_softirq+0x170/0x2a4)
    [    4.640000] [<c0018950>] (__do_softirq) from [<c0018d38>] (irq_exit+0x80/0x100)
    [    4.640000] [<c0018d38>] (irq_exit) from [<c0009a14>] (handle_IRQ+0x60/0x80)
    [    4.640000] [<c0009a14>] (handle_IRQ) from [<c0008538>] (handle_irq+0x88/0x9c)
    [    4.640000] [<c0008538>] (handle_irq) from [<c000bfb8>] (__irq_svc+0x38/0x48)
    [    4.640000] Exception stack(0xc0363f68 to 0xc0363fb0)
    [    4.640000] 3f60:                   00000001 c03676a8 00000000 20000013 c0362000 c0364020
    [    4.640000] 3f80: c0364110 ffffffff c0998200 66015261 c0352624 00000000 f6aa4ecf c0363fb0
    [    4.640000] 3fa0: c003da20 c0009b94 20000013 ffffffff
    [    4.640000] [<c000bfb8>] (__irq_svc) from [<c0009b94>] (arch_cpu_idle+0x3c/0x48)
    [    4.640000] [<c0009b94>] (arch_cpu_idle) from [<c0047548>] (cpu_startup_entry+0x80/0xf0)
    [    4.640000] [<c0047548>] (cpu_startup_entry) from [<c0339988>] (start_kernel+0x298/0x2e0)
    [    4.640000] Code: e5903030 e1a04000 e593000c e28d2004 (e5903000)
    [    4.950000] ---[ end trace e911ec2d43c1d3c3 ]---
    [    4.960000] Kernel panic - not syncing: Fatal exception in interrupt
    
    Changes since v5:
    
    1. add local dma_cookie_t to host struct
    2. store dmaengine_submit() return value to [1]
    3. use [1] when calling dmaengine_tx_status()
    
    Applies to next-20140117

 .../devicetree/bindings/mmc/moxa,moxart-sdhci.txt  |  28 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/sdhci-moxart.c                    | 931 +++++++++++++++++++++
 4 files changed, 969 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
 create mode 100644 drivers/mmc/host/sdhci-moxart.c

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
new file mode 100644
index 0000000..020b13e
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
@@ -0,0 +1,28 @@
+MOXA ART SD Host Controller Interface
+
+Required properties:
+
+- compatible :	Must be "moxa,moxart-sdhci"
+- reg :		Should contain registers location and length
+- interrupts :	Should contain the interrupt number
+- clocks :	Should contain phandle for the clock feeding the SDHCI controller
+
+Optional properties:
+
+These are optional but required to enable DMA transfer mode:
+
+- dmas :	Should contain two DMA channels, line request number must be 5 for
+		both channels
+- dma-names :	Must be "tx", "rx"
+
+Example:
+
+	sdhci: sdhci@98e00000 {
+		compatible = "moxa,moxart-sdhci";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&coreclk>;
+		dmas =  <&dma 0 5>,
+			<&dma 1 5>;
+		dma-names = "tx", "rx";
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 1384f67..e972190 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -283,6 +283,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_SDHCI_MOXART
+	tristate "MOXART SD Host Controller Interface support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects the MOXART SD Host Controller Interface.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 3483b6b..39e2554 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
 obj-$(CONFIG_MMC_SDHCI_BCM_KONA)	+= sdhci-bcm-kona.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
+obj-$(CONFIG_MMC_SDHCI_MOXART)		+= sdhci-moxart.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sdhci-moxart.c b/drivers/mmc/host/sdhci-moxart.c
new file mode 100644
index 0000000..f0bf3e0
--- /dev/null
+++ b/drivers/mmc/host/sdhci-moxart.c
@@ -0,0 +1,931 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technologies Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/of_dma.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#define MSD_CMD_REG		0
+#define MSD_ARG_REG		4
+#define MSD_RESP0_REG		8
+#define MSD_RESP1_REG		0x0c
+#define MSD_RESP2_REG		0x10
+#define MSD_RESP3_REG		0x14
+#define MSD_RESP_CMD_REG	0x18
+#define MSD_DATA_CTRL_REG	0x1c
+#define MSD_DATA_TIMER_REG	0x20
+#define MSD_DATA_LEN_REG	0x24
+#define MSD_STATUS_REG		0x28
+#define MSD_CLEAR_REG		0x2c
+#define MSD_INT_MASK_REG	0x30
+#define MSD_POWER_CTRL_REG	0x34
+#define MSD_CLOCK_CTRL_REG	0x38
+#define MSD_BUS_WIDTH_REG	0x3c
+#define MSD_DATA_WIN_REG	0x40
+#define MSD_FEATURE_REG		0x44
+#define MSD_REVISION_REG	0x48
+
+#define MMC_RSP_SHORT		1
+#define MMC_RSP_LONG		2
+#define MMC_RSP_MASK		3
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+#define REG_COMMAND		0
+#define REG_ARGUMENT		4
+#define REG_RESPONSE0		8
+#define REG_RESPONSE1		12
+#define REG_RESPONSE2		16
+#define REG_RESPONSE3		20
+#define REG_RESPONSE_COMMAND	24
+#define REG_DATA_CONTROL	28
+#define REG_DATA_TIMER		32
+#define REG_DATA_LENGTH		36
+#define REG_STATUS		40
+#define REG_CLEAR		44
+#define REG_INTERRUPT_MASK	48
+#define REG_POWER_CONTROL	52
+#define REG_CLOCK_CONTROL	56
+#define REG_BUS_WIDTH		60
+#define REG_DATA_WINDOW		64
+#define REG_FEATURE		68
+#define REG_REVISION		72
+
+/* REG_COMMAND */
+#define MSD_SDC_RST		BIT(10)
+#define MSD_CMD_EN		BIT(9)
+#define MSD_APP_CMD		BIT(8)
+#define MSD_LONG_RSP		BIT(7)
+#define MSD_NEED_RSP		BIT(6)
+#define MSD_CMD_IDX_MASK	0x3f
+
+/* REG_RESPONSE_COMMAND */
+#define MSD_RSP_CMD_APP		BIT(6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+
+/* REG_DATA_CONTROL */
+#define MSD_DATA_EN		BIT(6)
+#define MSD_DMA_EN		BIT(5)
+#define MSD_DATA_WRITE		BIT(4)
+#define MSD_BLK_SIZE_MASK	0x0f
+
+/* REG_DATA_LENGTH */
+#define MSD_DATA_LEN_MASK	0xffffff
+
+/* REG_STATUS */
+#define MSD_WRITE_PROT		BIT(12)
+#define MSD_CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to interrupt or clear register */
+#define MSD_CARD_CHANGE		BIT(10)
+#define MSD_FIFO_ORUN		BIT(9)
+#define MSD_FIFO_URUN		BIT(8)
+#define MSD_DATA_END		BIT(7)
+#define MSD_CMD_SENT		BIT(6)
+#define MSD_DATA_CRC_OK		BIT(5)
+#define MSD_RSP_CRC_OK		BIT(4)
+#define MSD_DATA_TIMEOUT	BIT(3)
+#define MSD_RSP_TIMEOUT		BIT(2)
+#define MSD_DATA_CRC_FAIL	BIT(1)
+#define MSD_RSP_CRC_FAIL	BIT(0)
+
+/* REG_POWER_CONTROL */
+#define MSD_SD_POWER_ON		BIT(4)
+#define MSD_SD_POWER_MASK	0x0f
+
+/* REG_CLOCK_CONTROL */
+#define MSD_CLK_DIS		BIT(8)
+#define MSD_CLK_SD		BIT(7)
+#define MSD_CLK_DIV_MASK	0x7f
+
+/* REG_BUS_WIDTH */
+#define MSD_WIDE_BUS_SUPPORT	BIT(3)
+#define MSD_WIDE_BUS		BIT(2)	/* bus width is 4 bytes */
+#define MSD_SINGLE_BUS		BIT(0)	/* bus width is 1 byte */
+
+/* REG_FEATURE */
+#define MSD_CPRM_FUNCTION	BIT(8)
+
+struct moxart_host {
+	spinlock_t			lock;
+	void __iomem			*base;
+	phys_addr_t			reg_phys;
+
+	struct dma_chan			*dma_chan_rx;
+	struct dma_chan			*dma_chan_tx;
+	struct dma_async_tx_descriptor	*tx_desc;
+	dma_cookie_t			cur_dma_cookie;
+	unsigned int			dma_direction;
+	bool				have_dma;
+	struct completion		dma_complete;
+	struct completion		pio_complete;
+
+	struct mmc_host			*mmc;
+	struct mmc_request		*mrq;
+
+	struct scatterlist		*cur_sg;
+	unsigned int			num_sg;
+	unsigned int			remain;
+	int				size;
+
+	unsigned int			timeout;
+	long				sysclk;
+	bool				removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16	/* 16 bytes */
+
+#define DMA_FIFO_LEN_FORCE		0
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, host->base + REG_CLEAR);
+	writel(cmd->arg, host->base + REG_ARGUMENT);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, host->base + REG_COMMAND);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, host->base +
+				       REG_CLEAR);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, host->base + REG_CLEAR);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(host->base +
+						      REG_RESPONSE0);
+					cmd->resp[2] =
+						readl(host->base +
+						      REG_RESPONSE1);
+					cmd->resp[1] =
+						readl(host->base +
+						      REG_RESPONSE2);
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE3);
+				} else {
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * this seems to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 * when this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host		*host = param;
+	struct dma_chan			*chan = host->tx_desc->chan;
+	struct dma_tx_state		txs;
+
+	if (dmaengine_tx_status(chan, host->cur_dma_cookie, &txs) == DMA_ERROR)
+		dev_err_ratelimited(mmc_dev(host->mmc), "%s: DMA error\n",
+				    __func__);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+	struct dma_chan *dma_chan_cur;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		direction_dev = DMA_MEM_TO_DEV;
+		dma_chan_cur = host->dma_chan_tx;
+	} else {
+		direction_dev = DMA_DEV_TO_MEM;
+		dma_chan_cur = host->dma_chan_rx;
+	}
+
+	/*
+	 * because dma_map_sg takes both sg and sg_len as arguments
+	 * (and maps the entire list of buffers) data->sg does not
+	 * have to be incremented between calls (as is required
+	 * in moxart_transfer_pio)
+	 */
+	len = dma_map_sg(dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		desc = dmaengine_prep_slave_sg(dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		dev_err(mmc_dev(host->mmc),
+			"%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		host->tx_desc = desc;
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		host->cur_dma_cookie = dmaengine_submit(desc);
+		dma_async_issue_pending(dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* when DMA is available this is used only for SD_APP_SEND_SCR */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       host->base + REG_DATA_WINDOW);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* byte order reversed only when reading SCR */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					host->base + REG_DATA_WINDOW);
+			} else {
+				*(unsigned int *)buffer =
+					readl(host->base + REG_DATA_WINDOW);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered) {
+		moxart_next_sg(host);
+		/*
+		 * used to goto "transfer_start" label here,
+		 * there is no need, this function will be
+		 * called again from interrupt.
+		 *
+		 * compare with DMA where sg increment is
+		 * redundant thanks to dma_map_sg
+		 */
+	} else {
+		complete(&host->pio_complete);
+	}
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, host->base + REG_DATA_TIMER);
+	writel(host->size, host->base + REG_DATA_LENGTH);
+	writel(datactrl, host->base + REG_DATA_CONTROL);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, host->base + REG_CLEAR);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, host->base + REG_CLEAR);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE. instead, check
+	 * host->mrq != NULL in moxart_irq to avoid unnecessary calls
+	 * to moxart_transfer_pio
+	 * if this happens during transfer the mmc_request in
+	 * moxart_request should still be valid
+	 * (which is why host->mrq can be set NULL here)
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       host->base + REG_CLEAR);
+	writel(MSD_CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* only use DMA/PIO (and wait) when there is data */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, host->base +
+			       REG_INTERRUPT_MASK);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_tx->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       host->base + REG_INTERRUPT_MASK);
+
+			status = readl(host->base + REG_STATUS);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer started from interrupt */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		/* removed during transfer? (interrupts were just enabled..) */
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(host->base + REG_STATUS);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       host->base + REG_CLEAR);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, host->base + REG_CLOCK_CONTROL);
+	} else if (!(readl(host->base + REG_CLOCK_CONTROL) & MSD_CLK_DIS)) {
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(host->base + REG_POWER_CONTROL) & ~MSD_SD_POWER_ON,
+		       host->base + REG_POWER_CONTROL);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       host->base + REG_POWER_CONTROL);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, host->base + REG_BUS_WIDTH);
+	else
+		writel(MSD_WIDE_BUS, host->base + REG_BUS_WIDTH);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+
+	return !!(readl(host->base + REG_STATUS) & MSD_WRITE_PROT);
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+	if (irq <= 0) {
+		dev_err(dev, "irq_of_parse_and_map failed\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc))
+		return PTR_ERR(reg_mmc);
+
+	mmc->ops = &moxart_ops;
+
+	/*
+	 * hardware does not support MMC_CAP_SD_HIGHSPEED
+	 * CMD6 will timeout and make things not work
+	 */
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* support 2.0v - 3.6v power */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->base = reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* disable all interrupt */
+	writel(0, host->base + REG_INTERRUPT_MASK);
+
+	/* reset chip */
+	writel(MSD_SDC_RST, host->base + REG_COMMAND);
+
+	/* wait for reset finished */
+	while (readl(host->base + REG_COMMAND) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
+	host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		dev_dbg(dev, "%s: using PIO mode transfer\n", __func__);
+
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+			__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + MSD_DATA_WIN_REG;
+		cfg.dst_addr = 0;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+
+		/*
+		 * there seems to be a max size on transfers so
+		 * set max_blk_count low for both DMA and PIO
+		 *
+		 * sending large chunks result either in timeout
+		 * or render the MMC controller unresponsive
+		 * (status register 0 on consecutive read retries,
+		 * also see comments in moxart_send_command)
+		 *
+		 * obviously, DMA is quicker and can handle
+		 * larger chunks but setting it higher than 16
+		 * can still bug the controller
+		 */
+		mmc->max_blk_count = 16;
+	}
+
+	ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, host->base + REG_INTERRUPT_MASK);
+		writel(0, host->base + REG_POWER_CONTROL);
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_sdhci_match[] = {
+	{ .compatible = "moxa,moxart-sdhci" },
+	{ }
+};
+
+static struct platform_driver moxart_sdhci_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "sdhci-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_sdhci_match,
+	},
+};
+module_platform_driver(moxart_sdhci_driver);
+
+MODULE_ALIAS("platform:sdhci-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v6] mmc: sdhci-moxart: Add MOXA ART SDHCI driver
  2014-01-17  9:43         ` [PATCH v6] " Jonas Jensen
@ 2014-01-17 14:40           ` Arnd Bergmann
  2014-01-21 10:52           ` [PATCH v7] mmc: moxart: Add MOXA ART SD/MMC driver Jonas Jensen
  1 sibling, 0 replies; 12+ messages in thread
From: Arnd Bergmann @ 2014-01-17 14:40 UTC (permalink / raw)
  To: Jonas Jensen
  Cc: chris, linux-mmc, cjb, devicetree, linux-arm-kernel,
	linux-kernel, arm, mark.rutland

On Friday 17 January 2014, Jonas Jensen wrote:
> Add SDHCI driver for MOXA ART SoCs.
> 
> Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>

I think this should be renamed to something other than SDHCI, since that
implies a specific register layout and would use the sdhci.c driver.

Maybe moxart-mmc?

> diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
> new file mode 100644
> index 0000000..020b13e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-sdhci.txt
> @@ -0,0 +1,28 @@
> +MOXA ART SD Host Controller Interface
> +
> +Required properties:
> +
> +- compatible :	Must be "moxa,moxart-sdhci"
> +- reg :		Should contain registers location and length
> +- interrupts :	Should contain the interrupt number
> +- clocks :	Should contain phandle for the clock feeding the SDHCI controller
> +
> +Optional properties:
> +
> +These are optional but required to enable DMA transfer mode:
> +
> +- dmas :	Should contain two DMA channels, line request number must be 5 for
> +		both channels
> +- dma-names :	Must be "tx", "rx"

I think you should add a reference to bindings/mmc/mmc.txt here.

> +#define MSD_CMD_REG		0
> +#define MSD_ARG_REG		4
> +#define MSD_RESP0_REG		8
> +#define MSD_RESP1_REG		0x0c
> +#define MSD_RESP2_REG		0x10
> +#define MSD_RESP3_REG		0x14
> +#define MSD_RESP_CMD_REG	0x18
> +#define MSD_DATA_CTRL_REG	0x1c
> +#define MSD_DATA_TIMER_REG	0x20
> +#define MSD_DATA_LEN_REG	0x24
> +#define MSD_STATUS_REG		0x28
> +#define MSD_CLEAR_REG		0x2c
> +#define MSD_INT_MASK_REG	0x30
> +#define MSD_POWER_CTRL_REG	0x34
> +#define MSD_CLOCK_CTRL_REG	0x38
> +#define MSD_BUS_WIDTH_REG	0x3c
> +#define MSD_DATA_WIN_REG	0x40
> +#define MSD_FEATURE_REG		0x44
> +#define MSD_REVISION_REG	0x48
> +
> +#define MMC_RSP_SHORT		1
> +#define MMC_RSP_LONG		2
> +#define MMC_RSP_MASK		3
> +#define MMC_ERR_NONE		0
> +#define MMC_ERR_TIMEOUT		1
> +#define MMC_MODE_MMC		0
> +#define MMC_MODE_SD		1
> +#define MMC_ERR_BADCRC		2
> +#define MMC_VDD_360		23
> +
> +#define MSD_RETRY_COUNT		10
> +
> +#define REG_COMMAND		0
> +#define REG_ARGUMENT		4
> +#define REG_RESPONSE0		8
> +#define REG_RESPONSE1		12
> +#define REG_RESPONSE2		16
> +#define REG_RESPONSE3		20
> +#define REG_RESPONSE_COMMAND	24
> +#define REG_DATA_CONTROL	28
> +#define REG_DATA_TIMER		32
> +#define REG_DATA_LENGTH		36
> +#define REG_STATUS		40
> +#define REG_CLEAR		44
> +#define REG_INTERRUPT_MASK	48
> +#define REG_POWER_CONTROL	52
> +#define REG_CLOCK_CONTROL	56
> +#define REG_BUS_WIDTH		60
> +#define REG_DATA_WINDOW		64
> +#define REG_FEATURE		68
> +#define REG_REVISION		72

The lists seem duplicated here, there is an MSD_foo_REG for each REG_foo.

> +	/*
> +	 * hardware does not support MMC_CAP_SD_HIGHSPEED
> +	 * CMD6 will timeout and make things not work
> +	 */
> +	mmc->caps = MMC_CAP_4_BIT_DATA;

Better get the bus-width from DT by calling the mmc_of_parse
function. Some boards might connect only one data bit, or in
fact 8 if it's an eMMC.

	Arnd

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v7] mmc: moxart: Add MOXA ART SD/MMC driver
  2014-01-17  9:43         ` [PATCH v6] " Jonas Jensen
  2014-01-17 14:40           ` Arnd Bergmann
@ 2014-01-21 10:52           ` Jonas Jensen
  2014-04-09 13:54             ` [PATCH v8] " Jonas Jensen
  1 sibling, 1 reply; 12+ messages in thread
From: Jonas Jensen @ 2014-01-21 10:52 UTC (permalink / raw)
  To: chris
  Cc: linux-mmc, cjb, devicetree, linux-arm-kernel, linux-kernel, arm,
	mark.rutland, arnd, Jonas Jensen

Add SD/MMC driver for MOXA ART SoCs.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    v6 was supposed to fix a panic from dmaengine_tx_status() but it
    still happened. This is the sort of panic that lasts between reboots
    but not between kernel reflash.
    
    dmaengine_tx_status() is used only to print DMA error, that's why
    I decided to remove it, instead of risking permanent kernel panic.
    
    Changes since v6:
    
    1. use mmc_of_parse()
    2. remove duplicate defines MSD_*
    3. add reference to bindings/mmc/mmc.txt in DT binding
    4. update DT binding example
       ("coreclk" renamed "clk_apb", DMA channel number is no longer needed)
    5. rename "sdhci-moxart" "moxart-mmc"
    6. remove dmaengine_tx_status() / don't print DMA error
    7. format comments / use correct grammar
    
    Applies to next-20140121

 .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  32 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/moxart-mmc.c                      | 872 +++++++++++++++++++++
 4 files changed, 914 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
 create mode 100644 drivers/mmc/host/moxart-mmc.c

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
new file mode 100644
index 0000000..c2045d1
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
@@ -0,0 +1,32 @@
+MOXA ART SD Host Controller Interface
+
+  Inherits from mmc binding[1].
+
+  [1] Documentation/devicetree/bindings/mmc/mmc.txt
+
+Required properties:
+
+- compatible :	Must be "moxa,moxart-mmc"
+- reg :		Should contain registers location and length
+- interrupts :	Should contain the interrupt number
+- clocks :	Should contain phandle for the clock feeding the SDHCI controller
+
+Optional properties:
+
+These are optional but required to enable DMA transfer mode:
+
+- dmas :	Should contain two DMA channels, line request number must be 5 for
+		both channels
+- dma-names :	Must be "tx", "rx"
+
+Example:
+
+	mmc: mmc@98e00000 {
+		compatible = "moxa,moxart-mmc";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&clk_apb>;
+		dmas =  <&dma 5>,
+			<&dma 5>;
+		dma-names = "tx", "rx";
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 1384f67..15806d6 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -283,6 +283,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_MOXART
+	tristate "MOXART SD/MMC Host Controller support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects support for the MOXART SD/MMC Host Controller.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 3483b6b..6b90eda 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 obj-$(CONFIG_MMC_VUB300)	+= vub300.o
 obj-$(CONFIG_MMC_USHC)		+= ushc.o
 obj-$(CONFIG_MMC_WMT)		+= wmt-sdmmc.o
+obj-$(CONFIG_MMC_MOXART)	+= moxart-mmc.o
 
 obj-$(CONFIG_MMC_REALTEK_PCI)	+= rtsx_pci_sdmmc.o
 
diff --git a/drivers/mmc/host/moxart-mmc.c b/drivers/mmc/host/moxart-mmc.c
new file mode 100644
index 0000000..be12e12
--- /dev/null
+++ b/drivers/mmc/host/moxart-mmc.c
@@ -0,0 +1,872 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technologies Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/mmc.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/sizes.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/of_dma.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#define MMC_RSP_SHORT		1
+#define MMC_RSP_LONG		2
+#define MMC_RSP_MASK		3
+#define MMC_ERR_NONE		0
+#define MMC_ERR_TIMEOUT		1
+#define MMC_MODE_MMC		0
+#define MMC_MODE_SD		1
+#define MMC_ERR_BADCRC		2
+#define MMC_VDD_360		23
+
+#define MSD_RETRY_COUNT		10
+
+#define REG_COMMAND		0
+#define REG_ARGUMENT		4
+#define REG_RESPONSE0		8
+#define REG_RESPONSE1		12
+#define REG_RESPONSE2		16
+#define REG_RESPONSE3		20
+#define REG_RESPONSE_COMMAND	24
+#define REG_DATA_CONTROL	28
+#define REG_DATA_TIMER		32
+#define REG_DATA_LENGTH		36
+#define REG_STATUS		40
+#define REG_CLEAR		44
+#define REG_INTERRUPT_MASK	48
+#define REG_POWER_CONTROL	52
+#define REG_CLOCK_CONTROL	56
+#define REG_BUS_WIDTH		60
+#define REG_DATA_WINDOW		64
+#define REG_FEATURE		68
+#define REG_REVISION		72
+
+/* REG_COMMAND */
+#define MSD_SDC_RST		BIT(10)
+#define MSD_CMD_EN		BIT(9)
+#define MSD_APP_CMD		BIT(8)
+#define MSD_LONG_RSP		BIT(7)
+#define MSD_NEED_RSP		BIT(6)
+#define MSD_CMD_IDX_MASK	0x3f
+
+/* REG_RESPONSE_COMMAND */
+#define MSD_RSP_CMD_APP		BIT(6)
+#define MSD_RSP_CMD_IDX_MASK	0x3f
+
+/* REG_DATA_CONTROL */
+#define MSD_DATA_EN		BIT(6)
+#define MSD_DMA_EN		BIT(5)
+#define MSD_DATA_WRITE		BIT(4)
+#define MSD_BLK_SIZE_MASK	0x0f
+
+/* REG_DATA_LENGTH */
+#define MSD_DATA_LEN_MASK	0xffffff
+
+/* REG_STATUS */
+#define MSD_WRITE_PROT		BIT(12)
+#define MSD_CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to either registers, interrupt or clear. */
+#define MSD_CARD_CHANGE		BIT(10)
+#define MSD_FIFO_ORUN		BIT(9)
+#define MSD_FIFO_URUN		BIT(8)
+#define MSD_DATA_END		BIT(7)
+#define MSD_CMD_SENT		BIT(6)
+#define MSD_DATA_CRC_OK		BIT(5)
+#define MSD_RSP_CRC_OK		BIT(4)
+#define MSD_DATA_TIMEOUT	BIT(3)
+#define MSD_RSP_TIMEOUT		BIT(2)
+#define MSD_DATA_CRC_FAIL	BIT(1)
+#define MSD_RSP_CRC_FAIL	BIT(0)
+
+/* REG_POWER_CONTROL */
+#define MSD_SD_POWER_ON		BIT(4)
+#define MSD_SD_POWER_MASK	0x0f
+
+/* REG_CLOCK_CONTROL */
+#define MSD_CLK_DIS		BIT(8)
+#define MSD_CLK_SD		BIT(7)
+#define MSD_CLK_DIV_MASK	0x7f
+
+/* REG_BUS_WIDTH */
+#define MSD_WIDE_BUS_SUPPORT	BIT(3)
+#define MSD_WIDE_BUS		BIT(2)	/* 4 bytes */
+#define MSD_SINGLE_BUS		BIT(0)	/* 1 byte */
+
+/* REG_FEATURE */
+#define MSD_CPRM_FUNCTION	BIT(8)
+
+struct moxart_host {
+	spinlock_t			lock;
+	void __iomem			*base;
+	phys_addr_t			reg_phys;
+
+	struct dma_chan			*dma_chan_rx;
+	struct dma_chan			*dma_chan_tx;
+	struct dma_async_tx_descriptor	*tx_desc;
+	unsigned int			dma_direction;
+	bool				have_dma;
+	struct completion		dma_complete;
+	struct completion		pio_complete;
+
+	struct mmc_host			*mmc;
+	struct mmc_request		*mrq;
+
+	struct scatterlist		*cur_sg;
+	unsigned int			num_sg;
+	unsigned int			remain;
+	int				size;
+
+	unsigned int			timeout;
+	long				sysclk;
+	bool				removed;
+};
+
+#define MSD_FIFO_LENW	4	/* 4 words, total 4 * 4 = 16 bytes */
+#define MSD_FIFO_LENB	16
+
+#define DMA_FIFO_LEN_FORCE		0
+#define MIN_POWER (MMC_VDD_360 - MSD_SD_POWER_MASK)
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->remain = host->cur_sg->length;
+
+	if (host->remain > host->size)
+		host->remain = host->size;
+
+	data->error = MMC_ERR_NONE;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->remain = host->cur_sg->length;
+		remain = host->size - data->bytes_xfered;
+		if (remain > 0 && remain < host->remain)
+			host->remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	unsigned int status, cmdctrl;
+	int retry = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: cmd->opcode=%d\n",
+		__func__, cmd->opcode);
+
+	cmd->error = MMC_ERR_TIMEOUT;
+
+	writel(MSD_RSP_TIMEOUT | MSD_RSP_CRC_OK |
+	       MSD_RSP_CRC_FAIL | MSD_CMD_SENT, host->base + REG_CLEAR);
+	writel(cmd->arg, host->base + REG_ARGUMENT);
+
+	cmdctrl = cmd->opcode & MSD_CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
+	    cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= MSD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= (MSD_LONG_RSP | MSD_NEED_RSP);
+	else
+		cmdctrl |= MSD_NEED_RSP;
+
+	writel(cmdctrl | MSD_CMD_EN, host->base + REG_COMMAND);
+
+	while (retry++ < MSD_RETRY_COUNT) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & MSD_CARD_DETECT) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_CARD_DETECT\n",
+				__func__);
+			cmd->error = MMC_ERR_TIMEOUT;
+			break;
+		}
+		if (cmdctrl & MSD_NEED_RSP) {
+			dev_dbg(mmc_dev(host->mmc), "%s: MSD_NEED_RSP\n",
+				__func__);
+			if (status & MSD_RSP_TIMEOUT) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_TIMEOUT\n",
+					__func__);
+				writel(MSD_RSP_TIMEOUT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_TIMEOUT;
+				break;
+			}
+			if ((cmd->flags & MMC_RSP_CRC) &&
+				(status & MSD_RSP_CRC_FAIL)) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_FAIL\n",
+					__func__);
+				writel(MSD_RSP_CRC_FAIL, host->base +
+				       REG_CLEAR);
+				cmd->error = MMC_ERR_BADCRC;
+				break;
+			}
+			if (status & MSD_RSP_CRC_OK) {
+				dev_dbg(mmc_dev(host->mmc), "%s: MSD_RSP_CRC_OK\n",
+					__func__);
+				writel(MSD_RSP_CRC_OK, host->base + REG_CLEAR);
+
+				if (cmd->flags & MMC_RSP_136) {
+					cmd->resp[3] =
+						readl(host->base +
+						      REG_RESPONSE0);
+					cmd->resp[2] =
+						readl(host->base +
+						      REG_RESPONSE1);
+					cmd->resp[1] =
+						readl(host->base +
+						      REG_RESPONSE2);
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE3);
+				} else {
+					cmd->resp[0] =
+						readl(host->base +
+						      REG_RESPONSE0);
+				}
+
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		} else {
+			dev_dbg(mmc_dev(host->mmc), "%s: !(cmdctrl & MSD_NEED_RSP)\n",
+				__func__);
+			if (status & MSD_CMD_SENT) {
+				writel(MSD_CMD_SENT, host->base + REG_CLEAR);
+				cmd->error = MMC_ERR_NONE;
+				break;
+			}
+		}
+	}
+
+	if (retry >= (MSD_RETRY_COUNT - 1)) {
+		/*
+		 * Seem to happen a lot on or after CMD25
+		 * (MMC_WRITE_MULTIPLE_BLOCK) with more than 4 blocks
+		 * (>4096), possibly because the transfer is too big
+		 * or takes too long to complete.
+		 *
+		 * When this happens the controller is usually rendered
+		 * unresponsive and can only be recovered with reboot.
+		 */
+		dev_dbg(mmc_dev(host->mmc), "%s: WARNING! no valid status found!\n",
+			__func__);
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host *host = param;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p\n", __func__, host);
+
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	unsigned int len, direction_dev;
+	struct dma_async_tx_descriptor *desc = NULL;
+	struct dma_chan *dma_chan_cur;
+
+	if (host->size == data->bytes_xfered)
+		return;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		direction_dev = DMA_MEM_TO_DEV;
+		dma_chan_cur = host->dma_chan_tx;
+	} else {
+		direction_dev = DMA_DEV_TO_MEM;
+		dma_chan_cur = host->dma_chan_rx;
+	}
+
+	len = dma_map_sg(dma_chan_cur->device->dev, data->sg,
+			 data->sg_len, host->dma_direction);
+
+	if (len > 0) {
+		desc = dmaengine_prep_slave_sg(dma_chan_cur, data->sg,
+					       len, direction_dev,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		dev_err(mmc_dev(host->mmc),
+			"%s: dma_map_sg returned zero length\n",
+			__func__);
+	}
+
+	if (desc) {
+		host->tx_desc = desc;
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(dma_chan_cur);
+	}
+
+	data->bytes_xfered += host->remain;
+}
+
+/* When DMA is available this is used only for SD_APP_SEND_SCR. */
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	unsigned char *buffer;
+	unsigned int wcnt, i;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	if (host->size == data->bytes_xfered)
+		goto transfer_complete;
+
+	buffer = sg_virt(host->cur_sg);
+	wcnt = host->remain >> 2;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			writel(*(unsigned int *)buffer,
+			       host->base + REG_DATA_WINDOW);
+			udelay(10);
+		}
+	} else {
+		for (i = 0; i < wcnt; i++, buffer += 4) {
+			/* Byte order is reversed only when reading SCR. */
+			if (data->mrq->cmd->opcode == SD_APP_SEND_SCR) {
+				*(unsigned int *)buffer = ioread32be(
+					host->base + REG_DATA_WINDOW);
+			} else {
+				*(unsigned int *)buffer =
+					readl(host->base + REG_DATA_WINDOW);
+			}
+			udelay(10);
+		}
+	}
+	wcnt <<= 2;
+	host->remain -= wcnt;
+	data->bytes_xfered += wcnt;
+
+	if (host->size != data->bytes_xfered)
+		moxart_next_sg(host);
+	else
+		complete(&host->pio_complete);
+
+transfer_complete:
+	dev_dbg(mmc_dev(host->mmc), "%s: host->size=%d host->remain=%u\n",
+		__func__, host->size, host->remain);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	unsigned int timeout, datactrl;
+	int blksz_bits;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	if (!data)
+		return;
+
+	host->size = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	timeout = (host->mmc->f_max / 1000) * (data->timeout_ns / 1000);
+	timeout *= 2;
+
+	datactrl = (blksz_bits & MSD_BLK_SIZE_MASK) | MSD_DATA_EN;
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= MSD_DATA_WRITE;
+
+	if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+	    && host->have_dma) {
+		datactrl |= MSD_DMA_EN;
+	}
+
+	dev_dbg(mmc_dev(host->mmc), "%s: blocks=%d blksz=%d datactrl=0x%08x\n",
+		__func__, data->blocks,	data->blksz, datactrl);
+	dev_dbg(mmc_dev(host->mmc), "%s: timeout=%u timeout_ns=%u\n",
+		__func__, timeout, data->timeout_ns);
+
+	writel(timeout, host->base + REG_DATA_TIMER);
+	writel(host->size, host->base + REG_DATA_LENGTH);
+	writel(datactrl, host->base + REG_DATA_CONTROL);
+}
+
+static void moxart_transfer_check(struct mmc_data *data,
+				  struct moxart_host *host)
+{
+	unsigned int status, count = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	while (1) {
+		udelay(10);
+		status = readl(host->base + REG_STATUS);
+		if (status & (MSD_DATA_CRC_OK | MSD_DATA_CRC_FAIL
+			| MSD_DATA_END)	|| count > 10)
+			break;
+		dev_dbg(mmc_dev(host->mmc), "%s: waiting for status=%08x ..\n",
+			__func__, status);
+		count++;
+	}
+	if (status & MSD_DATA_CRC_OK) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_OK\n", __func__);
+		writel(MSD_DATA_CRC_OK, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_CRC_FAIL) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_CRC_FAIL\n",
+			__func__);
+		writel(MSD_DATA_CRC_FAIL, host->base + REG_CLEAR);
+		data->error = MMC_ERR_TIMEOUT;
+	}
+	if (status & MSD_DATA_END) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_END\n",
+			__func__);
+		writel(MSD_DATA_END, host->base + REG_CLEAR);
+	}
+	if (status & MSD_DATA_TIMEOUT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: MSD_DATA_TIMEOUT\n",
+			__func__);
+		writel(MSD_DATA_TIMEOUT, host->base + REG_CLEAR);
+	}
+}
+
+static void moxart_card_change(struct moxart_host *host)
+{
+	int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		dev_dbg(mmc_dev(host->mmc), "%s: card removed\n", __func__);
+		if (host->have_dma && host->size > MSD_FIFO_LENB) {
+			dev_dbg(mmc_dev(host->mmc), "%s: call dmaengine_terminate_all\n",
+				__func__);
+			dmaengine_terminate_all(host->dma_chan_rx);
+			dmaengine_terminate_all(host->dma_chan_tx);
+		}
+		host->removed = true;
+		delay = 0;
+	} else {
+		dev_dbg(mmc_dev(host->mmc), "%s: card inserted\n", __func__);
+		host->removed = false;
+		delay = 500;
+	}
+
+	/*
+	 * Clearing FIFO interrupts here does not stop a follow up
+	 * MSD_FIFO_*RUN after MSD_CARD_CHANGE.
+	 *
+	 * This is why moxart_irq() has a check (host->mrq != NULL),
+	 * as it avoids unnecessary calls to moxart_transfer_pio().
+	 *
+	 * If it's during transfer, the mmc_request pointer
+	 * in moxart_request() should still be valid
+	 * (which is why host->mrq can be set NULL here).
+	 */
+	host->mrq = NULL;
+	writel(MSD_CARD_CHANGE | MSD_FIFO_ORUN | MSD_FIFO_URUN,
+	       host->base + REG_CLEAR);
+	writel(MSD_CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	dev_dbg(mmc_dev(host->mmc), "%s: call mmc_detect_change\n", __func__);
+	mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long dma_time, pio_time, flags;
+	unsigned int status;
+
+	dev_dbg(mmc_dev(host->mmc), "%s\n", __func__);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(host->base + REG_STATUS) & MSD_CARD_DETECT) {
+		mrq->cmd->error = MMC_ERR_TIMEOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		/* Only use DMA/PIO (and wait) when there's data. */
+		if (((host->size > MSD_FIFO_LENB) || DMA_FIFO_LEN_FORCE)
+			&& host->have_dma) {
+
+			writel(MSD_CARD_CHANGE, host->base +
+			       REG_INTERRUPT_MASK);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			host->dma_direction = (mrq->cmd->data->flags
+					      & MMC_DATA_WRITE) ?
+					      DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			dma_time = wait_for_completion_interruptible_timeout(
+				   &host->dma_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: dma_time=%lu (DMA wait time)\n",
+				__func__, dma_time);
+
+			dma_unmap_sg(host->dma_chan_tx->device->dev,
+				     mrq->cmd->data->sg, mrq->cmd->data->sg_len,
+				     host->dma_direction);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MSD_FIFO_URUN | MSD_FIFO_ORUN | MSD_CARD_CHANGE,
+			       host->base + REG_INTERRUPT_MASK);
+
+			status = readl(host->base + REG_STATUS);
+			dev_dbg(mmc_dev(host->mmc), "%s: status=%08x\n",
+				__func__, status);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfer will start from interrupt. */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+			dev_dbg(mmc_dev(host->mmc), "%s: pio_time=%lu (PIO wait time)\n",
+				__func__, pio_time);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		if (host->removed) {
+			dev_dbg(mmc_dev(host->mmc), "%s: host removed during transfer!\n",
+				__func__);
+			mrq->cmd->error = MMC_ERR_TIMEOUT;
+		}
+
+		moxart_transfer_check(mrq->cmd->data, host);
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	unsigned int status;
+	unsigned long flags;
+
+	status = readl(host->base + REG_STATUS);
+
+	dev_dbg(mmc_dev(host->mmc), "%s: host=%p status=%08x\n",
+		__func__, host, status);
+
+	if (status & MSD_CARD_CHANGE) {
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_card_change\n",
+			__func__);
+		moxart_card_change(host);
+	}
+	if (status & (MSD_FIFO_ORUN | MSD_FIFO_URUN) && host->mrq) {
+		writel(status & (MSD_FIFO_ORUN | MSD_FIFO_URUN),
+		       host->base + REG_CLEAR);
+		dev_dbg(mmc_dev(host->mmc), "%s: call moxart_transfer_pio\n",
+			__func__);
+		spin_lock_irqsave(&host->lock, flags);
+		moxart_transfer_pio(host);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	unsigned short power;
+	int div;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (ios->clock) {
+		div = (host->sysclk / (host->mmc->f_max * 2)) - 1;
+
+		if (div > MSD_CLK_DIV_MASK)
+			div = MSD_CLK_DIV_MASK;
+		else if (div < 0)
+			div = 0;
+
+		div |= MSD_CLK_SD;
+		writel(div, host->base + REG_CLOCK_CONTROL);
+	} else if (!(readl(host->base + REG_CLOCK_CONTROL) & MSD_CLK_DIS)) {
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(host->base + REG_POWER_CONTROL) & ~MSD_SD_POWER_ON,
+		       host->base + REG_POWER_CONTROL);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(MSD_SD_POWER_ON | (unsigned int) power,
+		       host->base + REG_POWER_CONTROL);
+	}
+
+	if (ios->bus_width == MMC_BUS_WIDTH_1)
+		writel(MSD_SINGLE_BUS, host->base + REG_BUS_WIDTH);
+	else
+		writel(MSD_WIDE_BUS, host->base + REG_BUS_WIDTH);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+
+	return !!(readl(host->base + REG_STATUS) & MSD_WRITE_PROT);
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int ret;
+	struct dma_slave_config cfg;
+	unsigned int irq;
+	struct clk *clk;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "%s: mmc_alloc_host failed\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "%s: could not get MMC base resource\n", __func__);
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+	if (irq <= 0) {
+		dev_err(dev, "irq_of_parse_and_map failed\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc))
+		return PTR_ERR(reg_mmc);
+
+	mmc_of_parse(mmc);
+
+	mmc->ops = &moxart_ops;
+	mmc->f_min = 400000;
+	mmc->f_max = 25000000;
+	mmc->ocr_avail = 0xffff00;	/* Support 2.0v - 3.6v power. */
+	mmc->max_segs = 32;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->base = reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "%s: of_clk_get failed\n", __func__);
+		return PTR_ERR(clk);
+	}
+	host->sysclk = clk_get_rate(clk);
+
+	spin_lock_init(&host->lock);
+
+	/* Disable interrupts. */
+	writel(0, host->base + REG_INTERRUPT_MASK);
+
+	/* Reset chip. */
+	writel(MSD_SDC_RST, host->base + REG_COMMAND);
+
+	/* Wait for reset finished. */
+	while (readl(host->base + REG_COMMAND) & MSD_SDC_RST)
+		udelay(10);
+
+	host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
+	host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
+
+	if (!host->dma_chan_rx || !host->dma_chan_tx) {
+		dev_dbg(dev, "%s: using PIO mode transfer\n", __func__);
+
+		host->have_dma = false;
+		mmc->max_blk_count = 1;
+	} else {
+		dev_dbg(dev, "%s: using 2 DMA channels rx=%p tx=%p\n",
+			__func__, host->dma_chan_rx, host->dma_chan_tx);
+
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = (unsigned int)host->reg_phys + REG_DATA_WINDOW;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = (unsigned int)host->reg_phys + REG_DATA_WINDOW;
+		cfg.dst_addr = 0;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+
+		host->have_dma = true;
+		mmc->max_blk_count = 16;
+	}
+
+	ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "%s: IRQ=%d\n", __func__, irq);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static void moxart_release_dma(struct moxart_host *host)
+{
+	if (host->dma_chan_tx) {
+		struct dma_chan *chan = host->dma_chan_tx;
+		host->dma_chan_tx = NULL;
+		dma_release_channel(chan);
+	}
+	if (host->dma_chan_rx) {
+		struct dma_chan *chan = host->dma_chan_rx;
+		host->dma_chan_rx = NULL;
+		dma_release_channel(chan);
+	}
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		moxart_release_dma(host);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, host->base + REG_INTERRUPT_MASK);
+		writel(0, host->base + REG_POWER_CONTROL);
+		writel(readl(host->base + REG_CLOCK_CONTROL) | MSD_CLK_DIS,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_mmc_match[] = {
+	{ .compatible = "moxa,moxart-mmc" },
+	{ }
+};
+
+static struct platform_driver moxart_mmc_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "mmc-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_mmc_match,
+	},
+};
+module_platform_driver(moxart_mmc_driver);
+
+MODULE_ALIAS("platform:mmc-moxart");
+MODULE_DESCRIPTION("MOXART SDHCI driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v8] mmc: moxart: Add MOXA ART SD/MMC driver
  2014-01-21 10:52           ` [PATCH v7] mmc: moxart: Add MOXA ART SD/MMC driver Jonas Jensen
@ 2014-04-09 13:54             ` Jonas Jensen
  2014-04-23 13:37               ` Arnd Bergmann
  2014-04-24 11:52               ` Ulf Hansson
  0 siblings, 2 replies; 12+ messages in thread
From: Jonas Jensen @ 2014-04-09 13:54 UTC (permalink / raw)
  To: chris
  Cc: ulf.hansson, linux-mmc, cjb, devicetree, linux-arm-kernel,
	linux-kernel, arm, mark.rutland, arnd, Jonas Jensen

Add SD/MMC driver for MOXA ART SoCs.

The "MOXA ART MMC controller" is likely a faraday "ftsdc010",
a controller with support in U-Boot:

http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
---

Notes:
    This has been cleaned up further, especially the return value fiasco in v7.
    
    The "MOXA ART MMC controller" is likely a faraday "ftsdc010" (supported by U-Boot [1]).
    Registers are the same albeit named slightly different.
    
    I think this can support other "ftsdc010" SoCs with one reservation for
    power control, which may need hardware specific changes.
    
    Changes since v7:
    
    1. merge code from U-Boot "ftsdc010" driver [1]
    
       Specifically how it implements status wait, clock divider, fifo width, bus width.
    
    2. return value cleanup, MMC_ERR_TIMEOUT is no longer used or set at the top of every request
    
       i.e. the default (no error) return value (0) is respected.
    
       Error is only set on actual error, "MMC_ERR_NONE" is no longer used.
    
    3. FIFO size is now read from register (and respected during PIO transfer)
    4. supported bus width(s) is now read from register
    5. !(of_dma_request_slave_channel()) return value check is wrong,
       IS_ERR() must be used.
    6. clean up and remove unused defines / header includes
    7. add "faraday,ftsdc010" compatible string (requested by Arnd)
    
    [1] http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c
    
    Applies to next-20140409

 .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  30 +
 drivers/mmc/host/Kconfig                           |   9 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/moxart-mmc.c                      | 730 +++++++++++++++++++++
 4 files changed, 770 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
 create mode 100644 drivers/mmc/host/moxart-mmc.c

diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
new file mode 100644
index 0000000..b638191
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
@@ -0,0 +1,30 @@
+MOXA ART MMC Host Controller Interface
+
+  Inherits from mmc binding[1].
+
+  [1] Documentation/devicetree/bindings/mmc/mmc.txt
+
+Required properties:
+
+- compatible :	Must be "moxa,moxart-mmc" or "faraday,ftsdc010"
+- reg :		Should contain registers location and length
+- interrupts :	Should contain the interrupt number
+- clocks :	Should contain phandle for the clock feeding the MMC controller
+
+Optional properties:
+
+- dmas :	Should contain two DMA channels, line request number must be 5 for
+		both channels
+- dma-names :	Must be "tx", "rx"
+
+Example:
+
+	mmc: mmc@98e00000 {
+		compatible = "moxa,moxart-mmc";
+		reg = <0x98e00000 0x5C>;
+		interrupts = <5 0>;
+		clocks = <&clk_apb>;
+		dmas =  <&dma 5>,
+			<&dma 5>;
+		dma-names = "tx", "rx";
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 8aaf8c1..eb93b81 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -283,6 +283,15 @@ config MMC_SDHCI_BCM2835
 
 	  If unsure, say N.
 
+config MMC_MOXART
+	tristate "MOXART SD/MMC Host Controller support"
+	depends on ARCH_MOXART && MMC
+	help
+	  This selects support for the MOXART SD/MMC Host Controller.
+	  MOXA provides one multi-functional card reader which can
+	  be found on some embedded hardware such as UC-7112-LX.
+	  If you have a controller with this interface, say Y here.
+
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
 	depends on ARCH_OMAP
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 0c8aa5e..dfa6ecb 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 obj-$(CONFIG_MMC_VUB300)	+= vub300.o
 obj-$(CONFIG_MMC_USHC)		+= ushc.o
 obj-$(CONFIG_MMC_WMT)		+= wmt-sdmmc.o
+obj-$(CONFIG_MMC_MOXART)	+= moxart-mmc.o
 
 obj-$(CONFIG_MMC_REALTEK_PCI)	+= rtsx_pci_sdmmc.o
 
diff --git a/drivers/mmc/host/moxart-mmc.c b/drivers/mmc/host/moxart-mmc.c
new file mode 100644
index 0000000..74924a0
--- /dev/null
+++ b/drivers/mmc/host/moxart-mmc.c
@@ -0,0 +1,730 @@
+/*
+ * MOXA ART MMC host driver.
+ *
+ * Copyright (C) 2014 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * Based on code from
+ * Moxa Technologies Co., Ltd. <www.moxa.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/of_dma.h>
+#include <linux/spinlock.h>
+
+#define REG_COMMAND		0
+#define REG_ARGUMENT		4
+#define REG_RESPONSE0		8
+#define REG_RESPONSE1		12
+#define REG_RESPONSE2		16
+#define REG_RESPONSE3		20
+#define REG_RESPONSE_COMMAND	24
+#define REG_DATA_CONTROL	28
+#define REG_DATA_TIMER		32
+#define REG_DATA_LENGTH		36
+#define REG_STATUS		40
+#define REG_CLEAR		44
+#define REG_INTERRUPT_MASK	48
+#define REG_POWER_CONTROL	52
+#define REG_CLOCK_CONTROL	56
+#define REG_BUS_WIDTH		60
+#define REG_DATA_WINDOW		64
+#define REG_FEATURE		68
+#define REG_REVISION		72
+
+/* REG_COMMAND */
+#define CMD_SDC_RESET		BIT(10)
+#define CMD_EN			BIT(9)
+#define CMD_APP_CMD		BIT(8)
+#define CMD_LONG_RSP		BIT(7)
+#define CMD_NEED_RSP		BIT(6)
+#define CMD_IDX_MASK		0x3f
+
+/* REG_RESPONSE_COMMAND */
+#define RSP_CMD_APP		BIT(6)
+#define RSP_CMD_IDX_MASK	0x3f
+
+/* REG_DATA_CONTROL */
+#define DCR_DATA_FIFO_RESET     BIT(8)
+#define DCR_DATA_THRES          BIT(7)
+#define DCR_DATA_EN		BIT(6)
+#define DCR_DMA_EN		BIT(5)
+#define DCR_DATA_WRITE		BIT(4)
+#define DCR_BLK_SIZE		0x0f
+
+/* REG_DATA_LENGTH */
+#define DATA_LEN_MASK		0xffffff
+
+/* REG_STATUS */
+#define WRITE_PROT		BIT(12)
+#define CARD_DETECT		BIT(11)
+/* 1-10 below can be sent to either registers, interrupt or clear. */
+#define CARD_CHANGE		BIT(10)
+#define FIFO_ORUN		BIT(9)
+#define FIFO_URUN		BIT(8)
+#define DATA_END		BIT(7)
+#define CMD_SENT		BIT(6)
+#define DATA_CRC_OK		BIT(5)
+#define RSP_CRC_OK		BIT(4)
+#define DATA_TIMEOUT		BIT(3)
+#define RSP_TIMEOUT		BIT(2)
+#define DATA_CRC_FAIL		BIT(1)
+#define RSP_CRC_FAIL		BIT(0)
+
+#define MASK_RSP		(RSP_TIMEOUT | RSP_CRC_FAIL | \
+				 RSP_CRC_OK  | CARD_DETECT  | CMD_SENT)
+
+#define MASK_DATA		(DATA_CRC_OK   | DATA_END | \
+				 DATA_CRC_FAIL | DATA_TIMEOUT)
+
+#define MASK_INTR_PIO		(FIFO_URUN | FIFO_ORUN | CARD_CHANGE)
+
+/* REG_POWER_CONTROL */
+#define SD_POWER_ON		BIT(4)
+#define SD_POWER_MASK		0x0f
+
+/* REG_CLOCK_CONTROL */
+#define CLK_HISPD		BIT(9)
+#define CLK_OFF			BIT(8)
+#define CLK_SD			BIT(7)
+#define CLK_DIV_MASK		0x7f
+
+/* REG_BUS_WIDTH */
+#define BUS_WIDTH_8		BIT(2)
+#define BUS_WIDTH_4		BIT(1)
+#define BUS_WIDTH_1		BIT(0)
+
+#define MMC_VDD_360		23
+#define MIN_POWER		(MMC_VDD_360 - SD_POWER_MASK)
+#define MAX_RETRIES		500000
+
+struct moxart_host {
+	spinlock_t			lock;
+
+	void __iomem			*base;
+
+	phys_addr_t			reg_phys;
+
+	struct dma_chan			*dma_chan_tx;
+	struct dma_chan                 *dma_chan_rx;
+	struct dma_async_tx_descriptor	*tx_desc;
+	struct mmc_host			*mmc;
+	struct mmc_request		*mrq;
+	struct scatterlist		*cur_sg;
+	struct completion		dma_complete;
+	struct completion		pio_complete;
+
+	u32				num_sg;
+	u32				data_remain;
+	u32				data_len;
+	u32				fifo_width;
+	u32				timeout;
+	u32				rate;
+
+	long				sysclk;
+
+	bool				have_dma;
+	bool				is_removed;
+};
+
+static inline void moxart_init_sg(struct moxart_host *host,
+				  struct mmc_data *data)
+{
+	host->cur_sg = data->sg;
+	host->num_sg = data->sg_len;
+	host->data_remain = host->cur_sg->length;
+
+	if (host->data_remain > host->data_len)
+		host->data_remain = host->data_len;
+}
+
+static inline int moxart_next_sg(struct moxart_host *host)
+{
+	int remain;
+	struct mmc_data *data = host->mrq->cmd->data;
+
+	host->cur_sg++;
+	host->num_sg--;
+
+	if (host->num_sg > 0) {
+		host->data_remain = host->cur_sg->length;
+		remain = host->data_len - data->bytes_xfered;
+		if (remain > 0 && remain < host->data_remain)
+			host->data_remain = remain;
+	}
+
+	return host->num_sg;
+}
+
+static int moxart_wait_for_status(struct moxart_host *host,
+				  u32 mask, u32 *status)
+{
+	int ret = -ETIMEDOUT;
+	u32 i;
+
+	for (i = 0; i < MAX_RETRIES; i++) {
+		*status = readl(host->base + REG_STATUS);
+		if (!(*status & mask)) {
+			udelay(5);
+			continue;
+		}
+		writel(*status & mask, host->base + REG_CLEAR);
+		ret = 0;
+		break;
+	}
+
+	if (ret)
+		dev_err(mmc_dev(host->mmc), "timed out waiting for status\n");
+
+	return ret;
+}
+
+
+static void moxart_send_command(struct moxart_host *host,
+	struct mmc_command *cmd)
+{
+	u32 status, cmdctrl;
+
+	writel(RSP_TIMEOUT  | RSP_CRC_OK |
+	       RSP_CRC_FAIL | CMD_SENT, host->base + REG_CLEAR);
+	writel(cmd->arg, host->base + REG_ARGUMENT);
+
+	cmdctrl = cmd->opcode & CMD_IDX_MASK;
+	if (cmdctrl == SD_APP_SET_BUS_WIDTH    || cmdctrl == SD_APP_OP_COND   ||
+	    cmdctrl == SD_APP_SEND_SCR         || cmdctrl == SD_APP_SD_STATUS ||
+	    cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
+		cmdctrl |= CMD_APP_CMD;
+
+	if (cmd->flags & MMC_RSP_PRESENT)
+		cmdctrl |= CMD_NEED_RSP;
+
+	if (cmd->flags & MMC_RSP_136)
+		cmdctrl |= CMD_LONG_RSP;
+
+	writel(cmdctrl | CMD_EN, host->base + REG_COMMAND);
+
+	if (moxart_wait_for_status(host, MASK_RSP, &status) == -ETIMEDOUT)
+		cmd->error = -ETIMEDOUT;
+
+	if (status & RSP_TIMEOUT) {
+		cmd->error = -ETIMEDOUT;
+		return;
+	}
+	if (status & RSP_CRC_FAIL) {
+		cmd->error = -EIO;
+		return;
+	}
+	if (status & RSP_CRC_OK) {
+		if (cmd->flags & MMC_RSP_136) {
+			cmd->resp[3] = readl(host->base + REG_RESPONSE0);
+			cmd->resp[2] = readl(host->base + REG_RESPONSE1);
+			cmd->resp[1] = readl(host->base + REG_RESPONSE2);
+			cmd->resp[0] = readl(host->base + REG_RESPONSE3);
+		} else {
+			cmd->resp[0] = readl(host->base + REG_RESPONSE0);
+		}
+	}
+}
+
+static void moxart_dma_complete(void *param)
+{
+	struct moxart_host *host = param;
+
+	complete(&host->dma_complete);
+}
+
+static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
+{
+	u32 len, dir_data, dir_slave;
+	unsigned long dma_time;
+	struct dma_async_tx_descriptor *desc = NULL;
+	struct dma_chan *dma_chan;
+
+	if (host->data_len == data->bytes_xfered)
+		return;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		dma_chan = host->dma_chan_tx;
+		dir_data = DMA_TO_DEVICE;
+		dir_slave = DMA_MEM_TO_DEV;
+	} else {
+		dma_chan = host->dma_chan_rx;
+		dir_data = DMA_FROM_DEVICE;
+		dir_slave = DMA_DEV_TO_MEM;
+	}
+
+	len = dma_map_sg(dma_chan->device->dev, data->sg,
+			 data->sg_len, dir_data);
+
+	if (len > 0) {
+		desc = dmaengine_prep_slave_sg(dma_chan, data->sg,
+					       len, dir_slave,
+					       DMA_PREP_INTERRUPT |
+					       DMA_CTRL_ACK);
+	} else {
+		dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n");
+	}
+
+	if (desc) {
+		host->tx_desc = desc;
+		desc->callback = moxart_dma_complete;
+		desc->callback_param = host;
+		dmaengine_submit(desc);
+		dma_async_issue_pending(dma_chan);
+	}
+
+	data->bytes_xfered += host->data_remain;
+
+	dma_time = wait_for_completion_interruptible_timeout(
+		   &host->dma_complete, host->timeout);
+
+	dma_unmap_sg(dma_chan->device->dev,
+		     data->sg, data->sg_len,
+		     dir_data);
+}
+
+
+static void moxart_transfer_pio(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	u32 *sgp, len = 0, remain, status;
+
+	if (host->data_len == data->bytes_xfered)
+		return;
+
+	sgp = sg_virt(host->cur_sg);
+	remain = host->data_remain;
+
+	if (data->flags & MMC_DATA_WRITE) {
+		while (remain > 0) {
+			if (moxart_wait_for_status(host, FIFO_URUN, &status)
+			     == -ETIMEDOUT) {
+				data->error = -ETIMEDOUT;
+				complete(&host->pio_complete);
+				return;
+			}
+			for (len = 0; len < remain && len < host->fifo_width;) {
+				iowrite32(*sgp, host->base + REG_DATA_WINDOW);
+				sgp++;
+				len += 4;
+			}
+			remain -= len;
+		}
+
+	} else {
+		while (remain > 0) {
+			if (moxart_wait_for_status(host, FIFO_ORUN, &status)
+			    == -ETIMEDOUT) {
+				data->error = -ETIMEDOUT;
+				complete(&host->pio_complete);
+				return;
+			}
+			for (len = 0; len < remain && len < host->fifo_width;) {
+				/* SCR data must be read in big endian. */
+				if (data->mrq->cmd->opcode == SD_APP_SEND_SCR)
+					*sgp = ioread32be(host->base +
+							  REG_DATA_WINDOW);
+				else
+					*sgp = ioread32(host->base +
+							REG_DATA_WINDOW);
+				sgp++;
+				len += 4;
+			}
+			remain -= len;
+		}
+	}
+
+	data->bytes_xfered += host->data_remain - remain;
+	host->data_remain = remain;
+
+	if (host->data_len != data->bytes_xfered)
+		moxart_next_sg(host);
+	else
+		complete(&host->pio_complete);
+}
+
+static void moxart_prepare_data(struct moxart_host *host)
+{
+	struct mmc_data *data = host->mrq->cmd->data;
+	u32 datactrl;
+	int blksz_bits;
+
+	if (!data)
+		return;
+
+	host->data_len = data->blocks * data->blksz;
+	blksz_bits = ffs(data->blksz) - 1;
+	BUG_ON(1 << blksz_bits != data->blksz);
+
+	moxart_init_sg(host, data);
+
+	datactrl = DCR_DATA_EN | (blksz_bits & DCR_BLK_SIZE);
+
+	if (data->flags & MMC_DATA_WRITE)
+		datactrl |= DCR_DATA_WRITE;
+
+	if ((host->data_len > host->fifo_width) && host->have_dma)
+		datactrl |= DCR_DMA_EN;
+
+	writel(DCR_DATA_FIFO_RESET, host->base + REG_DATA_CONTROL);
+	writel(MASK_DATA | FIFO_URUN | FIFO_ORUN, host->base + REG_CLEAR);
+	writel(host->rate, host->base + REG_DATA_TIMER);
+	writel(host->data_len, host->base + REG_DATA_LENGTH);
+	writel(datactrl, host->base + REG_DATA_CONTROL);
+}
+
+static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long pio_time, flags;
+	u32 status;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	init_completion(&host->dma_complete);
+	init_completion(&host->pio_complete);
+
+	host->mrq = mrq;
+
+	if (readl(host->base + REG_STATUS) & CARD_DETECT) {
+		mrq->cmd->error = -ETIMEDOUT;
+		goto request_done;
+	}
+
+	moxart_prepare_data(host);
+	moxart_send_command(host, host->mrq->cmd);
+
+	if (mrq->cmd->data) {
+		if ((host->data_len > host->fifo_width) && host->have_dma) {
+
+			writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			moxart_transfer_dma(mrq->cmd->data, host);
+
+			spin_lock_irqsave(&host->lock, flags);
+		} else {
+
+			writel(MASK_INTR_PIO, host->base + REG_INTERRUPT_MASK);
+
+			spin_unlock_irqrestore(&host->lock, flags);
+
+			/* PIO transfers start from interrupt. */
+			pio_time = wait_for_completion_interruptible_timeout(
+				   &host->pio_complete, host->timeout);
+
+			spin_lock_irqsave(&host->lock, flags);
+		}
+
+		if (host->is_removed) {
+			dev_err(mmc_dev(host->mmc), "card removed\n");
+			mrq->cmd->error = -ETIMEDOUT;
+			goto request_done;
+		}
+
+		if (moxart_wait_for_status(host, MASK_DATA, &status)
+		    == -ETIMEDOUT) {
+			mrq->cmd->data->error = -ETIMEDOUT;
+			goto request_done;
+		}
+
+		if (status & DATA_CRC_FAIL)
+			mrq->cmd->data->error = -ETIMEDOUT;
+
+		if (mrq->cmd->data->stop)
+			moxart_send_command(host, mrq->cmd->data->stop);
+	}
+
+request_done:
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t moxart_irq(int irq, void *devid)
+{
+	struct moxart_host *host = (struct moxart_host *)devid;
+	u32 status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	status = readl(host->base + REG_STATUS);
+	if (status & CARD_CHANGE) {
+		host->is_removed = status & CARD_DETECT;
+		if (host->is_removed && host->have_dma) {
+			dmaengine_terminate_all(host->dma_chan_tx);
+			dmaengine_terminate_all(host->dma_chan_rx);
+		}
+		host->mrq = NULL;
+		writel(MASK_INTR_PIO, host->base + REG_CLEAR);
+		writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
+		mmc_detect_change(host->mmc, 0);
+	}
+	if (status & (FIFO_ORUN | FIFO_URUN) && host->mrq)
+		moxart_transfer_pio(host);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	u8 power, div;
+	u32 ctrl;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (ios->clock) {
+		for (div = 0; div < CLK_DIV_MASK; ++div) {
+			if (ios->clock >= host->sysclk / (2 * (div + 1)))
+				break;
+		}
+		ctrl = CLK_SD | div;
+		host->rate = host->sysclk / (2 * (div + 1));
+		if (host->rate > host->sysclk)
+			ctrl |= CLK_HISPD;
+		writel(ctrl, host->base + REG_CLOCK_CONTROL);
+	}
+
+	if (ios->power_mode == MMC_POWER_OFF) {
+		writel(readl(host->base + REG_POWER_CONTROL) & ~SD_POWER_ON,
+		       host->base + REG_POWER_CONTROL);
+	} else {
+		if (ios->vdd < MIN_POWER)
+			power = 0;
+		else
+			power = ios->vdd - MIN_POWER;
+
+		writel(SD_POWER_ON | (u32) power,
+		       host->base + REG_POWER_CONTROL);
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_4:
+		writel(BUS_WIDTH_4, host->base + REG_BUS_WIDTH);
+		break;
+	case MMC_BUS_WIDTH_8:
+		writel(BUS_WIDTH_8, host->base + REG_BUS_WIDTH);
+		break;
+	default:
+		writel(BUS_WIDTH_1, host->base + REG_BUS_WIDTH);
+		break;
+	}
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static int moxart_get_ro(struct mmc_host *mmc)
+{
+	struct moxart_host *host = mmc_priv(mmc);
+
+	return !!(readl(host->base + REG_STATUS) & WRITE_PROT);
+}
+
+static struct mmc_host_ops moxart_ops = {
+	.request = moxart_request,
+	.set_ios = moxart_set_ios,
+	.get_ro = moxart_get_ro,
+};
+
+static int moxart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource res_mmc;
+	struct mmc_host *mmc;
+	struct moxart_host *host = NULL;
+	struct dma_slave_config cfg;
+	struct clk *clk;
+	void __iomem *reg_mmc;
+	dma_cap_mask_t mask;
+	int irq, ret;
+	u32 i;
+
+	mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
+	if (!mmc) {
+		dev_err(dev, "mmc_alloc_host failed\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = of_address_to_resource(node, 0, &res_mmc);
+	if (ret) {
+		dev_err(dev, "of_address_to_resource failed\n");
+		goto out;
+	}
+
+	irq = irq_of_parse_and_map(node, 0);
+	if (irq <= 0) {
+		dev_err(dev, "irq_of_parse_and_map failed\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	clk = of_clk_get(node, 0);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "of_clk_get failed\n");
+		ret = PTR_ERR(clk);
+		goto out;
+	}
+
+	reg_mmc = devm_ioremap_resource(dev, &res_mmc);
+	if (IS_ERR(reg_mmc)) {
+		ret = PTR_ERR(reg_mmc);
+		goto out;
+	}
+
+	mmc_of_parse(mmc);
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->base = reg_mmc;
+	host->reg_phys = res_mmc.start;
+	host->timeout = msecs_to_jiffies(1000);
+	host->sysclk = clk_get_rate(clk);
+	host->fifo_width = readl(host->base + REG_FEATURE) << 2;
+	host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
+	host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
+
+	spin_lock_init(&host->lock);
+
+	mmc->ops = &moxart_ops;
+	mmc->f_max = DIV_ROUND_CLOSEST(host->sysclk, 2);
+	mmc->f_min = DIV_ROUND_CLOSEST(host->sysclk, CLK_DIV_MASK * 2);
+	mmc->ocr_avail = 0xffff00;	/* Support 2.0v - 3.6v power. */
+
+	if (IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) {
+		dev_dbg(dev, "PIO mode transfer enabled\n");
+		host->have_dma = false;
+	} else {
+		dev_dbg(dev, "DMA channels found (%p,%p)\n",
+			 host->dma_chan_tx, host->dma_chan_rx);
+		host->have_dma = true;
+
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = host->reg_phys + REG_DATA_WINDOW;
+		dmaengine_slave_config(host->dma_chan_tx, &cfg);
+
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = host->reg_phys + REG_DATA_WINDOW;
+		cfg.dst_addr = 0;
+		dmaengine_slave_config(host->dma_chan_rx, &cfg);
+	}
+
+	switch ((readl(host->base + REG_BUS_WIDTH) >> 3) & 3) {
+	case 1:
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+		break;
+	case 2:
+		mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
+		break;
+	default:
+		break;
+	}
+
+	writel(0, host->base + REG_INTERRUPT_MASK);
+
+	writel(CMD_SDC_RESET, host->base + REG_COMMAND);
+	for (i = 0; i < MAX_RETRIES; i++) {
+		if (!(readl(host->base + REG_COMMAND) & CMD_SDC_RESET))
+			break;
+		udelay(5);
+	}
+
+	ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
+	if (ret)
+		goto out;
+
+	dev_set_drvdata(dev, mmc);
+	mmc_add_host(mmc);
+
+	dev_dbg(dev, "IRQ=%d, FIFO is %d bytes\n", irq, host->fifo_width);
+
+	return 0;
+
+out:
+	if (mmc)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static int moxart_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct moxart_host *host = mmc_priv(mmc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	if (mmc) {
+		if (!IS_ERR(host->dma_chan_tx))
+			dma_release_channel(host->dma_chan_tx);
+		if (!IS_ERR(host->dma_chan_rx))
+			dma_release_channel(host->dma_chan_rx);
+		mmc_remove_host(mmc);
+		mmc_free_host(mmc);
+
+		writel(0, host->base + REG_INTERRUPT_MASK);
+		writel(0, host->base + REG_POWER_CONTROL);
+		writel(readl(host->base + REG_CLOCK_CONTROL) | CLK_OFF,
+		       host->base + REG_CLOCK_CONTROL);
+	}
+
+	kfree(host);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_mmc_match[] = {
+	{ .compatible = "moxa,moxart-mmc" },
+	{ .compatible = "faraday,ftsdc010" },
+	{ }
+};
+
+static struct platform_driver moxart_mmc_driver = {
+	.probe      = moxart_probe,
+	.remove     = moxart_remove,
+	.driver     = {
+		.name		= "mmc-moxart",
+		.owner		= THIS_MODULE,
+		.of_match_table	= moxart_mmc_match,
+	},
+};
+module_platform_driver(moxart_mmc_driver);
+
+MODULE_ALIAS("platform:mmc-moxart");
+MODULE_DESCRIPTION("MOXA ART MMC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
-- 
1.8.2.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v8] mmc: moxart: Add MOXA ART SD/MMC driver
  2014-04-09 13:54             ` [PATCH v8] " Jonas Jensen
@ 2014-04-23 13:37               ` Arnd Bergmann
  2014-04-24 11:52               ` Ulf Hansson
  1 sibling, 0 replies; 12+ messages in thread
From: Arnd Bergmann @ 2014-04-23 13:37 UTC (permalink / raw)
  To: Jonas Jensen
  Cc: chris, ulf.hansson, linux-mmc, cjb, devicetree, linux-arm-kernel,
	linux-kernel, arm, mark.rutland

On Wednesday 09 April 2014, Jonas Jensen wrote:
> Add SD/MMC driver for MOXA ART SoCs.
> 
> The "MOXA ART MMC controller" is likely a faraday "ftsdc010",
> a controller with support in U-Boot:
> 
> http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c
> 
> Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>

Acked-by: Arnd Bergmann <arnd@arndb.de>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v8] mmc: moxart: Add MOXA ART SD/MMC driver
  2014-04-09 13:54             ` [PATCH v8] " Jonas Jensen
  2014-04-23 13:37               ` Arnd Bergmann
@ 2014-04-24 11:52               ` Ulf Hansson
  1 sibling, 0 replies; 12+ messages in thread
From: Ulf Hansson @ 2014-04-24 11:52 UTC (permalink / raw)
  To: Jonas Jensen
  Cc: Chris Ball, linux-mmc, Chris Ball, devicetree, linux-arm-kernel,
	linux-kernel, arm, Mark Rutland, Arnd Bergmann

On 9 April 2014 15:54, Jonas Jensen <jonas.jensen@gmail.com> wrote:
> Add SD/MMC driver for MOXA ART SoCs.
>
> The "MOXA ART MMC controller" is likely a faraday "ftsdc010",
> a controller with support in U-Boot:
>
> http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c
>
> Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>

Acked-by: Ulf Hansson <ulf.hansson@linaro.org>

> ---
>
> Notes:
>     This has been cleaned up further, especially the return value fiasco in v7.
>
>     The "MOXA ART MMC controller" is likely a faraday "ftsdc010" (supported by U-Boot [1]).
>     Registers are the same albeit named slightly different.
>
>     I think this can support other "ftsdc010" SoCs with one reservation for
>     power control, which may need hardware specific changes.
>
>     Changes since v7:
>
>     1. merge code from U-Boot "ftsdc010" driver [1]
>
>        Specifically how it implements status wait, clock divider, fifo width, bus width.
>
>     2. return value cleanup, MMC_ERR_TIMEOUT is no longer used or set at the top of every request
>
>        i.e. the default (no error) return value (0) is respected.
>
>        Error is only set on actual error, "MMC_ERR_NONE" is no longer used.
>
>     3. FIFO size is now read from register (and respected during PIO transfer)
>     4. supported bus width(s) is now read from register
>     5. !(of_dma_request_slave_channel()) return value check is wrong,
>        IS_ERR() must be used.
>     6. clean up and remove unused defines / header includes
>     7. add "faraday,ftsdc010" compatible string (requested by Arnd)
>
>     [1] http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c
>
>     Applies to next-20140409
>
>  .../devicetree/bindings/mmc/moxa,moxart-mmc.txt    |  30 +
>  drivers/mmc/host/Kconfig                           |   9 +
>  drivers/mmc/host/Makefile                          |   1 +
>  drivers/mmc/host/moxart-mmc.c                      | 730 +++++++++++++++++++++
>  4 files changed, 770 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
>  create mode 100644 drivers/mmc/host/moxart-mmc.c
>
> diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> new file mode 100644
> index 0000000..b638191
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> @@ -0,0 +1,30 @@
> +MOXA ART MMC Host Controller Interface
> +
> +  Inherits from mmc binding[1].
> +
> +  [1] Documentation/devicetree/bindings/mmc/mmc.txt
> +
> +Required properties:
> +
> +- compatible : Must be "moxa,moxart-mmc" or "faraday,ftsdc010"
> +- reg :                Should contain registers location and length
> +- interrupts : Should contain the interrupt number
> +- clocks :     Should contain phandle for the clock feeding the MMC controller
> +
> +Optional properties:
> +
> +- dmas :       Should contain two DMA channels, line request number must be 5 for
> +               both channels
> +- dma-names :  Must be "tx", "rx"
> +
> +Example:
> +
> +       mmc: mmc@98e00000 {
> +               compatible = "moxa,moxart-mmc";
> +               reg = <0x98e00000 0x5C>;
> +               interrupts = <5 0>;
> +               clocks = <&clk_apb>;
> +               dmas =  <&dma 5>,
> +                       <&dma 5>;
> +               dma-names = "tx", "rx";
> +       };
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 8aaf8c1..eb93b81 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -283,6 +283,15 @@ config MMC_SDHCI_BCM2835
>
>           If unsure, say N.
>
> +config MMC_MOXART
> +       tristate "MOXART SD/MMC Host Controller support"
> +       depends on ARCH_MOXART && MMC
> +       help
> +         This selects support for the MOXART SD/MMC Host Controller.
> +         MOXA provides one multi-functional card reader which can
> +         be found on some embedded hardware such as UC-7112-LX.
> +         If you have a controller with this interface, say Y here.
> +
>  config MMC_OMAP
>         tristate "TI OMAP Multimedia Card Interface support"
>         depends on ARCH_OMAP
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 0c8aa5e..dfa6ecb 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -50,6 +50,7 @@ obj-$(CONFIG_MMC_JZ4740)      += jz4740_mmc.o
>  obj-$(CONFIG_MMC_VUB300)       += vub300.o
>  obj-$(CONFIG_MMC_USHC)         += ushc.o
>  obj-$(CONFIG_MMC_WMT)          += wmt-sdmmc.o
> +obj-$(CONFIG_MMC_MOXART)       += moxart-mmc.o
>
>  obj-$(CONFIG_MMC_REALTEK_PCI)  += rtsx_pci_sdmmc.o
>
> diff --git a/drivers/mmc/host/moxart-mmc.c b/drivers/mmc/host/moxart-mmc.c
> new file mode 100644
> index 0000000..74924a0
> --- /dev/null
> +++ b/drivers/mmc/host/moxart-mmc.c
> @@ -0,0 +1,730 @@
> +/*
> + * MOXA ART MMC host driver.
> + *
> + * Copyright (C) 2014 Jonas Jensen
> + *
> + * Jonas Jensen <jonas.jensen@gmail.com>
> + *
> + * Based on code from
> + * Moxa Technologies Co., Ltd. <www.moxa.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2.  This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +
> +#include <linux/version.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/blkdev.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmaengine.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/sd.h>
> +#include <linux/sched.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/clk.h>
> +#include <linux/bitops.h>
> +#include <linux/of_dma.h>
> +#include <linux/spinlock.h>
> +
> +#define REG_COMMAND            0
> +#define REG_ARGUMENT           4
> +#define REG_RESPONSE0          8
> +#define REG_RESPONSE1          12
> +#define REG_RESPONSE2          16
> +#define REG_RESPONSE3          20
> +#define REG_RESPONSE_COMMAND   24
> +#define REG_DATA_CONTROL       28
> +#define REG_DATA_TIMER         32
> +#define REG_DATA_LENGTH                36
> +#define REG_STATUS             40
> +#define REG_CLEAR              44
> +#define REG_INTERRUPT_MASK     48
> +#define REG_POWER_CONTROL      52
> +#define REG_CLOCK_CONTROL      56
> +#define REG_BUS_WIDTH          60
> +#define REG_DATA_WINDOW                64
> +#define REG_FEATURE            68
> +#define REG_REVISION           72
> +
> +/* REG_COMMAND */
> +#define CMD_SDC_RESET          BIT(10)
> +#define CMD_EN                 BIT(9)
> +#define CMD_APP_CMD            BIT(8)
> +#define CMD_LONG_RSP           BIT(7)
> +#define CMD_NEED_RSP           BIT(6)
> +#define CMD_IDX_MASK           0x3f
> +
> +/* REG_RESPONSE_COMMAND */
> +#define RSP_CMD_APP            BIT(6)
> +#define RSP_CMD_IDX_MASK       0x3f
> +
> +/* REG_DATA_CONTROL */
> +#define DCR_DATA_FIFO_RESET     BIT(8)
> +#define DCR_DATA_THRES          BIT(7)
> +#define DCR_DATA_EN            BIT(6)
> +#define DCR_DMA_EN             BIT(5)
> +#define DCR_DATA_WRITE         BIT(4)
> +#define DCR_BLK_SIZE           0x0f
> +
> +/* REG_DATA_LENGTH */
> +#define DATA_LEN_MASK          0xffffff
> +
> +/* REG_STATUS */
> +#define WRITE_PROT             BIT(12)
> +#define CARD_DETECT            BIT(11)
> +/* 1-10 below can be sent to either registers, interrupt or clear. */
> +#define CARD_CHANGE            BIT(10)
> +#define FIFO_ORUN              BIT(9)
> +#define FIFO_URUN              BIT(8)
> +#define DATA_END               BIT(7)
> +#define CMD_SENT               BIT(6)
> +#define DATA_CRC_OK            BIT(5)
> +#define RSP_CRC_OK             BIT(4)
> +#define DATA_TIMEOUT           BIT(3)
> +#define RSP_TIMEOUT            BIT(2)
> +#define DATA_CRC_FAIL          BIT(1)
> +#define RSP_CRC_FAIL           BIT(0)
> +
> +#define MASK_RSP               (RSP_TIMEOUT | RSP_CRC_FAIL | \
> +                                RSP_CRC_OK  | CARD_DETECT  | CMD_SENT)
> +
> +#define MASK_DATA              (DATA_CRC_OK   | DATA_END | \
> +                                DATA_CRC_FAIL | DATA_TIMEOUT)
> +
> +#define MASK_INTR_PIO          (FIFO_URUN | FIFO_ORUN | CARD_CHANGE)
> +
> +/* REG_POWER_CONTROL */
> +#define SD_POWER_ON            BIT(4)
> +#define SD_POWER_MASK          0x0f
> +
> +/* REG_CLOCK_CONTROL */
> +#define CLK_HISPD              BIT(9)
> +#define CLK_OFF                        BIT(8)
> +#define CLK_SD                 BIT(7)
> +#define CLK_DIV_MASK           0x7f
> +
> +/* REG_BUS_WIDTH */
> +#define BUS_WIDTH_8            BIT(2)
> +#define BUS_WIDTH_4            BIT(1)
> +#define BUS_WIDTH_1            BIT(0)
> +
> +#define MMC_VDD_360            23
> +#define MIN_POWER              (MMC_VDD_360 - SD_POWER_MASK)
> +#define MAX_RETRIES            500000
> +
> +struct moxart_host {
> +       spinlock_t                      lock;
> +
> +       void __iomem                    *base;
> +
> +       phys_addr_t                     reg_phys;
> +
> +       struct dma_chan                 *dma_chan_tx;
> +       struct dma_chan                 *dma_chan_rx;
> +       struct dma_async_tx_descriptor  *tx_desc;
> +       struct mmc_host                 *mmc;
> +       struct mmc_request              *mrq;
> +       struct scatterlist              *cur_sg;
> +       struct completion               dma_complete;
> +       struct completion               pio_complete;
> +
> +       u32                             num_sg;
> +       u32                             data_remain;
> +       u32                             data_len;
> +       u32                             fifo_width;
> +       u32                             timeout;
> +       u32                             rate;
> +
> +       long                            sysclk;
> +
> +       bool                            have_dma;
> +       bool                            is_removed;
> +};
> +
> +static inline void moxart_init_sg(struct moxart_host *host,
> +                                 struct mmc_data *data)
> +{
> +       host->cur_sg = data->sg;
> +       host->num_sg = data->sg_len;
> +       host->data_remain = host->cur_sg->length;
> +
> +       if (host->data_remain > host->data_len)
> +               host->data_remain = host->data_len;
> +}
> +
> +static inline int moxart_next_sg(struct moxart_host *host)
> +{
> +       int remain;
> +       struct mmc_data *data = host->mrq->cmd->data;
> +
> +       host->cur_sg++;
> +       host->num_sg--;
> +
> +       if (host->num_sg > 0) {
> +               host->data_remain = host->cur_sg->length;
> +               remain = host->data_len - data->bytes_xfered;
> +               if (remain > 0 && remain < host->data_remain)
> +                       host->data_remain = remain;
> +       }
> +
> +       return host->num_sg;
> +}
> +
> +static int moxart_wait_for_status(struct moxart_host *host,
> +                                 u32 mask, u32 *status)
> +{
> +       int ret = -ETIMEDOUT;
> +       u32 i;
> +
> +       for (i = 0; i < MAX_RETRIES; i++) {
> +               *status = readl(host->base + REG_STATUS);
> +               if (!(*status & mask)) {
> +                       udelay(5);
> +                       continue;
> +               }
> +               writel(*status & mask, host->base + REG_CLEAR);
> +               ret = 0;
> +               break;
> +       }
> +
> +       if (ret)
> +               dev_err(mmc_dev(host->mmc), "timed out waiting for status\n");
> +
> +       return ret;
> +}
> +
> +
> +static void moxart_send_command(struct moxart_host *host,
> +       struct mmc_command *cmd)
> +{
> +       u32 status, cmdctrl;
> +
> +       writel(RSP_TIMEOUT  | RSP_CRC_OK |
> +              RSP_CRC_FAIL | CMD_SENT, host->base + REG_CLEAR);
> +       writel(cmd->arg, host->base + REG_ARGUMENT);
> +
> +       cmdctrl = cmd->opcode & CMD_IDX_MASK;
> +       if (cmdctrl == SD_APP_SET_BUS_WIDTH    || cmdctrl == SD_APP_OP_COND   ||
> +           cmdctrl == SD_APP_SEND_SCR         || cmdctrl == SD_APP_SD_STATUS ||
> +           cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
> +               cmdctrl |= CMD_APP_CMD;
> +
> +       if (cmd->flags & MMC_RSP_PRESENT)
> +               cmdctrl |= CMD_NEED_RSP;
> +
> +       if (cmd->flags & MMC_RSP_136)
> +               cmdctrl |= CMD_LONG_RSP;
> +
> +       writel(cmdctrl | CMD_EN, host->base + REG_COMMAND);
> +
> +       if (moxart_wait_for_status(host, MASK_RSP, &status) == -ETIMEDOUT)
> +               cmd->error = -ETIMEDOUT;
> +
> +       if (status & RSP_TIMEOUT) {
> +               cmd->error = -ETIMEDOUT;
> +               return;
> +       }
> +       if (status & RSP_CRC_FAIL) {
> +               cmd->error = -EIO;
> +               return;
> +       }
> +       if (status & RSP_CRC_OK) {
> +               if (cmd->flags & MMC_RSP_136) {
> +                       cmd->resp[3] = readl(host->base + REG_RESPONSE0);
> +                       cmd->resp[2] = readl(host->base + REG_RESPONSE1);
> +                       cmd->resp[1] = readl(host->base + REG_RESPONSE2);
> +                       cmd->resp[0] = readl(host->base + REG_RESPONSE3);
> +               } else {
> +                       cmd->resp[0] = readl(host->base + REG_RESPONSE0);
> +               }
> +       }
> +}
> +
> +static void moxart_dma_complete(void *param)
> +{
> +       struct moxart_host *host = param;
> +
> +       complete(&host->dma_complete);
> +}
> +
> +static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
> +{
> +       u32 len, dir_data, dir_slave;
> +       unsigned long dma_time;
> +       struct dma_async_tx_descriptor *desc = NULL;
> +       struct dma_chan *dma_chan;
> +
> +       if (host->data_len == data->bytes_xfered)
> +               return;
> +
> +       if (data->flags & MMC_DATA_WRITE) {
> +               dma_chan = host->dma_chan_tx;
> +               dir_data = DMA_TO_DEVICE;
> +               dir_slave = DMA_MEM_TO_DEV;
> +       } else {
> +               dma_chan = host->dma_chan_rx;
> +               dir_data = DMA_FROM_DEVICE;
> +               dir_slave = DMA_DEV_TO_MEM;
> +       }
> +
> +       len = dma_map_sg(dma_chan->device->dev, data->sg,
> +                        data->sg_len, dir_data);
> +
> +       if (len > 0) {
> +               desc = dmaengine_prep_slave_sg(dma_chan, data->sg,
> +                                              len, dir_slave,
> +                                              DMA_PREP_INTERRUPT |
> +                                              DMA_CTRL_ACK);
> +       } else {
> +               dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n");
> +       }
> +
> +       if (desc) {
> +               host->tx_desc = desc;
> +               desc->callback = moxart_dma_complete;
> +               desc->callback_param = host;
> +               dmaengine_submit(desc);
> +               dma_async_issue_pending(dma_chan);
> +       }
> +
> +       data->bytes_xfered += host->data_remain;
> +
> +       dma_time = wait_for_completion_interruptible_timeout(
> +                  &host->dma_complete, host->timeout);
> +
> +       dma_unmap_sg(dma_chan->device->dev,
> +                    data->sg, data->sg_len,
> +                    dir_data);
> +}
> +
> +
> +static void moxart_transfer_pio(struct moxart_host *host)
> +{
> +       struct mmc_data *data = host->mrq->cmd->data;
> +       u32 *sgp, len = 0, remain, status;
> +
> +       if (host->data_len == data->bytes_xfered)
> +               return;
> +
> +       sgp = sg_virt(host->cur_sg);
> +       remain = host->data_remain;
> +
> +       if (data->flags & MMC_DATA_WRITE) {
> +               while (remain > 0) {
> +                       if (moxart_wait_for_status(host, FIFO_URUN, &status)
> +                            == -ETIMEDOUT) {
> +                               data->error = -ETIMEDOUT;
> +                               complete(&host->pio_complete);
> +                               return;
> +                       }
> +                       for (len = 0; len < remain && len < host->fifo_width;) {
> +                               iowrite32(*sgp, host->base + REG_DATA_WINDOW);
> +                               sgp++;
> +                               len += 4;
> +                       }
> +                       remain -= len;
> +               }
> +
> +       } else {
> +               while (remain > 0) {
> +                       if (moxart_wait_for_status(host, FIFO_ORUN, &status)
> +                           == -ETIMEDOUT) {
> +                               data->error = -ETIMEDOUT;
> +                               complete(&host->pio_complete);
> +                               return;
> +                       }
> +                       for (len = 0; len < remain && len < host->fifo_width;) {
> +                               /* SCR data must be read in big endian. */
> +                               if (data->mrq->cmd->opcode == SD_APP_SEND_SCR)
> +                                       *sgp = ioread32be(host->base +
> +                                                         REG_DATA_WINDOW);
> +                               else
> +                                       *sgp = ioread32(host->base +
> +                                                       REG_DATA_WINDOW);
> +                               sgp++;
> +                               len += 4;
> +                       }
> +                       remain -= len;
> +               }
> +       }
> +
> +       data->bytes_xfered += host->data_remain - remain;
> +       host->data_remain = remain;
> +
> +       if (host->data_len != data->bytes_xfered)
> +               moxart_next_sg(host);
> +       else
> +               complete(&host->pio_complete);
> +}
> +
> +static void moxart_prepare_data(struct moxart_host *host)
> +{
> +       struct mmc_data *data = host->mrq->cmd->data;
> +       u32 datactrl;
> +       int blksz_bits;
> +
> +       if (!data)
> +               return;
> +
> +       host->data_len = data->blocks * data->blksz;
> +       blksz_bits = ffs(data->blksz) - 1;
> +       BUG_ON(1 << blksz_bits != data->blksz);
> +
> +       moxart_init_sg(host, data);
> +
> +       datactrl = DCR_DATA_EN | (blksz_bits & DCR_BLK_SIZE);
> +
> +       if (data->flags & MMC_DATA_WRITE)
> +               datactrl |= DCR_DATA_WRITE;
> +
> +       if ((host->data_len > host->fifo_width) && host->have_dma)
> +               datactrl |= DCR_DMA_EN;
> +
> +       writel(DCR_DATA_FIFO_RESET, host->base + REG_DATA_CONTROL);
> +       writel(MASK_DATA | FIFO_URUN | FIFO_ORUN, host->base + REG_CLEAR);
> +       writel(host->rate, host->base + REG_DATA_TIMER);
> +       writel(host->data_len, host->base + REG_DATA_LENGTH);
> +       writel(datactrl, host->base + REG_DATA_CONTROL);
> +}
> +
> +static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> +       struct moxart_host *host = mmc_priv(mmc);
> +       unsigned long pio_time, flags;
> +       u32 status;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       init_completion(&host->dma_complete);
> +       init_completion(&host->pio_complete);
> +
> +       host->mrq = mrq;
> +
> +       if (readl(host->base + REG_STATUS) & CARD_DETECT) {
> +               mrq->cmd->error = -ETIMEDOUT;
> +               goto request_done;
> +       }
> +
> +       moxart_prepare_data(host);
> +       moxart_send_command(host, host->mrq->cmd);
> +
> +       if (mrq->cmd->data) {
> +               if ((host->data_len > host->fifo_width) && host->have_dma) {
> +
> +                       writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
> +
> +                       spin_unlock_irqrestore(&host->lock, flags);
> +
> +                       moxart_transfer_dma(mrq->cmd->data, host);
> +
> +                       spin_lock_irqsave(&host->lock, flags);
> +               } else {
> +
> +                       writel(MASK_INTR_PIO, host->base + REG_INTERRUPT_MASK);
> +
> +                       spin_unlock_irqrestore(&host->lock, flags);
> +
> +                       /* PIO transfers start from interrupt. */
> +                       pio_time = wait_for_completion_interruptible_timeout(
> +                                  &host->pio_complete, host->timeout);
> +
> +                       spin_lock_irqsave(&host->lock, flags);
> +               }
> +
> +               if (host->is_removed) {
> +                       dev_err(mmc_dev(host->mmc), "card removed\n");
> +                       mrq->cmd->error = -ETIMEDOUT;
> +                       goto request_done;
> +               }
> +
> +               if (moxart_wait_for_status(host, MASK_DATA, &status)
> +                   == -ETIMEDOUT) {
> +                       mrq->cmd->data->error = -ETIMEDOUT;
> +                       goto request_done;
> +               }
> +
> +               if (status & DATA_CRC_FAIL)
> +                       mrq->cmd->data->error = -ETIMEDOUT;
> +
> +               if (mrq->cmd->data->stop)
> +                       moxart_send_command(host, mrq->cmd->data->stop);
> +       }
> +
> +request_done:
> +       spin_unlock_irqrestore(&host->lock, flags);
> +       mmc_request_done(host->mmc, mrq);
> +}
> +
> +static irqreturn_t moxart_irq(int irq, void *devid)
> +{
> +       struct moxart_host *host = (struct moxart_host *)devid;
> +       u32 status;
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       status = readl(host->base + REG_STATUS);
> +       if (status & CARD_CHANGE) {
> +               host->is_removed = status & CARD_DETECT;
> +               if (host->is_removed && host->have_dma) {
> +                       dmaengine_terminate_all(host->dma_chan_tx);
> +                       dmaengine_terminate_all(host->dma_chan_rx);
> +               }
> +               host->mrq = NULL;
> +               writel(MASK_INTR_PIO, host->base + REG_CLEAR);
> +               writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
> +               mmc_detect_change(host->mmc, 0);
> +       }
> +       if (status & (FIFO_ORUN | FIFO_URUN) && host->mrq)
> +               moxart_transfer_pio(host);
> +
> +       spin_unlock_irqrestore(&host->lock, flags);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> +       struct moxart_host *host = mmc_priv(mmc);
> +       unsigned long flags;
> +       u8 power, div;
> +       u32 ctrl;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       if (ios->clock) {
> +               for (div = 0; div < CLK_DIV_MASK; ++div) {
> +                       if (ios->clock >= host->sysclk / (2 * (div + 1)))
> +                               break;
> +               }
> +               ctrl = CLK_SD | div;
> +               host->rate = host->sysclk / (2 * (div + 1));
> +               if (host->rate > host->sysclk)
> +                       ctrl |= CLK_HISPD;
> +               writel(ctrl, host->base + REG_CLOCK_CONTROL);
> +       }
> +
> +       if (ios->power_mode == MMC_POWER_OFF) {
> +               writel(readl(host->base + REG_POWER_CONTROL) & ~SD_POWER_ON,
> +                      host->base + REG_POWER_CONTROL);
> +       } else {
> +               if (ios->vdd < MIN_POWER)
> +                       power = 0;
> +               else
> +                       power = ios->vdd - MIN_POWER;
> +
> +               writel(SD_POWER_ON | (u32) power,
> +                      host->base + REG_POWER_CONTROL);
> +       }
> +
> +       switch (ios->bus_width) {
> +       case MMC_BUS_WIDTH_4:
> +               writel(BUS_WIDTH_4, host->base + REG_BUS_WIDTH);
> +               break;
> +       case MMC_BUS_WIDTH_8:
> +               writel(BUS_WIDTH_8, host->base + REG_BUS_WIDTH);
> +               break;
> +       default:
> +               writel(BUS_WIDTH_1, host->base + REG_BUS_WIDTH);
> +               break;
> +       }
> +
> +       spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +
> +static int moxart_get_ro(struct mmc_host *mmc)
> +{
> +       struct moxart_host *host = mmc_priv(mmc);
> +
> +       return !!(readl(host->base + REG_STATUS) & WRITE_PROT);
> +}
> +
> +static struct mmc_host_ops moxart_ops = {
> +       .request = moxart_request,
> +       .set_ios = moxart_set_ios,
> +       .get_ro = moxart_get_ro,
> +};
> +
> +static int moxart_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +       struct resource res_mmc;
> +       struct mmc_host *mmc;
> +       struct moxart_host *host = NULL;
> +       struct dma_slave_config cfg;
> +       struct clk *clk;
> +       void __iomem *reg_mmc;
> +       dma_cap_mask_t mask;
> +       int irq, ret;
> +       u32 i;
> +
> +       mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
> +       if (!mmc) {
> +               dev_err(dev, "mmc_alloc_host failed\n");
> +               ret = -ENOMEM;
> +               goto out;
> +       }
> +
> +       ret = of_address_to_resource(node, 0, &res_mmc);
> +       if (ret) {
> +               dev_err(dev, "of_address_to_resource failed\n");
> +               goto out;
> +       }
> +
> +       irq = irq_of_parse_and_map(node, 0);
> +       if (irq <= 0) {
> +               dev_err(dev, "irq_of_parse_and_map failed\n");
> +               ret = -EINVAL;
> +               goto out;
> +       }
> +
> +       clk = of_clk_get(node, 0);
> +       if (IS_ERR(clk)) {
> +               dev_err(dev, "of_clk_get failed\n");
> +               ret = PTR_ERR(clk);
> +               goto out;
> +       }
> +
> +       reg_mmc = devm_ioremap_resource(dev, &res_mmc);
> +       if (IS_ERR(reg_mmc)) {
> +               ret = PTR_ERR(reg_mmc);
> +               goto out;
> +       }
> +
> +       mmc_of_parse(mmc);
> +
> +       dma_cap_zero(mask);
> +       dma_cap_set(DMA_SLAVE, mask);
> +
> +       host = mmc_priv(mmc);
> +       host->mmc = mmc;
> +       host->base = reg_mmc;
> +       host->reg_phys = res_mmc.start;
> +       host->timeout = msecs_to_jiffies(1000);
> +       host->sysclk = clk_get_rate(clk);
> +       host->fifo_width = readl(host->base + REG_FEATURE) << 2;
> +       host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
> +       host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
> +
> +       spin_lock_init(&host->lock);
> +
> +       mmc->ops = &moxart_ops;
> +       mmc->f_max = DIV_ROUND_CLOSEST(host->sysclk, 2);
> +       mmc->f_min = DIV_ROUND_CLOSEST(host->sysclk, CLK_DIV_MASK * 2);
> +       mmc->ocr_avail = 0xffff00;      /* Support 2.0v - 3.6v power. */
> +
> +       if (IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) {
> +               dev_dbg(dev, "PIO mode transfer enabled\n");
> +               host->have_dma = false;
> +       } else {
> +               dev_dbg(dev, "DMA channels found (%p,%p)\n",
> +                        host->dma_chan_tx, host->dma_chan_rx);
> +               host->have_dma = true;
> +
> +               cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +               cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +
> +               cfg.direction = DMA_MEM_TO_DEV;
> +               cfg.src_addr = 0;
> +               cfg.dst_addr = host->reg_phys + REG_DATA_WINDOW;
> +               dmaengine_slave_config(host->dma_chan_tx, &cfg);
> +
> +               cfg.direction = DMA_DEV_TO_MEM;
> +               cfg.src_addr = host->reg_phys + REG_DATA_WINDOW;
> +               cfg.dst_addr = 0;
> +               dmaengine_slave_config(host->dma_chan_rx, &cfg);
> +       }
> +
> +       switch ((readl(host->base + REG_BUS_WIDTH) >> 3) & 3) {
> +       case 1:
> +               mmc->caps |= MMC_CAP_4_BIT_DATA;
> +               break;
> +       case 2:
> +               mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
> +               break;
> +       default:
> +               break;
> +       }
> +
> +       writel(0, host->base + REG_INTERRUPT_MASK);
> +
> +       writel(CMD_SDC_RESET, host->base + REG_COMMAND);
> +       for (i = 0; i < MAX_RETRIES; i++) {
> +               if (!(readl(host->base + REG_COMMAND) & CMD_SDC_RESET))
> +                       break;
> +               udelay(5);
> +       }
> +
> +       ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
> +       if (ret)
> +               goto out;
> +
> +       dev_set_drvdata(dev, mmc);
> +       mmc_add_host(mmc);
> +
> +       dev_dbg(dev, "IRQ=%d, FIFO is %d bytes\n", irq, host->fifo_width);
> +
> +       return 0;
> +
> +out:
> +       if (mmc)
> +               mmc_free_host(mmc);
> +       return ret;
> +}
> +
> +static int moxart_remove(struct platform_device *pdev)
> +{
> +       struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
> +       struct moxart_host *host = mmc_priv(mmc);
> +
> +       dev_set_drvdata(&pdev->dev, NULL);
> +
> +       if (mmc) {
> +               if (!IS_ERR(host->dma_chan_tx))
> +                       dma_release_channel(host->dma_chan_tx);
> +               if (!IS_ERR(host->dma_chan_rx))
> +                       dma_release_channel(host->dma_chan_rx);
> +               mmc_remove_host(mmc);
> +               mmc_free_host(mmc);
> +
> +               writel(0, host->base + REG_INTERRUPT_MASK);
> +               writel(0, host->base + REG_POWER_CONTROL);
> +               writel(readl(host->base + REG_CLOCK_CONTROL) | CLK_OFF,
> +                      host->base + REG_CLOCK_CONTROL);
> +       }
> +
> +       kfree(host);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id moxart_mmc_match[] = {
> +       { .compatible = "moxa,moxart-mmc" },
> +       { .compatible = "faraday,ftsdc010" },
> +       { }
> +};
> +
> +static struct platform_driver moxart_mmc_driver = {
> +       .probe      = moxart_probe,
> +       .remove     = moxart_remove,
> +       .driver     = {
> +               .name           = "mmc-moxart",
> +               .owner          = THIS_MODULE,
> +               .of_match_table = moxart_mmc_match,
> +       },
> +};
> +module_platform_driver(moxart_mmc_driver);
> +
> +MODULE_ALIAS("platform:mmc-moxart");
> +MODULE_DESCRIPTION("MOXA ART MMC driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
> --
> 1.8.2.1
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2014-04-24 11:52 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-07-10  9:28 [PATCH] mmc: sdhci-moxart: Add MOXA ART SDHCI driver Jonas Jensen
2013-07-17 12:51 ` [PATCH v2] " Jonas Jensen
2013-07-29 10:49   ` [PATCH v3] " Jonas Jensen
2013-07-30  9:08     ` Mark Rutland
2013-08-06 13:57     ` [PATCH v4] " Jonas Jensen
2013-12-11 12:03       ` [PATCH v5] " Jonas Jensen
2014-01-17  9:43         ` [PATCH v6] " Jonas Jensen
2014-01-17 14:40           ` Arnd Bergmann
2014-01-21 10:52           ` [PATCH v7] mmc: moxart: Add MOXA ART SD/MMC driver Jonas Jensen
2014-04-09 13:54             ` [PATCH v8] " Jonas Jensen
2014-04-23 13:37               ` Arnd Bergmann
2014-04-24 11:52               ` Ulf Hansson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).