This patch adds the MMC/SD/SDIO Arasan host controller.
Advanced DMA, Single DMA and PIO modes are supported.
The former is the default.
This has been tested on the 7108/06 STM platforms.

Signed-off-by: Giuseppe Cavallaro <[email protected]>
---
 drivers/mmc/host/Kconfig        |    7 +
 drivers/mmc/host/Makefile       |    1 +
 drivers/mmc/host/arasan.c       | 1354 +++++++++++++++++++++++++++++++++++++++
 drivers/mmc/host/arasan.h       |  237 +++++++
 include/linux/mmc/arasan_plat.h |   49 ++
 5 files changed, 1648 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/host/arasan.c
 create mode 100644 drivers/mmc/host/arasan.h
 create mode 100644 include/linux/mmc/arasan_plat.h

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f06d06e..f403f1d 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -432,3 +432,10 @@ config MMC_SH_MMCIF
          This selects the MMC Host Interface controler (MMCIF).
 
          This driver supports MMCIF in sh7724/sh7757/sh7372.
+
+config MMC_ARASAN
+       tristate "Arasan MMC/SD/SDIO host driver"
+       depends on CPU_SUBTYPE_ST40
+       help
+         This selects the Arasan MMC/SD/SDIO host controller integrated
+         in the STMicroelectronics platforms (stx7108 and stx7106).
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e30c2ee..8337f15 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710)       += cb710-mmc.o
 obj-$(CONFIG_MMC_VIA_SDMMC)    += via-sdmmc.o
 obj-$(CONFIG_SDH_BFIN)         += bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)     += sh_mmcif.o
+obj-$(CONFIG_MMC_ARASAN)       += arasan.o
 
 obj-$(CONFIG_MMC_SDHCI_OF)     += sdhci-of.o
 sdhci-of-y                             := sdhci-of-core.o
