mmc: sdhci-pci: add runtime pm support
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 3 Oct 2011 12:33:34 +0000 (15:33 +0300)
committerChris Ball <cjb@laptop.org>
Wed, 26 Oct 2011 20:32:20 +0000 (16:32 -0400)
Ths patch allows runtime PM for sdhci-pci, runtime suspending after
inactivity of 50ms and ensuring runtime resume before SDHC registers
are accessed.  During runtime suspend, interrupts are masked.
The host controller state is restored at runtime resume.

For Medfield, the host controller's card detect mechanism is
supplanted by an always-on GPIO which provides for card detect wake-up.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/host/sdhci-pci.c
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h
include/linux/mmc/sdhci.h

index 4487b84..f49b184 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/io.h>
 #include <linux/gpio.h>
 #include <linux/sfi.h>
 #include <linux/io.h>
 #include <linux/gpio.h>
 #include <linux/sfi.h>
+#include <linux/pm_runtime.h>
 
 #include "sdhci.h"
 
 
 #include "sdhci.h"
 
@@ -63,6 +64,8 @@ struct sdhci_pci_slot {
 
        int                     pci_bar;
        int                     rst_n_gpio;
 
        int                     pci_bar;
        int                     rst_n_gpio;
+       int                     cd_gpio;
+       int                     cd_irq;
 };
 
 struct sdhci_pci_chip {
 };
 
 struct sdhci_pci_chip {
@@ -190,6 +193,70 @@ static int mfd_emmc_gpio_parse(struct sfi_table_header *table)
        return 0;
 }
 
        return 0;
 }
 
+#ifdef CONFIG_PM_RUNTIME
+
+static irqreturn_t mfd_sd_cd(int irq, void *dev_id)
+{
+       struct sdhci_pci_slot *slot = dev_id;
+       struct sdhci_host *host = slot->host;
+
+       mmc_detect_change(host->mmc, msecs_to_jiffies(200));
+       return IRQ_HANDLED;
+}
+
+#define MFLD_SD_CD_PIN 69
+
+static int mfd_sd_probe_slot(struct sdhci_pci_slot *slot)
+{
+       int err, irq, gpio = MFLD_SD_CD_PIN;
+
+       slot->cd_gpio = -EINVAL;
+       slot->cd_irq = -EINVAL;
+
+       err = gpio_request(gpio, "sd_cd");
+       if (err < 0)
+               goto out;
+
+       err = gpio_direction_input(gpio);
+       if (err < 0)
+               goto out_free;
+
+       irq = gpio_to_irq(gpio);
+       if (irq < 0)
+               goto out_free;
+
+       err = request_irq(irq, mfd_sd_cd, IRQF_TRIGGER_RISING |
+                         IRQF_TRIGGER_FALLING, "sd_cd", slot);
+       if (err)
+               goto out_free;
+
+       slot->cd_gpio = gpio;
+       slot->cd_irq = irq;
+       slot->host->quirks2 |= SDHCI_QUIRK2_OWN_CARD_DETECTION;
+
+       return 0;
+
+out_free:
+       gpio_free(gpio);
+out:
+       dev_warn(&slot->chip->pdev->dev, "failed to setup card detect wake up\n");
+       return 0;
+}
+
+static void mfd_sd_remove_slot(struct sdhci_pci_slot *slot, int dead)
+{
+       if (slot->cd_irq >= 0)
+               free_irq(slot->cd_irq, slot);
+       gpio_free(slot->cd_gpio);
+}
+
+#else
+
+#define mfd_sd_probe_slot      NULL
+#define mfd_sd_remove_slot     NULL
+
+#endif
+
 static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot)
 {
        const char *name = NULL;
 static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot)
 {
        const char *name = NULL;
@@ -214,7 +281,7 @@ static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot)
                slot->host->mmc->caps |= MMC_CAP_HW_RESET;
        }
 
                slot->host->mmc->caps |= MMC_CAP_HW_RESET;
        }
 
-       slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA;
+       slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE;
 
        slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC;
 
 
        slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC;
 
