diff mbox

[v8] mmc: moxart: Add MOXA ART SD/MMC driver

Message ID 1397051651-29501-1-git-send-email-jonas.jensen@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jonas Jensen April 9, 2014, 1:54 p.m. UTC
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

Comments

Arnd Bergmann April 23, 2014, 1:37 p.m. UTC | #1
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>
Ulf Hansson April 24, 2014, 11:52 a.m. UTC | #2
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
>
diff mbox

Patch

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>");