This supports the newish (as of last summer) pullup and vbus session calls,
using them to support PM.  Also sparse fixes.  Tested on multiple boards;
please merge.

- Dvae
This has various updates to the PXA 21x/25x/26x UDC driver.

    - Implement the "new" pullup() and vbus_session() methods, and
      use them to keep the UDC 48 MHz clock off much of the time.

	* Reworked that ugly Lubbock VBUS IRQ code.  Claim both IRQs,
	  enable only one at a time; clock the UDC only when VBUS is
	  present.  (And get rid of rude runtime messages.)

	* Implement driver model suspend() and resume() calls.  When
	  this device suspends, it clocks off the UDC.  On boards that
	  support it (including Zaurus clamshells, but not Lubbock)
	  the D+ pullup is disabled, so the host won't see the device.

    - Hmm, the "latest" errata defined some "Must Be One" bits.  OK.

    - Change the LED support for debugging.  It stopped compiling for
      Lubbock a while back.  This switches to the standard LED calls
      (so it can work on non-Lubbock hardware), removes the EP0 calls
      (not very useful any more), and for Lubbock now initializes the
      hex leds (U-Boot doesn't enable them, BLOB did).

    - "sparse" updates, and get rid of a warning that's pointless
      unless someone's working on DMA support;

Tested on Lubbock (VBUS sensing but no pullup) and some Zaurus
clamshells (pullup but no VBUS).

Signed-off-by: David Brownell <[EMAIL PROTECTED]>

--- a/drivers/usb/gadget/pxa2xx_udc.c	2005-03-12 16:35:52 -08:00
+++ b/drivers/usb/gadget/pxa2xx_udc.c	2005-03-12 16:35:52 -08:00
@@ -310,7 +311,7 @@
 	/* flush fifo (mostly for IN buffers) */
 	pxa2xx_ep_fifo_flush (_ep);
 
-	ep->desc = 0;
+	ep->desc = NULL;
 	ep->stopped = 1;
 
 	DBG(DBG_VERBOSE, "%s disabled\n", _ep->name);
@@ -334,7 +335,7 @@
 
 	req = kmalloc (sizeof *req, gfp_flags);
 	if (!req)
-		return 0;
+		return NULL;
 
 	memset (req, 0, sizeof *req);
 	INIT_LIST_HEAD (&req->queue);
@@ -369,7 +370,11 @@
 
 	retval = kmalloc (bytes, gfp_flags & ~(__GFP_DMA|__GFP_HIGHMEM));
 	if (retval)
+#ifdef	USE_DMA
 		*dma = virt_to_bus (retval);
+#else
+		*dma = (dma_addr_t)~0;
+#endif
 	return retval;
 }
 
@@ -411,7 +416,6 @@
 static inline void ep0_idle (struct pxa2xx_udc *dev)
 {
 	dev->ep0state = EP0_IDLE;
-	LED_EP0_OFF;
 }
 
 static int
@@ -930,7 +934,7 @@
 			case EP0_IN_DATA_PHASE:
 				dev->stats.write.ops++;
 				if (write_ep0_fifo(ep, req))
-					req = 0;
+					req = NULL;
 				break;
 
 			case EP0_OUT_DATA_PHASE:
@@ -940,7 +944,8 @@
 					DBG(DBG_VERBOSE, "ep0 config ack%s\n",
 						dev->has_cfr ?  "" : " raced");
 					if (dev->has_cfr)
-						UDCCFR = UDCCFR_AREN|UDCCFR_ACM;
+						UDCCFR = UDCCFR_AREN|UDCCFR_ACM
+							|UDCCFR_MB1;
 					done(ep, req, 0);
 					dev->ep0state = EP0_END_XFER;
 					local_irq_restore (flags);
@@ -952,7 +957,7 @@
 						&& read_ep0_fifo(ep, req))) {
 					ep0_idle(dev);
 					done(ep, req, 0);
-					req = 0;
+					req = NULL;
 				}
 				break;
 
