diff options
Diffstat (limited to 'drivers/mmc/host')
-rw-r--r-- | drivers/mmc/host/Kconfig | 41 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 5 | ||||
-rw-r--r-- | drivers/mmc/host/dw_mmc.c | 803 | ||||
-rw-r--r-- | drivers/mmc/host/dw_mmc.h | 20 | ||||
-rw-r--r-- | drivers/mmc/host/mshci-s3c-dma.c | 220 | ||||
-rw-r--r-- | drivers/mmc/host/mshci-s3c.c | 644 | ||||
-rw-r--r-- | drivers/mmc/host/mshci.c | 2245 | ||||
-rw-r--r-- | drivers/mmc/host/mshci.h | 463 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-s3c.c | 215 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 279 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 4 |
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); |