diff options
-rw-r--r-- | drivers/mmc/core/core.c | 177 | ||||
-rw-r--r-- | drivers/mmc/core/host.c | 1 | ||||
-rw-r--r-- | drivers/mmc/core/host.h | 2 | ||||
-rw-r--r-- | include/linux/mmc/host.h | 47 |
4 files changed, 221 insertions, 6 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index e22d2b5576ec..fb24a096dba8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c | |||
@@ -344,6 +344,101 @@ unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz) | |||
344 | EXPORT_SYMBOL(mmc_align_data_size); | 344 | EXPORT_SYMBOL(mmc_align_data_size); |
345 | 345 | ||
346 | /** | 346 | /** |
347 | * mmc_host_enable - enable a host. | ||
348 | * @host: mmc host to enable | ||
349 | * | ||
350 | * Hosts that support power saving can use the 'enable' and 'disable' | ||
351 | * methods to exit and enter power saving states. For more information | ||
352 | * see comments for struct mmc_host_ops. | ||
353 | */ | ||
354 | int mmc_host_enable(struct mmc_host *host) | ||
355 | { | ||
356 | if (!(host->caps & MMC_CAP_DISABLE)) | ||
357 | return 0; | ||
358 | |||
359 | if (host->en_dis_recurs) | ||
360 | return 0; | ||
361 | |||
362 | if (host->nesting_cnt++) | ||
363 | return 0; | ||
364 | |||
365 | cancel_delayed_work_sync(&host->disable); | ||
366 | |||
367 | if (host->enabled) | ||
368 | return 0; | ||
369 | |||
370 | if (host->ops->enable) { | ||
371 | int err; | ||
372 | |||
373 | host->en_dis_recurs = 1; | ||
374 | err = host->ops->enable(host); | ||
375 | host->en_dis_recurs = 0; | ||
376 | |||
377 | if (err) { | ||
378 | pr_debug("%s: enable error %d\n", | ||
379 | mmc_hostname(host), err); | ||
380 | return err; | ||
381 | } | ||
382 | } | ||
383 | host->enabled = 1; | ||
384 | return 0; | ||
385 | } | ||
386 | EXPORT_SYMBOL(mmc_host_enable); | ||
387 | |||
388 | static int mmc_host_do_disable(struct mmc_host *host, int lazy) | ||
389 | { | ||
390 | if (host->ops->disable) { | ||
391 | int err; | ||
392 | |||
393 | host->en_dis_recurs = 1; | ||
394 | err = host->ops->disable(host, lazy); | ||
395 | host->en_dis_recurs = 0; | ||
396 | |||
397 | if (err < 0) { | ||
398 | pr_debug("%s: disable error %d\n", | ||
399 | mmc_hostname(host), err); | ||
400 | return err; | ||
401 | } | ||
402 | if (err > 0) { | ||
403 | unsigned long delay = msecs_to_jiffies(err); | ||
404 | |||
405 | mmc_schedule_delayed_work(&host->disable, delay); | ||
406 | } | ||
407 | } | ||
408 | host->enabled = 0; | ||
409 | return 0; | ||
410 | } | ||
411 | |||
412 | /** | ||
413 | * mmc_host_disable - disable a host. | ||
414 | * @host: mmc host to disable | ||
415 | * | ||
416 | * Hosts that support power saving can use the 'enable' and 'disable' | ||
417 | * methods to exit and enter power saving states. For more information | ||
418 | * see comments for struct mmc_host_ops. | ||
419 | */ | ||
420 | int mmc_host_disable(struct mmc_host *host) | ||
421 | { | ||
422 | int err; | ||
423 | |||
424 | if (!(host->caps & MMC_CAP_DISABLE)) | ||
425 | return 0; | ||
426 | |||
427 | if (host->en_dis_recurs) | ||
428 | return 0; | ||
429 | |||
430 | if (--host->nesting_cnt) | ||
431 | return 0; | ||
432 | |||
433 | if (!host->enabled) | ||
434 | return 0; | ||
435 | |||
436 | err = mmc_host_do_disable(host, 0); | ||
437 | return err; | ||
438 | } | ||
439 | EXPORT_SYMBOL(mmc_host_disable); | ||
440 | |||
441 | /** | ||
347 | * __mmc_claim_host - exclusively claim a host | 442 | * __mmc_claim_host - exclusively claim a host |
348 | * @host: mmc host to claim | 443 | * @host: mmc host to claim |
349 | * @abort: whether or not the operation should be aborted | 444 | * @abort: whether or not the operation should be aborted |
@@ -379,11 +474,81 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) | |||
379 | wake_up(&host->wq); | 474 | wake_up(&host->wq); |
380 | spin_unlock_irqrestore(&host->lock, flags); | 475 | spin_unlock_irqrestore(&host->lock, flags); |
381 | remove_wait_queue(&host->wq, &wait); | 476 | remove_wait_queue(&host->wq, &wait); |
477 | if (!stop) | ||
478 | mmc_host_enable(host); | ||
382 | return stop; | 479 | return stop; |
383 | } | 480 | } |
384 | 481 | ||
385 | EXPORT_SYMBOL(__mmc_claim_host); | 482 | EXPORT_SYMBOL(__mmc_claim_host); |
386 | 483 | ||
484 | static int mmc_try_claim_host(struct mmc_host *host) | ||
485 | { | ||
486 | int claimed_host = 0; | ||
487 | unsigned long flags; | ||
488 | |||
489 | spin_lock_irqsave(&host->lock, flags); | ||
490 | if (!host->claimed) { | ||
491 | host->claimed = 1; | ||
492 | claimed_host = 1; | ||
493 | } | ||
494 | spin_unlock_irqrestore(&host->lock, flags); | ||
495 | return claimed_host; | ||
496 | } | ||
497 | |||
498 | static void mmc_do_release_host(struct mmc_host *host) | ||
499 | { | ||
500 | unsigned long flags; | ||
501 | |||
502 | spin_lock_irqsave(&host->lock, flags); | ||
503 | host->claimed = 0; | ||
504 | spin_unlock_irqrestore(&host->lock, flags); | ||
505 | |||
506 | wake_up(&host->wq); | ||
507 | } | ||
508 | |||
509 | void mmc_host_deeper_disable(struct work_struct *work) | ||
510 | { | ||
511 | struct mmc_host *host = | ||
512 | container_of(work, struct mmc_host, disable.work); | ||
513 | |||
514 | /* If the host is claimed then we do not want to disable it anymore */ | ||
515 | if (!mmc_try_claim_host(host)) | ||
516 | return; | ||
517 | mmc_host_do_disable(host, 1); | ||
518 | mmc_do_release_host(host); | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * mmc_host_lazy_disable - lazily disable a host. | ||
523 | * @host: mmc host to disable | ||
524 | * | ||
525 | * Hosts that support power saving can use the 'enable' and 'disable' | ||
526 | * methods to exit and enter power saving states. For more information | ||
527 | * see comments for struct mmc_host_ops. | ||
528 | */ | ||
529 | int mmc_host_lazy_disable(struct mmc_host *host) | ||
530 | { | ||
531 | if (!(host->caps & MMC_CAP_DISABLE)) | ||
532 | return 0; | ||
533 | |||
534 | if (host->en_dis_recurs) | ||
535 | return 0; | ||
536 | |||
537 | if (--host->nesting_cnt) | ||
538 | return 0; | ||
539 | |||
540 | if (!host->enabled) | ||
541 | return 0; | ||
542 | |||
543 | if (host->disable_delay) { | ||
544 | mmc_schedule_delayed_work(&host->disable, | ||
545 | msecs_to_jiffies(host->disable_delay)); | ||
546 | return 0; | ||
547 | } else | ||
548 | return mmc_host_do_disable(host, 1); | ||
549 | } | ||
550 | EXPORT_SYMBOL(mmc_host_lazy_disable); | ||
551 | |||
387 | /** | 552 | /** |
388 | * mmc_release_host - release a host | 553 | * mmc_release_host - release a host |
389 | * @host: mmc host to release | 554 | * @host: mmc host to release |
@@ -393,15 +558,11 @@ EXPORT_SYMBOL(__mmc_claim_host); | |||
393 | */ | 558 | */ |
394 | void mmc_release_host(struct mmc_host *host) | 559 | void mmc_release_host(struct mmc_host *host) |
395 | { | 560 | { |
396 | unsigned long flags; | ||
397 | |||
398 | WARN_ON(!host->claimed); | 561 | WARN_ON(!host->claimed); |
399 | 562 | ||
400 | spin_lock_irqsave(&host->lock, flags); | 563 | mmc_host_lazy_disable(host); |
401 | host->claimed = 0; | ||
402 | spin_unlock_irqrestore(&host->lock, flags); | ||
403 | 564 | ||
404 | wake_up(&host->wq); | 565 | mmc_do_release_host(host); |
405 | } | 566 | } |
406 | 567 | ||
407 | EXPORT_SYMBOL(mmc_release_host); | 568 | EXPORT_SYMBOL(mmc_release_host); |
@@ -953,6 +1114,8 @@ void mmc_stop_host(struct mmc_host *host) | |||
953 | spin_unlock_irqrestore(&host->lock, flags); | 1114 | spin_unlock_irqrestore(&host->lock, flags); |
954 | #endif | 1115 | #endif |
955 | 1116 | ||
1117 | if (host->caps & MMC_CAP_DISABLE) | ||
1118 | cancel_delayed_work(&host->disable); | ||
956 | cancel_delayed_work(&host->detect); | 1119 | cancel_delayed_work(&host->detect); |
957 | mmc_flush_scheduled_work(); | 1120 | mmc_flush_scheduled_work(); |
958 | 1121 | ||
@@ -981,6 +1144,8 @@ void mmc_stop_host(struct mmc_host *host) | |||
981 | */ | 1144 | */ |
982 | int mmc_suspend_host(struct mmc_host *host, pm_message_t state) | 1145 | int mmc_suspend_host(struct mmc_host *host, pm_message_t state) |
983 | { | 1146 | { |
1147 | if (host->caps & MMC_CAP_DISABLE) | ||
1148 | cancel_delayed_work(&host->disable); | ||
984 | cancel_delayed_work(&host->detect); | 1149 | cancel_delayed_work(&host->detect); |
985 | mmc_flush_scheduled_work(); | 1150 | mmc_flush_scheduled_work(); |
986 | 1151 | ||
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 5e945e64ead7..a268d12f1af0 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c | |||
@@ -83,6 +83,7 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) | |||
83 | spin_lock_init(&host->lock); | 83 | spin_lock_init(&host->lock); |
84 | init_waitqueue_head(&host->wq); | 84 | init_waitqueue_head(&host->wq); |
85 | INIT_DELAYED_WORK(&host->detect, mmc_rescan); | 85 | INIT_DELAYED_WORK(&host->detect, mmc_rescan); |
86 | INIT_DELAYED_WORK_DEFERRABLE(&host->disable, mmc_host_deeper_disable); | ||
86 | 87 | ||
87 | /* | 88 | /* |
88 | * By default, hosts do not support SGIO or large requests. | 89 | * By default, hosts do not support SGIO or large requests. |
diff --git a/drivers/mmc/core/host.h b/drivers/mmc/core/host.h index c2dc3d2d9f9a..8c87e1109a34 100644 --- a/drivers/mmc/core/host.h +++ b/drivers/mmc/core/host.h | |||
@@ -14,5 +14,7 @@ | |||
14 | int mmc_register_host_class(void); | 14 | int mmc_register_host_class(void); |
15 | void mmc_unregister_host_class(void); | 15 | void mmc_unregister_host_class(void); |
16 | 16 | ||
17 | void mmc_host_deeper_disable(struct work_struct *work); | ||
18 | |||
17 | #endif | 19 | #endif |
18 | 20 | ||
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 3e7615e9087e..338a9b3d51e4 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h | |||
@@ -51,6 +51,35 @@ struct mmc_ios { | |||
51 | }; | 51 | }; |
52 | 52 | ||
53 | struct mmc_host_ops { | 53 | struct mmc_host_ops { |
54 | /* | ||
55 | * Hosts that support power saving can use the 'enable' and 'disable' | ||
56 | * methods to exit and enter power saving states. 'enable' is called | ||
57 | * when the host is claimed and 'disable' is called (or scheduled with | ||
58 | * a delay) when the host is released. The 'disable' is scheduled if | ||
59 | * the disable delay set by 'mmc_set_disable_delay()' is non-zero, | ||
60 | * otherwise 'disable' is called immediately. 'disable' may be | ||
61 | * scheduled repeatedly, to permit ever greater power saving at the | ||
62 | * expense of ever greater latency to re-enable. Rescheduling is | ||
63 | * determined by the return value of the 'disable' method. A positive | ||
64 | * value gives the delay in milliseconds. | ||
65 | * | ||
66 | * In the case where a host function (like set_ios) may be called | ||
67 | * with or without the host claimed, enabling and disabling can be | ||
68 | * done directly and will nest correctly. Call 'mmc_host_enable()' and | ||
69 | * 'mmc_host_lazy_disable()' for this purpose, but note that these | ||
70 | * functions must be paired. | ||
71 | * | ||
72 | * Alternatively, 'mmc_host_enable()' may be paired with | ||
73 | * 'mmc_host_disable()' which calls 'disable' immediately. In this | ||
74 | * case the 'disable' method will be called with 'lazy' set to 0. | ||
75 | * This is mainly useful for error paths. | ||
76 | * | ||
77 | * Because lazy disable may be called from a work queue, the 'disable' | ||
78 | * method must claim the host when 'lazy' != 0, which will work | ||
79 | * correctly because recursion is detected and handled. | ||
80 | */ | ||
81 | int (*enable)(struct mmc_host *host); | ||
82 | int (*disable)(struct mmc_host *host, int lazy); | ||
54 | void (*request)(struct mmc_host *host, struct mmc_request *req); | 83 | void (*request)(struct mmc_host *host, struct mmc_request *req); |
55 | /* | 84 | /* |
56 | * Avoid calling these three functions too often or in a "fast path", | 85 | * Avoid calling these three functions too often or in a "fast path", |
@@ -118,6 +147,7 @@ struct mmc_host { | |||
118 | #define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */ | 147 | #define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */ |
119 | #define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */ | 148 | #define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */ |
120 | #define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */ | 149 | #define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */ |
150 | #define MMC_CAP_DISABLE (1 << 7) /* Can the host be disabled */ | ||
121 | 151 | ||
122 | /* host specific block data */ | 152 | /* host specific block data */ |
123 | unsigned int max_seg_size; /* see blk_queue_max_segment_size */ | 153 | unsigned int max_seg_size; /* see blk_queue_max_segment_size */ |
@@ -142,6 +172,13 @@ struct mmc_host { | |||
142 | unsigned int removed:1; /* host is being removed */ | 172 | unsigned int removed:1; /* host is being removed */ |
143 | #endif | 173 | #endif |
144 | 174 | ||
175 | /* Only used with MMC_CAP_DISABLE */ | ||
176 | int enabled; /* host is enabled */ | ||
177 | int nesting_cnt; /* "enable" nesting count */ | ||
178 | int en_dis_recurs; /* detect recursion */ | ||
179 | unsigned int disable_delay; /* disable delay in msecs */ | ||
180 | struct delayed_work disable; /* disabling work */ | ||
181 | |||
145 | struct mmc_card *card; /* device attached to this host */ | 182 | struct mmc_card *card; /* device attached to this host */ |
146 | 183 | ||
147 | wait_queue_head_t wq; | 184 | wait_queue_head_t wq; |
@@ -197,5 +234,15 @@ struct regulator; | |||
197 | int mmc_regulator_get_ocrmask(struct regulator *supply); | 234 | int mmc_regulator_get_ocrmask(struct regulator *supply); |
198 | int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit); | 235 | int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit); |
199 | 236 | ||
237 | int mmc_host_enable(struct mmc_host *host); | ||
238 | int mmc_host_disable(struct mmc_host *host); | ||
239 | int mmc_host_lazy_disable(struct mmc_host *host); | ||
240 | |||
241 | static inline void mmc_set_disable_delay(struct mmc_host *host, | ||
242 | unsigned int disable_delay) | ||
243 | { | ||
244 | host->disable_delay = disable_delay; | ||
245 | } | ||
246 | |||
200 | #endif | 247 | #endif |
201 | 248 | ||