diff mbox

[6/6] USB: ehci-omap: Implement suspend/resume

Message ID 1373473405-27589-1-git-send-email-rogerq@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Roger Quadros July 10, 2013, 4:23 p.m. UTC
Call ehci_suspend/resume() during runtime suspend/resume
as well as system suspend/resume.

Use a flag "bound" to indicate that the HCD structures are valid.
This is only true between usb_add_hcd() and usb_remove_hcd() calls.

The flag can be used by omap_ehci_runtime_suspend/resume() handlers
to avoid calling ehci_suspend/resume() when we are no longer bound
to the HCD e.g. during probe() and remove();

Signed-off-by: Roger Quadros <rogerq@ti.com>
---
 drivers/usb/host/ehci-omap.c |   64 ++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 62 insertions(+), 2 deletions(-)

Comments

Alan Stern July 10, 2013, 7:04 p.m. UTC | #1
On Wed, 10 Jul 2013, Roger Quadros wrote:

> Call ehci_suspend/resume() during runtime suspend/resume
> as well as system suspend/resume.
> 
> Use a flag "bound" to indicate that the HCD structures are valid.
> This is only true between usb_add_hcd() and usb_remove_hcd() calls.
> 
> The flag can be used by omap_ehci_runtime_suspend/resume() handlers
> to avoid calling ehci_suspend/resume() when we are no longer bound
> to the HCD e.g. during probe() and remove();

> @@ -293,15 +301,67 @@ static const struct of_device_id omap_ehci_dt_ids[] = {
>  
>  MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids);
>  
> +static int omap_ehci_suspend(struct device *dev)
> +{
> +	struct usb_hcd *hcd = dev_get_drvdata(dev);
> +
> +	dev_dbg(dev, "%s\n", __func__);
> +
> +	return ehci_suspend(hcd, true);
> +}
> +
> +static int omap_ehci_resume(struct device *dev)
> +{
> +	struct usb_hcd *hcd = dev_get_drvdata(dev);
> +
> +	dev_dbg(dev, "%s\n", __func__);
> +
> +	return ehci_resume(hcd, false);
> +}

There are three problems here.  The first is very simple: the wakeup 
settings are messed up.  Wakeup is supposed to be enabled always for
runtime suspend, whereas it is controlled by device_may_wakeup() for
system suspend.  You reversed them.

The other two problems are both related to the interaction between
system PM and runtime PM.  Suppose the controller is already runtime
suspended when the system goes to sleep.  Because it is runtime
suspended, it is enabled for wakeup.  But device_may_wakeup() could
return false; if this happens then you have to do a runtime-resume in
omap_ehci_suspend() before calling ehci_suspend(), so that the
controller can be suspended again with wakeups disabled.  (Or you could
choose an alternative method for accomplishing the same result, such as
disabling the wakeup signal from the pad without going through a whole
EHCI resume/suspend cycle.)  Conversely, if device_may_wakeup() returns
true then you shouldn't do anything at all, because the controller is
already suspended with the correct wakeup setting.

For the third problem, suppose the controller was runtime suspended
when the system went to sleep.  When the system wakes up, the
controller will become active, so you have to inform the runtime PM
core about its change of state.  Basically, if omap_ehci_resume() sees
that ehci_resume() returned 0 then it must do:

	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);

All of these issues are discussed (among lots of other material) in 
Documentation/power/runtime_pm.txt.

Alan Stern
Roger Quadros July 11, 2013, 8:50 a.m. UTC | #2
On 07/10/2013 10:04 PM, Alan Stern wrote:
> On Wed, 10 Jul 2013, Roger Quadros wrote:
> 
>> Call ehci_suspend/resume() during runtime suspend/resume
>> as well as system suspend/resume.
>>
>> Use a flag "bound" to indicate that the HCD structures are valid.
>> This is only true between usb_add_hcd() and usb_remove_hcd() calls.
>>
>> The flag can be used by omap_ehci_runtime_suspend/resume() handlers
>> to avoid calling ehci_suspend/resume() when we are no longer bound
>> to the HCD e.g. during probe() and remove();
> 
>> @@ -293,15 +301,67 @@ static const struct of_device_id omap_ehci_dt_ids[] = {
>>  
>>  MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids);
>>  
>> +static int omap_ehci_suspend(struct device *dev)
>> +{
>> +	struct usb_hcd *hcd = dev_get_drvdata(dev);
>> +
>> +	dev_dbg(dev, "%s\n", __func__);
>> +
>> +	return ehci_suspend(hcd, true);
>> +}
>> +
>> +static int omap_ehci_resume(struct device *dev)
>> +{
>> +	struct usb_hcd *hcd = dev_get_drvdata(dev);
>> +
>> +	dev_dbg(dev, "%s\n", __func__);
>> +
>> +	return ehci_resume(hcd, false);
>> +}
> 
> There are three problems here.  The first is very simple: the wakeup 
> settings are messed up.  Wakeup is supposed to be enabled always for
> runtime suspend, whereas it is controlled by device_may_wakeup() for
> system suspend.  You reversed them.

