USB: musb: support host/gadget role switching on Blackfin parts
authorCliff Cai <cliff.cai@analog.com>
Thu, 25 Mar 2010 11:25:19 +0000 (13:25 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 20 May 2010 20:21:35 +0000 (13:21 -0700)
Signed-off-by: Cliff Cai <cliff.cai@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/musb/blackfin.c

index eb4392f..8052e76 100644 (file)
@@ -170,6 +170,13 @@ static irqreturn_t blackfin_interrupt(int irq, void *__hci)
                retval = musb_interrupt(musb);
        }
 
+       /* Start sampling ID pin, when plug is removed from MUSB */
+       if (is_otg_enabled(musb) && (musb->xceiv->state == OTG_STATE_B_IDLE
+               || musb->xceiv->state == OTG_STATE_A_WAIT_BCON)) {
+               mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
+               musb->a_wait_bcon = TIMER_DELAY;
+       }
+
        spin_unlock_irqrestore(&musb->lock, flags);
 
        return retval;
@@ -180,6 +187,7 @@ static void musb_conn_timer_handler(unsigned long _musb)
        struct musb *musb = (void *)_musb;
        unsigned long flags;
        u16 val;
+       static u8 toggle;
 
        spin_lock_irqsave(&musb->lock, flags);
        switch (musb->xceiv->state) {
@@ -187,10 +195,44 @@ static void musb_conn_timer_handler(unsigned long _musb)
        case OTG_STATE_A_WAIT_BCON:
                /* Start a new session */
                val = musb_readw(musb->mregs, MUSB_DEVCTL);
+               val &= ~MUSB_DEVCTL_SESSION;
+               musb_writew(musb->mregs, MUSB_DEVCTL, val);
                val |= MUSB_DEVCTL_SESSION;
                musb_writew(musb->mregs, MUSB_DEVCTL, val);
+               /* Check if musb is host or peripheral. */
+               val = musb_readw(musb->mregs, MUSB_DEVCTL);
+
+               if (!(val & MUSB_DEVCTL_BDEVICE)) {
+                       gpio_set_value(musb->config->gpio_vrsel, 1);
+                       musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
+               } else {
+                       gpio_set_value(musb->config->gpio_vrsel, 0);
+                       /* Ignore VBUSERROR and SUSPEND IRQ */
+                       val = musb_readb(musb->mregs, MUSB_INTRUSBE);
+                       val &= ~MUSB_INTR_VBUSERROR;
+                       musb_writeb(musb->mregs, MUSB_INTRUSBE, val);
 
+                       val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR;
+                       musb_writeb(musb->mregs, MUSB_INTRUSB, val);
+                       if (is_otg_enabled(musb))
+                               musb->xceiv->state = OTG_STATE_B_IDLE;
+                       else
+                               musb_writeb(musb->mregs, MUSB_POWER, MUSB_POWER_HSENAB);
+               }
+               mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
+               break;
+       case OTG_STATE_B_IDLE:
+
+               if (!is_peripheral_enabled(musb))
+                       break;
+               /* Start a new session.  It seems that MUSB needs taking
+                * some time to recognize the type of the plug inserted?
+                */
                val = musb_readw(musb->mregs, MUSB_DEVCTL);
+               val |= MUSB_DEVCTL_SESSION;
+               musb_writew(musb->mregs, MUSB_DEVCTL, val);
+               val = musb_readw(musb->mregs, MUSB_DEVCTL);
+
                if (!(val & MUSB_DEVCTL_BDEVICE)) {
                        gpio_set_value(musb->config->gpio_vrsel, 1);
                        musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
@@ -205,12 +247,27 @@ static void musb_conn_timer_handler(unsigned long _musb)
                        val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR;
                        musb_writeb(musb->mregs, MUSB_INTRUSB, val);
 
-                       val = MUSB_POWER_HSENAB;
-                       musb_writeb(musb->mregs, MUSB_POWER, val);
+                       /* Toggle the Soft Conn bit, so that we can response to
+                        * the inserting of either A-plug or B-plug.
+                        */
+                       if (toggle) {
+                               val = musb_readb(musb->mregs, MUSB_POWER);
+                               val &= ~MUSB_POWER_SOFTCONN;
+                               musb_writeb(musb->mregs, MUSB_POWER, val);
+                               toggle = 0;
+                       } else {
+                               val = musb_readb(musb->mregs, MUSB_POWER);
+                               val |= MUSB_POWER_SOFTCONN;
+                               musb_writeb(musb->mregs, MUSB_POWER, val);
+                               toggle = 1;
+                       }
+                       /* The delay time is set to 1/4 second by default,
+                        * shortening it, if accelerating A-plug detection
+                        * is needed in OTG mode.
+                        */
+                       mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY / 4);
                }
-               mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
                break;
-
        default:
                DBG(1, "%s state not handled\n", otg_state_string(musb));
                break;
@@ -222,7 +279,7 @@ static void musb_conn_timer_handler(unsigned long _musb)
 
 void musb_platform_enable(struct musb *musb)
 {
-       if (is_host_enabled(musb)) {
+       if (!is_otg_enabled(musb) && is_host_enabled(musb)) {
                mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
                musb->a_wait_bcon = TIMER_DELAY;
        }
@@ -256,7 +313,7 @@ static int bfin_set_power(struct otg_transceiver *x, unsigned mA)
 
 void musb_platform_try_idle(struct musb *musb, unsigned long timeout)
 {
-       if (is_host_enabled(musb))
+       if (!is_otg_enabled(musb) && is_host_enabled(musb))
                mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY);
 }