usb: ehci-msm-hsic: replace wakelock with wakeup source

In some cases of system suspend, it's possible a runtime resume
may be called on a device if it's already autosuspended (say, in
order to set a device- or port-specific feature, such as the remote
wakeup setting) just prior to putting it back into low power mode.
In this case, if the controller was also auto-suspended it will also
be temporarily runtime resumed by the fact that it is an ancestor to
said device.

The incoming wakelock re-implementation from Google based on wakeup
sources breaks this driver in the above scenario since now acquiring
a wakelock (which calls __pm_stay_awake) during runtime resume while
a simultaneous asynchronous suspend callback of another device is
called will cause it to abort the entire suspend attempt.

This prompts reconsideration of why a wakelock is needed in this
driver. One apparent use is to ensure this controller's runtime
suspend is called before system suspend is allowed. In other words,
we wish to guarantee the HSIC child devices are suspended before
suspending the controller. This can be done by checking that the root
hub device is suspended, else we can abort the suspend callback by
returning -EBUSY.

To handle the cases of interrupts happening during systemwide suspend
but after the controller was already suspended (and thus its runtime
PM disabled), call the pm_stay_awake() to denote a wakeup event. This
will cause the suspend to abort as well as block it from happening
until the interrupt can be serviced--after the controller resumes.

Change-Id: I4fab47c54f7d57065833d96261de08dcdd7238dc
Signed-off-by: Jack Pham <jackp@codeaurora.org>
This commit is contained in:
Jack Pham
2012-07-26 10:31:03 -07:00
committed by Stephen Boyd
parent cd9144709a
commit 3f3ba6ffeb

View File

@@ -27,7 +27,6 @@
#include <linux/err.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/wakelock.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
@@ -57,14 +56,13 @@ struct msm_hsic_hcd {
struct clk *phy_clk;
struct clk *cal_clk;
struct regulator *hsic_vddcx;
bool async_int;
atomic_t in_lpm;
struct wake_lock wlock;
int peripheral_status_irq;
int wakeup_irq;
int wakeup_gpio;
bool wakeup_irq_enabled;
atomic_t pm_usage_cnt;
bool irq_enabled;
bool async_int;
uint32_t bus_perf_client;
uint32_t wakeup_int_cnt;
enum usb_vdd_type vdd_type;
@@ -599,13 +597,15 @@ static int msm_hsic_suspend(struct msm_hsic_hcd *mehci)
disable_irq(hcd->irq);
/* make sure we don't race against a remote wakeup */
if (test_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags) ||
/* make sure we don't race against the root hub being resumed */
if (HCD_RH_RUNNING(hcd) || HCD_WAKEUP_PENDING(hcd) ||
readl_relaxed(USB_PORTSC) & PORT_RESUME) {
dev_dbg(mehci->dev, "wakeup pending, aborting suspend\n");
dev_warn(mehci->dev, "%s: Root hub is not suspended\n",
__func__);
enable_irq(hcd->irq);
return -EBUSY;
}
mehci->irq_enabled = false;
/*
* PHY may take some time or even fail to enter into low power
@@ -666,14 +666,13 @@ static int msm_hsic_suspend(struct msm_hsic_hcd *mehci)
}
atomic_set(&mehci->in_lpm, 1);
mehci->irq_enabled = true;
enable_irq(hcd->irq);
mehci->wakeup_irq_enabled = 1;
enable_irq_wake(mehci->wakeup_irq);
enable_irq(mehci->wakeup_irq);
wake_unlock(&mehci->wlock);
dev_info(mehci->dev, "HSIC-USB in low power mode\n");
return 0;
@@ -697,8 +696,6 @@ static int msm_hsic_resume(struct msm_hsic_hcd *mehci)
mehci->wakeup_irq_enabled = 0;
}
wake_lock(&mehci->wlock);
if (mehci->bus_perf_client && debug_bus_voting_enabled) {
ret = msm_bus_scale_client_update_request(
mehci->bus_perf_client, 1);
@@ -757,14 +754,15 @@ skip_phy_resume:
if (mehci->async_int) {
mehci->async_int = false;
pm_runtime_put_noidle(mehci->dev);
enable_irq(hcd->irq);
}
if (atomic_read(&mehci->pm_usage_cnt)) {
atomic_set(&mehci->pm_usage_cnt, 0);
pm_runtime_put_noidle(mehci->dev);
if (!mehci->irq_enabled) {
enable_irq(hcd->irq);
mehci->irq_enabled = true;
}
pm_relax(mehci->dev);
dev_info(mehci->dev, "HSIC-USB exited from low power mode\n");
return 0;
@@ -777,9 +775,11 @@ static irqreturn_t msm_hsic_irq(struct usb_hcd *hcd)
if (atomic_read(&mehci->in_lpm)) {
disable_irq_nosync(hcd->irq);
mehci->irq_enabled = false;
dev_dbg(mehci->dev, "phy async intr\n");
mehci->async_int = true;
pm_runtime_get(mehci->dev);
pm_stay_awake(mehci->dev);
return IRQ_HANDLED;
}
@@ -996,7 +996,9 @@ static irqreturn_t msm_hsic_wakeup_irq(int irq, void *data)
dev_dbg(mehci->dev, "%s: hsic remote wakeup interrupt cnt: %u\n",
__func__, mehci->wakeup_int_cnt);
wake_lock(&mehci->wlock);
mehci->async_int = true;
pm_runtime_get(mehci->dev);
pm_stay_awake(mehci->dev);
if (mehci->wakeup_irq_enabled) {
mehci->wakeup_irq_enabled = 0;
@@ -1004,11 +1006,6 @@ static irqreturn_t msm_hsic_wakeup_irq(int irq, void *data)
disable_irq_nosync(irq);
}
if (!atomic_read(&mehci->pm_usage_cnt)) {
atomic_set(&mehci->pm_usage_cnt, 1);
pm_runtime_get(mehci->dev);
}
return IRQ_HANDLED;
}
@@ -1303,9 +1300,8 @@ static int __devinit ehci_hsic_msm_probe(struct platform_device *pdev)
goto unconfig_gpio;
}
mehci->irq_enabled = true;
device_init_wakeup(&pdev->dev, 1);
wake_lock_init(&mehci->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev));
wake_lock(&mehci->wlock);
if (mehci->peripheral_status_irq) {
ret = request_threaded_irq(mehci->peripheral_status_irq,
@@ -1412,7 +1408,6 @@ static int __devexit ehci_hsic_msm_remove(struct platform_device *pdev)
msm_hsic_init_vddcx(mehci, 0);
msm_hsic_init_clocks(mehci, 0);
wake_lock_destroy(&mehci->wlock);
iounmap(hcd->regs);
usb_put_hcd(hcd);