Indeed. I'll fix that up.

However, even if wakeup is disabled during system suspend, the OMAP will
still wakeup on any activity on the USB pins.

The only way to disable that is to disable IO pad wakeup. This can
only be done in mfd/omap-usb-host.c

I suppose I can use device_may_wakeup() there and configure the IO pad
wakeup accordingly.

> 
> The other two problems are both related to the interaction between
> system PM and runtime PM.  Suppose the controller is already runtime
> suspended when the system goes to sleep.  Because it is runtime
> suspended, it is enabled for wakeup.  But device_may_wakeup() could
> return false; if this happens then you have to do a runtime-resume in
> omap_ehci_suspend() before calling ehci_suspend(), so that the
> controller can be suspended again with wakeups disabled.  (Or you could
> choose an alternative method for accomplishing the same result, such as
> disabling the wakeup signal from the pad without going through a whole
> EHCI resume/suspend cycle.)  Conversely, if device_may_wakeup() returns
> true then you shouldn't do anything at all, because the controller is
> already suspended with the correct wakeup setting.

I think this case is taken care of by the Runtime PM core at least for the OMAP
platform according to the documentation

http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/power/runtime_pm.txt#n647

At the end of this mail is the log during system suspend/resume

You can notice the following sequence

-ehci runtime suspends
-system suspend triggered
-ehci runtime resumes
-ehci suspends (uses new wakeup settings)
-system wakeup triggered
-ehci resumes
-ehci runtime suspends

> 
> For the third problem, suppose the controller was runtime suspended
> when the system went to sleep.  When the system wakes up, the
> controller will become active, so you have to inform the runtime PM
> core about its change of state.  Basically, if omap_ehci_resume() sees
> that ehci_resume() returned 0 then it must do:
> 
> 	pm_runtime_disable(dev);
> 	pm_runtime_set_active(dev);
> 	pm_runtime_enable(dev);
> 
> All of these issues are discussed (among lots of other material) in 
> Documentation/power/runtime_pm.txt.

Is this still applicable? Documentation claims

   "During system resume it calls pm_runtime_enable() and pm_runtime_put_sync()
    for every device right after executing the subsystem-level .resume_early()
    callback and right after executing the subsystem-level .resume() callback
    for it, respectively."

cheers,
-roger

---
[   13.256286] smsc95xx 1-2.1:1.0 eth0: entering SUSPEND2 mode
[   13.264648] usb 1-2.1: usb auto-suspend, wakeup 0
[   13.281677] hub 1-2:1.0: hub_suspend
[   13.285858] usb 1-2: unlink qh256-0001/dd443880 start 1 [1/0 us]
/
/ # [   13.296386] usb 1-2: usb auto-suspend, wakeup 1
[   13.321411] hub 1-0:1.0: hub_suspend
[   13.325225] usb usb1: bus auto-suspend, wakeup 1
[   13.330139] ehci-omap 48064800.ehci: suspend root hub
[   13.336395] ehci-omap 48064800.ehci: omap_ehci_runtime_suspend

