aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mmc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/Kconfig2
-rwxr-xr-x[-rw-r--r--]drivers/mmc/host/sdhci-s3c.c107
-rw-r--r--drivers/mmc/host/sdhci.c120
-rw-r--r--drivers/mmc/host/sdhci.h3
4 files changed, 207 insertions, 25 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 56dbf3f..0ff3b56 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -204,7 +204,7 @@ config MMC_SDHCI_SPEAR
config MMC_SDHCI_S3C_DMA
bool "DMA support on S3C SDHCI"
- depends on MMC_SDHCI_S3C && EXPERIMENTAL
+ depends on MMC_SDHCI_S3C
help
Enable DMA support on the Samsung S3C SDHCI glue. The DMA
has proved to be problematic if the controller encounters
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c
index 8cd999f..fb3eba6 100644..100755
--- a/drivers/mmc/host/sdhci-s3c.c
+++ b/drivers/mmc/host/sdhci-s3c.c
@@ -21,6 +21,7 @@
#include <linux/gpio.h>
#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
#include <plat/sdhci.h>
#include <plat/regs-sdhci.h>
@@ -45,6 +46,7 @@ struct sdhci_s3c {
struct resource *ioarea;
struct s3c_sdhci_platdata *pdata;
unsigned int cur_clk;
+ bool cur_clk_set;
int ext_cd_irq;
int ext_cd_gpio;
@@ -79,7 +81,7 @@ static void sdhci_s3c_check_sclk(struct sdhci_host *host)
tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
- writel(tmp, host->ioaddr + 0x80);
+ writel(tmp, host->ioaddr + S3C_SDHCI_CONTROL2);
}
}
@@ -113,6 +115,46 @@ static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host)
return max;
}
+static void sdhci_s3c_set_ios(struct sdhci_host *host,
+ struct mmc_ios *ios)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ struct s3c_sdhci_platdata *pdata = ourhost->pdata;
+ int width;
+ u8 tmp;
+
+ sdhci_s3c_check_sclk(host);
+
+ if (ios->power_mode != MMC_POWER_OFF) {
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_8:
+ width = 8;
+ tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL);
+ writeb(tmp | SDHCI_S3C_CTRL_8BITBUS,
+ host->ioaddr + SDHCI_HOST_CONTROL);
+ 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);
+ pdata->rx_cfg = 0;
+ pdata->tx_cfg = 0;
+ }
+}
+
/**
* sdhci_s3c_consider_clock - consider one the bus clocks for current setting
* @ourhost: Our SDHCI instance.
@@ -187,13 +229,14 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
/* select the new clock source */
- if (ourhost->cur_clk != best_src) {
+ if (ourhost->cur_clk != best_src || !ourhost->cur_clk_set) {
struct clk *clk = ourhost->clk_bus[best_src];
/* turn clock off to card before changing clock source */
writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
ourhost->cur_clk = best_src;
+ ourhost->cur_clk_set = true;
host->max_clk = clk_get_rate(clk);
ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
@@ -201,18 +244,15 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
}
+}
- /* reconfigure the hardware for new clock rate */
-
- {
- struct mmc_ios ios;
-
- ios.clock = clock;
+static void sdhci_s3c_adjust_cfg(struct sdhci_host *host, int rw)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ struct s3c_sdhci_platdata *pdata = ourhost->pdata;
- if (ourhost->pdata->cfg_card)
- (ourhost->pdata->cfg_card)(ourhost->pdev, host->ioaddr,
- &ios, NULL);
- }
+ if(pdata->adjust_cfg_card)
+ pdata->adjust_cfg_card(pdata, host->ioaddr, rw);
}
/**
@@ -316,6 +356,8 @@ static struct sdhci_ops sdhci_s3c_ops = {
.set_clock = sdhci_s3c_set_clock,
.get_min_clock = sdhci_s3c_get_min_clock,
.platform_8bit_width = sdhci_s3c_platform_8bit_width,
+ .set_ios = sdhci_s3c_set_ios,
+ .adjust_cfg = sdhci_s3c_adjust_cfg,
};
static void sdhci_s3c_notify_change(struct platform_device *dev, int state)
@@ -487,6 +529,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
/* Setup quirks for the controller */
host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
+ host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
+ if (pdata->must_maintain_clock)
+ host->quirks |= SDHCI_QUIRK_MUST_MAINTAIN_CLOCK;
#ifndef CONFIG_MMC_SDHCI_S3C_DMA
@@ -494,6 +539,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
* support as well. */
host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
+ /* PIO currently has problems with multi-block IO */
+ host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK;
+
#endif /* CONFIG_MMC_SDHCI_S3C_DMA */
/* It seems we do not get an DATA transfer complete on non-busy
@@ -534,6 +582,11 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
+ /* Set pm_flags for built_in device */
+ host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;
+ if (pdata->built_in)
+ host->mmc->pm_flags = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;
+
ret = sdhci_add_host(host);
if (ret) {
dev_err(dev, "sdhci_add_host() failed\n");
@@ -557,8 +610,10 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
err_req_regs:
for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
- clk_disable(sc->clk_bus[ptr]);
- clk_put(sc->clk_bus[ptr]);
+ if (sc->clk_bus[ptr]) {
+ clk_disable(sc->clk_bus[ptr]);
+ clk_put(sc->clk_bus[ptr]);
+ }
}
err_no_busclks:
@@ -576,11 +631,16 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_s3c *sc = sdhci_priv(host);
- int ptr;
+ int ptr, dead = 0;
+ u32 scratch;
if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup)
pdata->ext_cd_cleanup(&sdhci_s3c_notify_change);
+ scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
+ if (scratch == (u32)-1)
+ dead = 1;
+
if (sc->ext_cd_irq)
free_irq(sc->ext_cd_irq, sc);
@@ -589,7 +649,7 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
sdhci_remove_host(host, 1);
- for (ptr = 0; ptr < 3; ptr++) {
+ for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
if (sc->clk_bus[ptr]) {
clk_disable(sc->clk_bus[ptr]);
clk_put(sc->clk_bus[ptr]);
@@ -614,6 +674,11 @@ static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm)
{
struct sdhci_host *host = platform_get_drvdata(dev);
+ struct mmc_host *mmc = host->mmc;
+
+ if (mmc->card && (mmc->card->type == MMC_TYPE_SDIO))
+ mmc->pm_flags |= MMC_PM_KEEP_POWER;
+
sdhci_suspend_host(host, pm);
return 0;
}
@@ -621,8 +686,18 @@ static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm)
static int sdhci_s3c_resume(struct platform_device *dev)
{
struct sdhci_host *host = platform_get_drvdata(dev);
+ struct s3c_sdhci_platdata *pdata = dev->dev.platform_data;
+ u32 ier;
sdhci_resume_host(host);
+
+ if (pdata->enable_intr_on_resume) {
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ ier |= SDHCI_INT_CARD_INT;
+ sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+ }
+
return 0;
}
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index d517a21..862ad52 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -25,6 +25,9 @@
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+
+#include <plat/regs-sdhci.h>
#include "sdhci.h"
@@ -104,6 +107,24 @@ static void sdhci_dumpregs(struct sdhci_host *host)
* *
\*****************************************************************************/
+static void sdhci_enable_clock_card(struct sdhci_host *host)
+{
+ u16 clk;
+
+ clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL);
+ clk |= SDHCI_CLOCK_CARD_EN;
+ writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL);
+}
+
+static void sdhci_disable_clock_card(struct sdhci_host *host)
+{
+ u16 clk;
+
+ clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL);
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL);
+}
+
static void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
{
u32 ier;
@@ -930,9 +951,14 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
int flags;
u32 mask;
unsigned long timeout;
+#if defined(CONFIG_MMC_SDHCI_S3C) || defined(CONFIG_MMC_SDHCI_MODULE)
+ int i;
+#endif
WARN_ON(host->cmd);
+ del_timer(&host->busy_check_timer);
+
/* Wait max 10 ms */
timeout = 10;
@@ -962,8 +988,12 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
host->cmd = cmd;
+ sdhci_enable_clock_card(host);
sdhci_prepare_data(host, cmd);
+ if(cmd->flags & MMC_RSP_BUSY)
+ sdhci_writeb(host, 0xE, SDHCI_TIMEOUT_CONTROL);
+
sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
sdhci_set_transfer_mode(host, cmd);
@@ -994,6 +1024,17 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK))
flags |= SDHCI_CMD_DATA;
+#if defined(CONFIG_MMC_SDHCI_S3C) || defined(CONFIG_MMC_SDHCI_MODULE)
+ mask = readl(host->ioaddr + SDHCI_INT_STATUS);
+ writel(mask & SDHCI_INT_DATA_MASK & SDHCI_INT_CMD_MASK, host->ioaddr + SDHCI_INT_STATUS);
+
+ for(i=0; i<0x1000000; i++) {
+ mask = readl(host->ioaddr + S3C64XX_SDHCI_CONTROL4);
+ if(!(mask & S3C64XX_SDHCI_CONTROL4_BUSY))
+ break;
+ }
+#endif
+
sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
}
@@ -1289,6 +1330,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
sdhci_reinit(host);
}
+ if (host->ops->set_ios)
+ host->ops->set_ios(host, ios);
+
sdhci_set_clock(host, ios->clock);
if (ios->power_mode == MMC_POWER_OFF)
@@ -1798,6 +1842,26 @@ static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
spin_unlock_irqrestore(&host->lock, flags);
}
+void sdhci_adjust_cfg(struct mmc_host *mmc, int rw)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ struct mmc_ios *ios = &mmc->ios;
+ unsigned int clock;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if(host->ops->adjust_cfg)
+ host->ops->adjust_cfg(host, rw);
+
+ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
static const struct mmc_host_ops sdhci_ops = {
.request = sdhci_request,
.set_ios = sdhci_set_ios,
@@ -1806,6 +1870,7 @@ static const struct mmc_host_ops sdhci_ops = {
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
.execute_tuning = sdhci_execute_tuning,
.enable_preset_value = sdhci_enable_preset_value,
+ .adjust_cfg = sdhci_adjust_cfg,
};
/*****************************************************************************\
@@ -1851,6 +1916,9 @@ static void sdhci_tasklet_finish(unsigned long param)
host = (struct sdhci_host*)param;
+ if(host == NULL)
+ return;
+
/*
* If this tasklet gets rescheduled while running, it will
* be run again afterwards but without any active request.
@@ -1864,6 +1932,9 @@ static void sdhci_tasklet_finish(unsigned long param)
mrq = host->mrq;
+ if(mrq == NULL || mrq->cmd == NULL)
+ goto out;
+
/*
* The controller needs a reset of internal state machines
* upon error conditions.
@@ -1889,6 +1960,12 @@ static void sdhci_tasklet_finish(unsigned long param)
sdhci_reset(host, SDHCI_RESET_CMD);
sdhci_reset(host, SDHCI_RESET_DATA);
}
+out:
+ if((readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_DATA_INHIBIT) ||
+ (host->quirks & SDHCI_QUIRK_MUST_MAINTAIN_CLOCK))
+ mod_timer(&host->busy_check_timer, jiffies + msecs_to_jiffies(10));
+ else
+ sdhci_disable_clock_card(host);
host->mrq = NULL;
host->cmd = NULL;
@@ -1949,6 +2026,24 @@ static void sdhci_tuning_timer(unsigned long data)
spin_unlock_irqrestore(&host->lock, flags);
}
+static void sdhci_busy_check_timer(unsigned long data)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = (struct sdhci_host*)data;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if((readl(host->ioaddr + SDHCI_PRESENT_STATE) & (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)) ||
+ (host->quirks & SDHCI_QUIRK_MUST_MAINTAIN_CLOCK))
+ mod_timer(&host->busy_check_timer, jiffies + msecs_to_jiffies(10));
+ else
+ sdhci_disable_clock_card(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
/*****************************************************************************\
* *
* Interrupt handling *
@@ -1975,6 +2070,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)
if (host->cmd->error) {
tasklet_schedule(&host->finish_tasklet);
+ host->cmd = NULL;
return;
}
@@ -2219,7 +2315,7 @@ out:
int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
{
- int ret;
+ int ret = 0;
sdhci_disable_card_detection(host);
@@ -2231,10 +2327,17 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
}
ret = mmc_suspend_host(host->mmc);
- if (ret)
+ if (ret) {
+ sdhci_enable_card_detection(host);
return ret;
+ }
- free_irq(host->irq, host);
+ sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK);
+
+ del_timer(&host->busy_check_timer);
+
+ if (host->irq)
+ disable_irq(host->irq);
if (host->vmmc)
ret = regulator_disable(host->vmmc);
@@ -2246,7 +2349,7 @@ EXPORT_SYMBOL_GPL(sdhci_suspend_host);
int sdhci_resume_host(struct sdhci_host *host)
{
- int ret;
+ int ret = 0;
if (host->vmmc) {
int ret = regulator_enable(host->vmmc);
@@ -2260,15 +2363,14 @@ int sdhci_resume_host(struct sdhci_host *host)
host->ops->enable_dma(host);
}
- ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
- mmc_hostname(host->mmc), host);
- if (ret)
- return ret;
+ if (host->irq)
+ enable_irq(host->irq);
sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER));
mmiowb();
ret = mmc_resume_host(host->mmc);
+
sdhci_enable_card_detection(host);
/* Set the re-tuning expiration flag */
@@ -2699,6 +2801,7 @@ int sdhci_add_host(struct sdhci_host *host)
sdhci_tasklet_finish, (unsigned long)host);
setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
+ setup_timer(&host->busy_check_timer, sdhci_busy_check_timer, (unsigned long)host);
if (host->version >= SDHCI_SPEC_300) {
init_waitqueue_head(&host->buf_ready_int);
@@ -2804,6 +2907,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
del_timer_sync(&host->timer);
if (host->version >= SDHCI_SPEC_300)
del_timer_sync(&host->tuning_timer);
+ del_timer_sync(&host->busy_check_timer);
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->finish_tasklet);
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 745c42f..98d0bc2 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -274,6 +274,9 @@ struct sdhci_ops {
void (*platform_reset_exit)(struct sdhci_host *host, u8 mask);
int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);
+ void (*set_ios)(struct sdhci_host *host,
+ struct mmc_ios *ios);
+ void (*adjust_cfg)(struct sdhci_host *host, int rw);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS