patch-2.4.0-test5 linux/drivers/usb/usb-ohci.c

Next file: linux/drivers/usb/usb.c
Previous file: linux/drivers/usb/uhci.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test4/linux/drivers/usb/usb-ohci.c linux/drivers/usb/usb-ohci.c
@@ -11,6 +11,11 @@
  * 
  * History:
  * 
+ * 2000/06/28 use PCI hotplug framework, for better power management
+ *	and for Cardbus support (David Brownell)
+ * 2000/earlier:  fixes for NEC/Lucent chips; suspend/resume handling
+ *	when the controller loses power; handle UE; cleanup; ...
+ *
  * v5.2 1999/12/07 URB 3rd preview, 
  * v5.1 1999/11/30 URB 2nd preview, cpia, (usb-scsi)
  * v5.0 1999/11/22 URB Technical preview, Paul Mackerras powerbook susp/resume 
@@ -24,8 +29,6 @@
  * v2.1 1999/05/09  code clean up
  * v2.0 1999/05/04 
  * v1.0 1999/04/27 initial release
- * 
- 
  */
  
 #include <linux/config.h>
@@ -38,6 +41,7 @@
 #include <linux/malloc.h>
 #include <linux/smp_lock.h>
 #include <linux/errno.h>
+#include <linux/init.h>
 #include <linux/timer.h>
 #include <linux/list.h>
 #include <linux/interrupt.h>  /* for in_interrupt() */
@@ -50,13 +54,15 @@
 #include <asm/unaligned.h>
 
 #define OHCI_USE_NPS		// force NoPowerSwitching mode
+// #define OHCI_VERBOSE_DEBUG	/* not always helpful */
 
 #include "usb-ohci.h"
 
-#include <linux/pm.h>
-static int handle_pm_event (struct pm_dev *dev, pm_request_t rqst, void *data);
 
 #ifdef CONFIG_PMAC_PBOOK
+/* All this PMAC_PBOOK stuff should disappear when those
+ * platforms fully support the 2.4 kernel PCI APIs.
+ */
 #include <linux/adb.h>
 #include <linux/pmu.h>
 #endif
@@ -105,13 +111,15 @@
 static void urb_print (urb_t * urb, char * str, int small)
 {
 	unsigned int pipe= urb->pipe;
-	int i, len;
 	
 	if (!urb->dev || !urb->dev->bus) {
 		dbg("%s URB: no dev", str);
 		return;
 	}
 	
+#ifndef	OHCI_VERBOSE_DEBUG
+	if (urb->status != 0)
+#endif
 	dbg("%s URB:[%4x] dev:%2d,ep:%2d-%c,type:%s,flags:%4x,len:%d/%d,stat:%d(%x)", 
 			str,
 		 	sohci_get_current_frame_number (urb->dev), 
@@ -124,7 +132,10 @@
 		 	urb->actual_length, 
 		 	urb->transfer_buffer_length,
 		 	urb->status, urb->status);
+#ifdef	OHCI_VERBOSE_DEBUG
 	if (!small) {
+		int i, len;
+
 		if (usb_pipecontrol (pipe)) {
 			printk (KERN_DEBUG __FILE__ ": cmd(8):");
 			for (i = 0; i < 8 ; i++) 
@@ -142,6 +153,7 @@
 			printk ("%s stat:%d\n", i < len? "...": "", urb->status);
 		}
 	} 
+#endif
 }
 
 /* just for debugging; prints non-empty branches of the int ed tree inclusive iso eds*/