/ # 
/ # echo mem > /sys/power/state 
[   26.821838] PM: Syncing filesystems ... done.
[   26.828491] PM: Preparing system for mem sleep
[   26.841796] Freezing user space processes ... (elapsed 0.000 seconds) done.
[   26.849426] Freezing remaining freezable tasks ... (elapsed 0.002 seconds) done.
[   26.860046] PM: Entering mem sleep
[   26.863769] Suspending console(s) (use no_console_suspend to debug)
[   26.878234] ehci-omap 48064800.ehci: omap_ehci_runtime_resume
[   26.878295] usb usb1: usb auto-resume
[   26.878326] ehci-omap 48064800.ehci: resume root hub
[   26.878417] hub 1-0:1.0: hub_resume
[   26.878570] hub 1-0:1.0: port 2: status 0507 change 0000
[   26.879089] hub 1-0:1.0: hub_suspend
[   26.879211] usb usb1: bus suspend, wakeup 0
[   26.879241] ehci-omap 48064800.ehci: suspend root hub
[   26.891845] ehci-omap 48064800.ehci: omap_ehci_suspend may_wakeup 0
[   26.923797] PM: suspend of devices complete after 47.912 msecs
[   26.929443] PM: late suspend of devices complete after 5.645 msecs
[   26.935882] PM: noirq suspend of devices complete after 6.378 msecs
[   26.935943] Disabling non-boot CPUs ...
[   26.936065] Successfully put all powerdomains to target state
[   26.939636] PM: noirq resume of devices complete after 3.448 msecs
[   26.945190] PM: early resume of devices complete after 4.180 msecs
[   26.954833] ehci-omap 48064800.ehci: omap_ehci_resume
[   26.955383] usb usb1: usb resume
[   26.955383] ehci-omap 48064800.ehci: resume root hub
[   26.955413] hub 1-0:1.0: hub_resume
[   26.955535] hub 1-0:1.0: port 2: status 0507 change 0000
[   26.955902] usb 1-2: usb resume
[   26.990661] ehci-omap 48064800.ehci: GetStatus port:2 status 001005 0  ACK POWER sig=se0 PE CONNECT
[   27.010528] usb 1-2: finish resume
[   27.010894] hub 1-2:1.0: hub_resume
[   27.011352] hub 1-2:1.0: port 1: status 0507 change 0000
[   27.012390] ehci-omap 48064800.ehci: reused qh dd443880 schedule
[   27.012390] usb 1-2: link qh256-0001/dd443880 start 1 [1/0 us]
[   27.013244] usb 1-2.1: usb resume
[   27.070800] usb 1-2.1: finish resume
[   27.072998] PM: resume of devices complete after 127.716 msecs
[   27.243438] PM: Finishing wakeup.
[   27.246948] Restarting tasks ... [   27.251312] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000
[   27.257171] hub 1-2:1.0: state 7 ports 5 chg 0000 evt 0000
done.
/ # [   29.074493] smsc95xx 1-2.1:1.0 eth0: entering SUSPEND2 mode
[   29.082458] usb 1-2.1: usb auto-suspend, wakeup 0
[   29.100891] hub 1-2:1.0: hub_suspend
[   29.104736] usb 1-2: unlink qh256-0001/dd443880 start 1 [1/0 us]
[   29.114288] usb 1-2: usb auto-suspend, wakeup 1
[   29.130859] hub 1-0:1.0: hub_suspend
[   29.134735] usb usb1: bus auto-suspend, wakeup 1
[   29.139648] ehci-omap 48064800.ehci: suspend root hub
[   29.145446] ehci-omap 48064800.ehci: omap_ehci_runtime_suspend
Alan Stern July 11, 2013, 3:14 p.m. UTC | #3
On Thu, 11 Jul 2013, Roger Quadros wrote:

> > The other two problems are both related to the interaction between
> > system PM and runtime PM.  Suppose the controller is already runtime
> > suspended when the system goes to sleep.  Because it is runtime
> > suspended, it is enabled for wakeup.  But device_may_wakeup() could
> > return false; if this happens then you have to do a runtime-resume in
> > omap_ehci_suspend() before calling ehci_suspend(), so that the
> > controller can be suspended again with wakeups disabled.  (Or you could
> > choose an alternative method for accomplishing the same result, such as
> > disabling the wakeup signal from the pad without going through a whole
> > EHCI resume/suspend cycle.)  Conversely, if device_may_wakeup() returns
> > true then you shouldn't do anything at all, because the controller is
> > already suspended with the correct wakeup setting.
> 
> I think this case is taken care of by the Runtime PM core at least for the OMAP
> platform according to the documentation
> 
> http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/power/runtime_pm.txt#n647