@@ -970,10 +975,10 @@
 		} else if ((ep->bEndpointAddress & USB_DIR_IN) != 0
 				&& (*ep->reg_udccs & UDCCS_BI_TFS) != 0
 				&& write_fifo(ep, req)) {
-			req = 0;
+			req = NULL;
 		} else if ((*ep->reg_udccs & UDCCS_BO_RFS) != 0
 				&& read_fifo(ep, req)) {
-			req = 0;
+			req = NULL;
 		}
 
 		if (likely (req && ep->desc) && ep->dma < 0)
@@ -1094,7 +1099,6 @@
 		start_watchdog(ep->dev);
 		ep->dev->req_pending = 0;
 		ep->dev->ep0state = EP0_STALL;
-		LED_EP0_OFF;
 
  	/* and bulk/intr endpoints like dropping stalls too */
  	} else {
@@ -1194,13 +1198,71 @@
 	return 0;
 }
 
+static void stop_activity(struct pxa2xx_udc *, struct usb_gadget_driver *);
+static void udc_enable (struct pxa2xx_udc *);
+static void udc_disable(struct pxa2xx_udc *);
+
+/* We disable the UDC -- and its 48 MHz clock -- whenever it's not
+ * in active use.  
+ */
+static int pullup(struct pxa2xx_udc *udc, int is_active)
+{
+	is_active = is_active && udc->vbus && udc->pullup;
+	DMSG("%s\n", is_active ? "active" : "inactive");
+	if (is_active)
+		udc_enable(udc);
+	else {
+		if (udc->gadget.speed != USB_SPEED_UNKNOWN) {
+			DMSG("disconnect %s\n", udc->driver
+				? udc->driver->driver.name
+				: "(no driver)");
+			stop_activity(udc, udc->driver);
+		}
+		udc_disable(udc);
+	}
+	return 0;
+}
+
+/* VBUS reporting logically comes from a transceiver */
+static int pxa2xx_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
+{
+	struct pxa2xx_udc	*udc;
+
+	udc = container_of(_gadget, struct pxa2xx_udc, gadget);
+	udc->vbus = is_active = (is_active != 0);
+	DMSG("vbus %s\n", is_active ? "supplied" : "inactive");
+	pullup(udc, is_active);
+	return 0;
+}
+
+/* drivers may have software control over D+ pullup */
+static int pxa2xx_udc_pullup(struct usb_gadget *_gadget, int is_active)
+{
+	struct pxa2xx_udc	*udc;
+
+	udc = container_of(_gadget, struct pxa2xx_udc, gadget);
+
+	/* not all boards support pullup control */
+	if (!udc->mach->udc_command)
+		return -EOPNOTSUPP;
+
+	is_active = (is_active != 0);
+	udc->pullup = is_active;
+	pullup(udc, is_active);
+	return 0;
+}
+
 static const struct usb_gadget_ops pxa2xx_udc_ops = {
-	.get_frame	 = pxa2xx_udc_get_frame,
-	.wakeup		 = pxa2xx_udc_wakeup,
-	// current versions must always be self-powered
+	.get_frame	= pxa2xx_udc_get_frame,
+	.wakeup		= pxa2xx_udc_wakeup,
+	.vbus_session	= pxa2xx_udc_vbus_session,
+	.pullup		= pxa2xx_udc_pullup,
+
+	// .vbus_draw ... boards may consume current from VBUS, up to
+	// 100-500mA based on config.  the 500uA suspend ceiling means
+	// that exclusively vbus-powered PXA designs violate USB specs.
 };
 
-
 /*-------------------------------------------------------------------------*/
 
 #ifdef CONFIG_USB_GADGET_DEBUG_FILES
@@ -1427,7 +1489,7 @@
 		if (i != 0)
 			list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list);
 
-		ep->desc = 0;
+		ep->desc = NULL;
 		ep->stopped = 0;
 		INIT_LIST_HEAD (&ep->queue);
 		ep->pio_irqs = ep->dma_irqs = 0;
@@ -1446,6 +1508,7 @@
 #ifdef	CONFIG_ARCH_PXA
         /* Enable clock for USB device */
 	pxa_set_cken(CKEN11_USB, 1);
+	udelay(5);
 #endif
 
 	/* try to clear these bits before we enable the udc */
