ChangeSet 1.1713.7.2, 2004/04/02 09:56:28-08:00, david-b@pacbell.net

[PATCH] USB: ehci updates:  CONFIG_PCI, integrated TT

Generalize the driver a bit:

 - PCI-specific handling is restricted to a small chunk of
   init code.  Non-PCI implementations are in the pipeline.

 - Merge support from ARC International (Craig Nadler) for
   their integrated root hub transaction translators (on PCI).
   Other implementations should be similar.


 drivers/usb/host/Kconfig    |   11 +++++++++
 drivers/usb/host/ehci-dbg.c |    6 +++--
 drivers/usb/host/ehci-hcd.c |   50 ++++++++++++++++++++++++++++++++++++--------
 drivers/usb/host/ehci-hub.c |   18 ++++++++++++++-
 drivers/usb/host/ehci-q.c   |   15 ++++++++++---
 drivers/usb/host/ehci.h     |   40 +++++++++++++++++++++++++++++++++++
 include/linux/pci_ids.h     |    3 ++
 7 files changed, 127 insertions(+), 16 deletions(-)


diff -Nru a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
--- a/drivers/usb/host/Kconfig	Wed Apr 14 14:34:12 2004
+++ b/drivers/usb/host/Kconfig	Wed Apr 14 14:34:12 2004
@@ -38,6 +38,17 @@
 	  EHCI or USB 2.0 transaction translator implementations.
 	  It should work for ISO-OUT transfers, like audio.
 
+config USB_EHCI_ROOT_HUB_TT
+	bool "Root Hub Transaction Translators (EXPERIMENTAL)"
+	depends on USB_EHCI_HCD && EXPERIMENTAL
+	---help---
+	  Some EHCI chips have vendor-specific extensions to integrate
+	  transaction translators, so that no OHCI or UHCI companion
+	  controller is needed.  It's safe to say "y" even if your
+	  controller doesn't support this feature.
+
+	  This supports the EHCI implementation from ARC International.
+
 config USB_OHCI_HCD
 	tristate "OHCI HCD support"
 	depends on USB
diff -Nru a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c
--- a/drivers/usb/host/ehci-dbg.c	Wed Apr 14 14:34:12 2004
+++ b/drivers/usb/host/ehci-dbg.c	Wed Apr 14 14:34:12 2004
@@ -628,8 +628,10 @@
 	/* Capability Registers */
 	i = HC_VERSION(readl (&ehci->caps->hc_capbase));
 	temp = scnprintf (next, size,
-		"PCI device %s\nEHCI %x.%02x, hcd state %d (driver " DRIVER_VERSION ")\n",
-		pci_name(to_pci_dev(hcd->self.controller)),
+		"bus %s device %s\n"
+		"EHCI %x.%02x, hcd state %d (driver " DRIVER_VERSION ")\n",
+		hcd->self.controller->bus->name,
+		hcd->self.controller->bus_id,
 		i >> 8, i & 0x0ff, ehci->hcd.state);
 	size -= temp;
 	next += temp;
diff -Nru a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
--- a/drivers/usb/host/ehci-hcd.c	Wed Apr 14 14:34:12 2004
+++ b/drivers/usb/host/ehci-hcd.c	Wed Apr 14 14:34:12 2004
@@ -279,6 +279,8 @@
 	spin_unlock_irqrestore (&ehci->lock, flags);
 }
 
+#ifdef	CONFIG_PCI
+
 /* EHCI 0.96 (and later) section 5.1 says how to kick BIOS/SMM/...
  * off the controller (maybe it can boot from highspeed USB disks).
  */
@@ -307,6 +309,8 @@
 	return 0;
 }
 
+#endif
+
 static int
 ehci_reboot (struct notifier_block *self, unsigned long code, void *null)
 {
@@ -335,8 +339,12 @@
 	dbg_hcs_params (ehci, "reset");
 	dbg_hcc_params (ehci, "reset");
 
+#ifdef	CONFIG_PCI
 	/* EHCI 0.96 and later may have "extended capabilities" */
-	temp = HCC_EXT_CAPS (readl (&ehci->caps->hcc_params));
+	if (hcd->self.controller->bus == &pci_bus_type)
+		temp = HCC_EXT_CAPS (readl (&ehci->caps->hcc_params));
+	else
+		temp = 0;
 	while (temp) {
 		u32		cap;
 
@@ -356,6 +364,7 @@
 		}
 		temp = (cap >> 8) & 0xff;
 	}
+#endif
 
 	/* cache this readonly data; minimize PCI reads */
 	ehci->hcs_params = readl (&ehci->caps->hcs_params);
@@ -372,7 +381,7 @@
 	struct usb_bus		*bus;
 	int			retval;
 	u32			hcc_params;
-	u8                      tempbyte;
+	u8                      sbrn = 0;
 
 	init_timer (&ehci->watchdog);
 	ehci->watchdog.function = ehci_watchdog;
@@ -406,6 +415,29 @@
 	writel (INTR_MASK, &ehci->regs->intr_enable);
 	writel (ehci->periodic_dma, &ehci->regs->frame_list);
 
+#ifdef	CONFIG_PCI
+	if (hcd->self.controller->bus == &pci_bus_type) {
+		struct pci_dev		*pdev;
+
+		pdev = to_pci_dev(hcd->self.controller);
+
+		/* Serial Bus Release Number is at PCI 0x60 offset */
+		pci_read_config_byte(pdev, 0x60, &sbrn);
+
+		/* help hc dma work well with cachelines */
+		pci_set_mwi (pdev);
+
+		/* chip-specific init */
+		switch (pdev->vendor) {
+		case PCI_VENDOR_ID_ARC:
+			if (pdev->device == PCI_DEVICE_ID_ARC_EHCI)
+				ehci->is_arc_rh_tt = 1;
+			break;
+		}
+
+	}
+#endif
+
 	/*
 	 * dedicate a qh for the async ring head, since we couldn't unlink
 	 * a 'real' qh without stopping the async schedule [4.8].  use it
@@ -443,9 +475,6 @@
 #endif
 	}
 
-	/* help hc dma work well with cachelines */
-	pci_set_mwi (to_pci_dev(ehci->hcd.self.controller));
-
 	/* clear interrupt enables, set irq latency */
 	temp = readl (&ehci->regs->command) & 0x0fff;
 	if (log2_irq_thresh < 0 || log2_irq_thresh > 6)
@@ -493,12 +522,10 @@
 	writel (FLAG_CF, &ehci->regs->configured_flag);
 	readl (&ehci->regs->command);	/* unblock posted write */
 
-        /* PCI Serial Bus Release Number is at 0x60 offset */
-	pci_read_config_byte(to_pci_dev(hcd->self.controller), 0x60, &tempbyte);
 	temp = HC_VERSION(readl (&ehci->caps->hc_capbase));
 	ehci_info (ehci,
 		"USB %x.%x enabled, EHCI %x.%02x, driver %s\n",
-		((tempbyte & 0xf0)>>4), (tempbyte & 0x0f),
+		((sbrn & 0xf0)>>4), (sbrn & 0x0f),
 		temp >> 8, temp & 0xff, DRIVER_VERSION);
 
 	/*
@@ -995,7 +1022,9 @@
 
 /*-------------------------------------------------------------------------*/
 
