aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrian Hunter <adrian.hunter@nokia.com>2009-09-22 19:44:29 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-23 10:39:33 -0400
commit8ea926b22e2d13238e4d65d8f61c48fe424e6f4f (patch)
treed62846c0f7aa50afce52bd8a9ed4b2affeaa1889
parent27cce39f555def6f5ebe7f03d69ccc44ab25f0b2 (diff)
mmc: add 'enable' and 'disable' methods to mmc host
MMC hosts that support power saving can use the 'enable' and 'disable' methods to exit and enter power saving states. An explanation of their use is provided in the comments added to include/linux/mmc/host.h. 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>
-rw-r--r--drivers/mmc/core/core.c177
-rw-r--r--drivers/mmc/core/host.c1
-rw-r--r--drivers/mmc/core/host.h2
-rw-r--r--include/linux/mmc/host.h47
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)
344EXPORT_SYMBOL(mmc_align_data_size); 344EXPORT_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 */
354int 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}
386EXPORT_SYMBOL(mmc_host_enable);
387
388static 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 */
420int 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}
439EXPORT_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
385EXPORT_SYMBOL(__mmc_claim_host); 482EXPORT_SYMBOL(__mmc_claim_host);
386 483
484static 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
498static 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
509void 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 */
529int 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}
550EXPORT_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 */
394void mmc_release_host(struct mmc_host *host) 559void 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
407EXPORT_SYMBOL(mmc_release_host); 568EXPORT_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 */
982int mmc_suspend_host(struct mmc_host *host, pm_message_t state) 1145int 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 @@
14int mmc_register_host_class(void); 14int mmc_register_host_class(void);
15void mmc_unregister_host_class(void); 15void mmc_unregister_host_class(void);
16 16
17void 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
53struct mmc_host_ops { 53struct 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;
197int mmc_regulator_get_ocrmask(struct regulator *supply); 234int mmc_regulator_get_ocrmask(struct regulator *supply);
198int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit); 235int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit);
199 236
237int mmc_host_enable(struct mmc_host *host);
238int mmc_host_disable(struct mmc_host *host);
239int mmc_host_lazy_disable(struct mmc_host *host);
240
241static 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