summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorGerrit Code Review <gerrit2@git-master.nvidia.com>2010-01-20 01:50:51 +0200
committerGerrit Code Review <gerrit2@git-master.nvidia.com>2010-01-20 01:50:51 +0200
commit5469351e76022cf7464ef3bba8d59a7272d0991d (patch)
treed1e84211e10dd641f6a4573e74b212965b79dcff /drivers
parentb54b9845a8e24462b719a0dba29bbc3aa9f94d53 (diff)
parent98968646e2faada52420581bfcf5f80362f94f69 (diff)
Merge change I860e1f4b into android-tegra-2.6.29
* changes: TEGRA - Adding USB OTG feature
Diffstat (limited to 'drivers')
-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
7 files changed, 577 insertions, 76 deletions
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");