From 59d37d8db8630a0ad6ee5eaf322abb08700117f5 Mon Sep 17 00:00:00 2001
From: Andy Luo <yifei.luo@intel.com>
Date: Wed, 24 Nov 2010 16:43:37 +0800
Subject: [PATCH] usb: core: add runtime pm support

This patch enables runtime pm support for usb host driver

Signed-off-by: Andy Luo <yifei.luo@intel.com>
---
 drivers/usb/core/hcd-pci.c |  197 +++++++++++++++++++++++++++++++-------------
 1 files changed, 140 insertions(+), 57 deletions(-)

diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index 1cf2d1e..ed1515f 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -250,6 +250,10 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (retval != 0)
 		goto err4;
 	set_hs_companion(dev, hcd);
+
+	pm_runtime_set_active(&dev->dev);
+	pm_runtime_enable(&dev->dev);
+	pm_runtime_allow(&dev->dev);
 	return retval;
 
  err4:
@@ -292,6 +296,18 @@ void usb_hcd_pci_remove(struct pci_dev *dev)
 	if (!hcd)
 		return;
 
+	pm_runtime_forbid(&dev->dev);
+	pm_runtime_disable(&dev->dev);
+	pm_runtime_set_suspended(&dev->dev);
+
+	/* Fake an interrupt request in order to give the driver a chance
+	 * to test whether the controller hardware has been removed (e.g.,
+	 * cardbus physical eject).
+	 */
+	local_irq_disable();
+	usb_hcd_irq(0, hcd);
+	local_irq_enable();
+
 	usb_remove_hcd(hcd);
 	if (hcd->driver->flags & HCD_MEMORY) {
 		iounmap(hcd->regs);
@@ -317,12 +333,48 @@ void usb_hcd_pci_shutdown(struct pci_dev *dev)
 	if (!hcd)
 		return;
 
-	if (hcd->driver->shutdown)
+	if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) &&
+			hcd->driver->shutdown)
 		hcd->driver->shutdown(hcd);
 }
 EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown);
 
-#ifdef	CONFIG_PM_SLEEP
+#ifdef	CONFIG_PM_OPS
+
+#ifdef	CONFIG_PPC_PMAC
+static void pci_power_config(struct pci_dev *pci_dev, int enable)
+{
+	/* Enanble or disable ASIC clocks for USB */
+	if (machine_is(powermac)) {
+		struct device_node	*of_node;
+
+		of_node = pci_device_to_OF_node(pci_dev);
+		if (of_node)
+			pmac_call_feature(PMAC_FTR_USB_ENABLE,
+					of_node, 0, enable);
+	}
+}
+
+#elif defined(CONFIG_X86_MRST)
+
+static inline void pci_power_config(struct pci_dev *pci_dev, int enable)
+{
+	/* MRST/MFLD */
+	if (enable) {
+		pci_restore_state(pci_dev);
+		pci_set_power_state(pci_dev, PCI_D0);
+	} else {
+		pci_save_state(pci_dev);
+		pci_set_power_state(pci_dev, PCI_D3hot);
+	}
+}
+
+#else
+
+static inline void pci_power_config(struct pci_dev *pci_dev, int enable)
+{}
+
+#endif	/* CONFIG_PPC_PMAC */
 
 static int check_root_hub_suspended(struct device *dev)
 {
@@ -337,7 +389,7 @@ static int check_root_hub_suspended(struct device *dev)
 	return 0;
 }
 
-static int hcd_pci_suspend(struct device *dev)
+static int suspend_common(struct device *dev, bool do_wakeup)
 {
 	struct pci_dev		*pci_dev = to_pci_dev(dev);
 	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);
@@ -374,6 +426,48 @@ static int hcd_pci_suspend(struct device *dev)
 	return retval;
 }
 
+static int resume_common(struct device *dev, int event)
+{
+	struct pci_dev		*pci_dev = to_pci_dev(dev);
+	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);
+	int			retval;
+
+	if (hcd->state != HC_STATE_SUSPENDED) {
+		dev_dbg(dev, "can't resume, not suspended!\n");
+		return 0;
+	}
+
+	retval = pci_enable_device(pci_dev);
+	if (retval < 0) {
+		dev_err(dev, "can't re-enable after resume, %d!\n", retval);
+		return retval;
+	}
+
+	pci_set_master(pci_dev);
+
+	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
+
+	if (hcd->driver->pci_resume) {
+		if (event != PM_EVENT_AUTO_RESUME)
+			wait_for_companions(pci_dev, hcd);
+
+		retval = hcd->driver->pci_resume(hcd,
+				event == PM_EVENT_RESTORE);
+		if (retval) {
+			dev_err(dev, "PCI post-resume error %d!\n", retval);
+			usb_hc_died(hcd);
+		}
+	}
+	return retval;
+}
+
+#ifdef	CONFIG_PM_SLEEP
+
+static int hcd_pci_suspend(struct device *dev)
+{
+	return suspend_common(dev, device_may_wakeup(dev));
+}
+
 static int hcd_pci_suspend_noirq(struct device *dev)
 {
 	struct pci_dev		*pci_dev = to_pci_dev(dev);
@@ -408,16 +502,7 @@ static int hcd_pci_suspend_noirq(struct device *dev)
 		return retval;
 	}
 
-#ifdef CONFIG_PPC_PMAC
-	/* Disable ASIC clocks for USB */
-	if (machine_is(powermac)) {
-		struct device_node	*of_node;
-
-		of_node = pci_device_to_OF_node(pci_dev);
-		if (of_node)
-			pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0);
-	}
-#endif
+	pci_power_config(pci_dev, 0);
 	return retval;
 }
 
@@ -425,69 +510,65 @@ static int hcd_pci_resume_noirq(struct device *dev)
 {
 	struct pci_dev		*pci_dev = to_pci_dev(dev);
 
-#ifdef CONFIG_PPC_PMAC
-	/* Reenable ASIC clocks for USB */
-	if (machine_is(powermac)) {
-		struct device_node *of_node;
-
-		of_node = pci_device_to_OF_node(pci_dev);
-		if (of_node)
-			pmac_call_feature(PMAC_FTR_USB_ENABLE,
-						of_node, 0, 1);
-	}
-#endif
+	pci_power_config(pci_dev, 1);
 
 	/* Go back to D0 and disable remote wakeup */
 	pci_back_from_sleep(pci_dev);
 	return 0;
 }
 
-static int resume_common(struct device *dev, bool hibernated)
+static int hcd_pci_resume(struct device *dev)
 {
-	struct pci_dev		*pci_dev = to_pci_dev(dev);
-	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);
-	int			retval;
+	return resume_common(dev, PM_EVENT_RESUME);
+}
 
-	if (hcd->state != HC_STATE_SUSPENDED) {
-		dev_dbg(dev, "can't resume, not suspended!\n");
-		return 0;
-	}
+static int hcd_pci_restore(struct device *dev)
+{
+	return resume_common(dev, PM_EVENT_RESTORE);
+}
 
-	retval = pci_enable_device(pci_dev);
-	if (retval < 0) {
-		dev_err(dev, "can't re-enable after resume, %d!\n", retval);
-		return retval;
-	}
+#else
 
-	pci_set_master(pci_dev);
+#define hcd_pci_suspend		NULL
+#define hcd_pci_suspend_noirq	NULL
+#define hcd_pci_resume_noirq	NULL
+#define hcd_pci_resume		NULL
+#define hcd_pci_restore		NULL
 
-	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
+#endif	/* CONFIG_PM_SLEEP */
 
-	if (hcd->driver->pci_resume) {
-		/* This call should be made only during system resume,
-		 * not during runtime resume.
-		 */
-		wait_for_companions(pci_dev, hcd);
+#ifdef	CONFIG_PM_RUNTIME
 
-		retval = hcd->driver->pci_resume(hcd, hibernated);
-		if (retval) {
-			dev_err(dev, "PCI post-resume error %d!\n", retval);
-			usb_hc_died(hcd);
-		}
-	}
+static int hcd_pci_runtime_suspend(struct device *dev)
+{
+	int	retval;
+
+	device_set_wakeup_enable(dev, 1);
+	retval = suspend_common(dev, true);
+	if (retval == 0)
+		pci_power_config(to_pci_dev(dev), 0);
+	dev_dbg(dev, "hcd_pci_runtime_suspend: %d\n", retval);
 	return retval;
 }
 
-static int hcd_pci_resume(struct device *dev)
+static int hcd_pci_runtime_resume(struct device *dev)
 {
-	return resume_common(dev, false);
-}
+	int	retval;
 
-static int hcd_pci_restore(struct device *dev)
-{
-	return resume_common(dev, true);
+	device_set_wakeup_enable(dev, 0);
+	pci_power_config(to_pci_dev(dev), 1);
+	retval = resume_common(dev, PM_EVENT_RESTORE);
+	dev_dbg(dev, "hcd_pci_runtime_resume: %d\n", retval);
+	return retval;
 }
 
+#else
+
+#define hcd_pci_runtime_suspend	NULL
+#define hcd_pci_runtime_resume	NULL
+
+#endif	/* CONFIG_PM_RUNTIME */
+
 const struct dev_pm_ops usb_hcd_pci_pm_ops = {
 	.suspend	= hcd_pci_suspend,
 	.suspend_noirq	= hcd_pci_suspend_noirq,
@@ -501,7 +582,9 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = {
 	.poweroff_noirq	= hcd_pci_suspend_noirq,
 	.restore_noirq	= hcd_pci_resume_noirq,
 	.restore	= hcd_pci_restore,
+	.runtime_suspend = hcd_pci_runtime_suspend,
+	.runtime_resume	= hcd_pci_runtime_resume,
 };
 EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops);
 
-#endif	/* CONFIG_PM_SLEEP */
+#endif	/* CONFIG_PM_OPS */
-- 
1.7.2.1