-/* EHCI spec says PCI is required. */
+/* EHCI 1.0 doesn't require PCI */
+
+#ifdef	CONFIG_PCI
 
 /* PCI driver selection metadata; PCI hotplugging uses this */
 static const struct pci_device_id pci_ids [] = { {
@@ -1020,6 +1049,9 @@
 	.resume =	usb_hcd_pci_resume,
 #endif
 };
+
+#endif	/* PCI */
+
 
 #define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
 
diff -Nru a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
--- a/drivers/usb/host/ehci-hub.c	Wed Apr 14 14:34:12 2004
+++ b/drivers/usb/host/ehci-hub.c	Wed Apr 14 14:34:12 2004
@@ -40,6 +40,15 @@
 
 	/* if reset finished and it's still not enabled -- handoff */
 	if (!(port_status & PORT_PE)) {
+
+		/* with integrated TT, there's nobody to hand it to! */
+		if (ehci_is_ARC(ehci)) {
+			ehci_dbg (ehci,
+				"Failed to enable port %d on root hub TT\n",
+				index+1);
+			return port_status;
+		}
+
 		ehci_dbg (ehci, "port %d full speed --> companion\n",
 			index + 1);
 
@@ -257,7 +266,8 @@
 		if (!(temp & PORT_OWNER)) {
 			if (temp & PORT_CONNECT) {
 				status |= 1 << USB_PORT_FEAT_CONNECTION;
-				status |= 1 << USB_PORT_FEAT_HIGHSPEED;
+				// status may be from integrated TT
+				status |= ehci_port_speed(ehci, temp);
 			}
 			if (temp & PORT_PE)
 				status |= 1 << USB_PORT_FEAT_ENABLE;
@@ -307,8 +317,12 @@
 					&ehci->regs->port_status [wIndex]);
 			break;
 		case USB_PORT_FEAT_RESET:
-			/* line status bits may report this as low speed */
+			/* line status bits may report this as low speed,
+			 * which can be fine if this root hub has a
+			 * transaction translator built in.
+			 */
 			if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
+					&& !ehci_is_ARC(ehci)
 					&& PORT_USB11 (temp)) {
 				ehci_dbg (ehci,
 					"port %d low speed --> companion\n",
diff -Nru a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
--- a/drivers/usb/host/ehci-q.c	Wed Apr 14 14:34:12 2004
+++ b/drivers/usb/host/ehci-q.c	Wed Apr 14 14:34:12 2004
@@ -165,7 +165,10 @@
 		/* if async CSPLIT failed, try cleaning out the TT buffer */
 		} else if (urb->dev->tt && !usb_pipeint (urb->pipe)
 				&& ((token & QTD_STS_MMF) != 0
-					|| QTD_CERR(token) == 0)) {
+					|| QTD_CERR(token) == 0)
+				&& (!ehci_is_ARC(ehci)
+                	                || urb->dev->tt->hub !=
+						ehci->hcd.self.root_hub)) {
 #ifdef DEBUG
 			struct usb_device *tt = urb->dev->tt->hub;
 			dev_dbg (&tt->dev,
@@ -576,7 +579,7 @@
 // when each interface/altsetting is established.  Unlink
 // any previous qh and cancel its urbs first; endpoints are
 // implicitly reset then (data toggle too).
-// That'd mean updating how usbcore talks to HCDs. (2.5?)
+// That'd mean updating how usbcore talks to HCDs. (2.7?)
 
 
 /*
@@ -674,7 +677,13 @@
 
 		info2 |= (EHCI_TUNE_MULT_TT << 30);
 		info2 |= urb->dev->ttport << 23;
-		info2 |= urb->dev->tt->hub->devnum << 16;
+
+		/* set the address of the TT; for ARC's integrated
+		 * root hub tt, leave it zeroed.
+		 */
+		if (!ehci_is_ARC(ehci)
+				|| urb->dev->tt->hub != ehci->hcd.self.root_hub)
+			info2 |= urb->dev->tt->hub->devnum << 16;
 
 		/* NOTE:  if (PIPE_INTERRUPT) { scheduler sets c-mask } */
 
diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
--- a/drivers/usb/host/ehci.h	Wed Apr 14 14:34:12 2004
+++ b/drivers/usb/host/ehci.h	Wed Apr 14 14:34:12 2004
@@ -85,6 +85,8 @@
 	unsigned long		actions;
 	unsigned		stamp;
 
+	unsigned		is_arc_rh_tt:1;	/* ARC roothub with TT */
+
 	/* irq statistics */
 #ifdef EHCI_STATS
 	struct ehci_stats	stats;
@@ -549,6 +551,44 @@
 	dma_addr_t		fstn_dma;
 	union ehci_shadow	fstn_next;	/* ptr to periodic q entry */
 } __attribute__ ((aligned (32)));
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_USB_EHCI_ROOT_HUB_TT
+
+/*
+ * Some EHCI controllers have a Transaction Translator built into the
+ * root hub. This is a non-standard feature.  Each controller will need
+ * to add code to the following inline functions, and call them as
+ * needed (mostly in root hub code).
+ */
+
+#define	ehci_is_ARC(e)			((e)->is_arc_rh_tt)
+
+/* Returns the speed of a device attached to a port on the root hub. */
+static inline unsigned int
+ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
+{
+	if (ehci_is_ARC(ehci)) {
+		switch ((portsc>>26)&3) {
+		case 0:
+			return 0;
+		case 1:
+			return (1<<USB_PORT_FEAT_LOWSPEED);
+		case 2:
+		default:
+			return (1<<USB_PORT_FEAT_HIGHSPEED);
+		}
+	}
+	return (1<<USB_PORT_FEAT_HIGHSPEED);
+}
+
+#else
+
+#define	ehci_is_ARC(e)			(0)
+
+#define	ehci_port_speed(ehci, portsc)	(1<<USB_PORT_FEAT_HIGHSPEED)
+#endif
 
 /*-------------------------------------------------------------------------*/
 
diff -Nru a/include/linux/pci_ids.h b/include/linux/pci_ids.h
--- a/include/linux/pci_ids.h	Wed Apr 14 14:34:12 2004
+++ b/include/linux/pci_ids.h	Wed Apr 14 14:34:12 2004
@@ -1870,6 +1870,9 @@
 #define PCI_DEVICE_ID_ALTIMA_AC9100	0x03ea
 #define PCI_DEVICE_ID_ALTIMA_AC1003	0x03eb
 
+#define PCI_VENDOR_ID_ARC               0x192E
+#define PCI_DEVICE_ID_ARC_EHCI          0x0101
+
 #define PCI_VENDOR_ID_SYMPHONY		0x1c1c
 #define PCI_DEVICE_ID_SYMPHONY_101	0x0001