No; that section refers only to races, not to wakeup settings.

> At the end of this mail is the log during system suspend/resume
> 
> You can notice the following sequence
> 
> -ehci runtime suspends
> -system suspend triggered
> -ehci runtime resumes
> -ehci suspends (uses new wakeup settings)
> -system wakeup triggered
> -ehci resumes
> -ehci runtime suspends

This is because the root hub was runtime suspended with the wrong
wakeup setting.  The USB core, which is careful about these things,
resumed and re-suspended it with the proper wakeup setting.  In the 
process, the controller had to be runtime resumed as well.

Try doing the test over again, but this time with the root hub enabled 
for wakeup and the controller disabled.  (I know this is a bizarre 
combination, but try it anyway.)  Also, after the system wakes up, see 
whether the root hub and controller get runtime suspended.

> > For the third problem, suppose the controller was runtime suspended
> > when the system went to sleep.  When the system wakes up, the
> > controller will become active, so you have to inform the runtime PM
> > core about its change of state.  Basically, if omap_ehci_resume() sees
> > that ehci_resume() returned 0 then it must do:
> > 
> > 	pm_runtime_disable(dev);
> > 	pm_runtime_set_active(dev);
> > 	pm_runtime_enable(dev);
> > 
> > All of these issues are discussed (among lots of other material) in 
> > Documentation/power/runtime_pm.txt.
> 
> Is this still applicable? Documentation claims
> 
>    "During system resume it calls pm_runtime_enable() and pm_runtime_put_sync()
>     for every device right after executing the subsystem-level .resume_early()
>     callback and right after executing the subsystem-level .resume() callback
>     for it, respectively."

Yes, this is applicable, but it is irrelevant to the problem I 
described.  You still have to tell the runtime PM core that the device 
is now active.

Alan Stern
Roger Quadros July 22, 2013, 1:16 p.m. UTC | #4
Hi Alan,

On 07/11/2013 06:14 PM, Alan Stern wrote:
> On Thu, 11 Jul 2013, Roger Quadros wrote:
> 
>>> The other two problems are both related to the interaction between
>>> system PM and runtime PM.  Suppose the controller is already runtime
>>> suspended when the system goes to sleep.  Because it is runtime
>>> suspended, it is enabled for wakeup.  But device_may_wakeup() could
>>> return false; if this happens then you have to do a runtime-resume in
>>> omap_ehci_suspend() before calling ehci_suspend(), so that the
>>> controller can be suspended again with wakeups disabled.  (Or you could
>>> choose an alternative method for accomplishing the same result, such as
>>> disabling the wakeup signal from the pad without going through a whole
>>> EHCI resume/suspend cycle.)  Conversely, if device_may_wakeup() returns
>>> true then you shouldn't do anything at all, because the controller is
>>> already suspended with the correct wakeup setting.
>>
>> I think this case is taken care of by the Runtime PM core at least for the OMAP
>> platform according to the documentation
>>
>> http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/power/runtime_pm.txt#n647
> 
> No; that section refers only to races, not to wakeup settings.
> 
>> At the end of this mail is the log during system suspend/resume
>>
>> You can notice the following sequence
>>
>> -ehci runtime suspends
>> -system suspend triggered
>> -ehci runtime resumes
>> -ehci suspends (uses new wakeup settings)
>> -system wakeup triggered
>> -ehci resumes
>> -ehci runtime suspends
> 
> This is because the root hub was runtime suspended with the wrong
> wakeup setting.  The USB core, which is careful about these things,
> resumed and re-suspended it with the proper wakeup setting.  In the 
> process, the controller had to be runtime resumed as well.
> 
> Try doing the test over again, but this time with the root hub enabled 
> for wakeup and the controller disabled.  (I know this is a bizarre 
> combination, but try it anyway.)  Also, after the system wakes up, see 
> whether the root hub and controller get runtime suspended.
> 

The first part of the test caught the problem were we were trying to access
EHCI registers when HW is not accessible. So this was a good test case.

