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.c269
1 files changed, 179 insertions, 90 deletions
diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c
index 4c04e6e183f1..d95d238fd9d6 100644
--- a/drivers/usb/otg/tegra-otg.c
+++ b/drivers/usb/otg/tegra-otg.c
@@ -42,10 +42,18 @@
#define USB_VBUS_INT_STATUS (1 << 9)
#define USB_VBUS_STATUS (1 << 10)
#define USB_INTS (USB_VBUS_INT_STATUS | USB_ID_INT_STATUS)
+#define USB_INT_EN (USB_VBUS_INT_EN | USB_ID_INT_EN | \
+ USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN)
typedef void (*callback_t)(enum usb_otg_state to,
enum usb_otg_state from, void *args);
+#ifdef DEBUG
+#define DBG(stuff...) pr_info("tegra-otg: " stuff)
+#else
+#define DBG(stuff...) do {} while (0)
+#endif
+
struct tegra_otg_data {
struct otg_transceiver otg;
unsigned long int_status;
@@ -56,11 +64,11 @@ struct tegra_otg_data {
struct platform_device *pdev;
struct work_struct work;
unsigned int intr_reg_data;
- bool detect_vbus;
bool clk_enabled;
callback_t charger_cb;
void *charger_cb_data;
+ bool interrupt_mode;
};
static struct tegra_otg_data *tegra_clone;
@@ -106,6 +114,24 @@ static const char *tegra_state_name(enum usb_otg_state state)
}
}
+static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en)
+{
+ unsigned long val;
+
+ clk_enable(tegra->clk);
+ val = otg_readl(tegra, USB_PHY_WAKEUP);
+ if (en)
+ val |= USB_INT_EN;
+ else
+ val &= ~USB_INT_EN;
+ otg_writel(tegra, val, USB_PHY_WAKEUP);
+ /* Add delay to make sure register is updated */
+ udelay(1);
+ clk_disable(tegra->clk);
+
+ return val;
+}
+
static struct platform_device *
tegra_usb_otg_host_register(struct platform_device *ehci_device,
struct tegra_ehci_platform_data *pdata)
@@ -157,19 +183,27 @@ static void tegra_usb_otg_host_unregister(struct platform_device *pdev)
void tegra_start_host(struct tegra_otg_data *tegra)
{
+ DBG("%s(%d) BEGIN\n", __func__, __LINE__);
+
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) END\n", __func__, __LINE__);
}
void tegra_stop_host(struct tegra_otg_data *tegra)
{
+ DBG("%s(%d) BEGIN\n", __func__, __LINE__);
+
if (tegra->pdev) {
tegra_usb_otg_host_unregister(tegra->pdev);
tegra->pdev = NULL;
}
+
+ DBG("%s(%d) END\n", __func__, __LINE__);
}
int register_otg_callback(callback_t cb, void *args)
@@ -182,72 +216,80 @@ int register_otg_callback(callback_t cb, void *args)
}
EXPORT_SYMBOL_GPL(register_otg_callback);
-static void irq_work(struct work_struct *work)
+static void tegra_change_otg_state(struct tegra_otg_data *tegra,
+ enum usb_otg_state to)
{
- struct tegra_otg_data *tegra =
- container_of(work, struct tegra_otg_data, work);
struct otg_transceiver *otg = &tegra->otg;
enum usb_otg_state from = otg->state;
- enum usb_otg_state to = OTG_STATE_UNDEFINED;
- unsigned long flags;
- unsigned long status;
- if (tegra->detect_vbus) {
- tegra->detect_vbus = false;
- tegra_otg_enable_clk();
+ if(!tegra->interrupt_mode){
+ DBG("OTG: Vbus detection is disabled");
return;
}
- clk_enable(tegra->clk);
-
- spin_lock_irqsave(&tegra->lock, flags);
-
- status = tegra->int_status;
-
- if (tegra->int_status & USB_ID_INT_STATUS) {
- if (status & USB_ID_STATUS) {
- if ((status & USB_VBUS_STATUS) && (from != OTG_STATE_A_HOST))
- to = OTG_STATE_B_PERIPHERAL;
- else
- to = OTG_STATE_A_SUSPEND;
- }
- else
- to = OTG_STATE_A_HOST;
- }
- if (from != OTG_STATE_A_HOST) {
- if (tegra->int_status & USB_VBUS_INT_STATUS) {
- if (status & USB_VBUS_STATUS)
- to = OTG_STATE_B_PERIPHERAL;
- else
- to = OTG_STATE_A_SUSPEND;
- }
- }
- spin_unlock_irqrestore(&tegra->lock, flags);
+ DBG("%s(%d) requested otg state %s-->%s\n", __func__,
+ __LINE__, tegra_state_name(from), tegra_state_name(to));
- if (to != OTG_STATE_UNDEFINED) {
+ if (to != OTG_STATE_UNDEFINED && from != to) {
otg->state = to;
-
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 (to == OTG_STATE_A_SUSPEND) {
- if (from == OTG_STATE_A_HOST)
+ if (from == OTG_STATE_A_SUSPEND) {
+ if (to == OTG_STATE_B_PERIPHERAL && otg->gadget)
+ usb_gadget_vbus_connect(otg->gadget);
+ else if (to == OTG_STATE_A_HOST)
+ tegra_start_host(tegra);
+ } else if (from == OTG_STATE_A_HOST) {
+ if (to == OTG_STATE_A_SUSPEND)
tegra_stop_host(tegra);
- else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget)
+ } else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) {
+ if (to == OTG_STATE_A_SUSPEND)
usb_gadget_vbus_disconnect(otg->gadget);
- } else if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) {
- if (from == OTG_STATE_A_SUSPEND)
- usb_gadget_vbus_connect(otg->gadget);
- } else if (to == OTG_STATE_A_HOST) {
- if (from == OTG_STATE_A_SUSPEND)
- tegra_start_host(tegra);
}
}
+}
+static void irq_work(struct work_struct *work)
+{
+ struct tegra_otg_data *tegra =
+ container_of(work, struct tegra_otg_data, work);
+ struct otg_transceiver *otg = &tegra->otg;
+ enum usb_otg_state from = otg->state;
+ enum usb_otg_state to = OTG_STATE_UNDEFINED;
+ unsigned long flags;
+ unsigned long status;
+
+ clk_enable(tegra->clk);
+
+ spin_lock_irqsave(&tegra->lock, flags);
+
+ status = tegra->int_status;
+ /* Debug prints */
+ DBG("%s(%d) status = 0x%x\n", __func__, __LINE__, status);
+ if ((status & USB_ID_INT_STATUS) &&
+ (status & USB_VBUS_INT_STATUS))
+ DBG("%s(%d) got vbus & id interrupt\n", __func__, __LINE__);
+ else {
+ if (status & USB_ID_INT_STATUS)
+ DBG("%s(%d) got id interrupt\n", __func__, __LINE__);
+ if (status & USB_VBUS_INT_STATUS)
+ DBG("%s(%d) got vbus interrupt\n", __func__, __LINE__);
+ }
+
+ if (!(status & USB_ID_STATUS))
+ to = OTG_STATE_A_HOST;
+ else if (status & USB_VBUS_STATUS && from != OTG_STATE_A_HOST)
+ to = OTG_STATE_B_PERIPHERAL;
+ else
+ to = OTG_STATE_A_SUSPEND;
+
+ spin_unlock_irqrestore(&tegra->lock, flags);
+ tegra_change_otg_state(tegra, to);
clk_disable(tegra->clk);
tegra_otg_disable_clk();
}
@@ -262,10 +304,10 @@ static irqreturn_t tegra_otg_irq(int irq, void *data)
val = otg_readl(tegra, USB_PHY_WAKEUP);
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;
- tegra->detect_vbus = false;
schedule_work(&tegra->work);
}
}
@@ -277,8 +319,7 @@ static irqreturn_t tegra_otg_irq(int irq, void *data)
void tegra_otg_check_vbus_detection(void)
{
- tegra_clone->detect_vbus = true;
- schedule_work(&tegra_clone->work);
+ tegra_otg_enable_clk();
}
EXPORT_SYMBOL(tegra_otg_check_vbus_detection);
@@ -287,33 +328,26 @@ static int tegra_otg_set_peripheral(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->gadget = gadget;
- clk_enable(tegra->clk);
- val = otg_readl(tegra, USB_PHY_WAKEUP);
- val |= (USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN);
- val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN);
- otg_writel(tegra, val, USB_PHY_WAKEUP);
- /* Add delay to make sure register is updated */
- udelay(1);
- clk_disable(tegra->clk);
+ 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)) {
+ else if (!(val & USB_ID_STATUS))
val |= USB_ID_INT_STATUS;
- } else {
+ 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;
- tegra->detect_vbus = false;
schedule_work (&tegra->work);
}
+ DBG("%s(%d) END\n", __func__, __LINE__);
return 0;
}
@@ -322,6 +356,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;
@@ -334,6 +369,7 @@ static int tegra_otg_set_host(struct otg_transceiver *otg,
otg_writel(tegra, val, USB_PHY_WAKEUP);
clk_disable(tegra->clk);
+ DBG("%s(%d) END\n", __func__, __LINE__);
return 0;
}
@@ -347,6 +383,46 @@ static int tegra_otg_set_suspend(struct otg_transceiver *otg, int suspend)
return 0;
}
+static ssize_t show_host_en(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct tegra_otg_data *tegra = platform_get_drvdata(pdev);
+
+ *buf = tegra->interrupt_mode ? '0': '1';
+ strcat(buf, "\n");
+ return strlen(buf);
+}
+
+static ssize_t store_host_en(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ 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 (host) {
+ enable_interrupt(tegra, false);
+ tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND);
+ tegra_change_otg_state(tegra, OTG_STATE_A_HOST);
+ tegra->interrupt_mode = false;
+ } else {
+ tegra->interrupt_mode = true;
+ tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND);
+ enable_interrupt(tegra, true);
+ }
+
+ return count;
+}
+
+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;
@@ -373,6 +449,7 @@ static int tegra_otg_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, tegra);
tegra_clone = tegra;
tegra->clk_enabled = false;
+ tegra->interrupt_mode = true;
tegra->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(tegra->clk)) {
@@ -424,6 +501,13 @@ static int tegra_otg_probe(struct platform_device *pdev)
if (!ehci_pdata->default_enable)
clk_disable(tegra->clk);
dev_info(&pdev->dev, "otg transceiver registered\n");
+
+ err = device_create_file(&pdev->dev, &dev_attr_enable_host);
+ if (err) {
+ dev_warn(&pdev->dev, "Can't register sysfs attribute\n");
+ goto err_irq;
+ }
+
return 0;
err_irq:
@@ -461,17 +545,21 @@ 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;
- /* store the interupt enable for cable ID and VBUS */
+ int val;
+ DBG("%s(%d) BEGIN state : %s\n", __func__, __LINE__,
+ tegra_state_name(otg->state));
+
clk_enable(tegra_otg->clk);
- tegra_otg->intr_reg_data = readl(tegra_otg->regs + USB_PHY_WAKEUP);
- writel(0, (tegra_otg->regs + USB_PHY_WAKEUP));
+ val = readl(tegra_otg->regs + USB_PHY_WAKEUP);
+ val &= ~USB_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;
- }
+ /* suspend peripheral mode, host mode is taken care by host driver */
+ if (otg->state == OTG_STATE_B_PERIPHERAL)
+ tegra_change_otg_state(tegra_otg, OTG_STATE_A_SUSPEND);
+
+ DBG("%s(%d) END\n", __func__, __LINE__);
tegra_otg_disable_clk();
return 0;
}
@@ -480,34 +568,35 @@ 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 otg_transceiver *otg = &tegra_otg->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 */
+ /* Clear pending interrupts */
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);
+ writel(val, tegra_otg->regs + USB_PHY_WAKEUP);
+ DBG("%s(%d) PHY WAKEUP register : 0x%x\n", __func__, __LINE__, val);
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);
- }
+ /* 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, OTG_STATE_A_SUSPEND);
+
+ /* Enable interrupt and call work to set to appropriate state */
+ spin_lock_irqsave(&tegra_otg->lock, flags);
+ tegra_otg->int_status = (val | USB_INT_EN);
+ spin_unlock_irqrestore(&tegra_otg->lock, flags);
+ irq_work(&tegra_otg->work);
- return;
+ clk_enable(tegra_otg->clk);
+ val = readl(tegra_otg->regs + USB_PHY_WAKEUP);
+ val |= USB_INT_EN;
+ writel(val, tegra_otg->regs + USB_PHY_WAKEUP);
+ clk_disable(tegra_otg->clk);
+ DBG("%s(%d) END\n", __func__, __LINE__);
}
static const struct dev_pm_ops tegra_otg_pm_ops = {