#define OMAP_MMC_DATADIR_WRITE 2
#define MMC_TIMEOUT_MS 20
#define OMAP_MMC_MASTER_CLOCK 96000000
-#define DRIVER_NAME "mmci-omap"
+#define DRIVER_NAME "mmci-omap-hs"
/*
* One controller can have multiple slots, like on some omap boards using
#define OMAP_HSMMC_WRITE(base, reg, val) \
__raw_writel((val), (base) + OMAP_HSMMC_##reg)
+enum {OFF = 0, ON};
+#define IDLE_TIMEOUT (jiffies_to_msecs(10))
+
struct mmc_omap_host {
struct device *dev;
struct mmc_host *mmc;
int initstr;
int slot_id;
int dbclk_enabled;
+
+ struct timer_list idle_timer;
+ spinlock_t clk_lock; /* for changing enabled state */
+ unsigned int fclk_enabled:1;
+
struct omap_mmc_platform_data *pdata;
};
+static int mmc_omap_fclk_state(struct mmc_omap_host *host, unsigned int state)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&host->clk_lock, flags);
+ del_timer(&host->idle_timer);
+ if (host->fclk_enabled != state) {
+ if (state == ON) {
+ ret = clk_enable(host->fclk);
+ if (ret != 0)
+ goto err_out;
+
+ dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
+ } else {
+ clk_disable(host->fclk);
+ dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
+ }
+ host->fclk_enabled = state;
+ }
+
+err_out:
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ return ret;
+}
+
+static void mmc_omap_idle_timer(unsigned long data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+ mmc_omap_fclk_state(host, OFF);
+}
+
+static void mmc_omap_fclk_lazy_disable(struct mmc_omap_host *host)
+{
+ mod_timer(&host->idle_timer, jiffies + IDLE_TIMEOUT);
+}
+
/*
* Stop clock to the card
*/
/*
* Unlike OMAP1 controller, the cmdtype does not seem to be based on
- * ac, bc, adtc, bcr. Only CMD12 needs a val of 0x3, rest 0x0.
+ * ac, bc, adtc, bcr. Only commands ending an open ended transfer need
+ * a val of 0x3, rest 0x0.
*/
- if (cmd->opcode == 12)
+ if (cmd == host->mrq->stop)
cmdtype = 0x3;
cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
if (!data->stop) {
host->mrq = NULL;
+ mmc_omap_fclk_lazy_disable(host);
mmc_request_done(host->mmc, data->mrq);
return;
}
}
if (host->data == NULL || cmd->error) {
host->mrq = NULL;
+ mmc_omap_fclk_lazy_disable(host);
mmc_request_done(host->mmc, cmd->mrq);
}
}
int ret;
/* Disable the clocks */
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_disable(host->dbclk);
if (ret != 0)
goto err;
- clk_enable(host->fclk);
+ mmc_omap_fclk_state(host, ON);
clk_enable(host->iclk);
clk_enable(host->dbclk);
/*
* If a MMC dual voltage card is detected, the set_ios fn calls
* this fn with VDD bit set for 1.8V. Upon card removal from the
- * slot, mmc_omap_detect fn sets the VDD back to 3V.
+ * slot, omap_mmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF.
*
* Only MMC1 supports 3.0V. MMC2 will not function if SDVS30 is
* set in HCTL.
*/
- if (host->id == OMAP_MMC1_DEVID && (((1 << vdd) == MMC_VDD_32_33) ||
- ((1 << vdd) == MMC_VDD_33_34)))
- reg_val |= SDVS30;
- if ((1 << vdd) == MMC_VDD_165_195)
+ if (host->id == OMAP_MMC1_DEVID) {
+ if (((1 << vdd) == MMC_VDD_32_33) ||
+ ((1 << vdd) == MMC_VDD_33_34))
+ reg_val |= SDVS30;
+ else if ((1 << vdd) == MMC_VDD_165_195)
+ reg_val |= SDVS18;
+ } else
reg_val |= SDVS18;
OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
mmc_carddetect_work);
sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
+ mmc_omap_fclk_state(host, ON);
if (host->carddetect) {
- if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
- /*
- * Set the VDD back to 3V when the card is removed
- * before the set_ios fn turns off the power.
- */
- vdd = fls(host->mmc->ocr_avail) - 1;
- if (omap_mmc_switch_opcond(host, vdd) != 0)
- host->mmc->ios.vdd = vdd;
- }
mmc_detect_change(host->mmc, (HZ * 200) / 1000);
} else {
OMAP_HSMMC_WRITE(host->base, SYSCTL,
while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ;
mmc_detect_change(host->mmc, (HZ * 50) / 1000);
}
+ mmc_omap_fclk_lazy_disable(host);
}
/*
WARN_ON(host->mrq != NULL);
host->mrq = req;
+ mmc_omap_fclk_state(host, ON);
mmc_omap_prepare_data(host, req);
mmc_omap_start_command(host, req->cmd, req->data);
}
-
/* Routine to configure clock values. Exposed API to core */
static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
unsigned long regval;
unsigned long timeout;
+ mmc_omap_fclk_state(host, ON);
+
switch (ios->power_mode) {
case MMC_POWER_OFF:
mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+
+ /*
+ * Reset bus voltage to 3V if it got set to 1.8V earlier.
+ * REVISIT: If we are able to detect cards after unplugging
+ * a 1.8V card, this code should not be needed.
+ */
+ regval = OMAP_HSMMC_READ(host->base, HCTL);
+ if (regval & SDVSDET) {
+ regval &= ~SDVSDET;
+ OMAP_HSMMC_WRITE(host->base, HCTL, regval);
+ }
break;
case MMC_POWER_UP:
mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd);
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base, CON) | OD);
+
+ mmc_omap_fclk_lazy_disable(host);
}
static int omap_hsmmc_get_cd(struct mmc_host *mmc)
struct mmc_host *mmc;
struct mmc_omap_host *host = NULL;
struct resource *res;
- int ret = 0, irq;
+ int ret = 0, irq, reg;
u32 hctl, capa;
if (pdata == NULL) {
goto err1;
}
- if (clk_enable(host->fclk) != 0) {
+ spin_lock_init(&host->clk_lock);
+ setup_timer(&host->idle_timer, mmc_omap_idle_timer,
+ (unsigned long) host);
+
+ if (mmc_omap_fclk_state(host, ON) != 0) {
clk_put(host->iclk);
clk_put(host->fclk);
goto err1;
}
-
if (clk_enable(host->iclk) != 0) {
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_put(host->iclk);
clk_put(host->fclk);
goto err1;
* MMC can still work without debounce clock.
*/
if (IS_ERR(host->dbclk))
- dev_dbg(mmc_dev(host->mmc), "Failed to get debounce clock\n");
+ dev_warn(mmc_dev(host->mmc), "Failed to get debounce clock\n");
else
if (clk_enable(host->dbclk) != 0)
dev_dbg(mmc_dev(host->mmc), "Enabling debounce"
if (ret < 0)
goto err_cover_switch;
}
+ mmc_omap_fclk_lazy_disable(host);
return 0;
err_irq_cd_init:
free_irq(host->irq, host);
err_irq:
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_put(host->fclk);
clk_put(host->iclk);
{
struct mmc_omap_host *host = platform_get_drvdata(pdev);
struct resource *res;
- u16 vdd = 0;
- if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
- /*
- * Set the vdd back to 3V,
- * applicable for dual volt support.
- */
- vdd = fls(host->mmc->ocr_avail) - 1;
- if (omap_mmc_switch_opcond(host, vdd) != 0)
- host->mmc->ios.vdd = vdd;
- }
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res)
- release_mem_region(res->start, res->end - res->start + 1);
-
- platform_set_drvdata(pdev, NULL);
if (host) {
mmc_remove_host(host->mmc);
if (host->pdata->cleanup)
free_irq(mmc_slot(host).card_detect_irq, host);
flush_scheduled_work();
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_put(host->fclk);
clk_put(host->iclk);
iounmap(host->base);
}
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, res->end - res->start + 1);
+ platform_set_drvdata(pdev, NULL);
+
return 0;
}
if (ret == 0) {
host->suspended = 1;
+ mmc_omap_fclk_state(host, ON);
OMAP_HSMMC_WRITE(host->base, ISE, 0);
OMAP_HSMMC_WRITE(host->base, IE, 0);
" level suspend\n");
}
- if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
- OMAP_HSMMC_WRITE(host->base, HCTL,
- OMAP_HSMMC_READ(host->base, HCTL)
- & SDVSCLR);
- OMAP_HSMMC_WRITE(host->base, HCTL,
- OMAP_HSMMC_READ(host->base, HCTL)
- | SDVS30);
- OMAP_HSMMC_WRITE(host->base, HCTL,
- OMAP_HSMMC_READ(host->base, HCTL)
- | SDBP);
- }
-
- clk_disable(host->fclk);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_disable(host->dbclk);
}
return 0;
if (host) {
-
- ret = clk_enable(host->fclk);
- if (ret)
+ int i;
+ if (mmc_omap_fclk_state(host, ON) != 0)
goto clk_en_err;
ret = clk_enable(host->iclk);
if (ret) {
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_put(host->fclk);
goto clk_en_err;
}
dev_dbg(mmc_dev(host->mmc),
"Enabling debounce clk failed\n");
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+ for (i = 0; i < 100; i++)
+ if (OMAP_HSMMC_READ(host->base, HCTL) & SDBP)
+ break;
+
if (host->pdata->resume) {
ret = host->pdata->resume(&pdev->dev, host->slot_id);
if (ret)
ret = mmc_resume_host(host->mmc);
if (ret == 0)
host->suspended = 0;
+
+ mmc_omap_fclk_lazy_disable(host);
}
return ret;