>>> For the third problem, suppose the controller was runtime suspended
>>> when the system went to sleep.  When the system wakes up, the
>>> controller will become active, so you have to inform the runtime PM
>>> core about its change of state.  Basically, if omap_ehci_resume() sees
>>> that ehci_resume() returned 0 then it must do:
>>>
>>> 	pm_runtime_disable(dev);
>>> 	pm_runtime_set_active(dev);
>>> 	pm_runtime_enable(dev);
>>>
>>> All of these issues are discussed (among lots of other material) in 
>>> Documentation/power/runtime_pm.txt.
>>
>> Is this still applicable? Documentation claims
>>
>>    "During system resume it calls pm_runtime_enable() and pm_runtime_put_sync()
>>     for every device right after executing the subsystem-level .resume_early()
>>     callback and right after executing the subsystem-level .resume() callback
>>     for it, respectively."
> 
> Yes, this is applicable, but it is irrelevant to the problem I 
> described.  You still have to tell the runtime PM core that the device 
> is now active.

Right, I understand it now. How does the below code look?

+static int omap_ehci_suspend(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       bool do_wakeup = device_may_wakeup(dev);
+       int ret;
+
+       dev_dbg(dev, "%s may_wakeup %d\n", __func__, do_wakeup);
+
+       if (pm_runtime_status_suspended(dev)) {
+               pm_runtime_get_sync(dev);
+               ehci_resume(hcd, false);
+               ret = ehci_suspend(hcd, do_wakeup);
+               pm_runtime_put_sync(dev);
+
+       } else {
+               ret = ehci_suspend(hcd, do_wakeup);
+       }
+
+       return ret;
+}
+
+static int omap_ehci_resume(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       int ret;
+
+       dev_dbg(dev, "%s\n", __func__);
+
+       ret = ehci_resume(hcd, false);
+       if (!ret) {
+               /*
+                * Controller was powered ON so reflect state
+                */
+               pm_runtime_disable(dev);
+               pm_runtime_set_active(dev);
+               pm_runtime_enable(dev);
+       }
+
+       return ret;
+}
+
+static int omap_ehci_runtime_suspend(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
+
+       dev_dbg(dev, "%s\n", __func__);
+
+       if (omap->bound)
+               ehci_suspend(hcd, true);
+
+       return 0;
+}
+
+static int omap_ehci_runtime_resume(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
+
+       dev_dbg(dev, "%s\n", __func__);
+
+       if (omap->bound)
+               ehci_resume(hcd, false);
+
+       return 0;
+}


cheers,
-roger
Alan Stern July 22, 2013, 3:18 p.m. UTC | #5
On Mon, 22 Jul 2013, Roger Quadros wrote:

> Right, I understand it now. How does the below code look?
> 
> +static int omap_ehci_suspend(struct device *dev)
> +{
> +       struct usb_hcd *hcd = dev_get_drvdata(dev);
> +       bool do_wakeup = device_may_wakeup(dev);
> +       int ret;
> +
> +       dev_dbg(dev, "%s may_wakeup %d\n", __func__, do_wakeup);
> +
> +       if (pm_runtime_status_suspended(dev)) {

You need to do this only when do_wakeup is false.

> +               pm_runtime_get_sync(dev);
> +               ehci_resume(hcd, false);
> +               ret = ehci_suspend(hcd, do_wakeup);
> +               pm_runtime_put_sync(dev);

It would be better to call pm_runtime_resume(dev) at the start instead
of pm_runtime_get_sync(), and eliminate the last two lines.  Then the
ehci_suspend() below could be moved outside the "if" statement.

Alternatively, if you are able to turn off the wakeup setting without
going through an entire resume/suspend cycle, that would be preferable.

> +
> +       } else {
> +               ret = ehci_suspend(hcd, do_wakeup);
> +       }
> +
> +       return ret;
> +}
> +
> +static int omap_ehci_resume(struct device *dev)
> +{
> +       struct usb_hcd *hcd = dev_get_drvdata(dev);
> +       int ret;
> +
> +       dev_dbg(dev, "%s\n", __func__);
> +
> +       ret = ehci_resume(hcd, false);
> +       if (!ret) {
> +               /*
> +                * Controller was powered ON so reflect state
> +                */
> +               pm_runtime_disable(dev);
> +               pm_runtime_set_active(dev);
> +               pm_runtime_enable(dev);
> +       }
> +
> +       return ret;
> +}

All good.

Alan Stern
Roger Quadros July 23, 2013, 9:18 a.m. UTC | #6
On 07/22/2013 06:18 PM, Alan Stern wrote:
> On Mon, 22 Jul 2013, Roger Quadros wrote:
> 
>> Right, I understand it now. How does the below code look?
>>
>> +static int omap_ehci_suspend(struct device *dev)
>> +{
>> +       struct usb_hcd *hcd = dev_get_drvdata(dev);
>> +       bool do_wakeup = device_may_wakeup(dev);
>> +       int ret;
>> +
>> +       dev_dbg(dev, "%s may_wakeup %d\n", __func__, do_wakeup);
>> +
>> +       if (pm_runtime_status_suspended(dev)) {
> 
> You need to do this only when do_wakeup is false.

Right. Missed that.
> 
>> +               pm_runtime_get_sync(dev);
>> +               ehci_resume(hcd, false);
>> +               ret = ehci_suspend(hcd, do_wakeup);
>> +               pm_runtime_put_sync(dev);
> 
> It would be better to call pm_runtime_resume(dev) at the start instead
> of pm_runtime_get_sync(), and eliminate the last two lines.  Then the
> ehci_suspend() below could be moved outside the "if" statement.

Why do I find pm_runtime_* so confusing ;)

> 
> Alternatively, if you are able to turn off the wakeup setting without
> going through an entire resume/suspend cycle, that would be preferable.
> 

As we don't rely on the EHCI controller's interrupt any more after we
power it down, maybe ehci_resume/suspend cycle is not required at all on OMAP,
even if the wakeup setting is disabled during system suspend.

As the wakeup is controlled entirely by pad wakeup, all we need to do is make sure
the IO pad wakeup is configured correctly based on do_wakeup. How this is done
is still in transition but it might be turn out to be as simple as irq_set_irq_wake()

So IMHO, for ehci-omap this should suffice

static int omap_ehci_suspend(struct device *dev)
{
	struct usb_hcd *hcd = dev_get_drvdata(dev);
	bool do_wakeup = device_may_wakeup(dev);
	int ret = 0;

	if (!pm_runtime_status_suspended(dev))
		ret = ehci_suspend(hcd, do_wakeup);

	/* Not tested yet */
	irq_set_irq_wake(hcd->irq, do_wakeup);

	return ret;
}

What do you think?

As the OMAP IO pad wakeup management code [1] is still in transition, I'll wait till
that gets settled and then resend this series.

cheers,
-roger

[1] - http://thread.gmane.org/gmane.linux.ports.arm.omap/99010
Alan Stern July 23, 2013, 2:14 p.m. UTC | #7
On Tue, 23 Jul 2013, Roger Quadros wrote:

> >> +               pm_runtime_get_sync(dev);
> >> +               ehci_resume(hcd, false);
> >> +               ret = ehci_suspend(hcd, do_wakeup);
> >> +               pm_runtime_put_sync(dev);
> > 
> > It would be better to call pm_runtime_resume(dev) at the start instead
> > of pm_runtime_get_sync(), and eliminate the last two lines.  Then the
> > ehci_suspend() below could be moved outside the "if" statement.
> 
> Why do I find pm_runtime_* so confusing ;)

It tries to provide every service anyone might want, and ends up being 
confusingly large.

In this case, though, the reasoning is simple.  We know that after the 
system resumes, the device will be back in the active state.  Hence 
there's no point in calling pm_runtime_put_sync here, other than to 
balance the _get_sync above.  Since there's no danger of another thread 
trying to do a runtime suspend at this point, you don't need to 
increment the usage counter.  Therefore pm_runtime_resume is good 
enough.

> > Alternatively, if you are able to turn off the wakeup setting without
> > going through an entire resume/suspend cycle, that would be preferable.
> > 
> 
> As we don't rely on the EHCI controller's interrupt any more after we
> power it down, maybe ehci_resume/suspend cycle is not required at all on OMAP,
> even if the wakeup setting is disabled during system suspend.
> 
> As the wakeup is controlled entirely by pad wakeup, all we need to do is make sure
> the IO pad wakeup is configured correctly based on do_wakeup. How this is done
> is still in transition but it might be turn out to be as simple as irq_set_irq_wake()
> 
> So IMHO, for ehci-omap this should suffice
> 
> static int omap_ehci_suspend(struct device *dev)
> {
> 	struct usb_hcd *hcd = dev_get_drvdata(dev);
> 	bool do_wakeup = device_may_wakeup(dev);
> 	int ret = 0;
> 
> 	if (!pm_runtime_status_suspended(dev))
> 		ret = ehci_suspend(hcd, do_wakeup);
> 
> 	/* Not tested yet */
> 	irq_set_irq_wake(hcd->irq, do_wakeup);
> 
> 	return ret;
> }
> 
> What do you think?

