Commit 89c57744 authored by David Brownell's avatar David Brownell Committed by Tony Lindgren

MUSB: TUSB OTG enumeration support

This gives TUSB host side enumeration support for low speed devices (like
USB mice), through a Mini-A connector that's not removed ... basically, a
non-OTG configuration, with the ID pin always grounded.

Basically it punts the "turn VBUS power on/off" to board-specific logic,
and implements it for TUSB using software switching (rather than having
the controller do it).

There are various issues, notably

    (a)	remote wakeup not getting passed to the root hub then down
	to the device (mouse) that issued the wakeup;

    (b)	strange "vbus error" reports on device connection if nothing
    	is hooked up at driver initialization; and

    (c) full or high speed devices see spurious disconnect events
	right after they've been reset (hw bug?);

Includes various small cleanups too, notably starting to obey the OTG
state machine and use the OTG timer.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
parent 35ccd236
......@@ -229,6 +229,11 @@ static void davinci_vbus_power(struct musb *musb, int is_on, int sleeping)
sleeping ? "immediate" : "deferred");
}
static void davinci_set_vbus(struct musb *musb, int is_on)
{
return davinci_vbus_power(musb, is_on, 0);
}
static irqreturn_t davinci_interrupt(int irq, void *__hci, struct pt_regs *r)
{
unsigned long flags;
......@@ -334,6 +339,9 @@ int __devinit musb_platform_init(struct musb *musb)
#endif
davinci_vbus_power(musb, musb->board_mode == MUSB_HOST, 1);
if (is_host_enabled(musb))
musb->board_set_vbus = davinci_set_vbus;
/* reset the controller */
musb_writel(tibase, DAVINCI_USB_CTRL_REG, 0x1);
......
......@@ -60,4 +60,6 @@ static inline int _dbg_level(unsigned l)
#define DBG(level,fmt,args...) xprintk(level,KERN_DEBUG,fmt, ## args)
extern const char *otg_state_string(struct musb *);
#endif // __MUSB_LINUX_DEBUG_H__
......@@ -48,9 +48,9 @@
#include "davinci.h"
static const char *state_string(enum usb_otg_state state)
const char *otg_state_string(struct musb *musb)
{
switch (state) {
switch (musb->xceiv.state) {
case OTG_STATE_A_IDLE: return "a_idle";
case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise";
case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon";
......@@ -485,8 +485,9 @@ static int dump_header_stats(struct musb *pThis, char *buffer)
return count;
buffer += count;
code = sprintf(buffer, "OTG state: %s\n",
state_string(pThis->xceiv.state));
code = sprintf(buffer, "OTG state: %s; %sactive\n",
otg_state_string(pThis),
pThis->is_active ? "" : "in");
if (code < 0)
return code;
buffer += code;
......@@ -544,7 +545,7 @@ static int dump_header_stats(struct musb *pThis, char *buffer)
#ifdef CONFIG_USB_TUSB6010
code = sprintf(buffer,
"TUSB6010: devconf %08x, phy enable %08x drive %08x"
"\n\totg %08x timer %08x"
"\n\totg %03x timer %08x"
"\n\tprcm conf %08x mgmt %08x; intmask %08x"
"\n",
musb_readl(pThis->ctrl_base, TUSB_DEV_CONF),
......
......@@ -399,9 +399,13 @@ struct musb {
struct list_head in_bulk; /* of musb_qh */
struct list_head out_bulk; /* of musb_qh */
struct musb_qh *periodic[32]; /* tree of interrupt+iso */
#endif
/* called with IRQs blocked; ON/nonzero implies starting a session,
* and waiting at least a_wait_vrise_tmout.
*/
void (*board_set_vbus)(struct musb *, int is_on);
struct dma_controller *pDmaController;
struct device *controller;
......@@ -489,6 +493,11 @@ struct musb {
#endif
};
static inline void musb_set_vbus(struct musb *musb, int is_on)
{
musb->board_set_vbus(musb, is_on);
}
#ifdef CONFIG_USB_GADGET_MUSB_HDRC
static inline struct musb *gadget_to_musb(struct usb_gadget *g)
{
......
......@@ -416,6 +416,7 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
pThis->bEnd0Stage = MGC_END0_START;
pThis->xceiv.state = OTG_STATE_A_IDLE;
MUSB_HST_MODE(pThis);
musb_set_vbus(pThis, 1);
handled = IRQ_HANDLED;
......@@ -454,12 +455,9 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
}; s; }),
pThis->vbuserr_retry);
/* after hw goes to A_IDLE, try connecting again */
pThis->xceiv.state = OTG_STATE_A_IDLE;
if (pThis->vbuserr_retry--)
musb_writeb(pBase, MGC_O_HDRC_DEVCTL,
MGC_M_DEVCTL_SESSION);
return IRQ_HANDLED;
/* go through A_WAIT_VFALL then start a new session */
musb_set_vbus(pThis, 0);
handled = IRQ_HANDLED;
} else
pThis->vbuserr_retry = VBUSERR_RETRY_COUNT;
......@@ -498,14 +496,10 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
pThis->xceiv.state = OTG_STATE_B_HOST;
break;
default:
DBG(2, "connect in state %d\n", pThis->xceiv.state);
/* FALLTHROUGH */
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_WAIT_VRISE:
pThis->xceiv.state = OTG_STATE_A_HOST;
break;
}
DBG(1, "CONNECT (host state %d)\n", pThis->xceiv.state);
DBG(1, "CONNECT (%s)\n", otg_state_string(pThis));
otg_input_changed(pThis, devctl, FALSE, TRUE, FALSE);
}
#endif /* CONFIG_USB_MUSB_HDRC_HCD */
......@@ -678,8 +672,10 @@ void musb_start(struct musb * pThis)
switch (pThis->board_mode) {
case MUSB_HOST:
musb_set_vbus(pThis, 1);
break;
case MUSB_OTG:
musb_writeb(pBase, MGC_O_HDRC_DEVCTL, MGC_M_DEVCTL_SESSION);
WARN("how to start OTG session?\n");
break;
case MUSB_PERIPHERAL:
state = musb_readb(pBase, MGC_O_HDRC_DEVCTL);
......
......@@ -179,7 +179,7 @@ static int tusb_set_power(struct otg_transceiver *x, unsigned mA)
* (to be fixed in rev3 silicon) ... symptoms include disconnect
* or looping suspend/resume cycles
*/
void tusb_set_clock_source(struct musb *musb, int mode)
static void tusb_set_clock_source(struct musb *musb, int mode)
{
void __iomem *base = musb->ctrl_base;
u32 reg;
......@@ -209,8 +209,17 @@ static void tusb_allow_idle(struct musb *musb, u32 wakeup_enables)
wakeup_enables |= TUSB_PRCM_WNORCS;
musb_writel(base, TUSB_PRCM_WAKEUP_MASK, ~wakeup_enables);
// FIXME issue 4, when host (driving vbus), enable hipower comparator
/* REVISIT writeup of WLD implies that if WLD set and ID is grounded,
* TUSB_PHY_OTG_CTRL.TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP must be cleared.
* Presumably that's mostly to save power, hence WLD is immaterial ...
*/
reg = musb_readl(base, TUSB_PRCM_MNGMT);
reg &= ~TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN; /* REVISIT leave alone? */
/* issue 4: when driving vbus, leave hipower comparator active */
if (!is_host_active(musb))
reg &= ~TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN;
reg |= TUSB_PRCM_MNGMT_OTG_SESS_END_EN
| TUSB_PRCM_MNGMT_PM_IDLE
| TUSB_PRCM_MNGMT_DEV_IDLE;
......@@ -296,8 +305,60 @@ void musb_platform_try_idle(struct musb *musb)
if (musb->is_active)
del_timer(&musb_idle_timer);
else
mod_timer(&musb_idle_timer, jiffies +
(is_host_active(musb) ? msecs_to_jiffies(3) : 0));
mod_timer(&musb_idle_timer, jiffies + msecs_to_jiffies(3));
}
/* ticks of 60 MHz clock */
#define DEVCLOCK 60000000
#define OTG_TIMER_MS(msecs) ((msecs) \
? (TUSB_DEV_OTG_TIMER_VAL((DEVCLOCK/1000)*(msecs)) \
| TUSB_DEV_OTG_TIMER_ENABLE) \
: 0)
static void tusb_set_vbus(struct musb *musb, int is_on)
{
void __iomem *base = musb->ctrl_base;
u32 conf, prcm, timer;
u8 devctl;
/* we control CPEN in software not hardware ... */
prcm = musb_readl(base, TUSB_PRCM_MNGMT);
conf = musb_readl(base, TUSB_DEV_CONF);
devctl = musb_readb(musb->pRegs, MGC_O_HDRC_DEVCTL);
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;
conf |= TUSB_DEV_CONF_USB_HOST_MODE;
} else {
prcm &= ~TUSB_PRCM_MNGMT_5V_CPEN;
timer = 0;
if (musb->xceiv.default_a) {
musb->xceiv.state = OTG_STATE_A_WAIT_VFALL;
devctl &= ~MGC_M_DEVCTL_SESSION;
} else
musb->is_active = 0;
}
prcm &= ~(TUSB_PRCM_MNGMT_15_SW_EN | TUSB_PRCM_MNGMT_33_SW_EN);
musb_writel(base, TUSB_PRCM_MNGMT, prcm);
musb_writel(base, TUSB_DEV_OTG_TIMER, timer);
musb_writel(base, TUSB_DEV_CONF, conf);
musb_writeb(musb->pRegs, MGC_O_HDRC_DEVCTL, devctl);
DBG(1, "VBUS %s, devctl %02x otg %3x conf %08x prcm %08x\n",
is_on ? "on" : "off",
musb_readb(musb->pRegs, MGC_O_HDRC_DEVCTL),
musb_readl(base, TUSB_DEV_OTG_STAT),
conf, prcm);
}
static inline void
......@@ -309,25 +370,22 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
if ((int_src & TUSB_INT_SRC_ID_STATUS_CHNG)) {
int default_a;
default_a = is_host_enabled(musb)
&& (otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS);
if (is_otg_enabled(musb))
default_a = !!(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS);
else
default_a = is_host_enabled(musb);
if (default_a != musb->xceiv.default_a) {
musb->xceiv.default_a = default_a;
if (musb->xceiv.default_a) {
musb->xceiv.state = OTG_STATE_A_IDLE;
/* REVISIT start the session? */
} else
musb->xceiv.state = OTG_STATE_B_IDLE;
DBG(1, "Default-%c\n", musb->xceiv.default_a
? 'A' : 'B');
musb->is_active = 1;
tusb_set_vbus(musb, default_a);
}
}
/* VBUS state change */
if (int_src & TUSB_INT_SRC_VBUS_SENSE_CHNG) {
/* no vbus ~= disconnect */
if (!is_host_enabled(musb) || !musb->xceiv.default_a) {
/* B-dev state machine: no vbus ~= disconnect */
if ((is_otg_enabled(musb) && !musb->xceiv.default_a)
|| !is_host_enabled(musb)) {
/* REVISIT use the b_sess_valid comparator, not
* lowpower one; TUSB_DEV_OTG_STAT_SESS_VALID ?
......@@ -345,15 +403,82 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
? "b_peripheral" : "b_idle");
schedule_work(&musb->irq_work);
} else /* A-dev state machine */ {
DBG(4, "vbus change, %s, otg %03x\n",
otg_state_string(musb), otg_stat);
switch (musb->xceiv.state) {
case OTG_STATE_A_WAIT_VRISE:
/* ignore; A-session-valid < VBUS_VALID/2,
* we monitor this with the timer
*/
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.
*/
if (musb->vbuserr_retry) {
musb->vbuserr_retry--;
tusb_set_vbus(musb, 1);
}
break;
default:
break;
}
}
}
/* OTG timer expiration */
if (int_src & TUSB_INT_SRC_OTG_TIMEOUT) {
DBG(3, "tusb: OTG timer expired\n");
musb_writel(base, TUSB_DEV_OTG_TIMER,
musb_readl(base, TUSB_DEV_OTG_TIMER)
| TUSB_DEV_OTG_TIMER_ENABLE);
DBG(4, "%s timer, %03x\n", otg_state_string(musb), otg_stat);
switch (musb->xceiv.state) {
case OTG_STATE_A_WAIT_VRISE:
/* VBUS has probably been valid for a while now */
if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) {
u8 devctl;
devctl = musb_readb(musb->pRegs,
MGC_O_HDRC_DEVCTL);
if ((devctl & MGC_M_DEVCTL_VBUS)
!= MGC_M_DEVCTL_VBUS) {
DBG(2, "devctl %02x\n", devctl);
break;
}
/* request a session, then DEVCTL_HM will
* be set by the controller
*/
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)
musb_writel(base, TUSB_DEV_OTG_TIMER,
OTG_TIMER_MS(OTG_TIME_A_WAIT_BCON));
else
musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
} else {
ERR("vbus rise time too slow\n");
tusb_set_vbus(musb, 0);
}
break;
case OTG_STATE_A_WAIT_BCON:
if (OTG_TIME_A_WAIT_BCON)
tusb_set_vbus(musb, 0);
break;
case OTG_STATE_A_SUSPEND:
break;
case OTG_STATE_B_WAIT_ACON:
break;
default:
break;
}
musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
}
}
......@@ -402,6 +527,8 @@ static irqreturn_t tusb_interrupt(int irq, void *__hci, struct pt_regs *r)
}
DBG(3, "wake %sactive %02x\n",
musb->is_active ? "" : "in", reg);
// REVISIT host side TUSB_PRCM_WHOSTDISCON, TUSB_PRCM_WBUS
}
/* OTG state change reports (annoyingly) not issued by Mentor core */
......@@ -485,13 +612,6 @@ void musb_platform_enable(struct musb * musb)
/* Acknowledge pending interrupt(s) */
musb_writel(base, TUSB_INT_SRC_CLEAR, ~TUSB_INT_MASK_RESERVED_BITS);
#if 0
/* Set OTG timer for about one second */
musb_writel(base, TUSB_DEV_OTG_TIMER,
TUSB_DEV_OTG_TIMER_ENABLE |
TUSB_DEV_OTG_TIMER_VAL(0x3c00000));
#endif
/* Only 0 clock cycles for minimum interrupt de-assertion time and
* interrupt polarity active low seems to work reliably here */
musb_writel(base, TUSB_INT_CTRL_CONF,
......@@ -511,7 +631,9 @@ void musb_platform_enable(struct musb * musb)
*/
void musb_platform_disable(struct musb *musb)
{
if (is_dma_capable()) {
/* FIXME stop DMA, IRQs, timers, ... */
if (is_dma_capable() && !dma_off) {
printk(KERN_WARNING "%s %s: dma still active\n",
__FILE__, __FUNCTION__);
dma_off = 1;
......@@ -574,6 +696,7 @@ static int tusb_start(struct musb *musb)
void __iomem *base = musb->ctrl_base;
int ret = -1;
unsigned long flags;
u32 reg;
if (musb->board_set_power)
ret = musb->board_set_power(1);
......@@ -616,6 +739,15 @@ static int tusb_start(struct musb *musb)
TUSB_PRCM_MNGMT_OTG_ID_PULLUP);
tusb_setup_cpu_interface(musb);
/* simplify: always sense/pullup ID pins, as if in OTG mode */
reg = musb_readl(base, TUSB_PHY_OTG_CTRL);
reg |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP;
musb_writel(base, TUSB_PHY_OTG_CTRL, reg);
reg = musb_readl(base, TUSB_PHY_OTG_CTRL_ENABLE);
reg |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP;
musb_writel(base, TUSB_PHY_OTG_CTRL_ENABLE, reg);
spin_unlock_irqrestore(&musb->Lock, flags);
return 0;
......@@ -659,7 +791,11 @@ int __devinit musb_platform_init(struct musb *musb)
return -ENODEV;
}
musb->isr = tusb_interrupt;
musb->xceiv.set_power = tusb_set_power;
if (is_host_enabled(musb))
musb->board_set_vbus = tusb_set_vbus;
if (is_peripheral_enabled(musb))
musb->xceiv.set_power = tusb_set_power;
setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb);
......
......@@ -212,6 +212,9 @@ int musb_hub_control(
musb_port_suspend(musb, FALSE);
break;
case USB_PORT_FEAT_POWER:
if (!(is_otg_enabled(musb) && hcd->self.is_b_host))
musb_set_vbus(musb, 0);
break;
case USB_PORT_FEAT_C_CONNECTION:
case USB_PORT_FEAT_C_ENABLE:
case USB_PORT_FEAT_C_OVER_CURRENT:
......
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