@@ -1469,7 +1532,7 @@
 		/* pxa255 (a0+) can avoid a set_config race that could
 		 * prevent gadget drivers from configuring correctly
 		 */
-		UDCCFR = UDCCFR_ACM;
+		UDCCFR = UDCCFR_ACM | UDCCFR_MB1;
 	} else {
 		/* "USB test mode" for pxa250 errata 40-42 (stepping a0, a1)
 		 * which could result in missing packets and interrupts.
@@ -1498,18 +1561,13 @@
 	}
 #endif
 
-	/* caller must be able to sleep in order to cope
-	 * with startup transients.
-	 */
-	msleep(100);
-
 	/* enable suspend/resume and reset irqs */
 	udc_clear_mask_UDCCR(UDCCR_SRM | UDCCR_REM);
 
 	/* enable ep0 irqs */
 	UICR0 &= ~UICR0_IM0;
 
-	/* if hardware supports it, connect to usb and wait for host */
+	/* if hardware supports it, pullup D+ and wait for reset */
 	let_usb_appear();
 }
 
@@ -1540,6 +1598,7 @@
 	/* first hook up the driver ... */
 	dev->driver = driver;
 	dev->gadget.dev.driver = &driver->driver;
+	dev->pullup = 1;
 
 	device_add (&dev->gadget.dev);
 	retval = driver->bind(&dev->gadget);
@@ -1548,18 +1607,17 @@
 				driver->driver.name, retval);
 		device_del (&dev->gadget.dev);
 
-		dev->driver = 0;
-		dev->gadget.dev.driver = 0;
+		dev->driver = NULL;
+		dev->gadget.dev.driver = NULL;
 		return retval;
 	}
 	device_create_file(dev->dev, &dev_attr_function);
 
 	/* ... then enable host detection and ep0; and we're ready
 	 * for set_configuration as well as eventual disconnect.
-	 * NOTE:  this shouldn't power up until later.
 	 */
 	DMSG("registered gadget driver '%s'\n", driver->driver.name);
-	udc_enable(dev);
+	pullup(dev, 1);
 	dump_state(dev);
 	return 0;
 }
@@ -1572,7 +1630,7 @@
 
 	/* don't disconnect drivers more than once */
 	if (dev->gadget.speed == USB_SPEED_UNKNOWN)
-		driver = 0;
+		driver = NULL;
 	dev->gadget.speed = USB_SPEED_UNKNOWN;
 
 	/* prevent new request submissions, kill any outstanding requests  */
@@ -1603,12 +1661,12 @@
 		return -EINVAL;
 
 	local_irq_disable();
-	udc_disable(dev);
+	pullup(dev, 0);
 	stop_activity(dev, driver);
 	local_irq_enable();
 
 	driver->unbind(&dev->gadget);
-	dev->driver = 0;
+	dev->driver = NULL;
 
 	device_del (&dev->gadget.dev);
 	device_remove_file(dev->dev, &dev_attr_function);
@@ -1624,61 +1682,41 @@
 
 #ifdef CONFIG_ARCH_LUBBOCK
 
-/* Lubbock can report connect or disconnect irqs.  Likely more hardware
- * could support it as a timer callback.
- *
- * FIXME for better power management, keep the hardware powered down
- * until a host is powering the link.  means scheduling work later
- * in some task that can udc_enable().
+/* Lubbock has separate connect and disconnect irqs.  More typical designs
+ * use one GPIO as the VBUS IRQ, and another to control the D+ pullup.
  */
 
-#define	enable_disconnect_irq() \
-	if (machine_is_lubbock()) { enable_irq(LUBBOCK_USB_DISC_IRQ); }
-#define	disable_disconnect_irq() \
-	if (machine_is_lubbock()) { disable_irq(LUBBOCK_USB_DISC_IRQ); }
-
 static irqreturn_t
