kern/60099: Fix suspend and resume on USB ohci controllers
amistry
amistry at am-productions.biz
Tue Dec 9 17:10:19 PST 2003
>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 at 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:
More information about the freebsd-bugs
mailing list