From 810e08ee425f8d7327e4b30069a9b2b3dccfc67d Mon Sep 17 00:00:00 2001 From: Fredrik Soderstedt Date: Wed, 17 Apr 2013 13:50:53 +0200 Subject: mmc: core: Only execute tuning for SDR50 and SDR104 Only execute tuning for sd and sdio devices that are using SDR50 or SDR104. Make sure clock is hold during tuning for sdio devices. Signed-off-by: Fredrik Soderstedt Acked-by: Johan Rudholm Acked-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sd.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers/mmc/core/sd.c') diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 9e645e19cec..8373d2288bb 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -646,8 +646,13 @@ static int mmc_sd_init_uhs_card(struct mmc_card *card) if (err) goto out; - /* SPI mode doesn't define CMD19 */ - if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning) { + /* + * SPI mode doesn't define CMD19 and tuning is only valid for SDR50 and + * SDR104 mode SD-cards. Note that tuning is mandatory for SDR104. + */ + if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning && + (card->sd_bus_speed == UHS_SDR50_BUS_SPEED || + card->sd_bus_speed == UHS_SDR104_BUS_SPEED)) { mmc_host_clk_hold(card->host); err = card->host->ops->execute_tuning(card->host, MMC_SEND_TUNING_BLOCK); -- cgit v1.2.3-70-g09d2 From e94cfef698aae6b209d8918dd319312e4b02118d Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 2 May 2013 14:02:38 +0200 Subject: mmc: block: Enable runtime pm for mmc blkdevice Once the mmc blkdevice is being probed, runtime pm will be enabled. By using runtime autosuspend, the power save operations can be done when request inactivity occurs for a certain time. Right now the selected timeout value is set to 3 s. Obviously this value will likely need to be configurable somehow since it needs to be trimmed depending on the power save algorithm. For SD-combo cards, we are still leaving the enablement of runtime PM to the SDIO init sequence since it depends on the capabilities of the SDIO func driver. Moreover, when the blk device is being suspended, we make sure the device will be runtime resumed. The reason for doing this is that we want the host suspend sequence to be unaware of any runtime power save operations done for the card in this phase. Thus it can just handle the suspend as the card is fully powered from a runtime perspective. Finally, this patch prepares to make it possible to move BKOPS handling into the runtime callbacks for the mmc bus_ops. Thus IDLE BKOPS can be accomplished. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 32 ++++++++++++++++++++++++++------ drivers/mmc/core/core.c | 23 +++++++++++++++++++++++ drivers/mmc/core/debugfs.c | 8 ++++---- drivers/mmc/core/mmc.c | 4 ++-- drivers/mmc/core/sd.c | 4 ++-- include/linux/mmc/core.h | 3 +++ 6 files changed, 60 insertions(+), 14 deletions(-) (limited to 'drivers/mmc/core/sd.c') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 80b05b28024..c900d2818aa 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -224,7 +225,7 @@ static ssize_t power_ro_lock_store(struct device *dev, md = mmc_blk_get(dev_to_disk(dev)); card = md->queue.card; - mmc_claim_host(card->host); + mmc_get_card(card); ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP, card->ext_csd.boot_ro_lock | @@ -235,7 +236,7 @@ static ssize_t power_ro_lock_store(struct device *dev, else card->ext_csd.boot_ro_lock |= EXT_CSD_BOOT_WP_B_PWR_WP_EN; - mmc_release_host(card->host); + mmc_put_card(card); if (!ret) { pr_info("%s: Locking boot partition ro until next power on\n", @@ -522,7 +523,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, mrq.cmd = &cmd; - mmc_claim_host(card->host); + mmc_get_card(card); err = mmc_blk_part_switch(card, md); if (err) @@ -599,7 +600,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, } cmd_rel_host: - mmc_release_host(card->host); + mmc_put_card(card); cmd_done: mmc_blk_put(md); @@ -1921,7 +1922,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) if (req && !mq->mqrq_prev->req) /* claim host only for the first request */ - mmc_claim_host(card->host); + mmc_get_card(card); ret = mmc_blk_part_switch(card, md); if (ret) { @@ -1965,7 +1966,7 @@ out: * In case sepecial request, there is no reentry to * the 'mmc_blk_issue_rq' with 'mqrq_prev->req'. */ - mmc_release_host(card->host); + mmc_put_card(card); return ret; } @@ -2362,6 +2363,19 @@ static int mmc_blk_probe(struct mmc_card *card) if (mmc_add_disk(part_md)) goto out; } + + pm_runtime_set_autosuspend_delay(&card->dev, 3000); + pm_runtime_use_autosuspend(&card->dev); + + /* + * Don't enable runtime PM for SD-combo cards here. Leave that + * decision to be taken during the SDIO init sequence instead. + */ + if (card->type != MMC_TYPE_SD_COMBO) { + pm_runtime_set_active(&card->dev); + pm_runtime_enable(&card->dev); + } + return 0; out: @@ -2375,9 +2389,13 @@ static void mmc_blk_remove(struct mmc_card *card) struct mmc_blk_data *md = mmc_get_drvdata(card); mmc_blk_remove_parts(card, md); + pm_runtime_get_sync(&card->dev); mmc_claim_host(card->host); mmc_blk_part_switch(card, md); mmc_release_host(card->host); + if (card->type != MMC_TYPE_SD_COMBO) + pm_runtime_disable(&card->dev); + pm_runtime_put_noidle(&card->dev); mmc_blk_remove_req(md); mmc_set_drvdata(card, NULL); } @@ -2389,6 +2407,7 @@ static int mmc_blk_suspend(struct mmc_card *card) struct mmc_blk_data *md = mmc_get_drvdata(card); if (md) { + pm_runtime_get_sync(&card->dev); mmc_queue_suspend(&md->queue); list_for_each_entry(part_md, &md->part, part) { mmc_queue_suspend(&part_md->queue); @@ -2412,6 +2431,7 @@ static int mmc_blk_resume(struct mmc_card *card) list_for_each_entry(part_md, &md->part, part) { mmc_queue_resume(&part_md->queue); } + pm_runtime_put(&card->dev); } return 0; } diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index dc0cb5929c6..0f86144b0c5 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -970,6 +970,29 @@ void mmc_release_host(struct mmc_host *host) } EXPORT_SYMBOL(mmc_release_host); +/* + * This is a helper function, which fetches a runtime pm reference for the + * card device and also claims the host. + */ +void mmc_get_card(struct mmc_card *card) +{ + pm_runtime_get_sync(&card->dev); + mmc_claim_host(card->host); +} +EXPORT_SYMBOL(mmc_get_card); + +/* + * This is a helper function, which releases the host and drops the runtime + * pm reference for the card device. + */ +void mmc_put_card(struct mmc_card *card) +{ + mmc_release_host(card->host); + pm_runtime_mark_last_busy(&card->dev); + pm_runtime_put_autosuspend(&card->dev); +} +EXPORT_SYMBOL(mmc_put_card); + /* * Internal function that does the actual ios call to the host driver, * optionally printing some debug output. diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 35c2f85b195..54829c0ed00 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -258,13 +258,13 @@ static int mmc_dbg_card_status_get(void *data, u64 *val) u32 status; int ret; - mmc_claim_host(card->host); + mmc_get_card(card); ret = mmc_send_status(data, &status); if (!ret) *val = status; - mmc_release_host(card->host); + mmc_put_card(card); return ret; } @@ -291,9 +291,9 @@ static int mmc_ext_csd_open(struct inode *inode, struct file *filp) goto out_free; } - mmc_claim_host(card->host); + mmc_get_card(card); err = mmc_send_ext_csd(card, ext_csd); - mmc_release_host(card->host); + mmc_put_card(card); if (err) goto out_free; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index a0469cf7933..903f3815018 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1380,14 +1380,14 @@ static void mmc_detect(struct mmc_host *host) BUG_ON(!host); BUG_ON(!host->card); - mmc_claim_host(host); + mmc_get_card(host->card); /* * Just check if our card has been removed. */ err = _mmc_detect_card_removed(host); - mmc_release_host(host); + mmc_put_card(host->card); if (err) { mmc_remove(host); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 8373d2288bb..17fa2d271dd 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1042,14 +1042,14 @@ static void mmc_sd_detect(struct mmc_host *host) BUG_ON(!host); BUG_ON(!host->card); - mmc_claim_host(host); + mmc_get_card(host->card); /* * Just check if our card has been removed. */ err = _mmc_detect_card_removed(host); - mmc_release_host(host); + mmc_put_card(host->card); if (err) { mmc_sd_remove(host); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index bd06ff566bf..443243b241d 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -190,6 +190,9 @@ extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); extern void mmc_release_host(struct mmc_host *host); extern int mmc_try_claim_host(struct mmc_host *host); +extern void mmc_get_card(struct mmc_card *card); +extern void mmc_put_card(struct mmc_card *card); + extern int mmc_flush_cache(struct mmc_card *); extern int mmc_detect_card_removed(struct mmc_host *host); -- cgit v1.2.3-70-g09d2 From c4d770d72492df510077b277f21ac5f0dad9e5eb Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 2 May 2013 14:02:39 +0200 Subject: mmc: core: Support aggressive power management for (e)MMC/SD Aggressive power management is suitable when saving power is essential. At request inactivity timeout, aka pm runtime autosuspend timeout, the card will be suspended. Once a new request arrives, the card will be re-initalized and thus the first request will suffer from a latency. This latency is card-specific, experiments has shown in general that SD-cards has quite poor initialization time, around 300ms-1100ms. eMMC is not surprisingly far better but still a couple of hundreds of ms has been observed. Except for the request latency, it is important to know that suspending the card will also prevent the card from executing internal house-keeping operations in idle mode. This could mean degradation in performance. To use this feature make sure the request inactivity timeout is chosen carefully. This has not been done as a part of this patch. Enable this feature by using host cap MMC_CAP_AGGRESSIVE_PM and by setting CONFIG_MMC_UNSAFE_RESUME. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sd.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/host.h | 2 +- 3 files changed, 100 insertions(+), 1 deletion(-) (limited to 'drivers/mmc/core/sd.c') diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 903f3815018..506f4ee84e1 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1454,6 +1454,54 @@ static int mmc_resume(struct mmc_host *host) return err; } + +/* + * Callback for runtime_suspend. + */ +static int mmc_runtime_suspend(struct mmc_host *host) +{ + int err; + + if (!(host->caps & MMC_CAP_AGGRESSIVE_PM)) + return 0; + + mmc_claim_host(host); + + err = mmc_suspend(host); + if (err) { + pr_err("%s: error %d doing aggessive suspend\n", + mmc_hostname(host), err); + goto out; + } + mmc_power_off(host); + +out: + mmc_release_host(host); + return err; +} + +/* + * Callback for runtime_resume. + */ +static int mmc_runtime_resume(struct mmc_host *host) +{ + int err; + + if (!(host->caps & MMC_CAP_AGGRESSIVE_PM)) + return 0; + + mmc_claim_host(host); + + mmc_power_up(host); + err = mmc_resume(host); + if (err) + pr_err("%s: error %d doing aggessive resume\n", + mmc_hostname(host), err); + + mmc_release_host(host); + return 0; +} + static int mmc_power_restore(struct mmc_host *host) { int ret; @@ -1514,6 +1562,8 @@ static const struct mmc_bus_ops mmc_ops_unsafe = { .detect = mmc_detect, .suspend = mmc_suspend, .resume = mmc_resume, + .runtime_suspend = mmc_runtime_suspend, + .runtime_resume = mmc_runtime_resume, .power_restore = mmc_power_restore, .alive = mmc_alive, }; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 17fa2d271dd..aeaae7c3b22 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1100,6 +1100,53 @@ static int mmc_sd_resume(struct mmc_host *host) return err; } +/* + * Callback for runtime_suspend. + */ +static int mmc_sd_runtime_suspend(struct mmc_host *host) +{ + int err; + + if (!(host->caps & MMC_CAP_AGGRESSIVE_PM)) + return 0; + + mmc_claim_host(host); + + err = mmc_sd_suspend(host); + if (err) { + pr_err("%s: error %d doing aggessive suspend\n", + mmc_hostname(host), err); + goto out; + } + mmc_power_off(host); + +out: + mmc_release_host(host); + return err; +} + +/* + * Callback for runtime_resume. + */ +static int mmc_sd_runtime_resume(struct mmc_host *host) +{ + int err; + + if (!(host->caps & MMC_CAP_AGGRESSIVE_PM)) + return 0; + + mmc_claim_host(host); + + mmc_power_up(host); + err = mmc_sd_resume(host); + if (err) + pr_err("%s: error %d doing aggessive resume\n", + mmc_hostname(host), err); + + mmc_release_host(host); + return 0; +} + static int mmc_sd_power_restore(struct mmc_host *host) { int ret; @@ -1124,6 +1171,8 @@ static const struct mmc_bus_ops mmc_sd_ops = { static const struct mmc_bus_ops mmc_sd_ops_unsafe = { .remove = mmc_sd_remove, .detect = mmc_sd_detect, + .runtime_suspend = mmc_sd_runtime_suspend, + .runtime_resume = mmc_sd_runtime_resume, .suspend = mmc_sd_suspend, .resume = mmc_sd_resume, .power_restore = mmc_sd_power_restore, diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 6f3851533de..374098bae83 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -239,7 +239,7 @@ struct mmc_host { #define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */ #define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */ #define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */ - +#define MMC_CAP_AGGRESSIVE_PM (1 << 7) /* Suspend (e)MMC/SD at idle */ #define MMC_CAP_NONREMOVABLE (1 << 8) /* Nonremovable e.g. eMMC */ #define MMC_CAP_WAIT_WHILE_BUSY (1 << 9) /* Waits while card is busy */ #define MMC_CAP_ERASE (1 << 10) /* Allow erase/trim commands */ -- cgit v1.2.3-70-g09d2 From 74590263384e5d4601de7f0ee2790477578829ea Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 10 Jun 2013 17:03:38 +0200 Subject: mmc: core: Push common suspend|resume code into each bus_ops By moving code from the mmc_suspend|resume_host down into each .suspend|resume bus_ops callback, we get a more flexible solution. Some nice side effects are that we get a better understanding of each bus_ops suspend|resume sequence and the common code don't have to take care of specific corner cases, especially for the SDIO case. Signed-off-by: Ulf Hansson Tested-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 31 +++---------------------------- drivers/mmc/core/mmc.c | 4 ++++ drivers/mmc/core/sd.c | 4 ++++ drivers/mmc/core/sdio.c | 21 +++++++++++++++++++++ 4 files changed, 32 insertions(+), 28 deletions(-) (limited to 'drivers/mmc/core/sd.c') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 7a8a42d3a03..da3b9078ea6 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2621,9 +2621,6 @@ int mmc_suspend_host(struct mmc_host *host) { int err = 0; - cancel_delayed_work(&host->detect); - mmc_flush_scheduled_work(); - mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { if (host->bus_ops->suspend) @@ -2631,9 +2628,6 @@ int mmc_suspend_host(struct mmc_host *host) } mmc_bus_put(host); - if (!err && !mmc_card_keep_power(host)) - mmc_power_off(host); - return err; } EXPORT_SYMBOL(mmc_suspend_host); @@ -2644,39 +2638,20 @@ EXPORT_SYMBOL(mmc_suspend_host); */ int mmc_resume_host(struct mmc_host *host) { - int err = 0; + int err; mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { - if (!mmc_card_keep_power(host)) { - mmc_power_up(host); - mmc_select_voltage(host, host->ocr); - /* - * Tell runtime PM core we just powered up the card, - * since it still believes the card is powered off. - * Note that currently runtime PM is only enabled - * for SDIO cards that are MMC_CAP_POWER_OFF_CARD - */ - if (mmc_card_sdio(host->card) && - (host->caps & MMC_CAP_POWER_OFF_CARD)) { - pm_runtime_disable(&host->card->dev); - pm_runtime_set_active(&host->card->dev); - pm_runtime_enable(&host->card->dev); - } - } BUG_ON(!host->bus_ops->resume); err = host->bus_ops->resume(host); - if (err) { + if (err) pr_warning("%s: error %d during resume " "(card was removed?)\n", mmc_hostname(host), err); - err = 0; - } } - host->pm_flags &= ~MMC_PM_KEEP_POWER; mmc_bus_put(host); - return err; + return 0; } EXPORT_SYMBOL(mmc_resume_host); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 91326099082..c8c7135c84b 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1494,6 +1494,8 @@ static int mmc_suspend(struct mmc_host *host) err = mmc_deselect_cards(host); host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); + if (!err) + mmc_power_off(host); out: mmc_release_host(host); return err; @@ -1513,6 +1515,8 @@ static int mmc_resume(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); + mmc_power_up(host); + mmc_select_voltage(host, host->ocr); err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index aeaae7c3b22..cacef27b00d 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1075,6 +1075,8 @@ static int mmc_sd_suspend(struct mmc_host *host) if (!mmc_host_is_spi(host)) err = mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; + if (!err) + mmc_power_off(host); mmc_release_host(host); return err; @@ -1094,6 +1096,8 @@ static int mmc_sd_resume(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); + mmc_power_up(host); + mmc_select_voltage(host, host->ocr); err = mmc_sd_init_card(host, host->ocr, host->card); mmc_release_host(host); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index be8cca8d302..80d89cff730 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -963,6 +963,9 @@ static int mmc_sdio_suspend(struct mmc_host *host) mmc_release_host(host); } + if (!err && !mmc_card_keep_power(host)) + mmc_power_off(host); + return err; } @@ -976,6 +979,23 @@ static int mmc_sdio_resume(struct mmc_host *host) /* Basic card reinitialization. */ mmc_claim_host(host); + /* Restore power if needed */ + if (!mmc_card_keep_power(host)) { + mmc_power_up(host); + mmc_select_voltage(host, host->ocr); + /* + * Tell runtime PM core we just powered up the card, + * since it still believes the card is powered off. + * Note that currently runtime PM is only enabled + * for SDIO cards that are MMC_CAP_POWER_OFF_CARD + */ + if (host->caps & MMC_CAP_POWER_OFF_CARD) { + pm_runtime_disable(&host->card->dev); + pm_runtime_set_active(&host->card->dev); + pm_runtime_enable(&host->card->dev); + } + } + /* No need to reinitialize powered-resumed nonremovable cards */ if (mmc_card_is_removable(host) || !mmc_card_keep_power(host)) { sdio_reset(host); @@ -1013,6 +1033,7 @@ static int mmc_sdio_resume(struct mmc_host *host) } } + host->pm_flags &= ~MMC_PM_KEEP_POWER; return err; } -- cgit v1.2.3-70-g09d2 From 5992e78697484d8f58b749f87a06f89f4ba40a47 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 10 Jun 2013 17:03:42 +0200 Subject: mmc: core: Add shutdown callback for SD bus_ops For the SD .shutdown callback we re-use the SD suspend function since it performs the relevant actions. Signed-off-by: Ulf Hansson Signed-off-by: Chris Ball --- drivers/mmc/core/sd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/mmc/core/sd.c') diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index cacef27b00d..176d125f5b5 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1170,6 +1170,7 @@ static const struct mmc_bus_ops mmc_sd_ops = { .resume = NULL, .power_restore = mmc_sd_power_restore, .alive = mmc_sd_alive, + .shutdown = mmc_sd_suspend, }; static const struct mmc_bus_ops mmc_sd_ops_unsafe = { @@ -1181,6 +1182,7 @@ static const struct mmc_bus_ops mmc_sd_ops_unsafe = { .resume = mmc_sd_resume, .power_restore = mmc_sd_power_restore, .alive = mmc_sd_alive, + .shutdown = mmc_sd_suspend, }; static void mmc_sd_attach_bus_ops(struct mmc_host *host) -- cgit v1.2.3-70-g09d2