Nice and simple.  It looks good.

> As the OMAP IO pad wakeup management code [1] is still in transition, I'll wait till
> that gets settled and then resend this series.

Okay.

Alan Stern
diff mbox

Patch

diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
index 9bd7dfe..6d1fde97 100644
--- a/drivers/usb/host/ehci-omap.c
+++ b/drivers/usb/host/ehci-omap.c
@@ -69,6 +69,7 @@  static const char hcd_name[] = "ehci-omap";
 struct omap_hcd {
 	struct usb_phy *phy[OMAP3_HS_USB_PORTS]; /* one PHY for each port */
 	int nports;
+	bool bound;	/* HCD structures are valid */
 };
 
 static inline void ehci_write(void __iomem *base, u32 reg, u32 val)
@@ -159,6 +160,7 @@  static int ehci_hcd_omap_probe(struct platform_device *pdev)
 	hcd->rsrc_start = res->start;
 	hcd->rsrc_len = resource_size(res);
 	hcd->regs = regs;
+	hcd->has_wakeup_irq = true;
 	hcd_to_ehci(hcd)->caps = regs;
 
 	omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
@@ -216,6 +218,8 @@  static int ehci_hcd_omap_probe(struct platform_device *pdev)
 		goto err_pm_runtime;
 	}
 
+	omap->bound = true;
+
 	/*
 	 * Bring PHYs out of reset for non PHY modes.
 	 * Even though HSIC mode is a PHY-less mode, the reset
@@ -232,6 +236,8 @@  static int ehci_hcd_omap_probe(struct platform_device *pdev)
 		usb_phy_set_suspend(omap->phy[i], 0);
 	}
 
+	pm_runtime_put_sync(dev);
+
 	return 0;
 
 err_pm_runtime:
@@ -264,7 +270,9 @@  static int ehci_hcd_omap_remove(struct platform_device *pdev)
 	struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
 	int i;
 
+	pm_runtime_get_sync(dev);
 	usb_remove_hcd(hcd);
+	omap->bound = false;
 
 	for (i = 0; i < omap->nports; i++) {
 		if (omap->phy[i])
@@ -293,15 +301,67 @@  static const struct of_device_id omap_ehci_dt_ids[] = {
 
 MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids);
 
+static int omap_ehci_suspend(struct device *dev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return ehci_suspend(hcd, true);
+}
+
+static int omap_ehci_resume(struct device *dev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return ehci_resume(hcd, false);
+}
+
+static int omap_ehci_runtime_suspend(struct device *dev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
+	bool do_wakeup = device_may_wakeup(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	if (omap->bound)
+		ehci_suspend(hcd, do_wakeup);
+
+	return 0;
+}
+
+static int omap_ehci_runtime_resume(struct device *dev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	if (omap->bound)
+		ehci_resume(hcd, false);
+
+	return 0;
+}
+
+
+static const struct dev_pm_ops omap_ehci_pm_ops = {
+	.suspend = omap_ehci_suspend,
+	.resume = omap_ehci_resume,
+	.runtime_suspend = omap_ehci_runtime_suspend,
+	.runtime_resume = omap_ehci_runtime_resume,
+};
+
 static struct platform_driver ehci_hcd_omap_driver = {
 	.probe			= ehci_hcd_omap_probe,
 	.remove			= ehci_hcd_omap_remove,
 	.shutdown		= ehci_hcd_omap_shutdown,
-	/*.suspend		= ehci_hcd_omap_suspend, */
-	/*.resume		= ehci_hcd_omap_resume, */
 	.driver = {
 		.name		= hcd_name,
 		.of_match_table = omap_ehci_dt_ids,
+		.pm		= &omap_ehci_pm_ops,
 	}
 };