-usb_connection_irq(int irq, void *_dev, struct pt_regs *r)
+lubbock_vbus_irq(int irq, void *_dev, struct pt_regs *r)
 {
 	struct pxa2xx_udc	*dev = _dev;
+	int			vbus;
 
 	dev->stats.irqs++;
 	HEX_DISPLAY(dev->stats.irqs);
-
-	if (!is_usb_connected()) {
-		LED_CONNECTED_OFF;
-		disable_disconnect_irq();
-		/* report disconnect just once */
-		if (dev->gadget.speed != USB_SPEED_UNKNOWN) {
-			DMSG("disconnect %s\n",
-				dev->driver ? dev->driver->driver.name : 0);
-			stop_activity(dev, dev->driver);
-
-			// udc_disable (dev);
-			// no more udc irqs
-			// maybe "ACTION=disconnect /sbin/hotplug gadget".
-		}
-	} else if (dev->gadget.speed == USB_SPEED_UNKNOWN) {
+	switch (irq) {
+	case LUBBOCK_USB_IRQ:
 		LED_CONNECTED_ON;
-
-		DMSG("?? connect irq ??\n");
-
-		// if there's no driver bound, ignore; else
-		// udc_enable (dev);
-		// UDC irqs drive the rest.
-		// maybe "ACTION=connect /sbin/hotplug gadget".
+		vbus = 1;
+		disable_irq(LUBBOCK_USB_IRQ);
+		enable_irq(LUBBOCK_USB_DISC_IRQ);
+		break;
+	case LUBBOCK_USB_DISC_IRQ:
+		LED_CONNECTED_OFF;
+		vbus = 0;
+		disable_irq(LUBBOCK_USB_DISC_IRQ);
+		enable_irq(LUBBOCK_USB_IRQ);
+		break;
+	default:
+		return IRQ_NONE;
 	}
+
+	pxa2xx_udc_vbus_session(&dev->gadget, vbus);
 	return IRQ_HANDLED;
 }
 
 #endif
 
-#ifndef	enable_disconnect_irq
-#warning USB disconnect() is not yet reported.
-#define	enable_disconnect_irq()		do {} while (0)
-#define	disable_disconnect_irq()	do {} while (0)
-#endif
-
 
 /*-------------------------------------------------------------------------*/
 
@@ -1720,7 +1758,7 @@
 	} u;
 
 	if (list_empty(&ep->queue))
-		req = 0;
+		req = NULL;
 	else
 		req = list_entry(ep->queue.next, struct pxa2xx_request, queue);
 
@@ -1764,14 +1802,11 @@
 				goto bad_setup;
 
 got_setup:
-			le16_to_cpus (&u.r.wValue);
-			le16_to_cpus (&u.r.wIndex);
-			le16_to_cpus (&u.r.wLength);
-
-			LED_EP0_ON;
 			DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n",
 				u.r.bRequestType, u.r.bRequest,
-				u.r.wValue, u.r.wIndex, u.r.wLength);
+				le16_to_cpu(u.r.wValue),
+				le16_to_cpu(u.r.wIndex),
+				le16_to_cpu(u.r.wLength));
 
 			/* cope with automagic for some standard requests. */
 			dev->req_std = (u.r.bRequestType & USB_TYPE_MASK)
@@ -1803,7 +1838,8 @@
 					 *  - ep reset doesn't include halt(?).
 					 */
 					DMSG("broken set_interface (%d/%d)\n",
-						u.r.wIndex, u.r.wValue);
+						le16_to_cpu(u.r.wIndex),
+						le16_to_cpu(u.r.wValue));
 					goto config_change;
 				}
 				break;
@@ -1847,7 +1883,6 @@
 				ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall");
 				start_watchdog(dev);
 				dev->ep0state = EP0_STALL;
