diff options
author | Denis Karpov <ext-denis.2.karpov@nokia.com> | 2009-09-22 19:44:49 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-23 10:39:35 -0400 |
commit | dd498effcfa6a196ba097adae3c5aa641115df88 (patch) | |
tree | f511fbd298255a97f690f5aaabb714719ef04c88 /drivers/mmc/host/omap_hsmmc.c | |
parent | 23d99bb923fc23aeb1086d60eb1c70602b4e2036 (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.c | 245 |
1 files changed, 215 insertions, 30 deletions
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index a20c38385d2a..016914c2c4e7 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 | ||
995 | static 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 | |||
1008 | static 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 | ||
1146 | static int omap_hsmmc_get_cd(struct mmc_host *mmc) | 1133 | static 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 | ||
1193 | static 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 | |||
1194 | enum {ENABLED = 0, DISABLED, OFF}; | ||
1195 | |||
1196 | /* Handler for [ENABLED -> DISABLED] transition */ | ||
1197 | static 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 */ | ||
1212 | static 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 */ | ||
1251 | static 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 */ | ||
1269 | static 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 | */ | ||
1292 | static 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 | */ | ||
1310 | static 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 | |||
1331 | static 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 | |||
1344 | static 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 | |||
1354 | static 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 | |||
1364 | static 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); |
1244 | err: | 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); |