@@ -238,6 +305,8 @@ static const struct sdhci_pci_fixes sdhci_intel_mrst_hc1_hc2 = {
 
 static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = {
        .quirks         = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
 
 static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = {
        .quirks         = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
+       .probe_slot     = mfd_sd_probe_slot,
+       .remove_slot    = mfd_sd_remove_slot,
 };
 
 static const struct sdhci_pci_fixes sdhci_intel_mfd_sdio = {
 };
 
 static const struct sdhci_pci_fixes sdhci_intel_mfd_sdio = {
@@ -1018,6 +1087,95 @@ static int sdhci_pci_resume(struct pci_dev *pdev)
 
 #endif /* CONFIG_PM */
 
 
 #endif /* CONFIG_PM */
 
+#ifdef CONFIG_PM_RUNTIME
+
+static int sdhci_pci_runtime_suspend(struct device *dev)
+{
+       struct pci_dev *pdev = container_of(dev, struct pci_dev, dev);
+       struct sdhci_pci_chip *chip;
+       struct sdhci_pci_slot *slot;
+       pm_message_t state = { .event = PM_EVENT_SUSPEND };
+       int i, ret;
+
+       chip = pci_get_drvdata(pdev);
+       if (!chip)
+               return 0;
+
+       for (i = 0; i < chip->num_slots; i++) {
+               slot = chip->slots[i];
+               if (!slot)
+                       continue;
+
+               ret = sdhci_runtime_suspend_host(slot->host);
+
+               if (ret) {
+                       for (i--; i >= 0; i--)
+                               sdhci_runtime_resume_host(chip->slots[i]->host);
+                       return ret;
+               }
+       }
+
+       if (chip->fixes && chip->fixes->suspend) {
+               ret = chip->fixes->suspend(chip, state);
+               if (ret) {
+                       for (i = chip->num_slots - 1; i >= 0; i--)
+                               sdhci_runtime_resume_host(chip->slots[i]->host);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int sdhci_pci_runtime_resume(struct device *dev)
+{
+       struct pci_dev *pdev = container_of(dev, struct pci_dev, dev);
+       struct sdhci_pci_chip *chip;
+       struct sdhci_pci_slot *slot;
+       int i, ret;
+
+       chip = pci_get_drvdata(pdev);
+       if (!chip)
+               return 0;
+
+       if (chip->fixes && chip->fixes->resume) {
+               ret = chip->fixes->resume(chip);
+               if (ret)
+                       return ret;
+       }
+
+       for (i = 0; i < chip->num_slots; i++) {
+               slot = chip->slots[i];
+               if (!slot)
+                       continue;
+
+               ret = sdhci_runtime_resume_host(slot->host);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int sdhci_pci_runtime_idle(struct device *dev)
+{
+       return 0;
+}
+
+#else
+
+#define sdhci_pci_runtime_suspend      NULL
+#define sdhci_pci_runtime_resume       NULL
+#define sdhci_pci_runtime_idle         NULL
+
+#endif
+
+static const struct dev_pm_ops sdhci_pci_pm_ops = {
+       .runtime_suspend = sdhci_pci_runtime_suspend,
+       .runtime_resume = sdhci_pci_runtime_resume,
+       .runtime_idle = sdhci_pci_runtime_idle,
+};
+
 /*****************************************************************************\
  *                                                                           *
  * Device probing/removal                                                    *
 /*****************************************************************************\
  *                                                                           *
  * Device probing/removal                                                    *
@@ -1133,6 +1291,21 @@ static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
        sdhci_free_host(slot->host);
 }
 
        sdhci_free_host(slot->host);
 }
 
+static void __devinit sdhci_pci_runtime_pm_allow(struct device *dev)
+{
+       pm_runtime_put_noidle(dev);
+       pm_runtime_allow(dev);
+       pm_runtime_set_autosuspend_delay(dev, 50);
+       pm_runtime_use_autosuspend(dev);
+       pm_suspend_ignore_children(dev, 1);
+}
+
+static void __devexit sdhci_pci_runtime_pm_forbid(struct device *dev)
+{
+       pm_runtime_forbid(dev);
+       pm_runtime_get_noresume(dev);
+}
+
 static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
                                     const struct pci_device_id *ent)
 {
 static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
                                     const struct pci_device_id *ent)
 {
@@ -1208,6 +1381,8 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
                chip->slots[i] = slot;
        }
 
                chip->slots[i] = slot;
        }
 
+       sdhci_pci_runtime_pm_allow(&pdev->dev);
+
        return 0;
 
 free:
        return 0;
 
 free:
@@ -1224,6 +1399,8 @@ static void __devexit sdhci_pci_remove(struct pci_dev *pdev)
        int i;
        struct sdhci_pci_chip *chip;
 
        int i;
        struct sdhci_pci_chip *chip;
 
+       sdhci_pci_runtime_pm_forbid(&pdev->dev);
+
        chip = pci_get_drvdata(pdev);
 
        if (chip) {
        chip = pci_get_drvdata(pdev);
 
        if (chip) {
@@ -1244,6 +1421,9 @@ static struct pci_driver sdhci_driver = {
        .remove =       __devexit_p(sdhci_pci_remove),
        .suspend =      sdhci_pci_suspend,
        .resume =       sdhci_pci_resume,
        .remove =       __devexit_p(sdhci_pci_remove),
        .suspend =      sdhci_pci_suspend,
        .resume =       sdhci_pci_resume,
+       .driver =       {
+               .pm =   &sdhci_pci_pm_ops
+       },
 };
 
 /*****************************************************************************\
 };
 
 /*****************************************************************************\
index 9394860..155deb8 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/slab.h>
 #include <linux/scatterlist.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 #include <linux/scatterlist.h>
 #include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
 
 #include <linux/leds.h>
 
 
 #include <linux/leds.h>
 
@@ -42,6 +43,7 @@
 #define MAX_TUNING_LOOP 40
 
 static unsigned int debug_quirks = 0;
 #define MAX_TUNING_LOOP 40
 
 static unsigned int debug_quirks = 0;
+static unsigned int debug_quirks2;
 
 static void sdhci_finish_data(struct sdhci_host *);
 
 
 static void sdhci_finish_data(struct sdhci_host *);
 
@@ -50,6 +52,20 @@ static void sdhci_finish_command(struct sdhci_host *);
 static int sdhci_execute_tuning(struct mmc_host *mmc);
 static void sdhci_tuning_timer(unsigned long data);
 
 static int sdhci_execute_tuning(struct mmc_host *mmc);
 static void sdhci_tuning_timer(unsigned long data);
 
+#ifdef CONFIG_PM_RUNTIME
+static int sdhci_runtime_pm_get(struct sdhci_host *host);
+static int sdhci_runtime_pm_put(struct sdhci_host *host);
+#else
+static inline int sdhci_runtime_pm_get(struct sdhci_host *host)
+{
+       return 0;
+}
+static inline int sdhci_runtime_pm_put(struct sdhci_host *host)
+{
+       return 0;
+}
+#endif
+
 static void sdhci_dumpregs(struct sdhci_host *host)
 {
        printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
 static void sdhci_dumpregs(struct sdhci_host *host)
 {
        printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
@@ -133,6 +149,9 @@ static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
        if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
                return;
 
        if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
                return;
 
+       if (host->quirks2 & SDHCI_QUIRK2_OWN_CARD_DETECTION)
+               return;
+
        present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
                              SDHCI_CARD_PRESENT;
        irqs = present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT;
        present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
                              SDHCI_CARD_PRESENT;
        irqs = present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT;
@@ -252,11 +271,14 @@ static void sdhci_led_control(struct led_classdev *led,
 
        spin_lock_irqsave(&host->lock, flags);
 
 
        spin_lock_irqsave(&host->lock, flags);
 
+       if (host->runtime_suspended)
+               goto out;
+
        if (brightness == LED_OFF)
                sdhci_deactivate_led(host);
        else
                sdhci_activate_led(host);
        if (brightness == LED_OFF)
                sdhci_deactivate_led(host);
        else
                sdhci_activate_led(host);
-
+out:
        spin_unlock_irqrestore(&host->lock, flags);
 }
 #endif
        spin_unlock_irqrestore(&host->lock, flags);
 }
 #endif
@@ -1210,6 +1232,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
 
        host = mmc_priv(mmc);
 
 
        host = mmc_priv(mmc);
 
+       sdhci_runtime_pm_get(host);
+
        spin_lock_irqsave(&host->lock, flags);
 
        WARN_ON(host->mrq != NULL);
        spin_lock_irqsave(&host->lock, flags);
 
        WARN_ON(host->mrq != NULL);
@@ -1270,14 +1294,11 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
-static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios)
 {
 {
-       struct sdhci_host *host;
        unsigned long flags;
        u8 ctrl;
 
        unsigned long flags;
        u8 ctrl;
 
-       host = mmc_priv(mmc);
-
        spin_lock_irqsave(&host->lock, flags);
 
        if (host->flags & SDHCI_DEVICE_DEAD)
        spin_lock_irqsave(&host->lock, flags);
 
        if (host->flags & SDHCI_DEVICE_DEAD)
@@ -1427,7 +1448,16 @@ out:
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
-static int check_ro(struct sdhci_host *host)
+static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+
+       sdhci_runtime_pm_get(host);
+       sdhci_do_set_ios(host, ios);
+       sdhci_runtime_pm_put(host);
+}
+
+static int sdhci_check_ro(struct sdhci_host *host)
 {
        unsigned long flags;
        int is_readonly;
 {
        unsigned long flags;
        int is_readonly;
@@ -1451,19 +1481,16 @@ static int check_ro(struct sdhci_host *host)
 
 #define SAMPLE_COUNT   5
 
 
 #define SAMPLE_COUNT   5
 
-static int sdhci_get_ro(struct mmc_host *mmc)
+static int sdhci_do_get_ro(struct sdhci_host *host)
 {
 {
-       struct sdhci_host *host;
        int i, ro_count;
 
        int i, ro_count;
 
-       host = mmc_priv(mmc);
-
        if (!(host->quirks & SDHCI_QUIRK_UNSTABLE_RO_DETECT))
        if (!(host->quirks & SDHCI_QUIRK_UNSTABLE_RO_DETECT))
-               return check_ro(host);
+               return sdhci_check_ro(host);
 
        ro_count = 0;
        for (i = 0; i < SAMPLE_COUNT; i++) {
 
        ro_count = 0;
        for (i = 0; i < SAMPLE_COUNT; i++) {
-               if (check_ro(host)) {
+               if (sdhci_check_ro(host)) {
                        if (++ro_count > SAMPLE_COUNT / 2)
                                return 1;
                }
                        if (++ro_count > SAMPLE_COUNT / 2)
                                return 1;
                }
@@ -1480,38 +1507,56 @@ static void sdhci_hw_reset(struct mmc_host *mmc)
                host->ops->hw_reset(host);
 }
 
                host->ops->hw_reset(host);
 }
 
-static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+static int sdhci_get_ro(struct mmc_host *mmc)
 {
 {
-       struct sdhci_host *host;
-       unsigned long flags;
-
-       host = mmc_priv(mmc);
+       struct sdhci_host *host = mmc_priv(mmc);
+       int ret;
 
 
-       spin_lock_irqsave(&host->lock, flags);
+       sdhci_runtime_pm_get(host);
+       ret = sdhci_do_get_ro(host);
+       sdhci_runtime_pm_put(host);
+       return ret;
+}
 
 
+static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable)
+{
        if (host->flags & SDHCI_DEVICE_DEAD)
                goto out;
 
        if (host->flags & SDHCI_DEVICE_DEAD)
                goto out;
 
+       if (enable)
+               host->flags |= SDHCI_SDIO_IRQ_ENABLED;
+       else
+               host->flags &= ~SDHCI_SDIO_IRQ_ENABLED;
+
+       /* SDIO IRQ will be enabled as appropriate in runtime resume */
+       if (host->runtime_suspended)
+               goto out;
+
        if (enable)
                sdhci_unmask_irqs(host, SDHCI_INT_CARD_INT);
        else
                sdhci_mask_irqs(host, SDHCI_INT_CARD_INT);
 out:
        mmiowb();
        if (enable)
                sdhci_unmask_irqs(host, SDHCI_INT_CARD_INT);
        else
                sdhci_mask_irqs(host, SDHCI_INT_CARD_INT);
 out:
        mmiowb();
+}
+
+static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+       unsigned long flags;
 
 
+       spin_lock_irqsave(&host->lock, flags);
+       sdhci_enable_sdio_irq_nolock(host, enable);
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
-static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
-       struct mmc_ios *ios)
+static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host,
+                                               struct mmc_ios *ios)
 {
 {
-       struct sdhci_host *host;
        u8 pwr;
        u16 clk, ctrl;
        u32 present_state;
 
        u8 pwr;
        u16 clk, ctrl;
        u32 present_state;
 
-       host = mmc_priv(mmc);
-
        /*
         * Signal Voltage Switching is only applicable for Host Controllers
         * v3.00 and above.
        /*
         * Signal Voltage Switching is only applicable for Host Controllers
         * v3.00 and above.
@@ -1604,6 +1649,20 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
                return 0;
 }
 
                return 0;
 }
 
+static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+       struct mmc_ios *ios)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+       int err;
+
+       if (host->version < SDHCI_SPEC_300)
+               return 0;
+       sdhci_runtime_pm_get(host);
+       err = sdhci_do_start_signal_voltage_switch(host, ios);
+       sdhci_runtime_pm_put(host);
+       return err;
+}
+
 static int sdhci_execute_tuning(struct mmc_host *mmc)
 {
        struct sdhci_host *host;
 static int sdhci_execute_tuning(struct mmc_host *mmc)
 {
        struct sdhci_host *host;
@@ -1615,6 +1674,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc)
 
        host = mmc_priv(mmc);
 
 
        host = mmc_priv(mmc);
 
+       sdhci_runtime_pm_get(host);
        disable_irq(host->irq);
        spin_lock(&host->lock);
 
        disable_irq(host->irq);
        spin_lock(&host->lock);
 
@@ -1632,6 +1692,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc)
        else {
                spin_unlock(&host->lock);
                enable_irq(host->irq);
        else {
                spin_unlock(&host->lock);
                enable_irq(host->irq);
+               sdhci_runtime_pm_put(host);
                return 0;
        }
 
                return 0;
        }
 
@@ -1657,7 +1718,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc)
        timeout = 150;
        do {
                struct mmc_command cmd = {0};
        timeout = 150;
        do {
                struct mmc_command cmd = {0};
-               struct mmc_request mrq = {0};
+               struct mmc_request mrq = {NULL};
 
                if (!tuning_loop_counter && !timeout)
                        break;
 
                if (!tuning_loop_counter && !timeout)
                        break;
@@ -1775,18 +1836,16 @@ out:
        sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);
        spin_unlock(&host->lock);
        enable_irq(host->irq);
        sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);
        spin_unlock(&host->lock);
        enable_irq(host->irq);
+       sdhci_runtime_pm_put(host);
 
        return err;
 }
 
 
        return err;
 }
 
-static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
+static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable)
 {
 {
-       struct sdhci_host *host;
        u16 ctrl;
        unsigned long flags;
 
        u16 ctrl;
        unsigned long flags;
 
-       host = mmc_priv(mmc);
-
        /* Host Controller v3.00 defines preset value registers */
        if (host->version < SDHCI_SPEC_300)
                return;
        /* Host Controller v3.00 defines preset value registers */
        if (host->version < SDHCI_SPEC_300)
                return;
@@ -1802,14 +1861,25 @@ static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
        if (enable && !(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
                ctrl |= SDHCI_CTRL_PRESET_VAL_ENABLE;
                sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
        if (enable && !(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
                ctrl |= SDHCI_CTRL_PRESET_VAL_ENABLE;
                sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+               host->flags |= SDHCI_PV_ENABLED;
        } else if (!enable && (ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
                ctrl &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
                sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
        } else if (!enable && (ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
                ctrl &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
                sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+               host->flags &= ~SDHCI_PV_ENABLED;
        }
 
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
        }
 
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
+static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+
+       sdhci_runtime_pm_get(host);
+       sdhci_do_enable_preset_value(host, enable);
+       sdhci_runtime_pm_put(host);
+}
+
 static const struct mmc_host_ops sdhci_ops = {
        .request        = sdhci_request,
        .set_ios        = sdhci_set_ios,
 static const struct mmc_host_ops sdhci_ops = {
        .request        = sdhci_request,
        .set_ios        = sdhci_set_ios,
@@ -1836,19 +1906,19 @@ static void sdhci_tasklet_card(unsigned long param)
 
        spin_lock_irqsave(&host->lock, flags);
 
 
        spin_lock_irqsave(&host->lock, flags);
 
-       if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
-               if (host->mrq) {
-                       printk(KERN_ERR "%s: Card removed during transfer!\n",
-                               mmc_hostname(host->mmc));
-                       printk(KERN_ERR "%s: Resetting controller.\n",
-                               mmc_hostname(host->mmc));
+       /* Check host->mrq first in case we are runtime suspended */
+       if (host->mrq &&
+           !(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+               printk(KERN_ERR "%s: Card removed during transfer!\n",
+                       mmc_hostname(host->mmc));
+               printk(KERN_ERR "%s: Resetting controller.\n",
+                       mmc_hostname(host->mmc));
 
 
-                       sdhci_reset(host, SDHCI_RESET_CMD);
-                       sdhci_reset(host, SDHCI_RESET_DATA);
+               sdhci_reset(host, SDHCI_RESET_CMD);
+               sdhci_reset(host, SDHCI_RESET_DATA);
 
 
-                       host->mrq->cmd->error = -ENOMEDIUM;
-                       tasklet_schedule(&host->finish_tasklet);
-               }
+               host->mrq->cmd->error = -ENOMEDIUM;
+               tasklet_schedule(&host->finish_tasklet);
        }
 
        spin_unlock_irqrestore(&host->lock, flags);
        }
 
        spin_unlock_irqrestore(&host->lock, flags);
@@ -1864,14 +1934,16 @@ static void sdhci_tasklet_finish(unsigned long param)
 
        host = (struct sdhci_host*)param;
 
 
        host = (struct sdhci_host*)param;
 
+       spin_lock_irqsave(&host->lock, flags);
+
         /*
          * If this tasklet gets rescheduled while running, it will
          * be run again afterwards but without any active request.
          */
         /*
          * If this tasklet gets rescheduled while running, it will
          * be run again afterwards but without any active request.
          */
-       if (!host->mrq)
+       if (!host->mrq) {
+               spin_unlock_irqrestore(&host->lock, flags);
                return;
                return;
-
-       spin_lock_irqsave(&host->lock, flags);
+       }
 
        del_timer(&host->timer);
 
 
        del_timer(&host->timer);
 
@@ -1915,6 +1987,7 @@ static void sdhci_tasklet_finish(unsigned long param)
        spin_unlock_irqrestore(&host->lock, flags);
 
        mmc_request_done(host->mmc, mrq);
        spin_unlock_irqrestore(&host->lock, flags);
 
        mmc_request_done(host->mmc, mrq);
+       sdhci_runtime_pm_put(host);
 }
 
 static void sdhci_timeout_timer(unsigned long data)
 }
 
 static void sdhci_timeout_timer(unsigned long data)
@@ -2146,12 +2219,19 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
 static irqreturn_t sdhci_irq(int irq, void *dev_id)
 {
        irqreturn_t result;
 static irqreturn_t sdhci_irq(int irq, void *dev_id)
 {
        irqreturn_t result;
-       struct sdhci_hosthost = dev_id;
+       struct sdhci_host *host = dev_id;
        u32 intmask;
        int cardint = 0;
 
        spin_lock(&host->lock);
 
        u32 intmask;
        int cardint = 0;
 
        spin_lock(&host->lock);
 
+       if (host->runtime_suspended) {
+               spin_unlock(&host->lock);
+               printk(KERN_WARNING "%s: got irq while runtime suspended\n",
+                      mmc_hostname(host->mmc));
+               return IRQ_HANDLED;
+       }
+
        intmask = sdhci_readl(host, SDHCI_INT_STATUS);
 
        if (!intmask || intmask == 0xffffffff) {
        intmask = sdhci_readl(host, SDHCI_INT_STATUS);
 
        if (!intmask || intmask == 0xffffffff) {
@@ -2285,7 +2365,6 @@ int sdhci_resume_host(struct sdhci_host *host)
                        return ret;
        }
 
                        return ret;
        }
 
-
        if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
                if (host->ops->enable_dma)
                        host->ops->enable_dma(host);
        if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
                if (host->ops->enable_dma)
                        host->ops->enable_dma(host);
@@ -2324,6 +2403,90 @@ EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups);
 
 #endif /* CONFIG_PM */
 
 
 #endif /* CONFIG_PM */
 
+#ifdef CONFIG_PM_RUNTIME
+
+static int sdhci_runtime_pm_get(struct sdhci_host *host)
+{
+       return pm_runtime_get_sync(host->mmc->parent);
+}
+
+static int sdhci_runtime_pm_put(struct sdhci_host *host)
+{
+       pm_runtime_mark_last_busy(host->mmc->parent);
+       return pm_runtime_put_autosuspend(host->mmc->parent);
+}
+
+int sdhci_runtime_suspend_host(struct sdhci_host *host)
+{
+       unsigned long flags;
+       int ret = 0;
+
+       /* Disable tuning since we are suspending */
+       if (host->version >= SDHCI_SPEC_300 &&
+           host->tuning_mode == SDHCI_TUNING_MODE_1) {
+               del_timer_sync(&host->tuning_timer);
+               host->flags &= ~SDHCI_NEEDS_RETUNING;
+       }
+
+       spin_lock_irqsave(&host->lock, flags);
+       sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK);
+       spin_unlock_irqrestore(&host->lock, flags);
+
+       synchronize_irq(host->irq);
+
+       spin_lock_irqsave(&host->lock, flags);
+       host->runtime_suspended = true;
+       spin_unlock_irqrestore(&host->lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(sdhci_runtime_suspend_host);
+
+int sdhci_runtime_resume_host(struct sdhci_host *host)
+{
+       unsigned long flags;
+       int ret = 0, host_flags = host->flags;
+
+       if (host_flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
+               if (host->ops->enable_dma)
+                       host->ops->enable_dma(host);
+       }
+
+       sdhci_init(host, 0);
+
+       /* Force clock and power re-program */
+       host->pwr = 0;
+       host->clock = 0;
+       sdhci_do_set_ios(host, &host->mmc->ios);
+
+       sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios);
+       if (host_flags & SDHCI_PV_ENABLED)
+               sdhci_do_enable_preset_value(host, true);
+
+       /* Set the re-tuning expiration flag */
+       if ((host->version >= SDHCI_SPEC_300) && host->tuning_count &&
+           (host->tuning_mode == SDHCI_TUNING_MODE_1))
+               host->flags |= SDHCI_NEEDS_RETUNING;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       host->runtime_suspended = false;
+
+       /* Enable SDIO IRQ */
+       if ((host->flags & SDHCI_SDIO_IRQ_ENABLED))
+               sdhci_enable_sdio_irq_nolock(host, true);
+
+       /* Enable Card Detection */
+       sdhci_enable_card_detection(host);
+
+       spin_unlock_irqrestore(&host->lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host);
+
+#endif
+
 /*****************************************************************************\
  *                                                                           *
  * Device allocation/registration                                            *
 /*****************************************************************************\
  *                                                                           *
  * Device allocation/registration                                            *
@@ -2366,6 +2529,8 @@ int sdhci_add_host(struct sdhci_host *host)
 
        if (debug_quirks)
                host->quirks = debug_quirks;
 
        if (debug_quirks)
                host->quirks = debug_quirks;
+       if (debug_quirks2)
+               host->quirks2 = debug_quirks2;
 
        sdhci_reset(host, SDHCI_RESET_ALL);
 
 
        sdhci_reset(host, SDHCI_RESET_ALL);
 
@@ -2888,9 +3053,11 @@ module_init(sdhci_drv_init);
 module_exit(sdhci_drv_exit);
 
 module_param(debug_quirks, uint, 0444);
 module_exit(sdhci_drv_exit);
 
 module_param(debug_quirks, uint, 0444);
+module_param(debug_quirks2, uint, 0444);
 
 MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
 MODULE_DESCRIPTION("Secure Digital Host Controller Interface core driver");
 MODULE_LICENSE("GPL");
 
 MODULE_PARM_DESC(debug_quirks, "Force certain quirks.");
 
 MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
 MODULE_DESCRIPTION("Secure Digital Host Controller Interface core driver");
 MODULE_LICENSE("GPL");
 
 MODULE_PARM_DESC(debug_quirks, "Force certain quirks.");
+MODULE_PARM_DESC(debug_quirks2, "Force certain other quirks.");
index 7bd919c..0a5b654 100644 (file)
@@ -379,4 +379,9 @@ extern int sdhci_resume_host(struct sdhci_host *host);
 extern void sdhci_enable_irq_wakeups(struct sdhci_host *host);
 #endif
 
 extern void sdhci_enable_irq_wakeups(struct sdhci_host *host);
 #endif
 
+#ifdef CONFIG_PM_RUNTIME
+extern int sdhci_runtime_suspend_host(struct sdhci_host *host);
+extern int sdhci_runtime_resume_host(struct sdhci_host *host);
+#endif
+
 #endif /* __SDHCI_HW_H */
 #endif /* __SDHCI_HW_H */
index 5666f3a..e4b6935 100644 (file)
@@ -88,6 +88,10 @@ struct sdhci_host {
 /* The read-only detection via SDHCI_PRESENT_STATE register is unstable */
 #define SDHCI_QUIRK_UNSTABLE_RO_DETECT                 (1<<31)
 
 /* The read-only detection via SDHCI_PRESENT_STATE register is unstable */
 #define SDHCI_QUIRK_UNSTABLE_RO_DETECT                 (1<<31)
 
+       unsigned int quirks2;   /* More deviations from spec. */
+
+#define SDHCI_QUIRK2_OWN_CARD_DETECTION                        (1<<0)
+
        int irq;                /* Device IRQ */
        void __iomem *ioaddr;   /* Mapped address */
 
        int irq;                /* Device IRQ */
        void __iomem *ioaddr;   /* Mapped address */
 
@@ -115,6 +119,8 @@ struct sdhci_host {
 #define SDHCI_NEEDS_RETUNING   (1<<5)  /* Host needs retuning */
 #define SDHCI_AUTO_CMD12       (1<<6)  /* Auto CMD12 support */
 #define SDHCI_AUTO_CMD23       (1<<7)  /* Auto CMD23 support */
 #define SDHCI_NEEDS_RETUNING   (1<<5)  /* Host needs retuning */
 #define SDHCI_AUTO_CMD12       (1<<6)  /* Auto CMD12 support */
 #define SDHCI_AUTO_CMD23       (1<<7)  /* Auto CMD23 support */
+#define SDHCI_PV_ENABLED       (1<<8)  /* Preset value enabled */
+#define SDHCI_SDIO_IRQ_ENABLED (1<<9)  /* SDIO irq enabled */
 
        unsigned int version;   /* SDHCI spec. version */
 
 
        unsigned int version;   /* SDHCI spec. version */
 
@@ -125,6 +131,8 @@ struct sdhci_host {
        unsigned int clock;     /* Current clock (MHz) */
        u8 pwr;                 /* Current voltage */
 
        unsigned int clock;     /* Current clock (MHz) */
        u8 pwr;                 /* Current voltage */
 
+       bool runtime_suspended; /* Host is runtime suspended */
+
        struct mmc_request *mrq;        /* Current request */
        struct mmc_command *cmd;        /* Current command */
        struct mmc_data *data;  /* Current data request */
        struct mmc_request *mrq;        /* Current request */
        struct mmc_command *cmd;        /* Current command */
        struct mmc_data *data;  /* Current data request */