diff --git a/drivers/mmc/host/arasan.c b/drivers/mmc/host/arasan.c
new file mode 100644
index 0000000..7dc49ae
--- /dev/null
+++ b/drivers/mmc/host/arasan.c
@@ -0,0 +1,1354 @@
+/*
+ * Arasan MMC/SD/SDIO driver
+ *
+ *  This is the driver for the Arasan MMC/SD/SDIO host controller
+ *  integrated in the STMicroelectronics platforms
+ *
+ * Author: Giuseppe Cavallaro <[email protected]>
+ * Copyright (C) 2010 STMicroelectronics Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/mbus.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/irq.h>
+#include <linux/highmem.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/arasan_plat.h>
+
+#include <asm/sizes.h>
+#include <asm/unaligned.h>
+
+#include "arasan.h"
+
+/* To enable more debug information. */
+#undef ARASAN_DEBUG
+/*#define ARASAN_DEBUG*/
+#ifdef ARASAN_DEBUG
+#define DBG(fmt, args...)  pr_info(fmt, ## args)
+#else
+#define DBG(fmt, args...)  do { } while (0)
+#endif
+
+static int maxfreq = ARASAN_CLOCKRATE_MAX;
+module_param(maxfreq, int, S_IRUGO);
+MODULE_PARM_DESC(maxfreq, "Maximum card clock frequency (default 50MHz)");
+
+static unsigned int adma = 1;
+module_param(adma, int, S_IRUGO);
+MODULE_PARM_DESC(adma, "Disable/Enable the Advanced DMA mode");
+
+static unsigned int led;
+module_param(led, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(led, "Enable|Disable LED");
+
+static unsigned int pio;
+module_param(pio, int, S_IRUGO);
+MODULE_PARM_DESC(pio, "PIO mode (no DMA)");
+
+struct arasan_cap {
+       unsigned int timer_freq;
+       unsigned int timer_unit;
+       unsigned int base_clk_sd;
+       unsigned int max_blk_len;
+       unsigned int adma2;
+       unsigned int high_speed;
+       unsigned int sdma;
+       unsigned int suspend;
+       unsigned int voltage33;
+       unsigned int voltage30;
+       unsigned int voltage18;
+       unsigned int int_mode;
+       unsigned int spi;
+       unsigned int spi_block;
+};
+
+struct arasan_host {
+       void __iomem *base;
+       struct mmc_request *mrq;
+       unsigned int intr_en;
+       u8 ctrl;
+       unsigned int sg_frags;
+       struct timer_list timer;
+       struct mmc_host *mmc;
+       struct device *dev;
+       struct resource *res;
+       int irq;
+       struct arasan_cap cap;
+       u8 vdd;
+       unsigned int freq;
+       unsigned int status;
+       unsigned int adma;
+       unsigned int use_pio;
+       u16 pio_blksz;
+       u32 pio_blocks;
+       u32 *pio_blkbuf;
+       spinlock_t lock;
+       struct tasklet_struct card_tasklet;
+       u8 *adma_desc;
+       dma_addr_t adma_addr;
+       int need_poll;
+};
+
+static inline void arsan_sw_reset(struct arasan_host *host, unsigned int flag)
+{
+       /* After completing the reset, wait the HC clears these bits */
+       if (likely(flag == reset_all)) {
+               writeb(ARSAN_RESET_ALL, host->base + ARASAN_SW_RESET);
+               do { } while ((readb(host->base + ARASAN_SW_RESET)) &
+                        ARSAN_RESET_ALL);
+       } else if (flag == reset_cmd_line) {
+               writeb(ARSAN_RESET_CMD_LINE, host->base + ARASAN_SW_RESET);
+               do { } while ((readb(host->base + ARASAN_SW_RESET)) &
+                        ARSAN_RESET_CMD_LINE);
+
+       } else if (flag == reset_dat_line) {
+               writeb(ARSAN_RESET_DAT_LINE, host->base + ARASAN_SW_RESET);
+               do { } while ((readb(host->base + ARASAN_SW_RESET)) &
+                        ARSAN_RESET_DAT_LINE);
+       }
+}
+
+static inline void arsan_hc_version(struct arasan_host *host)
+{
+       u16 version;
+
+       version = readw(host->base + ARASAN_HOST_VERSION);
+       pr_debug("Arasan MMC/SDIO:\n\tHC Vendor Version Number: %d\n",
+                (version >> 8));
+       pr_debug("\tHC SPEC Version Number: %d\n", (version & 0x00ff));
+}
+
+static void arasan_capabilities(struct arasan_host *host)
+{
+       unsigned int cap;
+       unsigned int max_blk_len;
+
+       cap = readl(host->base + ARASAN_CAPABILITIES);
+
+       pr_debug("\tArasan capabilities: 0x%x\n", cap);
+
+       host->cap.timer_freq = cap & 0x3f;
+       host->cap.timer_unit = (cap >> 7) & 0x1;
+
+       pr_debug("\tTimeout Clock Freq: %d %s\n", host->cap.timer_freq,
+                host->cap.timer_unit ? "MHz" : "KHz");
+
+       host->cap.base_clk_sd = (cap >> 8) & 0x3f;
+       pr_debug("\tBase Clock Freq for SD: %d MHz\n", host->cap.base_clk_sd);
+
+       max_blk_len = (cap >> 16) & 0x3;
+       switch (max_blk_len) {
+       case 0:
+               host->cap.max_blk_len = 512;
+               break;
+       case 1:
+               host->cap.max_blk_len = 1024;
+               break;
+       case 2:
+               host->cap.max_blk_len = 2048;
+               break;
+       case 3:
+               host->cap.max_blk_len = 4096;
+               break;
+       default:
+               break;
+       }
+       pr_debug("\tMax Block size: %d bytes\n", host->cap.max_blk_len);
+
+       host->cap.adma2 = (cap >> 19) & 0x1;
+       host->cap.high_speed = (cap >> 21) & 0x1;
+       host->cap.sdma = (cap >> 22) & 0x1;
+
+       pr_debug("\tadma2 %s, high speed %s, sdma %s\n",
+                host->cap.adma2 ? "Yes" : "Not",
+                host->cap.high_speed ? "Yes" : "Not",
+                host->cap.sdma ? "Yes" : "Not");
+
+       host->cap.suspend = (cap >> 23) & 0x1;
+       pr_debug("\tsuspend/resume %s suported\n",
+                host->cap.adma2 ? "is" : "Not");
+
+       /* Disable adma user option if cap not supported. */
+       if (!host->cap.adma2)
+               adma = 0;
+
+       host->cap.voltage33 = (cap >> 24) & 0x1;
+       host->cap.voltage30 = (cap >> 25) & 0x1;
+       host->cap.voltage18 = (cap >> 26) & 0x1;
+       host->cap.int_mode = (cap >> 27) & 0x1;
+       host->cap.spi = (cap >> 29) & 0x1;
+       host->cap.spi_block = (cap >> 30) & 0x1;
+
+       if (host->cap.voltage33)
+               pr_debug("\t3.3V voltage suported\n");
+       if (host->cap.voltage30)
+               pr_debug("\t3.0V voltage suported\n");
+       if (host->cap.voltage18)
+               pr_debug("\t1.8V voltage suported\n");
+
+       if (host->cap.int_mode)
+               pr_debug("\tInterrupt Mode supported\n");
+       if (host->cap.spi)
+               pr_debug("\tSPI Mode supported\n");
+       if (host->cap.spi_block)
+               pr_debug("\tSPI Block Mode supported\n");
+}
+
+static void arasan_ctrl_led(struct arasan_host *host, unsigned int flag)
+{
+       if (led) {
+               u8 ctrl_reg = readb(host->base + ARASAN_HOST_CTRL);
+
+               if (flag)
+                       ctrl_reg |= ARASAN_HOST_CTRL_LED;
+               else
+                       ctrl_reg &= ~ARASAN_HOST_CTRL_LED;
+
+               host->ctrl = ctrl_reg;
+               writeb(host->ctrl, host->base + ARASAN_HOST_CTRL);
+       }
+}
+
+static inline void arasan_set_interrupts(struct arasan_host *host)
+{
+       host->intr_en = ARASAN_IRQ_DEFAULT_MASK;
+       writel(host->intr_en, host->base + ARASAN_NORMAL_INT_STATUS_EN);
+       writel(host->intr_en, host->base + ARASAN_NORMAL_INT_SIGN_EN);
+}
+
+static inline void arasan_clear_interrupts(struct arasan_host *host)
+{
+       writel(0, host->base + ARASAN_NORMAL_INT_STATUS_EN);
+       writel(0, host->base + ARASAN_ERR_INT_STATUS_EN);
+       writel(0, host->base + ARASAN_NORMAL_INT_SIGN_EN);
+}
+
+static void arasan_power_set(struct arasan_host *host, unsigned int pwr, u8 
vdd)
+{
+       u8 pwr_reg;
+
+       pwr_reg = readb(host->base + ARASAN_PWR_CTRL);
+
+       host->vdd = (1 << vdd);
+
+       if (pwr) {
+               pwr_reg &= 0xf1;
+
+               if ((host->vdd & MMC_VDD_165_195) && host->cap.voltage18)
+                       pwr_reg |= ARASAN_PWR_BUS_VOLTAGE_18;
+               else if ((host->vdd & MMC_VDD_29_30) && host->cap.voltage30)
+                       pwr_reg |= ARASAN_PWR_BUS_VOLTAGE_30;
+               else if ((host->vdd & MMC_VDD_32_33) && host->cap.voltage33)
+                       pwr_reg |= ARASAN_PWR_BUS_VOLTAGE_33;
+
+               pwr_reg |= ARASAN_PWR_CTRL_UP;
+       } else
+               pwr_reg &= ~ARASAN_PWR_CTRL_UP;
+
+       DBG("%s: pwr_reg 0x%x, host->vdd = 0x%x\n", __func__, pwr_reg,
+           host->vdd);
+       writeb(pwr_reg, host->base + ARASAN_PWR_CTRL);
+}
+
+static int arasan_test_card(struct arasan_host *host)
+{
+       unsigned int ret = 0;
+       u32 present = readl(host->base + ARASAN_PRESENT_STATE);
+       if (likely(!(present & ARASAN_PRESENT_STATE_CARD_PRESENT)))
+               ret = -1;
+
+#ifdef ARASAN_DEBUG
+       if (present & ARASAN_PRESENT_STATE_CARD_STABLE)
+               pr_info("\tcard stable...");
+       if (!(present & ARASAN_PRESENT_STATE_WR_EN))
+               pr_info("\tcard Write protected...");
+       if (present & ARASAN_PRESENT_STATE_BUFFER_RD_EN)
+               pr_info("\tPIO Read Enable...");
+       if (present & ARASAN_PRESENT_STATE_BUFFER_WR_EN)
+               pr_info("\tPIO Write Enable...");
+       if (present & ARASAN_PRESENT_STATE_RD_ACTIVE)
+               pr_info("\tRead Xfer data...");
+       if (present & ARASAN_PRESENT_STATE_WR_ACTIVE)
+               pr_info("\tWrite Xfer data...");
+       if (present & ARASAN_PRESENT_STATE_DAT_ACTIVE)
+               pr_info("\tDAT line active...");
+#endif
+       return ret;
+}
+static void arasan_set_clock(struct arasan_host *host, unsigned int freq)
+{
+       u16 clock = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       if ((host->freq != freq) && (freq)) {
+               u16 divisor;
+
+               /* Ensure clock is off before making any changes */
+               writew(clock, host->base + ARASAN_CLOCK_CTRL);
+
+               /* core checks if this is a good freq < max_freq */
+               host->freq = freq;
+
+               DBG("%s:\n\tnew freq %d", __func__, host->freq);
+
+               /* Work out divisor for specified clock frequency */
+               for (divisor = 1; divisor <= 256; divisor *= 2)
+                       /* Find first divisor producing a frequency less
+                        * than or equal to MHz */
+                       if ((maxfreq / divisor) <= freq)
+                               break;
+
+               DBG("\tdivisor %d", divisor);
+               /* Set the clock divisor and enable the internal clock */
+               clock = divisor << (ARASAN_CLOCK_CTRL_SDCLK_SHIFT);
+               clock &= ARASAN_CLOCK_CTRL_SDCLK_MASK;
+               clock |= ARASAN_CLOCK_CTRL_ICLK_ENABLE;
+               writew(clock, host->base + ARASAN_CLOCK_CTRL);
+
+               /* Busy wait for the clock to become stable */
+               do { } while (((readw(host->base + ARASAN_CLOCK_CTRL)) &
+                         ARASAN_CLOCK_CTRL_ICLK_STABLE) == 0);
+
+               /* Enable the SD clock */
+               clock |= ARASAN_CLOCK_CTRL_SDCLK_ENABLE;
+               writew(clock, host->base + ARASAN_CLOCK_CTRL);
+
+               DBG("\tclk ctrl reg. [0x%x]\n",
+                   (unsigned int)readw(host->base + ARASAN_CLOCK_CTRL));
+       }
+
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/* Read the response from the card */
+static void arasan_get_resp(struct mmc_command *cmd, struct arasan_host *host)
+{
+       unsigned int i;
+       unsigned int resp[4];
+
+       for (i = 0; i < 4; i++)
+               resp[i] = readl(host->base + ARASAN_RSP(i));
+
+       if (cmd->flags & MMC_RSP_136) {
+               cmd->resp[3] = (resp[0] << 8);
+               cmd->resp[2] = (resp[0] >> 24) | (resp[1] << 8);
+               cmd->resp[1] = (resp[1] >> 24) | (resp[2] << 8);
+               cmd->resp[0] = (resp[2] >> 24) | (resp[3] << 8);
+       } else {
+               cmd->resp[0] = resp[0];
+               cmd->resp[1] = resp[1];
+       }
+
+       DBG("%s: resp length %s\n-(CMD%u):\n %08x %08x %08x %08x\n"
+           "-RAW reg:\n %08x %08x %08x %08x\n",
+           __func__, (cmd->flags & MMC_RSP_136) ? "136" : "48", cmd->opcode,
+           cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3],
+           resp[0], resp[1], resp[2], resp[3]);
+}
+
+static void arasan_read_block_pio(struct arasan_host *host)
+{
+       unsigned long flags;
+       u16 blksz;
+
+       DBG("\tPIO reading\n");
+
+       local_irq_save(flags);
+
+       for (blksz = host->pio_blksz; blksz > 0; blksz -= 4) {
+               *host->pio_blkbuf =
+                   readl(host->base + ARASAN_BUFF);
+               host->pio_blkbuf++;
+       }
+
+       local_irq_restore(flags);
+}
+
+static void arasan_write_block_pio(struct arasan_host *host)
+{
+       unsigned long flags;
+       u16 blksz;
+
+       DBG("\tPIO writing\n");
+       local_irq_save(flags);
+
+       for (blksz = host->pio_blksz; blksz > 0; blksz -= 4) {
+               writel(*host->pio_blkbuf,
+                      host->base + ARASAN_BUFF);
+               host->pio_blkbuf++;
+       }
+
+       local_irq_restore(flags);
+}
+
+static void arasan_data_pio(struct arasan_host *host)
+{
+       if (host->pio_blocks == 0)
+               return;
+
+       if (host->status == STATE_DATA_READ) {
+               while (readl(host->base + ARASAN_PRESENT_STATE) &
+                            ARASAN_PRESENT_STATE_BUFFER_RD_EN) {
+
+                       arasan_read_block_pio(host);
+
+                       host->pio_blocks--;
+                       if (host->pio_blocks == 0)
+                               break;
+               }
+
+       } else {
+               while (readl(host->base + ARASAN_PRESENT_STATE) &
+                            ARASAN_PRESENT_STATE_BUFFER_WR_EN) {
+
+                       arasan_write_block_pio(host);
+
+                       host->pio_blocks--;
+                       if (host->pio_blocks == 0)
+                               break;
+               }
+       }
+       DBG("\tPIO transfer complete.\n");
+}
+
+static void arasan_start_cmd(struct arasan_host *host, struct mmc_command *cmd)
+{
+       u16 cmdreg = 0;
+
+       /* Command Request */
+       cmdreg = ARASAN_CMD_INDEX(cmd->opcode);
+       DBG("%s: cmd type %04x,  CMD%d\n", __func__,
+           mmc_resp_type(cmd), cmd->opcode);
+
+       if (cmd->flags & MMC_RSP_BUSY) {
+               cmdreg |= ARASAN_CMD_RSP_48BUSY;
+               DBG("\tResponse length 48 check Busy.\n");
+       } else if (cmd->flags & MMC_RSP_136) {
+               cmdreg |= ARASAN_CMD_RSP_136;
+               DBG("\tResponse length 136\n");
+       } else if (cmd->flags & MMC_RSP_PRESENT) {
+               cmdreg |= ARASAN_CMD_RSP_48;
+               DBG("\tResponse length 48\n");
+       } else {
+               cmdreg |= ARASAN_CMD_RSP_NONE;
+               DBG("\tNo Response\n");
+       }
+
+       if (cmd->flags & MMC_RSP_CRC) {
+               cmdreg |= ARASAN_CMD_CHECK_CMDCRC;
+               DBG("\tCheck the CRC field in the response\n");
+       }
+       if (cmd->flags & MMC_RSP_OPCODE) {
+               cmdreg |= ARASAN_CMD_INDX_CHECK;
+               DBG("\tCheck the Index field in the response\n");
+       }
+
+       /* Wait until the CMD line is not in use */
+       do { } while ((readl(host->base + ARASAN_PRESENT_STATE)) &
+                ARASAN_PRESENT_STATE_CMD_INHIBIT);
+
+       /* Set the argument register */
+       writel(cmd->arg, host->base + ARASAN_ARG);
+
+       /* Data present and must be transferred */
+       if (likely(host->mrq->data)) {
+               cmdreg |= ARASAN_CMD_DATA_PRESENT;
+               if (cmd->flags & MMC_RSP_BUSY)
+                       /* Wait for data inhibit */
+                       do { } while ((readl(host->base +
+                                       ARASAN_PRESENT_STATE)) &
+                                ARASAN_PRESENT_STATE_DAT_INHIBIT);
+       }
+
+       /* Write the Command */
+       writew(cmdreg, host->base + ARASAN_CMD);
+
+       DBG("\tcmd: 0x%x cmd reg: 0x%x - cmd->arg 0x%x, reg 0x%x\n",
+           cmdreg, readw(host->base + ARASAN_CMD), cmd->arg,
+           readl(host->base + ARASAN_ARG));
+}
+
+#ifdef ARASAN_DEBUG
+static void arasan_adma_error(struct arasan_host *host)
+{
+       u8 status = readb(host->base + ARASAN_ADMA_ERR_STATUS);
+
+       if (status & ARASAN_ADMA_ERROR_LENGTH)
+               pr_err("-ADMA Length Mismatch Error...");
+
+       if (status & ARASAN_ADMA_ERROR_ST_TFR)
+               pr_err("-Transfer Data Error desc: ");
+       else if (status & ARASAN_ADMA_ERROR_ST_FDS)
+               pr_err("-Fetch Data Error desc: ");
+       else if (status & ARASAN_ADMA_ERROR_ST_STOP)
+               pr_err("-Stop DMA Data Error desc: ");
+
+       pr_err("0x%x", readl(host->base + ARASAN_ADMA_ADDRESS));
+}
+
+static void arasan_adma_dump_desc(u8 *desc)
+{
+       __le32 *dma;
+       __le16 *len;
+       u8 attr;
+
+       pr_info("\tDescriptors:");
+
+       while (1) {
+               dma = (__le32 *) (desc + 4);
+               len = (__le16 *) (desc + 2);
+               attr = *desc;
+
+               pr_info("\t\t%p: Buff 0x%08x, len %d, Attr 0x%02x\n",
+                       desc, le32_to_cpu(*dma), le16_to_cpu(*len), attr);
+
+               desc += 8;
+
+               if (attr & 2)   /* END of descriptor */
+                       break;
+       }
+}
+#else
+static void arasan_adma_error(struct arasan_host *host)
+{
+}
+
+static void arasan_adma_dump_desc(u8 *desc)
+{
+}
+#endif
+
+static int arasan_init_sg(struct arasan_host *host)
+{
+
+       host->adma_desc = kmalloc((ARASAN_DMA_DESC_NUM * 2 + 1) * 4,
+                                 GFP_KERNEL);
+
+       if (unlikely(host->adma_desc == NULL))
+               return -ENOMEM;
+
+       return 0;
+}
+
+static void arasan_adma_table_pre(struct arasan_host *host,
+                                 struct mmc_data *data)
+{
+       int direction, i;
+       u8 *desc;
+       struct scatterlist *sg;
+       int len;
+       dma_addr_t addr;
+
+       if (host->status == STATE_DATA_READ)
+               direction = DMA_FROM_DEVICE;
+       else
+               direction = DMA_TO_DEVICE;
+
+       DBG("\t%s: sg entries %d\n", __func__, data->sg_len);
+
+       host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg,
+                                   data->sg_len, direction);
+       desc = host->adma_desc;
+
+       for_each_sg(data->sg, sg, host->sg_frags, i) {
+               addr = sg_dma_address(sg);
+               len = sg_dma_len(sg);
+
+               DBG("\t\tFrag %d: addr 0x%x, len %d\n", i, addr, len);
+
+               /* Preparing the descriptor */
+               desc[7] = (addr >> 24) & 0xff;
+               desc[6] = (addr >> 16) & 0xff;
+               desc[5] = (addr >> 8) & 0xff;
+               desc[4] = (addr >> 0) & 0xff;
+
+               desc[3] = (len >> 8) & 0xff;
+               desc[2] = (len >> 0) & 0xff;
+
+               desc[1] = 0x00;
+               desc[0] = 0x21;
+
+               desc += 8;
+       }
+       desc -= 8;
+       desc[0] = 0x23;
+
+       arasan_adma_dump_desc(host->adma_desc);
+
+       host->adma_addr = dma_map_single(mmc_dev(host->mmc),
+                                        host->adma_desc,
+                                        (ARASAN_DMA_DESC_NUM * 2 + 1) * 4,
+                                        DMA_TO_DEVICE);
+
+       writel(host->adma_addr, host->base + ARASAN_ADMA_ADDRESS);
+}
+
+static void arasan_adma_table_post(struct arasan_host *host,
+                                  struct mmc_data *data)
+{
+       int direction;
+
+       if (host->status == STATE_DATA_READ)
+               direction = DMA_FROM_DEVICE;
+       else
+               direction = DMA_TO_DEVICE;
+
+       DBG("\t%s\n", __func__);
+
+       dma_unmap_single(mmc_dev(host->mmc), host->adma_addr,
+                        (ARASAN_DMA_DESC_NUM * 2 + 1) * 4, DMA_TO_DEVICE);
+
+       dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction);
+}
+
+static int arasan_setup_data(struct arasan_host *host)
+{
+       u16 blksz;
+       u16 xfer = 0;
+       struct mmc_data *data = host->mrq->data;
+
+       DBG("%s:\n\t%s mode, data dir: %s; Buff=0x%08x,"
+           "blocks=%d, blksz=%d\n", __func__, host->use_pio ? "PIO" : "DMA",
+           (data->flags & MMC_DATA_READ) ? "read" : "write",
+           (unsigned int)sg_virt(data->sg), data->blocks, data->blksz);
+
+       /* Transfer Direction */
+       if (data->flags & MMC_DATA_READ) {
+               xfer |= ARASAN_XFER_DATA_DIR;
+               host->status = STATE_DATA_READ;
+       } else {
+               xfer &= ~ARASAN_XFER_DATA_DIR;
+               host->status = STATE_DATA_WRITE;
+       }
+
+       xfer |= ARASAN_XFER_BLK_COUNT_EN;
+
+       if (data->blocks > 1)
+               xfer |= ARASAN_XFER_MULTI_BLK | ARASAN_XFER_AUTOCMD12;
+
+       /* Set the block size register */
+       blksz = ARASAN_BLOCK_SIZE_SDMA_512KB;
+       blksz |= (data->blksz & ARASAN_BLOCK_SIZE_TRANSFER);
+       blksz |= (data->blksz & 0x1000) ? ARASAN_BLOCK_SIZE_SDMA_8KB : 0;
+
+       writew(blksz, host->base + ARASAN_BLK_SIZE);
+
+       /* Set the block count register */
+       writew(data->blocks, host->base + ARASAN_BLK_COUNT);
+
+       /* PIO mode is used when 'pio' var is set by the user or no
+        * sdma is available from HC caps. */
+       if (unlikely(host->use_pio || (host->cap.sdma == 0))) {
+               host->pio_blksz = data->blksz;
+               host->pio_blocks = data->blocks;
+               host->pio_blkbuf = sg_virt(data->sg);
+       } else {
+               dma_addr_t phys_addr;
+
+               /* Enable DMA */
+               xfer |= ARASAN_XFER_DMA_EN;
+
+               /* Scatter list init */
+               host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg,
+                                           data->sg_len,
+                                           (host->status & STATE_DATA_READ) ?
+                                           DMA_FROM_DEVICE : DMA_TO_DEVICE);
+
+               phys_addr = sg_dma_address(data->sg);
+
+               if (likely(host->adma)) {
+                       /* Set the host control register dma bits for adma
+                        * if supported and enabled by user. */
+                       host->ctrl |= ARASAN_HOST_CTRL_ADMA2_32;
+
+                       /* Prepare ADMA table */
+                       arasan_adma_table_pre(host, data);
+               } else {
+                       /* SDMA Mode selected (default mode) */
+                       host->ctrl &= ~ARASAN_HOST_CTRL_ADMA2_64;
+
+                       writel((unsigned int)phys_addr,
+                              host->base + ARASAN_SDMA_SYS_ADDR);
+               }
+               writeb(host->ctrl, host->base + ARASAN_HOST_CTRL);
+
+       }
+       /* Set the data transfer mode register */
+       writew(xfer, host->base + ARASAN_XFER_MODE);
+
+       DBG("\tHC Reg [xfer 0x%x] [blksz 0x%x] [blkcount 0x%x] [CRTL 0x%x]\n",
+           readw(host->base + ARASAN_XFER_MODE),
+           readw(host->base + ARASAN_BLK_SIZE),
+           readw(host->base + ARASAN_BLK_COUNT),
+           readb(host->base + ARASAN_HOST_CTRL));
+
+       return 0;
+}
+
+static void arasan_finish_data(struct arasan_host *host)
+{
+       struct mmc_data *data = host->mrq->data;
+
+       DBG("\t%s\n", __func__);
+
+       if (unlikely(host->pio_blkbuf)) {
+               host->pio_blksz = 0;
+               host->pio_blocks = 0;
+               host->pio_blkbuf = NULL;
+       } else {
+               if (likely(host->adma)) {
+                       arasan_adma_table_post(host, data);
+               } else {
+                       dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+                                    host->sg_frags,
+                                    (host->status & STATE_DATA_READ) ?
+                                    DMA_FROM_DEVICE : DMA_TO_DEVICE);
+               }
+       }
+
+       data->bytes_xfered = data->blocks * data->blksz;
+       host->status = STATE_CMD;
+}
+
+static int arasan_finish_cmd(unsigned int err_status, unsigned int status,
+                            unsigned int opcode)
+{
+       int ret = 0;
+
+       if (unlikely(err_status)) {
+               if (err_status & ARASAN_CMD_TIMEOUT) {
+                       DBG("\tcmd_timeout...\n");
+                       ret = -ETIMEDOUT;
+               }
+               if (err_status & ARASAN_CMD_CRC_ERROR) {
+                       DBG("\tcmd_crc_error...\n");
+                       ret = -EILSEQ;
+               }
+               if (err_status & ARASAN_CMD_END_BIT_ERROR) {
+                       DBG("\tcmd_end_bit_error...\n");
+                       ret = -EILSEQ;
+               }
+               if (err_status & ARASAN_CMD_INDEX_ERROR) {
+                       DBG("\tcmd_index_error...\n");
+                       ret = -EILSEQ;
+               }
+       }
+       if (likely(status & ARASAN_N_CMD_COMPLETE))
+               DBG("\tCommand (CMD%u) Completed irq...\n", opcode);
+
+       return ret;
+}
+
+/* Enable/Disable Normal and Error interrupts */
+static void aranan_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+       unsigned long flags;
+       struct arasan_host *host = mmc_priv(mmc);
+
+       DBG("%s: %s CARD_IRQ\n", __func__, enable ? "enable" : "disable");
+
+       spin_lock_irqsave(&host->lock, flags);
+       if (enable)
+               host->intr_en = ARASAN_IRQ_DEFAULT_MASK;
+       else
+               host->intr_en = 0;
+
+       writel(host->intr_en, host->base + ARASAN_NORMAL_INT_STATUS_EN);
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void arasan_timeout_timer(unsigned long data)
+{
+       struct arasan_host *host = (struct arasan_host *)data;
+       struct mmc_request *mrq;
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       if ((host->mrq) && (host->status == STATE_CMD)) {
+               mrq = host->mrq;
+
+               pr_debug("%s: Timeout waiting for hardware interrupt.\n",
+                        mmc_hostname(host->mmc));
+
+               writel(0xffffffff, host->base + ARASAN_NORMAL_INT_STATUS);
+               aranan_enable_sdio_irq(host->mmc, 1);
+
+               if (mrq->data) {
+                       arasan_finish_data(host);
+                       arsan_sw_reset(host, reset_dat_line);
+                       mrq->data->error = -ETIMEDOUT;
+               }
+               if (likely(mrq->cmd)) {
+                       mrq->cmd->error = -ETIMEDOUT;
+                       arsan_sw_reset(host, reset_cmd_line);
+                       arasan_get_resp(mrq->cmd, host);
+               }
+               arasan_ctrl_led(host, 0);
+               host->mrq = NULL;
+               mmc_request_done(host->mmc, mrq);
+       }
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/* Process requests from the MMC layer */
+static void arasan_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct arasan_host *host = mmc_priv(mmc);
+       struct mmc_command *cmd = mrq->cmd;
+       unsigned long flags;
+
+       BUG_ON(host->mrq != NULL);
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       DBG(">>> araran_request:\n");
+       /* Check that there is a card in the slot */
+       if (unlikely(arasan_test_card(host) < 0)) {
+               DBG("%s: Error: No card present...\n", mmc_hostname(host->mmc));
+
+               mrq->cmd->error = -ENOMEDIUM;
+               mmc_request_done(mmc, mrq);
+               spin_unlock_irqrestore(&host->lock, flags);
+               return;
+       }
+
+       host->mrq = mrq;
+
+       host->status = STATE_CMD;
+       if (likely(mrq->data))
+               arasan_setup_data(host);
+
+       /* Turn-on/off the LED when send/complete a cmd */
+       arasan_ctrl_led(host, 1);
+
+       arasan_start_cmd(host, cmd);
+
+       mod_timer(&host->timer, jiffies + 5 * HZ);
+
+       DBG("<<< araran_request done!\n");
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int arasan_get_ro(struct mmc_host *mmc)
+{
+       struct arasan_host *host = mmc_priv(mmc);
+
+       u32 ro = readl(host->base + ARASAN_PRESENT_STATE);
+       if (!(ro & ARASAN_PRESENT_STATE_WR_EN))
+               return 1;
+
+       return 0;
+}
+
+/* I/O bus settings (MMC clock/power ...) */
+static void arasan_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct arasan_host *host = mmc_priv(mmc);
+       u8 ctrl_reg = readb(host->base + ARASAN_HOST_CTRL);
+
+       DBG("%s: pwr %d, clk %d, vdd %d, bus_width %d, timing %d\n",
+           __func__, ios->power_mode, ios->clock, ios->vdd, ios->bus_width,
+           ios->timing);
+
+       /* Set the power supply mode */
+       if (ios->power_mode == MMC_POWER_OFF)
+               arasan_power_set(host, 0, ios->vdd);
+       else
+               arasan_power_set(host, 1, ios->vdd);
+
+       /* Timing (high speed supported?) */
+       if ((ios->timing == MMC_TIMING_MMC_HS ||
+            ios->timing == MMC_TIMING_SD_HS) && host->cap.high_speed)
+               ctrl_reg |= ARASAN_HOST_CTRL_HIGH_SPEED;
+
+       /* Clear the current bus width configuration */
+       ctrl_reg &= ~ARASAN_HOST_CTRL_SD_MASK;
+
+       /* Set SD bus bit mode */
+       switch (ios->bus_width) {
+       case MMC_BUS_WIDTH_8:
+               ctrl_reg |= ARASAN_HOST_CTRL_SD8;
+               break;
+       case MMC_BUS_WIDTH_4:
+               ctrl_reg |= ARASAN_HOST_CTRL_SD;
+               break;
+       }
+
+       /* Default to maximum timeout */
+       writeb(0x0e, host->base + ARASAN_TIMEOUT_CTRL);
+
+       /* Disable Card Interrupt in Host in case we change
+        * the Bus Width. */
+       aranan_enable_sdio_irq(host->mmc, 0);
+
+       host->ctrl = ctrl_reg;
+       writeb(host->ctrl, host->base + ARASAN_HOST_CTRL);
+
+       aranan_enable_sdio_irq(host->mmc, 1);
+
+       /* Set clock */
+       arasan_set_clock(host, ios->clock);
+}
+
+/* Tasklet for Card-detection */
+static void arasan_tasklet_card(unsigned long data)
+{
+       unsigned long flags;
+       struct arasan_host *host = (struct arasan_host *)data;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       if (likely((readl(host->base + ARASAN_PRESENT_STATE) &
+                   ARASAN_PRESENT_STATE_CARD_PRESENT))) {
+               if (host->mrq) {
+                       pr_err("%s: Card removed during transfer!\n",
+                              mmc_hostname(host->mmc));
+                       /* Reset cmd and dat lines */
+                       arsan_sw_reset(host, reset_cmd_line);
+                       arsan_sw_reset(host, reset_dat_line);
+
+                       if (likely(host->mrq->cmd)) {
+                               host->mrq->cmd->error = -ENOMEDIUM;
+                               mmc_request_done(host->mmc, host->mrq);
+                       }
+               }
+       }
+
+       spin_unlock_irqrestore(&host->lock, flags);
+
+       if (likely(host->mmc))
+               mmc_detect_change(host->mmc, msecs_to_jiffies(200));
+}
+
+static irqreturn_t arasan_irq(int irq, void *dev)
+{
+       struct arasan_host *host = dev;
+       unsigned int status, err_status, handled = 0;
+       struct mmc_command *cmd = NULL;
+       struct mmc_data *data = NULL;
+
+       spin_lock(&host->lock);
+
+       /* Interrupt Status */
+       status = readl(host->base + ARASAN_NORMAL_INT_STATUS);
+       err_status = (status >> 16) & 0xffff;
+
+       DBG("%s: Normal IRQ status  0x%x, Error status 0x%x\n",
+           __func__, status & 0xffff, err_status);
+
+       if ((!host->need_poll) &&
+           ((status & ARASAN_N_CARD_REMOVAL) ||
+                   (status & ARASAN_N_CARD_INS)))
+                       tasklet_schedule(&host->card_tasklet);
+
+       if (unlikely(!host->mrq))
+               goto out;
+
+       cmd = host->mrq->cmd;
+       data = host->mrq->data;
+
+       cmd->error = 0;
+       /* Check for any CMD interrupts */
+       if (likely(status & ARASAN_INT_CMD_MASK)) {
+
+               cmd->error = arasan_finish_cmd(err_status, status, cmd->opcode);
+               if (cmd->error)
+                       arsan_sw_reset(host, reset_cmd_line);
+
+               if ((host->status == STATE_CMD) || cmd->error) {
+                       arasan_get_resp(cmd, host);
+
+                       handled = 1;
+               }
+       }
+
+       /* Check for any data interrupts */
+       if (likely((status & ARASAN_INT_DATA_MASK)) && data) {
+               data->error = 0;
+               if (unlikely(err_status)) {
+                       if (err_status & ARASAN_DATA_TIMEOUT_ERROR) {
+                               DBG("\tdata_timeout_error...\n");
+                               data->error = -ETIMEDOUT;
+                       }
+                       if (err_status & ARASAN_DATA_CRC_ERROR) {
+                               DBG("\tdata_crc_error...\n");
+                               data->error = -EILSEQ;
+                       }
+                       if (err_status & ARASAN_DATA_END_ERROR) {
+                               DBG("\tdata_end_error...\n");
+                               data->error = -EILSEQ;
+                       }
+                       if (err_status & ARASAN_AUTO_CMD12_ERROR) {
+                               unsigned int err_cmd12 =
+                                   readw(host->base + ARASAN_CMD12_ERR_STATUS);
+
+                               DBG("\tc12err 0x%04x\n", err_cmd12);
+
+                               if (err_cmd12 & ARASAN_AUTOCMD12_ERR_NOTEXE)
+                                       data->stop->error = -ENOEXEC;
+
+                               if ((err_cmd12 & ARASAN_AUTOCMD12_ERR_TIMEOUT)
+                                   && !(err_cmd12 & ARASAN_AUTOCMD12_ERR_CRC))
+                                       /* Timeout Error */
+                                       data->stop->error = -ETIMEDOUT;
+                               else if (!(err_cmd12 &
+                                          ARASAN_AUTOCMD12_ERR_TIMEOUT)
+                                        && (err_cmd12 &
+                                            ARASAN_AUTOCMD12_ERR_CRC))
+                                       /* CRC Error */
+                                       data->stop->error = -EILSEQ;
+                               else if ((err_cmd12 &
+                                         ARASAN_AUTOCMD12_ERR_TIMEOUT)
+                                        && (err_cmd12 &
+                                            ARASAN_AUTOCMD12_ERR_CRC))
+                                       DBG("\tCMD line Conflict\n");
+                       }
+                       arsan_sw_reset(host, reset_dat_line);
+                       handled = 1;
+               } else {
+                       if (likely(((status & ARASAN_N_BUFF_READ) ||
+                                   status & ARASAN_N_BUFF_WRITE))) {
+                               DBG("\tData R/W interrupts...\n");
+                               arasan_data_pio(host);
+                       }
+
+                       if (likely(status & ARASAN_N_DMA_IRQ))
+                               DBG("\tDMA interrupts...\n");
+
+                       if (likely(status & ARASAN_N_TRANS_COMPLETE)) {
+                               DBG("\tData XFER completed interrupts...\n");
+                               arasan_finish_data(host);
+                               if (data->stop) {
+                                       u32 opcode = data->stop->opcode;
+                                       data->stop->error =
+                                           arasan_finish_cmd(err_status,
+                                                             status, opcode);
+                                       arasan_get_resp(data->stop, host);
+                               }
+                               handled = 1;
+                       }
+               }
+       }
+       if (err_status & ARASAN_ADMA_ERROR) {
+               DBG("\tADMA Error...\n");
+               arasan_adma_error(host);
+               cmd->error = -EIO;
+       }
+       if (err_status & ARASAN_CURRENT_LIMIT_ERROR) {
+               DBG("\tPower Fail...\n");
+               cmd->error = -EIO;
+       }
+
+       if (likely(host->mrq && handled)) {
+               struct mmc_request *mrq = host->mrq;
+
+               arasan_ctrl_led(host, 0);
+
+               del_timer(&host->timer);
+
+               host->mrq = NULL;
+               DBG("\tcalling mmc_request_done...\n");
+               mmc_request_done(host->mmc, mrq);
+       }
+out:
+       DBG("\tclear status and exit...\n");
+       writel(status, host->base + ARASAN_NORMAL_INT_STATUS);
+
+       spin_unlock(&host->lock);
+
+       return IRQ_HANDLED;
+}
+
+static void arasan_setup_hc(struct arasan_host *host)
+{
+       /* Clear all the interrupts before resetting */
+       arasan_clear_interrupts(host);
+
+       /* Reset All and get the HC version */
+       arsan_sw_reset(host, reset_all);
+
+       /* Print HC version and SPEC */
+       arsan_hc_version(host);
+
+       /* Set capabilities and print theri info */
+       arasan_capabilities(host);
+
+       /* Enable interrupts */
+       arasan_set_interrupts(host);
+}
+
+static const struct mmc_host_ops arasan_ops = {
+       .request = arasan_request,
+       .get_ro = arasan_get_ro,
+       .set_ios = arasan_set_ios,
+       .enable_sdio_irq = aranan_enable_sdio_irq,
+};
+
+static int __init arasan_probe(struct platform_device *pdev)
+{
+       struct mmc_host *mmc = NULL;
+       struct arasan_host *host = NULL;
+       const struct arasan_platform_data *arasan_data;
+       struct resource *r;
+       int ret, irq;
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+       irq = platform_get_irq_byname(pdev, "mmcirq");
+
+       arasan_data = pdev->dev.platform_data;
+
+       if (!r || irq < 0 || !arasan_data)
+               return -ENXIO;
+
+       r = request_mem_region(r->start, resource_size(r), pdev->name);
+       if (!r) {
+               pr_err("%s: ERROR: memory allocation failed\n", __func__);
+               return -EBUSY;
+               goto out;
+       }
+       /* Allocate the mmc_host with private data size */
+       mmc = mmc_alloc_host(sizeof(struct arasan_host), &pdev->dev);
+       if (!mmc) {
+               pr_err("%s: ERROR: mmc_alloc_host failed\n", __func__);
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* Verify resource from the platform */
+       ret = arasan_claim_resource(pdev);
+       if (ret < 0)
+               goto out;
+
+       host = mmc_priv(mmc);
+       host->mmc = mmc;
+       host->dev = &pdev->dev;
+       host->res = r;
+
+       host->need_poll = arasan_data->need_poll;
+       if (host->need_poll) {
+               mmc->caps |= MMC_CAP_NEEDS_POLL;
+               DBG("\tHC needs polling to detect the card...");
+       } else
+               /* no set the MMC_CAP_NEEDS_POLL in cap */
+               tasklet_init(&host->card_tasklet, arasan_tasklet_card,
+                            (unsigned long)host);
+
+       host->base = ioremap(r->start, resource_size(r));
+       if (!host->base) {
+               pr_err("%s: ERROR: memory mapping failed\n", __func__);
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       ret =
+           request_irq(irq, arasan_irq, IRQF_SHARED, ARASAN_DRIVER_NAME, host);
+       if (ret) {
+               pr_err("%s: cannot assign irq %d\n", __func__, irq);
+               goto out;
+       } else
+               host->irq = irq;
+
+       spin_lock_init(&host->lock);
+
+       /* Setup the Host Controller according to its capabilities */
+       arasan_setup_hc(host);
+
+       mmc->ops = &arasan_ops;
+
+       if (host->cap.voltage33)
+               mmc->ocr_avail |= MMC_VDD_32_33 | MMC_VDD_33_34;
+       if (host->cap.voltage30)
+               mmc->ocr_avail |= MMC_VDD_29_30;
+       if (host->cap.voltage18)
+               mmc->ocr_avail |= MMC_VDD_165_195;
+
+       mmc->caps = MMC_CAP_SDIO_IRQ;
+       mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
+
+       if (host->cap.high_speed)
+               mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
+
+       host->freq = host->cap.timer_freq * 1000000;
+       host->use_pio = pio;
+       mmc->f_max = maxfreq;
+       mmc->f_min = mmc->f_max / 256;
+
+       /*
+        * Maximum block size. This is specified in the capabilities register.
+        */
+       mmc->max_blk_size = host->cap.max_blk_len;
+       mmc->max_blk_count = 65535;
+
+       mmc->max_hw_segs = 1;
+       mmc->max_phys_segs = 128;
+       mmc->max_seg_size = 65535;
+       mmc->max_req_size = 524288;
+
+       /* Passing the "pio" option, we force the driver to not
+        * use any DMA engines. */
+       if (unlikely(host->use_pio)) {
+               adma = 0;
+               pr_debug("\tPIO mode\n");
+       } else {
+               if (likely(adma)) {
+                       /* Turn-on the ADMA if supported by the HW
+                        * or Fall back to SDMA in case of failures */
+                       pr_debug("\tADMA mode\n");
+                       ret = arasan_init_sg(host);
+                       if (unlikely(ret)) {
+                               pr_warning("\tSG init failed (disable ADMA)\n");
+                               adma = 0;
+                       } else
+                               /* Set the Maximum number of segments
+                                * becasue we can do scatter/gathering in ADMA
+                                * mode. */
+                               mmc->max_hw_segs = 128;
+               } else
+                       pr_debug("\tSDMA mode\n");
+       }
+       host->adma = adma;
+
+       platform_set_drvdata(pdev, mmc);
+       ret = mmc_add_host(mmc);
+       if (ret)
+               goto out;
+
+       setup_timer(&host->timer, arasan_timeout_timer, (unsigned long)host);
+
+       pr_info("%s: driver initialized... IRQ: %d, Base addr 0x%x\n",
+               mmc_hostname(mmc), irq, (unsigned int)host->base);
+
+#ifdef ARASAN_DEBUG
+       led = 1;
+#endif
+       return 0;
+out:
+       if (host) {
+               if (host->irq)
+                       free_irq(host->irq, host);
+               if (host->base)
+                       iounmap(host->base);
+       }
+       if (r)
+               release_resource(r);
+       if (mmc)
+               mmc_free_host(mmc);
+
+       return ret;
+}
+
+static int __exit arasan_remove(struct platform_device *pdev)
+{
+       struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+       if (mmc) {
+               struct arasan_host *host = mmc_priv(mmc);
+
+               arasan_clear_interrupts(host);
+               if (!host->need_poll)
+                       tasklet_kill(&host->card_tasklet);
+               mmc_remove_host(mmc);
+               free_irq(host->irq, host);
+               arasan_power_set(host, 0, -1);
+               iounmap(host->base);
+               if (likely(host->adma))
+                       kfree(host->adma_desc);
+               release_resource(host->res);
+               mmc_free_host(mmc);
+       }
+       platform_set_drvdata(pdev, NULL);
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int arasan_suspend(struct platform_device *dev, pm_message_t state)
+{
+       struct mmc_host *mmc = platform_get_drvdata(dev);
+       struct arasan_host *host = mmc_priv(mmc);
+       int ret = 0;
+
+       if (mmc && host->cap.suspend)
+               ret = mmc_suspend_host(mmc);
+
+       return ret;
+}
+
+static int arasan_resume(struct platform_device *dev)
+{
+       struct mmc_host *mmc = platform_get_drvdata(dev);
+       struct arasan_host *host = mmc_priv(mmc);
+       int ret = 0;
+
+       if (mmc && host->cap.suspend)
+               ret = mmc_resume_host(mmc);
+
+       return ret;
+}
+#endif
+
+static struct platform_driver arasan_driver = {
+       .remove = __exit_p(arasan_remove),
+#ifdef CONFIG_PM
+       .suspend = arasan_suspend,
+       .resume = arasan_resume,
+#endif
+       .driver = {
+                  .name = ARASAN_DRIVER_NAME,
+                  },
+};
+
+static int __init arasan_init(void)
+{
+       return platform_driver_probe(&arasan_driver, arasan_probe);
+}
+
+static void __exit arasan_exit(void)
+{
+       platform_driver_unregister(&arasan_driver);
+}
+
+#ifndef MODULE
+static int __init arasan_cmdline_opt(char *str)
+{
+       char *opt;
+
+       if (!str || !*str)
+               return -EINVAL;
+
+       while ((opt = strsep(&str, ",")) != NULL) {
+               if (!strncmp(opt, "maxfreq:", 8))
+                       strict_strtoul(opt + 8, 0, (unsigned long *)&maxfreq);
+               else if (!strncmp(opt, "adma:", 5))
+                       strict_strtoul(opt + 5, 0, (unsigned long *)&adma);
+               else if (!strncmp(opt, "led:", 4))
+                       strict_strtoul(opt + 4, 0, (unsigned long *)&led);
+               else if (!strncmp(opt, "pio:", 4))
+                       strict_strtoul(opt + 4, 0, (unsigned long *)&pio);
+       }
+       return 0;
+}
+
+__setup("arasanmmc=", arasan_cmdline_opt);
+#endif
+
+module_init(arasan_init);
+module_exit(arasan_exit);
+
+MODULE_AUTHOR("Giuseppe Cavallaro <[email protected]>");
+MODULE_DESCRIPTION("Arasan MMC/SD/SDIO Host Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/arasan.h b/drivers/mmc/host/arasan.h
new file mode 100644
index 0000000..56a4f8b
--- /dev/null
+++ b/drivers/mmc/host/arasan.h
@@ -0,0 +1,237 @@
+/*
+ * Author: Giuseppe Cavallaro <[email protected]>
+ *
+ * Copyright (C) 2010  STMicroelectronics Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __ARASAN_H
+#define __ARASAN_H
+
+#define ARASAN_CLOCKRATE_MAX   50000000
+#define ARASAN_DRIVER_NAME     "arasan"
+#define ARASAN_DMA_DESC_NUM    128
+
+/*
+ * Register offsets
+ */
+#define ARASAN_SDMA_SYS_ADDR           0x000
+#define ARASAN_BLK_SIZE                        0x004
+#define ARASAN_BLK_COUNT               0x006
+#define ARASAN_ARG                     0x008
+#define ARASAN_XFER_MODE               0x00c
+#define ARASAN_CMD                     0x00e
+#define ARASAN_RSP(i)                  (0x010 + ((i)<<2))
+#define ARASAN_RSP0                    0x010
+#define ARASAN_RSP1                    0x012
+#define ARASAN_RSP2                    0x014
+#define ARASAN_RSP3                    0x016
+#define ARASAN_RSP4                    0x018
+#define ARASAN_RSP5                    0x01a
+#define ARASAN_RSP6                    0x01c
+#define ARASAN_RSP7                    0x01e
+#define ARASAN_BUFF                    0x020
+#define ARASAN_PRESENT_STATE           0x024
+#define ARASAN_HOST_CTRL               0x028
+#define ARASAN_PWR_CTRL                        0x029
+#define ARASAN_GAP_CTRL                        0x02a
+#define ARASAN_GAP_WAKEUP              0x02b
+#define ARASAN_CLOCK_CTRL              0x02c
+#define ARASAN_TIMEOUT_CTRL            0x02e
+#define ARASAN_SW_RESET                        0x02f
+
+#define ARASAN_NORMAL_INT_STATUS       0x030
+#define ARASAN_ERR_INT_STATUS          0x032
+#define ARASAN_NORMAL_INT_STATUS_EN    0x034
+#define ARASAN_ERR_INT_STATUS_EN       0x036
+#define ARASAN_NORMAL_INT_SIGN_EN      0x038
+#define ARASAN_ERR_INT_SIGN_EN         0x03a
+
+#define ARASAN_CMD12_ERR_STATUS                0x03c
+
+#define ARASAN_CAPABILITIES            0x040
+
+#define ARASAN_ADMA_ERR_STATUS         0x054
+#define ARASAN_ADMA_ADDRESS            0x058
+
+#define ARASAN_SPI_INT_SUPPORT         0x0f0
+#define ARASAN_HOST_VERSION            0x0fe
+
+/* Error Interrupt Status Register */
+#define ARASAN_CMD_TIMEOUT             (1 << 0)
+#define ARASAN_CMD_CRC_ERROR           (1 << 1)
+#define ARASAN_CMD_END_BIT_ERROR       (1 << 2)
+#define ARASAN_CMD_INDEX_ERROR         (1 << 3)
+#define ARASAN_DATA_TIMEOUT_ERROR      (1 << 4)
+#define ARASAN_DATA_CRC_ERROR          (1 << 5)
+#define ARASAN_DATA_END_ERROR          (1 << 6)
+#define ARASAN_CURRENT_LIMIT_ERROR     (1 << 7)
+#define ARASAN_AUTO_CMD12_ERROR                (1 << 8)
+#define ARASAN_ADMA_ERROR              (1 << 9)
+#define ARASAN_TARGET_RESP_ERROR       (1 << 12)
+#define ARASAN_CEATA_ERROR             (1 << 13)
+
+/* Error Interrupt Status ENABLE reg. (0- Masked, 1: Enabled) */
+#define ARASAN_E_EN_CMD_TIMEOUT                (1 << 0)
+#define ARASAN_E_EN_CMD_CRC_ERROR      (1 << 1)
+#define ARASAN_E_EN_CMD_END_BIT_ERROR  (1 << 2)
+#define ARASAN_E_EN_CMD_INDEX_ERROR    (1 << 3)
+#define ARASAN_E_EN_DATA_TIMEOUT_ERROR (1 << 4)
+#define ARASAN_E_EN_DATA_CRC_ERROR     (1 << 5)
+#define ARASAN_E_EN_DATA_END_ERROR     (1 << 6)
+#define ARASAN_E_EN_CURRENT_LIMIT_ERROR        (1 << 7)
+#define ARASAN_E_EN_AUTO_CMD12_ERROR   (1 << 8)
+#define ARASAN_E_EN_ADMA_ERROR         (1 << 9)
+#define ARASAN_E_EN_TARGET_RESP_ERROR  (1 << 12)
+#define ARASAN_E_EN_CEATA_ERROR                (1 << 13)
+
+/* Normal Interrupt Status Register */
+#define ARASAN_N_CMD_COMPLETE          (1 << 0)
+#define ARASAN_N_TRANS_COMPLETE                (1 << 1)
+#define ARASAN_N_BLK_GAP_EVENT         (1 << 2)
+#define ARASAN_N_DMA_IRQ               (1 << 3)
+#define ARASAN_N_BUFF_WRITE            (1 << 4)
+#define ARASAN_N_BUFF_READ             (1 << 5)
+#define ARASAN_N_CARD_INS              (1 << 6)
+#define ARASAN_N_CARD_REMOVAL          (1 << 7)
+#define ARASAN_N_CARD_IRQ              (1 << 8)
+#define ARASAN_N_ERROR_IRQ             (1 << 15)
+
+/* Normal Interrupt Status ENABLE reg. (0- Masked, 1: Enabled) */
+#define ARASAN_N_EN_CMD_COMPLETE       (1 << 0)
+#define ARASAN_N_EN_TRANS_COMPL                (1 << 1)
+#define ARASAN_N_EN_BLOCK_GAP          (1 << 2)
+#define ARASAN_N_EN_DMA_IRQ            (1 << 3)
+#define ARASAN_N_EN_BUFF_WRITE         (1 << 4)
+#define ARASAN_N_EN_BUFF_READ          (1 << 5)
+#define ARASAN_N_EN_CARD_INS           (1 << 6)
+#define ARASAN_N_EN_CARD_REM           (1 << 7)
+#define ARASAN_N_EN_CARD_IRQ           (1 << 8)
+
+/* Default Enable Normal/Error interrupt mask */
+#define ARASAN_IRQ_DEFAULT_MASK                0x02ff00fb
+
+/* Mask normal and error fields */
+#define ARASAN_INT_DATA_MASK           0x0070003a
+#define ARASAN_INT_CMD_MASK            0x000f0001
+
+/* Command Register */
+#define ARASAN_CMD_RSP_NONE            (0 << 0)
+#define ARASAN_CMD_RSP_136             (1 << 0)
+#define ARASAN_CMD_RSP_48              (2 << 0)
+#define ARASAN_CMD_RSP_48BUSY          (3 << 0)
+#define ARASAN_CMD_CHECK_CMDCRC                (1 << 3)
+#define ARASAN_CMD_INDX_CHECK          (1 << 4)
+#define ARASAN_CMD_DATA_PRESENT                (1 << 5)
+#define ARASAN_COMMAD_TYPE_NORM                (0 << 6)
+#define ARASAN_COMMAD_TYPE_SUSP                (1 << 6)
+#define ARASAN_COMMAD_TYPE_RESU                (2 << 6)
+#define ARASAN_COMMAD_TYPE_ABOR                (3 << 6)
+#define ARASAN_CMD_INDEX(x)            ((x) << 8)
+
+/* Transfer Mode Register */
+#define ARASAN_XFER_DMA_EN             (1 << 0)
+#define ARASAN_XFER_BLK_COUNT_EN       (1 << 1)
+#define ARASAN_XFER_AUTOCMD12          (1 << 2)        /* 1: Enable */
+#define ARASAN_XFER_DATA_DIR           (1 << 4)        /* 0: Write, 1: Read */
+#define ARASAN_XFER_MULTI_BLK          (1 << 5)        /* 0: Single 1: Multi */
+#define ARASAN_XFER_SPI_MODE           (1 << 7)        /* 1: SPI 0: SD Mode */
+
+enum xfer_dat_cmd_status {
+       STATE_CMD = 0,
+       STATE_DATA_WRITE = 1,
+       STATE_DATA_READ = 2,
+       STATE_DATA_STOP = 2,
+};
+
+/* Software Reset */
+#define ARSAN_RESET_ALL                        0x1
+#define ARSAN_RESET_CMD_LINE           0x2
+#define ARSAN_RESET_DAT_LINE           0x4
+
+enum sw_reset_cmd {
+       reset_all = 0,
+       reset_cmd_line = 1,
+       reset_dat_line = 2,
+};
+
+/* Host Control Register */
+#define ARASAN_HOST_CTRL_LED           (1 << 0)
+#define ARASAN_HOST_CTRL_SD            (1 << 1)        /* 1: 4 bit mode */
+#define ARASAN_HOST_CTRL_HIGH_SPEED    (1 << 2)
+#define ARASAN_HOST_CTRL_SDMA_SEL      (0 << 3)
+#define ARASAN_HOST_CTRL_ADMA1         (1 << 3)
+#define ARASAN_HOST_CTRL_ADMA2_32      (2 << 3)
+#define ARASAN_HOST_CTRL_ADMA2_64      (3 << 3)
+#define ARASAN_HOST_CTRL_SD8           (1 << 5)
+#define ARASAN_HOST_CTRL_CARD_LEV_TEST (1 << 6)
+#define ARASAN_HOST_CTRL_CARD_SIG_TEST (1 << 7)
+
+#define ARASAN_HOST_CTRL_SD_MASK       0x22
+
+/* Clock Control Register */
+#define ARASAN_CLOCK_CTRL_SDCLK_MASK   0xff00
+#define ARASAN_CLOCK_CTRL_SDCLK_SHIFT  7
+#define ARASAN_CLOCK_CTRL_SDCLK_256    0x8000
+#define ARASAN_CLOCK_CTRL_SDCLK_128    0x4000
+#define ARASAN_CLOCK_CTRL_SDCLK_64     0x2000
+#define ARASAN_CLOCK_CTRL_SDCLK_32     0x1000
+#define ARASAN_CLOCK_CTRL_SDCLK_16     0x0800
+#define ARASAN_CLOCK_CTRL_SDCLK_8      0x0400
+#define ARASAN_CLOCK_CTRL_SDCLK_4      0x0200
+#define ARASAN_CLOCK_CTRL_SDCLK_2      0x0100
+#define ARASAN_CLOCK_CTRL_SDCLK_1      0x0000
+#define ARASAN_CLOCK_CTRL_SDCLK_ENABLE (1 << 2)
+#define ARASAN_CLOCK_CTRL_ICLK_STABLE  (1 << 1)
+#define ARASAN_CLOCK_CTRL_ICLK_ENABLE  (1 << 0)
+
+/* Power Control Register */
+#define ARASAN_PWR_CTRL_UP             (1 << 0)        /* 1: Power-On */
+#define ARASAN_PWR_BUS_VOLTAGE_33      (7 << 1)
+#define ARASAN_PWR_BUS_VOLTAGE_30      (6 << 1)
+#define ARASAN_PWR_BUS_VOLTAGE_18      (5 << 1)
+
+/* CMD12 error status bits */
+#define ARASAN_AUTOCMD12_ERR_NOTEXE    (1 << 0)
+#define ARASAN_AUTOCMD12_ERR_TIMEOUT   (1 << 1)
+#define ARASAN_AUTOCMD12_ERR_CRC       (1 << 2)
+#define ARASAN_AUTOCMD12_ERR_ENDBIT    (1 << 3)
+#define ARASAN_AUTOCMD12_ERR_INDEX     (1 << 4)
+#define ARASAN_AUTOCMD12_ERR_NOT_ISSUED        (1 << 7)
+
+/* Present State Register */
+#define ARASAN_PRESENT_STATE_DAT7_4       0x1e000000
+#define ARASAN_PRESENT_STATE_CMD_LINE     0x01000000
+#define ARASAN_PRESENT_STATE_DAT3_0       0x00f00000
+#define ARASAN_PRESENT_STATE_WR_EN        0x00080000
+#define ARASAN_PRESENT_STATE_CARD_DETECT  0x00040000
+#define ARASAN_PRESENT_STATE_CARD_STABLE  0x00020000
+#define ARASAN_PRESENT_STATE_CARD_PRESENT 0x00010000
+#define ARASAN_PRESENT_STATE_BUFFER_RD_EN 0x00000800
+#define ARASAN_PRESENT_STATE_BUFFER_WR_EN 0x00000400
+#define ARASAN_PRESENT_STATE_RD_ACTIVE    0x00000200
+#define ARASAN_PRESENT_STATE_WR_ACTIVE    0x00000100
+#define ARASAN_PRESENT_STATE_DAT_ACTIVE   0x00000004
+#define ARASAN_PRESENT_STATE_DAT_INHIBIT  0x00000002
+#define ARASAN_PRESENT_STATE_CMD_INHIBIT  0x00000001
+
+/* Block size register defines */
+#define ARASAN_BLOCK_SIZE_SDMA_512KB   0x7000
+#define ARASAN_BLOCK_SIZE_SDMA_256KB   0x6000
+#define ARASAN_BLOCK_SIZE_SDMA_128KB   0x5000
+#define ARASAN_BLOCK_SIZE_SDMA_64KB    0x4000
+#define ARASAN_BLOCK_SIZE_SDMA_32KB    0x3000
+#define ARASAN_BLOCK_SIZE_SDMA_16KB    0x2000
+#define ARASAN_BLOCK_SIZE_SDMA_8KB     0x1000
+#define ARASAN_BLOCK_SIZE_SDMA_4KB     0x0000
+#define ARASAN_BLOCK_SIZE_TRANSFER     0x0fff
+
+/* ADMA Error Status Register */
+#define ARASAN_ADMA_ERROR_LENGTH          0x04
+#define ARASAN_ADMA_ERROR_ST_TFR          0x03
+#define ARASAN_ADMA_ERROR_ST_FDS          0x01
+#define ARASAN_ADMA_ERROR_ST_STOP         0x00
+#endif
diff --git a/include/linux/mmc/arasan_plat.h b/include/linux/mmc/arasan_plat.h
new file mode 100644
index 0000000..9e16287
--- /dev/null
+++ b/include/linux/mmc/arasan_plat.h
@@ -0,0 +1,49 @@
+/*
+ * Author: Giuseppe Cavallaro <[email protected]>
+ *
+ * include/linux/mmc/arsan_plat.h
+ *
+ * platform data for the Arasan MMC/SD/SDI HC driver
+ *
+ * Copyright (C) 2010  STMicroelectronics Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License.
+ *
+ */
+
+#ifndef __ARASAN_PLAT_H__
+#define __ARASAN_PLAT_H__
+
+struct arasan_platform_data {
+       unsigned int need_poll;
+#ifdef CONFIG_STM_DRIVERS
+       struct stm_pad_config *pad_config;
+#endif
+};
+
+/* ARASAN Resource configuration */
+#ifdef CONFIG_STM_DRIVERS
+#include <linux/stm/platform.h>
+#include <linux/stm/pad.h>
+static inline int arasan_claim_resource(struct platform_device *pdev)
+{
+       int ret = 0;
+       struct arasan_platform_data *plat_dat = pdev->dev.platform_data;
+
+       /* Pad routing setup required on STM platforms */
+       if (!devm_stm_pad_claim(&pdev->dev, plat_dat->pad_config,
+                               dev_name(&pdev->dev))) {
+               pr_err("%s: Failed to request pads!\n", __func__);
+               ret = -ENODEV;
+       }
+       return ret;
+}
+#else
+static inline int arasan_claim_resource(struct platform_device *pdev)
+{
+       return 0;
+}
+#endif
+#endif
-- 
1.5.5.6

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to