From a637c5056ef52fbb7c41eb7537a9ec3d150231ad Mon Sep 17 00:00:00 2001
From: David Brownell <dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org>
Date: Thu, 2 Apr 2009 10:16:11 -0700
Subject: [PATCH] musb: make initial HNP roleswitch work (v2)

Minor HNP bugfixes, so the initial role switch works:

 - A-Device:
     * disconnect-during-suspend enters A_PERIPHERAL state
     * kill OTG timer after reset as A_PERIPHERAL ...
     * ... and also pass that reset to the gadget
     * once HNP succeeds, clear the "ignore_disconnect" flag
     * from A_PERIPHERAL, disconnect transitions to A_WAIT_BCON

 - B-Device:
     * kill OTG timer on entry to B_HOST state (HNP succeeded)
     * once HNP succeeds, clear "ignore_disconnect" flag
     * kick the root hub only _after_ the state is adjusted

Other state transitions are left alone.  Notably, exit paths from
the "roles have switched" state ... A_PERIPHERAL handling of that
stays seriously broken.

Signed-off-by: David Brownell <dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org>
---
 drivers/usb/musb/musb_core.c    |   27 ++++++++++++++++-----------
 drivers/usb/musb/musb_gadget.c  |    2 +-
 drivers/usb/musb/musb_virthub.c |   11 ++++++++++-
 3 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c
index 05c5dd3..9dc995a 100644
--- a/drivers/usb/musb/musb_core.c
+++ b/drivers/usb/musb/musb_core.c
@@ -587,28 +587,23 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
 		if (devctl & MUSB_DEVCTL_LSDEV)
 			musb->port1_status |= USB_PORT_STAT_LOW_SPEED;
 
-		if (hcd->status_urb)
-			usb_hcd_poll_rh_status(hcd);
-		else
-			usb_hcd_resume_root_hub(hcd);
-
-		MUSB_HST_MODE(musb);
-
 		/* indicate new connection to OTG machine */
 		switch (musb->xceiv->state) {
 		case OTG_STATE_B_PERIPHERAL:
 			if (int_usb & MUSB_INTR_SUSPEND) {
 				DBG(1, "HNP: SUSPEND+CONNECT, now b_host\n");
-				musb->xceiv->state = OTG_STATE_B_HOST;
-				hcd->self.is_b_host = 1;
 				int_usb &= ~MUSB_INTR_SUSPEND;
+				goto b_host;
 			} else
 				DBG(1, "CONNECT as b_peripheral???\n");
 			break;
 		case OTG_STATE_B_WAIT_ACON:
-			DBG(1, "HNP: Waiting to switch to b_host state\n");
+			DBG(1, "HNP: CONNECT, now b_host\n");
+b_host:
 			musb->xceiv->state = OTG_STATE_B_HOST;
 			hcd->self.is_b_host = 1;
+			musb->ignore_disconnect = 0;
+			del_timer(&musb->otg_timer);
 			break;
 		default:
 			if ((devctl & MUSB_DEVCTL_VBUS)
@@ -618,6 +613,14 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
 			}
 			break;
 		}
+
+		/* poke the root hub */
+		MUSB_HST_MODE(musb);
+		if (hcd->status_urb)
+			usb_hcd_poll_rh_status(hcd);
+		else
+			usb_hcd_resume_root_hub(hcd);
+
 		DBG(1, "CONNECT (%s) devctl %02x\n",
 				otg_state_string(musb), devctl);
 	}
@@ -662,7 +665,9 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
 					+ msecs_to_jiffies(TA_WAIT_BCON(musb)));
 				break;
 			case OTG_STATE_A_PERIPHERAL:
-				musb_hnp_stop(musb);
+				musb->ignore_disconnect = 0;
+				del_timer(&musb->otg_timer);
+				musb_g_reset(musb);
 				break;
 			case OTG_STATE_B_WAIT_ACON:
 				DBG(1, "HNP: RESET (%s), to b_peripheral\n",
diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c
index 2fbfba5..7dd3d59 100644
--- a/drivers/usb/musb/musb_gadget.c
+++ b/drivers/usb/musb/musb_gadget.c
@@ -1964,7 +1964,7 @@ void musb_g_disconnect(struct musb *musb)
 		musb->xceiv->state = OTG_STATE_A_IDLE;
 		break;
 	case OTG_STATE_A_PERIPHERAL:
-		musb->xceiv->state = OTG_STATE_A_WAIT_VFALL;
+		musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
 		break;
 	case OTG_STATE_B_WAIT_ACON:
 	case OTG_STATE_B_HOST:
diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c
index 7e7900f..14f7cf3 100644
--- a/drivers/usb/musb/musb_virthub.c
+++ b/drivers/usb/musb/musb_virthub.c
@@ -187,8 +187,17 @@ void musb_root_disconnect(struct musb *musb)
 	musb->is_active = 0;
 
 	switch (musb->xceiv->state) {
-	case OTG_STATE_A_HOST:
 	case OTG_STATE_A_SUSPEND:
+#ifdef	CONFIG_USB_MUSB_OTG
+		if (is_otg_enabled(musb)
+				&& musb->xceiv->host->b_hnp_enable) {
+			musb->xceiv->state = OTG_STATE_A_PERIPHERAL;
+			musb->g.is_a_peripheral = 1;
+			break;
+		}
+#endif
+		/* FALLTHROUGH */
+	case OTG_STATE_A_HOST:
 		musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
 		musb->is_active = 0;
 		break;
-- 
1.6.0.4

