/*
* Version Information
*/
-#define DRIVER_VERSION "v2.2"
+#define DRIVER_VERSION "v2.3"
#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, \
Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, \
Alan Stern"
#include "uhci-debug.c"
#include "uhci-q.c"
-static int suspend_allowed(struct uhci_hcd *uhci)
-{
- unsigned long io_addr = uhci->io_addr;
- int i;
-
- if (to_pci_dev(uhci_dev(uhci))->vendor != PCI_VENDOR_ID_INTEL)
- return 1;
-
- /* Some of Intel's USB controllers have a bug that causes false
- * resume indications if any port has an over current condition.
- * To prevent problems, we will not allow a global suspend if
- * any ports are OC.
- *
- * Some motherboards using Intel's chipsets (but not using all
- * the USB ports) appear to hardwire the over current inputs active
- * to disable the USB ports.
- */
-
- /* check for over current condition on any port */
- for (i = 0; i < uhci->rh_numports; i++) {
- if (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_OC)
- return 0;
- }
-
- return 1;
-}
-
static void reset_hc(struct uhci_hcd *uhci)
{
unsigned long io_addr = uhci->io_addr;
outw(0, uhci->io_addr + USBINTR);
/* Global reset for 50ms */
- uhci->state = UHCI_RESET;
outw(USBCMD_GRESET, io_addr + USBCMD);
msleep(50);
outw(0, io_addr + USBCMD);
msleep(10);
uhci->resume_detect = 0;
uhci->is_stopped = UHCI_IS_STOPPED;
+ uhci->rh_state = UHCI_RH_RESET;
}
-static void suspend_hc(struct uhci_hcd *uhci)
+static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci)
{
- unsigned long io_addr = uhci->io_addr;
+ int port;
- dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__);
- uhci->state = UHCI_SUSPENDED;
- uhci->resume_detect = 0;
- outw(USBCMD_EGSM, io_addr + USBCMD);
+ switch (to_pci_dev(uhci_dev(uhci))->vendor) {
+ default:
+ break;
+
+ case PCI_VENDOR_ID_GENESYS:
+ /* Genesys Logic's GL880S controllers don't generate
+ * resume-detect interrupts.
+ */
+ return 1;
+
+ case PCI_VENDOR_ID_INTEL:
+ /* Some of Intel's USB controllers have a bug that causes
+ * resume-detect interrupts if any port has an over-current
+ * condition. To make matters worse, some motherboards
+ * hardwire unused USB ports' over-current inputs active!
+ * To prevent problems, we will not enable resume-detect
+ * interrupts if any ports are OC.
+ */
+ for (port = 0; port < uhci->rh_numports; ++port) {
+ if (inw(uhci->io_addr + USBPORTSC1 + port * 2) &
+ USBPORTSC_OC)
+ return 1;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void suspend_hc(struct uhci_hcd *uhci, enum uhci_rh_state new_state)
+__releases(uhci->lock)
+__acquires(uhci->lock)
+{
+ int auto_stop;
+ int int_enable;
+
+ auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
+ dev_dbg(uhci_dev(uhci), "%s%s\n", __FUNCTION__,
+ (auto_stop ? " (auto-stop)" : ""));
+
+ /* If we get a suspend request when we're already auto-stopped
+ * then there's nothing to do.
+ */
+ if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
+ uhci->rh_state = new_state;
+ return;
+ }
+
+ /* Enable resume-detect interrupts if they work.
+ * Then enter Global Suspend mode, still configured.
+ */
+ int_enable = (resume_detect_interrupts_are_broken(uhci) ?
+ 0 : USBINTR_RESUME);
+ outw(int_enable, uhci->io_addr + USBINTR);
+ outw(USBCMD_EGSM | USBCMD_CF, uhci->io_addr + USBCMD);
+ udelay(5);
+
+ /* If we're auto-stopping then no devices have been attached
+ * for a while, so there shouldn't be any active URBs and the
+ * controller should stop after a few microseconds. Otherwise
+ * we will give the controller one frame to stop.
+ */
+ if (!auto_stop && !(inw(uhci->io_addr + USBSTS) & USBSTS_HCH)) {
+ uhci->rh_state = UHCI_RH_SUSPENDING;
+ spin_unlock_irq(&uhci->lock);
+ msleep(1);
+ spin_lock_irq(&uhci->lock);
+ }
+ if (!(inw(uhci->io_addr + USBSTS) & USBSTS_HCH))
+ dev_warn(uhci_dev(uhci), "Controller not stopped yet!\n");
- /* FIXME: Wait for the controller to actually stop */
uhci_get_current_frame_number(uhci);
+ smp_wmb();
+
+ uhci->rh_state = new_state;
uhci->is_stopped = UHCI_IS_STOPPED;
+ uhci->resume_detect = 0;
uhci_scan_schedule(uhci, NULL);
}
static void wakeup_hc(struct uhci_hcd *uhci)
+__releases(uhci->lock)
+__acquires(uhci->lock)
{
- unsigned long io_addr = uhci->io_addr;
+ dev_dbg(uhci_dev(uhci), "%s%s\n", __FUNCTION__,
+ uhci->rh_state == UHCI_RH_AUTO_STOPPED ?
+ " (auto-start)" : "");
- switch (uhci->state) {
- case UHCI_SUSPENDED: /* Start the resume */
- dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__);
-
- /* Global resume for >= 20ms */
- outw(USBCMD_FGR | USBCMD_EGSM, io_addr + USBCMD);
- uhci->state = UHCI_RESUMING_1;
- uhci->state_end = jiffies + msecs_to_jiffies(20);
- uhci->is_stopped = 0;
- break;
+ /* If we are auto-stopped then no devices are attached so there's
+ * no need for wakeup signals. Otherwise we send Global Resume
+ * for 20 ms.
+ */
+ if (uhci->rh_state == UHCI_RH_SUSPENDED) {
+ uhci->rh_state = UHCI_RH_RESUMING;
+ outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,
+ uhci->io_addr + USBCMD);
+ spin_unlock_irq(&uhci->lock);
+ msleep(20);
+ spin_lock_irq(&uhci->lock);
- case UHCI_RESUMING_1: /* End global resume */
- uhci->state = UHCI_RESUMING_2;
- outw(0, io_addr + USBCMD);
- /* Falls through */
-
- case UHCI_RESUMING_2: /* Wait for EOP to be sent */
- if (inw(io_addr + USBCMD) & USBCMD_FGR)
- break;
-
- /* Run for at least 1 second, and
- * mark it configured with a 64-byte max packet */
- uhci->state = UHCI_RUNNING_GRACE;
- uhci->state_end = jiffies + HZ;
- outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP,
- io_addr + USBCMD);
- break;
+ /* End Global Resume and wait for EOP to be sent */
+ outw(USBCMD_CF, uhci->io_addr + USBCMD);
+ udelay(4);
+ if (inw(uhci->io_addr + USBCMD) & USBCMD_FGR)
+ dev_warn(uhci_dev(uhci), "FGR not stopped yet!\n");
+ }
- case UHCI_RUNNING_GRACE: /* Now allowed to suspend */
- uhci->state = UHCI_RUNNING;
- break;
+ uhci->rh_state = UHCI_RH_RUNNING;
+ uhci->is_stopped = 0;
+ smp_wmb();
- default:
- break;
- }
+ /* Mark it configured and running with a 64-byte max packet.
+ * All interrupts are enabled, even though RD won't do anything.
+ */
+ outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, uhci->io_addr + USBCMD);
+ outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,
+ uhci->io_addr + USBINTR);
}
static int start_hc(struct uhci_hcd *uhci)
outl(uhci->fl->dma_handle, io_addr + USBFLBASEADD);
/* Run and mark it configured with a 64-byte max packet */
- uhci->state = UHCI_RUNNING_GRACE;
- uhci->state_end = jiffies + HZ;
+ uhci->rh_state = UHCI_RH_RUNNING;
outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD);
uhci->is_stopped = 0;
return 0;
}
-static void hc_state_transitions(struct uhci_hcd *uhci)
+static void rh_state_transitions(struct uhci_hcd *uhci)
{
- switch (uhci->state) {
- case UHCI_RUNNING:
-
- /* global suspend if nothing connected for 1 second */
- if (!any_ports_active(uhci) && suspend_allowed(uhci)) {
- uhci->state = UHCI_SUSPENDING_GRACE;
- uhci->state_end = jiffies + HZ;
- }
- break;
-
- case UHCI_SUSPENDING_GRACE:
- if (any_ports_active(uhci))
- uhci->state = UHCI_RUNNING;
- else if (time_after_eq(jiffies, uhci->state_end))
- suspend_hc(uhci);
- break;
-
- case UHCI_SUSPENDED:
-
- /* wakeup if requested by a device */
- if (uhci->resume_detect)
- wakeup_hc(uhci);
- break;
-
- case UHCI_RESUMING_1:
- case UHCI_RESUMING_2:
- case UHCI_RUNNING_GRACE:
- if (time_after_eq(jiffies, uhci->state_end))
- wakeup_hc(uhci);
- break;
-
- default:
- break;
+ switch (uhci->rh_state) {
+ case UHCI_RH_RUNNING:
+ /* are any devices attached? */
+ if (!any_ports_active(uhci)) {
+ uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
+ uhci->auto_stop_time = jiffies + HZ;
+ }
+ break;
+
+ case UHCI_RH_RUNNING_NODEVS:
+ /* auto-stop if nothing connected for 1 second */
+ if (any_ports_active(uhci))
+ uhci->rh_state = UHCI_RH_RUNNING;
+ else if (time_after_eq(jiffies, uhci->auto_stop_time))
+ suspend_hc(uhci, UHCI_RH_AUTO_STOPPED);
+ break;
+
+ case UHCI_RH_AUTO_STOPPED:
+ /* wakeup if requested by a device */
+ if (uhci->resume_detect)
+ wakeup_hc(uhci);
+ break;
+
+ default:
+ break;
}
}
check_fsbr(uhci);
/* Poll for and perform state transitions */
- hc_state_transitions(uhci);
- if (unlikely(uhci->suspended_ports && uhci->state != UHCI_SUSPENDED))
+ rh_state_transitions(uhci);
+ if (unlikely(uhci->suspended_ports))
uhci_check_ports(uhci);
restart_timer(uhci);
if (status & USBSTS_HCPE)
dev_err(uhci_dev(uhci), "host controller process "
"error, something bad happened!\n");
- if ((status & USBSTS_HCH) && uhci->state > 0) {
+ if ((status & USBSTS_HCH) &&
+ uhci->rh_state >= UHCI_RH_RUNNING) {
dev_err(uhci_dev(uhci), "host controller halted, "
"very bad!\n");
/* FIXME: Reset the controller, fix the offending TD */
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
spin_lock_irq(&uhci->lock);
-
- /* Don't try to suspend broken motherboards, reset instead */
- if (suspend_allowed(uhci))
- suspend_hc(uhci);
- else {
- spin_unlock_irq(&uhci->lock);
- reset_hc(uhci);
- spin_lock_irq(&uhci->lock);
- uhci_scan_schedule(uhci, NULL);
- }
-
+ suspend_hc(uhci, UHCI_RH_SUSPENDED);
spin_unlock_irq(&uhci->lock);
return 0;
}
static int uhci_resume(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
- int rc;
-
- pci_set_master(to_pci_dev(uhci_dev(uhci)));
spin_lock_irq(&uhci->lock);
-
- if (uhci->state == UHCI_SUSPENDED) {
+ if (uhci->rh_state == UHCI_RH_SUSPENDED) {
/*
* Some systems don't maintain the UHCI register values
outl(uhci->fl->dma_handle, uhci->io_addr + USBFLBASEADD);
outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC |
USBINTR_SP, uhci->io_addr + USBINTR);
- uhci->resume_detect = 1;
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,
USBLEGSUP_DEFAULT);
- } else {
- spin_unlock_irq(&uhci->lock);
- reset_hc(uhci);
- if ((rc = start_hc(uhci)) != 0)
- return rc;
- spin_lock_irq(&uhci->lock);
+ wakeup_hc(uhci);
}
- hcd->state = HC_STATE_RUNNING;
-
spin_unlock_irq(&uhci->lock);
+
+ hcd->state = HC_STATE_RUNNING;
return 0;
}
#endif
static int uhci_hcd_get_frame_number(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
- int frame_number;
unsigned long flags;
+ int is_stopped;
+ int frame_number;
/* Minimize latency by avoiding the spinlock */
local_irq_save(flags);
- rmb();
- frame_number = (uhci->is_stopped ? uhci->frame_number :
+ is_stopped = uhci->is_stopped;
+ smp_rmb();
+ frame_number = (is_stopped ? uhci->frame_number :
inw(uhci->io_addr + USBFRNUM));
local_irq_restore(flags);
return frame_number;