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 { ...@@ -415,6 +415,7 @@ struct musb {
struct musb_hw_ep aLocalEnd[MUSB_C_NUM_EPS]; struct musb_hw_ep aLocalEnd[MUSB_C_NUM_EPS];
#define control_ep aLocalEnd #define control_ep aLocalEnd
#define VBUSERR_RETRY_COUNT 3
u16 vbuserr_retry; u16 vbuserr_retry;
u16 wEndMask; u16 wEndMask;
u8 bEndCount; u8 bEndCount;
......
...@@ -108,9 +108,6 @@ ...@@ -108,9 +108,6 @@
#endif #endif
#include "musbdefs.h" #include "musbdefs.h"
// #ifdef CONFIG_USB_MUSB_HDRC_HCD
#define VBUSERR_RETRY_COUNT 2 /* is this too few? */
// #endif
#ifdef CONFIG_ARCH_DAVINCI #ifdef CONFIG_ARCH_DAVINCI
...@@ -411,8 +408,52 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB, ...@@ -411,8 +408,52 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
} }
if (bIntrUSB & MGC_M_INTR_VBUSERROR) { if (bIntrUSB & MGC_M_INTR_VBUSERROR) {
int ignore = 0;
DBG(1, "VBUS_ERROR (%02x, %s), retry #%d\n", devctl, /* 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 in %s (%02x, %s), retry #%d, port1 %08x\n",
otg_state_string(pThis),
devctl,
({ char *s; ({ char *s;
switch (devctl & MGC_M_DEVCTL_VBUS) { switch (devctl & MGC_M_DEVCTL_VBUS) {
case 0 << MGC_S_DEVCTL_VBUS: case 0 << MGC_S_DEVCTL_VBUS:
...@@ -425,13 +466,14 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB, ...@@ -425,13 +466,14 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
default: default:
s = "VALID"; break; s = "VALID"; break;
}; s; }), }; s; }),
pThis->vbuserr_retry); VBUSERR_RETRY_COUNT - pThis->vbuserr_retry,
pThis->port1_status);
/* go through A_WAIT_VFALL then start a new session */ /* go through A_WAIT_VFALL then start a new session */
if (!ignore)
musb_set_vbus(pThis, 0); musb_set_vbus(pThis, 0);
handled = IRQ_HANDLED; handled = IRQ_HANDLED;
} else }
pThis->vbuserr_retry = VBUSERR_RETRY_COUNT;
if (bIntrUSB & MGC_M_INTR_CONNECT) { if (bIntrUSB & MGC_M_INTR_CONNECT) {
handled = IRQ_HANDLED; handled = IRQ_HANDLED;
...@@ -468,10 +510,13 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB, ...@@ -468,10 +510,13 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
pThis->xceiv.state = OTG_STATE_B_HOST; pThis->xceiv.state = OTG_STATE_B_HOST;
break; break;
default: default:
if ((devctl & MGC_M_DEVCTL_VBUS)
== (3 << MGC_S_DEVCTL_VBUS))
pThis->xceiv.state = OTG_STATE_A_HOST; pThis->xceiv.state = OTG_STATE_A_HOST;
break; 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 */ #endif /* CONFIG_USB_MUSB_HDRC_HCD */
...@@ -566,18 +611,35 @@ static irqreturn_t musb_stage2_irq(struct musb * pThis, u8 bIntrUSB, ...@@ -566,18 +611,35 @@ static irqreturn_t musb_stage2_irq(struct musb * pThis, u8 bIntrUSB,
#endif #endif
if ((bIntrUSB & MGC_M_INTR_DISCONNECT) && !pThis->bIgnoreDisconnect) { 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); MUSB_MODE(pThis), devctl);
handled = IRQ_HANDLED; handled = IRQ_HANDLED;
pThis->is_active = 0;
/* need to check it against pThis, because devctl is going switch (pThis->xceiv.state) {
* to report ID low as soon as the device gets disconnected #ifdef CONFIG_USB_MUSB_HDRC_HCD
*/ case OTG_STATE_A_HOST:
if (is_host_active(pThis)) case OTG_STATE_A_SUSPEND:
musb_root_disconnect(pThis); 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); musb_g_disconnect(pThis);
break;
#endif /* GADGET */
default:
WARN("unhandled DISCONNECT transition (%s)\n",
otg_state_string(pThis));
break;
}
schedule_work(&pThis->irq_work); schedule_work(&pThis->irq_work);
} }
...@@ -1299,8 +1361,6 @@ irqreturn_t musb_interrupt(struct musb *musb) ...@@ -1299,8 +1361,6 @@ irqreturn_t musb_interrupt(struct musb *musb)
if (musb->int_usb & STAGE0_MASK) if (musb->int_usb & STAGE0_MASK)
retval |= musb_stage0_irq(musb, musb->int_usb, retval |= musb_stage0_irq(musb, musb->int_usb,
devctl, power); devctl, power);
else
musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
/* "stage 1" is handling endpoint irqs */ /* "stage 1" is handling endpoint irqs */
...@@ -1450,7 +1510,7 @@ musb_cable_show(struct device *dev, struct device_attribute *attr, char *buf) ...@@ -1450,7 +1510,7 @@ musb_cable_show(struct device *dev, struct device_attribute *attr, char *buf)
int vbus; int vbus;
spin_lock_irqsave(&musb->Lock, flags); 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 ... */ /* REVISIT: connect-A != connect-B ... */
vbus = musb_platform_get_vbus_status(musb); vbus = musb_platform_get_vbus_status(musb);
if (vbus) if (vbus)
......
...@@ -336,7 +336,10 @@ static void tusb_set_vbus(struct musb *musb, int is_on) ...@@ -336,7 +336,10 @@ static void tusb_set_vbus(struct musb *musb, int is_on)
u32 conf, prcm, timer; u32 conf, prcm, timer;
u8 devctl; 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); prcm = musb_readl(base, TUSB_PRCM_MNGMT);
conf = musb_readl(base, TUSB_DEV_CONF); conf = musb_readl(base, TUSB_DEV_CONF);
...@@ -344,25 +347,28 @@ static void tusb_set_vbus(struct musb *musb, int is_on) ...@@ -344,25 +347,28 @@ static void tusb_set_vbus(struct musb *musb, int is_on)
if (is_on) { if (is_on) {
musb->is_active = 1; musb->is_active = 1;
prcm |= TUSB_PRCM_MNGMT_5V_CPEN;
timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_VRISE); timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_VRISE);
musb->xceiv.default_a = 1; musb->xceiv.default_a = 1;
musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; musb->xceiv.state = OTG_STATE_A_WAIT_VRISE;
devctl |= MGC_M_DEVCTL_SESSION;
conf |= TUSB_DEV_CONF_USB_HOST_MODE; conf |= TUSB_DEV_CONF_USB_HOST_MODE;
MUSB_HST_MODE(musb);
} else { } else {
prcm &= ~TUSB_PRCM_MNGMT_5V_CPEN; musb->is_active = 0;
timer = 0; timer = 0;
if (musb->xceiv.default_a) { /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and
musb->xceiv.state = OTG_STATE_A_WAIT_VFALL; * jumping right to B_IDLE...
devctl &= ~MGC_M_DEVCTL_SESSION; */
} else {
musb->xceiv.default_a = 0;
musb->xceiv.state = OTG_STATE_B_IDLE; musb->xceiv.state = OTG_STATE_B_IDLE;
musb->is_active = 0; 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); 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) ...@@ -402,6 +408,16 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
/* B-dev state machine: no vbus ~= disconnect */ /* B-dev state machine: no vbus ~= disconnect */
if ((is_otg_enabled(musb) && !musb->xceiv.default_a) if ((is_otg_enabled(musb) && !musb->xceiv.default_a)
|| !is_host_enabled(musb)) { || !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 (otg_stat & TUSB_DEV_OTG_STAT_SESS_END) {
if (musb->xceiv.state != OTG_STATE_B_IDLE) { if (musb->xceiv.state != OTG_STATE_B_IDLE) {
...@@ -426,9 +442,8 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base) ...@@ -426,9 +442,8 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
*/ */
break; break;
case OTG_STATE_A_WAIT_VFALL: case OTG_STATE_A_WAIT_VFALL:
/* REVISIT this irq triggers at too high a /* REVISIT this irq triggers during short
* voltage ... we probably need to use the * spikes causet by enumeration ...
* OTG timer to wait for session end.
*/ */
if (musb->vbuserr_retry) { if (musb->vbuserr_retry) {
musb->vbuserr_retry--; musb->vbuserr_retry--;
...@@ -449,30 +464,32 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base) ...@@ -449,30 +464,32 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
switch (musb->xceiv.state) { switch (musb->xceiv.state) {
case OTG_STATE_A_WAIT_VRISE: 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); devctl = musb_readb(musb->pRegs, MGC_O_HDRC_DEVCTL);
if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) { if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) {
u32 timer;
if ((devctl & MGC_M_DEVCTL_VBUS) if ((devctl & MGC_M_DEVCTL_VBUS)
!= MGC_M_DEVCTL_VBUS) { != MGC_M_DEVCTL_VBUS) {
DBG(2, "devctl %02x\n", devctl); DBG(2, "devctl %02x\n", devctl);
break; break;
} }
musb->xceiv.state = OTG_STATE_A_WAIT_BCON;
/* request a session, then DEVCTL_HM will /* REVISIT: if nothing is connected yet,
* be set by the controller * 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) */ /* 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, musb_writel(base, TUSB_DEV_OTG_TIMER,
OTG_TIMER_MS(OTG_TIME_A_WAIT_BCON)); timer);
else
musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
} else { } else {
/* REVISIT report overcurrent to hub? */
ERR("vbus too slow, devctl %02x\n", devctl); ERR("vbus too slow, devctl %02x\n", devctl);
tusb_set_vbus(musb, 0); tusb_set_vbus(musb, 0);
} }
...@@ -488,7 +505,6 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base) ...@@ -488,7 +505,6 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
default: default:
break; break;
} }
musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
} }
} }
...@@ -747,8 +763,7 @@ static int __devinit tusb_start(struct musb *musb) ...@@ -747,8 +763,7 @@ static int __devinit tusb_start(struct musb *musb)
musb_writel(base, TUSB_VLYNQ_CTRL, 8); musb_writel(base, TUSB_VLYNQ_CTRL, 8);
/* Select PHY free running 60MHz as a system clock */ /* Select PHY free running 60MHz as a system clock */
musb_writel(base, TUSB_PRCM_CONF, //FIXME: CPEN should not be needed! tusb_set_clock_source(musb, 1);
TUSB_PRCM_CONF_SFW_CPEN | TUSB_PRCM_CONF_SYS_CLKSEL(1));
/* VBus valid timer 1us, disable DFT/Debug and VLYNQ clocks for /* VBus valid timer 1us, disable DFT/Debug and VLYNQ clocks for
* power saving, enable VBus detect and session end comparators, * power saving, enable VBus detect and session end comparators,
......
...@@ -144,6 +144,7 @@ static void musb_port_reset(struct musb *musb, u8 bReset) ...@@ -144,6 +144,7 @@ static void musb_port_reset(struct musb *musb, u8 bReset)
| (USB_PORT_STAT_C_ENABLE << 16); | (USB_PORT_STAT_C_ENABLE << 16);
usb_hcd_poll_rh_status(musb_to_hcd(musb)); 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