summaryrefslogtreecommitdiff
path: root/drivers/usb/otg/tegra-otg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/otg/tegra-otg.c')
-rw-r--r--drivers/usb/otg/tegra-otg.c274
1 files changed, 129 insertions, 145 deletions
diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c
index 68d402afb25e..a5e85bf90d14 100644
--- a/drivers/usb/otg/tegra-otg.c
+++ b/drivers/usb/otg/tegra-otg.c
@@ -3,7 +3,7 @@
*
* OTG transceiver driver for Tegra UTMI phy
*
- * Copyright (C) 2010 NVIDIA Corp.
+ * Copyright (C) 2010-2012 NVIDIA CORPORATION. All rights reserved.
* Copyright (C) 2010 Google, Inc.
*
* This program is free software; you can redistribute it and/or modify it
@@ -24,7 +24,6 @@
#include <linux/usb.h>
#include <linux/usb/otg.h>
#include <linux/usb/gadget.h>
-#include <linux/usb/hcd.h>
#include <linux/platform_device.h>
#include <linux/platform_data/tegra_usb.h>
#include <linux/clk.h>
@@ -41,10 +40,8 @@
#define USB_VBUS_INT_EN (1 << 8)
#define USB_VBUS_INT_STATUS (1 << 9)
#define USB_VBUS_STATUS (1 << 10)
-#define USB_INTS (USB_VBUS_INT_STATUS | USB_ID_INT_STATUS)
-
-typedef void (*callback_t)(enum usb_otg_state to,
- enum usb_otg_state from, void *args);
+#define USB_INT_EN (USB_VBUS_INT_EN | USB_ID_INT_EN | \
+ USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN)
#ifdef DEBUG
#define DBG(stuff...) pr_info("tegra-otg: " stuff)
@@ -63,11 +60,11 @@ struct tegra_otg_data {
struct work_struct work;
unsigned int intr_reg_data;
bool clk_enabled;
- callback_t charger_cb;
- void *charger_cb_data;
-
bool interrupt_mode;
+ bool builtin_host;
+ bool suspended
};
+
static struct tegra_otg_data *tegra_clone;
static inline unsigned long otg_readl(struct tegra_otg_data *tegra,
@@ -82,20 +79,6 @@ static inline void otg_writel(struct tegra_otg_data *tegra, unsigned long val,
writel(val, tegra->regs + offset);
}
-static void tegra_otg_enable_clk(void)
-{
- if (!tegra_clone->clk_enabled)
- clk_enable(tegra_clone->clk);
- tegra_clone->clk_enabled = true;
-}
-
-static void tegra_otg_disable_clk(void)
-{
- if (tegra_clone->clk_enabled)
- clk_disable(tegra_clone->clk);
- tegra_clone->clk_enabled = false;
-}
-
static const char *tegra_state_name(enum usb_otg_state state)
{
switch (state) {
@@ -119,12 +102,13 @@ static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en)
clk_enable(tegra->clk);
val = otg_readl(tegra, USB_PHY_WAKEUP);
if (en) {
- val |= (USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN);
- val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN);
- } else {
- val &= ~(USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN);
- val &= ~(USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN);
+ if (tegra->builtin_host)
+ val |= USB_INT_EN;
+ else
+ val = USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN;
}
+ else
+ val &= ~USB_INT_EN;
otg_writel(tegra, val, USB_PHY_WAKEUP);
/* Add delay to make sure register is updated */
udelay(1);
@@ -133,81 +117,70 @@ static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en)
return val;
}
-static struct platform_device *
-tegra_usb_otg_host_register(struct platform_device *ehci_device,
- struct tegra_ehci_platform_data *pdata)
+static void tegra_start_host(struct tegra_otg_data *tegra)
{
- struct platform_device *pdev;
+ struct tegra_usb_otg_data *pdata = tegra->otg.dev->platform_data;
+ struct platform_device *pdev, *ehci_device = pdata->ehci_device;
void *platform_data;
int val;
+ DBG("%s(%d) Begin\n", __func__, __LINE__);
+
+ if (tegra->pdev)
+ return ;
+ /* prepare device structure for registering host*/
pdev = platform_device_alloc(ehci_device->name, ehci_device->id);
if (!pdev)
- return NULL;
+ return ;
val = platform_device_add_resources(pdev, ehci_device->resource,
ehci_device->num_resources);
if (val)
goto error;
- pdev->dev.dma_mask = ehci_device->dev.dma_mask;
+ pdev->dev.dma_mask = ehci_device->dev.dma_mask;
pdev->dev.coherent_dma_mask = ehci_device->dev.coherent_dma_mask;
- platform_data = kmalloc(sizeof(struct tegra_ehci_platform_data),
- GFP_KERNEL);
+ platform_data = kmalloc(sizeof(struct tegra_usb_platform_data), GFP_KERNEL);
if (!platform_data)
goto error;
- memcpy(platform_data, pdata, sizeof(struct tegra_ehci_platform_data));
+ memcpy(platform_data, pdata->ehci_pdata,
+ sizeof(struct tegra_usb_platform_data));
pdev->dev.platform_data = platform_data;
-
val = platform_device_add(pdev);
if (val)
goto error_add;
- return pdev;
+ tegra->pdev = pdev;
+ DBG("%s(%d) End\n", __func__, __LINE__);
+ return ;
error_add:
kfree(platform_data);
error:
pr_err("%s: failed to add the host controller device\n", __func__);
platform_device_put(pdev);
- return NULL;
+ tegra->pdev = NULL;
}
-static void tegra_usb_otg_host_unregister(struct platform_device *pdev)
+static void tegra_stop_host(struct tegra_otg_data *tegra)
{
- kfree(pdev->dev.platform_data);
- pdev->dev.platform_data = NULL;
- platform_device_unregister(pdev);
-}
+ struct platform_device *pdev = tegra->pdev;
-void tegra_start_host(struct tegra_otg_data *tegra)
-{
- struct tegra_otg_platform_data *pdata = tegra->otg.dev->platform_data;
- if (!tegra->pdev) {
- tegra->pdev = tegra_usb_otg_host_register(pdata->ehci_device,
- pdata->ehci_pdata);
- }
-}
+ DBG("%s(%d) Begin\n", __func__, __LINE__);
-void tegra_stop_host(struct tegra_otg_data *tegra)
-{
- if (tegra->pdev) {
- tegra_usb_otg_host_unregister(tegra->pdev);
+ if (pdev) {
+ /* unregister host from otg */
+ kfree(pdev->dev.platform_data);
+ pdev->dev.platform_data = NULL;
+ platform_device_unregister(pdev);
tegra->pdev = NULL;
}
-}
-int register_otg_callback(callback_t cb, void *args)
-{
- if (!tegra_clone)
- return -ENODEV;
- tegra_clone->charger_cb = cb;
- tegra_clone->charger_cb_data = args;
- return 0;
+ DBG("%s(%d) End\n", __func__, __LINE__);
}
-EXPORT_SYMBOL_GPL(register_otg_callback);
+
static void tegra_change_otg_state(struct tegra_otg_data *tegra,
enum usb_otg_state to)
@@ -228,9 +201,6 @@ static void tegra_change_otg_state(struct tegra_otg_data *tegra,
dev_info(tegra->otg.dev, "%s --> %s\n", tegra_state_name(from),
tegra_state_name(to));
- if (tegra->charger_cb)
- tegra->charger_cb(to, from, tegra->charger_cb_data);
-
if (from == OTG_STATE_A_SUSPEND) {
if (to == OTG_STATE_B_PERIPHERAL && otg->gadget)
usb_gadget_vbus_connect(otg->gadget);
@@ -256,10 +226,7 @@ static void irq_work(struct work_struct *work)
unsigned long flags;
unsigned long status;
- clk_enable(tegra->clk);
-
spin_lock_irqsave(&tegra->lock, flags);
-
status = tegra->int_status;
/* Debug prints */
@@ -274,7 +241,7 @@ static void irq_work(struct work_struct *work)
DBG("%s(%d) got vbus interrupt\n", __func__, __LINE__);
}
- if (!(status & USB_ID_STATUS))
+ if (!(status & USB_ID_STATUS) && (status & USB_ID_INT_EN))
to = OTG_STATE_A_HOST;
else if (status & USB_VBUS_STATUS && from != OTG_STATE_A_HOST)
to = OTG_STATE_B_PERIPHERAL;
@@ -283,8 +250,6 @@ static void irq_work(struct work_struct *work)
spin_unlock_irqrestore(&tegra->lock, flags);
tegra_change_otg_state(tegra, to);
- clk_disable(tegra->clk);
- tegra_otg_disable_clk();
}
static irqreturn_t tegra_otg_irq(int irq, void *data)
@@ -294,51 +259,52 @@ static irqreturn_t tegra_otg_irq(int irq, void *data)
unsigned long val;
spin_lock_irqsave(&tegra->lock, flags);
-
val = otg_readl(tegra, USB_PHY_WAKEUP);
+ DBG("%s(%d) interrupt val = 0x%x\n", __func__, __LINE__, val);
+
if (val & (USB_VBUS_INT_EN | USB_ID_INT_EN)) {
+ DBG("%s(%d) PHY_WAKEUP = 0x%x\n", __func__, __LINE__, val);
otg_writel(tegra, val, USB_PHY_WAKEUP);
if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) {
tegra->int_status = val;
schedule_work(&tegra->work);
}
}
-
spin_unlock_irqrestore(&tegra->lock, flags);
return IRQ_HANDLED;
}
-void tegra_otg_check_vbus_detection(void)
-{
- tegra_otg_enable_clk();
-}
-EXPORT_SYMBOL(tegra_otg_check_vbus_detection);
static int tegra_otg_set_peripheral(struct otg_transceiver *otg,
struct usb_gadget *gadget)
{
struct tegra_otg_data *tegra;
unsigned long val;
+ DBG("%s(%d) BEGIN\n", __func__, __LINE__);
tegra = container_of(otg, struct tegra_otg_data, otg);
otg->gadget = gadget;
val = enable_interrupt(tegra, true);
- if ((val & USB_ID_STATUS) && (val & USB_VBUS_STATUS)) {
+ if ((val & USB_ID_STATUS) && (val & USB_VBUS_STATUS))
val |= USB_VBUS_INT_STATUS;
- } else if (!(val & USB_ID_STATUS)) {
- val |= USB_ID_INT_STATUS;
- } else {
- val &= ~(USB_ID_INT_STATUS | USB_VBUS_INT_STATUS);
+ else if (!(val & USB_ID_STATUS)) {
+ if(!tegra->builtin_host)
+ val &= ~USB_ID_INT_STATUS;
+ else
+ val |= USB_ID_INT_STATUS;
}
+ else
+ val &= ~(USB_ID_INT_STATUS | USB_VBUS_INT_STATUS);
if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) {
tegra->int_status = val;
- schedule_work (&tegra->work);
+ schedule_work(&tegra->work);
}
+ DBG("%s(%d) END\n", __func__, __LINE__);
return 0;
}
@@ -347,6 +313,7 @@ static int tegra_otg_set_host(struct otg_transceiver *otg,
{
struct tegra_otg_data *tegra;
unsigned long val;
+ DBG("%s(%d) BEGIN\n", __func__, __LINE__);
tegra = container_of(otg, struct tegra_otg_data, otg);
otg->host = host;
@@ -354,11 +321,11 @@ static int tegra_otg_set_host(struct otg_transceiver *otg,
clk_enable(tegra->clk);
val = otg_readl(tegra, USB_PHY_WAKEUP);
val &= ~(USB_VBUS_INT_STATUS | USB_ID_INT_STATUS);
-
val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN);
otg_writel(tegra, val, USB_PHY_WAKEUP);
clk_disable(tegra->clk);
+ DBG("%s(%d) END\n", __func__, __LINE__);
return 0;
}
@@ -389,12 +356,9 @@ static ssize_t store_host_en(struct device *dev, struct device_attribute *attr,
struct platform_device *pdev = to_platform_device(dev);
struct tegra_otg_data *tegra = platform_get_drvdata(pdev);
unsigned long host;
- int err;
- err = kstrtoul(buf, 10, &host);
- if (err < 0) {
- return err;
- }
+ if (sscanf(buf, "%d", &host) != 1 || host < 0 || host > 1)
+ return -EINVAL;
if (host) {
enable_interrupt(tegra, false);
@@ -415,9 +379,8 @@ static DEVICE_ATTR(enable_host, 0644, show_host_en, store_host_en);
static int tegra_otg_probe(struct platform_device *pdev)
{
struct tegra_otg_data *tegra;
- struct tegra_otg_platform_data *otg_pdata;
- struct tegra_ehci_platform_data *ehci_pdata;
struct resource *res;
+ struct tegra_usb_otg_data *pdata = dev_get_platdata(&pdev->dev);
int err;
tegra = kzalloc(sizeof(struct tegra_otg_data), GFP_KERNEL);
@@ -425,8 +388,6 @@ static int tegra_otg_probe(struct platform_device *pdev)
return -ENOMEM;
tegra->otg.dev = &pdev->dev;
- otg_pdata = tegra->otg.dev->platform_data;
- ehci_pdata = otg_pdata->ehci_pdata;
tegra->otg.label = "tegra-otg";
tegra->otg.state = OTG_STATE_UNDEFINED;
tegra->otg.set_host = tegra_otg_set_host;
@@ -435,10 +396,14 @@ static int tegra_otg_probe(struct platform_device *pdev)
tegra->otg.set_power = tegra_otg_set_power;
spin_lock_init(&tegra->lock);
+ if (pdata) {
+ tegra->builtin_host = !pdata->ehci_pdata->builtin_host_disabled;
+ }
+
platform_set_drvdata(pdev, tegra);
tegra_clone = tegra;
- tegra->clk_enabled = false;
tegra->interrupt_mode = true;
+ tegra->suspended = false;
tegra->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(tegra->clk)) {
@@ -480,15 +445,23 @@ static int tegra_otg_probe(struct platform_device *pdev)
tegra->irq = res->start;
err = request_threaded_irq(tegra->irq, tegra_otg_irq,
NULL,
- IRQF_SHARED, "tegra-otg", tegra);
+ IRQF_SHARED | IRQF_TRIGGER_HIGH,
+ "tegra-otg", tegra);
if (err) {
dev_err(&pdev->dev, "Failed to register IRQ\n");
goto err_irq;
}
- INIT_WORK (&tegra->work, irq_work);
- if (!ehci_pdata->default_enable)
- clk_disable(tegra->clk);
+ err = enable_irq_wake(tegra->irq);
+ if (err < 0) {
+ dev_warn(&pdev->dev,
+ "Couldn't enable USB otg mode wakeup, irq=%d, error=%d\n",
+ tegra->irq, err);
+ err = 0;
+ }
+
+ INIT_WORK(&tegra->work, irq_work);
+
dev_info(&pdev->dev, "otg transceiver registered\n");
err = device_create_file(&pdev->dev, &dev_attr_enable_host);
@@ -497,6 +470,8 @@ static int tegra_otg_probe(struct platform_device *pdev)
goto err_irq;
}
+ clk_disable(tegra->clk);
+
return 0;
err_irq:
@@ -532,58 +507,67 @@ static int __exit tegra_otg_remove(struct platform_device *pdev)
static int tegra_otg_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
- struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev);
- struct otg_transceiver *otg = &tegra_otg->otg;
- enum usb_otg_state from = otg->state;
- unsigned int val;
-
- /* store the interupt enable for cable ID and VBUS */
- clk_enable(tegra_otg->clk);
- tegra_otg->intr_reg_data = readl(tegra_otg->regs + USB_PHY_WAKEUP);
- val = tegra_otg->intr_reg_data & ~(USB_ID_INT_EN | USB_VBUS_INT_EN);
- writel(val, (tegra_otg->regs + USB_PHY_WAKEUP));
- clk_disable(tegra_otg->clk);
-
- if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) {
- usb_gadget_vbus_disconnect(otg->gadget);
- otg->state = OTG_STATE_A_SUSPEND;
- }
- tegra_otg_disable_clk();
+ struct tegra_otg_data *tegra = platform_get_drvdata(pdev);
+ struct otg_transceiver *otg = &tegra->otg;
+ int val;
+ DBG("%s(%d) BEGIN state : %s\n", __func__, __LINE__,
+ tegra_state_name(otg->state));
+
+ clk_enable(tegra->clk);
+ val = otg_readl(tegra, USB_PHY_WAKEUP);
+ val &= ~(USB_ID_INT_EN | USB_VBUS_INT_EN);
+ otg_writel(tegra, val, USB_PHY_WAKEUP);
+ clk_disable(tegra->clk);
+
+ /* Suspend peripheral mode, host mode is taken care by host driver */
+ if (otg->state == OTG_STATE_B_PERIPHERAL)
+ tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND);
+
+ tegra->suspended = true;
+
+ DBG("%s(%d) END\n", __func__, __LINE__);
return 0;
}
static void tegra_otg_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
- struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev);
+ struct tegra_otg_data *tegra = platform_get_drvdata(pdev);
+ struct otg_transceiver *otg = &tegra->otg;
int val;
unsigned long flags;
+ DBG("%s(%d) BEGIN\n", __func__, __LINE__);
- tegra_otg_enable_clk();
-
- /* Following delay is intentional.
- * It is placed here after observing system hang.
- * Root cause is not confirmed.
- */
- msleep(1);
- /* restore the interupt enable for cable ID and VBUS */
- clk_enable(tegra_otg->clk);
- writel(tegra_otg->intr_reg_data, (tegra_otg->regs + USB_PHY_WAKEUP));
- val = readl(tegra_otg->regs + USB_PHY_WAKEUP);
- clk_disable(tegra_otg->clk);
-
- /* A device might be connected while CPU is in sleep mode. In this case no interrupt
- * will be triggered
- * force irq_work to recheck connected devices
- */
- if (!(val & USB_ID_STATUS)) {
- spin_lock_irqsave(&tegra_otg->lock, flags);
- tegra_otg->int_status = (val | USB_ID_INT_STATUS );
- schedule_work(&tegra_otg->work);
- spin_unlock_irqrestore(&tegra_otg->lock, flags);
- }
+ if (!tegra->suspended)
+ return;
+
+ /* Clear pending interrupts */
+ clk_enable(tegra->clk);
+ val = otg_readl(tegra, USB_PHY_WAKEUP);
+ otg_writel(tegra, val, USB_PHY_WAKEUP);
+ DBG("%s(%d) PHY WAKEUP register : 0x%x\n", __func__, __LINE__, val);
+ clk_disable(tegra->clk);
+
+ /* Handle if host cable is replaced with device during suspend state */
+ if (otg->state == OTG_STATE_A_HOST && (val & USB_ID_STATUS))
+ tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND);
+
+ /* Enable interrupt and call work to set to appropriate state */
+ spin_lock_irqsave(&tegra->lock, flags);
+ if (tegra->builtin_host)
+ tegra->int_status = val | USB_INT_EN;
+ else
+ tegra->int_status = val | USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN |
+ USB_ID_PIN_WAKEUP_EN;
+
+ spin_unlock_irqrestore(&tegra->lock, flags);
+ irq_work(&tegra->work);
+
+ enable_interrupt(tegra, true);
+
+ tegra->suspended = false;
- return;
+ DBG("%s(%d) END\n", __func__, __LINE__);
}
static const struct dev_pm_ops tegra_otg_pm_ops = {