Commit 66fd8ad5 authored by Adrian Hunter's avatar Adrian Hunter Committed by Chris Ball

mmc: sdhci-pci: add runtime pm support

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: default avatarAdrian Hunter <adrian.hunter@intel.com>
Signed-off-by: default avatarChris Ball <cjb@laptop.org>
parent 08a7e1df
......@@ -24,6 +24,7 @@
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/sfi.h>
#include <linux/pm_runtime.h>
#include "sdhci.h"
......@@ -63,6 +64,8 @@ struct sdhci_pci_slot {
int pci_bar;
int rst_n_gpio;
int cd_gpio;
int cd_irq;
};
struct sdhci_pci_chip {
......@@ -190,6 +193,70 @@ static int mfd_emmc_gpio_parse(struct sfi_table_header *table)
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;
......@@ -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_8_BIT_DATA;
slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE;
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,
.probe_slot = mfd_sd_probe_slot,
.remove_slot = mfd_sd_remove_slot,
};
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 */
#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 *
......@@ -1133,6 +1291,21 @@ static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
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)
{
......@@ -1208,6 +1381,8 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
chip->slots[i] = slot;
}
sdhci_pci_runtime_pm_allow(&pdev->dev);
return 0;
free:
......@@ -1224,6 +1399,8 @@ static void __devexit sdhci_pci_remove(struct pci_dev *pdev)
int i;
struct sdhci_pci_chip *chip;
sdhci_pci_runtime_pm_forbid(&pdev->dev);
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,
.driver = {
.pm = &sdhci_pci_pm_ops
},
};
/*****************************************************************************\
......
This diff is collapsed.
......@@ -379,4 +379,9 @@ extern int sdhci_resume_host(struct sdhci_host *host);
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 */
......@@ -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)
unsigned int quirks2; /* More deviations from spec. */
#define SDHCI_QUIRK2_OWN_CARD_DETECTION (1<<0)
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_PV_ENABLED (1<<8) /* Preset value enabled */
#define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */
unsigned int version; /* SDHCI spec. version */
......@@ -125,6 +131,8 @@ struct sdhci_host {
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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment