diff mbox

[RFC,4/6] USB: ehci-omap: Suspend the controller during bus suspend

Message ID 51CAEEC4.4030304@ti.com (mailing list archive)
State New, archived
Headers show

Commit Message

Roger Quadros June 26, 2013, 1:38 p.m. UTC
On 06/25/2013 08:38 PM, Alan Stern wrote:
> On Tue, 25 Jun 2013, Roger Quadros wrote:
> 
>> On 06/24/2013 10:34 PM, Alan Stern wrote:
>>> On Mon, 24 Jun 2013, Roger Quadros wrote:
>>>
>>>> OK I've tried to handle all this in an alternate way. Now the controller suspend/resume
>>>> and runtime suspend/resume is independent of bus suspend.
>>>>
>>>> The controller now runtime suspends when all devices on the bus have suspended and
>>>> the hub auto suspends. NOTE: HW_ACCESSIBLE is still set on runtime_suspend.
>>>> The challenge here is to process the interrupt in this state.
>>>
>>> The situation is a little peculiar.  Does the hardware really use the
>>> same IRQ for reporting wakeup events when the controller is suspended
>>> and for reporting normal I/O events?
>>
>> No and yes :). Actually the Pad wakeup comes as a separate IRQ from hardware.
>> The omap pinctrl driver captures that, determines which pad caused the wakeup and
>> routes it to the appropriate interrupt based on the mapping provided in the device tree.
>> In the ehci-omap case we provide the EHCI IRQ number in the mapping for the USB host pads.
> 
> Could the mapping be changed so that a different interrupt vector was 
> used for wakeups and normal I/O?  That would make this a little easier, 
> although it wouldn't solve the general problem.

I'm not sure which IRQ we can map it to, but it could be mapped to some
free IRQ number. Since it doesn't make things easier, I think we can leave
it as it is for now.

> 
>>>>  	if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd)))
>>>>  		rc = IRQ_NONE;
>>>> +	else if (pm_runtime_status_suspended(hcd->self.controller)) {
>>>> +		/*
>>>> +		 * We can't handle it yet so disable IRQ, make note of it
>>>> +		 * and resume root hub (i.e. controller as well)
>>>> +		 */
>>>> +		disable_irq_nosync(hcd->irq);
>>>> +		set_bit(HCD_FLAG_IRQ_DISABLED, &hcd->flags);
>>>> +		usb_hcd_resume_root_hub(hcd);
>>>> +		rc = IRQ_HANDLED;
>>>> +	}
>>>
>>> This part will have to be different.
>>>
>>> Certainly if HCD_DEAD(hcd) then we want to return IRQ_NONE.  Likewise
>>> if (!HCD_HW_ACCESSIBLE(hcd) && !hcd->has_wakeup_interrupts).  In all
>>> other cases we have to call the HCD's interrupt handler.
> 
> There's still a race problem.  Suppose a normal wakeup interrupt occurs
> just before or as the controller gets suspended.  By the time the code
> here runs, HCD_HW_ACCESSIBLE may have been cleared by the suspend
> routine.  The interrupt would be lost.  Depending on the design of the
> controller, the entire wakeup signal could end up getting lost as well.

But if I call ehci_suspend() in the runtime_suspend handler, this race
won't happen right?

> 
> Do you know how the OMAP EHCI controller behaves?  Under what 
> conditions does it send the wakeup IRQ?  How do you tell it to turn off 
> the wakeup IRQ?

Once the controller is suspended, the wakeup IRQ comes out-of-band. i.e. through
pad wakeup and pinctrl subsystem.
The only way to turn that wakeup off is to disable the wakeup enable bit on the pad.
This could be done by not putting the pins in the IDLE_WAKEUP state during
suspend.

ff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
>> index d53547d..24e21a2 100644
>> --- a/drivers/usb/core/hcd.c
>> +++ b/drivers/usb/core/hcd.c
>> @@ -2136,6 +2136,11 @@ static void hcd_resume_work(struct work_struct *work)
>>  	usb_lock_device(udev);
>>  	usb_remote_wakeup(udev);
>>  	usb_unlock_device(udev);
>> +	if (HCD_IRQ_DISABLED(hcd)) {
>> +		/* Interrupt was disabled */
>> +		clear_bit(HCD_FLAG_IRQ_DISABLED, &hcd->flags);
>> +		enable_irq(hcd->irq);
>> +	}
>>  }
>>  
>>  /**
>> @@ -2223,7 +2228,9 @@ irqreturn_t usb_hcd_irq (int irq, void *__hcd)
>>  	 */
>>  	local_irq_save(flags);
>>  
>> -	if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd)))
>> +	if (unlikely(HCD_DEAD(hcd)))
>> +		rc = IRQ_NONE;
>> +	else if (!HCD_HW_ACCESSIBLE(hcd) && !hcd->has_wakeup_irq)
>>  		rc = IRQ_NONE;
> 
> Add an unlikely() here too.
> 
OK.

>>  	else if (hcd->driver->irq(hcd) == IRQ_NONE)
>>  		rc = IRQ_NONE;
>> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
>> index 246e124..1844e31 100644
>> --- a/drivers/usb/host/ehci-hcd.c
>> +++ b/drivers/usb/host/ehci-hcd.c
>> @@ -689,6 +689,21 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
>>  
>>  	spin_lock (&ehci->lock);
>>  
>> +	if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) {
>> +		/*
>> +		 * We got a wakeup interrupt while the controller was
>> +		 * suspending or suspended.  We can't handle it now, so
>> +		 * disable the IRQ and resume the root hub (and hence
>> +		 * the controller too).
>> +		 */
>> +		disable_irq_nosync(hcd->irq);
>> +		set_bit(HCD_FLAG_IRQ_DISABLED, &hcd->flags);
> 
> To be safe, let's put these two statements inside
> 
> 		if (hcd->has_wakeup_irq) { ... }
> 
> I think if the right sort of race occurs, we could end up here even 
> when hcd->has_wakeup_irq is clear.  So this test is needed.  We don't 
> want to disable a shared IRQ line.
> 

OK.

>> +		spin_unlock(&ehci->lock);
>> +
>> +		usb_hcd_resume_root_hub(hcd);
>> +		return IRQ_HANDLED;
>> +	}
>> +
>>  	status = ehci_readl(ehci, &ehci->regs->status);
>>  
>>  	/* e.g. cardbus physical eject */
>>
>> diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
>> index 16d7150..ead7d12 100644
>> --- a/drivers/usb/host/ehci-omap.c
>> +++ b/drivers/usb/host/ehci-omap.c
> 
> This part seems reasonable, once the runtime PM routines are fixed.
> 

Thanks for the review.

I updated the ehci-omap.c driver to call ehci_suspend/resume during runtime_suspend/resume.
After that, it stopped detecting the port status change event when a device was plugged
to an external HUB. The wakeup irq was coming and the root hub/controller were being resumed,
but after that, no hub_irq.

Adding some delay (or printk) somewhere in the resume path fixes the issue. I'm not sure what
is going on and why the delay is needed. Below is the ehci-omap patch. I've put the delay
in the runtime_resume handler.

e.g. log

[    8.674377] usb usb1: usb wakeup-resume
[    8.678833] ehci-omap 48064800.ehci: omap_ehci_runtime_resume
[    8.695190] usb usb1: usb auto-resume
[    8.699066] ehci-omap 48064800.ehci: resume root hub
[    8.704437] hub 1-0:1.0: hub_resume
[    8.708312] hub 1-0:1.0: port 2: status 0507 change 0000
[    8.714630] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000

<---- gets stuck here in the failing case---->

[    8.723541] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0004
[    8.729400] ehci-omap 48064800.ehci: GetStatus port:2 status 001005 0  ACK POWER sig=se0 PE CONNECT
[    8.753204] usb 1-2: usb wakeup-resume
[    8.757293] usb 1-2: finish resume
[    8.761627] hub 1-2:1.0: hub_resume

Comments

Alan Stern June 27, 2013, 3:40 p.m. UTC | #1
On Wed, 26 Jun 2013, Roger Quadros wrote:

> > Could the mapping be changed so that a different interrupt vector was 
> > used for wakeups and normal I/O?  That would make this a little easier, 
> > although it wouldn't solve the general problem.
> 
> I'm not sure which IRQ we can map it to, but it could be mapped to some
> free IRQ number. Since it doesn't make things easier, I think we can leave
> it as it is for now.

All right.

> > There's still a race problem.  Suppose a normal wakeup interrupt occurs
> > just before or as the controller gets suspended.  By the time the code
> > here runs, HCD_HW_ACCESSIBLE may have been cleared by the suspend
> > routine.  The interrupt would be lost.  Depending on the design of the
> > controller, the entire wakeup signal could end up getting lost as well.
> 
> But if I call ehci_suspend() in the runtime_suspend handler, this race
> won't happen right?

That race doesn't apply to your system anyway; it matters only on 
systems where hcd->has_wakeup_irq isn't set.  The only way to fix it 
involves changing ehci_suspend() somewhat (and making the equivalent 
change for other HCDs too).  Those musings above were just me thinking 
out loud about the problems involved in implementing reliable wakeups.

> > Do you know how the OMAP EHCI controller behaves?  Under what 
> > conditions does it send the wakeup IRQ?  How do you tell it to turn off 
> > the wakeup IRQ?
> 
> Once the controller is suspended, the wakeup IRQ comes out-of-band. i.e. through
> pad wakeup and pinctrl subsystem.
> The only way to turn that wakeup off is to disable the wakeup enable bit on the pad.
> This could be done by not putting the pins in the IDLE_WAKEUP state during
> suspend.

That's not what I meant.  Never mind the pinctrl; I was asking about
the EHCI controller itself.  Under what circumstances does the
controller assert its wakeup signal?  And how do you tell it to stop
asserting that signal?

> Thanks for the review.
> 
> I updated the ehci-omap.c driver to call ehci_suspend/resume during runtime_suspend/resume.
> After that, it stopped detecting the port status change event when a device was plugged
> to an external HUB. The wakeup irq was coming and the root hub/controller were being resumed,
> but after that, no hub_irq.

Wait a minute.  I'm not clear on what happened.  You're starting out
with the controller, the root hub, and the external hub all suspended, 
right?  Then you plugged a new device into the external hub.  This 
caused the controller and the root hub to wake up, but not the external 
hub?

> Adding some delay (or printk) somewhere in the resume path fixes the issue. I'm not sure what
> is going on and why the delay is needed. Below is the ehci-omap patch. I've put the delay
> in the runtime_resume handler.
> 
> e.g. log
> 
> [    8.674377] usb usb1: usb wakeup-resume
> [    8.678833] ehci-omap 48064800.ehci: omap_ehci_runtime_resume
> [    8.695190] usb usb1: usb auto-resume
> [    8.699066] ehci-omap 48064800.ehci: resume root hub
> [    8.704437] hub 1-0:1.0: hub_resume
> [    8.708312] hub 1-0:1.0: port 2: status 0507 change 0000
> [    8.714630] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000
> 
> <---- gets stuck here in the failing case---->
> 
> [    8.723541] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0004
> [    8.729400] ehci-omap 48064800.ehci: GetStatus port:2 status 001005 0  ACK POWER sig=se0 PE CONNECT
> [    8.753204] usb 1-2: usb wakeup-resume
> [    8.757293] usb 1-2: finish resume
> [    8.761627] hub 1-2:1.0: hub_resume

Yeah, we need more debugging info.  In ehci_irq(), right after the
"pstatus = ehci_readl(..." line, what is the value of pstatus?  And in
the GetPortStatus case of ehci_hub_control(), right after the "temp =
ehci_readl(..." line, what is the value of temp?

> @@ -286,15 +293,70 @@ 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);
> +	bool do_wakeup = device_may_wakeup(dev);
> +
> +	dev_dbg(dev, "%s: do_wakeup: %d\n", __func__, do_wakeup);
> +
> +	return ehci_suspend(hcd, do_wakeup);
> +}
> +
> +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);
> +}

Those two routines look okay.

> +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->initialized)
> +		ehci_suspend(hcd, do_wakeup);

Here you should not use do_wakeup.  The second argument should always
be "true", because wakeup is always enabled during runtime suspend.

Also, why do you need omap->initialized?  Do you think you might get a 
wakeup interrupt before the controller has been fully set up?  I don't 
see how you could, given the pm_runtime_get_sync() call in the probe 
routine.

Alan Stern
Roger Quadros June 28, 2013, 12:20 p.m. UTC | #2
On 06/27/2013 06:40 PM, Alan Stern wrote:
> On Wed, 26 Jun 2013, Roger Quadros wrote:
> 
> 
> That race doesn't apply to your system anyway; it matters only on 
> systems where hcd->has_wakeup_irq isn't set.  The only way to fix it 
> involves changing ehci_suspend() somewhat (and making the equivalent 
> change for other HCDs too).  Those musings above were just me thinking 
> out loud about the problems involved in implementing reliable wakeups.
> 

OK.

>>> Do you know how the OMAP EHCI controller behaves?  Under what 
>>> conditions does it send the wakeup IRQ?  How do you tell it to turn off 
>>> the wakeup IRQ?
>>
>> Once the controller is suspended, the wakeup IRQ comes out-of-band. i.e. through
>> pad wakeup and pinctrl subsystem.
>> The only way to turn that wakeup off is to disable the wakeup enable bit on the pad.
>> This could be done by not putting the pins in the IDLE_WAKEUP state during
>> suspend.
> 
> That's not what I meant.  Never mind the pinctrl; I was asking about
> the EHCI controller itself.  Under what circumstances does the
> controller assert its wakeup signal?  And how do you tell it to stop
> asserting that signal?

I believe this would be through the EHCI Interrupt enable register (USBINTR).
I'm not aware of any other mechanism.

>> I updated the ehci-omap.c driver to call ehci_suspend/resume during runtime_suspend/resume.
>> After that, it stopped detecting the port status change event when a device was plugged
>> to an external HUB. The wakeup irq was coming and the root hub/controller were being resumed,
>> but after that, no hub_irq.
> 
> Wait a minute.  I'm not clear on what happened.  You're starting out
> with the controller, the root hub, and the external hub all suspended, 
> right?  Then you plugged a new device into the external hub.  This 

This is right.

> caused the controller and the root hub to wake up, but not the external 
> hub?
>
Right. It seems the external hub has signaled remote wakeup but the kernel doesn't
resume the root hub's port it is connected to.

By observing the detailed logs below you can see that the root hub does not generate
an INTerrupt transaction to notify the port status change event. I've captured the pstatus
and GetPortStatus info as well.

Failing case
------------

[   16.108032] usb usb1: usb auto-resume
[   16.108062] ehci-omap 48064800.ehci: resume root hub
[   16.108154] hub 1-0:1.0: hub_resume
[   16.108398] ehci_hub_control GetPortStatus, port 1 temp = 0x1000
[   16.108459] ehci_hub_control GetPortStatus, port 2 temp = 0x14c5
[   16.108551] hub 1-0:1.0: port 2: status 0507 change 0000
[   16.108612] ehci_hub_control GetPortStatus, port 3 temp = 0x1000
[   16.108642] hub 1-0:1.0: hub_activate submitting urb
[   16.109222] ehci_irq port 3 pstatus 0x1000
[   16.109222] ehci_irq port 2 pstatus 0x14c5
[   16.109252] ehci_irq port 1 pstatus 0x1000
[   16.109374] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000

Passing case
------------

/ # [   19.704589] usb usb1: usb wakeup-resume
[   19.709075] ehci-omap 48064800.ehci: omap_ehci_runtime_resume
[   19.715423] usb usb1: usb auto-resume
[   19.719299] ehci-omap 48064800.ehci: resume root hub
[   19.724670] hub 1-0:1.0: hub_resume
[   19.728424] ehci_hub_control GetPortStatus, port 1 temp = 0x1000
[   19.734863] ehci_hub_control GetPortStatus, port 2 temp = 0x14c5
[   19.741271] hub 1-0:1.0: port 2: status 0507 change 0000
[   19.746948] ehci_hub_control GetPortStatus, port 3 temp = 0x1000
[   19.753448] hub 1-0:1.0: hub_activate submitting urb
[   19.759216] ehci_irq port 3 pstatus 0x1000
[   19.763519] ehci_irq port 2 pstatus 0x14c5
[   19.767822] ehci_irq port 1 pstatus 0x1000
[   19.772155] hub 1-0:1.0: hub_irq 

<---This is the Port Status change hub INT which is missing in the failing case--->

[   19.775604] hub 1-0:1.0: hub_irq submitting urb
[   19.780548] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0004
[   19.786407] hub 1-0:1.0: hub_irq
[   19.789916] hub 1-0:1.0: hub_irq submitting urb
[   19.794799] ehci_hub_control GetPortStatus, port 2 temp = 0x14c5
[   19.801147] ehci-omap 48064800.ehci: GetStatus port:2 status 001005 0  ACK POWER sig=se0 PE CONNECT
[   19.822937] usb 1-2: usb wakeup-resume
[   19.826995] ehci_hub_control GetPortStatus, port 2 temp = 0x1005
[   19.833404] usb 1-2: finish resume
[   19.837738] hub 1-2:1.0: hub_resume
[   19.841613] hub 1-2:1.0: port 1: status 0507 change 0000
[   19.848358] hub 1-2:1.0: port 4: status 0101 change 0001
[   19.962890] hub 1-2:1.0: hub_activate submitting urb
[   19.968139] ehci-omap 48064800.ehci: reused qh dd450200 schedule
[   19.974456] usb 1-2: link qh256-0001/dd450200 start 1 [1/0 us]
[   19.980743] hub 1-0:1.0: resume on port 2, status 0
[   19.985961] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000
[   19.991760] hub 1-2:1.0: state 7 ports 5 chg 0010 evt 0000
[   19.997741] hub 1-2:1.0: port 4, status 0101, change 0000, 12 Mb/s
[   20.083129] usb 1-2.4: new high-speed USB device number 4 using ehci-omap
<snip>

One more thing is that the delay didn't help if I reduce printk verbosity to 7.
So the debug prints are also adding some delays around the place which is affecting
the behaviour.

>> +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->initialized)
>> +		ehci_suspend(hcd, do_wakeup);
> 
> Here you should not use do_wakeup.  The second argument should always
> be "true", because wakeup is always enabled during runtime suspend.

OK.

> 
> Also, why do you need omap->initialized?  Do you think you might get a 
> wakeup interrupt before the controller has been fully set up?  I don't 
> see how you could, given the pm_runtime_get_sync() call in the probe 
> routine.
> 

During probe we need to runtime_resume the device before usb_add_hcd() since the
controller clocks must be enabled before any registers are accessed.
However, we cannot call ehci_resume() before usb_add_hcd(). So to prevent this
chicken & egg situation, I've used the omap->initialized flag. It only indicates that
the ehci structures are initialized and we can call ehci_resume/suspend().

cheers,
-roger
Alan Stern June 28, 2013, 7:06 p.m. UTC | #3
On Fri, 28 Jun 2013, Roger Quadros wrote:

> > That's not what I meant.  Never mind the pinctrl; I was asking about
> > the EHCI controller itself.  Under what circumstances does the
> > controller assert its wakeup signal?  And how do you tell it to stop
> > asserting that signal?
> 
> I believe this would be through the EHCI Interrupt enable register (USBINTR).
> I'm not aware of any other mechanism.

That's strange, because ehci_suspend() sets the intr_enable register to 
0.  So how do you ever get any wakeup interrupts at all?

> Right. It seems the external hub has signaled remote wakeup but the kernel doesn't
> resume the root hub's port it is connected to.
> 
> By observing the detailed logs below you can see that the root hub does not generate
> an INTerrupt transaction to notify the port status change event. I've captured the pstatus
> and GetPortStatus info as well.

We don't need an interrupt.  The driver is supposed to detect the
remote wakeup sent by the external hub all by itself.

> Failing case
> ------------
> 
> [   16.108032] usb usb1: usb auto-resume
> [   16.108062] ehci-omap 48064800.ehci: resume root hub
> [   16.108154] hub 1-0:1.0: hub_resume
> [   16.108398] ehci_hub_control GetPortStatus, port 1 temp = 0x1000
> [   16.108459] ehci_hub_control GetPortStatus, port 2 temp = 0x14c5

Here's where we should detect it.  Look at the GetPortStatus case in
ehci_hub_control(); the PORT_RESUME bit (0x0040) is set in temp, so the
"Remote Wakeup received?" code should run.  In particular, these lines
should run:

			/* resume signaling for 20 msec */
			ehci->reset_done[wIndex] = jiffies
					+ msecs_to_jiffies(20);
			usb_hcd_start_port_resume(&hcd->self, wIndex);
			/* check the port again */
			mod_timer(&ehci_to_hcd(ehci)->rh_timer,
					ehci->reset_done[wIndex]);

Therefore 20 ms later, around timestamp 16.128459,
ehci_hub_status_data() should have been called.  At that time, the
root-hub port should have been fully resumed.

> [   16.108551] hub 1-0:1.0: port 2: status 0507 change 0000
> [   16.108612] ehci_hub_control GetPortStatus, port 3 temp = 0x1000
> [   16.108642] hub 1-0:1.0: hub_activate submitting urb
> [   16.109222] ehci_irq port 3 pstatus 0x1000
> [   16.109222] ehci_irq port 2 pstatus 0x14c5
> [   16.109252] ehci_irq port 1 pstatus 0x1000
> [   16.109374] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000

But apparently nothing happened.  Why not?  Did the rh_timer get reset?  
Maybe you can find out what went wrong.

(Hmmm, we seem to be missing a

			set_bit(wIndex, &ehci->resuming_ports);

line in there...)

> > Also, why do you need omap->initialized?  Do you think you might get a 
> > wakeup interrupt before the controller has been fully set up?  I don't 
> > see how you could, given the pm_runtime_get_sync() call in the probe 
> > routine.
> > 
> 
> During probe we need to runtime_resume the device before usb_add_hcd() since the
> controller clocks must be enabled before any registers are accessed.
> However, we cannot call ehci_resume() before usb_add_hcd(). So to prevent this
> chicken & egg situation, I've used the omap->initialized flag. It only indicates that
> the ehci structures are initialized and we can call ehci_resume/suspend().

Ah, yes.  Other subsystems, such as PCI, face exactly the same problem.

You probably shouldn't call it "initialized", though, because the same
issue arises in ehci_hcd_omap_remove() -- the pm_runtime_put_sync()  
there would end up calling ehci_suspend() after usb_remove_hcd().  
"bound" or "started" would be better names.

Alan Stern
Roger Quadros July 1, 2013, 8:16 a.m. UTC | #4
On 06/28/2013 10:06 PM, Alan Stern wrote:
> On Fri, 28 Jun 2013, Roger Quadros wrote:
> 
>>> That's not what I meant.  Never mind the pinctrl; I was asking about
>>> the EHCI controller itself.  Under what circumstances does the
>>> controller assert its wakeup signal?  And how do you tell it to stop
>>> asserting that signal?
>>
>> I believe this would be through the EHCI Interrupt enable register (USBINTR).
>> I'm not aware of any other mechanism.
> 
> That's strange, because ehci_suspend() sets the intr_enable register to 
> 0.  So how do you ever get any wakeup interrupts at all?

Because after ehci_suspend() for OMAP, we solely rely on the out of band wake up
mechanism. i.e. Pad wakeup.
> 
>> Right. It seems the external hub has signaled remote wakeup but the kernel doesn't
>> resume the root hub's port it is connected to.
>>
>> By observing the detailed logs below you can see that the root hub does not generate
>> an INTerrupt transaction to notify the port status change event. I've captured the pstatus
>> and GetPortStatus info as well.
> 
> We don't need an interrupt.  The driver is supposed to detect the
> remote wakeup sent by the external hub all by itself.

OK. Then it could point to a bug in our stack.
> 
>> Failing case
>> ------------
>>
>> [   16.108032] usb usb1: usb auto-resume
>> [   16.108062] ehci-omap 48064800.ehci: resume root hub
>> [   16.108154] hub 1-0:1.0: hub_resume
>> [   16.108398] ehci_hub_control GetPortStatus, port 1 temp = 0x1000
>> [   16.108459] ehci_hub_control GetPortStatus, port 2 temp = 0x14c5
> 
> Here's where we should detect it.  Look at the GetPortStatus case in
> ehci_hub_control(); the PORT_RESUME bit (0x0040) is set in temp, so the
> "Remote Wakeup received?" code should run.  In particular, these lines
> should run:
> 
> 			/* resume signaling for 20 msec */
> 			ehci->reset_done[wIndex] = jiffies
> 					+ msecs_to_jiffies(20);
> 			usb_hcd_start_port_resume(&hcd->self, wIndex);
> 			/* check the port again */
> 			mod_timer(&ehci_to_hcd(ehci)->rh_timer,
> 					ehci->reset_done[wIndex]);
> 
> Therefore 20 ms later, around timestamp 16.128459,
> ehci_hub_status_data() should have been called.  At that time, the
> root-hub port should have been fully resumed.

OK. right.
> 
>> [   16.108551] hub 1-0:1.0: port 2: status 0507 change 0000
>> [   16.108612] ehci_hub_control GetPortStatus, port 3 temp = 0x1000
>> [   16.108642] hub 1-0:1.0: hub_activate submitting urb
>> [   16.109222] ehci_irq port 3 pstatus 0x1000
>> [   16.109222] ehci_irq port 2 pstatus 0x14c5
>> [   16.109252] ehci_irq port 1 pstatus 0x1000
>> [   16.109374] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000
> 
> But apparently nothing happened.  Why not?  Did the rh_timer get reset?  
> Maybe you can find out what went wrong.
> 

Sure. I'll investigate.

> (Hmmm, we seem to be missing a
> 
> 			set_bit(wIndex, &ehci->resuming_ports);
> 
> line in there...)
> 
>>> Also, why do you need omap->initialized?  Do you think you might get a 
>>> wakeup interrupt before the controller has been fully set up?  I don't 
>>> see how you could, given the pm_runtime_get_sync() call in the probe 
>>> routine.
>>>
>>
>> During probe we need to runtime_resume the device before usb_add_hcd() since the
>> controller clocks must be enabled before any registers are accessed.
>> However, we cannot call ehci_resume() before usb_add_hcd(). So to prevent this
>> chicken & egg situation, I've used the omap->initialized flag. It only indicates that
>> the ehci structures are initialized and we can call ehci_resume/suspend().
> 
> Ah, yes.  Other subsystems, such as PCI, face exactly the same problem.
> 
> You probably shouldn't call it "initialized", though, because the same
> issue arises in ehci_hcd_omap_remove() -- the pm_runtime_put_sync()  
> there would end up calling ehci_suspend() after usb_remove_hcd().  
> "bound" or "started" would be better names.
> 
OK. Started seems fine.

cheers,
-roger
Alan Stern July 1, 2013, 4:24 p.m. UTC | #5
On Mon, 1 Jul 2013, Roger Quadros wrote:

> On 06/28/2013 10:06 PM, Alan Stern wrote:
> > On Fri, 28 Jun 2013, Roger Quadros wrote:
> > 
> >>> That's not what I meant.  Never mind the pinctrl; I was asking about
> >>> the EHCI controller itself.  Under what circumstances does the
> >>> controller assert its wakeup signal?  And how do you tell it to stop
> >>> asserting that signal?
> >>
> >> I believe this would be through the EHCI Interrupt enable register (USBINTR).
> >> I'm not aware of any other mechanism.
> > 
> > That's strange, because ehci_suspend() sets the intr_enable register to 
> > 0.  So how do you ever get any wakeup interrupts at all?
> 
> Because after ehci_suspend() for OMAP, we solely rely on the out of band wake up
> mechanism. i.e. Pad wakeup.

I don't know what Pad wakeup is.  The wakeup signal has to originate 
from the EHCI controller, doesn't it?  If not, how does the Pad know 
when a wakeup is needed?


> We can't enable_irq at the start as the controller will only be resumed
> after usb_remote_wakeup().

Hmmm, yes, I had realized that at one point and then forgot it.  You
don't want an IRQ to arrive before you're prepared to handle it.

This suggests that you really want to call enable_irq() call at the end
of ehci_resume() instead of the beginning.  (Convert the "return 0" to
a jump to the end of the routine.)

Alan Stern
Felipe Balbi July 1, 2013, 4:49 p.m. UTC | #6
On Mon, Jul 01, 2013 at 12:24:07PM -0400, Alan Stern wrote:
> On Mon, 1 Jul 2013, Roger Quadros wrote:
> 
> > On 06/28/2013 10:06 PM, Alan Stern wrote:
> > > On Fri, 28 Jun 2013, Roger Quadros wrote:
> > > 
> > >>> That's not what I meant.  Never mind the pinctrl; I was asking about
> > >>> the EHCI controller itself.  Under what circumstances does the
> > >>> controller assert its wakeup signal?  And how do you tell it to stop
> > >>> asserting that signal?
> > >>
> > >> I believe this would be through the EHCI Interrupt enable register (USBINTR).
> > >> I'm not aware of any other mechanism.
> > > 
> > > That's strange, because ehci_suspend() sets the intr_enable register to 
> > > 0.  So how do you ever get any wakeup interrupts at all?
> > 
> > Because after ehci_suspend() for OMAP, we solely rely on the out of band wake up
> > mechanism. i.e. Pad wakeup.
> 
> I don't know what Pad wakeup is.  The wakeup signal has to originate 
> from the EHCI controller, doesn't it?  If not, how does the Pad know 
> when a wakeup is needed?

That's really an OMAP thing, I guess. Pad wakeup sits in the PRCM (IIRC)
inside and always on power domain. EHCI sits in another power domain
which be turned off. When it's turned off (power gated and clock gated)
the EHCI block has no means to wakeup whatsoever. That's when pad wakeup
comes into play. It is generated when PRCM sees a change in the actual
pad of the die. To check who should 'own' the wakeup, it checks the
muxing settings to verify whose pad is that.
Alan Stern July 1, 2013, 9:01 p.m. UTC | #7
On Mon, 1 Jul 2013, Felipe Balbi wrote:

> > I don't know what Pad wakeup is.  The wakeup signal has to originate 
> > from the EHCI controller, doesn't it?  If not, how does the Pad know 
> > when a wakeup is needed?
> 
> That's really an OMAP thing, I guess. Pad wakeup sits in the PRCM (IIRC)
> inside and always on power domain. EHCI sits in another power domain
> which be turned off. When it's turned off (power gated and clock gated)
> the EHCI block has no means to wakeup whatsoever. That's when pad wakeup
> comes into play. It is generated when PRCM sees a change in the actual
> pad of the die. To check who should 'own' the wakeup, it checks the
> muxing settings to verify whose pad is that.

How does the PRCM know which changes should generate wakeup events?  
In the EHCI controller, this is managed by the settings of the WKOC_E,
WKDSCNNT_E, and WKCNNT_E bits in the PORTSC registers.  But if EHCI is
powered off, those bits can't be used.

Also, once the wakeup signal has been turned on, how does it get turned 
off again?

Alan Stern
Roger Quadros July 2, 2013, 8:22 a.m. UTC | #8
On 07/02/2013 12:01 AM, Alan Stern wrote:
> On Mon, 1 Jul 2013, Felipe Balbi wrote:
> 
>>> I don't know what Pad wakeup is.  The wakeup signal has to originate 
>>> from the EHCI controller, doesn't it?  If not, how does the Pad know 
>>> when a wakeup is needed?
>>
>> That's really an OMAP thing, I guess. Pad wakeup sits in the PRCM (IIRC)
>> inside and always on power domain. EHCI sits in another power domain
>> which be turned off. When it's turned off (power gated and clock gated)
>> the EHCI block has no means to wakeup whatsoever. That's when pad wakeup
>> comes into play. It is generated when PRCM sees a change in the actual
>> pad of the die. To check who should 'own' the wakeup, it checks the
>> muxing settings to verify whose pad is that.
> 
> How does the PRCM know which changes should generate wakeup events?  

It doesn't know. It will always generate a wakeup on any change in the monitored pins.
We can only configure which pins we want to monitor.
So we can't choose which wakeup events we want to monitor. We just can
enable or disable all events.

> In the EHCI controller, this is managed by the settings of the WKOC_E,
> WKDSCNNT_E, and WKCNNT_E bits in the PORTSC registers.  But if EHCI is
> powered off, those bits can't be used.

Is "powered off" same as ehci_suspend? If yes then how does it work on other systems
for remote wakeup?

> 
> Also, once the wakeup signal has been turned on, how does it get turned 
> off again?

That is taken care of by the OMAP pinctrl driver. It will always turn off the
wakeup signal once the event is detected and forwarded as an IRQ.

I know that all this is a bit ugly :(.

cheers,
-roger
Alan Stern July 2, 2013, 5:17 p.m. UTC | #9
On Tue, 2 Jul 2013, Roger Quadros wrote:

> On 07/02/2013 12:01 AM, Alan Stern wrote:
> > On Mon, 1 Jul 2013, Felipe Balbi wrote:
> > 
> >>> I don't know what Pad wakeup is.  The wakeup signal has to originate 
> >>> from the EHCI controller, doesn't it?  If not, how does the Pad know 
> >>> when a wakeup is needed?
> >>
> >> That's really an OMAP thing, I guess. Pad wakeup sits in the PRCM (IIRC)
> >> inside and always on power domain. EHCI sits in another power domain
> >> which be turned off. When it's turned off (power gated and clock gated)
> >> the EHCI block has no means to wakeup whatsoever. That's when pad wakeup
> >> comes into play. It is generated when PRCM sees a change in the actual
> >> pad of the die. To check who should 'own' the wakeup, it checks the
> >> muxing settings to verify whose pad is that.
> > 
> > How does the PRCM know which changes should generate wakeup events?  
> 
> It doesn't know. It will always generate a wakeup on any change in the monitored pins.
> We can only configure which pins we want to monitor.
> So we can't choose which wakeup events we want to monitor. We just can
> enable or disable all events.
> 
> > In the EHCI controller, this is managed by the settings of the WKOC_E,
> > WKDSCNNT_E, and WKCNNT_E bits in the PORTSC registers.  But if EHCI is
> > powered off, those bits can't be used.
> 
> Is "powered off" same as ehci_suspend? If yes then how does it work on other systems
> for remote wakeup?

Above, Felipe wrote that on OMAP, EHCI sits in a power domain which is
turned off: power gated and clock gated.  ehci_suspend() doesn't do
those things, but they are what I was referring to.

A PCI-based EHCI controller has two power sources: the core well (which
is turned off during suspend) and the auxiliary well (which remains
powered).  That's how remote wakeup works; it uses the auxiliary well.

> > Also, once the wakeup signal has been turned on, how does it get turned 
> > off again?
> 
> That is taken care of by the OMAP pinctrl driver. It will always turn off the
> wakeup signal once the event is detected and forwarded as an IRQ.
> 
> I know that all this is a bit ugly :(.

I still a little confused.  The wakeup signal turns on.  Then the
pinctrl driver sees it, forwards it as an IRQ, and turns the wakeup
signal off.  But what if the IRQ is disabled (as it would be with your
patch)?  Does the IRQ line remain active until it causes an interrupt?  
What eventually turns off the IRQ line?

Alan Stern
Roger Quadros July 3, 2013, 9:13 a.m. UTC | #10
On 07/02/2013 08:17 PM, Alan Stern wrote:
> On Tue, 2 Jul 2013, Roger Quadros wrote:
> 
>> On 07/02/2013 12:01 AM, Alan Stern wrote:
>>> On Mon, 1 Jul 2013, Felipe Balbi wrote:
>>>
>>>>> I don't know what Pad wakeup is.  The wakeup signal has to originate 
>>>>> from the EHCI controller, doesn't it?  If not, how does the Pad know 
>>>>> when a wakeup is needed?
>>>>
>>>> That's really an OMAP thing, I guess. Pad wakeup sits in the PRCM (IIRC)
>>>> inside and always on power domain. EHCI sits in another power domain
>>>> which be turned off. When it's turned off (power gated and clock gated)
>>>> the EHCI block has no means to wakeup whatsoever. That's when pad wakeup
>>>> comes into play. It is generated when PRCM sees a change in the actual
>>>> pad of the die. To check who should 'own' the wakeup, it checks the
>>>> muxing settings to verify whose pad is that.
>>>
>>> How does the PRCM know which changes should generate wakeup events?  
>>
>> It doesn't know. It will always generate a wakeup on any change in the monitored pins.
>> We can only configure which pins we want to monitor.
>> So we can't choose which wakeup events we want to monitor. We just can
>> enable or disable all events.
>>
>>> In the EHCI controller, this is managed by the settings of the WKOC_E,
>>> WKDSCNNT_E, and WKCNNT_E bits in the PORTSC registers.  But if EHCI is
>>> powered off, those bits can't be used.
>>
>> Is "powered off" same as ehci_suspend? If yes then how does it work on other systems
>> for remote wakeup?
> 
> Above, Felipe wrote that on OMAP, EHCI sits in a power domain which is
> turned off: power gated and clock gated.  ehci_suspend() doesn't do
> those things, but they are what I was referring to.

OK right, those things are done by OMAP platform support code.
> 
> A PCI-based EHCI controller has two power sources: the core well (which
> is turned off during suspend) and the auxiliary well (which remains
> powered).  That's how remote wakeup works; it uses the auxiliary well.
> 

OK. Thanks for the info.

>>> Also, once the wakeup signal has been turned on, how does it get turned 
>>> off again?
>>
>> That is taken care of by the OMAP pinctrl driver. It will always turn off the
>> wakeup signal once the event is detected and forwarded as an IRQ.
>>
>> I know that all this is a bit ugly :(.
> 
> I still a little confused.  The wakeup signal turns on.  Then the
> pinctrl driver sees it, forwards it as an IRQ, and turns the wakeup
> signal off.  But what if the IRQ is disabled (as it would be with your
> patch)?  Does the IRQ line remain active until it causes an interrupt?  
> What eventually turns off the IRQ line?
> 

In the beagle/panda board, the USB lines of the OMAP are used in ULPI mode.
Here we are monitoring DATA0, DATA1 and DATA3 lines which get configured as Linestate
and Interrupt of the PHY device whenever the PHY is put into suspend mode. This usually
happens when we suspend the EHCI controller.

When EHCI is suspended, the pinctrl wakeup mechanism is active. Whenever there is
a change in the monitored lines we get the PAD IRQ and hence the EHCI IRQ. We don't really
care much about which line changed state.
(NOTE: while suspending, we didn't disable the EHCI IRQ). So we will always get the first IRQ
and then we disable it and queue a hub_resume_work.
Then EHCI resumes, pinctrl wakeup is disabled and EHCI IRQ is enabled.

When pinctrl wakeup mechanism is active, it clears the wakeup event flag after a PAD IRQ, but it
has no control on disabling the interrupt source. If there is a change in the pad, the
PAD IRQ will fire again.

cheers,
-roger
Felipe Balbi July 3, 2013, 12:57 p.m. UTC | #11
Hi,

On Tue, Jul 02, 2013 at 01:17:58PM -0400, Alan Stern wrote:
> A PCI-based EHCI controller has two power sources: the core well (which
> is turned off during suspend) and the auxiliary well (which remains
> powered).  That's how remote wakeup works; it uses the auxiliary well.

This, kinda, matches what OMAP tries to do with pad wakeup. Just that
pad wakeup sits outside of the device itself. Perhaps we could look into
how PCI handles the aux well and take some inspiration from there.

Any pointers under drivers/pci/ would be great :-)
Roger Quadros July 3, 2013, 1:06 p.m. UTC | #12
On 07/03/2013 03:57 PM, Felipe Balbi wrote:
> Hi,
> 
> On Tue, Jul 02, 2013 at 01:17:58PM -0400, Alan Stern wrote:
>> A PCI-based EHCI controller has two power sources: the core well (which
>> is turned off during suspend) and the auxiliary well (which remains
>> powered).  That's how remote wakeup works; it uses the auxiliary well.
> 
> This, kinda, matches what OMAP tries to do with pad wakeup. Just that
> pad wakeup sits outside of the device itself. Perhaps we could look into
> how PCI handles the aux well and take some inspiration from there.
> 
> Any pointers under drivers/pci/ would be great :-)
> 
From what I understood, auxiliary well is just a power source, and it keeps
the EHCI controller powered even during suspend.

If that is true then it is different from our situation as we power down the
EHCI controller completely.

cheers,
-roger
Felipe Balbi July 3, 2013, 1:15 p.m. UTC | #13
On Wed, Jul 03, 2013 at 04:06:04PM +0300, Roger Quadros wrote:
> On 07/03/2013 03:57 PM, Felipe Balbi wrote:
> > Hi,
> > 
> > On Tue, Jul 02, 2013 at 01:17:58PM -0400, Alan Stern wrote:
> >> A PCI-based EHCI controller has two power sources: the core well (which
> >> is turned off during suspend) and the auxiliary well (which remains
> >> powered).  That's how remote wakeup works; it uses the auxiliary well.
> > 
> > This, kinda, matches what OMAP tries to do with pad wakeup. Just that
> > pad wakeup sits outside of the device itself. Perhaps we could look into
> > how PCI handles the aux well and take some inspiration from there.
> > 
> > Any pointers under drivers/pci/ would be great :-)
> > 
> From what I understood, auxiliary well is just a power source, and it keeps
> the EHCI controller powered even during suspend.
> 
> If that is true then it is different from our situation as we power down the
> EHCI controller completely.

right but our "auxiliary well" keeps PRCM powered which can wake EHCI up
;-)

