84e250ffa7
Let the otg_transceiver in MUSB be managed by an external driver; don't assume it's integrated. OMAP3 chips need it to be external, and there may be ways to interact with the transceiver which add functionality to the system. Platform init code is responsible for setting up the transeciver, probably using the NOP transceiver for integrated transceivers. External ones will use whatever the board init code provided, such as twl4030 or something more hands-off. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
319 lines
7.7 KiB
C
319 lines
7.7 KiB
C
/*
|
|
* Copyright (C) 2005-2007 by Texas Instruments
|
|
* Some code has been taken from tusb6010.c
|
|
* Copyrights for that are attributable to:
|
|
* Copyright (C) 2006 Nokia Corporation
|
|
* Tony Lindgren <tony@atomide.com>
|
|
*
|
|
* This file is part of the Inventra Controller Driver for Linux.
|
|
*
|
|
* The Inventra Controller Driver for Linux is free software; you
|
|
* can redistribute it and/or modify it under the terms of the GNU
|
|
* General Public License version 2 as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* The Inventra Controller Driver for Linux is distributed in
|
|
* the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
* License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with The Inventra Controller Driver for Linux ; if not,
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place,
|
|
* Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
|
|
#include <asm/mach-types.h>
|
|
#include <mach/hardware.h>
|
|
#include <mach/mux.h>
|
|
|
|
#include "musb_core.h"
|
|
#include "omap2430.h"
|
|
|
|
#ifdef CONFIG_ARCH_OMAP3430
|
|
#define get_cpu_rev() 2
|
|
#endif
|
|
|
|
#define MUSB_TIMEOUT_A_WAIT_BCON 1100
|
|
|
|
static struct timer_list musb_idle_timer;
|
|
|
|
static void musb_do_idle(unsigned long _musb)
|
|
{
|
|
struct musb *musb = (void *)_musb;
|
|
unsigned long flags;
|
|
#ifdef CONFIG_USB_MUSB_HDRC_HCD
|
|
u8 power;
|
|
#endif
|
|
u8 devctl;
|
|
|
|
spin_lock_irqsave(&musb->lock, flags);
|
|
|
|
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
|
|
|
switch (musb->xceiv->state) {
|
|
case OTG_STATE_A_WAIT_BCON:
|
|
devctl &= ~MUSB_DEVCTL_SESSION;
|
|
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
|
|
|
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
|
if (devctl & MUSB_DEVCTL_BDEVICE) {
|
|
musb->xceiv->state = OTG_STATE_B_IDLE;
|
|
MUSB_DEV_MODE(musb);
|
|
} else {
|
|
musb->xceiv->state = OTG_STATE_A_IDLE;
|
|
MUSB_HST_MODE(musb);
|
|
}
|
|
break;
|
|
#ifdef CONFIG_USB_MUSB_HDRC_HCD
|
|
case OTG_STATE_A_SUSPEND:
|
|
/* finish RESUME signaling? */
|
|
if (musb->port1_status & MUSB_PORT_STAT_RESUME) {
|
|
power = musb_readb(musb->mregs, MUSB_POWER);
|
|
power &= ~MUSB_POWER_RESUME;
|
|
DBG(1, "root port resume stopped, power %02x\n", power);
|
|
musb_writeb(musb->mregs, MUSB_POWER, power);
|
|
musb->is_active = 1;
|
|
musb->port1_status &= ~(USB_PORT_STAT_SUSPEND
|
|
| MUSB_PORT_STAT_RESUME);
|
|
musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
|
|
usb_hcd_poll_rh_status(musb_to_hcd(musb));
|
|
/* NOTE: it might really be A_WAIT_BCON ... */
|
|
musb->xceiv->state = OTG_STATE_A_HOST;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_USB_MUSB_HDRC_HCD
|
|
case OTG_STATE_A_HOST:
|
|
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
|
if (devctl & MUSB_DEVCTL_BDEVICE)
|
|
musb->xceiv->state = OTG_STATE_B_IDLE;
|
|
else
|
|
musb->xceiv->state = OTG_STATE_A_WAIT_BCON;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&musb->lock, flags);
|
|
}
|
|
|
|
|
|
void musb_platform_try_idle(struct musb *musb, unsigned long timeout)
|
|
{
|
|
unsigned long default_timeout = jiffies + msecs_to_jiffies(3);
|
|
static unsigned long last_timer;
|
|
|
|
if (timeout == 0)
|
|
timeout = default_timeout;
|
|
|
|
/* Never idle if active, or when VBUS timeout is not set as host */
|
|
if (musb->is_active || ((musb->a_wait_bcon == 0)
|
|
&& (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) {
|
|
DBG(4, "%s active, deleting timer\n", otg_state_string(musb));
|
|
del_timer(&musb_idle_timer);
|
|
last_timer = jiffies;
|
|
return;
|
|
}
|
|
|
|
if (time_after(last_timer, timeout)) {
|
|
if (!timer_pending(&musb_idle_timer))
|
|
last_timer = timeout;
|
|
else {
|
|
DBG(4, "Longer idle timer already pending, ignoring\n");
|
|
return;
|
|
}
|
|
}
|
|
last_timer = timeout;
|
|
|
|
DBG(4, "%s inactive, for idle timer for %lu ms\n",
|
|
otg_state_string(musb),
|
|
(unsigned long)jiffies_to_msecs(timeout - jiffies));
|
|
mod_timer(&musb_idle_timer, timeout);
|
|
}
|
|
|
|
void musb_platform_enable(struct musb *musb)
|
|
{
|
|
}
|
|
void musb_platform_disable(struct musb *musb)
|
|
{
|
|
}
|
|
static void omap_vbus_power(struct musb *musb, int is_on, int sleeping)
|
|
{
|
|
}
|
|
|
|
static void omap_set_vbus(struct musb *musb, int is_on)
|
|
{
|
|
u8 devctl;
|
|
/* HDRC controls CPEN, but beware current surges during device
|
|
* connect. They can trigger transient overcurrent conditions
|
|
* that must be ignored.
|
|
*/
|
|
|
|
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
|
|
|
if (is_on) {
|
|
musb->is_active = 1;
|
|
musb->xceiv->default_a = 1;
|
|
musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
|
|
devctl |= MUSB_DEVCTL_SESSION;
|
|
|
|
MUSB_HST_MODE(musb);
|
|
} else {
|
|
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 &= ~MUSB_DEVCTL_SESSION;
|
|
|
|
MUSB_DEV_MODE(musb);
|
|
}
|
|
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
|
|
|
DBG(1, "VBUS %s, devctl %02x "
|
|
/* otg %3x conf %08x prcm %08x */ "\n",
|
|
otg_state_string(musb),
|
|
musb_readb(musb->mregs, MUSB_DEVCTL));
|
|
}
|
|
|
|
static int musb_platform_resume(struct musb *musb);
|
|
|
|
int musb_platform_set_mode(struct musb *musb, u8 musb_mode)
|
|
{
|
|
u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
|
|
|
|
devctl |= MUSB_DEVCTL_SESSION;
|
|
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int __init musb_platform_init(struct musb *musb)
|
|
{
|
|
u32 l;
|
|
|
|
#if defined(CONFIG_ARCH_OMAP2430)
|
|
omap_cfg_reg(AE5_2430_USB0HS_STP);
|
|
#endif
|
|
|
|
/* We require some kind of external transceiver, hooked
|
|
* up through ULPI. TWL4030-family PMICs include one,
|
|
* which needs a driver, drivers aren't always needed.
|
|
*/
|
|
musb->xceiv = otg_get_transceiver();
|
|
if (!musb->xceiv) {
|
|
pr_err("HS USB OTG: no transceiver configured\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
musb_platform_resume(musb);
|
|
|
|
l = omap_readl(OTG_SYSCONFIG);
|
|
l &= ~ENABLEWAKEUP; /* disable wakeup */
|
|
l &= ~NOSTDBY; /* remove possible nostdby */
|
|
l |= SMARTSTDBY; /* enable smart standby */
|
|
l &= ~AUTOIDLE; /* disable auto idle */
|
|
l &= ~NOIDLE; /* remove possible noidle */
|
|
l |= SMARTIDLE; /* enable smart idle */
|
|
l |= AUTOIDLE; /* enable auto idle */
|
|
omap_writel(l, OTG_SYSCONFIG);
|
|
|
|
l = omap_readl(OTG_INTERFSEL);
|
|
l |= ULPI_12PIN;
|
|
omap_writel(l, OTG_INTERFSEL);
|
|
|
|
pr_debug("HS USB OTG: revision 0x%x, sysconfig 0x%02x, "
|
|
"sysstatus 0x%x, intrfsel 0x%x, simenable 0x%x\n",
|
|
omap_readl(OTG_REVISION), omap_readl(OTG_SYSCONFIG),
|
|
omap_readl(OTG_SYSSTATUS), omap_readl(OTG_INTERFSEL),
|
|
omap_readl(OTG_SIMENABLE));
|
|
|
|
omap_vbus_power(musb, musb->board_mode == MUSB_HOST, 1);
|
|
|
|
if (is_host_enabled(musb))
|
|
musb->board_set_vbus = omap_set_vbus;
|
|
musb->a_wait_bcon = MUSB_TIMEOUT_A_WAIT_BCON;
|
|
|
|
setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int musb_platform_suspend(struct musb *musb)
|
|
{
|
|
u32 l;
|
|
|
|
if (!musb->clock)
|
|
return 0;
|
|
|
|
/* in any role */
|
|
l = omap_readl(OTG_FORCESTDBY);
|
|
l |= ENABLEFORCE; /* enable MSTANDBY */
|
|
omap_writel(l, OTG_FORCESTDBY);
|
|
|
|
l = omap_readl(OTG_SYSCONFIG);
|
|
l |= ENABLEWAKEUP; /* enable wakeup */
|
|
omap_writel(l, OTG_SYSCONFIG);
|
|
|
|
otg_set_suspend(musb->xceiv, 1);
|
|
|
|
if (musb->set_clock)
|
|
musb->set_clock(musb->clock, 0);
|
|
else
|
|
clk_disable(musb->clock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int musb_platform_resume(struct musb *musb)
|
|
{
|
|
u32 l;
|
|
|
|
if (!musb->clock)
|
|
return 0;
|
|
|
|
otg_set_suspend(musb->xceiv, 0);
|
|
|
|
if (musb->set_clock)
|
|
musb->set_clock(musb->clock, 1);
|
|
else
|
|
clk_enable(musb->clock);
|
|
|
|
l = omap_readl(OTG_SYSCONFIG);
|
|
l &= ~ENABLEWAKEUP; /* disable wakeup */
|
|
omap_writel(l, OTG_SYSCONFIG);
|
|
|
|
l = omap_readl(OTG_FORCESTDBY);
|
|
l &= ~ENABLEFORCE; /* disable MSTANDBY */
|
|
omap_writel(l, OTG_FORCESTDBY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int musb_platform_exit(struct musb *musb)
|
|
{
|
|
|
|
omap_vbus_power(musb, 0 /*off*/, 1);
|
|
|
|
musb_platform_suspend(musb);
|
|
|
|
clk_put(musb->clock);
|
|
musb->clock = 0;
|
|
|
|
return 0;
|
|
}
|