-				LED_EP0_OFF;
 
 			/* deferred i/o == no response yet */
 			} else if (dev->req_pending) {
@@ -1948,7 +1983,7 @@
 			req = list_entry(ep->queue.next,
 					struct pxa2xx_request, queue);
 		else
-			req = 0;
+			req = NULL;
 
 		// TODO check FST handling
 
@@ -2050,8 +2085,6 @@
 
 			if ((UDCCR & UDCCR_UDA) == 0) {
 				DBG(DBG_VERBOSE, "USB reset start\n");
-				if (dev->gadget.speed != USB_SPEED_UNKNOWN)
-					disable_disconnect_irq();
 
 				/* reset driver and endpoints,
 				 * in case that's not yet done
@@ -2059,12 +2092,11 @@
 				stop_activity (dev, dev->driver);
 
 			} else {
-				INFO("USB reset\n");
+				DBG(DBG_VERBOSE, "USB reset end\n");
 				dev->gadget.speed = USB_SPEED_FULL;
 				LED_CONNECTED_ON;
 				memset(&dev->stats, 0, sizeof dev->stats);
 				/* driver and endpoints are still reset */
-				enable_disconnect_irq();
 			}
 
 		} else {
@@ -2478,6 +2548,8 @@
 	udc_disable(dev);
 	udc_reinit(dev);
 
+	dev->vbus = is_usb_connected();
+
 	/* irq setup after old hardware state is cleaned up */
 	retval = request_irq(IRQ_USB, pxa2xx_udc_irq,
 			SA_INTERRUPT, driver_name, dev);
@@ -2490,18 +2562,32 @@
 
 #ifdef CONFIG_ARCH_LUBBOCK
 	if (machine_is_lubbock()) {
-		disable_irq(LUBBOCK_USB_DISC_IRQ);
 		retval = request_irq(LUBBOCK_USB_DISC_IRQ,
-				usb_connection_irq,
-				SA_INTERRUPT /* OOPSING | SA_SAMPLE_RANDOM */,
+				lubbock_vbus_irq,
+				SA_INTERRUPT | SA_SAMPLE_RANDOM,
 				driver_name, dev);
 		if (retval != 0) {
-			enable_irq(LUBBOCK_USB_DISC_IRQ);
 			printk(KERN_ERR "%s: can't get irq %i, err %d\n",
 				driver_name, LUBBOCK_USB_DISC_IRQ, retval);
+lubbock_fail0:
+			free_irq(IRQ_USB, dev);
 			return -EBUSY;
 		}
-		dev->got_disc = 1;
+		retval = request_irq(LUBBOCK_USB_IRQ,
+				lubbock_vbus_irq,
+				SA_INTERRUPT | SA_SAMPLE_RANDOM,
+				driver_name, dev);
+		if (retval != 0) {
+			printk(KERN_ERR "%s: can't get irq %i, err %d\n",
+				driver_name, LUBBOCK_USB_IRQ, retval);
+			free_irq(LUBBOCK_USB_DISC_IRQ, dev);
+			goto lubbock_fail0;
+		}
+#ifdef DEBUG
+		/* with U-Boot (but not BLOB), hex is off by default */
+		HEX_DISPLAY(dev->stats.irqs);
+		LUB_DISC_BLNK_LED &= 0xff;
+#endif
 	}
 #endif
 	create_proc_files();
@@ -2510,7 +2596,7 @@
 }
 static int __exit pxa2xx_udc_remove(struct device *_dev)
 {
-	struct pxa2xx_udc *dev = _dev->driver_data;
+	struct pxa2xx_udc *dev = dev_get_drvdata(_dev);
 
 	udc_disable(dev);
 	remove_proc_files();
@@ -2520,15 +2606,57 @@
 		free_irq(IRQ_USB, dev);
 		dev->got_irq = 0;
 	}
