]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
musb_hdrc: host vbus and connect/disconnect fixes
authorDavid Brownell <dbrownell@users.sourceforge.net>
Thu, 9 Nov 2006 20:19:43 +0000 (22:19 +0200)
committerTony Lindgren <tony@atomide.com>
Thu, 9 Nov 2006 20:19:43 +0000 (22:19 +0200)
Teach host side VBUS powerup and connect logic about the problem whereby
the HDRC core wrongly reports VBUS_ERROR during initial powerup.

Remove previous non-working workaround for tusb powering VBUS:  don't
try handling CPEN by hand.

Rest of the fix for disconnection issues in A_SUSPEND.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
drivers/usb/musb/musbdefs.h
drivers/usb/musb/plat_uds.c
drivers/usb/musb/tusb6010.c
drivers/usb/musb/virthub.c

index cea6ea71a57131d4edfaa1911213f466ac436f4b..ce3c77ec33818b70a5ac4ff9d26af0559839ff96 100644 (file)
@@ -415,6 +415,7 @@ struct musb {
        struct musb_hw_ep        aLocalEnd[MUSB_C_NUM_EPS];
 #define control_ep             aLocalEnd
 
+#define VBUSERR_RETRY_COUNT    3
        u16                     vbuserr_retry;
        u16 wEndMask;
        u8 bEndCount;
index dd471d059d7c4dc9d7056489e686fc221a59538f..b345d6f7fae8a613118c069789cbb359c9ece058 100644 (file)
 #endif
 
 #include "musbdefs.h"
-// #ifdef CONFIG_USB_MUSB_HDRC_HCD
-#define VBUSERR_RETRY_COUNT    2               /* is this too few? */
-// #endif
 
 
 #ifdef CONFIG_ARCH_DAVINCI
@@ -411,8 +408,52 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
        }
 
        if (bIntrUSB & MGC_M_INTR_VBUSERROR) {
+               int     ignore = 0;
+
+               /* During connection as an A-Device, we may see a short
+                * current spikes causing voltage drop, because of cable
+                * and peripheral capacitance combined with vbus draw.
+                * (So: less common with truly self-powered devices, where
+                * vbus doesn't act like a power supply.)
+                *
+                * Such spikes are short; usually less than ~500 usec, max
+                * of ~2 msec.  That is, they're not sustained overcurrent
+                * errors, though they're reported using VBUSERROR irqs.
+                *
+                * Workarounds:  (a) hardware: use self powered devices.
+                * (b) software:  ignore non-repeated VBUS errors.
+                *
+                * REVISIT:  do delays from lots of DEBUG_KERNEL checks
+                * make trouble here, keeping VBUS < 4.4V ?
+                */
+               switch (pThis->xceiv.state) {
+               case OTG_STATE_A_HOST:
+                       /* recovery is dicey once we've gotten past the
+                        * initial stages of enumeration, but if VBUS
+                        * stayed ok at the other end of the link, and
+                        * another reset is due (at least for high speed,
+                        * to redo the chirp etc), it might work OK...
+                        */
+               case OTG_STATE_A_WAIT_BCON:
+               case OTG_STATE_A_WAIT_VRISE:
+                       if (pThis->vbuserr_retry) {
+                               pThis->vbuserr_retry--;
+                               ignore = 1;
+                               devctl |= MGC_M_DEVCTL_SESSION;
+                               musb_writeb(pBase, MGC_O_HDRC_DEVCTL, devctl);
+                       } else {
+                               pThis->port1_status |=
+                                         (1 << USB_PORT_FEAT_OVER_CURRENT)
+                                       | (1 << USB_PORT_FEAT_C_OVER_CURRENT);
+                       }
+                       break;
+               default:
+                       break;
+               }
 
-               DBG(1, "VBUS_ERROR (%02x, %s), retry #%d\n", devctl,
+               DBG(1, "VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n",
+                               otg_state_string(pThis),
+                               devctl,
                                ({ char *s;
                                switch (devctl & MGC_M_DEVCTL_VBUS) {
                                case 0 << MGC_S_DEVCTL_VBUS:
@@ -425,13 +466,14 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
                                default:
                                        s = "VALID"; break;
                                }; s; }),
-                               pThis->vbuserr_retry);
+                               VBUSERR_RETRY_COUNT - pThis->vbuserr_retry,
+                               pThis->port1_status);
 
                /* go through A_WAIT_VFALL then start a new session */
-               musb_set_vbus(pThis, 0);
+               if (!ignore)
+                       musb_set_vbus(pThis, 0);
                handled = IRQ_HANDLED;
-       } else
-               pThis->vbuserr_retry = VBUSERR_RETRY_COUNT;
+       }
 
        if (bIntrUSB & MGC_M_INTR_CONNECT) {
                handled = IRQ_HANDLED;
@@ -468,10 +510,13 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
                        pThis->xceiv.state = OTG_STATE_B_HOST;
                        break;
                default:
-                       pThis->xceiv.state = OTG_STATE_A_HOST;
+                       if ((devctl & MGC_M_DEVCTL_VBUS)
+                                       == (3 << MGC_S_DEVCTL_VBUS))
+                               pThis->xceiv.state = OTG_STATE_A_HOST;
                        break;
                }