What I'm saying is that from ehci-omap's perspective, there's very
little difference, specially since we route the wakeup through the same
IRQ line anyway. Perhaps we could take some inspiration from the PCI
land to make our hwmod/omap_device a little easier from driver
perspective.

Or maybe it doesn't make sense ;-)
Roger Quadros July 9, 2013, 1:58 p.m. UTC | #14
On 06/28/2013 10:06 PM, Alan Stern wrote:
> On Fri, 28 Jun 2013, Roger Quadros wrote:
> 
>>> That's not what I meant.  Never mind the pinctrl; I was asking about
>>> the EHCI controller itself.  Under what circumstances does the
>>> controller assert its wakeup signal?  And how do you tell it to stop
>>> asserting that signal?
>>
>> I believe this would be through the EHCI Interrupt enable register (USBINTR).
>> I'm not aware of any other mechanism.
> 
> That's strange, because ehci_suspend() sets the intr_enable register to 
> 0.  So how do you ever get any wakeup interrupts at all?
> 
>> Right. It seems the external hub has signaled remote wakeup but the kernel doesn't
>> resume the root hub's port it is connected to.
>>
>> By observing the detailed logs below you can see that the root hub does not generate
>> an INTerrupt transaction to notify the port status change event. I've captured the pstatus
>> and GetPortStatus info as well.
> 
> We don't need an interrupt.  The driver is supposed to detect the
> remote wakeup sent by the external hub all by itself.
> 
>> Failing case
>> ------------
>>
>> [   16.108032] usb usb1: usb auto-resume
>> [   16.108062] ehci-omap 48064800.ehci: resume root hub
>> [   16.108154] hub 1-0:1.0: hub_resume
>> [   16.108398] ehci_hub_control GetPortStatus, port 1 temp = 0x1000
>> [   16.108459] ehci_hub_control GetPortStatus, port 2 temp = 0x14c5
> 
> Here's where we should detect it.  Look at the GetPortStatus case in
> ehci_hub_control(); the PORT_RESUME bit (0x0040) is set in temp, so the
> "Remote Wakeup received?" code should run.  In particular, these lines
> should run:
> 
> 			/* resume signaling for 20 msec */
> 			ehci->reset_done[wIndex] = jiffies
> 					+ msecs_to_jiffies(20);
> 			usb_hcd_start_port_resume(&hcd->self, wIndex);
> 			/* check the port again */
> 			mod_timer(&ehci_to_hcd(ehci)->rh_timer,
> 					ehci->reset_done[wIndex]);
> 
> Therefore 20 ms later, around timestamp 16.128459,
> ehci_hub_status_data() should have been called.  At that time, the
> root-hub port should have been fully resumed.
> 
>> [   16.108551] hub 1-0:1.0: port 2: status 0507 change 0000
>> [   16.108612] ehci_hub_control GetPortStatus, port 3 temp = 0x1000
>> [   16.108642] hub 1-0:1.0: hub_activate submitting urb
>> [   16.109222] ehci_irq port 3 pstatus 0x1000
>> [   16.109222] ehci_irq port 2 pstatus 0x14c5
>> [   16.109252] ehci_irq port 1 pstatus 0x1000
>> [   16.109374] hub 1-0:1.0: state 7 ports 3 chg 0000 evt 0000
> 
> But apparently nothing happened.  Why not?  Did the rh_timer get reset?  
> Maybe you can find out what went wrong.
> 
> (Hmmm, we seem to be missing a
> 
> 			set_bit(wIndex, &ehci->resuming_ports);
> 
> line in there...)
> 

Right. This is indeed the problem. I've cooked up a patch for that and will send
it separately in a moment.

cheers,
-roger
diff mbox

Patch

diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
index 16d7150..c687610 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 initialized;
 };
 
 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;
@@ -210,6 +212,8 @@  static int ehci_hcd_omap_probe(struct platform_device *pdev)
 		goto err_pm_runtime;
 	}
 
+	omap->initialized = true;
+
 	/*
 	 * Bring PHYs out of reset.
 	 * Even though HSIC mode is a PHY-less mode, the reset
@@ -225,6 +229,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:
@@ -257,6 +263,7 @@  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);
 
 	for (i = 0; i < omap->nports; i++) {
@@ -286,15 +293,70 @@  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);
+	bool do_wakeup = device_may_wakeup(dev);
+
+	dev_dbg(dev, "%s: do_wakeup: %d\n", __func__, do_wakeup);
+
+	return ehci_suspend(hcd, do_wakeup);
+}
+
+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->initialized)
+		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->initialized) {
+		ehci_resume(hcd, false);
+		mdelay(10);
+	}
+
+	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 = of_match_ptr(omap_ehci_dt_ids),
+		.pm		= &omap_ehci_pm_ops,
 	}
 };