-	if (machine_is_lubbock() && dev->got_disc) {
+	if (machine_is_lubbock()) {
 		free_irq(LUBBOCK_USB_DISC_IRQ, dev);
-		dev->got_disc = 0;
+		free_irq(LUBBOCK_USB_IRQ, dev);
+	}
+	dev_set_drvdata(_dev, NULL);
+	the_controller = NULL;
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef	CONFIG_PM
+
+/* USB suspend (controlled by the host) and system suspend (controlled
+ * by the PXA) don't necessarily work well together.  If USB is active,
+ * the 48 MHz clock is required; so the system can't enter 33 MHz idle
+ * mode, or any deeper PM saving state.
+ *
+ * For now, we punt and forcibly disconnect from the USB host when PXA
+ * enters any suspend state.  While we're disconnected, we always disable
+ * the 48MHz USB clock ... allowing PXA sleep and/or 33 MHz idle states. 
+ * Boards without software pullup control shouldn't use those states.
+ * VBUS IRQs should probably be ignored so that the PXA device just acts
+ * "dead" to USB hosts until system resume.
+ */
+static int pxa2xx_udc_suspend(struct device *dev, u32 state, u32 level)
+{
+	struct pxa2xx_udc	*udc = dev_get_drvdata(dev);
+
+	if (level == SUSPEND_POWER_DOWN) {
+		if (!udc->mach->udc_command)
+			WARN("USB host won't detect disconnect!\n");
+		pullup(udc, 0);
 	}
-	dev_set_drvdata(_dev, 0);
-	the_controller = 0;
 	return 0;
 }
 
+static int pxa2xx_udc_resume(struct device *dev, u32 level)
+{
+	struct pxa2xx_udc	*udc = dev_get_drvdata(dev);
+
+	if (level == RESUME_POWER_ON)
+		pullup(udc, 1);
+	return 0;
+}
+
+#else
+#define	pxa2xx_udc_suspend	NULL
+#define	pxa2xx_udc_resume	NULL
+#endif
+
 /*-------------------------------------------------------------------------*/
 
 static struct device_driver udc_driver = {
@@ -2536,10 +2664,8 @@
 	.bus		= &platform_bus_type,
 	.probe		= pxa2xx_udc_probe,
 	.remove		= __exit_p(pxa2xx_udc_remove),
-
-	// FIXME power management support
-	// .suspend = ... disable UDC
-	// .resume = ... re-enable UDC
+	.suspend	= pxa2xx_udc_suspend,
+	.resume		= pxa2xx_udc_resume,
 };
 
 static int __init udc_init(void)
--- a/drivers/usb/gadget/pxa2xx_udc.h	2005-03-12 16:35:52 -08:00
+++ b/drivers/usb/gadget/pxa2xx_udc.h	2005-03-12 16:35:52 -08:00
@@ -40,6 +40,9 @@
 #define UDCCFR_AREN	(1 << 7)	/* ACK response enable (now) */
 #define UDCCFR_ACM	(1 << 2)	/* ACK control mode (wait for AREN) */
 
+/* latest pxa255 errata define new "must be one" bits in UDCCFR */
+#define	UDCCFR_MB1	(0xff & ~(UDCCFR_AREN|UDCCFR_ACM))
+
 /*-------------------------------------------------------------------------*/
 
 struct pxa2xx_udc;
@@ -120,7 +123,8 @@
 	enum ep0_state				ep0state;
 	struct udc_stats			stats;
 	unsigned				got_irq : 1,
-						got_disc : 1,
+						vbus : 1,
+						pullup : 1,
 						has_cfr : 1,
 						req_pending : 1,
 						req_std : 1,
@@ -143,14 +147,7 @@
 
 #ifdef DEBUG
 #define HEX_DISPLAY(n)	if (machine_is_lubbock()) { LUB_HEXLED = (n); }
-
-#define LED_CONNECTED_ON	if (machine_is_lubbock()) { \
-	DISCRETE_LED_ON(D26); }
-#define LED_CONNECTED_OFF	if(machine_is_lubbock()) { \
-	DISCRETE_LED_OFF(D26); LUB_HEXLED = 0; }
-#define LED_EP0_ON	if (machine_is_lubbock()) { DISCRETE_LED_ON(D25); }
-#define LED_EP0_OFF	if (machine_is_lubbock()) { DISCRETE_LED_OFF(D25); }
-#endif /* DEBUG */
+#endif
 
 #endif
 
@@ -161,13 +158,19 @@
 #define HEX_DISPLAY(n)		do {} while(0)
 #endif
 
+#ifdef DEBUG
+#include <asm/leds.h>
+
+#define LED_CONNECTED_ON	leds_event(led_green_on)
+#define LED_CONNECTED_OFF	do { \
+					leds_event(led_green_off); \
+					HEX_DISPLAY(0); \
+				} while(0)
+#endif
+
 #ifndef LED_CONNECTED_ON
 #define LED_CONNECTED_ON	do {} while(0)
 #define LED_CONNECTED_OFF	do {} while(0)
-#endif
-#ifndef LED_EP0_ON
-#define LED_EP0_ON		do {} while (0)
-#define LED_EP0_OFF		do {} while (0)
 #endif
 
 /*-------------------------------------------------------------------------*/

Reply via email to