diff options
author | Jarkko Lavinen <jarkko.lavinen@nokia.com> | 2009-09-22 19:44:53 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-23 10:39:35 -0400 |
commit | 13189e78caa824f0285b1823519c020591641207 (patch) | |
tree | 772d20a61b63540ae2154d2e3bfb9af55147527b /drivers/mmc/host | |
parent | 623821f71bb40f9972630eb1f779817d462ef0ee (diff) |
omap_hsmmc: add mmc card sleep and awake support
After 1 second of inactivity, put card and/or regulator to sleep. After 8
seconds of inactivity, turn off the power.
Signed-off-by: Jarkko Lavinen <jarkko.lavinen@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')
-rw-r--r-- | drivers/mmc/host/omap_hsmmc.c | 162 |
1 files changed, 88 insertions, 74 deletions
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index c82ec7f4e1a7..b83f7a4d8e28 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c | |||
@@ -27,6 +27,7 @@ | |||
27 | #include <linux/timer.h> | 27 | #include <linux/timer.h> |
28 | #include <linux/clk.h> | 28 | #include <linux/clk.h> |
29 | #include <linux/mmc/host.h> | 29 | #include <linux/mmc/host.h> |
30 | #include <linux/mmc/core.h> | ||
30 | #include <linux/io.h> | 31 | #include <linux/io.h> |
31 | #include <linux/semaphore.h> | 32 | #include <linux/semaphore.h> |
32 | #include <mach/dma.h> | 33 | #include <mach/dma.h> |
@@ -115,7 +116,8 @@ | |||
115 | 116 | ||
116 | /* Timeouts for entering power saving states on inactivity, msec */ | 117 | /* Timeouts for entering power saving states on inactivity, msec */ |
117 | #define OMAP_MMC_DISABLED_TIMEOUT 100 | 118 | #define OMAP_MMC_DISABLED_TIMEOUT 100 |
118 | #define OMAP_MMC_OFF_TIMEOUT 1000 | 119 | #define OMAP_MMC_SLEEP_TIMEOUT 1000 |
120 | #define OMAP_MMC_OFF_TIMEOUT 8000 | ||
119 | 121 | ||
120 | /* | 122 | /* |
121 | * One controller can have multiple slots, like on some omap boards using | 123 | * One controller can have multiple slots, like on some omap boards using |
@@ -1182,20 +1184,21 @@ static void omap_hsmmc_init(struct mmc_omap_host *host) | |||
1182 | 1184 | ||
1183 | /* | 1185 | /* |
1184 | * Dynamic power saving handling, FSM: | 1186 | * Dynamic power saving handling, FSM: |
1185 | * ENABLED -> DISABLED -> OFF / REGSLEEP | 1187 | * ENABLED -> DISABLED -> CARDSLEEP / REGSLEEP -> OFF |
1186 | * ^___________| | | 1188 | * ^___________| | | |
1187 | * |______________________| | 1189 | * |______________________|______________________| |
1188 | * | 1190 | * |
1189 | * ENABLED: mmc host is fully functional | 1191 | * ENABLED: mmc host is fully functional |
1190 | * DISABLED: fclk is off | 1192 | * DISABLED: fclk is off |
1191 | * OFF: fclk is off,voltage regulator is off | 1193 | * CARDSLEEP: fclk is off, card is asleep, voltage regulator is asleep |
1192 | * REGSLEEP: fclk is off,voltage regulator is asleep | 1194 | * REGSLEEP: fclk is off, voltage regulator is asleep |
1195 | * OFF: fclk is off, voltage regulator is off | ||
1193 | * | 1196 | * |
1194 | * Transition handlers return the timeout for the next state transition | 1197 | * Transition handlers return the timeout for the next state transition |
1195 | * or negative error. | 1198 | * or negative error. |
1196 | */ | 1199 | */ |
1197 | 1200 | ||
1198 | enum {ENABLED = 0, DISABLED, REGSLEEP, OFF}; | 1201 | enum {ENABLED = 0, DISABLED, CARDSLEEP, REGSLEEP, OFF}; |
1199 | 1202 | ||
1200 | /* Handler for [ENABLED -> DISABLED] transition */ | 1203 | /* Handler for [ENABLED -> DISABLED] transition */ |
1201 | static int omap_mmc_enabled_to_disabled(struct mmc_omap_host *host) | 1204 | static int omap_mmc_enabled_to_disabled(struct mmc_omap_host *host) |
@@ -1209,46 +1212,72 @@ static int omap_mmc_enabled_to_disabled(struct mmc_omap_host *host) | |||
1209 | if (host->power_mode == MMC_POWER_OFF) | 1212 | if (host->power_mode == MMC_POWER_OFF) |
1210 | return 0; | 1213 | return 0; |
1211 | 1214 | ||
1212 | return msecs_to_jiffies(OMAP_MMC_OFF_TIMEOUT); | 1215 | return msecs_to_jiffies(OMAP_MMC_SLEEP_TIMEOUT); |
1213 | } | 1216 | } |
1214 | 1217 | ||
1215 | /* Handler for [DISABLED -> OFF] transition */ | 1218 | /* Handler for [DISABLED -> REGSLEEP / CARDSLEEP] transition */ |
1216 | static int omap_mmc_disabled_to_off(struct mmc_omap_host *host) | 1219 | static int omap_mmc_disabled_to_sleep(struct mmc_omap_host *host) |
1217 | { | 1220 | { |
1218 | int new_state; | 1221 | int err, new_state; |
1219 | |||
1220 | dev_dbg(mmc_dev(host->mmc), "DISABLED -> OFF\n"); | ||
1221 | 1222 | ||
1222 | if (!mmc_try_claim_host(host->mmc)) | 1223 | if (!mmc_try_claim_host(host->mmc)) |
1223 | return 0; | 1224 | return 0; |
1224 | 1225 | ||
1225 | clk_enable(host->fclk); | 1226 | clk_enable(host->fclk); |
1226 | |||
1227 | omap_mmc_restore_ctx(host); | 1227 | omap_mmc_restore_ctx(host); |
1228 | if (mmc_card_can_sleep(host->mmc)) { | ||
1229 | err = mmc_card_sleep(host->mmc); | ||
1230 | if (err < 0) { | ||
1231 | clk_disable(host->fclk); | ||
1232 | mmc_release_host(host->mmc); | ||
1233 | return err; | ||
1234 | } | ||
1235 | new_state = CARDSLEEP; | ||
1236 | } else | ||
1237 | new_state = REGSLEEP; | ||
1238 | if (mmc_slot(host).set_sleep) | ||
1239 | mmc_slot(host).set_sleep(host->dev, host->slot_id, 1, 0, | ||
1240 | new_state == CARDSLEEP); | ||
1241 | /* FIXME: turn off bus power and perhaps interrupts too */ | ||
1242 | clk_disable(host->fclk); | ||
1243 | host->dpm_state = new_state; | ||
1244 | |||
1245 | mmc_release_host(host->mmc); | ||
1246 | |||
1247 | dev_dbg(mmc_dev(host->mmc), "DISABLED -> %s\n", | ||
1248 | host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP"); | ||
1228 | 1249 | ||
1229 | if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) || | 1250 | if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) || |
1230 | mmc_slot(host).card_detect || | 1251 | mmc_slot(host).card_detect || |
1231 | (mmc_slot(host).get_cover_state && | 1252 | (mmc_slot(host).get_cover_state && |
1232 | mmc_slot(host).get_cover_state(host->dev, host->slot_id))) { | 1253 | mmc_slot(host).get_cover_state(host->dev, host->slot_id))) |
1233 | mmc_power_save_host(host->mmc); | 1254 | return msecs_to_jiffies(OMAP_MMC_OFF_TIMEOUT); |
1234 | new_state = OFF; | 1255 | |
1235 | } else { | 1256 | return 0; |
1236 | if (mmc_slot(host).set_sleep) | 1257 | } |
1237 | mmc_slot(host).set_sleep(host->dev, host->slot_id, | 1258 | |
1238 | 1, 0, 0); | 1259 | /* Handler for [REGSLEEP / CARDSLEEP -> OFF] transition */ |
1239 | new_state = REGSLEEP; | 1260 | static int omap_mmc_sleep_to_off(struct mmc_omap_host *host) |
1261 | { | ||
1262 | if (!mmc_try_claim_host(host->mmc)) | ||
1263 | return 0; | ||
1264 | |||
1265 | if (!((host->mmc->caps & MMC_CAP_NONREMOVABLE) || | ||
1266 | mmc_slot(host).card_detect || | ||
1267 | (mmc_slot(host).get_cover_state && | ||
1268 | mmc_slot(host).get_cover_state(host->dev, host->slot_id)))) { | ||
1269 | mmc_release_host(host->mmc); | ||
1270 | return 0; | ||
1240 | } | 1271 | } |
1241 | 1272 | ||
1242 | OMAP_HSMMC_WRITE(host->base, ISE, 0); | 1273 | mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); |
1243 | OMAP_HSMMC_WRITE(host->base, IE, 0); | 1274 | host->vdd = 0; |
1244 | OMAP_HSMMC_WRITE(host->base, HCTL, | 1275 | host->power_mode = MMC_POWER_OFF; |
1245 | OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP); | ||
1246 | 1276 | ||
1247 | clk_disable(host->fclk); | 1277 | dev_dbg(mmc_dev(host->mmc), "%s -> OFF\n", |
1248 | clk_disable(host->iclk); | 1278 | host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP"); |
1249 | clk_disable(host->dbclk); | ||
1250 | 1279 | ||
1251 | host->dpm_state = new_state; | 1280 | host->dpm_state = OFF; |
1252 | 1281 | ||
1253 | mmc_release_host(host->mmc); | 1282 | mmc_release_host(host->mmc); |
1254 | 1283 | ||
@@ -1273,62 +1302,43 @@ static int omap_mmc_disabled_to_enabled(struct mmc_omap_host *host) | |||
1273 | return 0; | 1302 | return 0; |
1274 | } | 1303 | } |
1275 | 1304 | ||
1276 | /* Handler for [OFF -> ENABLED] transition */ | 1305 | /* Handler for [SLEEP -> ENABLED] transition */ |
1277 | static int omap_mmc_off_to_enabled(struct mmc_omap_host *host) | 1306 | static int omap_mmc_sleep_to_enabled(struct mmc_omap_host *host) |
1278 | { | 1307 | { |
1279 | clk_enable(host->fclk); | 1308 | if (!mmc_try_claim_host(host->mmc)) |
1280 | clk_enable(host->iclk); | 1309 | return 0; |
1281 | |||
1282 | if (clk_enable(host->dbclk)) | ||
1283 | dev_dbg(mmc_dev(host->mmc), | ||
1284 | "Enabling debounce clk failed\n"); | ||
1285 | 1310 | ||
1311 | clk_enable(host->fclk); | ||
1286 | omap_mmc_restore_ctx(host); | 1312 | omap_mmc_restore_ctx(host); |
1287 | omap_hsmmc_init(host); | 1313 | if (mmc_slot(host).set_sleep) |
1288 | mmc_power_restore_host(host->mmc); | 1314 | mmc_slot(host).set_sleep(host->dev, host->slot_id, 0, |
1315 | host->vdd, host->dpm_state == CARDSLEEP); | ||
1316 | if (mmc_card_can_sleep(host->mmc)) | ||
1317 | mmc_card_awake(host->mmc); | ||
1318 | |||
1319 | dev_dbg(mmc_dev(host->mmc), "%s -> ENABLED\n", | ||
1320 | host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP"); | ||
1289 | 1321 | ||
1290 | host->dpm_state = ENABLED; | 1322 | host->dpm_state = ENABLED; |
1291 | 1323 | ||
1292 | dev_dbg(mmc_dev(host->mmc), "OFF -> ENABLED\n"); | 1324 | mmc_release_host(host->mmc); |
1293 | 1325 | ||
1294 | return 0; | 1326 | return 0; |
1295 | } | 1327 | } |
1296 | 1328 | ||
1297 | /* Handler for [REGSLEEP -> ENABLED] transition */ | 1329 | /* Handler for [OFF -> ENABLED] transition */ |
1298 | static int omap_mmc_regsleep_to_enabled(struct mmc_omap_host *host) | 1330 | static int omap_mmc_off_to_enabled(struct mmc_omap_host *host) |
1299 | { | 1331 | { |
1300 | unsigned long timeout; | ||
1301 | |||
1302 | dev_dbg(mmc_dev(host->mmc), "REGSLEEP -> ENABLED\n"); | ||
1303 | |||
1304 | clk_enable(host->fclk); | 1332 | clk_enable(host->fclk); |
1305 | clk_enable(host->iclk); | ||
1306 | |||
1307 | if (clk_enable(host->dbclk)) | ||
1308 | dev_dbg(mmc_dev(host->mmc), | ||
1309 | "Enabling debounce clk failed\n"); | ||
1310 | 1333 | ||
1311 | omap_mmc_restore_ctx(host); | 1334 | omap_mmc_restore_ctx(host); |
1312 | 1335 | omap_hsmmc_init(host); | |
1313 | /* | 1336 | mmc_power_restore_host(host->mmc); |
1314 | * We turned off interrupts and bus power. Interrupts | ||
1315 | * are turned on by 'mmc_omap_start_command()' so we | ||
1316 | * just need to turn on the bus power here. | ||
1317 | */ | ||
1318 | OMAP_HSMMC_WRITE(host->base, HCTL, | ||
1319 | OMAP_HSMMC_READ(host->base, HCTL) | SDBP); | ||
1320 | |||
1321 | timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); | ||
1322 | while ((OMAP_HSMMC_READ(host->base, HCTL) & SDBP) != SDBP && | ||
1323 | time_before(jiffies, timeout)) | ||
1324 | ; | ||
1325 | |||
1326 | if (mmc_slot(host).set_sleep) | ||
1327 | mmc_slot(host).set_sleep(host->dev, host->slot_id, | ||
1328 | 0, host->vdd, 0); | ||
1329 | 1337 | ||
1330 | host->dpm_state = ENABLED; | 1338 | host->dpm_state = ENABLED; |
1331 | 1339 | ||
1340 | dev_dbg(mmc_dev(host->mmc), "OFF -> ENABLED\n"); | ||
1341 | |||
1332 | return 0; | 1342 | return 0; |
1333 | } | 1343 | } |
1334 | 1344 | ||
@@ -1342,8 +1352,9 @@ static int omap_mmc_enable(struct mmc_host *mmc) | |||
1342 | switch (host->dpm_state) { | 1352 | switch (host->dpm_state) { |
1343 | case DISABLED: | 1353 | case DISABLED: |
1344 | return omap_mmc_disabled_to_enabled(host); | 1354 | return omap_mmc_disabled_to_enabled(host); |
1355 | case CARDSLEEP: | ||
1345 | case REGSLEEP: | 1356 | case REGSLEEP: |
1346 | return omap_mmc_regsleep_to_enabled(host); | 1357 | return omap_mmc_sleep_to_enabled(host); |
1347 | case OFF: | 1358 | case OFF: |
1348 | return omap_mmc_off_to_enabled(host); | 1359 | return omap_mmc_off_to_enabled(host); |
1349 | default: | 1360 | default: |
@@ -1369,7 +1380,10 @@ static int omap_mmc_disable(struct mmc_host *mmc, int lazy) | |||
1369 | return 0; | 1380 | return 0; |
1370 | } | 1381 | } |
1371 | case DISABLED: | 1382 | case DISABLED: |
1372 | return omap_mmc_disabled_to_off(host); | 1383 | return omap_mmc_disabled_to_sleep(host); |
1384 | case CARDSLEEP: | ||
1385 | case REGSLEEP: | ||
1386 | return omap_mmc_sleep_to_off(host); | ||
1373 | default: | 1387 | default: |
1374 | dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n"); | 1388 | dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n"); |
1375 | return -EINVAL; | 1389 | return -EINVAL; |
@@ -1441,8 +1455,7 @@ static int mmc_regs_show(struct seq_file *s, void *data) | |||
1441 | host->dpm_state, mmc->nesting_cnt, | 1455 | host->dpm_state, mmc->nesting_cnt, |
1442 | host->context_loss, context_loss); | 1456 | host->context_loss, context_loss); |
1443 | 1457 | ||
1444 | if (host->suspended || host->dpm_state == OFF || | 1458 | if (host->suspended || host->dpm_state == OFF) { |
1445 | host->dpm_state == REGSLEEP) { | ||
1446 | seq_printf(s, "host suspended, can't read registers\n"); | 1459 | seq_printf(s, "host suspended, can't read registers\n"); |
1447 | return 0; | 1460 | return 0; |
1448 | } | 1461 | } |
@@ -1617,7 +1630,8 @@ static int __init omap_mmc_probe(struct platform_device *pdev) | |||
1617 | mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; | 1630 | mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; |
1618 | mmc->max_seg_size = mmc->max_req_size; | 1631 | mmc->max_seg_size = mmc->max_req_size; |
1619 | 1632 | ||
1620 | mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; | 1633 | mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | |
1634 | MMC_CAP_WAIT_WHILE_BUSY; | ||
1621 | 1635 | ||
1622 | if (pdata->slots[host->slot_id].wires >= 8) | 1636 | if (pdata->slots[host->slot_id].wires >= 8) |
1623 | mmc->caps |= MMC_CAP_8_BIT_DATA; | 1637 | mmc->caps |= MMC_CAP_8_BIT_DATA; |