From amistry@crumpet.united-ware.com  Tue Dec  9 17:04:33 2003
Return-Path: <amistry@crumpet.united-ware.com>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id 9B1A016A4CE
	for <FreeBSD-gnats-submit@freebsd.org>; Tue,  9 Dec 2003 17:04:33 -0800 (PST)
Received: from crumpet.united-ware.com (ddsl-66-42-172-210.fuse.net [66.42.172.210])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 7FE3443D1D
	for <FreeBSD-gnats-submit@freebsd.org>; Tue,  9 Dec 2003 17:04:31 -0800 (PST)
	(envelope-from amistry@crumpet.united-ware.com)
Received: from crumpet.united-ware.com (ddsl-66-42-172-210.fuse.net [66.42.172.210])
	by crumpet.united-ware.com (8.12.8p2/8.12.8) with ESMTP id hBA17rZh046431
	(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO)
	for <FreeBSD-gnats-submit@freebsd.org>; Tue, 9 Dec 2003 20:07:53 -0500 (EST)
	(envelope-from amistry@crumpet.united-ware.com)
Received: (from amistry@localhost)
	by crumpet.united-ware.com (8.12.8p2/8.12.8/Submit) id hBA17rlt046430;
	Tue, 9 Dec 2003 20:07:53 -0500 (EST)
	(envelope-from amistry)
Message-Id: <200312100107.hBA17rlt046430@crumpet.united-ware.com>
Date: Tue, 9 Dec 2003 20:07:53 -0500 (EST)
From: amistry <amistry@am-productions.biz>
Reply-To: amistry <amistry@am-productions.biz>
To: FreeBSD-gnats-submit@freebsd.org
Cc:
Subject: Fix suspend and resume on USB ohci controllers
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         60099
>Category:       kern
>Synopsis:       Fix suspend and resume on USB ohci controllers
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    jmg
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Dec 09 17:10:15 PST 2003
>Closed-Date:    Mon Dec 22 10:42:12 PST 2003
>Last-Modified:  Mon Dec 22 10:42:12 PST 2003
>Originator:     Anish Mistry
>Release:        FreeBSD 5.2-BETA i386
>Organization:
AM Productions
>Environment:
System: FreeBSD littleguy.am-productions.biz 5.2-BETA FreeBSD 5.2-BETA #7: Mon Dec  8 19:39:44 EST 2003     amistry@littleguy.am-productions.biz:/usr/obj/usr/src/sys/LITTLEGUYACPI  i386

	
>Description:
	With some usb ohci controllers the usb ports no longer function after
a resume from suspend (S3).  These controllers need to be reinitilized so that
they continue to function after a resume.
	
>How-To-Repeat:
	Suspend your system then resume and depending on your controller the
usb ports may no longer function.
	
>Fix:

	

--- ohci-sr-20031209.patch begins here ---
diff -u sys/dev/usb.orig/ohci.c sys/dev/usb/ohci.c
--- sys/dev/usb.orig/ohci.c	Mon Dec  8 21:23:51 2003
+++ sys/dev/usb/ohci.c	Tue Dec  9 19:42:29 2003
@@ -1010,7 +1010,7 @@
 	DPRINTF(("ohci_shutdown: stopping the HC\n"));
 	OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
 }
-
+#endif
 /*
  * Handle suspend/resume.
  *
@@ -1018,6 +1018,139 @@
  * called from an intterupt context.  This is all right since we
  * are almost suspended anyway.
  */
