aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mmc/host/omap_hsmmc.c
diff options
context:
space:
mode:
authorDenis Karpov <ext-denis.2.karpov@nokia.com>2009-09-22 19:44:49 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-23 10:39:35 -0400
commitdd498effcfa6a196ba097adae3c5aa641115df88 (patch)
treef511fbd298255a97f690f5aaabb714719ef04c88 /drivers/mmc/host/omap_hsmmc.c
parent23d99bb923fc23aeb1086d60eb1c70602b4e2036 (diff)
omap_hsmmc: support for deeper power saving states
Support for multi-level dynamic power saving states in omap_hsmmc (ENABLED->DISABLED->OFF). In the "deepest" state (OFF) we switch off the voltage regulators. 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>
Diffstat (limited to 'drivers/mmc/host/omap_hsmmc.c')
-rw-r--r--drivers/mmc/host/omap_hsmmc.c245
1 files changed, 215 insertions, 30 deletions
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index a20c38385d2..016914c2c4e 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -113,6 +113,10 @@
113#define OMAP_MMC_MASTER_CLOCK 96000000 113#define OMAP_MMC_MASTER_CLOCK 96000000
114#define DRIVER_NAME "mmci-omap-hs" 114#define DRIVER_NAME "mmci-omap-hs"
115 115
116/* Timeouts for entering power saving states on inactivity, msec */
117#define OMAP_MMC_DISABLED_TIMEOUT 100
118#define OMAP_MMC_OFF_TIMEOUT 1000
119
116/* 120/*
117 * One controller can have multiple slots, like on some omap boards using 121 * One controller can have multiple slots, like on some omap boards using
118 * omap.c controller driver. Luckily this is not currently done on any known 122 * omap.c controller driver. Luckily this is not currently done on any known
@@ -157,6 +161,7 @@ struct mmc_omap_host {
157 int dbclk_enabled; 161 int dbclk_enabled;
158 int response_busy; 162 int response_busy;
159 int context_loss; 163 int context_loss;
164 int dpm_state;
160 165
161 struct omap_mmc_platform_data *pdata; 166 struct omap_mmc_platform_data *pdata;
162}; 167};
@@ -992,29 +997,6 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
992 return 0; 997 return 0;
993} 998}
994 999
995static int omap_mmc_enable(struct mmc_host *mmc)
996{
997 struct mmc_omap_host *host = mmc_priv(mmc);
998 int err;
999
1000 err = clk_enable(host->fclk);
1001 if (err)
1002 return err;
1003 dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
1004 omap_mmc_restore_ctx(host);
1005 return 0;
1006}
1007
1008static int omap_mmc_disable(struct mmc_host *mmc, int lazy)
1009{
1010 struct mmc_omap_host *host = mmc_priv(mmc);
1011
1012 omap_mmc_save_ctx(host);
1013 clk_disable(host->fclk);
1014 dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
1015 return 0;
1016}
1017
1018/* 1000/*
1019 * Request function. for read/write operation 1001 * Request function. for read/write operation
1020 */ 1002 */
@@ -1068,6 +1050,8 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
1068 host->power_mode = ios->power_mode; 1050 host->power_mode = ios->power_mode;
1069 } 1051 }
1070 1052
1053 /* FIXME: set registers based only on changes to ios */
1054
1071 con = OMAP_HSMMC_READ(host->base, CON); 1055 con = OMAP_HSMMC_READ(host->base, CON);
1072 switch (mmc->ios.bus_width) { 1056 switch (mmc->ios.bus_width) {
1073 case MMC_BUS_WIDTH_8: 1057 case MMC_BUS_WIDTH_8:
@@ -1140,7 +1124,10 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
1140 else 1124 else
1141 OMAP_HSMMC_WRITE(host->base, CON, con & ~OD); 1125 OMAP_HSMMC_WRITE(host->base, CON, con & ~OD);
1142 1126
1143 mmc_host_lazy_disable(host->mmc); 1127 if (host->power_mode == MMC_POWER_OFF)
1128 mmc_host_disable(host->mmc);
1129 else
1130 mmc_host_lazy_disable(host->mmc);
1144} 1131}
1145 1132
1146static int omap_hsmmc_get_cd(struct mmc_host *mmc) 1133static int omap_hsmmc_get_cd(struct mmc_host *mmc)
@@ -1190,7 +1177,191 @@ static void omap_hsmmc_init(struct mmc_omap_host *host)
1190 set_sd_bus_power(host); 1177 set_sd_bus_power(host);
1191} 1178}
1192 1179
1193static struct mmc_host_ops mmc_omap_ops = { 1180/*
1181 * Dynamic power saving handling, FSM:
1182 * ENABLED -> DISABLED -> OFF
1183 * ^___________| |
1184 * |______________________|
1185 *
1186 * ENABLED: mmc host is fully functional
1187 * DISABLED: fclk is off
1188 * OFF: fclk is off,voltage regulator is off
1189 *
1190 * Transition handlers return the timeout for the next state transition
1191 * or negative error.
1192 */
1193
1194enum {ENABLED = 0, DISABLED, OFF};
1195
1196/* Handler for [ENABLED -> DISABLED] transition */
1197static int omap_mmc_enabled_to_disabled(struct mmc_omap_host *host)
1198{
1199 omap_mmc_save_ctx(host);
1200 clk_disable(host->fclk);
1201 host->dpm_state = DISABLED;
1202
1203 dev_dbg(mmc_dev(host->mmc), "ENABLED -> DISABLED\n");
1204
1205 if (host->power_mode == MMC_POWER_OFF)
1206 return 0;
1207
1208 return msecs_to_jiffies(OMAP_MMC_OFF_TIMEOUT);
1209}
1210
1211/* Handler for [DISABLED -> OFF] transition */
1212static int omap_mmc_disabled_to_off(struct mmc_omap_host *host)
1213{
1214 int new_state;
1215
1216 dev_dbg(mmc_dev(host->mmc), "DISABLED -> OFF\n");
1217
1218 if (!mmc_try_claim_host(host->mmc))
1219 return 0;
1220
1221 clk_enable(host->fclk);
1222
1223 omap_mmc_restore_ctx(host);
1224
1225 if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
1226 mmc_slot(host).card_detect ||
1227 (mmc_slot(host).get_cover_state &&
1228 mmc_slot(host).get_cover_state(host->dev, host->slot_id))) {
1229 mmc_power_save_host(host->mmc);
1230 new_state = OFF;
1231 } else
1232 new_state = DISABLED;
1233
1234 OMAP_HSMMC_WRITE(host->base, ISE, 0);
1235 OMAP_HSMMC_WRITE(host->base, IE, 0);
1236 OMAP_HSMMC_WRITE(host->base, HCTL,
1237 OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
1238
1239 clk_disable(host->fclk);
1240 clk_disable(host->iclk);
1241 clk_disable(host->dbclk);
1242
1243 host->dpm_state = new_state;
1244
1245 mmc_release_host(host->mmc);
1246
1247 return 0;
1248}
1249
1250/* Handler for [DISABLED -> ENABLED] transition */
1251static int omap_mmc_disabled_to_enabled(struct mmc_omap_host *host)
1252{
1253 int err;
1254
1255 err = clk_enable(host->fclk);
1256 if (err < 0)
1257 return err;
1258
1259 omap_mmc_restore_ctx(host);
1260
1261 host->dpm_state = ENABLED;
1262
1263 dev_dbg(mmc_dev(host->mmc), "DISABLED -> ENABLED\n");
1264
1265 return 0;
1266}
1267
1268/* Handler for [OFF -> ENABLED] transition */
1269static int omap_mmc_off_to_enabled(struct mmc_omap_host *host)
1270{
1271 clk_enable(host->fclk);
1272 clk_enable(host->iclk);
1273
1274 if (clk_enable(host->dbclk))
1275 dev_dbg(mmc_dev(host->mmc),
1276 "Enabling debounce clk failed\n");
1277
1278 omap_mmc_restore_ctx(host);
1279 omap_hsmmc_init(host);
1280 mmc_power_restore_host(host->mmc);
1281
1282 host->dpm_state = ENABLED;
1283
1284 dev_dbg(mmc_dev(host->mmc), "OFF -> ENABLED\n");
1285
1286 return 0;
1287}
1288
1289/*
1290 * Bring MMC host to ENABLED from any other PM state.
1291 */
1292static int omap_mmc_enable(struct mmc_host *mmc)
1293{
1294 struct mmc_omap_host *host = mmc_priv(mmc);
1295
1296 switch (host->dpm_state) {
1297 case DISABLED:
1298 return omap_mmc_disabled_to_enabled(host);
1299 case OFF:
1300 return omap_mmc_off_to_enabled(host);
1301 default:
1302 dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n");
1303 return -EINVAL;
1304 }
1305}
1306
1307/*
1308 * Bring MMC host in PM state (one level deeper).
1309 */
1310static int omap_mmc_disable(struct mmc_host *mmc, int lazy)
1311{
1312 struct mmc_omap_host *host = mmc_priv(mmc);
1313
1314 switch (host->dpm_state) {
1315 case ENABLED: {
1316 int delay;
1317
1318 delay = omap_mmc_enabled_to_disabled(host);
1319 if (lazy || delay < 0)
1320 return delay;
1321 return 0;
1322 }
1323 case DISABLED:
1324 return omap_mmc_disabled_to_off(host);
1325 default:
1326 dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n");
1327 return -EINVAL;
1328 }
1329}
1330
1331static int omap_mmc_enable_fclk(struct mmc_host *mmc)
1332{
1333 struct mmc_omap_host *host = mmc_priv(mmc);
1334 int err;
1335
1336 err = clk_enable(host->fclk);
1337 if (err)
1338 return err;
1339 dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
1340 omap_mmc_restore_ctx(host);
1341 return 0;
1342}
1343
1344static int omap_mmc_disable_fclk(struct mmc_host *mmc, int lazy)
1345{
1346 struct mmc_omap_host *host = mmc_priv(mmc);
1347
1348 omap_mmc_save_ctx(host);
1349 clk_disable(host->fclk);
1350 dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
1351 return 0;
1352}
1353
1354static const struct mmc_host_ops mmc_omap_ops = {
1355 .enable = omap_mmc_enable_fclk,
1356 .disable = omap_mmc_disable_fclk,
1357 .request = omap_mmc_request,
1358 .set_ios = omap_mmc_set_ios,
1359 .get_cd = omap_hsmmc_get_cd,
1360 .get_ro = omap_hsmmc_get_ro,
1361 /* NYET -- enable_sdio_irq */
1362};
1363
1364static const struct mmc_host_ops mmc_omap_ps_ops = {
1194 .enable = omap_mmc_enable, 1365 .enable = omap_mmc_enable,
1195 .disable = omap_mmc_disable, 1366 .disable = omap_mmc_disable,
1196 .request = omap_mmc_request, 1367 .request = omap_mmc_request,
@@ -1214,15 +1385,22 @@ static int mmc_regs_show(struct seq_file *s, void *data)
1214 1385
1215 seq_printf(s, "mmc%d:\n" 1386 seq_printf(s, "mmc%d:\n"
1216 " enabled:\t%d\n" 1387 " enabled:\t%d\n"
1388 " dpm_state:\t%d\n"
1217 " nesting_cnt:\t%d\n" 1389 " nesting_cnt:\t%d\n"
1218 " ctx_loss:\t%d:%d\n" 1390 " ctx_loss:\t%d:%d\n"
1219 "\nregs:\n", 1391 "\nregs:\n",
1220 mmc->index, mmc->enabled ? 1 : 0, mmc->nesting_cnt, 1392 mmc->index, mmc->enabled ? 1 : 0,
1393 host->dpm_state, mmc->nesting_cnt,
1221 host->context_loss, context_loss); 1394 host->context_loss, context_loss);
1222 1395
1396 if (host->suspended || host->dpm_state == OFF) {
1397 seq_printf(s, "host suspended, can't read registers\n");
1398 return 0;
1399 }
1400
1223 if (clk_enable(host->fclk) != 0) { 1401 if (clk_enable(host->fclk) != 0) {
1224 seq_printf(s, "can't read the regs\n"); 1402 seq_printf(s, "can't read the regs\n");
1225 goto err; 1403 return 0;
1226 } 1404 }
1227 1405
1228 seq_printf(s, "SYSCONFIG:\t0x%08x\n", 1406 seq_printf(s, "SYSCONFIG:\t0x%08x\n",
@@ -1241,7 +1419,7 @@ static int mmc_regs_show(struct seq_file *s, void *data)
1241 OMAP_HSMMC_READ(host->base, CAPA)); 1419 OMAP_HSMMC_READ(host->base, CAPA));
1242 1420
1243 clk_disable(host->fclk); 1421 clk_disable(host->fclk);
1244err: 1422
1245 return 0; 1423 return 0;
1246} 1424}
1247 1425
@@ -1323,7 +1501,11 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
1323 platform_set_drvdata(pdev, host); 1501 platform_set_drvdata(pdev, host);
1324 INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect); 1502 INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
1325 1503
1326 mmc->ops = &mmc_omap_ops; 1504 if (pdata->slots[host->slot_id].power_saving)
1505 mmc->ops = &mmc_omap_ps_ops;
1506 else
1507 mmc->ops = &mmc_omap_ops;
1508
1327 mmc->f_min = 400000; 1509 mmc->f_min = 400000;
1328 mmc->f_max = 52000000; 1510 mmc->f_max = 52000000;
1329 1511
@@ -1346,7 +1528,10 @@ static int __init omap_mmc_probe(struct platform_device *pdev)
1346 omap_mmc_save_ctx(host); 1528 omap_mmc_save_ctx(host);
1347 1529
1348 mmc->caps |= MMC_CAP_DISABLE; 1530 mmc->caps |= MMC_CAP_DISABLE;
1349 mmc_set_disable_delay(mmc, 100); 1531 mmc_set_disable_delay(mmc, OMAP_MMC_DISABLED_TIMEOUT);
1532 /* we start off in DISABLED state */
1533 host->dpm_state = DISABLED;
1534
1350 if (mmc_host_enable(host->mmc) != 0) { 1535 if (mmc_host_enable(host->mmc) != 0) {
1351 clk_put(host->iclk); 1536 clk_put(host->iclk);
1352 clk_put(host->fclk); 1537 clk_put(host->fclk);