Keshava Munegowda <keshava_mgowda@xxxxxx> writes: > From: Keshava Munegowda <Keshava_mgowda@xxxxxx> > > The global suspend and resume functions for usbhs core driver > are implemented.These routine are called when the global suspend > and resume occurs. Before calling these functions, the > bus suspend and resume of ehci and ohci drivers are called > from runtime pm. > > Signed-off-by: Keshava Munegowda <keshava_mgowda@xxxxxx> First, from what I can see, this is only a partial implementation of runtime PM. What I mean is that the runtime PM methods are used only during the suspend path. The rest of the time the USB host IP block is left enabled, even when nothing is connected. I tested this on my 3530/Overo board, and verified that indeed the usbhost powerdomain hits retention on suspend, but while idle, when nothing is connected, I would expect the driver could be clever enough to use runtime PM (probably using autosuspend timeouts) to disable the hardware as well. > --- > drivers/mfd/omap-usb-host.c | 103 +++++++++++++++++++++++++++++++++++++++++++ > 1 files changed, 103 insertions(+), 0 deletions(-) > > diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c > index 43de12a..32d19e2 100644 > --- a/drivers/mfd/omap-usb-host.c > +++ b/drivers/mfd/omap-usb-host.c > @@ -146,6 +146,10 @@ > #define is_ehci_hsic_mode(x) (x == OMAP_EHCI_PORT_MODE_HSIC) > > > +/* USBHS state bits */ > +#define OMAP_USBHS_INIT 0 > +#define OMAP_USBHS_SUSPEND 4 These additional state bits don't seem to be necessary. For suspend, just check 'pm_runtime_is_suspended()' The init flag is only used in the suspend/resume hooks, but the need for it is a side effect of not correctly using the runtime PM callbacks. Remember that the runtime PM get/put hooks have usage counting. Only when the usage count transitions to/from zero is the actual hardware-level enable/disable (via omap_hwmod) being done. The current code is making the assumption that every call to get/put is going to result in an enable/disable of the hardware. Instead, all of the code that needs to be run only upon actual enable/disable of the hardware should be done in the driver's runtime_suspend/runtime_resume callbacks. These are only called when the hardware actually changes state. Not knowing that much about the EHCI block, upon first glance, it looks like mmuch of what is done in usbhs_enable() should actually be done in the ->runtime_resume() callback, and similarily, much of what is done in usbhs_disable() should be done in the ->runtime_suspend() callback. Another thing to be aware of is that runtime PM can be disabled from userspace. For example, try this: echo on > /sys/devices/platform/omap/usbhs_omap/power/control This disables runtime PM for the device. After doing this and suspending, you'll notice that usbhost powerdomain no longer hits retention on suspend. Setting it back to 'auto' allows it to work again. Because of this, you can not simply call pm_runtime_put() from the static suspend callback. You should check pm_runtime_is_suspended(). If it is, there's nothing to do. If not, the runtime PM callbacks for the subsystem need to manually be called. See drivers/i2c/i2c-omap.c for an example (and check the version in my PM branch, which has a fix required starting with kernel v3.0.) While I'm preaching on runtime PM here, some other things that should be cleaned up because they duplicate what other frameworks are doing: - drivers should not be touching their SYSCONFIG register. This is managed by omap_hwmod - current driver is doing usage counting, but runtime PM core is already handling usage counting. My apologies for not reviewing the runtime PM work in this driver earlier. Some of the problems above come from code that's already in mainline (which I should've reviewed earlier), and some are added with this series. All of them should be cleaned up before merging this. Kevin > struct usbhs_hcd_omap { > struct clk *xclk60mhsp1_ck; > struct clk *xclk60mhsp2_ck; > @@ -165,6 +169,7 @@ struct usbhs_hcd_omap { > u32 usbhs_rev; > spinlock_t lock; > int count; > + unsigned long state; > }; > /*-------------------------------------------------------------------------*/ > > @@ -809,6 +814,8 @@ static int usbhs_enable(struct device *dev) > (pdata->ehci_data->reset_gpio_port[1], 1); > } > > + set_bit(OMAP_USBHS_INIT, &omap->state); > + > end_count: > omap->count++; > spin_unlock_irqrestore(&omap->lock, flags); > @@ -897,6 +904,7 @@ static void usbhs_disable(struct device *dev) > } > > pm_runtime_put_sync(dev); > + clear_bit(OMAP_USBHS_INIT, &omap->state); > > /* The gpio_free migh sleep; so unlock the spinlock */ > spin_unlock_irqrestore(&omap->lock, flags); > @@ -926,10 +934,105 @@ void omap_usbhs_disable(struct device *dev) > } > EXPORT_SYMBOL_GPL(omap_usbhs_disable); > > +#ifdef CONFIG_PM > + > +static int usbhs_resume(struct device *dev) > +{ > + struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); > + struct usbhs_omap_platform_data *pdata = &omap->platdata; > + unsigned long flags = 0; > + > + dev_dbg(dev, "Resuming TI HSUSB Controller\n"); > + > + if (!pdata) { > + dev_dbg(dev, "missing platform_data\n"); > + return -ENODEV; > + } > + > + spin_lock_irqsave(&omap->lock, flags); > + > + if (!test_bit(OMAP_USBHS_INIT, &omap->state) || > + !test_bit(OMAP_USBHS_SUSPEND, &omap->state)) > + goto end_resume; > + > + pm_runtime_get_sync(dev); > + > + if (is_omap_usbhs_rev2(omap)) { > + if (is_ehci_tll_mode(pdata->port_mode[0])) { > + clk_enable(omap->usbhost_p1_fck); > + clk_enable(omap->usbtll_p1_fck); > + } > + if (is_ehci_tll_mode(pdata->port_mode[1])) { > + clk_enable(omap->usbhost_p2_fck); > + clk_enable(omap->usbtll_p2_fck); > + } > + clk_enable(omap->utmi_p1_fck); > + clk_enable(omap->utmi_p2_fck); > + } > + clear_bit(OMAP_USBHS_SUSPEND, &omap->state); > + > +end_resume: > + spin_unlock_irqrestore(&omap->lock, flags); > + return 0; > +} > + > + > +static int usbhs_suspend(struct device *dev) > +{ > + struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); > + struct usbhs_omap_platform_data *pdata = &omap->platdata; > + unsigned long flags = 0; > + > + dev_dbg(dev, "Suspending TI HSUSB Controller\n"); > + > + if (!pdata) { > + dev_dbg(dev, "missing platform_data\n"); > + return -ENODEV; > + } > + > + spin_lock_irqsave(&omap->lock, flags); > + > + if (!test_bit(OMAP_USBHS_INIT, &omap->state) || > + test_bit(OMAP_USBHS_SUSPEND, &omap->state)) > + goto end_suspend; > + > + if (is_omap_usbhs_rev2(omap)) { > + if (is_ehci_tll_mode(pdata->port_mode[0])) { > + clk_disable(omap->usbhost_p1_fck); > + clk_disable(omap->usbtll_p1_fck); > + } > + if (is_ehci_tll_mode(pdata->port_mode[1])) { > + clk_disable(omap->usbhost_p2_fck); > + clk_disable(omap->usbtll_p2_fck); > + } > + clk_disable(omap->utmi_p2_fck); > + clk_disable(omap->utmi_p1_fck); > + } > + > + set_bit(OMAP_USBHS_SUSPEND, &omap->state); > + pm_runtime_put_sync(dev); > + > +end_suspend: > + spin_unlock_irqrestore(&omap->lock, flags); > + return 0; > +} > + > + > +static const struct dev_pm_ops usbhsomap_dev_pm_ops = { > + .suspend = usbhs_suspend, > + .resume = usbhs_resume, > +}; > + > +#define USBHS_OMAP_DEV_PM_OPS (&usbhsomap_dev_pm_ops) > +#else > +#define USBHS_OMAP_DEV_PM_OPS NULL > +#endif > + > static struct platform_driver usbhs_omap_driver = { > .driver = { > .name = (char *)usbhs_driver_name, > .owner = THIS_MODULE, > + .pm = USBHS_OMAP_DEV_PM_OPS, > }, > .remove = __exit_p(usbhs_omap_remove), > }; -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html