Commit 41416eec authored by David Brownell's avatar David Brownell Committed by Tony Lindgren

musb_hdrc: host vbus and connect/disconnect fixes

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: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
parent b659d87e
......@@ -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;
......
......@@ -108,9 +108,6 @@
#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)
......
......@@ -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,
......
......@@ -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;
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment