summary refs log tree commit diff
diff options
context:
space:
mode:
authorManuel Lauss <mano@roarinelk.homelinux.net>2008-06-23 09:09:37 +0200
committerGreg Kroah-Hartman <gregkh@suse.de>2008-07-21 15:16:44 -0700
commit42bfc7b44f724fb5ce838fc2f552a3eb8cd768ec (patch)
treed97b0ca257dbadd4fafc6c55cef742d877d78d07
parent53c81a348fa1d21dd042d9c9a9f91795f83fed66 (diff)
downloadlinux-42bfc7b44f724fb5ce838fc2f552a3eb8cd768ec.tar.gz
USB: Au1xxx-usb: suspend/resume support.
Copy the OHCI/EHCI PM callbacks of the PCI implementation since
they work equally well on Au1xxx hardware.

Tested on Au1200.

Signed-off-by: Manuel Lauss <mano@roarinelk.homelinux.net>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

-rw-r--r--drivers/usb/host/ehci-au1xxx.c115
-rw-r--r--drivers/usb/host/ohci-au1xxx.c59
2 files changed, 155 insertions, 19 deletions
diff --git a/drivers/usb/host/ehci-au1xxx.c b/drivers/usb/host/ehci-au1xxx.c
index 12ed80684ad1..bf69f4739107 100644
--- a/drivers/usb/host/ehci-au1xxx.c
+++ b/drivers/usb/host/ehci-au1xxx.c
@@ -195,29 +195,124 @@ static int ehci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
 	return 0;
 }
 
- /*TBD*/
-/*static int ehci_hcd_au1xxx_drv_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int ehci_hcd_au1xxx_drv_suspend(struct platform_device *pdev,
+					pm_message_t message)
 {
-	struct platform_device *pdev = to_platform_device(dev);
-	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	struct usb_hcd *hcd = platform_get_drvdata(pdev);
+	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+	unsigned long flags;
+	int rc;
 
 	return 0;
+	rc = 0;
+
+	if (time_before(jiffies, ehci->next_statechange))
+		msleep(10);
+
+	/* Root hub was already suspended. Disable irq emission and
+	 * mark HW unaccessible, bail out if RH has been resumed. Use
+	 * the spinlock to properly synchronize with possible pending
+	 * RH suspend or resume activity.
+	 *
+	 * This is still racy as hcd->state is manipulated outside of
+	 * any locks =P But that will be a different fix.
+	 */
+	spin_lock_irqsave(&ehci->lock, flags);
+	if (hcd->state != HC_STATE_SUSPENDED) {
+		rc = -EINVAL;
+		goto bail;
+	}
+	ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+	(void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+	/* make sure snapshot being resumed re-enumerates everything */
+	if (message.event == PM_EVENT_PRETHAW) {
+		ehci_halt(ehci);
+		ehci_reset(ehci);
+	}
+
+	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+	au1xxx_stop_ehc();
+
+bail:
+	spin_unlock_irqrestore(&ehci->lock, flags);
+
+	// could save FLADJ in case of Vaux power loss
+	// ... we'd only use it to handle clock skew
+
+	return rc;
 }
-static int ehci_hcd_au1xxx_drv_resume(struct device *dev)
+
+
+static int ehci_hcd_au1xxx_drv_resume(struct platform_device *pdev)
 {
-	struct platform_device *pdev = to_platform_device(dev);
-	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	struct usb_hcd *hcd = platform_get_drvdata(pdev);
+	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+	au1xxx_start_ehc();
+
+	// maybe restore FLADJ
+
+	if (time_before(jiffies, ehci->next_statechange))
+		msleep(100);
+
+	/* Mark hardware accessible again as we are out of D3 state by now */
+	set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+	/* If CF is still set, we maintained PCI Vaux power.
+	 * Just undo the effect of ehci_pci_suspend().
+	 */
+	if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
+		int	mask = INTR_MASK;
+
+		if (!hcd->self.root_hub->do_remote_wakeup)
+			mask &= ~STS_PCD;
+		ehci_writel(ehci, mask, &ehci->regs->intr_enable);
+		ehci_readl(ehci, &ehci->regs->intr_enable);
+		return 0;
+	}
+
+	ehci_dbg(ehci, "lost power, restarting\n");
+	usb_root_hub_lost_power(hcd->self.root_hub);
+
+	/* Else reset, to cope with power loss or flush-to-storage
+	 * style "resume" having let BIOS kick in during reboot.
+	 */
+	(void) ehci_halt(ehci);
+	(void) ehci_reset(ehci);
+
+	/* emptying the schedule aborts any urbs */
+	spin_lock_irq(&ehci->lock);
+	if (ehci->reclaim)
+		end_unlink_async(ehci);
+	ehci_work(ehci);
+	spin_unlock_irq(&ehci->lock);
+
+	ehci_writel(ehci, ehci->command, &ehci->regs->command);
+	ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+	ehci_readl(ehci, &ehci->regs->command);	/* unblock posted writes */
+
+	/* here we "know" root ports should always stay powered */
+	ehci_port_power(ehci, 1);
+
+	hcd->state = HC_STATE_SUSPENDED;
 
 	return 0;
 }
-*/
+
+#else
+#define ehci_hcd_au1xxx_drv_suspend NULL
+#define ehci_hcd_au1xxx_drv_resume NULL
+#endif
 
 static struct platform_driver ehci_hcd_au1xxx_driver = {
 	.probe		= ehci_hcd_au1xxx_drv_probe,
 	.remove		= ehci_hcd_au1xxx_drv_remove,
 	.shutdown	= usb_hcd_platform_shutdown,
-	/*.suspend      = ehci_hcd_au1xxx_drv_suspend, */
-	/*.resume       = ehci_hcd_au1xxx_drv_resume, */
+	.suspend	= ehci_hcd_au1xxx_drv_suspend,
+	.resume		= ehci_hcd_au1xxx_drv_resume,
 	.driver = {
 		.name	= "au1xxx-ehci",
 		.owner	= THIS_MODULE,
diff --git a/drivers/usb/host/ohci-au1xxx.c b/drivers/usb/host/ohci-au1xxx.c
index 29c970ad487a..c0948008fe3d 100644
--- a/drivers/usb/host/ohci-au1xxx.c
+++ b/drivers/usb/host/ohci-au1xxx.c
@@ -248,27 +248,68 @@ static int ohci_hcd_au1xxx_drv_remove(struct platform_device *pdev)
 	return 0;
 }
 
-	/*TBD*/
-/*static int ohci_hcd_au1xxx_drv_suspend(struct platform_device *dev)
+#ifdef CONFIG_PM
+static int ohci_hcd_au1xxx_drv_suspend(struct platform_device *pdev,
+					pm_message_t message)
 {
-	struct usb_hcd *hcd = platform_get_drvdata(dev);
+	struct usb_hcd *hcd = platform_get_drvdata(pdev);
+	struct ohci_hcd	*ohci = hcd_to_ohci(hcd);
+	unsigned long flags;
+	int rc;
+
+	rc = 0;
+
+	/* Root hub was already suspended. Disable irq emission and
+	 * mark HW unaccessible, bail out if RH has been resumed. Use
+	 * the spinlock to properly synchronize with possible pending
+	 * RH suspend or resume activity.
+	 *
+	 * This is still racy as hcd->state is manipulated outside of
+	 * any locks =P But that will be a different fix.
+	 */
+	spin_lock_irqsave(&ohci->lock, flags);
+	if (hcd->state != HC_STATE_SUSPENDED) {
+		rc = -EINVAL;
+		goto bail;
+	}
+	ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
+	(void)ohci_readl(ohci, &ohci->regs->intrdisable);
 
-	return 0;
+	/* make sure snapshot being resumed re-enumerates everything */
+	if (message.event == PM_EVENT_PRETHAW)
+		ohci_usb_reset(ohci);
+
+	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+	au1xxx_stop_ohc();
+bail:
+	spin_unlock_irqrestore(&ohci->lock, flags);
+
+	return rc;
 }
-static int ohci_hcd_au1xxx_drv_resume(struct platform_device *dev)
+
+static int ohci_hcd_au1xxx_drv_resume(struct platform_device *pdev)
 {
-	struct usb_hcd *hcd = platform_get_drvdata(dev);
+	struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+	au1xxx_start_ohc();
+
+	set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+	ohci_finish_controller_resume(hcd);
 
 	return 0;
 }
-*/
+#else
+#define ohci_hcd_au1xxx_drv_suspend NULL
+#define ohci_hcd_au1xxx_drv_resume NULL
+#endif
 
 static struct platform_driver ohci_hcd_au1xxx_driver = {
 	.probe		= ohci_hcd_au1xxx_drv_probe,
 	.remove		= ohci_hcd_au1xxx_drv_remove,
 	.shutdown	= usb_hcd_platform_shutdown,
-	/*.suspend	= ohci_hcd_au1xxx_drv_suspend, */
-	/*.resume	= ohci_hcd_au1xxx_drv_resume, */
+	.suspend	= ohci_hcd_au1xxx_drv_suspend,
+	.resume		= ohci_hcd_au1xxx_drv_resume,
 	.driver		= {
 		.name	= "au1xxx-ohci",
 		.owner	= THIS_MODULE,