summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVenkat Moganty <vmoganty@nvidia.com>2010-01-15 19:56:46 +0530
committerVenkat Moganty <vmoganty@nvidia.com>2010-01-19 11:24:37 +0530
commit98968646e2faada52420581bfcf5f80362f94f69 (patch)
treeca199f73fa08e29726fc53b0050fb253d1b8e6c3
parent9d42c3949099b2310182e97b3ea23e641dc82286 (diff)
TEGRA - Adding USB OTG feature
Enabling support for the OTG in NVIDIA Tegra SoCs by providing simple transceiver interface for detecting the Host or Device based on the USBID and VBUS sensors. Bug 629098 USB OTG problem Tested on Whistler with Android USB OTG is tested on whistler USB port1, by setting the USB mode type as "NvOdmUsbModeType_OTG" in the ODM usb property of arch/arm/mach-tegra/odm_kit/query/whistler/nvodm_query.c Tested OTG-HOST by connecting USB keyboard and OTG-device by connecting adb. Change-Id: I860e1f4be5f7c96765c2ce1ca320fdf212164811
-rw-r--r--arch/arm/configs/tegra_whistler_android_defconfig4
-rw-r--r--arch/arm/mach-tegra/include/mach/platform.h1
-rwxr-xr-x[-rw-r--r--]arch/arm/mach-tegra/include/nvddk_usbphy.h6
-rwxr-xr-x[-rw-r--r--]arch/arm/mach-tegra/init_common.c71
-rwxr-xr-x[-rw-r--r--]arch/arm/mach-tegra/nvddk/nvddk_usbphy.c65
-rwxr-xr-x[-rw-r--r--]arch/arm/mach-tegra/nvddk/nvddk_usbphy_priv.h8
-rwxr-xr-x[-rw-r--r--]arch/arm/mach-tegra/tegra_sysmap.c14
-rwxr-xr-x[-rw-r--r--]drivers/usb/gadget/fsl_udc_core.c146
-rwxr-xr-x[-rw-r--r--]drivers/usb/gadget/tegra_udc.c7
-rwxr-xr-x[-rw-r--r--]drivers/usb/host/ehci-tegra.c206
-rw-r--r--drivers/usb/host/ehci.h11
-rwxr-xr-x[-rw-r--r--]drivers/usb/otg/Kconfig9
-rwxr-xr-x[-rw-r--r--]drivers/usb/otg/Makefile1
-rwxr-xr-xdrivers/usb/otg/tegra-otg.c273
-rwxr-xr-x[-rw-r--r--]include/linux/tegra_devices.h8
15 files changed, 743 insertions, 87 deletions
diff --git a/arch/arm/configs/tegra_whistler_android_defconfig b/arch/arm/configs/tegra_whistler_android_defconfig
index 6894b5364305..fe2839172cd2 100644
--- a/arch/arm/configs/tegra_whistler_android_defconfig
+++ b/arch/arm/configs/tegra_whistler_android_defconfig
@@ -1,7 +1,7 @@
#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.29
-# Wed Jan 13 20:07:37 2010
+# Mon Jan 18 12:46:38 2010
#
CONFIG_ARM=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
@@ -1175,6 +1175,8 @@ CONFIG_UDC_FSL_NR_ENDPOINTS=32
#
# OTG and related infrastructure
#
+CONFIG_USB_OTG_UTILS=y
+CONFIG_USB_TEGRA_OTG=y
# CONFIG_USB_GPIO_VBUS is not set
CONFIG_MMC=y
CONFIG_EMBEDDED_MMC_START_OFFSET=y
diff --git a/arch/arm/mach-tegra/include/mach/platform.h b/arch/arm/mach-tegra/include/mach/platform.h
index f80632aff0a8..98064eab1bfa 100644
--- a/arch/arm/mach-tegra/include/mach/platform.h
+++ b/arch/arm/mach-tegra/include/mach/platform.h
@@ -21,6 +21,7 @@
#ifndef __MACH_TEGRA_PLATFORM_H
extern unsigned long tegra_get_module_inst_base(const char *, int);
+extern unsigned long tegra_get_module_inst_size(const char *, int);
extern unsigned int tegra_get_module_inst_irq(const char *, int, int);
#define TEGRA_PL310_BASE (tegra_get_module_inst_base("pl310", 0))
diff --git a/arch/arm/mach-tegra/include/nvddk_usbphy.h b/arch/arm/mach-tegra/include/nvddk_usbphy.h
index 283a5d9ac43c..ef1be673e8d4 100644..100755
--- a/arch/arm/mach-tegra/include/nvddk_usbphy.h
+++ b/arch/arm/mach-tegra/include/nvddk_usbphy.h
@@ -272,22 +272,24 @@ void NvDdkUsbPhyClose(NvDdkUsbPhyHandle hUsbPhy);
* reinitializing.
*
* @param hUsbPhy Handle acquired during the NvDdkUsbPhyOpen() call.
+ * @param IsHostMode indicates the host mode or not.
* @param IsDpd Deep sleep power up or not .
*
* @retval NvSuccess
* @retval NvError_Timeout If phy clock is not stable in expected time.
*/
-NvError NvDdkUsbPhyPowerUp(NvDdkUsbPhyHandle hUsbPhy, NvBool IsDpd);
+NvError NvDdkUsbPhyPowerUp(NvDdkUsbPhyHandle hUsbPhy, NvBool IsHostMode, NvBool IsDpd);
/**
* Powers down the PHY. It could be low power mode or shutdown.
*
* @param hUsbPhy Handle acquired during the NvDdkUsbPhyOpen() call.
+ * @param IsHostMode indicates the host mode or not.
* @param IsDpd Handle Deep sleep power down or not .
*
* @retval NvSuccess
*/
-NvError NvDdkUsbPhyPowerDown(NvDdkUsbPhyHandle hUsbPhy, NvBool IsDpd);
+NvError NvDdkUsbPhyPowerDown(NvDdkUsbPhyHandle hUsbPhy, NvBool IsHostMode, NvBool IsDpd);
/**
* Perform an I/O control operation on the device.
diff --git a/arch/arm/mach-tegra/init_common.c b/arch/arm/mach-tegra/init_common.c
index 32746e5e4d50..97809fc0d086 100644..100755
--- a/arch/arm/mach-tegra/init_common.c
+++ b/arch/arm/mach-tegra/init_common.c
@@ -30,6 +30,7 @@
#include "nvrm_init.h"
#include "nvrm_drf.h"
#include "mach/nvrm_linux.h"
+#include "mach/platform.h"
#include "nvos.h"
#include "nvutil.h"
#include "nvassert.h"
@@ -474,7 +475,8 @@ static void __init tegra_register_usb_gadget(void)
/* fixme: add ulpi here? */
p = NvOdmQueryGetUsbProperty(NvOdmIoModule_Usb, i);
- if (!p || !(p->UsbMode & NvOdmUsbModeType_Device))
+ if (!p || !((p->UsbMode & NvOdmUsbModeType_Device) ||
+ (p->UsbMode & NvOdmUsbModeType_OTG)))
continue;
irq = NvRmGetIrqForLogicalInterrupt(s_hRmGlobal, mod, 0);
@@ -555,7 +557,9 @@ static void __init tegra_register_usb_host(void)
/* fixme: add ulpi here? */
p = NvOdmQueryGetUsbProperty(NvOdmIoModule_Usb, i);
- if (!p || !(p->UsbMode & NvOdmUsbModeType_Host) || tegra_is_udc(mod))
+ if (!p || !((p->UsbMode & NvOdmUsbModeType_Host) ||
+ (p->UsbMode & NvOdmUsbModeType_OTG) ) ||
+ (tegra_is_udc(mod) && !(p->UsbMode & NvOdmUsbModeType_OTG)))
continue;
irq = NvRmGetIrqForLogicalInterrupt(s_hRmGlobal, mod, 0);
@@ -588,8 +592,71 @@ fail:
}
#endif
+#if !defined(CONFIG_USB_TEGRA_OTG)
+#define tegra_register_usb_otg() do {} while (0)
+#else
+
+static void __init tegra_register_usb_otg(void)
+{
+ NvU32 port_count, i;
+
+ port_count = NvRmModuleGetNumInstances(s_hRmGlobal, NvRmModuleID_Usb2Otg);
+
+ for (i=0; i<port_count; i++) {
+ const NvOdmUsbProperty *p;
+ struct platform_device *platdev = NULL;
+ struct resource *res;
+ struct tegra_otg_platform_data pdata;
+
+ p = NvOdmQueryGetUsbProperty(NvOdmIoModule_Usb, i);
+ if (!p || !(p->UsbMode & NvOdmUsbModeType_OTG))
+ continue;
+
+ platdev = platform_device_alloc("tegra-otg", i);
+ if (!platdev) {
+ pr_err("unable to allocate device for tegra-otg\n");
+ goto fail;
+ }
+
+ res = kzalloc(sizeof(struct resource)*2, GFP_KERNEL);
+ if (!res) {
+ pr_err("unable to allocate resource for tegra-otg\n");
+ goto fail;
+ }
+
+ res[0].flags = IORESOURCE_MEM;
+ res[0].start = tegra_get_module_inst_base("usbotg", i);
+ res[0].end = res[0].start + tegra_get_module_inst_size("usbotg", i) - 1;
+ res[1].flags = IORESOURCE_IRQ;
+ res[1].start = res[1].end = tegra_get_module_inst_irq("usbotg", i, 0);
+
+ if (platform_device_add_resources(platdev, res, 2)) {
+ pr_err("unable to add resources to tegra-otg device\n");
+ goto fail;
+ }
+
+ memset(&pdata, 0, sizeof(pdata));
+ pdata.instance = i;
+ pdata.usb_property = p;
+
+ if (platform_device_add_data(platdev, &pdata, sizeof(pdata))) {
+ pr_err("unable to add platform data to tegra-otg device\n");
+ goto fail;
+ }
+
+ if (platform_device_add(platdev)) {
+ pr_err("unable to add tegra-otg device\n");
+ goto fail;
+ }
+ }
+fail:
+ ;
+}
+#endif
+
void __init tegra_register_usb(void)
{
+ tegra_register_usb_otg();
tegra_register_usb_gadget();
tegra_register_usb_host();
}
diff --git a/arch/arm/mach-tegra/nvddk/nvddk_usbphy.c b/arch/arm/mach-tegra/nvddk/nvddk_usbphy.c
index 6b973c172746..fe3c3252d39f 100644..100755
--- a/arch/arm/mach-tegra/nvddk/nvddk_usbphy.c
+++ b/arch/arm/mach-tegra/nvddk/nvddk_usbphy.c
@@ -134,7 +134,7 @@ UsbPhyPowerRailEnable(
if( !UsbPhyDiscover( pUsbPhy ) )
{
// Do nothing if no power rail info is discovered
- return;
+ return;
}
/* enable the power rail */
@@ -336,6 +336,39 @@ UsbPhyIoctlUsbBisuHintsOnOff(
return UsbPhyDfsBusyHint(pUsbPhy, pOnOff->OnOff, pOnOff->BoostDurationMs);
}
+/*
+ * NvDdkUsbPhyHelperThread() - Thread to control the Busy hints and Vbus.
+ *
+ * Busy hints and Vbus are controlled based on the USB cable connection status.
+ * USB cable status change(connect/disconnect) is identified in the ISR.
+ * Busy hints function cannot be called from ISR as NvRmPowerBusyHintMulti()
+ * uses vfree and vmalloc functions which are not supposed to call from the
+ * ISR context. This helper thread is signaled from the ISR by calling Phy
+ * suspend/resume APIs to control the busy hints and VBUS.
+ */
+static void
+NvDdkUsbPhyHelperThread(
+ void* pArgs)
+{
+ NvDdkUsbPhyHandle hUsbPhy = (NvDdkUsbPhyHandle)pArgs;
+
+ hUsbPhy->Stopped = NV_FALSE;
+
+ while (!hUsbPhy->Stopped)
+ {
+ /* wait for the signal to turn on/off the busy hints and vbus */
+ NvOsSemaphoreWaitTimeout(hUsbPhy->HelperThreadSema, NV_WAIT_INFINITE);
+
+ /* Turn on/off the USB busy hints */
+ UsbPhyDfsBusyHint(hUsbPhy, hUsbPhy->IsPhyPoweredUp, NV_WAIT_INFINITE);
+ /* Turn on/off the vbus for host mode */
+ if (hUsbPhy->IsHostMode)
+ {
+ UsbPrivEnableVbus(hUsbPhy, hUsbPhy->IsPhyPoweredUp);
+ }
+ }
+}
+
NvError
NvDdkUsbPhyOpen(
NvRmDeviceHandle hRm,
@@ -459,7 +492,15 @@ NvDdkUsbPhyOpen(
NvRmPowerRegister(pUsbPhy->hRmDevice,
pUsbPhy->hPwrEventSem, &pUsbPhy->RmPowerClientId));
- if (pUsbPhy->pProperty->UsbMode == NvOdmUsbModeType_Host)
+ NV_CHECK_ERROR_CLEANUP(
+ NvOsSemaphoreCreate(&pUsbPhy->HelperThreadSema, 0));
+
+ NV_CHECK_ERROR_CLEANUP(
+ NvOsThreadCreate(&NvDdkUsbPhyHelperThread,
+ (void*)pUsbPhy, &pUsbPhy->hThreadId));
+
+ if ((pUsbPhy->pProperty->UsbMode == NvOdmUsbModeType_Host) ||
+ (pUsbPhy->pProperty->UsbMode == NvOdmUsbModeType_OTG))
{
UsbPrivEnableVbus(pUsbPhy, NV_TRUE);
}
@@ -471,6 +512,9 @@ NvDdkUsbPhyOpen(
// Open the H/W interface
UsbPhyOpenHwInterface(pUsbPhy);
+ /* enable the busy hints for USB */
+ UsbPhyDfsBusyHint(pUsbPhy, NV_TRUE, NV_WAIT_INFINITE);
+
// Initialize the USB Phy
NV_CHECK_ERROR_CLEANUP(UsbPhyInitialize(pUsbPhy));
}
@@ -542,13 +586,18 @@ NvDdkUsbPhyClose(
hUsbPhy->CloseHwInterface(hUsbPhy);
}
- if (hUsbPhy->pProperty->UsbMode == NvOdmUsbModeType_Host)
+ if ((hUsbPhy->pProperty->UsbMode == NvOdmUsbModeType_Host) ||
+ (hUsbPhy->pProperty->UsbMode == NvOdmUsbModeType_OTG))
{
UsbPrivEnableVbus(hUsbPhy, NV_FALSE);
}
UsbPhyPowerRailEnable(hUsbPhy, NV_FALSE);
+ hUsbPhy->Stopped = NV_TRUE;
+ NvOsThreadJoin(hUsbPhy->hThreadId);
+ NvOsSemaphoreDestroy(hUsbPhy->HelperThreadSema);
+
NvRmPhysicalMemUnmap(
(void*)hUsbPhy->UsbVirAdr, hUsbPhy->UsbBankSize);
@@ -562,6 +611,7 @@ NvDdkUsbPhyClose(
NvError
NvDdkUsbPhyPowerUp(
NvDdkUsbPhyHandle hUsbPhy,
+ NvBool IsHostMode,
NvBool IsDpd)
{
NvError e = NvSuccess;
@@ -593,10 +643,12 @@ NvDdkUsbPhyPowerUp(
NVRM_MODULE_ID(NvRmModuleID_Usb2Otg, hUsbPhy->Instance),
hUsbPhy->RmPowerClientId, NV_TRUE));
}
-
+ // Power up the Phy
NV_CHECK_ERROR_CLEANUP(hUsbPhy->PowerUp(hUsbPhy));
-
hUsbPhy->IsPhyPoweredUp = NV_TRUE;
+ hUsbPhy->IsHostMode = IsHostMode;
+ // signal to set the busy hints and vbus
+ NvOsSemaphoreSignal(hUsbPhy->HelperThreadSema);
//NvOsDebugPrintf("NvDdkUsbPhyPowerUp::VOLTAGE ON\n");
@@ -609,6 +661,7 @@ fail:
NvError
NvDdkUsbPhyPowerDown(
NvDdkUsbPhyHandle hUsbPhy,
+ NvBool IsHostMode,
NvBool IsDpd)
{
NvError e = NvSuccess;
@@ -648,6 +701,8 @@ NvDdkUsbPhyPowerDown(
}
hUsbPhy->IsPhyPoweredUp = NV_FALSE;
+ hUsbPhy->IsHostMode = IsHostMode;
+ NvOsSemaphoreSignal(hUsbPhy->HelperThreadSema);
//NvOsDebugPrintf("NvDdkUsbPhyPowerDown::VOLTAGE OFF\n");
diff --git a/arch/arm/mach-tegra/nvddk/nvddk_usbphy_priv.h b/arch/arm/mach-tegra/nvddk/nvddk_usbphy_priv.h
index 9d7fb7c8e8e7..741ea1674399 100644..100755
--- a/arch/arm/mach-tegra/nvddk/nvddk_usbphy_priv.h
+++ b/arch/arm/mach-tegra/nvddk/nvddk_usbphy_priv.h
@@ -126,6 +126,14 @@ typedef struct NvDdkUsbPhyRec
NvBool IsPhyPoweredUp;
// Utmpi Pad Config control structure
NvDdkUsbPhyUtmiPadConfig *pUtmiPadConfig;
+ // Thread ID for the helper thread
+ NvOsThreadHandle hThreadId;
+ // semphore for signaling the thread
+ NvOsSemaphoreHandle HelperThreadSema;
+ // variable to control the thread loop
+ NvBool Stopped;
+ // Indicates phy powered up for the host mode
+ NvBool IsHostMode;
// Set of function pointers to access the usb phy hardware interface.
// Pointer to the h/w specific PowerUp function.
NvError (*PowerUp)(NvDdkUsbPhyHandle hUsbPhy);
diff --git a/arch/arm/mach-tegra/tegra_sysmap.c b/arch/arm/mach-tegra/tegra_sysmap.c
index cb5b88ced984..55154b546b12 100644..100755
--- a/arch/arm/mach-tegra/tegra_sysmap.c
+++ b/arch/arm/mach-tegra/tegra_sysmap.c
@@ -36,6 +36,8 @@ static NvRmModuleID tegra_map_name_to_mod(const char *name, int inst)
return NVRM_MODULE_ID(NvRmPrivModuleID_ArmPerif, inst);
else if (!strcmp(name, "pcie"))
return NVRM_MODULE_ID(NvRmPrivModuleID_Pcie, inst);
+ else if (!strcmp(name, "usbotg"))
+ return NVRM_MODULE_ID(NvRmModuleID_Usb2Otg, inst);
return (NvRmModuleID) 0;
}
@@ -55,6 +57,18 @@ unsigned long tegra_get_module_inst_base(const char *name, int inst)
return (unsigned long)phys;
}
+unsigned long tegra_get_module_inst_size(const char *name, int inst)
+{
+ NvRmModuleID mod_id = tegra_map_name_to_mod(name, inst);
+ NvRmPhysAddr phys = 0xffffffffUL;
+ NvU32 len = 0;
+
+ if (mod_id)
+ NvRmModuleGetBaseAddress(s_hRmGlobal, mod_id, &phys, &len);
+
+ return (unsigned long)len;
+}
+
unsigned int tegra_get_module_inst_irq(const char *name, int inst,
int mod_int_id)
{
diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c
index bbfedf5e9d20..a0753a9d9788 100644..100755
--- a/drivers/usb/gadget/fsl_udc_core.c
+++ b/drivers/usb/gadget/fsl_udc_core.c
@@ -291,14 +291,16 @@ static void dr_controller_run(struct fsl_udc *udc)
#if defined(CONFIG_ARCH_TEGRA)
unsigned long timeout;
#define FSL_UDC_RUN_TIMEOUT 1000
-
- /* Enable cable detection interrupt, without setting the
- * USB_SYS_VBUS_WAKEUP_INT bit. USB_SYS_VBUS_WAKEUP_INT is
- * clear on write */
- temp = fsl_readl(&usb_sys_regs->vbus_wakeup);
- temp |= (USB_SYS_VBUS_WAKEUP_INT_ENABLE | USB_SYS_VBUS_WAKEUP_ENABLE);
- temp &= ~USB_SYS_VBUS_WAKEUP_INT_STATUS;
- fsl_writel(temp, &usb_sys_regs->vbus_wakeup);
+ /* If OTG transceiver is available, then it handles the VBUS detection */
+ if ( !udc_controller->transceiver) {
+ /* Enable cable detection interrupt, without setting the
+ * USB_SYS_VBUS_WAKEUP_INT bit. USB_SYS_VBUS_WAKEUP_INT is
+ * clear on write */
+ temp = fsl_readl(&usb_sys_regs->vbus_wakeup);
+ temp |= (USB_SYS_VBUS_WAKEUP_INT_ENABLE | USB_SYS_VBUS_WAKEUP_ENABLE);
+ temp &= ~USB_SYS_VBUS_WAKEUP_INT_STATUS;
+ fsl_writel(temp, &usb_sys_regs->vbus_wakeup);
+ }
#endif
@@ -1756,6 +1758,27 @@ static void reset_irq(struct fsl_udc *udc)
#endif
}
+#if defined(CONFIG_ARCH_TEGRA)
+/*
+ * Restart device controller in the OTG mode on VBUS detection
+ */
+static void fsl_udc_restart(struct fsl_udc *udc)
+{
+ /* setup the controller in the device mode */
+ dr_controller_setup(udc);
+ /* Reset all internal used Queues */
+ reset_queues(udc);
+ /* setup EP0 for setup packet */
+ ep0_setup(udc);
+ /* start the controller */
+ dr_controller_run(udc);
+ /* initialize the USB and EP states */
+ udc->usb_state = USB_STATE_ATTACHED;
+ udc->ep0_state = WAIT_FOR_SETUP;
+ udc->ep0_dir = 0;
+}
+#endif
+
/*
* USB device controller interrupt handler
*/
@@ -1769,44 +1792,67 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc)
u32 temp;
#endif
- /* Disable ISR for OTG host mode */
- if (udc->stopped)
- return IRQ_NONE;
- spin_lock_irqsave(&udc->lock, flags);
- irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr);
- /* Clear notification bits */
- fsl_writel(irq_src, &dr_regs->usbsts);
-
- /* VDBG("irq_src [0x%8x]", irq_src); */
#if defined(CONFIG_ARCH_TEGRA)
- /* VBUS A session detection interrupts. When the interrupt is received,
- * the mark the vbus active shadow.
- */
- temp = fsl_readl(&usb_sys_regs->vbus_wakeup);
- if (temp & USB_SYS_VBUS_WAKEUP_INT_STATUS) {
- if (temp & USB_SYS_VBUS_STATUS) {
- udc->vbus_active = 1;
- //printk(KERN_INFO "USB cable connected\n");
+ spin_lock_irqsave(&udc->lock, flags);
+ /* check OTG tranceiver is available or not */
+ if (udc->transceiver) {
+ if (udc->transceiver->state == OTG_STATE_B_PERIPHERAL) {
+ if (!udc->vbus_active) {
+ /* set vbus active and enable the usb clocks */
+ udc->vbus_active = 1;
+ platform_udc_clk_resume();
+ fsl_udc_restart(udc);
+ }
+ } else if (udc->transceiver->state == OTG_STATE_A_SUSPEND) {
+ if (udc->vbus_active) {
+ /* stop the controller and turn off the clocks */
+ dr_controller_stop(udc);
+ platform_udc_clk_suspend();
+ udc->vbus_active = 0;
+ udc->usb_state = USB_STATE_DEFAULT;
+ udc->transceiver->state = OTG_STATE_UNDEFINED;
+ }
} else {
- reset_queues(udc);
- udc->vbus_active = 0;
- udc->usb_state = USB_STATE_DEFAULT;
- //printk("USB cable dis-connected\n");
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return IRQ_HANDLED;
}
+ }else {
+ /* VBUS A session detection interrupts. When the interrupt is received,
+ * mark the vbus active shadow.
+ */
+ temp = fsl_readl(&usb_sys_regs->vbus_wakeup);
/* write back the register to clear the interrupt */
fsl_writel(temp, &usb_sys_regs->vbus_wakeup);
-
- if (udc->vbus_active) {
- platform_udc_clk_resume();
- } else {
- platform_udc_clk_suspend();
+ if (temp & USB_SYS_VBUS_WAKEUP_INT_STATUS) {
+ if (temp & USB_SYS_VBUS_STATUS) {
+ udc->vbus_active = 1;
+ platform_udc_clk_resume();
+ //printk(KERN_INFO "USB cable connected\n");
+ } else {
+ reset_queues(udc);
+ udc->vbus_active = 0;
+ udc->usb_state = USB_STATE_DEFAULT;
+ platform_udc_clk_suspend();
+ //printk("USB cable dis-connected\n");
+ }
+ status = IRQ_HANDLED;
}
-
- status = IRQ_HANDLED;
}
+ spin_unlock_irqrestore(&udc->lock, flags);
#endif
+ /* Disable ISR for OTG host mode */
+ if (udc->stopped)
+ return IRQ_NONE;
+
+ spin_lock_irqsave(&udc->lock, flags);
+ irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr);
+ /* Clear notification bits */
+ fsl_writel(irq_src, &dr_regs->usbsts);
+
+ /* VDBG("irq_src [0x%8x]", irq_src); */
+
/* Need to resume? */
if (udc->usb_state == USB_STATE_SUSPENDED)
if ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SUSPEND) == 0)
@@ -1899,15 +1945,34 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver)
udc_controller->driver = NULL;
goto out;
}
+ printk(KERN_INFO "%s: bind to driver %s\n",
+ udc_controller->gadget.name, driver->driver.name);
+
+#if defined(CONFIG_ARCH_TEGRA)
+ if (udc_controller->transceiver) {
+ if (!(fsl_readl(&usb_sys_regs->vbus_wakeup) & USB_SYS_VBUS_STATUS)) {
+ /* If VBUS is not present then power down the clocks */
+ udc_controller->vbus_active = 0;
+ platform_udc_clk_suspend();
+ /* set the gadget driver and quit (don't run the controller) */
+ otg_set_peripheral(udc_controller->transceiver,
+ &udc_controller->gadget);
+ goto out;
+ } else {
+ /* VBUS detected set the gadget driver and run the controller */
+ otg_set_peripheral(udc_controller->transceiver,
+ &udc_controller->gadget);
+ udc_controller->transceiver->state = OTG_STATE_B_PERIPHERAL;
+ udc_controller->vbus_active = 1;
+ }
+ }
+#endif
/* Enable DR IRQ reg and Set usbcmd reg Run bit */
dr_controller_run(udc_controller);
udc_controller->usb_state = USB_STATE_ATTACHED;
udc_controller->ep0_state = WAIT_FOR_SETUP;
udc_controller->ep0_dir = 0;
- printk(KERN_INFO "%s: bind to driver %s\n",
- udc_controller->gadget.name, driver->driver.name);
-
out:
if (retval)
printk("gadget driver register failed %d\n", retval);
@@ -2504,6 +2569,9 @@ static int __init fsl_udc_probe(struct platform_device *pdev)
create_proc_file();
#if defined(CONFIG_ARCH_TEGRA)
+ /* Get the OTG transceiver. If OTG is enabled then transceiver will be set
+ * otherwise transceiver will be NULL */
+ udc_controller->transceiver = otg_get_transceiver();
/* Power down the phy if cable is not connected */
if (!(fsl_readl(&usb_sys_regs->vbus_wakeup) & USB_SYS_VBUS_STATUS))
platform_udc_clk_suspend();
diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c
index ec4e19746022..c55712c3f9fc 100644..100755
--- a/drivers/usb/gadget/tegra_udc.c
+++ b/drivers/usb/gadget/tegra_udc.c
@@ -38,6 +38,9 @@ int tegra_udc_clk_init(struct platform_device *pdev)
if (nverr != NvSuccess)
return -ENODEV;
+ /* Power up the USB phy */
+ NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerUp(s_hUsbPhy, NV_FALSE, 0));
+
return 0;
}
@@ -51,10 +54,10 @@ void tegra_udc_clk_release(void)
void tegra_udc_clk_suspend(void)
{
- NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerDown(s_hUsbPhy, 0));
+ NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerDown(s_hUsbPhy, NV_FALSE, 0));
}
void tegra_udc_clk_resume(void)
{
- NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerUp(s_hUsbPhy, 0));
+ NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerUp(s_hUsbPhy, NV_FALSE, 0));
}
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 698e5f7f365e..71e59c4ee723 100644..100755
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -37,11 +37,100 @@
#include "nvrm_hardware_access.h"
#include "nvddk_usbphy.h"
-#define TEGRA_USB_ID_INT_ENABLE (1 << 0)
-#define TEGRA_USB_ID_INT_STATUS (1 << 1)
-#define TEGRA_USB_ID_PIN_STATUS (1 << 2)
-#define TEGRA_USB_ID_PIN_WAKEUP_ENABLE (1 << 6)
-#define TEGRA_USB_PHY_WAKEUP_REG_OFFSET (0x408)
+#define TEGRA_USB_ID_INT_ENABLE (1 << 0)
+#define TEGRA_USB_ID_INT_STATUS (1 << 1)
+#define TEGRA_USB_ID_PIN_STATUS (1 << 2)
+#define TEGRA_USB_ID_PIN_WAKEUP_ENABLE (1 << 6)
+#define TEGRA_USB_PHY_WAKEUP_REG_OFFSET (0x408)
+#define TEGRA_USB_USBMODE_REG_OFFSET (0x1a8)
+#define TEGRA_USB_USBMODE_HOST (3)
+
+
+static int tegra_ehci_hub_control (
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength
+) {
+ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
+ u32 __iomem *status_reg = &ehci->regs->port_status[
+ (wIndex & 0xff) - 1];
+ u32 temp;
+ struct tegra_hcd_platform_data *pdata;
+ unsigned long flags;
+ int retval = 0;
+
+ /* initialize the platform data pointer */
+ pdata = hcd->self.controller->platform_data;
+
+ /* In ehci_hub_control() for USB_PORT_FEAT_ENABLE clears the other bits
+ * that are write on clear, by wrting back the register read value, so
+ * USB_PORT_FEAT_ENABLE is handled here by masking the set on clear bits */
+ if ((typeReq == ClearPortFeature) && (wValue == USB_PORT_FEAT_ENABLE)) {
+ spin_lock_irqsave (&ehci->lock, flags);
+ temp = ehci_readl(ehci, status_reg);
+ ehci_writel(ehci, (temp & ~PORT_RWC_BITS) & ~PORT_PE, status_reg);
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ return retval;
+ }
+
+ /* Handle the hub control events here */
+ retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+
+ /* Power down the USB phy when there is no port connection and all
+ * HUB events are cleared by checking the lower four bits
+ * (PORT_CONNECT | PORT_CSC | PORT_PE | PORT_PEC) */
+ if ((pdata->pUsbProperty->UsbMode == NvOdmUsbModeType_OTG)
+ && ehci->transceiver) {
+ if (ehci->transceiver->state == OTG_STATE_A_SUSPEND) {
+ temp = ehci_readl(ehci, status_reg);
+ if (!(temp & (PORT_CONNECT | PORT_CSC | PORT_PE | PORT_PEC))
+ && ehci->host_reinited) {
+ NvDdkUsbPhyPowerDown(pdata->hUsbPhy, NV_TRUE, 0);
+ ehci->transceiver->state = OTG_STATE_UNDEFINED;
+ ehci->host_reinited = 0;
+ }
+ }
+ }
+
+ return retval;
+}
+
+
+static void tegra_ehci_restart (struct usb_hcd *hcd)
+{
+ unsigned int temp;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ /* Set to Host mode by setting bit 0-1 of USB device mode register */
+ temp = readl(hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET);
+ writel((temp | TEGRA_USB_USBMODE_HOST),
+ (hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET));
+
+ /* reset the ehci controller */
+ ehci->controller_resets_phy = 0;
+ ehci_reset(ehci);
+ ehci->controller_resets_phy = 1;
+ /* setup the frame list and Async q heads */
+ ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list);
+ ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next);
+ /* setup the command register and set the controller in RUN mode */
+ ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET);
+ ehci->command |= CMD_RUN;
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+
+ down_write(&ehci_cf_port_reset_rwsem);
+ hcd->state = HC_STATE_RUNNING;
+ /* unblock posted writes */
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ ehci_readl(ehci, &ehci->regs->command);
+ up_write(&ehci_cf_port_reset_rwsem);
+
+ /* Turn On Interrupts */
+ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
+}
static void tegra_ehci_shutdown (struct usb_hcd *hcd)
{
@@ -51,13 +140,14 @@ static void tegra_ehci_shutdown (struct usb_hcd *hcd)
/* ehci_shutdown touches the USB controller registers, make sure
* controller has clocks to it, if controller is already in power up
* status, calling the NvDdkUsbPhyPowerUp will just return */
- NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerUp(pdata->hUsbPhy, 0));
+ NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerUp(pdata->hUsbPhy, NV_TRUE, 0));
/* call ehci shut down */
ehci_shutdown(hcd);
/* we are ready to shut down, powerdown the phy */
- NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerDown(pdata->hUsbPhy, 0));
+ NV_ASSERT_SUCCESS(NvDdkUsbPhyPowerDown(pdata->hUsbPhy, NV_TRUE, 0));
}
+
static irqreturn_t tegra_ehci_irq (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
@@ -68,19 +158,38 @@ static irqreturn_t tegra_ehci_irq (struct usb_hcd *hcd)
spin_lock (&ehci->lock);
- if (pdata->pUsbProperty->IdPinDetectionType ==
- NvOdmUsbIdPinType_CableId) {
- /* read otgsc register for ID pin status change */
- status = readl(hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET);
- writel(status, (hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET));
-
- /* Check if there is any ID pin interrupt */
- if (status & TEGRA_USB_ID_INT_STATUS) {
- /* Check pin status and enable/disable the power */
- if (status & TEGRA_USB_ID_PIN_STATUS) {
- NvDdkUsbPhyPowerDown(pdata->hUsbPhy, 0);
- } else {
- NvDdkUsbPhyPowerUp(pdata->hUsbPhy, 0);
+ if ((pdata->pUsbProperty->UsbMode == NvOdmUsbModeType_OTG)
+ && ehci->transceiver) {
+ if (ehci->transceiver->state == OTG_STATE_A_HOST) {
+ if (!ehci->host_reinited) {
+ ehci->host_reinited = 1;
+ NvDdkUsbPhyPowerUp(pdata->hUsbPhy, NV_TRUE, 0);
+ tegra_ehci_restart(hcd);
+ }
+ } else if (ehci->transceiver->state == OTG_STATE_A_SUSPEND) {
+ if (!ehci->host_reinited) {
+ spin_unlock (&ehci->lock);
+ return IRQ_HANDLED;
+ }
+ } else {
+ spin_unlock (&ehci->lock);
+ return IRQ_HANDLED;
+ }
+ } else {
+ if (pdata->pUsbProperty->IdPinDetectionType ==
+ NvOdmUsbIdPinType_CableId) {
+ /* read otgsc register for ID pin status change */
+ status = readl(hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET);
+ writel(status, (hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET));
+
+ /* Check if there is any ID pin interrupt */
+ if (status & TEGRA_USB_ID_INT_STATUS) {
+ /* Check pin status and enable/disable the power */
+ if (status & TEGRA_USB_ID_PIN_STATUS) {
+ NvDdkUsbPhyPowerDown(pdata->hUsbPhy, NV_TRUE, 0);
+ } else {
+ NvDdkUsbPhyPowerUp(pdata->hUsbPhy, NV_TRUE, 0);
+ }
}
}
}
@@ -90,6 +199,7 @@ static irqreturn_t tegra_ehci_irq (struct usb_hcd *hcd)
return ehci_irq(hcd);
}
+
static int tegra_ehci_reinit(struct usb_hcd *hcd)
{
struct tegra_hcd_platform_data *pdata;
@@ -99,6 +209,7 @@ static int tegra_ehci_reinit(struct usb_hcd *hcd)
NV_CHECK_ERROR_CLEANUP(NvDdkUsbPhyOpen(s_hRmGlobal,
pdata->instance, &pdata->hUsbPhy));
+ NV_CHECK_ERROR_CLEANUP(NvDdkUsbPhyPowerUp(pdata->hUsbPhy, NV_TRUE, 0));
return 0;
@@ -178,7 +289,7 @@ static const struct hc_driver tegra_ehci_hc_driver = {
.endpoint_disable = ehci_endpoint_disable,
.get_frame_number = ehci_get_frame,
.hub_status_data = ehci_hub_status_data,
- .hub_control = ehci_hub_control,
+ .hub_control = tegra_ehci_hub_control,
.bus_suspend = tegra_ehci_bus_suspend,
.bus_resume = tegra_ehci_bus_resume,
.relinquish_port = ehci_relinquish_port,
@@ -266,8 +377,9 @@ static int tegra_ehci_probe(struct platform_device *pdev)
hcd->regs = vaddr;
/* Set to Host mode by setting bit 0-1 of USB device mode register */
- temp = readl(hcd->regs + 0x1a8);
- writel((temp | 0x3), (hcd->regs + 0x1a8));
+ temp = readl(hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET);
+ writel((temp | TEGRA_USB_USBMODE_HOST),
+ (hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET));
irq = NvRmGetIrqForLogicalInterrupt(s_hRmGlobal,
NVRM_MODULE_ID(NvRmModuleID_Usb2Otg, instance), 0);
@@ -280,19 +392,43 @@ static int tegra_ehci_probe(struct platform_device *pdev)
goto fail;
platform_set_drvdata(pdev, hcd);
- if (pdata->pUsbProperty->IdPinDetectionType ==
- NvOdmUsbIdPinType_CableId) {
- /* enable the cable ID interrupt */
- temp = readl(hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET);
- temp |= TEGRA_USB_ID_INT_ENABLE;
- temp |= TEGRA_USB_ID_PIN_WAKEUP_ENABLE;
- writel(temp, (hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET));
-
- /* Check if we detect any device connected */
- if (temp & TEGRA_USB_ID_PIN_STATUS) {
- NvDdkUsbPhyPowerDown(pdata->hUsbPhy, 0);
+ if (pdata->pUsbProperty->UsbMode == NvOdmUsbModeType_OTG) {
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ ehci->transceiver = otg_get_transceiver();
+ if (ehci->transceiver) {
+ otg_set_host(ehci->transceiver, (struct usb_bus *)hcd);
+ /* Stop the controller and power down the phy, OTG will start the
+ * host driver based on the ID pin detection */
+ ehci_halt(ehci);
+ /* reset the host and put the controller in idle mode */
+ temp = ehci_readl(ehci, &ehci->regs->command);
+ temp |= CMD_RESET;
+ ehci_writel(ehci, temp, &ehci->regs->command);
+ temp = readl(hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET);
+ writel((temp & ~TEGRA_USB_USBMODE_HOST),
+ (hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET));
+ NvDdkUsbPhyPowerDown(pdata->hUsbPhy, NV_TRUE, 0);
+ ehci->host_reinited = 0;
} else {
- NvDdkUsbPhyPowerUp(pdata->hUsbPhy, 0);
+ dev_err(&pdev->dev, "Cannot get OTG transceiver\n");
+ e = -ENODEV;
+ goto fail;
+ }
+ } else {
+ if (pdata->pUsbProperty->IdPinDetectionType ==
+ NvOdmUsbIdPinType_CableId) {
+ /* enable the cable ID interrupt */
+ temp = readl(hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET);
+ temp |= TEGRA_USB_ID_INT_ENABLE;
+ temp |= TEGRA_USB_ID_PIN_WAKEUP_ENABLE;
+ writel(temp, (hcd->regs + TEGRA_USB_PHY_WAKEUP_REG_OFFSET));
+
+ /* Check if we detect any device connected */
+ if (temp & TEGRA_USB_ID_PIN_STATUS) {
+ NvDdkUsbPhyPowerDown(pdata->hUsbPhy, NV_TRUE, 0);
+ } else {
+ NvDdkUsbPhyPowerUp(pdata->hUsbPhy, NV_TRUE, 0);
+ }
}
}
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 3126273c95ea..091e26e40850 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -106,6 +106,13 @@ struct ehci_hcd { /* one per controller */
unsigned long suspended_ports; /* which ports are
suspended */
+#ifdef CONFIG_USB_OTG_UTILS
+ /*
+ * Transceiver decleration for OTG support;
+ */
+ struct otg_transceiver *transceiver;
+#endif
+
/* per-HC memory pools (could be per-bus, but ...) */
struct dma_pool *qh_pool; /* qh per active urb */
struct dma_pool *qtd_pool; /* one or more per qh */
@@ -126,6 +133,7 @@ struct ehci_hcd { /* one per controller */
unsigned big_endian_desc:1;
unsigned has_amcc_usb23:1;
unsigned controller_resets_phy:1;
+ unsigned host_reinited:1;
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
@@ -230,6 +238,9 @@ static void free_cached_itd_list(struct ehci_hcd *ehci);
/*-------------------------------------------------------------------------*/
#include <linux/usb/ehci_def.h>
+#ifdef CONFIG_USB_OTG_UTILS
+#include <linux/usb/otg.h>
+#endif
/*-------------------------------------------------------------------------*/
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig
index ee55b449ffde..0aed0b7b7fa5 100644..100755
--- a/drivers/usb/otg/Kconfig
+++ b/drivers/usb/otg/Kconfig
@@ -17,6 +17,15 @@ if USB || USB_GADGET
#
# USB Transceiver Drivers
#
+config USB_TEGRA_OTG
+ boolean "NVIDIA Tegra OTG support"
+ depends on USB && ARCH_TEGRA && USB_EHCI_HCD && USB_GADGET_TEGRA
+ select USB_OTG_UTILS
+ help
+ This driver enables support for the OTG in NVIDIA Tegra SoCs by
+ providing simple transceiver interface for detecting the Host or
+ Device based on the USBID and VBUS sensors.
+
config USB_GPIO_VBUS
tristate "GPIO based peripheral-only VBUS sensing 'transceiver'"
depends on GENERIC_GPIO
diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile
index d73c7cf5e2f7..f1119a773efb 100644..100755
--- a/drivers/usb/otg/Makefile
+++ b/drivers/usb/otg/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_USB_OTG_UTILS) += otg.o
# transceiver drivers
+obj-$(CONFIG_USB_TEGRA_OTG) += tegra-otg.o
obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o
obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o
diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c
new file mode 100755
index 000000000000..832939e1875d
--- /dev/null
+++ b/drivers/usb/otg/tegra-otg.c
@@ -0,0 +1,273 @@
+/*
+ * tegra-otg.c
+ *
+ * OTG driver for detecting the USB ID and VBUS for NVIDIA Tegra SoCs
+ *
+ * Copyright (C) 2010 NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/usb.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/gadget.h>
+#include <linux/tegra_devices.h>
+#include <linux/platform_device.h>
+#include <asm/io.h>
+#include "../core/hcd.h"
+#include "mach/nvrm_linux.h"
+
+#define TEGRA_USB_ID_INT_ENABLE (1 << 0)
+#define TEGRA_USB_ID_INT_STATUS (1 << 1)
+#define TEGRA_USB_ID_STATUS (1 << 2)
+#define TEGRA_USB_ID_PIN_WAKEUP_ENABLE (1 << 6)
+#define TEGRA_USB_VBUS_WAKEUP_ENABLE (1 << 30)
+#define TEGRA_USB_VBUS_INT_ENABLE (1 << 8)
+#define TEGRA_USB_VBUS_INT_STATUS (1 << 9)
+#define TEGRA_USB_VBUS_STATUS (1 << 10)
+#define TEGRA_USB_WAKEUP_REG_OFFSET (0x408)
+
+static const char driver_name[] = "tegra-otg";
+
+/*
+ * Needs to be loaded before the UDC and Host driver that will use it.
+ */
+struct tegra_otg_data {
+ struct otg_transceiver otg;
+ struct device *dev;
+ spinlock_t lock;
+ int irq; /* irq allocated */
+ void __iomem *regs; /* device memory/io */
+ int instance; /* instance number of the controller */
+ NvDdkUsbPhyHandle usb_phy; /* handle to the USB phy */
+};
+
+static struct tegra_otg_data *sg_tegra_otg = NULL;
+
+
+/* VBUS change IRQ handler */
+static irqreturn_t tegra_otg_irq(int irq, void *data)
+{
+ struct platform_device *pdev = data;
+ struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = (struct usb_hcd *)tegra_otg->otg.host;
+ unsigned int status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tegra_otg->lock, flags);
+
+ status = readl(tegra_otg->regs + TEGRA_USB_WAKEUP_REG_OFFSET);
+ writel(status, tegra_otg->regs + TEGRA_USB_WAKEUP_REG_OFFSET);
+
+ if (tegra_otg->otg.host) {
+ /* Check if there is any ID pin interrupt */
+ if (status & TEGRA_USB_ID_INT_STATUS) {
+ if (status & TEGRA_USB_ID_STATUS) {
+ tegra_otg->otg.state = OTG_STATE_A_SUSPEND;
+ } else {
+ tegra_otg->otg.state = OTG_STATE_A_HOST;
+ hcd->state = HC_STATE_RUNNING;
+ }
+ }
+ }
+
+ if (tegra_otg->otg.gadget && (tegra_otg->otg.state != OTG_STATE_A_HOST)) {
+ if (status & TEGRA_USB_VBUS_INT_STATUS) {
+ if (status & TEGRA_USB_VBUS_STATUS) {
+ tegra_otg->otg.state = OTG_STATE_B_PERIPHERAL;
+ } else {
+ tegra_otg->otg.state = OTG_STATE_A_SUSPEND;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&tegra_otg->lock, flags);
+ return IRQ_HANDLED;
+}
+
+/* OTG transceiver interface */
+static int tegra_otg_set_peripheral(struct otg_transceiver *otg,
+ struct usb_gadget *gadget)
+{
+ unsigned int temp;
+ unsigned long flags;
+
+ otg->gadget = gadget;
+ temp = readl(sg_tegra_otg->regs + TEGRA_USB_WAKEUP_REG_OFFSET);
+ temp |= (TEGRA_USB_VBUS_INT_ENABLE | TEGRA_USB_VBUS_WAKEUP_ENABLE);
+ temp &= ~TEGRA_USB_VBUS_INT_STATUS;
+ writel(temp, (sg_tegra_otg->regs + TEGRA_USB_WAKEUP_REG_OFFSET));
+
+ spin_lock_irqsave(&sg_tegra_otg->lock, flags);
+ /* Check if we detect any device connected */
+ if (!(temp & TEGRA_USB_ID_STATUS)) {
+ otg->state = OTG_STATE_A_HOST;
+ NvDdkUsbPhyPowerUp(sg_tegra_otg->usb_phy, NV_TRUE, 0);
+ }
+ spin_unlock_irqrestore(&sg_tegra_otg->lock, flags);
+
+ return 0;
+}
+
+static int tegra_otg_set_host(struct otg_transceiver *otg,
+ struct usb_bus *host)
+{
+ unsigned int temp;
+ struct tegra_otg_platform_data *pdata;
+
+ pdata = sg_tegra_otg->dev->platform_data;
+ otg->host = host;
+
+ if (pdata->usb_property->IdPinDetectionType ==
+ NvOdmUsbIdPinType_CableId) {
+ /* enable the cable ID interrupt */
+ temp = readl(sg_tegra_otg->regs + TEGRA_USB_WAKEUP_REG_OFFSET);
+ temp |= (TEGRA_USB_ID_INT_ENABLE | TEGRA_USB_ID_PIN_WAKEUP_ENABLE);
+ writel(temp, (sg_tegra_otg->regs + TEGRA_USB_WAKEUP_REG_OFFSET));
+ }
+
+ return 0;
+}
+
+/* effective for B devices, ignored for A-peripheral */
+static int tegra_otg_set_power(struct otg_transceiver *otg, unsigned mA)
+{
+ /* Draw from the Host in device mode */
+
+ return 0;
+}
+
+/* for non-OTG B devices: set/clear transceiver suspend mode */
+static int tegra_otg_set_suspend(struct otg_transceiver *otg, int suspend)
+{
+ /* Draw 0mA in the suspend mode */
+ return 0;
+}
+
+/* platform driver interface */
+static int __init tegra_otg_probe(struct platform_device *pdev)
+{
+ int err = 0;
+ struct resource *res;
+ int instance = pdev->id;
+ NvError e;
+
+ sg_tegra_otg = kzalloc(sizeof(struct tegra_otg_data), GFP_KERNEL);
+ if (!sg_tegra_otg)
+ return -ENOMEM;
+
+ spin_lock_init(&sg_tegra_otg->lock);
+ platform_set_drvdata(pdev, sg_tegra_otg);
+
+ NV_CHECK_ERROR_CLEANUP(
+ NvDdkUsbPhyOpen(s_hRmGlobal, instance, &sg_tegra_otg->usb_phy)
+ );
+ NV_CHECK_ERROR_CLEANUP(
+ NvDdkUsbPhyPowerUp(sg_tegra_otg->usb_phy, NV_TRUE, 0)
+ );
+ sg_tegra_otg->instance = pdev->id;
+ sg_tegra_otg->dev = &pdev->dev;
+ sg_tegra_otg->otg.label = driver_name;
+ sg_tegra_otg->otg.state = OTG_STATE_UNDEFINED;
+ sg_tegra_otg->otg.set_peripheral = tegra_otg_set_peripheral;
+ sg_tegra_otg->otg.set_host = tegra_otg_set_host;
+ sg_tegra_otg->otg.set_power = tegra_otg_set_power;
+ sg_tegra_otg->otg.set_suspend = tegra_otg_set_suspend;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ err = -ENXIO;
+ goto err_irq;
+ }
+
+ sg_tegra_otg->regs = ioremap(res->start, resource_size(res));
+ if (!sg_tegra_otg->regs) {
+ err = -ENOMEM;
+ goto err_irq;
+ }
+
+ sg_tegra_otg->irq = platform_get_irq(pdev, 0);
+ if (!sg_tegra_otg->irq) {
+ err = -ENODEV;
+ goto err_irq;
+ }
+
+ err = request_irq(sg_tegra_otg->irq, tegra_otg_irq, IRQF_SHARED,
+ driver_name, pdev);
+ if (err) {
+ printk("cannot request irq %d err %d\n",
+ sg_tegra_otg->irq, err);
+ goto err_mem_map;
+ }
+
+ /* only active when a gadget is registered */
+ err = otg_set_transceiver(&sg_tegra_otg->otg);
+ if (err) {
+ dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
+ err);
+ goto err_otg;
+ }
+
+ NvDdkUsbPhyPowerDown(sg_tegra_otg->usb_phy, NV_TRUE, 0);
+
+ return 0;
+
+err_otg:
+ free_irq(sg_tegra_otg->irq, &pdev->dev);
+err_mem_map:
+ iounmap(sg_tegra_otg->regs);
+err_irq:
+ NvDdkUsbPhyClose(sg_tegra_otg->usb_phy);
+fail:
+ platform_set_drvdata(pdev, NULL);
+ kfree(sg_tegra_otg);
+ return err;
+}
+
+static int __exit tegra_otg_remove(struct platform_device *pdev)
+{
+ struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev);
+
+ otg_set_transceiver(NULL);
+ free_irq(tegra_otg->irq, &pdev->dev);
+ iounmap(tegra_otg->regs);
+ NvDdkUsbPhyClose(tegra_otg->usb_phy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(tegra_otg);
+ sg_tegra_otg = NULL;
+
+ return 0;
+}
+
+static struct platform_driver tegra_otg_driver = {
+ .driver = {
+ .name = driver_name,
+ },
+ .remove = __exit_p(tegra_otg_remove),
+ .probe = tegra_otg_probe,
+};
+
+static int __init tegra_otg_init(void)
+{
+ return platform_driver_register(&tegra_otg_driver);
+}
+module_init(tegra_otg_init);
+
+static void __exit tegra_otg_exit(void)
+{
+ platform_driver_unregister(&tegra_otg_driver);
+}
+module_exit(tegra_otg_exit);
+
+MODULE_DESCRIPTION("Tegra OTG driver");
diff --git a/include/linux/tegra_devices.h b/include/linux/tegra_devices.h
index 93d7324d2e37..143037a4a64e 100644..100755
--- a/include/linux/tegra_devices.h
+++ b/include/linux/tegra_devices.h
@@ -48,10 +48,16 @@ struct tegra_hcd_platform_data {
* macrocell interface) PHY on USB controllers 0 and 2. These 2 PHYs
* have its own rails.
*/
- NvU32 phyPowerRail;
+ NvU32 phyPowerRail;
NvDdkUsbPhyHandle hUsbPhy;
};
+/* Platform data for USB OTG driver */
+struct tegra_otg_platform_data {
+ NvU32 instance;
+ const NvOdmUsbProperty *usb_property;
+};
+
/* Platfrom data for I2C bus driver */
struct tegra_i2c_platform_data {
NvU32 IoModuleID;