+usbd_status
+ohci_resume(struct ohci_softc *sc)
+{
+	uint32_t ctl, ival, fm, per;
+#ifdef USB_DEBUG
+	uint32_t rev;
+#endif
+	int s;
+#ifdef USB_DEBUG		
+#if defined(__OpenBSD__)
+	DPRINTF((","));
+#else
+	DPRINTF(("%s:", USBDEVNAME(sc->sc_bus.bdev)));
+#endif
+	rev = OREAD4(sc, OHCI_REVISION);
+	DPRINTF((" OHCI version %d.%d%s\n", OHCI_REV_HI(rev), OHCI_REV_LO(rev),
+		OHCI_REV_LEGACY(rev) ? ", legacy support" : ""));
+	DPRINTF(("ohci_resume: controller state: "));
+	switch(OREAD4(sc, OHCI_CONTROL) & OHCI_HCFS_MASK) {
+		case OHCI_HCFS_SUSPEND:
+			DPRINTF(("SUSPEND"));
+			break;
+		case OHCI_HCFS_RESUME:
+			DPRINTF(("RESUME"));
+			break;
+		case OHCI_HCFS_RESET:
+			DPRINTF(("RESET"));
+			break;
+		case OHCI_HCFS_OPERATIONAL:
+			DPRINTF(("OPERATIONAL"));
+			break;
+	}
+	DPRINTF(("\n"));
+#endif
+	s = splhardusb();
+	/* The controller only responds to resume or reset writes at this point, so lets resume */
+	/* We are only supposed to enter resume state from a suspend state.  Should we check? */
+	OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESUME);
+	usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY);	
+#ifdef USB_DEBUG
+	/* check if the controller has resumed */
+	ctl = OREAD4(sc, OHCI_CONTROL);
+	if((ctl & OHCI_HCFS_RESUME) == OHCI_HCFS_RESUME) {
+		DPRINTF(("ohci_resume: controller state: RESUME\n"));
+	} else {
+		/* panic or abort? */
+		DPRINTF(("ohci_resume: ??? controller did not resume!\n"));
+		DPRINTF(("ohci_resume: OHCI_CONTROL: 0x%x\n",ctl));
+	}
+	
+	ohci_dumpregs(sc);
+#endif
+
+	/* reset or controller may not start */
+	OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+	usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY);
+
+	/* spec says save frame interrupt value, reset, then restore */
+	ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL));
+	OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */
+	usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY);
+
+	/* Some broken BIOSes do not recover these values */
+	OWRITE4(sc, OHCI_HCCA, DMAADDR(&sc->sc_hccadma, 0));
+	OWRITE4(sc, OHCI_CONTROL_HEAD_ED, sc->sc_ctrl_head->physaddr);
+	OWRITE4(sc, OHCI_BULK_HEAD_ED, sc->sc_bulk_head->physaddr);
+	/* disable all interrupts and then switch on all desired interrupts */
+	OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+	OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_intre | OHCI_MIE );
+
+	fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT;
+	fm |= OHCI_FSMPS(ival) | ival;
+	OWRITE4(sc, OHCI_FM_INTERVAL, fm);
+	per = OHCI_PERIODIC(ival);
+	OWRITE4(sc, OHCI_PERIODIC_START, per);
+
+	/* start controller */
+	ctl = sc->sc_control;
+	OWRITE4(sc, OHCI_CONTROL, ctl);
+	usb_delay_ms(&sc->sc_bus, USB_RESUME_RECOVERY);
+
+	/* power up ports */
+	OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC);
+	usb_delay_ms(&sc->sc_bus, OHCI_ENABLE_POWER_DELAY);
+	splx(s);
+#ifdef USB_DEBUG
+	ohci_dumpregs(sc);
+#endif
+	return (USBD_NORMAL_COMPLETION);
+}
+
+usbd_status
+ohci_suspend(struct ohci_softc *sc)
+{
+	uint32_t ctl;
+	int s;
+
+#ifdef USB_DEBUG
+	ohci_dumpregs(sc);
+#endif
+
+	/*
+	 * Preserve register values, in case that APM BIOS
+	 * does not recover them.
+	 */
+	sc->sc_control = OREAD4(sc, OHCI_CONTROL);
+	sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE);
+	s = splhardusb();
+	/* disable interrupts */
+	OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+	splx(s);
+	/* Reset to stop processing frames or the controller might not suspend */
+	OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+	usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY);
+	/* now suspend */
+	ctl = OHCI_HCFS_SUSPEND;
+	OWRITE4(sc, OHCI_CONTROL, ctl);
+	usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT);
+#ifdef USB_DEBUG
+	/* check if the controller is suspended */
+	ctl = OREAD4(sc, OHCI_CONTROL);
+	if((ctl & OHCI_HCFS_SUSPEND) == OHCI_HCFS_SUSPEND) {
+		DPRINTF(("ohci_suspend: controller state: SUSPEND.\n"));
+	} else {
+		/* panic or abort? */
+		DPRINTF(("ohci_suspend: ??? controller did not suspend!\n"));
+		DPRINTF(("ohci_suspend: OHCI_CONTROL: 0x%x\n", ctl));
+	}
+#endif
+	return (USBD_NORMAL_COMPLETION);
+}
+
+#if defined(__NetBSD__) || defined(__OpenBSD__)
 void
 ohci_power(int why, void *v)
 {
diff -u sys/dev/usb.orig/ohci_pci.c sys/dev/usb/ohci_pci.c
--- sys/dev/usb.orig/ohci_pci.c	Mon Dec  8 21:23:54 2003
+++ sys/dev/usb/ohci_pci.c	Tue Dec  9 19:13:39 2003
@@ -304,10 +304,38 @@
 	return 0;
 }
 