@@ -307,7 +319,7 @@
 
 static void ohci_dump (ohci_t *controller, int verbose)
 {
-	dbg ("OHCI controller %s state", controller->ohci_dev->slot_name);
+	dbg ("OHCI controller usb-%s state", controller->ohci_dev->slot_name);
 
 	// dumps some of the state we know about
 	ohci_dump_status (controller);
@@ -421,7 +433,7 @@
 	if (usb_pipedevice (pipe) == ohci->rh.devnum) 
 		return rh_submit_urb (urb);
 	
-	/* when controller's hung, permit only hub cleanup attempts
+	/* when controller's hung, permit only roothub cleanup attempts
 	 * such as powering down ports */
 	if (ohci->disabled)
 		return -ESHUTDOWN;
@@ -509,6 +521,7 @@
 /*-------------------------------------------------------------------------*/
 
 /* deactivate all TDs and remove the private part of the URB */
+/* interrupt callers must use async unlink mode */
  
 static int sohci_unlink_urb (urb_t * urb)
 {
@@ -537,21 +550,34 @@
 		/* URB active? */
 		if (urb->status == USB_ST_URB_PENDING && !ohci->disabled) {
 			urb_priv_t  * urb_priv = urb->hcpriv;
-			urb_priv->state = URB_DEL; 
 
-			/* we want to delete the TDs of an URB from an ed 
-			 * request the deletion, it will be handled at the
-			 * next USB-frame */
+			/* interrupt code may not sleep; it must use
+			 * async status return to unlink pending urbs.
+			 */
+			if (!(urb->transfer_flags & USB_ASYNC_UNLINK)
+					&& in_interrupt ()) {
+				err ("bug in call from %p; use async!",
+					__builtin_return_address(0));
+				return -EWOULDBLOCK;
+			}
+
+
+			/* flag the urb and its TDs for deletion in some
+			 * upcoming SF interrupt delete list processing
+			 */
+			urb_priv->state = URB_DEL; 
 			
 			spin_lock_irqsave (&usb_ed_lock, flags);
 			ep_rm_ed (urb->dev, urb_priv->ed);
 			urb_priv->ed->state |= ED_URB_DEL;
 			spin_unlock_irqrestore (&usb_ed_lock, flags);
+
 			if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) {
 				usb_dec_dev_use (urb->dev);	
 				add_wait_queue (&op_wakeup, &wait);
 				current->state = TASK_UNINTERRUPTIBLE;
-				if (!schedule_timeout (HZ / 10)) /* wait until all TDs are deleted */
+				/* wait until all TDs are deleted */
+				if (!schedule_timeout (HZ / 10))
 					err("unlink URB timeout!");
 				remove_wait_queue (&op_wakeup, &wait); 
 				urb->status = -ENOENT;
@@ -593,7 +619,8 @@
 
 /*-------------------------------------------------------------------------*/
 
-/* free private data space of usb device */
+/* may be called from interrupt context */
+/* frees private data space of usb device */
   
 static int sohci_free_dev (struct usb_device * usb_dev)
 {
@@ -604,7 +631,8 @@
 	struct ohci_device * dev = usb_to_ohci (usb_dev);
 	ohci_t * ohci = usb_dev->bus->hcpriv;
 	
-	if (!dev) return 0;
+	if (!dev)
+		return 0;
 	
 	if (usb_dev->devnum >= 0) {
 	
@@ -613,7 +641,8 @@
 		for(i = 0; i < NUM_EDS; i++) {
   			ed = &(dev->ed[i]);
   			if (ed->state != ED_NEW) {
-  				if (ed->state == ED_OPER) ep_unlink (ohci, ed);
+  				if (ed->state == ED_OPER)
+					ep_unlink (ohci, ed);
   				ep_rm_ed (usb_dev, ed);
   				ed->state = ED_DEL;
   				cnt++;
@@ -622,10 +651,28 @@
   		spin_unlock_irqrestore (&usb_ed_lock, flags);
   		
 		if (cnt > 0) {
-			add_wait_queue (&op_wakeup, &wait);
-			current->state = TASK_UNINTERRUPTIBLE;
-			schedule_timeout (HZ / 10);
-			remove_wait_queue (&op_wakeup, &wait);
+
+			if (ohci->disabled) {
+				/* FIXME: Something like this should kick in,
+				 * though it's currently an exotic case ...
+				 * the controller won't ever be touching
+				 * these lists again!!
+				dl_del_list (ohci,
+					le16_to_cpu (ohci->hcca.frame_no) & 1);
+				 */
+				warn ("TD leak, %d", cnt);
+
+			} else if (!in_interrupt ()) {
+				/* SF interrupt handler calls dl_del_list */
+				add_wait_queue (&op_wakeup, &wait);
+				current->state = TASK_UNINTERRUPTIBLE;
+				schedule_timeout (HZ / 10);
+				remove_wait_queue (&op_wakeup, &wait);
+
+			} else {
+				/* drivers mustn't expect this to work */
+				err ("can't free tds, interrupt context");
+			}
 		}
 	}
 	kfree (dev);
@@ -942,24 +989,31 @@
  
 /* request the removal of an endpoint
  * put the ep on the rm_list and request a stop of the bulk or ctrl list 
- * real removal is done at the next start of frame (SOF) hardware interrupt */
+ * real removal is done at the next start frame (SF) hardware interrupt */
  
 static void ep_rm_ed (struct usb_device * usb_dev, ed_t * ed)
 {    
 	unsigned int frame;
 	ohci_t * ohci = usb_dev->bus->hcpriv;
 
-	if ((ed->state & ED_DEL) || (ed->state & ED_URB_DEL)) return;
+	if ((ed->state & ED_DEL) || (ed->state & ED_URB_DEL))
+		return;
 	
 	ed->hwINFO  |=  cpu_to_le32 (OHCI_ED_SKIP);
-	
-	writel (OHCI_INTR_SF, &ohci->regs->intrstatus);
-	writel (OHCI_INTR_SF, &ohci->regs->intrenable); /* enable sof interrupt */
+
+	if (!ohci->disabled) {
+		/* enable SOF interrupt */
+		writel (OHCI_INTR_SF, &ohci->regs->intrstatus);
+		writel (OHCI_INTR_SF, &ohci->regs->intrenable);
+	}
 
 	frame = le16_to_cpu (ohci->hcca.frame_no) & 0x1;
 	ed->ed_rm_list = ohci->ed_rm_list[frame];
 	ohci->ed_rm_list[frame] = ed;
 
+	if (ohci->disabled)
+		return;
+
 	switch (ed->type) {
 		case CTRL: /* stop CTRL list */
 			writel (ohci->hc_control &= ~OHCI_CTRL_CLE, &ohci->regs->control); 
@@ -1240,11 +1294,16 @@
   		if ((ed->type & 3) == BULK) bulk |= 1;  
    	}
    	
-   	if (ctrl) writel (0, &ohci->regs->ed_controlcurrent); /* reset CTRL list */
-   	if (bulk) writel (0, &ohci->regs->ed_bulkcurrent);    /* reset BULK list */
-	if (!ohci->ed_rm_list[!frame]) { 		/* enable CTRL and BULK lists */ 
-  		ohci->hc_control |= OHCI_CTRL_CLE | OHCI_CTRL_BLE;
-  		writel (ohci->hc_control, &ohci->regs->control);   
+	/* maybe reenable CTRL and BULK lists */ 
+	if (!ohci->disabled) {
+		if (ctrl) 	/* reset CTRL list */
+			writel (0, &ohci->regs->ed_controlcurrent);
+		if (bulk)	/* reset BULK list */
+			writel (0, &ohci->regs->ed_bulkcurrent);
+		if (!ohci->ed_rm_list[!frame]) {
+			ohci->hc_control |= OHCI_CTRL_CLE | OHCI_CTRL_BLE;
+			writel (ohci->hc_control, &ohci->regs->control);   
+		}
 	}
    	ohci->ed_rm_list[frame] = NULL;
 
@@ -1393,7 +1452,7 @@
 
 	num_ports = readl (&ohci->regs->roothub.a) & RH_A_NDP; 
 	if (num_ports > MAX_ROOT_PORTS) {
-		err ("bogus NDP=%d for OHCI %s", num_ports,
+		err ("bogus NDP=%d for OHCI usb-%s", num_ports,
 			ohci->ohci_dev->slot_name);
 		err ("rereads as NDP=%d",
 			readl (&ohci->regs->roothub.a) & RH_A_NDP);
@@ -1658,7 +1717,7 @@
 	}
 	
 #ifdef	DEBUG
-	ohci_dump_roothub (ohci, 0);
+	// ohci_dump_roothub (ohci, 0);
 #endif
 
 	len = min(len, leni);
@@ -1714,7 +1773,7 @@
 	/* Disable HC interrupts */
 	writel (OHCI_INTR_MIE, &ohci->regs->intrdisable);
 
-	dbg("USB HC reset_hc %s: ctrl = %x ;",
+	dbg("USB HC reset_hc usb-%s: ctrl = 0x%x ;",
 		ohci->ohci_dev->slot_name,
 		readl (&ohci->regs->control));
 
@@ -1730,7 +1789,6 @@
 		}	
 		udelay (1);
 	}	 
-	ohci->disabled = 0;
 	return 0;
 }
 
@@ -1747,6 +1805,8 @@
   	struct usb_device  * usb_dev;
 	struct ohci_device * dev;
 	
+	ohci->disabled = 1;
+
 	/* Tell the controller where the control and bulk lists are
 	 * The lists are empty now. */
 	 
@@ -1792,6 +1852,7 @@
 		return -ENODEV;
 	}
 	
+	ohci->disabled = 0;
 	return 0;
 }
 
@@ -1815,7 +1876,7 @@
 
 	if (ints & OHCI_INTR_UE) {
 		ohci->disabled++;
-		err ("OHCI Unrecoverable Error, controller %s disabled",
+		err ("OHCI Unrecoverable Error, controller usb-%s disabled",
 			ohci->ohci_dev->slot_name);
 		// e.g. due to PCI Master/Target Abort
 
@@ -1853,28 +1914,9 @@
 
 /*-------------------------------------------------------------------------*/
 
-/* reinitialize after controller reset */
-
-static void hc_reinit_ohci (ohci_t *ohci)
-{
-	int i;
-
-	/* for load balancing of the interrupt branches */
-	for (i = 0; i < NUM_INTS; i++) ohci->ohci_int_load[i] = 0;
-	for (i = 0; i < NUM_INTS; i++) ohci->hcca.int_table[i] = 0;
-	
-	ohci->ed_rm_list [0] = NULL;
-	ohci->ed_rm_list [1] = NULL;
-
-	/* end of control and bulk lists */	 
-	ohci->ed_isotail     = NULL;
-	ohci->ed_controltail = NULL;
-	ohci->ed_bulktail    = NULL;
-} 
-
 /* allocate OHCI */
 
-static ohci_t * hc_alloc_ohci (void * mem_base)
+static ohci_t * __devinit hc_alloc_ohci (void * mem_base)
 {
 	ohci_t * ohci;
 	struct usb_bus * bus;
@@ -1896,6 +1938,7 @@
 
 	ohci->bus = bus;
 	bus->hcpriv = (void *) ohci;
+	ohci->disabled = 1;
  
 	return ohci;
 } 
@@ -1907,7 +1950,7 @@
 
 static void hc_release_ohci (ohci_t * ohci)
 {	
-	dbg ("USB HC release ohci %s", ohci->ohci_dev->slot_name);
+	dbg ("USB HC release ohci usb-%s", ohci->ohci_dev->slot_name);
 
 	/* disconnect all devices */    
 	if (ohci->bus->root_hub)
@@ -1924,6 +1967,9 @@
 
 	usb_deregister_bus (ohci->bus);
 	usb_free_bus (ohci->bus);
+
+	list_del (&ohci->ohci_hcd_list);
+	INIT_LIST_HEAD (&ohci->ohci_hcd_list);
     
 	/* unmap the IO address space */
 	iounmap (ohci->regs);
@@ -1935,8 +1981,11 @@
 
 /* Increment the module usage count, start the control thread and
  * return success. */
+
+static struct pci_driver ohci_pci_driver;
  
-static int hc_found_ohci (struct pci_dev *dev, int irq, void * mem_base)
+static int __devinit
+hc_found_ohci (struct pci_dev *dev, int irq, void * mem_base)
 {
 	ohci_t * ohci;
 	char buf[8], *bufp = buf;
@@ -1948,7 +1997,7 @@
 #endif
 	printk(KERN_INFO __FILE__ ": USB OHCI at membase 0x%lx, IRQ %s\n",
 		(unsigned long)	mem_base, bufp);
-	printk(KERN_INFO __FILE__ ": pci slot %s, %s\n", dev->slot_name, dev->name);
+	printk(KERN_INFO __FILE__ ": usb-%s, %s\n", dev->slot_name, dev->name);
     
 	ohci = hc_alloc_ohci (mem_base);
 	if (!ohci) {
@@ -1965,58 +2014,263 @@
 		return -ENODEV;
 	}
 
+	/* FIXME this is a second HC reset; why?? */
 	writel (ohci->hc_control = OHCI_USB_RESET, &ohci->regs->control);
 	wait_ms (10);
+
 	usb_register_bus (ohci->bus);
 	
-	if (request_irq (irq, hc_interrupt, SA_SHIRQ, "usb-ohci", ohci) == 0) {
-		struct pm_dev *pmdev;
-
-		ohci->irq = irq;     
-		hc_start (ohci);
+	if (request_irq (irq, hc_interrupt, SA_SHIRQ,
+			ohci_pci_driver.name, ohci) != 0) {
+		err ("request interrupt %d failed", irq);
+		hc_release_ohci (ohci);
+		return -EBUSY;
+	}
+	ohci->irq = irq;     
 
-		pmdev = pm_register (PM_PCI_DEV,
-				     PM_PCI_ID(dev),
-				     handle_pm_event);
-		if (pmdev)
-			pmdev->data = ohci;
+	if (hc_start (ohci) < 0) {
+		err ("can't start usb-%s", dev->slot_name);
+		hc_release_ohci (ohci);
+		return -EBUSY;
+	}
 
 #ifdef	DEBUG
-		ohci_dump (ohci, 1);
+	ohci_dump (ohci, 1);
 #endif
-		return 0;
- 	}	
- 	err("request interrupt %d failed", irq);
-	hc_release_ohci (ohci);
-	return -EBUSY;
+	return 0;
 }
 
 /*-------------------------------------------------------------------------*/
- 
-static int hc_start_ohci (struct pci_dev * dev)
+
+#ifdef	CONFIG_PM
+
+/* controller died; cleanup debris, then restart */
+/* must not be called from interrupt context */
+
+static void hc_restart (ohci_t *ohci)
+{
+	int temp;
+	int i;
+
+	ohci->disabled = 1;
+	if (ohci->bus->root_hub)
+		usb_disconnect (&ohci->bus->root_hub);
+	
+	/* empty the interrupt branches */
+	for (i = 0; i < NUM_INTS; i++) ohci->ohci_int_load[i] = 0;
+	for (i = 0; i < NUM_INTS; i++) ohci->hcca.int_table[i] = 0;
+	
+	/* no EDs to remove */
+	ohci->ed_rm_list [0] = NULL;
+	ohci->ed_rm_list [1] = NULL;
+
+	/* empty control and bulk lists */	 
+	ohci->ed_isotail     = NULL;
+	ohci->ed_controltail = NULL;
+	ohci->ed_bulktail    = NULL;
+
+	if ((temp = hc_reset (ohci)) < 0 || (temp = hc_start (ohci)) < 0) {
+		err ("can't restart usb-%s, %d", ohci->ohci_dev->slot_name, temp);
+	} else
+		dbg ("restart usb-%s completed", ohci->ohci_dev->slot_name);
+}
+
+#endif	/* CONFIG_PM */
+
+/*-------------------------------------------------------------------------*/
+
+/* configured so that an OHCI device is always provided */
+/* always called with process context; sleeping is OK */
+
+static int __devinit
+ohci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
 {
-	unsigned long mem_base;
+	unsigned long mem_resource;
+	u8 latency, limit;
+	void *mem_base;
 
 	if (pci_enable_device(dev) < 0)
 		return -ENODEV;
 	
+	/* controller writes into our memory */
 	pci_set_master (dev);
-	mem_base = pci_resource_start(dev, 0);
-	mem_base = (unsigned long) ioremap_nocache (mem_base, 4096);
+	pci_read_config_byte (dev, PCI_LATENCY_TIMER, &latency);
+	if (latency) {
+		pci_read_config_byte (dev, PCI_MAX_LAT, &limit);
+		if (limit && limit < latency) {
+			dbg ("PCI latency reduced to max %d", limit);
+			pci_write_config_byte (dev, PCI_LATENCY_TIMER, limit);
+		}
+	}
 
+	/* we read its hardware registers as memory */
+	mem_resource = pci_resource_start(dev, 0);
+	/* request_mem_region ... */
+	mem_base = ioremap_nocache (mem_resource, 4096);
 	if (!mem_base) {
 		err("Error mapping OHCI memory");
 		return -EFAULT;
 	}
-	return hc_found_ohci (dev, dev->irq, (void *) mem_base);
+
+	return hc_found_ohci (dev, dev->irq, mem_base);
 } 
 
 /*-------------------------------------------------------------------------*/
 
+/* may be called from interrupt context [interface spec] */
+/* may be called without controller present */
+/* may be called with controller, bus, and devices active */
+
+static void __devexit
+ohci_pci_remove (struct pci_dev *dev)
+{
+	ohci_t		*ohci = (ohci_t *) dev->driver_data;
+
+	dbg ("remove %s controller usb-%s%s%s",
+		hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS),
+		dev->slot_name,
+		ohci->disabled ? " (disabled)" : "",
+		in_interrupt () ? " in interrupt" : ""
+		);
+
+	/* don't wake up sleeping controllers, or block in interrupt context */
+	if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER || in_interrupt ()) {
+		dbg ("controller being disabled");
+		ohci->disabled = 1;
+	}
+
+	/* on return, USB will always be reset (if present) */
+	if (ohci->disabled)
+		writel (ohci->hc_control = OHCI_USB_RESET,
+			&ohci->regs->control);
+
+	hc_release_ohci (ohci);
+}
+
+
+#ifdef	CONFIG_PM
+
+/*-------------------------------------------------------------------------*/
+
+static void
+ohci_pci_suspend (struct pci_dev *dev)
+{
+	ohci_t		*ohci = (ohci_t *) dev->driver_data;
+
+	if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER) {
+		dbg ("can't suspend usb-%s (state is %s)", dev->slot_name,
+			hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS));
+		return;
+	}
+
+	/* act as if usb suspend can always be used */
+	info ("USB suspend: usb-%s", dev->slot_name);
+#ifdef CONFIG_PMAC_PBOOK
+	disable_irq (ohci->irq);
+	/* else, 2.4 assumes shared irqs -- don't disable */
+#endif
+	ohci->hc_control = OHCI_USB_SUSPEND;
+	writel (ohci->hc_control, &ohci->regs->control);
+	wait_ms (10);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void
+ohci_pci_resume (struct pci_dev *dev)
+{
+	ohci_t		*ohci = (ohci_t *) dev->driver_data;
+	int		temp;
+
+	/* did we suspend, or were we powered off? */
+	ohci->hc_control = readl (&ohci->regs->control);
+	temp = ohci->hc_control & OHCI_CTRL_HCFS;
+
+#ifdef DEBUG
+	/* the registers may look crazy here */
+	ohci_dump_status (ohci);
+#endif
+
+	switch (temp) {
+
+	case OHCI_USB_RESET:	// lost power
+		info ("USB restart: usb-%s", dev->slot_name);
+		hc_restart (ohci);
+		break;
+
+	case OHCI_USB_SUSPEND:	// host wakeup
+	case OHCI_USB_RESUME:	// remote wakeup
+		info ("USB continue: usb-%s from %s wakeup", dev->slot_name,
+			(temp == OHCI_USB_SUSPEND)
+				? "host" : "remote");
+		ohci->hc_control = OHCI_USB_RESUME;
+		writel (ohci->hc_control, &ohci->regs->control);
+		wait_ms (20);
+
+		temp = readl (&ohci->regs->control);
+		temp = ohci->hc_control & OHCI_CTRL_HCFS;
+		if (temp != OHCI_USB_RESUME) {
+			err ("controller usb-%s won't resume", dev->slot_name);
+			ohci->disabled = 1;
+			return;
+		}
+
+		ohci->disabled = 0;
+		ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
+		writel (ohci->hc_control, &ohci->regs->control);
+#ifdef CONFIG_PMAC_PBOOK
+		enable_irq (ohci->irq);
+#endif
+		break;
+
+	default:
+		warn ("odd PCI resume for usb-%s", dev->slot_name);
+	}
+}
+
+#endif	/* CONFIG_PM */
+
+
+/*-------------------------------------------------------------------------*/
+
+static const struct __devinitdata pci_device_id	ohci_pci_ids [] = { {
+
+	/* handle any USB OHCI controller */
+	class: 		((PCI_CLASS_SERIAL_USB << 8) | 0x10),
+	class_mask: 	~0,
+
+	/* no matter who makes it */
+	vendor:		PCI_ANY_ID,
+	device:		PCI_ANY_ID,
+	subvendor:	PCI_ANY_ID,
+	subdevice:	PCI_ANY_ID,
+
+	}, { /* end: all zeroes */ }
+};
+
+MODULE_DEVICE_TABLE (pci, ohci_pci_ids);
+
+static struct pci_driver ohci_pci_driver = {
+	name:		"usb-ohci",
+	id_table:	&ohci_pci_ids [0],
+
+	probe:		ohci_pci_probe,
+	remove:		ohci_pci_remove,
+
+#ifdef	CONFIG_PMAC_PBOOK
+	/* pbook PCI thinks different ... for now :-) */
+#else
+#ifdef	CONFIG_PM
+	suspend:	ohci_pci_suspend,
+	resume:		ohci_pci_resume,
+#endif	/* PM */
+#endif	/* PBOOK */
+};
+
+ 
 #ifdef CONFIG_PMAC_PBOOK
 
-/* On Powerbooks, put the controller into suspend mode when going
- * to sleep, and do a resume when waking up. */
+/*-------------------------------------------------------------------------*/
 
 static int ohci_sleep_notify (struct pmu_sleep_notifier * self, int when)
 {
@@ -2028,16 +2282,10 @@
 
 		switch (when) {
 		case PBOOK_SLEEP_NOW:
-			disable_irq (ohci->irq);
-			writel (ohci->hc_control = OHCI_USB_SUSPEND, &ohci->regs->control);
-			wait_ms (10);
+			ohci_pci_suspend (ohci->ohci_dev);
 			break;
 		case PBOOK_WAKE:
- 			writel (ohci->hc_control = OHCI_USB_RESUME, &ohci->regs->control);
-			wait_ms (20);
-			ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
-			writel (ohci->hc_control, &ohci->regs->control);
-			enable_irq (ohci->irq);
+			ohci_pci_resume (ohci->ohci_dev);
 			break;
 		}
 	}
@@ -2051,108 +2299,33 @@
 
 /*-------------------------------------------------------------------------*/
 
-static int handle_pm_event (struct pm_dev *dev, pm_request_t rqst, void *data)
+int __init ohci_hcd_init (void) 
 {
-	ohci_t * ohci = (ohci_t*) dev->data;
-	int temp = 0;
-
-	if (ohci) {
-		switch (rqst) {
-		case PM_SUSPEND:
-			/* act as if usb suspend can always be used */
-			dbg("USB suspend: %s", ohci->ohci_dev->slot_name);
-			ohci->hc_control = OHCI_USB_SUSPEND;
-			writel (ohci->hc_control, &ohci->regs->control);
-			wait_ms (10);
-			break;
-
-		case PM_RESUME:
-			/* did we suspend, or were we powered off? */
-			ohci->hc_control = readl (&ohci->regs->control);
-			temp = ohci->hc_control & OHCI_CTRL_HCFS;
-			switch (temp) {
-
-			case OHCI_USB_RESET:	// lost power
-				dbg("USB reset: %s", ohci->ohci_dev->slot_name);
-				ohci->disabled = 1;
-				if (ohci->bus->root_hub)
-					usb_disconnect (&ohci->bus->root_hub);
-				hc_reinit_ohci (ohci);
-				if ((temp = hc_reset (ohci)) < 0
-					|| (temp = hc_start (ohci)) < 0) {
-					ohci->disabled = 1;
-					err ("can't restart %s, %d",
-						ohci->ohci_dev->slot_name,
-						temp);
-				}
-				dbg ("reset done");
-				break;
-
-			case OHCI_USB_SUSPEND:	// host wakeup
-			case OHCI_USB_RESUME:	// remote wakeup
-				dbg("USB resume: %s", ohci->ohci_dev->slot_name);
-				ohci->hc_control = OHCI_USB_RESUME;
-				writel (ohci->hc_control, &ohci->regs->control);
-				wait_ms (20);
-
-				ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
-				writel (ohci->hc_control, &ohci->regs->control);
-				break;
-
-			default:
-				warn ("odd PM_RESUME");
-			}
-			break;
-		}
-	}
-	return 0;
-}
+	int ret = pci_module_init (&ohci_pci_driver);
 
-/*-------------------------------------------------------------------------*/
-
-#define PCI_CLASS_SERIAL_USB_OHCI 0x0C0310
- 
-int ohci_hcd_init (void) 
-{
-	int ret = -ENODEV;
-	struct pci_dev * dev = NULL;
- 
-	while ((dev = pci_find_class (PCI_CLASS_SERIAL_USB_OHCI, dev))) { 
-		if (hc_start_ohci(dev) >= 0) ret = 0;
-	}
-    
 #ifdef CONFIG_PMAC_PBOOK
-	pmu_register_sleep_notifier (&ohci_sleep_notifier);
+	if (ret >= 0)
+		pmu_register_sleep_notifier (&ohci_sleep_notifier);
 #endif  
-    return ret;
+	return ret;
 }
 
-/*-------------------------------------------------------------------------*/
-
 #ifdef MODULE
-int init_module (void) 
-{
-	return ohci_hcd_init ();
-}
 
 /*-------------------------------------------------------------------------*/
 
-void cleanup_module (void) 
+void __exit ohci_hcd_cleanup (void) 
 {	
-	ohci_t * ohci;
-	
-	pm_unregister_all (handle_pm_event);
-
 #ifdef CONFIG_PMAC_PBOOK
 	pmu_unregister_sleep_notifier (&ohci_sleep_notifier);
 #endif  
-
-	while (!list_empty (&ohci_hcd_list)) {
-		ohci = list_entry (ohci_hcd_list.next, ohci_t, ohci_hcd_list);
-		list_del (&ohci->ohci_hcd_list);
-		INIT_LIST_HEAD (&ohci->ohci_hcd_list);
-		hc_release_ohci (ohci);
-	}		
+	pci_unregister_driver (&ohci_pci_driver);
 }
-#endif //MODULE
 
+module_init (ohci_hcd_init);
+module_exit (ohci_hcd_cleanup);
+
+#endif /* MODULE */
+
+
+MODULE_DESCRIPTION ("USB OHCI Host Controller Driver");

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)