omap_hsmmc: context save/restore support
Keep the context over PM dynamic OFF states. Signed-off-by: Denis Karpov <ext-denis.2.karpov@nokia.com> Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com> Acked-by: Matt Fleming <matt@console-pimps.org> Cc: Ian Molton <ian@mnementh.co.uk> Cc: "Roberto A. Foglietta" <roberto.foglietta@gmail.com> Cc: Jarkko Lavinen <jarkko.lavinen@nokia.com> Cc: Denis Karpov <ext-denis.2.karpov@nokia.com> Cc: Pierre Ossman <pierre@ossman.eu> Cc: Philip Langdale <philipl@overt.org> Cc: "Madhusudhan" <madhu.cr@ti.com> Cc: <linux-mmc@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
a3621465b4
commit
11dd62a741
1 changed files with 184 additions and 10 deletions
|
@ -37,6 +37,7 @@
|
|||
|
||||
/* OMAP HSMMC Host Controller Registers */
|
||||
#define OMAP_HSMMC_SYSCONFIG 0x0010
|
||||
#define OMAP_HSMMC_SYSSTATUS 0x0014
|
||||
#define OMAP_HSMMC_CON 0x002C
|
||||
#define OMAP_HSMMC_BLK 0x0104
|
||||
#define OMAP_HSMMC_ARG 0x0108
|
||||
|
@ -96,6 +97,8 @@
|
|||
#define DUAL_VOLT_OCR_BIT 7
|
||||
#define SRC (1 << 25)
|
||||
#define SRD (1 << 26)
|
||||
#define SOFTRESET (1 << 1)
|
||||
#define RESETDONE (1 << 0)
|
||||
|
||||
/*
|
||||
* FIXME: Most likely all the data using these _DEVID defines should come
|
||||
|
@ -154,6 +157,8 @@ struct mmc_omap_host {
|
|||
int slot_id;
|
||||
int dbclk_enabled;
|
||||
int response_busy;
|
||||
int context_loss;
|
||||
|
||||
struct omap_mmc_platform_data *pdata;
|
||||
};
|
||||
|
||||
|
@ -168,6 +173,166 @@ static void omap_mmc_stop_clock(struct mmc_omap_host *host)
|
|||
dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/*
|
||||
* Restore the MMC host context, if it was lost as result of a
|
||||
* power state change.
|
||||
*/
|
||||
static int omap_mmc_restore_ctx(struct mmc_omap_host *host)
|
||||
{
|
||||
struct mmc_ios *ios = &host->mmc->ios;
|
||||
struct omap_mmc_platform_data *pdata = host->pdata;
|
||||
int context_loss = 0;
|
||||
u32 hctl, capa, con;
|
||||
u16 dsor = 0;
|
||||
unsigned long timeout;
|
||||
|
||||
if (pdata->get_context_loss_count) {
|
||||
context_loss = pdata->get_context_loss_count(host->dev);
|
||||
if (context_loss < 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
dev_dbg(mmc_dev(host->mmc), "context was %slost\n",
|
||||
context_loss == host->context_loss ? "not " : "");
|
||||
if (host->context_loss == context_loss)
|
||||
return 1;
|
||||
|
||||
/* Wait for hardware reset */
|
||||
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
|
||||
while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE
|
||||
&& time_before(jiffies, timeout))
|
||||
;
|
||||
|
||||
/* Do software reset */
|
||||
OMAP_HSMMC_WRITE(host->base, SYSCONFIG, SOFTRESET);
|
||||
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
|
||||
while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE
|
||||
&& time_before(jiffies, timeout))
|
||||
;
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
|
||||
OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
|
||||
|
||||
if (host->id == OMAP_MMC1_DEVID) {
|
||||
if (host->power_mode != MMC_POWER_OFF &&
|
||||
(1 << ios->vdd) <= MMC_VDD_23_24)
|
||||
hctl = SDVS18;
|
||||
else
|
||||
hctl = SDVS30;
|
||||
capa = VS30 | VS18;
|
||||
} else {
|
||||
hctl = SDVS18;
|
||||
capa = VS18;
|
||||
}
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, HCTL,
|
||||
OMAP_HSMMC_READ(host->base, HCTL) | hctl);
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, CAPA,
|
||||
OMAP_HSMMC_READ(host->base, CAPA) | capa);
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, HCTL,
|
||||
OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
|
||||
while ((OMAP_HSMMC_READ(host->base, HCTL) & SDBP) != SDBP
|
||||
&& time_before(jiffies, timeout))
|
||||
;
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
||||
OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
|
||||
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
|
||||
|
||||
/* Do not initialize card-specific things if the power is off */
|
||||
if (host->power_mode == MMC_POWER_OFF)
|
||||
goto out;
|
||||
|
||||
con = OMAP_HSMMC_READ(host->base, CON);
|
||||
switch (ios->bus_width) {
|
||||
case MMC_BUS_WIDTH_8:
|
||||
OMAP_HSMMC_WRITE(host->base, CON, con | DW8);
|
||||
break;
|
||||
case MMC_BUS_WIDTH_4:
|
||||
OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
|
||||
OMAP_HSMMC_WRITE(host->base, HCTL,
|
||||
OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT);
|
||||
break;
|
||||
case MMC_BUS_WIDTH_1:
|
||||
OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
|
||||
OMAP_HSMMC_WRITE(host->base, HCTL,
|
||||
OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ios->clock) {
|
||||
dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
|
||||
if (dsor < 1)
|
||||
dsor = 1;
|
||||
|
||||
if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
|
||||
dsor++;
|
||||
|
||||
if (dsor > 250)
|
||||
dsor = 250;
|
||||
}
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, SYSCTL,
|
||||
OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
|
||||
OMAP_HSMMC_WRITE(host->base, SYSCTL, (dsor << 6) | (DTO << 16));
|
||||
OMAP_HSMMC_WRITE(host->base, SYSCTL,
|
||||
OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
|
||||
while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS
|
||||
&& time_before(jiffies, timeout))
|
||||
;
|
||||
|
||||
OMAP_HSMMC_WRITE(host->base, SYSCTL,
|
||||
OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
|
||||
|
||||
con = OMAP_HSMMC_READ(host->base, CON);
|
||||
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
|
||||
OMAP_HSMMC_WRITE(host->base, CON, con | OD);
|
||||
else
|
||||
OMAP_HSMMC_WRITE(host->base, CON, con & ~OD);
|
||||
out:
|
||||
host->context_loss = context_loss;
|
||||
|
||||
dev_dbg(mmc_dev(host->mmc), "context is restored\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save the MMC host context (store the number of power state changes so far).
|
||||
*/
|
||||
static void omap_mmc_save_ctx(struct mmc_omap_host *host)
|
||||
{
|
||||
struct omap_mmc_platform_data *pdata = host->pdata;
|
||||
int context_loss;
|
||||
|
||||
if (pdata->get_context_loss_count) {
|
||||
context_loss = pdata->get_context_loss_count(host->dev);
|
||||
if (context_loss < 0)
|
||||
return;
|
||||
host->context_loss = context_loss;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int omap_mmc_restore_ctx(struct mmc_omap_host *host)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void omap_mmc_save_ctx(struct mmc_omap_host *host)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Send init stream sequence to card
|
||||
* before sending IDLE command
|
||||
|
@ -830,6 +995,7 @@ static int omap_mmc_enable(struct mmc_host *mmc)
|
|||
if (err)
|
||||
return err;
|
||||
dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
|
||||
omap_mmc_restore_ctx(host);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -837,6 +1003,7 @@ static int omap_mmc_disable(struct mmc_host *mmc, int lazy)
|
|||
{
|
||||
struct mmc_omap_host *host = mmc_priv(mmc);
|
||||
|
||||
omap_mmc_save_ctx(host);
|
||||
clk_disable(host->fclk);
|
||||
dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
|
||||
return 0;
|
||||
|
@ -941,7 +1108,7 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||
|
||||
/* Wait till the ICS bit is set */
|
||||
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
|
||||
while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2
|
||||
while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS
|
||||
&& time_before(jiffies, timeout))
|
||||
msleep(1);
|
||||
|
||||
|
@ -1021,12 +1188,19 @@ static int mmc_regs_show(struct seq_file *s, void *data)
|
|||
{
|
||||
struct mmc_host *mmc = s->private;
|
||||
struct mmc_omap_host *host = mmc_priv(mmc);
|
||||
struct omap_mmc_platform_data *pdata = host->pdata;
|
||||
int context_loss = 0;
|
||||
|
||||
if (pdata->get_context_loss_count)
|
||||
context_loss = pdata->get_context_loss_count(host->dev);
|
||||
|
||||
seq_printf(s, "mmc%d:\n"
|
||||
" enabled:\t%d\n"
|
||||
" nesting_cnt:\t%d\n"
|
||||
" ctx_loss:\t%d:%d\n"
|
||||
"\nregs:\n",
|
||||
mmc->index, mmc->enabled ? 1 : 0, mmc->nesting_cnt);
|
||||
mmc->index, mmc->enabled ? 1 : 0, mmc->nesting_cnt,
|
||||
host->context_loss, context_loss);
|
||||
|
||||
if (clk_enable(host->fclk) != 0) {
|
||||
seq_printf(s, "can't read the regs\n");
|
||||
|
@ -1151,6 +1325,8 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
|
|||
goto err1;
|
||||
}
|
||||
|
||||
omap_mmc_save_ctx(host);
|
||||
|
||||
mmc->caps |= MMC_CAP_DISABLE;
|
||||
mmc_set_disable_delay(mmc, 100);
|
||||
if (mmc_host_enable(host->mmc) != 0) {
|
||||
|
@ -1385,21 +1561,19 @@ static int omap_mmc_resume(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
if (host) {
|
||||
|
||||
if (mmc_host_enable(host->mmc) != 0)
|
||||
goto clk_en_err;
|
||||
|
||||
ret = clk_enable(host->iclk);
|
||||
if (ret) {
|
||||
mmc_host_disable(host->mmc);
|
||||
clk_put(host->fclk);
|
||||
if (ret)
|
||||
goto clk_en_err;
|
||||
}
|
||||
|
||||
if (clk_enable(host->dbclk) != 0)
|
||||
dev_dbg(mmc_dev(host->mmc),
|
||||
"Enabling debounce clk failed\n");
|
||||
|
||||
if (mmc_host_enable(host->mmc) != 0) {
|
||||
clk_disable(host->iclk);
|
||||
goto clk_en_err;
|
||||
}
|
||||
|
||||
omap_hsmmc_init(host);
|
||||
|
||||
if (host->pdata->resume) {
|
||||
|
|
Reference in a new issue