+/* implement suspend and resume */
+static int
+ohci_pci_suspend(device_t self)
+{
+	int err;
+	ohci_softc_t *sc = device_get_softc(self);
+	err = bus_generic_suspend(self);
+	if (err)
+		return err;
+	ohci_suspend(sc);
+	return 0;
+}
+
+static int
+ohci_pci_resume(device_t self)
+{
+	ohci_softc_t *sc = device_get_softc(self);
+	pci_set_powerstate(self, PCI_POWERSTATE_D0);
+	if(ohci_resume(sc) != USBD_NORMAL_COMPLETION) {
+		return ENXIO;
+	}
+	bus_generic_resume(self);
+	return 0;
+}
+
 static device_method_t ohci_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe, ohci_pci_probe),
 	DEVMETHOD(device_attach, ohci_pci_attach),
+	DEVMETHOD(device_detach, bus_generic_detach),
+	DEVMETHOD(device_suspend, ohci_pci_suspend),
+	DEVMETHOD(device_resume, ohci_pci_resume),
 	DEVMETHOD(device_shutdown, bus_generic_shutdown),
 
 	/* Bus interface */
diff -u sys/dev/usb.orig/ohcivar.h sys/dev/usb/ohcivar.h
--- sys/dev/usb.orig/ohcivar.h	Mon Dec  8 21:23:51 2003
+++ sys/dev/usb/ohcivar.h	Mon Dec  8 21:24:21 2003
@@ -158,6 +158,8 @@
 #define OXFER(xfer) ((struct ohci_xfer *)(xfer))
 
 usbd_status	ohci_init(ohci_softc_t *);
+usbd_status	ohci_suspend(ohci_softc_t *);
+usbd_status	ohci_resume(ohci_softc_t *);
 int		ohci_intr(void *);
 #if defined(__NetBSD__) || defined(__OpenBSD__)
 int		ohci_detach(ohci_softc_t *, int);
--- ohci-sr-20031209.patch ends here ---


>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: freebsd-bugs->jmg 
Responsible-Changed-By: jmg 
Responsible-Changed-When: Tue Dec 9 17:24:18 PST 2003 
Responsible-Changed-Why:  
this code looks good. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=60099 

From: Anish Mistry <amistry@am-productions.biz>
To: freebsd-gnats-submit@FreeBSD.org, amistry@am-productions.biz
Cc:  
Subject: Re: kern/60099: Fix suspend and resume on USB ohci controllers
Date: Mon, 22 Dec 2003 12:28:29 -0500

 The recent commit by Mr. Shibagaki does the same thing that this PR does, and 
 also fixes the problem in a more correct manner.  This PR may now be closed.
 
 --
 Anish Mistry
State-Changed-From-To: open->closed 
State-Changed-By: jmg 
State-Changed-When: Mon Dec 22 10:41:18 PST 2003 
State-Changed-Why:  
requestor comments that shiba committed this change in rev1.140 of 
sys/dev/usb/ohci.c and can now be closed. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=60099 
>Unformatted:
