aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mmc/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host')
-rw-r--r--drivers/mmc/host/Kconfig41
-rw-r--r--drivers/mmc/host/Makefile5
-rw-r--r--drivers/mmc/host/dw_mmc.c803
-rw-r--r--drivers/mmc/host/dw_mmc.h20
-rw-r--r--drivers/mmc/host/mshci-s3c-dma.c220
-rw-r--r--drivers/mmc/host/mshci-s3c.c644
-rw-r--r--drivers/mmc/host/mshci.c2245
-rw-r--r--drivers/mmc/host/mshci.h463
-rw-r--r--drivers/mmc/host/sdhci-s3c.c215
-rw-r--r--drivers/mmc/host/sdhci.c279
-rw-r--r--drivers/mmc/host/sdhci.h4
11 files changed, 4732 insertions, 207 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 56dbf3f..d6a7e81 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -24,6 +24,47 @@ config MMC_PXA
If unsure, say N.
+config MMC_MSHCI
+ tristate "Mobile Storage Host Controller Interface support"
+ depends on HAS_DMA
+ help
+ This selects the Mobile Storage Host Controller Interface.
+ It is made by synopsys. It supports SD/MMC card.
+
+ If you have a controller with this interface, say Y or M here. You
+ also need to enable an appropriate bus interface.
+
+ If unsure, say N.
+
+config MMC_MSHCI_S3C_DMA_MAP
+ tristate "Use own S3C_DMA_MAP function for mshci"
+ depends on MMC_MSHCI
+ help
+ This selects using the s3c_dma_map_sg, s3c_unmap_sg functions.
+ Those functions are optimized for flushing cache.
+
+ If unsure, say N.
+
+config MMC_MSHCI_ASYNC_OPS
+ tristate "Use Asyn ops like pre_req, post_req"
+ depends on MMC_MSHCI
+ help
+ This selects using the pre_req and post_req functions.
+ These functions might make the performance of MMC better.
+
+ If unsure, say N.
+
+config MMC_MSHCI_ENABLE_CACHE
+ tristate "Use Cache defined in eMMC 4.5"
+ depends on MMC_MSHCI
+ default n
+ help
+ This selects eMMC cache control feature of eMMC4.5.
+ These functions might make the performance of MMC better.
+ This should be used when the eMMC device supports cache feature.
+
+ If unsure, say N.
+
config MMC_SDHCI
tristate "Secure Digital Host Controller Interface support"
depends on HAS_DMA
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 58a5cf7..7e8ec52 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -7,6 +7,10 @@ obj-$(CONFIG_MMC_PXA) += pxamci.o
obj-$(CONFIG_MMC_IMX) += imxmmc.o
obj-$(CONFIG_MMC_MXC) += mxcmmc.o
obj-$(CONFIG_MMC_MXS) += mxs-mmc.o
+obj-$(CONFIG_MMC_DW) += dw_mmc.o
+obj-$(CONFIG_MMC_MSHCI) += mshci.o
+obj-$(CONFIG_MMC_MSHCI) += mshci-s3c.o
+obj-$(CONFIG_MMC_MSHCI_S3C_DMA_MAP) += mshci-s3c-dma.o
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o
obj-$(CONFIG_MMC_SDHCI_PXA) += sdhci-pxa.o
@@ -38,7 +42,6 @@ obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
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_DW) += dw_mmc.o
obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
obj-$(CONFIG_MMC_VUB300) += vub300.o
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index 66dcddb..0305a70 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -34,6 +34,10 @@
#include <linux/bitops.h>
#include <linux/regulator/consumer.h>
+#include <plat/cpu.h>
+
+#include <mach/board_rev.h>
+
#include "dw_mmc.h"
/* Common flag combinations */
@@ -46,7 +50,15 @@
DW_MCI_CMD_ERROR_FLAGS | SDMMC_INT_HLE)
#define DW_MCI_SEND_STATUS 1
#define DW_MCI_RECV_STATUS 2
-#define DW_MCI_DMA_THRESHOLD 16
+#define DW_MCI_DMA_THRESHOLD 4
+
+/* Incresing sg_list size for eMMC 4.5 performance by incresing
+ max DMA Transfer size from 1MB to 4MB */
+#if defined(CONFIG_MACH_P10)
+#define SG_LIST_ALLOC_SIZE (PAGE_SIZE * 4)
+#else
+#define SG_LIST_ALLOC_SIZE PAGE_SIZE
+#endif
#ifdef CONFIG_MMC_DW_IDMAC
struct idmac_desc {
@@ -61,7 +73,7 @@ struct idmac_desc {
u32 des1; /* Buffer sizes */
#define IDMAC_SET_BUFFER1_SIZE(d, s) \
- ((d)->des1 = ((d)->des1 & 0x03ffc000) | ((s) & 0x3fff))
+ ((d)->des1 = ((d)->des1 & 0x03ffe000) | ((s) & 0x1fff))
u32 des2; /* buffer 1 physical address */
@@ -100,6 +112,27 @@ struct dw_mci_slot {
int last_detect_state;
};
+#define MAX_TUING_LOOP 40
+
+static const u8 tuning_blk_pattern[] = {
+ 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc,
+ 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff,
+ 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff,
+ 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd,
+ 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb,
+ 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff,
+ 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff,
+ 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc,
+ 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff,
+ 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee,
+ 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd,
+ 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff,
+ 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff,
+ 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee,
+};
+
#if defined(CONFIG_DEBUG_FS)
static int dw_mci_req_show(struct seq_file *s, void *v)
{
@@ -221,6 +254,54 @@ err:
}
#endif /* defined(CONFIG_DEBUG_FS) */
+static void dw_mci_clear_set_irqs(struct dw_mci *host, u32 clear, u32 set)
+{
+ u32 ier;
+
+ /* clear interrupt */
+ mci_writel(host, RINTSTS, clear);
+
+ ier = mci_readl(host, INTMASK);
+
+ ier &= ~clear;
+ ier |= set;
+
+ mci_writel(host, INTMASK, ier);
+}
+
+static void dw_mci_unmask_irqs(struct dw_mci *host, u32 irqs)
+{
+ dw_mci_clear_set_irqs(host, 0, irqs);
+}
+
+static void dw_mci_mask_irqs(struct dw_mci *host, u32 irqs)
+{
+ dw_mci_clear_set_irqs(host, irqs, 0);
+}
+
+static void dw_mci_set_card_detection(struct dw_mci *host, bool enable)
+{
+ u32 irqs = SDMMC_INT_CD;
+
+ if (host->quirks & DW_MCI_QUIRK_BROKEN_CARD_DETECTION)
+ return;
+
+ if (enable)
+ dw_mci_unmask_irqs(host, irqs);
+ else
+ dw_mci_mask_irqs(host, irqs);
+}
+
+static void dw_mci_enable_card_detection(struct dw_mci *host)
+{
+ dw_mci_set_card_detection(host, true);
+}
+
+static void dw_mci_disable_card_detection(struct dw_mci *host)
+{
+ dw_mci_set_card_detection(host, false);
+}
+
static void dw_mci_set_timeout(struct dw_mci *host)
{
/* timeout (maximum) */
@@ -230,6 +311,7 @@ static void dw_mci_set_timeout(struct dw_mci *host)
static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)
{
struct mmc_data *data;
+ struct dw_mci_slot *slot = mmc_priv(mmc);
u32 cmdr;
cmd->error = -EINPROGRESS;
@@ -259,6 +341,10 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)
cmdr |= SDMMC_CMD_DAT_WR;
}
+ /* Use hold bit register */
+ if (slot->host->pdata->set_io_timing)
+ cmdr |= SDMMC_USE_HOLD_REG;
+
return cmdr;
}
@@ -284,7 +370,7 @@ static void send_stop_cmd(struct dw_mci *host, struct mmc_data *data)
/* DMA interface functions */
static void dw_mci_stop_dma(struct dw_mci *host)
{
- if (host->use_dma) {
+ if (host->using_dma) {
host->dma_ops->stop(host);
host->dma_ops->cleanup(host);
} else {
@@ -299,9 +385,10 @@ static void dw_mci_dma_cleanup(struct dw_mci *host)
struct mmc_data *data = host->data;
if (data)
- dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
- ((data->flags & MMC_DATA_WRITE)
- ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+ if (!data->host_cookie)
+ dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
+ ((data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
}
static void dw_mci_idmac_stop_dma(struct dw_mci *host)
@@ -398,7 +485,7 @@ static int dw_mci_idmac_init(struct dw_mci *host)
int i;
/* Number of descriptors in the ring buffer */
- host->ring_size = PAGE_SIZE / sizeof(struct idmac_desc);
+ host->ring_size = host->buf_size / sizeof(struct idmac_desc);
/* Forward link the descriptor list */
for (i = 0, p = host->sg_cpu; i < host->ring_size - 1; i++, p++)
@@ -417,24 +504,15 @@ static int dw_mci_idmac_init(struct dw_mci *host)
return 0;
}
-static struct dw_mci_dma_ops dw_mci_idmac_ops = {
- .init = dw_mci_idmac_init,
- .start = dw_mci_idmac_start_dma,
- .stop = dw_mci_idmac_stop_dma,
- .complete = dw_mci_idmac_complete_dma,
- .cleanup = dw_mci_dma_cleanup,
-};
-#endif /* CONFIG_MMC_DW_IDMAC */
-
-static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
+static int dw_mci_pre_dma_transfer(struct dw_mci *host,
+ struct mmc_data *data,
+ int next)
{
struct scatterlist *sg;
- unsigned int i, direction, sg_len;
- u32 temp;
+ int i, sg_len;
- /* If we don't have a channel, we can't do DMA */
- if (!host->use_dma)
- return -ENODEV;
+ if (!next && data->host_cookie)
+ return data->host_cookie;
/*
* We don't do DMA on "complex" transfers, i.e. with
@@ -443,6 +521,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
*/
if (data->blocks * data->blksz < DW_MCI_DMA_THRESHOLD)
return -EINVAL;
+
if (data->blksz & 3)
return -EINVAL;
@@ -451,13 +530,95 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
return -EINVAL;
}
- if (data->flags & MMC_DATA_READ)
- direction = DMA_FROM_DEVICE;
- else
- direction = DMA_TO_DEVICE;
+ sg_len = dma_map_sg(&host->pdev->dev, data->sg,
+ data->sg_len, ((data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+ if (sg_len == 0)
+ return -EINVAL;
+
+ if (next)
+ data->host_cookie = sg_len;
- sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len,
- direction);
+ return sg_len;
+}
+
+static void dw_mci_pre_req(struct mmc_host *mmc,
+ struct mmc_request *mrq,
+ bool is_first_req)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+
+ if (!data)
+ return;
+
+ if (data->host_cookie) {
+ data->host_cookie = 0;
+ return;
+ }
+
+ if (slot->host->use_dma) {
+ if (dw_mci_pre_dma_transfer(slot->host, mrq->data, 1) < 0)
+ data->host_cookie = 0;
+ }
+}
+
+static void dw_mci_post_req(struct mmc_host *mmc,
+ struct mmc_request *mrq,
+ int err)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+
+ if (!data)
+ return;
+
+ if (slot->host->use_dma) {
+ if (data->host_cookie)
+ dma_unmap_sg(&slot->host->pdev->dev, data->sg,
+ data->sg_len,
+ ((data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+ data->host_cookie = 0;
+ }
+}
+
+static struct dw_mci_dma_ops dw_mci_idmac_ops = {
+ .init = dw_mci_idmac_init,
+ .start = dw_mci_idmac_start_dma,
+ .stop = dw_mci_idmac_stop_dma,
+ .complete = dw_mci_idmac_complete_dma,
+ .cleanup = dw_mci_dma_cleanup,
+};
+#else
+static int dw_mci_pre_dma_transfer(struct dw_mci *host,
+ struct mmc_data *data,
+ bool next)
+{
+ return -ENOSYS;
+}
+#define dw_mci_pre_req NULL
+#define dw_mci_post_req NULL
+#endif /* CONFIG_MMC_DW_IDMAC */
+
+static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
+{
+ int sg_len;
+ u32 temp;
+
+ host->using_dma = 0;
+
+ /* If we don't have a channel, we can't do DMA */
+ if (!host->use_dma)
+ return -ENODEV;
+
+ sg_len = dw_mci_pre_dma_transfer(host, data, 0);
+ if (sg_len < 0) {
+ host->dma_ops->stop(host);
+ return sg_len;
+ }
+
+ host->using_dma = 1;
dev_vdbg(&host->pdev->dev,
"sd sg_cpu: %#lx sg_dma: %#lx sg_len: %d\n",
@@ -470,6 +631,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
mci_writel(host, CTRL, temp);
/* Disable RX/TX IRQs, let DMA handle it */
+ mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR);
temp = mci_readl(host, INTMASK);
temp &= ~(SDMMC_INT_RXDR | SDMMC_INT_TXDR);
mci_writel(host, INTMASK, temp);
@@ -490,13 +652,20 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
host->data = data;
if (dw_mci_submit_data_dma(host, data)) {
+ int flags = SG_MITER_ATOMIC;
+ if (host->data->flags & MMC_DATA_READ)
+ flags |= SG_MITER_TO_SG;
+ else
+ flags |= SG_MITER_FROM_SG;
+
+ sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
host->sg = data->sg;
- host->pio_offset = 0;
if (data->flags & MMC_DATA_READ)
host->dir_status = DW_MCI_RECV_STATUS;
else
host->dir_status = DW_MCI_SEND_STATUS;
+ mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR);
temp = mci_readl(host, INTMASK);
temp |= SDMMC_INT_TXDR | SDMMC_INT_RXDR;
mci_writel(host, INTMASK, temp);
@@ -574,17 +743,17 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot)
}
/* Set the current slot bus width */
- mci_writel(host, CTYPE, slot->ctype);
+ mci_writel(host, CTYPE, (slot->ctype << slot->id));
}
-static void dw_mci_start_request(struct dw_mci *host,
- struct dw_mci_slot *slot)
+static void __dw_mci_start_request(struct dw_mci *host,
+ struct dw_mci_slot *slot, struct mmc_command *cmd)
{
struct mmc_request *mrq;
- struct mmc_command *cmd;
struct mmc_data *data;
u32 cmdflags;
+ host->prv_err = 0;
mrq = slot->mrq;
if (host->pdata->select_slot)
host->pdata->select_slot(slot->id);
@@ -599,14 +768,13 @@ static void dw_mci_start_request(struct dw_mci *host,
host->completed_events = 0;
host->data_status = 0;
- data = mrq->data;
+ data = cmd->data;
if (data) {
dw_mci_set_timeout(host);
mci_writel(host, BYTCNT, data->blksz*data->blocks);
mci_writel(host, BLKSIZ, data->blksz);
}
- cmd = mrq->cmd;
cmdflags = dw_mci_prepare_command(slot->mmc, cmd);
/* this is the first command, send the initialization clock */
@@ -624,6 +792,17 @@ static void dw_mci_start_request(struct dw_mci *host,
host->stop_cmdr = dw_mci_prepare_command(slot->mmc, mrq->stop);
}
+static void dw_mci_start_request(struct dw_mci *host,
+ struct dw_mci_slot *slot)
+{
+ struct mmc_request *mrq = slot->mrq;
+ struct mmc_command *cmd;
+
+ cmd = mrq->sbc ? mrq->sbc : mrq->cmd;
+ __dw_mci_start_request(host, slot, cmd);
+}
+
+/* must be called with host->lock held */
static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
struct mmc_request *mrq)
{
@@ -647,15 +826,44 @@ static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct dw_mci_slot *slot = mmc_priv(mmc);
struct dw_mci *host = slot->host;
+ ktime_t expr;
+ u64 add_time = 50000; /* 50us */
+ int timeout = 100000;
WARN_ON(slot->mrq);
if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) {
mrq->cmd->error = -ENOMEDIUM;
+ host->prv_err = 1;
mmc_request_done(mmc, mrq);
return;
}
+ do {
+ if (mrq->cmd->opcode == MMC_STOP_TRANSMISSION)
+ break;
+
+ if (mci_readl(host, STATUS) & (1 << 9)) {
+ if (!timeout) {
+ printk(KERN_ERR "%s: Data0: Never released\n",
+ mmc_hostname(mmc));
+ mrq->cmd->error = -ENOTRECOVERABLE;
+ host->prv_err = 1;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+ if (host->prv_err) {
+ udelay(10);
+ } else {
+ expr = ktime_add_ns(ktime_get(), add_time);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_hrtimeout(&expr, HRTIMER_MODE_ABS);
+ }
+ timeout--;
+ } else
+ break;
+ } while(1);
+
/* We don't support multiple blocks of weird lengths. */
dw_mci_queue_request(host, slot, mrq);
}
@@ -680,12 +888,19 @@ static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
break;
}
+ regs = mci_readl(slot->host, UHS_REG);
+
/* DDR mode set */
- if (ios->ddr) {
- regs = mci_readl(slot->host, UHS_REG);
+ if (ios->timing == MMC_TIMING_UHS_DDR50)
regs |= (0x1 << slot->id) << 16;
- mci_writel(slot->host, UHS_REG, regs);
- }
+ else
+ /* 1, 4, 8 Bit SDR */
+ regs &= ~(0x1 << slot->id) << 16;
+
+ mci_writel(slot->host, UHS_REG, regs);
+
+ if (slot->host->pdata->set_io_timing)
+ slot->host->pdata->set_io_timing(slot->host, ios->timing);
if (ios->clock) {
/*
@@ -702,6 +917,9 @@ static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
default:
break;
}
+
+ if (slot->host->pdata->cfg_gpio)
+ slot->host->pdata->cfg_gpio(mmc->ios.bus_width);
}
static int dw_mci_get_ro(struct mmc_host *mmc)
@@ -746,11 +964,186 @@ static int dw_mci_get_cd(struct mmc_host *mmc)
return present;
}
+static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct dw_mci *host = slot->host;
+ u32 int_mask;
+
+ /* Enable/disable Slot Specific SDIO interrupt */
+ int_mask = mci_readl(host, INTMASK);
+ if (enb) {
+ mci_writel(host, INTMASK,
+ (int_mask | (1 << SDMMC_INT_SDIO(slot->id))));
+ } else {
+ mci_writel(host, INTMASK,
+ (int_mask & ~(1 << SDMMC_INT_SDIO(slot->id))));
+ }
+}
+
+static u8 dw_mci_tuning_sampling(struct dw_mci * host)
+{
+ u32 clksel;
+ u8 sample;
+
+ clksel = mci_readl(host, CLKSEL);
+ sample = clksel & 0x7;
+ sample = (++sample == 8) ? 0 : sample;
+ clksel = (clksel & 0xfffffff8) | (sample & 0x7);
+ mci_writel(host, CLKSEL, clksel);
+
+ return sample;
+}
+
+static void dw_mci_set_sampling(struct dw_mci * host, u8 sample)
+{
+ u32 clksel;
+
+ clksel = mci_readl(host, CLKSEL);
+ clksel = (clksel & 0xfffffff8) | (sample & 0x7);
+ mci_writel(host, CLKSEL, clksel);
+}
+
+static u8 dw_mci_get_sampling(struct dw_mci * host)
+{
+ u32 clksel;
+ u8 sample;
+
+ clksel = mci_readl(host, CLKSEL);
+ sample = clksel & 0x7;
+
+ return sample;
+}
+
+static u8 get_median_sample(u8 map)
+{
+ u8 min = 0, max = 0;
+ u8 pos;
+ u8 i;
+
+ for (i = 0; i < 4; i++) {
+ if ((map >> (4 + i)) & 0x1)
+ max = 4 + i;
+ if ((map >> (3 - i)) & 0x1)
+ min = 3 - i;
+ }
+
+ pos = max;
+ do {
+ max = pos;
+ pos = DIV_ROUND_CLOSEST(min + max, 2);
+ if ((map >> pos) & 0x1)
+ break;
+
+ } while(pos != max);
+
+ return pos;
+}
+
+static int dw_mci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct dw_mci *host = slot->host;
+ unsigned int tuning_loop = MAX_TUING_LOOP;
+ u8 *tuning_blk;
+ u8 blksz;
+ u8 tune, start_tune;
+ u8 map = 0, mid;
+
+ if (opcode == MMC_SEND_TUNING_BLOCK_HS200) {
+ if (mmc->ios.bus_width == MMC_BUS_WIDTH_8)
+ blksz = 128;
+ else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4)
+ blksz = 64;
+ else
+ return -EINVAL;
+ } else if (opcode == MMC_SEND_TUNING_BLOCK) {
+ blksz = 64;
+ } else {
+ dev_err(&mmc->class_dev,
+ "Undefined command(%d) for tuning\n",
+ opcode);
+ return -EINVAL;
+ }
+
+ tuning_blk = kmalloc(blksz, GFP_KERNEL);
+ if (!tuning_blk)
+ return -ENOMEM;
+
+ start_tune = dw_mci_get_sampling(host);
+
+ do {
+ struct mmc_request mrq = {NULL};
+ struct mmc_command cmd = {0};
+ struct mmc_command stop = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+
+ cmd.opcode = opcode;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
+
+ data.blksz = blksz;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, tuning_blk, blksz);
+ dw_mci_set_timeout(host);
+
+ mrq.cmd = &cmd;
+ mrq.stop = &stop;
+ mrq.data = &data;
+ host->mrq = &mrq;
+
+ tune = dw_mci_tuning_sampling(host);
+
+ mmc_wait_for_req(mmc, &mrq);
+
+ if (!cmd.error && !data.error) {
+ if (!memcmp(tuning_blk_pattern, tuning_blk, blksz))
+ map |= (1 << tune);
+ } else {
+ dev_dbg(&mmc->class_dev,
+ "Tuning error: cmd.error:%d, data.error:%d\n",
+ cmd.error, data.error);
+ }
+
+ if (start_tune == tune) {
+ if (!map) {
+ tuning_loop = 0;
+ break;
+ }
+
+ mid = get_median_sample(map);
+ dw_mci_set_sampling(host, mid);
+ break;
+ }
+
+ } while(--tuning_loop);
+
+ kfree(tuning_blk);
+
+ if (!tuning_loop)
+ return -EIO;
+
+ return 0;
+}
+
static const struct mmc_host_ops dw_mci_ops = {
- .request = dw_mci_request,
- .set_ios = dw_mci_set_ios,
- .get_ro = dw_mci_get_ro,
- .get_cd = dw_mci_get_cd,
+ .request = dw_mci_request,
+ .pre_req = dw_mci_pre_req,
+ .post_req = dw_mci_post_req,
+ .set_ios = dw_mci_set_ios,
+ .get_ro = dw_mci_get_ro,
+ .get_cd = dw_mci_get_cd,
+ .enable_sdio_irq = dw_mci_enable_sdio_irq,
+ .execute_tuning = dw_mci_execute_tuning,
};
static void dw_mci_request_end(struct dw_mci *host, struct mmc_request *mrq)
@@ -821,6 +1214,7 @@ static void dw_mci_command_complete(struct dw_mci *host, struct mmc_command *cmd
host->data = NULL;
dw_mci_stop_dma(host);
}
+ host->prv_err = 1;
}
}
@@ -853,7 +1247,13 @@ static void dw_mci_tasklet_func(unsigned long priv)
cmd = host->cmd;
host->cmd = NULL;
set_bit(EVENT_CMD_COMPLETE, &host->completed_events);
- dw_mci_command_complete(host, host->mrq->cmd);
+ dw_mci_command_complete(host, cmd);
+ if ((cmd == host->mrq->sbc) && !cmd->error) {
+ prev_state = state = STATE_SENDING_CMD;
+ __dw_mci_start_request(host, host->cur_slot, host->mrq->cmd);
+ goto unlock;
+ }
+
if (!host->mrq->data || cmd->error) {
dw_mci_request_end(host, host->mrq);
goto unlock;
@@ -905,6 +1305,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
status);
data->error = -EIO;
}
+ host->prv_err = 1;
} else {
data->bytes_xfered = data->blocks * data->blksz;
data->error = 0;
@@ -915,6 +1316,12 @@ static void dw_mci_tasklet_func(unsigned long priv)
goto unlock;
}
+ if (host->mrq->sbc && !data->error) {
+ data->stop->error = 0;
+ dw_mci_request_end(host, host->mrq);
+ goto unlock;
+ }
+
prev_state = state = STATE_SENDING_STOP;
if (!data->error)
send_stop_cmd(host, data);
@@ -954,7 +1361,7 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt)
cnt = cnt >> 1;
while (cnt > 0) {
- mci_writew(host, DATA, *pdata++);
+ mci_writew(host, DATA(host->data_offset), *pdata++);
cnt--;
}
}
@@ -967,7 +1374,7 @@ static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt)
cnt = cnt >> 1;
while (cnt > 0) {
- *pdata++ = mci_readw(host, DATA);
+ *pdata++ = mci_readw(host, DATA(host->data_offset));
cnt--;
}
}
@@ -981,7 +1388,7 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt)
cnt = cnt >> 2;
while (cnt > 0) {
- mci_writel(host, DATA, *pdata++);
+ mci_writel(host, DATA(host->data_offset), *pdata++);
cnt--;
}
}
@@ -995,7 +1402,7 @@ static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt)
cnt = cnt >> 2;
while (cnt > 0) {
- *pdata++ = mci_readl(host, DATA);
+ *pdata++ = mci_readl(host, DATA(host->data_offset));
cnt--;
}
}
@@ -1008,7 +1415,7 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt)
cnt = cnt >> 3;
while (cnt > 0) {
- mci_writeq(host, DATA, *pdata++);
+ mci_writeq(host, DATA(host->data_offset), *pdata++);
cnt--;
}
}
@@ -1021,60 +1428,49 @@ static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt)
cnt = cnt >> 3;
while (cnt > 0) {
- *pdata++ = mci_readq(host, DATA);
+ *pdata++ = mci_readq(host, DATA(host->data_offset));
cnt--;
}
}
static void dw_mci_read_data_pio(struct dw_mci *host)
{
- struct scatterlist *sg = host->sg;
- void *buf = sg_virt(sg);
- unsigned int offset = host->pio_offset;
+ struct sg_mapping_iter *sg_miter = &host->sg_miter;
+ void *buf;
+ unsigned int offset;
struct mmc_data *data = host->data;
int shift = host->data_shift;
u32 status;
unsigned int nbytes = 0, len;
+ unsigned int remain, fcnt;
do {
- len = SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift;
- if (offset + len <= sg->length) {
- host->pull_data(host, (void *)(buf + offset), len);
+ if (!sg_miter_next(sg_miter))
+ goto done;
- offset += len;
- nbytes += len;
-
- if (offset == sg->length) {
- flush_dcache_page(sg_page(sg));
- host->sg = sg = sg_next(sg);
- if (!sg)
- goto done;
+ buf = sg_miter->addr;
+ remain = sg_miter->length;
+ offset = 0;
- offset = 0;
- buf = sg_virt(sg);
- }
- } else {
- unsigned int remaining = sg->length - offset;
- host->pull_data(host, (void *)(buf + offset),
- remaining);
- nbytes += remaining;
-
- flush_dcache_page(sg_page(sg));
- host->sg = sg = sg_next(sg);
- if (!sg)
- goto done;
-
- offset = len - remaining;
- buf = sg_virt(sg);
- host->pull_data(host, buf, offset);
- nbytes += offset;
- }
+ do {
+ fcnt = SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift;
+ len = min(remain, fcnt);
+ if (!len)
+ break;
+ host->pull_data(host, (void *)(buf + offset), len);
+ nbytes += len;
+ offset += len;
+ remain -= len;
+ } while (remain);
+ sg_miter->consumed = offset;
status = mci_readl(host, MINTSTS);
mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
if (status & DW_MCI_DATA_ERROR_FLAGS) {
host->data_status = status;
data->bytes_xfered += nbytes;
+ sg_miter_stop(sg_miter);
+ host->sg = NULL;
smp_wmb();
set_bit(EVENT_DATA_ERROR, &host->pending_events);
@@ -1083,65 +1479,64 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
return;
}
} while (status & SDMMC_INT_RXDR); /*if the RXDR is ready read again*/
- len = SDMMC_GET_FCNT(mci_readl(host, STATUS));
- host->pio_offset = offset;
data->bytes_xfered += nbytes;
+
+ if (!remain) {
+ if (!sg_miter_next(sg_miter))
+ goto done;
+ sg_miter->consumed = 0;
+ }
+ sg_miter_stop(sg_miter);
return;
done:
data->bytes_xfered += nbytes;
+ sg_miter_stop(sg_miter);
+ host->sg = NULL;
smp_wmb();
set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
}
static void dw_mci_write_data_pio(struct dw_mci *host)
{
- struct scatterlist *sg = host->sg;
- void *buf = sg_virt(sg);
- unsigned int offset = host->pio_offset;
+ struct sg_mapping_iter *sg_miter = &host->sg_miter;
+ void *buf;
+ unsigned int offset;
struct mmc_data *data = host->data;
int shift = host->data_shift;
u32 status;
unsigned int nbytes = 0, len;
+ unsigned int fifo_depth = host->fifo_depth;
+ unsigned int remain, fcnt;
do {
- len = SDMMC_FIFO_SZ -
- (SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift);
- if (offset + len <= sg->length) {
+ if (!sg_miter_next(sg_miter))
+ goto done;
+
+ buf = sg_miter->addr;
+ remain = sg_miter->length;
+ offset = 0;
+
+ do {
+ fcnt = SDMMC_GET_FCNT(mci_readl(host, STATUS));
+ fcnt = (fifo_depth - fcnt) << shift;
+ len = min(remain, fcnt);
+ if (!len)
+ break;
host->push_data(host, (void *)(buf + offset), len);
-
- offset += len;
nbytes += len;
- if (offset == sg->length) {
- host->sg = sg = sg_next(sg);
- if (!sg)
- goto done;
-
- offset = 0;
- buf = sg_virt(sg);
- }
- } else {
- unsigned int remaining = sg->length - offset;
-
- host->push_data(host, (void *)(buf + offset),
- remaining);
- nbytes += remaining;
-
- host->sg = sg = sg_next(sg);
- if (!sg)
- goto done;
-
- offset = len - remaining;
- buf = sg_virt(sg);
- host->push_data(host, (void *)buf, offset);
- nbytes += offset;
- }
+ offset += len;
+ remain -= len;
+ } while (remain);
+ sg_miter->consumed = offset;
status = mci_readl(host, MINTSTS);
mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
if (status & DW_MCI_DATA_ERROR_FLAGS) {
host->data_status = status;
data->bytes_xfered += nbytes;
+ sg_miter_stop(sg_miter);
+ host->sg = NULL;
smp_wmb();
@@ -1151,14 +1546,20 @@ static void dw_mci_write_data_pio(struct dw_mci *host)
return;
}
} while (status & SDMMC_INT_TXDR); /* if TXDR write again */
-
- host->pio_offset = offset;
data->bytes_xfered += nbytes;
+ if (!remain) {
+ if (!sg_miter_next(sg_miter))
+ goto done;
+ sg_miter->consumed = 0;
+ }
+ sg_miter_stop(sg_miter);
return;
done:
data->bytes_xfered += nbytes;
+ sg_miter_stop(sg_miter);
+ host->sg = NULL;
smp_wmb();
set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
}
@@ -1179,6 +1580,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
struct dw_mci *host = dev_id;
u32 status, pending;
unsigned int pass_count = 0;
+ int i;
do {
status = mci_readl(host, RINTSTS);
@@ -1202,7 +1604,8 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
host->cmd_status = status;
smp_wmb();
set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
- tasklet_schedule(&host->tasklet);
+ if (!(pending & SDMMC_INT_RTO))
+ tasklet_schedule(&host->tasklet);
}
if (pending & DW_MCI_DATA_ERROR_FLAGS) {
@@ -1211,7 +1614,8 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
host->data_status = status;
smp_wmb();
set_bit(EVENT_DATA_ERROR, &host->pending_events);
- tasklet_schedule(&host->tasklet);
+ if (!(pending & SDMMC_INT_DTO))
+ tasklet_schedule(&host->tasklet);
}
if (pending & SDMMC_INT_DATA_OVER) {
@@ -1223,6 +1627,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
if (host->sg != NULL)
dw_mci_read_data_pio(host);
}
+
set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
tasklet_schedule(&host->tasklet);
}
@@ -1249,6 +1654,15 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
tasklet_schedule(&host->card_tasklet);
}
+ /* Handle SDIO Interrupts */
+ for (i = 0; i < host->num_slots; i++) {
+ struct dw_mci_slot *slot = host->slot[i];
+ if (pending & SDMMC_INT_SDIO(i)) {
+ mci_writel(host, RINTSTS, SDMMC_INT_SDIO(i));
+ mmc_signal_sdio_irq(slot->mmc);
+ }
+ }
+
} while (pass_count++ < 5);
#ifdef CONFIG_MMC_DW_IDMAC
@@ -1308,6 +1722,7 @@ static void dw_mci_tasklet_card(unsigned long data)
break;
case STATE_SENDING_CMD:
mrq->cmd->error = -ENOMEDIUM;
+ host->prv_err = 1;
if (!mrq->data)
break;
/* fall through */
@@ -1331,6 +1746,7 @@ static void dw_mci_tasklet_card(unsigned long data)
} else {
list_del(&slot->queue_node);
mrq->cmd->error = -ENOMEDIUM;
+ host->prv_err = 1;
if (mrq->data)
mrq->data->error = -ENOMEDIUM;
if (mrq->stop)
@@ -1353,6 +1769,7 @@ static void dw_mci_tasklet_card(unsigned long data)
* block interrupt, hence setting the
* scatter-gather pointer to NULL.
*/
+ sg_miter_stop(&host->sg_miter);
host->sg = NULL;
ctrl = mci_readl(host, CTRL);
@@ -1376,6 +1793,34 @@ static void dw_mci_tasklet_card(unsigned long data)
}
}
+static void dw_mci_notify_change(struct platform_device *dev, int state)
+{
+ struct dw_mci *host = platform_get_drvdata(dev);
+ unsigned long flags;
+
+ if (host) {
+ spin_lock_irqsave(&host->lock, flags);
+ if (state) {
+ dev_dbg(&dev->dev, "card inserted.\n");
+ host->quirks |= DW_MCI_QUIRK_BROKEN_CARD_DETECTION;
+ } else {
+ dev_dbg(&dev->dev, "card removed.\n");
+ host->quirks &= ~DW_MCI_QUIRK_BROKEN_CARD_DETECTION;
+ }
+ tasklet_schedule(&host->card_tasklet);
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+}
+
+static irqreturn_t dw_mci_detect_interrupt(int irq, void *dev_id)
+{
+ struct dw_mci_slot *slot = dev_id;
+
+ tasklet_schedule(&slot->host->card_tasklet);
+
+ return IRQ_HANDLED;
+}
+
static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id)
{
struct mmc_host *mmc;
@@ -1411,20 +1856,18 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id)
else
mmc->caps = 0;
+ if (host->pdata->caps2)
+ mmc->caps2 = host->pdata->caps2;
+ else
+ mmc->caps2 = 0;
+
if (host->pdata->get_bus_wd)
if (host->pdata->get_bus_wd(slot->id) >= 4)
mmc->caps |= MMC_CAP_4_BIT_DATA;
if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED)
- mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
-#ifdef CONFIG_MMC_DW_IDMAC
- mmc->max_segs = host->ring_size;
- mmc->max_blk_size = 65536;
- mmc->max_blk_count = host->ring_size;
- mmc->max_seg_size = 0x1000;
- mmc->max_req_size = mmc->max_seg_size * mmc->max_blk_count;
-#else
if (host->pdata->blk_settings) {
mmc->max_segs = host->pdata->blk_settings->max_segs;
mmc->max_blk_size = host->pdata->blk_settings->max_blk_size;
@@ -1432,14 +1875,21 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id)
mmc->max_req_size = host->pdata->blk_settings->max_req_size;
mmc->max_seg_size = host->pdata->blk_settings->max_seg_size;
} else {
+#ifdef CONFIG_MMC_DW_IDMAC
+ mmc->max_segs = host->ring_size;
+ mmc->max_blk_size = 65536;
+ mmc->max_seg_size = 0x1000;
+ mmc->max_req_size = mmc->max_seg_size * host->ring_size;
+ mmc->max_blk_count = mmc->max_req_size / 512;
+#else
/* Useful defaults if platform data is unset. */
mmc->max_segs = 64;
mmc->max_blk_size = 65536; /* BLKSIZ is 16 bits */
mmc->max_blk_count = 512;
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
mmc->max_seg_size = mmc->max_req_size;
- }
#endif /* CONFIG_MMC_DW_IDMAC */
+ }
host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
if (IS_ERR(host->vmmc)) {
@@ -1448,6 +1898,8 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id)
} else
regulator_enable(host->vmmc);
+ host->pdata->init(id, dw_mci_detect_interrupt, host);
+
if (dw_mci_get_cd(mmc))
set_bit(DW_MMC_CARD_PRESENT, &slot->flags);
else
@@ -1486,8 +1938,13 @@ static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id)
static void dw_mci_init_dma(struct dw_mci *host)
{
+ if (host->pdata->buf_size)
+ host->buf_size = host->pdata->buf_size;
+ else
+ host->buf_size = PAGE_SIZE;
+
/* Alloc memory for sg translation */
- host->sg_cpu = dma_alloc_coherent(&host->pdev->dev, PAGE_SIZE,
+ host->sg_cpu = dma_alloc_coherent(&host->pdev->dev, host->buf_size,
&host->sg_dma, GFP_KERNEL);
if (!host->sg_cpu) {
dev_err(&host->pdev->dev, "%s: could not alloc DMA memory\n",
@@ -1588,6 +2045,28 @@ static int dw_mci_probe(struct platform_device *pdev)
goto err_freehost;
}
+ host->hclk = clk_get(&pdev->dev, pdata->hclk_name);
+ if (IS_ERR(host->hclk)) {
+ dev_err(&pdev->dev,
+ "failed to get hclk\n");
+ ret = PTR_ERR(host->hclk);
+ goto err_freehost;
+ }
+ clk_enable(host->hclk);
+
+ host->cclk = clk_get(&pdev->dev, pdata->cclk_name);
+ if (IS_ERR(host->cclk)) {
+ dev_err(&pdev->dev,
+ "failed to get cclk\n");
+ ret = PTR_ERR(host->cclk);
+ goto err_free_hclk;
+ }
+ clk_enable(host->cclk);
+
+ if ((soc_is_exynos4412() || soc_is_exynos4212())
+ && (samsung_rev() < EXYNOS4412_REV_1_0))
+ pdata->bus_hz = 66 * 1000 * 1000;
+
host->bus_hz = pdata->bus_hz;
host->quirks = pdata->quirks;
@@ -1597,7 +2076,7 @@ static int dw_mci_probe(struct platform_device *pdev)
ret = -ENOMEM;
host->regs = ioremap(regs->start, regs->end - regs->start + 1);
if (!host->regs)
- goto err_freehost;
+ goto err_free_cclk;
host->dma_ops = pdata->dma_ops;
dw_mci_init_dma(host);
@@ -1645,8 +2124,19 @@ static int dw_mci_probe(struct platform_device *pdev)
* FIFO threshold settings RxMark = fifo_size / 2 - 1,
* Tx Mark = fifo_size / 2 DMA Size = 8
*/
- fifo_size = mci_readl(host, FIFOTH);
- fifo_size = (fifo_size >> 16) & 0x7ff;
+ if (!host->pdata->fifo_depth) {
+ /*
+ * Power-on value of RX_WMark is FIFO_DEPTH-1, but this may
+ * have been overwritten by the bootloader, just like we're
+ * about to do, so if you know the value for your hardware, you
+ * should put it in the platform data.
+ */
+ fifo_size = mci_readl(host, FIFOTH);
+ fifo_size = 1 + ((fifo_size >> 16) & 0xfff);
+ } else {
+ fifo_size = host->pdata->fifo_depth;
+ }
+ host->fifo_depth = fifo_size;
host->fifoth_val = ((0x2 << 28) | ((fifo_size/2 - 1) << 16) |
((fifo_size/2) << 0));
mci_writel(host, FIFOTH, host->fifoth_val);
@@ -1680,6 +2170,24 @@ static int dw_mci_probe(struct platform_device *pdev)
}
/*
+ * In 2.40a spec, Data offset is changed.
+ * Need to check the version-id and set data-offset for DATA register.
+ */
+ host->verid = SDMMC_GET_VERID(mci_readl(host, VERID));
+ dev_info(&pdev->dev, "Version ID is %04x\n", host->verid);
+
+ if (host->verid < DW_MMC_240A)
+ host->data_offset = DATA_OFFSET;
+ else
+ host->data_offset = DATA_240A_OFFSET;
+
+ if (host->pdata->cd_type == DW_MCI_CD_EXTERNAL) {
+ host->pdata->ext_cd_init(&dw_mci_notify_change);
+ if (host->pdata->caps == MMC_CAP_UHS_SDR50 && samsung_rev() >= EXYNOS5250_REV_1_0)
+ clk_set_rate(host->cclk, 200 * 100 * 100);
+ }
+
+ /*
* Enable interrupts for command done, data over, data empty, card det,
* receive ready and error such as transmit, receive timeout, crc error
*/
@@ -1690,7 +2198,9 @@ static int dw_mci_probe(struct platform_device *pdev)
mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE); /* Enable mci interrupt */
dev_info(&pdev->dev, "DW MMC controller at irq %d, "
- "%d bit host data width\n", irq, width);
+ "%d bit host data width, "
+ "%u deep fifo\n",
+ irq, width, fifo_size);
if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO)
dev_info(&pdev->dev, "Internal DMAC interrupt fix enabled.\n");
@@ -1708,7 +2218,7 @@ err_init_slot:
err_dmaunmap:
if (host->use_dma && host->dma_ops->exit)
host->dma_ops->exit(host);
- dma_free_coherent(&host->pdev->dev, PAGE_SIZE,
+ dma_free_coherent(&host->pdev->dev, host->buf_size,
host->sg_cpu, host->sg_dma);
iounmap(host->regs);
@@ -1717,6 +2227,13 @@ err_dmaunmap:
regulator_put(host->vmmc);
}
+err_free_cclk:
+ clk_disable(host->cclk);
+ clk_put(host->cclk);
+
+err_free_hclk:
+ clk_disable(host->hclk);
+ clk_put(host->hclk);
err_freehost:
kfree(host);
@@ -1731,6 +2248,10 @@ static int __exit dw_mci_remove(struct platform_device *pdev)
mci_writel(host, RINTSTS, 0xFFFFFFFF);
mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */
+ if (host->pdata->cd_type == DW_MCI_CD_EXTERNAL) {
+ host->pdata->ext_cd_cleanup(&dw_mci_notify_change);
+ }
+
platform_set_drvdata(pdev, NULL);
for (i = 0; i < host->num_slots; i++) {
@@ -1744,7 +2265,8 @@ static int __exit dw_mci_remove(struct platform_device *pdev)
mci_writel(host, CLKSRC, 0);
free_irq(platform_get_irq(pdev, 0), host);
- dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+ dma_free_coherent(&pdev->dev, host->buf_size, host->sg_cpu,
+ host->sg_dma);
if (host->use_dma && host->dma_ops->exit)
host->dma_ops->exit(host);
@@ -1754,6 +2276,11 @@ static int __exit dw_mci_remove(struct platform_device *pdev)
regulator_put(host->vmmc);
}
+ clk_disable(host->cclk);
+ clk_put(host->cclk);
+ clk_disable(host->hclk);
+ clk_put(host->hclk);
+
iounmap(host->regs);
kfree(host);
@@ -1769,6 +2296,8 @@ static int dw_mci_suspend(struct platform_device *pdev, pm_message_t mesg)
int i, ret;
struct dw_mci *host = platform_get_drvdata(pdev);
+ dw_mci_disable_card_detection(host);
+
for (i = 0; i < host->num_slots; i++) {
struct dw_mci_slot *slot = host->slot[i];
if (!slot)
diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h
index 23c662a..345e2d7 100644
--- a/drivers/mmc/host/dw_mmc.h
+++ b/drivers/mmc/host/dw_mmc.h
@@ -14,6 +14,8 @@
#ifndef _DW_MMC_H_
#define _DW_MMC_H_
+#define DW_MMC_240A 0x240a
+
#define SDMMC_CTRL 0x000
#define SDMMC_PWREN 0x004
#define SDMMC_CLKDIV 0x008
@@ -51,7 +53,15 @@
#define SDMMC_IDINTEN 0x090
#define SDMMC_DSCADDR 0x094
#define SDMMC_BUFADDR 0x098
-#define SDMMC_DATA 0x100
+#define SDMMC_CLKSEL 0x09c
+#define SDMMC_DATA(x) (x)
+
+/*
+ * Data offset is difference according to Version
+ * Lower than 2.40a : data register offest is 0x100
+ */
+#define DATA_OFFSET 0x100
+#define DATA_240A_OFFSET 0x200
/* shift bit field */
#define _SBF(f, v) ((v) << (f))
@@ -82,7 +92,7 @@
#define SDMMC_CTYPE_4BIT BIT(0)
#define SDMMC_CTYPE_1BIT 0
/* Interrupt status & mask register defines */
-#define SDMMC_INT_SDIO BIT(16)
+#define SDMMC_INT_SDIO(n) BIT(16 + (n))
#define SDMMC_INT_EBE BIT(15)
#define SDMMC_INT_ACD BIT(14)
#define SDMMC_INT_SBE BIT(13)
@@ -102,6 +112,7 @@
#define SDMMC_INT_ERROR 0xbfc2
/* Command register defines */
#define SDMMC_CMD_START BIT(31)
+#define SDMMC_USE_HOLD_REG BIT(29)
#define SDMMC_CMD_CCS_EXP BIT(23)
#define SDMMC_CMD_CEATA_RD BIT(22)
#define SDMMC_CMD_UPD_CLK BIT(21)
@@ -117,8 +128,7 @@
#define SDMMC_CMD_RESP_EXP BIT(6)
#define SDMMC_CMD_INDX(n) ((n) & 0x1F)
/* Status register defines */
-#define SDMMC_GET_FCNT(x) (((x)>>17) & 0x1FF)
-#define SDMMC_FIFO_SZ 32
+#define SDMMC_GET_FCNT(x) (((x)>>17) & 0x1FFF)
/* Internal DMAC interrupt defines */
#define SDMMC_IDMAC_INT_AI BIT(9)
#define SDMMC_IDMAC_INT_NI BIT(8)
@@ -131,6 +141,8 @@
#define SDMMC_IDMAC_ENABLE BIT(7)
#define SDMMC_IDMAC_FB BIT(1)
#define SDMMC_IDMAC_SWRESET BIT(0)
+/* Version ID register define */
+#define SDMMC_GET_VERID(x) ((x) & 0xFFFF)
/* Register access macros */
#define mci_readl(dev, reg) \
diff --git a/drivers/mmc/host/mshci-s3c-dma.c b/drivers/mmc/host/mshci-s3c-dma.c
new file mode 100644
index 0000000..d62f544
--- /dev/null
+++ b/drivers/mmc/host/mshci-s3c-dma.c
@@ -0,0 +1,220 @@
+/*
+* linux/drivers/mmc/host/mshci-s3c-dma.c
+* Mobile Storage Host Controller Interface driver
+*
+* Copyright (c) 2011 Samsung Electronics Co., Ltd.
+* http://www.samsung.com
+*
+
+* 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, or (at
+* your option) any later version.
+*
+*/
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/gfp.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+
+#include <asm/memory.h>
+#include <asm/highmem.h>
+#include <asm/cacheflush.h>
+#include <asm/tlbflush.h>
+#include <asm/sizes.h>
+
+#include <linux/mmc/host.h>
+
+#include "mshci.h"
+
+
+static void mshci_s3c_dma_cache_maint_page(struct page *page,
+ unsigned long offset, size_t size, enum dma_data_direction dir,
+ void (*op)(const void *, size_t, int), int flush_type, int enable)
+{
+ /*
+ * A single sg entry may refer to multiple physically contiguous
+ * pages. But we still need to process highmem pages individually.
+ * If highmem is not configured then the bulk of this loop gets
+ * optimized out.
+ */
+ size_t left = size;
+ do {
+ size_t len = left;
+ void *vaddr;
+
+ if (PageHighMem(page)) {
+ if (len + offset > PAGE_SIZE) {
+ if (offset >= PAGE_SIZE) {
+ page += offset / PAGE_SIZE;
+ offset %= PAGE_SIZE;
+ }
+ len = PAGE_SIZE - offset;
+ }
+ vaddr = kmap_high_get(page);
+ if (vaddr) {
+ vaddr += offset;
+ if (flush_type == 0 && enable)
+ op(vaddr, len, dir);
+ kunmap_high(page);
+ } else if (cache_is_vipt()) {
+ /* unmapped pages might still be cached */
+ vaddr = kmap_atomic(page);
+ op(vaddr + offset, len, dir);
+ kunmap_atomic(vaddr);
+ }
+ } else {
+ vaddr = page_address(page) + offset;
+ if (flush_type == 0 && enable)
+ op(vaddr, len, dir);
+ }
+ offset = 0;
+ page++;
+ left -= len;
+
+ } while (left);
+}
+
+
+void mshci_s3c_dma_page_cpu_to_dev(struct page *page, unsigned long off,
+ size_t size, enum dma_data_direction dir, int flush_type)
+{
+ unsigned long paddr;
+
+ if (dir != DMA_FROM_DEVICE) {
+ mshci_s3c_dma_cache_maint_page(page, off, size, dir,
+ dmac_map_area,
+ flush_type, 1);
+
+ paddr = page_to_phys(page) + off;
+ if (flush_type != 2) {
+ outer_clean_range(paddr, paddr + size);
+ }
+ /* FIXME: non-speculating: flush on bidirectional mappings? */
+ } else {
+ paddr = page_to_phys(page) + off;
+
+ /* if flush all L1 cache,
+ L2 cache dose not neet to be clean.
+ because, all buffer dose not have split space */
+ if (flush_type != 2) {
+ outer_clean_range(paddr, paddr + size);
+ outer_inv_range(paddr, paddr + size);
+ }
+ /* FIXME: non-speculating: flush on bidirectional mappings? */
+
+ mshci_s3c_dma_cache_maint_page(page, off, size, dir,
+ dmac_unmap_area,
+ flush_type, 1);
+ }
+}
+
+
+static inline dma_addr_t mshci_s3c_dma_map_page(struct device *dev,
+ struct page *page, unsigned long offset, size_t size,
+ enum dma_data_direction dir, int flush_type)
+{
+ BUG_ON(!valid_dma_direction(dir));
+
+ mshci_s3c_dma_page_cpu_to_dev(page, offset, size, dir, flush_type);
+
+ return pfn_to_dma(dev, page_to_pfn(page)) + offset;
+}
+
+int mshci_s3c_dma_map_sg(struct mshci_host *host, struct device *dev,
+ struct scatterlist *sg, int nents, enum dma_data_direction dir,
+ int flush_type)
+{
+ struct scatterlist *s;
+ int i, j;
+
+ BUG_ON(!valid_dma_direction(dir));
+
+ if (flush_type == 2) {
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ flush_all_cpu_caches();
+ outer_flush_all();
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+ } else if(flush_type == 1) {
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ flush_all_cpu_caches();
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+ }
+
+ for_each_sg(sg, s, nents, i) {
+ s->dma_address = mshci_s3c_dma_map_page(dev, sg_page(s),
+ s->offset, s->length, dir, flush_type);
+ if (dma_mapping_error(dev, s->dma_address)) {
+ goto bad_mapping;
+ }
+ }
+
+ debug_dma_map_sg(dev, sg, nents, nents, dir);
+
+ /* in case of invaldating cache, invaldating L2 cache
+ must be done prior to invaldating L1 cache */
+#if 0
+ if (dir == DMA_FROM_DEVICE) {
+ if (flush_type == 1) {
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ flush_all_cpu_caches();
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+ }
+ }
+#endif
+ return nents;
+
+ bad_mapping:
+ for_each_sg(sg, s, i, j)
+ dma_unmap_page(dev, sg_dma_address(s), sg_dma_len(s), dir);
+ return 0;
+}
+
+void mshci_s3c_dma_page_dev_to_cpu(struct page *page, unsigned long off,
+ size_t size, enum dma_data_direction dir, int flush_type)
+{
+
+ unsigned long paddr = page_to_phys(page) + off;
+
+ /* FIXME: non-speculating: not required */
+ /* don't bother invalidating if DMA to device */
+
+ mshci_s3c_dma_cache_maint_page(page, off, size, dir, dmac_unmap_area,
+ flush_type, 0);
+}
+
+
+static inline void mshci_s3c_dma_unmap_page(struct device *dev,
+ dma_addr_t handle, size_t size,
+ enum dma_data_direction dir, int flush_type)
+{
+ mshci_s3c_dma_page_dev_to_cpu(pfn_to_page(dma_to_pfn(dev, handle)), \
+ handle & ~PAGE_MASK, size, dir, flush_type);
+}
+
+
+void mshci_s3c_dma_unmap_sg(struct mshci_host *host,
+ struct device *dev, struct scatterlist *sg,
+ int nents, enum dma_data_direction dir, int flush_type)
+{
+#if 1
+ struct scatterlist *s;
+ int i;
+
+ if (dir == DMA_TO_DEVICE)
+ for_each_sg(sg, s, nents, i)
+ mshci_s3c_dma_unmap_page(dev, sg_dma_address(s),
+ sg_dma_len(s),dir, flush_type);
+#endif
+}
+
+MODULE_DESCRIPTION("Samsung MSHCI (HSMMC) own dma map functions");
+MODULE_AUTHOR("Hyunsung Jang, <hs79.jang@samsung.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:s3c-mshci");
+
diff --git a/drivers/mmc/host/mshci-s3c.c b/drivers/mmc/host/mshci-s3c.c
new file mode 100644
index 0000000..58670ca
--- /dev/null
+++ b/drivers/mmc/host/mshci-s3c.c
@@ -0,0 +1,644 @@
+/*
+* linux/drivers/mmc/host/mshci-s3c.c
+* Mobile Storage Host Controller Interface driver
+*
+* Copyright (c) 2011 Samsung Electronics Co., Ltd.
+* http://www.samsung.com
+*
+* Based on linux/drivers/mmc/host/sdhci-s3c.c
+*
+* 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, or (at
+* your option) any later version.
+*
+*/
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <linux/mmc/host.h>
+
+#include <plat/gpio-cfg.h>
+#include <plat/mshci.h>
+#include <plat/clock.h>
+#include <plat/cpu.h>
+
+#include "mshci.h"
+
+#ifdef CONFIG_MMC_MSHCI_S3C_DMA_MAP
+int mshci_s3c_dma_map_sg(struct mshci_host *host, struct device *dev,
+ struct scatterlist *sg, int nents,
+ enum dma_data_direction dir, int flush_type);
+
+void mshci_s3c_dma_unmap_sg(struct mshci_host *host, struct device *dev,
+ struct scatterlist *sg, int nents,
+ enum dma_data_direction dir, int flush_type);
+#endif
+
+#define MAX_BUS_CLK (1)
+
+/**
+ * struct mshci_s3c - S3C MSHCI instance
+ * @host: The MSHCI host created
+ * @pdev: The platform device we where created from.
+ * @ioarea: The resource created when we claimed the IO area.
+ * @pdata: The platform data for this controller.
+ * @cur_clk: The index of the current bus clock.
+ * @clk_io: The clock for the internal bus interface.
+ * @clk_bus: The clocks that are available for the SD/MMC bus clock.
+ */
+struct mshci_s3c {
+ struct mshci_host *host;
+ struct platform_device *pdev;
+ struct resource *ioarea;
+ struct s3c_mshci_platdata *pdata;
+ unsigned int cur_clk;
+ int ext_cd_irq;
+ int ext_cd_gpio;
+
+ struct clk *clk_io;
+ struct clk *clk_bus[MAX_BUS_CLK];
+};
+
+static inline struct mshci_s3c *to_s3c(struct mshci_host *host)
+{
+ return mshci_priv(host);
+}
+
+/**
+ * mshci_s3c_get_max_clk - callback to get maximum clock frequency.
+ * @host: The MSHCI host instance.
+ *
+ * Callback to return the maximum clock rate acheivable by the controller.
+*/
+static unsigned int mshci_s3c_get_max_clk(struct mshci_host *host)
+{
+ struct mshci_s3c *ourhost = to_s3c(host);
+ struct clk *busclk;
+ unsigned int rate, max;
+ int clk;
+
+ for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) {
+ busclk = ourhost->clk_bus[clk];
+ if (!busclk)
+ continue;
+
+ rate = clk_get_rate(busclk);
+ /* It should be checked later ############# */
+ if (rate > max) {
+ if ((soc_is_exynos4412() || soc_is_exynos4212()) &&
+ (samsung_rev() >= EXYNOS4412_REV_1_0))
+ max = rate >> 2;
+ else
+ max = rate >> 1;
+ }
+ }
+
+ /* max clock can be change after changing clock source. */
+ host->mmc->f_max = max;
+ return max;
+}
+
+/**
+ * mshci_s3c_consider_clock - consider one the bus clocks for current setting
+ * @ourhost: Our MSHCI instance.
+ * @src: The source clock index.
+ * @wanted: The clock frequency wanted.
+ */
+static unsigned int mshci_s3c_consider_clock(struct mshci_s3c *ourhost,
+ unsigned int src,
+ unsigned int wanted)
+{
+ unsigned long rate;
+ struct clk *clksrc = ourhost->clk_bus[src];
+ int div;
+
+ if (!clksrc)
+ return UINT_MAX;
+
+ rate = clk_get_rate(clksrc);
+
+ for (div = 1; div < 256; div *= 2) {
+ if ((rate / div) <= wanted)
+ break;
+ }
+
+ dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n",
+ src, rate, wanted, rate / div);
+
+ return wanted - (rate / div);
+}
+
+/**
+ * mshci_s3c_set_clock - callback on clock change
+ * @host: The MSHCI host being changed
+ * @clock: The clock rate being requested.
+ *
+ * When the card's clock is going to be changed, look at the new frequency
+ * and find the best clock source to go with it.
+*/
+static void mshci_s3c_set_clock(struct mshci_host *host, unsigned int clock)
+{
+ struct mshci_s3c *ourhost = to_s3c(host);
+ unsigned int best = UINT_MAX;
+ unsigned int delta;
+ int best_src = 0;
+ int src;
+
+ /* don't bother if the clock is going off. */
+ if (clock == 0)
+ return;
+
+ for (src = 0; src < MAX_BUS_CLK; src++) {
+ delta = mshci_s3c_consider_clock(ourhost, src, clock);
+ if (delta < best) {
+ best = delta;
+ best_src = src;
+ }
+ }
+
+ dev_dbg(&ourhost->pdev->dev,
+ "selected source %d, clock %d, delta %d\n",
+ best_src, clock, best);
+
+ /* select the new clock source */
+
+ if (ourhost->cur_clk != best_src) {
+ struct clk *clk = ourhost->clk_bus[best_src];
+
+ ourhost->cur_clk = best_src;
+ host->max_clk = clk_get_rate(clk);
+ }
+
+ /* reconfigure the hardware for new clock rate */
+
+ {
+ struct mmc_ios ios;
+
+ ios.clock = clock;
+
+ if (ourhost->pdata->cfg_card)
+ (ourhost->pdata->cfg_card)(ourhost->pdev, host->ioaddr,
+ &ios, NULL);
+ }
+}
+
+/**
+ * mshci_s3c_get_ro - callback for get_ro
+ * @host: The MSHCI host being changed
+ *
+ * If the WP pin is connected with GPIO, can get the value which indicates
+ * the card is locked or not.
+*/
+static int mshci_s3c_get_ro(struct mmc_host *mmc)
+{
+ struct mshci_s3c *ourhost = to_s3c(mmc_priv(mmc));
+
+ return gpio_get_value(ourhost->pdata->wp_gpio);
+}
+
+/**
+ * mshci_s3c_cfg_wp - configure GPIO for WP pin
+ * @gpio_num: GPIO number which connected with WP line from SD/MMC slot
+ *
+ * Configure GPIO for using WP line
+ */
+static void mshci_s3c_cfg_wp(unsigned int gpio_num)
+{
+ s3c_gpio_cfgpin(gpio_num, S3C_GPIO_INPUT);
+ s3c_gpio_setpull(gpio_num, S3C_GPIO_PULL_UP);
+}
+
+static void mshci_s3c_set_ios(struct mshci_host *host,
+ struct mmc_ios *ios)
+{
+ struct mshci_s3c *ourhost = to_s3c(host);
+ struct s3c_mshci_platdata *pdata = ourhost->pdata;
+ int width;
+
+ if (ios->power_mode != MMC_POWER_OFF) {
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_8:
+ width = 8;
+ break;
+ case MMC_BUS_WIDTH_4:
+ width = 4;
+ break;
+ case MMC_BUS_WIDTH_1:
+ width = 1;
+ break;
+ default:
+ BUG();
+ }
+
+ if (pdata->cfg_gpio)
+ pdata->cfg_gpio(ourhost->pdev, width);
+ }
+
+ if (pdata->cfg_card)
+ pdata->cfg_card(ourhost->pdev, host->ioaddr,
+ ios, host->mmc->card);
+
+ if (pdata->cfg_ddr) {
+ if (ios->timing == MMC_TIMING_UHS_DDR50)
+ pdata->cfg_ddr(ourhost->pdev, 1);
+ else
+ pdata->cfg_ddr(ourhost->pdev, 0);
+ }
+ /* after change DDR/SDR, max_clk has been changed.
+ You should re-calc the max_clk */
+ host->max_clk = mshci_s3c_get_max_clk(host);
+}
+
+/**
+ * mshci_s3c_init_card - Reset eMMC device
+ *
+ * init eMMC_card.
+ */
+
+static void mshci_s3c_init_card(struct mshci_host *host)
+{
+ struct mshci_s3c *ourhost = to_s3c(host);
+ struct s3c_mshci_platdata *pdata = ourhost->pdata;
+
+ if (pdata->init_card)
+ pdata->init_card(ourhost->pdev);
+}
+
+static int mshci_s3c_get_fifo_depth(struct mshci_host *host)
+{
+ struct mshci_s3c *ourhost = to_s3c(host);
+ struct s3c_mshci_platdata *pdata = ourhost->pdata;
+
+ return pdata->fifo_depth;
+}
+
+
+static struct mshci_ops mshci_s3c_ops = {
+ .get_max_clock = mshci_s3c_get_max_clk,
+ .set_clock = mshci_s3c_set_clock,
+ .set_ios = mshci_s3c_set_ios,
+ .init_card = mshci_s3c_init_card,
+#ifdef CONFIG_MMC_MSHCI_S3C_DMA_MAP
+ .dma_map_sg = mshci_s3c_dma_map_sg,
+ .dma_unmap_sg = mshci_s3c_dma_unmap_sg,
+#endif
+ .get_fifo_depth = mshci_s3c_get_fifo_depth,
+};
+
+static void mshci_s3c_notify_change(struct platform_device *dev, int state)
+{
+ struct mshci_host *host;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ host = platform_get_drvdata(dev);
+ if (host) {
+ if (state) {
+ dev_dbg(&dev->dev, "card inserted.\n");
+ host->flags &= ~MSHCI_DEVICE_DEAD;
+ tasklet_schedule(&host->card_tasklet);
+ } else {
+ dev_dbg(&dev->dev, "card removed.\n");
+ host->flags |= MSHCI_DEVICE_DEAD;
+ tasklet_schedule(&host->card_tasklet);
+ }
+ }
+ local_irq_restore(flags);
+}
+
+static irqreturn_t mshci_s3c_gpio_card_detect_isr(int irq, void *dev_id)
+{
+ struct mshci_s3c *sc = dev_id;
+ int status = gpio_get_value(sc->ext_cd_gpio);
+ if (sc->pdata->ext_cd_gpio_invert)
+ status = !status;
+ mshci_s3c_notify_change(sc->pdev, status);
+ return IRQ_HANDLED;
+}
+
+
+static int __devinit mshci_s3c_probe(struct platform_device *pdev)
+{
+ struct s3c_mshci_platdata *pdata = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct mshci_host *host;
+ struct mshci_s3c *sc;
+ struct resource *res;
+ int ret, irq, ptr, clks;
+
+ if (!pdata) {
+ dev_err(dev, "no device data specified\n");
+ return -ENOENT;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "no irq specified\n");
+ return irq;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "no memory specified\n");
+ return -ENOENT;
+ }
+ host = mshci_alloc_host(dev, sizeof(struct mshci_s3c));
+ if (IS_ERR(host)) {
+ dev_err(dev, "mshci_alloc_host() failed\n");
+ return PTR_ERR(host);
+ }
+ sc = mshci_priv(host);
+
+ if (soc_is_exynos4210()) {
+ host->data_addr = 0x0;
+ host->hold_bit = 0;
+ } else {
+ host->data_addr = 0x100;
+ host->hold_bit = CMD_USE_HOLD_REG;
+ }
+
+ sc->host = host;
+ sc->pdev = pdev;
+ sc->pdata = pdata;
+ sc->ext_cd_gpio = -1;
+
+ platform_set_drvdata(pdev, host);
+
+ sc->clk_io = clk_get(dev, "dwmci");
+ if (IS_ERR(sc->clk_io)) {
+ dev_err(dev, "failed to get io clock\n");
+ ret = PTR_ERR(sc->clk_io);
+ goto err_io_clk;
+ }
+
+ /* enable the local io clock and keep it running for the moment. */
+ clk_enable(sc->clk_io);
+
+ for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
+ struct clk *clk;
+ char *name = pdata->clocks[ptr];
+
+ if (name == NULL)
+ continue;
+ clk = clk_get(dev, name);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "failed to get clock %s\n", name);
+ continue;
+ }
+
+#if defined(CONFIG_EXYNOS4_MSHC_VPLL_46MHZ) || \
+ defined(CONFIG_EXYNOS4_MSHC_EPLL_45MHZ)
+ if (!strcmp("sclk_dwmci", name)) {
+ struct clk *parent_clk;
+
+ parent_clk = clk_get_parent(clk);
+
+ if (!parent_clk) {
+ dev_err(dev, "failed to get parent clock %s\n"
+ , (char *)(clk->name));
+ } else {
+ for ( ; ; ) {
+ parent_clk = clk_get_parent(parent_clk);
+ if (parent_clk) {
+#ifdef CONFIG_EXYNOS4_MSHC_EPLL_45MHZ
+ if (!strcmp("fout_epll", \
+ parent_clk->name) &&
+ soc_is_exynos4210()) {
+ clk_set_rate \
+ (parent_clk, 180633600);
+ pdata->cfg_ddr(pdev, 0);
+#elif defined(CONFIG_EXYNOS4_MSHC_VPLL_46MHZ)
+ if (!strcmp("fout_vpll", \
+ parent_clk->name)) {
+ clk_set_rate \
+ (parent_clk, 370882812);
+ pdata->cfg_ddr(pdev, 0);
+#endif
+ clk_enable(parent_clk);
+ break;
+ } else
+ continue;
+ } else {
+ dev_err(dev, "failed to"
+ "get parent"
+ "clock %s\n"
+ , clk->name);
+ break;
+ }
+ }
+ }
+ }
+#endif
+ clks++;
+ sc->clk_bus[ptr] = clk;
+ clk_enable(clk);
+
+ dev_info(dev, "clock source %d: %s (%ld Hz)\n",
+ ptr, name, clk_get_rate(clk));
+ }
+
+ if (clks == 0) {
+ dev_err(dev, "failed to find any bus clocks\n");
+ ret = -ENOENT;
+ goto err_no_busclks;
+ }
+
+ sc->ioarea = request_mem_region(res->start, resource_size(res),
+ mmc_hostname(host->mmc));
+ if (!sc->ioarea) {
+ dev_err(dev, "failed to reserve register area\n");
+ ret = -ENXIO;
+ goto err_req_regs;
+ }
+
+ host->ioaddr = ioremap_nocache(res->start, resource_size(res));
+ if (!host->ioaddr) {
+ dev_err(dev, "failed to map registers\n");
+ ret = -ENXIO;
+ goto err_add_host;
+ }
+
+ /* Ensure we have minimal gpio selected CMD/CLK/Detect */
+ if (pdata->cfg_gpio)
+ pdata->cfg_gpio(pdev, pdata->max_width);
+ else
+ dev_err(dev, "cfg_gpio dose not exist.!\n");
+
+ host->hw_name = "samsung-mshci";
+ host->ops = &mshci_s3c_ops;
+ host->quirks = 0;
+ host->irq = irq;
+
+ if (pdata->host_caps)
+ host->mmc->caps = pdata->host_caps;
+ else
+ host->mmc->caps = 0;
+
+ if (pdata->host_caps2)
+ host->mmc->caps2 = pdata->host_caps2;
+ else
+ host->mmc->caps2 = 0;
+
+ if (pdata->cd_type == S3C_MSHCI_CD_PERMANENT) {
+ host->quirks |= MSHCI_QUIRK_BROKEN_PRESENT_BIT;
+ host->mmc->caps |= MMC_CAP_NONREMOVABLE;
+ if (pdata->int_power_gpio) {
+ gpio_set_value(pdata->int_power_gpio, 1);
+ s3c_gpio_cfgpin(pdata->int_power_gpio,
+ S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(pdata->int_power_gpio,
+ S3C_GPIO_PULL_NONE);
+ }
+ }
+
+ /* IF SD controller's WP pin donsn't connected with SD card and there
+ * is an allocated GPIO for getting WP data form SD card,
+ * use this quirk and send the GPIO number in pdata->wp_gpio. */
+ if (pdata->has_wp_gpio && gpio_is_valid(pdata->wp_gpio)) {
+ mshci_s3c_ops.get_ro = mshci_s3c_get_ro;
+ host->quirks |= MSHCI_QUIRK_NO_WP_BIT;
+ mshci_s3c_cfg_wp(pdata->wp_gpio);
+ }
+
+ ret = mshci_add_host(host);
+
+ if (pdata->cd_type == S3C_MSHCI_CD_GPIO &&
+ gpio_is_valid(pdata->ext_cd_gpio)) {
+
+ ret = gpio_request(pdata->ext_cd_gpio, "MSHCI EXT CD");
+ if (ret) {
+ dev_err(&pdev->dev, "cannot request gpio for card detect\n");
+ goto err_add_host;
+ }
+
+ sc->ext_cd_gpio = pdata->ext_cd_gpio;
+
+ sc->ext_cd_irq = gpio_to_irq(pdata->ext_cd_gpio);
+ if (sc->ext_cd_irq &&
+ request_irq(sc->ext_cd_irq,
+ mshci_s3c_gpio_card_detect_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&pdev->dev), sc)) {
+ dev_err(&pdev->dev, "cannot request irq for card detect\n");
+ sc->ext_cd_irq = 0;
+ }
+ dev_dbg(&pdev->dev, "mshci detects a card insertion/removal"
+ "by EINT\n");
+ }
+
+ if (ret) {
+ dev_err(dev, "mshci_add_host() failed\n");
+ goto err_add_host;
+ }
+
+ device_enable_async_suspend(dev);
+
+ return 0;
+
+ err_add_host:
+ if (host->ioaddr)
+ iounmap(host->ioaddr);
+ release_mem_region(sc->ioarea->start, resource_size(sc->ioarea));
+
+ err_req_regs:
+ for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
+ clk_disable(sc->clk_bus[ptr]);
+ clk_put(sc->clk_bus[ptr]);
+ }
+
+ err_no_busclks:
+ clk_disable(sc->clk_io);
+ clk_put(sc->clk_io);
+
+ err_io_clk:
+ mshci_free_host(host);
+ return ret;
+}
+
+static int __devexit mshci_s3c_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int mshci_s3c_suspend(struct platform_device *dev, pm_message_t pm)
+{
+ struct mshci_host *host = platform_get_drvdata(dev);
+ struct s3c_mshci_platdata *pdata = dev->dev.platform_data;
+
+ mshci_suspend_host(host, pm);
+
+ if (pdata->set_power)
+ pdata->set_power(dev, 0);
+
+ return 0;
+}
+
+static int mshci_s3c_resume(struct platform_device *dev)
+{
+ struct mshci_host *host = platform_get_drvdata(dev);
+ struct s3c_mshci_platdata *pdata = dev->dev.platform_data;
+
+ if (pdata->set_power)
+ pdata->set_power(dev, 1);
+
+ mshci_resume_host(host);
+ return 0;
+}
+
+static void mshci_s3c_shutdown(struct platform_device *dev, pm_message_t pm)
+{
+ struct mshci_host *host = platform_get_drvdata(dev);
+ struct s3c_mshci_platdata *pdata = dev->dev.platform_data;
+
+ mshci_suspend_host(host, pm);
+
+ if (pdata->shutdown)
+ pdata->shutdown();
+}
+
+
+#else
+#define mshci_s3c_suspend NULL
+#define mshci_s3c_resume NULL
+#endif
+
+static struct platform_driver mshci_s3c_driver = {
+ .probe = mshci_s3c_probe,
+ .remove = __devexit_p(mshci_s3c_remove),
+ .suspend = mshci_s3c_suspend,
+ .resume = mshci_s3c_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "dw_mmc",
+ },
+};
+
+static int __init mshci_s3c_init(void)
+{
+ return platform_driver_register(&mshci_s3c_driver);
+}
+
+static void __exit mshci_s3c_exit(void)
+{
+ platform_driver_unregister(&mshci_s3c_driver);
+}
+
+#ifdef CONFIG_FAST_RESUME
+beforeresume_initcall(mshci_s3c_init);
+#else
+module_init(mshci_s3c_init);
+#endif
+module_exit(mshci_s3c_exit);
+
+MODULE_DESCRIPTION("Samsung MSHCI (HSMMC) glue");
+MODULE_AUTHOR("Hyunsung Jang, <hs79.jang@samsung.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:dw_mmc");
diff --git a/drivers/mmc/host/mshci.c b/drivers/mmc/host/mshci.c
new file mode 100644
index 0000000..45997c5
--- /dev/null
+++ b/drivers/mmc/host/mshci.c
@@ -0,0 +1,2245 @@
+/*
+* linux/drivers/mmc/host/mshci.c
+* Mobile Storage Host Controller Interface driver
+*
+* Copyright (c) 2011 Samsung Electronics Co., Ltd.
+* http://www.samsung.com
+*
+* Based on linux/drivers/mmc/host/sdhci.c
+*
+* 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, or (at
+* your option) any later version.
+*
+*/
+
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+
+#include <linux/leds.h>
+
+#include <linux/mmc/host.h>
+
+#include <plat/cpu.h>
+
+#include "mshci.h"
+
+#define DRIVER_NAME "mshci"
+
+#define DBG(f, x...) \
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__, ## x)
+
+#define SDHC_CLK_ON 1
+#define SDHC_CLK_OFF 0
+
+static unsigned int debug_quirks;
+
+static void mshci_prepare_data(struct mshci_host *, struct mmc_data *);
+static void mshci_finish_data(struct mshci_host *);
+
+static void mshci_send_command(struct mshci_host *, struct mmc_command *);
+static void mshci_finish_command(struct mshci_host *);
+static void mshci_fifo_init(struct mshci_host *host);
+
+static void mshci_set_clock(struct mshci_host *host,
+ unsigned int clock, u32 bus_width);
+
+#define MSHCI_MAX_DMA_SINGLE_TRANS_SIZE (0x1000)
+#define MSHCI_MAX_DMA_TRANS_SIZE (0x400000)
+#define MSHCI_MAX_DMA_LIST (MSHCI_MAX_DMA_TRANS_SIZE / \
+ MSHCI_MAX_DMA_SINGLE_TRANS_SIZE)
+
+static void mshci_dumpregs(struct mshci_host *host)
+{
+ printk(KERN_DEBUG DRIVER_NAME ": ============== REGISTER DUMP ==============\n");
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CTRL: 0x%08x\n",
+ mshci_readl(host, MSHCI_CTRL));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_PWREN: 0x%08x\n",
+ mshci_readl(host, MSHCI_PWREN));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CLKDIV: 0x%08x\n",
+ mshci_readl(host, MSHCI_CLKDIV));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CLKSRC: 0x%08x\n",
+ mshci_readl(host, MSHCI_CLKSRC));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CLKENA: 0x%08x\n",
+ mshci_readl(host, MSHCI_CLKENA));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_TMOUT: 0x%08x\n",
+ mshci_readl(host, MSHCI_TMOUT));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CTYPE: 0x%08x\n",
+ mshci_readl(host, MSHCI_CTYPE));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_BLKSIZ: 0x%08x\n",
+ mshci_readl(host, MSHCI_BLKSIZ));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_BYTCNT: 0x%08x\n",
+ mshci_readl(host, MSHCI_BYTCNT));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_INTMSK: 0x%08x\n",
+ mshci_readl(host, MSHCI_INTMSK));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CMDARG: 0x%08x\n",
+ mshci_readl(host, MSHCI_CMDARG));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CMD: 0x%08x\n",
+ mshci_readl(host, MSHCI_CMD));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_MINTSTS: 0x%08x\n",
+ mshci_readl(host, MSHCI_MINTSTS));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_RINTSTS: 0x%08x\n",
+ mshci_readl(host, MSHCI_RINTSTS));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_STATUS: 0x%08x\n",
+ mshci_readl(host, MSHCI_STATUS));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_FIFOTH: 0x%08x\n",
+ mshci_readl(host, MSHCI_FIFOTH));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CDETECT: 0x%08x\n",
+ mshci_readl(host, MSHCI_CDETECT));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_WRTPRT: 0x%08x\n",
+ mshci_readl(host, MSHCI_WRTPRT));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_GPIO: 0x%08x\n",
+ mshci_readl(host, MSHCI_GPIO));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_TCBCNT: 0x%08x\n",
+ mshci_readl(host, MSHCI_TCBCNT));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_TBBCNT: 0x%08x\n",
+ mshci_readl(host, MSHCI_TBBCNT));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_DEBNCE: 0x%08x\n",
+ mshci_readl(host, MSHCI_DEBNCE));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_USRID: 0x%08x\n",
+ mshci_readl(host, MSHCI_USRID));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_VERID: 0x%08x\n",
+ mshci_readl(host, MSHCI_VERID));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_HCON: 0x%08x\n",
+ mshci_readl(host, MSHCI_HCON));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_UHS_REG: 0x%08x\n",
+ mshci_readl(host, MSHCI_UHS_REG));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_BMOD: 0x%08x\n",
+ mshci_readl(host, MSHCI_BMOD));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_PLDMND: 0x%08x\n",
+ mshci_readl(host, MSHCI_PLDMND));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_DBADDR: 0x%08x\n",
+ mshci_readl(host, MSHCI_DBADDR));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_IDSTS: 0x%08x\n",
+ mshci_readl(host, MSHCI_IDSTS));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_IDINTEN: 0x%08x\n",
+ mshci_readl(host, MSHCI_IDINTEN));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_DSCADDR: 0x%08x\n",
+ mshci_readl(host, MSHCI_DSCADDR));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_BUFADDR: 0x%08x\n",
+ mshci_readl(host, MSHCI_BUFADDR));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_WAKEUPCON: 0x%08x\n",
+ mshci_readl(host, MSHCI_WAKEUPCON));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_CLOCKCON: 0x%08x\n",
+ mshci_readl(host, MSHCI_CLOCKCON));
+ printk(KERN_DEBUG DRIVER_NAME ": MSHCI_FIFODAT: 0x%08x\n",
+ mshci_readl(host, MSHCI_FIFODAT + host->data_addr));
+ printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n");
+}
+
+
+/*****************************************************************************\
+ * *
+ * Low level functions *
+ * *
+\*****************************************************************************/
+
+static void mshci_clear_set_irqs(struct mshci_host *host, u32 clear, u32 set)
+{
+ u32 ier;
+
+ ier = mshci_readl(host, MSHCI_INTMSK);
+ ier &= ~clear;
+ ier |= set;
+ mshci_writel(host, ier, MSHCI_INTMSK);
+}
+
+static void mshci_unmask_irqs(struct mshci_host *host, u32 irqs)
+{
+ mshci_clear_set_irqs(host, 0, irqs);
+}
+
+static void mshci_mask_irqs(struct mshci_host *host, u32 irqs)
+{
+ mshci_clear_set_irqs(host, irqs, 0);
+}
+
+static void mshci_set_card_detection(struct mshci_host *host, bool enable)
+{
+ u32 irqs = INTMSK_CDETECT;
+
+ /* it can makes a problme if enable CD_DETECT interrupt,
+ * when CD pin dose not exist. */
+ if (host->quirks & MSHCI_QUIRK_BROKEN_CARD_DETECTION ||
+ host->quirks & MSHCI_QUIRK_BROKEN_PRESENT_BIT) {
+ mshci_mask_irqs(host, irqs);
+ } else if (enable) {
+ mshci_unmask_irqs(host, irqs);
+ } else {
+ mshci_mask_irqs(host, irqs);
+ }
+}
+
+static void mshci_enable_card_detection(struct mshci_host *host)
+{
+ mshci_set_card_detection(host, true);
+}
+
+static void mshci_disable_card_detection(struct mshci_host *host)
+{
+ mshci_set_card_detection(host, false);
+}
+
+static void mshci_reset_ciu(struct mshci_host *host)
+{
+ u32 timeout = 100;
+ u32 ier;
+
+ ier = mshci_readl(host, MSHCI_CTRL);
+ ier |= CTRL_RESET;
+
+ mshci_writel(host, ier, MSHCI_CTRL);
+ while (mshci_readl(host, MSHCI_CTRL) & CTRL_RESET) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Reset CTRL never completed.\n",
+ mmc_hostname(host->mmc));
+ mshci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+}
+
+static void mshci_reset_fifo(struct mshci_host *host)
+{
+ u32 timeout = 100;
+ u32 ier;
+
+ ier = mshci_readl(host, MSHCI_CTRL);
+ ier |= FIFO_RESET;
+
+ mshci_writel(host, ier, MSHCI_CTRL);
+ while (mshci_readl(host, MSHCI_CTRL) & FIFO_RESET) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Reset FIFO never completed.\n",
+ mmc_hostname(host->mmc));
+ mshci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+}
+
+static void mshci_reset_dma(struct mshci_host *host)
+{
+ u32 timeout = 100;
+ u32 ier;
+
+ ier = mshci_readl(host, MSHCI_CTRL);
+ ier |= DMA_RESET;
+
+ mshci_writel(host, ier, MSHCI_CTRL);
+ while (mshci_readl(host, MSHCI_CTRL) & DMA_RESET) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Reset DMA never completed.\n",
+ mmc_hostname(host->mmc));
+ mshci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+}
+
+static void mshci_reset_all(struct mshci_host *host)
+{
+ int count, err = 0;
+
+ /* Wait max 100 ms */
+ count = 10000;
+
+ /* before reset ciu, it should check DATA0. if when DATA0 is low and
+ it resets ciu, it might make a problem */
+ do {
+ if (!(mshci_readl(host, MSHCI_STATUS) & (1<<9))) {
+ udelay(100);
+ if (!(mshci_readl(host, MSHCI_STATUS) & (1<<9))) {
+ udelay(100);
+ if (!(mshci_readl(host, MSHCI_STATUS) & (1<<9)))
+ break;
+ }
+ }
+ if (count == 0) {
+ printk(KERN_ERR "%s: Controller never released "
+ "data0 before reset ciu.\n",
+ mmc_hostname(host->mmc));
+ mshci_dumpregs(host);
+ err = 1;
+ break;
+ }
+ count--;
+ udelay(10);
+ } while (1);
+
+ if (err && host->ops->init_card) {
+ printk(KERN_ERR "%s: eMMC's data lines get low.\n"
+ "Reset eMMC.\n", mmc_hostname(host->mmc));
+ host->ops->init_card(host);
+ }
+
+ mshci_reset_ciu(host);
+ udelay(1);
+ mshci_reset_fifo(host);
+ udelay(1);
+ mshci_reset_dma(host);
+ udelay(1);
+}
+
+static void mshci_init(struct mshci_host *host)
+{
+ mshci_reset_all(host);
+
+ /* clear interrupt status */
+ mshci_writel(host, INTMSK_ALL, MSHCI_RINTSTS);
+
+ mshci_clear_set_irqs(host, INTMSK_ALL,
+ INTMSK_CDETECT | INTMSK_RE |
+ INTMSK_CDONE | INTMSK_DTO | INTMSK_TXDR | INTMSK_RXDR |
+ INTMSK_RCRC | INTMSK_DCRC | INTMSK_RTO | INTMSK_DRTO |
+ INTMSK_HTO | INTMSK_FRUN | INTMSK_HLE | INTMSK_SBE |
+ INTMSK_EBE);
+}
+
+static void mshci_reinit(struct mshci_host *host)
+{
+ mshci_init(host);
+ mshci_enable_card_detection(host);
+}
+
+/*****************************************************************************\
+ * *
+ * Core functions *
+ * *
+\*****************************************************************************/
+
+static void mshci_read_block_pio(struct mshci_host *host)
+{
+ unsigned long flags;
+ size_t fifo_cnt, len, chunk;
+ u32 uninitialized_var(scratch);
+ u8 *buf;
+
+ DBG("PIO reading\n");
+
+ fifo_cnt = (mshci_readl(host, MSHCI_STATUS)&FIFO_COUNT)>>17;
+ fifo_cnt *= FIFO_WIDTH;
+ chunk = 0;
+
+ local_irq_save(flags);
+
+ while (fifo_cnt) {
+ if (!sg_miter_next(&host->sg_miter))
+ BUG();
+
+ len = min(host->sg_miter.length, fifo_cnt);
+
+ fifo_cnt -= len;
+ host->sg_miter.consumed = len;
+
+ buf = host->sg_miter.addr;
+
+ while (len) {
+ if (chunk == 0) {
+ scratch = mshci_readl(host,
+ MSHCI_FIFODAT + host->data_addr);
+ chunk = 4;
+ }
+
+ *buf = scratch & 0xFF;
+
+ buf++;
+ scratch >>= 8;
+ chunk--;
+ len--;
+ }
+ }
+
+ sg_miter_stop(&host->sg_miter);
+
+ local_irq_restore(flags);
+}
+
+static void mshci_write_block_pio(struct mshci_host *host)
+{
+ unsigned long flags;
+ size_t fifo_cnt, len, chunk;
+ u32 scratch;
+ u8 *buf;
+
+ DBG("PIO writing\n");
+
+ fifo_cnt = 8;
+
+ fifo_cnt *= FIFO_WIDTH;
+ chunk = 0;
+ scratch = 0;
+
+ local_irq_save(flags);
+
+ while (fifo_cnt) {
+ if (!sg_miter_next(&host->sg_miter)) {
+
+ /* Even though transfer is complete,
+ * TXDR interrupt occurs again.
+ * So, it has to check that it has really
+ * no next sg buffer or just DTO interrupt
+ * has not occured yet.
+ */
+
+ if ((host->data->blocks * host->data->blksz) ==
+ host->data_transfered)
+ break; /* transfer done but DTO not yet */
+ BUG();
+ }
+ len = min(host->sg_miter.length, fifo_cnt);
+
+ fifo_cnt -= len;
+ host->sg_miter.consumed = len;
+ host->data_transfered += len;
+
+ buf = (host->sg_miter.addr);
+
+ while (len) {
+ scratch |= (u32)*buf << (chunk * 8);
+
+ buf++;
+ chunk++;
+ len--;
+
+ if ((chunk == 4) || ((len == 0) && (fifo_cnt == 0))) {
+ mshci_writel(host, scratch,
+ MSHCI_FIFODAT + host->data_addr);
+ chunk = 0;
+ scratch = 0;
+ }
+ }
+ }
+
+ sg_miter_stop(&host->sg_miter);
+
+ local_irq_restore(flags);
+}
+
+static void mshci_transfer_pio(struct mshci_host *host)
+{
+ BUG_ON(!host->data);
+
+ if (host->blocks == 0)
+ return;
+
+ if (host->data->flags & MMC_DATA_READ)
+ mshci_read_block_pio(host);
+ else
+ mshci_write_block_pio(host);
+
+ DBG("PIO transfer complete.\n");
+}
+
+static void mshci_set_mdma_desc(u8 *desc_vir, u8 *desc_phy,
+ u32 des0, u32 des1, u32 des2)
+{
+ ((struct mshci_idmac *)(desc_vir))->des0 = des0;
+ ((struct mshci_idmac *)(desc_vir))->des1 = des1;
+ ((struct mshci_idmac *)(desc_vir))->des2 = des2;
+ ((struct mshci_idmac *)(desc_vir))->des3 = (u32)desc_phy +
+ sizeof(struct mshci_idmac);
+}
+
+static int mshci_mdma_table_pre(struct mshci_host *host,
+ struct mmc_data *data)
+{
+ int direction;
+
+ u8 *desc_vir, *desc_phy;
+ dma_addr_t addr;
+ int len;
+
+ struct scatterlist *sg;
+ int i;
+ u32 des_flag;
+ u32 size_idmac = sizeof(struct mshci_idmac);
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+
+ if (!data->host_cookie) {
+ if (host->ops->dma_map_sg && data->blocks >= 2048) {
+ /* if transfer size is bigger than 1MiB */
+ host->sg_count = host->ops->dma_map_sg(host,
+ mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 2);
+ } else if (host->ops->dma_map_sg && data->blocks >= 128) {
+ /* if transfer size is bigger than 64KiB */
+ host->sg_count = host->ops->dma_map_sg(host,
+ mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 1);
+ } else {
+ host->sg_count = dma_map_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction);
+ }
+
+ if (host->sg_count == 0)
+ goto fail;
+ } else
+ host->sg_count = data->host_cookie;
+
+ desc_vir = host->idma_desc;
+
+ /* to know phy address */
+ host->idma_addr = dma_map_single(mmc_dev(host->mmc),
+ host->idma_desc,
+ /* cache flush for only transfer size */
+ (host->sg_count+1) * 16,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(mmc_dev(host->mmc), host->idma_addr))
+ goto unmap_entries;
+ BUG_ON(host->idma_addr & 0x3);
+
+ desc_phy = (u8 *)host->idma_addr;
+
+ for_each_sg(data->sg, sg, host->sg_count, i) {
+ addr = sg_dma_address(sg);
+ len = sg_dma_len(sg);
+
+ /* tran, valid */
+ des_flag = (MSHCI_IDMAC_OWN|MSHCI_IDMAC_CH);
+ des_flag |= (i == 0) ? MSHCI_IDMAC_FS : 0;
+
+ mshci_set_mdma_desc(desc_vir, desc_phy, des_flag, len, addr);
+ desc_vir += size_idmac;
+ desc_phy += size_idmac;
+
+ /*
+ * If this triggers then we have a calculation bug
+ * somewhere. :/
+ */
+ WARN_ON((desc_vir - host->idma_desc) > MSHCI_MAX_DMA_LIST * \
+ size_idmac);
+ }
+
+ /*
+ * Add a terminating flag.
+ */
+ ((struct mshci_idmac *)(desc_vir-size_idmac))->des0 |= MSHCI_IDMAC_LD;
+
+ /* it has to dma map again to resync vir data to phy data */
+ host->idma_addr = dma_map_single(mmc_dev(host->mmc),
+ host->idma_desc,
+ /* cache flush for only transfer size */
+ (host->sg_count+1) * 16,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(mmc_dev(host->mmc), host->idma_addr))
+ goto unmap_entries;
+ BUG_ON(host->idma_addr & 0x3);
+
+ return 0;
+
+unmap_entries:
+ if (host->ops->dma_unmap_sg && data->blocks >= 2048) {
+ /* if transfer size is bigger than 1MiB */
+ host->ops->dma_unmap_sg(host, mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 2);
+ } else if (host->ops->dma_unmap_sg && data->blocks >= 128) {
+ /* if transfer size is bigger than 64KiB */
+ host->ops->dma_unmap_sg(host, mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 1);
+ } else {
+ dma_unmap_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction);
+ }
+fail:
+ return -EINVAL;
+}
+
+static void mshci_idma_table_post(struct mshci_host *host,
+ struct mmc_data *data)
+{
+ int direction;
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+
+ dma_unmap_single(mmc_dev(host->mmc), host->idma_addr,
+ /* cache flush for only transfer size */
+ (host->sg_count+1) * 16,
+ DMA_TO_DEVICE);
+
+ if (!host->mmc->ops->post_req || !data->host_cookie) {
+ if (host->ops->dma_unmap_sg && data->blocks >= 2048) {
+ /* if transfer size is bigger than 1MiB */
+ host->ops->dma_unmap_sg(host, mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 2);
+ } else if (host->ops->dma_unmap_sg && data->blocks >= 128) {
+ /* if transfer size is bigger than 64KiB */
+ host->ops->dma_unmap_sg(host, mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 1);
+ } else {
+ dma_unmap_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction);
+ }
+ }
+}
+
+static u32 mshci_calc_timeout(struct mshci_host *host, struct mmc_data *data)
+{
+ return 0xffffffff; /* this value SHOULD be optimized */
+}
+
+static void mshci_set_transfer_irqs(struct mshci_host *host)
+{
+ u32 dma_irqs = INTMSK_DMA;
+ u32 pio_irqs = INTMSK_TXDR | INTMSK_RXDR;
+
+ if (host->flags & MSHCI_REQ_USE_DMA)
+ mshci_clear_set_irqs(host, dma_irqs, 0);
+ else
+ mshci_clear_set_irqs(host, 0, pio_irqs);
+}
+
+static void mshci_prepare_data(struct mshci_host *host, struct mmc_data *data)
+{
+ u32 count;
+ u32 ret;
+
+ WARN_ON(host->data);
+
+ if (data == NULL)
+ return;
+
+ BUG_ON(data->blksz > host->mmc->max_blk_size);
+ BUG_ON(data->blocks > host->mmc->max_blk_count);
+
+ host->data = data;
+ host->data_early = 0;
+
+ count = mshci_calc_timeout(host, data);
+ mshci_writel(host, count, MSHCI_TMOUT);
+
+ mshci_reset_fifo(host);
+
+ if (host->flags & (MSHCI_USE_IDMA))
+ host->flags |= MSHCI_REQ_USE_DMA;
+
+ if (data->host_cookie)
+ goto check_done;
+ /*
+ * FIXME: This doesn't account for merging when mapping the
+ * scatterlist.
+ */
+ if (host->flags & MSHCI_REQ_USE_DMA) {
+ /* mshc's IDMAC can't transfer data that is not aligned
+ * or has length not divided by 4 byte. */
+ int i;
+ struct scatterlist *sg;
+
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->length & 0x3) {
+ DBG("Reverting to PIO because of "
+ "transfer size (%d)\n",
+ sg->length);
+ host->flags &= ~MSHCI_REQ_USE_DMA;
+ break;
+ } else if (sg->offset & 0x3) {
+ DBG("Reverting to PIO because of "
+ "bad alignment\n");
+ host->flags &= ~MSHCI_REQ_USE_DMA;
+ break;
+ }
+ }
+ }
+check_done:
+
+ if (host->flags & MSHCI_REQ_USE_DMA) {
+ ret = mshci_mdma_table_pre(host, data);
+ if (ret) {
+ /*
+ * This only happens when someone fed
+ * us an invalid request.
+ */
+ WARN_ON(1);
+ host->flags &= ~MSHCI_REQ_USE_DMA;
+ } else {
+ mshci_writel(host, host->idma_addr,
+ MSHCI_DBADDR);
+ }
+ }
+
+ if (host->flags & MSHCI_REQ_USE_DMA) {
+ /* enable DMA, IDMA interrupts and IDMAC */
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) |
+ ENABLE_IDMAC|DMA_ENABLE), MSHCI_CTRL);
+ mshci_writel(host, (mshci_readl(host, MSHCI_BMOD) |
+ (BMOD_IDMAC_ENABLE|BMOD_IDMAC_FB)),
+ MSHCI_BMOD);
+ mshci_writel(host, INTMSK_IDMAC_ERROR, MSHCI_IDINTEN);
+ }
+
+ if (!(host->flags & MSHCI_REQ_USE_DMA)) {
+ int flags;
+
+ flags = SG_MITER_ATOMIC;
+ if (host->data->flags & MMC_DATA_READ)
+ flags |= SG_MITER_TO_SG;
+ else
+ flags |= SG_MITER_FROM_SG;
+
+ sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+ host->blocks = data->blocks;
+
+ printk(KERN_ERR "it starts transfer on PIO\n");
+ }
+
+ /* set transfered data as 0. this value only uses for PIO write */
+ host->data_transfered = 0;
+ mshci_set_transfer_irqs(host);
+
+ mshci_writel(host, data->blksz, MSHCI_BLKSIZ);
+ mshci_writel(host, (data->blocks * data->blksz), MSHCI_BYTCNT);
+}
+
+static u32 mshci_set_transfer_mode(struct mshci_host *host,
+ struct mmc_data *data)
+{
+ u32 ret = 0;
+
+ if (data == NULL)
+ return ret;
+
+ WARN_ON(!host->data);
+
+ /* this cmd has data to transmit */
+ ret |= CMD_DATA_EXP_BIT;
+
+ if (data->flags & MMC_DATA_WRITE)
+ ret |= CMD_RW_BIT;
+ if (data->flags & MMC_DATA_STREAM)
+ ret |= CMD_TRANSMODE_BIT;
+
+ return ret;
+}
+
+static void mshci_finish_data(struct mshci_host *host)
+{
+ struct mmc_data *data;
+
+ BUG_ON(!host->data);
+
+ data = host->data;
+ host->data = NULL;
+
+ if (host->flags & MSHCI_REQ_USE_DMA) {
+ mshci_idma_table_post(host, data);
+ /* disable IDMAC and DMA interrupt */
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) &
+ ~(DMA_ENABLE|ENABLE_IDMAC)), MSHCI_CTRL);
+ /* mask all interrupt source of IDMAC */
+ mshci_writel(host, 0x0, MSHCI_IDINTEN);
+ }
+
+ if (data->error) {
+ /* to go to idle state */
+ mshci_reset_ciu(host);
+ /* to clear fifo */
+ mshci_reset_fifo(host);
+ /* to reset dma */
+ mshci_reset_dma(host);
+ data->bytes_xfered = 0;
+ } else
+ data->bytes_xfered = data->blksz * data->blocks;
+
+ /*
+ * Need to send CMD12 if -
+ * a) open-ended multiblock transfer (no CMD23)
+ * b) error in multiblock transfer
+ */
+ if (data->stop && ((data->error) ||
+ !(host->mmc->caps & MMC_CAP_CMD23) ||
+ ((host->mmc->caps & MMC_CAP_CMD23) &&
+ !host->mrq->sbc))) /* packed cmd case */
+ mshci_send_command(host, data->stop);
+ else
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+static void mshci_wait_release_start_bit(struct mshci_host *host)
+{
+ u32 loop_count = 1000000;
+
+ ktime_t expires;
+ u64 add_time = 100000; /* 100us */
+
+ /* before off clock, make sure data busy is released. */
+ while (mshci_readl(host, MSHCI_STATUS) & (1<<9) && --loop_count) {
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ expires = ktime_add_ns(ktime_get(), add_time);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_hrtimeout(&expires, HRTIMER_MODE_ABS);
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+ }
+ if (loop_count == 0)
+ printk(KERN_ERR "%s: cmd_strt_bit not released for 11sec\n",
+ mmc_hostname(host->mmc));
+
+ loop_count = 1000000;
+ do {
+ if (!(mshci_readl(host, MSHCI_CMD) & CMD_STRT_BIT))
+ break;
+ loop_count--;
+ udelay(1);
+ } while (loop_count);
+ if (loop_count == 0)
+ printk(KERN_ERR "%s: cmd_strt_bit not released for 1sec\n",
+ mmc_hostname(host->mmc));
+}
+
+static void mshci_clock_onoff(struct mshci_host *host, bool val)
+{
+ mshci_wait_release_start_bit(host);
+
+ if (val) {
+ mshci_writel(host, (0x1<<0), MSHCI_CLKENA);
+ mshci_writel(host, 0, MSHCI_CMD);
+ mshci_writel(host, CMD_ONLY_CLK, MSHCI_CMD);
+ } else {
+ mshci_writel(host, (0x0<<0), MSHCI_CLKENA);
+ mshci_writel(host, 0, MSHCI_CMD);
+ mshci_writel(host, CMD_ONLY_CLK, MSHCI_CMD);
+ }
+}
+
+static void mshci_send_command(struct mshci_host *host, struct mmc_command *cmd)
+{
+ int flags, ret;
+
+ WARN_ON(host->cmd);
+
+ /* clear error_state */
+ if (cmd->opcode != 12)
+ host->error_state = 0;
+
+ /* disable interrupt before issuing cmd to the card. */
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) & ~INT_ENABLE),
+ MSHCI_CTRL);
+
+ mod_timer(&host->timer, jiffies + 10 * HZ);
+
+ host->cmd = cmd;
+
+ mshci_prepare_data(host, cmd->data);
+
+ mshci_writel(host, cmd->arg, MSHCI_CMDARG);
+
+ flags = mshci_set_transfer_mode(host, cmd->data);
+
+ if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+ printk(KERN_ERR "%s: Unsupported response type!\n",
+ mmc_hostname(host->mmc));
+ cmd->error = -EINVAL;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ flags |= CMD_RESP_EXP_BIT;
+ if (cmd->flags & MMC_RSP_136)
+ flags |= CMD_RESP_LENGTH_BIT;
+ }
+ if (cmd->flags & MMC_RSP_CRC)
+ flags |= CMD_CHECK_CRC_BIT;
+
+ flags |= (cmd->opcode | CMD_STRT_BIT | host->hold_bit |
+ CMD_WAIT_PRV_DAT_BIT);
+
+ ret = mshci_readl(host, MSHCI_CMD);
+ if (ret & CMD_STRT_BIT)
+ printk(KERN_ERR "CMD busy. current cmd %d. last cmd reg 0x%x\n",
+ cmd->opcode, ret);
+
+ mshci_writel(host, flags, MSHCI_CMD);
+
+ /* enable interrupt upon it sends a command to the card. */
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) | INT_ENABLE),
+ MSHCI_CTRL);
+}
+
+static void mshci_finish_command(struct mshci_host *host)
+{
+ int i;
+
+ BUG_ON(host->cmd == NULL);
+
+ if (host->cmd->flags & MMC_RSP_PRESENT) {
+ if (host->cmd->flags & MMC_RSP_136) {
+ /*
+ * response data are overturned.
+ */
+ for (i = 0; i < 4; i++) {
+ host->cmd->resp[0] = mshci_readl(host,
+ MSHCI_RESP3);
+ host->cmd->resp[1] = mshci_readl(host,
+ MSHCI_RESP2);
+ host->cmd->resp[2] = mshci_readl(host,
+ MSHCI_RESP1);
+ host->cmd->resp[3] = mshci_readl(host,
+ MSHCI_RESP0);
+ }
+ } else {
+ host->cmd->resp[0] = mshci_readl(host, MSHCI_RESP0);
+ }
+ }
+
+ host->cmd->error = 0;
+
+ if (host->data && host->data_early)
+ mshci_finish_data(host);
+
+ if (!host->cmd->data)
+ tasklet_schedule(&host->finish_tasklet);
+
+ host->cmd = NULL;
+}
+
+static void mshci_set_clock(struct mshci_host *host,
+ unsigned int clock, u32 ddr)
+{
+ int div;
+
+ /* befor changing clock. clock needs to be off. */
+ mshci_clock_onoff(host, CLK_DISABLE);
+
+ if (clock == 0)
+ goto out;
+
+ if (clock >= host->max_clk) {
+ div = 0;
+ } else {
+ for (div = 1; div <= 0xff; div++) {
+ /* div value should not be greater than 0xff */
+ if ((host->max_clk / (div<<1)) <= clock)
+ break;
+ }
+ }
+
+ mshci_wait_release_start_bit(host);
+
+ mshci_writel(host, div, MSHCI_CLKDIV);
+
+ mshci_writel(host, 0, MSHCI_CMD);
+ mshci_writel(host, CMD_ONLY_CLK, MSHCI_CMD);
+ mshci_writel(host, mshci_readl(host, MSHCI_CMD)&(~CMD_SEND_CLK_ONLY),
+ MSHCI_CMD);
+
+ mshci_clock_onoff(host, CLK_ENABLE);
+
+out:
+ host->clock = clock;
+}
+
+static void mshci_set_power(struct mshci_host *host, unsigned short power)
+{
+ u8 pwr = power;
+
+ if (power == (unsigned short)-1)
+ pwr = 0;
+
+ if (host->pwr == pwr)
+ return;
+
+ host->pwr = pwr;
+
+ if (pwr == 0)
+ mshci_writel(host, 0, MSHCI_PWREN);
+ else
+ mshci_writel(host, 0x1, MSHCI_PWREN);
+}
+
+#ifdef CONFIG_MMC_POLLING_WAIT_CMD23
+static void mshci_check_sbc_status(struct mshci_host *host, int intmask)
+{
+ int timeout, int_status;;
+
+ /* wait for command done or error by polling */
+ timeout = 0x100000; /* it is bigger than 1ms */
+ do {
+ int_status = mshci_readl(host, MSHCI_RINTSTS);
+ if (int_status & CMD_STATUS)
+ break;
+ timeout--;
+ } while (timeout);
+
+ /* clear pending interupt bit */
+ mshci_writel(host, int_status, MSHCI_RINTSTS);
+
+ /* check whether command error has been occured or not. */
+ if (int_status & INTMSK_HTO) {
+ printk(KERN_ERR "%s: %s Host timeout error\n",
+ mmc_hostname(host->mmc),
+ __func__);
+ host->mrq->sbc->error = -ETIMEDOUT;
+ } else if (int_status & INTMSK_DRTO) {
+ printk(KERN_ERR "%s: %s Data read timeout error\n",
+ mmc_hostname(host->mmc),
+ __func__);
+ host->mrq->sbc->error = -ETIMEDOUT;
+ } else if (int_status & INTMSK_SBE) {
+ printk(KERN_ERR "%s: %s FIFO Start bit error\n",
+ mmc_hostname(host->mmc),
+ __func__);
+ host->mrq->sbc->error = -EIO;
+ } else if (int_status & INTMSK_EBE) {
+ printk(KERN_ERR "%s: %s FIFO Endbit/Write no CRC error\n",
+ mmc_hostname(host->mmc),
+ __func__);
+ host->mrq->sbc->error = -EIO;
+ } else if (int_status & INTMSK_DCRC) {
+ printk(KERN_ERR "%s: %s Data CRC error\n",
+ mmc_hostname(host->mmc),
+ __func__);
+ host->mrq->sbc->error = -EIO;
+ } else if (int_status & INTMSK_FRUN) {
+ printk(KERN_ERR "%s: %s FIFO underrun/overrun error\n",
+ mmc_hostname(host->mmc),
+ __func__);
+ host->mrq->sbc->error = -EIO;
+ } else if (int_status & CMD_ERROR) {
+ printk(KERN_ERR "%s: %s cmd %s error\n",
+ mmc_hostname(host->mmc),
+ __func__, (intmask & INTMSK_RCRC) ?
+ "response crc" :
+ (intmask & INTMSK_RE) ? "response" :
+ "response timeout");
+ host->mrq->sbc->error = -ETIMEDOUT;
+ }
+
+ if (host->mrq->sbc->error) {
+ /* restore interrupt mask bit */
+ mshci_writel(host, intmask, MSHCI_INTMSK);
+ return;
+ }
+
+ if (!timeout) {
+ printk(KERN_ERR "%s: %s no interrupt occured\n",
+ mmc_hostname(host->mmc), __func__);
+ host->mrq->sbc->error = -ETIMEDOUT;
+ /* restore interrupt mask bit */
+ mshci_writel(host, intmask, MSHCI_INTMSK);
+ return;
+ }
+
+ /* command done interrupt has been occured with no errors.
+ nothing to do. just return to the previous function */
+ if ((int_status & INTMSK_CDONE) && !(int_status & CMD_ERROR)) {
+ /* restore interrupt mask bit */
+ mshci_writel(host, intmask, MSHCI_INTMSK);
+ return;
+ }
+
+ /* should not be here */
+ printk(KERN_ERR "%s: an error that has not to be occured was"
+ " occured 0x%x\n",mmc_hostname(host->mmc),int_status);
+}
+
+static void mshci_send_sbc(struct mshci_host *host, struct mmc_command *cmd)
+{
+ int flags = 0, ret, intmask;
+
+ WARN_ON(host->cmd);
+
+ /* disable interrupt before issuing cmd to the card. */
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) & ~INT_ENABLE),
+ MSHCI_CTRL);
+
+ host->cmd = cmd;
+
+ mod_timer(&host->timer, jiffies + 10 * HZ);
+
+ mshci_writel(host, cmd->arg, MSHCI_CMDARG);
+
+ if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+ printk(KERN_ERR "%s: Unsupported response type!\n",
+ mmc_hostname(host->mmc));
+ cmd->error = -EINVAL;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ flags |= CMD_RESP_EXP_BIT;
+ if (cmd->flags & MMC_RSP_136)
+ flags |= CMD_RESP_LENGTH_BIT;
+ }
+ if (cmd->flags & MMC_RSP_CRC)
+ flags |= CMD_CHECK_CRC_BIT;
+
+ flags |= (cmd->opcode | CMD_STRT_BIT | host->hold_bit |
+ CMD_WAIT_PRV_DAT_BIT);
+
+ ret = mshci_readl(host, MSHCI_CMD);
+ if (ret & CMD_STRT_BIT)
+ printk(KERN_ERR "CMD busy. current cmd %d. last cmd reg 0x%x\n",
+ cmd->opcode, ret);
+
+ /* backup interrupt mask bit */
+ intmask = mshci_readl(host, MSHCI_INTMSK);
+
+ /* disable interrupts for sbc command. it will wait for command done
+ by polling. it expects a faster repsonse */
+ mshci_clear_set_irqs(host, INTMSK_ALL, 0);
+
+ /* send command */
+ mshci_writel(host, flags, MSHCI_CMD);
+
+ /* enable interrupt upon it sends a command to the card. */
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) | INT_ENABLE),
+ MSHCI_CTRL);
+
+ /* check the interrupt by polling */
+ mshci_check_sbc_status(host,intmask);
+}
+#endif
+
+/*****************************************************************************\
+ * *
+ * MMC callbacks *
+ * *
+\*****************************************************************************/
+
+static void mshci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct mshci_host *host;
+ bool present;
+ int timeout;
+ ktime_t expires;
+ u64 add_time = 50000; /* 50us */
+
+ host = mmc_priv(mmc);
+
+ WARN_ON(host->mrq != NULL);
+
+ host->mrq = mrq;
+
+ /* Wait max 1 sec */
+ timeout = 100000;
+
+ /* We shouldn't wait for data inihibit for stop commands, even
+ though they might use busy signaling */
+ if ((mrq->cmd->opcode == 12) || (mrq->cmd->opcode == 13)) {
+ /* nothing to do */
+ } else {
+ for (;;) {
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+ if (mshci_readl(host, MSHCI_STATUS) & (1<<9)) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Controller never"
+ " released data0.\n",
+ mmc_hostname(host->mmc));
+ mshci_dumpregs(host);
+
+ mrq->cmd->error = -ENOTRECOVERABLE;
+ host->error_state = 1;
+
+ tasklet_schedule \
+ (&host->finish_tasklet);
+ spin_unlock_irqrestore \
+ (&host->lock, host->sl_flags);
+ return;
+ }
+ timeout--;
+
+ /* if previous command made an error,
+ * this function might be called by tasklet.
+ * So, it SHOULD NOT use schedule_hrtimeout */
+ if (host->error_state == 1) {
+ spin_unlock_irqrestore
+ (&host->lock, host->sl_flags);
+ udelay(10);
+ } else {
+ spin_unlock_irqrestore
+ (&host->lock, host->sl_flags);
+ expires = ktime_add_ns
+ (ktime_get(), add_time);
+ set_current_state
+ (TASK_UNINTERRUPTIBLE);
+ schedule_hrtimeout
+ (&expires, HRTIMER_MODE_ABS);
+ }
+ } else {
+ spin_unlock_irqrestore(&host->lock,
+ host->sl_flags);
+ break;
+ }
+ }
+ }
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+ /* If polling, assume that the card is always present. */
+ if (host->quirks & MSHCI_QUIRK_BROKEN_CARD_DETECTION ||
+ host->quirks & MSHCI_QUIRK_BROKEN_PRESENT_BIT)
+ present = true;
+ else
+ present = !(mshci_readl(host, MSHCI_CDETECT) & CARD_PRESENT);
+
+ if (!present || host->flags & MSHCI_DEVICE_DEAD) {
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ } else {
+#ifdef CONFIG_MMC_POLLING_WAIT_CMD23
+ if (mrq->sbc) {
+ mshci_send_sbc(host, mrq->sbc);
+ if (mrq->sbc->error) {
+ tasklet_schedule(&host->finish_tasklet);
+ } else {
+ if (host->cmd)
+ host->cmd = NULL;
+ mshci_send_command(host, mrq->cmd);
+ }
+ } else
+#endif
+ mshci_send_command(host, mrq->cmd);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+}
+
+static void mshci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mshci_host *host;
+ u32 regs;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (host->flags & MSHCI_DEVICE_DEAD)
+ goto out;
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ mshci_reinit(host);
+
+#ifdef CONFIG_MMC_CLKGATE
+ /* gating the clock and out */
+ if (mmc->clk_gated) {
+ WARN_ON(ios->clock != 0);
+ if (host->clock != 0)
+ mshci_set_clock(host, ios->clock, ios->ddr);
+ goto out;
+ }
+#endif
+
+ if (host->ops->set_ios)
+ host->ops->set_ios(host, ios);
+
+ mshci_set_clock(host, ios->clock, ios->ddr);
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ mshci_set_power(host, -1);
+ else
+ mshci_set_power(host, ios->vdd);
+
+ regs = mshci_readl(host, MSHCI_UHS_REG);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_8) {
+ mshci_writel(host, (0x1<<16), MSHCI_CTYPE);
+ if (ios->timing == MMC_TIMING_UHS_DDR50) {
+ regs |= (0x1 << 16);
+ mshci_writel(host, regs, MSHCI_UHS_REG);
+ /* if exynos4412 EVT1 or the latest one */
+ if (soc_is_exynos4412() &&
+ samsung_rev() >= EXYNOS4412_REV_1_0) {
+ if ((host->max_clk/2) < 46300000) {
+ mshci_writel(host, (0x00010001),
+ MSHCI_CLKSEL);
+ } else {
+ mshci_writel(host, (0x00020002),
+ MSHCI_CLKSEL);
+ }
+ } else {
+ if ((host->max_clk/2) < 40000000)
+ mshci_writel(host, (0x00010001),
+ MSHCI_CLKSEL);
+ else
+ mshci_writel(host, (0x00020002),
+ MSHCI_CLKSEL);
+ }
+ } else {
+ regs &= ~(0x1 << 16);
+ mshci_writel(host, regs|(0x0<<0), MSHCI_UHS_REG);
+ mshci_writel(host, (0x00010001), MSHCI_CLKSEL);
+ }
+ } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ mshci_writel(host, (0x1<<0), MSHCI_CTYPE);
+ if (ios->timing == MMC_TIMING_UHS_DDR50) {
+ regs |= (0x1 << 16);
+ mshci_writel(host, regs, MSHCI_UHS_REG);
+ mshci_writel(host, (0x00010001), MSHCI_CLKSEL);
+ } else {
+ regs &= ~(0x1 << 16);
+ mshci_writel(host, regs|(0x0<<0), MSHCI_UHS_REG);
+ mshci_writel(host, (0x00010001), MSHCI_CLKSEL);
+ }
+ } else {
+ regs &= ~(0x1 << 16);
+ mshci_writel(host, regs|0, MSHCI_UHS_REG);
+ mshci_writel(host, (0x0<<0), MSHCI_CTYPE);
+ mshci_writel(host, (0x00010001), MSHCI_CLKSEL);
+ }
+out:
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+}
+
+static int mshci_get_ro(struct mmc_host *mmc)
+{
+ struct mshci_host *host;
+ int wrtprt;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (host->quirks & MSHCI_QUIRK_NO_WP_BIT)
+ wrtprt = host->ops->get_ro(mmc) ? 0 : WRTPRT_ON;
+ else if (host->flags & MSHCI_DEVICE_DEAD)
+ wrtprt = 0;
+ else
+ wrtprt = mshci_readl(host, MSHCI_WRTPRT);
+
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+
+ return wrtprt & WRTPRT_ON;
+}
+
+static void mshci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct mshci_host *host;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (host->flags & MSHCI_DEVICE_DEAD)
+ goto out;
+
+ if (enable)
+ mshci_unmask_irqs(host, SDIO_INT_ENABLE);
+ else
+ mshci_mask_irqs(host, SDIO_INT_ENABLE);
+out:
+ mmiowb();
+
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+}
+
+static void mshci_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+ struct mshci_host *host;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (host->flags & MSHCI_DEVICE_DEAD)
+ goto out;
+
+ if (host->ops->init_card)
+ host->ops->init_card(host);
+out:
+ mmiowb();
+
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+}
+
+static void mshci_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
+ bool is_first_req)
+{
+ struct mshci_host *host;
+ struct mmc_data *data = mrq->data;
+ int sg_count, direction;
+
+ host = mmc_priv(mmc);
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (!data)
+ goto out;
+
+ if (data->host_cookie) {
+ data->host_cookie = 0;
+ goto out;
+ }
+
+ if (host->flags & MSHCI_USE_IDMA) {
+ /* mshc's IDMAC can't transfer data that is not aligned
+ * or has length not divided by 4 byte. */
+ int i;
+ struct scatterlist *sg;
+
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->length & 0x3) {
+ DBG("Reverting to PIO because of "
+ "transfer size (%d)\n",
+ sg->length);
+ data->host_cookie = 0;
+ goto out;
+ } else if (sg->offset & 0x3) {
+ DBG("Reverting to PIO because of "
+ "bad alignment\n");
+ host->flags &= ~MSHCI_REQ_USE_DMA;
+ data->host_cookie = 0;
+ goto out;
+ }
+ }
+ }
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+
+ if (host->ops->dma_map_sg && data->blocks >= 2048) {
+ /* if transfer size is bigger than 1MiB */
+ sg_count = host->ops->dma_map_sg(host,
+ mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 2);
+ } else if (host->ops->dma_map_sg && data->blocks >= 128) {
+ /* if transfer size is bigger than 64KiB */
+ sg_count = host->ops->dma_map_sg(host,
+ mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 1);
+ } else {
+ sg_count = dma_map_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction);
+ }
+
+ if (sg_count == 0)
+ data->host_cookie = 0;
+ else
+ data->host_cookie = sg_count;
+out:
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ return;
+}
+
+static void mshci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
+ int err)
+{
+ struct mshci_host *host;
+ struct mmc_data *data = mrq->data;
+ int direction;
+
+ host = mmc_priv(mmc);
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (!data)
+ goto out;
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+
+ if ((host->ops->dma_unmap_sg && data->blocks >= 2048 &&
+ data->host_cookie)) {
+ /* if transfer size is bigger than 1MiB */
+ host->ops->dma_unmap_sg(host, mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 2);
+ } else if ((host->ops->dma_unmap_sg && data->blocks >= 128 &&
+ data->host_cookie)) {
+ /* if transfer size is bigger than 64KiB */
+ host->ops->dma_unmap_sg(host, mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction, 1);
+ } else if (data->host_cookie) {
+ dma_unmap_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len, direction);
+ }
+out:
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ return;
+}
+
+static struct mmc_host_ops mshci_ops = {
+ .request = mshci_request,
+ .set_ios = mshci_set_ios,
+ .get_ro = mshci_get_ro,
+ .enable_sdio_irq = mshci_enable_sdio_irq,
+ .init_card = mshci_init_card,
+#ifdef CONFIG_MMC_MSHCI_ASYNC_OPS
+ .pre_req = mshci_pre_req,
+ .post_req = mshci_post_req,
+#endif
+};
+
+/*****************************************************************************\
+ * *
+ * Tasklets *
+ * *
+\*****************************************************************************/
+
+static void mshci_tasklet_card(unsigned long param)
+{
+ struct mshci_host *host;
+
+ host = (struct mshci_host *)param;
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if ((host->quirks & MSHCI_QUIRK_BROKEN_CARD_DETECTION) ||
+ (host->quirks & MSHCI_QUIRK_BROKEN_PRESENT_BIT) ||
+ (mshci_readl(host, MSHCI_CDETECT) & CARD_PRESENT)) {
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ printk(KERN_ERR "%s: Resetting controller.\n",
+ mmc_hostname(host->mmc));
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(200));
+}
+
+static void mshci_tasklet_finish(unsigned long param)
+{
+ struct mshci_host *host;
+ struct mmc_request *mrq;
+
+ host = (struct mshci_host *)param;
+
+ if (host == NULL)
+ return;
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ del_timer(&host->timer);
+
+ mrq = host->mrq;
+
+ if (mrq == NULL || mrq->cmd == NULL)
+ goto out;
+
+ /*
+ * The controller needs a reset of internal state machines
+ * upon error conditions.
+ */
+ if (!(host->flags & MSHCI_DEVICE_DEAD) &&
+ (mrq->cmd->error ||
+#ifdef CONFIG_MMC_POLLING_WAIT_CMD23
+ (mrq->sbc && mrq->sbc->error) ||
+#endif
+ (mrq->data && (mrq->data->error ||
+ (mrq->data->stop && mrq->data->stop->error))))) {
+ mshci_reset_fifo(host);
+ }
+
+out:
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+
+ if (mrq)
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void mshci_timeout_timer(unsigned long data)
+{
+ struct mshci_host *host;
+
+ host = (struct mshci_host *)data;
+
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Timeout waiting for hardware "
+ "interrupt.\n", mmc_hostname(host->mmc));
+ mshci_dumpregs(host);
+
+ if (host->data) {
+ host->data->error = -ETIMEDOUT;
+ mshci_finish_data(host);
+ } else {
+ if (host->cmd)
+ host->cmd->error = -ETIMEDOUT;
+ else
+ host->mrq->cmd->error = -ETIMEDOUT;
+
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+}
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+static void mshci_cmd_irq(struct mshci_host *host, u32 intmask)
+{
+ BUG_ON(intmask == 0);
+
+ if (!host->cmd) {
+ printk(KERN_ERR "%s: Got command interrupt 0x%08x even "
+ "though no command operation was in progress.\n",
+ mmc_hostname(host->mmc), (unsigned)intmask);
+ mshci_dumpregs(host);
+ return;
+ }
+
+ if (intmask & INTMSK_RTO) {
+ host->cmd->error = -ETIMEDOUT;
+ printk(KERN_ERR "%s: cmd %d response timeout error\n",
+ mmc_hostname(host->mmc), host->cmd->opcode);
+ } else if (intmask & (INTMSK_RCRC | INTMSK_RE)) {
+ host->cmd->error = -EILSEQ;
+ printk(KERN_ERR "%s: cmd %d repsonse %s error\n",
+ mmc_hostname(host->mmc), host->cmd->opcode,
+ (intmask & INTMSK_RCRC) ? "crc" : "RE");
+ }
+ if (host->cmd->error) {
+ /* to notify an error happend */
+ host->error_state = 1;
+#if defined(CONFIG_MACH_M0) || defined(CONFIG_MACH_P4NOTE) || \
+ defined(CONFIG_MACH_C1_USA_ATT) \
+ || defined(CONFIG_MACH_GRANDE) || defined(CONFIG_MACH_IRON)
+ if (host->mmc && host->mmc->card)
+ mshci_dumpregs(host);
+#endif
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+
+ if (intmask & INTMSK_CDONE)
+ mshci_finish_command(host);
+}
+
+static void mshci_data_irq(struct mshci_host *host, u32 intmask, u8 intr_src)
+{
+ BUG_ON(intmask == 0);
+
+ if (!host->data) {
+ /*
+ * The "data complete" interrupt is also used to
+ * indicate that a busy state has ended. See comment
+ * above in mshci_cmd_irq().
+ */
+ if (host->cmd && (host->cmd->flags & MMC_RSP_BUSY)) {
+ if (intmask & INTMSK_DTO) {
+ mshci_finish_command(host);
+ return;
+ }
+ }
+
+ printk(KERN_ERR "%s: Got data interrupt 0x%08x from %s "
+ "even though no data operation was in progress.\n",
+ mmc_hostname(host->mmc), (unsigned)intmask,
+ intr_src ? "MINT" : "IDMAC");
+ mshci_dumpregs(host);
+
+ return;
+ }
+ if (intr_src == INT_SRC_MINT) {
+ if (intmask & INTMSK_HTO) {
+ printk(KERN_ERR "%s: Host timeout error\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -ETIMEDOUT;
+ /* debugging for Host timeout error */
+ mshci_dumpregs(host);
+ } else if (intmask & INTMSK_DRTO) {
+ printk(KERN_ERR "%s: Data read timeout error\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -ETIMEDOUT;
+ } else if (intmask & INTMSK_SBE) {
+ printk(KERN_ERR "%s: FIFO Start bit error\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ } else if (intmask & INTMSK_EBE) {
+ printk(KERN_ERR "%s: FIFO Endbit/Write no CRC error\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ } else if (intmask & INTMSK_DCRC) {
+ printk(KERN_ERR "%s: Data CRC error\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ } else if (intmask & INTMSK_FRUN) {
+ printk(KERN_ERR "%s: FIFO underrun/overrun error\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ }
+ } else {
+ if (intmask & IDSTS_FBE) {
+ printk(KERN_ERR "%s: Fatal Bus error on DMA\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ } else if (intmask & IDSTS_CES) {
+ printk(KERN_ERR "%s: Card error on DMA\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ } else if (intmask & IDSTS_DU) {
+ printk(KERN_ERR "%s: Description error on DMA\n",
+ mmc_hostname(host->mmc));
+ host->data->error = -EIO;
+ }
+ }
+
+ if (host->data->error) {
+ /* to notify an error happend */
+ host->error_state = 1;
+#if defined(CONFIG_MACH_M0) || defined(CONFIG_MACH_P4NOTE) || \
+ defined(CONFIG_MACH_C1_USA_ATT) \
+ || defined(CONFIG_MACH_GRANDE) || defined(CONFIG_MACH_IRON)
+ if (host->mmc && host->mmc->card)
+ mshci_dumpregs(host);
+#endif
+ mshci_finish_data(host);
+ } else {
+ if (!(host->flags & MSHCI_REQ_USE_DMA) &&
+ (((host->data->flags & MMC_DATA_READ) &&
+ (intmask & (INTMSK_RXDR | INTMSK_DTO))) ||
+ ((host->data->flags & MMC_DATA_WRITE) &&
+ (intmask & (INTMSK_TXDR)))))
+ mshci_transfer_pio(host);
+
+ if (intmask & INTMSK_DTO) {
+ if (host->cmd) {
+ /*
+ * Data managed to finish before the
+ * command completed. Make sure we do
+ * things in the proper order.
+ */
+ host->data_early = 1;
+ } else {
+ mshci_finish_data(host);
+ }
+ }
+ }
+}
+
+static irqreturn_t mshci_irq(int irq, void *dev_id)
+{
+ irqreturn_t result;
+ struct mshci_host *host = dev_id;
+ u32 intmask;
+ int cardint = 0;
+ int timeout = 0x10000;
+
+ spin_lock(&host->lock);
+
+ intmask = mshci_readl(host, MSHCI_MINTSTS);
+
+ if (!intmask || intmask == 0xffffffff) {
+ /* check if there is a interrupt for IDMAC */
+ intmask = mshci_readl(host, MSHCI_IDSTS);
+ if (intmask) {
+ mshci_writel(host, intmask, MSHCI_IDSTS);
+ mshci_data_irq(host, intmask, INT_SRC_IDMAC);
+ result = IRQ_HANDLED;
+ goto out;
+ }
+ result = IRQ_NONE;
+ goto out;
+ }
+ DBG("*** %s got interrupt: 0x%08x\n",
+ mmc_hostname(host->mmc), intmask);
+
+ mshci_writel(host, intmask, MSHCI_RINTSTS);
+
+ if (intmask & (INTMSK_CDETECT)) {
+ if (!(host->mmc->caps & MMC_CAP_NONREMOVABLE))
+ tasklet_schedule(&host->card_tasklet);
+ }
+ intmask &= ~INTMSK_CDETECT;
+
+ if (intmask & CMD_STATUS) {
+ if (!(intmask & INTMSK_CDONE) && (intmask & INTMSK_RTO)) {
+ /*
+ * when a error about command timeout occurs,
+ * cmd done intr comes together.
+ * cmd done intr comes later than error intr.
+ * so, it has to wait for cmd done intr.
+ */
+ while (--timeout && !(mshci_readl(host, MSHCI_MINTSTS)
+ & INTMSK_CDONE))
+ ; /* Nothing to do */
+ if (!timeout)
+ printk(KERN_ERR"*** %s time out for CDONE intr\n",
+ mmc_hostname(host->mmc));
+ else
+ mshci_writel(host, INTMSK_CDONE,
+ MSHCI_RINTSTS);
+ mshci_cmd_irq(host, intmask & CMD_STATUS);
+ } else {
+ mshci_cmd_irq(host, intmask & CMD_STATUS);
+ }
+ }
+
+ if (intmask & DATA_STATUS) {
+ if (!(intmask & INTMSK_DTO) && (intmask & INTMSK_DRTO)) {
+ /*
+ * when a error about data timout occurs,
+ * DTO intr comes together.
+ * DTO intr comes later than error intr.
+ * so, it has to wait for DTO intr.
+ */
+ while (--timeout && !(mshci_readl(host, MSHCI_MINTSTS)
+ & INTMSK_DTO))
+ ; /* Nothing to do */
+ if (!timeout)
+ printk(KERN_ERR"*** %s time out for DTO intr\n",
+ mmc_hostname(host->mmc));
+ else
+ mshci_writel(host, INTMSK_DTO,
+ MSHCI_RINTSTS);
+ mshci_data_irq(host, intmask & DATA_STATUS,
+ INT_SRC_MINT);
+ } else {
+ mshci_data_irq(host, intmask & DATA_STATUS,
+ INT_SRC_MINT);
+ }
+ }
+
+ intmask &= ~(CMD_STATUS | DATA_STATUS);
+
+ if (intmask & SDIO_INT_ENABLE)
+ cardint = 1;
+
+ intmask &= ~SDIO_INT_ENABLE;
+
+ if (intmask) {
+ printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n",
+ mmc_hostname(host->mmc), intmask);
+ mshci_dumpregs(host);
+ }
+
+ result = IRQ_HANDLED;
+
+ mmiowb();
+out:
+ spin_unlock(&host->lock);
+
+ /*
+ * We have to delay this as it calls back into the driver.
+ */
+ if (cardint)
+ mmc_signal_sdio_irq(host->mmc);
+
+ return result;
+}
+
+/*****************************************************************************\
+ * *
+ * Suspend/resume *
+ * *
+\*****************************************************************************/
+
+#ifdef CONFIG_PM
+
+int mshci_suspend_host(struct mshci_host *host, pm_message_t state)
+{
+ int ret;
+
+ mshci_disable_card_detection(host);
+
+ ret = mmc_suspend_host(host->mmc);
+ if (ret)
+ return ret;
+
+ free_irq(host->irq, host);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mshci_suspend_host);
+
+int mshci_resume_host(struct mshci_host *host)
+{
+ int ret;
+ int count;
+
+ if (host->flags & (MSHCI_USE_IDMA)) {
+ if (host->ops->enable_dma)
+ host->ops->enable_dma(host);
+ }
+
+ mshci_init(host);
+
+ ret = request_irq(host->irq, mshci_irq, IRQF_SHARED,
+ mmc_hostname(host->mmc), host);
+ if (ret)
+ return ret;
+
+ mmiowb();
+
+ mshci_fifo_init(host);
+
+ /* set debounce filter value*/
+ mshci_writel(host, 0xfffff, MSHCI_DEBNCE);
+
+ /* clear card type. set 1bit mode */
+ mshci_writel(host, 0x0, MSHCI_CTYPE);
+
+ /* set bus mode register for IDMAC */
+ if (host->flags & MSHCI_USE_IDMA) {
+ mshci_writel(host, BMOD_IDMAC_RESET, MSHCI_BMOD);
+ count = 100;
+ while ((mshci_readl(host, MSHCI_BMOD) & BMOD_IDMAC_RESET)
+ && --count)
+ ; /* nothing to do */
+
+ mshci_writel(host, (mshci_readl(host, MSHCI_BMOD) |
+ (BMOD_IDMAC_ENABLE|BMOD_IDMAC_FB)), MSHCI_BMOD);
+ }
+
+ ret = mmc_resume_host(host->mmc);
+ if (ret)
+ return ret;
+
+ mshci_enable_card_detection(host);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mshci_resume_host);
+
+#endif /* CONFIG_PM */
+
+/*****************************************************************************\
+ * *
+ * Device allocation/registration *
+ * *
+\*****************************************************************************/
+
+struct mshci_host *mshci_alloc_host(struct device *dev,
+ size_t priv_size)
+{
+ struct mmc_host *mmc;
+ struct mshci_host *host;
+
+ WARN_ON(dev == NULL);
+
+ mmc = mmc_alloc_host(sizeof(struct mshci_host) + priv_size, dev);
+ if (!mmc)
+ return ERR_PTR(-ENOMEM);
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ return host;
+}
+
+static void mshci_fifo_init(struct mshci_host *host)
+{
+ int fifo_val, fifo_depth, fifo_threshold;
+
+ fifo_val = mshci_readl(host, MSHCI_FIFOTH);
+ fifo_depth = host->ops->get_fifo_depth(host);
+ fifo_threshold = fifo_depth/2;
+ host->fifo_threshold = fifo_threshold;
+ host->fifo_depth = fifo_threshold*2;
+
+ printk(KERN_INFO "%s: FIFO WMARK FOR RX 0x%x WX 0x%x. ###########\n",
+ mmc_hostname(host->mmc), fifo_depth,
+ fifo_threshold);
+
+ fifo_val &= ~(RX_WMARK | TX_WMARK | MSIZE_MASK);
+
+ fifo_val |= (fifo_threshold | ((fifo_threshold-1)<<16));
+ if (fifo_threshold >= 0x40)
+ fifo_val |= MSIZE_64;
+ else if (fifo_threshold >= 0x20)
+ fifo_val |= MSIZE_32;
+ else if (fifo_threshold >= 0x10)
+ fifo_val |= MSIZE_16;
+ else if (fifo_threshold >= 0x8)
+ fifo_val |= MSIZE_8;
+ else
+ fifo_val |= MSIZE_1;
+
+ mshci_writel(host, fifo_val, MSHCI_FIFOTH);
+}
+EXPORT_SYMBOL_GPL(mshci_alloc_host);
+
+int mshci_add_host(struct mshci_host *host)
+{
+ struct mmc_host *mmc;
+ int ret, count;
+
+ WARN_ON(host == NULL);
+ if (host == NULL)
+ return -EINVAL;
+
+ mmc = host->mmc;
+
+ if (debug_quirks)
+ host->quirks = debug_quirks;
+
+ mshci_reset_all(host);
+
+ host->version = mshci_readl(host, MSHCI_VERID);
+
+ /* there are no reasons not to use DMA */
+ host->flags |= MSHCI_USE_IDMA;
+
+ if (host->flags & MSHCI_USE_IDMA) {
+ /* We need to allocate descriptors for all sg entries
+ * MSHCI_MAX_DMA_LIST transfer for each of those entries. */
+ host->idma_desc = kmalloc(MSHCI_MAX_DMA_LIST * \
+ sizeof(struct mshci_idmac), GFP_KERNEL);
+ if (!host->idma_desc) {
+ kfree(host->idma_desc);
+ printk(KERN_WARNING "%s: Unable to allocate IDMA "
+ "buffers. Falling back to standard DMA.\n",
+ mmc_hostname(mmc));
+ host->flags &= ~MSHCI_USE_IDMA;
+ }
+ }
+
+ /*
+ * If we use DMA, then it's up to the caller to set the DMA
+ * mask, but PIO does not need the hw shim so we set a new
+ * mask here in that case.
+ */
+ if (!(host->flags & (MSHCI_USE_IDMA))) {
+ host->dma_mask = DMA_BIT_MASK(64);
+ mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
+ }
+
+ printk(KERN_ERR "%s: Version ID 0x%x.\n",
+ mmc_hostname(host->mmc), host->version);
+
+ host->max_clk = 0;
+
+ if (host->max_clk == 0) {
+ if (!host->ops->get_max_clock) {
+ printk(KERN_ERR
+ "%s: Hardware doesn't specify base clock "
+ "frequency.\n", mmc_hostname(mmc));
+ return -ENODEV;
+ }
+ host->max_clk = host->ops->get_max_clock(host);
+ }
+
+ /*
+ * Set host parameters.
+ */
+ if (host->ops->get_ro)
+ mshci_ops.get_ro = host->ops->get_ro;
+
+ mmc->ops = &mshci_ops;
+ mmc->f_min = 400000;
+ mmc->f_max = host->max_clk;
+ mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE;
+
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ mmc->ocr_avail = 0;
+ mmc->ocr_avail |= MMC_VDD_32_33|MMC_VDD_33_34;
+ mmc->ocr_avail |= MMC_VDD_29_30|MMC_VDD_30_31;
+
+
+ if (mmc->ocr_avail == 0) {
+ printk(KERN_ERR "%s: Hardware doesn't report any "
+ "support voltages.\n", mmc_hostname(mmc));
+ return -ENODEV;
+ }
+
+ spin_lock_init(&host->lock);
+
+ /*
+ * Maximum number of segments. Depends on if the hardware
+ * can do scatter/gather or not.
+ */
+ if (host->flags & MSHCI_USE_IDMA)
+ mmc->max_segs = MSHCI_MAX_DMA_LIST;
+ else /* PIO */
+ mmc->max_segs = MSHCI_MAX_DMA_LIST;
+
+ mmc->max_segs = MSHCI_MAX_DMA_LIST;
+
+ /*
+ * Maximum number of sectors in one transfer. Limited by DMA boundary
+ * size (4KiB).
+ * Limited by CPU I/O boundry size (0xfffff000 KiB)
+ */
+
+ /* to prevent starvation of a process that want to access SD device
+ * it should limit size that transfer at one time. */
+ mmc->max_req_size = MSHCI_MAX_DMA_TRANS_SIZE;
+
+ /*
+ * Maximum segment size. Could be one segment with the maximum number
+ * of bytes. When doing hardware scatter/gather, each entry cannot
+ * be larger than 4 KiB though.
+ */
+ if (host->flags & MSHCI_USE_IDMA)
+ mmc->max_seg_size = 0x1000;
+ else
+ mmc->max_seg_size = mmc->max_req_size;
+
+ /* from SD spec 2.0 and MMC spec 4.2, block size has been
+ * fixed to 512 byte */
+ mmc->max_blk_size = 0;
+
+ mmc->max_blk_size = 512 << mmc->max_blk_size;
+
+ /*
+ * Maximum block count.
+ */
+ mmc->max_blk_count = MSHCI_MAX_DMA_TRANS_SIZE / mmc->max_blk_size ;
+
+ /*
+ * Init tasklets.
+ */
+ tasklet_init(&host->card_tasklet,
+ mshci_tasklet_card, (unsigned long)host);
+ tasklet_init(&host->finish_tasklet,
+ mshci_tasklet_finish, (unsigned long)host);
+
+ setup_timer(&host->timer, mshci_timeout_timer, (unsigned long)host);
+
+ ret = request_irq(host->irq, mshci_irq, IRQF_SHARED,
+ mmc_hostname(mmc), host);
+ if (ret)
+ goto untasklet;
+
+ mshci_init(host);
+
+ mshci_writel(host, (mshci_readl(host, MSHCI_CTRL) | INT_ENABLE),
+ MSHCI_CTRL);
+
+ mshci_fifo_init(host);
+
+ /* set debounce filter value*/
+ mshci_writel(host, 0xfffff, MSHCI_DEBNCE);
+
+ /* clear card type. set 1bit mode */
+ mshci_writel(host, 0x0, MSHCI_CTYPE);
+
+ /* set bus mode register for IDMAC */
+ if (host->flags & MSHCI_USE_IDMA) {
+ mshci_writel(host, BMOD_IDMAC_RESET, MSHCI_BMOD);
+ count = 100;
+ while ((mshci_readl(host, MSHCI_BMOD) & BMOD_IDMAC_RESET)
+ && --count)
+ ; /* nothing to do */
+
+ mshci_writel(host, (mshci_readl(host, MSHCI_BMOD) |
+ (BMOD_IDMAC_ENABLE|BMOD_IDMAC_FB)), MSHCI_BMOD);
+ }
+#ifdef CONFIG_MMC_DEBUG
+ mshci_dumpregs(host);
+#endif
+
+ mmiowb();
+
+ mmc_add_host(mmc);
+
+ printk(KERN_INFO "%s: MSHCI controller on %s [%s] using %s\n",
+ mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
+ (host->flags & MSHCI_USE_IDMA) ? "IDMA" : "PIO");
+
+ mshci_enable_card_detection(host);
+
+ return 0;
+
+untasklet:
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mshci_add_host);
+
+void mshci_remove_host(struct mshci_host *host, int dead)
+{
+ if (dead) {
+ spin_lock_irqsave(&host->lock, host->sl_flags);
+
+ host->flags |= MSHCI_DEVICE_DEAD;
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Controller removed during "
+ " transfer!\n", mmc_hostname(host->mmc));
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ spin_unlock_irqrestore(&host->lock, host->sl_flags);
+ }
+
+ mshci_disable_card_detection(host);
+
+ mmc_remove_host(host->mmc);
+
+ if (!dead)
+ mshci_reset_all(host);
+
+ free_irq(host->irq, host);
+
+ del_timer_sync(&host->timer);
+
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+
+ kfree(host->idma_desc);
+
+ host->idma_desc = NULL;
+ host->align_buffer = NULL;
+}
+EXPORT_SYMBOL_GPL(mshci_remove_host);
+
+void mshci_free_host(struct mshci_host *host)
+{
+ mmc_free_host(host->mmc);
+}
+EXPORT_SYMBOL_GPL(mshci_free_host);
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init mshci_drv_init(void)
+{
+ int ret = 0;
+ printk(KERN_INFO DRIVER_NAME
+ ": Mobile Storage Host Controller Interface driver\n");
+ printk(KERN_INFO DRIVER_NAME
+ ": Copyright (c) 2011 Samsung Electronics Co., Ltd\n");
+
+ return ret;
+}
+
+static void __exit mshci_drv_exit(void)
+{
+}
+
+module_init(mshci_drv_init);
+module_exit(mshci_drv_exit);
+
+module_param(debug_quirks, uint, 0444);
+
+MODULE_AUTHOR("Hyunsung Jang, <hs79.jang@samsung.com>");
+MODULE_DESCRIPTION("Mobile Storage Host Controller Interface core driver");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM_DESC(debug_quirks, "Force certain quirks.");
diff --git a/drivers/mmc/host/mshci.h b/drivers/mmc/host/mshci.h
new file mode 100644
index 0000000..40212ae
--- /dev/null
+++ b/drivers/mmc/host/mshci.h
@@ -0,0 +1,463 @@
+/*
+* linux/drivers/mmc/host/mshci.h
+* Mobile Storage Host Controller Interface driver
+*
+* Copyright (c) 2011 Samsung Electronics Co., Ltd.
+* http://www.samsung.com
+*
+* Based on linux/drivers/mmc/host/sdhci.h
+*
+* 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, or (at
+* your option) any later version.
+*
+*/
+
+#include <linux/scatterlist.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/io.h>
+
+/*
+ * Controller registers
+ */
+/*****************************************************/
+/* MSHC Internal Registers */
+/*****************************************************/
+
+#define MSHCI_CTRL 0x00 /* Control */
+#define MSHCI_PWREN 0x04 /* Power-enable */
+#define MSHCI_CLKDIV 0x08 /* Clock divider */
+#define MSHCI_CLKSRC 0x0C /* Clock source */
+#define MSHCI_CLKENA 0x10 /* Clock enable */
+#define MSHCI_TMOUT 0x14 /* Timeout */
+#define MSHCI_CTYPE 0x18 /* Card type */
+#define MSHCI_BLKSIZ 0x1C /* Block Size */
+#define MSHCI_BYTCNT 0x20 /* Byte count */
+#define MSHCI_INTMSK 0x24 /* Interrupt Mask */
+#define MSHCI_CMDARG 0x28 /* Command Argument */
+#define MSHCI_CMD 0x2C /* Command */
+#define MSHCI_RESP0 0x30 /* Response 0 */
+#define MSHCI_RESP1 0x34 /* Response 1 */
+#define MSHCI_RESP2 0x38 /* Response 2 */
+#define MSHCI_RESP3 0x3C /* Response 3 */
+#define MSHCI_MINTSTS 0x40 /* Masked interrupt status */
+#define MSHCI_RINTSTS 0x44 /* Raw interrupt status */
+#define MSHCI_STATUS 0x48 /* Status */
+#define MSHCI_FIFOTH 0x4C /* FIFO threshold */
+#define MSHCI_CDETECT 0x50 /* Card detect */
+#define MSHCI_WRTPRT 0x54 /* Write protect */
+#define MSHCI_GPIO 0x58 /* General Purpose IO */
+#define MSHCI_TCBCNT 0x5C /* Transferred CIU byte count */
+#define MSHCI_TBBCNT 0x60 /* Transferred host/DMA to/from byte count */
+#define MSHCI_DEBNCE 0x64 /* Card detect debounce */
+#define MSHCI_USRID 0x68 /* User ID */
+#define MSHCI_VERID 0x6C /* Version ID */
+#define MSHCI_HCON 0x70 /* Hardware Configuration */
+#define MSHCI_UHS_REG 0x74 /* UHS and DDR setting */
+#define MSHCI_BMOD 0x80 /* Bus mode register */
+#define MSHCI_PLDMND 0x84 /* Poll demand */
+#define MSHCI_DBADDR 0x88 /* Descriptor list base address */
+#define MSHCI_IDSTS 0x8C /* Internal DMAC status */
+#define MSHCI_IDINTEN 0x90 /* Internal DMAC interrupt enable */
+#define MSHCI_DSCADDR 0x94 /* Current host descriptor address */
+#define MSHCI_BUFADDR 0x98 /* Current host buffer address */
+#define MSHCI_CLKSEL 0x9C /* Clock Selection Register */
+#define MSHCI_WAKEUPCON 0xA0 /* Wakeup control register */
+#define MSHCI_CLOCKCON 0xA4 /* Clock (delay) control register */
+#define MSHCI_FIFODAT 0x100 /* FIFO data read write */
+
+/*****************************************************
+ * Control Register Register
+ * MSHCI_CTRL - offset 0x00
+ *****************************************************/
+
+#define CTRL_RESET (0x1<<0) /* Reset DWC_mobile_storage controller */
+#define FIFO_RESET (0x1<<1) /* Reset FIFO */
+#define DMA_RESET (0x1<<2) /* Reset DMA interface */
+#define INT_ENABLE (0x1<<4) /* Global interrupt enable/disable bit */
+#define DMA_ENABLE (0x1<<5) /* DMA transfer mode enable/disable bit */
+#define READ_WAIT (0x1<<6) /* For sending read-wait to SDIO cards */
+#define SEND_IRQ_RESP (0x1<<7) /* Send auto IRQ response */
+#define ABRT_READ_DATA (0x1<<8)
+#define SEND_CCSD (0x1<<9)
+#define SEND_AS_CCSD (0x1<<10)
+#define CEATA_INTSTAT (0x1<<11)
+#define CARD_VOLA (0xF<<16)
+#define CARD_VOLB (0xF<<20)
+#define ENABLE_OD_PULLUP (0x1<<24)
+#define ENABLE_IDMAC (0x1<<25)
+
+#define MSHCI_RESET_ALL (0x1)
+
+/*****************************************************
+ * Power Enable Register
+ * MSHCI_PWREN - offset 0x04
+ *****************************************************/
+#define POWER_ENABLE (0x1<<0)
+
+/*****************************************************
+ * Clock Divider Register
+ * MSHCI_CLKDIV - offset 0x08
+ *****************************************************/
+#define CLK_DIVIDER0 (0xFF<<0)
+#define CLK_DIVIDER1 (0xFF<<8)
+#define CLK_DIVIDER2 (0xFF<<16)
+#define CLK_DIVIDER3 (0xFF<<24)
+
+/*****************************************************
+ * Clock Enable Register
+ * MSHCI_CLKENA - offset 0x10
+ *****************************************************/
+#define CLK_SDMMC_MAX (48000000) /* 96Mhz. it SHOULDBE optimized */
+#define CLK_ENABLE (0x1<<0)
+#define CLK_DISABLE (0x0<<0)
+
+/*****************************************************
+ * Timeout Register
+ * MSHCI_TMOUT - offset 0x14
+ *****************************************************/
+#define RSP_TIMEOUT (0xFF<<0)
+#define DATA_TIMEOUT (0xFFFFFF<<8)
+
+/*****************************************************
+ * Card Type Register
+ * MSHCI_CTYPE - offset 0x18
+ *****************************************************/
+#define CARD_WIDTH4 (0xFFFF<<0)
+#define CARD_WIDTH8 (0xFFFF<<16)
+
+/*****************************************************
+ * Block Size Register
+ * MSHCI_BLKSIZ - offset 0x1C
+ *****************************************************/
+#define BLK_SIZ (0xFFFF<<0)
+
+/*****************************************************
+ * Interrupt Mask Register
+ * MSHCI_INTMSK - offset 0x24
+ *****************************************************/
+#define INT_MASK (0xFFFF<<0)
+#define SDIO_INT_MASK (0xFFFF<<16)
+#define SDIO_INT_ENABLE (0x1<<16)
+
+/* interrupt bits */
+#define INTMSK_ALL 0xFFFFFFFF
+#define INTMSK_CDETECT (0x1<<0)
+#define INTMSK_RE (0x1<<1)
+#define INTMSK_CDONE (0x1<<2)
+#define INTMSK_DTO (0x1<<3)
+#define INTMSK_TXDR (0x1<<4)
+#define INTMSK_RXDR (0x1<<5)
+#define INTMSK_RCRC (0x1<<6)
+#define INTMSK_DCRC (0x1<<7)
+#define INTMSK_RTO (0x1<<8)
+#define INTMSK_DRTO (0x1<<9)
+#define INTMSK_HTO (0x1<<10)
+#define INTMSK_FRUN (0x1<<11)
+#define INTMSK_HLE (0x1<<12)
+#define INTMSK_SBE (0x1<<13)
+#define INTMSK_ACD (0x1<<14)
+#define INTMSK_EBE (0x1<<15)
+#define INTMSK_DMA (INTMSK_ACD|INTMSK_RXDR|INTMSK_TXDR)
+
+#define INT_SRC_IDMAC (0x0)
+#define INT_SRC_MINT (0x1)
+
+
+/*****************************************************
+ * Command Register
+ * MSHCI_CMD - offset 0x2C
+ *****************************************************/
+
+#define CMD_RESP_EXP_BIT (0x1<<6)
+#define CMD_RESP_LENGTH_BIT (0x1<<7)
+#define CMD_CHECK_CRC_BIT (0x1<<8)
+#define CMD_DATA_EXP_BIT (0x1<<9)
+#define CMD_RW_BIT (0x1<<10)
+#define CMD_TRANSMODE_BIT (0x1<<11)
+#define CMD_SENT_AUTO_STOP_BIT (0x1<<12)
+#define CMD_WAIT_PRV_DAT_BIT (0x1<<13)
+#define CMD_ABRT_CMD_BIT (0x1<<14)
+#define CMD_SEND_INIT_BIT (0x1<<15)
+#define CMD_CARD_NUM_BITS (0x1F<<16)
+#define CMD_SEND_CLK_ONLY (0x1<<21)
+#define CMD_READ_CEATA (0x1<<22)
+#define CMD_CCS_EXPECTED (0x1<<23)
+#define CMD_USE_HOLD_REG (0x1<<29)
+#define CMD_STRT_BIT (0x1<<31)
+#define CMD_ONLY_CLK (CMD_STRT_BIT | CMD_SEND_CLK_ONLY | \
+ CMD_WAIT_PRV_DAT_BIT)
+
+/*****************************************************
+ * Masked Interrupt Status Register
+ * MSHCI_MINTSTS - offset 0x40
+ *****************************************************/
+/*****************************************************
+ * Raw Interrupt Register
+ * MSHCI_RINTSTS - offset 0x44
+ *****************************************************/
+#define INT_STATUS (0xFFFF<<0)
+#define SDIO_INTR (0xFFFF<<16)
+#define DATA_ERR (INTMSK_EBE|INTMSK_SBE|INTMSK_HLE|INTMSK_FRUN|\
+ INTMSK_EBE|INTMSK_DCRC)
+#define DATA_TOUT (INTMSK_HTO|INTMSK_DRTO)
+#define DATA_STATUS (DATA_ERR|DATA_TOUT|INTMSK_RXDR|INTMSK_TXDR|INTMSK_DTO)
+#define CMD_STATUS (INTMSK_RTO|INTMSK_RCRC|INTMSK_CDONE|INTMSK_RE)
+#define CMD_ERROR (INTMSK_RCRC|INTMSK_RTO|INTMSK_RE)
+
+/*****************************************************
+ * Status Register
+ * MSHCI_STATUS - offset 0x48
+ *****************************************************/
+#define FIFO_RXWTRMARK (0x1<<0)
+#define FIFO_TXWTRMARK (0x1<<1)
+#define FIFO_EMPTY (0x1<<2)
+#define FIFO_FULL (0x1<<3)
+#define CMD_FSMSTAT (0xF<<4)
+#define DATA_3STATUS (0x1<<8)
+#define DATA_BUSY (0x1<<9)
+#define DATA_MCBUSY (0x1<<10)
+#define RSP_INDEX (0x3F<<11)
+#define FIFO_COUNT (0x1FFF<<17)
+#define DMA_ACK (0x1<<30)
+#define DMA_REQ (0x1<<31)
+#define FIFO_WIDTH (0x4)
+#define FIFO_DEPTH (0x20)
+
+/*Command FSM status */
+#define FSM_IDLE (0<<4)
+#define FSM_SEND_INIT_SEQ (1<<4)
+#define FSM_TX_CMD_STARTBIT (2<<4)
+#define FSM_TX_CMD_TXBIT (3<<4)
+#define FSM_TX_CMD_INDEX_ARG (4<<4)
+#define FSM_TX_CMD_CRC7 (5<<4)
+#define FSM_TX_CMD_ENDBIT (6<<4)
+#define FSM_RX_RESP_STARTBIT (7<<4)
+#define FSM_RX_RESP_IRQRESP (8<<4)
+#define FSM_RX_RESP_TXBIT (9<<4)
+#define FSM_RX_RESP_CMDIDX (10<<4)
+#define FSM_RX_RESP_DATA (11<<4)
+#define FSM_RX_RESP_CRC7 (12<<4)
+#define FSM_RX_RESP_ENDBIT (13<<4)
+#define FSM_CMD_PATHWAITNCC (14<<4)
+#define FSM_WAIT (15<<4)
+
+/*****************************************************
+ * FIFO Threshold Watermark Register
+ * MSHCI_FIFOTH - offset 0x4C
+ *****************************************************/
+#define TX_WMARK (0xFFF<<0)
+#define RX_WMARK (0xFFF<<16)
+#define MSIZE_MASK (0x7<<28)
+
+/* DW DMA Mutiple Transaction Size */
+#define MSIZE_1 (0<<28)
+#define MSIZE_4 (1<<28)
+#define MSIZE_8 (2<<28)
+#define MSIZE_16 (3<<28)
+#define MSIZE_32 (4<<28)
+#define MSIZE_64 (5<<28)
+#define MSIZE_128 (6<<28)
+#define MSIZE_256 (7<<28)
+
+/*****************************************************
+ * FIFO Threshold Watermark Register
+ * MSHCI_FIFOTH - offset 0x4C
+ *****************************************************/
+#define GPI (0xFF<<0)
+#define GPO (0xFFFF<<8)
+
+
+/*****************************************************
+ * Card Detect Register
+ * MSHCI_CDETECT - offset 0x50
+ * It assumes there is only one SD slot
+ *****************************************************/
+#define CARD_PRESENT (0x1<<0)
+
+/*****************************************************
+ * Write Protect Register
+ * MSHCI_WRTPRT - offset 0x54
+ * It assumes there is only one SD slot
+ *****************************************************/
+#define WRTPRT_ON (0x1<<0)
+
+/*****************************************************
+ * Bus Mode Register
+ * MSHCI_BMOD - offset 0x80
+ *****************************************************/
+#define BMOD_IDMAC_RESET (0x1<<0)
+#define BMOD_IDMAC_FB (0x1<<1)
+#define BMOD_IDMAC_ENABLE (0x1<<7)
+
+/*****************************************************
+ * Hardware Configuration Register
+ * MSHCI_HCON - offset 0x70
+ *****************************************************/
+#define CARD_TYPE (0x1<<0)
+#define NUM_CARDS (0x1F<<1)
+#define H_BUS_TYPE (0x1<<6)
+#define H_DATA_WIDTH (0x7<<7)
+#define H_ADDR_WIDTH (0x3F<<10)
+#define DMA_INTERFACE (0x3<<16)
+#define GE_DMA_DATA_WIDTH (0x7<<18)
+#define FIFO_RAM_INSIDE (0x1<<21)
+#define UMPLEMENT_HOLD_REG (0x1<<22)
+#define SET_CLK_FALSE_PATH (0x1<<23)
+#define NUM_CLK_DIVIDER (0x3<<24)
+
+/*****************************************************
+ * Hardware Configuration Register
+ * MSHCI_IDSTS - offset 0x8c
+ *****************************************************/
+#define IDSTS_FSM (0xf<<13)
+#define IDSTS_EB (0x7<<10)
+#define IDSTS_AIS (0x1<<9)
+#define IDSTS_NIS (0x1<<8)
+#define IDSTS_CES (0x1<<5)
+#define IDSTS_DU (0x1<<4)
+#define IDSTS_FBE (0x1<<2)
+#define IDSTS_RI (0x1<<1)
+#define IDSTS_TI (0x1<<0)
+
+struct mshci_ops;
+
+struct mshci_idmac {
+ u32 des0;
+ u32 des1;
+ u32 des2;
+ u32 des3;
+#define MSHCI_IDMAC_OWN (1<<31)
+#define MSHCI_IDMAC_ER (1<<5)
+#define MSHCI_IDMAC_CH (1<<4)
+#define MSHCI_IDMAC_FS (1<<3)
+#define MSHCI_IDMAC_LD (1<<2)
+#define MSHCI_IDMAC_DIC (1<<1)
+#define INTMSK_IDMAC_ALL (0x337)
+#define INTMSK_IDMAC_ERROR (0x214)
+};
+
+struct mshci_host {
+ /* Data set by hardware interface driver */
+ const char *hw_name; /* Hardware bus name */
+
+ unsigned int quirks; /* Deviations from spec. */
+/* Controller has no write-protect pin connected with SD card */
+#define MSHCI_QUIRK_NO_WP_BIT (1<<0)
+#define MSHCI_QUIRK_BROKEN_CARD_DETECTION (1<<1)
+#define MSHCI_QUIRK_BROKEN_PRESENT_BIT (1<<2)
+
+ int irq; /* Device IRQ */
+ void __iomem *ioaddr; /* Mapped address */
+
+ const struct mshci_ops *ops; /* Low level hw interface */
+
+ /* Internal data */
+ struct mmc_host *mmc; /* MMC structure */
+ u64 dma_mask; /* custom DMA mask */
+
+ spinlock_t lock; /* Mutex */
+
+ int flags; /* Host attributes */
+#define MSHCI_USE_IDMA (1<<1) /* Host is ADMA capable */
+#define MSHCI_REQ_USE_DMA (1<<2) /* Use DMA for this req. */
+#define MSHCI_DEVICE_DEAD (1<<3) /* Device unresponsive */
+
+ unsigned int version; /* SDHCI spec. version */
+
+ unsigned int max_clk; /* Max possible freq (MHz) */
+ unsigned int timeout_clk; /* Timeout freq (KHz) */
+
+ unsigned int clock; /* Current clock (MHz) */
+ unsigned int clock_to_restore; /* Saved clock for dynamic clock gating (MHz) */
+ u8 pwr; /* Current voltage */
+
+ struct mmc_request *mrq; /* Current request */
+ struct mmc_command *cmd; /* Current command */
+ struct mmc_data *data; /* Current data request */
+ unsigned int data_early:1; /* Data finished before cmd */
+
+ struct sg_mapping_iter sg_miter; /* SG state for PIO */
+ unsigned int blocks; /* remaining PIO blocks */
+
+ int sg_count; /* Mapped sg entries */
+
+ u8 *idma_desc; /* ADMA descriptor table */
+ u8 *align_buffer; /* Bounce buffer */
+
+ dma_addr_t idma_addr; /* Mapped ADMA descr. table */
+ dma_addr_t align_addr; /* Mapped bounce buffer */
+
+ struct tasklet_struct card_tasklet; /* Tasklet structures */
+ struct tasklet_struct finish_tasklet;
+
+ struct timer_list timer; /* Timer for timeouts */
+
+ u32 fifo_depth;
+ u32 fifo_threshold;
+ u32 data_transfered;
+
+ /* IP version control */
+ u32 data_addr;
+ u32 hold_bit;
+
+ u32 error_state;
+
+ unsigned long sl_flags;
+ unsigned long private[0] ____cacheline_aligned;
+};
+
+struct mshci_ops {
+ void (*set_clock)(struct mshci_host *host, unsigned int clock);
+
+ int (*enable_dma)(struct mshci_host *host);
+ unsigned int (*get_max_clock)(struct mshci_host *host);
+ unsigned int (*get_min_clock)(struct mshci_host *host);
+ unsigned int (*get_timeout_clock)(struct mshci_host *host);
+ void (*set_ios)(struct mshci_host *host,
+ struct mmc_ios *ios);
+ int (*get_ro) (struct mmc_host *mmc);
+ void (*init_issue_cmd)(struct mshci_host *host);
+ void (*init_card)(struct mshci_host *host);
+
+ int (*dma_map_sg)(struct mshci_host *host,
+ struct device *dev,
+ struct scatterlist *sg,
+ int nents, enum dma_data_direction dir,
+ int flush_type);
+ void (*dma_unmap_sg)(struct mshci_host *host,
+ struct device *dev,
+ struct scatterlist *sg,
+ int nents, enum dma_data_direction dir,
+ int flush_type);
+ int (*get_fifo_depth)(struct mshci_host *host);
+};
+
+static inline void mshci_writel(struct mshci_host *host, u32 val, int reg)
+{
+ __raw_writel(val, host->ioaddr + reg);
+}
+
+static inline u32 mshci_readl(struct mshci_host *host, int reg)
+{
+ return readl(host->ioaddr + reg);
+}
+
+extern struct mshci_host *mshci_alloc_host(struct device *dev,
+ size_t priv_size);
+extern void mshci_free_host(struct mshci_host *host);
+
+static inline void *mshci_priv(struct mshci_host *host)
+{
+ return (void *)host->private;
+}
+
+extern int mshci_add_host(struct mshci_host *host);
+extern void mshci_remove_host(struct mshci_host *host, int dead);
+
+#ifdef CONFIG_PM
+extern int mshci_suspend_host(struct mshci_host *host, pm_message_t state);
+extern int mshci_resume_host(struct mshci_host *host);
+#endif
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c
index 4a5c501..712c8c6 100644
--- a/drivers/mmc/host/sdhci-s3c.c
+++ b/drivers/mmc/host/sdhci-s3c.c
@@ -24,6 +24,7 @@
#include <plat/sdhci.h>
#include <plat/regs-sdhci.h>
+#include <plat/gpio-cfg.h>
#include "sdhci.h"
@@ -47,6 +48,7 @@ struct sdhci_s3c {
unsigned int cur_clk;
int ext_cd_irq;
int ext_cd_gpio;
+ int ext_cd_gpio_invert;
struct clk *clk_io;
struct clk *clk_bus[MAX_BUS_CLK];
@@ -212,6 +214,12 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
if (ourhost->pdata->cfg_card)
(ourhost->pdata->cfg_card)(ourhost->pdev, host->ioaddr,
&ios, NULL);
+#ifdef CONFIG_MACH_MIDAS
+ /* call cfg_gpio with 4bit data bus */
+ if (ourhost->pdata->cfg_gpio)
+ ourhost->pdata->cfg_gpio(ourhost->pdev, 4);
+
+#endif
}
}
@@ -288,6 +296,7 @@ static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock)
static int sdhci_s3c_platform_8bit_width(struct sdhci_host *host, int width)
{
u8 ctrl;
+ struct sdhci_s3c *ourhost = to_s3c(host);
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
@@ -295,14 +304,23 @@ static int sdhci_s3c_platform_8bit_width(struct sdhci_host *host, int width)
case MMC_BUS_WIDTH_8:
ctrl |= SDHCI_CTRL_8BITBUS;
ctrl &= ~SDHCI_CTRL_4BITBUS;
+ /* call cfg_gpio with 8bit data bus */
+ if (ourhost->pdata->cfg_gpio)
+ ourhost->pdata->cfg_gpio(ourhost->pdev, 8);
break;
case MMC_BUS_WIDTH_4:
ctrl |= SDHCI_CTRL_4BITBUS;
ctrl &= ~SDHCI_CTRL_8BITBUS;
+ /* call cfg_gpio with 4bit data bus */
+ if (ourhost->pdata->cfg_gpio)
+ ourhost->pdata->cfg_gpio(ourhost->pdev, 4);
break;
default:
- ctrl &= ~SDHCI_CTRL_4BITBUS;
ctrl &= ~SDHCI_CTRL_8BITBUS;
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ /* call cfg_gpio with 1bit data bus */
+ if (ourhost->pdata->cfg_gpio)
+ ourhost->pdata->cfg_gpio(ourhost->pdev, 1);
break;
}
@@ -311,11 +329,49 @@ static int sdhci_s3c_platform_8bit_width(struct sdhci_host *host, int width)
return 0;
}
+#ifdef CONFIG_MIDAS_COMMON
+/* midas board control the vdd for tflash by gpio,
+ not regulator directly.
+ so, code related vdd control should be added */
+static void sdhci_s3c_vtf_on_off(int on_off)
+{
+#ifdef CONFIG_MIDAS_COMMON
+ int gpio = GPIO_TF_EN;
+#else
+ int gpio = EXYNOS4212_GPJ0(7);
+#endif
+
+ if (on_off) {
+ gpio_set_value(gpio, 1);
+ } else {
+ gpio_set_value(gpio, 0);
+ }
+}
+
+
+static int sdhci_s3c_get_card_exist(struct sdhci_host *host)
+{
+ struct sdhci_s3c *sc;
+ int status;
+
+ sc = sdhci_priv(host);
+
+ status = gpio_get_value(sc->ext_cd_gpio);
+ if (sc->pdata->ext_cd_gpio_invert)
+ status = !status;
+
+ return status;
+}
+#endif
+
static struct sdhci_ops sdhci_s3c_ops = {
.get_max_clock = sdhci_s3c_get_max_clk,
.set_clock = sdhci_s3c_set_clock,
.get_min_clock = sdhci_s3c_get_min_clock,
.platform_8bit_width = sdhci_s3c_platform_8bit_width,
+#ifdef CONFIG_MIDAS_COMMON
+ .set_power = sdhci_s3c_vtf_on_off,
+#endif
};
static void sdhci_s3c_notify_change(struct platform_device *dev, int state)
@@ -327,11 +383,13 @@ static void sdhci_s3c_notify_change(struct platform_device *dev, int state)
spin_lock_irqsave(&host->lock, flags);
if (state) {
dev_dbg(&dev->dev, "card inserted.\n");
- host->flags &= ~SDHCI_DEVICE_DEAD;
+ pr_info("%s: card inserted.\n",
+ mmc_hostname(host->mmc));
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
} else {
dev_dbg(&dev->dev, "card removed.\n");
- host->flags |= SDHCI_DEVICE_DEAD;
+ pr_info("%s: card removed.\n",
+ mmc_hostname(host->mmc));
host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
}
tasklet_schedule(&host->card_tasklet);
@@ -345,6 +403,17 @@ static irqreturn_t sdhci_s3c_gpio_card_detect_thread(int irq, void *dev_id)
int status = gpio_get_value(sc->ext_cd_gpio);
if (sc->pdata->ext_cd_gpio_invert)
status = !status;
+
+ if (sc->host->mmc) {
+ if (status)
+ mmc_host_sd_set_present(sc->host->mmc);
+ else
+ mmc_host_sd_clear_present(sc->host->mmc);
+
+ pr_debug("SDcard present state=%d.\n",
+ mmc_host_sd_present(sc->host->mmc));
+ }
+
sdhci_s3c_notify_change(sc->pdev, status);
return IRQ_HANDLED;
}
@@ -354,8 +423,7 @@ static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc)
struct s3c_sdhci_platdata *pdata = sc->pdata;
struct device *dev = &sc->pdev->dev;
- if (gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD") == 0) {
- sc->ext_cd_gpio = pdata->ext_cd_gpio;
+ if (sc->ext_cd_gpio > 0) {
sc->ext_cd_irq = gpio_to_irq(pdata->ext_cd_gpio);
if (sc->ext_cd_irq &&
request_threaded_irq(sc->ext_cd_irq, NULL,
@@ -365,16 +433,56 @@ static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc)
int status = gpio_get_value(sc->ext_cd_gpio);
if (pdata->ext_cd_gpio_invert)
status = !status;
+
+ if (status)
+ mmc_host_sd_set_present(sc->host->mmc);
+ else
+ mmc_host_sd_clear_present(sc->host->mmc);
+
+ /* T-Flash EINT for CD SHOULD be wakeup source */
+ irq_set_irq_wake(sc->ext_cd_irq, 1);
+
sdhci_s3c_notify_change(sc->pdev, status);
} else {
dev_warn(dev, "cannot request irq for card detect\n");
sc->ext_cd_irq = 0;
}
+ }
+}
+
+extern struct class *sec_class;
+static struct device *sd_detection_cmd_dev;
+
+static ssize_t sd_detection_cmd_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdhci_s3c *sc = dev_get_drvdata(dev);
+ unsigned int detect;
+
+ if (sc && sc->ext_cd_gpio)
+ detect = gpio_get_value(sc->ext_cd_gpio);
+ else {
+ pr_info("%s : External SD detect pin Error\n", __func__);
+ return sprintf(buf, "Error\n");
+ }
+
+ if (sc->pdata->ext_cd_gpio_invert) {
+ pr_info("%s : Invert External SD detect pin\n", __func__);
+ detect = !detect;
+ }
+
+ pr_info("%s : detect = %d.\n", __func__, detect);
+ if (detect) {
+ pr_debug("sdhci: card inserted.\n");
+ return sprintf(buf, "Insert\n");
} else {
- dev_err(dev, "cannot request gpio for card detect\n");
+ pr_debug("sdhci: card removed.\n");
+ return sprintf(buf, "Remove\n");
}
}
+static DEVICE_ATTR(status, 0444, sd_detection_cmd_show, NULL);
+
static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
{
struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
@@ -472,7 +580,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
if (!host->ioaddr) {
dev_err(dev, "failed to map registers\n");
ret = -ENXIO;
- goto err_req_regs;
+ goto err_add_host;
}
/* Ensure we have minimal gpio selected CMD/CLK/Detect */
@@ -501,11 +609,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
* SDHCI block, or a missing configuration that needs to be set. */
host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;
- /* This host supports the Auto CMD12 */
- host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
-
- if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
- pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
+ if (pdata->cd_type == S3C_SDHCI_CD_NONE)
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
@@ -514,6 +618,10 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
+ /* if vmmc_name is in pdata */
+ if (pdata->vmmc_name)
+ host->vmmc_name = pdata->vmmc_name;
+
host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
SDHCI_QUIRK_32BIT_DMA_SIZE);
@@ -534,17 +642,70 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
+ /* for BCM WIFI */
+ if (pdata->pm_flags)
+ host->mmc->pm_flags |= pdata->pm_flags;
+
+ /* To turn on vmmc regulator only if sd card exists,
+ GPIO pin for card detection should be initialized.
+ Moved from sdhci_s3c_setup_card_detect_gpio() function */
+ if (pdata->cd_type == S3C_SDHCI_CD_GPIO &&
+ gpio_is_valid(pdata->ext_cd_gpio)) {
+ if (gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD") == 0) {
+ sc->ext_cd_gpio = pdata->ext_cd_gpio;
+ sc->ext_cd_gpio_invert = pdata->ext_cd_gpio_invert;
+
+ mmc_host_sd_set_present(host->mmc);
+ if (sd_detection_cmd_dev == NULL &&
+ sc->ext_cd_gpio) {
+ sd_detection_cmd_dev =
+ device_create(sec_class, NULL, 0,
+ NULL, "sdcard");
+ if (IS_ERR(sd_detection_cmd_dev))
+ pr_err("Fail to create sysfs dev\n");
+
+ if (device_create_file(sd_detection_cmd_dev,
+ &dev_attr_status) < 0)
+ pr_err("Fail to create sysfs file\n");
+
+ dev_set_drvdata(sd_detection_cmd_dev, sc);
+ }
+#ifdef CONFIG_MIDAS_COMMON
+ /* set TF_EN gpio as OUTPUT */
+ gpio_request(GPIO_TF_EN, "TF_EN");
+ gpio_direction_output(GPIO_TF_EN, 1);
+ s3c_gpio_cfgpin(GPIO_TF_EN, S3C_GPIO_SFN(1));
+ s3c_gpio_setpull(GPIO_TF_EN, S3C_GPIO_PULL_NONE);
+#endif
+ } else {
+ dev_err(dev, "cannot request gpio for card detect\n");
+ }
+ }
+
ret = sdhci_add_host(host);
if (ret) {
dev_err(dev, "sdhci_add_host() failed\n");
goto err_add_host;
}
+ /* if it is set SDHCI_QUIRK_BROKEN_CARD_DETECTION before calling
+ sdhci_add_host, in sdhci_add_host, MMC_CAP_NEEDS_POLL flag will
+ be set. The flag S3C_SDHCI_CD_PERMANENT dose not need to
+ detect a card by polling. */
+ if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT || \
+ pdata->cd_type == S3C_SDHCI_CD_GPIO)
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+
/* The following two methods of card detection might call
sdhci_s3c_notify_change() immediately, so they can be called
only after sdhci_add_host(). Setup errors are ignored. */
- if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init)
+ if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init) {
pdata->ext_cd_init(&sdhci_s3c_notify_change);
+#ifdef CONFIG_MACH_PX
+ if (pdata->ext_pdev)
+ pdata->ext_pdev(pdev);
+#endif
+ }
if (pdata->cd_type == S3C_SDHCI_CD_GPIO &&
gpio_is_valid(pdata->ext_cd_gpio))
sdhci_s3c_setup_card_detect_gpio(sc);
@@ -552,8 +713,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
return 0;
err_add_host:
- release_resource(sc->ioarea);
- kfree(sc->ioarea);
+ if (host->ioaddr)
+ iounmap(host->ioaddr);
+ release_mem_region(sc->ioarea->start, resource_size(sc->ioarea));
err_req_regs:
for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
@@ -613,17 +775,27 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm)
{
struct sdhci_host *host = platform_get_drvdata(dev);
+ int ret = 0;
- sdhci_suspend_host(host, pm);
- return 0;
+ ret = sdhci_suspend_host(host, pm);
+
+ return ret;
+}
+
+static void sdhci_s3c_shutdown(struct platform_device *dev)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ sdhci_shutdown_host(host);
}
static int sdhci_s3c_resume(struct platform_device *dev)
{
struct sdhci_host *host = platform_get_drvdata(dev);
+ int ret = 0;
- sdhci_resume_host(host);
- return 0;
+ ret = sdhci_resume_host(host);
+ return ret;
}
#else
@@ -636,6 +808,7 @@ static struct platform_driver sdhci_s3c_driver = {
.remove = __devexit_p(sdhci_s3c_remove),
.suspend = sdhci_s3c_suspend,
.resume = sdhci_s3c_resume,
+ .shutdown = sdhci_s3c_shutdown,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-sdhci",
@@ -652,7 +825,11 @@ static void __exit sdhci_s3c_exit(void)
platform_driver_unregister(&sdhci_s3c_driver);
}
+#ifdef CONFIG_FAST_RESUME
+beforeresume_initcall(sdhci_s3c_init);
+#else
module_init(sdhci_s3c_init);
+#endif
module_exit(sdhci_s3c_exit);
MODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue");
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 8bcd5e9..bf86690 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -25,18 +25,23 @@
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
#include "sdhci.h"
+#include <linux/gpio.h>
+
#define DRIVER_NAME "sdhci"
#define DBG(f, x...) \
- pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x)
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__, ## x)
+#ifndef CONFIG_FAST_RESUME
#if defined(CONFIG_LEDS_CLASS) || (defined(CONFIG_LEDS_CLASS_MODULE) && \
defined(CONFIG_MMC_SDHCI_MODULE))
#define SDHCI_USE_LEDS_CLASS
#endif
+#endif
#define MAX_TUNING_LOOP 40
@@ -46,9 +51,25 @@ static void sdhci_finish_data(struct sdhci_host *);
static void sdhci_send_command(struct sdhci_host *, struct mmc_command *);
static void sdhci_finish_command(struct sdhci_host *);
-static int sdhci_execute_tuning(struct mmc_host *mmc);
+static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
static void sdhci_tuning_timer(unsigned long data);
+#define MAX_BUS_CLK (4)
+
+struct sdhci_s3c {
+ struct sdhci_host *host;
+ struct platform_device *pdev;
+ struct resource *ioarea;
+ struct s3c_sdhci_platdata *pdata;
+ unsigned int cur_clk;
+ int ext_cd_irq;
+ int ext_cd_gpio;
+ int ext_cd_gpio_invert;
+
+ struct clk *clk_io;
+ struct clk *clk_bus[MAX_BUS_CLK];
+};
+
static void sdhci_dumpregs(struct sdhci_host *host)
{
printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
@@ -624,9 +645,14 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
/* timeout in us */
if (!data)
target_timeout = cmd->cmd_timeout_ms * 1000;
- else
- target_timeout = data->timeout_ns / 1000 +
- data->timeout_clks / host->clock;
+ else {
+ /* patch added for divide by zero once issue. */
+ if (host && host->clock)
+ target_timeout = data->timeout_ns / 1000 +
+ data->timeout_clks / host->clock;
+ else
+ return 0;
+ }
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
host->timeout_clk = host->clock / 1000;
@@ -651,6 +677,17 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
break;
}
+ /* card's type is SD, set timeout */
+ if (host->mmc->card && mmc_card_sd(host->mmc->card)) {
+ count += 2;
+ /*
+ * It's to prevent warning error log,
+ * If count value is more than 0xD before add 2.
+ */
+ if (count >= 0xF)
+ count = 0xE;
+ }
+
if (count >= 0xF) {
printk(KERN_WARNING "%s: Too large timeout requested for CMD%d!\n",
mmc_hostname(host->mmc), cmd->opcode);
@@ -1006,7 +1043,7 @@ static void sdhci_finish_command(struct sdhci_host *host)
if (host->cmd->flags & MMC_RSP_PRESENT) {
if (host->cmd->flags & MMC_RSP_136) {
/* CRC is stripped so we need to do some shifting. */
- for (i = 0;i < 4;i++) {
+ for (i = 0 ; i < 4 ; i++) {
host->cmd->resp[i] = sdhci_readl(host,
SDHCI_RESPONSE + (3-i)*4) << 8;
if (i != 3)
@@ -1044,7 +1081,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
u16 clk = 0;
unsigned long timeout;
- if (clock == host->clock)
+ if (clock && clock == host->clock)
return;
if (host->ops->set_clock) {
@@ -1250,13 +1287,12 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
if ((host->flags & SDHCI_NEEDS_RETUNING) &&
!(present_state & (SDHCI_DOING_WRITE | SDHCI_DOING_READ))) {
spin_unlock_irqrestore(&host->lock, flags);
- sdhci_execute_tuning(mmc);
+ sdhci_execute_tuning(mmc, mrq->cmd->opcode);
spin_lock_irqsave(&host->lock, flags);
/* Restore original mmc_request structure */
host->mrq = mrq;
}
-
if (mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23))
sdhci_send_command(host, mrq->sbc);
else
@@ -1275,10 +1311,41 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
host = mmc_priv(mmc);
+ if ((!mmc_host_sd_present(mmc) ||
+ (mmc_host_sd_present(mmc) &&
+ !mmc_host_sd_init_stat(mmc) &&
+ mmc_host_sd_prev_stat(mmc))) &&
+ ios->power_mode == MMC_POWER_OFF) {
+ mmc_host_sd_clear_prev_stat(mmc);
+ if (host->vmmc && regulator_is_enabled(host->vmmc)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(0);
+#endif
+ regulator_disable(host->vmmc);
+ pr_info("%s : MMC Card OFF %s\n", __func__,
+ host->hw_name);
+ }
+ } else if (mmc_host_sd_present(mmc) &&
+ !mmc_host_sd_prev_stat(mmc)) {
+ mmc_host_sd_set_prev_stat(mmc);
+ if (host->vmmc && !regulator_is_enabled(host->vmmc)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(1);
+#endif
+ regulator_enable(host->vmmc);
+ pr_info("%s : MMC Card ON %s\n", __func__,
+ host->hw_name);
+ }
+ }
+
spin_lock_irqsave(&host->lock, flags);
- if (host->flags & SDHCI_DEVICE_DEAD)
+ if (host->flags & SDHCI_DEVICE_DEAD) {
+ sdhci_set_clock(host, 0);
goto out;
+ }
/*
* Reset the chip on each power off.
@@ -1415,7 +1482,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
* signalling timeout and CRC errors even on CMD0. Resetting
* it on each ios seems to solve the problem.
*/
- if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
+ if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
out:
@@ -1468,6 +1535,14 @@ static int sdhci_get_ro(struct mmc_host *mmc)
return 0;
}
+static void sdhci_hw_reset(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (host->ops && host->ops->hw_reset)
+ host->ops->hw_reset(host);
+}
+
static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct sdhci_host *host;
@@ -1592,7 +1667,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
return 0;
}
-static int sdhci_execute_tuning(struct mmc_host *mmc)
+static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
{
struct sdhci_host *host;
u16 ctrl;
@@ -1650,7 +1725,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc)
if (!tuning_loop_counter && !timeout)
break;
- cmd.opcode = MMC_SEND_TUNING_BLOCK;
+ cmd.opcode = opcode;
cmd.arg = 0;
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
cmd.retries = 0;
@@ -1802,6 +1877,7 @@ static const struct mmc_host_ops sdhci_ops = {
.request = sdhci_request,
.set_ios = sdhci_set_ios,
.get_ro = sdhci_get_ro,
+ .hw_reset = sdhci_hw_reset,
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
.execute_tuning = sdhci_execute_tuning,
@@ -1819,16 +1895,16 @@ static void sdhci_tasklet_card(unsigned long param)
struct sdhci_host *host;
unsigned long flags;
- host = (struct sdhci_host*)param;
+ host = (struct sdhci_host *)param;
spin_lock_irqsave(&host->lock, flags);
if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
if (host->mrq) {
printk(KERN_ERR "%s: Card removed during transfer!\n",
- mmc_hostname(host->mmc));
+ mmc_hostname(host->mmc));
printk(KERN_ERR "%s: Resetting controller.\n",
- mmc_hostname(host->mmc));
+ mmc_hostname(host->mmc));
sdhci_reset(host, SDHCI_RESET_CMD);
sdhci_reset(host, SDHCI_RESET_DATA);
@@ -1840,7 +1916,11 @@ static void sdhci_tasklet_card(unsigned long param)
spin_unlock_irqrestore(&host->lock, flags);
- mmc_detect_change(host->mmc, msecs_to_jiffies(200));
+ if (host->vmmc &&
+ !(host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION))
+ mmc_detect_change(host->mmc, msecs_to_jiffies(0));
+ else
+ mmc_detect_change(host->mmc, msecs_to_jiffies(200));
}
static void sdhci_tasklet_finish(unsigned long param)
@@ -1849,12 +1929,12 @@ static void sdhci_tasklet_finish(unsigned long param)
unsigned long flags;
struct mmc_request *mrq;
- host = (struct sdhci_host*)param;
+ host = (struct sdhci_host *)param;
- /*
- * If this tasklet gets rescheduled while running, it will
- * be run again afterwards but without any active request.
- */
+ /*
+ * If this tasklet gets rescheduled while running, it will
+ * be run again afterwards but without any active request.
+ */
if (!host->mrq)
return;
@@ -1888,6 +1968,10 @@ static void sdhci_tasklet_finish(unsigned long param)
controllers do not like that. */
sdhci_reset(host, SDHCI_RESET_CMD);
sdhci_reset(host, SDHCI_RESET_DATA);
+#ifdef CONFIG_MACH_PX
+ printk(KERN_DEBUG "%s: Controller is resetted!\n",
+ mmc_hostname(host->mmc));
+#endif
}
host->mrq = NULL;
@@ -1909,7 +1993,7 @@ static void sdhci_timeout_timer(unsigned long data)
struct sdhci_host *host;
unsigned long flags;
- host = (struct sdhci_host*)data;
+ host = (struct sdhci_host *)data;
spin_lock_irqsave(&host->lock, flags);
@@ -1967,14 +2051,27 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)
return;
}
- if (intmask & SDHCI_INT_TIMEOUT)
+ if (intmask & SDHCI_INT_TIMEOUT) {
+ printk(KERN_INFO "%s: cmd %d command timeout error\n",
+ mmc_hostname(host->mmc), host->cmd->opcode);
host->cmd->error = -ETIMEDOUT;
- else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT |
- SDHCI_INT_INDEX))
+ } else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT |
+ SDHCI_INT_INDEX)) {
+ printk(KERN_ERR "%s: cmd %d %s error\n",
+ mmc_hostname(host->mmc), host->cmd->opcode,
+ (intmask & SDHCI_INT_CRC) ? "command crc" :
+ (intmask & SDHCI_INT_END_BIT) ? "command end bit" :
+ "command index error");
host->cmd->error = -EILSEQ;
+ }
+
if (host->cmd->error) {
tasklet_schedule(&host->finish_tasklet);
+#ifdef CONFIG_MACH_PX
+ printk(KERN_DEBUG "%s: finish tasklet schedule\n",
+ mmc_hostname(host->mmc));
+#endif
return;
}
@@ -2068,15 +2165,17 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
return;
}
- if (intmask & SDHCI_INT_DATA_TIMEOUT)
- host->data->error = -ETIMEDOUT;
- else if (intmask & SDHCI_INT_DATA_END_BIT)
- host->data->error = -EILSEQ;
- else if ((intmask & SDHCI_INT_DATA_CRC) &&
- SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
- != MMC_BUS_TEST_R)
+ if (intmask & SDHCI_INT_DATA_TIMEOUT) {
+ printk(KERN_ERR "%s: cmd %d data timeout error\n",
+ mmc_hostname(host->mmc), host->mrq->cmd->opcode);
+ host->data->error = -ETIMEDOUT;
+ } else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT)) {
+ printk(KERN_ERR "%s: cmd %d %s error\n",
+ mmc_hostname(host->mmc), host->mrq->cmd->opcode,
+ (intmask & SDHCI_INT_DATA_CRC) ? "data crc" :
+ "command end bit");
host->data->error = -EILSEQ;
- else if (intmask & SDHCI_INT_ADMA_ERROR) {
+ } else if (intmask & SDHCI_INT_ADMA_ERROR) {
printk(KERN_ERR "%s: ADMA error\n", mmc_hostname(host->mmc));
sdhci_show_adma_error(host);
host->data->error = -EIO;
@@ -2133,7 +2232,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
static irqreturn_t sdhci_irq(int irq, void *dev_id)
{
irqreturn_t result;
- struct sdhci_host* host = dev_id;
+ struct sdhci_host *host = dev_id;
u32 intmask;
int cardint = 0;
@@ -2230,28 +2329,68 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
host->flags &= ~SDHCI_NEEDS_RETUNING;
}
+ if (host->mmc->pm_flags & MMC_PM_IGNORE_SUSPEND_RESUME) {
+ host->mmc->pm_flags |= MMC_PM_KEEP_POWER;
+ pr_info("%s : Enter WIFI suspend\n", __func__);
+ }
+
ret = mmc_suspend_host(host->mmc);
if (ret)
return ret;
free_irq(host->irq, host);
- if (host->vmmc)
- ret = regulator_disable(host->vmmc);
+ if (host->vmmc) {
+ if (regulator_is_enabled(host->vmmc)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(0);
+#endif
+ ret = regulator_disable(host->vmmc);
+ pr_info("%s : MMC Card OFF\n", __func__);
+ }
+ }
return ret;
}
EXPORT_SYMBOL_GPL(sdhci_suspend_host);
+void sdhci_shutdown_host(struct sdhci_host *host)
+{
+ sdhci_disable_card_detection(host);
+
+ free_irq(host->irq, host);
+
+ if (host->vmmc) {
+ if (regulator_is_enabled(host->vmmc)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(0);
+#endif
+ regulator_disable(host->vmmc);
+ pr_info("%s : MMC Card OFF\n", __func__);
+#if defined(CONFIG_TARGET_LOCALE_KOR)
+ mdelay(5);
+#endif
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(sdhci_shutdown_host);
+
int sdhci_resume_host(struct sdhci_host *host)
{
int ret;
- if (host->vmmc) {
- int ret = regulator_enable(host->vmmc);
+ if (host->vmmc && !regulator_is_enabled(host->vmmc)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(1);
+#endif
+ ret = regulator_enable(host->vmmc);
if (ret)
return ret;
+ pr_info("%s : MMC Card ON\n", __func__);
}
@@ -2276,6 +2415,13 @@ int sdhci_resume_host(struct sdhci_host *host)
(host->tuning_mode == SDHCI_TUNING_MODE_1))
host->flags |= SDHCI_NEEDS_RETUNING;
+#ifdef CONFIG_MACH_PX
+ /* host has a card and the card is SDIO type */
+ if (host->mmc->card && mmc_card_sdio(host->mmc->card)) {
+ /* enable sdio interrupt */
+ sdhci_enable_sdio_irq(host->mmc, 1);
+ }
+#endif
return ret;
}
@@ -2326,6 +2472,7 @@ int sdhci_add_host(struct sdhci_host *host)
u32 max_current_caps;
unsigned int ocr_avail;
int ret;
+ struct sdhci_s3c *sc;
WARN_ON(host == NULL);
if (host == NULL)
@@ -2483,7 +2630,12 @@ int sdhci_add_host(struct sdhci_host *host)
} else
mmc->f_min = host->max_clk / SDHCI_MAX_DIV_SPEC_200;
- mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23;
+ if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
+ mmc->max_discard_to = (1 << 27) / (mmc->f_max / 1000);
+ else
+ mmc->max_discard_to = (1 << 27) / host->timeout_clk;
+
+ mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE;
if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12)
host->flags |= SDHCI_AUTO_CMD12;
@@ -2541,6 +2693,18 @@ int sdhci_add_host(struct sdhci_host *host)
if (caps[1] & SDHCI_DRIVER_TYPE_D)
mmc->caps |= MMC_CAP_DRIVER_TYPE_D;
+ if (mmc->pm_flags & MMC_PM_IGNORE_SUSPEND_RESUME)
+ mmc->pm_caps |= MMC_PM_KEEP_POWER;
+
+ /*
+ * If Power Off Notify capability is enabled by the host,
+ * set notify to short power off notify timeout value.
+ */
+ if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY)
+ mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT;
+ else
+ mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE;
+
/* Initial value for re-tuning timer count */
host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >>
SDHCI_RETUNING_TIMER_COUNT_SHIFT;
@@ -2715,12 +2879,32 @@ int sdhci_add_host(struct sdhci_host *host)
if (ret)
goto untasklet;
- host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
+ sc = sdhci_priv(host);
+
+ if (host->vmmc_name)
+ host->vmmc = regulator_get(mmc_dev(mmc), host->vmmc_name);
+ else
+ host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
+
if (IS_ERR(host->vmmc)) {
- printk(KERN_INFO "%s: no vmmc regulator found\n", mmc_hostname(mmc));
+ printk(KERN_ERR "%s: no %s regulator found\n",
+ mmc_hostname(mmc),
+ host->vmmc_name ? host->vmmc_name : "vmmc");
host->vmmc = NULL;
} else {
- regulator_enable(host->vmmc);
+ printk(KERN_INFO "%s: %s regulator found\n",
+ mmc_hostname(mmc),
+ host->vmmc_name ? host->vmmc_name : "vmmc");
+ if (sc->ext_cd_gpio) {
+ if (gpio_get_value(sc->ext_cd_gpio) != (sc->ext_cd_gpio_invert)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(1);
+#endif
+ regulator_enable(host->vmmc);
+ mdelay(100);
+ }
+ }
}
sdhci_init(host, 0);
@@ -2809,7 +2993,11 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->finish_tasklet);
- if (host->vmmc) {
+ if (host->vmmc && regulator_is_enabled(host->vmmc)) {
+#ifdef CONFIG_MIDAS_COMMON
+ if (host->ops->set_power)
+ host->ops->set_power(0);
+#endif
regulator_disable(host->vmmc);
regulator_put(host->vmmc);
}
@@ -2838,11 +3026,12 @@ EXPORT_SYMBOL_GPL(sdhci_free_host);
static int __init sdhci_drv_init(void)
{
+ int ret = 0;
printk(KERN_INFO DRIVER_NAME
": Secure Digital Host Controller Interface driver\n");
printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
- return 0;
+ return ret;
}
static void __exit sdhci_drv_exit(void)
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 745c42f..b5f48f8 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -273,7 +273,8 @@ struct sdhci_ops {
void (*platform_reset_enter)(struct sdhci_host *host, u8 mask);
void (*platform_reset_exit)(struct sdhci_host *host, u8 mask);
int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);
-
+ void (*hw_reset)(struct sdhci_host *host);
+ void (*set_power)(int on_off);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
@@ -374,6 +375,7 @@ extern int sdhci_add_host(struct sdhci_host *host);
extern void sdhci_remove_host(struct sdhci_host *host, int dead);
#ifdef CONFIG_PM
+extern void sdhci_shutdown_host(struct sdhci_host *host);
extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);
extern int sdhci_resume_host(struct sdhci_host *host);
extern void sdhci_enable_irq_wakeups(struct sdhci_host *host);