This patch allows the SDHCI to wake up the system on the following events: 1) Card Interrupts. 2) Card Insertion. 3) Card Removal. Indeed, on a set-top-box, it can be nice to have the capability to wake-up the system when a card is inserted. These new module parameters have been added to select which events have to wake-up the system: o wake_on_card_int o wake_on_card_ins o wake_on_card_rem Ex: $ echo 1 > /sys/module/sdhci/parameters/wakeup_card_ins A new quirk has been also added to allow hosts, based on sdhci d.d., to set the device as wake-up capable. Signed-off-by: Giuseppe Cavallaro <peppe.cavallaro@xxxxxx> --- drivers/mmc/host/sdhci-pci.c | 2 +- drivers/mmc/host/sdhci.c | 104 ++++++++++++++++++++++++++++++++++++++---- drivers/mmc/host/sdhci.h | 2 +- include/linux/mmc/sdhci.h | 2 + 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 3d9c246..de610f1 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -668,7 +668,7 @@ static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state) slot_pm_flags = slot->host->mmc->pm_flags; if (slot_pm_flags & MMC_PM_WAKE_SDIO_IRQ) - sdhci_enable_irq_wakeups(slot->host); + sdhci_enable_irq_wakeups(slot->host, SDHCI_WAKE_ON_INT); pm_flags |= slot_pm_flags; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 154cbf8..531033e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -37,7 +37,20 @@ #define SDHCI_USE_LEDS_CLASS #endif -static unsigned int debug_quirks = 0; +static unsigned int debug_quirks; +#ifdef CONFIG_PM +static unsigned int wakeup_card_int; +module_param(wakeup_card_int, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(wakeup_card_int, "Wake Up on Card interrupt event"); + +static unsigned int wakeup_card_ins; +module_param(wakeup_card_ins, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(wakeup_card_ins, "Wake UP on Card insertion event"); + +static unsigned int wakeup_card_rem; +module_param(wakeup_card_rem, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(wakeup_card_rem, "Wake UP on Card removal event"); +#endif /* CONFIG_PM */ static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *); static void sdhci_finish_data(struct sdhci_host *); @@ -1629,6 +1642,71 @@ out: #ifdef CONFIG_PM +static void sdhci_can_wakeup(struct sdhci_host *host) +{ + struct device *dev = host->mmc->parent; + + /* SDHCI is capable to wakeup the system */ + device_set_wakeup_capable(dev, 1); + + /* Enable wakeup */ + device_set_wakeup_enable(dev, 1); + enable_irq_wake(host->irq); +} + +/* + * According to the SD SPEC to wake up the system + * the HC has to be programmed as below: + * - SD Bus power: ON + * - SD clock: Stop + * - Internal clock: Oscillate + * - Data transfer witdh: 0 + * + * While suspending we have to: + * 1) Set BUS width to 1 + * 2) Mask the Signal Interrupt register + * 3) Clean the Signal Interrupt register + * 4) Set the enable bits of each factor to 1 in the Wakeup + * control register + * 5) Set the bits of Normal Interrupt Status Enable register + * to use wakeup. + */ +static void sdhci_set_wakeup(struct sdhci_host *host) +{ + u16 clk; + u8 pwr; + mmc_pm_flag_t wakeflag = 0; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + pwr = sdhci_readb(host, SDHCI_POWER_CONTROL); + pwr |= SDHCI_POWER_ON; + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); + + sdhci_mask_irqs(host, SDHCI_INT_NORMAL_MASK | + SDHCI_INT_ERROR_MASK); + sdhci_writel(host, 0, SDHCI_INT_STATUS); + sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); + sdhci_writel(host, SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT | + SDHCI_INT_CARD_INT, SDHCI_INT_ENABLE); + /* + * Program the Wakeup Control register according to the event selected. + * If there is no event (SDHCI_WAKE_UP_CONTROL = 0x0) so the device + * won't be able to wake up the system. + */ + if (wakeup_card_int) + wakeflag |= SDHCI_WAKE_ON_INT; + if (wakeup_card_ins) + wakeflag |= SDHCI_WAKE_ON_INSERT; + if (wakeup_card_rem) + wakeflag |= SDHCI_WAKE_ON_REMOVE; + + sdhci_enable_irq_wakeups(host, wakeflag); +} + int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) { int ret; @@ -1639,7 +1717,10 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) if (ret) return ret; - free_irq(host->irq, host); + if (device_may_wakeup(host->mmc->parent)) + sdhci_set_wakeup(host); + else + free_irq(host->irq, host); if (host->vmmc) ret = regulator_disable(host->vmmc); @@ -1665,11 +1746,12 @@ int sdhci_resume_host(struct sdhci_host *host) host->ops->enable_dma(host); } - ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, - mmc_hostname(host->mmc), host); - if (ret) - return ret; - + if (!(device_may_wakeup(host->mmc->parent))) { + ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, + mmc_hostname(host->mmc), host); + if (ret) + return ret; + } sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER)); mmiowb(); @@ -1681,11 +1763,11 @@ int sdhci_resume_host(struct sdhci_host *host) EXPORT_SYMBOL_GPL(sdhci_resume_host); -void sdhci_enable_irq_wakeups(struct sdhci_host *host) +void sdhci_enable_irq_wakeups(struct sdhci_host *host, int mode) { u8 val; val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL); - val |= SDHCI_WAKE_ON_INT; + val |= mode; sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL); } @@ -1987,6 +2069,10 @@ int sdhci_add_host(struct sdhci_host *host) sdhci_enable_card_detection(host); +#ifdef CONFIG_PM + if (host->quirks & SDHCI_QUIRK_WANT_WAKEUP_ON_CARD) + sdhci_can_wakeup(host); +#endif return 0; #ifdef SDHCI_USE_LEDS_CLASS diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index d52a716..1682542 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -320,7 +320,7 @@ extern void sdhci_remove_host(struct sdhci_host *host, int dead); #ifdef CONFIG_PM extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state); extern int sdhci_resume_host(struct sdhci_host *host); -extern void sdhci_enable_irq_wakeups(struct sdhci_host *host); +extern void sdhci_enable_irq_wakeups(struct sdhci_host *host, int mode); #endif #endif /* __SDHCI_HW_H */ diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index 1fdc673..8aa3c2e 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -83,6 +83,8 @@ struct sdhci_host { #define SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 (1<<28) /* Controller doesn't have HISPD bit field in HI-SPEED SD card */ #define SDHCI_QUIRK_NO_HISPD_BIT (1<<29) +/* Controller wants to wakeup on card events, i.e. card ins, rem or int */ +#define SDHCI_QUIRK_WANT_WAKEUP_ON_CARD (1<<30) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ -- 1.5.5.6 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html