* [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller
@ 2010-07-28 6:38 Giuseppe CAVALLARO
2010-07-28 6:40 ` Peppe CAVALLARO
2010-07-28 8:39 ` David Vrabel
0 siblings, 2 replies; 4+ messages in thread
From: Giuseppe CAVALLARO @ 2010-07-28 6:38 UTC (permalink / raw)
To: linux-mmc; +Cc: Giuseppe Cavallaro
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 <peppe.cavallaro@st.com>
---
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 <peppe.cavallaro@st.com>
+ * 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 <peppe.cavallaro@st.com>");
+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 <peppe.cavallaro@st.com>
+ *
+ * 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 <peppe.cavallaro@st.com>
+ *
+ * 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
^ permalink raw reply related [flat|nested] 4+ messages in thread
* RE: [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller
2010-07-28 6:38 [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller Giuseppe CAVALLARO
@ 2010-07-28 6:40 ` Peppe CAVALLARO
2010-07-28 8:39 ` David Vrabel
1 sibling, 0 replies; 4+ messages in thread
From: Peppe CAVALLARO @ 2010-07-28 6:40 UTC (permalink / raw)
To: linux-mmc@vger.kernel.org
> -----Original Message-----
> From: Giuseppe CAVALLARO [mailto:peppe.cavallaro@st.com]
> Sent: Wednesday, July 28, 2010 8:39 AM
> To: linux-mmc@vger.kernel.org
> Cc: Peppe CAVALLARO
> Subject: [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller
>
> 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 <peppe.cavallaro@st.com>
Hello!
Just a few notes, I hope useful for reviewing the driver:
- its configuration depends on ST40 CPU because I have only
tested it on STM platforms (ST40 based). At any rate, the driver
builds fine on x86 machine against the latest Kernel from git.
- I've added the linux/mmc/arasan_plat.h for the driver's
platform information and the drivers/mmc/host/arasan.h
header file. I've voluntarily preferred to split the two headers
avoiding to include the header comes from ../../../driver/mmc/host
within our include/linux/stm/platform.h.
I don't know if it's the best solution and if you like it.
I did the same for the stmmac driver in net-2.6, in the past.
- No problems while running the mmc_test.ko.
Welcome advice and feedback.
Best Regards,
Giuseppe
> ---
> 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 <peppe.cavallaro@st.com>
> + * 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 <peppe.cavallaro@st.com>");
> +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 <peppe.cavallaro@st.com>
> + *
> + * 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 <peppe.cavallaro@st.com>
> + *
> + * 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
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller
2010-07-28 6:38 [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller Giuseppe CAVALLARO
2010-07-28 6:40 ` Peppe CAVALLARO
@ 2010-07-28 8:39 ` David Vrabel
2010-07-28 9:28 ` Peppe CAVALLARO
1 sibling, 1 reply; 4+ messages in thread
From: David Vrabel @ 2010-07-28 8:39 UTC (permalink / raw)
To: Giuseppe CAVALLARO; +Cc: linux-mmc
Giuseppe CAVALLARO wrote:
> This patch adds the MMC/SD/SDIO Arasan host controller.
> Advanced DMA, Single DMA and PIO modes are supported.
> The former is the default.
The Arasan IP is a Standard Host Controller so you should look at using
the existing sdhci driver. The sdhci-s3c driver provides an an example
of how to do this.
David
^ permalink raw reply [flat|nested] 4+ messages in thread
* RE: [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller
2010-07-28 8:39 ` David Vrabel
@ 2010-07-28 9:28 ` Peppe CAVALLARO
0 siblings, 0 replies; 4+ messages in thread
From: Peppe CAVALLARO @ 2010-07-28 9:28 UTC (permalink / raw)
To: David Vrabel; +Cc: linux-mmc@vger.kernel.org
Hi David,
> -----Original Message-----
> From: David Vrabel [mailto:david.vrabel@csr.com]
> Sent: Wednesday, July 28, 2010 10:40 AM
> To: Peppe CAVALLARO
> Cc: linux-mmc@vger.kernel.org
> Subject: Re: [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller
>
> Giuseppe CAVALLARO wrote:
> > This patch adds the MMC/SD/SDIO Arasan host controller.
> > Advanced DMA, Single DMA and PIO modes are supported.
> > The former is the default.
>
> The Arasan IP is a Standard Host Controller so you should look at using
> the existing sdhci driver. The sdhci-s3c driver provides an an example
> of how to do this.
So I've to move part of my work to another file (e.g. sdhci-stm) obviously reworking all in order to use the existent code (sdhci core etc).
I'll post a new patch asap
Regards,
Peppe
>
> David
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2010-07-28 9:29 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-07-28 6:38 [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller Giuseppe CAVALLARO
2010-07-28 6:40 ` Peppe CAVALLARO
2010-07-28 8:39 ` David Vrabel
2010-07-28 9:28 ` Peppe CAVALLARO
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.