-               DBG(1, "CONNECT (%s)\n", otg_state_string(pThis));
+               DBG(1, "CONNECT (%s) devctl %02x\n",
+                               otg_state_string(pThis), devctl);
        }
 #endif /* CONFIG_USB_MUSB_HDRC_HCD */
 
@@ -566,18 +611,35 @@ static irqreturn_t musb_stage2_irq(struct musb * pThis, u8 bIntrUSB,
 #endif
 
        if ((bIntrUSB & MGC_M_INTR_DISCONNECT) && !pThis->bIgnoreDisconnect) {
-               DBG(1, "DISCONNECT as %s, devctl %02x\n",
+               DBG(1, "DISCONNECT (%s) as %s, devctl %02x\n",
+                               otg_state_string(pThis),
                                MUSB_MODE(pThis), devctl);
                handled = IRQ_HANDLED;
-               pThis->is_active = 0;
 
-               /* need to check it against pThis, because devctl is going
-                * to report ID low as soon as the device gets disconnected
-                */
-               if (is_host_active(pThis))
+               switch (pThis->xceiv.state) {
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+               case OTG_STATE_A_HOST:
+               case OTG_STATE_A_SUSPEND:
                        musb_root_disconnect(pThis);
-               else
+                       break;
+#endif /* HOST */
+#ifdef CONFIG_USB_MUSB_OTG
+               case OTG_STATE_A_PERIPHERAL:
+               case OTG_STATE_B_HOST:
+                       musb_root_disconnect(pThis);
+                       /* FALLTHROUGH */
+               case OTG_STATE_B_WAIT_ACON:
+#endif /* OTG */
+#ifdef CONFIG_USB_GADGET_MUSB_HDRC
+               case OTG_STATE_B_PERIPHERAL:
                        musb_g_disconnect(pThis);
+                       break;
+#endif /* GADGET */
+               default:
+                       WARN("unhandled DISCONNECT transition (%s)\n",
+                               otg_state_string(pThis));
+                       break;
+               }
 
                schedule_work(&pThis->irq_work);
        }
@@ -1299,8 +1361,6 @@ irqreturn_t musb_interrupt(struct musb *musb)
        if (musb->int_usb & STAGE0_MASK)
                retval |= musb_stage0_irq(musb, musb->int_usb,
                                devctl, power);
-       else
-               musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
 
        /* "stage 1" is handling endpoint irqs */
 
@@ -1450,7 +1510,7 @@ musb_cable_show(struct device *dev, struct device_attribute *attr, char *buf)
        int vbus;
 
        spin_lock_irqsave(&musb->Lock, flags);
-#ifdef CONFIG_USB_TUSB6010
+#if defined(CONFIG_USB_TUSB6010) && !defined(CONFIG_USB_MUSB_OTG)
        /* REVISIT: connect-A != connect-B ... */
        vbus = musb_platform_get_vbus_status(musb);
        if (vbus)
index 0e3b444edd679edd850daf53c6adf4d43f534a2d..bae3100c66dc45b62e955018b63e7857b3787371 100644 (file)
@@ -336,7 +336,10 @@ static void tusb_set_vbus(struct musb *musb, int is_on)
        u32             conf, prcm, timer;
        u8              devctl;
 
-       /* we control CPEN in software not hardware ... */
+       /* HDRC controls CPEN, but beware current surges during device
+        * connect.  They can trigger transient overcurrent conditions
+        * that must be ignored.
+        */
 
        prcm = musb_readl(base, TUSB_PRCM_MNGMT);
        conf = musb_readl(base, TUSB_DEV_CONF);
@@ -344,25 +347,28 @@ static void tusb_set_vbus(struct musb *musb, int is_on)
 
        if (is_on) {
                musb->is_active = 1;
-               prcm |= TUSB_PRCM_MNGMT_5V_CPEN;
                timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_VRISE);
 
                musb->xceiv.default_a = 1;
                musb->xceiv.state = OTG_STATE_A_WAIT_VRISE;
+               devctl |= MGC_M_DEVCTL_SESSION;
 
                conf |= TUSB_DEV_CONF_USB_HOST_MODE;
-
+               MUSB_HST_MODE(musb);
        } else {
-               prcm &= ~TUSB_PRCM_MNGMT_5V_CPEN;
+               musb->is_active = 0;
                timer = 0;
 
-               if (musb->xceiv.default_a) {
-                       musb->xceiv.state = OTG_STATE_A_WAIT_VFALL;
-                       devctl &= ~MGC_M_DEVCTL_SESSION;
-               } else {
-                       musb->xceiv.state = OTG_STATE_B_IDLE;
-                       musb->is_active = 0;
-               }
+               /* NOTE:  we're skipping A_WAIT_VFALL -> A_IDLE and
+                * jumping right to B_IDLE...
+                */
+
+               musb->xceiv.default_a = 0;
+               musb->xceiv.state = OTG_STATE_B_IDLE;
+               devctl &= ~MGC_M_DEVCTL_SESSION;
+
+               conf &= ~TUSB_DEV_CONF_USB_HOST_MODE;
+               MUSB_DEV_MODE(musb);
        }
        prcm &= ~(TUSB_PRCM_MNGMT_15_SW_EN | TUSB_PRCM_MNGMT_33_SW_EN);
 
@@ -402,6 +408,16 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
                /* B-dev state machine:  no vbus ~= disconnect */
                if ((is_otg_enabled(musb) && !musb->xceiv.default_a)
                                || !is_host_enabled(musb)) {
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+                       // ? musb_root_disconnect(musb);
+                       musb->port1_status &=
+                               ~(USB_PORT_STAT_CONNECTION
+                               | USB_PORT_STAT_ENABLE
+                               | USB_PORT_STAT_LOW_SPEED
+                               | USB_PORT_STAT_HIGH_SPEED
+                               | USB_PORT_STAT_TEST
+                               );
+#endif
 
                        if (otg_stat & TUSB_DEV_OTG_STAT_SESS_END) {
                                if (musb->xceiv.state != OTG_STATE_B_IDLE) {
@@ -426,9 +442,8 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
                                 */
                                break;
                        case OTG_STATE_A_WAIT_VFALL:
-                               /* REVISIT this irq triggers at too high a
-                                * voltage ... we probably need to use the
-                                * OTG timer to wait for session end.
+                               /* REVISIT this irq triggers during short
+                                * spikes causet by enumeration ...
                                 */
                                if (musb->vbuserr_retry) {
                                        musb->vbuserr_retry--;
@@ -449,30 +464,32 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
 
                switch (musb->xceiv.state) {
                case OTG_STATE_A_WAIT_VRISE:
-                       /* VBUS has probably been valid for a while now */
+                       /* VBUS has probably been valid for a while now,
+                        * but may well have bounced out of range a bit
+                        */
                        devctl = musb_readb(musb->pRegs, MGC_O_HDRC_DEVCTL);
                        if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) {
+                               u32     timer;
+
                                if ((devctl & MGC_M_DEVCTL_VBUS)
                                                != MGC_M_DEVCTL_VBUS) {
                                        DBG(2, "devctl %02x\n", devctl);
                                        break;
                                }
+                               musb->xceiv.state = OTG_STATE_A_WAIT_BCON;
 
-                               /* request a session, then DEVCTL_HM will
-                                * be set by the controller
+                               /* REVISIT: if nothing is connected yet,
+                                * mark controller as inactive so that
+                                * we can suspend the TUSB chip.
                                 */
-                               devctl |= MGC_M_DEVCTL_SESSION;
-                               musb_writeb(musb->pRegs, MGC_O_HDRC_DEVCTL,
-                                               devctl);
-                               musb->xceiv.state = OTG_STATE_A_WAIT_BCON;
 
                                /* timeout 0 == infinite (like non-OTG hosts) */
-                               if (OTG_TIME_A_WAIT_BCON)
+                               timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_BCON);
+                               if (timer)
                                        musb_writel(base, TUSB_DEV_OTG_TIMER,
-                                               OTG_TIMER_MS(OTG_TIME_A_WAIT_BCON));
-                               else
-                                       musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
+                                                       timer);
                        } else {
+                               /* REVISIT report overcurrent to hub? */
                                ERR("vbus too slow, devctl %02x\n", devctl);
                                tusb_set_vbus(musb, 0);
                        }
@@ -488,7 +505,6 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
                default:
                        break;
                }
-               musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
        }
 }
 
@@ -747,8 +763,7 @@ static int __devinit tusb_start(struct musb *musb)
        musb_writel(base, TUSB_VLYNQ_CTRL, 8);
 
        /* Select PHY free running 60MHz as a system clock */
-       musb_writel(base, TUSB_PRCM_CONF, //FIXME: CPEN should not be needed!
-                       TUSB_PRCM_CONF_SFW_CPEN | TUSB_PRCM_CONF_SYS_CLKSEL(1));
+       tusb_set_clock_source(musb, 1);
 
        /* VBus valid timer 1us, disable DFT/Debug and VLYNQ clocks for
         * power saving, enable VBus detect and session end comparators,
index 618251f4c17412305d946063467b530b0168c359..41ae741430c652215415991a1ba192e7dca1cc18 100644 (file)
@@ -144,6 +144,7 @@ static void musb_port_reset(struct musb *musb, u8 bReset)
                                        | (USB_PORT_STAT_C_ENABLE << 16);
                usb_hcd_poll_rh_status(musb_to_hcd(musb));
 
+               musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
        }
 }