Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 9 Dec 2003 20:07:53 -0500 (EST)
From:      amistry <amistry@am-productions.biz>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   kern/60099: Fix suspend and resume on USB ohci controllers
Message-ID:  <200312100107.hBA17rlt046430@crumpet.united-ware.com>
Resent-Message-ID: <200312100110.hBA1AGNg029010@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         60099
>Category:       kern
>Synopsis:       Fix suspend and resume on USB ohci controllers
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Dec 09 17:10:15 PST 2003
>Closed-Date:
>Last-Modified:
>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:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200312100107.hBA17rlt046430>